From 5bc91576596f0ec5143c3c5796e788418c189fbe Mon Sep 17 00:00:00 2001 From: Lewis Moten Date: Sun, 12 May 2024 17:36:54 -0400 Subject: [PATCH] add data crc --- CRC.js | 3 + PacketUtils.js | 5 +- Panels/PacketizationPanel.js | 60 ++++++- StreamManager.js | 75 ++++++++- converters.js | 57 ++++++- index.html | 3 +- index.js | 292 ++++++++++++++++++----------------- 7 files changed, 342 insertions(+), 153 deletions(-) diff --git a/CRC.js b/CRC.js index 803c8b5..398dab0 100644 --- a/CRC.js +++ b/CRC.js @@ -1,3 +1,6 @@ +// CRC codes are not signed +export const INVALID = -1; + function calcCrc( bytes, size, diff --git a/PacketUtils.js b/PacketUtils.js index 026e998..5a7d310 100644 --- a/PacketUtils.js +++ b/PacketUtils.js @@ -2,6 +2,7 @@ let SEGMENT_DURATION = 30; let PACKET_SIZE_BITS = 8; let DATA_SIZE_BITS = 8; let DATA_SIZE_CRC_BITS = 8; +let DATA_CRC_BITS = 8; let BITS_PER_SEGMENT = 1; let PACKET_ENCODING = false; let PACKET_ENCODING_SIZE = 7; @@ -14,6 +15,7 @@ export const changeConfiguration = (config) => { packetSizeBitCount, dataSizeBitCount, dataSizeCrcBitCount, + dataCrcBitCount, bitsPerSegment, packetEncoding, packetEncodingBitCount, @@ -23,6 +25,7 @@ export const changeConfiguration = (config) => { PACKET_SIZE_BITS = packetSizeBitCount; DATA_SIZE_BITS = dataSizeBitCount; DATA_SIZE_CRC_BITS = dataSizeCrcBitCount; + DATA_CRC_BITS = dataCrcBitCount; BITS_PER_SEGMENT = bitsPerSegment; PACKET_ENCODING = packetEncoding; PACKET_ENCODING_SIZE = packetEncodingBitCount; @@ -81,7 +84,7 @@ export const canSendPacket = () => { } export const getPacketEncodingBlockCount = () => isPacketEncoded() ? Math.floor(getPacketMaxBitCount() / packetEncodingBlockSize()) : 0; -export const getPacketizationHeaderBitCount = () => DATA_SIZE_BITS + DATA_SIZE_CRC_BITS; +export const getPacketizationHeaderBitCount = () => DATA_SIZE_BITS + DATA_SIZE_CRC_BITS + DATA_CRC_BITS; export const getPacketizationBitCountFromBitCount = (bitCount) => bitCount + getPacketizationHeaderBitCount(); export const getPacketizationBitCountFromByteCount = (byteCount) => getPacketizationBitCountFromBitCount(byteCount * 8); diff --git a/Panels/PacketizationPanel.js b/Panels/PacketizationPanel.js index 55d5c30..209e161 100644 --- a/Panels/PacketizationPanel.js +++ b/Panels/PacketizationPanel.js @@ -5,25 +5,59 @@ class PacketizationPanel extends BasePanel { constructor() { super('Packetization'); - this.openField('Packet Size'); + this.openField('Max Data Size'); + this.addText('2^'); + this.addInputNumber('data-size-power', 16, {min: 0, max: 32, eventName: 'dataSizePowerChange', translation: 'power of 2'}); + this.addText(' '); + this.addDynamicText('data-size', 'n/a') + this.closeField(); + + this.openField('CRC on Size'); + this.addDropdown('data-size-crc', [ + {text: 'None', value: 0}, + {text: 'CRC-8', value: 8}, + {text: 'CRC-16', value: 16}, + {text: 'CRC-32', value: 32}, + ], 'dataSizeCrcChange'); + this.closeField(); + + this.openField('CRC on Data'); + this.addDropdown('data-crc', [ + {text: 'None', value: 0}, + {text: 'CRC-8', value: 8}, + {text: 'CRC-16', value: 16}, + {text: 'CRC-32', value: 32}, + ], 'dataCrcChange'); + this.closeField(); + + this.addSection('Packets'); + this.openField('Size'); this.addText('2^'); this.addInputNumber('size-power', 5, {min: 0, max: 16, eventName: 'sizePowerChange', translation: 'power of 2'}); this.addText(' '); - this.addDynamicText('size') + this.addDynamicText('size', 'n/a') this.closeField(); - this.addSection('Encoding'); + this.addCheckboxes('packet-encoding', [ + { text: 'Error Correction', id: 'error-correction', checked: true, eventName: 'errorCorrectionChange' }, + ]); + + + this.addSection('Sampling Period'); this.addCheckboxes('packet-encoding', [ { text: 'Interleaving', id: 'interleaving', checked: true, eventName: 'interleavingChange' }, - { text: 'Error Correction', id: 'error-correction', checked: true, eventName: 'errorCorrectionChange' }, ]); this.addEventListener('sizePowerChange', this.handleSizePowerChange); this.dispatcher.emit('sizePowerChange', {value: this.getSize()}); + + this.addEventListener('dataSizePowerChange', this.handleDataSizePowerChange); + this.dispatcher.emit('dataSizePowerChange', {value: this.getDataSizePower()}); + this.dispatcher.emit('dataSizeChange', {value: this.getDataSize()}); }; - getSizePower = () => parseInt(this.getValueById('size-power')); + getSizePower = () => this.getNumberById('size-power'); setSizePower = (value) => { this.setValueById('size-power', value); this.handleSizePowerChange({value}); @@ -33,6 +67,22 @@ class PacketizationPanel extends BasePanel { this.setValueById('size', byteSize(this.getSize())); } + getDataSizePower = () => this.getNumberById('data-size-power'); + setDataSizePower = (value) => { + this.setValueById('data-size-power', value); + this.handleDataSizePowerChange({value}); + } + getDataSize = () => 2 ** this.getDataSizePower(); + handleDataSizePowerChange = () => { + this.setValueById('data-size', byteSize(this.getDataSize())); + } + + getDataSizeCrc = () => this.getNumberById('data-size-crc'); + setDataSizeCrc = bitLength => this.setValueById('data-size-crc', bitLength) + + getDataCrc = () => this.getNumberById('data-crc'); + setDataCrc = bitLength => this.setValueById('data-crc', bitLength) + getInterleaving = () => this.getCheckedById('interleaving'); setInterleaving = (value) => this.setCheckedById('interleaving', value); diff --git a/StreamManager.js b/StreamManager.js index 5fb599e..e53000a 100644 --- a/StreamManager.js +++ b/StreamManager.js @@ -1,5 +1,10 @@ import Dispatcher from "./Dispatcher"; -import { bitsToInt } from "./converters"; +import * as CRC from './CRC'; +import { + bitsToBytes, + bitsToInt, + numberToBytes +} from "./converters"; const dispatcher = new Dispatcher('StreamManager', ['change']); @@ -102,6 +107,23 @@ export const getAllPacketBits = () => { } return bits; } +export const getAllPacketBitsDecoded = () => { + const packetCount = getPacketReceivedCount(); + const bits = []; + for(let packetIndex = 0; packetIndex < packetCount; packetIndex++) { + bits.push(...getPacketBitsDecoded(packetIndex)); + } + return bits; +} +export const getDataBytes = () => { + const bits = getAllPacketBitsDecoded(); + bits.splice(0, getStreamHeaderBitCount()); + const bytes = bitsToBytes(bits); + if(isTransferByteCountTrusted()) { + bytes.length = getTransferByteCount(); + } + return bytes; +} export const getPacketBits = (packetIndex, defaultBit = 0) => { const bits = []; const packet = BITS[packetIndex] ?? []; @@ -121,10 +143,19 @@ export const getPacketBitsDecoded = (packetIndex, defaultBit = 0) => { const bits = getPacketBits(packetIndex, defaultBit); return PACKET_ENCODING.decode(bits); } +const getStreamHeaderBitCount = () => { + return Object.keys(STREAM_HEADERS).reduce((lastBit, key) => { + const {index = 0, length = 0} = STREAM_HEADERS[key]; + if(length === 0) return lastBit; + if(lastBit < index + length) return index + length; + return lastBit; + }, 0); +} const getStreamHeaderBits = name => { const header = STREAM_HEADERS[name]; if(!header) return []; const { index, length } = header; + if(length === 0) return []; const packetCount = getPacketReceivedCount(); const bits = []; for(let packetIndex = 0; packetIndex < packetCount; packetIndex++) { @@ -136,6 +167,48 @@ const getStreamHeaderBits = name => { export const getTransferByteCount = () => { const name = 'transfer byte count'; const length = STREAM_HEADERS[name].length; + if(length === 0) return 1; const bits = getStreamHeaderBits(name); return bitsToInt(bits, length); } +export const getTransferByteCountCrc = () => { + const name = 'transfer byte count crc'; + const length = STREAM_HEADERS[name].length; + if(length === 0) return 0; + const bits = getStreamHeaderBits(name); + if(bits.length !== length) return CRC.INVALID; + return bitsToInt(bits, length); +} +export const getTransferByteCountActualCrc = () => { + const countBits = getStreamHeaderBits('transfer byte count').length; + if(countBits === 0) return 0; + + const crcBits = getStreamHeaderBits('transfer byte count crc').length; + if(crcBits === 0) return 0; + + const count = getTransferByteCount(); + const bytesOfCount = numberToBytes(count, countBits); + return CRC.check(bytesOfCount, crcBits) +} +export const isTransferByteCountTrusted = () => { + return getTransferByteCountCrc() === getTransferByteCountActualCrc(); +} +export function getTransferDataCrc() { + const name = 'transfer byte crc'; + const length = STREAM_HEADERS[name].length; + if(length === 0) return 0; + const bits = getStreamHeaderBits(name); + if(bits.length !== length) return CRC.INVALID; + return bitsToInt(bits, length); +} +export function getTransferActualDataCrc() { + const name = 'transfer byte crc'; + const length = STREAM_HEADERS[name].length; + if(length === 0) return 0; + const crcBits = getStreamHeaderBits(name).length; + const bytes = getDataBytes(); + return CRC.check(bytes, crcBits); +} +export const isTransferDataTrusted = () => { + return getTransferDataCrc() === getTransferActualDataCrc(); +} \ No newline at end of file diff --git a/converters.js b/converters.js index bcc510b..2e22d82 100644 --- a/converters.js +++ b/converters.js @@ -1,10 +1,63 @@ +export const numberToBytes = (number, bitLength) => { + const byteCount = Math.ceil(bitLength/8); + const bytes = []; + for(let i = 0; i < byteCount; i++) { + bytes.push((number >> (8 * (byteCount - 1 - i))) & 0xFF); + } + return bytes; +} +export const numberToHex = (bitLength) => { + const digits = Math.ceil(bitLength / 4); + return (number) => '0x' + number.toString(16).padStart(digits, '0').toUpperCase(); +} + +export function numberToBits(number, bitLength) { + const bits = []; + for(let i = bitLength - 1; i >= 0; i--) + bits.push((number >> i) & 1); + return bits; +} +export function bytesToText(bytes) { + if(!(bytes instanceof ArrayBuffer || ArrayBuffer.isView(bytes))) { + bytes = new Uint8Array(bytes).buffer; + } + return new TextDecoder().decode(bytes); +} +export function bytesToBits(bytes) { + return bytes.reduce((bits, byte) => [ + ...bits, + ...byte.toString(2).padStart(8, '0').split('').map(Number) + ], []); +} + +export function textToBytes(text) { + return new TextEncoder().encode(text); +} +export function textToBits(text) { + return bytesToBits(textToBytes(text)); +} + +export function bitsToText(bits) { + const bytes = new Uint8Array(bitsToBytes(bits)); + return bytesToText(bytes.buffer); +} +export function bitsToBytes(bits) { + const bytes = []; + for(let i = 0; i < bits.length; i+= 8) { + bytes.push(parseInt(bits.slice(i, i + 8).join(''), 2)); + } + return bytes; +} export const bitsToInt = (bits, bitLength) => { - parseInt(bits + if(bits.length === 0) return 0; + if(bitLength <= 0) return 0; + return parseInt(bits // only grab the bits we need .slice(0, bitLength) // combine into string .join('') // Assume missing bits were zeros - .padEnd(bitLength, '0') + .padEnd(bitLength, '0'), + 2 ); } diff --git a/index.html b/index.html index 1fa21de..f9504c3 100644 --- a/index.html +++ b/index.html @@ -79,7 +79,8 @@

Decoded

Bytes: N/A
- Length CRC-8: N/A
+ Length CRC: N/A
+ Data CRC: N/A

Errors

Encoded Segments: N/A%
Encoded Packets: N/A%
diff --git a/index.js b/index.js index 25dac9d..7867161 100644 --- a/index.js +++ b/index.js @@ -16,13 +16,23 @@ import PacketizationPanel from "./Panels/PacketizationPanel"; import AvailableFskPairsPanel from "./Panels/AvailableFskPairsPanel"; import FrequencyGraphPanel from "./Panels/FrequencyGraphPanel"; import GraphConfigurationPanel from './Panels/GraphConfigurationPanel' +import { + bitsToInt, + bitsToBytes, + bitsToText, + bytesToBits, + bytesToText, + numberToBytes, + numberToBits, + numberToHex, + textToBits, + textToBytes, +} from './converters'; var audioContext; var microphoneStream; var microphoneNode; var analyser; var sentDataTextArea; -const MAXIMUM_PACKETIZATION_SIZE_BITS = 16; -const CRC_BIT_COUNT = 8; // bits as they are sent let SENT_ORIGINAL_TEXT = ''; @@ -96,6 +106,10 @@ function handleWindowLoad() { signalPanel.setTimeoutMilliseconds(60); packetizationPanel.setSizePower(5); + packetizationPanel.setDataSizePower(16); + packetizationPanel.setDataSizeCrc(8); + packetizationPanel.setDataCrc(16); + packetizationPanel.setErrorCorrection(true); packetizationPanel.setInterleaving(true); @@ -153,6 +167,9 @@ function handleWindowLoad() { configurationChanged(); }); packetizationPanel.addEventListener('errorCorrectionChange', configurationChanged); + packetizationPanel.addEventListener('dataSizePowerChange', configurationChanged); + packetizationPanel.addEventListener('dataSizeCrcChange', configurationChanged); + packetizationPanel.addEventListener('dataCrcChange', configurationChanged); availableFskPairsPanel.addEventListener('change', (event) => { frequencyGraphPanel.setFskPairs(event.selected); @@ -252,6 +269,10 @@ function updateStreamManager() { StreamManager.setPacketEncoding( packetizationPanel.getErrorCorrection() ? HammingEncoding : undefined ); + const xferCountLength = packetizationPanel.getDataSizePower(); + const xferCountCrcLength = xferCountLength === 0 ? 0 : packetizationPanel.getDataSizeCrc(); + const xferCrcLength = packetizationPanel.getDataCrc(); + StreamManager.changeConfiguration({ bitsPerPacket: PacketUtils.getPacketMaxBitCount(), segmentsPerPacket: PacketUtils.getPacketSegmentCount(), @@ -259,11 +280,15 @@ function updateStreamManager() { streamHeaders: { 'transfer byte count': { index: 0, - length: MAXIMUM_PACKETIZATION_SIZE_BITS + length: xferCountLength }, 'transfer byte count crc': { - index: MAXIMUM_PACKETIZATION_SIZE_BITS, - length: CRC_BIT_COUNT + index: xferCountLength, + length: xferCountCrcLength + }, + 'transfer byte crc': { + index: xferCountLength + xferCountCrcLength, + length: xferCrcLength }, } }); @@ -276,8 +301,9 @@ function updatePacketUtils() { PacketUtils.changeConfiguration({ segmentDurationMilliseconds: signalPanel.getSegmentDurationMilliseconds(), packetSizeBitCount: packetizationPanel.getSizePower(), - dataSizeBitCount: MAXIMUM_PACKETIZATION_SIZE_BITS, - dataSizeCrcBitCount: CRC_BIT_COUNT, + dataSizeBitCount: packetizationPanel.getDataSizePower(), + dataSizeCrcBitCount: packetizationPanel.getDataSizeCrc(), + dataCrcBitCount: packetizationPanel.getDataCrc(), bitsPerSegment, packetEncoding: packetizationPanel.getErrorCorrection(), packetEncodingBitCount: ERROR_CORRECTION_BLOCK_SIZE, @@ -375,22 +401,20 @@ function percentInFrequency(hz, frequencyResolution) { return percent; } -function logSent(text) { - // display what is being sent - sentDataTextArea.value += text + '\n'; - sentDataTextArea.scrollTop = sentDataTextArea.scrollHeight; -} - function sendBytes(bytes) { const byteCount = bytes.length; if(byteCount === 0) { - logSent('Nothing to send!'); + document.getElementById('sent-data').innerText = 'Nothing to send!'; return; - } else if(byteCount > 0xFFFF) { - logSent('Too much to send!'); + } else if(byteCount > packetizationPanel.getDataSize()) { + document.getElementById('sent-data').innerText = `Attempted to send too much data. Limit is ${Humanize.byteSize(packetizationPanel.getDataSize())}. Tried to send ${Humanize.byteSize(byteCount)}`; return; } + AudioReceiver.reset(); + StreamManager.reset(); + frequencyGraphPanel.start(); + const bits = bytesToBits(bytes); SENT_ORIGINAL_TEXT = bytesToText(bytes); @@ -398,12 +422,44 @@ function sendBytes(bytes) { // packetization headers // data length - const dataLengthBits = numberToBits(bytes.length, MAXIMUM_PACKETIZATION_SIZE_BITS); - // crc on data length - const dataLengthCrcBits = numberToBits(CRC.check(bitsToBytes(dataLengthBits), CRC_BIT_COUNT), CRC_BIT_COUNT); + let dataLengthBits = []; + let dataLengthCrcBits = []; + let dataSizeCrcNumber = 0; + const dataLengthBitLength = packetizationPanel.getDataSizePower(); + if(dataLengthBitLength !== 0) { + dataLengthBits = numberToBits(bytes.length, dataLengthBitLength); + + // crc on data length + const dataSizeCrcBitLength = packetizationPanel.getDataSizeCrc(); + if(dataSizeCrcBitLength !== 0) { + const bytes = bitsToBytes(dataLengthBits); + dataSizeCrcNumber = CRC.check(bytes, dataSizeCrcBitLength); + dataLengthCrcBits = numberToBits(dataSizeCrcNumber, dataSizeCrcBitLength); + } + } + + // crc on data + let dataCrcBits = []; + const dataCrcBitLength = packetizationPanel.getDataCrc(); + let dataCrcNumber = 0; + if(dataCrcBitLength !== 0) { + dataCrcNumber = CRC.check(bytes, dataCrcBitLength); + dataCrcBits = numberToBits(dataCrcNumber, dataCrcBitLength); + } + console.log('sending headers', { + // dataLengthBits, + // dataLengthCrcBits, + // dataCrcBits, + dataCrcHex: numberToHex(packetizationPanel.getDataCrc())(dataCrcNumber), + dataSizeCrcHex: numberToHex(packetizationPanel.getDataSizeCrc())(dataSizeCrcNumber), + }) // prefix with headers - bits.unshift(...dataLengthBits, ...dataLengthCrcBits); + bits.unshift( + ...dataLengthBits, + ...dataLengthCrcBits, + ...dataCrcBits + ); const bitCount = bits.length; @@ -418,13 +474,10 @@ function sendBytes(bytes) { const packetCount = PacketUtils.getPacketCount(bitCount); const totalDurationSeconds = PacketUtils.getDataTransferDurationSeconds(bitCount); - const errorCorrectionBits = []; - AudioSender.beginAt(startSeconds); // send all packets for(let i = 0; i < packetCount; i++) { let packet = PacketUtils.getPacketBits(bits, i); - errorCorrectionBits.push(...packet); SENT_ENCODED_BITS.push(...packet); if(packet.length > packetBitCount) { console.error('Too many bits in the packet. tried to send %s, limited to %s', packet.length, packetBitCount); @@ -522,40 +575,30 @@ function resumeGraph() { } } -function getTransferredCorrectedBits() { - const bits = []; - const packetCount = StreamManager.getPacketReceivedCount(); - for(let packetIndex = 0; packetIndex < packetCount; packetIndex++) { - let packetBits = StreamManager.getPacketBits(packetIndex); - if(packetizationPanel.getErrorCorrection()) { - bits.push(...HammingEncoding.decode(packetBits)); - } else { - bits.push(...packetBits); - } - } - return bits; -} - function handleStreamManagerChange() { const channelCount = availableFskPairsPanel.getSelectedFskPairs().length; let allRawBits = StreamManager.getStreamBits(); let allEncodedBits = StreamManager.getAllPacketBits(); - let allDecodedBits = getTransferredCorrectedBits(); + let allDecodedBits = StreamManager.getAllPacketBitsDecoded(); // get packet data before removing decoded bits - const transmissionByteCount = parseTransmissionByteCount(allDecodedBits); - const transmissionByteCountCrc = parseTransmissionByteCountCrc(allDecodedBits) - const transmissionByteCountActualCrc = CRC.check( - bitsToBytes( - numberToBits( - transmissionByteCount, - MAXIMUM_PACKETIZATION_SIZE_BITS - ) - ), CRC_BIT_COUNT - ); - const trustedLength = transmissionByteCountCrc === transmissionByteCountActualCrc; + const transmissionByteCount = StreamManager.getTransferByteCount(); + const transmissionByteCountCrc = StreamManager.getTransferByteCountCrc(); + const transmissionByteCountActualCrc = StreamManager.getTransferByteCountActualCrc(); + + const trustedLength = StreamManager.isTransferByteCountTrusted(); const totalBitsTransferring = parseTotalBitsTransferring(allDecodedBits); + const transmissionDataCrc = StreamManager.getTransferDataCrc(); + const transmissionDataActualCrc = StreamManager.getTransferActualDataCrc(); + const transmissionCrc = document.getElementById('received-packet-original-data-crc'); + transmissionCrc.innerText = numberToHex(packetizationPanel.getDataCrc())(transmissionDataCrc); + const trustedData = StreamManager.isTransferDataTrusted(); + transmissionCrc.className = trustedData ? 'bit-correct' : 'bit-wrong'; + if(!trustedData) { + transmissionCrc.innerText += ' (Expected ' + numberToHex(packetizationPanel.getDataCrc())(transmissionDataActualCrc) + ')'; + } + // reduce all decoded bits based on original data sent allDecodedBits = removeDecodedHeadersAndPadding(allDecodedBits); @@ -604,10 +647,10 @@ function handleStreamManagerChange() { ''); document.getElementById('received-packet-original-bytes').innerText = transmissionByteCount.toLocaleString(); const packetCrc = document.getElementById('received-packet-original-bytes-crc'); - packetCrc.innerText = '0x' + asHex(2)(transmissionByteCountCrc); + packetCrc.innerText = numberToHex(packetizationPanel.getDataSizeCrc())(transmissionByteCountCrc); packetCrc.className = trustedLength ? 'bit-correct' : 'bit-wrong'; if(!trustedLength) { - packetCrc.innerText += ' (Expected 0x' + asHex(2)(transmissionByteCountActualCrc) + ')'; + packetCrc.innerText += ' (Expected ' + numberToHex(packetizationPanel.getDataSizeCrc())(transmissionByteCountActualCrc) + ')'; } document.getElementById('received-encoded-bits-error-percent').innerText = ( @@ -619,44 +662,23 @@ function handleStreamManagerChange() { document.getElementById('received-decoded-bits-error-percent').innerText = ( Math.floor((1 - (correctedDecodedBits / allDecodedBits.length)) * 10000) * 0.01 ).toLocaleString(); - // ArrayBuffer / ArrayBufferView - const receivedText = bitsToText(allDecodedBits); + + const bytes = StreamManager.getDataBytes(); + const receivedText = bytesToText(bytes); + messagePanel.setReceived( receivedText.split('').reduce(textExpectorReducer(SENT_ORIGINAL_TEXT), '') ); } -function asHex(length) { - return (number) => number.toString(16).padStart(length, '0').toUpperCase(); -} -function parseDataTransferDurationMilliseconds() { - const decodedBits = getTransferredCorrectedBits(); - const byteCount = parseTransmissionByteCount(decodedBits); - return PacketUtils.getDataTransferDurationMillisecondsFromByteCount(byteCount); -} function parseTotalBitsTransferring() { - const dataByteCount = parseTransmissionByteCount(); + const dataByteCount = StreamManager.getTransferByteCount(); const bitCount = PacketUtils.getPacketizationBitCountFromByteCount(dataByteCount); const segments = getTotalSegmentCount(bitCount); return segments * availableFskPairsPanel.getSelectedFskPairs().length; } -function parseTransmissionByteCountCrc() { - let decodedBits = getTransferredCorrectedBits(); - const offset = MAXIMUM_PACKETIZATION_SIZE_BITS; - decodedBits = decodedBits.slice(offset, offset + CRC_BIT_COUNT); - return bitsToInt(decodedBits, CRC_BIT_COUNT); -} -function parseTransmissionByteCount() { - let decodedBits = getTransferredCorrectedBits(); - decodedBits = decodedBits.slice(0, MAXIMUM_PACKETIZATION_SIZE_BITS); - while(decodedBits.length < MAXIMUM_PACKETIZATION_SIZE_BITS) { - // assume maximum value possible - // until we have enough bits to find the real size - decodedBits.push(1); - } - return bitsToInt(decodedBits, MAXIMUM_PACKETIZATION_SIZE_BITS); -} + function removeEncodedPadding(bits) { - const sizeBits = MAXIMUM_PACKETIZATION_SIZE_BITS; + const sizeBits = packetizationPanel.getDataSizePower(); const dataSize = ERROR_CORRECTION_DATA_SIZE; const blockSize = ERROR_CORRECTION_BLOCK_SIZE; let bitsNeeded = sizeBits; @@ -675,7 +697,10 @@ function removeEncodedPadding(bits) { const dataByteCount = StreamManager.getTransferByteCount(); // determine how many decoded bits need to be sent (including the size) - const totalBits = (dataByteCount * 8) + MAXIMUM_PACKETIZATION_SIZE_BITS + CRC_BIT_COUNT; + let totalBits = (dataByteCount * 8); + totalBits += packetizationPanel.getDataSizePower(); + if(packetizationPanel.getDataSizePower() !== 0) totalBits += packetizationPanel.getDataSizeCrc(); + totalBits += packetizationPanel.getDataCrc(); let encodingBitCount = totalBits; if(packetizationPanel.getErrorCorrection()) { const blocks = Math.ceil(encodingBitCount / dataSize); @@ -691,17 +716,51 @@ function removeEncodedPadding(bits) { return bits; } function removeDecodedHeadersAndPadding(bits) { - const sizeBits = MAXIMUM_PACKETIZATION_SIZE_BITS; - let bitCount = bits.length / 8; - if(bits.length >= sizeBits) { - bitCount = bitsToInt(bits.slice(0, sizeBits), sizeBits); + const sizeBitCount = packetizationPanel.getDataSizePower(); + const sizeCrcBitCount = packetizationPanel.getDataSizeCrc(); + const dataCrcBitCount = packetizationPanel.getDataCrc(); + let byteCount; + let offset = 0; + if(sizeBitCount !== 0) { + offset += sizeBitCount; + // header bits only? + if(bits.length <= offset) return []; + byteCount = bitsToInt(bits.slice(0, sizeBitCount), sizeBitCount); + if(sizeCrcBitCount !== 0) { + offset += sizeBitCount; + // header bits only? + if(bits.length <= offset) return []; + let countCrc = bitsToInt(bits.slice(sizeBitCount, sizeBitCount + sizeCrcBitCount), sizeCrcBitCount); + let actualCountCrc = CRC.check(numberToBytes(sizeCrcBitCount, sizeBitCount), sizeCrcBitCount); + // can we trust the size? + if(countCrc !== actualCountCrc) { + if(dataCrcBitCount !== 0) { + offset += dataCrcBitCount; + if(bits.length <= offset) return []; + } + // Change based off of header bits + byteCount = (bits.length - offset) / 8; + } + } else if(dataCrcBitCount !== 0) { + offset += dataCrcBitCount; + if(bits.length <= offset) return []; + } + // remove headers and excessive bits + const bitCount = byteCount * 8; + return bits.slice(offset, offset + bitCount).splice(bitCount); + } else { + // size not included. Means 1 byte max + // crc not valid on size + // crc valid on byte + const dataCrcBitCount = packetizationPanel.getDataCrc(); + if(dataCrcBitCount === 0) { + // bits are pure data for 1 byte + return bits.slice(0, 8); + } else { + // get byte after data crc + return bits.slice(dataCrcBitCount, dataCrcBitCount + 8); + } } - // remove size and crc header - bits.splice(0, sizeBits + CRC_BIT_COUNT); - - // remove excessive bits - bits.splice(bitCount * 8); - return bits; } const bitReducer = (packetBitSize, blockSize, blockCallback) => (all, bit, i) => { const packetIndex = Math.floor(i / packetBitSize); @@ -780,63 +839,10 @@ function getAudioContext() { } return audioContext; } -function bitsToInt(bits, bitLength) { - // only grab the bits we need - const bitString = bits.slice(0, bitLength) - // combine into string - .join('') - // Assume missing bits were zeros - .padEnd(bitLength, '0'); - // parse as int - return parseInt(bitString, 2); -} -function intToBytes(int, bitLength) { - const byteCount = Math.ceil(bitLength/8); - const bytes = []; - for(let i = 0; i < byteCount; i++) { - bytes.push((int >> (8 * (byteCount - 1 - i))) & 0xFF); - } - return bytes; -} -function numberToBits(number, bitLength) { - const bits = []; - for(let i = bitLength - 1; i >= 0; i--) - bits.push((number >> i) & 1); - return bits; -} -function bytesToText(bytes) { - return new TextDecoder().decode(bytes); -} -function textToBytes(text) { - return new TextEncoder().encode(text); -} -function bytesToBits(bytes) { - return bytes.reduce((bits, byte) => [ - ...bits, - ...byte.toString(2).padStart(8, '0').split('').map(Number) - ], []); -} -function bitsToBytes(bits) { - const bytes = []; - for(let i = 0; i < bits.length; i+= 8) { - bytes.push(parseInt(bits.slice(i, i + 8).join(''), 2)); - } - return bytes; -} -function textToBits(text) { - return bytesToBits(textToBytes(text)); -} -function bitsToText(bits) { - const bytes = new Uint8Array(bitsToBytes(bits)); - return bytesToText(bytes.buffer); -} function handleSendButtonClick() { if(messagePanel.getSendButtonText() === 'Stop') { AudioSender.stop(); } else { - AudioReceiver.reset(); - StreamManager.reset(); - frequencyGraphPanel.start(); const text = messagePanel.getMessage(); sendBytes(textToBytes(text)); }