Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

QRTP-B and typed headers #28

Merged
merged 53 commits into from
Mar 21, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
53 commits
Select commit Hold shift + click to select a range
751141f
refactor ggwave
OrionReed Mar 19, 2025
0c89c10
fix protocol setter and add interface
OrionReed Mar 19, 2025
3b874a8
cleanup demo
OrionReed Mar 19, 2025
2c1288b
restyle
OrionReed Mar 19, 2025
7803b00
fixes
OrionReed Mar 19, 2025
36f8d9c
wip QRTPB
OrionReed Mar 19, 2025
5d1294c
backchanelling
OrionReed Mar 19, 2025
b5ed273
wip
OrionReed Mar 19, 2025
bd110bd
wip
OrionReed Mar 20, 2025
f5c0e96
cleanup types, tests and add numPairs
OrionReed Mar 20, 2025
a10a11e
simplify
OrionReed Mar 20, 2025
885dd5b
simplify
OrionReed Mar 20, 2025
f770e7b
simplify
OrionReed Mar 20, 2025
fcedc61
simplify
OrionReed Mar 20, 2025
dcf7131
simplify
OrionReed Mar 20, 2025
c89ed31
simplify
OrionReed Mar 20, 2025
fea027a
simplify
OrionReed Mar 20, 2025
fc123ce
renames
OrionReed Mar 20, 2025
558f6ef
dashes for fixed sizes
OrionReed Mar 20, 2025
6cf24d6
strongly typed headers
OrionReed Mar 20, 2025
7c0ec4d
prettify types
OrionReed Mar 20, 2025
306cd02
fix tests
OrionReed Mar 20, 2025
93d0f5e
remove comments
OrionReed Mar 20, 2025
06a1c34
refactor qrtp to use header
OrionReed Mar 20, 2025
23fde69
simplify QRTP
OrionReed Mar 20, 2025
9a5fdcc
fix
OrionReed Mar 20, 2025
a7270bc
simplify qrtp
OrionReed Mar 20, 2025
207026a
move hash function
OrionReed Mar 20, 2025
11b8de3
simplify
OrionReed Mar 20, 2025
dc1b58d
cleanup qrtp api
OrionReed Mar 20, 2025
96f7ebd
simplify qrtp
OrionReed Mar 20, 2025
147c7cb
better hash func
OrionReed Mar 20, 2025
0a50373
redesign
OrionReed Mar 20, 2025
67c83b0
api cleanup
OrionReed Mar 20, 2025
70a4ced
fix qrtp
OrionReed Mar 20, 2025
43c1de2
tweak
OrionReed Mar 20, 2025
b1fd858
even simpler!
OrionReed Mar 20, 2025
f440288
even simpler!
OrionReed Mar 20, 2025
0f0bf58
doc
OrionReed Mar 20, 2025
525b90c
cleanup demo
OrionReed Mar 20, 2025
c51ddd6
rebuild demo
OrionReed Mar 21, 2025
287be1d
debug info
OrionReed Mar 21, 2025
88b5f78
ascii diagram element
OrionReed Mar 21, 2025
86d965b
wip qrtp-b
OrionReed Mar 21, 2025
a4d54d5
works!
OrionReed Mar 21, 2025
72ab169
cleanup UI
OrionReed Mar 21, 2025
0962c53
fixes
OrionReed Mar 21, 2025
fcc4bcc
use ranges
OrionReed Mar 21, 2025
6a136b1
add speed indicators
OrionReed Mar 21, 2025
d7764ee
fixes
OrionReed Mar 21, 2025
97a007b
cleanup
OrionReed Mar 21, 2025
4f04345
renames
OrionReed Mar 21, 2025
e1acdb0
fix lockb
OrionReed Mar 21, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file modified bun.lockb
Binary file not shown.
624 changes: 624 additions & 0 deletions labs/QRTP-B.ts

Large diffs are not rendered by default.

483 changes: 73 additions & 410 deletions labs/QRTP.ts

Large diffs are not rendered by default.

205 changes: 205 additions & 0 deletions labs/__tests__/QRTP.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,205 @@
import { beforeEach, describe, expect, test } from 'bun:test';
import { QRTP } from '../QRTP';

describe('QRTP Protocol', () => {
let senderQRTP: QRTP;
let receiverQRTP: QRTP;
let senderCode: string = '';
let receiverCode: string = '';

beforeEach(() => {
senderQRTP = new QRTP();
receiverQRTP = new QRTP();

// Track current QR codes via events
senderQRTP.on('qrUpdate', ({ data }) => (senderCode = data));
receiverQRTP.on('qrUpdate', ({ data }) => (receiverCode = data));
});

test('should properly segment message', () => {
const message = 'This is a test message that will be broken into chunks';
let initData: any;

senderQRTP.on('init', (data) => {
initData = data;
});

senderQRTP.setMessage(message, 10);
expect(initData.total).toBe(Math.ceil(message.length / 10));
});

test('simulates a complete message transfer with acknowledgments', () => {
// Setup listeners
let completeReceived = false;
let receivedChunks: string[] = [];
let ackCount = 0;

receiverQRTP.on('complete', () => {
completeReceived = true;
});

receiverQRTP.on('chunk', (event) => {
receivedChunks[event.index] = event.payload;
});

senderQRTP.on('ack', (event) => {
ackCount++;
});

// Send a message from sender to receiver
const testMessage = 'Hello, world!';
senderQRTP.setMessage(testMessage, 5);

// Step 1: Receiver scans the first QR code from sender
receiverQRTP.parseCode(senderCode);
expect(receivedChunks[0]).toBe('Hello');
expect(completeReceived).toBe(false);

// Step 2: Sender scans QR code from receiver (which contains ack)
senderQRTP.parseCode(receiverCode);
expect(ackCount).toBe(1);

// Step 3: Receiver scans the second QR code
receiverQRTP.parseCode(senderCode);
expect(receivedChunks[1]).toBe(', wor');
expect(completeReceived).toBe(false);

// Step 4: Sender scans second ack
senderQRTP.parseCode(receiverCode);
expect(ackCount).toBe(2);

// Step 5: Receiver scans the final chunk
receiverQRTP.parseCode(senderCode);
expect(receivedChunks[2]).toBe('ld!');
expect(completeReceived).toBe(true);

// Step 6: Sender scans final ack
senderQRTP.parseCode(receiverCode);
expect(ackCount).toBe(3);
});

test('does not advance when acknowledgment does not match', () => {
let ackCount = 0;
senderQRTP.on('ack', () => ackCount++);

// Setup sender with a message
senderQRTP.setMessage('Test message', 6);

// Create an invalid QR code with wrong ack
const invalidAckCode = `QRTP3:invalid-hash$Test m`;

// Sender processes the invalid ack
senderQRTP.parseCode(invalidAckCode);

// Verify no ack event was emitted
expect(ackCount).toBe(0);
});

test('one-way transfer works when receiver is not sending data', () => {
let completeReceived = false;
let receivedChunks: string[] = [];
let ackCount = 0;

receiverQRTP.on('complete', () => {
completeReceived = true;
});

receiverQRTP.on('chunk', (event) => {
receivedChunks[event.index] = event.payload;
});

senderQRTP.on('ack', () => ackCount++);

// Send a message from sender to receiver
senderQRTP.setMessage('One-way message', 6);

// Complete the transfer
receiverQRTP.parseCode(senderCode);
senderQRTP.parseCode(receiverCode);
receiverQRTP.parseCode(senderCode);
senderQRTP.parseCode(receiverCode);
receiverQRTP.parseCode(senderCode);

// Verify complete message was received
expect(completeReceived).toBe(true);
expect(receivedChunks.join('')).toBe('One-way message');
expect(ackCount).toBe(2);
});

test('resets protocol state correctly', () => {
let initCount = 0;
let chunkCount = 0;

senderQRTP.on('init', () => initCount++);
receiverQRTP.on('chunk', () => chunkCount++);

// Setup with some data
senderQRTP.setMessage('Test data');
receiverQRTP.parseCode(senderCode);
expect(chunkCount).toBe(1);

// Reset both instances
senderQRTP.setMessage(null);
receiverQRTP.setMessage(null);

// Send new message after reset
senderQRTP.setMessage('New data');
expect(initCount).toBe(2); // Should get another init event

receiverQRTP.parseCode(senderCode);
expect(chunkCount).toBe(2); // Should get new chunks
});

test('bidirectional message exchange between two devices', () => {
const aliceMessage = 'Hello Bob!';
const bobMessage = 'Hi Alice!';
const chunkSize = 5;

// Setup devices
const alice = new QRTP();
const bob = new QRTP();
let aliceCode = '';
let bobCode = '';

alice.on('qrUpdate', ({ data }) => (aliceCode = data));
bob.on('qrUpdate', ({ data }) => (bobCode = data));

// Track received messages
let aliceReceivedComplete = false;
let bobReceivedComplete = false;
let aliceChunks: string[] = [];
let bobChunks: string[] = [];

alice.on('complete', () => {
aliceReceivedComplete = true;
});
bob.on('complete', () => {
bobReceivedComplete = true;
});

alice.on('chunk', (event) => {
aliceChunks[event.index] = event.payload;
});
bob.on('chunk', (event) => {
bobChunks[event.index] = event.payload;
});

// Start the exchange
alice.setMessage(aliceMessage, chunkSize);
bob.setMessage(bobMessage, chunkSize);

// Simulate exchange rounds
for (let round = 0; round < 10; round++) {
if (aliceReceivedComplete && bobReceivedComplete) break;

alice.parseCode(bobCode);
bob.parseCode(aliceCode);
}

// Verify results
expect(aliceReceivedComplete).toBe(true);
expect(bobReceivedComplete).toBe(true);
expect(aliceChunks.join('')).toBe(bobMessage);
expect(bobChunks.join('')).toBe(aliceMessage);
});
});
Loading