1#![allow(deprecated)]
2
3pub(super) mod key;
4pub mod rendezvous;
5mod server_messages;
6#[cfg(test)]
7mod test;
8
9pub mod wordlist;
11
12use serde_derive::{Deserialize, Serialize};
13use std::{borrow::Cow, str::FromStr};
14use thiserror::Error;
15
16use crate::Wordlist;
17
18use self::{rendezvous::*, server_messages::EncryptedMessage};
19
20use crypto_secretbox as secretbox;
21
22#[derive(Debug, thiserror::Error)]
24#[non_exhaustive]
25pub enum WormholeError {
26 #[error("Corrupt message received from peer")]
28 ProtocolJson(
29 #[from]
30 #[source]
31 serde_json::Error,
32 ),
33 #[error("Error with the rendezvous server connection")]
35 ServerError(
36 #[from]
37 #[source]
38 rendezvous::RendezvousError,
39 ),
40 #[error("Protocol error: {}", _0)]
43 Protocol(Box<str>),
44 #[error(
48 "Key confirmation failed. If you didn't mistype the code, \
49 this is a sign of an attacker guessing passwords. Please try \
50 again some time later."
51 )]
52 PakeFailed,
53 #[error("Cannot decrypt a received message")]
55 Crypto,
56 #[error("Nameplate is unclaimed: {}", _0)]
58 UnclaimedNameplate(Nameplate),
59 #[error("The provided code is invalid: {_0}")]
61 CodeInvalid(#[from] ParseCodeError),
62}
63
64impl WormholeError {
65 pub fn is_scared(&self) -> bool {
67 matches!(self, Self::PakeFailed)
68 }
69}
70
71impl From<std::convert::Infallible> for WormholeError {
72 fn from(_: std::convert::Infallible) -> Self {
73 unreachable!()
74 }
75}
76
77#[derive(Clone, Debug, PartialEq, Eq)]
81#[deprecated(
82 since = "0.7.0",
83 note = "part of the response of `Wormhole::connect_without_code(...)` and `Wormhole::connect_with_code(...) please use 'MailboxConnection::create(...)`/`MailboxConnection::connect(..)` and `Wormhole::connect(mailbox_connection)' instead"
84)]
85pub struct WormholeWelcome {
86 pub welcome: Option<String>,
88 pub code: Code,
90}
91
92pub struct MailboxConnection<V: serde::Serialize + Send + Sync + 'static> {
111 config: AppConfig<V>,
113 server: RendezvousServer,
115 welcome: Option<String>,
117 mailbox: Mailbox,
119 code: Code,
121}
122
123impl<V: serde::Serialize + Send + Sync + 'static> MailboxConnection<V> {
124 pub async fn create(config: AppConfig<V>, code_length: usize) -> Result<Self, WormholeError> {
141 Self::create_with_validated_password(
142 config,
143 Wordlist::default_wordlist(code_length).choose_words(),
144 )
145 .await
146 }
147
148 pub async fn create_with_password(
171 config: AppConfig<V>,
172 #[cfg(not(feature = "entropy"))] password: &str,
173 #[cfg(feature = "entropy")] password: Password,
174 ) -> Result<Self, WormholeError> {
175 #[cfg(not(feature = "entropy"))]
176 let password = password.parse().map_err(ParseCodeError::from)?;
177
178 Self::create_with_validated_password(config, password).await
179 }
180
181 async fn create_with_validated_password(
188 config: AppConfig<V>,
189 password: Password,
190 ) -> Result<Self, WormholeError> {
191 let (mut server, welcome) =
192 RendezvousServer::connect(&config.id, &config.rendezvous_url).await?;
193 let (nameplate, mailbox) = server.allocate_claim_open().await?;
194 let code = Code::from_components(nameplate, password);
195
196 Ok(MailboxConnection {
197 config,
198 server,
199 mailbox,
200 code,
201 welcome,
202 })
203 }
204
205 pub async fn connect(
226 config: AppConfig<V>,
227 code: Code,
228 allocate: bool,
229 ) -> Result<Self, WormholeError> {
230 let (mut server, welcome) =
231 RendezvousServer::connect(&config.id, &config.rendezvous_url).await?;
232 let nameplate = code.nameplate();
233
234 if !allocate {
237 let nameplates = server.list_nameplates().await?;
238 if !nameplates.contains(&nameplate) {
239 server.shutdown(Mood::Errory).await?;
240 return Err(WormholeError::UnclaimedNameplate(nameplate));
241 }
242 }
243 let mailbox = server.claim_open(nameplate).await?;
244
245 Ok(MailboxConnection {
246 config,
247 server,
248 mailbox,
249 code,
250 welcome,
251 })
252 }
253
254 pub async fn shutdown(self, mood: Mood) -> Result<(), WormholeError> {
277 self.server
278 .shutdown(mood)
279 .await
280 .map_err(WormholeError::ServerError)
281 }
282
283 pub fn welcome(&self) -> Option<&str> {
285 self.welcome.as_deref()
286 }
287
288 pub fn mailbox(&self) -> &Mailbox {
290 &self.mailbox
291 }
292
293 pub fn code(&self) -> &Code {
295 &self.code
296 }
297}
298
299#[derive(Debug)]
303pub struct Wormhole {
304 #[allow(deprecated)]
305 server: RendezvousServer,
306 phase: u64,
307 key: key::Key<key::WormholeKey>,
308 appid: AppID,
309 #[deprecated(since = "0.7.0", note = "Use the verifier() method")]
311 pub verifier: Box<secretbox::Key>,
312 #[deprecated(since = "0.7.0", note = "Use the our_version() method")]
314 pub our_version: Box<dyn std::any::Any + Send + Sync>,
315 #[deprecated(since = "0.7.0", note = "Use the peer_version() method")]
317 pub peer_version: serde_json::Value,
318}
319
320impl Wormhole {
321 #[deprecated(
331 since = "0.7.0",
332 note = "please use 'MailboxConnection::create(..) and Wormhole::connect(mailbox_connection)' instead"
333 )]
334 #[allow(deprecated)]
335 pub async fn connect_without_code(
336 config: AppConfig<impl serde::Serialize + Send + Sync + 'static>,
337 code_length: usize,
338 ) -> Result<
339 (
340 WormholeWelcome,
341 impl std::future::Future<Output = Result<Self, WormholeError>>,
342 ),
343 WormholeError,
344 > {
345 let mailbox_connection = MailboxConnection::create(config, code_length).await?;
346 Ok((
347 WormholeWelcome {
348 welcome: mailbox_connection.welcome.clone(),
349 code: mailbox_connection.code.clone(),
350 },
351 Self::connect(mailbox_connection),
352 ))
353 }
354
355 #[deprecated(
359 since = "0.7.0",
360 note = "please use 'MailboxConnection::connect(..) and Wormhole::connect(mailbox_connection)' instead"
361 )]
362 #[allow(deprecated)]
363 pub async fn connect_with_code(
364 config: AppConfig<impl serde::Serialize + Send + Sync + 'static>,
365 code: Code,
366 ) -> Result<(WormholeWelcome, Self), WormholeError> {
367 let mailbox_connection = MailboxConnection::connect(config, code.clone(), true).await?;
368 Ok((
369 WormholeWelcome {
370 welcome: mailbox_connection.welcome.clone(),
371 code,
372 },
373 Self::connect(mailbox_connection).await?,
374 ))
375 }
376
377 pub async fn connect(
381 mailbox_connection: MailboxConnection<impl serde::Serialize + Send + Sync + 'static>,
382 ) -> Result<Self, WormholeError> {
383 let MailboxConnection {
384 config,
385 mut server,
386 mailbox: _mailbox,
387 code,
388 welcome: _welcome,
389 } = mailbox_connection;
390
391 let (pake_state, pake_msg_ser) = key::make_pake(code.as_ref(), &config.id);
393 server.send_peer_message(Phase::PAKE, pake_msg_ser).await?;
394
395 let peer_pake = key::extract_pake_msg(&server.next_peer_message_some().await?.body)?;
397 let key = pake_state
398 .finish(&peer_pake)
399 .map_err(|_| WormholeError::PakeFailed)
400 .map(|key| *secretbox::Key::from_slice(&key))?;
401
402 let mut versions = key::VersionsMessage::new();
404 versions.set_app_versions(serde_json::to_value(&config.app_version).unwrap());
405 let (version_phase, version_msg) = key::build_version_msg(server.side(), &key, &versions);
406 server.send_peer_message(version_phase, version_msg).await?;
407 let peer_version = server.next_peer_message_some().await?;
408
409 let versions: key::VersionsMessage = peer_version
411 .decrypt(&key)
412 .ok_or(WormholeError::PakeFailed)
413 .and_then(|plaintext| {
414 serde_json::from_slice(&plaintext).map_err(WormholeError::ProtocolJson)
415 })?;
416
417 let peer_version = versions.app_versions;
418
419 if server.needs_nameplate_release() {
420 server.release_nameplate().await?;
421 }
422
423 tracing::info!("Found peer on the rendezvous server.");
424
425 #[allow(deprecated)]
427 Ok(Self {
428 server,
429 appid: config.id,
430 phase: 0,
431 key: key::Key::new(key.into()),
432 verifier: Box::new(key::derive_verifier(&key)),
433 our_version: Box::new(config.app_version),
434 peer_version,
435 })
436 }
437
438 pub async fn send(&mut self, plaintext: Vec<u8>) -> Result<(), WormholeError> {
440 let phase_string = Phase::numeric(self.phase);
441 self.phase += 1;
442 let data_key = key::derive_phase_key(self.server.side(), &self.key, &phase_string);
443 let (_nonce, encrypted) = key::encrypt_data(&data_key, &plaintext);
444 self.server
445 .send_peer_message(phase_string, encrypted)
446 .await?;
447 Ok(())
448 }
449
450 pub async fn send_json<T: serde::Serialize>(
461 &mut self,
462 message: &T,
463 ) -> Result<(), WormholeError> {
464 self.send(serde_json::to_vec(message).unwrap()).await
465 }
466
467 pub async fn receive(&mut self) -> Result<Vec<u8>, WormholeError> {
469 loop {
470 let peer_message = match self.server.next_peer_message().await? {
471 Some(peer_message) => peer_message,
472 None => continue,
473 };
474 if peer_message.phase.to_num().is_none() {
475 todo!("log and ignore, for future expansion");
477 }
478
479 let decrypted_message = peer_message
481 .decrypt(&self.key)
482 .ok_or(WormholeError::Crypto)?;
483
484 return Ok(decrypted_message);
486 }
487 }
488
489 pub async fn receive_json<T>(&mut self) -> Result<Result<T, serde_json::Error>, WormholeError>
497 where
498 T: for<'a> serde::Deserialize<'a>,
499 {
500 self.receive().await.map(|data: Vec<u8>| {
501 serde_json::from_slice(&data).inspect_err(|_| {
502 tracing::error!(
503 "Received invalid data from peer: '{}'",
504 String::from_utf8_lossy(&data)
505 );
506 })
507 })
508 }
509
510 pub async fn close(self) -> Result<(), WormholeError> {
512 tracing::debug!("Closing Wormhole…");
513 self.server.shutdown(Mood::Happy).await.map_err(Into::into)
514 }
515
516 pub fn appid(&self) -> &AppID {
521 &self.appid
522 }
523
524 pub fn key(&self) -> &key::Key<key::WormholeKey> {
529 &self.key
530 }
531
532 pub fn verifier(&self) -> &secretbox::Key {
545 #[allow(deprecated)]
546 &self.verifier
547 }
548
549 pub fn our_version(&self) -> &(dyn std::any::Any + Send + Sync) {
553 #[allow(deprecated)]
554 &*self.our_version
555 }
556
557 pub fn peer_version(&self) -> &serde_json::Value {
563 #[allow(deprecated)]
564 &self.peer_version
565 }
566}
567
568#[derive(Debug, PartialEq, Copy, Clone, Deserialize, Serialize, derive_more::Display)]
573pub enum Mood {
574 #[serde(rename = "happy")]
576 Happy,
577 #[serde(rename = "lonely")]
579 Lonely,
580 #[serde(rename = "errory")]
582 Errory,
583 #[serde(rename = "scary")]
587 Scared,
588 #[serde(rename = "unwelcome")]
590 Unwelcome,
591}
592
593#[derive(PartialEq, Eq, Clone, Debug)]
605pub struct AppConfig<V> {
606 pub id: AppID,
608 pub rendezvous_url: Cow<'static, str>,
610 pub app_version: V,
612}
613
614impl<V> AppConfig<V> {
615 pub fn id(mut self, id: AppID) -> Self {
617 self.id = id;
618 self
619 }
620
621 pub fn rendezvous_url(mut self, rendezvous_url: Cow<'static, str>) -> Self {
623 self.rendezvous_url = rendezvous_url;
624 self
625 }
626}
627
628impl<V: serde::Serialize> AppConfig<V> {
629 pub fn app_version(mut self, app_version: V) -> Self {
631 self.app_version = app_version;
632 self
633 }
634}
635
636#[derive(
642 PartialEq, Eq, Clone, Debug, Deserialize, Serialize, derive_more::Display, derive_more::Deref,
643)]
644#[deref(forward)]
645pub struct AppID(
646 #[deref]
647 #[deprecated(since = "0.7.0", note = "use the AsRef<str> implementation")]
648 pub Cow<'static, str>,
649);
650
651impl AppID {
652 pub fn new(id: impl Into<Cow<'static, str>>) -> Self {
654 AppID(id.into())
655 }
656}
657
658impl From<String> for AppID {
659 fn from(s: String) -> Self {
660 Self::new(s)
661 }
662}
663
664impl AsRef<str> for AppID {
665 fn as_ref(&self) -> &str {
666 &self.0
667 }
668}
669
670#[derive(
672 PartialEq, Eq, Clone, Debug, Deserialize, Serialize, derive_more::Display, derive_more::Deref,
673)]
674#[serde(transparent)]
675#[display("MySide({})", "&*_0")]
676#[deprecated(
677 since = "0.7.0",
678 note = "This will be a private type in the future. Open an issue if you require access to protocol intrinsics in the future"
679)]
680pub struct MySide(EitherSide);
681
682impl MySide {
683 pub fn generate() -> MySide {
684 use rand::{rngs::OsRng, RngCore};
685
686 let mut bytes: [u8; 5] = [0; 5];
687 OsRng.fill_bytes(&mut bytes);
688
689 MySide(EitherSide(hex::encode(bytes)))
690 }
691
692 #[cfg(test)]
695 pub fn unchecked_from_string(s: String) -> MySide {
696 MySide(EitherSide(s))
697 }
698}
699
700#[derive(
702 PartialEq, Eq, Clone, Debug, Deserialize, Serialize, derive_more::Display, derive_more::Deref,
703)]
704#[serde(transparent)]
705#[display("TheirSide({})", "&*_0")]
706#[deprecated(
707 since = "0.7.0",
708 note = "This will be a private type in the future. Open an issue if you require access to protocol intrinsics in the future"
709)]
710pub struct TheirSide(EitherSide);
711
712impl<S: Into<String>> From<S> for TheirSide {
713 fn from(s: S) -> TheirSide {
714 TheirSide(EitherSide(s.into()))
715 }
716}
717
718#[derive(
719 PartialEq, Eq, Clone, Debug, Deserialize, Serialize, derive_more::Display, derive_more::Deref,
720)]
721#[serde(transparent)]
722#[deref(forward)]
723#[display("{}", "&*_0")]
724#[deprecated(
725 since = "0.7.0",
726 note = "This will be a private type in the future. Open an issue if you require access to protocol intrinsics in the future"
727)]
728pub struct EitherSide(pub String);
729
730impl<S: Into<String>> From<S> for EitherSide {
731 fn from(s: S) -> EitherSide {
732 EitherSide(s.into())
733 }
734}
735
736#[derive(PartialEq, Eq, Clone, Debug, Hash, Deserialize, Serialize, derive_more::Display)]
737#[serde(transparent)]
738#[deprecated(
739 since = "0.7.0",
740 note = "This will be a private type in the future. Open an issue if you require access to protocol intrinsics in the future"
741)]
742pub struct Phase(Cow<'static, str>);
743
744impl Phase {
745 pub const VERSION: Self = Phase(Cow::Borrowed("version"));
746 pub const PAKE: Self = Phase(Cow::Borrowed("pake"));
747
748 pub fn numeric(phase: u64) -> Self {
749 Phase(phase.to_string().into())
750 }
751
752 #[allow(dead_code)]
753 pub fn is_version(&self) -> bool {
754 self == &Self::VERSION
755 }
756
757 #[allow(dead_code)]
758 pub fn is_pake(&self) -> bool {
759 self == &Self::PAKE
760 }
761
762 pub fn to_num(&self) -> Option<u64> {
763 self.0.parse().ok()
764 }
765}
766
767impl AsRef<str> for Phase {
768 fn as_ref(&self) -> &str {
769 &self.0
770 }
771}
772
773#[derive(PartialEq, Eq, Clone, Debug, Deserialize, Serialize, derive_more::Display)]
774#[serde(transparent)]
775#[deprecated(
776 since = "0.7.0",
777 note = "This will be a private type in the future. Open an issue if you require access to protocol intrinsics in the future"
778)]
779pub struct Mailbox(pub String);
780
781#[derive(PartialEq, Eq, PartialOrd, Ord, Clone, Debug, Copy, derive_more::Display, Error)]
783#[display("Nameplate is not a number. Nameplates must be a number >= 1.")]
784#[non_exhaustive]
785pub struct ParseNameplateError {}
786
787#[derive(
790 PartialEq, Eq, Clone, Debug, Deserialize, Serialize, derive_more::Display, derive_more::Deref,
791)]
792#[serde(transparent)]
793#[deref(forward)]
794#[display("{}", _0)]
795#[cfg(not(feature = "entropy"))]
796pub struct Nameplate(
797 #[deprecated(since = "0.7.0", note = "use the AsRef<str> implementation")] pub String,
798);
799
800#[derive(PartialEq, Eq, Clone, Debug, Deserialize, Serialize, derive_more::Display)]
803#[serde(transparent)]
804#[display("{}", _0)]
805#[cfg(feature = "entropy")]
806pub struct Nameplate(String);
807
808#[allow(deprecated)]
809impl Nameplate {
810 #[deprecated(
814 since = "0.7.2",
815 note = "Nameplates will be required to be numbers soon. Use the [std::str::FromStr] implementation"
816 )]
817 #[cfg(not(feature = "entropy"))]
818 pub fn new(n: impl Into<String>) -> Self {
819 let nameplate = n.into();
820 if let Err(err) = Nameplate::from_str(&nameplate) {
821 tracing::error!("{err}");
822 }
823
824 #[allow(unsafe_code)]
825 unsafe {
826 Self::new_unchecked(&nameplate)
827 }
828 }
829
830 #[allow(unsafe_code)]
834 #[doc(hidden)]
835 pub unsafe fn new_unchecked(n: &str) -> Self {
836 Nameplate(n.into())
837 }
838}
839
840impl FromStr for Nameplate {
841 type Err = ParseNameplateError;
842
843 fn from_str(s: &str) -> Result<Self, Self::Err> {
844 if !s.chars().all(|c| c.is_ascii_digit()) || u128::from_str(s) == Ok(0) {
845 Err(ParseNameplateError {})
846 } else {
847 Ok(Self(s.to_string()))
848 }
849 }
850}
851
852#[allow(deprecated)]
854impl From<Nameplate> for String {
855 fn from(value: Nameplate) -> Self {
856 value.0
857 }
858}
859
860#[allow(deprecated)]
864#[cfg(not(feature = "entropy"))]
865impl From<String> for Nameplate {
866 fn from(value: String) -> Self {
867 tracing::debug!(
868 "Implementation of From<String> for Nameplate is deprecated. Use the FromStr implementation instead"
869 );
870
871 if let Err(err) = Nameplate::from_str(&value) {
872 tracing::error!("{err} This will be a hard error in the future.");
873 }
874
875 Self(value)
876 }
877}
878
879#[allow(deprecated)]
881impl AsRef<str> for Nameplate {
882 fn as_ref(&self) -> &str {
883 &self.0
884 }
885}
886
887#[derive(PartialEq, Eq, PartialOrd, Ord, Clone, Debug, Copy, derive_more::Display, Error)]
905#[non_exhaustive]
906pub enum ParsePasswordError {
907 #[display("Password too short. It is only {value} bytes, but must be at least {required}")]
909 TooShort {
910 value: usize,
912 required: usize,
914 },
915 #[display(
917 "Password too weak. It can be guessed with an average of {value} tries, but must be at least {required}"
918 )]
919 LittleEntropy {
920 value: u64,
922 required: u64,
924 },
925}
926
927#[derive(Clone, Debug, Serialize, derive_more::Display)]
930#[serde(transparent)]
931#[display("{password}")]
932pub struct Password {
933 password: String,
934 #[serde(skip)]
935 #[cfg(feature = "entropy")]
936 entropy: zxcvbn::Entropy,
937}
938
939impl PartialEq for Password {
940 fn eq(&self, other: &Self) -> bool {
941 self.password == other.password
942 }
943}
944
945impl Eq for Password {}
946
947impl Password {
948 #[allow(unsafe_code)]
954 #[doc(hidden)]
955 pub unsafe fn new_unchecked(n: impl Into<String>) -> Self {
956 let password = n.into();
957 #[cfg(feature = "entropy")]
958 let entropy = Self::calculate_entropy(&password);
959
960 Password {
961 password,
962 #[cfg(feature = "entropy")]
963 entropy,
964 }
965 }
966
967 #[cfg(feature = "entropy")]
968 fn calculate_entropy(password: &str) -> zxcvbn::Entropy {
969 static PGP_WORDLIST: std::sync::OnceLock<Vec<&str>> = std::sync::OnceLock::new();
970 let words = PGP_WORDLIST.get_or_init(|| {
971 Wordlist::default_wordlist(2)
973 .into_words()
974 .map(|s| &*s.leak())
975 .collect::<Vec<_>>()
976 });
977
978 zxcvbn::zxcvbn(password, &words[..])
979 }
980}
981
982impl From<Password> for String {
983 fn from(value: Password) -> Self {
984 value.password
985 }
986}
987
988impl AsRef<str> for Password {
989 fn as_ref(&self) -> &str {
990 &self.password
991 }
992}
993
994impl FromStr for Password {
995 type Err = ParsePasswordError;
996
997 fn from_str(password: &str) -> Result<Self, Self::Err> {
998 let password = password.to_string();
999
1000 if password.len() < 4 {
1001 Err(ParsePasswordError::TooShort {
1002 value: password.len(),
1003 required: 4,
1004 })
1005 } else {
1006 #[cfg(feature = "entropy")]
1007 return {
1008 let entropy = Self::calculate_entropy(&password);
1009 if entropy.guesses() < 2_u64.pow(16) {
1010 return Err(ParsePasswordError::LittleEntropy {
1011 value: entropy.guesses(),
1012 required: 2_u64.pow(16),
1013 });
1014 }
1015 Ok(Self { password, entropy })
1016 };
1017
1018 #[cfg(not(feature = "entropy"))]
1019 Ok(Self { password })
1020 }
1021 }
1022}
1023
1024#[derive(PartialEq, Eq, PartialOrd, Ord, Clone, Debug, Copy, derive_more::Display, Error)]
1026#[non_exhaustive]
1027pub enum ParseCodeError {
1028 #[display("The code is empty")]
1030 Empty,
1031 #[display("A code must contain at least one '-' to separate nameplate from password")]
1033 SeparatorMissing,
1034 #[display("{_0}")]
1036 Nameplate(#[from] ParseNameplateError),
1037 #[display("{_0}")]
1039 Password(#[from] ParsePasswordError),
1040}
1041
1042#[derive(PartialEq, Eq, Clone, Debug, derive_more::Display, derive_more::Deref)]
1049#[display("{}", _0)]
1050#[cfg(not(feature = "entropy"))]
1051pub struct Code(
1052 #[deprecated(since = "0.7.0", note = "use the std::fmt::Display implementation")] pub String,
1053);
1054
1055#[derive(PartialEq, Eq, Clone, Debug, derive_more::Display)]
1062#[display("{}", _0)]
1063#[cfg(feature = "entropy")]
1064pub struct Code(String);
1065
1066#[allow(deprecated)]
1067impl Code {
1068 #[deprecated(
1072 since = "0.7.2",
1073 note = "Use [`from_components`] or the [std::str::FromStr] implementation"
1074 )]
1075 #[cfg(not(feature = "entropy"))]
1076 pub fn new(nameplate: &Nameplate, password: &str) -> Self {
1077 if let Err(err) = Password::from_str(password) {
1078 tracing::error!("{err}");
1079 }
1080
1081 #[allow(unsafe_code)]
1082 unsafe {
1083 Self::from_components(nameplate.clone(), Password::new_unchecked(password))
1084 }
1085 }
1086
1087 pub fn from_components(nameplate: Nameplate, password: Password) -> Self {
1089 Self(format!("{nameplate}-{password}"))
1090 }
1091
1092 #[deprecated(since = "0.7.2", note = "Use [Self::nameplate] and [Self::password]")]
1094 pub fn split(&self) -> (Nameplate, String) {
1095 let mut iter = self.0.splitn(2, '-');
1096 #[allow(unsafe_code)]
1097 let nameplate = unsafe { Nameplate::new_unchecked(iter.next().unwrap()) };
1098 let password = iter.next().unwrap();
1099 (nameplate, password.to_string())
1100 }
1101
1102 pub fn nameplate(&self) -> Nameplate {
1104 #[allow(unsafe_code)]
1106 unsafe {
1107 Nameplate::new_unchecked(self.0.split('-').next().unwrap())
1108 }
1109 }
1110
1111 pub fn password(&self) -> Password {
1113 #[allow(unsafe_code)]
1115 unsafe {
1116 Password::new_unchecked(self.0.splitn(2, '-').last().unwrap())
1117 }
1118 }
1119}
1120
1121#[allow(deprecated)]
1123impl From<Code> for String {
1124 fn from(value: Code) -> Self {
1125 value.0
1126 }
1127}
1128
1129#[cfg(not(feature = "entropy"))]
1133impl From<String> for Code {
1134 fn from(value: String) -> Self {
1135 tracing::debug!(
1136 "Implementation of From<String> for Code is deprecated. Use the FromStr implementation instead"
1137 );
1138
1139 if let Err(err) = Code::from_str(&value) {
1140 tracing::error!("{err} This will be a hard error in the future.");
1141 }
1142
1143 Self(value)
1144 }
1145}
1146
1147impl FromStr for Code {
1148 type Err = ParseCodeError;
1149
1150 fn from_str(s: &str) -> Result<Self, Self::Err> {
1151 match s.split_once('-') {
1152 Some((n, p)) => {
1153 let password: Password = p.parse()?;
1154 let nameplate: Nameplate = n.parse()?;
1155
1156 Ok(Self(format!("{}-{}", nameplate, password)))
1157 },
1158 None if s.is_empty() => Err(ParseCodeError::Empty),
1159 None => Err(ParseCodeError::SeparatorMissing),
1160 }
1161 }
1162}
1163
1164#[allow(deprecated)]
1166impl AsRef<str> for Code {
1167 fn as_ref(&self) -> &str {
1168 &self.0
1169 }
1170}