From 5a65182f50e19c42da474d0b73aff762b5ee4a74 Mon Sep 17 00:00:00 2001 From: Lewis Moten Date: Tue, 14 May 2024 02:24:59 -0400 Subject: [PATCH] add pack/unpack functions --- PacketUtils.js | 230 ++++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 207 insertions(+), 23 deletions(-) diff --git a/PacketUtils.js b/PacketUtils.js index 5a7d310..8a48f98 100644 --- a/PacketUtils.js +++ b/PacketUtils.js @@ -1,3 +1,6 @@ +import { bitsToBytes, bitsToInt, numberToBits, numberToBytes, numberToHex } from "./converters"; +import * as CRC from './CRC'; + let SEGMENT_DURATION = 30; let PACKET_SIZE_BITS = 8; let DATA_SIZE_BITS = 8; @@ -8,6 +11,8 @@ let PACKET_ENCODING = false; let PACKET_ENCODING_SIZE = 7; let PACKET_DECODING_SIZE = 4; let ENCODING; +let PACKET_CRC_BIT_COUNT = 0; +let PACKET_SEQUENCE_NUMBER_BIT_COUNT = 0; export const changeConfiguration = (config) => { const { @@ -19,7 +24,9 @@ export const changeConfiguration = (config) => { bitsPerSegment, packetEncoding, packetEncodingBitCount, - packetDecodingBitCount + packetDecodingBitCount, + packetSequenceNumberBitCount, + packetCrcBitCount } = config; SEGMENT_DURATION = segmentDurationMilliseconds; PACKET_SIZE_BITS = packetSizeBitCount; @@ -30,11 +37,14 @@ export const changeConfiguration = (config) => { PACKET_ENCODING = packetEncoding; PACKET_ENCODING_SIZE = packetEncodingBitCount; PACKET_DECODING_SIZE = packetDecodingBitCount; + PACKET_CRC_BIT_COUNT = packetCrcBitCount; + PACKET_SEQUENCE_NUMBER_BIT_COUNT = packetSequenceNumberBitCount; } export const setEncoding = (encoding) => { ENCODING = encoding; } -const encodePacket = (bits) => ENCODING.encode(bits); +const encodePacket = (packetBits) => isPacketEncoded() ? ENCODING.encode(packetBits) : packetBits; +const decodePacket = (packetBits) => isPacketEncoded() ? ENCODING.decode(packetBits) : packetBits; export const getSegmentDurationMilliseconds = () => SEGMENT_DURATION; export const getPacketMaxByteCount = () => 2 ** PACKET_SIZE_BITS; export const getDataMaxByteCount = () => 2 ** DATA_SIZE_BITS; @@ -42,16 +52,23 @@ export const getBitsPerSegment = () => BITS_PER_SEGMENT; export const isPacketEncoded = () => PACKET_ENCODING; export const packetEncodingBlockSize = () => isPacketEncoded() ? PACKET_ENCODING_SIZE : 0; export const packetDecodingBlockSize = () => isPacketEncoded() ? PACKET_DECODING_SIZE : 0; -export const getPacketDataBitCount = () => { +export const getPacketEncodedBitCount = () => { if(isPacketEncoded()) return getPacketEncodingBlockCount() * PACKET_DECODING_SIZE; return getPacketMaxBitCount(); } +export const getPacketDataByteCount = () => { + const availableBitCount = getPacketEncodedBitCount() - getPacketHeaderBitCount(); + // We only transfer full bytes within packets + return Math.floor(availableBitCount / 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; + let usedBits = bitsInLastPacket + getPacketHeaderBitCount() ; if(isPacketEncoded()) { const blocks = Math.ceil(usedBits / packetDecodingBlockSize()) usedBits = blocks * packetEncodingBlockSize(); @@ -78,8 +95,10 @@ export const getMaxPackets = () => export const getMaxDurationMilliseconds = () => getMaxPackets() * getPacketDurationMilliseconds(); export const getPacketEncodingBitCount = () => getPacketEncodingBlockCount() * packetEncodingBlockSize(); export const canSendPacket = () => { - const maxBits = getPacketMaxBitCount(); - if(maxBits < 1) return false; + 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 isPacketEncoded() ? maxBits >= packetEncodingBlockSize() : true; } export const getPacketEncodingBlockCount = () => @@ -98,8 +117,18 @@ export const getDataTransferDurationMillisecondsFromByteCount = (byteCount) => getDataTransferDurationMilliseconds(getPacketizationBitCountFromByteCount(byteCount)); export const getDataTransferDurationSeconds = (bitCount) => getDataTransferDurationMilliseconds(bitCount) / 1000; +export const packetStats = byteCount => { + const bitCount = byteCount * 8; + const packetCount = getPacketCount(bitCount); + return ({ + packetCount, + sampleCount: packetCount * getPacketSegmentCount(), + durationMilliseconds: packetCount * getPacketDurationMilliseconds(), + totalBitCount: packetCount * getPacketMaxBitCount(), + }); +}; export const getPacketCount = (bitCount) => - canSendPacket() ? Math.ceil(bitCount / getPacketDataBitCount()) : 0; + canSendPacket() ? Math.ceil(bitCount / getPacketEncodedBitCount()) : 0; export const getDataTransferDurationMilliseconds = (bitCount) => getPacketCount(bitCount) * getPacketDurationMilliseconds(); export const getPacketDurationSeconds = () => getPacketDurationMilliseconds() / 1000; @@ -111,22 +140,6 @@ export const getPacketDurationMilliseconds = () => export const getPacketIndex = (transferStartedMilliseconds, time) => Math.floor((time - transferStartedMilliseconds) / getPacketDurationMilliseconds()); -export function getPacketUsedBits(bits, packetIndex) { - if(!canSendPacket()) return []; - - // How many data bits will be in our packet? - const dataBitCount = getPacketDataBitCount(); - - // grab our data - const startIndex = packetIndex * dataBitCount; - const endIndex = startIndex + dataBitCount; - let packetBits = bits.slice(startIndex, endIndex); - - return isPacketEncoded() ? encodePacket(packetBits) : packetBits; -} -export const getPacketBits = (bits, packetIndex) => - canSendPacket() ? getPacketUsedBits(bits, packetIndex) : []; - export function getPacketSegmentIndex(transferStartedMilliseconds, time) { return getTranserSegmentIndex(transferStartedMilliseconds, time) % getPacketSegmentCount(); } @@ -148,3 +161,174 @@ export function getPacketStartMilliseconds(transferStartedMilliseconds, packetIn 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 = (bits) => ({ + 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); + + // add our headers + const unusedBits = new Array(getPacketHeaderUnusedBitCount()).fill(0); + + // 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); + if(packetIndex < 3) + console.log('write sequence %s', packetIndex, packetIndex.toString(2)); + } + + const headerBits = [ + ...sequenceNumberBits, + ...dataLengthBits, + ...unusedBits + ] + + if(PACKET_CRC_BIT_COUNT !== 0) { + // convert to bytes + const bytes = bitsToBytes([...headerBits, ...packetBits]); + const crc = CRC.check(bytes, PACKET_CRC_BIT_COUNT); + const crcBits = numberToBits(crc, PACKET_CRC_BIT_COUNT); + if(packetIndex < 3) + console.log('write packet %s crc 0b%s', packetIndex, crc.toString(2)); + + // CRC must be first + headerBits.unshift(...crcBits); + } + packetBits.unshift(...headerBits); + + if(packetIndex < 3) { + console.log('WRITE packet %s bits', packetIndex, packetBits.slice(0, 20).join('')); + } + // encode our packet + const encodedBits = encodePacket(packetBits); + 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(isPacketEncoded()) { + packetBits.splice(0, getPacketEncodedBitCount()); + packetBits = decodePacket(packetBits); + } + if(packetIndex < 3) { + console.log('READ packet %s bits', packetIndex, packetBits.slice(0, 20).join('')); + } + const extraHeaderBitCount = getPacketHeaderUnusedBitCount(); + const headerBitCount = getPacketHeaderBitCount(); + + // Detect data size + const sizeBits = packetBits.slice(PACKET_CRC_BIT_COUNT + PACKET_SEQUENCE_NUMBER_BIT_COUNT, PACKET_SIZE_BITS); + const dataSizeByteCount = numberToBytes(sizeBits, PACKET_SIZE_BITS); + unpacked.size = dataSizeByteCount + + // Process CRC header FIRST (ensures all other headers are valid) + if(PACKET_CRC_BIT_COUNT !== 0) { + const crcBits = packetBits.slice(0, PACKET_CRC_BIT_COUNT); + const expectedCrc = bitsToInt(crcBits, PACKET_CRC_BIT_COUNT); + if(packetIndex < 3) { + console.log('read packet %s crc', packetIndex, numberToHex(PACKET_CRC_BIT_COUNT)(expectedCrc)) + } + unpacked.crc = expectedCrc; + + // Run CRC on all bits except CRC header + const crcLength = (headerBitCount - PACKET_CRC_BIT_COUNT) + (dataSizeByteCount * 8); + const crcCopy = packetBits.slice(PACKET_CRC_BIT_COUNT, crcLength); + + // check the crc is valid + const bytes = bitsToBytes(crcCopy); + unpacked.actualCrc = CRC.check(bytes, PACKET_CRC_BIT_COUNT); + + // remove CRC header + packetBits.splice(0, PACKET_CRC_BIT_COUNT); + } + + // Process sequence header + if(PACKET_SEQUENCE_NUMBER_BIT_COUNT === 0) { + unpacked.sequence = packetIndex; + } else { + const sequenceBits = packetBits.slice(0, PACKET_SEQUENCE_NUMBER_BIT_COUNT); + const sequence = bitsToInt(sequenceBits, PACKET_SEQUENCE_NUMBER_BIT_COUNT); + if(packetIndex < 3) + console.log('read packetIndex %s as sequence %s', packetIndex, sequence, sequenceBits.join('')) + unpacked.sequence = sequence; + // remove sequence number header + packetBits.splice(0, PACKET_SEQUENCE_NUMBER_BIT_COUNT); + } + + // Remove packet size header + packetBits.splice(0, PACKET_SIZE_BITS); + + // remove unused header bits + if(extraHeaderBitCount !== 0) packetBits.splice(0, extraHeaderBitCount); + + // Reduce remaining data to proper size + packetBits.splice(0, 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_SEGMENT; + const offset = packetIndex * packetBitCount; + const packetBits = bits.slice(offset, offset + packetBitCount); + + if(packetBits.length === 0) return unpacked; + + return this.getPacketFromBits(packetBits, packetIndex); + + } +}); \ No newline at end of file