3
3
4
4
use alloc:: string:: { String , ToString } ;
5
5
6
+ // Note that `REQUIRED_EXTRA_LEN` includes the (implicit) trailing `.`
7
+ const REQUIRED_EXTRA_LEN : usize = ".user._bitcoin-payment." . len ( ) + 1 ;
8
+
6
9
/// A struct containing the two parts of a BIP 353 Human Readable Name - the user and domain parts.
7
10
///
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
9
12
/// non-empty.
10
13
///
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.
13
17
///
14
18
/// This struct can also be used for LN-Address recipients.
15
19
///
16
20
/// [Homograph Attacks]: https://en.wikipedia.org/wiki/IDN_homograph_attack
17
21
#[ derive( Clone , Debug , Hash , PartialEq , Eq ) ]
18
22
pub 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 ,
22
26
}
23
27
24
28
/// 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 {
29
33
impl HumanReadableName {
30
34
/// Constructs a new [`HumanReadableName`] from the `user` and `domain` parts. See the
31
35
/// 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 , ( ) > {
33
37
// 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 ] ;
36
40
}
37
- // Note that `REQUIRED_EXTRA_LEN` includes the (now implicit) trailing `.`
38
- const REQUIRED_EXTRA_LEN : usize = ".user._bitcoin-payment." . len ( ) + 1 ;
39
41
if user. len ( ) + domain. len ( ) + REQUIRED_EXTRA_LEN > 255 {
40
42
return Err ( ( ) ) ;
41
43
}
@@ -45,7 +47,14 @@ impl HumanReadableName {
45
47
if !str_chars_allowed ( & user) || !str_chars_allowed ( & domain) {
46
48
return Err ( ( ) ) ;
47
49
}
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
+ } )
49
58
}
50
59
51
60
/// Constructs a new [`HumanReadableName`] from the standard encoding - `user`@`domain`.
@@ -55,19 +64,22 @@ impl HumanReadableName {
55
64
pub fn from_encoded ( encoded : & str ) -> Result < HumanReadableName , ( ) > {
56
65
if let Some ( ( user, domain) ) = encoded. strip_prefix ( '₿' ) . unwrap_or ( encoded) . split_once ( "@" )
57
66
{
58
- Self :: new ( user. to_string ( ) , domain. to_string ( ) )
67
+ Self :: new ( user, domain)
59
68
} else {
60
69
Err ( ( ) )
61
70
}
62
71
}
63
72
64
73
/// Gets the `user` part of this Human Readable Name
65
74
pub fn user ( & self ) -> & str {
66
- & self . user
75
+ let bytes = & self . contents [ ..self . user_len as usize ] ;
76
+ core:: str:: from_utf8 ( bytes) . expect ( "Checked in constructor" )
67
77
}
68
78
69
79
/// Gets the `domain` part of this Human Readable Name
70
80
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
+ core:: str:: from_utf8 ( bytes) . expect ( "Checked in constructor" )
72
84
}
73
85
}
0 commit comments