Skip to content

Commit 04c9b60

Browse files
implement connect outbound, fix connection close handling, and add finger command
1 parent be58596 commit 04c9b60

7 files changed

Lines changed: 139 additions & 26 deletions

File tree

package-lock.json

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "ollieos",
3-
"version": "2.3.0",
3+
"version": "2.3.1",
44
"description": "",
55
"main": "server.js",
66
"scripts": {

src/kernel/network.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -105,15 +105,15 @@ export abstract class AbstractClientSocket {
105105
await this._handle_send_internal(uint8_data);
106106
}
107107

108-
protected abstract _handle_close_internal(): Promise<void>;
108+
protected abstract _handle_close_internal(passive: boolean): Promise<void>;
109109

110-
async close(): Promise<void> {
110+
async close(passive = false): Promise<void> {
111111
if (this.ready_state === SocketReadyState.CLOSING || this.ready_state === SocketReadyState.CLOSED) {
112112
return;
113113
}
114114

115115
this.ready_state = SocketReadyState.CLOSING;
116-
await this._handle_close_internal();
116+
await this._handle_close_internal(passive);
117117

118118
// invoke close listeners
119119
for (const listener of this.#close_listeners) {

src/network_impl/porter.ts

Lines changed: 74 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,14 @@ interface CloseConnectionMessage extends BaseMessage {
2525
sock_id: string;
2626
}
2727

28-
type OutboundMessage = BindMessage | UnbindMessage | DataMessage | CloseConnectionMessage;
28+
interface ConnectMessage extends BaseMessage {
29+
type: "connect_req";
30+
host: string;
31+
port: number;
32+
force_sock_id?: string;
33+
}
34+
35+
type OutboundMessage = BindMessage | UnbindMessage | DataMessage | CloseConnectionMessage | ConnectMessage;
2936

3037
interface IncomingConnectionMessage extends BaseMessage {
3138
type: "incoming_connection";
@@ -35,32 +42,36 @@ interface IncomingConnectionMessage extends BaseMessage {
3542

3643
interface ConnectionClosingMessage extends BaseMessage {
3744
type: "connection_closing";
38-
port: number;
45+
port?: number;
3946
sock_id: string;
4047
}
4148

42-
interface AckBindMessage extends BaseMessage {
43-
type: "ack_bind";
44-
port: number;
49+
interface AckMessage extends BaseMessage {
4550
success: boolean;
4651
error?: string;
4752
}
4853

49-
interface AckUnbindMessage extends BaseMessage {
54+
interface AckBindMessage extends AckMessage {
55+
type: "ack_bind";
56+
port: number;
57+
}
58+
59+
interface AckUnbindMessage extends AckMessage {
5060
type: "ack_unbind";
5161
port: number;
52-
success: boolean;
53-
error?: string;
5462
}
5563

56-
interface AckCloseConnectionMessage extends BaseMessage {
64+
interface AckCloseConnectionMessage extends AckMessage {
5765
type: "ack_close_connection";
5866
sock_id: string;
59-
success: boolean;
60-
error?: string;
6167
}
6268

63-
type InboundMessage = IncomingConnectionMessage | ConnectionClosingMessage | AckBindMessage | AckUnbindMessage | DataMessage | AckCloseConnectionMessage;
69+
interface AckConnectMessage extends AckMessage {
70+
type: "ack_connect";
71+
sock_id: string;
72+
}
73+
74+
type InboundMessage = IncomingConnectionMessage | ConnectionClosingMessage | AckBindMessage | AckUnbindMessage | DataMessage | AckCloseConnectionMessage | AckConnectMessage;
6475

6576
const uint8_to_base64 = (data: Uint8Array): string => {
6677
let binary = "";
@@ -79,7 +90,12 @@ export class PorterBridgeClientSocket extends AbstractClientSocket {
7990
this.ready_state = SocketReadyState.OPEN;
8091
}
8192

82-
protected async _handle_close_internal(): Promise<void> {
93+
protected async _handle_close_internal(passive: boolean): Promise<void> {
94+
if (passive) {
95+
// nothing to do
96+
return;
97+
}
98+
8399
const msg: CloseConnectionMessage = {
84100
type: "close_connection",
85101
sock_id: this.id,
@@ -187,7 +203,7 @@ export class PorterBridgeNetworkManager extends AbstractNetworkManager {
187203

188204
constructor() {
189205
super();
190-
this.#connect();
206+
this.#connect_to_ws();
191207
}
192208

193209
#ws: WebSocket;
@@ -256,7 +272,7 @@ export class PorterBridgeNetworkManager extends AbstractNetworkManager {
256272
});
257273
}
258274

259-
#connect() {
275+
#connect_to_ws() {
260276
// open websocket connection on port 9000, handling all routing (rather than making a separate listener/connection for each socket)
261277
this.#ws = new WebSocket("ws://127.0.0.1:9000");
262278

@@ -285,7 +301,8 @@ export class PorterBridgeNetworkManager extends AbstractNetworkManager {
285301
case "connection_closing": {
286302
const client = this.#client_map.get(data.sock_id);
287303
if (client) {
288-
client.close().catch(err => {
304+
// emit passive close event
305+
client.close(true).catch(err => {
289306
console.error(`Error closing client socket ${data.sock_id}:`, err);
290307
});
291308
} else {
@@ -325,7 +342,7 @@ export class PorterBridgeNetworkManager extends AbstractNetworkManager {
325342

326343
// attempt to reconnect with exponential backoff
327344
setTimeout(() => {
328-
this.#connect();
345+
this.#connect_to_ws();
329346
}, Math.min(50 * 2 ** this.#reconnect_attempts, 3000));
330347
this.#reconnect_attempts++;
331348
});
@@ -384,6 +401,45 @@ export class PorterBridgeNetworkManager extends AbstractNetworkManager {
384401
}
385402

386403
async connect(host: string, port: number): Promise<AbstractClientSocket> {
387-
throw new Error("Outbound connections are not supported in porter networking yet");
404+
// generate a sock id that we will provide to immediately reserve in the map without waiting for a reply
405+
const sock_id = crypto.randomUUID();
406+
407+
const msg: ConnectMessage = {
408+
type: "connect_req",
409+
host,
410+
port,
411+
force_sock_id: sock_id,
412+
};
413+
414+
await this.wait_for_ws_ready();
415+
this.#ws.send(JSON.stringify(msg));
416+
417+
return new Promise<PorterBridgeClientSocket>((resolve, reject) => {
418+
const handler = (event: MessageEvent) => {
419+
const data = JSON.parse(event.data) as InboundMessage;
420+
421+
// TODO: move this routing logic to the manager instead of having each connect call add its own listener for efficiency
422+
if (data.type === "ack_connect" && data.sock_id === sock_id) {
423+
this.#ws.removeEventListener("message", handler);
424+
425+
if (data.success) {
426+
// create the client and add to map
427+
const client = new PorterBridgeClientSocket(sock_id, this);
428+
this.#client_map.set(sock_id, client);
429+
430+
// cleanup on close
431+
client.add_event_listener("close", () => {
432+
this.#client_map.delete(sock_id);
433+
});
434+
435+
resolve(client);
436+
} else {
437+
reject(new Error(data.error || `Failed to connect to ${host}:${port}`));
438+
}
439+
}
440+
};
441+
442+
this.#ws.addEventListener("message", handler);
443+
});
388444
}
389445
}

src/programs/@ALL.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,8 +38,9 @@ export { default as unalias } from "./unalias";
3838
export { default as ps } from "./ps";
3939
export { default as kill } from "./kill";
4040
export { default as spark } from "./spark";
41-
4241
export { default as hello_http } from "./hello_http";
42+
export { default as finger } from "./finger";
43+
4344
export { default as telnetd } from "./services/telnetd";
4445

4546
// shhhhh!

src/programs/finger.ts

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
import type {Program} from "../types";
2+
3+
export default {
4+
name: "finger",
5+
description: "Displays information about a user on a remote system.",
6+
usage_suffix: "<username>@<hostname>",
7+
arg_descriptions: {
8+
"<username>@<hostname>": "The username and hostname of the user to query. For example, ollieg@happynetbox.com"
9+
},
10+
compat: "2.0.0",
11+
completion: async () => [],
12+
main: async (data) => {
13+
// extract from data to make code less verbose
14+
const {term, process, kernel, args} = data;
15+
16+
if (args.length !== 1) {
17+
term.writeln(`${term.ansi.PREFABS.error}Invalid number of arguments. Usage: finger <username>@<hostname>${term.ansi.STYLE.reset_all}`);
18+
return 1;
19+
}
20+
21+
const [user, host] = args[0].split("@");
22+
if (!user || !host) {
23+
term.writeln(`${term.ansi.PREFABS.error}Invalid argument format. Usage: finger <username>@<hostname>${term.ansi.STYLE.reset_all}`);
24+
return 1;
25+
}
26+
27+
if (!kernel.has_network_manager()) {
28+
term.writeln(`${term.ansi.PREFABS.error}No network manager found. This program requires a network manager to function.${term.ansi.STYLE.reset_all}`);
29+
return 1;
30+
}
31+
32+
const net_manager = kernel.get_network_manager();
33+
if (!await net_manager.is_up(true)) {
34+
term.writeln(`${term.ansi.PREFABS.error}Network is down!${term.ansi.STYLE.reset_all}`);
35+
return 1;
36+
}
37+
38+
const socket = await process.network_connect(host, 79);
39+
if (!socket) {
40+
term.writeln(`${term.ansi.PREFABS.error}Failed to connect to ${host}:79${term.ansi.STYLE.reset_all}`);
41+
return 1;
42+
}
43+
44+
socket.add_event_listener("data", term.write.bind(term));
45+
socket.send(`${user}\r\n`);
46+
47+
// wait until connection is closed
48+
await new Promise<void>((resolve) => {
49+
socket.add_event_listener("close", resolve);
50+
});
51+
52+
return 0;
53+
}
54+
} as Program;

src/programs/hex.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,9 @@ export default {
4949
}
5050

5151
if (args[0] === "-h") {
52-
await kernel.spawn("help", ["hex"], shell).completion;
52+
const spawn_result = kernel.spawn("help", ["hex"], shell);
53+
spawn_result.process.kill(await spawn_result.completion);
54+
return 0;
5355
}
5456

5557
// get filepath

0 commit comments

Comments
 (0)