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