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