Define message-size string accounting as UTF-8 byte length (with explicit string-length exceptions) - #516
Open
sacOO7 wants to merge 1 commit into
Open
Define message-size string accounting as UTF-8 byte length (with explicit string-length exceptions)#516sacOO7 wants to merge 1 commit into
sacOO7 wants to merge 1 commit into
Conversation
…N representations as string length) Disambiguate every message-size string clause in features.md. "length"/"string length" was undefined (UTF-8 bytes vs UTF-16 code units), and the spec was internally contradictory (OM3d string-length vs OD3g byte-length), causing cross-SDK gate divergence for non-ASCII clientIds and map keys. - UTF-8 byte length: OM3f (object clientId), MCR3a1/MST3c/MRM3a/OMP4a1 (all map/operation keys), OD3e (string), OD3g (json; "byte" -> "UTF-8 byte"), TM6f (string data). - Explicit UTF-16 string length: TM6b (object/array data), TM6d and OM3d (extras) reworded from bare "string length" to "string length (the number of UTF-16 code units)". - Canonical umbrella at TO3l8f: a single default (message-size string accounting is UTF-8 byte length, including OD3g) with the two UTF-16 exceptions (extras TM6d/OM3d and object/array data TM6b), matching Ably's published message-size accounting. Supersedes #331.
There was a problem hiding this comment.
Pull request overview
Clarifies the specification’s message-size accounting rules by defining how to measure string sizes (defaulting to UTF-8 byte length) and explicitly documenting the UTF-16 code-unit exceptions, to eliminate cross-SDK divergence and internal inconsistencies.
Changes:
- Defines string
data,clientId, and LiveObjects map key sizing as UTF-8 byte length (e.g.,TM6f,OM3f,MCR3a1,MST3c,MRM3a,OMP4a1,OD3e,OD3g). - Makes the UTF-16 code-unit basis explicit for JSON-stringified
extrasand object/arraydatasizing (TM6b,TM6d,OM3d). - Adds an umbrella rule at
TO3l8fdefining the default and enumerating the exceptions.
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
| - `(TO3l8d)` This clause has been replaced by [TM6d](#TM6d) | ||
| - `(TO3l8e)` This clause has been replaced by [TM6e](#TM6e) | ||
| - `(TO3l8f)` The size is defined as the sum of all message sizes being published, calculated based on [TM6](#TM6), [TP5](#TP5) and [OM3](#OM3) | ||
| - `(TO3l8f)` The size is defined as the sum of all message sizes being published, calculated based on [TM6](#TM6), [TP5](#TP5) and [OM3](#OM3). Unless a clause explicitly states otherwise, string sizes in message-size accounting are measured as their UTF-8 byte length (this includes the `json` `ObjectData` payload, [OD3g](#OD3g)). The only exceptions are the `extras` property ([TM6d](#TM6d)/[OM3d](#OM3d)) and an `Object` or `Array` `data` property ([TM6b](#TM6b)), whose JSON representations are instead measured by string length (the number of UTF-16 code units), matching the service's published accounting for `extras` |
sacOO7
added a commit
to ably/ably-cocoa
that referenced
this pull request
Aug 12, 2026
…a1); align regular-Message extras sizing with the published accounting Message-size accounting now matches Ably's published per-field rule: every plain string field and map key is measured as its UTF-8 byte length, while extras (and JSON-stringified object/array data) keep the documented "string length of its JSON representation" (UTF-16 code units). Sites changed: - ObjectMessage.swift: ObjectsMap.size (OMP4a1) key measurement utf16.count -> utf8.count, so map-state entry keys now match the MapCreate/MapSet/MapRemove operation keys; extras stays utf16.count; the string-measurement comment block is rewritten for the resolved convention. - ARTBaseMessage.m#messageSize (core regular Message): extras changed from UTF-8 byte length to NSString.length (UTF-16 string length) and object/array data from jsonData length (bytes) to jsonString length (UTF-16); clientId and string data stay UTF-8. Tests: +4 non-ASCII ObjectMessage tests (clientIdIsUTF8ByteLength OM3f, mapSetKeyIsUTF8ByteLength MST3c, objectsMapEntryKeyIsUTF8ByteLength OMP4a1, extrasIsUTF16StringLengthOfJSON OM3d); +1 core test test__027__ in UtilitiesTests covering name/clientId/string-data UTF-8 and extras/object-data UTF-16. Spec: ably/specification#516
sacOO7
added a commit
to ably/ably-java
that referenced
this pull request
Aug 12, 2026
…(OMP4a1) Message-size accounting matches Ably's published per-field rule: every plain string field and map key is measured as its UTF-8 byte length, while extras keeps the documented "string length of its JSON representation" (UTF-16 code units). Sites changed: - WireObjectMessage.kt: WireObjectsMap.size (OMP4a1) key measurement it.key.length -> it.key.byteSize, so map-state entry keys now match the MapCreate/MapSet/MapRemove operation keys; fixed a duplicated-// comment typo; corrected the WireObjectData json branch comment from OD3e to OD3g; extras keeps gson.toJson(it).length (UTF-16) now with an explanatory comment. Tests: +1 non-ASCII test testObjectMapStateEntryKeyUnicodeSizeIsUtf8 (OMP4a1). Spec: ably/specification#516
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.
Problem
The client-side publish-size gate (
RTO15dforObjectMessage,TO3l8/maxMessageSizeforMessage) sums per-field sizes and rejects an over-limit publish before it hits the wire. The spec told SDKs to measure string fields by their "length", but never said whether "length" means UTF-8 bytes or UTF-16 code units. For ASCII these coincide; for non-ASCII they diverge (你= 3 UTF-8 bytes / 1 UTF-16 code unit;😊= 4 UTF-8 bytes / 2 UTF-16 code units).The spec was also internally contradictory:
OM3d(extras): "the string length of its JSON representation" — reads as UTF-16 code units.OD3g(json): "the byte length of its JSON-encoded string representation" — explicitly bytes.OM3f/MCR3a1/MST3c/MRM3a/OMP4a1/OD3e/TM6f: bare "length" — undefined.Because "length" was undefined, the SDKs diverged, and the same map key or
clientIdproduced different gate results across SDKs. Concrete before-state (clientId = "émile", keys ="héllo👍", string data ="你好", extras ={"k":"你"}):clientId(OM3f)émilehéllo👍héllo👍你好{"k":"你"}This surfaces the concrete gate-flip: a boundary
ObjectMessagewhoseclientId/keys push it just over the limit is accepted by js (UTF-16 undercount) but rejected by cocoa/java (UTF-8) — the same publish, two verdicts. Two distinct defects:clientIdand operation keys (UTF-16) while cocoa/java counted them in UTF-8 bytes.MST3c, UTF-8) or via map state (OMP4a1, UTF-16).The authoritative definition already exists in Ably's published billing/limits accounting — How is maximum message size measured? — verbatim:
Note the deliberate asymmetry in the published contract:
name/clientId/stringdataare UTF-8 bytes, butextrasis string length of its JSON representation.Relationship to #331
This supersedes #331, the earlier attempt at the same disambiguation. This work substantially agrees with #331 — its conclusions and these changes converge on the same unit for every field both cover (string
data,name,clientId,OD3e→ UTF-8), and per-point coverage is documented below. Two deltas:extrasdeliberately follows the published docs (UTF-16 string length) where Message/PresenceMessage/Annotation size #331's discussion had converged on UTF-8. Measuringextrasin UTF-8 would over-count (bytes ≥ units) and false-reject documented-valid messages — the worse failure direction. Message/PresenceMessage/Annotation size #331's UTF-8-for-extras was a hedged consistency preference ("technically a bug ... not one that matters much"), with no authoritative server-side statement that the server byte-countsextras. The docs are the currently-published enforced contract.MCR3a1/MST3c/MRM3a/OMP4a1) andOD3g, which Message/PresenceMessage/Annotation size #331 predates, and uses a single canonical umbrella atTO3l8finstead of per-type duplication (addressing Message/PresenceMessage/Annotation size #331's "don't copy-paste size clauses per type" review feedback).#331 is now
CONFLICTING(the repo migratedtextile/features.textile→specifications/features.md, so its diff no longer applies). It is left open for the author to close; this PR notes the supersession.Solution
Match the service's published accounting per field. Every plain string and map key is measured in UTF-8 bytes;
extras(and the regular-Messageobject/arraydataJSON) keep the documented "string length" (UTF-16 code units).name(regular Message)clientId(regular + object)datadata/bytesObjectData.stringObjectData.jsonObjectData.numberObjectData.booleanextras(regular + object)data(regular Message)Per-clause edits to
specifications/features.md:TM6f(string data),OM3f(object clientId),MCR3a1,MST3c,MRM3a,OMP4a1(all map/operation keys),OD3e(string),OD3g(json — clarified "byte" → "UTF-8 byte").TM6b(object/array data),TM6dandOM3d(extras) — reworded from bare "string length" to "string length (the number of UTF-16 code units)".TO3l8f: one sentence establishing the default (all message-size string accounting is UTF-8 byte length unless a clause states otherwise, explicitly including thejsonOD3gpayload) and enumerating the two UTF-16 exceptions (extrasTM6d/OM3d and object/arraydataTM6b). Deliberately one umbrella rule rather than per-field/per-type duplication.Wording-only change; no test additions in this repo.
SDK status
objectmessage.ts(clientId/OM3f, OMP4a1, MCR3a1, MST3c, MRM3a →dataSizeBytes);message.tsgetMessageSize(name+clientId →Utils.dataSizeBytes)extrasstaysJSON.stringify(...).lengthObjectMessage.swiftObjectsMap.size(OMP4a1:utf16.count→utf8.count— the one behavioural change);ARTBaseMessage.mcore-Message alignmentextras/object-arraydataUTF-16feature/liveobjects-implementationWireObjectMessage.ktWireObjectsMap.size(OMP4a1:.length→.byteSize)extrasUTF-16refactor/uts-objects-unit-into-liveobjectsNotes
extras/UTF-16 rationale. The published accounting has an explicit rule forextras("string length of its JSON representation") distinct from the byte rule for other strings. Matching it exactly (UTF-16 code units) makes the gate neither false-accept nor false-rejectextrasrelative to the documented contract. UTF-8 would over-count and false-reject documented-valid messages. This is a deliberate documented exception, not an inconsistency; all three SDKs already agreed on UTF-16 here.UTF-8 byte length ≥ UTF-16 code-unit count(equality only for ASCII). Measuring a key in UTF-8 bytes equals the server's on-the-wire byte count, so the gate never false-accepts a key the server would reject. UTF-8 strictly dominates UTF-16 for keys.name/clientIdfor regularMessagehave no explicit per-field sub-clause inTM6; they are covered by theTO3l8fumbrella default (UTF-8).TP5(PresenceMessage) needed no edit — it already delegates wholesale toTM6, andRSAN1a4(annotations) delegates toTO3l8, so presence and annotation sizing inherit the rule with no separate edits.