33
44use alloc:: string:: { String , ToString } ;
55
6+ // Note that `REQUIRED_EXTRA_LEN` includes the (implicit) trailing `.`
7+ const REQUIRED_EXTRA_LEN : usize = ".user._bitcoin-payment." . len ( ) + 1 ;
8+
69/// A struct containing the two parts of a BIP 353 Human Readable Name - the user and domain parts.
710///
8- /// The `user` and `domain` parts, together, cannot exceed 232 bytes in length, and both must be
11+ /// The `user` and `domain` parts, together, cannot exceed 231 bytes in length, and both must be
912/// non-empty.
1013///
11- /// To protect against [Homograph Attacks], both parts of a Human Readable Name must be plain
12- /// ASCII.
14+ /// If you intend to handle non-ASCII `user` or `domain` parts, you must handle [Homograph Attacks]
15+ /// and do punycode en-/de-coding yourself. This struc will always handle only plain ASCII `user`
16+ /// and `domain` parts.
1317///
1418/// This struct can also be used for LN-Address recipients.
1519///
1620/// [Homograph Attacks]: https://en.wikipedia.org/wiki/IDN_homograph_attack
1721#[ derive( Clone , Debug , Hash , PartialEq , Eq ) ]
1822pub 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 ,
23+ contents : [ u8 ; 255 - REQUIRED_EXTRA_LEN ] ,
24+ user_len : u8 ,
25+ domain_len : u8 ,
2226}
2327
2428/// Check if the chars in `s` are allowed to be included in a hostname.
@@ -29,13 +33,11 @@ pub(crate) fn str_chars_allowed(s: &str) -> bool {
2933impl HumanReadableName {
3034 /// Constructs a new [`HumanReadableName`] from the `user` and `domain` parts. See the
3135 /// struct-level documentation for more on the requirements on each.
32- pub fn new ( user : String , mut domain : String ) -> Result < HumanReadableName , ( ) > {
36+ pub fn new ( user : & str , mut domain : & str ) -> Result < HumanReadableName , ( ) > {
3337 // First normalize domain and remove the optional trailing `.`
34- if domain. ends_with ( "." ) {
35- domain. pop ( ) ;
38+ if domain. ends_with ( '.' ) {
39+ domain = & domain [ ..domain . len ( ) - 1 ] ;
3640 }
37- // Note that `REQUIRED_EXTRA_LEN` includes the (now implicit) trailing `.`
38- const REQUIRED_EXTRA_LEN : usize = ".user._bitcoin-payment." . len ( ) + 1 ;
3941 if user. len ( ) + domain. len ( ) + REQUIRED_EXTRA_LEN > 255 {
4042 return Err ( ( ) ) ;
4143 }
@@ -45,7 +47,14 @@ impl HumanReadableName {
4547 if !str_chars_allowed ( & user) || !str_chars_allowed ( & domain) {
4648 return Err ( ( ) ) ;
4749 }
48- Ok ( HumanReadableName { user, domain } )
50+ let mut contents = [ 0 ; 255 - REQUIRED_EXTRA_LEN ] ;
51+ contents[ ..user. len ( ) ] . copy_from_slice ( user. as_bytes ( ) ) ;
52+ contents[ user. len ( ) ..user. len ( ) + domain. len ( ) ] . copy_from_slice ( domain. as_bytes ( ) ) ;
53+ Ok ( HumanReadableName {
54+ contents,
55+ user_len : user. len ( ) as u8 ,
56+ domain_len : domain. len ( ) as u8 ,
57+ } )
4958 }
5059
5160 /// Constructs a new [`HumanReadableName`] from the standard encoding - `user`@`domain`.
@@ -55,19 +64,22 @@ impl HumanReadableName {
5564 pub fn from_encoded ( encoded : & str ) -> Result < HumanReadableName , ( ) > {
5665 if let Some ( ( user, domain) ) = encoded. strip_prefix ( '₿' ) . unwrap_or ( encoded) . split_once ( "@" )
5766 {
58- Self :: new ( user. to_string ( ) , domain. to_string ( ) )
67+ Self :: new ( user, domain)
5968 } else {
6069 Err ( ( ) )
6170 }
6271 }
6372
6473 /// Gets the `user` part of this Human Readable Name
6574 pub fn user ( & self ) -> & str {
66- & self . user
75+ let bytes = & self . contents [ ..self . user_len as usize ] ;
76+ str:: from_utf8 ( bytes) . expect ( "Checked in constructor" )
6777 }
6878
6979 /// Gets the `domain` part of this Human Readable Name
7080 pub fn domain ( & self ) -> & str {
71- & self . domain
81+ let user_len = self . user_len as usize ;
82+ let bytes = & self . contents [ user_len..user_len + self . domain_len as usize ] ;
83+ str:: from_utf8 ( bytes) . expect ( "Checked in constructor" )
7284 }
7385}
0 commit comments