1#![warn(missing_docs)]
20#![doc(
21 html_logo_url = "https://raw.githubusercontent.com/getsentry/relay/master/artwork/relay-icon.png",
22 html_favicon_url = "https://raw.githubusercontent.com/getsentry/relay/master/artwork/relay-icon.png"
23)]
24
25use std::fmt;
26use std::str::FromStr;
27
28use chrono::{DateTime, Duration, Utc};
29use data_encoding::BASE64URL_NOPAD;
30use ed25519_dalek::{Signer, Verifier};
31use hmac::{Hmac, Mac};
32use rand::rngs::OsRng;
33use rand::{thread_rng, RngCore};
34use relay_common::time::UnixTimestamp;
35use serde::de::DeserializeOwned;
36use serde::{Deserialize, Serialize};
37use sha2::Sha512;
38use uuid::Uuid;
39
40include!(concat!(env!("OUT_DIR"), "/constants.gen.rs"));
41
42const LATEST_VERSION: RelayVersion = RelayVersion::new(VERSION_MAJOR, VERSION_MINOR, VERSION_PATCH);
44
45const OLDEST_VERSION: RelayVersion = RelayVersion::new(0, 0, 0); pub type RelayId = Uuid;
50
51#[derive(Clone, Copy, Debug, Default, Eq, Ord, PartialEq, PartialOrd)]
53pub struct RelayVersion {
54 major: u8,
55 minor: u8,
56 patch: u8,
57}
58
59impl RelayVersion {
60 pub fn current() -> Self {
62 LATEST_VERSION
63 }
64
65 pub fn oldest() -> Self {
70 OLDEST_VERSION
71 }
72
73 pub const fn new(major: u8, minor: u8, patch: u8) -> Self {
75 Self {
76 major,
77 minor,
78 patch,
79 }
80 }
81
82 pub fn supported(self) -> bool {
84 self >= Self::oldest()
85 }
86
87 pub fn outdated(self) -> bool {
89 self < Self::current()
90 }
91}
92
93impl fmt::Display for RelayVersion {
94 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
95 write!(f, "{}.{}.{}", self.major, self.minor, self.patch)
96 }
97}
98
99#[derive(Clone, Copy, Debug, Default, thiserror::Error)]
101#[error("invalid relay version string")]
102pub struct ParseRelayVersionError;
103
104impl FromStr for RelayVersion {
105 type Err = ParseRelayVersionError;
106
107 fn from_str(s: &str) -> Result<Self, Self::Err> {
108 let mut iter = s
109 .split(&['.', '-'][..])
110 .map(|s| s.parse().map_err(|_| ParseRelayVersionError));
111
112 let major = iter.next().ok_or(ParseRelayVersionError)??;
113 let minor = iter.next().ok_or(ParseRelayVersionError)??;
114 let patch = iter.next().ok_or(ParseRelayVersionError)??;
115
116 Ok(Self::new(major, minor, patch))
117 }
118}
119
120relay_common::impl_str_serde!(RelayVersion, "a version string");
121
122#[derive(Debug, Eq, Hash, PartialEq, thiserror::Error)]
124pub enum KeyParseError {
125 #[error("bad key encoding")]
127 BadEncoding,
128 #[error("bad key data")]
130 BadKey,
131}
132
133#[derive(Debug, thiserror::Error)]
135pub enum UnpackError {
136 #[error("invalid signature on data")]
138 BadSignature,
139 #[error("bad key encoding")]
141 BadEncoding,
142 #[error("could not deserialize payload")]
144 BadPayload(#[source] serde_json::Error),
145 #[error("signature is too old")]
147 SignatureExpired,
148}
149
150#[derive(Serialize, Deserialize, Debug)]
154pub struct SignatureHeader {
155 #[serde(rename = "t", skip_serializing_if = "Option::is_none")]
157 pub timestamp: Option<DateTime<Utc>>,
158}
159
160impl SignatureHeader {
161 pub fn expired(&self, max_age: Duration) -> bool {
163 if let Some(ts) = self.timestamp {
164 ts < (Utc::now() - max_age)
165 } else {
166 false
167 }
168 }
169}
170
171impl Default for SignatureHeader {
172 fn default() -> SignatureHeader {
173 SignatureHeader {
174 timestamp: Some(Utc::now()),
175 }
176 }
177}
178
179#[derive(Clone)]
185pub struct SecretKey {
186 inner: ed25519_dalek::SigningKey,
187}
188
189#[derive(Serialize, Deserialize, Debug)]
191pub struct Registration {
192 relay_id: RelayId,
193}
194
195impl SecretKey {
196 pub fn sign(&self, data: &[u8]) -> String {
200 self.sign_with_header(data, &SignatureHeader::default())
201 }
202
203 pub fn sign_with_header(&self, data: &[u8], header: &SignatureHeader) -> String {
209 let mut header =
210 serde_json::to_vec(&header).expect("attempted to pack non json safe header");
211 let header_encoded = BASE64URL_NOPAD.encode(&header);
212 header.push(b'\x00');
213 header.extend_from_slice(data);
214 let sig = self.inner.sign(&header);
215 let mut sig_encoded = BASE64URL_NOPAD.encode(&sig.to_bytes());
216 sig_encoded.push('.');
217 sig_encoded.push_str(&header_encoded);
218 sig_encoded
219 }
220
221 pub fn pack<S: Serialize>(&self, data: S) -> (Vec<u8>, String) {
223 self.pack_with_header(data, &SignatureHeader::default())
224 }
225
226 pub fn pack_with_header<S: Serialize>(
228 &self,
229 data: S,
230 header: &SignatureHeader,
231 ) -> (Vec<u8>, String) {
232 let json = serde_json::to_vec(&data).expect("attempted to pack non json safe data");
235 let sig = self.sign_with_header(&json, header);
236 (json, sig)
237 }
238}
239
240impl PartialEq for SecretKey {
241 fn eq(&self, other: &SecretKey) -> bool {
242 self.inner.to_keypair_bytes() == other.inner.to_keypair_bytes()
243 }
244}
245
246impl Eq for SecretKey {}
247
248impl FromStr for SecretKey {
249 type Err = KeyParseError;
250
251 fn from_str(s: &str) -> Result<SecretKey, KeyParseError> {
252 let bytes = match BASE64URL_NOPAD.decode(s.as_bytes()) {
253 Ok(bytes) => bytes,
254 _ => return Err(KeyParseError::BadEncoding),
255 };
256
257 let inner = if let Ok(keypair) = bytes.as_slice().try_into() {
258 ed25519_dalek::SigningKey::from_keypair_bytes(&keypair)
259 .map_err(|_| KeyParseError::BadKey)?
260 } else if let Ok(secret_key) = bytes.try_into() {
261 ed25519_dalek::SigningKey::from_bytes(&secret_key)
262 } else {
263 return Err(KeyParseError::BadKey);
264 };
265
266 Ok(SecretKey { inner })
267 }
268}
269
270impl fmt::Display for SecretKey {
271 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
272 if f.alternate() {
273 write!(
274 f,
275 "{}",
276 BASE64URL_NOPAD.encode(&self.inner.to_keypair_bytes())
277 )
278 } else {
279 write!(f, "{}", BASE64URL_NOPAD.encode(&self.inner.to_bytes()))
280 }
281 }
282}
283
284impl fmt::Debug for SecretKey {
285 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
286 write!(f, "SecretKey(\"{self}\")")
287 }
288}
289
290relay_common::impl_str_serde!(SecretKey, "a secret key");
291
292#[derive(Clone, Eq, PartialEq)]
298pub struct PublicKey {
299 inner: ed25519_dalek::VerifyingKey,
300}
301
302impl PublicKey {
303 pub fn verify_meta(&self, data: &[u8], sig: &str) -> Option<SignatureHeader> {
306 let mut iter = sig.splitn(2, '.');
307 let sig_bytes = match iter.next() {
308 Some(sig_encoded) => BASE64URL_NOPAD.decode(sig_encoded.as_bytes()).ok()?,
309 None => return None,
310 };
311 let sig = ed25519_dalek::Signature::from_slice(&sig_bytes).ok()?;
312
313 let header = match iter.next() {
314 Some(header_encoded) => BASE64URL_NOPAD.decode(header_encoded.as_bytes()).ok()?,
315 None => return None,
316 };
317 let mut to_verify = header.clone();
318 to_verify.push(b'\x00');
319 to_verify.extend_from_slice(data);
320 if self.inner.verify(&to_verify, &sig).is_ok() {
321 serde_json::from_slice(&header).ok()
322 } else {
323 None
324 }
325 }
326
327 pub fn verify(&self, data: &[u8], sig: &str) -> bool {
329 self.verify_meta(data, sig).is_some()
330 }
331
332 pub fn verify_timestamp(&self, data: &[u8], sig: &str, max_age: Option<Duration>) -> bool {
334 self.verify_meta(data, sig)
335 .map(|header| max_age.is_none() || !header.expired(max_age.unwrap()))
336 .unwrap_or(false)
337 }
338
339 pub fn unpack_meta<D: DeserializeOwned>(
341 &self,
342 data: &[u8],
343 signature: &str,
344 ) -> Result<(SignatureHeader, D), UnpackError> {
345 if let Some(header) = self.verify_meta(data, signature) {
346 serde_json::from_slice(data)
347 .map(|data| (header, data))
348 .map_err(UnpackError::BadPayload)
349 } else {
350 Err(UnpackError::BadSignature)
351 }
352 }
353
354 pub fn unpack<D: DeserializeOwned>(
359 &self,
360 data: &[u8],
361 signature: &str,
362 max_age: Option<Duration>,
363 ) -> Result<D, UnpackError> {
364 let (header, data) = self.unpack_meta(data, signature)?;
365 if max_age.is_none() || !header.expired(max_age.unwrap()) {
366 Ok(data)
367 } else {
368 Err(UnpackError::SignatureExpired)
369 }
370 }
371}
372
373impl FromStr for PublicKey {
374 type Err = KeyParseError;
375
376 fn from_str(s: &str) -> Result<PublicKey, KeyParseError> {
377 let Ok(bytes) = BASE64URL_NOPAD.decode(s.as_bytes()) else {
378 return Err(KeyParseError::BadEncoding);
379 };
380
381 let inner = match bytes.try_into() {
382 Ok(bytes) => ed25519_dalek::VerifyingKey::from_bytes(&bytes)
383 .map_err(|_| KeyParseError::BadKey)?,
384 Err(_) => return Err(KeyParseError::BadKey),
385 };
386
387 Ok(PublicKey { inner })
388 }
389}
390
391impl fmt::Display for PublicKey {
392 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
393 write!(f, "{}", BASE64URL_NOPAD.encode(&self.inner.to_bytes()))
394 }
395}
396
397impl fmt::Debug for PublicKey {
398 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
399 write!(f, "PublicKey(\"{self}\")")
400 }
401}
402
403relay_common::impl_str_serde!(PublicKey, "a public key");
404
405pub fn generate_relay_id() -> RelayId {
407 Uuid::new_v4()
408}
409
410pub fn generate_key_pair() -> (SecretKey, PublicKey) {
412 let mut csprng = OsRng;
413 let kp = ed25519_dalek::SigningKey::generate(&mut csprng);
414 let pk = kp.verifying_key();
415 (SecretKey { inner: kp }, PublicKey { inner: pk })
416}
417
418#[derive(Clone, Debug, Deserialize, Serialize)]
437pub struct SignedRegisterState(String);
438
439impl SignedRegisterState {
440 fn mac(secret: &[u8]) -> Hmac<Sha512> {
442 Hmac::new_from_slice(secret).expect("HMAC takes variable keys")
443 }
444
445 fn sign(state: RegisterState, secret: &[u8]) -> Self {
447 let json = serde_json::to_string(&state).expect("relay register state serializes to JSON");
448 let token = BASE64URL_NOPAD.encode(json.as_bytes());
449
450 let mut mac = Self::mac(secret);
451 mac.update(token.as_bytes());
452 let signature = BASE64URL_NOPAD.encode(&mac.finalize().into_bytes());
453
454 Self(format!("{token}:{signature}"))
455 }
456
457 fn split(&self) -> (&str, &str) {
459 let mut split = self.as_str().splitn(2, ':');
460 (split.next().unwrap_or(""), split.next().unwrap_or(""))
461 }
462
463 pub fn as_str(&self) -> &str {
465 self.0.as_str()
466 }
467
468 pub fn unpack(
473 &self,
474 secret: &[u8],
475 max_age: Option<Duration>,
476 ) -> Result<RegisterState, UnpackError> {
477 let (token, signature) = self.split();
478 let code = BASE64URL_NOPAD
479 .decode(signature.as_bytes())
480 .map_err(|_| UnpackError::BadEncoding)?;
481
482 let mut mac = Self::mac(secret);
483 mac.update(token.as_bytes());
484 mac.verify_slice(&code)
485 .map_err(|_| UnpackError::BadSignature)?;
486
487 let json = BASE64URL_NOPAD
488 .decode(token.as_bytes())
489 .map_err(|_| UnpackError::BadEncoding)?;
490 let state =
491 serde_json::from_slice::<RegisterState>(&json).map_err(UnpackError::BadPayload)?;
492
493 if let Some(max_age) = max_age {
494 let secs = state.timestamp().as_secs() as i64;
495 if secs + max_age.num_seconds() < Utc::now().timestamp() {
496 return Err(UnpackError::SignatureExpired);
497 }
498 }
499
500 Ok(state)
501 }
502}
503
504impl fmt::Display for SignedRegisterState {
505 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
506 self.as_str().fmt(f)
507 }
508}
509
510#[derive(Clone, Deserialize, Serialize)]
516pub struct RegisterState {
517 timestamp: UnixTimestamp,
518 relay_id: RelayId,
519 public_key: PublicKey,
520 rand: String,
521}
522
523impl RegisterState {
524 pub fn timestamp(&self) -> UnixTimestamp {
526 self.timestamp
527 }
528
529 pub fn relay_id(&self) -> RelayId {
531 self.relay_id
532 }
533
534 pub fn public_key(&self) -> &PublicKey {
536 &self.public_key
537 }
538}
539
540fn nonce() -> String {
542 let mut rng = thread_rng();
543 let mut bytes = vec![0u8; 64];
544 rng.fill_bytes(&mut bytes);
545 BASE64URL_NOPAD.encode(&bytes)
546}
547
548#[derive(Serialize, Deserialize, Debug)]
554pub struct RegisterRequest {
555 relay_id: RelayId,
556 public_key: PublicKey,
557 #[serde(default)]
558 version: RelayVersion,
559}
560
561impl RegisterRequest {
562 pub fn new(relay_id: &RelayId, public_key: &PublicKey) -> RegisterRequest {
564 RegisterRequest {
565 relay_id: *relay_id,
566 public_key: public_key.clone(),
567 version: RelayVersion::current(),
568 }
569 }
570
571 pub fn bootstrap_unpack(
577 data: &[u8],
578 signature: &str,
579 max_age: Option<Duration>,
580 ) -> Result<RegisterRequest, UnpackError> {
581 let req: RegisterRequest = serde_json::from_slice(data).map_err(UnpackError::BadPayload)?;
582 let pk = req.public_key();
583 pk.unpack(data, signature, max_age)
584 }
585
586 pub fn relay_id(&self) -> RelayId {
588 self.relay_id
589 }
590
591 pub fn public_key(&self) -> &PublicKey {
593 &self.public_key
594 }
595
596 pub fn into_challenge(self, secret: &[u8]) -> RegisterChallenge {
598 let state = RegisterState {
599 timestamp: UnixTimestamp::now(),
600 relay_id: self.relay_id,
601 public_key: self.public_key,
602 rand: nonce(),
603 };
604
605 RegisterChallenge {
606 relay_id: self.relay_id,
607 token: SignedRegisterState::sign(state, secret),
608 }
609 }
610}
611
612#[derive(Serialize, Deserialize, Debug)]
614pub struct RegisterChallenge {
615 relay_id: RelayId,
616 token: SignedRegisterState,
617}
618
619impl RegisterChallenge {
620 pub fn relay_id(&self) -> &RelayId {
622 &self.relay_id
623 }
624
625 pub fn token(&self) -> &str {
627 self.token.as_str()
628 }
629
630 pub fn into_response(self) -> RegisterResponse {
632 RegisterResponse {
633 relay_id: self.relay_id,
634 token: self.token,
635 version: RelayVersion::current(),
636 }
637 }
638}
639
640#[derive(Serialize, Deserialize, Debug)]
645pub struct RegisterResponse {
646 relay_id: RelayId,
647 token: SignedRegisterState,
648 #[serde(default)]
649 version: RelayVersion,
650}
651
652impl RegisterResponse {
653 pub fn unpack(
655 data: &[u8],
656 signature: &str,
657 secret: &[u8],
658 max_age: Option<Duration>,
659 ) -> Result<(Self, RegisterState), UnpackError> {
660 let response: Self = serde_json::from_slice(data).map_err(UnpackError::BadPayload)?;
661 let state = response.token.unpack(secret, max_age)?;
662
663 if let Some(header) = state.public_key().verify_meta(data, signature) {
664 if max_age.is_some_and(|m| header.expired(m)) {
665 return Err(UnpackError::SignatureExpired);
666 }
667 } else {
668 return Err(UnpackError::BadSignature);
669 }
670
671 Ok((response, state))
672 }
673
674 pub fn relay_id(&self) -> RelayId {
676 self.relay_id
677 }
678
679 pub fn token(&self) -> &str {
681 self.token.as_str()
682 }
683
684 pub fn version(&self) -> RelayVersion {
686 self.version
687 }
688}
689
690#[cfg(test)]
691mod tests {
692 use super::*;
693
694 #[test]
695 fn test_keys() {
696 let sk: SecretKey =
697 "OvXFVm1tIUi8xDTuyHX1SSqdMc8nCt2qU9IUaH5p7oUk5pHZsdnfXNiMWiMLtSE86J3N9Peo5CBP1YQHDUkApQ"
698 .parse()
699 .unwrap();
700 let pk: PublicKey = "JOaR2bHZ31zYjFojC7UhPOidzfT3qOQgT9WEBw1JAKU"
701 .parse()
702 .unwrap();
703
704 assert_eq!(
705 sk.to_string(),
706 "OvXFVm1tIUi8xDTuyHX1SSqdMc8nCt2qU9IUaH5p7oU"
707 );
708 assert_eq!(
709 format!("{sk:#}"),
710 "OvXFVm1tIUi8xDTuyHX1SSqdMc8nCt2qU9IUaH5p7oUk5pHZsdnfXNiMWiMLtSE86J3N9Peo5CBP1YQHDUkApQ"
711 );
712 assert_eq!(
713 pk.to_string(),
714 "JOaR2bHZ31zYjFojC7UhPOidzfT3qOQgT9WEBw1JAKU"
715 );
716
717 assert_eq!(
718 "bad data".parse::<SecretKey>(),
719 Err(KeyParseError::BadEncoding)
720 );
721 assert_eq!("OvXF".parse::<SecretKey>(), Err(KeyParseError::BadKey));
722
723 assert_eq!(
724 "bad data".parse::<PublicKey>(),
725 Err(KeyParseError::BadEncoding)
726 );
727 assert_eq!("OvXF".parse::<PublicKey>(), Err(KeyParseError::BadKey));
728 }
729
730 #[test]
731 fn test_serializing() {
732 let sk: SecretKey =
733 "OvXFVm1tIUi8xDTuyHX1SSqdMc8nCt2qU9IUaH5p7oUk5pHZsdnfXNiMWiMLtSE86J3N9Peo5CBP1YQHDUkApQ"
734 .parse()
735 .unwrap();
736 let pk: PublicKey = "JOaR2bHZ31zYjFojC7UhPOidzfT3qOQgT9WEBw1JAKU"
737 .parse()
738 .unwrap();
739
740 let sk_json = serde_json::to_string(&sk).unwrap();
741 assert_eq!(sk_json, "\"OvXFVm1tIUi8xDTuyHX1SSqdMc8nCt2qU9IUaH5p7oU\"");
742
743 let pk_json = serde_json::to_string(&pk).unwrap();
744 assert_eq!(pk_json, "\"JOaR2bHZ31zYjFojC7UhPOidzfT3qOQgT9WEBw1JAKU\"");
745
746 assert_eq!(serde_json::from_str::<SecretKey>(&sk_json).unwrap(), sk);
747 assert_eq!(serde_json::from_str::<PublicKey>(&pk_json).unwrap(), pk);
748 }
749
750 #[test]
751 fn test_signatures() {
752 let (sk, pk) = generate_key_pair();
753 let data = b"Hello World!";
754
755 let sig = sk.sign(data);
756 assert!(pk.verify(data, &sig));
757
758 let bad_sig =
759 "jgubwSf2wb2wuiRpgt2H9_bdDSMr88hXLp5zVuhbr65EGkSxOfT5ILIWr623twLgLd0bDgHg6xzOaUCX7XvUCw";
760 assert!(!pk.verify(data, bad_sig));
761 }
762
763 #[test]
764 fn test_registration() {
765 let max_age = Duration::minutes(15);
766
767 let relay_id = generate_relay_id();
769 let (sk, pk) = generate_key_pair();
770
771 let request = RegisterRequest::new(&relay_id, &pk);
773
774 let (request_bytes, request_sig) = sk.pack(request);
776
777 let request =
779 RegisterRequest::bootstrap_unpack(&request_bytes, &request_sig, Some(max_age)).unwrap();
780 assert_eq!(request.relay_id(), relay_id);
781 assert_eq!(request.public_key(), &pk);
782
783 let upstream_secret = b"secret";
784
785 let challenge = request.into_challenge(upstream_secret);
787 let challenge_token = challenge.token().to_owned();
788 assert_eq!(challenge.relay_id(), &relay_id);
789 assert!(challenge.token().len() > 40);
790
791 let state = SignedRegisterState(challenge_token.clone());
793 let register_state = state.unpack(upstream_secret, None).unwrap();
794 assert_eq!(register_state.public_key, pk);
795 assert_eq!(register_state.relay_id, relay_id);
796
797 let response = challenge.into_response();
799
800 let (response_bytes, response_sig) = sk.pack(response);
802 let (response, _) = RegisterResponse::unpack(
803 &response_bytes,
804 &response_sig,
805 upstream_secret,
806 Some(max_age),
807 )
808 .unwrap();
809
810 assert_eq!(response.relay_id(), relay_id);
811 assert_eq!(response.token(), challenge_token);
812 assert_eq!(response.version, LATEST_VERSION);
813 }
814
815 #[test]
820 #[allow(clippy::print_stdout, reason = "helper test to generate output")]
821 fn test_generate_strings_for_test_auth_py() {
822 let max_age = Duration::minutes(15);
823 println!("Generating test data for test_auth.py...");
824
825 let relay_id = generate_relay_id();
827 println!("RELAY_ID = b\"{relay_id}\"");
828 let (sk, pk) = generate_key_pair();
829 println!("RELAY_KEY = b\"{pk}\"");
830
831 let request = RegisterRequest::new(&relay_id, &pk);
833 println!("REQUEST = b'{}'", serde_json::to_string(&request).unwrap());
834
835 let (request_bytes, request_sig) = sk.pack(&request);
837 println!("REQUEST_SIG = \"{request_sig}\"");
838
839 let request =
841 RegisterRequest::bootstrap_unpack(&request_bytes, &request_sig, Some(max_age)).unwrap();
842
843 let upstream_secret = b"secret";
844
845 let challenge = request.into_challenge(upstream_secret);
847 let challenge_token = challenge.token().to_owned();
848 println!("TOKEN = \"{challenge_token}\"");
849
850 let response = challenge.into_response();
852 let serialized_response = serde_json::to_string(&response).unwrap();
853 let (_, response_sig) = sk.pack(&response);
854
855 println!("RESPONSE = b'{serialized_response}'");
856 println!("RESPONSE_SIG = \"{response_sig}\"");
857
858 println!("RELAY_VERSION = \"{LATEST_VERSION}\"");
859 }
860
861 #[test]
863 fn test_deserialize_old_response() {
864 let serialized_challenge = "{\"relay_id\":\"6b7d15b8-cee2-4354-9fee-dae7ef43e434\",\"token\":\"eyJ0aW1lc3RhbXAiOjE1OTg5Njc0MzQsInJlbGF5X2lkIjoiNmI3ZDE1YjgtY2VlMi00MzU0LTlmZWUtZGFlN2VmNDNlNDM0IiwicHVibGljX2tleSI6ImtNcEdieWRIWlN2b2h6ZU1sZ2hjV3dIZDhNa3JlS0d6bF9uY2RrWlNPTWciLCJyYW5kIjoiLUViNG9Hal80dUZYOUNRRzFBVmdqTjRmdGxaNU9DSFlNOFl2d1podmlyVXhUY0tFSWYtQzhHaldsZmgwQTNlMzYxWE01dVh0RHhvN00tbWhZeXpWUWcifQ:KJUDXlwvibKNQmex-_Cu1U0FArlmoDkyqP7bYIDGrLXudfjGfCjH-UjNsUHWVDnbM28YdQ-R2MBSyF51aRLQcw\"}";
865 let result: RegisterResponse = serde_json::from_str(serialized_challenge).unwrap();
866 assert_eq!(
867 result.relay_id,
868 Uuid::parse_str("6b7d15b8-cee2-4354-9fee-dae7ef43e434").unwrap()
869 )
870 }
871
872 #[test]
873 fn test_relay_version_current() {
874 assert_eq!(
875 env!("CARGO_PKG_VERSION"),
876 RelayVersion::current().to_string()
877 );
878 }
879
880 #[test]
881 fn test_relay_version_oldest() {
882 assert_eq!("0.0.0", RelayVersion::oldest().to_string());
884 }
885
886 #[test]
887 fn test_relay_version_parse() {
888 assert_eq!(
889 RelayVersion::new(20, 7, 0),
890 "20.7.0-beta.0".parse().unwrap()
891 );
892 }
893
894 #[test]
895 fn test_relay_version_oldest_supported() {
896 assert!(RelayVersion::oldest().supported());
897 }
898
899 #[test]
900 fn test_relay_version_any_supported() {
901 assert!(RelayVersion::default().supported());
904 }
905
906 #[test]
907 fn test_relay_version_from_str() {
908 assert_eq!(RelayVersion::new(20, 7, 0), "20.7.0".parse().unwrap());
909 }
910}