Loading the parity record
Fetching the measured run — the pinned upstream oracle, every symbol and every case.
Fetching the measured run — the pinned upstream oracle, every symbol and every case.
Parity / Socket.IO
Every number on this page was produced by running both implementations over the same cases: the real engine.io-parser package pinned at 5.2.3 answers first, and its answer is the expectation the Go port is held to. Nothing is a hand-written expectation, so a new upstream release re-scores the port on its own. See Socket.IO for the port's own documentation. Source: github.com/malcolmston/socket.io.
Not a generic diagram: every node below names an artefact of this harness — the pinned package it installed, the runner files it started, the case files it streamed, and the counts it wrote out.
| Case group | Cases | Match | Mismatch | Group parity |
|---|---|---|---|---|
| wire-decode | 50 | 50 | 0 | 100.0% |
| wire-encode | 45 | 41 | 4 | 91.1% |
| behaviour | 34 | 34 | 0 | 100.0% |
| eio-frame | 33 | 32 | 1 | 97.0% |
| interop | 3 | 3 | 0 | 100.0% |
Every exported symbol of the upstream package, and what the port offers for it. The upstream list is derived mechanically, never from a README: cd parity/socket.io/node && node enum.mjs # all classes
cd parity/socket.io/node && node enum.mjs Server # one class. A symbol with no case is untested, never a match.
| Upstream symbol | Go symbol | Status | Cases | Note |
|---|---|---|---|---|
| Server.attach | Server.Attach | match | all behaviour/interop cases | Go mounts on an http.ServeMux |
| Server.close | Server.Close | match | all behaviour/interop cases | |
| Server.of | Server.Of | match | namespace-connect | |
| Server.use | Server.Use | match | handshake-auth | |
| Server.emit | Server.Emit | match | rooms-broadcast-all | |
| Server.on("connection") | Server.OnConnection | match | most behaviour cases | Go has no generic emitter, only this hook |
| Server.to | Server.To | match | rooms-namespace-to-includes-sender | |
| Server.except | Server.Except | match | rooms-except | |
| Server.fetchSockets | Server.FetchSockets | match | rooms-fetch-sockets | Go returns []*Socket synchronously, upstream a Promise |
| Server.socketsJoin | Server.SocketsJoin | match | rooms-sockets-join-leave | |
| Server.socketsLeave | Server.SocketsLeave | match | rooms-sockets-join-leave | |
| Server.sockets | Server.Sockets | differs | rooms-fetch-sockets | upstream io.sockets is the default *Namespace*; Go returns the socket list |
| Server.in | Server.In | untested | — | alias of to; no case |
| Server.local | Server.Local | untested | — | single-node only, unobservable here |
| Server.disconnectSockets | Server.DisconnectSockets | untested | — | |
| Server.serverSideEmit | Server.ServerSideEmit | untested | — | multi-node fan-out; unobservable single-node |
| Server.path | Options.Path | differs | — | upstream is a chainable getter/setter method |
| Server.compress | BroadcastOperator.Compress | differs | — | not on Server; also a no-op in the port |
| Server.volatile | BroadcastOperator.Volatile | differs | — | not on Server |
| Server.timeout | BroadcastOperator.Timeout | differs | — | not on Server; the operator's value is never used |
| Server.adapter | Namespace.SetAdapter | differs | — | per-namespace in Go, server-wide upstream |
| Server.listen | — | missing | — | Go composes with net/http instead |
| Server.attachApp | — | missing | — | µWebSockets.js integration |
| Server.serveClient | — | missing | — | the port serves no client bundle |
| Server.allSockets | — | missing | — | deprecated upstream |
| Server.serverSideEmitWithAck | — | missing | — | |
| Server.send / Server.write | — | missing | — | emit("message", …) aliases |
| Server.engine | — | missing | — | no Engine.IO server object is exposed |
| Server.connectTimeout | — | missing | — | no handshake deadline is enforced |
| — | Server.Handler | extra | all behaviour cases | wraps the server as http.Handler middleware |
| — | Server.ServeHTTP | extra | all behaviour cases | |
| — | Server.OnServerEvent | extra | — | pairs with SetBroadcaster |
| — | Server.SetBroadcaster | extra | — | |
| — | socketio.New / socketio.Options | extra | all behaviour cases | Go constructor |
| Namespace.name | Namespace.Name | match | namespace-connect | |
| Namespace.use | Namespace.Use | match | namespace-middleware-accept, namespace-middleware-reject | |
| Namespace.on("connection") | Namespace.OnConnection | match | namespace-connect | |
| Namespace.emit | Namespace.Emit | match | rooms-broadcast-all | via Server.Emit, which delegates to the default namespace |
| Namespace.to | Namespace.To | match | rooms-namespace-to-includes-sender | |
| Namespace.except | Namespace.Except | match | rooms-except | |
| Namespace.fetchSockets | Namespace.FetchSockets | match | rooms-fetch-sockets | |
| Namespace.socketsJoin | Namespace.SocketsJoin | match | rooms-sockets-join-leave | |
| Namespace.socketsLeave | Namespace.SocketsLeave | match | rooms-sockets-join-leave | |
| Namespace.in | Namespace.In | untested | — | alias of to |
| Namespace.local | Namespace.Local | untested | — | |
| Namespace.disconnectSockets | Namespace.DisconnectSockets | untested | — | |
| Namespace.adapter | Namespace.SetAdapter | differs | — | upstream exposes the adapter object; Go only a setter |
| Namespace.sockets | Namespace.Sockets | differs | rooms-fetch-sockets | upstream is a Map<id, Socket>; Go a slice |
| Namespace.serverSideEmit | — | missing | — | only on Server in Go |
| Namespace.serverSideEmitWithAck | — | missing | — | |
| Namespace.compress | — | missing | — | |
| Namespace.volatile | — | missing | — | |
| Namespace.timeout | — | missing | — | |
| Namespace.send / Namespace.write | — | missing | — | |
| Namespace.server | — | missing | — | no back-pointer to the Server |
| — | Namespace.SocketsInRoom | extra | rooms-fetch-sockets, rooms-sockets-join-leave | stands in for io.in(room).fetchSockets() |
| Socket.id | Socket.ID | match | namespace-connect, broadcast cases | |
| Socket.on(ev, fn) | Socket.On | match | emit-scalar-args, ack-values, … | Go handlers *return* the ack payload instead of receiving a callback |
| Socket.emit | Socket.Emit | differs | binary-server-to-client, reserved-event-server-emit | no reserved-name guard**: upstream throws on emit("connect"/"disconnect"/"connect_error"), the port sends them |
| Socket.emitWithAck | Socket.EmitAck / Socket.EmitWithAck | match | server-ack | |
| Socket.timeout | (the timeout argument of EmitAck) | differs | server-ack-no-handler | not chainable; and the timeout is unreachable — see divergences |
| Socket.onAny | Socket.OnAny | match | onany | Go's callback has no access to the ack callback |
| Socket.prependAny | Socket.PrependAny | untested | — | |
| Socket.offAny | Socket.OffAny | untested | — | |
| Socket.listenersAny | Socket.ListenersAny | untested | — | |
| Socket.off(ev, fn) | Socket.Off(ev) | differs | handler-off | Go removes **all** handlers for the event; upstream removes one function. Same observable outcome in the case, different contract |
| Socket.join | Socket.Join | match | rooms-self-room | |
| Socket.leave | Socket.Leave | match | rooms-self-room | |
| Socket.rooms | Socket.Rooms | match | rooms-self-room | Go returns an unordered slice; upstream an insertion-ordered Set |
| Socket.to | Socket.To | match | rooms-socket-to-excludes-sender | sender exclusion verified |
| Socket.broadcast | Socket.Broadcast | match | rooms-socket-broadcast | |
| Socket.except | Socket.Except | untested | — | |
| Socket.in | Socket.In | untested | — | |
| Socket.local | Socket.Local* | missing | — | not present on Socket in Go |
| Socket.disconnect | Socket.Disconnect | differs | disconnect-server-namespace, disconnect-server-transport, disconnect-namespace-then-emit | disconnect(false) leaves the port's client unaware; see divergences |
| Socket.on("disconnect") | Socket.OnDisconnect | match | disconnect-client-initiated, disconnect-server-* | reason strings match |
| Socket.on("disconnecting") | — | missing | disconnect-client-initiated | no pre-disconnect hook, so rooms cannot be read on the way out |
| Socket.handshake | Socket.Auth, Handshake, NewHandshake | differs | handshake-auth | Socket.handshake is a field with auth/headers/query/address/time/issued; Go exposes only Auth(), and its Handshake type is not reachable from a Socket |
| Socket.data | Socket.Data / Set / Get / GetString / Delete | match | socket-data | |
| Socket.nsp | Socket.Namespace | match | namespace-connect | |
| Socket.disconnected | — | missing | — | no connection-state accessor |
| Socket.connected | — | missing | — | ditto |
| Socket.conn | — | missing | — | no transport handle |
| Socket.request | — | missing | — | no access to the handshake http.Request |
| Socket.compress | — | missing | — | |
| Socket.volatile | — | missing | — | |
| Socket.send / Socket.write | — | missing | — | |
| Socket.use | — | missing | — | per-socket (per-packet) middleware |
| Socket.onAnyOutgoing / prependAnyOutgoing / offAnyOutgoing / listenersAnyOutgoing | — | missing | — | no outbound catch-all |
| Socket.leaveAll | — | missing | — | |
| Socket.timeout().emitWithAck chain | — | missing | — | |
| — | Socket.GetString | extra | socket-data | typed convenience accessor |
| BroadcastOperator.emit | BroadcastOperator.Emit | match | all rooms-* broadcast cases | |
| BroadcastOperator.to | BroadcastOperator.To | match | rooms-namespace-to-includes-sender | Go's To **replaces** the room list rather than appending |
| BroadcastOperator.in | BroadcastOperator.In | match | rooms-namespace-to-includes-sender | |
| BroadcastOperator.except | BroadcastOperator.Except / ExceptRoom | match | rooms-except | Go splits socket-id and room exclusion into two methods |
| BroadcastOperator.local | BroadcastOperator.Local | untested | — | |
| BroadcastOperator.compress | BroadcastOperator.Compress | untested | — | recorded but unused by the port |
| BroadcastOperator.volatile | BroadcastOperator.Volatile | untested | — | recorded but unused by the port |
| BroadcastOperator.timeout | BroadcastOperator.Timeout | untested | — | recorded but unused: there is no operator-level ack |
Every case the harness streamed to both runners, with the exact upstream symbol and Go symbol it exercised. A deliberate, documented difference is a deviation and is counted apart from a mismatch.
| Case | Group | Upstream symbol | Go symbol | Status | Note |
|---|---|---|---|---|---|
| emit-scalar-args | behaviour | socket.emit / socket.on | Socket.Emit / Socket.On | match | argument marshalling AND in-order delivery of five back-to-back events |
| ack-values | behaviour | socket.emit(ev, cb) | Client.EmitWithAck | match | |
| ack-multi-values | behaviour | ack callback arity | EventHandler return value | match | |
| ack-empty-call | behaviour | cb() | EventHandler returning []any{} | match | |
| ack-no-handler | behaviour | Socket._onevent | Socket.dispatch | match | upstream never acks an event with no handler; the port acks with an empty payload |
| ack-handler-never-calls | behaviour | Socket._onevent | Socket.dispatch | match | a handler that never invokes its callback must not produce an ack |
| ack-binary | behaviour | BINARY_ACK round trip | Socket.dispatch + EncodeBinary | match | |
| server-ack | behaviour | socket.timeout().emitWithAck | Socket.EmitAck | match | |
| server-ack-no-handler | behaviour | socket.timeout().emitWithAck | Socket.EmitAck | match | same auto-ack defect on the client side, so a server-side ack timeout is unreachable Go-to-Go |
| onany | behaviour | socket.onAny | Socket.OnAny | match | |
| binary-server-to-client | behaviour | socket.emit(Buffer) | Socket.Emit([]byte) | match | |
| binary-client-to-server | behaviour | socket.emit(Buffer) | Client.Emit([]byte) | match | |
| namespace-connect | behaviour | io.of | Server.Of | match | |
| namespace-middleware-accept | behaviour | nsp.use | Namespace.Use | match | middleware order, then the connection handler |
| namespace-middleware-reject | behaviour | nsp.use + connect_error | Namespace.Use + ConnectError | match | |
| namespace-middleware-silent | behaviour | nsp.use | Namespace.runMiddleware | match | middleware that never calls next() must leave the handshake unanswered |
| namespace-invalid | behaviour | "Invalid namespace" connect_error | conn.handleConnect | match | |
| handshake-auth | behaviour | socket.handshake.auth | Socket.Auth | match | |
| rooms-self-room | behaviour | socket.rooms / join / leave | Socket.Rooms / Join / Leave | match | |
| rooms-socket-to-excludes-sender | behaviour | socket.to(room).emit | Socket.To().Emit | match | |
| rooms-namespace-to-includes-sender | behaviour | io.to(room).emit | Server.To().Emit | match | |
| rooms-except | behaviour | io.except(room).emit | Server.Except().Emit | match | |
| rooms-broadcast-all | behaviour | io.emit | Server.Emit | match | |
| rooms-socket-broadcast | behaviour | socket.broadcast.emit | Socket.Broadcast().Emit | match | |
| rooms-fetch-sockets | behaviour | io.fetchSockets / io.in(room).fetchSockets | Server.FetchSockets / Namespace.SocketsInRoom | match | |
| rooms-sockets-join-leave | behaviour | io.socketsJoin / io.socketsLeave | Server.SocketsJoin / SocketsLeave | match | |
| disconnect-client-initiated | behaviour | socket.on("disconnecting"/"disconnect") | Socket.OnDisconnect | match | |
| disconnect-server-namespace | behaviour | socket.disconnect(false) | Socket.Disconnect(false) | match | |
| disconnect-server-transport | behaviour | socket.disconnect(true) | Socket.Disconnect(true) | match | |
| disconnect-namespace-then-emit | behaviour | socket.emit after disconnect | Socket.Emit after Disconnect | match | |
| reserved-event-server-emit | behaviour | RESERVED_EVENTS guard (server) | Socket.Emit | match | |
| reserved-event-client-emit | behaviour | RESERVED_EVENTS guard (client) | client.Client.Emit | match | |
| handler-off | behaviour | socket.off(ev, fn) | Socket.Off(ev) | match | |
| socket-data | behaviour | socket.data | Socket.Set / Socket.Data | match | |
| eio-enc-open | eio-frame | encodePacket | engineio.Packet.Encode | match | |
| eio-enc-close | eio-frame | encodePacket | engineio.Packet.Encode | match | |
| eio-enc-ping-bare | eio-frame | encodePacket | engineio.Packet.Encode | match | the v4 heartbeat ping has no payload |
| eio-enc-ping-probe | eio-frame | encodePacket | engineio.Packet.Encode | match | the upgrade handshake probe |
| eio-enc-pong-probe | eio-frame | encodePacket | engineio.Packet.Encode | match | |
| eio-enc-message | eio-frame | encodePacket | engineio.Packet.Encode | match | a Socket.IO EVENT inside an Engine.IO MESSAGE |
| eio-enc-message-empty | eio-frame | encodePacket | engineio.Packet.Encode | match | |
| eio-enc-message-unicode | eio-frame | encodePacket | engineio.Packet.Encode | match | |
| eio-enc-upgrade | eio-frame | encodePacket | engineio.Packet.Encode | match | |
| eio-enc-noop | eio-frame | encodePacket | engineio.Packet.Encode | match | |
| eio-enc-binary | eio-frame | encodePacket | engineio.Packet.Encode | match | base64 with the "b" prefix |
| eio-enc-binary-empty | eio-frame | encodePacket | engineio.Packet.Encode | match | |
| eio-enc-binary-pad | eio-frame | encodePacket | engineio.Packet.Encode | match | base64 padding |
| eio-dec-open | eio-frame | decodePacket | engineio.Decode | match | |
| eio-dec-ping-bare | eio-frame | decodePacket | engineio.Decode | match | |
| eio-dec-ping-probe | eio-frame | decodePacket | engineio.Decode | match | |
| eio-dec-message | eio-frame | decodePacket | engineio.Decode | match | |
| eio-dec-noop | eio-frame | decodePacket | engineio.Decode | match | |
| eio-dec-binary | eio-frame | decodePacket | engineio.Decode | match | |
| eio-dec-binary-empty | eio-frame | decodePacket | engineio.Decode | match | |
| eio-dec-binary-bad-base64 | eio-frame | decodePacket | engineio.Decode | deviation | Node's Buffer.from(s,'base64') is lenient and yields an empty buffer for invalid input; Go's base64 decoder errors. The port is deliberately stricter (upstream is the outlier). |
| eio-dec-empty | eio-frame | decodePacket | engineio.Decode | match | |
| eio-dec-type-out-of-range | eio-frame | decodePacket | engineio.Decode | match | |
| eio-dec-type-nonnumeric | eio-frame | decodePacket | engineio.Decode | match | |
| eio-encpay-single | eio-frame | encodePayload | engineio.EncodePayload | match | |
| eio-encpay-two | eio-frame | encodePayload | engineio.EncodePayload | match | |
| eio-encpay-mixed | eio-frame | encodePayload | engineio.EncodePayload | match | binary members are base64 in a polling payload |
| eio-decpay-two | eio-frame | decodePayload | engineio.DecodePayload | match | |
| eio-decpay-mixed | eio-frame | decodePayload | engineio.DecodePayload | match | |
| eio-decpay-single | eio-frame | decodePayload | engineio.DecodePayload | match | |
| eio-decpay-empty | eio-frame | decodePayload | engineio.DecodePayload | match | an empty polling body |
| eio-decpay-trailing-sep | eio-frame | decodePayload | engineio.DecodePayload | match | |
| eio-decpay-bad-member | eio-frame | decodePayload | engineio.DecodePayload | match | one malformed member must fail the whole payload |
| interop-go-server-node-client | interop | socket.io-client -> Server | socketio.Server | match | the real JS client driving the Go server |
| interop-node-server-go-client | interop | Server -> socket.io-client | client.Client | match | the Go client driving the real JS server |
| interop-go-server-go-client | interop | Server + socket.io-client | socketio.Server + client.Client | match | both sides ported |
| dec-connect | wire-decode | Decoder.add | Decoder.Add | match | |
| dec-connect-auth | wire-decode | Decoder.add | Decoder.Add | match | |
| dec-connect-nsp | wire-decode | Decoder.add | Decoder.Add | match | |
| dec-connect-nsp-auth | wire-decode | Decoder.add | Decoder.Add | match | |
| dec-connect-string-payload | wire-decode | Decoder.isPayloadValid | DecodePacket | match | a CONNECT payload must be an object |
| dec-connect-array-payload | wire-decode | Decoder.isPayloadValid | DecodePacket | match | upstream uses isObject(), which rejects arrays |
| dec-disconnect | wire-decode | Decoder.add | Decoder.Add | match | |
| dec-disconnect-nsp | wire-decode | Decoder.add | Decoder.Add | match | |
| dec-disconnect-payload | wire-decode | Decoder.isPayloadValid | DecodePacket | match | a DISCONNECT must not carry a payload |
| dec-event | wire-decode | Decoder.add | Decoder.Add | match | |
| dec-event-no-payload | wire-decode | Decoder.add | Decoder.Add | match | an absent payload is accepted even for EVENT |
| dec-event-empty-array | wire-decode | Decoder.isPayloadValid | DecodePacket | match | an EVENT payload must be a NON-EMPTY array |
| dec-event-object-payload | wire-decode | Decoder.isPayloadValid | DecodePacket | match | |
| dec-event-nsp | wire-decode | Decoder.add | Decoder.Add | match | |
| dec-event-nsp-no-comma | wire-decode | Decoder.add | Decoder.Add | match | an unterminated namespace swallows the rest of the packet |
| dec-event-nsp-ack | wire-decode | Decoder.add | Decoder.Add | match | |
| dec-event-ack-zero | wire-decode | Decoder.add | Decoder.Add | match | ack id 0 must not be confused with 'no ack' |
| dec-event-ack-leading-zeros | wire-decode | Decoder.add | Decoder.Add | match | |
| dec-event-ack-huge | wire-decode | Decoder.add | Decoder.Add | match | ack id beyond 2^53 |
| dec-event-numeric-nsp | wire-decode | Decoder.add | Decoder.Add | match | |
| dec-event-unicode | wire-decode | Decoder.add | Decoder.Add | match | |
| dec-event-escapes | wire-decode | Decoder.add | Decoder.Add | match | |
| dec-event-trailing-garbage | wire-decode | Decoder.add | Decoder.Add | match | malformed JSON payload |
| dec-event-truncated-json | wire-decode | Decoder.add | Decoder.Add | match | |
| dec-event-two-packets | wire-decode | Decoder.add | Decoder.Add | match | the decoder is reusable across packets |
| dec-ack | wire-decode | Decoder.add | Decoder.Add | match | |
| dec-ack-empty-array | wire-decode | Decoder.isPayloadValid | DecodePacket | match | unlike EVENT, an ACK may carry an empty array |
| dec-ack-no-id | wire-decode | Decoder.add | Decoder.Add | match | |
| dec-ack-nsp | wire-decode | Decoder.add | Decoder.Add | match | |
| dec-connect-error-object | wire-decode | Decoder.add | Decoder.Add | match |
A case can show the port is less safe than the library it ports. Those findings are written into security.json and filed as draft advisories, never as public issues with a working repro.
| Finding | Severity | Affected | Cases | Summary |
|---|---|---|---|---|
| GHSA-3r64-4j95-pf42 | medium | <= 0.4.0 | — | Out-of-range binary placeholders are handed to the application as data instead of rejected |
| GHSA-fv7v-hmg8-9xmx | critical | <= 0.4.0 | — | Namespace middleware that never calls next() admits the socket, bypassing authentication |
Socket.IO's repo also ports 2 packages that each have their own upstream — for example a qs port inside the express repo is measured against npm qs, not against express. Each has a separate harness under parity/socket.io/nested/, its own pinned oracle, and its own score; none of them changes the Socket.IO score above. Together they add 202 cases and 52 inventoried symbols.
| Nested port | Its own upstream | Go module | Cases | Mismatch | Deviations | Symbols | Parity |
|---|---|---|---|---|---|---|---|
| socket.io | socket.io-client@4.8.1; socket.io-client@4.8.1 + socket.io-parser@4.2.7 | github.com/malcolmston/socketio v0.4.0 => ../../../../socket.io | 101 | 0 | 6 | 36 | 100.0% |
| socket.io | engine.io-parser@5.2.3; engine.io-parser@5.2.3 + engine.io@6.6.4 | github.com/malcolmston/socketio v0.4.0 => ../../../../socket.io | 101 | 0 | 5 | 16 | 100.0% |