Skip to main content

log_decoder_c_bindings/
lib.rs

1// Copyright 2021 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
3
4use bumpalo::Bump;
5use diagnostics_log_encoding;
6use diagnostics_log_encoding::parse::ParseError;
7use diagnostics_message::error::MessageError;
8use diagnostics_message::ffi::{CPPMessageFormatter, CppArray, LogMessage};
9use diagnostics_message::{self as message, MonikerWithUrl};
10use std::ffi::CString;
11use std::ops::{Deref, DerefMut};
12use std::os::raw::c_char;
13use std::ptr::NonNull;
14use thiserror::Error;
15
16/// # Safety
17///
18/// Same as for `std::slice::from_raw_parts`. Summarizing in terms of this API:
19///
20/// - `msg` must be valid for reads for `size`, and it must be properly aligned.
21/// - `msg` must point to `size` consecutive u8 values.
22/// - The `size` of the slice must be no larger than `isize::MAX`, and adding
23///   that size to data must not "wrap around" the address space. See the safety
24///   documentation of pointer::offset.
25#[unsafe(no_mangle)]
26pub unsafe extern "C" fn fuchsia_decode_log_message_to_json(
27    msg: *const u8,
28    size: usize,
29) -> *mut c_char {
30    if msg.is_null() || size == 0 {
31        return std::ptr::null_mut();
32    }
33    // SAFETY: caller guarantees msg is valid for reads of size bytes.
34    let managed_ptr = unsafe { std::slice::from_raw_parts(msg, size) };
35    let Ok(data) = message::from_structured(
36        MonikerWithUrl { moniker: "test_moniker".try_into().unwrap(), url: "".into() },
37        managed_ptr,
38    ) else {
39        return std::ptr::null_mut();
40    };
41    let Ok(item) = serde_json::to_string(&data) else {
42        return std::ptr::null_mut();
43    };
44    let Ok(c_string) = CString::new(format!("[{}]", item)) else {
45        return std::ptr::null_mut();
46    };
47    c_string.into_raw()
48}
49
50/// LogMessages struct containing log messages
51/// It is created by calling fuchsia_decode_log_messages_to_struct,
52/// and freed by calling fuchsia_free_log_messages.
53/// Log messages contain embedded pointers to the bytes from
54/// which they were created, so the memory referred to
55/// by the LogMessages must not be modified or free'd until
56/// the LogMessages are free'd.
57#[repr(C)]
58pub struct LogMessages<'a> {
59    messages: CppArray<'a, &'a LogMessage<'a>>,
60    error_str: *const c_char,
61    allocator: AliasableBox<Bump>,
62}
63
64#[derive(Error, Debug)]
65pub enum DecodeError {
66    #[error(transparent)]
67    Message(#[from] MessageError),
68    #[error(transparent)]
69    ParserError(#[from] ParseError),
70}
71
72pub type MessageParser = message::MessageParser;
73
74#[unsafe(no_mangle)]
75pub extern "C" fn fuchsia_new_message_parser() -> *mut MessageParser {
76    Box::into_raw(Box::new(MessageParser::default()))
77}
78
79/// # Safety
80///
81/// This should only be called with a pointer obtained through
82/// `fuchsia_new_message_parser`.
83#[unsafe(no_mangle)]
84pub unsafe extern "C" fn fuchsia_free_message_parser(parser: *mut MessageParser) {
85    if !parser.is_null() {
86        // SAFETY: parser must be a valid MessageParser constructed from
87        // fuchsia_new_message_parser.
88        unsafe { drop(Box::from_raw(parser)) };
89    }
90}
91
92/// # Safety
93///
94/// - This function is NOT thread-safe. The caller must ensure that it is not called
95///   concurrently with the same `parser` pointer.
96///
97/// Same as for `std::slice::from_raw_parts`. Summarizing in terms of this API:
98///
99/// - `msg` must be valid for reads for `size`, and it must be properly aligned.
100/// - `msg` must point to `size` consecutive u8 values.
101/// - The `size` of the slice must be no larger than `isize::MAX`, and adding
102///   that size to data must not "wrap around" the address space. See the safety
103///   documentation of pointer::offset.
104/// If identity is provided, it must contain a valid moniker and URL.
105///
106/// The returned LogMessages must be free'd with fuchsia_free_log_messages(log_messages).  Free'ing
107/// the LogMessages struct frees the bump allocator itself (and everything allocated from it).
108#[unsafe(no_mangle)]
109pub unsafe extern "C" fn fuchsia_decode_log_messages_to_struct<'a>(
110    msg: *const u8,
111    size: usize,
112    expect_extended_attribution: bool,
113    parser: *mut MessageParser,
114) -> LogMessages<'a> {
115    let allocator = AliasableBox::new(Bump::new());
116
117    // SAFETY: The C++ side is responsible for managing the lifetime.  We want to return
118    // `LogMessages<'a>`, so we create a reference to the allocator here with a 'a lifetime.  We are
119    // using `AliasableBox` which allows us to move `allocator` without invalidating any of the
120    // data.
121    let allocator_ref: &'a Bump = unsafe { allocator.get_ref() };
122
123    // SAFETY: If `parser` is non-null, it must be valid and the caller guarantees exclusive access
124    // to it.
125    let maybe_parser = unsafe { parser.as_mut() };
126
127    // SAFETY: The caller guarantees that `msg` is valid for reads for `size` bytes.
128    // The returned `LogMessages<'a>` borrows from `msg` for lifetime `'a`.
129    let buf: &'a [u8] = unsafe { std::slice::from_raw_parts(msg, size) };
130
131    let messages = if let Some(parser) = maybe_parser {
132        fuchsia_decode_log_messages_to_struct_internal(buf, parser, allocator_ref)
133    } else {
134        fuchsia_decode_log_messages_to_struct_internal_legacy(
135            buf,
136            expect_extended_attribution,
137            allocator_ref,
138        )
139    };
140
141    match messages {
142        Ok(messages) => {
143            let messages: &[_] =
144                allocator_ref.alloc_slice_fill_iter(messages.into_iter().map(|m| &*m));
145            LogMessages { messages: messages.into(), error_str: std::ptr::null(), allocator }
146        }
147        Err(err) => LogMessages {
148            messages: CppArray::default(),
149            error_str: allocator_ref
150                .alloc_slice_copy(CString::new(err.to_string()).unwrap().as_bytes_with_nul())
151                .as_ptr() as *const c_char,
152            allocator,
153        },
154    }
155}
156
157/// Decodes log messages from a FXT stream.
158fn fuchsia_decode_log_messages_to_struct_internal<'a>(
159    buf: &'a [u8],
160    parser: &mut MessageParser,
161    allocator: &'a Bump,
162) -> Result<Vec<&'a mut LogMessage<'a>>, DecodeError> {
163    let mut messages = vec![];
164    let mut current_slice = buf.as_ref();
165    let formatter = CPPMessageFormatter(allocator);
166    loop {
167        let (data, remaining) = parser.parse_next(current_slice, &formatter)?;
168
169        if let Some(data) = data {
170            messages.push(data);
171        }
172        if remaining.is_empty() {
173            break;
174        }
175        current_slice = remaining;
176    }
177
178    Ok(messages)
179}
180
181/// Decodes log messages from a legacy FXT stream.
182fn fuchsia_decode_log_messages_to_struct_internal_legacy<'a>(
183    buf: &'a [u8],
184    expect_extended_attribution: bool,
185    allocator: &'a Bump,
186) -> Result<Vec<&'a mut LogMessage<'a>>, DecodeError> {
187    let mut messages = vec![];
188    let mut current_slice = buf.as_ref();
189    loop {
190        let (data, remaining) = if expect_extended_attribution {
191            message::ffi::ffi_from_extended_record(current_slice, allocator)?
192        } else {
193            let (_, remaining_after_parse) =
194                diagnostics_log_encoding::parse::parse_record(current_slice)?;
195            let record_len = current_slice.len() - remaining_after_parse.len();
196            let record_slice = &current_slice[..record_len];
197            let (data, _) = message::ffi::ffi_from_extended_record(record_slice, allocator)?;
198            (data, remaining_after_parse)
199        };
200        messages.push(data);
201        if remaining.is_empty() {
202            break;
203        }
204        current_slice = remaining;
205    }
206
207    Ok(messages)
208}
209
210/// # Safety
211///
212/// This should only be called with a pointer obtained through
213/// `fuchsia_decode_log_message_to_json`.
214#[unsafe(no_mangle)]
215pub unsafe extern "C" fn fuchsia_free_decoded_log_message(msg: *mut c_char) {
216    let str_to_free = unsafe { CString::from_raw(msg) };
217    let _freer = str_to_free;
218}
219
220/// # Safety
221///
222/// This should only be called with `input` obtained through
223/// `fuchsia_decode_log_messages_to_struct`.
224#[unsafe(no_mangle)]
225pub unsafe extern "C" fn fuchsia_free_log_messages(input: LogMessages<'_>) {
226    drop(input);
227}
228
229/// Like `Box` except that it can be moved when there are live pointers.
230#[repr(C)]
231struct AliasableBox<T>(NonNull<T>);
232
233impl<T> AliasableBox<T> {
234    /// Returns a reference with an arbitrary lifetime.
235    unsafe fn get_ref<'a>(&self) -> &'a T {
236        // SAFETY: The caller must make this safe.
237        unsafe { self.0.as_ref() }
238    }
239}
240
241impl<T> AliasableBox<T> {
242    fn new(value: T) -> Self {
243        // SAFETY: `Box::into_raw` won't return null.
244        Self(unsafe { NonNull::new_unchecked(Box::into_raw(Box::new(value))) })
245    }
246}
247
248impl<T> Drop for AliasableBox<T> {
249    fn drop(&mut self) {
250        // SAFETY: We own the pointer.
251        unsafe {
252            let _ = Box::from_raw(self.0.as_ptr());
253        }
254    }
255}
256
257impl<T> Deref for AliasableBox<T> {
258    type Target = T;
259    fn deref(&self) -> &Self::Target {
260        // SAFETY: We own the pointer.
261        unsafe { self.0.as_ref() }
262    }
263}
264
265impl<T> DerefMut for AliasableBox<T> {
266    fn deref_mut(&mut self) -> &mut Self::Target {
267        // SAFETY: We own the pointer.
268        unsafe { self.0.as_mut() }
269    }
270}