@@ -179,42 +179,58 @@ impl OnionMessageContents for DNSResolverMessage {
179179 }
180180}
181181
182+ // Note that `REQUIRED_EXTRA_LEN` includes the (implicit) trailing `.`
183+ const REQUIRED_EXTRA_LEN : usize = ".user._bitcoin-payment." . len ( ) + 1 ;
184+
182185/// A struct containing the two parts of a BIP 353 Human Readable Name - the user and domain parts.
183186///
184- /// The `user` and `domain` parts, together, cannot exceed 232 bytes in length, and both must be
187+ /// The `user` and `domain` parts, together, cannot exceed 231 bytes in length, and both must be
185188/// non-empty.
186189///
187- /// To protect against [Homograph Attacks], both parts of a Human Readable Name must be plain
188- /// ASCII.
190+ /// If you intend to handle non-ASCII `user` or `domain` parts, you must handle [Homograph Attacks]
191+ /// and do punycode en-/de-coding yourself. This struc will always handle only plain ASCII `user`
192+ /// and `domain` parts.
193+ ///
194+ /// This struct can also be used for LN-Address recipients.
189195///
190196/// [Homograph Attacks]: https://en.wikipedia.org/wiki/IDN_homograph_attack
191197#[ derive( Clone , Debug , Hash , PartialEq , Eq ) ]
192198pub struct HumanReadableName {
193- // TODO Remove the heap allocations given the whole data can't be more than 256 bytes.
194- user : String ,
195- domain : String ,
199+ contents : [ u8 ; 255 - REQUIRED_EXTRA_LEN ] ,
200+ user_len : u8 ,
201+ domain_len : u8 ,
202+ }
203+
204+ /// Check if the chars in `s` are allowed to be included in a hostname.
205+ pub ( crate ) fn str_chars_allowed ( s : & str ) -> bool {
206+ s. chars ( ) . all ( |c| c. is_ascii_alphanumeric ( ) || c == '.' || c == '_' || c == '-' )
196207}
197208
198209impl HumanReadableName {
199210 /// Constructs a new [`HumanReadableName`] from the `user` and `domain` parts. See the
200211 /// struct-level documentation for more on the requirements on each.
201- pub fn new ( user : String , mut domain : String ) -> Result < HumanReadableName , ( ) > {
212+ pub fn new ( user : & str , mut domain : & str ) -> Result < HumanReadableName , ( ) > {
202213 // First normalize domain and remove the optional trailing `.`
203- if domain. ends_with ( "." ) {
204- domain. pop ( ) ;
214+ if domain. ends_with ( '.' ) {
215+ domain = & domain [ ..domain . len ( ) - 1 ] ;
205216 }
206- // Note that `REQUIRED_EXTRA_LEN` includes the (now implicit) trailing `.`
207- const REQUIRED_EXTRA_LEN : usize = ".user._bitcoin-payment." . len ( ) + 1 ;
208217 if user. len ( ) + domain. len ( ) + REQUIRED_EXTRA_LEN > 255 {
209218 return Err ( ( ) ) ;
210219 }
211220 if user. is_empty ( ) || domain. is_empty ( ) {
212221 return Err ( ( ) ) ;
213222 }
214- if !Hostname :: str_is_valid_hostname ( & user) || !Hostname :: str_is_valid_hostname ( & domain) {
223+ if !str_chars_allowed ( & user) || !str_chars_allowed ( & domain) {
215224 return Err ( ( ) ) ;
216225 }
217- Ok ( HumanReadableName { user, domain } )
226+ let mut contents = [ 0 ; 255 - REQUIRED_EXTRA_LEN ] ;
227+ contents[ ..user. len ( ) ] . copy_from_slice ( user. as_bytes ( ) ) ;
228+ contents[ user. len ( ) ..user. len ( ) + domain. len ( ) ] . copy_from_slice ( domain. as_bytes ( ) ) ;
229+ Ok ( HumanReadableName {
230+ contents,
231+ user_len : user. len ( ) as u8 ,
232+ domain_len : domain. len ( ) as u8 ,
233+ } )
218234 }
219235
220236 /// Constructs a new [`HumanReadableName`] from the standard encoding - `user`@`domain`.
@@ -224,49 +240,50 @@ impl HumanReadableName {
224240 pub fn from_encoded ( encoded : & str ) -> Result < HumanReadableName , ( ) > {
225241 if let Some ( ( user, domain) ) = encoded. strip_prefix ( '₿' ) . unwrap_or ( encoded) . split_once ( "@" )
226242 {
227- Self :: new ( user. to_string ( ) , domain. to_string ( ) )
243+ Self :: new ( user, domain)
228244 } else {
229245 Err ( ( ) )
230246 }
231247 }
232248
233249 /// Gets the `user` part of this Human Readable Name
234250 pub fn user ( & self ) -> & str {
235- & self . user
251+ let bytes = & self . contents [ ..self . user_len as usize ] ;
252+ core:: str:: from_utf8 ( bytes) . expect ( "Checked in constructor" )
236253 }
237254
238255 /// Gets the `domain` part of this Human Readable Name
239256 pub fn domain ( & self ) -> & str {
240- & self . domain
257+ let user_len = self . user_len as usize ;
258+ let bytes = & self . contents [ user_len..user_len + self . domain_len as usize ] ;
259+ core:: str:: from_utf8 ( bytes) . expect ( "Checked in constructor" )
241260 }
242261}
243262
244263// Serialized per the requirements for inclusion in a BOLT 12 `invoice_request`
245264impl Writeable for HumanReadableName {
246265 fn write < W : Writer > ( & self , writer : & mut W ) -> Result < ( ) , io:: Error > {
247- ( self . user . len ( ) as u8 ) . write ( writer) ?;
248- writer. write_all ( & self . user . as_bytes ( ) ) ?;
249- ( self . domain . len ( ) as u8 ) . write ( writer) ?;
250- writer. write_all ( & self . domain . as_bytes ( ) )
266+ ( self . user ( ) . len ( ) as u8 ) . write ( writer) ?;
267+ writer. write_all ( & self . user ( ) . as_bytes ( ) ) ?;
268+ ( self . domain ( ) . len ( ) as u8 ) . write ( writer) ?;
269+ writer. write_all ( & self . domain ( ) . as_bytes ( ) )
251270 }
252271}
253272
254273impl Readable for HumanReadableName {
255274 fn read < R : io:: Read > ( reader : & mut R ) -> Result < Self , DecodeError > {
256- let mut read_bytes = [ 0 ; 255 ] ;
257-
275+ let mut user_bytes = [ 0 ; 255 ] ;
258276 let user_len: u8 = Readable :: read ( reader) ?;
259- reader. read_exact ( & mut read_bytes[ ..user_len as usize ] ) ?;
260- let user_bytes: Vec < u8 > = read_bytes[ ..user_len as usize ] . into ( ) ;
261- let user = match String :: from_utf8 ( user_bytes) {
277+ reader. read_exact ( & mut user_bytes[ ..user_len as usize ] ) ?;
278+ let user = match core:: str:: from_utf8 ( & user_bytes[ ..user_len as usize ] ) {
262279 Ok ( user) => user,
263280 Err ( _) => return Err ( DecodeError :: InvalidValue ) ,
264281 } ;
265282
283+ let mut domain_bytes = [ 0 ; 255 ] ;
266284 let domain_len: u8 = Readable :: read ( reader) ?;
267- reader. read_exact ( & mut read_bytes[ ..domain_len as usize ] ) ?;
268- let domain_bytes: Vec < u8 > = read_bytes[ ..domain_len as usize ] . into ( ) ;
269- let domain = match String :: from_utf8 ( domain_bytes) {
285+ reader. read_exact ( & mut domain_bytes[ ..domain_len as usize ] ) ?;
286+ let domain = match core:: str:: from_utf8 ( & domain_bytes[ ..domain_len as usize ] ) {
270287 Ok ( domain) => domain,
271288 Err ( _) => return Err ( DecodeError :: InvalidValue ) ,
272289 } ;
0 commit comments