Skip to content

Instantly share code, notes, and snippets.

@cameronbourke
Created May 1, 2019 13:42
Show Gist options
  • Save cameronbourke/2c6d2bae778a6b1b3b14dc99edb29694 to your computer and use it in GitHub Desktop.
Save cameronbourke/2c6d2bae778a6b1b3b14dc99edb29694 to your computer and use it in GitHub Desktop.
Simulating a simple network with two nodes A and B, with a buffer on the outbound link from A to B.
// Constants
// -----------------------------------------------
const DATA_PACKET_SIZE = 500 + 20 + 20;
const ACK_PACKET_SIZE = 0;
const BITS_TO_BYTE = 8;
const TOCK_MS = 1;
const LINK_RATE_BPS = 2 * Math.pow(10, 6);
const LINK_DELAY_MS = 50;
const BUFFER_SIZE = 20;
let maxAvgRTT = Number.NEGATIVE_INFINITY;
let minAvgRTT = Number.POSITIVE_INFINITY;
// Utils
// -----------------------------------------------
class Queue {
constructor (maxsize = 100) {
this.maxsize = maxsize;
this.list = [];
}
enqueue (el) {
if (this.list.length >= this.maxsize)
throw new Error("QUEUE_OVERFLOW");
this.list.push(el);
}
dequeue () {
if (this.list.length <= 0)
return null;
return this.list.shift();
}
peak () {
if (this.isEmpty()) return null;
return this.list[0];
}
isEmpty () {
return this.size() <= 0;
}
size () {
return this.list.length;
}
toString () {
const els = this.list.reduce((acc, packet) => {
if (acc === "") return `${packet.id}`;
return `${acc}, ${packet.id}`;
}, '');
return `{ ${els} }`;
}
}
// Classes
// -----------------------------------------------
class Packet {
constructor (size) {
this.id = Packet.id++;
this.enqueuedAt = -1;
this.transmittedAt = -1;
this.receivedAt = -1;
this.bytes = size;
this.size = size;
}
}
Packet.id = 0;
class Link {
constructor () {
this.transmitting = new Queue();
this.transmitted = [];
this.speed = LINK_RATE_BPS;
this.delay = LINK_DELAY_MS;
}
propagate (prevTime, nextTime) {
while (!this.transmitting.isEmpty()) {
const packet = this.transmitting.peak();
if (packet.transmittedAt == nextTime) break;
if ((packet.transmittedAt + this.delay) > nextTime) break;
packet.receivedAt = nextTime;
this.transmitted.push(this.transmitting.dequeue());
}
}
receive (prevTime, nextTime) {
const received = [...this.transmitted];
this.transmitted = [];
return received;
}
send (packet, time) {
packet.transmittedAt = time;
this.transmitting.enqueue(packet);
}
}
class InputBufferedLink extends Link {
constructor () {
super();
this.buffer = new Queue(BUFFER_SIZE);
}
send (packet, time) {
packet.enqueuedAt = time;
this.buffer.enqueue(packet);
}
transmit (prevTime, nextTime) {
const timeElapsed = nextTime - prevTime;
const bytesPerMs = (this.speed / 1000) / BITS_TO_BYTE;
let bytes = Math.round(timeElapsed * bytesPerMs);
while (bytes > 0 && !this.buffer.isEmpty()) {
const packet = this.buffer.peak();
if (bytes <= packet.bytes) {
packet.bytes = packet.bytes - bytes;
bytes = 0;
}
else {
bytes = bytes - packet.bytes;
packet.transmittedAt = nextTime;
packet.bytes = packet.size;
this.transmitting.enqueue(this.buffer.dequeue());
}
}
}
}
class Node {
constructor ({ packetSize, outwardLink, inwardLink }) {
this.packetSize = packetSize;
this.outwardLink = outwardLink;
this.inwardLink = inwardLink;
this.receivedN = 0;
this.received = [];
}
receive (prevTime, nextTime) {
this.received = this.inwardLink.receive();
this.receivedN += this.received.length;
}
clearReceived () {
this.received = [];
}
}
class SendingNode extends Node {
constructor (opts) {
super(opts);
this.unackedN = 0;
this.cwnd = 1;
}
send (prevTime, nextTime) {
this.unackedN -= this.received.length;
const sumRTT = this.received.reduce((acc, packet) => {
return acc + (packet.receivedAt - packet.enqueuedAt);
}, 0);
if (this.received.length) {
const avgRTT = sumRTT / this.received.length;
if (avgRTT < minAvgRTT) minAvgRTT = avgRTT;
if (avgRTT > maxAvgRTT) maxAvgRTT = avgRTT;
}
this.clearReceived();
if (this.receivedN >= this.cwnd) {
this.receivedN = 0;
this.cwnd++;
}
while (this.unackedN < this.cwnd) {
this.outwardLink.send(new Packet(this.packetSize), nextTime);
this.unackedN++;
}
}
}
class ReceivingNode extends Node {
send (prevTime, nextTime) {
this.received.forEach(() => {
const ack = new Packet(ACK_PACKET_SIZE);
this.outwardLink.send(ack, nextTime);
});
this.clearReceived();
}
}
function run () {
const linkAB = new InputBufferedLink();
const linkBA = new Link();
const nodeA = new SendingNode({
packetSize: DATA_PACKET_SIZE,
outwardLink: linkAB,
inwardLink: linkBA,
});
const nodeB = new ReceivingNode({
packetSize: ACK_PACKET_SIZE,
outwardLink: linkBA,
inwardLink: linkAB,
});
function tick (prevTime) {
const nextTime = prevTime + 1;
try {
nodeA.send(prevTime, nextTime);
}
catch (e) {
if (e.message === "QUEUE_OVERFLOW") {
console.log('MAX CWND after %ds is: %d MSS', Math.round(nextTime / 1000), nodeA.cwnd - 1);
console.log('MIN AVG RTT is: %dms', minAvgRTT);
console.log('MAX AVG RTT is: %dms', maxAvgRTT);
}
else {
console.log('e', e);
console.log('e.message', e.message);
}
return;
}
linkAB.transmit(prevTime, nextTime);
linkAB.propagate(prevTime, nextTime);
nodeB.receive(prevTime, nextTime);
nodeB.send(prevTime, nextTime);
linkBA.propagate(prevTime, nextTime);
nodeA.receive(prevTime, nextTime);
// uncomment below to see log of buffer every tick
// console.log(linkAB.buffer.toString());
setTimeout(() => tick(nextTime), TOCK_MS);
}
console.log('running sim...');
tick(0);
}
run();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment