11//! A type for storing Human Readable Names (HRNs) which can be resolved using BIP 353 and the DNS
22//! or LNURL-Pay and LN-Address.
33
4- use alloc:: string:: { String , ToString } ;
4+ // Note that `REQUIRED_EXTRA_LEN` includes the (implicit) trailing `.`
5+ const REQUIRED_EXTRA_LEN : usize = ".user._bitcoin-payment." . len ( ) + 1 ;
56
67/// A struct containing the two parts of a BIP 353 Human Readable Name - the user and domain parts.
78///
8- /// The `user` and `domain` parts, together, cannot exceed 232 bytes in length, and both must be
9+ /// The `user` and `domain` parts, together, cannot exceed 231 bytes in length, and both must be
910/// non-empty.
1011///
11- /// To protect against [Homograph Attacks], both parts of a Human Readable Name must be plain
12- /// ASCII.
12+ /// If you intend to handle non-ASCII `user` or `domain` parts, you must handle [Homograph Attacks]
13+ /// and do punycode en-/de-coding yourself. This struc will always handle only plain ASCII `user`
14+ /// and `domain` parts.
1315///
1416/// This struct can also be used for LN-Address recipients.
1517///
1618/// [Homograph Attacks]: https://en.wikipedia.org/wiki/IDN_homograph_attack
1719#[ derive( Clone , Debug , Hash , PartialEq , Eq ) ]
1820pub struct HumanReadableName {
19- // TODO Remove the heap allocations given the whole data can't be more than 256 bytes.
20- user : String ,
21- domain : String ,
21+ contents : [ u8 ; 255 - REQUIRED_EXTRA_LEN ] ,
22+ user_len : u8 ,
23+ domain_len : u8 ,
2224}
2325
2426/// Check if the chars in `s` are allowed to be included in a hostname.
@@ -29,13 +31,11 @@ pub(crate) fn str_chars_allowed(s: &str) -> bool {
2931impl HumanReadableName {
3032 /// Constructs a new [`HumanReadableName`] from the `user` and `domain` parts. See the
3133 /// struct-level documentation for more on the requirements on each.
32- pub fn new ( user : String , mut domain : String ) -> Result < HumanReadableName , ( ) > {
34+ pub fn new ( user : & str , mut domain : & str ) -> Result < HumanReadableName , ( ) > {
3335 // First normalize domain and remove the optional trailing `.`
34- if domain. ends_with ( "." ) {
35- domain. pop ( ) ;
36+ if domain. ends_with ( '.' ) {
37+ domain = & domain [ ..domain . len ( ) - 1 ] ;
3638 }
37- // Note that `REQUIRED_EXTRA_LEN` includes the (now implicit) trailing `.`
38- const REQUIRED_EXTRA_LEN : usize = ".user._bitcoin-payment." . len ( ) + 1 ;
3939 if user. len ( ) + domain. len ( ) + REQUIRED_EXTRA_LEN > 255 {
4040 return Err ( ( ) ) ;
4141 }
@@ -45,7 +45,14 @@ impl HumanReadableName {
4545 if !str_chars_allowed ( & user) || !str_chars_allowed ( & domain) {
4646 return Err ( ( ) ) ;
4747 }
48- Ok ( HumanReadableName { user, domain } )
48+ let mut contents = [ 0 ; 255 - REQUIRED_EXTRA_LEN ] ;
49+ contents[ ..user. len ( ) ] . copy_from_slice ( user. as_bytes ( ) ) ;
50+ contents[ user. len ( ) ..user. len ( ) + domain. len ( ) ] . copy_from_slice ( domain. as_bytes ( ) ) ;
51+ Ok ( HumanReadableName {
52+ contents,
53+ user_len : user. len ( ) as u8 ,
54+ domain_len : domain. len ( ) as u8 ,
55+ } )
4956 }
5057
5158 /// Constructs a new [`HumanReadableName`] from the standard encoding - `user`@`domain`.
@@ -55,19 +62,22 @@ impl HumanReadableName {
5562 pub fn from_encoded ( encoded : & str ) -> Result < HumanReadableName , ( ) > {
5663 if let Some ( ( user, domain) ) = encoded. strip_prefix ( '₿' ) . unwrap_or ( encoded) . split_once ( "@" )
5764 {
58- Self :: new ( user. to_string ( ) , domain. to_string ( ) )
65+ Self :: new ( user, domain)
5966 } else {
6067 Err ( ( ) )
6168 }
6269 }
6370
6471 /// Gets the `user` part of this Human Readable Name
6572 pub fn user ( & self ) -> & str {
66- & self . user
73+ let bytes = & self . contents [ ..self . user_len as usize ] ;
74+ core:: str:: from_utf8 ( bytes) . expect ( "Checked in constructor" )
6775 }
6876
6977 /// Gets the `domain` part of this Human Readable Name
7078 pub fn domain ( & self ) -> & str {
71- & self . domain
79+ let user_len = self . user_len as usize ;
80+ let bytes = & self . contents [ user_len..user_len + self . domain_len as usize ] ;
81+ core:: str:: from_utf8 ( bytes) . expect ( "Checked in constructor" )
7282 }
7383}
0 commit comments