// 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> = {}, ): 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; }