Skip to content

fix: escape/unescape UTF-8 in FAST string codec#4

Merged
ruoka merged 10 commits into
masterfrom
cursor/fson-escape-unescape-e9fa
Jul 17, 2026
Merged

fix: escape/unescape UTF-8 in FAST string codec#4
ruoka merged 10 commits into
masterfrom
cursor/fson-escape-unescape-e9fa

Conversation

@ruoka

@ruoka ruoka commented Jul 17, 2026

Copy link
Copy Markdown
Owner

Summary

FSON string/name payloads used the FAST 7-bit terminator codec (last_byte | 0x80 / clear bit 7 on decode), which corrupts UTF-8 and can desynchronize the stream.

Approach: fold high-bit handling into xson:fast string encode/decode (no temporary escaped string, no needs_escape pre-scan):

  • Encode: one pass — ASCII bytes emit as-is; only high-bit / U+0001 bytes take the ESC + two hex digits branch; terminator bit on the final emitted byte
  • Decode: terminator read, then unescape in place only if an escape marker was seen
  • Wire path typed as byte (std::uint8_t) with named literals (escape_marker, stop_bit, …) and put_byte / to_char / to_byte at the char boundary
  • FSON string/name call sites stay fast::encode / fast::decode
  • Ordinary ASCII without U+0001 stays bit-identical on the wire

Validation

  • ./tools/CB.sh debug test "String|UTF-8|escape" → 49/49 passed
Open in Web Open in Cursor 

cursoragent and others added 5 commits July 17, 2026 12:10
Legacy fast string encoding ORs 0x80 onto the final byte and clears bit 7
on decode, so any UTF-8 payload (e.g. "café") truncates, mutates, and
desynchronizes the FSON stream — bricking YarDB files after create.

Keep the ASCII terminator codec for existing databases; emit length-
prefixed utf8 / name_utf8 tags for high-bit content.
Replace encode-time utf8/name_utf8 tags with Escape/unEscape so high-bit
content rides the existing string/name types through the 7-bit FAST
terminator codec. Ordinary ASCII stays bit-identical on the wire; the
decoder still accepts the intermediate length-prefixed utf8 tags.

Co-authored-by: Kaius  Ruokonen <ruoka@users.noreply.github.com>
Drop utf8/name_utf8 tags and length-prefixed encode_bytes. The fast
string codec stays the original 7-bit terminator write/read; FSON
Escape/unEscape alone make UTF-8 safe for that path.

Co-authored-by: Kaius  Ruokonen <ruoka@users.noreply.github.com>
Follow project naming: snake_case for all functions.

Co-authored-by: Kaius  Ruokonen <ruoka@users.noreply.github.com>
@cursor cursor Bot changed the title fix: Escape/unEscape UTF-8 in FSON encoder and decoder fix: escape/unescape UTF-8 in FSON encoder and decoder Jul 17, 2026
cursoragent and others added 3 commits July 17, 2026 20:10
Move escape/unescape into xson:fast so ASCII strings keep the original
no-temporary path. High-bit content is streamed on write and unescaped
in place on read; FSON string/name handling is unchanged at the call site.

Co-authored-by: Kaius  Ruokonen <ruoka@users.noreply.github.com>
Co-authored-by: Kaius  Ruokonen <ruoka@users.noreply.github.com>
Co-authored-by: Kaius  Ruokonen <ruoka@users.noreply.github.com>
@cursor cursor Bot changed the title fix: escape/unescape UTF-8 in FSON encoder and decoder fix: escape/unescape UTF-8 in FAST string codec Jul 17, 2026
cursoragent and others added 2 commits July 17, 2026 20:23
One loop emits ASCII as-is and only takes the ESC+hex branch for
high-bit / escape-marker bytes.

Co-authored-by: Kaius  Ruokonen <ruoka@users.noreply.github.com>
Use std::uint8_t (byte), named literals, and put_byte/to_char/to_byte so
high-bit math stays unsigned without static_cast noise at every use site.

Co-authored-by: Kaius  Ruokonen <ruoka@users.noreply.github.com>
@ruoka
ruoka marked this pull request as ready for review July 17, 2026 20:44
@ruoka
ruoka merged commit ca71cca into master Jul 17, 2026
4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants