Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Extract src/websockets from Polykey to js-ws #5

Merged
merged 149 commits into from
Sep 25, 2023
Merged

Conversation

amydevs
Copy link
Member

@amydevs amydevs commented Aug 17, 2023

Description

This PR moves src/websockets from https://github.com/MatrixAI/Polykey to this repository.
It also implements backpressure and stream multiplexing according to the spec in #2.

Issues Fixed

Tasks

  • 1. Move source from src/websockets
  • 2. Separate WebSocketConnection and WebSocketStream out of WebSocketConnection
  • 3. StreamMessage Parser
    • parseVarInt
    • parseStreamId
    • parseStreamMessageType
    • parseStreamMessageAckPayload
    • parseStreamMessageClosePayload
    • parseStreamMessageErrorPayload
    • parseStreamMessage
    1. StreamMessage Generator
    • generateVarInt
    • generateStreamId
    • generateStreamMessageType
    • generateStreamMessageAckPayload
    • generateStreamMessageClosePayload
    • generateStreamMessageErrorPayload
    • generateStreamMessage
  • 5. WebSocketStreamQueue to buffer generated StreamMessages
  • 6. Benchmarks
    • Over net.Socket (TCP)
    • Over ws.WebSocket
    • Over WebSocketConnection
    • Over WebSocketStream
    • iPerf3
  • Rework Events
    • Rework Stopping of WebSocketConnection to rely on Events
    • Rework Destruction of WebSocketStream to rely on Events
  • Injectable http/s Server for WebSocketServer
    • Allow https server to be injected
    • [ ] Allow for http server to be injected - no need for this because we are just going to focus on https only
    • Allow for server to be created and encapsulated in the lifecycle of WebSocketServer depending on TLS configuration
  • Standardize TLS errors
  • Hostname Resolution

Final checklist

  • Domain specific tests
  • Full tests
  • Updated inline-comment documentation
  • Lint fixed
  • Squash and rebased
  • Sanity check the final build

@amydevs amydevs changed the title WIP Draft: Extract src/websockets from Polykey to js-ws Aug 17, 2023
@ghost
Copy link

ghost commented Aug 17, 2023

👇 Click on the image for a new way to code review

Review these changes using an interactive CodeSee Map

Legend

CodeSee Map legend

@amydevs amydevs marked this pull request as draft August 17, 2023 05:55
@tegefaulkes tegefaulkes self-requested a review August 17, 2023 06:12
@CMCDragonkai
Copy link
Member

Can this fix #2 and #4 at the same time?

@tegefaulkes
Copy link
Contributor

We shouldn't kitchen sink PRs, Try do do them in a new PR if they're not required by changes in this one.

@amydevs
Copy link
Member Author

amydevs commented Aug 18, 2023

When we are in a node environment, we are using the ws library. In these cases, the user is able to provide a verifyCallback to verify that the server-supplied peerCert is as expected. This is done by adding an event listener to the upgrade event on a ws.WebSocket event target. This facilitates connecting the PKE backend to PolyKey nodes (using self-signed certs).

However, this event is not exposed in the window.WebSocket object that exists within browsers. There is no way to allow a self-signed certificate without user intervention within browsers, hence we should throw an error is a verifyCallback if provided in a browser environment. This facilitates connecting the PKE frontend to the PKE backend (using certs issued by a valid CA).

@CMCDragonkai
Copy link
Member

Yea the browser will be considered a deficient environment. The API should look the same but we should be strict on creation where we throw errors for unimplemented features because of browser limitations.

@amydevs
Copy link
Member Author

amydevs commented Aug 18, 2023

There will need to be new classes created, I'm in the process of doing so at the moment.
Mostly WSSocket and WSConnection.

The reason for the first being that in QUIC, a QUICSocket has a one to many relationship with QUICConnection. Meanwhile, a WebSocketConnection has a one to one relationship with a ws.WebSocket or window.WebSocket.
Hence, a WSSocket class needs to be created to wrap and group ws.WebSockets, emulating how a QUICSocket can have multiple connections.

This will mean that the interfaces for WSServer and WSClient need to change. These classes originally contained WebSocketStream instances, which were one-to-one with ws.WebSocket. Essentially WebSocketStream is what really the WSConnection should be, barring the ReadableWritablePair, which should be sectioned off to WSStream instead.

WSConnection will have a one-to-many relationship with WSStream by implementing muxing.

@amydevs
Copy link
Member Author

amydevs commented Aug 18, 2023

WSSocket will be simply a container for WSConnections and ws.WebSockets.

Furthermore, the creation of WSServer should no longer be asynchronous. The start should be the real binding of the https.Server instead. Instead of creating a WebSocketStream on connectionHandler, it should be now WSConnection instead. WSConnection should parse the the ws.WebSocket received messages for the creation of WSStreams

@tegefaulkes
Copy link
Contributor

Feel free to ask me any questions about how the WebSockets and WebStreams work, I've worked with them a fair bit.

@amydevs
Copy link
Member Author

amydevs commented Aug 23, 2023

Only the server should be pinging, and the client responds with pongs. We do this as ping/ponging is not available on the window.WebSocket instance. However, browsers will automatically respond with pongs to server-sent pings, rather than exposing the feature to the user. Since we cannot control this behaviour, we have to implement similar behaviour on our WebSocketClient in a nodejs context. Or we can implement application-level ping/ponging.

@CMCDragonkai
Copy link
Member

Ignore the browser environment for a moment.

The websocket spec already defines how ping/pong works. I'm not actually sure which side is supposed to be sending the pings. Can you find out? Either way, keep alive mechanism must exist on both sides so that if the other side stops sending activity, we end up timing out.

According to a quick search, browser websockets might actually be doing the keep alive internally already, so when using browser websockets, you just throw an exception when the parameter is set. Similar to what we talked about already about configuration not available on the browser.

@CMCDragonkai
Copy link
Member

@amydevs update the spec above with all the changes that is required. Including the spec on how the ping will work between client and server as drawn on the whiteboard.

@amydevs
Copy link
Member Author

amydevs commented Aug 24, 2023

for client - server ping-pong keep alive here's what should happen:
client and server both start pinging initially
if the client receives a ping, stop pinging
if the server receives a ping, keep pinging
if the server receives two pings, stop pinging

this could be expanded upon by using the keepalive interval timer.
for example, if the keepalive interval timer on a server is higher than on a client, the client can eagerly ping to establish keepalive responsibility

@amydevs amydevs force-pushed the feature-migration branch 4 times, most recently from af84a62 to a0a44fa Compare September 7, 2023 06:26
@CMCDragonkai
Copy link
Member

CMCDragonkai commented Sep 7, 2023

Regarding benchmarking:

iperf -s # server side
iperf -c 127.0.0.1 -l 1024 # client side

image

Do this on your local system to test your localhost. Use the -l 1024 to use a message buffer size of 1024 bytes or 1 KiB. (We always use KiB, MiB, GiB, never KB, MB, GB).

On the Linux OS, the kernel will set the underlying TCP buffer size. We don't need to change this.

Get a baseline for just regular TCP doing the same thing, you can compare against iperf3. That gives you the overhead of JS.

Then use ws in binary mode and do the same thing with 1024 bytes, now you know the overhead of ws.

Then use js-ws and benchmark only the connection, not the stream. Now you now the overhead of js-ws's connection.

Then use js-ws and benchmark a single stream on single connection. Now you know the overhead over the muxing/demuxing. In this mode, you now have 1 additional buffer to control, and that's the per-stream buffer, keep this consistent and try different per-stream buffer sizes, doubling each time.

@amydevs
Copy link
Member Author

amydevs commented Sep 7, 2023

my iperf3 results performed with

iperf3 -s # server side
iperf3 -c 127.0.0.1 -l 1024 # client side

image

@amydevs amydevs force-pushed the feature-migration branch 6 times, most recently from 8889a59 to f2f9895 Compare September 8, 2023 07:15
@amydevs
Copy link
Member Author

amydevs commented Sep 22, 2023

Good idea to document how your TLS behaves btw with all the potential failure scenarios.

i think i'm going to have to change how the TLS errors to be more specific rather than just internal, though the problem is that i can't really tell a socket error is because of a peer TLS failure in the case where verifyCallback is null, because ws simply errors that the server has dropped the socket

@amydevs
Copy link
Member Author

amydevs commented Sep 22, 2023

i'll probably standardize the TLS errors like QUIC first and then document them

@CMCDragonkai
Copy link
Member

Some things to port over from js-quic:

@amydevs amydevs marked this pull request as ready for review September 23, 2023 15:22
@amydevs amydevs changed the title Draft: Extract src/websockets from Polykey to js-ws Extract src/websockets from Polykey to js-ws Sep 23, 2023
@amydevs
Copy link
Member Author

amydevs commented Sep 25, 2023

I've added inline documentation to WebSocketConnection:

/**
 * Note that on TLS verification failure, {@link events.EventWebSocketConnectionError} is emitted with the following `event.detail`:
 * - If we failed to verify the peer, the `event.detail` will be an instance of {@link errors.ErrorWebSocketConnectionLocalTLS}.
 * - If the peer failed to verify us, the `event.detail` will be an instance of {@link errors.ErrorWebSocketConnectionPeer} with an error code of {@link utils.ConnectionErrorCode.AbnormalClosure}.
 *
 * The reason for this is that when the peer fails to verify us, Node only tells us that the TCP socket has been reset, but not why.
 */

@amydevs amydevs merged commit 7d83d35 into staging Sep 25, 2023
1 check passed
@CMCDragonkai
Copy link
Member

Hooray!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Development

Successfully merging this pull request may close these issues.

Support stream multiplexing for websockets Extract src/websockets from Polykey to js-ws
3 participants