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::{CPPArray, LogMessage};
9use diagnostics_message::{self as message, MonikerWithUrl};
10use std::ffi::CString;
11use std::os::raw::c_char;
12use thiserror::Error;
13
14/// # Safety
15///
16/// Same as for `std::slice::from_raw_parts`. Summarizing in terms of this API:
17///
18/// - `msg` must be valid for reads for `size`, and it must be properly aligned.
19/// - `msg` must point to `size` consecutive u8 values.
20/// - The `size` of the slice must be no larger than `isize::MAX`, and adding
21/// that size to data must not "wrap around" the address space. See the safety
22/// documentation of pointer::offset.
23#[unsafe(no_mangle)]
24pub unsafe extern "C" fn fuchsia_decode_log_message_to_json(
25 msg: *const u8,
26 size: usize,
27) -> *mut c_char {
28 let managed_ptr = unsafe { std::slice::from_raw_parts(msg, size) };
29 let data = &message::from_structured(
30 MonikerWithUrl { moniker: "test_moniker".try_into().unwrap(), url: "".into() },
31 managed_ptr,
32 )
33 .unwrap();
34 let item = serde_json::to_string(&data).unwrap();
35 CString::new(format!("[{}]", item)).unwrap().into_raw()
36}
37
38/// Memory-managed state to be free'd on the Rust side
39/// when the log messages are destroyed.
40pub struct ManagedState<'a> {
41 allocator: Bump,
42 message_array: Vec<*mut LogMessage<'a>>,
43}
44
45impl Drop for LogMessages<'_> {
46 fn drop(&mut self) {
47 unsafe {
48 // SAFETY: All pointers in message_array are assumed to be valid.
49 // Other unsafe code in this file and in C++ ensures this invariant.
50
51 // Free all managed state in the log messages.
52 // The log messages themselves don't need to be explicitly free'd
53 // as they are owned by the Bump allocator.
54 let state = Box::from_raw(self.state);
55 for msg in &state.message_array {
56 std::ptr::drop_in_place(*msg);
57 }
58 }
59 }
60}
61
62/// LogMessages struct containing log messages
63/// It is created by calling fuchsia_decode_log_messages_to_struct,
64/// and freed by calling fuchsia_free_log_messages.
65/// Log messages contain embedded pointers to the bytes from
66/// which they were created, so the memory referred to
67/// by the LogMessages must not be modified or free'd until
68/// the LogMessages are free'd.
69#[repr(C)]
70pub struct LogMessages<'a> {
71 messages: CPPArray<*mut LogMessage<'a>>,
72 state: *mut ManagedState<'a>,
73 error_str: *mut c_char,
74}
75
76#[derive(Error, Debug)]
77pub enum DecodeError {
78 #[error(transparent)]
79 Message(#[from] MessageError),
80 #[error(transparent)]
81 ParserError(#[from] ParseError),
82}
83
84/// # Safety
85///
86/// Same as for `std::slice::from_raw_parts`. Summarizing in terms of this API:
87///
88/// - `msg` must be valid for reads for `size`, and it must be properly aligned.
89/// - `msg` must point to `size` consecutive u8 values.
90/// - 'msg' must outlive the returned LogMessages struct, and must not be free'd
91/// until fuchsia_free_log_messages has been called.
92/// - The `size` of the slice must be no larger than `isize::MAX`, and adding
93/// that size to data must not "wrap around" the address space. See the safety
94/// documentation of pointer::offset.
95/// If identity is provided, it must contain a valid moniker and URL.
96///
97/// The returned LogMessages may be free'd with fuchsia_free_log_messages(log_messages).
98/// Free'ing the LogMessages struct does the following, in this order:
99/// * Frees memory associated with each individual log message
100/// * Frees the bump allocator itself (and everything allocated from it), as well as
101/// the message array itself.
102/// If a malformed message is passed, returns nullptr.
103#[unsafe(no_mangle)]
104pub unsafe extern "C" fn fuchsia_decode_log_messages_to_struct(
105 msg: *const u8,
106 size: usize,
107 expect_extended_attribution: bool,
108) -> LogMessages<'static> {
109 unsafe {
110 fuchsia_decode_log_messages_to_struct_internal(msg, size, expect_extended_attribution)
111 }
112 .unwrap_or_else(|err| LogMessages {
113 messages: (&vec![]).into(),
114 state: std::ptr::null_mut(),
115 error_str: CString::new(err.to_string())
116 .map(|value| value.into_raw())
117 .unwrap_or(std::ptr::null_mut()),
118 })
119}
120
121/// # Safety
122///
123/// Same as for `std::slice::from_raw_parts`. Summarizing in terms of this API:
124///
125/// - `msg` must be valid for reads for `size`, and it must be properly aligned.
126/// - `msg` must point to `size` consecutive u8 values.
127/// - 'msg' must outlive the returned LogMessages struct, and must not be free'd
128/// until fuchsia_free_log_messages has been called.
129/// - The `size` of the slice must be no larger than `isize::MAX`, and adding
130/// that size to data must not "wrap around" the address space. See the safety
131/// documentation of pointer::offset.
132/// If identity is provided, it must contain a valid moniker and URL.
133///
134/// The returned LogMessages may be free'd with fuchsia_free_log_messages(log_messages).
135/// Free'ing the LogMessages struct does the following, in this order:
136/// * Frees memory associated with each individual log message
137/// * Frees the bump allocator itself (and everything allocated from it), as well as
138/// the message array itself.
139unsafe fn fuchsia_decode_log_messages_to_struct_internal(
140 msg: *const u8,
141 size: usize,
142 expect_extended_attribution: bool,
143) -> Result<LogMessages<'static>, DecodeError> {
144 let mut state = Box::new(ManagedState { allocator: Bump::new(), message_array: vec![] });
145 let buf = unsafe { std::slice::from_raw_parts(msg, size) };
146 let mut current_slice = buf.as_ref();
147 loop {
148 let (data, remaining) = if expect_extended_attribution {
149 message::ffi::ffi_from_extended_record(
150 current_slice,
151 // SAFETY: The returned LogMessage must NOT outlive the bump allocator.
152 // This is ensured by the allocator living in the heap-allocated ManagedState
153 // struct which frees the LogMessages first when dropped, before allowing the bump
154 // allocator itself to be freed.
155 unsafe { &*(&state.allocator as *const Bump) },
156 )?
157 } else {
158 let (_, remaining_after_parse) =
159 diagnostics_log_encoding::parse::parse_record(current_slice)?;
160 let record_len = current_slice.len() - remaining_after_parse.len();
161 let record_slice = ¤t_slice[..record_len];
162 let (data, _) = message::ffi::ffi_from_extended_record(
163 record_slice,
164 // SAFETY: The returned LogMessage must NOT outlive the bump allocator.
165 // This is ensured by the allocator living in the heap-allocated ManagedState
166 // struct which frees the LogMessages first when dropped, before allowing the bump
167 // allocator itself to be freed.
168 unsafe { &*(&state.allocator as *const Bump) },
169 )?;
170 (data, remaining_after_parse)
171 };
172 state.message_array.push(data as *mut LogMessage<'static>);
173 if remaining.is_empty() {
174 break;
175 }
176 current_slice = remaining;
177 }
178
179 Ok(LogMessages {
180 messages: (&state.message_array).into(),
181 state: Box::into_raw(state),
182 error_str: std::ptr::null_mut(),
183 })
184}
185
186/// # Safety
187///
188/// This should only be called with a pointer obtained through
189/// `fuchsia_decode_log_message_to_json`.
190#[unsafe(no_mangle)]
191pub unsafe extern "C" fn fuchsia_free_decoded_log_message(msg: *mut c_char) {
192 let str_to_free = unsafe { CString::from_raw(msg) };
193 let _freer = str_to_free;
194}
195
196/// # Safety
197///
198/// This should only be called with a pointer obtained through
199/// `fuchsia_decode_log_messages_to_struct`. This method
200/// should not be called if state is nullptr.
201#[unsafe(no_mangle)]
202pub unsafe extern "C" fn fuchsia_free_log_messages(input: LogMessages<'_>) {
203 drop(input);
204}