fix(encode): deterministically sort simple value and float map keys#186
fix(encode): deterministically sort simple value and float map keys#186spokodev wants to merge 1 commit into
Conversation
|
Thanks for picking this up (tbh I haven't cared much about non-string keys), and it's a nice start, but this still isn't RFC 7049 (3.9) ordering for float keys since we're aiming for that. The rule is: encoded length first, then bytewise lexical order; numeric value isn't equivalent. What we have here currently is:
So we need to make the major-7 comparison equivalent to encoded length followed by encoded bytes, honouring Also we should Also fwiw RFC's 8949 restated rules end up having major-7 sorted in the same way, it's just defined differently:
For major-7 we end up in the same place anyway, so nice and consistent and easy in this case. |
The default map key sorter compared key types with `keyToken1.type !== keyToken2.type` and fell back to `Type.compare`, which only looks at the major type. false, true, null, undefined and floats all share major type 7, so any pair of them compared as equal and kept their insertion order — a map keyed by these values encoded to different bytes depending on insertion order. Route keys that share a major type through the type encoder's `compareTokens`, and give major 7 a comparator that orders keys by their canonical CBOR encoding per RFC 7049 3.9: shorter encoding first, then bytewise. The four simple values encode to a single byte so they sort ahead of every float, and comparing the encoded bytes rather than the numeric value makes negative floats (+1.5 `f93e00` before -1.5 `f9be00`), mixed encoding widths (3-byte Infinity before a 9-byte float64) and NaN (no numeric comparison, so previously insertion-order dependent) all deterministic in both insertion orders. The comparator honours `options.float64`: `compareTokens` and the default `mapSorter` now receive the encode options so the sort matches the bytes that are actually written (a value encoded 3-byte by default but 9-byte under float64 sorts differently, and now correctly). Options are passed as a trailing argument, so custom `(e1, e2)` sorters are unaffected. This changes the encoded output for maps with major 7 keys whose previous order was insertion-dependent.
ff012a9 to
c610e95
Compare
|
Reworked to order major 7 keys by their canonical encoding rather than by value.
It honours Tests cover negative floats, mixed widths, NaN and the float64 case in both insertion orders. Full suite green. |
The default map key sorter compared key types with
keyToken1.type !== keyToken2.typeand fell back toType.compare, which only looks at the major type. false, true, null, undefined and floats all share major type 7, so any pair of them compared as equal and kept their insertion order. A map keyed by these values encoded to different bytes depending on insertion order, and pairs such as true/false came out in the wrong order (true0xf5before false0xf4).This routes keys that share a major type through the type encoder's
compareTokensand gives the major 7 encoder a comparator that orders the simple values by their encoded byte and floats by value, so these keys sort deterministically. Adds tests for simple value and float map keys.