201 lines
		
	
	
		
			5.6 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
			
		
		
	
	
			201 lines
		
	
	
		
			5.6 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
| // Copyright 2021 The Pigweed Authors
 | |
| //
 | |
| // Licensed under the Apache License, Version 2.0 (the "License"); you may not
 | |
| // use this file except in compliance with the License. You may obtain a copy of
 | |
| // the License at
 | |
| //
 | |
| //     https://www.apache.org/licenses/LICENSE-2.0
 | |
| //
 | |
| // Unless required by applicable law or agreed to in writing, software
 | |
| // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 | |
| // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 | |
| // License for the specific language governing permissions and limitations under
 | |
| // the License.
 | |
| 
 | |
| /** Decoder class for decoding bytes using HDLC protocol */
 | |
| 
 | |
| import * as protocol from './protocol';
 | |
| import * as util from './util';
 | |
| 
 | |
| const _MIN_FRAME_SIZE = 6; // 1 B address + 1 B control + 4 B CRC-32
 | |
| 
 | |
| /** Indicates if an error occurred */
 | |
| export enum FrameStatus {
 | |
|   OK = 'OK',
 | |
|   FCS_MISMATCH = 'frame check sequence failure',
 | |
|   FRAMING_ERROR = 'invalid flag or escape characters',
 | |
|   BAD_ADDRESS = 'address field too long',
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * A single HDLC frame
 | |
|  */
 | |
| export class Frame {
 | |
|   rawEncoded: Uint8Array;
 | |
|   rawDecoded: Uint8Array;
 | |
|   status: FrameStatus;
 | |
| 
 | |
|   address = -1;
 | |
|   control: Uint8Array = new Uint8Array();
 | |
|   data: Uint8Array = new Uint8Array();
 | |
| 
 | |
|   constructor(
 | |
|     rawEncoded: Uint8Array,
 | |
|     rawDecoded: Uint8Array,
 | |
|     status: FrameStatus = FrameStatus.OK
 | |
|   ) {
 | |
|     this.rawEncoded = rawEncoded;
 | |
|     this.rawDecoded = rawDecoded;
 | |
|     this.status = status;
 | |
| 
 | |
|     if (status === FrameStatus.OK) {
 | |
|       const [address, addressLength] = protocol.decodeAddress(rawDecoded);
 | |
|       if (addressLength === 0) {
 | |
|         this.status = FrameStatus.BAD_ADDRESS;
 | |
|         return;
 | |
|       }
 | |
|       this.address = address;
 | |
|       this.control = rawDecoded.slice(addressLength, addressLength + 1);
 | |
|       this.data = rawDecoded.slice(addressLength + 1, -4);
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   /**
 | |
|    * True if this represents a valid frame.
 | |
|    *
 | |
|    * If false, then parsing failed. The status is set to indicate what type of
 | |
|    * error occurred, and the data field contains all bytes parsed from the frame
 | |
|    * (including bytes parsed as address or control bytes).
 | |
|    */
 | |
|   ok(): boolean {
 | |
|     return this.status === FrameStatus.OK;
 | |
|   }
 | |
| }
 | |
| 
 | |
| enum DecoderState {
 | |
|   INTERFRAME,
 | |
|   FRAME,
 | |
|   FRAME_ESCAPE,
 | |
| }
 | |
| 
 | |
| /** Decodes one or more HDLC frames from a stream of data. */
 | |
| export class Decoder {
 | |
|   private decodedData = new Uint8Array();
 | |
|   private rawData = new Uint8Array();
 | |
|   private state = DecoderState.INTERFRAME;
 | |
| 
 | |
|   /**
 | |
|    *  Decodes and yields HDLC frames, including corrupt frames
 | |
|    *
 | |
|    *  The ok() method on Frame indicates whether it is valid or represents a
 | |
|    *  frame parsing error.
 | |
|    *
 | |
|    *  @yield Frames, which may be valid (frame.ok)) okr corrupt (!frame.ok())
 | |
|    */
 | |
|   *process(data: Uint8Array): IterableIterator<Frame> {
 | |
|     for (const byte of data) {
 | |
|       const frame = this.processByte(byte);
 | |
|       if (frame != null) {
 | |
|         yield frame;
 | |
|       }
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   /**
 | |
|    *  Decodes and yields valid HDLC frames, logging any errors.
 | |
|    *
 | |
|    *  @yield Valid HDLC frames
 | |
|    */
 | |
|   *processValidFrames(data: Uint8Array): IterableIterator<Frame> {
 | |
|     const frames = this.process(data);
 | |
|     for (const frame of frames) {
 | |
|       if (frame.ok()) {
 | |
|         yield frame;
 | |
|       } else {
 | |
|         console.warn(
 | |
|           'Failed to decode frame: %s; discarded %d bytes',
 | |
|           frame.status,
 | |
|           frame.rawEncoded.length
 | |
|         );
 | |
|         console.debug('Discarded data: %s', frame.rawEncoded);
 | |
|       }
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   private checkFrame(data: Uint8Array): FrameStatus {
 | |
|     if (data.length < _MIN_FRAME_SIZE) {
 | |
|       return FrameStatus.FRAMING_ERROR;
 | |
|     }
 | |
|     const frameCrc = new DataView(data.slice(-4).buffer).getInt8(0);
 | |
|     const crc = new DataView(
 | |
|       protocol.frameCheckSequence(data.slice(0, -4)).buffer
 | |
|     ).getInt8(0);
 | |
|     if (crc !== frameCrc) {
 | |
|       return FrameStatus.FCS_MISMATCH;
 | |
|     }
 | |
|     return FrameStatus.OK;
 | |
|   }
 | |
| 
 | |
|   private finishFrame(status: FrameStatus): Frame {
 | |
|     const frame = new Frame(
 | |
|       new Uint8Array(this.rawData),
 | |
|       new Uint8Array(this.decodedData),
 | |
|       status
 | |
|     );
 | |
|     this.rawData = new Uint8Array();
 | |
|     this.decodedData = new Uint8Array();
 | |
|     return frame;
 | |
|   }
 | |
| 
 | |
|   private processByte(byte: number): Frame | undefined {
 | |
|     let frame;
 | |
| 
 | |
|     // Record every byte except the flag character.
 | |
|     if (byte != protocol.FLAG) {
 | |
|       this.rawData = util.concatenate(this.rawData, Uint8Array.of(byte));
 | |
|     }
 | |
| 
 | |
|     switch (this.state) {
 | |
|       case DecoderState.INTERFRAME:
 | |
|         if (byte === protocol.FLAG) {
 | |
|           if (this.rawData.length > 0) {
 | |
|             frame = this.finishFrame(FrameStatus.FRAMING_ERROR);
 | |
|           }
 | |
|           this.state = DecoderState.FRAME;
 | |
|         }
 | |
|         break;
 | |
| 
 | |
|       case DecoderState.FRAME:
 | |
|         if (byte == protocol.FLAG) {
 | |
|           if (this.rawData.length > 0) {
 | |
|             frame = this.finishFrame(this.checkFrame(this.decodedData));
 | |
|           }
 | |
|         } else if (byte == protocol.ESCAPE) {
 | |
|           this.state = DecoderState.FRAME_ESCAPE;
 | |
|         } else {
 | |
|           this.decodedData = util.concatenate(
 | |
|             this.decodedData,
 | |
|             Uint8Array.of(byte)
 | |
|           );
 | |
|         }
 | |
|         break;
 | |
| 
 | |
|       case DecoderState.FRAME_ESCAPE:
 | |
|         if (byte === protocol.FLAG) {
 | |
|           frame = this.finishFrame(FrameStatus.FRAMING_ERROR);
 | |
|           this.state = DecoderState.FRAME;
 | |
|         } else if (protocol.VALID_ESCAPED_BYTES.includes(byte)) {
 | |
|           this.state = DecoderState.FRAME;
 | |
|           this.decodedData = util.concatenate(
 | |
|             this.decodedData,
 | |
|             Uint8Array.of(protocol.escape(byte))
 | |
|           );
 | |
|         } else {
 | |
|           this.state = DecoderState.INTERFRAME;
 | |
|         }
 | |
|         break;
 | |
|     }
 | |
|     return frame;
 | |
|   }
 | |
| }
 |