1#[cfg(feature = "chrono")]
25#[cfg_attr(docsrs, doc(cfg(feature = "chrono")))]
26pub mod chrono;
27
28#[cfg(feature = "time")]
29#[cfg_attr(docsrs, doc(cfg(feature = "time")))]
30#[allow(clippy::module_inception)]
32pub mod time;
33
34use crate::{tds::codec::Encode, SqlReadBytes};
35#[cfg(feature = "tds73")]
36use byteorder::{ByteOrder, LittleEndian};
37use bytes::{BufMut, BytesMut};
38#[cfg(feature = "tds73")]
39use futures_util::io::AsyncReadExt;
40
41#[derive(Copy, Clone, Debug, Eq, PartialEq)]
48#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
49pub struct DateTime {
50 days: i32,
51 seconds_fragments: u32,
52}
53
54impl DateTime {
55 pub(crate) const LEAP_SECOND: u32 = u32::MAX;
58
59 pub fn new(days: i32, seconds_fragments: u32) -> Self {
61 Self {
62 days,
63 seconds_fragments,
64 }
65 }
66
67 pub fn days(self) -> i32 {
70 self.days
71 }
72
73 pub fn seconds_fragments(self) -> u32 {
75 self.seconds_fragments
76 }
77
78 pub(crate) async fn decode<R>(src: &mut R) -> crate::Result<Self>
79 where
80 R: SqlReadBytes + Unpin,
81 {
82 let days = src.read_i32_le().await?;
83 let seconds_fragments = src.read_u32_le().await?;
84 let dt = Self {
85 days,
86 seconds_fragments,
87 };
88
89 dt.millis_of_day()
90 .map_err(|_| crate::Error::Protocol(format!("invalid datetime {dt:?}").into()))?;
91 Ok(dt)
92 }
93
94 pub(crate) fn millis_of_day(self) -> crate::Result<u32> {
97 const TICKS_PER_DAY: u32 = 300 * 86_400;
98
99 if self.seconds_fragments >= TICKS_PER_DAY {
100 return Err(crate::Error::Conversion(
101 format!(
102 "datetime: {} ticks is past midnight",
103 self.seconds_fragments
104 )
105 .into(),
106 ));
107 }
108
109 Ok((self.seconds_fragments * 10 + 1) / 3)
111 }
112
113 pub(crate) fn validate(self) -> crate::Result<()> {
115 if self.seconds_fragments == Self::LEAP_SECOND {
116 return Err(crate::Error::Conversion(
117 "datetime: SQL Server has no leap seconds (second 60)".into(),
118 ));
119 }
120 self.millis_of_day().map(drop)
121 }
122}
123
124impl Encode<BytesMut> for DateTime {
125 fn encode(self, dst: &mut BytesMut) -> crate::Result<()> {
126 self.validate()?;
127 dst.put_i32_le(self.days);
128 dst.put_u32_le(self.seconds_fragments);
129
130 Ok(())
131 }
132}
133
134#[derive(Copy, Clone, Debug, Eq, PartialEq)]
142#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
143pub struct SmallDateTime {
144 days: u16,
145 seconds_fragments: u16,
146}
147
148impl SmallDateTime {
149 pub fn new(days: u16, seconds_fragments: u16) -> Self {
151 Self {
152 days,
153 seconds_fragments,
154 }
155 }
156 pub fn days(self) -> u16 {
158 self.days
159 }
160
161 pub fn seconds_fragments(self) -> u16 {
163 self.seconds_fragments
164 }
165
166 pub(crate) async fn decode<R>(src: &mut R) -> crate::Result<Self>
167 where
168 R: SqlReadBytes + Unpin,
169 {
170 let days = src.read_u16_le().await?;
171 let seconds_fragments = src.read_u16_le().await?;
172 let dt = Self {
173 days,
174 seconds_fragments,
175 };
176
177 dt.minutes_of_day()
178 .map_err(|_| crate::Error::Protocol(format!("invalid smalldatetime {dt:?}").into()))?;
179 Ok(dt)
180 }
181
182 pub(crate) fn minutes_of_day(self) -> crate::Result<u32> {
184 match self.seconds_fragments {
185 m @ 0..=1439 => Ok(m as u32),
186 m => Err(crate::Error::Conversion(
187 format!("smalldatetime: {m} minutes is past midnight").into(),
188 )),
189 }
190 }
191
192 pub(crate) fn validate(self) -> crate::Result<()> {
194 self.minutes_of_day().map(drop)
195 }
196}
197
198impl Encode<BytesMut> for SmallDateTime {
199 fn encode(self, dst: &mut BytesMut) -> crate::Result<()> {
200 self.validate()?;
201 dst.put_u16_le(self.days);
202 dst.put_u16_le(self.seconds_fragments);
203
204 Ok(())
205 }
206}
207
208#[derive(Copy, Clone, Debug, Eq, PartialEq)]
215#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
216#[cfg(feature = "tds73")]
217#[cfg_attr(docsrs, doc(cfg(feature = "tds73")))]
218pub struct Date(u32);
219
220#[cfg(feature = "tds73")]
221#[cfg_attr(docsrs, doc(cfg(feature = "tds73")))]
222impl Date {
223 #[inline]
224 pub fn new(days: u32) -> Date {
229 assert_eq!(days >> 24, 0);
230 Date(days)
231 }
232
233 #[inline]
234 pub fn days(self) -> u32 {
236 self.0
237 }
238
239 #[cfg_attr(not(any(feature = "chrono", feature = "time")), allow(dead_code))]
242 pub(crate) fn from_signed_days(days: i64) -> Date {
243 Date(u32::try_from(days).unwrap_or(u32::MAX))
244 }
245
246 pub(crate) async fn decode<R>(src: &mut R) -> crate::Result<Self>
247 where
248 R: SqlReadBytes + Unpin,
249 {
250 let mut bytes = [0u8; 4];
251 src.read_exact(&mut bytes[..3]).await?;
252 Ok(Self::new(LittleEndian::read_u32(&bytes)))
253 }
254}
255
256#[cfg(feature = "tds73")]
257#[cfg_attr(docsrs, doc(cfg(feature = "tds73")))]
258impl Encode<BytesMut> for Date {
259 fn encode(self, dst: &mut BytesMut) -> crate::Result<()> {
260 const MAX_DAYS: u32 = 3_652_058;
263
264 if self.days() > MAX_DAYS {
265 return Err(crate::Error::Conversion(
266 "date out of range: SQL Server dates run from 0001-01-01 to 9999-12-31".into(),
267 ));
268 }
269
270 let mut tmp = [0u8; 4];
271 LittleEndian::write_u32(&mut tmp, self.days());
272 dst.extend_from_slice(&tmp[0..3]);
273
274 Ok(())
275 }
276}
277
278#[derive(Copy, Clone, Debug)]
285#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
286#[cfg(feature = "tds73")]
287#[cfg_attr(docsrs, doc(cfg(feature = "tds73")))]
288pub struct Time {
289 increments: u64,
290 scale: u8,
291}
292
293#[cfg(feature = "tds73")]
294#[cfg_attr(docsrs, doc(cfg(feature = "tds73")))]
295impl PartialEq for Time {
296 fn eq(&self, t: &Time) -> bool {
297 self.increments as f64 / 10f64.powi(self.scale as i32)
298 == t.increments as f64 / 10f64.powi(t.scale as i32)
299 }
300}
301
302#[cfg(feature = "tds73")]
303#[cfg_attr(docsrs, doc(cfg(feature = "tds73")))]
304impl Time {
305 pub(crate) const LEAP_SECOND: u64 = u64::MAX;
308
309 pub fn new(increments: u64, scale: u8) -> Self {
311 Self { increments, scale }
312 }
313
314 pub(crate) fn validate(self) -> crate::Result<()> {
316 self.len()?;
317
318 if self.increments < 86_400 * 10u64.pow(u32::from(self.scale)) {
319 return Ok(());
320 }
321
322 Err(crate::Error::Conversion(match self.increments {
323 Self::LEAP_SECOND => "time: SQL Server has no leap seconds (second 60)".into(),
324 n => format!(
325 "time out of range: {n} increments of 10^-{} s is 24 hours or more",
326 self.scale
327 )
328 .into(),
329 }))
330 }
331
332 #[inline]
333 pub fn increments(self) -> u64 {
338 self.increments
339 }
340
341 #[inline]
342 pub fn scale(self) -> u8 {
344 self.scale
345 }
346
347 #[inline]
348 pub(crate) fn len(self) -> crate::Result<u8> {
350 Ok(match self.scale {
351 0..=2 => 3,
352 3..=4 => 4,
353 5..=7 => 5,
354 _ => {
355 return Err(crate::Error::Protocol(
356 format!("timen: invalid scale {}", self.scale).into(),
357 ))
358 }
359 })
360 }
361
362 pub(crate) async fn decode<R>(src: &mut R, n: usize, rlen: usize) -> crate::Result<Time>
363 where
364 R: SqlReadBytes + Unpin,
365 {
366 let val = match (n, rlen) {
367 (0..=2, 3) => {
368 let hi = src.read_u16_le().await? as u64;
369 let lo = src.read_u8().await? as u64;
370
371 hi | lo << 16
372 }
373 (3..=4, 4) => src.read_u32_le().await? as u64,
374 (5..=7, 5) => {
375 let hi = src.read_u32_le().await? as u64;
376 let lo = src.read_u8().await? as u64;
377
378 hi | lo << 32
379 }
380 _ => {
381 return Err(crate::Error::Protocol(
382 format!("timen: invalid length {}", n).into(),
383 ))
384 }
385 };
386
387 Ok(Time {
388 increments: val,
389 scale: n as u8,
390 })
391 }
392}
393
394#[cfg(feature = "tds73")]
395#[cfg_attr(docsrs, doc(cfg(feature = "tds73")))]
396impl Encode<BytesMut> for Time {
397 fn encode(self, dst: &mut BytesMut) -> crate::Result<()> {
398 self.validate()?;
399
400 let len = usize::from(self.len()?);
402 dst.extend_from_slice(&self.increments.to_le_bytes()[..len]);
403
404 Ok(())
405 }
406}
407
408#[derive(Copy, Clone, Debug, PartialEq)]
409#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
410#[cfg(feature = "tds73")]
411#[cfg_attr(docsrs, doc(cfg(feature = "tds73")))]
412pub struct DateTime2 {
420 date: Date,
421 time: Time,
422}
423
424#[cfg(feature = "tds73")]
425#[cfg_attr(docsrs, doc(cfg(feature = "tds73")))]
426impl DateTime2 {
427 pub fn new(date: Date, time: Time) -> Self {
429 Self { date, time }
430 }
431
432 pub fn date(self) -> Date {
434 self.date
435 }
436
437 pub fn time(self) -> Time {
439 self.time
440 }
441
442 pub(crate) async fn decode<R>(src: &mut R, n: usize, rlen: usize) -> crate::Result<Self>
443 where
444 R: SqlReadBytes + Unpin,
445 {
446 let time = Time::decode(src, n, rlen).await?;
447
448 let mut bytes = [0u8; 4];
449 src.read_exact(&mut bytes[..3]).await?;
450 let date = Date::new(LittleEndian::read_u32(&bytes));
451
452 Ok(Self::new(date, time))
453 }
454}
455
456#[cfg(feature = "tds73")]
457#[cfg_attr(docsrs, doc(cfg(feature = "tds73")))]
458impl Encode<BytesMut> for DateTime2 {
459 fn encode(self, dst: &mut BytesMut) -> crate::Result<()> {
460 self.time.encode(dst)?;
461 self.date.encode(dst)
462 }
463}
464
465#[derive(Copy, Clone, Debug, PartialEq)]
466#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
467#[cfg(feature = "tds73")]
468#[cfg_attr(docsrs, doc(cfg(feature = "tds73")))]
469pub struct DateTimeOffset {
477 datetime2: DateTime2,
478 offset: i16,
479}
480
481#[cfg(feature = "tds73")]
482#[cfg_attr(docsrs, doc(cfg(feature = "tds73")))]
483impl DateTimeOffset {
484 pub(crate) const OFFSET_WITH_SECONDS: i16 = i16::MIN;
487
488 pub fn new(datetime2: DateTime2, offset: i16) -> Self {
491 Self { datetime2, offset }
492 }
493
494 pub fn datetime2(self) -> DateTime2 {
496 self.datetime2
497 }
498
499 pub fn offset(self) -> i16 {
501 self.offset
502 }
503
504 pub(crate) async fn decode<R>(src: &mut R, n: usize, rlen: u8) -> crate::Result<Self>
505 where
506 R: SqlReadBytes + Unpin,
507 {
508 let datetime2 = DateTime2::decode(src, n, rlen as usize).await?;
509 let offset = src.read_i16_le().await?;
510
511 Ok(Self { datetime2, offset })
512 }
513}
514
515#[cfg(feature = "tds73")]
516#[cfg_attr(docsrs, doc(cfg(feature = "tds73")))]
517impl Encode<BytesMut> for DateTimeOffset {
518 fn encode(self, dst: &mut BytesMut) -> crate::Result<()> {
519 match self.offset {
521 -840..=840 => {}
522 Self::OFFSET_WITH_SECONDS => {
523 return Err(crate::Error::Conversion(
524 "datetimeoffset: the UTC offset has seconds; SQL Server stores whole minutes"
525 .into(),
526 ))
527 }
528 o => {
529 return Err(crate::Error::Conversion(
530 format!("datetimeoffset: offset of {o} minutes is outside -14:00 to +14:00")
531 .into(),
532 ))
533 }
534 }
535
536 self.datetime2.encode(dst)?;
537 dst.put_i16_le(self.offset);
538
539 Ok(())
540 }
541}
542
543#[cfg(test)]
544mod tests {
545 use super::*;
546 use crate::sql_read_bytes::test_utils::IntoSqlReadBytes;
547
548 #[test]
549 fn datetime_accessors() {
550 let dt = DateTime::new(-100, 12345);
551 assert_eq!(dt.days(), -100);
552 assert_eq!(dt.seconds_fragments(), 12345);
553 }
554
555 #[tokio::test]
556 async fn datetime_round_trip_including_pre_1900() {
557 for dt in [
558 DateTime::new(0, 0),
559 DateTime::new(200, 3000),
560 DateTime::new(-53690, 0),
562 DateTime::new(2958463, 25919999),
563 ] {
564 let mut buf = BytesMut::new();
565 dt.encode(&mut buf).unwrap();
566 let decoded = DateTime::decode(&mut buf.into_sql_read_bytes())
567 .await
568 .unwrap();
569 assert_eq!(decoded, dt);
570 }
571 }
572
573 #[tokio::test]
574 async fn decode_rejects_a_time_past_midnight() {
575 let mut buf = BytesMut::new();
576 buf.put_i32_le(0);
577 buf.put_u32_le(25920000);
578 let err = DateTime::decode(&mut buf.into_sql_read_bytes()).await;
579 assert!(matches!(err, Err(crate::Error::Protocol(_))), "{err:?}");
580
581 let mut buf = BytesMut::new();
582 buf.put_u16_le(0);
583 buf.put_u16_le(1440);
584 let err = SmallDateTime::decode(&mut buf.into_sql_read_bytes()).await;
585 assert!(matches!(err, Err(crate::Error::Protocol(_))), "{err:?}");
586 }
587
588 #[test]
590 fn encode_rejects_a_time_past_midnight() {
591 for dt in [DateTime::new(0, 25920000), DateTime::new(0, u32::MAX)] {
592 let err = dt.encode(&mut BytesMut::new()).unwrap_err();
593 assert!(matches!(err, crate::Error::Conversion(_)), "{dt:?}");
594 }
595 let err = SmallDateTime::new(0, 1440).encode(&mut BytesMut::new());
596 assert!(matches!(err, Err(crate::Error::Conversion(_))), "{err:?}");
597
598 DateTime::new(0, 25919999)
599 .encode(&mut BytesMut::new())
600 .unwrap();
601 SmallDateTime::new(0, 1439)
602 .encode(&mut BytesMut::new())
603 .unwrap();
604 }
605
606 #[test]
607 fn datetime_millis_round_half_up() {
608 let ms = |ticks| DateTime::new(0, ticks).millis_of_day().unwrap();
609 assert_eq!(ms(1), 3);
611 assert_eq!(ms(2), 7);
612 assert_eq!(ms(3), 10);
613 assert_eq!(ms(299), 997);
614 assert_eq!(ms(25919999), 86_399_997);
615 assert!(DateTime::new(0, 25920000).millis_of_day().is_err());
616 assert!(DateTime::new(0, u32::MAX).millis_of_day().is_err());
617 }
618
619 #[test]
620 fn smalldatetime_accessors() {
621 let dt = SmallDateTime::new(100, 200);
622 assert_eq!(dt.days(), 100);
623 assert_eq!(dt.seconds_fragments(), 200);
624 }
625
626 #[tokio::test]
627 async fn smalldatetime_round_trip() {
628 let dt = SmallDateTime::new(65535, 1439);
629 let mut buf = BytesMut::new();
630 dt.encode(&mut buf).unwrap();
631 let decoded = SmallDateTime::decode(&mut buf.into_sql_read_bytes())
632 .await
633 .unwrap();
634 assert_eq!(decoded, dt);
635 }
636
637 #[cfg(feature = "tds73")]
638 #[test]
639 fn date_accessor_and_new() {
640 let date = Date::new(730119);
641 assert_eq!(date.days(), 730119);
642 }
643
644 #[cfg(feature = "tds73")]
645 #[test]
646 #[should_panic]
647 fn date_new_panics_on_overflow() {
648 Date::new(0x0100_0000);
650 }
651
652 #[cfg(feature = "tds73")]
653 #[tokio::test]
654 async fn date_round_trip() {
655 for days in [0u32, 1, 730119, 3_652_058] {
657 let date = Date::new(days);
658 let mut buf = BytesMut::new();
659 date.encode(&mut buf).unwrap();
660 assert_eq!(buf.len(), 3);
661 let decoded = Date::decode(&mut buf.into_sql_read_bytes()).await.unwrap();
662 assert_eq!(decoded, date);
663 }
664 }
665
666 #[cfg(feature = "tds73")]
667 #[test]
668 fn dates_outside_year_1_to_9999_are_encode_errors() {
669 for date in [
671 Date::new(3_652_059),
672 Date::new(0x00ff_ffff),
673 Date::from_signed_days(-1),
674 ] {
675 let err = date.encode(&mut BytesMut::new()).unwrap_err();
676 assert!(matches!(err, crate::Error::Conversion(_)), "{date:?}");
677
678 let dt2 = DateTime2::new(date, Time::new(0, 7));
679 let err = dt2.encode(&mut BytesMut::new()).unwrap_err();
680 assert!(matches!(err, crate::Error::Conversion(_)), "{date:?}");
681 }
682 }
683
684 #[cfg(feature = "tds73")]
685 #[test]
686 fn time_accessors_and_len() {
687 let time = Time::new(1234, 5);
688 assert_eq!(time.increments(), 1234);
689 assert_eq!(time.scale(), 5);
690 assert_eq!(time.len().unwrap(), 5);
691
692 assert_eq!(Time::new(0, 0).len().unwrap(), 3);
693 assert_eq!(Time::new(0, 3).len().unwrap(), 4);
694 assert!(Time::new(0, 8).len().is_err());
695 }
696
697 #[cfg(feature = "tds73")]
698 #[test]
699 fn time_partial_eq_across_scales() {
700 assert_eq!(Time::new(100, 2), Time::new(10_000_000, 7));
702 assert_ne!(Time::new(100, 2), Time::new(200, 2));
703 }
704
705 #[cfg(feature = "tds73")]
706 #[tokio::test]
707 async fn time_round_trip_all_len_buckets() {
708 for (increments, scale) in [(255u64, 2u8), (65535, 4), (16_777_215, 7)] {
709 let time = Time::new(increments, scale);
710 let rlen = time.len().unwrap();
711 let mut buf = BytesMut::new();
712 time.encode(&mut buf).unwrap();
713 let decoded = Time::decode(
714 &mut buf.into_sql_read_bytes(),
715 scale as usize,
716 rlen as usize,
717 )
718 .await
719 .unwrap();
720 assert_eq!(decoded, time);
721 }
722 }
723
724 #[cfg(feature = "tds73")]
725 #[tokio::test]
726 async fn time_round_trip_high_bytes_set() {
727 for (increments, scale) in [(0x0083_1234u64, 2u8), (0x00AB_1234_5678u64, 7)] {
736 let time = Time::new(increments, scale);
737 let rlen = time.len().unwrap();
738
739 let mut buf = BytesMut::new();
740 time.encode(&mut buf).unwrap();
741
742 let decoded = Time::decode(
743 &mut buf.into_sql_read_bytes(),
744 scale as usize,
745 rlen as usize,
746 )
747 .await
748 .unwrap();
749
750 assert_eq!(decoded, time);
751 assert_eq!(decoded.increments(), increments);
752 }
753 }
754
755 #[cfg(feature = "tds73")]
756 #[test]
757 fn times_from_24_hours_up_are_encode_errors() {
758 for time in [
761 Time::new(1 << 24, 0),
762 Time::new(1 << 32, 4),
763 Time::new(u64::MAX, 7),
764 Time::new(86_400, 0),
765 Time::new(8_640_000, 2),
766 Time::new(864_000_000_000, 7),
767 Time::new(Time::LEAP_SECOND, 7),
768 ] {
769 let err = time.encode(&mut BytesMut::new()).unwrap_err();
770 assert!(matches!(err, crate::Error::Conversion(_)), "{time:?}");
771
772 let dt2 = DateTime2::new(Date::new(1), time);
773 let err = dt2.encode(&mut BytesMut::new()).unwrap_err();
774 assert!(matches!(err, crate::Error::Conversion(_)), "{time:?}");
775 }
776
777 for scale in 0..=7 {
778 let last = 86_400 * 10u64.pow(scale) - 1;
779 Time::new(last, scale as u8)
780 .encode(&mut BytesMut::new())
781 .unwrap();
782 }
783 }
784
785 #[cfg(feature = "tds73")]
786 #[test]
787 fn datetimeoffset_offsets_beyond_14_hours_are_encode_errors() {
788 let dt2 = DateTime2::new(Date::new(1), Time::new(0, 7));
789 for offset in [-841, 841, i16::MAX, DateTimeOffset::OFFSET_WITH_SECONDS] {
790 let err = DateTimeOffset::new(dt2, offset)
791 .encode(&mut BytesMut::new())
792 .unwrap_err();
793 assert!(matches!(err, crate::Error::Conversion(_)), "{offset}");
794 }
795 for offset in [-840, 0, 840] {
796 DateTimeOffset::new(dt2, offset)
797 .encode(&mut BytesMut::new())
798 .unwrap();
799 }
800 }
801
802 #[cfg(feature = "tds73")]
803 #[tokio::test]
804 async fn time_decode_invalid_length_errors() {
805 let mut buf = BytesMut::new();
806 buf.put_u8(0);
807 let err = Time::decode(&mut buf.into_sql_read_bytes(), 0, 4).await;
809 assert!(err.is_err());
810 }
811
812 #[cfg(feature = "tds73")]
813 #[tokio::test]
814 async fn datetime2_round_trip_and_accessors() {
815 let dt2 = DateTime2::new(Date::new(730119), Time::new(222, 7));
816 assert_eq!(dt2.date(), Date::new(730119));
817 assert_eq!(dt2.time(), Time::new(222, 7));
818
819 let rlen = dt2.time().len().unwrap();
820 let mut buf = BytesMut::new();
821 dt2.encode(&mut buf).unwrap();
822 let decoded = DateTime2::decode(&mut buf.into_sql_read_bytes(), 7, rlen as usize)
823 .await
824 .unwrap();
825 assert_eq!(decoded, dt2);
826 }
827
828 #[cfg(feature = "tds73")]
829 #[tokio::test]
830 async fn datetimeoffset_round_trip_and_accessors() {
831 let dt2 = DateTime2::new(Date::new(730119), Time::new(222, 7));
832 let dto = DateTimeOffset::new(dt2, -120);
833 assert_eq!(dto.datetime2(), dt2);
834 assert_eq!(dto.offset(), -120);
835
836 let rlen = dto.datetime2().time().len().unwrap();
837 let mut buf = BytesMut::new();
838 dto.encode(&mut buf).unwrap();
839 let decoded = DateTimeOffset::decode(&mut buf.into_sql_read_bytes(), 7, rlen)
840 .await
841 .unwrap();
842 assert_eq!(decoded, dto);
843 }
844}