Skip to content

Commit 0498e32

Browse files
authored
feat: add congestion control to TCP implementation (#220)
Closes #219 This PR adds congestion control to the TCP implementation
1 parent 0b7626b commit 0498e32

File tree

5 files changed

+483
-158
lines changed

5 files changed

+483
-158
lines changed

src/packets/ethernet.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -116,7 +116,6 @@ export class EthernetFrame {
116116
type: number;
117117
// 46-1500 bytes
118118
// If the payload is smaller than 46 bytes, it is padded.
119-
// TODO: make this an interface
120119
// The payload
121120
payload: FramePayload;
122121
// 4 bytes

src/programs/http_client.ts

Lines changed: 69 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -9,19 +9,35 @@ import { ViewHost } from "../types/view-devices";
99
import { TOOLTIP_KEYS } from "../utils/constants/tooltips_constants";
1010
import { ProgramBase } from "./program_base";
1111

12+
const RESOURCE_MAP = new Map([
13+
["/small", generateResource(1024)],
14+
["/medium", generateResource(102400)],
15+
["/large", generateResource(10485760)],
16+
]);
17+
18+
function generateResource(size: number): Uint8Array {
19+
const resource = new Uint8Array(size);
20+
for (let i = 0; i < size; i++) {
21+
resource[i] = Math.floor(Math.random() * 256);
22+
}
23+
return resource;
24+
}
25+
1226
export class HttpClient extends ProgramBase {
1327
static readonly PROGRAM_NAME = TOOLTIP_KEYS.SEND_HTTP_REQUEST;
1428

1529
private dstId: DeviceId;
30+
private resource: string;
1631

1732
protected _parseInputs(inputs: string[]): void {
18-
if (inputs.length !== 1) {
33+
if (inputs.length !== 2) {
1934
console.error(
20-
"HttpClient requires 1 input. " + inputs.length + " were given.",
35+
"HttpClient requires 2 input. " + inputs.length + " were given.",
2136
);
2237
return;
2338
}
2439
this.dstId = parseInt(inputs[0]);
40+
this.resource = inputs[1];
2541
}
2642

2743
protected _run() {
@@ -51,19 +67,19 @@ export class HttpClient extends ProgramBase {
5167
return;
5268
}
5369

54-
// Encode dummy HTTP request
55-
const httpRequest = "GET / HTTP/1.1\r\nHost: " + dstDevice.ip + "\r\n\r\n";
56-
const content = new TextEncoder().encode(httpRequest);
70+
// Encode HTTP request
71+
const httpRequest = getContentRequest(
72+
this.runner.ip.toString(),
73+
this.resource,
74+
);
5775

5876
// Write request
5977
const socket = await this.runner.tcpConnect(this.dstId);
6078
if (!socket) {
61-
console.warn(
62-
"HttpClient failed to connect to socket. Program cancelled.",
63-
);
79+
console.warn("HttpClient failed to connect");
6480
return;
6581
}
66-
const wrote = await socket.write(content);
82+
const wrote = await socket.write(httpRequest);
6783
if (wrote < 0) {
6884
console.error("HttpClient failed to write to socket");
6985
return;
@@ -74,20 +90,39 @@ export class HttpClient extends ProgramBase {
7490

7591
// Read response
7692
const buffer = new Uint8Array(1024);
77-
const readLength = await socket.readAll(buffer);
78-
if (readLength < 0) {
79-
console.error("HttpClient failed to read from socket");
80-
return;
93+
const expectedLength = RESOURCE_MAP.get(this.resource)?.length || 0;
94+
let totalRead = 0;
95+
while (totalRead < expectedLength) {
96+
const readLength = await socket.read(buffer);
97+
if (readLength < 0) {
98+
console.error("HttpClient failed to read from socket");
99+
return;
100+
}
101+
totalRead += readLength;
81102
}
82103
}
83104

84105
static getProgramInfo(viewgraph: ViewGraph, srcId: DeviceId): ProgramInfo {
106+
const sizeOptions = [
107+
{ value: "/small", text: "1 KB" },
108+
{ value: "/medium", text: "100 KB" },
109+
{ value: "/large", text: "10 MB" },
110+
];
111+
85112
const programInfo = new ProgramInfo(this.PROGRAM_NAME);
86113
programInfo.withDestinationDropdown(viewgraph, srcId, Layer.App);
114+
programInfo.withDropdown("Size of requested resource", sizeOptions);
87115
return programInfo;
88116
}
89117
}
90118

119+
function getContentRequest(host: string, resource: string): Uint8Array {
120+
const httpRequest =
121+
"GET " + resource + " HTTP/1.1\r\nHost: " + host + "\r\n\r\n";
122+
const content = new TextEncoder().encode(httpRequest);
123+
return content;
124+
}
125+
91126
export class HttpServer extends ProgramBase {
92127
static readonly PROGRAM_NAME = TOOLTIP_KEYS.SERVE_HTTP_REQUESTS;
93128

@@ -156,20 +191,37 @@ export class HttpServer extends ProgramBase {
156191
const buffer = new Uint8Array(1024).fill(0);
157192
const readLength = await socket.readAll(buffer);
158193

159-
// eslint-disable-next-line @typescript-eslint/no-unused-vars
160194
const readContents = buffer.slice(0, readLength);
161195
if (readLength < 0) {
162196
console.error("HttpServer failed to read from socket");
163197
return;
164198
}
165199

166-
// TODO: validate request
200+
const requestContents = new TextDecoder().decode(readContents);
201+
const matches = requestContents.match(/GET (.+) HTTP\/1.1/);
202+
if (!matches || matches.length < 2) {
203+
console.error("HttpServer failed to parse request");
204+
return;
205+
}
206+
const resourceContents = RESOURCE_MAP.get(matches[1]);
207+
if (!resourceContents) {
208+
console.error("HttpServer failed to find requested resource");
209+
return;
210+
}
167211

168212
// Encode dummy HTTP response
169-
const httpResponse = "HTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n";
213+
const httpResponse =
214+
"HTTP/1.1 200 OK\r\nContent-Length: " +
215+
resourceContents.length +
216+
"\r\n\r\n";
170217
const content = new TextEncoder().encode(httpResponse);
171218
const wrote = await socket.write(content);
172-
if (wrote < 0) {
219+
if (wrote <= 0) {
220+
console.error("HttpServer failed to write to socket");
221+
return;
222+
}
223+
const wrote2 = await socket.write(resourceContents);
224+
if (wrote2 <= 0) {
173225
console.error("HttpServer failed to write to socket");
174226
return;
175227
}

0 commit comments

Comments
 (0)