Repository navigation
Describe the usage sharing record exactly, as the .NET packages write it - #65
Closed
jwrosewell wants to merge 3 commits into
Closed
jwrosewell wants to merge 3 commits into
jwrosewell wants to merge 3 commits into
Conversation
The Processing section described a record with escaped and truncated attributes that the .NET packages do not write. It now describes the record .NET writes, completely enough for any language to write the same bytes: the document's byte order mark, declaration and lack of whitespace, every element of a record in order with its content, how an Evidence key becomes an element, the truncation prefix at 10,000 UTF-16 code units, the \x form and BadSchema for control characters, the CDATA split and the escaping, an example, and the request detail. The example link moves to the .NET code in 51Degrees/51degrees-dotnet and the Java link goes.
This was referenced Nov 3, 2026
jwrosewell
added a commit
to 51Degrees/51degrees-rust
that referenced
this pull request
Nov 5, 2026
Part of #232. Records are now written byte for byte as draft 51Degrees/specifications#65 (at ae77ca4) sets them out, which is the form .NET's ShareUsageBase writes. - The new usage_record module writes the document. It starts with the UTF-8 byte order mark and the XML declaration, has nothing between elements, and writes an element with no content as <Name />. - SessionId, Sequence, DateSent and ClientIP are always written. Sequence is a 32-bit integer, or 0 when it is absent or is not one. Without a Sequence element it is read from the evidence text as .NET's int.TryParse reads it. - Each shared evidence entry is an element named by its prefix as the key spells it, with the field as given in Name and the value in CDATA, where ]]> is split across two sections. Entries are grouped by prefix in the order the evidence holds them. A key with no prefix is written whole, with its value as text. - A value over 10,000 UTF-16 code units keeps its first 10,000 after "[TRUNCATED BY USAGE SHARING] ". - A character from U+0000 to U+001F other than tab, line feed and carriage return becomes \x and four hexadecimal digits, and the record gains <BadSchema>true</BadSchema>. - An element name is checked against XML 1.0's name rules, so a prefix such as cafe with an accent is written, where it was left out before. The escaped and truncated attributes and the capitalised prefixes go, and quick-xml is no longer needed.
This was referenced Nov 6, 2026
# Conflicts: # pipeline-specification/pipeline-elements/usage-sharing-element.md
The Evidence entries row said the accepted Evidence decides which are shared. Usage sharing now accepts no Evidence, so the row says what rc says, being what the element finds less what the shared Evidence rules leave out, and the session id, sequence and client IP are the values found for their keys. The batching paragraph is left as rc has it, so that the change to how a message is filled can edit it alone. The .NET element fails the whole message for a key that starts with a dot, so the page no longer says such a key is written. Prefixes are grouped as the key spells them, as .NET does.
This was referenced Nov 9, 2026
Contributor
Author
|
Closing without merging. The usage sharing record stays as the Processing section of usage-sharing-element.md describes it: a replaced character marks its element Every language except .NET follows that section now. .NET keeps the record it writes today for the time being, as reference-implementation-notes.md now records (#118). .NET moves to the specification's record once both forms have been seen working downstream. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The usage sharing record on usage-sharing-element.md is changed to the record the .NET packages write today, described completely enough for any language to write the same bytes. Every implementation then moves to it, each in its own issue.
Who asked for this, and what review must check
James Rosewell asked for this work on 3 November 2026 ("Work on that"). He has not seen the text. The review must therefore check that the page records the bytes .NET actually writes, and not what the .NET source appears to say. The three checks this description listed before it left draft were run on 9 November against 51degrees-dotnet
rcat 9bf43bc0, and their results are below.Merge order
Merge this after #56 (how a message is filled, from the minimum to twice it), and after #97 (session tracking, back-off and repeat interval) if #97 is adopted. Whether #97 is adopted is still a question for James, so this page does not depend on it. It says nothing about the back-off, the repeat interval or the session cookie, and it refers to the shared Evidence rules for which values are shared, whatever #97 adds to them. Test merges with
git merge-treeand in a local branch showed this branch merging cleanly withrcalone, with #56 alone, with #97 alone, and after #56 and then #97 in that order.Base
This draft was opened into
main. Onlyrcmerges intomain, sorcat 4da1891 was merged into the branch (no rebase and no force push) and the base is nowrc. The conflict was in the Processing section, which drafts #89 and #99 changed onrc, with #64 and #71 changing its links. The merge (c86b057) kept this draft's record text in place ofrc's old list, and keptrc's link to the .NETBuildAndSendXmlmethod. The diff againstrcis this draft's own change, inusage-sharing-element.mdalone.What changes in the Processing section
ClientIPfollowsDateSent, andSessionId,Sequence,DateSentandClientIPare always written, an empty one as<SessionId />. They hold the values found forquery.session-id,query.sequenceandserver.client-ip.Nameand the value in CDATA, grouped by prefix in the order the values were found. A key with no prefix is an element named by the whole key.<BadSchema>true</BadSchema>and\xwith four lowercase hex digits replace theescapedattribute, and the[TRUNCATED BY USAGE SHARING]prefix at 10,000 UTF-16 code units replaces thetruncatedattribute.DateSent,Sequence,FlowElementandServerIPare defined exactly.ServerIPskips127.0.0.1and::1, as .NET does.The batching paragraph is left word for word as
rchas it, so #56 changes it alone.What this update changed (5119a09)
rcsays "The Evidence that is shared is what the element finds, as accepted Evidence describes, less what the shared Evidence rules leave out." The row now says the same, andSessionId,SequenceandClientIPtake the value found for their key, from the Evidence or the web request, as Usage sharing accepts no Evidence, and reads the web request instead #99 describes..was said to be written as an element named by the whole key. .NET fails the whole message for such a key (check 1, case H), so the page no longer says it is written. What such a key should do is left for review.headerandHeaderare two groups, which is what .NET writes (check 1, case E).rc's words, as above.The .NET checks
A console program referenced
pipeline/Engines.FiftyOneandpipeline/Webfrom 51degrees-dotnetrcat 9bf43bc0 as projects (-p:FiftyOneReferences=Project), built a Pipeline holding only theShareUsageElementwith a minimum of one entry per message, and captured the body it posted through anHttpClienthandler, then decompressed it. Nothing was sent to the network.Check 1, one whole document compared byte for byte. Passed.
For the page's example (
header.User-Agent=Mozilla/5.0,header.X-Test=a]]>b,query.51D_Test=x, U+0001,y), .NET sent 661 bytes, which decompressed to this, with U+FEFF standing for the byte order mark.The first three bytes are EF BB BF. With
DateSentset to the page's value andVersiontoPlatformreplaced by..., the bytes equal the page's example, with the<Devices>root and the declaration around it, byte for byte.Further cases on the same build, each matching the page unless it says otherwise.
Query.Session-Id=abc,QUERY.SEQUENCE=2,Server.Client-IP=192.0.2.1<SessionId>abc</SessionId><Sequence>2</Sequence>and<ClientIP>192.0.2.1</ClientIP>, none repeated as an entry, so the keys match without regard to casequery.sequence=x<Sequence>0</Sequence>[TRUNCATED BY USAGE SHARING]and its first 10,000 code units, 10,029 in allheader.B,query.51D_X,header.A,cookie.51D_C,Header.MixedheaderB then A, thenquery, thencookie, then a separate<Header Name="Mixed">group&<>"'in a valueX\x0002NamewithBadSchema, and&<>"'as they are inside CDATAnodot=a&b<c>d"e<nodot>a&b<c>d"e</nodot>.dot=wArgumentException: Invalid name character in '.dot'OverflowException: Value was either too large or too small for an unsigned byteloggedOverflowExceptionCases H, I and K are .NET failures that lose the whole message, not just the entry. I and K come from
EncodeInvalidXMLChars, which replaces every characterXmlConvert.IsXmlCharrefuses, being U+0000 to U+001F but also each half of a surrogate pair and U+FFFE and U+FFFF, and converts each withConvert.ToByte, which overflows above U+00FF (ShareUsageBase.cs lines 1130 to 1164). The page describes only the replacement .NET completes, U+0000 to U+001F. What a language should do with these three cases is a question for review. Each one loses every record in the message, so each is a .NET defect whatever the page comes to say.Check 2, the order of the Evidence from the web integration. The order is the web framework's, not the order on the wire.
The same program started Kestrel, sent one raw HTTP/1.1 request with its headers in this order, X-Zeta, User-Agent, Sec-CH-UA-Platform, Host, Accept-Language, Cookie, X-Alpha, Accept and Connection, and called
WebRequestEvidenceService.AddEvidenceFromRequest.Evidence.AsDictionary()then gave this order.So .NET's Evidence is in the order it was added, because
DataBaseholds it in aDictionary<string, object>(DataBase.cs line 54), and the web integration adds the headers, cookies, query string, form, session, client IP, protocol and request line in that order (WebRequestEvidenceService.cs). Within the headers it takes Kestrel's order, being the headers Kestrel knows in Kestrel's own order and then the others as they arrived. The record grouped them by prefix, soheader.protocolcame afterX-Alphain theheadergroup andserver.request-methodlast.Another language's web framework will order the headers its own way, so two languages give the same bytes only when they find the values in the same order. That is why the opening sentence now says so. The .NET draft for #99, 51degrees-dotnet#100, finds the Evidence first and then the web request's values for keys the Evidence does not hold (
ShareUsageEvidence.Find), which fits the page's rule of the order found.Check 3, the address order behind ServerIP. Same on this host, but the languages do not all ask the resolver.
On this Windows 11 machine the host name resolves to four addresses. .NET (
Dns.GetHostAddresses), Python (socket.getaddrinfo), Node.js (dns.lookupwithall) and Go (net.LookupIP) all gavefe80::fa6a:d78b:3740:52df,fe80::4be9:c889:4f11:c57d,192.168.3.74,172.28.96.1. Java (InetAddress.getAllByName) put the two IPv4 addresses first, in the same order. The page's rule, the first IPv4 address that is not127.0.0.1, picks192.168.3.74from every one of those lists, and .NET wrote<ServerIP>192.168.3.74</ServerIP>.The languages do not all use the resolver, though, on
rcof each repository.getHostAddressinShareUsageBase.java), which here was also192.168.3.74.server_ipinshare_usage_element.rs).serverIPinusagesharing.go), andnet.InterfaceAddrshere listed192.168.3.74first.ServerIPelement.So on a host whose default route leaves through an interface other than the first IPv4 address of its name, Java, Rust and Go would write a different
ServerIPfrom .NET today. Whether resolvers order a name's addresses the same way on every operating system was not tested, as only this host was available.What each language must change
.NET is the reference, so its output for the cases the page describes needs no change. Its failures in cases H, I and K are listed above. Each other language changes in its own issue, and the checks above add these points to them.
VersiontoPlatformbeforeSessionId, where .NET writes them afterClientIP, and takesServerIPfrom a route to 8.8.8.8 rather than the host name.SessionIdand write noServerIP.ServerIPfrom the interface addresses rather than the host name.pipeline-engines-fiftyone/src/usage_record.rslines 22 to 25). It should check the key that starts with.and the grouping of prefixes in another case against this update, and takesServerIPfrom the route rather than the host name.What it was checked against
ShareUsageBase.csandShareUsageElement.cson 51degrees-dotnetrcat 9bf43bc0, read and run as above.usage-sharing-element.mdonrcat 4da1891, including Usage sharing accepts no Evidence, and reads the web request instead #99's Accepted Evidence and Shared Evidence sections.scripts/utm-lint.ps1 -RepoRoot . -Campaign specifications, printed "UTM link lint: clean for campaign 'specifications'".