Skip to main content

WstringUnitOrder

Enum WstringUnitOrder 

Source
pub enum WstringUnitOrder {
    Stream,
    BigEndianAlways,
    ByteOrderMark,
    HostUnswapped,
}
Expand description

In what byte order a wstring’s code units go out, and whether a byte-order mark travels with them.

One axis and one decision, because the two halves are one: an implementation that writes a mark reads the units by it, and one that writes no mark has to have a rule for a stream that carries none.

§What was measured

The write direction, 2026-08-10 on the Linux test host, @final struct Sample { wstring w; octet tail; } with w = "a𝄞b":

little-endian stream
  zerodds  08000000      6100 34d8 1edd 6200  ab
  omniorb  0a000000 fffe 6100 34d8 1edd 6200  ab
big-endian stream
  zerodds  00000008      0061 d834 dd1e 0062  ab
  omniorb  00000010 fffe e400 …                     ("äöü ሴ Z", the long case)

omniORB swaps the length word into the stream’s order and leaves the mark and the content alone, always little-endian on this x86-64 host. In a big-endian stream its mark therefore reads as U+FFFE, the byte-swapped mark, which is precisely the signal that the units that follow are opposite to the stream — so omniORB’s output is self-describing where Cyclone’s identically-ordered output (WstringLengthPolicy::UnswappedUnits) is not.

The read direction, from docs/corba-column.md’s array-1d-wstring run over a little-endian stream carrying no mark:

zerodds  {"items":["","äZ","ሴ"]}
omniorb  {"items":["","娀","㐒"]}
jacorb   {"items":["","娀","㐒"]}

Both ORBs decode the units as UTF-16 big-endian whatever the stream says. That is not a defect in either: XTypes §7.4.1.1.2 makes the units the stream’s through ESWAP (row A10 of the inventory), and the OMG code-set rule for a GIOP 1.2 wstring makes them UTF-16’s own — big-endian in the absence of a mark. Each side is self-consistent and they cannot talk, and the failure is the silent kind: a wstring comes back well formed, of the right length, and holding different characters.

§Why this is a second setting and not a fifth WstringLengthPolicy

Because it crosses the length convention rather than extending it. omniORB’s mark rides on the octet count of WstringLengthPolicy::SPECIFICATION; nothing stops a GIOP 1.1 count from carrying one too. Folding the order into the length enum is what produced WstringLengthPolicy::UnswappedUnits, which is this axis’s little-endian point wearing a length policy’s name, and doing it again would give eight variants for two questions.

WstringLengthPolicy::UnswappedUnits therefore stays where it shipped, and the pair (UnswappedUnits, anything but [Self::Stream]) is refused rather than resolved — see WstringUnitOrder::conflicts_with_length, which is retired. Two settings that both decide one byte order, with a precedence rule between them, is the shape that produces a wrong character and no error.

Variants§

§

Stream

The stream’s own byte order, no mark.

XTypes 1.3 §7.4.1.1.2 through ESWAP, and what Cyclone, RTI and Fast DDS write in little endian and RTI and Fast DDS write in big. The default.

§

BigEndianAlways

UTF-16 big-endian whatever the stream says, no mark.

What omniORB and JacORB decode, and the CORBA rule for a GIOP 1.2 wstring that carries no mark. Reachable as --wstring-units big-endian-always and selected by Dialect::JacOrb.

JacORB’s write direction cannot tell this apart from Self::Stream: org.jacorb.orb.CDROutputStream has no setLittleEndian, so every JacORB byte string measured here is big-endian and the two forms coincide in it. What is measured is JacORB’s reader, which takes the units big-endian out of a little-endian stream — so this is the form a JacORB peer reads correctly, which is the half that decides what a writer should do. Recorded as an extrapolation from the read direction, in the place a reader of Dialect::JacOrb will look for it.

§

ByteOrderMark

A byte-order mark, then the units, both little-endian.

What omniORB 4.3.3 writes. The mark is U+FEFF, its two octets are counted in the length, and it is not a character of the string.

On read the mark decides: FEFF in the stream’s order means the units follow in the stream’s order, FFFE means they are opposite, and no mark at all means big-endian — the CORBA rule, and the one that makes this variant read a Self::BigEndianAlways peer as well as an omniORB one.

On write the mark and the units are little-endian, because that is what was measured, on x86-64, in both stream orders. “Little-endian” and not “the host’s order” for the reason WstringLengthPolicy::UnswappedUnits gives at more length: a big-endian host running omniORB would presumably write big-endian units and a plain FEFF, and that is not something a peer on a little-endian host can produce or this project can measure.

XTypes §7.4.1.1.2 forbids the mark outright — “The representation shall also not include any … byte-order mark” — so no dialect of a DDS implementation selects this and the default never will.

§

HostUnswapped

The units always little-endian, whatever the stream says, and no mark.

This point reproduces a peer misreading its own output, and it is on this axis because it is a byte order. It arrived as WstringLengthPolicy::UnswappedUnits — a byte order wearing a length policy’s name — and judgement 2 of docs/wire-axes-followup.md moved it here, which also retired the (UnswappedUnits, non-Stream) conflict rule: two settings that both decide one byte order, with a precedence between them, is the shape that produces a wrong character and no error. One axis cannot conflict with itself.

Cyclone swaps the length word for a big-endian stream and does not swap the content, so its big-endian "ab" leaves as 00000004 61006200 where RTI and Fast DDS write 00000004 00610062. Read as the big-endian stream the encapsulation identifier says it is, Cyclone’s "ab" is U+6100 U+6200 — two different characters, well-formed UTF-16, the right length, and nothing in the frame saying so. Cyclone reads its own bytes back correctly because it unswaps them the same way; every conforming peer gets the other string.

rosidl_typesupport_fastrtps does the same, measured 2026-08-12: wstring-unbounded-utf8 big endian is 00000007 e4000000 …, a big-endian length over little-endian four-octet units. Two implementations, and the second one is what makes this a point on the axis rather than one product’s defect.

In little endian it is byte for byte Self::Stream, which is why the divergence took a big-endian run to find.

“Little-endian” and not “the host’s order” because that is what was measured: Cyclone memcpys its native UTF-16 buffer, and every run is on x86-64. A big-endian host would presumably write big-endian units and be byte-identical to Self::Stream there — unmeasured, and not something a peer on a little-endian host can produce.

No Dialect takes it as a default, including Dialect::Cyclone: a default has to be the form a conforming reader gets right. It exists because refusing to model it means nobody can exchange a wstring with Cyclone over big endian at all, and “we decline to talk to that peer” is not a wire question the project gets to leave unanswered.

Implementations§

Source§

impl WstringUnitOrder

Source

pub const ALL: [Self; 4]

Every order.

Source

pub const fn is_default(self) -> bool

Whether this is the default.

Source

pub const fn as_str(self) -> &'static str

Short name, for configuration and diagnostics.

Source

pub const fn has_mark(self) -> bool

Whether a mark is written, and expected on read.

Source

pub const fn writes_little_endian_units(self, _position: WirePosition) -> bool

In what byte order one code unit goes out.

Takes the position for the reason WstringLengthPolicy’s methods do. Measured to agree at the member and the collection element in all nine implementations.

Source

pub fn parse(value: &str) -> Option<Self>

Parse a configuration value.

An unknown value returns None rather than falling back to the default: a typo in a wire-affecting setting must be loud.

Trait Implementations§

Source§

impl Clone for WstringUnitOrder

Source§

fn clone(&self) -> Self

Returns a duplicate of the value. Read more
1.0.0 (const: unstable) · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl Copy for WstringUnitOrder

Source§

impl Debug for WstringUnitOrder

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl Default for WstringUnitOrder

Source§

fn default() -> Self

Returns the “default value” for a type. Read more
Source§

impl Eq for WstringUnitOrder

Source§

impl Hash for WstringUnitOrder

Source§

fn hash<__H: Hasher>(&self, state: &mut __H)

Feeds this value into the given Hasher. Read more
1.3.0 · Source§

fn hash_slice<H>(data: &[Self], state: &mut H)
where H: Hasher, Self: Sized,

Feeds a slice of this type into the given Hasher. Read more
Source§

impl Ord for WstringUnitOrder

Source§

fn cmp(&self, other: &Self) -> Ordering

This method returns an Ordering between self and other. Read more
1.21.0 (const: unstable) · Source§

fn max(self, other: Self) -> Self
where Self: Sized,

Compares and returns the maximum of two values. Read more
1.21.0 (const: unstable) · Source§

fn min(self, other: Self) -> Self
where Self: Sized,

Compares and returns the minimum of two values. Read more
1.50.0 (const: unstable) · Source§

fn clamp(self, min: Self, max: Self) -> Self
where Self: Sized,

Restrict a value to a certain interval. Read more
Source§

fn clamp_to<R>(self, range: R) -> Self
where Self: Sized, R: ClampBounds<Self>,

🔬This is a nightly-only experimental API. (clamp_to)
Restrict a value to a certain range. Read more
Source§

impl PartialEq for WstringUnitOrder

Source§

fn eq(&self, other: &Self) -> bool

Equality operator ==. Read more
1.0.0 (const: unstable) · Source§

fn ne(&self, other: &Rhs) -> bool

Inequality operator !=. Read more
Source§

impl PartialOrd for WstringUnitOrder

Source§

fn partial_cmp(&self, other: &Self) -> Option<Ordering>

This method returns an ordering between self and other values if one exists. Read more
1.0.0 (const: unstable) · Source§

fn lt(&self, other: &Rhs) -> bool

Tests less than (for self and other) and is used by the < operator. Read more
1.0.0 (const: unstable) · Source§

fn le(&self, other: &Rhs) -> bool

Tests less than or equal to (for self and other) and is used by the <= operator. Read more
1.0.0 (const: unstable) · Source§

fn gt(&self, other: &Rhs) -> bool

Tests greater than (for self and other) and is used by the > operator. Read more
1.0.0 (const: unstable) · Source§

fn ge(&self, other: &Rhs) -> bool

Tests greater than or equal to (for self and other) and is used by the >= operator. Read more
Source§

impl StructuralPartialEq for WstringUnitOrder

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> CloneToUninit for T
where T: Clone,

Source§

unsafe fn clone_to_uninit(&self, dest: *mut u8)

🔬This is a nightly-only experimental API. (clone_to_uninit)
Performs copy-assignment from self to dest. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T> ToOwned for T
where T: Clone,

Source§

type Owned = T

The resulting type after obtaining ownership.
Source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
Source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = !

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, !>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.