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