1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251
use std::marker::PhantomData; use super::{Field, FieldCopyAccess, FieldSliceAccess}; /// A field view represents the field metadata stored in a [Field] plus it stores the underlying /// storage data it operates on, either as a reference to a slice `&[u8]`, `&mut [u8]`, or as /// an owning [Vec<u8>]. /// /// Since this API remembers the underlying storage data in a view object, you don't have to pass it /// in each time you're accessing a field. If you rather prefer an API that does not do that, /// take a look at the [Field] API. /// /// # Example: /// ``` /// use binary_layout::prelude::*; /// /// define_layout!(my_layout, LittleEndian, { /// field_one: u16, /// another_field: [u8; 16], /// something_else: u32, /// tail_data: [u8], /// }); /// /// fn func(storage_data: &mut [u8]) { /// let mut view = my_layout::View::new(storage_data); /// /// // read some data /// let format_version_header: u16 = view.field_one().read(); /// // equivalent: let format_version_header = u16::from_le_bytes((&storage_data[0..2]).try_into().unwrap()); /// /// // write some data /// view.something_else_mut().write(10); /// // equivalent: data_slice[18..22].copy_from_slice(&10u32.to_le_bytes()); /// /// // access a data region /// let tail_data: &[u8] = view.tail_data().data(); /// // equivalent: let tail_data: &[u8] = &data_slice[22..]; /// /// // and modify it /// view.tail_data_mut().data_mut()[..5].copy_from_slice(&[1, 2, 3, 4, 5]); /// // equivalent: data_slice[18..22].copy_from_slice(&[1, 2, 3, 4, 5]); /// } /// ``` pub struct FieldView<S, F: Field> { storage: S, _p: PhantomData<F>, } impl<S, F: Field> FieldView<S, F> { /// Create a new view for a field over a given storage. /// You probably shouldn't call this directly but should instead call /// `your_layout::View::new()`, which is generated by the /// [define_layout!] macro for you. pub fn new(storage: S) -> Self { Self { storage, _p: PhantomData, } } } impl<S: AsRef<[u8]>, F: FieldCopyAccess> FieldView<S, F> { /// Read the field from a given data region, assuming the defined layout, using the [FieldView] API. /// /// # Example /// ``` /// use binary_layout::prelude::*; /// /// define_layout!(my_layout, LittleEndian, { /// //... other fields ... /// some_integer_field: i8 /// //... other fields ... /// }); /// /// fn func(storage_data: &[u8]) { /// let view = my_layout::View::new(storage_data); /// let read: i8 = view.some_integer_field().read(); /// } /// ``` pub fn read(&self) -> F::HighLevelType { F::read(self.storage.as_ref()) } } impl<S: AsMut<[u8]>, F: FieldCopyAccess> FieldView<S, F> { /// Write the field to a given data region, assuming the defined layout, using the [FieldView] API. /// /// # Example /// ``` /// use binary_layout::prelude::*; /// /// define_layout!(my_layout, LittleEndian, { /// //... other fields ... /// some_integer_field: i8 /// //... other fields ... /// }); /// /// fn func(storage_data: &mut [u8]) { /// let mut view = my_layout::View::new(storage_data); /// view.some_integer_field_mut().write(10); /// } /// ``` pub fn write(&mut self, v: F::HighLevelType) { F::write(self.storage.as_mut(), v) } } impl<'a, S: 'a + AsRef<[u8]>, F: FieldSliceAccess<'a>> FieldView<S, F> { /// Immutably borrow the field from the data region, assuming the defined layout, using the [FieldView] API. /// This returns a borrowed sub-slice over the storage that only contains the given field. /// /// # Example /// ``` /// use binary_layout::prelude::*; /// /// define_layout!(my_layout, LittleEndian, { /// //... other fields ... /// tail_data: [u8], /// }); /// /// fn func(storage_data: &[u8]) { /// let view = my_layout::View::new(storage_data); /// let tail_data: &[u8] = view.tail_data().data(); /// } /// ``` pub fn data(&'a self) -> F::SliceType { F::data(self.storage.as_ref()) } } impl< 'a, S: ?Sized + AsRef<[u8]>, F: FieldSliceAccess<'a, SliceType = &'a [u8], MutSliceType = &'a mut [u8]>, > FieldView<&'a S, F> { /// Similar to [FieldView::data], but this also extracts the lifetime. The reference returned by [FieldView::data] can only life as long as the [FieldView] object lives. /// The reference returned by this function can live for as long as the original `packed_data` reference that as put into the [FieldView] lives. /// However, you can only call this if you let the [FieldView] die, it takes the `self` parameter by value. /// Also note that this function can only be called when the [FieldView] was constructed with either a `&[u8]` or a `&mut [u8]` as underlying storage for the `storage_data`. /// If the [FieldView] was constructed based on `Vec<u8>` storage, then this function semantically would have to return an owning subvector, but such a thing doesn't exist in Rust. /// /// # Example: /// ``` /// use binary_layout::prelude::*; /// /// define_layout!(my_layout, LittleEndian, { /// another_field: u64, /// tail_data: [u8], /// }); /// /// fn func(storage_data: &[u8]) -> &[u8] { /// let view = my_layout::View::new(storage_data); /// let tail_data: &[u8] = view.into_tail_data().extract(); /// // Now we return tail_data. Note that the view object doesn't survive /// // this function but we can still return the `tail_data` reference. /// // This wouldn't be possible with `FieldView::data`. /// tail_data /// } /// ``` pub fn extract(self) -> &'a [u8] { F::data(self.storage.as_ref()) } } impl< 'a, S: ?Sized + AsRef<[u8]>, F: FieldSliceAccess<'a, SliceType = &'a [u8], MutSliceType = &'a mut [u8]>, > FieldView<&'a mut S, F> { /// Similar to [FieldView::data], but this also extracts the lifetime. The reference returned by [FieldView::data] can only life as long as the [FieldView] object lives. /// The reference returned by this function can live for as long as the original `packed_data` reference that as put into the [FieldView] lives. /// However, you can only call this if you let the [FieldView] die, it takes the `self` parameter by value. /// Also note that this function can only be called when the [FieldView] was constructed with either a `&[u8]` or a `&mut [u8]` as underlying storage for the `storage_data`. /// If the [FieldView] was constructed based on `Vec<u8>` storage, then this function semantically would have to return an owning subvector, but such a thing doesn't exist in Rust. /// /// # Example: /// ``` /// use binary_layout::prelude::*; /// /// define_layout!(my_layout, LittleEndian, { /// another_field: u64, /// tail_data: [u8], /// }); /// /// fn func(storage_data: &[u8]) -> &[u8] { /// let view = my_layout::View::new(storage_data); /// let tail_data: &[u8] = view.into_tail_data().extract(); /// // Now we return tail_data. Note that the view object doesn't survive /// // this function but we can still return the `tail_data` reference. /// // This wouldn't be possible with `FieldView::data`. /// tail_data /// } /// ``` pub fn extract(self) -> &'a [u8] { let s: &'a S = self.storage; F::data(s.as_ref()) } } impl<'a, S: 'a + AsMut<[u8]>, F: FieldSliceAccess<'a>> FieldView<S, F> { /// Mutably borrow the field from the data region, assuming the defined layout, using the [FieldView] API. /// This returns a borrowed sub-slice over the storage that only contains the given field. /// /// # Example /// ``` /// use binary_layout::prelude::*; /// /// define_layout!(my_layout, LittleEndian, { /// //... other fields ... /// tail_data: [u8], /// }); /// /// fn func(storage_data: &mut [u8]) { /// let mut view = my_layout::View::new(storage_data); /// let tail_data: &mut [u8] = view.tail_data_mut().data_mut(); /// } /// ``` pub fn data_mut(&'a mut self) -> F::MutSliceType { F::data_mut(self.storage.as_mut()) } } impl< 'a, S: ?Sized + AsMut<[u8]>, F: FieldSliceAccess<'a, SliceType = &'a [u8], MutSliceType = &'a mut [u8]>, > FieldView<&'a mut S, F> { /// Similar to [FieldView::data], but this also extracts the lifetime. The reference returned by [FieldView::data] can only life as long as the [FieldView] object lives. /// The reference returned by this function can live for as long as the original `packed_data` reference that as put into the [FieldView] lives. /// However, you can only call this if you let the [FieldView] die, it takes the `self` parameter by value. /// Also note that this function can only be called when the [FieldView] was constructed with either a `&[u8]` or a `&mut [u8]` as underlying storage for the `storage_data`. /// If the [FieldView] was constructed based on `Vec<u8>` storage, then this function semantically would have to return an owning subvector, but such a thing doesn't exist in Rust. /// /// # Example: /// ``` /// use binary_layout::prelude::*; /// /// define_layout!(my_layout, LittleEndian, { /// another_field: u64, /// tail_data: [u8], /// }); /// /// fn func(storage_data: &[u8]) -> &[u8] { /// let view = my_layout::View::new(storage_data); /// let tail_data: &[u8] = view.into_tail_data().extract(); /// // Now we return tail_data. Note that the view object doesn't survive /// // this function but we can still return the `tail_data` reference. /// // This wouldn't be possible with `FieldView::data`. /// tail_data /// } /// ``` pub fn extract_mut(self) -> &'a mut [u8] { F::data_mut(self.storage.as_mut()) } }