|
ra8-firmware 0.1.0
Bare-metal firmware for the Renesas RA8 family (RA8D2 / RA8P1)
|
UTF-8 <-> UTF-16LE conversion and case folding for ra8_fs names. More...
Go to the source code of this file.
Enumerations | |
| enum | ra8_fs_utf_t : uint32_t { k_utf_ascii_max = 0x7FU , k_utf_cont_mask = 0xC0U , k_utf_cont_tag = 0x80U , k_utf_cont_payload = 0x3FU , k_utf_cont_shift = 6U , k_utf_lead2_mask = 0xE0U , k_utf_lead2_tag = 0xC0U , k_utf_lead2_payload = 0x1FU , k_utf_lead3_mask = 0xF0U , k_utf_lead3_tag = 0xE0U , k_utf_lead3_payload = 0x0FU , k_utf_lead4_mask = 0xF8U , k_utf_lead4_tag = 0xF0U , k_utf_lead4_payload = 0x07U , k_utf_len_1 = 1U , k_utf_len_2 = 2U , k_utf_len_3 = 3U , k_utf_len_4 = 4U , k_utf_min_2byte = 0x80U , k_utf_min_3byte = 0x800U , k_utf_min_4byte = 0x10000U , k_utf_sur_hi_first = 0xD800U , k_utf_sur_lo_first = 0xDC00U , k_utf_sur_last = 0xDFFFU , k_utf_sur_shift = 10U , k_utf_sur_mask = 0x3FFU , k_utf_unit_max = 0xFFFFU , k_utf_code_max = 0x10FFFFU , k_utf_byte_mask = 0xFFU , k_utf_byte_shift = 8U , k_utf8_max_per_unit = 3U } |
| Bit patterns, thresholds and shifts of the UTF-8 / UTF-16 encodings. More... | |
Functions | |
| ra8_err_t | priv_utf8_to_utf16 (const char *in, uint16_t *out, uint32_t cap, uint32_t *out_units) |
| Convert a NUL-terminated UTF-8 name into UTF-16LE code units. | |
| ra8_err_t | priv_utf16_to_utf8 (const uint16_t *in, uint32_t units, char *out, uint32_t cap) |
| Convert UTF-16LE code units into a NUL-terminated UTF-8 name. | |
| uint8_t | priv_utf16_ieq (const uint16_t *a, uint32_t an, const uint16_t *b, uint32_t bn) |
| Compare two UTF-16 names for case-insensitive equality. | |
| uint8_t | priv_utf16_all_ascii (const uint16_t *in, uint32_t units) |
Is every unit of in inside the ASCII range? | |
UTF-8 <-> UTF-16LE conversion and case folding for ra8_fs names.
Both on-disk name formats this adapter speaks store UTF-16LE: a VFAT long-name chain carries thirteen units per slot, and an exFAT file-name entry carries fifteen. The public API is UTF-8 char* and stays that way, so exactly one seam has to exist between the two – this one. It is a seam and not a scattering of casts on purpose: the three separate places that used to do their own byte-to-unit arithmetic each got it wrong in a different direction (#606), and a single conversion cannot disagree with itself.
The whole of Unicode that UTF-16 can express: the Basic Multilingual Plane in one unit, and the supplementary planes as a surrogate PAIR. A four-byte UTF-8 sequence therefore costs two UTF-16 units on disk and comes back as the same four bytes – NameLength on exFAT and the VFAT group count are unit counts, which is what the formats mean by "length" and what a host counts.
Everything a decoder may not silently accept, because accepting it is how a name stops round-tripping:
Each yields k_ra8_err_invalid_arg from the conversion, which every caller turns into a failed create / open rather than a mangled name. There is no substitution character anywhere in this module: ? in place of a code point is exactly the defect this file exists to remove.
The one asymmetry is deliberate. priv_utf16_to_utf8() can be handed an UNPAIRED surrogate, because that is a sequence of units that already exists on someone else's volume, and no UTF-8 string encodes it. It reports k_ra8_err_invalid_arg and the caller falls back to the entry's 8.3 alias, so the invariant "a name this library reports is a name it can re-open" holds even for a volume written by something that did not check.
priv_utf16_ieq() folds through priv_exfat_upcase_unit(), the canonical Microsoft up-case table this tree already embeds and writes at format time. That is the table the exFAT specification's name hash is defined against, so using it for FAT long names too means one fold serves both formats – and Resume.txt matching RESUME.TXT is the same mechanism as its accented sibling matching, rather than a second rule that only covers ASCII.
Definition in file ra8_fs_utf_internal.h.
| enum ra8_fs_utf_t : uint32_t |
Bit patterns, thresholds and shifts of the UTF-8 / UTF-16 encodings.
Named rather than inlined because every one of them appears in more than one direction of the codec: the mask that recognises a three-byte lead byte is the same mask that builds one. Unicode 15.0 Table 3-6 ("UTF-8 Bit Distribution") and Table 3-5 ("UTF-16 Bit Distribution") are the source for the layouts.
Definition at line 88 of file ra8_fs_utf_internal.h.
| uint8_t priv_utf16_all_ascii | ( | const uint16_t * | in, |
| uint32_t | units ) |
Is every unit of in inside the ASCII range?
The question a caller asks before it relies on the volume's up-case table: an ASCII-only name folds identically under every conforming table, so it is safe even when the volume carries one this build cannot reproduce, while a name with any other unit is not.
| [in] | in | Code units to inspect. |
| [in] | units | Number of units in in. |
| 1U | Every unit is at most k_utf_ascii_max (or units is 0). |
| 0U | At least one unit is above it. |
in addresses at least units readable units, or units is 0. in is not modified. Definition at line 543 of file ra8_fs_utf.c.
References k_utf_ascii_max.
Referenced by priv_exfat_name_to_units().
| uint8_t priv_utf16_ieq | ( | const uint16_t * | a, |
| uint32_t | an, | ||
| const uint16_t * | b, | ||
| uint32_t | bn ) |
Compare two UTF-16 names for case-insensitive equality.
Folds every unit through priv_exfat_upcase_unit() – the canonical up-case table – and compares unit by unit. Lengths must match first: the table is a simple one-to-one map, so folding never changes a name's length and a length difference is a difference.
Surrogate units fold to themselves, because the table covers the BMP and a supplementary code point has no simple case mapping inside it. Two supplementary characters therefore compare exactly, which is what a host does with the same table.
| [in] | a | First name's units. |
| [in] | an | Number of units in a. |
| [in] | b | Second name's units. |
| [in] | bn | Number of units in b. |
| 1U | The names are equal after folding. |
| 0U | They differ in length or in at least one folded unit. |
a addresses an units and b addresses bn units. Definition at line 529 of file ra8_fs_utf.c.
References priv_exfat_upcase_unit().
Referenced by internal_dir_find_long_sector().
| ra8_err_t priv_utf16_to_utf8 | ( | const uint16_t * | in, |
| uint32_t | units, | ||
| char * | out, | ||
| uint32_t | cap ) |
Convert UTF-16LE code units into a NUL-terminated UTF-8 name.
The inverse of priv_utf8_to_utf16(): a high surrogate followed by a low one becomes the four-byte form of the supplementary code point, every other unit becomes its one-, two- or three-byte form.
An unpaired surrogate – a high one not followed by a low one, or a low one on its own – is a name no UTF-8 string can express, so it is reported rather than substituted. The caller's fallback (the 8.3 alias on FAT) is a name that still opens the same file, which a replacement character would not be.
| [in] | in | Code units to convert. |
| [in] | units | Number of units in in. |
| [out] | out | Receives the NUL-terminated UTF-8 name. |
| [in] | cap | Capacity of out in bytes, including the terminator. |
| k_ra8_ok | Converted; out is NUL-terminated. |
| k_ra8_err_null_ptr | in or out is NULL, or cap is 0. |
| k_ra8_err_invalid_arg | in holds an unpaired surrogate. |
| k_ra8_err_no_mem | The name plus its terminator exceeds cap. |
out addresses at least cap writable bytes. in addresses at least units readable code units. out holds well-formed UTF-8 and a NUL terminator. out[0] is NUL, so a caller that ignores the code sees an empty name rather than a partial one.out; trivially thread-safe against distinct buffers.Definition at line 499 of file ra8_fs_utf.c.
References internal_utf16_take(), internal_utf8_put(), k_ra8_err_null_ptr, and k_ra8_ok.
Referenced by internal_exfat_gather_name(), internal_fat_dir_scan_sector(), and internal_listdir_visit_sector().
| ra8_err_t priv_utf8_to_utf16 | ( | const char * | in, |
| uint16_t * | out, | ||
| uint32_t | cap, | ||
| uint32_t * | out_units ) |
Convert a NUL-terminated UTF-8 name into UTF-16LE code units.
Decodes each UTF-8 sequence to a code point, rejecting every malformed and non-shortest form (see the file header), then emits it as one BMP unit or as a high/low surrogate pair. The output is NOT NUL-terminated: a UTF-16 name on either format carries an explicit length, and appending a terminator here would invite a caller to use it as one.
The whole conversion is transactional in the sense that matters: on any failure out_units is set to zero, so a caller that ignores the return code writes an empty name rather than a truncated one.
| [in] | in | NUL-terminated UTF-8 name. |
| [out] | out | Receives the code units. |
| [in] | cap | Capacity of out in UTF-16 units. |
| [out] | out_units | Receives the number of units written. |
| k_ra8_ok | Converted; out holds *out_units units. |
| k_ra8_err_null_ptr | in, out or out_units is NULL. |
| k_ra8_err_invalid_arg | in is not well-formed UTF-8. |
| k_ra8_err_no_mem | The name needs more than cap units. |
out addresses at least cap writable units. in is NUL-terminated within the caller's buffer. cap. out holds nothing meaningful.out; trivially thread-safe against distinct buffers.Definition at line 283 of file ra8_fs_utf.c.
References internal_utf16_put(), internal_utf8_next(), k_ra8_err_no_mem, k_ra8_err_null_ptr, and k_ra8_ok.
Referenced by priv_dir_find_long(), priv_exfat_name_to_units(), and priv_name_classify().