Skip to main content

fidl_next_codec/wire/string/
optional.rs

1// Copyright 2024 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5use core::fmt;
6use core::mem::MaybeUninit;
7use core::str::from_utf8;
8
9use munge::munge;
10
11use crate::{
12    Constrained, Decode, DecodeError, Decoder, EncodeError, EncodeOption, Encoder, FromWireOption,
13    FromWireOptionRef, IntoNatural, Slot, ValidationError, Wire, wire,
14};
15
16/// An optional FIDL string
17#[repr(transparent)]
18pub struct OptionalString<'de> {
19    vec: wire::OptionalVector<'de, u8>,
20}
21
22// SAFETY: `OptionalString` is a `#[repr(transparent)]` wrapper around
23// `wire::OptionalVector<'static, u8>`, which is `Wire`.
24unsafe impl Wire for OptionalString<'static> {
25    type Narrowed<'de> = OptionalString<'de>;
26
27    #[inline]
28    fn zero_padding(out: &mut MaybeUninit<Self>) {
29        munge!(let Self { vec } = out);
30        wire::OptionalVector::<u8>::zero_padding(vec);
31    }
32}
33
34impl OptionalString<'_> {
35    /// Encodes that a string is present in a slot.
36    #[inline]
37    pub fn encode_present(out: &mut MaybeUninit<Self>, len: u64) {
38        munge!(let Self { vec } = out);
39        wire::OptionalVector::encode_present(vec, len);
40    }
41
42    /// Encodes that a string is absent in a slot.
43    #[inline]
44    pub fn encode_absent(out: &mut MaybeUninit<Self>) {
45        munge!(let Self { vec } = out);
46        wire::OptionalVector::encode_absent(vec);
47    }
48
49    /// Returns whether the optional string is present.
50    #[inline]
51    pub fn is_some(&self) -> bool {
52        self.vec.is_some()
53    }
54
55    /// Returns whether the optional string is absent.
56    #[inline]
57    pub fn is_none(&self) -> bool {
58        self.vec.is_none()
59    }
60
61    /// Returns a reference to the underlying string, if any.
62    #[inline]
63    pub fn as_ref(&self) -> Option<&wire::String<'_>> {
64        // SAFETY: `wire::String` is a `#[repr(transparent)]` wrapper around `wire::Vector<u8>`.
65        // Casting the pointer is safe because they have the same layout.
66        self.vec.as_ref().map(|vec| unsafe { &*(vec as *const wire::Vector<'_, u8>).cast() })
67    }
68
69    /// Validate that this string's length falls within the limit.
70    fn validate_max_len(slot: Slot<'_, Self>, limit: u64) -> Result<(), ValidationError> {
71        munge!(let Self { vec } = slot);
72        match wire::OptionalVector::validate_max_len(vec, limit) {
73            Ok(()) => Ok(()),
74            Err(ValidationError::VectorTooLong { count, limit }) => {
75                Err(ValidationError::StringTooLong { count, limit })
76            }
77            Err(e) => Err(e),
78        }
79    }
80}
81
82impl Constrained for OptionalString<'_> {
83    type Constraint = u64;
84
85    fn validate(slot: Slot<'_, Self>, constraint: Self::Constraint) -> Result<(), ValidationError> {
86        Self::validate_max_len(slot, constraint)
87    }
88}
89
90impl fmt::Debug for OptionalString<'_> {
91    #[inline]
92    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
93        self.as_ref().fmt(f)
94    }
95}
96
97impl<T> PartialEq<Option<T>> for OptionalString<'_>
98where
99    for<'de> wire::String<'de>: PartialEq<T>,
100{
101    fn eq(&self, other: &Option<T>) -> bool {
102        match (self.as_ref(), other.as_ref()) {
103            (Some(lhs), Some(rhs)) => lhs == rhs,
104            (None, None) => true,
105            _ => false,
106        }
107    }
108}
109
110// SAFETY: If `decode` returns `Ok`, `slot` is guaranteed to contain a valid decoded
111// `OptionalString` because it delegates to `wire::OptionalVector::decode_raw` and validates that
112// the decoded bytes (if present) are valid UTF-8.
113unsafe impl<'de, D: Decoder<'de> + ?Sized> Decode<D> for OptionalString<'de> {
114    #[inline]
115    fn decode(slot: Slot<'_, Self>, decoder: &mut D, constraint: u64) -> Result<(), DecodeError> {
116        munge!(let Self { mut vec } = slot);
117
118        // SAFETY: `vec` is a valid slot for `OptionalVector`.
119        let result = unsafe { wire::OptionalVector::decode_raw(vec.as_mut(), decoder, constraint) };
120        match result {
121            Ok(()) => (),
122            Err(DecodeError::Validation(ValidationError::VectorTooLong { count, limit })) => {
123                return Err(DecodeError::Validation(ValidationError::StringTooLong {
124                    count,
125                    limit,
126                }));
127            }
128            Err(e) => return Err(e),
129        }
130        // SAFETY: `decode_raw` succeeded, so the slot contents are valid.
131        let vec = unsafe { vec.deref_unchecked() };
132        if let Some(bytes) = vec.as_ref() {
133            // Check if the string is valid ASCII (fast path)
134            if !bytes.as_slice().is_ascii() {
135                // Fall back to checking if the string is valid UTF-8 (slow path)
136                // We're using `from_utf8` more like an `is_utf8` here.
137                let _ = from_utf8(bytes)?;
138            }
139        }
140
141        Ok(())
142    }
143}
144
145// SAFETY: Delegates to `<&str>::encode_option` which is safe and fully initializes the output.
146unsafe impl<E: Encoder + ?Sized> EncodeOption<OptionalString<'static>, E> for String {
147    #[inline]
148    fn encode_option(
149        this: Option<Self>,
150        encoder: &mut E,
151        out: &mut MaybeUninit<OptionalString<'static>>,
152        constraint: u64,
153    ) -> Result<(), EncodeError> {
154        <&str>::encode_option(this.as_deref(), encoder, out, constraint)
155    }
156}
157
158// SAFETY: Delegates to `<&str>::encode_option` which is safe and fully initializes the output.
159unsafe impl<E: Encoder + ?Sized> EncodeOption<OptionalString<'static>, E> for &String {
160    #[inline]
161    fn encode_option(
162        this: Option<Self>,
163        encoder: &mut E,
164        out: &mut MaybeUninit<OptionalString<'static>>,
165        constraint: u64,
166    ) -> Result<(), EncodeError> {
167        <&str>::encode_option(this.map(String::as_str), encoder, out, constraint)
168    }
169}
170
171// SAFETY: `OptionalString` has no padding. `encode_option` initializes the output fully
172// by calling either `OptionalString::encode_present` or `OptionalString::encode_absent`.
173unsafe impl<E: Encoder + ?Sized> EncodeOption<OptionalString<'static>, E> for &str {
174    #[inline]
175    fn encode_option(
176        this: Option<Self>,
177        encoder: &mut E,
178        out: &mut MaybeUninit<OptionalString<'static>>,
179        _constraint: u64,
180    ) -> Result<(), EncodeError> {
181        if let Some(string) = this {
182            encoder.write(string.as_bytes());
183            OptionalString::encode_present(out, string.len() as u64);
184        } else {
185            OptionalString::encode_absent(out);
186        }
187        Ok(())
188    }
189}
190
191impl FromWireOption<OptionalString<'_>> for String {
192    #[inline]
193    fn from_wire_option(wire: OptionalString<'_>) -> Option<Self> {
194        // SAFETY: The bytes in a decoded `OptionalString` are validated to be valid UTF-8.
195        Vec::from_wire_option(wire.vec).map(|vec| unsafe { String::from_utf8_unchecked(vec) })
196    }
197}
198
199impl IntoNatural for OptionalString<'_> {
200    type Natural = Option<String>;
201}
202
203impl FromWireOptionRef<OptionalString<'_>> for String {
204    #[inline]
205    fn from_wire_option_ref(wire: &OptionalString<'_>) -> Option<Self> {
206        // SAFETY: The bytes in a decoded `OptionalString` are validated to be valid UTF-8.
207        Vec::from_wire_option_ref(&wire.vec).map(|vec| unsafe { String::from_utf8_unchecked(vec) })
208    }
209}
210
211#[cfg(test)]
212mod tests {
213    use crate::{DecoderExt as _, EncoderExt as _, chunks, wire};
214
215    #[test]
216    fn decode_optional_string() {
217        assert_eq!(
218            chunks![
219                0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
220                0x00, 0x00,
221            ]
222            .as_mut_slice()
223            .decode_with_constraint::<wire::OptionalString<'_>>(1000)
224            .unwrap(),
225            None::<&str>,
226        );
227    }
228
229    #[test]
230    fn encode_optional_string() {
231        assert_eq!(
232            Vec::encode_with_constraint(None::<String>, 1000).unwrap(),
233            chunks![
234                0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
235                0x00, 0x00,
236            ],
237        );
238    }
239}