Skip to main content

nom_exif/
values.rs

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