protobuf_native/
internal.rs

1// Copyright Materialize, Inc. All rights reserved.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License in the LICENSE file at the
6// root of this repository, or online at
7//
8//     http://www.apache.org/licenses/LICENSE-2.0
9//
10// Unless required by applicable law or agreed to in writing, software
11// distributed under the License is distributed on an "AS IS" BASIS,
12// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13// See the License for the specific language governing permissions and
14// limitations under the License.
15
16#[cfg(unix)]
17use std::ffi::OsStr;
18use std::fmt;
19use std::io::{Read, Write};
20use std::marker::PhantomData;
21use std::mem::MaybeUninit;
22use std::os::raw::{c_char, c_int, c_void};
23#[cfg(unix)]
24use std::os::unix::prelude::OsStrExt;
25use std::path::Path;
26
27use cxx::kind::Trivial;
28use cxx::{type_id, ExternType};
29
30use crate::OperationFailedError;
31
32// Pollyfill C++ APIs that aren't yet in cxx.
33// See: https://github.com/dtolnay/cxx/pull/984
34// See: https://github.com/dtolnay/cxx/pull/990
35
36#[cxx::bridge]
37mod ffi {
38    extern "Rust" {
39        unsafe fn vec_u8_set_len(v: &mut Vec<u8>, new_len: usize);
40    }
41
42    unsafe extern "C++" {
43        include!("protobuf-native/src/internal.h");
44
45        #[namespace = "absl"]
46        #[cxx_name = "string_view"]
47        type StringView<'a> = crate::internal::StringView<'a>;
48
49        #[namespace = "protobuf_native::internal"]
50        fn string_view_from_bytes(bytes: &[u8]) -> StringView;
51    }
52}
53
54unsafe fn vec_u8_set_len(v: &mut Vec<u8>, new_len: usize) {
55    v.set_len(new_len)
56}
57
58#[derive(Debug, Copy, Clone)]
59#[repr(C)]
60pub struct StringView<'a> {
61    repr: MaybeUninit<[*const c_void; 2]>,
62    borrow: PhantomData<&'a [c_char]>,
63}
64
65impl<'a> From<&'a str> for StringView<'a> {
66    fn from(s: &'a str) -> StringView<'a> {
67        ffi::string_view_from_bytes(s.as_bytes())
68    }
69}
70
71impl<'a> From<ProtobufPath<'a>> for StringView<'a> {
72    fn from(path: ProtobufPath<'a>) -> StringView<'a> {
73        ffi::string_view_from_bytes(path.as_bytes())
74    }
75}
76
77unsafe impl<'a> ExternType for StringView<'a> {
78    type Id = type_id!("absl::string_view");
79    type Kind = Trivial;
80}
81
82// Variable-width integer types.
83// See: https://github.com/google/autocxx/issues/422#issuecomment-826987408
84
85#[derive(Debug, Clone, Copy, Eq, PartialEq, Ord, PartialOrd, Hash)]
86pub struct CInt(pub c_int);
87
88impl CInt {
89    pub fn to_usize(self) -> Result<usize, OperationFailedError> {
90        usize::try_from(self.0).map_err(|_| OperationFailedError)
91    }
92
93    pub fn expect_usize(self) -> usize {
94        match self.to_usize() {
95            Ok(n) => n,
96            Err(_) => panic!("C int is not representible as a Rust usize: {}", self.0),
97        }
98    }
99
100    pub fn try_from<T>(value: T) -> Result<CInt, T::Error>
101    where
102        T: TryInto<c_int>,
103    {
104        value.try_into().map(CInt)
105    }
106
107    pub fn expect_from<T>(value: T) -> CInt
108    where
109        T: TryInto<c_int> + Copy + fmt::Display,
110    {
111        match CInt::try_from(value) {
112            Ok(n) => n,
113            Err(_) => panic!("value is not representable as a C int: {}", value),
114        }
115    }
116}
117
118unsafe impl ExternType for CInt {
119    type Id = type_id!("protobuf_native::internal::CInt");
120    type Kind = Trivial;
121}
122
123#[derive(Debug)]
124pub struct CVoid(pub c_void);
125
126unsafe impl ExternType for CVoid {
127    type Id = type_id!("protobuf_native::internal::CVoid");
128    type Kind = Trivial;
129}
130
131// `Read` and `Write` adaptors for C++.
132
133pub struct ReadAdaptor<'a>(pub &'a mut dyn Read);
134
135impl ReadAdaptor<'_> {
136    pub fn read(&mut self, buf: &mut [u8]) -> isize {
137        match self.0.read(buf) {
138            Ok(n) => n.try_into().expect("read bytes do not fit into isize"),
139            Err(_) => -1,
140        }
141    }
142}
143
144pub struct WriteAdaptor<'a>(pub &'a mut dyn Write);
145
146impl WriteAdaptor<'_> {
147    pub fn write(&mut self, buf: &[u8]) -> bool {
148        self.0.write_all(buf).as_status()
149    }
150}
151
152/// Extensions to [`Result`].
153pub trait ResultExt {
154    /// Converts this result into a status boolean.
155    ///
156    /// If the result is `Ok`, returns true. If the result is `Err`, returns
157    /// false.
158    fn as_status(&self) -> bool;
159}
160
161impl<T, E> ResultExt for Result<T, E> {
162    fn as_status(&self) -> bool {
163        match self {
164            Ok(_) => true,
165            Err(_) => false,
166        }
167    }
168}
169
170/// Extensions to [`bool`].
171pub trait BoolExt {
172    /// Converts this status boolean into a result.
173    ///
174    /// If the status boolean is true, returns `Ok`. If the status boolean is
175    /// false, returns `Err`.
176    fn as_result(self) -> Result<(), OperationFailedError>;
177}
178
179impl BoolExt for bool {
180    fn as_result(self) -> Result<(), OperationFailedError> {
181        match self {
182            true => Ok(()),
183            false => Err(OperationFailedError),
184        }
185    }
186}
187
188/// An adapter for passing paths to `libprotobuf`.
189///
190/// On Unix, the bytes in a path can be passed directly.
191///
192/// On Windows, the situation is complicated. Protobuf assumes paths are UTF-8
193/// and converts them to wide-character strings before passing them to the
194/// underlying Windows wide-char APIs. But paths in Rust might not valid UTF-8.
195/// There's not much we can do to handle invalid UTF-8 correctly; we just throw
196/// `to_string_lossy` at the problem and hope `libprotobuf` sorts it out.
197///
198/// The point is to make this correct and performant on Unix in all cases, and
199/// correct in Windows as long as the path is valid UTF-8.
200#[cfg(unix)]
201#[derive(Debug, Clone, Eq, PartialEq, Hash)]
202pub struct ProtobufPath<'a>(&'a Path);
203
204#[cfg(windows)]
205#[derive(Debug, Clone, Eq, PartialEq, Hash)]
206pub struct ProtobufPath<'a> {
207    inner: Vec<u8>,
208    _phantom: PhantomData<'a>,
209}
210
211#[cfg(unix)]
212impl<'a> ProtobufPath<'a> {
213    pub fn as_path(&self) -> impl AsRef<Path> + 'a {
214        self.0
215    }
216}
217
218#[cfg(unix)]
219impl<'a> From<&'a [u8]> for ProtobufPath<'a> {
220    fn from(p: &'a [u8]) -> ProtobufPath<'a> {
221        ProtobufPath(Path::new(OsStr::from_bytes(p)))
222    }
223}
224
225#[cfg(unix)]
226impl<'a> From<&'a Path> for ProtobufPath<'a> {
227    fn from(p: &'a Path) -> ProtobufPath<'a> {
228        ProtobufPath(p)
229    }
230}
231
232#[cfg(unix)]
233impl<'a> ProtobufPath<'a> {
234    pub fn as_bytes(&self) -> &'a [u8] {
235        self.0.as_os_str().as_bytes()
236    }
237}
238
239#[cfg(windows)]
240impl<'a> ProtobufPath<'a> {
241    pub fn as_path(&self) -> impl AsRef<Path> {
242        PathBuf::from(String::from_utf8_lossy(self.inner))
243    }
244}
245
246#[cfg(windows)]
247impl<'a> From<&'a [u8]> for ProtobufPath<'static> {
248    fn from(p: &'a [u8]) -> ProtobufPath<'static> {
249        ProtobufPath {
250            inner: p.to_vec(),
251            _phantom: PhantomData,
252        }
253    }
254}
255
256#[cfg(windows)]
257impl<'a> From<Path> for ProtobufPath<'a> {
258    fn from(p: Path) -> ProtobufPath<'a> {
259        ProtobufPath {
260            inner: p.to_string_lossy().into_owned().into_bytes(),
261            _phantom: PhantomData,
262        }
263    }
264}
265
266#[cfg(windows)]
267impl<'a> ProtobufPath<'a> {
268    pub fn as_bytes(&self) -> &'a [u8] {
269        &self.inner
270    }
271}
272
273macro_rules! unsafe_ffi_conversions {
274    ($ty:ty) => {
275        #[allow(dead_code)]
276        pub(crate) unsafe fn from_ffi_owned(from: *mut $ty) -> Pin<Box<Self>> {
277            std::mem::transmute(from)
278        }
279
280        #[allow(dead_code)]
281        pub(crate) unsafe fn from_ffi_ptr<'_a>(from: *const $ty) -> &'_a Self {
282            std::mem::transmute(from)
283        }
284
285        #[allow(dead_code)]
286        pub(crate) fn from_ffi_ref(from: &$ty) -> &Self {
287            unsafe { std::mem::transmute(from) }
288        }
289
290        #[allow(dead_code)]
291        pub(crate) unsafe fn from_ffi_mut<'_a>(from: *mut $ty) -> Pin<&'_a mut Self> {
292            std::mem::transmute(from)
293        }
294
295        #[allow(dead_code)]
296        pub(crate) fn as_ffi(&self) -> &$ty {
297            unsafe { std::mem::transmute(self) }
298        }
299
300        #[allow(dead_code)]
301        pub(crate) fn as_ffi_mut(self: Pin<&mut Self>) -> Pin<&mut $ty> {
302            unsafe { std::mem::transmute(self) }
303        }
304
305        #[allow(dead_code)]
306        pub(crate) fn as_ffi_mut_ptr(self: Pin<&mut Self>) -> *mut $ty {
307            unsafe { std::mem::transmute(self) }
308        }
309
310        #[allow(dead_code)]
311        pub(crate) unsafe fn as_ffi_mut_ptr_unpinned(&mut self) -> *mut $ty {
312            std::mem::transmute(self)
313        }
314    };
315}
316
317pub(crate) use unsafe_ffi_conversions;