1use super::super::iana::OptionCode;
23use super::super::message_builder::OptBuilder;
24use super::super::wire::{Composer, ParseError};
25use super::{ComposeOptData, Opt, OptData, ParseOptData};
26use crate::base::Serial;
27use crate::utils::base16;
28use core::{fmt, hash};
29use octseq::array::Array;
30use octseq::builder::OctetsBuilder;
31use octseq::octets::Octets;
32use octseq::parse::Parser;
33
34#[cfg_attr(
54 feature = "siphasher",
55 doc = "[`check_server_hash`](Self::check_server_hash)"
56)]
57#[cfg_attr(not(feature = "siphasher"), doc = "`check_server_hash`")]
58#[cfg_attr(
61 feature = "siphasher",
62 doc = "[`create_response`](Self::create_response)"
63)]
64#[cfg_attr(not(feature = "siphasher"), doc = "`create_response`")]
65#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
68#[cfg_attr(feature = "rand", derive(Default))]
69#[cfg_attr(feature = "serde", derive(serde::Serialize))]
70pub struct Cookie {
71 client: ClientCookie,
73
74 server: Option<ServerCookie>,
76}
77
78impl Cookie {
79 pub(super) const CODE: OptionCode = OptionCode::COOKIE;
81
82 #[must_use]
84 pub fn new(client: ClientCookie, server: Option<ServerCookie>) -> Self {
85 Cookie { client, server }
86 }
87
88 #[must_use]
90 pub fn client(&self) -> ClientCookie {
91 self.client
92 }
93
94 #[must_use]
96 pub fn server(&self) -> Option<&ServerCookie> {
97 self.server.as_ref()
98 }
99
100 pub fn parse<Octs: AsRef<[u8]> + ?Sized>(
102 parser: &mut Parser<'_, Octs>,
103 ) -> Result<Self, ParseError> {
104 Ok(Cookie::new(
105 ClientCookie::parse(parser)?,
106 ServerCookie::parse_opt(parser)?,
107 ))
108 }
109
110 #[cfg(feature = "siphasher")]
127 pub fn check_server_hash(
128 &self,
129 client_ip: crate::base::net::IpAddr,
130 secret: &[u8; 16],
131 timestamp_ok: impl FnOnce(Serial) -> bool,
132 ) -> bool {
133 self.server
134 .as_ref()
135 .and_then(|server| server.try_to_standard())
136 .and_then(|server| {
137 timestamp_ok(server.timestamp()).then_some(server)
138 })
139 .map(|server| server.check_hash(self.client(), client_ip, secret))
140 .unwrap_or(false)
141 }
142
143 #[cfg(feature = "rand")]
145 #[must_use]
146 pub fn create_initial() -> Self {
147 Self::new(ClientCookie::new_random(), None)
148 }
149
150 #[cfg(feature = "siphasher")]
155 pub fn create_response(
156 &self,
157 timestamp: Serial,
158 client_ip: crate::base::net::IpAddr,
159 secret: &[u8; 16],
160 ) -> Self {
161 Self::new(
162 self.client,
163 Some(
164 StandardServerCookie::calculate(
165 self.client,
166 timestamp,
167 client_ip,
168 secret,
169 )
170 .into(),
171 ),
172 )
173 }
174
175 pub(super) fn try_octets_from<E>(src: Self) -> Result<Self, E> {
179 Ok(src)
180 }
181}
182
183impl OptData for Cookie {
186 fn code(&self) -> OptionCode {
187 OptionCode::COOKIE
188 }
189}
190
191impl<'a, Octs: AsRef<[u8]> + ?Sized> ParseOptData<'a, Octs> for Cookie {
192 fn parse_option(
193 code: OptionCode,
194 parser: &mut Parser<'a, Octs>,
195 ) -> Result<Option<Self>, ParseError> {
196 if code == OptionCode::COOKIE {
197 Self::parse(parser).map(Some)
198 } else {
199 Ok(None)
200 }
201 }
202}
203
204impl ComposeOptData for Cookie {
205 fn compose_len(&self) -> u16 {
206 match self.server.as_ref() {
207 Some(server) => ClientCookie::COMPOSE_LEN
208 .checked_add(server.compose_len())
209 .expect("long server cookie"),
210 None => ClientCookie::COMPOSE_LEN,
211 }
212 }
213
214 fn compose_option<Target: OctetsBuilder + ?Sized>(
215 &self,
216 target: &mut Target,
217 ) -> Result<(), Target::AppendError> {
218 self.client.compose(target)?;
219 if let Some(server) = self.server.as_ref() {
220 server.compose(target)?;
221 }
222 Ok(())
223 }
224}
225
226impl fmt::Display for Cookie {
227 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
228 fmt::Display::fmt(&self.client, f)?;
229 if let Some(server) = self.server.as_ref() {
230 fmt::Display::fmt(server, f)?;
231 }
232 Ok(())
233 }
234}
235
236impl<Octs: Octets> Opt<Octs> {
239 pub fn cookie(&self) -> Option<Cookie> {
241 self.first()
242 }
243}
244
245impl<Target: Composer> OptBuilder<'_, Target> {
246 pub fn cookie(
248 &mut self,
249 cookie: Cookie,
250 ) -> Result<(), Target::AppendError> {
251 self.push(&cookie)
252 }
253
254 #[cfg(feature = "rand")]
259 pub fn initial_cookie(&mut self) -> Result<(), Target::AppendError> {
260 self.push(&Cookie::create_initial())
261 }
262}
263
264#[cfg_attr(
279 feature = "rand",
280 doc = "[`new_random`][ClientCookie::new_random]"
281)]
282#[cfg_attr(not(feature = "rand"), doc = "`new_random`")]
283#[derive(Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd)]
289pub struct ClientCookie([u8; 8]);
290
291#[cfg(feature = "serde")]
292impl serde::Serialize for ClientCookie {
293 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
294 where
295 S: serde::Serializer,
296 {
297 use octseq::serde::SerializeOctets;
298 self.0.serialize_octets(serializer)
299 }
300}
301
302impl ClientCookie {
303 #[must_use]
305 pub const fn from_octets(octets: [u8; 8]) -> Self {
306 Self(octets)
307 }
308
309 #[cfg(feature = "rand")]
311 #[must_use]
312 pub fn new_random() -> Self {
313 Self(rand::random())
314 }
315
316 #[must_use]
318 pub fn into_octets(self) -> [u8; 8] {
319 self.0
320 }
321
322 pub fn parse<Octs: AsRef<[u8]> + ?Sized>(
324 parser: &mut Parser<'_, Octs>,
325 ) -> Result<Self, ParseError> {
326 let mut res = Self::from_octets([0; 8]);
327 parser.parse_buf(res.as_mut())?;
328 Ok(res)
329 }
330
331 pub const COMPOSE_LEN: u16 = 8;
333
334 pub fn compose<Target: OctetsBuilder + ?Sized>(
336 &self,
337 target: &mut Target,
338 ) -> Result<(), Target::AppendError> {
339 target.append_slice(&self.0)
340 }
341}
342
343#[cfg(feature = "rand")]
346impl Default for ClientCookie {
347 fn default() -> Self {
348 Self::new_random()
349 }
350}
351
352impl From<[u8; 8]> for ClientCookie {
355 fn from(src: [u8; 8]) -> Self {
356 Self::from_octets(src)
357 }
358}
359
360impl From<ClientCookie> for [u8; 8] {
361 fn from(src: ClientCookie) -> Self {
362 src.0
363 }
364}
365
366impl AsRef<[u8]> for ClientCookie {
369 fn as_ref(&self) -> &[u8] {
370 self.0.as_ref()
371 }
372}
373
374impl AsMut<[u8]> for ClientCookie {
375 fn as_mut(&mut self) -> &mut [u8] {
376 self.0.as_mut()
377 }
378}
379
380impl hash::Hash for ClientCookie {
383 fn hash<H: hash::Hasher>(&self, state: &mut H) {
384 state.write(&self.0)
385 }
386}
387
388impl fmt::Display for ClientCookie {
391 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
392 base16::display(self.0.as_ref(), f)
393 }
394}
395
396#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
418pub struct ServerCookie(Array<32>);
419
420#[cfg(feature = "serde")]
421impl serde::Serialize for ServerCookie {
422 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
423 where
424 S: serde::Serializer,
425 {
426 use octseq::serde::SerializeOctets;
427 self.0.serialize_octets(serializer)
428 }
429}
430
431impl ServerCookie {
432 #[must_use]
439 pub fn from_octets(slice: &[u8]) -> Self {
440 assert!(slice.len() >= 8, "server cookie shorter than 8 octets");
441 let mut res = Array::new();
442 res.append_slice(slice)
443 .expect("server cookie longer tha 32 octets");
444 Self(res)
445 }
446
447 pub fn parse<Octs: AsRef<[u8]> + ?Sized>(
449 parser: &mut Parser<'_, Octs>,
450 ) -> Result<Self, ParseError> {
451 if parser.remaining() < 8 {
452 return Err(ParseError::form_error("short server cookie"));
453 }
454 let mut res = Array::new();
455 res.resize_raw(parser.remaining())
456 .map_err(|_| ParseError::form_error("long server cookie"))?;
457 parser.parse_buf(res.as_slice_mut())?;
458 Ok(Self(res))
459 }
460
461 pub fn parse_opt<Octs: AsRef<[u8]> + ?Sized>(
463 parser: &mut Parser<'_, Octs>,
464 ) -> Result<Option<Self>, ParseError> {
465 if parser.remaining() > 0 {
466 Self::parse(parser).map(Some)
467 } else {
468 Ok(None)
469 }
470 }
471
472 pub fn try_to_standard(&self) -> Option<StandardServerCookie> {
477 TryFrom::try_from(self.0.as_slice())
478 .map(StandardServerCookie)
479 .ok()
480 }
481
482 #[must_use]
484 pub fn compose_len(&self) -> u16 {
485 u16::try_from(self.0.len()).expect("long server cookie")
486 }
487
488 pub fn compose<Target: OctetsBuilder + ?Sized>(
490 &self,
491 target: &mut Target,
492 ) -> Result<(), Target::AppendError> {
493 target.append_slice(self.0.as_ref())
494 }
495}
496
497impl From<StandardServerCookie> for ServerCookie {
500 fn from(src: StandardServerCookie) -> Self {
501 Self::from_octets(&src.0)
502 }
503}
504
505impl AsRef<[u8]> for ServerCookie {
508 fn as_ref(&self) -> &[u8] {
509 self.0.as_ref()
510 }
511}
512
513impl fmt::Display for ServerCookie {
516 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
517 base16::display(self.0.as_ref(), f)
518 }
519}
520
521#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
543pub struct StandardServerCookie(
544 [u8; 16],
548);
549
550impl StandardServerCookie {
551 #[must_use]
553 pub fn new(
554 version: u8,
555 reserved: [u8; 3],
556 timestamp: Serial,
557 hash: [u8; 8],
558 ) -> Self {
559 let ts = timestamp.into_int().to_be_bytes();
560 Self([
561 version,
562 reserved[0],
563 reserved[1],
564 reserved[2],
565 ts[0],
566 ts[1],
567 ts[2],
568 ts[3],
569 hash[0],
570 hash[1],
571 hash[2],
572 hash[3],
573 hash[4],
574 hash[5],
575 hash[6],
576 hash[7],
577 ])
578 }
579
580 #[cfg(feature = "siphasher")]
582 pub fn calculate(
583 client_cookie: ClientCookie,
584 timestamp: Serial,
585 client_ip: crate::base::net::IpAddr,
586 secret: &[u8; 16],
587 ) -> Self {
588 let mut res = Self::new(1, [0; 3], timestamp, [0; 8]);
589 res.set_hash(res.calculate_hash(client_cookie, client_ip, secret));
590 res
591 }
592
593 #[must_use]
595 pub fn version(self) -> u8 {
596 self.0[0]
597 }
598
599 #[must_use]
601 pub fn reserved(self) -> [u8; 3] {
602 TryFrom::try_from(&self.0[1..4]).expect("bad slicing")
603 }
604
605 #[must_use]
607 pub fn timestamp(self) -> Serial {
608 Serial::from_be_bytes(
609 TryFrom::try_from(&self.0[4..8]).expect("bad slicing"),
610 )
611 }
612
613 #[must_use]
615 pub fn hash(self) -> [u8; 8] {
616 TryFrom::try_from(&self.0[8..]).expect("bad slicing")
617 }
618
619 pub fn set_hash(&mut self, hash: [u8; 8]) {
621 self.0[8..].copy_from_slice(&hash);
622 }
623
624 #[cfg(feature = "siphasher")]
626 pub fn check_hash(
627 self,
628 client_cookie: ClientCookie,
629 client_ip: crate::base::net::IpAddr,
630 secret: &[u8; 16],
631 ) -> bool {
632 self.calculate_hash(client_cookie, client_ip, secret) == self.hash()
633 }
634
635 #[cfg(feature = "siphasher")]
646 fn calculate_hash(
647 self,
648 client_cookie: ClientCookie,
649 client_ip: crate::base::net::IpAddr,
650 secret: &[u8; 16],
651 ) -> [u8; 8] {
652 use crate::base::net::IpAddr;
653 use core::hash::{Hash, Hasher};
654
655 let mut hasher = siphasher::sip::SipHasher24::new_with_key(secret);
656 client_cookie.hash(&mut hasher);
657 hasher.write(&self.0[..8]);
658 match client_ip {
659 IpAddr::V4(addr) => hasher.write(&addr.octets()),
660 IpAddr::V6(addr) => hasher.write(&addr.octets()),
661 }
662 hasher.finish().to_le_bytes()
663 }
664}
665
666impl fmt::Display for StandardServerCookie {
669 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
670 base16::display(self.0.as_ref(), f)
671 }
672}
673
674#[cfg(test)]
677mod test {
678 #[allow(unused_imports)]
679 use super::*;
680
681 #[cfg(all(feature = "siphasher", feature = "std"))]
683 mod standard_server {
684 use super::*;
685 use crate::base::net::{IpAddr, Ipv4Addr, Ipv6Addr};
686 use crate::base::wire::{compose_vec, parse_slice};
687
688 const CLIENT_1: IpAddr = IpAddr::V4(Ipv4Addr::new(198, 51, 100, 100));
689 const CLIENT_2: IpAddr = IpAddr::V4(Ipv4Addr::new(203, 0, 113, 203));
690 const CLIENT_6: IpAddr = IpAddr::V6(Ipv6Addr::new(
691 0x2001, 0xdb8, 0x220, 0x1, 0x59de, 0xd0f4, 0x8769, 0x82b8,
692 ));
693
694 const SECRET: [u8; 16] = [
695 0xe5, 0xe9, 0x73, 0xe5, 0xa6, 0xb2, 0xa4, 0x3f, 0x48, 0xe7, 0xdc,
696 0x84, 0x9e, 0x37, 0xbf, 0xcf,
697 ];
698
699 #[test]
701 fn new_cookie() {
702 let request = Cookie::new(
703 ClientCookie::from_octets([
704 0x24, 0x64, 0xc4, 0xab, 0xcf, 0x10, 0xc9, 0x57,
705 ]),
706 None,
707 );
708 assert_eq!(
709 compose_vec(|vec| request.compose_option(vec)),
710 base16::decode_vec("2464c4abcf10c957").unwrap()
711 );
712
713 assert_eq!(
714 compose_vec(|vec| {
715 request
716 .create_response(
717 Serial(1559731985),
718 CLIENT_1,
719 &SECRET,
720 )
721 .compose_option(vec)
722 }),
723 base16::decode_vec(
724 "2464c4abcf10c957010000005cf79f111f8130c3eee29480"
725 )
726 .unwrap()
727 );
728 }
729
730 #[test]
732 fn renew_cookie() {
733 let request = parse_slice(
734 &base16::decode_vec(
735 "2464c4abcf10c957010000005cf79f111f8130c3eee29480",
736 )
737 .unwrap(),
738 Cookie::parse,
739 )
740 .unwrap();
741 assert!(request
742 .check_server_hash(CLIENT_1, &SECRET, |serial| serial
743 == Serial(1559731985)));
744
745 assert_eq!(
746 compose_vec(|vec| {
747 request
748 .create_response(
749 Serial(1559734385),
750 CLIENT_1,
751 &SECRET,
752 )
753 .compose_option(vec)
754 }),
755 base16::decode_vec(
756 "2464c4abcf10c957010000005cf7a871d4a564a1442aca77"
757 )
758 .unwrap()
759 );
760 }
761
762 #[test]
764 fn non_zero_reserved() {
765 let request = parse_slice(
766 &base16::decode_vec(
767 "fc93fc62807ddb8601abcdef5cf78f71a314227b6679ebf5",
768 )
769 .unwrap(),
770 Cookie::parse,
771 )
772 .unwrap();
773 assert!(request
774 .check_server_hash(CLIENT_2, &SECRET, |serial| serial
775 == Serial(1559727985)));
776
777 assert_eq!(
778 compose_vec(|vec| {
779 request
780 .create_response(
781 Serial(1559734700),
782 CLIENT_2,
783 &SECRET,
784 )
785 .compose_option(vec)
786 }),
787 base16::decode_vec(
788 "fc93fc62807ddb86010000005cf7a9acf73a7810aca2381e"
789 )
790 .unwrap()
791 );
792 }
793
794 #[test]
796 fn new_secret() {
797 const OLD_SECRET: [u8; 16] = [
798 0xdd, 0x3b, 0xdf, 0x93, 0x44, 0xb6, 0x78, 0xb1, 0x85, 0xa6,
799 0xf5, 0xcb, 0x60, 0xfc, 0xa7, 0x15,
800 ];
801 const NEW_SECRET: [u8; 16] = [
802 0x44, 0x55, 0x36, 0xbc, 0xd2, 0x51, 0x32, 0x98, 0x07, 0x5a,
803 0x5d, 0x37, 0x96, 0x63, 0xc9, 0x62,
804 ];
805
806 let request = parse_slice(
807 &base16::decode_vec(
808 "22681ab97d52c298010000005cf7c57926556bd0934c72f8",
809 )
810 .unwrap(),
811 Cookie::parse,
812 )
813 .unwrap();
814 assert!(!request.check_server_hash(
815 CLIENT_6,
816 &NEW_SECRET,
817 |serial| serial == Serial(1559741817)
818 ));
819 assert!(request.check_server_hash(
820 CLIENT_6,
821 &OLD_SECRET,
822 |serial| serial == Serial(1559741817)
823 ));
824
825 assert_eq!(
826 compose_vec(|vec| {
827 request
828 .create_response(
829 Serial(1559741961),
830 CLIENT_6,
831 &NEW_SECRET,
832 )
833 .compose_option(vec)
834 }),
835 base16::decode_vec(
836 "22681ab97d52c298010000005cf7c609a6bb79d16625507a"
837 )
838 .unwrap()
839 );
840 }
841 }
842}