99
1010//! Data structures and encoding for `offer` messages.
1111
12- use bitcoin:: bech32;
13- use bitcoin:: bech32:: FromBase32 ;
1412use bitcoin:: blockdata:: constants:: genesis_block;
1513use bitcoin:: hash_types:: BlockHash ;
1614use bitcoin:: network:: constants:: Network ;
@@ -22,8 +20,8 @@ use core::str::FromStr;
2220use core:: time:: Duration ;
2321use io;
2422use ln:: features:: OfferFeatures ;
25- use ln :: msgs :: DecodeError ;
26- use util:: ser:: { Readable , WithLength , Writeable , Writer } ;
23+ use offers :: parse :: { Bech32Encode , ParseError , SemanticError } ;
24+ use util:: ser:: { WithLength , Writeable , Writer } ;
2725
2826use prelude:: * ;
2927
@@ -275,6 +273,7 @@ impl Offer {
275273 self . contents . send_invoice . as_ref ( )
276274 }
277275
276+ #[ cfg( test) ]
278277 fn as_bytes ( & self ) -> & [ u8 ] {
279278 & self . bytes
280279 }
@@ -292,6 +291,12 @@ impl Offer {
292291 }
293292}
294293
294+ impl AsRef < [ u8 ] > for Offer {
295+ fn as_ref ( & self ) -> & [ u8 ] {
296+ & self . bytes
297+ }
298+ }
299+
295300impl OfferContents {
296301 pub fn quantity_min ( & self ) -> u64 {
297302 self . quantity_min . unwrap_or ( 1 )
@@ -394,67 +399,17 @@ impl_writeable!(OnionMessagePath, { node_id, encrypted_recipient_data });
394399
395400type Empty = ( ) ;
396401
397- /// An `offer` parsed from a bech32-encoded string as a TLV stream and the corresponding bytes. The
398- /// latter is used to reflect fields in an `invoice_request`, some of which may be unknown.
399- struct ParsedOffer ( OfferTlvStream , Vec < u8 > ) ;
400-
401- /// Error when parsing a bech32 encoded message using [`str::parse`].
402- #[ derive( Debug , PartialEq ) ]
403- pub enum ParseError {
404- /// The bech32 encoding does not conform to the BOLT 12 requirements for continuing messages
405- /// across multiple parts (i.e., '+' followed by whitespace).
406- InvalidContinuation ,
407- /// The bech32 encoding's human-readable part does not match what was expected for the message
408- /// being parsed.
409- InvalidBech32Hrp ,
410- /// The string could not be bech32 decoded.
411- Bech32 ( bech32:: Error ) ,
412- /// The bech32 decoded string could not be decoded as the expected message type.
413- Decode ( DecodeError ) ,
414- /// The parsed message has invalid semantics.
415- InvalidSemantics ( SemanticError ) ,
416- }
417-
418- /// Error when interpreting a TLV stream as a specific type.
419- #[ derive( Debug , PartialEq ) ]
420- pub enum SemanticError {
421- /// The provided block hash does not correspond to a supported chain.
422- UnsupportedChain ,
423- /// A currency was provided without an amount.
424- UnexpectedCurrency ,
425- /// A required description was not provided.
426- MissingDescription ,
427- /// Either a node id or a blinded path was not provided.
428- MissingDestination ,
429- /// An empty set of blinded paths was provided.
430- MissingPaths ,
431- /// A quantity representing an empty range or that was outside of a valid range was provided.
432- InvalidQuantity ,
433- }
402+ impl Bech32Encode for Offer {
403+ type TlvStream = OfferTlvStream ;
434404
435- impl From < bech32:: Error > for ParseError {
436- fn from ( error : bech32:: Error ) -> Self {
437- Self :: Bech32 ( error)
438- }
439- }
440-
441- impl From < DecodeError > for ParseError {
442- fn from ( error : DecodeError ) -> Self {
443- Self :: Decode ( error)
444- }
445- }
446-
447- impl From < SemanticError > for ParseError {
448- fn from ( error : SemanticError ) -> Self {
449- Self :: InvalidSemantics ( error)
450- }
405+ const BECH32_HRP : & ' static str = "lno" ;
451406}
452407
453408impl FromStr for Offer {
454409 type Err = ParseError ;
455410
456411 fn from_str ( s : & str ) -> Result < Self , <Self as FromStr >:: Err > {
457- let ParsedOffer ( tlv_stream, bytes) = ParsedOffer :: from_str ( s) ?;
412+ let ( tlv_stream, bytes) = Offer :: from_bech32_str ( s) ?;
458413 let contents = OfferContents :: try_from ( tlv_stream) ?;
459414 Ok ( Offer { bytes, contents } )
460415 }
@@ -539,39 +494,9 @@ impl TryFrom<OfferTlvStream> for OfferContents {
539494 }
540495}
541496
542- const OFFER_BECH32_HRP : & str = "lno" ;
543-
544- impl FromStr for ParsedOffer {
545- type Err = ParseError ;
546-
547- fn from_str ( s : & str ) -> Result < Self , <Self as FromStr >:: Err > {
548- // Offer encoding may be split by '+' followed by optional whitespace.
549- for chunk in s. split ( '+' ) {
550- let chunk = chunk. trim_start ( ) ;
551- if chunk. is_empty ( ) || chunk. contains ( char:: is_whitespace) {
552- return Err ( ParseError :: InvalidContinuation ) ;
553- }
554- }
555-
556- let s = s. chars ( ) . filter ( |c| * c != '+' && !c. is_whitespace ( ) ) . collect :: < String > ( ) ;
557- let ( hrp, data) = bech32:: decode_without_checksum ( & s) ?;
558-
559- if hrp != OFFER_BECH32_HRP {
560- return Err ( ParseError :: InvalidBech32Hrp ) ;
561- }
562-
563- let data = Vec :: < u8 > :: from_base32 ( & data) ?;
564- Ok ( ParsedOffer ( Readable :: read ( & mut & data[ ..] ) ?, data) )
565- }
566- }
567-
568497impl core:: fmt:: Display for Offer {
569498 fn fmt ( & self , f : & mut core:: fmt:: Formatter ) -> Result < ( ) , core:: fmt:: Error > {
570- use bitcoin:: bech32:: ToBase32 ;
571- let data = self . as_bytes ( ) . to_base32 ( ) ;
572- bech32:: encode_without_checksum_to_fmt ( f, OFFER_BECH32_HRP , data) . expect ( "HRP is valid" ) . unwrap ( ) ;
573-
574- Ok ( ( ) )
499+ self . fmt_bech32_str ( f)
575500 }
576501}
577502
@@ -955,7 +880,7 @@ mod tests {
955880
956881#[ cfg( test) ]
957882mod bolt12_tests {
958- use super :: { Offer , ParseError , ParsedOffer } ;
883+ use super :: { Offer , ParseError } ;
959884 use bitcoin:: bech32;
960885 use ln:: msgs:: DecodeError ;
961886
@@ -981,8 +906,7 @@ mod bolt12_tests {
981906 "lno1qcp4256ypqpq86q2pucnq42ngssx2an9wfujqerp0yg06qg2qdd7t628sgykwj5kuc837qmlv9m9gr7sq8ap6erfgacv26nhp8zzcqgzhdvttlk22pw8fmwqqrvzst792mj35ypylj886ljkcmug03wg6heqqsqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq6muh550qsfva9fdes0ruph7ctk2s8aqq06r4jxj3msc448wzwy9sqs9w6ckhlv55zuwnkuqqxc9qhu24h9rggzflyw04l9d3hcslzu340jqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq2pqun4wd68jtn00fkxzcnn9ehhyec6qgqsz83qfwdpl28qqmc78ymlvhmxcsywdk5wrjnj36jryg488qwlrnzyjczlqsp9nyu4phcg6dqhlhzgxagfu7zh3d9re0sqp9ts2yfugvnnm9gxkcnnnkdpa084a6t520h5zhkxsdnghvpukvd43lastpwuh73k29qsy" ,
982907 ] ;
983908 for encoded_offer in & offers {
984- // TODO: Use Offer once Destination semantics are finalized.
985- if let Err ( e) = encoded_offer. parse :: < ParsedOffer > ( ) {
909+ if let Err ( e) = encoded_offer. parse :: < Offer > ( ) {
986910 panic ! ( "Invalid offer ({:?}): {}" , e, encoded_offer) ;
987911 }
988912 }
0 commit comments