show packet error stats

This commit is contained in:
Lewis Moten
2024-05-14 20:43:48 -04:00
parent da7feaf09e
commit c4a0d8afd1
8 changed files with 333 additions and 47 deletions

View File

@@ -10,7 +10,7 @@ import * as CRC from './CRC';
let SEGMENT_DURATION = 30;
let PACKET_SIZE_BITS = 8;
let DATA_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;
@@ -37,7 +37,7 @@ export const changeConfiguration = (config) => {
} = config;
SEGMENT_DURATION = segmentDurationMilliseconds;
PACKET_SIZE_BITS = packetSizeBitCount;
DATA_SIZE_BITS = dataSizeBitCount;
DATA_SIZE_BIT_COUNT = dataSizeBitCount;
DATA_SIZE_CRC_BITS = dataSizeCrcBitCount;
DATA_CRC_BITS = dataCrcBitCount;
BITS_PER_SAMPLE = bitsPerSegment;
@@ -60,7 +60,7 @@ const decodePacket = (packetBits) => IS_ENCODED ? ENCODING.decode(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_BITS;
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();
@@ -113,7 +113,7 @@ export const canSendPacket = () => {
return IS_ENCODED ? maxBits >= PACKET_ENCODED_BLOCK_SIZE : true;
}
export const getPacketizationHeaderBitCount = (padUnusedBits = true) => {
let count = DATA_SIZE_BITS + DATA_SIZE_CRC_BITS + DATA_CRC_BITS;
let count = DATA_SIZE_BIT_COUNT + DATA_SIZE_CRC_BITS + DATA_CRC_BITS;
if(padUnusedBits && count % 8 !== 0) {
count += 8 - (count % 8);
}
@@ -196,8 +196,8 @@ export const pack = (bytes) => {
let dataLengthBits = [];
let dataLengthCrcBits = [];
let dataSizeCrcNumber = 0;
if(DATA_SIZE_BITS !== 0) {
dataLengthBits = numberToBits(bytes.length, DATA_SIZE_BITS);
if(DATA_SIZE_BIT_COUNT !== 0) {
dataLengthBits = numberToBits(bytes.length, DATA_SIZE_BIT_COUNT);
// crc on data length
if(DATA_SIZE_CRC_BITS !== 0) {
@@ -217,7 +217,7 @@ export const pack = (bytes) => {
const headers = [
...dataLengthBits,
...dataLengthCrcBits,
...dataCrcBits
...dataCrcBits,
];
// pad headers to take full bytes
while(headers.length % 8 !== 0) {

View File

@@ -162,20 +162,32 @@ class BasePanel {
canvas.height = height;
return this.append(canvas);
}
addProgressBar = (id, percent) => {
addProgressBar = (id, ...percents) => {
const progressBar = document.createElement('div');
progressBar.className = 'progress-container';
const bar = document.createElement('div');
bar.id = this.childId(id);
bar.className = 'progress-bar';
bar.style.width = `${clamp(percent, 0, 1) * 100}%`;
progressBar.append(bar);
let sum = 0;
for(let i = 0; i < percents.length; i++) {
let percent = percents[i];
percent = clamp(percent, 0, 1 - sum);
sum += percent;
const bar = document.createElement('div');
bar.id = this.childId(`${id}-${i}`);
bar.className = 'progress-bar';
bar.style.width = `${clamp(percent, 0, 1) * 100}%`;
progressBar.append(bar);
}
this.append(progressBar);
}
setProgressById = (id, percent) => {
const element = document.getElementById(this.childId(id));
if(!element) throw new Error(`Unable to find ${id}`);
element.style.width = `${clamp(percent, 0, 1) * 100}%`;
setProgressById = (id, ...percents) => {
let sum = 0;
for(let i = 0; i < percents.length; i++) {
let percent = percents[i];
percent = clamp(percent, 0, 1 - sum);
sum += percent;
const element = document.getElementById(this.childId(`${id}-${i}`));
if(!element) throw new Error(`Unable to find ${id}`);
element.style.width = `${clamp(percent, 0, 1) * 100}%`;
}
}
childId = id => `${this.id}-${id}`;

View File

@@ -0,0 +1,52 @@
import BasePanel from './BasePanel';
class PacketErrorPanel extends BasePanel {
constructor() {
super('Packet Errors');
this.openField('CRC Check');
this.addDynamicText('crc', 'N/A');
this.closeField();
this.openField('CRC Size Check');
this.addDynamicText('crc-size', 'N/A');
this.closeField();
this.openField('Failed Packets');
this.addDynamicText('failed-packet-count', 'N/A');
this.addDynamicText('failed-packet-count-percent', '');
this.closeField();
this.addSection('Packet Retransmission')
this.addRadios('repeat', [
{ text: 'Automatic Repeat Request', value: 'arq', checked: true, eventName: 'hi' },
{ text: 'Manual Repeat Request', value: 'manual', checked: true, eventName: 'hi' }
]);
this.openField('Packets');
this.addInputText('request-packet-indexes', '');
this.closeField();
this.addButton('request-button', 'Request', 'requestPackets');
}
reset = () => {
this.setFailedPacketIndeces([]);
this.setSizeCrcUnavailable();
this.setCrcUnavailable();
}
setFailedPacketIndeces = (packetIndexes) => {
this.setValueById('request-packet-indexes', packetIndexes.join(', '));
this.setValueById('failed-packet-count', packetIndexes.length.toLocaleString());
}
getFailedPacketIndeces = () => {
let text = this.getValueById('request-packet-indexes');
return text.replace(/\s+/g, '').split(',').map(Number);
}
setCrcPassed = (passed) => this.setValueById('crc', passed ? 'Pass' : 'Fail');
setCrcUnavailable = () => this.setValueById('crc', 'N/A');
setSizeCrcPassed = (passed) => this.setValueById('crc-size', passed ? 'Pass' : 'Fail');
setSizeCrcUnavailable = () => this.setValueById('crc-size', 'N/A');
}
export default PacketErrorPanel;

View File

@@ -15,7 +15,7 @@ class ReceivePanel extends BasePanel {
this.addNewLine();
this.addDynamicText('id-state', 'Offline.');
this.addProgressBar('progress', .50);
this.addProgressBar('progress', .50, .25);
this.addCode('text', '', 'small');
this.addImage('image', undefined, {width: 32, height: 32});
@@ -69,7 +69,9 @@ class ReceivePanel extends BasePanel {
// stopWaitingForSignal = () => {
// AudioReceiver.stop();
// }
setProgress = percent => this.setProgressById('progress', percent);
setProgress = (percent, percent2 = 0) => {
this.setProgressById('progress', percent, percent2);
}
setReceivedHtml = (html) => this.setHtmlById('text', html);
setReceivedBytes = bytes => {
if(this.dataType === 'text') {
@@ -84,6 +86,30 @@ class ReceivePanel extends BasePanel {
this.display('text', value === 'text');
this.display('image', value === 'image');
}
setSuccessfulPacketCount = (count) => {
this.successfulPacketCount = count;
this.updateProgressBar();
}
setExpectedPacketCount = (count) => {
this.expectedPacketCount = count;
this.updateProgressBar();
}
setFailedPacketCount = (count) => {
this.failedPacketCount = count;
this.updateProgressBar();
}
updateProgressBar = () => {
const total = this.expectedPacketCount;
if(total === 0) {
this.setProgress(0, 0);
}
this.setProgress(
this.successfulPacketCount/total,
this.failedPacketCount/total
)
}
}
export default ReceivePanel;

View File

@@ -7,14 +7,24 @@ import {
bytesToBits,
numberToBytes,
numberToHex,
numberToAscii
numberToAscii,
bytesToNumber
} from "./converters";
const dispatcher = new Dispatcher('StreamManager', ['change']);
const dispatcher = new Dispatcher('StreamManager', [
'change',
'packetReceived',
'packetFailed',
'sizeReceived'
]);
let DATA = new Uint8ClampedArray();
const FAILED_SEQUENCES = [];
let FAILED_SEQUENCES = [];
let SUCCESS_SEQUENCES = [];
let SAMPLES_EXPECTED = 0;
let SAMPLES_RECEIVED = 0;
let DATA_CRC_BIT_COUNT = 0;
let DATA_SIZE_BIT_COUNT = 0;
let DATA_SIZE_CRC_BIT_COUNT = 0;
const BITS = [];
let BITS_PER_PACKET = 0;
@@ -29,10 +39,20 @@ let PACKET_ENCODING = {
export const addEventListener = dispatcher.addListener;
export const removeEventListener = dispatcher.removeListener;
const isPacketInRange = (packetIndex) => {
// Blindly accept. We can't do anything about it for now
if(!isSizeTrusted()) return true;
const { packetCount } = PacketUtils.packetStats(getSize());
return packetIndex < packetCount;
}
export const reset = () => {
let changed = false;
SAMPLES_RECEIVED = 0;
SAMPLES_EXPECTED = 0;
if(SUCCESS_SEQUENCES.length !== 0) {
SUCCESS_SEQUENCES.length = 0;
changed = true;
}
if(FAILED_SEQUENCES.length !== 0) {
FAILED_SEQUENCES.length = 0;
changed = true;
@@ -66,6 +86,9 @@ export const applyPacket = ({
bytes,
size
}) => {
let trustedSize = isSizeTrusted();
if(!isPacketInRange(sequence)) return;
const dataSize = PacketUtils.getPacketDataByteCount();
const offset = sequence * dataSize;
const length = offset + dataSize;
@@ -73,39 +96,179 @@ export const applyPacket = ({
if(FAILED_SEQUENCES.includes(sequence)) {
FAILED_SEQUENCES.splice(FAILED_SEQUENCES.indexOf(sequence), 1);
}
if(!SUCCESS_SEQUENCES.includes(sequence)) {
SUCCESS_SEQUENCES.push(sequence);
}
if(DATA.length < length) {
const copy = new Uint8ClampedArray(length);
copy.set(DATA.subarray(0, DATA.length), 0);
DATA = copy;
}
DATA.set(bytes, offset);
delete BITS[packetIndex];
if(!trustedSize && isSizeTrusted()) {
// We may now have a trusted size. update prior failures.
FAILED_SEQUENCES = FAILED_SEQUENCES.filter(isPacketInRange);
dispatcher.emit('sizeReceived');
}
dispatcher.emit('packetReceived');
} else {
console.log("Failed", sequence);
if(!FAILED_SEQUENCES.includes(sequence))
FAILED_SEQUENCES.push(sequence);
// do nothing if previously successful
if(!SUCCESS_SEQUENCES.includes(sequence)) {
// NOTE: Can we trust the sequence?
// Check if sequence out of range
if(!FAILED_SEQUENCES.includes(sequence))
FAILED_SEQUENCES.push(sequence);
dispatcher.emit('packetFailed', {sequence});
}
}
delete BITS[packetIndex]
}
export const getPercentReceived = () => {
if(SAMPLES_EXPECTED === 0) return 0;
return SAMPLES_RECEIVED / SAMPLES_EXPECTED;
export const getFailedPacketIndeces = () => FAILED_SEQUENCES;
export const countFailedPackets = () => FAILED_SEQUENCES.length;
export const countSuccessfulPackets = () => SUCCESS_SEQUENCES.length;
export const countExpectedPackets = () => {
if(!isSizeTrusted()) return 0;
return PacketUtils.packetStats(getSize()).packetCount;
}
export const setPacketsExpected = packetCount => {
if(packetCount < 0 || packetCount === Infinity) packetCount = 0;
// used when requesting individual packets out of sequence
SAMPLES_EXPECTED = packetCount * PacketUtils.getPacketSegmentCount();
}
export const getSizeAvailable = () => {
if(DATA_SIZE_BIT_COUNT === 0) return 1;
let lastBit = DATA_SIZE_BIT_COUNT;
let lastByte = Math.ceil(lastBit / 8);
if(DATA.length < lastByte) return false;
// Do we have a crc check on the size?
if(DATA_SIZE_CRC_BIT_COUNT !== 0) {
return getSizeCrcAvailable();
}
return true;
}
export const isSizeTrusted = () => {
if(!getSizeAvailable()) return false;
if(DATA_SIZE_CRC_BIT_COUNT !== 0) return getSizeCrcPassed();
return true;
}
export const getSize = () => {
if(DATA_SIZE_BIT_COUNT === 0) return 1;
if(!getSizeAvailable()) return -1;
let firstBit = 0;
let lastBit = DATA_SIZE_BIT_COUNT;
// Do we have the data?
let firstByte = Math.floor(firstBit / 8);
let lastByte = Math.ceil(lastBit / 8);
if(DATA.length < lastByte) return -1;
// Grab the data
let bits = bytesToBits(DATA.subarray(firstByte, lastByte));
if(firstBit % 8 !== 0) {
bits.splice(firstBit % 8);
}
bits.length = DATA_SIZE_BIT_COUNT
return bitsToInt(bits, DATA_SIZE_BIT_COUNT);
}
export const getSizeCrc = () => {
if(!getSizeCrcAvailable()) return CRC.INVALID;
let startBitIndex = DATA_SIZE_BIT_COUNT;
let endBitIndex = startBitIndex + DATA_SIZE_CRC_BIT_COUNT;
let startByte = Math.floor(startBitIndex / 8);
let endByte = Math.ceil(endBitIndex / 8);
if(DATA.length < endByte) return CRC.INVALID;
let bits = bytesToBits(DATA.subarray(startByte, endByte));
if(startBitIndex % 8 !== 0) bits.splice(0, startBitIndex);
bits.length = DATA_SIZE_CRC_BIT_COUNT;
return bitsToInt(bits, DATA_SIZE_CRC_BIT_COUNT);
}
export const getCrc = () => {
if(!getCrcAvailable()) return CRC.INVALID;
let startBitIndex = DATA_SIZE_BIT_COUNT + DATA_SIZE_CRC_BIT_COUNT;
let endBitIndex = startBitIndex + DATA_CRC_BIT_COUNT;
let startByte = Math.floor(startBitIndex / 8);
let endByte = Math.ceil(endBitIndex / 8);
if(DATA.length < endByte) return CRC.INVALID;
let bits = bytesToBits(DATA.subarray(startByte, endByte));
if(startBitIndex % 8 !== 0) bits.splice(0, startBitIndex);
bits.length = DATA_CRC_BIT_COUNT;
return bitsToInt(bits, DATA_CRC_BIT_COUNT);
}
export const getSizeCrcAvailable = () => {
if (DATA_SIZE_BIT_COUNT === 0) return false;
if (DATA_SIZE_CRC_BIT_COUNT === 0) return false;
const bitsNeeded = DATA_SIZE_BIT_COUNT + DATA_SIZE_CRC_BIT_COUNT;
return DATA.length >= Math.ceil(bitsNeeded / 8);
}
export const getCrcAvailable = () => {
if(DATA_CRC_BIT_COUNT === 0) return false;
if(!getSizeAvailable()) return false;
let byteCount = getSize();
if(DATA.length < byteCount) return false;
// Do we have enough bytes for the headers and underlying data?
let headerBitCount = DATA_SIZE_BIT_COUNT + DATA_CRC_BIT_COUNT + DATA_SIZE_CRC_BIT_COUNT;
if(headerBitCount % 8 !== 0)
headerBitCount += 8 - (headerBitCount % 8);
const headerByteCount = headerBitCount / 8;
byteCount += headerByteCount;
return DATA.length >= byteCount;
}
export const getSizeCrcPassed = () => {
if(!getSizeCrcAvailable()) return false;
const size = getSize();
const sizeCrc = getSizeCrc();
if(sizeCrc === CRC.INVALID) return false;
const crc = CRC.check(numberToBytes(size, DATA_SIZE_BIT_COUNT), DATA_SIZE_CRC_BIT_COUNT);
return crc === sizeCrc;
}
export const getCrcPassed = () => {
if(!getCrcAvailable()) return false;
if(!isSizeTrusted()) return false;
const size = getSize();
const crc = getCrc();
if(crc === CRC.INVALID) return false;
// Get Data
// How large is our header?
let headerBitCount = DATA_CRC_BIT_COUNT + DATA_SIZE_BIT_COUNT + DATA_SIZE_CRC_BIT_COUNT;
if(headerBitCount % 8 !== 0) headerBitCount += 8 - (headerBitCount % 8);
let headerByteCount = headerBitCount / 8;
// Get bytes needed to perform CRC check on
const data = DATA.subarray(headerByteCount, headerByteCount + size);
// Do the check
return crc === CRC.check(data, DATA_CRC_BIT_COUNT);
}
export const changeConfiguration = ({
segmentsPerPacket,
bitsPerPacket,
bitsPerSegment,
streamHeaders
streamHeaders,
dataCrcBitLength,
dataSizeBitCount,
dataSizeCrcBitCount
}) => {
BITS_PER_PACKET = bitsPerPacket;
SEGMENTS_PER_PACKET = segmentsPerPacket;
BITS_PER_SEGMENT = bitsPerSegment;
STREAM_HEADERS = streamHeaders;
DATA_CRC_BIT_COUNT = dataCrcBitLength;
DATA_SIZE_BIT_COUNT = dataSizeBitCount;
DATA_SIZE_CRC_BIT_COUNT = dataSizeCrcBitCount;
}
const noEncoding = bits => bits;
export const setPacketEncoding = ({ encode, decode } = {}) => {
@@ -118,6 +281,8 @@ export const addSample = (
bits
) => {
SAMPLES_RECEIVED++;
if(BITS[packetIndex] === undefined) {
BITS[packetIndex] = [];
}

View File

@@ -18,6 +18,13 @@ export function numberToBits(number, bitLength) {
bits.push((number >> i) & 1);
return bits;
}
export function bytesToNumber(bytes) {
let number = 0;
for(let i = 0; i < bytes.length; i++) {
number += bytes[i] << (8 * i);
}
return number;
}
export function bytesToText(bytes) {
if(!(bytes instanceof ArrayBuffer || ArrayBuffer.isView(bytes))) {
bytes = new Uint8Array(bytes).buffer;
@@ -25,6 +32,9 @@ export function bytesToText(bytes) {
return new TextDecoder().decode(bytes);
}
export function bytesToBits(bytes) {
if(ArrayBuffer.isView(bytes)) {
bytes = Array.from(bytes);
}
if(!Array.isArray(bytes)) return [];
return bytes.reduce((bits, byte) => [
...bits,

View File

@@ -15,27 +15,19 @@ import SignalPanel from "./Panels/SignalPanel";
import PacketizationPanel from "./Panels/PacketizationPanel";
import AvailableFskPairsPanel from "./Panels/AvailableFskPairsPanel";
import FrequencyGraphPanel from "./Panels/FrequencyGraphPanel";
import GraphConfigurationPanel from './Panels/GraphConfigurationPanel'
import GraphConfigurationPanel from './Panels/GraphConfigurationPanel';
import PacketErrorPanel from './Panels/PacketErrorPanel';
import SpeedPanel from './Panels/SpeedPanel';
import {
bitsToInt,
bitsToBytes,
bitsToText,
bytesToBits,
bytesToText,
numberToBytes,
numberToBits,
numberToHex,
textToBits,
textToBytes,
} from './converters';
import MicrophonePanel from "./Panels/MicrophonePanel";
import ReceivePanel from "./Panels/ReceivePanel";
var audioContext;
var microphoneStream;
var microphoneNode;
var analyser;
var sentDataTextArea;
// bits as they are sent
let SENT_ORIGINAL_TEXT = '';
@@ -72,6 +64,7 @@ const graphConfigurationPanel = new GraphConfigurationPanel();
const speedPanel = new SpeedPanel();
const microphonePanel = new MicrophonePanel();
const receivePanel = new ReceivePanel();
const packetErrorPanel = new PacketErrorPanel();
function handleWindowLoad() {
const panelContainer = document.getElementById('panel-container');
@@ -84,6 +77,7 @@ function handleWindowLoad() {
panelContainer.prepend(signalPanel.getDomElement());
panelContainer.prepend(bitsReceivedPanel.getDomElement());
panelContainer.prepend(bitsSentPanel.getDomElement());
panelContainer.prepend(packetErrorPanel.getDomElement());
panelContainer.prepend(receivePanel.getDomElement());
panelContainer.prepend(microphonePanel.getDomElement());
panelContainer.prepend(communicationsPanel.getDomElement());
@@ -103,9 +97,14 @@ function handleWindowLoad() {
receivePanel.setDataType(dataType);
})
receivePanel.setDataType(messagePanel.getDataType());
receivePanel.setProgress(0);
receivePanel.setExpectedPacketCount(100);
receivePanel.setFailedPacketCount(25);
receivePanel.setSuccessfulPacketCount(50);
receivePanel.setReceivedHtml('Ready.');
packetErrorPanel.reset();
bitsSentPanel.setCode('');
bitsReceivedPanel.setCode('');
@@ -222,9 +221,25 @@ function handleWindowLoad() {
AudioSender.addEventListener('end', () => messagePanel.setSendButtonText('Send'));
// Setup stream manager
StreamManager.addEventListener('change', handleStreamManagerChange);
StreamManager.addEventListener('packetFailed', () => {
packetErrorPanel.setFailedPacketIndeces(StreamManager.getFailedPacketIndeces());
});
StreamManager.addEventListener('packetReceived', () => {
// Failed indices changed?
packetErrorPanel.setFailedPacketIndeces(StreamManager.getFailedPacketIndeces());
if(StreamManager.getSizeCrcAvailable()) {
packetErrorPanel.setSizeCrcPassed(StreamManager.getSizeCrcPassed());
} else {
packetErrorPanel.setSizeCrcUnavailable();
}
if(StreamManager.getCrcAvailable()) {
packetErrorPanel.setCrcPassed(StreamManager.getCrcPassed());
} else {
packetErrorPanel.setCrcUnavailable();
}
});
// grab dom elements
sentDataTextArea = document.getElementById('sent-data');
const receivedChannelGraph = document.getElementById('received-channel-graph');
receivedChannelGraph.addEventListener('mouseover', handleReceivedChannelGraphMouseover);
receivedChannelGraph.addEventListener('mouseout', handleReceivedChannelGraphMouseout);
@@ -303,6 +318,9 @@ function updateStreamManager() {
bitsPerPacket: PacketUtils.getPacketMaxBitCount(),
segmentsPerPacket: PacketUtils.getPacketSegmentCount(),
bitsPerSegment: availableFskPairsPanel.getSelectedFskPairs().length,
dataCrcBitLength: packetizationPanel.getDataCrc(),
dataSizeBitCount: packetizationPanel.getDataSizePower(),
dataSizeCrcBitCount: packetizationPanel.getDataSizeCrc(),
streamHeaders: {
'transfer byte count': {
index: 0,
@@ -549,8 +567,9 @@ function resumeGraph() {
}
function handleStreamManagerChange() {
receivePanel.setProgress(StreamManager.getPercentReceived());
receivePanel.setSuccessfulPacketCount(StreamManager.countSuccessfulPackets());
receivePanel.setExpectedPacketCount(StreamManager.countExpectedPackets());
receivePanel.setFailedPacketCount(StreamManager.countFailedPackets());
const bytes = StreamManager.getDataBytes();
const receivedText = bytesToText(bytes);

View File

@@ -66,10 +66,12 @@ canvas {
min-height: 14px;
background-color: yellow;
position: relative;
z-index: 2;
left: 0;
width: 0;
/* transition: width 0.3s ease; */
display: inline-block;
transition: width 1s ease;
}
.progress-bar:nth-child(2) {
background-color: red;
}
.xprogress-container::after {
content: attr(data-percent) '%';