Skip to main content

nom_exif/
values.rs

1use std::{fmt::Display, string::FromUtf8Error};
2
3use chrono::{
4    offset::LocalResult, DateTime, FixedOffset, Local, NaiveDateTime, Offset, TimeZone as _, Utc,
5};
6
7use nom::{multi::many_m_n, number::Endianness};
8#[cfg(feature = "json_dump")]
9use serde::{Deserialize, Serialize, Serializer};
10use thiserror::Error;
11
12use crate::ExifTag;
13
14/// Represent a parsed entry value.
15#[derive(Debug, Clone, PartialEq)]
16pub enum EntryValue {
17    Text(String),
18    URational(URational),
19    IRational(IRational),
20
21    U8(u8),
22    U16(u16),
23    U32(u32),
24    U64(u64),
25
26    I8(i8),
27    I16(i16),
28    I32(i32),
29    I64(i64),
30
31    F32(f32),
32    F64(f64),
33
34    Time(DateTime<FixedOffset>),
35    Undefined(Vec<u8>),
36
37    URationalArray(Vec<URational>),
38    IRationalArray(Vec<IRational>),
39
40    U16Array(Vec<u16>),
41    U32Array(Vec<u32>),
42}
43
44#[derive(Clone, Debug, PartialEq, Eq)]
45pub(crate) struct EntryData<'a> {
46    pub endian: Endianness,
47    pub tag: u16,
48    pub data: &'a [u8],
49    pub data_format: DataFormat,
50    pub components_num: u32,
51}
52
53#[derive(Debug, Clone, Error)]
54pub(crate) enum ParseEntryError {
55    #[error("size is too big")]
56    EntrySizeTooBig,
57
58    #[error("data is invalid: {0}")]
59    InvalidData(String),
60
61    #[error("data format is unsupported (please file a bug): {0}")]
62    Unsupported(String),
63}
64
65impl From<chrono::ParseError> for ParseEntryError {
66    fn from(value: chrono::ParseError) -> Self {
67        ParseEntryError::InvalidData(format!("invalid time format: {value}"))
68    }
69}
70
71use ParseEntryError as Error;
72
73impl EntryData<'_> {
74    // Ensure that the returned Vec is not empty.
75    fn try_as_rationals<T: TryFromBytes>(&self) -> Result<Vec<Rational<T>>, Error> {
76        if self.components_num == 0 {
77            return Err(Error::InvalidData("components is 0".to_string()));
78        }
79
80        let mut vec = Vec::with_capacity(self.components_num as usize);
81        for i in 0..self.components_num {
82            let rational = decode_rational::<T>(&self.data[i as usize * 8..], self.endian)?;
83            vec.push(rational);
84        }
85        Ok(vec)
86    }
87}
88
89impl EntryValue {
90    /// Parse an IFD entry value.
91    ///
92    /// # Structure of IFD Entry
93    ///
94    /// ```txt
95    /// | 2   | 2           | 4              | 4                      |
96    /// | tag | data format | components num | data (value or offset) |
97    /// ```
98    ///
99    /// # Data size
100    ///
101    /// `data_size = components_num * bytes_per_component`
102    ///
103    /// `bytes_per_component` is determined by tag & data format.
104    ///
105    /// If data_size > 4, then the data area of entry stores the offset of the
106    /// value, not the value itself.
107    ///
108    /// # Data format
109    ///
110    /// See: [`DataFormat`].
111    pub(crate) fn parse(entry: &EntryData, tz: &Option<String>) -> Result<EntryValue, Error> {
112        if entry.data.is_empty() {
113            return Err(Error::InvalidData(
114                "invalid DirectoryEntry: entry data is empty".into(),
115            ));
116        }
117
118        let endian = entry.endian;
119        let tag = entry.tag;
120        let data_format = entry.data_format;
121        let data = entry.data;
122        let components_num = entry.components_num;
123
124        if data.is_empty() || components_num == 0 {
125            return Ok(EntryValue::variant_default(data_format));
126        }
127
128        let exif_tag: Result<ExifTag, _> = tag.try_into();
129        if let Ok(tag) = exif_tag {
130            if tag == ExifTag::DateTimeOriginal
131                || tag == ExifTag::CreateDate
132                || tag == ExifTag::ModifyDate
133            {
134                // assert_eq!(data_format, 2);
135                // if data_format != 2 {
136                //     return Err(Error::InvalidData(
137                //         "invalid DirectoryEntry: date format is invalid".into(),
138                //     ));
139                // }
140                let s = get_cstr(data).map_err(|e| Error::InvalidData(e.to_string()))?;
141
142                let t = if let Some(tz) = tz {
143                    let s = format!("{s} {tz}");
144                    DateTime::parse_from_str(&s, "%Y:%m:%d %H:%M:%S %z")?
145                } else {
146                    let t = NaiveDateTime::parse_from_str(&s, "%Y:%m:%d %H:%M:%S")?;
147                    let t = Local.from_local_datetime(&t);
148                    let t = if let LocalResult::Single(t) = t {
149                        Ok(t)
150                    } else {
151                        Err(Error::InvalidData(format!("parse time failed: {s}")))
152                    }?;
153
154                    t.with_timezone(t.offset())
155                };
156
157                return Ok(EntryValue::Time(t));
158            }
159        }
160
161        match data_format {
162            DataFormat::U8 => match components_num {
163                1 => Ok(Self::U8(data[0])),
164                x => Err(Error::Unsupported(format!(
165                    "usigned byte with {x} components"
166                ))),
167            },
168            DataFormat::Text => Ok(EntryValue::Text(
169                get_cstr(data).map_err(|e| Error::InvalidData(e.to_string()))?,
170            )),
171            DataFormat::U16 => {
172                if components_num == 1 {
173                    Ok(Self::U16(u16::try_from_bytes(data, endian)?))
174                } else {
175                    let (_, v) = many_m_n::<_, _, nom::error::Error<_>, _>(
176                        components_num as usize,
177                        components_num as usize,
178                        nom::number::complete::u16(endian),
179                    )(data)
180                    .map_err(|e| {
181                        ParseEntryError::InvalidData(format!("parse U16Array error: {e:?}"))
182                    })?;
183                    Ok(Self::U16Array(v))
184                }
185            }
186            DataFormat::U32 => {
187                if components_num == 1 {
188                    Ok(Self::U32(u32::try_from_bytes(data, endian)?))
189                } else {
190                    let (_, v) = many_m_n::<_, _, nom::error::Error<_>, _>(
191                        components_num as usize,
192                        components_num as usize,
193                        nom::number::complete::u32(endian),
194                    )(data)
195                    .map_err(|e| {
196                        ParseEntryError::InvalidData(format!("parse U32Array error: {e:?}"))
197                    })?;
198                    Ok(Self::U32Array(v))
199                }
200            }
201            DataFormat::URational => {
202                let rationals = entry.try_as_rationals::<u32>()?;
203                if rationals.len() == 1 {
204                    Ok(Self::URational(rationals[0]))
205                } else {
206                    Ok(Self::URationalArray(rationals))
207                }
208            }
209            DataFormat::I8 => match components_num {
210                1 => Ok(Self::I8(data[0] as i8)),
211                x => Err(Error::Unsupported(format!(
212                    "signed byte with {x} components"
213                ))),
214            },
215            DataFormat::Undefined => Ok(Self::Undefined(data.to_vec())),
216            DataFormat::I16 => match components_num {
217                1 => Ok(Self::I16(i16::try_from_bytes(data, endian)?)),
218                x => Err(Error::Unsupported(format!(
219                    "signed short with {x} components"
220                ))),
221            },
222            DataFormat::I32 => match components_num {
223                1 => Ok(Self::I32(i32::try_from_bytes(data, endian)?)),
224                x => Err(Error::Unsupported(format!(
225                    "signed long with {x} components"
226                ))),
227            },
228            DataFormat::IRational => {
229                let rationals = entry.try_as_rationals::<i32>()?;
230                if rationals.len() == 1 {
231                    Ok(Self::IRational(rationals[0]))
232                } else {
233                    Ok(Self::IRationalArray(rationals))
234                }
235            }
236            DataFormat::F32 => match components_num {
237                1 => Ok(Self::F32(f32::try_from_bytes(data, endian)?)),
238                x => Err(Error::Unsupported(format!("float with {x} components"))),
239            },
240            DataFormat::F64 => match components_num {
241                1 => Ok(Self::F64(f64::try_from_bytes(data, endian)?)),
242                x => Err(Error::Unsupported(format!("double with {x} components"))),
243            },
244        }
245    }
246
247    fn variant_default(data_format: DataFormat) -> EntryValue {
248        match data_format {
249            DataFormat::U8 => Self::U8(0),
250            DataFormat::Text => Self::Text(String::default()),
251            DataFormat::U16 => Self::U16(0),
252            DataFormat::U32 => Self::U32(0),
253            DataFormat::URational => Self::URational(URational::default()),
254            DataFormat::I8 => Self::I8(0),
255            DataFormat::Undefined => Self::Undefined(Vec::default()),
256            DataFormat::I16 => Self::I16(0),
257            DataFormat::I32 => Self::I32(0),
258            DataFormat::IRational => Self::IRational(IRational::default()),
259            DataFormat::F32 => Self::F32(0.0),
260            DataFormat::F64 => Self::F64(0.0),
261        }
262    }
263
264    pub fn as_str(&self) -> Option<&str> {
265        match self {
266            EntryValue::Text(v) => Some(v),
267            _ => None,
268        }
269    }
270
271    pub fn as_time(&self) -> Option<DateTime<FixedOffset>> {
272        match self {
273            EntryValue::Time(v) => Some(*v),
274            _ => None,
275        }
276    }
277
278    pub fn as_u8(&self) -> Option<u8> {
279        match self {
280            EntryValue::U8(v) => Some(*v),
281            _ => None,
282        }
283    }
284
285    pub fn as_i8(&self) -> Option<i8> {
286        match self {
287            EntryValue::I8(v) => Some(*v),
288            _ => None,
289        }
290    }
291
292    pub fn as_u16(&self) -> Option<u16> {
293        match self {
294            EntryValue::U16(v) => Some(*v),
295            _ => None,
296        }
297    }
298
299    pub fn as_i16(&self) -> Option<i16> {
300        match self {
301            EntryValue::I16(v) => Some(*v),
302            _ => None,
303        }
304    }
305
306    pub fn as_u64(&self) -> Option<u64> {
307        match self {
308            EntryValue::U64(v) => Some(*v),
309            _ => None,
310        }
311    }
312
313    pub fn as_u32(&self) -> Option<u32> {
314        match self {
315            EntryValue::U32(v) => Some(*v),
316            _ => None,
317        }
318    }
319
320    pub fn as_i32(&self) -> Option<i32> {
321        match self {
322            EntryValue::I32(v) => Some(*v),
323            _ => None,
324        }
325    }
326
327    pub fn as_urational(&self) -> Option<URational> {
328        if let EntryValue::URational(v) = self {
329            Some(*v)
330        } else {
331            None
332        }
333    }
334
335    pub fn as_irational(&self) -> Option<IRational> {
336        if let EntryValue::IRational(v) = self {
337            Some(*v)
338        } else {
339            None
340        }
341    }
342
343    pub fn as_urational_array(&self) -> Option<&[URational]> {
344        if let EntryValue::URationalArray(v) = self {
345            Some(v)
346        } else {
347            None
348        }
349    }
350
351    pub fn as_irational_array(&self) -> Option<&[IRational]> {
352        if let EntryValue::IRationalArray(v) = self {
353            Some(v)
354        } else {
355            None
356        }
357    }
358}
359
360/// # Exif Data format
361///
362/// ```txt
363/// | Value           |             1 |             2 |              3 |               4 |                 5 |            6 |
364/// |-----------------+---------------+---------------+----------------+-----------------+-------------------+--------------|
365/// | Format          | unsigned byte | ascii strings | unsigned short |   unsigned long | unsigned rational |  signed byte |
366/// | Bytes/component |             1 |             1 |              2 |               4 |                 8 |            1 |
367///
368/// | Value           |             7 |             8 |              9 |              10 |                11 |           12 |
369/// |-----------------+---------------+---------------+----------------+-----------------+-------------------+--------------|
370/// | Format          |     undefined |  signed short |    signed long | signed rational |      single float | double float |
371/// | Bytes/component |             1 |             2 |              4 |               8 |                 4 |            8 |
372/// ```
373///
374/// See: [Exif](https://www.media.mit.edu/pia/Research/deepview/exif.html).
375#[repr(u16)]
376#[derive(Clone, Copy, Debug, PartialEq, Eq)]
377#[allow(unused)]
378pub(crate) enum DataFormat {
379    U8 = 1,
380    Text = 2,
381    U16 = 3,
382    U32 = 4,
383    URational = 5,
384    I8 = 6,
385    Undefined = 7,
386    I16 = 8,
387    I32 = 9,
388    IRational = 10,
389    F32 = 11,
390    F64 = 12,
391}
392
393impl DataFormat {
394    pub fn component_size(&self) -> usize {
395        match self {
396            Self::U8 | Self::I8 | Self::Text | Self::Undefined => 1,
397            Self::U16 | Self::I16 => 2,
398            Self::U32 | Self::I32 | Self::F32 => 4,
399            Self::URational | Self::IRational | Self::F64 => 8,
400        }
401    }
402}
403
404impl TryFrom<u16> for DataFormat {
405    type Error = Error;
406    fn try_from(v: u16) -> Result<Self, Self::Error> {
407        if v >= Self::U8 as u16 && v <= Self::F64 as u16 {
408            Ok(unsafe { std::mem::transmute::<u16, Self>(v) })
409        } else {
410            Err(Error::InvalidData(format!("data format {v}")))
411        }
412    }
413}
414
415#[cfg(feature = "json_dump")]
416impl Serialize for EntryValue {
417    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
418    where
419        S: Serializer,
420    {
421        serializer.serialize_str(&self.to_string())
422    }
423}
424
425impl Display for EntryValue {
426    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
427        match self {
428            EntryValue::Text(v) => v.fmt(f),
429            EntryValue::URational(v) => {
430                format!("{}/{} ({:.04})", v.0, v.1, v.0 as f64 / v.1 as f64).fmt(f)
431            }
432            EntryValue::IRational(v) => {
433                format!("{}/{} ({:.04})", v.0, v.1, v.0 as f64 / v.1 as f64).fmt(f)
434            }
435            EntryValue::U32(v) => Display::fmt(&v, f),
436            EntryValue::U16(v) => Display::fmt(&v, f),
437            EntryValue::U64(v) => Display::fmt(&v, f),
438            EntryValue::I16(v) => Display::fmt(&v, f),
439            EntryValue::I32(v) => Display::fmt(&v, f),
440            EntryValue::I64(v) => Display::fmt(&v, f),
441            EntryValue::F32(v) => Display::fmt(&v, f),
442            EntryValue::F64(v) => Display::fmt(&v, f),
443            EntryValue::U8(v) => Display::fmt(&v, f),
444            EntryValue::I8(v) => Display::fmt(&v, f),
445            EntryValue::Time(v) => Display::fmt(&v.to_rfc3339(), f),
446            EntryValue::Undefined(v) => {
447                // Display up to MAX_DISPLAY_NUM components, and replace the rest with ellipsis
448                const MAX_DISPLAY_NUM: usize = 8;
449                let s = v
450                    .iter()
451                    .map(|x| format!("0x{x:02x}"))
452                    .take(MAX_DISPLAY_NUM + 1)
453                    .enumerate()
454                    .map(|(i, x)| {
455                        if i >= MAX_DISPLAY_NUM {
456                            "...".to_owned()
457                        } else {
458                            x
459                        }
460                    })
461                    .collect::<Vec<String>>()
462                    .join(", ");
463                format!("Undefined[{}]", s).fmt(f)
464            }
465            EntryValue::URationalArray(v) => {
466                format!("URationalArray[{}]", rationals_to_string::<u32>(v)).fmt(f)
467            }
468            EntryValue::IRationalArray(v) => {
469                format!("IRationalArray[{}]", rationals_to_string::<i32>(v)).fmt(f)
470            }
471            EntryValue::U32Array(v) => array_to_string("U32Array", v, f),
472            EntryValue::U16Array(v) => array_to_string("U16Array", v, f),
473        }
474    }
475}
476
477fn array_to_string<T: Display>(
478    name: &str,
479    v: &[T],
480    f: &mut std::fmt::Formatter,
481) -> Result<(), std::fmt::Error> {
482    format!(
483        "{}[{}]",
484        name,
485        v.iter()
486            .map(|x| x.to_string())
487            .collect::<Vec<String>>()
488            .join(", ")
489    )
490    .fmt(f)
491}
492
493fn rationals_to_string<T>(rationals: &[Rational<T>]) -> String
494where
495    T: Display + Into<f64> + Copy,
496{
497    // Display up to MAX_DISPLAY_NUM components, and replace the rest with ellipsis
498    const MAX_DISPLAY_NUM: usize = 3;
499    rationals
500        .iter()
501        .map(|x| format!("{}/{} ({:.04})", x.0, x.1, x.0.into() / x.1.into()))
502        .take(MAX_DISPLAY_NUM + 1)
503        .enumerate()
504        .map(|(i, x)| {
505            if i >= MAX_DISPLAY_NUM {
506                "...".to_owned()
507            } else {
508                x
509            }
510        })
511        .collect::<Vec<String>>()
512        .join(", ")
513}
514
515impl From<DateTime<Utc>> for EntryValue {
516    fn from(value: DateTime<Utc>) -> Self {
517        assert_eq!(value.offset().fix(), FixedOffset::east_opt(0).unwrap());
518        EntryValue::Time(value.fixed_offset())
519    }
520}
521
522impl From<DateTime<FixedOffset>> for EntryValue {
523    fn from(value: DateTime<FixedOffset>) -> Self {
524        EntryValue::Time(value)
525    }
526}
527
528impl From<u8> for EntryValue {
529    fn from(value: u8) -> Self {
530        EntryValue::U8(value)
531    }
532}
533impl From<u16> for EntryValue {
534    fn from(value: u16) -> Self {
535        EntryValue::U16(value)
536    }
537}
538impl From<u32> for EntryValue {
539    fn from(value: u32) -> Self {
540        EntryValue::U32(value)
541    }
542}
543impl From<u64> for EntryValue {
544    fn from(value: u64) -> Self {
545        EntryValue::U64(value)
546    }
547}
548
549impl From<i8> for EntryValue {
550    fn from(value: i8) -> Self {
551        EntryValue::I8(value)
552    }
553}
554impl From<i16> for EntryValue {
555    fn from(value: i16) -> Self {
556        EntryValue::I16(value)
557    }
558}
559impl From<i32> for EntryValue {
560    fn from(value: i32) -> Self {
561        EntryValue::I32(value)
562    }
563}
564impl From<i64> for EntryValue {
565    fn from(value: i64) -> Self {
566        EntryValue::I64(value)
567    }
568}
569
570impl From<f32> for EntryValue {
571    fn from(value: f32) -> Self {
572        EntryValue::F32(value)
573    }
574}
575impl From<f64> for EntryValue {
576    fn from(value: f64) -> Self {
577        EntryValue::F64(value)
578    }
579}
580
581impl From<String> for EntryValue {
582    fn from(value: String) -> Self {
583        EntryValue::Text(value)
584    }
585}
586
587impl From<&String> for EntryValue {
588    fn from(value: &String) -> Self {
589        EntryValue::Text(value.to_owned())
590    }
591}
592
593impl From<&str> for EntryValue {
594    fn from(value: &str) -> Self {
595        value.to_owned().into()
596    }
597}
598
599impl From<(u32, u32)> for EntryValue {
600    fn from(value: (u32, u32)) -> Self {
601        Self::URational(value.into())
602    }
603}
604
605impl From<(i32, i32)> for EntryValue {
606    fn from(value: (i32, i32)) -> Self {
607        Self::IRational((value.0, value.1).into())
608    }
609}
610
611// #[cfg_attr(feature = "json_dump", derive(Serialize, Deserialize))]
612// #[derive(Debug, Copy, Clone, PartialEq, Eq, Default)]
613// pub struct URational(pub u32, pub u32);
614
615pub type URational = Rational<u32>;
616pub type IRational = Rational<i32>;
617
618#[cfg_attr(feature = "json_dump", derive(Serialize, Deserialize))]
619#[derive(Debug, Copy, Clone, PartialEq, Eq, Default)]
620pub struct Rational<T>(pub T, pub T);
621
622impl<T> Rational<T>
623where
624    T: Copy + Into<f64>,
625{
626    pub fn as_float(&self) -> f64 {
627        std::convert::Into::<f64>::into(self.0) / std::convert::Into::<f64>::into(self.1)
628    }
629}
630
631impl<T> From<(T, T)> for Rational<T>
632where
633    T: Copy,
634{
635    fn from(value: (T, T)) -> Self {
636        Self(value.0, value.1)
637    }
638}
639
640impl<T> From<Rational<T>> for (T, T)
641where
642    T: Copy,
643{
644    fn from(value: Rational<T>) -> Self {
645        (value.0, value.1)
646    }
647}
648
649impl From<IRational> for URational {
650    fn from(value: IRational) -> Self {
651        Self(value.0 as u32, value.1 as u32)
652    }
653}
654
655fn get_cstr(data: &[u8]) -> std::result::Result<String, FromUtf8Error> {
656    String::from_utf8(
657        data.iter()
658            .take_while(|b| **b != 0)
659            .filter(|b| **b != 0)
660            .cloned()
661            .collect::<Vec<u8>>(),
662    )
663}
664
665pub(crate) trait TryFromBytes: Sized {
666    fn try_from_bytes(bs: &[u8], endian: Endianness) -> Result<Self, Error>;
667}
668
669impl TryFromBytes for u32 {
670    fn try_from_bytes(bs: &[u8], endian: Endianness) -> Result<Self, Error> {
671        fn make_err<T>() -> Error {
672            Error::InvalidData(format!(
673                "data is too small to convert to {}",
674                std::any::type_name::<T>(),
675            ))
676        }
677        match endian {
678            Endianness::Big => {
679                let (int_bytes, _) = bs
680                    .split_at_checked(std::mem::size_of::<Self>())
681                    .ok_or_else(make_err::<Self>)?;
682                Ok(Self::from_be_bytes(
683                    int_bytes.try_into().map_err(|_| make_err::<Self>())?,
684                ))
685            }
686            Endianness::Little => {
687                let (int_bytes, _) = bs
688                    .split_at_checked(std::mem::size_of::<Self>())
689                    .ok_or_else(make_err::<Self>)?;
690                Ok(Self::from_le_bytes(
691                    int_bytes.try_into().map_err(|_| make_err::<Self>())?,
692                ))
693            }
694            Endianness::Native => unimplemented!(),
695        }
696    }
697}
698
699impl TryFromBytes for i32 {
700    fn try_from_bytes(bs: &[u8], endian: Endianness) -> Result<Self, Error> {
701        fn make_err<T>() -> Error {
702            Error::InvalidData(format!(
703                "data is too small to convert to {}",
704                std::any::type_name::<T>(),
705            ))
706        }
707        match endian {
708            Endianness::Big => {
709                let (int_bytes, _) = bs
710                    .split_at_checked(std::mem::size_of::<Self>())
711                    .ok_or_else(make_err::<Self>)?;
712                Ok(Self::from_be_bytes(
713                    int_bytes.try_into().map_err(|_| make_err::<Self>())?,
714                ))
715            }
716            Endianness::Little => {
717                let (int_bytes, _) = bs
718                    .split_at_checked(std::mem::size_of::<Self>())
719                    .ok_or_else(make_err::<Self>)?;
720                Ok(Self::from_le_bytes(
721                    int_bytes.try_into().map_err(|_| make_err::<Self>())?,
722                ))
723            }
724            Endianness::Native => unimplemented!(),
725        }
726    }
727}
728
729impl TryFromBytes for u16 {
730    fn try_from_bytes(bs: &[u8], endian: Endianness) -> Result<Self, Error> {
731        fn make_err<T>() -> Error {
732            Error::InvalidData(format!(
733                "data is too small to convert to {}",
734                std::any::type_name::<T>(),
735            ))
736        }
737        match endian {
738            Endianness::Big => {
739                let (int_bytes, _) = bs
740                    .split_at_checked(std::mem::size_of::<Self>())
741                    .ok_or_else(make_err::<Self>)?;
742                Ok(Self::from_be_bytes(
743                    int_bytes.try_into().map_err(|_| make_err::<Self>())?,
744                ))
745            }
746            Endianness::Little => {
747                let (int_bytes, _) = bs
748                    .split_at_checked(std::mem::size_of::<Self>())
749                    .ok_or_else(make_err::<Self>)?;
750                Ok(Self::from_le_bytes(
751                    int_bytes.try_into().map_err(|_| make_err::<Self>())?,
752                ))
753            }
754            Endianness::Native => unimplemented!(),
755        }
756    }
757}
758
759impl TryFromBytes for i16 {
760    fn try_from_bytes(bs: &[u8], endian: Endianness) -> Result<Self, Error> {
761        fn make_err<T>() -> Error {
762            Error::InvalidData(format!(
763                "data is too small to convert to {}",
764                std::any::type_name::<T>(),
765            ))
766        }
767        match endian {
768            Endianness::Big => {
769                let (int_bytes, _) = bs
770                    .split_at_checked(std::mem::size_of::<Self>())
771                    .ok_or_else(make_err::<Self>)?;
772                Ok(Self::from_be_bytes(
773                    int_bytes.try_into().map_err(|_| make_err::<Self>())?,
774                ))
775            }
776            Endianness::Little => {
777                let (int_bytes, _) = bs
778                    .split_at_checked(std::mem::size_of::<Self>())
779                    .ok_or_else(make_err::<Self>)?;
780                Ok(Self::from_le_bytes(
781                    int_bytes.try_into().map_err(|_| make_err::<Self>())?,
782                ))
783            }
784            Endianness::Native => unimplemented!(),
785        }
786    }
787}
788
789impl TryFromBytes for f32 {
790    fn try_from_bytes(bs: &[u8], endian: Endianness) -> Result<Self, Error> {
791        fn make_err<T>() -> Error {
792            Error::InvalidData(format!(
793                "data is too small to convert to {}",
794                std::any::type_name::<T>(),
795            ))
796        }
797        match endian {
798            Endianness::Big => {
799                let (int_bytes, _) = bs
800                    .split_at_checked(std::mem::size_of::<Self>())
801                    .ok_or_else(make_err::<Self>)?;
802                Ok(Self::from_be_bytes(
803                    int_bytes.try_into().map_err(|_| make_err::<Self>())?,
804                ))
805            }
806            Endianness::Little => {
807                let (int_bytes, _) = bs
808                    .split_at_checked(std::mem::size_of::<Self>())
809                    .ok_or_else(make_err::<Self>)?;
810                Ok(Self::from_le_bytes(
811                    int_bytes.try_into().map_err(|_| make_err::<Self>())?,
812                ))
813            }
814            Endianness::Native => unimplemented!(),
815        }
816    }
817}
818
819impl TryFromBytes for f64 {
820    fn try_from_bytes(bs: &[u8], endian: Endianness) -> Result<Self, Error> {
821        fn make_err<T>() -> Error {
822            Error::InvalidData(format!(
823                "data is too small to convert to {}",
824                std::any::type_name::<T>(),
825            ))
826        }
827        match endian {
828            Endianness::Big => {
829                let (int_bytes, _) = bs
830                    .split_at_checked(std::mem::size_of::<Self>())
831                    .ok_or_else(make_err::<Self>)?;
832                Ok(Self::from_be_bytes(
833                    int_bytes.try_into().map_err(|_| make_err::<Self>())?,
834                ))
835            }
836            Endianness::Little => {
837                let (int_bytes, _) = bs
838                    .split_at_checked(std::mem::size_of::<Self>())
839                    .ok_or_else(make_err::<Self>)?;
840                Ok(Self::from_le_bytes(
841                    int_bytes.try_into().map_err(|_| make_err::<Self>())?,
842                ))
843            }
844            Endianness::Native => unimplemented!(),
845        }
846    }
847}
848
849pub(crate) fn decode_rational<T: TryFromBytes>(
850    data: &[u8],
851    endian: Endianness,
852) -> Result<Rational<T>, Error> {
853    if data.len() < 8 {
854        return Err(Error::InvalidData(
855            "data is too small to decode a rational".to_string(),
856        ));
857    }
858
859    let numerator = T::try_from_bytes(data, endian)?;
860    let denominator = T::try_from_bytes(&data[4..], endian)?; // Safe-slice
861    Ok(Rational::<T>(numerator, denominator))
862}
863
864#[cfg(test)]
865mod tests {
866    use chrono::{Local, NaiveDateTime, TimeZone};
867
868    use super::*;
869
870    #[test]
871    fn test_parse_time() {
872        let tz = Local::now().format("%:z").to_string();
873
874        let s = format!("2023:07:09 20:36:33 {tz}");
875        let t1 = DateTime::parse_from_str(&s, "%Y:%m:%d %H:%M:%S %z").unwrap();
876
877        let s = "2023:07:09 20:36:33";
878        let t2 = NaiveDateTime::parse_from_str(s, "%Y:%m:%d %H:%M:%S").unwrap();
879        let t2 = Local.from_local_datetime(&t2).unwrap();
880
881        let t3 = t2.with_timezone(t2.offset());
882
883        assert_eq!(t1, t2);
884        assert_eq!(t1, t3);
885    }
886
887    #[test]
888    fn test_iso_8601() {
889        let s = "2023-11-02T19:58:34+0800";
890        let t1 = DateTime::parse_from_str(s, "%+").unwrap();
891
892        let s = "2023-11-02T19:58:34+08:00";
893        let t2 = DateTime::parse_from_str(s, "%+").unwrap();
894
895        let s = "2023-11-02T19:58:34.026490+08:00";
896        let t3 = DateTime::parse_from_str(s, "%+").unwrap();
897
898        assert_eq!(t1, t2);
899        assert!(t3 > t2);
900    }
901}