384 lines
14 KiB
JavaScript
384 lines
14 KiB
JavaScript
import {
|
|
bitsToBytes,
|
|
bitsToInt,
|
|
numberToBits,
|
|
numberToBytes,
|
|
numberToHex,
|
|
bytesToBits
|
|
} from "./converters.js";
|
|
import * as CRC from './CRC.js';
|
|
|
|
let SEGMENT_DURATION = 30;
|
|
let PACKET_SIZE_BITS = 8;
|
|
let DATA_SIZE_BIT_COUNT = 8;
|
|
let DATA_SIZE_CRC_BITS = 8;
|
|
let DATA_CRC_BITS = 8;
|
|
let BITS_PER_SAMPLE = 1;
|
|
let IS_ENCODED = false;
|
|
let PACKET_ENCODED_BLOCK_SIZE = 1;
|
|
let PACKET_DECODED_BLOCK_SIZE = 1;
|
|
let ENCODING;
|
|
let PACKET_CRC_BIT_COUNT = 0;
|
|
let PACKET_SEQUENCE_NUMBER_BIT_COUNT = 0;
|
|
|
|
export const changeConfiguration = (config) => {
|
|
const {
|
|
segmentDurationMilliseconds,
|
|
packetSizeBitCount,
|
|
dataSizeBitCount,
|
|
dataSizeCrcBitCount,
|
|
dataCrcBitCount,
|
|
bitsPerSegment,
|
|
packetEncoding,
|
|
packetEncodingBitCount,
|
|
packetDecodingBitCount,
|
|
packetSequenceNumberBitCount,
|
|
packetCrcBitCount
|
|
} = config;
|
|
SEGMENT_DURATION = segmentDurationMilliseconds;
|
|
PACKET_SIZE_BITS = packetSizeBitCount;
|
|
DATA_SIZE_BIT_COUNT = dataSizeBitCount;
|
|
DATA_SIZE_CRC_BITS = dataSizeCrcBitCount;
|
|
DATA_CRC_BITS = dataCrcBitCount;
|
|
BITS_PER_SAMPLE = bitsPerSegment;
|
|
IS_ENCODED = packetEncoding;
|
|
if(IS_ENCODED) {
|
|
PACKET_ENCODED_BLOCK_SIZE = packetEncodingBitCount;
|
|
PACKET_DECODED_BLOCK_SIZE = packetDecodingBitCount;
|
|
} else {
|
|
PACKET_ENCODED_BLOCK_SIZE = 1;
|
|
PACKET_DECODED_BLOCK_SIZE = 1;
|
|
}
|
|
PACKET_CRC_BIT_COUNT = packetCrcBitCount;
|
|
PACKET_SEQUENCE_NUMBER_BIT_COUNT = packetSequenceNumberBitCount;
|
|
}
|
|
export const setEncoding = (encoding) => {
|
|
ENCODING = encoding;
|
|
}
|
|
const encodePacket = (packetBits) => IS_ENCODED ? ENCODING.encode(packetBits) : packetBits;
|
|
const decodePacket = (packetBits) => IS_ENCODED ? ENCODING.decode(packetBits) : packetBits;
|
|
export const getSegmentDurationMilliseconds = () => SEGMENT_DURATION;
|
|
export const getPacketMaxByteCount = () => 2 ** PACKET_SIZE_BITS;
|
|
export const getPacketMaxBitCount = () => (2 ** PACKET_SIZE_BITS) * 8;
|
|
export const getDataMaxByteCount = () => 2 ** DATA_SIZE_BIT_COUNT;
|
|
export const getPacketEncodedBitCount = () => getPacketEncodingBlockCount() * PACKET_ENCODED_BLOCK_SIZE;
|
|
export const getPacketEncodingBlockCount = () =>
|
|
IS_ENCODED ? Math.floor(getPacketMaxBitCount() / PACKET_ENCODED_BLOCK_SIZE) : getPacketMaxBitCount();
|
|
|
|
export const getPacketDataByteCount = () => {
|
|
const availableBitCount = getPacketEncodedBitCount();
|
|
const blocks = Math.floor(availableBitCount / PACKET_ENCODED_BLOCK_SIZE);
|
|
let decodedBits = blocks * PACKET_DECODED_BLOCK_SIZE;
|
|
decodedBits -= getPacketHeaderBitCount()
|
|
// We only transfer full bytes within packets
|
|
return Math.floor(decodedBits / 8);
|
|
}
|
|
export const getPacketDataBitCount = () => getPacketDataByteCount() * 8;
|
|
export function fromByteCountGetPacketLastUnusedBitCount(byteCount) {
|
|
const bitCount = byteCount * 8;
|
|
const availableBits = getPacketMaxBitCount();
|
|
const dataBitsPerPacket = getPacketDataBitCount();
|
|
|
|
let bitsInLastPacket = bitCount % dataBitsPerPacket;
|
|
let usedBits = bitsInLastPacket + getPacketHeaderBitCount() ;
|
|
if(IS_ENCODED) {
|
|
const blocks = Math.ceil(usedBits / PACKET_DECODED_BLOCK_SIZE)
|
|
usedBits = blocks * PACKET_ENCODED_BLOCK_SIZE;
|
|
}
|
|
return availableBits - usedBits;
|
|
}
|
|
export function getPacketLastSegmentUnusedBitCount() {
|
|
return (BITS_PER_SAMPLE - (getPacketMaxBitCount() % BITS_PER_SAMPLE));
|
|
}
|
|
export const getBaud = () => {
|
|
return Math.floor(BITS_PER_SAMPLE / getSegmentDurationSeconds());
|
|
}
|
|
export const getEffectiveBaud = () => {
|
|
return Math.floor(getPacketDataBitCount() / getPacketDurationSeconds());
|
|
}
|
|
export const getEncodedPacketDataBitCount = () => {
|
|
return IS_ENCODED ? getPacketEncodedBitCount() : 0;
|
|
}
|
|
export const getPacketUsedBitCount = () =>
|
|
IS_ENCODED ? getPacketEncodedBitCount() : getPacketMaxBitCount();
|
|
export const getPacketUnusedBitCount = () => getPacketMaxBitCount() - getPacketUsedBitCount();
|
|
export const getMaxPackets = () =>
|
|
Math.ceil((getDataMaxByteCount() * 8) / getPacketUsedBitCount());
|
|
export const getMaxDurationMilliseconds = () => getMaxPackets() * getPacketDurationMilliseconds();
|
|
export const canSendPacket = () => {
|
|
let maxBits = getPacketMaxBitCount();
|
|
// Need to be able to send at least 1 data bit with each packet
|
|
if(maxBits - getPacketHeaderBitCount() < 1) return false;
|
|
// Make sure we have enough encoding blocks within a packet
|
|
return IS_ENCODED ? maxBits >= PACKET_ENCODED_BLOCK_SIZE : true;
|
|
}
|
|
export const getPacketizationHeaderBitCount = (padUnusedBits = true) => {
|
|
let count = DATA_SIZE_BIT_COUNT + DATA_SIZE_CRC_BITS + DATA_CRC_BITS;
|
|
if(padUnusedBits && count % 8 !== 0) {
|
|
count += 8 - (count % 8);
|
|
}
|
|
return count;
|
|
}
|
|
export const getPacketizationHeaderByteCount = () => getPacketizationHeaderBitCount() / 8;
|
|
|
|
export const getPacketizationHeaderUnusedBitCount = () => {
|
|
return getPacketizationHeaderBitCount(true) - getPacketizationHeaderBitCount(false);
|
|
}
|
|
export const getPacketizationBitCountFromBitCount = (bitCount) => bitCount + getPacketizationHeaderBitCount();
|
|
|
|
export const packetStats = byteCount => {
|
|
|
|
const byteCountWithHeaders = byteCount + getPacketizationHeaderByteCount();
|
|
const packetCount = Math.ceil(byteCountWithHeaders / getPacketDataByteCount());
|
|
const packetByteSize = (2 ** PACKET_SIZE_BITS);
|
|
const samplesPerPacket = Math.ceil((packetByteSize * 8) / BITS_PER_SAMPLE)
|
|
const packetDurationSeconds = (samplesPerPacket * SEGMENT_DURATION) / 1000;
|
|
const samplePeriodCount = packetCount * samplesPerPacket;
|
|
const transferBitCount = samplePeriodCount * BITS_PER_SAMPLE;
|
|
return ({
|
|
packetCount,
|
|
samplePeriodCount, // to packet utils, these are "blocks"
|
|
transferBitCount: transferBitCount,
|
|
transferByteCount: Math.ceil(transferBitCount / 8),
|
|
totalDurationSeconds: packetCount * packetDurationSeconds,
|
|
packetDurationSeconds,
|
|
});
|
|
};
|
|
const packetsNeededToTransferBytes = (byteCount) =>
|
|
canSendPacket() ? Math.ceil(byteCount / getPacketDataByteCount()) : 0;
|
|
export const getDataTransferDurationMilliseconds = (bitCount) =>
|
|
packetsNeededToTransferBytes(bitCount/8) * getPacketDurationMilliseconds();
|
|
export const getPacketDurationSeconds = () => getPacketDurationMilliseconds() / 1000;
|
|
export const getSegmentDurationSeconds = () => getSegmentDurationMilliseconds() / 1000;
|
|
export const getPacketSegmentCount = () => Math.ceil(getPacketMaxBitCount() / BITS_PER_SAMPLE);
|
|
export const getPacketDurationMilliseconds = () =>
|
|
getPacketSegmentCount() * getSegmentDurationMilliseconds();
|
|
export const getPacketIndex = (transferStartedMilliseconds, time) =>
|
|
Math.floor((time - transferStartedMilliseconds) / getPacketDurationMilliseconds());
|
|
|
|
export function getPacketSegmentIndex(transferStartedMilliseconds, time) {
|
|
return getTranserSegmentIndex(transferStartedMilliseconds, time) % getPacketSegmentCount();
|
|
}
|
|
export function getTranserSegmentIndex(transferStartedMilliseconds, time) {
|
|
const transferMs = time - transferStartedMilliseconds;
|
|
const segmentMs = getSegmentDurationMilliseconds();
|
|
return Math.floor(transferMs / segmentMs);
|
|
}
|
|
export function getPacketSegmentStartMilliseconds(transferStartedMilliseconds, packetIndex, segmentIndex) {
|
|
const packetStart = getPacketStartMilliseconds(transferStartedMilliseconds, packetIndex);
|
|
const segmentOffset = segmentIndex * getSegmentDurationMilliseconds();
|
|
return packetStart + segmentOffset;
|
|
}
|
|
export function getPacketStartMilliseconds(transferStartedMilliseconds, packetIndex) {
|
|
if(packetIndex < 0) return 0;
|
|
if(packetIndex === 0) return transferStartedMilliseconds;
|
|
return transferStartedMilliseconds + (packetIndex * getPacketDurationMilliseconds());
|
|
}
|
|
export function getPacketSegmentEndMilliseconds(transferStartedMilliseconds, packetIndex, segmentIndex) {
|
|
return getPacketSegmentStartMilliseconds(transferStartedMilliseconds, packetIndex, segmentIndex + 1) - 0.1;
|
|
}
|
|
export const getPacketHeaderUnusedBitCount = () => 8 - (getPacketHeaderBitCount(false) % 8);
|
|
export const getPacketHeaderBitCount = (padAsBytes = true) => {
|
|
const bitCount = PACKET_CRC_BIT_COUNT +
|
|
PACKET_SEQUENCE_NUMBER_BIT_COUNT +
|
|
PACKET_SIZE_BITS;
|
|
if(padAsBytes && bitCount % 8 !== 0) {
|
|
return bitCount + (8 - (bitCount % 8))
|
|
}
|
|
return bitCount;
|
|
}
|
|
export const pack = (bytes) => {
|
|
|
|
const getHeaderBytes = () => {
|
|
|
|
// packetization headers
|
|
// data length
|
|
let dataLengthBits = [];
|
|
let dataLengthCrcBits = [];
|
|
let dataSizeCrcNumber = 0;
|
|
if(DATA_SIZE_BIT_COUNT !== 0) {
|
|
dataLengthBits = numberToBits(bytes.length, DATA_SIZE_BIT_COUNT);
|
|
|
|
// crc on data length
|
|
if(DATA_SIZE_CRC_BITS !== 0) {
|
|
const dataLengthBytes = bitsToBytes(dataLengthBits);
|
|
dataSizeCrcNumber = CRC.check(dataLengthBytes, DATA_SIZE_CRC_BITS);
|
|
dataLengthCrcBits = numberToBits(dataSizeCrcNumber, DATA_SIZE_CRC_BITS);
|
|
}
|
|
}
|
|
|
|
// crc on data
|
|
let dataCrcBits = [];
|
|
let dataCrcNumber = 0;
|
|
if(DATA_CRC_BITS !== 0) {
|
|
dataCrcNumber = CRC.check(bytes, DATA_CRC_BITS);
|
|
dataCrcBits = numberToBits(dataCrcNumber, DATA_CRC_BITS);
|
|
}
|
|
const headers = [
|
|
...dataLengthBits,
|
|
...dataLengthCrcBits,
|
|
...dataCrcBits,
|
|
];
|
|
const unusedBitCount = getPacketizationHeaderUnusedBitCount();
|
|
headers.push(...new Array(unusedBitCount).fill(0));
|
|
|
|
if(headers.length !== getPacketizationHeaderBitCount()) {
|
|
throw new Error(`Malformed header. Expected ${getPacketizationHeaderBitCount()} bits. We have ${headers.length}`);
|
|
}
|
|
|
|
// prefix bits with headers
|
|
return bitsToBytes(headers);
|
|
}
|
|
|
|
const bits = bytesToBits([...getHeaderBytes(), ...bytes]);
|
|
|
|
return ({
|
|
|
|
getBits: (packetIndex) => {
|
|
// Returns a packet in the following order:
|
|
// - [CRC]
|
|
// - [Sequence Number]
|
|
// - Data Length
|
|
// - [Unused Bits (% 8 padding)]
|
|
// - Data
|
|
if(!canSendPacket()) return [];
|
|
|
|
// How many data bits will be in our packet?
|
|
let dataBitCount = getPacketDataBitCount();
|
|
|
|
// grab our data
|
|
const startIndex = packetIndex * dataBitCount;
|
|
const endIndex = startIndex + dataBitCount;
|
|
let packetBits = bits.slice(startIndex, endIndex);
|
|
if(packetBits.length === 0) {
|
|
throw new Error(`Attempted to send packet ${packetIndex}, but no data available.`)
|
|
}
|
|
if(packetBits.length % 8 !== 0) {
|
|
throw new Error('Attempted to create a packet with extra bits.');
|
|
}
|
|
|
|
// data byte count
|
|
let byteCount = Math.ceil(packetBits.length / 8);
|
|
const dataLengthBits = numberToBits(byteCount, PACKET_SIZE_BITS);
|
|
let sequenceNumberBits = [];
|
|
// sequence number
|
|
if(PACKET_SEQUENCE_NUMBER_BIT_COUNT !== 0) {
|
|
sequenceNumberBits = numberToBits(packetIndex, PACKET_SEQUENCE_NUMBER_BIT_COUNT);
|
|
}
|
|
|
|
// add our headers
|
|
const headerBits = [
|
|
...sequenceNumberBits,
|
|
...dataLengthBits,
|
|
...new Array(getPacketHeaderUnusedBitCount()).fill(0)
|
|
]
|
|
|
|
if(PACKET_CRC_BIT_COUNT !== 0) {
|
|
// convert to bytes
|
|
const crcCheckBits = [...headerBits, ...packetBits];
|
|
const bytes = bitsToBytes(crcCheckBits);
|
|
const crc = CRC.check(bytes, PACKET_CRC_BIT_COUNT);
|
|
const crcBits = numberToBits(crc, PACKET_CRC_BIT_COUNT);
|
|
|
|
// CRC must be first
|
|
headerBits.unshift(...crcBits);
|
|
}
|
|
packetBits.unshift(...headerBits);
|
|
|
|
// encode our packet
|
|
const encodedBits = encodePacket(packetBits);
|
|
if(encodedBits.length > 2 ** PACKET_SIZE_BITS * 8) {
|
|
throw new Error(`Attempted to create packet exceeding ${2 ** PACKET_SIZE_BITS * 8} bits with ${encodedBits.length} bits.`);
|
|
}
|
|
return encodedBits;
|
|
}
|
|
});
|
|
}
|
|
|
|
export const unpack = (bits) => ({
|
|
getPacketFromBits: (packetBits, packetIndex) => {
|
|
const unpacked = {
|
|
crc: CRC.INVALID,
|
|
actualCrc: CRC.INVALID,
|
|
packetIndex,
|
|
sequence: -1,
|
|
bytes: [],
|
|
size: -1
|
|
};
|
|
if(packetBits.length === 0) return unpacked;
|
|
|
|
// Remove the extra bits not used by the packet
|
|
packetBits = packetBits.slice(0, getPacketMaxBitCount());
|
|
|
|
// Remove extra bits not used by encoding
|
|
if(IS_ENCODED) {
|
|
packetBits.splice(getPacketEncodedBitCount());// before or after... i thik we need encoded. 144 is too small for 256 available
|
|
packetBits = decodePacket(packetBits);
|
|
}
|
|
|
|
const headerBitCount = getPacketHeaderBitCount();
|
|
|
|
// Get data size
|
|
const sizeBits = packetBits.slice(
|
|
PACKET_CRC_BIT_COUNT + PACKET_SEQUENCE_NUMBER_BIT_COUNT,
|
|
PACKET_CRC_BIT_COUNT + PACKET_SEQUENCE_NUMBER_BIT_COUNT + PACKET_SIZE_BITS
|
|
);
|
|
const dataSizeByteCount = bitsToInt(sizeBits, PACKET_SIZE_BITS);
|
|
unpacked.size = dataSizeByteCount;
|
|
|
|
// Get sequence number
|
|
if(PACKET_SEQUENCE_NUMBER_BIT_COUNT === 0) {
|
|
unpacked.sequence = packetIndex;
|
|
} else {
|
|
const sequenceBits = packetBits.slice(
|
|
PACKET_CRC_BIT_COUNT,
|
|
PACKET_CRC_BIT_COUNT + PACKET_SEQUENCE_NUMBER_BIT_COUNT
|
|
);
|
|
const sequence = bitsToInt(sequenceBits, PACKET_SEQUENCE_NUMBER_BIT_COUNT);
|
|
unpacked.sequence = sequence;
|
|
}
|
|
|
|
// Get CRC Header
|
|
if(PACKET_CRC_BIT_COUNT !== 0) {
|
|
const crcBits = packetBits.slice(0, PACKET_CRC_BIT_COUNT);
|
|
const expectedCrc = bitsToInt(crcBits, PACKET_CRC_BIT_COUNT);
|
|
unpacked.crc = expectedCrc;
|
|
|
|
// Run CRC on all bits except CRC header
|
|
const bitCountToCheck = (headerBitCount - PACKET_CRC_BIT_COUNT) + (dataSizeByteCount * 8);
|
|
const crcCopy = packetBits.slice(PACKET_CRC_BIT_COUNT, PACKET_CRC_BIT_COUNT + bitCountToCheck);
|
|
|
|
// check the crc is valid
|
|
const bytes = bitsToBytes(crcCopy);
|
|
unpacked.actualCrc = CRC.check(bytes, PACKET_CRC_BIT_COUNT);
|
|
}
|
|
|
|
// Get data bits
|
|
packetBits = packetBits.slice(headerBitCount, headerBitCount + unpacked.size * 8);
|
|
|
|
// convert to bytes
|
|
unpacked.bytes = bitsToBytes(packetBits);
|
|
|
|
return unpacked;
|
|
},
|
|
getPacket: (packetIndex) => {
|
|
const unpacked = {
|
|
crc: CRC.INVALID,
|
|
actualCrc: CRC.INVALID,
|
|
sequence: -1,
|
|
bytes: [],
|
|
size: -1
|
|
};
|
|
|
|
if(!canSendPacket()) return unpacked;
|
|
|
|
// Get bits associated with the packet
|
|
const packetBitCount = getPacketSegmentCount() + BITS_PER_SAMPLE;
|
|
const offset = packetIndex * packetBitCount;
|
|
const packetBits = bits.slice(offset, offset + packetBitCount);
|
|
|
|
if(packetBits.length === 0) return unpacked;
|
|
|
|
return this.getPacketFromBits(packetBits, packetIndex);
|
|
}
|
|
}); |