25
25
use std:: { cmp, fmt} ;
26
26
use std:: marker:: PhantomData ;
27
27
28
+ use bitcoin:: bech32;
29
+ use bitcoin:: bech32:: { Base32Len , FromBase32 , ToBase32 , u5, WriteBase32 } ;
28
30
use ln:: msgs:: DecodeError ;
29
31
use util:: ser:: { Readable , Writeable , Writer } ;
30
32
@@ -51,6 +53,7 @@ mod sealed {
51
53
required_features: [ $( $( $required_feature: ident ) |* , ) * ] ,
52
54
optional_features: [ $( $( $optional_feature: ident ) |* , ) * ] ,
53
55
} ) => {
56
+ #[ derive( Eq , PartialEq ) ]
54
57
pub struct $context { }
55
58
56
59
impl Context for $context {
@@ -318,6 +321,7 @@ mod sealed {
318
321
/// appears.
319
322
///
320
323
/// (C-not exported) as we map the concrete feature types below directly instead
324
+ #[ derive( Eq ) ]
321
325
pub struct Features < T : sealed:: Context > {
322
326
/// Note that, for convenience, flags is LITTLE endian (despite being big-endian on the wire)
323
327
flags : Vec < u8 > ,
@@ -395,6 +399,68 @@ impl InvoiceFeatures {
395
399
}
396
400
}
397
401
402
+ impl ToBase32 for InvoiceFeatures {
403
+ fn write_base32 < W : WriteBase32 > ( & self , writer : & mut W ) -> Result < ( ) , <W as WriteBase32 >:: Err > {
404
+ // Explanation for the "4": the normal way to round up when dividing is to add the divisor
405
+ // minus one before dividing
406
+ let total_u5s = ( self . flags . len ( ) * 8 + 4 ) / 5 as usize ;
407
+ let mut res_u5s: Vec < u8 > = vec ! [ 0 ; total_u5s] ;
408
+ for ( byte_idx, byte) in self . flags . iter ( ) . enumerate ( ) {
409
+ let bit_pos_from_left_0_indexed = byte_idx * 8 ;
410
+ let new_u5_idx = total_u5s - ( bit_pos_from_left_0_indexed / 5 ) as usize - 1 ;
411
+ let new_bit_pos = bit_pos_from_left_0_indexed % 5 ;
412
+ let shifted_chunk_u16 = ( * byte as u16 ) << new_bit_pos;
413
+ res_u5s[ new_u5_idx] |= ( shifted_chunk_u16 & 0x001f ) as u8 ;
414
+ if new_u5_idx > 0 {
415
+ res_u5s[ new_u5_idx - 1 ] |= ( ( shifted_chunk_u16 >> 5 ) & 0x001f ) as u8 ;
416
+ } if new_u5_idx > 1 {
417
+ res_u5s[ new_u5_idx - 2 ] |= ( ( shifted_chunk_u16 >> 10 ) & 0x001f ) as u8 ;
418
+ }
419
+ }
420
+ // Trim the highest feature bits.
421
+ while !res_u5s. is_empty ( ) && res_u5s[ 0 ] == 0 {
422
+ res_u5s. remove ( 0 ) ;
423
+ }
424
+ let mut final_u5s = vec ! [ ] ;
425
+ for chunk in res_u5s. iter ( ) {
426
+ final_u5s. push ( u5:: try_from_u8 ( * chunk) . unwrap ( ) ) ;
427
+ }
428
+ writer. write ( & final_u5s)
429
+ }
430
+ }
431
+
432
+ impl Base32Len for InvoiceFeatures {
433
+ fn base32_len ( & self ) -> usize {
434
+ self . to_base32 ( ) . len ( )
435
+ }
436
+ }
437
+
438
+ impl FromBase32 for InvoiceFeatures {
439
+ type Err = bech32:: Error ;
440
+
441
+ fn from_base32 ( field_data : & [ u5 ] ) -> Result < InvoiceFeatures , bech32:: Error > {
442
+ // Explanation for the "7": the normal way to round up when dividing is to add the divisor
443
+ // minus one before dividing
444
+ let total_bytes = ( field_data. len ( ) * 5 + 7 ) / 8 as usize ;
445
+ let mut res_bytes: Vec < u8 > = vec ! [ 0 ; total_bytes] ;
446
+ for ( u5_idx, chunk) in field_data. iter ( ) . enumerate ( ) {
447
+ let bit_pos_from_right_0_indexed = ( field_data. len ( ) - u5_idx - 1 ) * 5 ;
448
+ let new_byte_idx = ( bit_pos_from_right_0_indexed / 8 ) as usize ;
449
+ let new_bit_pos = bit_pos_from_right_0_indexed % 8 ;
450
+ let chunk_u16 = chunk. to_u8 ( ) as u16 ;
451
+ res_bytes[ new_byte_idx] |= ( ( chunk_u16 << new_bit_pos) & 0xff ) as u8 ;
452
+ if new_byte_idx != total_bytes - 1 {
453
+ res_bytes[ new_byte_idx + 1 ] |= ( ( chunk_u16 >> ( 8 -new_bit_pos) ) & 0xff ) as u8 ;
454
+ }
455
+ }
456
+ // Trim the highest feature bits.
457
+ while !res_bytes. is_empty ( ) && res_bytes[ res_bytes. len ( ) - 1 ] == 0 {
458
+ res_bytes. pop ( ) ;
459
+ }
460
+ Ok ( InvoiceFeatures :: from_le_bytes ( res_bytes) )
461
+ }
462
+ }
463
+
398
464
impl < T : sealed:: Context > Features < T > {
399
465
/// Create a blank Features with no features set
400
466
pub fn empty ( ) -> Self {
@@ -427,7 +493,8 @@ impl<T: sealed::Context> Features<T> {
427
493
Features :: < C > { flags, mark : PhantomData , }
428
494
}
429
495
430
- /// Create a Features given a set of flags, in LE.
496
+ /// Create a Features given a set of flags, in little-endian. This is in reverse bit order from
497
+ /// the most on-the-wire encodings.
431
498
pub fn from_le_bytes ( flags : Vec < u8 > ) -> Features < T > {
432
499
Features {
433
500
flags,
@@ -627,6 +694,7 @@ impl<T: sealed::Context> Readable for Features<T> {
627
694
#[ cfg( test) ]
628
695
mod tests {
629
696
use super :: { ChannelFeatures , InitFeatures , InvoiceFeatures , NodeFeatures } ;
697
+ use bitcoin:: bech32:: { Base32Len , FromBase32 , ToBase32 , u5} ;
630
698
631
699
#[ test]
632
700
fn sanity_test_known_features ( ) {
@@ -741,4 +809,35 @@ mod tests {
741
809
assert ! ( features. requires_payment_secret( ) ) ;
742
810
assert ! ( features. supports_payment_secret( ) ) ;
743
811
}
812
+
813
+ #[ test]
814
+ fn invoice_features_encoding ( ) {
815
+ let features_as_u5s = vec ! [
816
+ u5:: try_from_u8( 6 ) . unwrap( ) ,
817
+ u5:: try_from_u8( 10 ) . unwrap( ) ,
818
+ u5:: try_from_u8( 25 ) . unwrap( ) ,
819
+ u5:: try_from_u8( 1 ) . unwrap( ) ,
820
+ u5:: try_from_u8( 10 ) . unwrap( ) ,
821
+ u5:: try_from_u8( 0 ) . unwrap( ) ,
822
+ u5:: try_from_u8( 20 ) . unwrap( ) ,
823
+ u5:: try_from_u8( 2 ) . unwrap( ) ,
824
+ u5:: try_from_u8( 0 ) . unwrap( ) ,
825
+ u5:: try_from_u8( 6 ) . unwrap( ) ,
826
+ u5:: try_from_u8( 0 ) . unwrap( ) ,
827
+ u5:: try_from_u8( 16 ) . unwrap( ) ,
828
+ u5:: try_from_u8( 1 ) . unwrap( ) ,
829
+ ] ;
830
+ let features = InvoiceFeatures :: from_le_bytes ( vec ! [ 1 , 2 , 3 , 4 , 5 , 42 , 100 , 101 ] ) ;
831
+
832
+ // Test length calculation.
833
+ assert_eq ! ( features. base32_len( ) , 13 ) ;
834
+
835
+ // Test serialization.
836
+ let features_serialized = features. to_base32 ( ) ;
837
+ assert_eq ! ( features_as_u5s, features_serialized) ;
838
+
839
+ // Test deserialization.
840
+ let features_deserialized = InvoiceFeatures :: from_base32 ( & features_as_u5s) . unwrap ( ) ;
841
+ assert_eq ! ( features, features_deserialized) ;
842
+ }
744
843
}
0 commit comments