Skip to main content

nom_exif/
values.rs

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