1
1
//! A type for storing Human Readable Names (HRNs) which can be resolved using BIP 353 and the DNS
2
2
//! or LNURL-Pay and LN-Address.
3
3
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 ;
5
6
6
7
/// A struct containing the two parts of a BIP 353 Human Readable Name - the user and domain parts.
7
8
///
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
9
10
/// non-empty.
10
11
///
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.
13
15
///
14
16
/// This struct can also be used for LN-Address recipients.
15
17
///
16
18
/// [Homograph Attacks]: https://en.wikipedia.org/wiki/IDN_homograph_attack
17
19
#[ derive( Clone , Debug , Hash , PartialEq , Eq ) ]
18
20
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 ,
21
+ contents : [ u8 ; 255 - REQUIRED_EXTRA_LEN ] ,
22
+ user_len : u8 ,
23
+ domain_len : u8 ,
22
24
}
23
25
24
26
/// 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 {
29
31
impl HumanReadableName {
30
32
/// Constructs a new [`HumanReadableName`] from the `user` and `domain` parts. See the
31
33
/// 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 , ( ) > {
33
35
// 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 ] ;
36
38
}
37
- // Note that `REQUIRED_EXTRA_LEN` includes the (now implicit) trailing `.`
38
- const REQUIRED_EXTRA_LEN : usize = ".user._bitcoin-payment." . len ( ) + 1 ;
39
39
if user. len ( ) + domain. len ( ) + REQUIRED_EXTRA_LEN > 255 {
40
40
return Err ( ( ) ) ;
41
41
}
@@ -45,7 +45,14 @@ impl HumanReadableName {
45
45
if !str_chars_allowed ( & user) || !str_chars_allowed ( & domain) {
46
46
return Err ( ( ) ) ;
47
47
}
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
+ } )
49
56
}
50
57
51
58
/// Constructs a new [`HumanReadableName`] from the standard encoding - `user`@`domain`.
@@ -55,19 +62,22 @@ impl HumanReadableName {
55
62
pub fn from_encoded ( encoded : & str ) -> Result < HumanReadableName , ( ) > {
56
63
if let Some ( ( user, domain) ) = encoded. strip_prefix ( '₿' ) . unwrap_or ( encoded) . split_once ( "@" )
57
64
{
58
- Self :: new ( user. to_string ( ) , domain. to_string ( ) )
65
+ Self :: new ( user, domain)
59
66
} else {
60
67
Err ( ( ) )
61
68
}
62
69
}
63
70
64
71
/// Gets the `user` part of this Human Readable Name
65
72
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" )
67
75
}
68
76
69
77
/// Gets the `domain` part of this Human Readable Name
70
78
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" )
72
82
}
73
83
}
0 commit comments