1use core::{fmt, hash};
23use octseq::array::Array;
24use octseq::builder::OctetsBuilder;
25use octseq::octets::Octets;
26use octseq::parse::Parser;
27use crate::base::Serial;
28use crate::utils::base16;
29use super::super::iana::OptionCode;
30use super::super::message_builder::OptBuilder;
31use super::super::wire::{Composer, ParseError};
32use super::{Opt, OptData, ComposeOptData, ParseOptData};
33
34
35#[cfg_attr(feature = "siphasher", doc = "[`check_server_hash`](Self::check_server_hash)")]
55#[cfg_attr(not(feature = "siphasher"), doc = "`check_server_hash`")]
56#[cfg_attr(feature = "siphasher", doc = "[`create_response`](Self::create_response)")]
59#[cfg_attr(not(feature = "siphasher"), doc = "`create_response`")]
60#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
63#[cfg_attr(feature = "rand", derive(Default))]
64#[cfg_attr(feature = "serde", derive(serde::Serialize))]
65pub struct Cookie {
66 client: ClientCookie,
68
69 server: Option<ServerCookie>,
71}
72
73impl Cookie {
74 pub(super) const CODE: OptionCode = OptionCode::COOKIE;
76
77 #[must_use]
79 pub fn new(
80 client: ClientCookie,
81 server: Option<ServerCookie>
82 ) -> Self {
83 Cookie { client, server }
84 }
85
86 #[must_use]
88 pub fn client(&self) -> ClientCookie {
89 self.client
90 }
91
92 #[must_use]
94 pub fn server(&self) -> Option<&ServerCookie> {
95 self.server.as_ref()
96 }
97
98 pub fn parse<Octs: AsRef<[u8]> + ?Sized>(
100 parser: &mut Parser<Octs>
101 ) -> Result<Self, ParseError> {
102 Ok(Cookie::new(
103 ClientCookie::parse(parser)?,
104 ServerCookie::parse_opt(parser)?,
105 ))
106 }
107
108 #[cfg(feature = "siphasher")]
125 pub fn check_server_hash(
126 &self,
127 client_ip: crate::base::net::IpAddr,
128 secret: &[u8; 16],
129 timestamp_ok: impl FnOnce(Serial) -> bool,
130 ) -> bool {
131 self.server.as_ref().and_then(|server| {
132 server.try_to_standard()
133 }).and_then(|server| {
134 timestamp_ok(server.timestamp()).then_some(server)
135 }).map(|server| {
136 server.check_hash(self.client(), client_ip, secret)
137 }).unwrap_or(false)
138 }
139
140 #[cfg(feature = "rand")]
142 #[must_use]
143 pub fn create_initial() -> Self {
144 Self::new(ClientCookie::new_random(), None)
145 }
146
147 #[cfg(feature = "siphasher")]
152 pub fn create_response(
153 &self,
154 timestamp: Serial,
155 client_ip: crate::base::net::IpAddr,
156 secret: &[u8; 16]
157 ) -> Self {
158 Self::new(
159 self.client,
160 Some(
161 StandardServerCookie::calculate(
162 self.client, timestamp, client_ip, secret
163 ).into()
164 )
165 )
166 }
167
168 pub(super) fn try_octets_from<E>(src: Self) -> Result<Self, E> {
172 Ok(src)
173 }
174}
175
176
177impl OptData for Cookie {
180 fn code(&self) -> OptionCode {
181 OptionCode::COOKIE
182 }
183}
184
185impl<'a, Octs: AsRef<[u8]> + ?Sized> ParseOptData<'a, Octs> for Cookie {
186 fn parse_option(
187 code: OptionCode,
188 parser: &mut Parser<'a, Octs>,
189 ) -> Result<Option<Self>, ParseError> {
190 if code == OptionCode::COOKIE {
191 Self::parse(parser).map(Some)
192 }
193 else {
194 Ok(None)
195 }
196 }
197}
198
199impl ComposeOptData for Cookie {
200 fn compose_len(&self) -> u16 {
201 match self.server.as_ref() {
202 Some(server) => {
203 ClientCookie::COMPOSE_LEN.checked_add(
204 server.compose_len()
205 ).expect("long server cookie")
206 }
207 None => ClientCookie::COMPOSE_LEN
208 }
209 }
210
211 fn compose_option<Target: OctetsBuilder + ?Sized>(
212 &self, target: &mut Target
213 ) -> Result<(), Target::AppendError> {
214 self.client.compose(target)?;
215 if let Some(server) = self.server.as_ref() {
216 server.compose(target)?;
217 }
218 Ok(())
219 }
220}
221
222impl fmt::Display for Cookie {
223 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
224 fmt::Display::fmt(&self.client, f)?;
225 if let Some(server) = self.server.as_ref() {
226 fmt::Display::fmt(server, f)?;
227 }
228 Ok(())
229 }
230}
231
232
233impl<Octs: Octets> Opt<Octs> {
236 pub fn cookie(&self) -> Option<Cookie> {
238 self.first()
239 }
240}
241
242impl<Target: Composer> OptBuilder<'_, Target> {
243 pub fn cookie(
245 &mut self, cookie: Cookie,
246 ) -> Result<(), Target::AppendError> {
247 self.push(&cookie)
248 }
249
250 #[cfg(feature = "rand")]
255 pub fn initial_cookie(&mut self) -> Result<(), Target::AppendError> {
256 self.push(&Cookie::create_initial())
257 }
258}
259
260
261#[cfg_attr(feature = "rand", doc = "[`new_random`][ClientCookie::new_random]")]
276#[cfg_attr(not(feature = "rand"), doc = "`new_random`")]
277#[derive(Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd)]
283pub struct ClientCookie([u8; 8]);
284
285#[cfg(feature = "serde")]
286impl serde::Serialize for ClientCookie {
287 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
288 where
289 S: serde::Serializer {
290 use octseq::serde::SerializeOctets;
291 self.0.serialize_octets(serializer)
292 }
293}
294
295impl ClientCookie {
296 #[must_use]
298 pub const fn from_octets(octets: [u8; 8]) -> Self {
299 Self(octets)
300 }
301
302 #[cfg(feature = "rand")]
304 #[must_use]
305 pub fn new_random() -> Self {
306 Self(rand::random())
307 }
308
309 #[must_use]
311 pub fn into_octets(self) -> [u8; 8] {
312 self.0
313 }
314
315 pub fn parse<Octs: AsRef<[u8]> + ?Sized>(
317 parser: &mut Parser<Octs>
318 ) -> Result<Self, ParseError> {
319 let mut res = Self::from_octets([0; 8]);
320 parser.parse_buf(res.as_mut())?;
321 Ok(res)
322 }
323
324 pub const COMPOSE_LEN: u16 = 8;
326
327 pub fn compose<Target: OctetsBuilder + ?Sized>(
329 &self, target: &mut Target
330 ) -> Result<(), Target::AppendError> {
331 target.append_slice(&self.0)
332 }
333}
334
335#[cfg(feature = "rand")]
338impl Default for ClientCookie {
339 fn default() -> Self {
340 Self::new_random()
341 }
342}
343
344impl From<[u8; 8]> for ClientCookie {
347 fn from(src: [u8; 8]) -> Self {
348 Self::from_octets(src)
349 }
350}
351
352impl From<ClientCookie> for [u8; 8] {
353 fn from(src: ClientCookie) -> Self {
354 src.0
355 }
356}
357
358impl AsRef<[u8]> for ClientCookie {
361 fn as_ref(&self) -> &[u8] {
362 self.0.as_ref()
363 }
364}
365
366impl AsMut<[u8]> for ClientCookie {
367 fn as_mut(&mut self) -> &mut [u8] {
368 self.0.as_mut()
369 }
370}
371
372impl hash::Hash for ClientCookie {
375 fn hash<H: hash::Hasher>(&self, state: &mut H) {
376 state.write(&self.0)
377 }
378}
379
380impl fmt::Display for ClientCookie {
383 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
384 base16::display(self.0.as_ref(), f)
385 }
386}
387
388
389#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
411pub struct ServerCookie(Array<32>);
412
413#[cfg(feature = "serde")]
414impl serde::Serialize for ServerCookie {
415 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
416 where
417 S: serde::Serializer {
418 use octseq::serde::SerializeOctets;
419 self.0.serialize_octets(serializer)
420 }
421}
422
423impl ServerCookie {
424 #[must_use]
431 pub fn from_octets(slice: &[u8]) -> Self {
432 assert!(slice.len() >= 8, "server cookie shorter than 8 octets");
433 let mut res = Array::new();
434 res.append_slice(slice).expect("server cookie longer tha 32 octets");
435 Self(res)
436 }
437
438 pub fn parse<Octs: AsRef<[u8]> + ?Sized>(
440 parser: &mut Parser<Octs>
441 ) -> Result<Self, ParseError> {
442 if parser.remaining() < 8 {
443 return Err(ParseError::form_error("short server cookie"))
444 }
445 let mut res = Array::new();
446 res.resize_raw(parser.remaining()).map_err(|_| {
447 ParseError::form_error("long server cookie")
448 })?;
449 parser.parse_buf(res.as_slice_mut())?;
450 Ok(Self(res))
451 }
452
453 pub fn parse_opt<Octs: AsRef<[u8]> + ?Sized>(
455 parser: &mut Parser<Octs>
456 ) -> Result<Option<Self>, ParseError> {
457 if parser.remaining() > 0 {
458 Self::parse(parser).map(Some)
459 }
460 else {
461 Ok(None)
462 }
463 }
464
465 pub fn try_to_standard(&self) -> Option<StandardServerCookie> {
470 TryFrom::try_from(self.0.as_slice()).map(StandardServerCookie).ok()
471 }
472
473 #[must_use]
475 pub fn compose_len(&self) -> u16 {
476 u16::try_from(self.0.len()).expect("long server cookie")
477 }
478
479 pub fn compose<Target: OctetsBuilder + ?Sized>(
481 &self, target: &mut Target
482 ) -> Result<(), Target::AppendError> {
483 target.append_slice(self.0.as_ref())
484 }
485}
486
487impl From<StandardServerCookie> for ServerCookie {
490 fn from(src: StandardServerCookie) -> Self {
491 Self::from_octets(&src.0)
492 }
493}
494
495impl AsRef<[u8]> for ServerCookie {
498 fn as_ref(&self) -> &[u8] {
499 self.0.as_ref()
500 }
501}
502
503impl fmt::Display for ServerCookie {
506 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
507 base16::display(self.0.as_ref(), f)
508 }
509}
510
511
512#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
534pub struct StandardServerCookie(
535 [u8; 16]
539);
540
541impl StandardServerCookie {
542 #[must_use]
544 pub fn new(
545 version: u8,
546 reserved: [u8; 3],
547 timestamp: Serial,
548 hash: [u8; 8]
549 ) -> Self {
550 let ts = timestamp.into_int().to_be_bytes();
551 Self(
552 [ version, reserved[0], reserved[1], reserved[2],
553 ts[0], ts[1], ts[2], ts[3],
554 hash[0], hash[1], hash[2], hash[3],
555 hash[4], hash[5], hash[6], hash[7],
556 ]
557 )
558 }
559
560 #[cfg(feature = "siphasher")]
562 pub fn calculate(
563 client_cookie: ClientCookie,
564 timestamp: Serial,
565 client_ip: crate::base::net::IpAddr,
566 secret: &[u8; 16]
567 ) -> Self {
568 let mut res = Self::new(1, [0; 3], timestamp, [0; 8]);
569 res.set_hash(
570 res.calculate_hash(client_cookie, client_ip, secret)
571 );
572 res
573 }
574
575 #[must_use]
577 pub fn version(self) -> u8 {
578 self.0[0]
579 }
580
581 #[must_use]
583 pub fn reserved(self) -> [u8; 3] {
584 TryFrom::try_from(&self.0[1..4]).expect("bad slicing")
585 }
586
587 #[must_use]
589 pub fn timestamp(self) -> Serial {
590 Serial::from_be_bytes(
591 TryFrom::try_from(&self.0[4..8]).expect("bad slicing")
592 )
593 }
594
595 #[must_use]
597 pub fn hash(self) -> [u8; 8] {
598 TryFrom::try_from(&self.0[8..]).expect("bad slicing")
599 }
600
601 pub fn set_hash(&mut self, hash: [u8; 8]) {
603 self.0[8..].copy_from_slice(&hash);
604 }
605
606 #[cfg(feature = "siphasher")]
608 pub fn check_hash(
609 self,
610 client_cookie: ClientCookie,
611 client_ip: crate::base::net::IpAddr,
612 secret: &[u8; 16]
613 ) -> bool {
614 self.calculate_hash(client_cookie, client_ip, secret) == self.hash()
615 }
616
617 #[cfg(feature = "siphasher")]
628 fn calculate_hash(
629 self,
630 client_cookie: ClientCookie,
631 client_ip: crate::base::net::IpAddr,
632 secret: &[u8; 16]
633 ) -> [u8; 8] {
634 use core::hash::{Hash, Hasher};
635 use crate::base::net::IpAddr;
636
637 let mut hasher = siphasher::sip::SipHasher24::new_with_key(secret);
638 client_cookie.hash(&mut hasher);
639 hasher.write(&self.0[..8]);
640 match client_ip {
641 IpAddr::V4(addr) => hasher.write(&addr.octets()),
642 IpAddr::V6(addr) => hasher.write(&addr.octets()),
643 }
644 hasher.finish().to_le_bytes()
645 }
646}
647
648impl fmt::Display for StandardServerCookie {
651 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
652 base16::display(self.0.as_ref(), f)
653 }
654}
655
656
657#[cfg(test)]
660mod test {
661 #[allow(unused_imports)]
662 use super::*;
663
664 #[cfg(all(feature = "siphasher", feature = "std"))]
666 mod standard_server {
667 use crate::base::net::{IpAddr, Ipv4Addr, Ipv6Addr};
668 use crate::base::wire::{compose_vec, parse_slice};
669 use super::*;
670
671 const CLIENT_1: IpAddr = IpAddr::V4(Ipv4Addr::new(198, 51, 100, 100));
672 const CLIENT_2: IpAddr = IpAddr::V4(Ipv4Addr::new(203, 0, 113, 203));
673 const CLIENT_6: IpAddr = IpAddr::V6(Ipv6Addr::new(
674 0x2001, 0xdb8, 0x220, 0x1, 0x59de, 0xd0f4, 0x8769, 0x82b8
675 ));
676
677 const SECRET: [u8; 16] = [
678 0xe5, 0xe9, 0x73, 0xe5, 0xa6, 0xb2, 0xa4, 0x3f,
679 0x48, 0xe7, 0xdc, 0x84, 0x9e, 0x37, 0xbf, 0xcf,
680 ];
681
682 #[test]
684 fn new_cookie() {
685 let request = Cookie::new(
686 ClientCookie::from_octets(
687 [ 0x24, 0x64, 0xc4, 0xab, 0xcf, 0x10, 0xc9, 0x57 ]
688 ),
689 None
690 );
691 assert_eq!(
692 compose_vec(|vec| request.compose_option(vec)),
693 base16::decode_vec("2464c4abcf10c957").unwrap()
694 );
695
696 assert_eq!(
697 compose_vec(|vec| {
698 request.create_response(
699 Serial(1559731985), CLIENT_1, &SECRET
700 ).compose_option(vec)
701 }),
702 base16::decode_vec(
703 "2464c4abcf10c957010000005cf79f111f8130c3eee29480"
704 ).unwrap()
705 );
706 }
707
708 #[test]
710 fn renew_cookie() {
711 let request = parse_slice(
712 &base16::decode_vec(
713 "2464c4abcf10c957010000005cf79f111f8130c3eee29480"
714 ).unwrap(),
715 Cookie::parse
716 ).unwrap();
717 assert!(
718 request.check_server_hash(
719 CLIENT_1, &SECRET,
720 |serial| serial == Serial(1559731985)
721 )
722 );
723
724 assert_eq!(
725 compose_vec(|vec| {
726 request.create_response(
727 Serial(1559734385), CLIENT_1, &SECRET
728 ).compose_option(vec)
729 }),
730 base16::decode_vec(
731 "2464c4abcf10c957010000005cf7a871d4a564a1442aca77"
732 ).unwrap()
733 );
734 }
735
736 #[test]
738 fn non_zero_reserved() {
739 let request = parse_slice(
740 &base16::decode_vec(
741 "fc93fc62807ddb8601abcdef5cf78f71a314227b6679ebf5"
742 ).unwrap(),
743 Cookie::parse
744 ).unwrap();
745 assert!(
746 request.check_server_hash(
747 CLIENT_2, &SECRET,
748 |serial| serial == Serial(1559727985)
749 )
750 );
751
752 assert_eq!(
753 compose_vec(|vec| {
754 request.create_response(
755 Serial(1559734700), CLIENT_2, &SECRET
756 ).compose_option(vec)
757 }),
758 base16::decode_vec(
759 "fc93fc62807ddb86010000005cf7a9acf73a7810aca2381e"
760 ).unwrap()
761 );
762 }
763
764 #[test]
766 fn new_secret() {
767
768 const OLD_SECRET: [u8; 16] = [
769 0xdd, 0x3b, 0xdf, 0x93, 0x44, 0xb6, 0x78, 0xb1,
770 0x85, 0xa6, 0xf5, 0xcb, 0x60, 0xfc, 0xa7, 0x15,
771 ];
772 const NEW_SECRET: [u8; 16] = [
773 0x44, 0x55, 0x36, 0xbc, 0xd2, 0x51, 0x32, 0x98,
774 0x07, 0x5a, 0x5d, 0x37, 0x96, 0x63, 0xc9, 0x62,
775 ];
776
777 let request = parse_slice(
778 &base16::decode_vec(
779 "22681ab97d52c298010000005cf7c57926556bd0934c72f8"
780 ).unwrap(),
781 Cookie::parse
782 ).unwrap();
783 assert!(
784 !request.check_server_hash(
785 CLIENT_6, &NEW_SECRET,
786 |serial| serial == Serial(1559741817)
787 )
788 );
789 assert!(
790 request.check_server_hash(
791 CLIENT_6, &OLD_SECRET,
792 |serial| serial == Serial(1559741817)
793 )
794 );
795
796 assert_eq!(
797 compose_vec(|vec| {
798 request.create_response(
799 Serial(1559741961), CLIENT_6, &NEW_SECRET
800 ).compose_option(vec)
801 }),
802 base16::decode_vec(
803 "22681ab97d52c298010000005cf7c609a6bb79d16625507a"
804 ).unwrap()
805 );
806 }
807 }
808}
809