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> {
112 config: AppConfig<V>,
114 server: RendezvousServer,
116 welcome: Option<String>,
118 mailbox: Mailbox,
120 code: Code,
122}
123
124impl<V: serde::Serialize + Send + Sync + 'static> MailboxConnection<V> {
125 pub async fn create(config: AppConfig<V>, code_length: usize) -> Result<Self, WormholeError> {
142 Self::create_with_validated_password(
143 config,
144 Wordlist::default_wordlist(code_length).choose_words(),
145 )
146 .await
147 }
148
149 pub async fn create_with_password(
172 config: AppConfig<V>,
173 #[cfg(not(feature = "entropy"))] password: &str,
174 #[cfg(feature = "entropy")] password: Password,
175 ) -> Result<Self, WormholeError> {
176 #[cfg(not(feature = "entropy"))]
177 let password = password.parse().map_err(ParseCodeError::from)?;
178
179 Self::create_with_validated_password(config, password).await
180 }
181
182 async fn create_with_validated_password(
189 config: AppConfig<V>,
190 password: Password,
191 ) -> Result<Self, WormholeError> {
192 let (mut server, welcome) =
193 RendezvousServer::connect(&config.id, &config.rendezvous_url).await?;
194 let (nameplate, mailbox) = server.allocate_claim_open().await?;
195 let code = Code::from_components(nameplate, password);
196
197 Ok(MailboxConnection {
198 config,
199 server,
200 mailbox,
201 code,
202 welcome,
203 })
204 }
205
206 pub async fn connect(
227 config: AppConfig<V>,
228 code: Code,
229 allocate: bool,
230 ) -> Result<Self, WormholeError> {
231 let (mut server, welcome) =
232 RendezvousServer::connect(&config.id, &config.rendezvous_url).await?;
233 let nameplate = code.nameplate();
234
235 if !allocate {
238 let nameplates = server.list_nameplates().await?;
239 if !nameplates.contains(&nameplate) {
240 server.shutdown(Mood::Errory).await?;
241 return Err(WormholeError::UnclaimedNameplate(nameplate));
242 }
243 }
244 let mailbox = server.claim_open(nameplate).await?;
245
246 Ok(MailboxConnection {
247 config,
248 server,
249 mailbox,
250 code,
251 welcome,
252 })
253 }
254
255 pub async fn shutdown(self, mood: Mood) -> Result<(), WormholeError> {
278 self.server
279 .shutdown(mood)
280 .await
281 .map_err(WormholeError::ServerError)
282 }
283
284 pub fn welcome(&self) -> Option<&str> {
286 self.welcome.as_deref()
287 }
288
289 pub fn mailbox(&self) -> &Mailbox {
291 &self.mailbox
292 }
293
294 pub fn code(&self) -> &Code {
296 &self.code
297 }
298}
299
300#[derive(Debug)]
304pub struct Wormhole {
305 #[allow(deprecated)]
306 server: RendezvousServer,
307 phase: u64,
308 key: key::Key<key::WormholeKey>,
309 appid: AppID,
310 #[deprecated(since = "0.7.0", note = "Use the verifier() method")]
312 pub verifier: Box<secretbox::Key>,
313 #[deprecated(since = "0.7.0", note = "Use the our_version() method")]
315 pub our_version: Box<dyn std::any::Any + Send + Sync>,
316 #[deprecated(since = "0.7.0", note = "Use the peer_version() method")]
318 pub peer_version: serde_json::Value,
319}
320
321impl Wormhole {
322 #[deprecated(
332 since = "0.7.0",
333 note = "please use 'MailboxConnection::create(..) and Wormhole::connect(mailbox_connection)' instead"
334 )]
335 #[allow(deprecated)]
336 pub async fn connect_without_code(
337 config: AppConfig<impl serde::Serialize + Send + Sync + 'static>,
338 code_length: usize,
339 ) -> Result<
340 (
341 WormholeWelcome,
342 impl std::future::Future<Output = Result<Self, WormholeError>>,
343 ),
344 WormholeError,
345 > {
346 let mailbox_connection = MailboxConnection::create(config, code_length).await?;
347 Ok((
348 WormholeWelcome {
349 welcome: mailbox_connection.welcome.clone(),
350 code: mailbox_connection.code.clone(),
351 },
352 Self::connect(mailbox_connection),
353 ))
354 }
355
356 #[deprecated(
360 since = "0.7.0",
361 note = "please use 'MailboxConnection::connect(..) and Wormhole::connect(mailbox_connection)' instead"
362 )]
363 #[allow(deprecated)]
364 pub async fn connect_with_code(
365 config: AppConfig<impl serde::Serialize + Send + Sync + 'static>,
366 code: Code,
367 ) -> Result<(WormholeWelcome, Self), WormholeError> {
368 let mailbox_connection = MailboxConnection::connect(config, code.clone(), true).await?;
369 Ok((
370 WormholeWelcome {
371 welcome: mailbox_connection.welcome.clone(),
372 code,
373 },
374 Self::connect(mailbox_connection).await?,
375 ))
376 }
377
378 pub async fn connect(
382 mailbox_connection: MailboxConnection<impl serde::Serialize + Send + Sync + 'static>,
383 ) -> Result<Self, WormholeError> {
384 let MailboxConnection {
385 config,
386 mut server,
387 mailbox: _mailbox,
388 code,
389 welcome: _welcome,
390 } = mailbox_connection;
391
392 let (pake_state, pake_msg_ser) = key::make_pake(code.as_ref(), &config.id);
394 server.send_peer_message(Phase::PAKE, pake_msg_ser).await?;
395
396 let peer_pake = key::extract_pake_msg(&server.next_peer_message_some().await?.body)?;
398 let key = pake_state
399 .finish(&peer_pake)
400 .map_err(|_| WormholeError::PakeFailed)
401 .map(|key| *secretbox::Key::from_slice(&key))?;
402
403 let mut versions = key::VersionsMessage::new();
405 versions.set_app_versions(serde_json::to_value(&config.app_version).unwrap());
406 let (version_phase, version_msg) = key::build_version_msg(server.side(), &key, &versions);
407 server.send_peer_message(version_phase, version_msg).await?;
408 let peer_version = server.next_peer_message_some().await?;
409
410 let versions: key::VersionsMessage = peer_version
412 .decrypt(&key)
413 .ok_or(WormholeError::PakeFailed)
414 .and_then(|plaintext| {
415 serde_json::from_slice(&plaintext).map_err(WormholeError::ProtocolJson)
416 })?;
417
418 let peer_version = versions.app_versions;
419
420 if server.needs_nameplate_release() {
421 server.release_nameplate().await?;
422 }
423
424 tracing::info!("Found peer on the rendezvous server.");
425
426 #[allow(deprecated)]
428 Ok(Self {
429 server,
430 appid: config.id,
431 phase: 0,
432 key: key::Key::new(key.into()),
433 verifier: Box::new(key::derive_verifier(&key)),
434 our_version: Box::new(config.app_version),
435 peer_version,
436 })
437 }
438
439 pub async fn send(&mut self, plaintext: Vec<u8>) -> Result<(), WormholeError> {
441 let phase_string = Phase::numeric(self.phase);
442 self.phase += 1;
443 let data_key = key::derive_phase_key(self.server.side(), &self.key, &phase_string);
444 let (_nonce, encrypted) = key::encrypt_data(&data_key, &plaintext);
445 self.server
446 .send_peer_message(phase_string, encrypted)
447 .await?;
448 Ok(())
449 }
450
451 pub async fn send_json<T: serde::Serialize>(
462 &mut self,
463 message: &T,
464 ) -> Result<(), WormholeError> {
465 self.send(serde_json::to_vec(message).unwrap()).await
466 }
467
468 pub async fn receive(&mut self) -> Result<Vec<u8>, WormholeError> {
470 loop {
471 let peer_message = match self.server.next_peer_message().await? {
472 Some(peer_message) => peer_message,
473 None => continue,
474 };
475 if peer_message.phase.to_num().is_none() {
476 todo!("log and ignore, for future expansion");
478 }
479
480 let decrypted_message = peer_message
482 .decrypt(&self.key)
483 .ok_or(WormholeError::Crypto)?;
484
485 return Ok(decrypted_message);
487 }
488 }
489
490 pub async fn receive_json<T>(&mut self) -> Result<Result<T, serde_json::Error>, WormholeError>
498 where
499 T: for<'a> serde::Deserialize<'a>,
500 {
501 self.receive().await.map(|data: Vec<u8>| {
502 serde_json::from_slice(&data).inspect_err(|_| {
503 tracing::error!(
504 "Received invalid data from peer: '{}'",
505 String::from_utf8_lossy(&data)
506 );
507 })
508 })
509 }
510
511 pub async fn close(self) -> Result<(), WormholeError> {
513 tracing::debug!("Closing Wormhole…");
514 self.server.shutdown(Mood::Happy).await.map_err(Into::into)
515 }
516
517 pub fn appid(&self) -> &AppID {
522 &self.appid
523 }
524
525 pub fn key(&self) -> &key::Key<key::WormholeKey> {
530 &self.key
531 }
532
533 pub fn verifier(&self) -> &secretbox::Key {
546 #[allow(deprecated)]
547 &self.verifier
548 }
549
550 pub fn our_version(&self) -> &(dyn std::any::Any + Send + Sync) {
554 #[allow(deprecated)]
555 &*self.our_version
556 }
557
558 pub fn peer_version(&self) -> &serde_json::Value {
564 #[allow(deprecated)]
565 &self.peer_version
566 }
567}
568
569#[derive(Debug, PartialEq, Copy, Clone, Deserialize, Serialize, derive_more::Display)]
574pub enum Mood {
575 #[serde(rename = "happy")]
577 Happy,
578 #[serde(rename = "lonely")]
580 Lonely,
581 #[serde(rename = "errory")]
583 Errory,
584 #[serde(rename = "scary")]
588 Scared,
589 #[serde(rename = "unwelcome")]
591 Unwelcome,
592}
593
594#[derive(PartialEq, Eq, Clone, Debug)]
606pub struct AppConfig<V> {
607 pub id: AppID,
609 pub rendezvous_url: Cow<'static, str>,
611 pub app_version: V,
613}
614
615impl<V> AppConfig<V> {
616 pub fn id(mut self, id: AppID) -> Self {
618 self.id = id;
619 self
620 }
621
622 pub fn rendezvous_url(mut self, rendezvous_url: Cow<'static, str>) -> Self {
624 self.rendezvous_url = rendezvous_url;
625 self
626 }
627}
628
629impl<V: serde::Serialize> AppConfig<V> {
630 pub fn app_version(mut self, app_version: V) -> Self {
632 self.app_version = app_version;
633 self
634 }
635}
636
637#[derive(
643 PartialEq, Eq, Clone, Debug, Deserialize, Serialize, derive_more::Display, derive_more::Deref,
644)]
645#[deref(forward)]
646pub struct AppID(
647 #[deref]
648 #[deprecated(since = "0.7.0", note = "use the AsRef<str> implementation")]
649 pub Cow<'static, str>,
650);
651
652impl AppID {
653 pub fn new(id: impl Into<Cow<'static, str>>) -> Self {
655 AppID(id.into())
656 }
657}
658
659impl From<String> for AppID {
660 fn from(s: String) -> Self {
661 Self::new(s)
662 }
663}
664
665impl AsRef<str> for AppID {
666 fn as_ref(&self) -> &str {
667 &self.0
668 }
669}
670
671#[derive(
673 PartialEq, Eq, Clone, Debug, Deserialize, Serialize, derive_more::Display, derive_more::Deref,
674)]
675#[serde(transparent)]
676#[display("MySide({})", "&*_0")]
677#[deprecated(
678 since = "0.7.0",
679 note = "This will be a private type in the future. Open an issue if you require access to protocol intrinsics in the future"
680)]
681pub struct MySide(EitherSide);
682
683impl MySide {
684 pub fn generate() -> MySide {
685 use rand::{rngs::OsRng, RngCore};
686
687 let mut bytes: [u8; 5] = [0; 5];
688 OsRng.fill_bytes(&mut bytes);
689
690 MySide(EitherSide(hex::encode(bytes)))
691 }
692
693 #[cfg(test)]
696 pub fn unchecked_from_string(s: String) -> MySide {
697 MySide(EitherSide(s))
698 }
699}
700
701#[derive(
703 PartialEq, Eq, Clone, Debug, Deserialize, Serialize, derive_more::Display, derive_more::Deref,
704)]
705#[serde(transparent)]
706#[display("TheirSide({})", "&*_0")]
707#[deprecated(
708 since = "0.7.0",
709 note = "This will be a private type in the future. Open an issue if you require access to protocol intrinsics in the future"
710)]
711pub struct TheirSide(EitherSide);
712
713impl<S: Into<String>> From<S> for TheirSide {
714 fn from(s: S) -> TheirSide {
715 TheirSide(EitherSide(s.into()))
716 }
717}
718
719#[derive(
720 PartialEq, Eq, Clone, Debug, Deserialize, Serialize, derive_more::Display, derive_more::Deref,
721)]
722#[serde(transparent)]
723#[deref(forward)]
724#[display("{}", "&*_0")]
725#[deprecated(
726 since = "0.7.0",
727 note = "This will be a private type in the future. Open an issue if you require access to protocol intrinsics in the future"
728)]
729pub struct EitherSide(pub String);
730
731impl<S: Into<String>> From<S> for EitherSide {
732 fn from(s: S) -> EitherSide {
733 EitherSide(s.into())
734 }
735}
736
737#[derive(PartialEq, Eq, Clone, Debug, Hash, Deserialize, Serialize, derive_more::Display)]
738#[serde(transparent)]
739#[deprecated(
740 since = "0.7.0",
741 note = "This will be a private type in the future. Open an issue if you require access to protocol intrinsics in the future"
742)]
743pub struct Phase(Cow<'static, str>);
744
745impl Phase {
746 pub const VERSION: Self = Phase(Cow::Borrowed("version"));
747 pub const PAKE: Self = Phase(Cow::Borrowed("pake"));
748
749 pub fn numeric(phase: u64) -> Self {
750 Phase(phase.to_string().into())
751 }
752
753 #[allow(dead_code)]
754 pub fn is_version(&self) -> bool {
755 self == &Self::VERSION
756 }
757
758 #[allow(dead_code)]
759 pub fn is_pake(&self) -> bool {
760 self == &Self::PAKE
761 }
762
763 pub fn to_num(&self) -> Option<u64> {
764 self.0.parse().ok()
765 }
766}
767
768impl AsRef<str> for Phase {
769 fn as_ref(&self) -> &str {
770 &self.0
771 }
772}
773
774#[derive(PartialEq, Eq, Clone, Debug, Deserialize, Serialize, derive_more::Display)]
775#[serde(transparent)]
776#[deprecated(
777 since = "0.7.0",
778 note = "This will be a private type in the future. Open an issue if you require access to protocol intrinsics in the future"
779)]
780pub struct Mailbox(pub String);
781
782#[derive(PartialEq, Eq, PartialOrd, Ord, Clone, Debug, Copy, derive_more::Display, Error)]
784#[display("Nameplate is not a number. Nameplates must be a number >= 1.")]
785#[non_exhaustive]
786pub struct ParseNameplateError {}
787
788#[derive(
791 PartialEq, Eq, Clone, Debug, Deserialize, Serialize, derive_more::Display, derive_more::Deref,
792)]
793#[serde(transparent)]
794#[deref(forward)]
795#[display("{}", _0)]
796#[cfg(not(feature = "entropy"))]
797pub struct Nameplate(
798 #[deprecated(since = "0.7.0", note = "use the AsRef<str> implementation")] pub String,
799);
800
801#[derive(PartialEq, Eq, Clone, Debug, Deserialize, Serialize, derive_more::Display)]
804#[serde(transparent)]
805#[display("{}", _0)]
806#[cfg(feature = "entropy")]
807pub struct Nameplate(String);
808
809#[allow(deprecated)]
810impl Nameplate {
811 #[deprecated(
815 since = "0.7.2",
816 note = "Nameplates will be required to be numbers soon. Use the [std::str::FromStr] implementation"
817 )]
818 #[cfg(not(feature = "entropy"))]
819 pub fn new(n: impl Into<String>) -> Self {
820 let nameplate = n.into();
821 if let Err(err) = Nameplate::from_str(&nameplate) {
822 tracing::error!("{err}");
823 }
824
825 #[allow(unsafe_code)]
826 unsafe {
827 Self::new_unchecked(&nameplate)
828 }
829 }
830
831 #[allow(unsafe_code)]
835 #[doc(hidden)]
836 pub unsafe fn new_unchecked(n: &str) -> Self {
837 Nameplate(n.into())
838 }
839}
840
841impl FromStr for Nameplate {
842 type Err = ParseNameplateError;
843
844 fn from_str(s: &str) -> Result<Self, Self::Err> {
845 if !s.chars().all(|c| c.is_ascii_digit()) || u128::from_str(s) == Ok(0) {
846 Err(ParseNameplateError {})
847 } else {
848 Ok(Self(s.to_string()))
849 }
850 }
851}
852
853#[allow(deprecated)]
855impl From<Nameplate> for String {
856 fn from(value: Nameplate) -> Self {
857 value.0
858 }
859}
860
861#[allow(deprecated)]
865#[cfg(not(feature = "entropy"))]
866impl From<String> for Nameplate {
867 fn from(value: String) -> Self {
868 tracing::debug!(
869 "Implementation of From<String> for Nameplate is deprecated. Use the FromStr implementation instead"
870 );
871
872 if let Err(err) = Nameplate::from_str(&value) {
873 tracing::error!("{err} This will be a hard error in the future.");
874 }
875
876 Self(value)
877 }
878}
879
880#[allow(deprecated)]
882impl AsRef<str> for Nameplate {
883 fn as_ref(&self) -> &str {
884 &self.0
885 }
886}
887
888#[derive(PartialEq, Eq, PartialOrd, Ord, Clone, Debug, Copy, derive_more::Display, Error)]
906#[non_exhaustive]
907pub enum ParsePasswordError {
908 #[display("Password too short. It is only {value} bytes, but must be at least {required}")]
910 TooShort {
911 value: usize,
913 required: usize,
915 },
916 #[display(
918 "Password too weak. It can be guessed with an average of {value} tries, but must be at least {required}"
919 )]
920 LittleEntropy {
921 value: u64,
923 required: u64,
925 },
926}
927
928#[derive(Clone, Debug, Serialize, derive_more::Display)]
931#[serde(transparent)]
932#[display("{password}")]
933pub struct Password {
934 password: String,
935 #[serde(skip)]
936 #[cfg(feature = "entropy")]
937 entropy: zxcvbn::Entropy,
938}
939
940impl PartialEq for Password {
941 fn eq(&self, other: &Self) -> bool {
942 self.password == other.password
943 }
944}
945
946impl Eq for Password {}
947
948impl Password {
949 #[allow(unsafe_code)]
955 #[doc(hidden)]
956 pub unsafe fn new_unchecked(n: impl Into<String>) -> Self {
957 let password = n.into();
958 #[cfg(feature = "entropy")]
959 let entropy = Self::calculate_entropy(&password);
960
961 Password {
962 password,
963 #[cfg(feature = "entropy")]
964 entropy,
965 }
966 }
967
968 #[cfg(feature = "entropy")]
969 fn calculate_entropy(password: &str) -> zxcvbn::Entropy {
970 static PGP_WORDLIST: std::sync::OnceLock<Vec<&str>> = std::sync::OnceLock::new();
971 let words = PGP_WORDLIST.get_or_init(|| {
972 Wordlist::default_wordlist(2)
974 .into_words()
975 .map(|s| &*s.leak())
976 .collect::<Vec<_>>()
977 });
978
979 zxcvbn::zxcvbn(password, &words[..])
980 }
981}
982
983impl From<Password> for String {
984 fn from(value: Password) -> Self {
985 value.password
986 }
987}
988
989impl AsRef<str> for Password {
990 fn as_ref(&self) -> &str {
991 &self.password
992 }
993}
994
995impl FromStr for Password {
996 type Err = ParsePasswordError;
997
998 fn from_str(password: &str) -> Result<Self, Self::Err> {
999 let password = password.to_string();
1000
1001 if password.len() < 4 {
1002 Err(ParsePasswordError::TooShort {
1003 value: password.len(),
1004 required: 4,
1005 })
1006 } else {
1007 #[cfg(feature = "entropy")]
1008 return {
1009 let entropy = Self::calculate_entropy(&password);
1010 if entropy.guesses() < 2_u64.pow(16) {
1011 return Err(ParsePasswordError::LittleEntropy {
1012 value: entropy.guesses(),
1013 required: 2_u64.pow(16),
1014 });
1015 }
1016 Ok(Self { password, entropy })
1017 };
1018
1019 #[cfg(not(feature = "entropy"))]
1020 Ok(Self { password })
1021 }
1022 }
1023}
1024
1025#[derive(PartialEq, Eq, PartialOrd, Ord, Clone, Debug, Copy, derive_more::Display, Error)]
1027#[non_exhaustive]
1028pub enum ParseCodeError {
1029 #[display("The code is empty")]
1031 Empty,
1032 #[display("A code must contain at least one '-' to separate nameplate from password")]
1034 SeparatorMissing,
1035 #[display("{_0}")]
1037 Nameplate(#[from] ParseNameplateError),
1038 #[display("{_0}")]
1040 Password(#[from] ParsePasswordError),
1041}
1042
1043#[derive(PartialEq, Eq, Clone, Debug, derive_more::Display, derive_more::Deref)]
1050#[display("{}", _0)]
1051#[cfg(not(feature = "entropy"))]
1052pub struct Code(
1053 #[deprecated(since = "0.7.0", note = "use the std::fmt::Display implementation")] pub String,
1054);
1055
1056#[derive(PartialEq, Eq, Clone, Debug, derive_more::Display)]
1063#[display("{}", _0)]
1064#[cfg(feature = "entropy")]
1065pub struct Code(String);
1066
1067#[allow(deprecated)]
1068impl Code {
1069 #[deprecated(
1073 since = "0.7.2",
1074 note = "Use [`from_components`] or the [std::str::FromStr] implementation"
1075 )]
1076 #[cfg(not(feature = "entropy"))]
1077 pub fn new(nameplate: &Nameplate, password: &str) -> Self {
1078 if let Err(err) = Password::from_str(password) {
1079 tracing::error!("{err}");
1080 }
1081
1082 #[allow(unsafe_code)]
1083 unsafe {
1084 Self::from_components(nameplate.clone(), Password::new_unchecked(password))
1085 }
1086 }
1087
1088 pub fn from_components(nameplate: Nameplate, password: Password) -> Self {
1090 Self(format!("{nameplate}-{password}"))
1091 }
1092
1093 #[deprecated(since = "0.7.2", note = "Use [Self::nameplate] and [Self::password]")]
1095 pub fn split(&self) -> (Nameplate, String) {
1096 let mut iter = self.0.splitn(2, '-');
1097 #[allow(unsafe_code)]
1098 let nameplate = unsafe { Nameplate::new_unchecked(iter.next().unwrap()) };
1099 let password = iter.next().unwrap();
1100 (nameplate, password.to_string())
1101 }
1102
1103 pub fn nameplate(&self) -> Nameplate {
1105 #[allow(unsafe_code)]
1107 unsafe {
1108 Nameplate::new_unchecked(self.0.split('-').next().unwrap())
1109 }
1110 }
1111
1112 pub fn password(&self) -> Password {
1114 #[allow(unsafe_code)]
1116 unsafe {
1117 Password::new_unchecked(self.0.splitn(2, '-').last().unwrap())
1118 }
1119 }
1120}
1121
1122#[allow(deprecated)]
1124impl From<Code> for String {
1125 fn from(value: Code) -> Self {
1126 value.0
1127 }
1128}
1129
1130#[cfg(not(feature = "entropy"))]
1134impl From<String> for Code {
1135 fn from(value: String) -> Self {
1136 tracing::debug!(
1137 "Implementation of From<String> for Code is deprecated. Use the FromStr implementation instead"
1138 );
1139
1140 if let Err(err) = Code::from_str(&value) {
1141 tracing::error!("{err} This will be a hard error in the future.");
1142 }
1143
1144 Self(value)
1145 }
1146}
1147
1148impl FromStr for Code {
1149 type Err = ParseCodeError;
1150
1151 fn from_str(s: &str) -> Result<Self, Self::Err> {
1152 match s.split_once('-') {
1153 Some((n, p)) => {
1154 let password: Password = p.parse()?;
1155 let nameplate: Nameplate = n.parse()?;
1156
1157 Ok(Self(format!("{}-{}", nameplate, password)))
1158 },
1159 None if s.is_empty() => Err(ParseCodeError::Empty),
1160 None => Err(ParseCodeError::SeparatorMissing),
1161 }
1162 }
1163}
1164
1165#[allow(deprecated)]
1167impl AsRef<str> for Code {
1168 fn as_ref(&self) -> &str {
1169 &self.0
1170 }
1171}