@@ -179,42 +179,58 @@ impl OnionMessageContents for DNSResolverMessage {
179
179
}
180
180
}
181
181
182
+ // Note that `REQUIRED_EXTRA_LEN` includes the (implicit) trailing `.`
183
+ const REQUIRED_EXTRA_LEN : usize = ".user._bitcoin-payment." . len ( ) + 1 ;
184
+
182
185
/// A struct containing the two parts of a BIP 353 Human Readable Name - the user and domain parts.
183
186
///
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
185
188
/// non-empty.
186
189
///
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.
189
195
///
190
196
/// [Homograph Attacks]: https://en.wikipedia.org/wiki/IDN_homograph_attack
191
197
#[ derive( Clone , Debug , Hash , PartialEq , Eq ) ]
192
198
pub 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 == '-' )
196
207
}
197
208
198
209
impl HumanReadableName {
199
210
/// Constructs a new [`HumanReadableName`] from the `user` and `domain` parts. See the
200
211
/// 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 , ( ) > {
202
213
// 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 ] ;
205
216
}
206
- // Note that `REQUIRED_EXTRA_LEN` includes the (now implicit) trailing `.`
207
- const REQUIRED_EXTRA_LEN : usize = ".user._bitcoin-payment." . len ( ) + 1 ;
208
217
if user. len ( ) + domain. len ( ) + REQUIRED_EXTRA_LEN > 255 {
209
218
return Err ( ( ) ) ;
210
219
}
211
220
if user. is_empty ( ) || domain. is_empty ( ) {
212
221
return Err ( ( ) ) ;
213
222
}
214
- if !Hostname :: str_is_valid_hostname ( & user) || !Hostname :: str_is_valid_hostname ( & domain) {
223
+ if !str_chars_allowed ( & user) || !str_chars_allowed ( & domain) {
215
224
return Err ( ( ) ) ;
216
225
}
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
+ } )
218
234
}
219
235
220
236
/// Constructs a new [`HumanReadableName`] from the standard encoding - `user`@`domain`.
@@ -224,49 +240,50 @@ impl HumanReadableName {
224
240
pub fn from_encoded ( encoded : & str ) -> Result < HumanReadableName , ( ) > {
225
241
if let Some ( ( user, domain) ) = encoded. strip_prefix ( '₿' ) . unwrap_or ( encoded) . split_once ( "@" )
226
242
{
227
- Self :: new ( user. to_string ( ) , domain. to_string ( ) )
243
+ Self :: new ( user, domain)
228
244
} else {
229
245
Err ( ( ) )
230
246
}
231
247
}
232
248
233
249
/// Gets the `user` part of this Human Readable Name
234
250
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" )
236
253
}
237
254
238
255
/// Gets the `domain` part of this Human Readable Name
239
256
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" )
241
260
}
242
261
}
243
262
244
263
// Serialized per the requirements for inclusion in a BOLT 12 `invoice_request`
245
264
impl Writeable for HumanReadableName {
246
265
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 ( ) )
251
270
}
252
271
}
253
272
254
273
impl Readable for HumanReadableName {
255
274
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 ] ;
258
276
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 ] ) {
262
279
Ok ( user) => user,
263
280
Err ( _) => return Err ( DecodeError :: InvalidValue ) ,
264
281
} ;
265
282
283
+ let mut domain_bytes = [ 0 ; 255 ] ;
266
284
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 ] ) {
270
287
Ok ( domain) => domain,
271
288
Err ( _) => return Err ( DecodeError :: InvalidValue ) ,
272
289
} ;
0 commit comments