xref: /aosp_15_r20/external/pigweed/pw_hdlc/ts/decoder.ts (revision 61c4878ac05f98d0ceed94b57d316916de578985)
1*61c4878aSAndroid Build Coastguard Worker// Copyright 2021 The Pigweed Authors
2*61c4878aSAndroid Build Coastguard Worker//
3*61c4878aSAndroid Build Coastguard Worker// Licensed under the Apache License, Version 2.0 (the "License"); you may not
4*61c4878aSAndroid Build Coastguard Worker// use this file except in compliance with the License. You may obtain a copy of
5*61c4878aSAndroid Build Coastguard Worker// the License at
6*61c4878aSAndroid Build Coastguard Worker//
7*61c4878aSAndroid Build Coastguard Worker//     https://www.apache.org/licenses/LICENSE-2.0
8*61c4878aSAndroid Build Coastguard Worker//
9*61c4878aSAndroid Build Coastguard Worker// Unless required by applicable law or agreed to in writing, software
10*61c4878aSAndroid Build Coastguard Worker// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
11*61c4878aSAndroid Build Coastguard Worker// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
12*61c4878aSAndroid Build Coastguard Worker// License for the specific language governing permissions and limitations under
13*61c4878aSAndroid Build Coastguard Worker// the License.
14*61c4878aSAndroid Build Coastguard Worker
15*61c4878aSAndroid Build Coastguard Worker/** Decoder class for decoding bytes using HDLC protocol */
16*61c4878aSAndroid Build Coastguard Worker
17*61c4878aSAndroid Build Coastguard Workerimport * as protocol from './protocol';
18*61c4878aSAndroid Build Coastguard Workerimport * as util from './util';
19*61c4878aSAndroid Build Coastguard Worker
20*61c4878aSAndroid Build Coastguard Workerconst _MIN_FRAME_SIZE = 6; // 1 B address + 1 B control + 4 B CRC-32
21*61c4878aSAndroid Build Coastguard Worker
22*61c4878aSAndroid Build Coastguard Worker/** Indicates if an error occurred */
23*61c4878aSAndroid Build Coastguard Workerexport enum FrameStatus {
24*61c4878aSAndroid Build Coastguard Worker  OK = 'OK',
25*61c4878aSAndroid Build Coastguard Worker  FCS_MISMATCH = 'frame check sequence failure',
26*61c4878aSAndroid Build Coastguard Worker  FRAMING_ERROR = 'invalid flag or escape characters',
27*61c4878aSAndroid Build Coastguard Worker  BAD_ADDRESS = 'address field too long',
28*61c4878aSAndroid Build Coastguard Worker}
29*61c4878aSAndroid Build Coastguard Worker
30*61c4878aSAndroid Build Coastguard Worker/**
31*61c4878aSAndroid Build Coastguard Worker * A single HDLC frame
32*61c4878aSAndroid Build Coastguard Worker */
33*61c4878aSAndroid Build Coastguard Workerexport class Frame {
34*61c4878aSAndroid Build Coastguard Worker  rawEncoded: Uint8Array;
35*61c4878aSAndroid Build Coastguard Worker  rawDecoded: Uint8Array;
36*61c4878aSAndroid Build Coastguard Worker  status: FrameStatus;
37*61c4878aSAndroid Build Coastguard Worker
38*61c4878aSAndroid Build Coastguard Worker  address = -1;
39*61c4878aSAndroid Build Coastguard Worker  control: Uint8Array = new Uint8Array(0);
40*61c4878aSAndroid Build Coastguard Worker  data: Uint8Array = new Uint8Array(0);
41*61c4878aSAndroid Build Coastguard Worker
42*61c4878aSAndroid Build Coastguard Worker  constructor(
43*61c4878aSAndroid Build Coastguard Worker    rawEncoded: Uint8Array,
44*61c4878aSAndroid Build Coastguard Worker    rawDecoded: Uint8Array,
45*61c4878aSAndroid Build Coastguard Worker    status: FrameStatus = FrameStatus.OK,
46*61c4878aSAndroid Build Coastguard Worker  ) {
47*61c4878aSAndroid Build Coastguard Worker    this.rawEncoded = rawEncoded;
48*61c4878aSAndroid Build Coastguard Worker    this.rawDecoded = rawDecoded;
49*61c4878aSAndroid Build Coastguard Worker    this.status = status;
50*61c4878aSAndroid Build Coastguard Worker
51*61c4878aSAndroid Build Coastguard Worker    if (status === FrameStatus.OK) {
52*61c4878aSAndroid Build Coastguard Worker      const [address, addressLength] = protocol.decodeAddress(rawDecoded);
53*61c4878aSAndroid Build Coastguard Worker      if (addressLength === 0) {
54*61c4878aSAndroid Build Coastguard Worker        this.status = FrameStatus.BAD_ADDRESS;
55*61c4878aSAndroid Build Coastguard Worker        return;
56*61c4878aSAndroid Build Coastguard Worker      }
57*61c4878aSAndroid Build Coastguard Worker      this.address = address;
58*61c4878aSAndroid Build Coastguard Worker      this.control = rawDecoded.slice(addressLength, addressLength + 1);
59*61c4878aSAndroid Build Coastguard Worker      this.data = rawDecoded.slice(addressLength + 1, -4);
60*61c4878aSAndroid Build Coastguard Worker    }
61*61c4878aSAndroid Build Coastguard Worker  }
62*61c4878aSAndroid Build Coastguard Worker
63*61c4878aSAndroid Build Coastguard Worker  /**
64*61c4878aSAndroid Build Coastguard Worker   * True if this represents a valid frame.
65*61c4878aSAndroid Build Coastguard Worker   *
66*61c4878aSAndroid Build Coastguard Worker   * If false, then parsing failed. The status is set to indicate what type of
67*61c4878aSAndroid Build Coastguard Worker   * error occurred, and the data field contains all bytes parsed from the frame
68*61c4878aSAndroid Build Coastguard Worker   * (including bytes parsed as address or control bytes).
69*61c4878aSAndroid Build Coastguard Worker   */
70*61c4878aSAndroid Build Coastguard Worker  ok(): boolean {
71*61c4878aSAndroid Build Coastguard Worker    return this.status === FrameStatus.OK;
72*61c4878aSAndroid Build Coastguard Worker  }
73*61c4878aSAndroid Build Coastguard Worker}
74*61c4878aSAndroid Build Coastguard Worker
75*61c4878aSAndroid Build Coastguard Workerenum DecoderState {
76*61c4878aSAndroid Build Coastguard Worker  INTERFRAME,
77*61c4878aSAndroid Build Coastguard Worker  FRAME,
78*61c4878aSAndroid Build Coastguard Worker  FRAME_ESCAPE,
79*61c4878aSAndroid Build Coastguard Worker}
80*61c4878aSAndroid Build Coastguard Worker
81*61c4878aSAndroid Build Coastguard Worker/** Decodes one or more HDLC frames from a stream of data. */
82*61c4878aSAndroid Build Coastguard Workerexport class Decoder {
83*61c4878aSAndroid Build Coastguard Worker  private decodedData = new Uint8Array(0);
84*61c4878aSAndroid Build Coastguard Worker  private rawData = new Uint8Array(0);
85*61c4878aSAndroid Build Coastguard Worker  private state = DecoderState.INTERFRAME;
86*61c4878aSAndroid Build Coastguard Worker
87*61c4878aSAndroid Build Coastguard Worker  /**
88*61c4878aSAndroid Build Coastguard Worker   *  Decodes and yields HDLC frames, including corrupt frames
89*61c4878aSAndroid Build Coastguard Worker   *
90*61c4878aSAndroid Build Coastguard Worker   *  The ok() method on Frame indicates whether it is valid or represents a
91*61c4878aSAndroid Build Coastguard Worker   *  frame parsing error.
92*61c4878aSAndroid Build Coastguard Worker   *
93*61c4878aSAndroid Build Coastguard Worker   *  @yield Frames, which may be valid (frame.ok)) okr corrupt (!frame.ok())
94*61c4878aSAndroid Build Coastguard Worker   */
95*61c4878aSAndroid Build Coastguard Worker  *process(data: Uint8Array): IterableIterator<Frame> {
96*61c4878aSAndroid Build Coastguard Worker    for (const byte of data) {
97*61c4878aSAndroid Build Coastguard Worker      const frame = this.processByte(byte);
98*61c4878aSAndroid Build Coastguard Worker      if (frame != null) {
99*61c4878aSAndroid Build Coastguard Worker        yield frame;
100*61c4878aSAndroid Build Coastguard Worker      }
101*61c4878aSAndroid Build Coastguard Worker    }
102*61c4878aSAndroid Build Coastguard Worker  }
103*61c4878aSAndroid Build Coastguard Worker
104*61c4878aSAndroid Build Coastguard Worker  /**
105*61c4878aSAndroid Build Coastguard Worker   *  Decodes and yields valid HDLC frames, logging any errors.
106*61c4878aSAndroid Build Coastguard Worker   *
107*61c4878aSAndroid Build Coastguard Worker   *  @yield Valid HDLC frames
108*61c4878aSAndroid Build Coastguard Worker   */
109*61c4878aSAndroid Build Coastguard Worker  *processValidFrames(data: Uint8Array): IterableIterator<Frame> {
110*61c4878aSAndroid Build Coastguard Worker    const frames = this.process(data);
111*61c4878aSAndroid Build Coastguard Worker    for (const frame of frames) {
112*61c4878aSAndroid Build Coastguard Worker      if (frame.ok()) {
113*61c4878aSAndroid Build Coastguard Worker        yield frame;
114*61c4878aSAndroid Build Coastguard Worker      } else {
115*61c4878aSAndroid Build Coastguard Worker        console.warn(
116*61c4878aSAndroid Build Coastguard Worker          'Failed to decode frame: %s; discarded %d bytes',
117*61c4878aSAndroid Build Coastguard Worker          frame.status,
118*61c4878aSAndroid Build Coastguard Worker          frame.rawEncoded.length,
119*61c4878aSAndroid Build Coastguard Worker        );
120*61c4878aSAndroid Build Coastguard Worker        console.debug('Discarded data: %s', frame.rawEncoded);
121*61c4878aSAndroid Build Coastguard Worker      }
122*61c4878aSAndroid Build Coastguard Worker    }
123*61c4878aSAndroid Build Coastguard Worker  }
124*61c4878aSAndroid Build Coastguard Worker
125*61c4878aSAndroid Build Coastguard Worker  private checkFrame(data: Uint8Array): FrameStatus {
126*61c4878aSAndroid Build Coastguard Worker    if (data.length < _MIN_FRAME_SIZE) {
127*61c4878aSAndroid Build Coastguard Worker      return FrameStatus.FRAMING_ERROR;
128*61c4878aSAndroid Build Coastguard Worker    }
129*61c4878aSAndroid Build Coastguard Worker    const frameCrc = new DataView(data.slice(-4).buffer).getInt8(0);
130*61c4878aSAndroid Build Coastguard Worker    const crc = new DataView(
131*61c4878aSAndroid Build Coastguard Worker      protocol.frameCheckSequence(data.slice(0, -4)).buffer,
132*61c4878aSAndroid Build Coastguard Worker    ).getInt8(0);
133*61c4878aSAndroid Build Coastguard Worker    if (crc !== frameCrc) {
134*61c4878aSAndroid Build Coastguard Worker      return FrameStatus.FCS_MISMATCH;
135*61c4878aSAndroid Build Coastguard Worker    }
136*61c4878aSAndroid Build Coastguard Worker    return FrameStatus.OK;
137*61c4878aSAndroid Build Coastguard Worker  }
138*61c4878aSAndroid Build Coastguard Worker
139*61c4878aSAndroid Build Coastguard Worker  private finishFrame(status: FrameStatus): Frame {
140*61c4878aSAndroid Build Coastguard Worker    const frame = new Frame(
141*61c4878aSAndroid Build Coastguard Worker      new Uint8Array(this.rawData),
142*61c4878aSAndroid Build Coastguard Worker      new Uint8Array(this.decodedData),
143*61c4878aSAndroid Build Coastguard Worker      status,
144*61c4878aSAndroid Build Coastguard Worker    );
145*61c4878aSAndroid Build Coastguard Worker    this.rawData = new Uint8Array(0);
146*61c4878aSAndroid Build Coastguard Worker    this.decodedData = new Uint8Array(0);
147*61c4878aSAndroid Build Coastguard Worker    return frame;
148*61c4878aSAndroid Build Coastguard Worker  }
149*61c4878aSAndroid Build Coastguard Worker
150*61c4878aSAndroid Build Coastguard Worker  private processByte(byte: number): Frame | undefined {
151*61c4878aSAndroid Build Coastguard Worker    let frame;
152*61c4878aSAndroid Build Coastguard Worker
153*61c4878aSAndroid Build Coastguard Worker    // Record every byte except the flag character.
154*61c4878aSAndroid Build Coastguard Worker    if (byte != protocol.FLAG) {
155*61c4878aSAndroid Build Coastguard Worker      this.rawData = util.concatenate(this.rawData, Uint8Array.of(byte));
156*61c4878aSAndroid Build Coastguard Worker    }
157*61c4878aSAndroid Build Coastguard Worker
158*61c4878aSAndroid Build Coastguard Worker    switch (this.state) {
159*61c4878aSAndroid Build Coastguard Worker      case DecoderState.INTERFRAME:
160*61c4878aSAndroid Build Coastguard Worker        if (byte === protocol.FLAG) {
161*61c4878aSAndroid Build Coastguard Worker          if (this.rawData.length > 0) {
162*61c4878aSAndroid Build Coastguard Worker            frame = this.finishFrame(FrameStatus.FRAMING_ERROR);
163*61c4878aSAndroid Build Coastguard Worker          }
164*61c4878aSAndroid Build Coastguard Worker          this.state = DecoderState.FRAME;
165*61c4878aSAndroid Build Coastguard Worker        }
166*61c4878aSAndroid Build Coastguard Worker        break;
167*61c4878aSAndroid Build Coastguard Worker
168*61c4878aSAndroid Build Coastguard Worker      case DecoderState.FRAME:
169*61c4878aSAndroid Build Coastguard Worker        if (byte == protocol.FLAG) {
170*61c4878aSAndroid Build Coastguard Worker          if (this.rawData.length > 0) {
171*61c4878aSAndroid Build Coastguard Worker            frame = this.finishFrame(this.checkFrame(this.decodedData));
172*61c4878aSAndroid Build Coastguard Worker          }
173*61c4878aSAndroid Build Coastguard Worker        } else if (byte == protocol.ESCAPE) {
174*61c4878aSAndroid Build Coastguard Worker          this.state = DecoderState.FRAME_ESCAPE;
175*61c4878aSAndroid Build Coastguard Worker        } else {
176*61c4878aSAndroid Build Coastguard Worker          this.decodedData = util.concatenate(
177*61c4878aSAndroid Build Coastguard Worker            this.decodedData,
178*61c4878aSAndroid Build Coastguard Worker            Uint8Array.of(byte),
179*61c4878aSAndroid Build Coastguard Worker          );
180*61c4878aSAndroid Build Coastguard Worker        }
181*61c4878aSAndroid Build Coastguard Worker        break;
182*61c4878aSAndroid Build Coastguard Worker
183*61c4878aSAndroid Build Coastguard Worker      case DecoderState.FRAME_ESCAPE:
184*61c4878aSAndroid Build Coastguard Worker        if (byte === protocol.FLAG) {
185*61c4878aSAndroid Build Coastguard Worker          frame = this.finishFrame(FrameStatus.FRAMING_ERROR);
186*61c4878aSAndroid Build Coastguard Worker          this.state = DecoderState.FRAME;
187*61c4878aSAndroid Build Coastguard Worker        } else if (protocol.VALID_ESCAPED_BYTES.includes(byte)) {
188*61c4878aSAndroid Build Coastguard Worker          this.state = DecoderState.FRAME;
189*61c4878aSAndroid Build Coastguard Worker          this.decodedData = util.concatenate(
190*61c4878aSAndroid Build Coastguard Worker            this.decodedData,
191*61c4878aSAndroid Build Coastguard Worker            Uint8Array.of(protocol.escape(byte)),
192*61c4878aSAndroid Build Coastguard Worker          );
193*61c4878aSAndroid Build Coastguard Worker        } else {
194*61c4878aSAndroid Build Coastguard Worker          this.state = DecoderState.INTERFRAME;
195*61c4878aSAndroid Build Coastguard Worker        }
196*61c4878aSAndroid Build Coastguard Worker        break;
197*61c4878aSAndroid Build Coastguard Worker    }
198*61c4878aSAndroid Build Coastguard Worker    return frame;
199*61c4878aSAndroid Build Coastguard Worker  }
200*61c4878aSAndroid Build Coastguard Worker}
201