Skip to content

Commit b161b7d

Browse files
authored
feat: use bytes for packet queue (#170)
This PR extracts the packet queue logic into a new class, to avoid breaking its invariants
1 parent a3bdc25 commit b161b7d

File tree

4 files changed

+108
-35
lines changed

4 files changed

+108
-35
lines changed

src/packets/icmp.ts

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,7 @@ export const ICMP_REPLY_TYPE_NUMBER = 0;
1313
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
1414
// | Type | Code | Checksum |
1515
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
16-
// | unused |
17-
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
18-
// | Internet Header + 64 bits of Original Data Datagram |
16+
// | variant data |
1917
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
2018
abstract class IcmpPacket implements IpPayload {
2119
// 8 bits
@@ -33,6 +31,11 @@ abstract class IcmpPacket implements IpPayload {
3331
return ICMP_PROTOCOL_NUMBER;
3432
}
3533

34+
byteLength(): number {
35+
const headerLength = 8;
36+
return headerLength + this._dataToBytes().length;
37+
}
38+
3639
toBytes(): Uint8Array {
3740
const data = this._dataToBytes();
3841
const buffer = Uint8Array.from([
@@ -60,6 +63,15 @@ abstract class IcmpPacket implements IpPayload {
6063
abstract getDetails(layer: Layer): Record<string, string | number | object>;
6164
}
6265

66+
// 0 1 2 3
67+
// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
68+
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
69+
// | Type | Code | Checksum |
70+
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
71+
// | Identifier | Sequence Number |
72+
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
73+
// | Data ...
74+
// +-+-+-+-+-
6375
class EchoMessage extends IcmpPacket {
6476
code = 0;
6577

src/packets/ip.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,9 @@ export const TCP_PROTOCOL_NUMBER = 6;
77
export const UDP_PROTOCOL_NUMBER = 17;
88

99
export class EmptyPayload implements IpPayload {
10+
byteLength() {
11+
return 0;
12+
}
1013
toBytes() {
1114
return new Uint8Array(0);
1215
}
@@ -118,6 +121,8 @@ export class IpAddressGenerator {
118121
}
119122

120123
export interface IpPayload {
124+
// Length of the payload in bytes
125+
byteLength(): number;
121126
// The bytes equivalent of the payload
122127
toBytes(): Uint8Array;
123128
// The number of the protocol
@@ -162,7 +167,8 @@ export class IPv4Packet implements FramePayload {
162167
// Length of the datagram, in bytes
163168
// 16 bits
164169
get totalLength() {
165-
return this.payload.toBytes().length + this.internetHeaderLength * 4;
170+
const headerSize = this.internetHeaderLength * 4;
171+
return headerSize + this.payload.byteLength();
166172
}
167173

168174
// Identifying value assigned by the sender

src/packets/tcp.ts

Lines changed: 17 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -162,10 +162,6 @@ export class TcpSegment implements IpPayload {
162162
this.data = data;
163163
}
164164

165-
getPacketType(): string {
166-
return "TCP";
167-
}
168-
169165
computeChecksum(): number {
170166
const segmentBytes = this.toBytes({ withChecksum: false });
171167

@@ -180,12 +176,15 @@ export class TcpSegment implements IpPayload {
180176
return computeIpChecksum(totalBytes);
181177
}
182178

183-
// Dummy Method for the moment
184-
getDetails(layer: Layer) {
185-
return { Layer: layer };
179+
// ### IpPayload ###
180+
181+
byteLength(): number {
182+
const headerSize = 5 /* number of rows */ * 4; /* bytes per row */
183+
// TODO: include options
184+
// const optionsSize = 0;
185+
return headerSize + this.data.length;
186186
}
187187

188-
// ### IpPayload ###
189188
toBytes({
190189
withChecksum = true,
191190
}: { withChecksum?: boolean } = {}): Uint8Array {
@@ -207,6 +206,16 @@ export class TcpSegment implements IpPayload {
207206
protocol(): number {
208207
return TCP_PROTOCOL_NUMBER;
209208
}
209+
210+
getPacketType(): string {
211+
return "TCP";
212+
}
213+
214+
// Dummy Method for the moment
215+
getDetails(layer: Layer) {
216+
return { Layer: layer };
217+
}
218+
210219
// ### IpPayload ###
211220
}
212221

src/types/devices/router.ts

Lines changed: 69 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -14,14 +14,11 @@ import { sendRawPacket } from "../packet";
1414
export class Router extends NetworkDevice {
1515
static DEVICE_TEXTURE: Texture;
1616

17-
private processingPackets = false;
17+
private packetQueue = new PacketQueue(1024);
18+
// Time in ms to process a single byte
19+
private timePerByte = 8;
20+
// Number of bytes processed
1821
private processingProgress = 0;
19-
// Time in ms to process a single packet
20-
private timePerPacket = 250;
21-
22-
private packetQueue: IPv4Packet[] = [];
23-
// TODO: we should count this in bytes
24-
private maxQueueSize = 5;
2522

2623
static getTexture() {
2724
if (!Router.DEVICE_TEXTURE) {
@@ -69,25 +66,21 @@ export class Router extends NetworkDevice {
6966
}
7067

7168
addPacketToQueue(datagram: IPv4Packet) {
72-
if (this.packetQueue.length >= this.maxQueueSize) {
69+
const wasEmpty = this.packetQueue.isEmpty();
70+
if (!this.packetQueue.enqueue(datagram)) {
7371
console.debug("Packet queue full, dropping packet");
7472
return;
7573
}
76-
this.packetQueue.push(datagram);
77-
// Start packet processor if not already running
78-
if (!this.processingPackets) {
79-
Ticker.shared.add(this.processPacket, this);
80-
this.processingPackets = true;
74+
if (wasEmpty) {
75+
this.startPacketProcessor();
8176
}
8277
}
8378

8479
processPacket(ticker: Ticker) {
85-
this.processingProgress += ticker.deltaMS;
86-
if (this.processingProgress < this.timePerPacket) {
80+
const datagram = this.getPacketsToProcess(ticker.deltaMS);
81+
if (!datagram) {
8782
return;
8883
}
89-
this.processingProgress -= this.timePerPacket;
90-
const datagram = this.packetQueue.pop();
9184
const devices = this.routePacket(datagram);
9285

9386
if (!devices || devices.length === 0) {
@@ -104,13 +97,30 @@ export class Router extends NetworkDevice {
10497
sendRawPacket(this.viewgraph, this.id, newFrame);
10598
}
10699

107-
// Stop processing packets if queue is empty
108-
if (this.packetQueue.length === 0) {
109-
Ticker.shared.remove(this.processPacket, this);
110-
this.processingPackets = false;
111-
this.processingProgress = 0;
112-
return;
100+
if (this.packetQueue.isEmpty()) {
101+
this.stopPacketProcessor();
102+
}
103+
}
104+
105+
startPacketProcessor() {
106+
this.processingProgress = 0;
107+
Ticker.shared.add(this.processPacket, this);
108+
}
109+
110+
stopPacketProcessor() {
111+
this.processingProgress = 0;
112+
Ticker.shared.remove(this.processPacket, this);
113+
}
114+
115+
getPacketsToProcess(timeMs: number): IPv4Packet | null {
116+
this.processingProgress += timeMs;
117+
const packetLength = this.packetQueue.getHead()?.totalLength;
118+
const progressNeeded = this.timePerByte * packetLength;
119+
if (this.processingProgress < progressNeeded) {
120+
return null;
113121
}
122+
this.processingProgress -= progressNeeded;
123+
return this.packetQueue.dequeue();
114124
}
115125

116126
routePacket(datagram: IPv4Packet): DeviceId[] {
@@ -147,3 +157,39 @@ export class Router extends NetworkDevice {
147157
return devices;
148158
}
149159
}
160+
161+
class PacketQueue {
162+
private queue: IPv4Packet[] = [];
163+
private queueSizeBytes = 0;
164+
private maxQueueSizeBytes: number;
165+
166+
constructor(maxQueueSizeBytes: number) {
167+
this.maxQueueSizeBytes = maxQueueSizeBytes;
168+
}
169+
170+
enqueue(packet: IPv4Packet) {
171+
if (this.queueSizeBytes >= this.maxQueueSizeBytes) {
172+
return false;
173+
}
174+
this.queue.push(packet);
175+
this.queueSizeBytes += packet.totalLength;
176+
return true;
177+
}
178+
179+
dequeue(): IPv4Packet | undefined {
180+
if (this.queue.length === 0) {
181+
return;
182+
}
183+
const packet = this.queue.shift();
184+
this.queueSizeBytes -= packet.totalLength;
185+
return packet;
186+
}
187+
188+
getHead(): IPv4Packet | undefined {
189+
return this.queue[0];
190+
}
191+
192+
isEmpty(): boolean {
193+
return this.queue.length === 0;
194+
}
195+
}

0 commit comments

Comments
 (0)