Files
mdg-filius/packages/server/test/modemRelay.test.ts
T
julian.kiedaischandClaude Opus 5.5 7b943d3d96 feat(server): the relay joins modems of different projects by a code (K3)
A modem that offers a connection to another project is found by six
characters derived from its project and itself (modemCode): a copy of the
project has another code, so no two modems offer the same one. The room
names its modems of other projects to F's relay at a start and none at a
stop; the relay pairs each offer with the first modem that joins its code,
one to one as Java's server modem takes one client, and passes the frames
between the rooms' threads. Every client hears how each modem stands
(`modems`: connected, waiting, taken, unpaired, ambiguous, detached, or
unavailable without F). The document keeps the connection and the code.

Measured on F with two threads: a ping from one project's notebook to the
other's goes through; a third project that wants the same code reads
taken, and takes it when the second stops; a deleted project's modem
leaves with its room.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 18:24:19 +00:00

118 lines
5.7 KiB
TypeScript

// The relay between modems of different projects (modemRelay.ts): pairs by
// the offering modem's code, one to one, the first that joins keeps it;
// frames go to the partner; each room hears how its modems stand when that
// changes.
import { describe, expect, it } from 'vitest';
import type { EthernetFrame } from '@mdg-filius/core';
import { modemCode } from '@mdg-filius/core';
import type { RelayState } from '@mdg-filius/sim';
import { ModemRelay, type ModemAddress } from '../src/modemRelay.js';
const frame: EthernetFrame = {
identity: 1,
sourceMacAddress: '02:00:00:00:00:01',
destinationMacAddress: 'FF:FF:FF:FF:FF:FF',
payload: { kind: 'arp', packet: { operation: 'request', senderMacAddress: '02:00:00:00:00:01', senderIPAddress: '10.0.0.1', targetMacAddress: 'FF:FF:FF:FF:FF:FF', targetIPAddress: '10.0.0.2' } },
};
function relay() {
const told: { project: string; states: Record<string, RelayState> }[] = [];
const delivered: { to: ModemAddress; frame: EthernetFrame }[] = [];
const log: string[] = [];
const modems = new ModemRelay({
states: (project, states) => told.push({ project, states: { ...states } }),
frame: (to, each) => delivered.push({ to, frame: each }),
paired: (offer, join, paired) => log.push(`${paired ? 'paired' : 'parted'} ${offer.project}/${offer.node} ${join.project}/${join.node}`),
});
return { modems, told, delivered, log };
}
const code = modemCode('teacher', 'm1');
describe('the relay between modems of different projects', () => {
it('pairs a modem that joins a code with the modem that offers it, and passes frames both ways', () => {
const { modems, told, delivered, log } = relay();
modems.setModems('teacher', [{ node: 'm1', relay: { role: 'offer' } }]);
expect(told).toEqual([{ project: 'teacher', states: { m1: 'waiting' } }]);
modems.setModems('group', [{ node: 'g1', relay: { role: 'join', code } }]);
expect(told.slice(1)).toEqual([
{ project: 'teacher', states: { m1: 'connected' } },
{ project: 'group', states: { g1: 'connected' } },
]);
expect(log).toEqual(['paired teacher/m1 group/g1']);
modems.frame({ project: 'group', node: 'g1' }, frame);
modems.frame({ project: 'teacher', node: 'm1' }, frame);
expect(delivered.map(({ to }) => to)).toEqual([
{ project: 'teacher', node: 'm1' },
{ project: 'group', node: 'g1' },
]);
expect(modems.stats()).toEqual({ modems: 2, pairs: 1 });
});
it('waits for a code no running modem offers, and loses what the waiting modem passes', () => {
const { modems, told, delivered } = relay();
modems.setModems('group', [{ node: 'g1', relay: { role: 'join', code: 'ZZZZZZ' } }]);
modems.frame({ project: 'group', node: 'g1' }, frame);
expect(told).toEqual([{ project: 'group', states: { g1: 'waiting' } }]);
expect(delivered).toEqual([]);
});
it('takes one modem per code, as Java takes one client: the first keeps it, the next is told it is taken', () => {
const { modems, told, log } = relay();
modems.setModems('teacher', [{ node: 'm1', relay: { role: 'offer' } }]);
modems.setModems('first', [{ node: 'a', relay: { role: 'join', code } }]);
modems.setModems('second', [{ node: 'b', relay: { role: 'join', code } }]);
expect(told.at(-1)).toEqual({ project: 'second', states: { b: 'taken' } });
// The first group starts anew: its pair goes, the waiting one takes the code; the first now waits behind it.
modems.setModems('first', []);
modems.setModems('first', [{ node: 'a', relay: { role: 'join', code } }]);
expect(log).toEqual(['paired teacher/m1 first/a', 'parted teacher/m1 first/a', 'paired teacher/m1 second/b']);
expect(told.slice(-1)).toEqual([{ project: 'first', states: { a: 'taken' } }]);
});
it('keeps a pair standing when another modem joins the same code', () => {
const { modems, log } = relay();
modems.setModems('first', [{ node: 'a', relay: { role: 'join', code } }]);
modems.setModems('teacher', [{ node: 'm1', relay: { role: 'offer' } }]);
modems.setModems('second', [{ node: 'b', relay: { role: 'join', code } }]);
modems.setModems('teacher', [{ node: 'm1', relay: { role: 'offer' } }]);
expect(log).toEqual(['paired teacher/m1 first/a']);
});
it('parts a pair when the offering simulation stops; the joining modem waits again', () => {
const { modems, told, delivered, log } = relay();
modems.setModems('teacher', [{ node: 'm1', relay: { role: 'offer' } }]);
modems.setModems('group', [{ node: 'g1', relay: { role: 'join', code } }]);
modems.setModems('teacher', []);
expect(told.at(-1)).toEqual({ project: 'group', states: { g1: 'waiting' } });
expect(log.at(-1)).toBe('parted teacher/m1 group/g1');
modems.frame({ project: 'group', node: 'g1' }, frame);
expect(delivered).toEqual([]);
expect(modems.stats()).toEqual({ modems: 1, pairs: 0 });
});
it('tells a room only what changed', () => {
const { modems, told } = relay();
modems.setModems('teacher', [
{ node: 'm1', relay: { role: 'offer' } },
{ node: 'm2', relay: { role: 'offer' } },
]);
modems.setModems('group', [{ node: 'g1', relay: { role: 'join', code } }]);
expect(told).toEqual([
{ project: 'teacher', states: { m1: 'waiting', m2: 'waiting' } },
{ project: 'teacher', states: { m1: 'connected' } },
{ project: 'group', states: { g1: 'connected' } },
]);
});
it('pairs two modems of one project, one offering, one joining its code', () => {
const { modems, told } = relay();
modems.setModems('one', [
{ node: 'm1', relay: { role: 'offer' } },
{ node: 'm2', relay: { role: 'join', code: modemCode('one', 'm1') } },
]);
expect(told).toEqual([{ project: 'one', states: { m1: 'connected', m2: 'connected' } }]);
});
});