Files
mdg-filius/packages/core/src/runtime/packets.ts
T
julian.kiedaischandClaude Opus 5.5 696334aa5a feat(core): event queue, Ethernet, switch, ARP, IPv4, ICMP, routing and firewall
First slice of the TypeScript port of the filius-ipad runtime engine,
with 29 tests ported from the Swift suite. Parts not ported yet throw
NotPortedError instead of guessing.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-29 19:22:57 +00:00

125 lines
3.4 KiB
TypeScript

// Ported from filius-ipad (GPLv2/GPLv3):
// State/TopologyNetworkRuntimeEngine.swift ("Java-observable packet and route models")
// Frames and packets are typed values, not bytes — what the Java packet viewer
// shows, not what a real wire would carry.
export interface EthernetFrame {
readonly identity: number;
readonly sourceMacAddress: string;
readonly destinationMacAddress: string;
readonly payload: EthernetPayload;
}
export type EthernetPayload =
| { readonly kind: 'arp'; readonly packet: ArpPacket }
| { readonly kind: 'ipv4'; readonly packet: IPv4Packet };
export type ArpOperation = 'request' | 'reply';
export interface ArpPacket {
readonly operation: ArpOperation;
readonly senderMacAddress: string;
readonly senderIPAddress: string;
readonly targetMacAddress: string;
readonly targetIPAddress: string;
}
export const IPV4_PROTOCOL = { icmp: 1, tcp: 6, udp: 17 } as const;
export type IPv4Protocol = keyof typeof IPV4_PROTOCOL;
export interface IPv4Packet {
readonly identity: number;
readonly senderIPAddress: string;
readonly receiverIPAddress: string;
/** 0…255 */
readonly timeToLive: number;
readonly protocol: IPv4Protocol;
readonly payload: IPv4Payload;
}
export type IPv4Payload =
| { readonly kind: 'icmp'; readonly message: IcmpMessage }
| { readonly kind: 'tcp'; readonly segment: TcpSegment }
| { readonly kind: 'udp'; readonly datagram: UdpDatagram };
/** The same packet one hop further: identity kept, TTL lowered, never below 0. */
export function forwardingClone(packet: IPv4Packet, decrementingTtlBy = 1): IPv4Packet {
return {
...packet,
timeToLive: decrementingTtlBy >= packet.timeToLive ? 0 : packet.timeToLive - decrementingTtlBy,
};
}
export type IcmpMessageKind =
| 'echoRequest'
| 'echoReply'
| 'destinationNetworkUnreachable'
| 'destinationHostUnreachable'
| 'timeExceeded';
export interface IcmpMessage {
readonly kind: IcmpMessageKind;
/** 0…65535 */
readonly identifier: number;
/** 0…65535 */
readonly sequenceNumber: number;
readonly data: Uint8Array;
readonly embeddedOriginalPacket?: IPv4Packet;
}
export function icmpMessage(
kind: IcmpMessageKind,
fields: Partial<Omit<IcmpMessage, 'kind'>> = {},
): IcmpMessage {
const message: IcmpMessage = {
kind,
identifier: fields.identifier ?? 0,
sequenceNumber: fields.sequenceNumber ?? 0,
data: fields.data ?? new Uint8Array(),
};
return fields.embeddedOriginalPacket === undefined
? message
: { ...message, embeddedOriginalPacket: fields.embeddedOriginalPacket };
}
export const TCP_FLAG = {
finish: 1 << 0,
synchronize: 1 << 1,
reset: 1 << 2,
push: 1 << 3,
acknowledgement: 1 << 4,
} as const;
export type TcpSocketState =
| 'CLOSED'
| 'LISTEN'
| 'SYN_RCVD'
| 'SYN_SENT'
| 'ESTABLISHED'
| 'CLOSE_WAIT'
| 'LAST_ACK'
| 'FIN_WAIT_1'
| 'FIN_WAIT_2'
| 'CLOSING'
| 'TIME_WAIT';
export interface TcpSegment {
readonly sourcePort: number;
readonly destinationPort: number;
readonly sequenceNumber: number;
readonly acknowledgementNumber: number;
/** Bit set of TCP_FLAG values. */
readonly flags: number;
readonly payload: Uint8Array;
}
export interface UdpDatagram {
readonly sourcePort: number;
readonly destinationPort: number;
readonly payload: Uint8Array;
}
export function packetIdentityInFrame(frame: EthernetFrame): number | undefined {
return frame.payload.kind === 'ipv4' ? frame.payload.packet.identity : undefined;
}