lv2rs_atom/
atom.rs

1//! Fundamental type definitions.
2use crate::frame::{WritingFrame, WritingFrameExt};
3use std::ffi::CStr;
4use std::marker::PhantomData;
5use std::os::raw::c_int;
6use urid::URID;
7
8/// Marker or header of an atom data structure.
9///
10/// This type is used to interpret data coming from the host or other plugins. It is always written
11/// in the beginning of a data packet and denotes the size of the packet and it's type.
12#[repr(C)]
13pub struct Atom {
14    size: c_int,
15    atom_type: URID,
16}
17
18impl Atom {
19    /// Return the size of the body.
20    pub fn size(&self) -> usize {
21        self.size as usize
22    }
23
24    /// Return a mutable reference to the body size.
25    pub fn mut_size(&mut self) -> &mut i32 {
26        &mut self.size
27    }
28
29    /// Return the type of the body.
30    pub fn atom_type(&self) -> URID {
31        self.atom_type
32    }
33
34    // Return a mutable reference to the atom body type.
35    pub fn mut_atom_type(&mut self) -> &mut URID {
36        &mut self.atom_type
37    }
38
39    /// Write an empty header to a writing frame.
40    ///
41    /// This function is for internal use and you should not use it externally. Since it does not
42    /// check what's around it, this function may invalidate the written atom structure. This is
43    /// also the reason why it's unsafe.
44    pub unsafe fn write_empty_header<
45        'a,
46        W: WritingFrame<'a> + WritingFrameExt<'a, A>,
47        A: AtomBody + ?Sized,
48    >(
49        frame: &mut W,
50        atom_type: URID,
51    ) -> Result<&'a mut Self, ()> {
52        let atom = Atom {
53            size: 0,
54            atom_type: atom_type,
55        };
56        frame.write_sized(&atom)
57    }
58
59    /// Return a slice of bytes containing the data.
60    ///
61    /// The returned slice will start directly after the atom and will have the size noted in the
62    /// header.
63    pub fn get_raw_body(&self) -> &[u8] {
64        unsafe {
65            std::slice::from_raw_parts(
66                (self as *const Atom).add(1) as *const u8,
67                self.size as usize,
68            )
69        }
70    }
71
72    /// Try the return a reference to the body.
73    ///
74    /// This function fails if a) the type URID in the atom does not match with A's URID or b)
75    /// the internal casting function tells that the data is malformed.
76    pub fn get_body<A: AtomBody + ?Sized>(
77        &self,
78        urids: &mut urid::CachedMap,
79    ) -> Result<&A, GetBodyError> {
80        if self.atom_type != urids.map(A::get_uri()) {
81            return Err(GetBodyError::WrongURID);
82        }
83        let raw_body = self.get_raw_body();
84        A::create_ref(raw_body).map_err(|_| GetBodyError::MalformedAtom)
85    }
86}
87
88/// Errors that may occur when calling [`Atom::get_body`](trait.Atom.html#method.get_body).
89#[derive(Debug)]
90pub enum GetBodyError {
91    /// The URID noted in the atom header is wrong.
92    ///
93    /// Maybe you tried to use the wrong atom type?
94    WrongURID,
95    /// The atom is malformed.
96    ///
97    /// You can't do much about it; This is another plugin's fault.
98    MalformedAtom,
99}
100
101/// Abstraction of atom bodies.
102///
103/// Atom bodies can be very different in size and shape and therefore, this trait contains only a
104/// small set of things atoms are capable of.
105///
106/// ## Implementing your own atom bodies.
107///
108/// First of all, you shouldn't. The set of included atom bodies is enough to express almost any
109/// information. On the other hand, if you really need to implement a new atom body, just implement
110/// this trait. This will give you most of the features you will need. However, this trait only
111/// lets you initialize a body; It does not give you means to extend it afterwards. If you want to
112/// do that, you should create an extension trait for [`WritingFrame`s](../frame/trait.WritingFrame.html),
113/// just like the [`TupleWritingFrame`](../tuple/trait.TupleWritingFrame.html).
114pub trait AtomBody {
115    /// The type of the parameter for [`initialize_body`](#tymethod.initialize_body)
116    ///
117    /// Since Rust does not support generic associated types yet, you can not use references here.
118    /// However, since `initialize_body` will receive a reference of this type, you can place
119    /// unsized types in here, like slices.
120    type InitializationParameter: ?Sized;
121
122    /// Return the URI of the atom type.
123    fn get_uri() -> &'static CStr;
124
125    /// Write out a basic but valid atom body.
126    ///
127    /// Implementors should use the writing frame to write out general information about the atom,
128    /// like body-specific headers or, in the case of scalars, the value itself. Please note that
129    /// * The [`Atom`](struct.Atom.html) was already written, you do not need to write
130    /// it yourself.
131    /// * You cannot alter the data after it was written. Once this method call is over, you only have
132    /// reading access to it by using the
133    /// [`get_atom_body`](../frame/trait.WritingFrameExt.html#method.get_atom_body) method of the
134    /// writing frame.
135    /// * The result must be a valid atom. You may not rely on future calls to make it valid.
136    /// * In most cases, you don't need to include padding. If padding is required, the writer will
137    /// include it when it is dropped.
138    /// * You do not need (and definitely should not try) to update the atom header for the new
139    /// size. The writer will keep track of that.
140    /// * Your implementation should work in a way that it can only return `Err` in cases
141    /// of insufficient memory.
142    ///
143    /// This method is unsafe since it can tamper if the integrity of the atom structure, for example
144    /// if called twice.
145    unsafe fn initialize_body<'a, W>(
146        writer: &mut W,
147        parameter: &Self::InitializationParameter,
148        urids: &mut urid::CachedMap,
149    ) -> Result<(), ()>
150    where
151        W: WritingFrame<'a> + WritingFrameExt<'a, Self>;
152
153    /// Try to create a `Self` reference from a slice of raw data.
154    ///
155    /// When implementing, you have to check if the data makes up a valid object of your type. If
156    /// this is not the case, return an `Err`.
157    fn create_ref<'a>(raw_body: &'a [u8]) -> Result<&'a Self, ()>;
158}
159
160/// Iterator over atoms.
161///
162/// This iterator takes a slice of bytes and tries to iterate over all atoms in this slice. If
163/// there is an error while iterating, iteration will end.
164pub struct AtomIterator<'a, H: 'static + Sized> {
165    data: &'a [u8],
166    position: usize,
167    phantom: PhantomData<H>,
168}
169
170impl<'a, H: 'static + Sized> AtomIterator<'a, H> {
171    /// Create a new atom iterator.
172    pub fn new(data: &'a [u8]) -> Self {
173        AtomIterator {
174            data: data,
175            position: 0,
176            phantom: PhantomData,
177        }
178    }
179}
180
181impl<'a, H: 'static + Sized> Iterator for AtomIterator<'a, H> {
182    type Item = (&'a H, &'a Atom);
183
184    fn next(&mut self) -> Option<(&'a H, &'a Atom)> {
185        use std::mem::size_of;
186
187        // pad to the next 64-bit aligned position, if nescessary.
188        if self.position % 8 != 0 {
189            self.position += 8 - self.position % 8;
190        }
191        if self.position >= self.data.len() {
192            return None;
193        }
194
195        let data = &self.data[self.position..];
196        if data.len() < size_of::<H>() + size_of::<Atom>() {
197            return None;
198        }
199
200        let pre_header_ptr = data.as_ptr() as *const H;
201        let pre_header = unsafe { pre_header_ptr.as_ref() }?;
202        let atom_ptr = unsafe { pre_header_ptr.add(1) } as *const Atom;
203        let atom = unsafe { atom_ptr.as_ref() }?;
204
205        // Apply the package of pre-header, atom and data to our position in the array.
206        self.position += size_of::<H>() + size_of::<Atom>() + atom.size as usize;
207
208        if self.position <= self.data.len() {
209            Some((pre_header, atom))
210        } else {
211            None
212        }
213    }
214}
215
216#[cfg(test)]
217mod test {
218    use crate::atom::*;
219
220    #[test]
221    fn test_chunk_iterator() {
222        struct TestPrefix {
223            value: u64,
224        }
225
226        // ##################
227        // creating the data.
228        // ##################
229        let mut data = Box::new([0u8; 256]);
230        let ptr = data.as_mut().as_mut_ptr();
231
232        // First prefix.
233        let mut ptr = ptr as *mut TestPrefix;
234        unsafe {
235            let mut_ref = ptr.as_mut().unwrap();
236            mut_ref.value = 650000;
237            // No padding needed, TestPrefix is eight bytes long.
238            ptr = ptr.add(1);
239        }
240
241        // First atom. We will fit a u8 after it, because it requires seven padding bytes, which
242        // is an important edge case.
243        let mut ptr = ptr as *mut Atom;
244        unsafe {
245            let mut_ref = ptr.as_mut().unwrap();
246            mut_ref.atom_type = 42;
247            mut_ref.size = 1;
248            ptr = ptr.add(1);
249        }
250        let mut ptr = ptr as *mut u8;
251        unsafe {
252            let mut_ref = ptr.as_mut().unwrap();
253            *mut_ref = 17;
254            ptr = ptr.add(1);
255        }
256
257        // Padding and second prefix.
258        let mut ptr = unsafe { ptr.add(7) } as *mut TestPrefix;
259        unsafe {
260            let mut_ref = ptr.as_mut().unwrap();
261            mut_ref.value = 4711;
262            // No padding needed, TestPrefix is eight bytes long.
263            ptr = ptr.add(1);
264        }
265
266        // Second atom.
267        let mut ptr = ptr as *mut Atom;
268        unsafe {
269            let mut_ref = ptr.as_mut().unwrap();
270            mut_ref.atom_type = 10;
271            mut_ref.size = 1;
272            ptr = ptr.add(1);
273        }
274        let ptr = ptr as *mut u8;
275        unsafe {
276            let mut_ref = ptr.as_mut().unwrap();
277            *mut_ref = 4;
278        }
279
280        // #####################
281        // Testing the iterator.
282        // #####################
283        let mut iter: AtomIterator<TestPrefix> = AtomIterator::new(data.as_ref());
284
285        // First atom
286        let (prefix, atom) = iter.next().unwrap();
287        assert_eq!(650000, prefix.value);
288        assert_eq!(42, atom.atom_type);
289        assert_eq!(1, atom.size);
290        assert_eq!(17, atom.get_raw_body()[0]);
291
292        // Second atom.
293        let (prefix, atom) = iter.next().unwrap();
294        assert_eq!(4711, prefix.value);
295        assert_eq!(10, atom.atom_type);
296        assert_eq!(1, atom.size);
297        assert_eq!(4, atom.get_raw_body()[0]);
298    }
299}
300
301/// Special templates for dynamically sized atoms.
302pub mod array {
303    use crate::atom::*;
304    use crate::frame::{WritingFrame, WritingFrameExt};
305    use std::mem::{size_of, transmute};
306
307    /// A header of an `ArrayAtomBody`.
308    ///
309    /// Many atoms have an additional body header and this trait represents said headers.
310    pub trait ArrayAtomHeader: Sized {
311        /// Type of the parameter for [`initialize`](#tymethod.initialize).
312        type InitializationParameter: ?Sized;
313
314        /// Write out the array atom header.
315        ///
316        /// The same rules from
317        /// [`AtomBody::initialize_body`](../trait.AtomBody.html#tymethod.initialize_body) apply.
318        unsafe fn initialize<'a, W, T>(
319            writer: &mut W,
320            parameter: &Self::InitializationParameter,
321            urids: &mut urid::CachedMap,
322        ) -> Result<(), ()>
323        where
324            T: 'static + Sized + Copy,
325            ArrayAtomBody<Self, T>: AtomBody,
326            W: WritingFrame<'a> + WritingFrameExt<'a, ArrayAtomBody<Self, T>>;
327    }
328
329    impl ArrayAtomHeader for () {
330        type InitializationParameter = ();
331
332        unsafe fn initialize<'a, W, T>(_: &mut W, _: &(), _: &mut urid::CachedMap) -> Result<(), ()>
333        where
334            T: 'static + Sized + Copy,
335            ArrayAtomBody<Self, T>: AtomBody,
336            W: WritingFrame<'a> + WritingFrameExt<'a, ArrayAtomBody<Self, T>>,
337        {
338            Ok(())
339        }
340    }
341
342    /// Abstract type for dynamically sized atom bodies.
343    ///
344    /// Many dynamically sized atoms bodies have a lot of their behaviour and raw representation in
345    /// common. Therefore, they are abstracted to this struct that contains a header and an array of
346    /// sized items.
347    ///
348    /// If you don't want to have a header, you can use `()` instead.
349    ///
350    /// Not all combinations of header and data items are atom bodies, but many methods rely on
351    /// the combination being an atom body.
352    #[repr(C)]
353    pub struct ArrayAtomBody<H, T>
354    where
355        H: ArrayAtomHeader,
356        T: 'static + Sized + Copy,
357    {
358        pub header: H,
359        pub data: [T],
360    }
361
362    impl<H, T> ArrayAtomBody<H, T>
363    where
364        Self: AtomBody,
365        H: ArrayAtomHeader,
366        T: 'static + Sized + Copy,
367    {
368        /// Internal method to initialize the body.
369        ///
370        /// It simply calls the initialization method of the header, the data array will be left
371        /// empty.
372        pub unsafe fn __initialize_body<'a, W>(
373            writer: &mut W,
374            parameter: &H::InitializationParameter,
375            urids: &mut urid::CachedMap,
376        ) -> Result<(), ()>
377        where
378            W: WritingFrame<'a> + WritingFrameExt<'a, Self>,
379        {
380            H::initialize(writer, parameter, urids)
381        }
382
383        /// Internal method to create an atom body reference.
384        pub fn __create_ref<'a>(raw_data: &'a [u8]) -> Result<&'a Self, ()> {
385            let array_header_size = size_of::<H>();
386            if raw_data.len() < array_header_size {
387                return Err(());
388            }
389
390            let tail_size = raw_data.len() - size_of::<H>();
391            // The size of the tail has to be a multiple of the contained type.
392            if tail_size % size_of::<T>() != 0 {
393                return Err(());
394            }
395            let tail_len = tail_size / size_of::<T>();
396
397            // This is were the unsafe things happen!
398            // We know the length of the string, therefore we can create a fat pointer to the atom.
399            let self_ptr: (*const u8, usize) = (raw_data.as_ptr(), tail_len);
400            let self_ref: &Self = unsafe { transmute(self_ptr) };
401
402            Ok(self_ref)
403        }
404
405        /// Push another value to the data array.
406        ///
407        /// In case of insufficient memory, an `Err` is returned.
408        ///
409        /// This method assumes that the atom was already initialized, but since can't be checked,
410        /// this method is unsafe.
411        pub unsafe fn push<'a, W>(writer: &mut W, value: T) -> Result<(), ()>
412        where
413            W: WritingFrame<'a> + WritingFrameExt<'a, Self>,
414        {
415            writer.write_sized(&value)?;
416            Ok(())
417        }
418
419        /// Append a `T` slice to the data.
420        ///
421        /// In case of insufficient memory, an `Err` is returned.
422        ///
423        /// This method assumes that the atom was already initialized, but since can't be checked,
424        /// this method is unsafe.
425        pub unsafe fn append<'a, W>(writer: &mut W, slice: &[T]) -> Result<(), ()>
426        where
427            W: WritingFrame<'a> + WritingFrameExt<'a, Self>,
428        {
429            let data = std::slice::from_raw_parts(
430                slice.as_ptr() as *const u8,
431                std::mem::size_of_val(slice),
432            );
433            writer.write_raw(data)?;
434            Ok(())
435        }
436    }
437}