X-Git-Url: http://git.bitcoin.ninja/index.cgi?a=blobdiff_plain;f=lightning%2Fsrc%2Foffers%2Foffer.rs;h=a2008b6a0b5a0899e47c09c495829fd6a567bfd4;hb=825ea9d062886a369bda9d30ad6890fada7f2ed8;hp=6451d9431a188f3b15440c1d687435e7068f7240;hpb=e64ebe8608a3efdf630b288a7bddddf916f0cf86;p=rust-lightning diff --git a/lightning/src/offers/offer.rs b/lightning/src/offers/offer.rs index 6451d943..a2008b6a 100644 --- a/lightning/src/offers/offer.rs +++ b/lightning/src/offers/offer.rs @@ -106,7 +106,7 @@ impl OfferBuilder { let offer = OfferContents { chains: None, metadata: None, amount: None, description, features: OfferFeatures::empty(), absolute_expiry: None, issuer: None, paths: None, - supported_quantity: Quantity::one(), signing_pubkey, + supported_quantity: Quantity::One, signing_pubkey, }; OfferBuilder { offer } } @@ -178,7 +178,7 @@ impl OfferBuilder { } /// Sets the quantity of items for [`Offer::supported_quantity`]. If not called, defaults to - /// [`Quantity::one`]. + /// [`Quantity::One`]. /// /// Successive calls to this method will override the previous setting. pub fn supported_quantity(mut self, quantity: Quantity) -> Self { @@ -232,7 +232,7 @@ impl OfferBuilder { /// An `Offer` is a potentially long-lived proposal for payment of a good or service. /// /// An offer is a precursor to an [`InvoiceRequest`]. A merchant publishes an offer from which a -/// customer may request an `Invoice` for a specific quantity and using an amount sufficient to +/// customer may request an [`Invoice`] for a specific quantity and using an amount sufficient to /// cover that quantity (i.e., at least `quantity * amount`). See [`Offer::amount`]. /// /// Offers may be denominated in currency other than bitcoin but are ultimately paid using the @@ -241,6 +241,7 @@ impl OfferBuilder { /// Through the use of [`BlindedPath`]s, offers provide recipient privacy. /// /// [`InvoiceRequest`]: crate::offers::invoice_request::InvoiceRequest +/// [`Invoice`]: crate::offers::invoice::Invoice #[derive(Clone, Debug)] pub struct Offer { // The serialized offer. Needed when creating an `InvoiceRequest` if the offer contains unknown @@ -249,9 +250,10 @@ pub struct Offer { pub(super) contents: OfferContents, } -/// The contents of an [`Offer`], which may be shared with an [`InvoiceRequest`] or an `Invoice`. +/// The contents of an [`Offer`], which may be shared with an [`InvoiceRequest`] or an [`Invoice`]. /// /// [`InvoiceRequest`]: crate::offers::invoice_request::InvoiceRequest +/// [`Invoice`]: crate::offers::invoice::Invoice #[derive(Clone, Debug)] pub(super) struct OfferContents { chains: Option>, @@ -319,13 +321,7 @@ impl Offer { /// Whether the offer has expired. #[cfg(feature = "std")] pub fn is_expired(&self) -> bool { - match self.absolute_expiry() { - Some(seconds_from_epoch) => match SystemTime::UNIX_EPOCH.elapsed() { - Ok(elapsed) => elapsed > seconds_from_epoch, - Err(_) => false, - }, - None => false, - } + self.contents.is_expired() } /// The issuer of the offer, possibly beginning with `user@domain` or `domain`. Intended to be @@ -359,7 +355,7 @@ impl Offer { /// The public key used by the recipient to sign invoices. pub fn signing_pubkey(&self) -> PublicKey { - self.contents.signing_pubkey + self.contents.signing_pubkey() } /// Creates an [`InvoiceRequest`] for the offer with the given `metadata` and `payer_id`, which @@ -410,6 +406,17 @@ impl OfferContents { self.chains().contains(&chain) } + #[cfg(feature = "std")] + pub(super) fn is_expired(&self) -> bool { + match self.absolute_expiry { + Some(seconds_from_epoch) => match SystemTime::UNIX_EPOCH.elapsed() { + Ok(elapsed) => elapsed > seconds_from_epoch, + Err(_) => false, + }, + None => false, + } + } + pub fn amount(&self) -> Option<&Amount> { self.amount.as_ref() } @@ -457,22 +464,24 @@ impl OfferContents { fn is_valid_quantity(&self, quantity: u64) -> bool { match self.supported_quantity { - Quantity::Bounded(n) => { - let n = n.get(); - if n == 1 { false } - else { quantity > 0 && quantity <= n } - }, + Quantity::Bounded(n) => quantity <= n.get(), Quantity::Unbounded => quantity > 0, + Quantity::One => quantity == 1, } } fn expects_quantity(&self) -> bool { match self.supported_quantity { - Quantity::Bounded(n) => n.get() != 1, + Quantity::Bounded(_) => true, Quantity::Unbounded => true, + Quantity::One => false, } } + pub(super) fn signing_pubkey(&self) -> PublicKey { + self.signing_pubkey + } + pub(super) fn as_tlv_stream(&self) -> OfferTlvStreamRef { let (currency, amount) = match &self.amount { None => (None, None), @@ -538,25 +547,24 @@ pub type CurrencyCode = [u8; 3]; /// Quantity of items supported by an [`Offer`]. #[derive(Clone, Copy, Debug, PartialEq)] pub enum Quantity { - /// Up to a specific number of items (inclusive). + /// Up to a specific number of items (inclusive). Use when more than one item can be requested + /// but is limited (e.g., because of per customer or inventory limits). + /// + /// May be used with `NonZeroU64::new(1)` but prefer to use [`Quantity::One`] if only one item + /// is supported. Bounded(NonZeroU64), - /// One or more items. + /// One or more items. Use when more than one item can be requested without any limit. Unbounded, + /// Only one item. Use when only a single item can be requested. + One, } impl Quantity { - /// The default quantity of one. - pub fn one() -> Self { - Quantity::Bounded(NonZeroU64::new(1).unwrap()) - } - fn to_tlv_record(&self) -> Option { match self { - Quantity::Bounded(n) => { - let n = n.get(); - if n == 1 { None } else { Some(n) } - }, + Quantity::Bounded(n) => Some(n.get()), Quantity::Unbounded => Some(0), + Quantity::One => None, } } } @@ -567,7 +575,7 @@ tlv_stream!(OfferTlvStream, OfferTlvStreamRef, 1..80, { (6, currency: CurrencyCode), (8, amount: (u64, HighZeroBytesDroppedBigSize)), (10, description: (String, WithoutLength)), - (12, features: OfferFeatures), + (12, features: (OfferFeatures, WithoutLength)), (14, absolute_expiry: (u64, HighZeroBytesDroppedBigSize)), (16, paths: (Vec, WithoutLength)), (18, issuer: (String, WithoutLength)), @@ -628,9 +636,8 @@ impl TryFrom for OfferContents { .map(|seconds_from_epoch| Duration::from_secs(seconds_from_epoch)); let supported_quantity = match quantity_max { - None => Quantity::one(), + None => Quantity::One, Some(0) => Quantity::Unbounded, - Some(1) => return Err(SemanticError::InvalidQuantity), Some(n) => Quantity::Bounded(NonZeroU64::new(n).unwrap()), }; @@ -697,7 +704,7 @@ mod tests { assert!(!offer.is_expired()); assert_eq!(offer.paths(), &[]); assert_eq!(offer.issuer(), None); - assert_eq!(offer.supported_quantity(), Quantity::one()); + assert_eq!(offer.supported_quantity(), Quantity::One); assert_eq!(offer.signing_pubkey(), pubkey(42)); assert_eq!( @@ -919,14 +926,15 @@ mod tests { #[test] fn builds_offer_with_supported_quantity() { + let one = NonZeroU64::new(1).unwrap(); let ten = NonZeroU64::new(10).unwrap(); let offer = OfferBuilder::new("foo".into(), pubkey(42)) - .supported_quantity(Quantity::one()) + .supported_quantity(Quantity::One) .build() .unwrap(); let tlv_stream = offer.as_tlv_stream(); - assert_eq!(offer.supported_quantity(), Quantity::one()); + assert_eq!(offer.supported_quantity(), Quantity::One); assert_eq!(tlv_stream.quantity_max, None); let offer = OfferBuilder::new("foo".into(), pubkey(42)) @@ -945,13 +953,21 @@ mod tests { assert_eq!(offer.supported_quantity(), Quantity::Bounded(ten)); assert_eq!(tlv_stream.quantity_max, Some(10)); + let offer = OfferBuilder::new("foo".into(), pubkey(42)) + .supported_quantity(Quantity::Bounded(one)) + .build() + .unwrap(); + let tlv_stream = offer.as_tlv_stream(); + assert_eq!(offer.supported_quantity(), Quantity::Bounded(one)); + assert_eq!(tlv_stream.quantity_max, Some(1)); + let offer = OfferBuilder::new("foo".into(), pubkey(42)) .supported_quantity(Quantity::Bounded(ten)) - .supported_quantity(Quantity::one()) + .supported_quantity(Quantity::One) .build() .unwrap(); let tlv_stream = offer.as_tlv_stream(); - assert_eq!(offer.supported_quantity(), Quantity::one()); + assert_eq!(offer.supported_quantity(), Quantity::One); assert_eq!(tlv_stream.quantity_max, None); } @@ -1083,7 +1099,7 @@ mod tests { #[test] fn parses_offer_with_quantity() { let offer = OfferBuilder::new("foo".into(), pubkey(42)) - .supported_quantity(Quantity::one()) + .supported_quantity(Quantity::One) .build() .unwrap(); if let Err(e) = offer.to_string().parse::() { @@ -1106,17 +1122,12 @@ mod tests { panic!("error parsing offer: {:?}", e); } - let mut tlv_stream = offer.as_tlv_stream(); - tlv_stream.quantity_max = Some(1); - - let mut encoded_offer = Vec::new(); - tlv_stream.write(&mut encoded_offer).unwrap(); - - match Offer::try_from(encoded_offer) { - Ok(_) => panic!("expected error"), - Err(e) => { - assert_eq!(e, ParseError::InvalidSemantics(SemanticError::InvalidQuantity)); - }, + let offer = OfferBuilder::new("foo".into(), pubkey(42)) + .supported_quantity(Quantity::Bounded(NonZeroU64::new(1).unwrap())) + .build() + .unwrap(); + if let Err(e) = offer.to_string().parse::() { + panic!("error parsing offer: {:?}", e); } }