Skip to main content

cu29_unifiedlog/
lib.rs

1#![cfg_attr(not(feature = "std"), no_std)]
2
3extern crate alloc;
4extern crate core;
5
6#[cfg(feature = "std")]
7pub mod memmap;
8pub mod noop;
9
10#[cfg(feature = "std")]
11mod compat {
12    // backward compatibility for the std implementation
13    pub use crate::memmap::LogPosition;
14    pub use crate::memmap::MmapUnifiedLogger as UnifiedLogger;
15    pub use crate::memmap::MmapUnifiedLoggerBuilder as UnifiedLoggerBuilder;
16    pub use crate::memmap::MmapUnifiedLoggerRead as UnifiedLoggerRead;
17    pub use crate::memmap::MmapUnifiedLoggerWrite as UnifiedLoggerWrite;
18    pub use crate::memmap::UnifiedLoggerIOReader;
19}
20
21#[cfg(feature = "std")]
22pub use compat::*;
23pub use noop::{NoopLogger, NoopSectionStorage};
24
25use alloc::string::ToString;
26#[cfg(not(feature = "std"))]
27use alloc::sync::Arc;
28use alloc::vec::Vec;
29use core::fmt::{Debug, Display, Formatter, Result as FmtResult};
30#[cfg(not(feature = "std"))]
31use spin::Mutex;
32#[cfg(feature = "std")]
33use std::sync::{Arc, Mutex};
34
35use bincode::error::EncodeError;
36use bincode::{Decode, Encode};
37use cu29_traits::{CuError, CuResult, UnifiedLogType, WriteStream};
38
39/// ID to spot the beginning of a Copper Log
40#[allow(dead_code)]
41pub const MAIN_MAGIC: [u8; 4] = [0xB4, 0xA5, 0x50, 0xFF]; // BRASS OFF
42
43/// ID to spot a section of Copper Log
44pub const SECTION_MAGIC: [u8; 2] = [0xFA, 0x57]; // FAST
45
46/// Version of the unified log **encapsulation only**: file headers, section
47/// headers, and the layout used to locate sections in a slab.
48///
49/// This is **never** a version of the encoded content inside sections. Changes
50/// to CopperLists, payload types, keyframes, or their serialization must not bump
51/// this value. Decode content with the logreader built for the exact application
52/// version that produced it; this header cannot establish content compatibility.
53/// The encapsulation remains version 1, unchanged since Copper's original format.
54pub const UNIFIED_LOG_FORMAT_VERSION: u8 = 1;
55
56pub const SECTION_HEADER_COMPACT_SIZE: u16 = 512; // Usual minimum size for a disk sector.
57
58/// The main file header of the datalogger.
59#[derive(Encode, Decode, Debug)]
60pub struct MainHeader {
61    pub magic: [u8; 4], // Magic number to identify the file.
62    /// Encapsulation version only; see [`UNIFIED_LOG_FORMAT_VERSION`].
63    /// Never versions encoded section content or determines decoder compatibility.
64    /// Content requires the producing application's matching logreader.
65    pub format_version: u8,
66    pub first_section_offset: u16, // This is to align with a page at write time.
67    pub page_size: u16,
68}
69
70impl Display for MainHeader {
71    fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
72        writeln!(
73            f,
74            "  Magic -> {:2x}{:2x}{:2x}{:2x}",
75            self.magic[0], self.magic[1], self.magic[2], self.magic[3]
76        )?;
77        writeln!(f, "  format_version -> {}", self.format_version)?;
78        writeln!(f, "  first_section_offset -> {}", self.first_section_offset)?;
79        writeln!(f, "  page_size -> {}", self.page_size)
80    }
81}
82
83/// Each concurrent sublogger is tracked through a section header.
84/// They form a linked list of sections.
85/// The entry type is used to identify the type of data in the section.
86#[derive(Encode, Decode, Debug)]
87pub struct SectionHeader {
88    pub magic: [u8; 2],  // Magic number to identify the section.
89    pub block_size: u16, // IMPORTANT: we assume this header fits in this block size.
90    pub entry_type: UnifiedLogType,
91    pub offset_to_next_section: u32, // offset from the first byte of this header to the first byte of the next header (MAGIC to MAGIC).
92    pub used: u32,                   // how much of the section is filled.
93    pub is_open: bool,               // true while being written, false once closed.
94}
95
96impl Display for SectionHeader {
97    fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
98        writeln!(f, "    Magic -> {:2x}{:2x}", self.magic[0], self.magic[1])?;
99        writeln!(f, "    type -> {:?}", self.entry_type)?;
100        write!(
101            f,
102            "    use  -> {} / {} (open: {})",
103            self.used, self.offset_to_next_section, self.is_open
104        )
105    }
106}
107
108impl Default for SectionHeader {
109    fn default() -> Self {
110        Self {
111            magic: SECTION_MAGIC,
112            block_size: 512,
113            entry_type: UnifiedLogType::Empty,
114            offset_to_next_section: 0,
115            used: 0,
116            is_open: true,
117        }
118    }
119}
120
121pub enum AllocatedSection<S: SectionStorage> {
122    NoMoreSpace,
123    Section(SectionHandle<S>),
124}
125
126/// A Storage is an append-only structure that can update a header section.
127pub trait SectionStorage: Send + Sync {
128    /// This rewinds the storage, serialize the header and jumps to the beginning of the user data storage.
129    fn initialize<E: Encode>(&mut self, header: &E) -> Result<usize, EncodeError>;
130    /// This updates the header leaving the position to the end of the user data storage.
131    fn post_update_header<E: Encode>(&mut self, header: &E) -> Result<usize, EncodeError>;
132    /// Appends the entry to the user data storage.
133    fn append<E: Encode>(&mut self, entry: &E) -> Result<usize, EncodeError>;
134    /// Flushes the section to the underlying storage
135    fn flush(&mut self) -> CuResult<usize>;
136}
137
138/// A SectionHandle is a handle to a section in the datalogger.
139/// It allows tracking the lifecycle of the section.
140#[derive(Default)]
141pub struct SectionHandle<S: SectionStorage> {
142    header: SectionHeader, // keep a copy of the header as metadata
143    storage: S,
144}
145
146impl<S: SectionStorage> SectionHandle<S> {
147    pub fn create(header: SectionHeader, mut storage: S) -> CuResult<Self> {
148        // Write the first version of the header.
149        let _ = storage.initialize(&header).map_err(|e| e.to_string())?;
150        Ok(Self { header, storage })
151    }
152
153    pub fn mark_closed(&mut self) {
154        self.header.is_open = false;
155    }
156    pub fn append<E: Encode>(&mut self, entry: E) -> Result<usize, EncodeError> {
157        self.storage.append(&entry)
158    }
159
160    pub fn get_storage(&self) -> &S {
161        &self.storage
162    }
163
164    pub fn get_storage_mut(&mut self) -> &mut S {
165        &mut self.storage
166    }
167
168    pub fn post_update_header(&mut self) -> Result<usize, EncodeError> {
169        self.storage.post_update_header(&self.header)
170    }
171}
172
173/// Basic statistics for the unified logger.
174/// Note: the total_allocated_space might grow for the std implementation
175pub struct UnifiedLogStatus {
176    pub total_used_space: usize,
177    pub total_allocated_space: usize,
178}
179
180/// Payload stored in the end-of-log section to signal whether the log was cleanly closed.
181#[derive(Encode, Decode, Debug, Clone)]
182pub struct EndOfLogMarker {
183    pub temporary: bool,
184}
185
186/// The writing interface to the unified logger.
187/// Writing is "almost" linear as various streams can allocate sections and track them until
188/// they drop them.
189pub trait UnifiedLogWrite<S: SectionStorage>: Send + Sync {
190    /// A section is a contiguous chunk of memory that can be used to write data.
191    /// It can store various types of data as specified by the entry_type.
192    /// The requested_section_size is the size of the section to allocate.
193    /// It returns a handle to the section that can be used to write data until
194    /// it is flushed with flush_section, it is then considered unmutable.
195    fn add_section(
196        &mut self,
197        entry_type: UnifiedLogType,
198        requested_section_size: usize,
199    ) -> CuResult<SectionHandle<S>>;
200
201    /// Flush the given section to the underlying storage.
202    fn flush_section(&mut self, section: &mut SectionHandle<S>);
203
204    /// Returns the current status of the unified logger.
205    fn status(&self) -> UnifiedLogStatus;
206}
207
208/// Read back a unified log linearly.
209pub trait UnifiedLogRead {
210    /// Read through the unified logger until it reaches the UnifiedLogType given in datalogtype.
211    /// It will return the byte array of the section if found.
212    fn read_next_section_type(&mut self, datalogtype: UnifiedLogType) -> CuResult<Option<Vec<u8>>>;
213
214    /// Read through the next section entry regardless of its type.
215    /// It will return the header and the byte array of the section.
216    /// Note the last Entry should be of UnifiedLogType::LastEntry if the log is not corrupted.
217    fn raw_read_section(&mut self) -> CuResult<(SectionHeader, Vec<u8>)>;
218}
219
220/// Create a new stream to write to the unifiedlogger.
221pub fn stream_write<E: Encode, S: SectionStorage>(
222    logger: Arc<Mutex<impl UnifiedLogWrite<S>>>,
223    entry_type: UnifiedLogType,
224    minimum_allocation_amount: usize,
225) -> CuResult<impl WriteStream<E>> {
226    LogStream::new(entry_type, logger, minimum_allocation_amount)
227}
228
229/// A wrapper around the unifiedlogger that implements the Write trait.
230pub struct LogStream<S: SectionStorage, L: UnifiedLogWrite<S>> {
231    entry_type: UnifiedLogType,
232    parent_logger: Arc<Mutex<L>>,
233    current_section: SectionHandle<S>,
234    current_position: usize,
235    minimum_allocation_amount: usize,
236    last_log_bytes: usize,
237}
238
239impl<S: SectionStorage, L: UnifiedLogWrite<S>> LogStream<S, L> {
240    /// Creates a concrete stream, including for borrowed canonical encoded entries.
241    pub fn new(
242        entry_type: UnifiedLogType,
243        parent_logger: Arc<Mutex<L>>,
244        minimum_allocation_amount: usize,
245    ) -> CuResult<Self> {
246        #[cfg(feature = "std")]
247        let section = parent_logger
248            .lock()
249            .map_err(|e| {
250                CuError::from("Could not lock a section at LogStream creation")
251                    .add_cause(e.to_string().as_str())
252            })?
253            .add_section(entry_type, minimum_allocation_amount)?;
254
255        #[cfg(not(feature = "std"))]
256        let section = parent_logger
257            .lock()
258            .add_section(entry_type, minimum_allocation_amount)?;
259
260        Ok(Self {
261            entry_type,
262            parent_logger,
263            current_section: section,
264            current_position: 0,
265            minimum_allocation_amount,
266            last_log_bytes: 0,
267        })
268    }
269}
270
271impl<S: SectionStorage, L: UnifiedLogWrite<S>> Debug for LogStream<S, L> {
272    fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
273        write!(
274            f,
275            "MmapStream {{ entry_type: {:?}, current_position: {}, minimum_allocation_amount: {} }}",
276            self.entry_type, self.current_position, self.minimum_allocation_amount
277        )
278    }
279}
280
281impl<E: Encode, S: SectionStorage, L: UnifiedLogWrite<S>> WriteStream<E> for LogStream<S, L> {
282    fn log(&mut self, obj: &E) -> CuResult<()> {
283        //let dst = self.current_section.get_user_buffer();
284        // let result = encode_into_slice(obj, dst, standard());
285        let result = self.current_section.append(obj);
286        match result {
287            Ok(nb_bytes) => {
288                self.current_position += nb_bytes;
289                self.current_section.header.used += nb_bytes as u32;
290                self.last_log_bytes = nb_bytes;
291                // Track encoded bytes so monitoring can compute actual bytes written.
292                Ok(())
293            }
294            Err(e) => match e {
295                EncodeError::UnexpectedEnd => {
296                    #[cfg(feature = "std")]
297                    let logger_guard = self.parent_logger.lock();
298
299                    #[cfg(not(feature = "std"))]
300                    let mut logger_guard = self.parent_logger.lock();
301
302                    #[cfg(feature = "std")]
303                    let mut logger_guard =
304                        match logger_guard {
305                            Ok(g) => g,
306                            Err(_) => return Err(
307                                "Logger mutex poisoned while reporting EncodeError::UnexpectedEnd"
308                                    .into(),
309                            ), // It will retry but at least not completely crash.
310                        };
311
312                    logger_guard.flush_section(&mut self.current_section);
313                    self.current_section = logger_guard
314                        .add_section(self.entry_type, self.minimum_allocation_amount)?;
315
316                    let result = self
317                        .current_section
318                        .append(obj)
319                        .map_err(|e| {
320                            CuError::from(
321                                "Failed to encode object in a newly minted section. Unrecoverable failure.",
322                            )
323                            .add_cause(e.to_string().as_str())
324                        })?; // If we fail just after creating a section, there is not much we can do.
325
326                    self.current_position += result;
327                    self.current_section.header.used += result as u32;
328                    self.last_log_bytes = result;
329                    Ok(())
330                }
331                _ => {
332                    let err =
333                        <&str as Into<CuError>>::into("Unexpected error while encoding object.")
334                            .add_cause(e.to_string().as_str());
335                    Err(err)
336                }
337            },
338        }
339    }
340
341    fn last_log_bytes(&self) -> Option<usize> {
342        Some(self.last_log_bytes)
343    }
344}
345
346impl<S: SectionStorage, L: UnifiedLogWrite<S>> Drop for LogStream<S, L> {
347    fn drop(&mut self) {
348        #[cfg(feature = "std")]
349        match self.parent_logger.lock() {
350            Ok(mut logger_guard) => {
351                logger_guard.flush_section(&mut self.current_section);
352            }
353            Err(_) => {
354                // Only surface the warning when a real poisoning occurred.
355                if !std::thread::panicking() {
356                    eprintln!("⚠️ MmapStream::drop: logger mutex poisoned");
357                }
358            }
359        }
360
361        #[cfg(not(feature = "std"))]
362        {
363            let mut logger_guard = self.parent_logger.lock();
364            logger_guard.flush_section(&mut self.current_section);
365        }
366    }
367}