xref: /aosp_15_r20/external/pigweed/pw_hdlc/ts/protocol.ts (revision 61c4878ac05f98d0ceed94b57d316916de578985)
1// Copyright 2021 The Pigweed Authors
2//
3// Licensed under the Apache License, Version 2.0 (the "License"); you may not
4// use this file except in compliance with the License. You may obtain a copy of
5// the License at
6//
7//     https://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
11// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
12// License for the specific language governing permissions and limitations under
13// the License.
14
15/** Low-level HDLC protocol features. */
16import { crc32 } from './crc32';
17
18/** Special flag character for delimiting HDLC frames. */
19export const FLAG = 0x7e;
20
21/** Special character for escaping other special characters in a frame. */
22export const ESCAPE = 0x7d;
23
24/** Characters allowed after a 0x7d escape character. */
25export const VALID_ESCAPED_BYTES = [0x5d, 0x5e];
26
27/** Frame control for unnumbered information */
28export const UI_FRAME_CONTROL = frameControl(0x00);
29
30/** Maximum allowed HDLC address (uint64_t in C++). */
31const MAX_ADDRESS = 2 ** 64 - 1;
32
33/**
34 * Bitwise OR operation on numbers up to MAX_ADDRESS size.
35 * Native bitwise operators only support signed Int32.
36 */
37function bitwiseOr(x: number, y: number) {
38  const highMask = 0x80000000;
39  const lowMask = 0x7fffffff;
40  const highX = ~~(x / highMask);
41  const highY = ~~(y / highMask);
42  const lowX = x & lowMask;
43  const lowY = y & lowMask;
44  const highOr = highX | highY;
45  const lowOr = lowX | lowY;
46  return highOr * highMask + lowOr;
47}
48
49function getUint8ArraySubset(array: Uint8Array, start: number, end: number) {
50  const subset = new Uint8Array(array.buffer, start, end - start);
51  return subset;
52}
53
54/** Calculates the CRC32 of |data| */
55export function frameCheckSequence(data: Uint8Array): Uint8Array {
56  const crc = crc32(
57    getUint8ArraySubset(data, data.byteOffset, data.byteLength),
58  );
59  const arr = new ArrayBuffer(4);
60  const view = new DataView(arr);
61  view.setUint32(0, crc, true); // litteEndian = true
62  return new Uint8Array(arr);
63}
64
65/** Escapes or unescapes a byte, which should have been preceeded by 0x7d */
66export function escape(byte: number): number {
67  return byte ^ 0x20;
68}
69
70/** Encodes an HDLC address as a one-terminated LSB varint. */
71export function encodeAddress(address: number): Uint8Array {
72  const byteList = [];
73  // eslint-disable-next-line no-constant-condition
74  while (true) {
75    byteList.push((address & 0x7f) << 1);
76    address >>= 7;
77    if (address === 0) {
78      break;
79    }
80  }
81
82  const result = Uint8Array.from(byteList);
83  result[result.length - 1] |= 0x1;
84  return result;
85}
86
87/** Decodes an HDLC address from a frame, returning it and its size. */
88export function decodeAddress(frame: Uint8Array): [number, number] {
89  let result = 0;
90  let length = 0;
91
92  while (length < frame.length) {
93    const byte = frame[length];
94    const shift = (byte >> 1) * 2 ** (length * 7);
95    result = bitwiseOr(result, shift);
96    length += 1;
97
98    if (shift > MAX_ADDRESS || result > MAX_ADDRESS) {
99      return [-1, 0];
100    }
101    if ((byte & 0x1) === 0x1) {
102      break;
103    }
104  }
105  return [result, length];
106}
107
108function frameControl(frameType: number): Uint8Array {
109  return Uint8Array.from([0x03 | frameType]);
110}
111