Conversation
|
Review requested:
|
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #66335 +/- ##
========================================
Coverage 90.36% 90.36%
========================================
Files 792 792
Lines 275564 275728 +164
Branches 52832 52867 +35
========================================
+ Hits 249016 249174 +158
+ Misses 16965 16957 -8
- Partials 9583 9597 +14
🚀 New features to boost your workflow:
|
|
First off: I like it! (LGTM) Question: unmask is just |
|
I kept it as a separate function to keep the API shape the same as bufferutil. Happy to coalesce but would like to see what @lpinca thinks |
|
The discussion about whether to add this feature to the Node.js core dates back to 2015 (#1010 (comment), #1010 (comment), #1202) and I think it would be a valuable addition. Every WebSocket implementation would benefit from this just like I don't have a strong option about Some tests are hard to follow. |
|
I think there's value in matching the current API so that there's just less for someone migrating from |
03fe080 to
ae2229e
Compare
ae2229e to
dc5a1d7
Compare
|
As a consumer of this API I would be extremely surprised to find that it requires the mask to be exactly 4 bytes. Nothing about the name If it's only a specialized utility for WebSocket, I don't think |
Fair enough, but even though I'm not sure that exposing it via |
|
Unrelated, is AI assistance disclosure needed? It seems it was used at least for tests. Feel free to ignore this comment if I'm wrong. |
... Can it? That name is fairly obvious about what it does, and it's useful for a wide variety of things. Not so here. Anyway I'd also be ok with simply calling it something that makes it clearer that this is not actually a generic mask utility. |
|
Took the "author ready" and "commit queue" labels back off while we figure out the naming and where to expose it. To be clear, I'm ambivalent on the color of this particular bikeshed so more than happy to go with whatever people dislike the least :-) |
dc5a1d7 to
3c3b67f
Compare
I agree, my point is that it is exposed via Using |
|
|
That would actually be a much worse name, since a WebSocketFrame consists of much more stuff. There’s a whole header section with all kinds of flags (type, flags, size, extended size, whether it is actually masked). So that would be quite misleading. |
|
I did almost immediately edit from |
|
If it's going to be |
|
wfm |
Add websocketMask(source, mask, output[, offset[, length]]) and websocketUnmask(buffer, mask), which XOR data with a repeating 4-byte key. This is the masking that WebSocket clients apply to every frame they send (RFC 6455, Section 5.3), and that servers undo on every frame they receive. Userland WebSocket implementations do this either with a byte-by-byte JS loop (undici, and ws without optional dependencies) or with the bufferutil native addon. bufferutil is installed for only about 3% of ws downloads and has no linux-arm64 prebuild, so almost all users run the JS loop. The signature matches bufferutil, so existing users can switch with a feature check, as ws did for buffer.isUtf8(). The implementation uses a V8 fast API call and processes 8-byte words, which the compiler vectorizes. It handles overlapping source and output views, and treats every view type as raw bytes. It is about 20x faster than the JS loop at 1 KiB and 40x faster at 64 KiB, and faster than bufferutil at every size from 32 bytes up. Signed-off-by: James M Snell <jasnell@gmail.com> Assisted-by: Opencode
3c3b67f to
ac760bc
Compare
|
Ok, I've renamed and move the apis. They are now:
I went ahead and kept the internal binding as is (on the |
|
TBH we should not do masking in websocket at all. Just use a 0 mask and we're still spec compliant. The use case for masking is not really relevant in the real world. |
|
Ok, my main motivation here is around the performance improvement, not the merits of the masking at all. Whether it's best to use a zero mask or not is something I'd need to leave to you (@ronag), @lpinca, @KhafraDev, etc to work out. For now, I'll mark this |
KhafraDev
left a comment
There was a problem hiding this comment.
ws has a generateMask option to skip it iirc. This will make masking and unmasking faster for everyone else.
|
cc @tsctx I know you were working on some masking optimizations |
Perhaps, but I would argue that it is not spec compliant.
|
Yeah, I wish masking wasn’t in the spec as well, but I think part of the goal is to make websocket implementations not have to rely on something like Given that such an implementation can implement both the client as well as the server side, the side masking could be on the other end, so just because we don’t mask (by masking with 0) does not actually mean we don’t have to deal with actually masked frames. So I’m pretty sure this suggestion doesn’t actually hold. It sort of works so long as we only think about client sockets, but as soon as you have server sockets a client will send actually masked frames. And arguably on the server side performance is even more relevant. |
|
Sounds like we managed to reach a decision/consensus faster than I thought 😆 ... It looks like there's general agreement. But I'm not going to be in a rush to land. Will leave this open for another day or two in case there are any other thoughts that come up. Assuming things still look good I'll run CI again, pray to the flaky test gods, then get this landed. |
Add http.websocketMask(source, mask, output[, offset[, length]]) and http.websocketUnmask(buffer, mask), which XOR data with a repeating 4-byte key. This is the masking that WebSocket clients apply to every frame they send (RFC 6455, Section 5.3), and that servers undo on every frame they receive.
undiciandws(without optional dependencies) both need mask and unmask. Thebufferutilnative addon makes it faster but it's an optional dependency that appears to only be installed withwsabout 3% of the time. So almost all users run the JS loop.Since we're shipping
undiciandWebSocketnow, it's worth making this faster.I put this onbufferbecause it's an operation on a buffer but it's likely only useful forWebSocket, so we could just as easily expose it viahttp. I have no particular preference.For inputs < 24, the JS loop is still faster because we hit the floor of the C++ binding, even with fast apis. There's not much we can do about that. At larger sizes the perf boost is significant.