2525use std:: { cmp, fmt} ;
2626use std:: marker:: PhantomData ;
2727
28+ use bitcoin:: bech32;
29+ use bitcoin:: bech32:: { Base32Len , FromBase32 , ToBase32 , u5, WriteBase32 } ;
2830use ln:: msgs:: DecodeError ;
2931use util:: ser:: { Readable , Writeable , Writer } ;
3032
@@ -51,6 +53,7 @@ mod sealed {
5153 required_features: [ $( $( $required_feature: ident ) |* , ) * ] ,
5254 optional_features: [ $( $( $optional_feature: ident ) |* , ) * ] ,
5355 } ) => {
56+ #[ derive( Eq , PartialEq ) ]
5457 pub struct $context { }
5558
5659 impl Context for $context {
@@ -318,6 +321,7 @@ mod sealed {
318321/// appears.
319322///
320323/// (C-not exported) as we map the concrete feature types below directly instead
324+ #[ derive( Eq ) ]
321325pub struct Features < T : sealed:: Context > {
322326 /// Note that, for convenience, flags is LITTLE endian (despite being big-endian on the wire)
323327 flags : Vec < u8 > ,
@@ -395,6 +399,68 @@ impl InvoiceFeatures {
395399 }
396400}
397401
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 length_u5s = ( self . flags . len ( ) * 8 + 4 ) / 5 as usize ;
407+ let mut res_u5s: Vec < u5 > = vec ! [ u5:: try_from_u8( 0 ) . unwrap( ) ; length_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 = length_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+ let curr_u5_as_u8 = res_u5s[ new_u5_idx] . to_u8 ( ) ;
414+ res_u5s[ new_u5_idx] = u5:: try_from_u8 ( curr_u5_as_u8 | ( ( shifted_chunk_u16 & 0x001f ) as u8 ) ) . unwrap ( ) ;
415+ if new_u5_idx > 0 {
416+ let curr_u5_as_u8 = res_u5s[ new_u5_idx - 1 ] . to_u8 ( ) ;
417+ res_u5s[ new_u5_idx - 1 ] = u5:: try_from_u8 ( curr_u5_as_u8 | ( ( ( shifted_chunk_u16 >> 5 ) & 0x001f ) as u8 ) ) . unwrap ( ) ;
418+ }
419+ if new_u5_idx > 1 {
420+ let curr_u5_as_u8 = res_u5s[ new_u5_idx - 2 ] . to_u8 ( ) ;
421+ res_u5s[ new_u5_idx - 2 ] = u5:: try_from_u8 ( curr_u5_as_u8 | ( ( ( shifted_chunk_u16 >> 10 ) & 0x001f ) as u8 ) ) . unwrap ( ) ;
422+ }
423+ }
424+ // Trim the highest feature bits.
425+ while !res_u5s. is_empty ( ) && res_u5s[ 0 ] == u5:: try_from_u8 ( 0 ) . unwrap ( ) {
426+ res_u5s. remove ( 0 ) ;
427+ }
428+ writer. write ( & res_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 length_bytes = ( field_data. len ( ) * 5 + 7 ) / 8 as usize ;
445+ let mut res_bytes: Vec < u8 > = vec ! [ 0 ; length_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 != length_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+
398464impl < T : sealed:: Context > Features < T > {
399465 /// Create a blank Features with no features set
400466 pub fn empty ( ) -> Self {
@@ -427,8 +493,8 @@ impl<T: sealed::Context> Features<T> {
427493 Features :: < C > { flags, mark : PhantomData , }
428494 }
429495
430- # [ cfg ( test ) ]
431- /// 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 byte order from
497+ /// most on-the-wire encodings .
432498 pub fn from_le_bytes ( flags : Vec < u8 > ) -> Features < T > {
433499 Features {
434500 flags,
@@ -628,6 +694,7 @@ impl<T: sealed::Context> Readable for Features<T> {
628694#[ cfg( test) ]
629695mod tests {
630696 use super :: { ChannelFeatures , InitFeatures , InvoiceFeatures , NodeFeatures } ;
697+ use bitcoin:: bech32:: { Base32Len , FromBase32 , ToBase32 , u5} ;
631698
632699 #[ test]
633700 fn sanity_test_known_features ( ) {
@@ -742,4 +809,35 @@ mod tests {
742809 assert ! ( features. requires_payment_secret( ) ) ;
743810 assert ! ( features. supports_payment_secret( ) ) ;
744811 }
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+ }
745843}
0 commit comments