From 6261d7c90c3ffe5d02aed440df68510d2ad18905 Mon Sep 17 00:00:00 2001 From: ProgrammerIn-wonderland <3838shah@gmail.com> Date: Tue, 14 Jan 2025 21:20:00 -0500 Subject: [PATCH 1/2] WIP TLS support --- src/puter-js/src/index.js | 6 +- .../src/modules/networking/PSocket.js | 20 +++--- src/puter-js/src/modules/networking/PTLS.js | 72 +++++++++++++++++++ .../src/modules/networking/PWispHandler.js | 3 +- 4 files changed, 88 insertions(+), 13 deletions(-) create mode 100644 src/puter-js/src/modules/networking/PTLS.js diff --git a/src/puter-js/src/index.js b/src/puter-js/src/index.js index 937b177a2..09d4a3f38 100644 --- a/src/puter-js/src/index.js +++ b/src/puter-js/src/index.js @@ -20,6 +20,7 @@ import { XDIncomingService } from './services/XDIncoming.js'; import { NoPuterYetService } from './services/NoPuterYet.js'; import { Debug } from './modules/Debug.js'; import { PSocket, wispInfo } from './modules/networking/PSocket.js'; +import { PTLSSocket } from "./modules/networking/PTLS.js" import { PWispHandler } from './modules/networking/PWispHandler.js'; // TODO: This is for a safe-guard below; we should check if we can @@ -330,7 +331,10 @@ window.puter = (function() { })).json())["token"]; wispInfo.handler = new PWispHandler(wispInfo.server, wispToken); this.net = { - Socket: PSocket + Socket: PSocket, + tls: { + TLSSocket: PTLSSocket + } } })(); diff --git a/src/puter-js/src/modules/networking/PSocket.js b/src/puter-js/src/modules/networking/PSocket.js index 1c0d66c87..1b59eb083 100644 --- a/src/puter-js/src/modules/networking/PSocket.js +++ b/src/puter-js/src/modules/networking/PSocket.js @@ -1,6 +1,5 @@ import EventListener from "../../lib/EventListener.js"; -import {PWispHandler} from "./PWispHandler.js" - +const texten = new TextEncoder(); export let wispInfo = { server: "wss://puter.cafe/", @@ -11,34 +10,35 @@ export class PSocket extends EventListener { _events = new Map(); _streamID; constructor(host, port) { - super(["data", "drain", "open", "close"]); + super(["data", "drain", "open", "close", "tlsdata", "tlsopen"]); const callbacks = { dataCallBack: (data) => { this.emit("data", data); }, closeCallBack: (reason) => { this.emit("close", false); // TODO, report errors - }, - openCallBack: () => { - this.emit("open"); } } this._streamID = wispInfo.handler.register(host, port, callbacks); + setTimeout(() => {this.emit("open", undefined)}, 0); } addListener(...args) { this.on(...args); } write(data, callback) { - if (data.buffer) { // typedArray + if (data.buffer) { // TypedArray wispInfo.handler.write(this._streamID, data); if (callback) callback(); - } else if (data.resize) { + } else if (data.resize) { // ArrayBuffer data.write(this._streamID, new Uint8Array(data)); if (callback) callback(); - } else if (data.arrayBuffer) { // Oh No, a blob, I need to handle this later, maybe with https://gist.github.com/jimmywarting/65c358f878cac8e7f39cfb7d43931f62? - + } else if (typeof(data) === "string") { + wispInfo.handler.write(this._streamID, texten.encode(data)) + if (callback) callback(); + } else { + throw new Error("Invalid data type (not TypedArray, ArrayBuffer or String!!)"); } } close() { diff --git a/src/puter-js/src/modules/networking/PTLS.js b/src/puter-js/src/modules/networking/PTLS.js new file mode 100644 index 000000000..aa48aa4b9 --- /dev/null +++ b/src/puter-js/src/modules/networking/PTLS.js @@ -0,0 +1,72 @@ +/** + * This file uses https://github.com/MercuryWorkshop/rustls-wasm authored by GitHub:@r58Playz under the MIT License + */ + +import { PSocket } from "./PSocket"; + +let rustls = undefined; + +export class PTLSSocket extends PSocket { + constructor(...args) { + super(...args); + (async() => { + if (!rustls) { + rustls = (await import( /* webpackIgnore: true */ "https://puter-net.b-cdn.net/rustls.js")) + // await rustls.default("https://puter-net.b-cdn.net/rustls.wasm") + await rustls.default("https://alicesworld.tech/fun/rustls.wasm") + } + // const socket = new puter.net.Socket("google.com", 443) + const readable = new ReadableStream({ + start: (controller) => { + super.on("data", (data) => { + controller.enqueue(data.buffer) + }) + super.on("close", () => { + controller.close() + }) + } + }) + + const writable = new WritableStream({ + write: (chunk) => { super.write(chunk); }, + abort: () => { super.close(); }, + close: () => { super.close(); }, + }) + + const {read, write} = await rustls.connect_tls(readable, writable, args[0]); + + this.writer = write.getWriter(); + // writer.write("GET / HTTP/1.1\r\nHost: google.com\r\n\r\n"); + let reader = read.getReader(); + let done = false; + this.emit("tlsopen", undefined); + + while (!done) { + const {done: readerDone, value} = await reader.read(); + done = readerDone; + if (!done) { + this.emit("tlsdata", value); + } + } + })(); + } + on(event, callback) { + if (event === "data" || event === "open") { + return super.on("tls" + event, callback) + } else { + return super.on(event, callback); + } + } + write(data, callback) { + if (data.buffer) { // TypedArray + this.writer.write(data.slice(0).buffer).then(callback); + } else if (data.resize) { // ArrayBuffer + this.writer.write(data).then(callback); + } else if (typeof(data) === "string"){ + this.writer.write(data).then(callback); + } else { + throw new Error("Invalid data type (not TypedArray, ArrayBuffer or String!!)"); + } + } + +} \ No newline at end of file diff --git a/src/puter-js/src/modules/networking/PWispHandler.js b/src/puter-js/src/modules/networking/PWispHandler.js index f5e3f9d54..1fbd23ae2 100644 --- a/src/puter-js/src/modules/networking/PWispHandler.js +++ b/src/puter-js/src/modules/networking/PWispHandler.js @@ -43,7 +43,7 @@ export class PWispHandler { } register(host, port, callbacks) { const streamID = this._nextStreamID++; - this.streamMap.set(streamID, {queue: [], streamID, buffer: this._bufferMax, dataCallBack: callbacks.dataCallBack, closeCallBack: callbacks.closeCallBack, openCallBack: callbacks.openCallBack}); + this.streamMap.set(streamID, {queue: [], streamID, buffer: this._bufferMax, dataCallBack: callbacks.dataCallBack, closeCallBack: callbacks.closeCallBack}); this._ws.send(createWispPacket({ packetType: CONNECT, streamType: TCP, @@ -51,7 +51,6 @@ export class PWispHandler { hostname: host, port: port })) - return streamID; } From 98be5c1323661a7f5fffb71878f46fc593d6bf2b Mon Sep 17 00:00:00 2001 From: ProgrammerIn-wonderland <3838shah@gmail.com> Date: Tue, 14 Jan 2025 23:54:47 -0500 Subject: [PATCH 2/2] error handling on TLS handshake and reporting wisp errors to client --- .../src/modules/networking/PSocket.js | 10 +++- src/puter-js/src/modules/networking/PTLS.js | 50 ++++++++++++++----- .../src/modules/networking/parsers.js | 9 ++++ 3 files changed, 55 insertions(+), 14 deletions(-) diff --git a/src/puter-js/src/modules/networking/PSocket.js b/src/puter-js/src/modules/networking/PSocket.js index 1b59eb083..bfd413ed1 100644 --- a/src/puter-js/src/modules/networking/PSocket.js +++ b/src/puter-js/src/modules/networking/PSocket.js @@ -1,4 +1,5 @@ import EventListener from "../../lib/EventListener.js"; +import { errors } from "./parsers.js"; const texten = new TextEncoder(); export let wispInfo = { @@ -10,13 +11,18 @@ export class PSocket extends EventListener { _events = new Map(); _streamID; constructor(host, port) { - super(["data", "drain", "open", "close", "tlsdata", "tlsopen"]); + super(["data", "drain", "open", "error", "close", "tlsdata", "tlsopen"]); const callbacks = { dataCallBack: (data) => { this.emit("data", data); }, closeCallBack: (reason) => { - this.emit("close", false); // TODO, report errors + if (reason !== 0x02) { + this.emit("error", new Error(errors[reason])); + this.emit("close", true); + return; + } + this.emit("close", false); } } diff --git a/src/puter-js/src/modules/networking/PTLS.js b/src/puter-js/src/modules/networking/PTLS.js index aa48aa4b9..6591d7666 100644 --- a/src/puter-js/src/modules/networking/PTLS.js +++ b/src/puter-js/src/modules/networking/PTLS.js @@ -12,29 +12,51 @@ export class PTLSSocket extends PSocket { (async() => { if (!rustls) { rustls = (await import( /* webpackIgnore: true */ "https://puter-net.b-cdn.net/rustls.js")) - // await rustls.default("https://puter-net.b-cdn.net/rustls.wasm") - await rustls.default("https://alicesworld.tech/fun/rustls.wasm") + await rustls.default("https://puter-net.b-cdn.net/rustls.wasm") } - // const socket = new puter.net.Socket("google.com", 443) + + let cancelled = false; const readable = new ReadableStream({ + /** + * + * @param {ReadableStreamDefaultController} controller + */ start: (controller) => { super.on("data", (data) => { controller.enqueue(data.buffer) }) super.on("close", () => { - controller.close() + if (!cancelled) + controller.close() }) + + }, + pull: (controller) => { + + }, + cancel: () => { + cancelled = true; } + }) const writable = new WritableStream({ write: (chunk) => { super.write(chunk); }, - abort: () => { super.close(); }, + abort: () => { console.log("hello"); super.close(); }, close: () => { super.close(); }, }) - - const {read, write} = await rustls.connect_tls(readable, writable, args[0]); - + + let read, write; + try { + const TLSConnnection = await rustls.connect_tls(readable, writable, args[0]) + read = TLSConnnection.read; + write = TLSConnnection.write; + } catch (e) { + this.emit("error", new Error("TLS Handshake failed: " + e)); + return; + } + + this.writer = write.getWriter(); // writer.write("GET / HTTP/1.1\r\nHost: google.com\r\n\r\n"); let reader = read.getReader(); @@ -42,10 +64,14 @@ export class PTLSSocket extends PSocket { this.emit("tlsopen", undefined); while (!done) { - const {done: readerDone, value} = await reader.read(); - done = readerDone; - if (!done) { - this.emit("tlsdata", value); + try { + const {done: readerDone, value} = await reader.read(); + done = readerDone; + if (!done) { + this.emit("tlsdata", value); + } + } catch (e) { + this.emit("error", e) } } })(); diff --git a/src/puter-js/src/modules/networking/parsers.js b/src/puter-js/src/modules/networking/parsers.js index 80808b32b..dcbbef2fd 100644 --- a/src/puter-js/src/modules/networking/parsers.js +++ b/src/puter-js/src/modules/networking/parsers.js @@ -14,6 +14,15 @@ export const UDP = 0x02; // Frequently used objects export const textde = new TextDecoder(); const texten = new TextEncoder(); +export const errors = { + 0x41: "Stream creation failed due to invalid information. This could be sent if the destination was a reserved address or the port is invalid." + ,0x42: "Stream creation failed due to an unreachable destination host. This could be sent if the destination is an domain which does not resolve to anything." + ,0x43: "Stream creation timed out due to the destination server not responding." + ,0x44: "Stream creation failed due to the destination server refusing the connection." + ,0x47: "TCP data transfer timed out." + ,0x48: "Stream destination address/domain is intentionally blocked by the proxy server." + ,0x49: "Connection throttled by the server." +} /** * @typedef {{packetType: number, streamID: number, streamType?: number, port?: number, hostname?: string, payload?: Uint8Array, reason?: number, remainingBuffer?: number}} ParsedWispPacket