@@ -17,6 +17,7 @@ use bitcoin::hash_types::BlockHash;
17
17
use bitcoin:: network:: constants:: Network ;
18
18
use bitcoin:: secp256k1:: { PublicKey , XOnlyPublicKey } ;
19
19
use bitcoin:: secp256k1:: schnorr:: Signature ;
20
+ use core:: convert:: TryFrom ;
20
21
use core:: str:: FromStr ;
21
22
use core:: time:: Duration ;
22
23
use ln:: PaymentHash ;
@@ -132,6 +133,8 @@ pub enum Amount {
132
133
} ,
133
134
}
134
135
136
+ const ISO4217_CODE_LEN : usize = 3 ;
137
+
135
138
///
136
139
#[ derive( Debug ) ]
137
140
pub enum Destination {
@@ -225,6 +228,31 @@ pub enum ParseError {
225
228
Bech32 ( bech32:: Error ) ,
226
229
/// The bech32 decoded string could not be decoded as the expected message type.
227
230
Decode ( DecodeError ) ,
231
+ /// The parsed message has invalid semantics.
232
+ InvalidSemantics ( SemanticError ) ,
233
+ }
234
+
235
+ #[ derive( Debug , PartialEq ) ]
236
+ ///
237
+ pub enum SemanticError {
238
+ ///
239
+ UnsupportedChain ,
240
+ ///
241
+ UnexpectedCurrency ,
242
+ ///
243
+ InvalidCurrencyEncoding ,
244
+ ///
245
+ MissingDescription ,
246
+ ///
247
+ MissingDestination ,
248
+ ///
249
+ DuplicateDestination ,
250
+ ///
251
+ MissingPaths ,
252
+ ///
253
+ InvalidQuantity ,
254
+ ///
255
+ UnexpectedRefund ,
228
256
}
229
257
230
258
impl From < bech32:: Error > for ParseError {
@@ -239,6 +267,111 @@ impl From<DecodeError> for ParseError {
239
267
}
240
268
}
241
269
270
+ impl From < SemanticError > for ParseError {
271
+ fn from ( error : SemanticError ) -> Self {
272
+ Self :: InvalidSemantics ( error)
273
+ }
274
+ }
275
+
276
+ impl FromStr for Offer {
277
+ type Err = ParseError ;
278
+
279
+ fn from_str ( s : & str ) -> Result < Self , <Self as FromStr >:: Err > {
280
+ let tlv_stream = OfferTlvStream :: from_str ( s) ?;
281
+ Ok ( Offer :: try_from ( tlv_stream) ?)
282
+ }
283
+ }
284
+
285
+ impl TryFrom < OfferTlvStream > for Offer {
286
+ type Error = SemanticError ;
287
+
288
+ fn try_from ( tlv_stream : OfferTlvStream ) -> Result < Self , Self :: Error > {
289
+ let OfferTlvStream {
290
+ chains, currency, amount, description, features, absolute_expiry, paths, issuer,
291
+ quantity_min, quantity_max, recurrence, node_id, send_invoice, refund_for, signature,
292
+ } = tlv_stream;
293
+
294
+ let supported_chains = [
295
+ genesis_block ( Network :: Bitcoin ) . block_hash ( ) ,
296
+ genesis_block ( Network :: Testnet ) . block_hash ( ) ,
297
+ genesis_block ( Network :: Signet ) . block_hash ( ) ,
298
+ genesis_block ( Network :: Regtest ) . block_hash ( ) ,
299
+ ] ;
300
+ let chains = match chains {
301
+ None => None ,
302
+ Some ( WithoutLength ( chains) ) => match chains. first ( ) {
303
+ None => Some ( chains) ,
304
+ Some ( chain) if supported_chains. contains ( chain) => Some ( chains) ,
305
+ _ => return Err ( SemanticError :: UnsupportedChain ) ,
306
+ } ,
307
+ } ;
308
+
309
+ let amount = match ( currency, amount) {
310
+ ( None , None ) => None ,
311
+ ( None , Some ( amount_msats) ) => Some ( Amount :: Bitcoin { amount_msats : amount_msats. 0 } ) ,
312
+ ( Some ( _) , None ) => return Err ( SemanticError :: UnexpectedCurrency ) ,
313
+ ( Some ( WithoutLength ( iso4217_code) ) , Some ( HighZeroBytesDroppedVarInt ( amount) ) ) => {
314
+ if iso4217_code. len ( ) != ISO4217_CODE_LEN {
315
+ return Err ( SemanticError :: InvalidCurrencyEncoding ) ;
316
+ }
317
+ Some ( Amount :: Currency { iso4217_code, amount } )
318
+ } ,
319
+ } ;
320
+
321
+ if description. is_none ( ) {
322
+ return Err ( SemanticError :: MissingDescription ) ;
323
+ }
324
+
325
+ let destination = match ( node_id, paths) {
326
+ ( None , None ) => return Err ( SemanticError :: MissingDestination ) ,
327
+ ( Some ( _) , Some ( _) ) => return Err ( SemanticError :: DuplicateDestination ) ,
328
+ ( Some ( node_id) , None ) => Destination :: NodeId ( node_id) ,
329
+ ( None , Some ( paths) ) if paths. 0 . is_empty ( ) => return Err ( SemanticError :: MissingPaths ) ,
330
+ ( None , Some ( WithoutLength ( paths) ) ) => Destination :: Paths ( paths) ,
331
+ } ;
332
+
333
+ if let Some ( HighZeroBytesDroppedVarInt ( quantity_min) ) = quantity_min {
334
+ if quantity_min < 1 {
335
+ return Err ( SemanticError :: InvalidQuantity ) ;
336
+ }
337
+
338
+ if let Some ( HighZeroBytesDroppedVarInt ( quantity_max) ) = quantity_max {
339
+ if quantity_min > quantity_max {
340
+ return Err ( SemanticError :: InvalidQuantity ) ;
341
+ }
342
+ }
343
+ }
344
+
345
+ if let Some ( HighZeroBytesDroppedVarInt ( quantity_max) ) = quantity_max {
346
+ if quantity_max < 1 {
347
+ return Err ( SemanticError :: InvalidQuantity ) ;
348
+ }
349
+ }
350
+
351
+ let send_invoice = match ( send_invoice, refund_for) {
352
+ ( None , None ) => None ,
353
+ ( None , Some ( _) ) => return Err ( SemanticError :: UnexpectedRefund ) ,
354
+ ( Some ( _) , _) => Some ( SendInvoice { refund_for } ) ,
355
+ } ;
356
+
357
+ Ok ( Offer {
358
+ chains,
359
+ amount,
360
+ description : description. unwrap ( ) . 0 ,
361
+ features,
362
+ absolute_expiry :
363
+ absolute_expiry. map ( |seconds_from_epoch| Duration :: from_secs ( seconds_from_epoch. 0 ) ) ,
364
+ issuer : issuer. map ( |issuer| issuer. 0 ) ,
365
+ destination,
366
+ quantity_min : quantity_min. map ( |quantity_min| quantity_min. 0 ) ,
367
+ quantity_max : quantity_max. map ( |quantity_max| quantity_max. 0 ) ,
368
+ recurrence,
369
+ send_invoice,
370
+ signature,
371
+ } )
372
+ }
373
+ }
374
+
242
375
const OFFER_BECH32_HRP : & str = "lno" ;
243
376
244
377
impl FromStr for OfferTlvStream {
0 commit comments