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;
|
|
}
|
|
}
|