domain/base/name/
uncertain.rs

1//! A domain name that can be both relative or absolute.
2//!
3//! This is a private module. Its public types are re-exported by the parent.
4
5use super::super::scan::Scanner;
6use super::super::wire::ParseError;
7use super::absolute::Name;
8use super::builder::{FromStrError, NameBuilder, PushError};
9use super::chain::{Chain, LongChainError};
10use super::label::{Label, LabelTypeError, SplitLabelError};
11use super::relative::{NameIter, RelativeName};
12use super::traits::ToLabelIter;
13#[cfg(feature = "bytes")]
14use bytes::Bytes;
15use core::{fmt, hash, str};
16use octseq::builder::{
17    EmptyBuilder, FreezeBuilder, FromBuilder, IntoBuilder,
18};
19#[cfg(feature = "serde")]
20use octseq::serde::{DeserializeOctets, SerializeOctets};
21#[cfg(feature = "std")]
22use std::vec::Vec;
23
24//------------ UncertainName ------------------------------------------------
25
26/// A domain name that may be absolute or relative.
27///
28/// This type is helpful when reading a domain name from some source where it
29/// may end up being absolute or not.
30#[derive(Clone)]
31pub enum UncertainName<Octets> {
32    Absolute(Name<Octets>),
33    Relative(RelativeName<Octets>),
34}
35
36impl<Octets> UncertainName<Octets> {
37    /// Creates a new uncertain domain name from an absolute domain name.
38    pub fn absolute(name: Name<Octets>) -> Self {
39        UncertainName::Absolute(name)
40    }
41
42    /// Creates a new uncertain domain name from a relative domain name.
43    pub fn relative(name: RelativeName<Octets>) -> Self {
44        UncertainName::Relative(name)
45    }
46
47    /// Creates a new uncertain domain name containing the root label only.
48    #[must_use]
49    pub fn root() -> Self
50    where
51        Octets: From<&'static [u8]>,
52    {
53        UncertainName::Absolute(Name::root())
54    }
55
56    /// Creates a new uncertain yet empty domain name.
57    #[must_use]
58    pub fn empty() -> Self
59    where
60        Octets: From<&'static [u8]>,
61    {
62        UncertainName::Relative(RelativeName::empty())
63    }
64
65    /// Creates a new domain name from its wire format representation.
66    ///
67    /// The returned name will correctly be identified as an absolute or
68    /// relative name.
69    pub fn from_octets(octets: Octets) -> Result<Self, UncertainDnameError>
70    where
71        Octets: AsRef<[u8]>,
72    {
73        if Self::is_slice_absolute(octets.as_ref())? {
74            Ok(UncertainName::Absolute(unsafe {
75                Name::from_octets_unchecked(octets)
76            }))
77        } else {
78            Ok(UncertainName::Relative(unsafe {
79                RelativeName::from_octets_unchecked(octets)
80            }))
81        }
82    }
83
84    /// Checks an octet slice for a name and returns whether it is absolute.
85    fn is_slice_absolute(
86        mut slice: &[u8],
87    ) -> Result<bool, UncertainDnameError> {
88        if slice.len() > Name::MAX_LEN {
89            return Err(UncertainDnameErrorEnum::LongName.into());
90        }
91        loop {
92            let (label, tail) = Label::split_from(slice)?;
93            if label.is_root() {
94                if tail.is_empty() {
95                    return Ok(true);
96                } else {
97                    return Err(UncertainDnameErrorEnum::TrailingData.into());
98                }
99            }
100            if tail.is_empty() {
101                return Ok(false);
102            }
103            slice = tail;
104        }
105    }
106
107    /// Creates a domain name from a sequence of characters.
108    ///
109    /// The sequence must result in a domain name in zone file
110    /// representation. That is, its labels should be separated by dots,
111    /// while actual dots, white space and backslashes should be escaped by a
112    /// preceeding backslash, and any byte value that is not a printable
113    /// ASCII character should be encoded by a backslash followed by its
114    /// three digit decimal value.
115    ///
116    /// If Internationalized Domain Names are to be used, the labels already
117    /// need to be in punycode-encoded form.
118    ///
119    /// If the last character is a dot, the name will be absolute, otherwise
120    /// it will be relative.
121    ///
122    /// If you have a string, you can also use the [`FromStr`] trait, which
123    /// really does the same thing.
124    ///
125    /// [`FromStr`]: std::str::FromStr
126    pub fn from_chars<C>(chars: C) -> Result<Self, FromStrError>
127    where
128        Octets: FromBuilder,
129        <Octets as FromBuilder>::Builder: FreezeBuilder<Octets = Octets>
130            + EmptyBuilder
131            + AsRef<[u8]>
132            + AsMut<[u8]>,
133        C: IntoIterator<Item = char>,
134    {
135        let mut builder =
136            NameBuilder::<<Octets as FromBuilder>::Builder>::new();
137        builder.append_chars(chars)?;
138        if builder.in_label() || builder.is_empty() {
139            Ok(builder.finish().into())
140        } else {
141            Ok(builder.into_name()?.into())
142        }
143    }
144
145    pub fn scan<S: Scanner<Name = Name<Octets>>>(
146        scanner: &mut S,
147    ) -> Result<Self, S::Error> {
148        scanner.scan_name().map(UncertainName::Absolute)
149    }
150}
151
152impl UncertainName<&'static [u8]> {
153    /// Creates an empty relative name atop a slice reference.
154    #[must_use]
155    pub fn empty_ref() -> Self {
156        Self::empty()
157    }
158
159    /// Creates an absolute name that is the root label atop a slice reference.
160    #[must_use]
161    pub fn root_ref() -> Self {
162        Self::root()
163    }
164}
165
166#[cfg(feature = "std")]
167impl UncertainName<Vec<u8>> {
168    /// Creates an empty relative name atop a `Vec<u8>`.
169    #[must_use]
170    pub fn empty_vec() -> Self {
171        Self::empty()
172    }
173
174    /// Creates an absolute name from the root label atop a `Vec<u8>`.
175    #[must_use]
176    pub fn root_vec() -> Self {
177        Self::root()
178    }
179}
180
181#[cfg(feature = "bytes")]
182impl UncertainName<Bytes> {
183    /// Creates an empty relative name atop a bytes value.
184    pub fn empty_bytes() -> Self {
185        Self::empty()
186    }
187
188    /// Creates an absolute name from the root label atop a bytes value.
189    pub fn root_bytes() -> Self {
190        Self::root()
191    }
192}
193
194impl<Octets> UncertainName<Octets> {
195    /// Returns whether the name is absolute.
196    pub fn is_absolute(&self) -> bool {
197        match *self {
198            UncertainName::Absolute(_) => true,
199            UncertainName::Relative(_) => false,
200        }
201    }
202
203    /// Returns whether the name is relative.
204    pub fn is_relative(&self) -> bool {
205        !self.is_absolute()
206    }
207
208    /// Returns a reference to an absolute name, if this name is absolute.
209    pub fn as_absolute(&self) -> Option<&Name<Octets>> {
210        match *self {
211            UncertainName::Absolute(ref name) => Some(name),
212            _ => None,
213        }
214    }
215
216    /// Returns a reference to a relative name, if the name is relative.
217    pub fn as_relative(&self) -> Option<&RelativeName<Octets>> {
218        match *self {
219            UncertainName::Relative(ref name) => Some(name),
220            _ => None,
221        }
222    }
223
224    /// Converts the name into an absolute name.
225    ///
226    /// If the name is relative, appends the root label to it using
227    /// [`RelativeName::into_absolute`].
228    pub fn into_absolute(self) -> Result<Name<Octets>, PushError>
229    where
230        Octets: AsRef<[u8]> + IntoBuilder,
231        <Octets as IntoBuilder>::Builder:
232            FreezeBuilder<Octets = Octets> + AsRef<[u8]> + AsMut<[u8]>,
233    {
234        match self {
235            UncertainName::Absolute(name) => Ok(name),
236            UncertainName::Relative(name) => name.into_absolute(),
237        }
238    }
239
240    /// Converts the name into an absolute name if it is absolute.
241    ///
242    /// Otherwise, returns itself as the error.
243    pub fn try_into_absolute(self) -> Result<Name<Octets>, Self> {
244        if let UncertainName::Absolute(name) = self {
245            Ok(name)
246        } else {
247            Err(self)
248        }
249    }
250
251    /// Converts the name into a relative name if it is relative.
252    ///
253    /// Otherwise just returns itself as the error.
254    pub fn try_into_relative(self) -> Result<RelativeName<Octets>, Self> {
255        if let UncertainName::Relative(name) = self {
256            Ok(name)
257        } else {
258            Err(self)
259        }
260    }
261
262    /// Returns a reference to the underlying octets sequence.
263    pub fn as_octets(&self) -> &Octets {
264        match *self {
265            UncertainName::Absolute(ref name) => name.as_octets(),
266            UncertainName::Relative(ref name) => name.as_octets(),
267        }
268    }
269
270    /// Returns an octets slice with the raw content of the name.
271    pub fn as_slice(&self) -> &[u8]
272    where
273        Octets: AsRef<[u8]>,
274    {
275        match *self {
276            UncertainName::Absolute(ref name) => name.as_slice(),
277            UncertainName::Relative(ref name) => name.as_slice(),
278        }
279    }
280
281    /// Makes an uncertain name absolute by chaining on a suffix if needed.
282    ///
283    /// The method converts the uncertain name into a chain that will
284    /// be absolute. If the name is already absolute, the chain will be the
285    /// name itself. If it is relative, if will be the concatenation of the
286    /// name and `suffix`.
287    pub fn chain<S: ToLabelIter>(
288        self,
289        suffix: S,
290    ) -> Result<Chain<Self, S>, LongChainError>
291    where
292        Octets: AsRef<[u8]>,
293    {
294        Chain::new_uncertain(self, suffix)
295    }
296}
297
298//--- From
299
300impl<Octets> From<Name<Octets>> for UncertainName<Octets> {
301    fn from(src: Name<Octets>) -> Self {
302        UncertainName::Absolute(src)
303    }
304}
305
306impl<Octets> From<RelativeName<Octets>> for UncertainName<Octets> {
307    fn from(src: RelativeName<Octets>) -> Self {
308        UncertainName::Relative(src)
309    }
310}
311
312//--- FromStr
313
314impl<Octets> str::FromStr for UncertainName<Octets>
315where
316    Octets: FromBuilder,
317    <Octets as FromBuilder>::Builder: EmptyBuilder
318        + FreezeBuilder<Octets = Octets>
319        + AsRef<[u8]>
320        + AsMut<[u8]>,
321{
322    type Err = FromStrError;
323
324    fn from_str(s: &str) -> Result<Self, Self::Err> {
325        Self::from_chars(s.chars())
326    }
327}
328
329//--- AsRef
330
331impl<Octs> AsRef<Octs> for UncertainName<Octs> {
332    fn as_ref(&self) -> &Octs {
333        match *self {
334            UncertainName::Absolute(ref name) => name.as_ref(),
335            UncertainName::Relative(ref name) => name.as_ref(),
336        }
337    }
338}
339
340impl<Octs: AsRef<[u8]>> AsRef<[u8]> for UncertainName<Octs> {
341    fn as_ref(&self) -> &[u8] {
342        match *self {
343            UncertainName::Absolute(ref name) => name.as_ref(),
344            UncertainName::Relative(ref name) => name.as_ref(),
345        }
346    }
347}
348
349//--- PartialEq, and Eq
350
351impl<Octets, Other> PartialEq<UncertainName<Other>> for UncertainName<Octets>
352where
353    Octets: AsRef<[u8]>,
354    Other: AsRef<[u8]>,
355{
356    fn eq(&self, other: &UncertainName<Other>) -> bool {
357        use UncertainName::*;
358
359        match (self, other) {
360            (Absolute(l), Absolute(r)) => l.eq(r),
361            (Relative(l), Relative(r)) => l.eq(r),
362            _ => false,
363        }
364    }
365}
366
367impl<Octets: AsRef<[u8]>> Eq for UncertainName<Octets> {}
368
369//--- Hash
370
371impl<Octets: AsRef<[u8]>> hash::Hash for UncertainName<Octets> {
372    fn hash<H: hash::Hasher>(&self, state: &mut H) {
373        for item in self.iter_labels() {
374            item.hash(state)
375        }
376    }
377}
378
379//--- ToLabelIter
380
381impl<Octs: AsRef<[u8]>> ToLabelIter for UncertainName<Octs> {
382    type LabelIter<'a>
383        = NameIter<'a>
384    where
385        Octs: 'a;
386
387    fn iter_labels(&self) -> Self::LabelIter<'_> {
388        match *self {
389            UncertainName::Absolute(ref name) => name.iter_labels(),
390            UncertainName::Relative(ref name) => name.iter_labels(),
391        }
392    }
393
394    fn compose_len(&self) -> u16 {
395        match *self {
396            UncertainName::Absolute(ref name) => name.compose_len(),
397            UncertainName::Relative(ref name) => name.compose_len(),
398        }
399    }
400}
401
402//--- IntoIterator
403
404impl<'a, Octets: AsRef<[u8]>> IntoIterator for &'a UncertainName<Octets> {
405    type Item = &'a Label;
406    type IntoIter = NameIter<'a>;
407
408    fn into_iter(self) -> Self::IntoIter {
409        self.iter_labels()
410    }
411}
412
413//--- Display and Debug
414
415impl<Octets: AsRef<[u8]>> fmt::Display for UncertainName<Octets> {
416    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
417        match *self {
418            UncertainName::Absolute(ref name) => {
419                write!(f, "{}.", name)
420            }
421            UncertainName::Relative(ref name) => name.fmt(f),
422        }
423    }
424}
425
426impl<Octets: AsRef<[u8]>> fmt::Debug for UncertainName<Octets> {
427    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
428        match *self {
429            UncertainName::Absolute(ref name) => {
430                write!(f, "UncertainName::Absolute({})", name)
431            }
432            UncertainName::Relative(ref name) => {
433                write!(f, "UncertainName::Relative({})", name)
434            }
435        }
436    }
437}
438
439//--- Serialize and Deserialize
440
441#[cfg(feature = "serde")]
442impl<Octets> serde::Serialize for UncertainName<Octets>
443where
444    Octets: AsRef<[u8]> + SerializeOctets,
445{
446    fn serialize<S: serde::Serializer>(
447        &self,
448        serializer: S,
449    ) -> Result<S::Ok, S::Error> {
450        if serializer.is_human_readable() {
451            serializer.serialize_newtype_struct(
452                "UncertainName",
453                &format_args!("{}", self),
454            )
455        } else {
456            serializer.serialize_newtype_struct(
457                "UncertainName",
458                &self.as_octets().as_serialized_octets(),
459            )
460        }
461    }
462}
463
464#[cfg(feature = "serde")]
465impl<'de, Octets> serde::Deserialize<'de> for UncertainName<Octets>
466where
467    Octets: FromBuilder + DeserializeOctets<'de>,
468    <Octets as FromBuilder>::Builder: EmptyBuilder
469        + FreezeBuilder<Octets = Octets>
470        + AsRef<[u8]>
471        + AsMut<[u8]>,
472{
473    fn deserialize<D: serde::Deserializer<'de>>(
474        deserializer: D,
475    ) -> Result<Self, D::Error> {
476        use core::marker::PhantomData;
477
478        struct InnerVisitor<'de, T: DeserializeOctets<'de>>(T::Visitor);
479
480        impl<'de, Octets> serde::de::Visitor<'de> for InnerVisitor<'de, Octets>
481        where
482            Octets: FromBuilder + DeserializeOctets<'de>,
483            <Octets as FromBuilder>::Builder: EmptyBuilder
484                + FreezeBuilder<Octets = Octets>
485                + AsRef<[u8]>
486                + AsMut<[u8]>,
487        {
488            type Value = UncertainName<Octets>;
489
490            fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result {
491                f.write_str("a domain name")
492            }
493
494            fn visit_str<E: serde::de::Error>(
495                self,
496                v: &str,
497            ) -> Result<Self::Value, E> {
498                use core::str::FromStr;
499
500                UncertainName::from_str(v).map_err(E::custom)
501            }
502
503            fn visit_borrowed_bytes<E: serde::de::Error>(
504                self,
505                value: &'de [u8],
506            ) -> Result<Self::Value, E> {
507                self.0.visit_borrowed_bytes(value).and_then(|octets| {
508                    UncertainName::from_octets(octets).map_err(E::custom)
509                })
510            }
511
512            #[cfg(feature = "std")]
513            fn visit_byte_buf<E: serde::de::Error>(
514                self,
515                value: std::vec::Vec<u8>,
516            ) -> Result<Self::Value, E> {
517                self.0.visit_byte_buf(value).and_then(|octets| {
518                    UncertainName::from_octets(octets).map_err(E::custom)
519                })
520            }
521        }
522
523        struct NewtypeVisitor<T>(PhantomData<T>);
524
525        impl<'de, Octets> serde::de::Visitor<'de> for NewtypeVisitor<Octets>
526        where
527            Octets: FromBuilder + DeserializeOctets<'de>,
528            <Octets as FromBuilder>::Builder: EmptyBuilder
529                + FreezeBuilder<Octets = Octets>
530                + AsRef<[u8]>
531                + AsMut<[u8]>,
532        {
533            type Value = UncertainName<Octets>;
534
535            fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result {
536                f.write_str("a domain name")
537            }
538
539            fn visit_newtype_struct<D: serde::Deserializer<'de>>(
540                self,
541                deserializer: D,
542            ) -> Result<Self::Value, D::Error> {
543                if deserializer.is_human_readable() {
544                    deserializer
545                        .deserialize_str(InnerVisitor(Octets::visitor()))
546                } else {
547                    Octets::deserialize_with_visitor(
548                        deserializer,
549                        InnerVisitor(Octets::visitor()),
550                    )
551                }
552            }
553        }
554
555        deserializer.deserialize_newtype_struct(
556            "UncertainName",
557            NewtypeVisitor(PhantomData),
558        )
559    }
560}
561
562//============ Error Types ===================================================
563
564//------------ UncertainDnameError -------------------------------------------
565
566/// A domain name wasn’t encoded correctly.
567#[derive(Clone, Copy, Debug, Eq, PartialEq)]
568pub struct UncertainDnameError(UncertainDnameErrorEnum);
569
570#[derive(Clone, Copy, Debug, Eq, PartialEq)]
571enum UncertainDnameErrorEnum {
572    /// The encoding contained an unknown or disallowed label type.
573    BadLabel(LabelTypeError),
574
575    /// The encoding contained a compression pointer.
576    CompressedName,
577
578    /// The name was longer than 255 octets.
579    LongName,
580
581    /// There was more data after the root label was encountered.
582    TrailingData,
583
584    /// The input ended in the middle of a label.
585    ShortInput,
586}
587
588//--- From
589
590impl From<LabelTypeError> for UncertainDnameError {
591    fn from(err: LabelTypeError) -> Self {
592        Self(UncertainDnameErrorEnum::BadLabel(err))
593    }
594}
595
596impl From<SplitLabelError> for UncertainDnameError {
597    fn from(err: SplitLabelError) -> UncertainDnameError {
598        Self(match err {
599            SplitLabelError::Pointer(_) => {
600                UncertainDnameErrorEnum::CompressedName
601            }
602            SplitLabelError::BadType(t) => {
603                UncertainDnameErrorEnum::BadLabel(t)
604            }
605            SplitLabelError::ShortInput => {
606                UncertainDnameErrorEnum::ShortInput
607            }
608        })
609    }
610}
611
612impl From<UncertainDnameErrorEnum> for UncertainDnameError {
613    fn from(err: UncertainDnameErrorEnum) -> Self {
614        Self(err)
615    }
616}
617
618//--- Display and Error
619
620impl fmt::Display for UncertainDnameError {
621    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
622        match self.0 {
623            UncertainDnameErrorEnum::BadLabel(ref err) => err.fmt(f),
624            UncertainDnameErrorEnum::CompressedName => {
625                f.write_str("compressed domain name")
626            }
627            UncertainDnameErrorEnum::LongName => {
628                f.write_str("long domain name")
629            }
630            UncertainDnameErrorEnum::TrailingData => {
631                f.write_str("trailing data")
632            }
633            UncertainDnameErrorEnum::ShortInput => {
634                ParseError::ShortInput.fmt(f)
635            }
636        }
637    }
638}
639
640#[cfg(feature = "std")]
641impl std::error::Error for UncertainDnameError {}
642
643//============ Testing =======================================================
644
645#[cfg(test)]
646#[cfg(feature = "std")]
647mod test {
648    use super::*;
649    use std::str::FromStr;
650    use std::string::String;
651
652    #[test]
653    fn from_str() {
654        type U = UncertainName<Vec<u8>>;
655
656        fn name(s: &str) -> U {
657            U::from_str(s).unwrap()
658        }
659
660        assert_eq!(
661            name("www.example.com").as_relative().unwrap().as_slice(),
662            b"\x03www\x07example\x03com"
663        );
664        assert_eq!(
665            name("www.example.com.").as_absolute().unwrap().as_slice(),
666            b"\x03www\x07example\x03com\0"
667        );
668
669        assert_eq!(
670            name(r"www\.example.com").as_slice(),
671            b"\x0bwww.example\x03com"
672        );
673        assert_eq!(
674            name(r"w\119w.example.com").as_slice(),
675            b"\x03www\x07example\x03com"
676        );
677        assert_eq!(
678            name(r"w\000w.example.com").as_slice(),
679            b"\x03w\0w\x07example\x03com"
680        );
681
682        U::from_str(r"w\01").unwrap_err();
683        U::from_str(r"w\").unwrap_err();
684        U::from_str(r"www..example.com").unwrap_err();
685        U::from_str(r"www.example.com..").unwrap_err();
686        U::from_str(r".www.example.com").unwrap_err();
687        U::from_str(r"www.\[322].example.com").unwrap_err();
688        U::from_str(r"www.\2example.com").unwrap_err();
689        U::from_str(r"www.\29example.com").unwrap_err();
690        U::from_str(r"www.\299example.com").unwrap_err();
691        U::from_str(r"www.\892example.com").unwrap_err();
692        U::from_str("www.e\0ample.com").unwrap_err();
693        U::from_str("www.eüample.com").unwrap_err();
694
695        // LongLabel
696        let mut s = String::from("www.");
697        for _ in 0..Label::MAX_LEN {
698            s.push('x');
699        }
700        s.push_str(".com");
701        assert!(U::from_str(&s).is_ok());
702        let mut s = String::from("www.");
703        for _ in 0..64 {
704            s.push('x');
705        }
706        s.push_str(".com");
707        U::from_str(&s).unwrap_err();
708
709        // Long Name
710        let mut s = String::new();
711        for _ in 0..50 {
712            s.push_str("four.");
713        }
714        let mut s1 = s.clone();
715        s1.push_str("com.");
716        assert_eq!(name(&s1).as_slice().len(), 255);
717        let mut s1 = s.clone();
718        s1.push_str("com");
719        assert_eq!(name(&s1).as_slice().len(), 254);
720        let mut s1 = s.clone();
721        s1.push_str("coma.");
722        U::from_str(&s1).unwrap_err();
723        let mut s1 = s.clone();
724        s1.push_str("coma");
725        U::from_str(&s1).unwrap_err();
726    }
727
728    #[cfg(feature = "serde")]
729    #[test]
730    fn ser_de() {
731        use serde_test::{assert_tokens, Configure, Token};
732
733        let abs_name =
734            UncertainName::<Vec<u8>>::from_str("www.example.com.").unwrap();
735        assert!(abs_name.is_absolute());
736
737        assert_tokens(
738            &abs_name.clone().compact(),
739            &[
740                Token::NewtypeStruct {
741                    name: "UncertainName",
742                },
743                Token::ByteBuf(b"\x03www\x07example\x03com\0"),
744            ],
745        );
746        assert_tokens(
747            &abs_name.readable(),
748            &[
749                Token::NewtypeStruct {
750                    name: "UncertainName",
751                },
752                Token::Str("www.example.com."),
753            ],
754        );
755
756        let rel_name =
757            UncertainName::<Vec<u8>>::from_str("www.example.com").unwrap();
758        assert!(rel_name.is_relative());
759
760        assert_tokens(
761            &rel_name.clone().compact(),
762            &[
763                Token::NewtypeStruct {
764                    name: "UncertainName",
765                },
766                Token::ByteBuf(b"\x03www\x07example\x03com"),
767            ],
768        );
769        assert_tokens(
770            &rel_name.readable(),
771            &[
772                Token::NewtypeStruct {
773                    name: "UncertainName",
774                },
775                Token::Str("www.example.com"),
776            ],
777        );
778    }
779}