Add InvoiceError message
authorJeffrey Czyz <jkczyz@gmail.com>
Thu, 18 May 2023 22:08:46 +0000 (17:08 -0500)
committerJeffrey Czyz <jkczyz@gmail.com>
Tue, 13 Jun 2023 18:07:46 +0000 (13:07 -0500)
If an InvoiceRequest or an Invoice delivered via an onion message cannot
be handled, the recipient should reply with an InvoiceError if a reply
path was given. Define the message and conversion from SemanticError.

lightning/src/offers/invoice_error.rs [new file with mode: 0644]
lightning/src/offers/mod.rs
lightning/src/util/ser_macros.rs

diff --git a/lightning/src/offers/invoice_error.rs b/lightning/src/offers/invoice_error.rs
new file mode 100644 (file)
index 0000000..e843264
--- /dev/null
@@ -0,0 +1,233 @@
+// This file is Copyright its original authors, visible in version control
+// history.
+//
+// This file is licensed under the Apache License, Version 2.0 <LICENSE-APACHE
+// or http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your option.
+// You may not use this file except in accordance with one or both of these
+// licenses.
+
+//! Data structures and encoding for `invoice_error` messages.
+
+use crate::io;
+use crate::ln::msgs::DecodeError;
+use crate::offers::parse::SemanticError;
+use crate::util::ser::{HighZeroBytesDroppedBigSize, Readable, WithoutLength, Writeable, Writer};
+use crate::util::string::UntrustedString;
+
+use crate::prelude::*;
+
+/// An error in response to an [`InvoiceRequest`] or an [`Invoice`].
+///
+/// [`InvoiceRequest`]: crate::offers::invoice_request::InvoiceRequest
+/// [`Invoice`]: crate::offers::invoice::Invoice
+#[derive(Clone, Debug)]
+#[cfg_attr(test, derive(PartialEq))]
+pub struct InvoiceError {
+       /// The field in the [`InvoiceRequest`] or the [`Invoice`] that contained an error.
+       ///
+       /// [`InvoiceRequest`]: crate::offers::invoice_request::InvoiceRequest
+       /// [`Invoice`]: crate::offers::invoice::Invoice
+       pub erroneous_field: Option<ErroneousField>,
+
+       /// An explanation of the error.
+       pub message: UntrustedString,
+}
+
+/// The field in the [`InvoiceRequest`] or the [`Invoice`] that contained an error.
+///
+/// [`InvoiceRequest`]: crate::offers::invoice_request::InvoiceRequest
+/// [`Invoice`]: crate::offers::invoice::Invoice
+#[derive(Clone, Debug)]
+#[cfg_attr(test, derive(PartialEq))]
+pub struct ErroneousField {
+       /// The type number of the TLV field containing the error.
+       pub tlv_fieldnum: u64,
+
+       /// A value to use for the TLV field to avoid the error.
+       pub suggested_value: Option<Vec<u8>>,
+}
+
+impl core::fmt::Display for InvoiceError {
+       fn fmt(&self, f: &mut core::fmt::Formatter) -> Result<(), core::fmt::Error> {
+               self.message.fmt(f)
+       }
+}
+
+impl Writeable for InvoiceError {
+       fn write<W: Writer>(&self, writer: &mut W) -> Result<(), io::Error> {
+               let tlv_fieldnum = self.erroneous_field.as_ref().map(|f| f.tlv_fieldnum);
+               let suggested_value =
+                       self.erroneous_field.as_ref().and_then(|f| f.suggested_value.as_ref());
+               write_tlv_fields!(writer, {
+                       (1, tlv_fieldnum, (option, encoding: (u64, HighZeroBytesDroppedBigSize))),
+                       (3, suggested_value, (option, encoding: (Vec<u8>, WithoutLength))),
+                       (5, WithoutLength(&self.message), required),
+               });
+               Ok(())
+       }
+}
+
+impl Readable for InvoiceError {
+       fn read<R: io::Read>(reader: &mut R) -> Result<Self, DecodeError> {
+               _init_and_read_tlv_fields!(reader, {
+                       (1, erroneous_field, (option, encoding: (u64, HighZeroBytesDroppedBigSize))),
+                       (3, suggested_value, (option, encoding: (Vec<u8>, WithoutLength))),
+                       (5, error, (option, encoding: (UntrustedString, WithoutLength))),
+               });
+
+               let erroneous_field = match (erroneous_field, suggested_value) {
+                       (None, None) => None,
+                       (None, Some(_)) => return Err(DecodeError::InvalidValue),
+                       (Some(tlv_fieldnum), suggested_value) => {
+                               Some(ErroneousField { tlv_fieldnum, suggested_value })
+                       },
+               };
+
+               let message = match error {
+                       None => return Err(DecodeError::InvalidValue),
+                       Some(error) => error,
+               };
+
+               Ok(InvoiceError { erroneous_field, message })
+       }
+}
+
+impl From<SemanticError> for InvoiceError {
+       fn from(error: SemanticError) -> Self {
+               InvoiceError {
+                       erroneous_field: None,
+                       message: UntrustedString(format!("{:?}", error)),
+               }
+       }
+}
+
+#[cfg(test)]
+mod tests {
+       use super::{ErroneousField, InvoiceError};
+
+       use crate::ln::msgs::DecodeError;
+       use crate::util::ser::{HighZeroBytesDroppedBigSize, Readable, VecWriter, WithoutLength, Writeable};
+       use crate::util::string::UntrustedString;
+
+       #[test]
+       fn parses_invoice_error_without_erroneous_field() {
+               let mut writer = VecWriter(Vec::new());
+               let invoice_error = InvoiceError {
+                       erroneous_field: None,
+                       message: UntrustedString("Invalid value".to_string()),
+               };
+               invoice_error.write(&mut writer).unwrap();
+
+               let buffer = writer.0;
+               match InvoiceError::read(&mut &buffer[..]) {
+                       Ok(invoice_error) => {
+                               assert_eq!(invoice_error.message, UntrustedString("Invalid value".to_string()));
+                               assert_eq!(invoice_error.erroneous_field, None);
+                       }
+                       Err(e) => panic!("Unexpected error: {:?}", e),
+               }
+       }
+
+       #[test]
+       fn parses_invoice_error_with_erroneous_field() {
+               let mut writer = VecWriter(Vec::new());
+               let invoice_error = InvoiceError {
+                       erroneous_field: Some(ErroneousField {
+                               tlv_fieldnum: 42,
+                               suggested_value: Some(vec![42; 32]),
+                       }),
+                       message: UntrustedString("Invalid value".to_string()),
+               };
+               invoice_error.write(&mut writer).unwrap();
+
+               let buffer = writer.0;
+               match InvoiceError::read(&mut &buffer[..]) {
+                       Ok(invoice_error) => {
+                               assert_eq!(invoice_error.message, UntrustedString("Invalid value".to_string()));
+                               assert_eq!(
+                                       invoice_error.erroneous_field,
+                                       Some(ErroneousField { tlv_fieldnum: 42, suggested_value: Some(vec![42; 32]) }),
+                               );
+                       }
+                       Err(e) => panic!("Unexpected error: {:?}", e),
+               }
+       }
+
+       #[test]
+       fn parses_invoice_error_without_suggested_value() {
+               let mut writer = VecWriter(Vec::new());
+               let invoice_error = InvoiceError {
+                       erroneous_field: Some(ErroneousField {
+                               tlv_fieldnum: 42,
+                               suggested_value: None,
+                       }),
+                       message: UntrustedString("Invalid value".to_string()),
+               };
+               invoice_error.write(&mut writer).unwrap();
+
+               let buffer = writer.0;
+               match InvoiceError::read(&mut &buffer[..]) {
+                       Ok(invoice_error) => {
+                               assert_eq!(invoice_error.message, UntrustedString("Invalid value".to_string()));
+                               assert_eq!(
+                                       invoice_error.erroneous_field,
+                                       Some(ErroneousField { tlv_fieldnum: 42, suggested_value: None }),
+                               );
+                       }
+                       Err(e) => panic!("Unexpected error: {:?}", e),
+               }
+       }
+
+       #[test]
+       fn fails_parsing_invoice_error_without_message() {
+               let tlv_fieldnum: Option<u64> = None;
+               let suggested_value: Option<&Vec<u8>> = None;
+               let error: Option<&String> = None;
+
+               let mut writer = VecWriter(Vec::new());
+               let mut write_tlv = || -> Result<(), DecodeError> {
+                       write_tlv_fields!(&mut writer, {
+                               (1, tlv_fieldnum, (option, encoding: (u64, HighZeroBytesDroppedBigSize))),
+                               (3, suggested_value, (option, encoding: (Vec<u8>, WithoutLength))),
+                               (5, error, (option, encoding: (String, WithoutLength))),
+                       });
+                       Ok(())
+               };
+               write_tlv().unwrap();
+
+               let buffer = writer.0;
+               match InvoiceError::read(&mut &buffer[..]) {
+                       Ok(_) => panic!("Expected error"),
+                       Err(e) => {
+                               assert_eq!(e, DecodeError::InvalidValue);
+                       },
+               }
+       }
+
+       #[test]
+       fn fails_parsing_invoice_error_without_field() {
+               let tlv_fieldnum: Option<u64> = None;
+               let suggested_value = vec![42; 32];
+               let error = "Invalid value".to_string();
+
+               let mut writer = VecWriter(Vec::new());
+               let mut write_tlv = || -> Result<(), DecodeError> {
+                       write_tlv_fields!(&mut writer, {
+                               (1, tlv_fieldnum, (option, encoding: (u64, HighZeroBytesDroppedBigSize))),
+                               (3, Some(&suggested_value), (option, encoding: (Vec<u8>, WithoutLength))),
+                               (5, Some(&error), (option, encoding: (String, WithoutLength))),
+                       });
+                       Ok(())
+               };
+               write_tlv().unwrap();
+
+               let buffer = writer.0;
+               match InvoiceError::read(&mut &buffer[..]) {
+                       Ok(_) => panic!("Expected error"),
+                       Err(e) => {
+                               assert_eq!(e, DecodeError::InvalidValue);
+                       },
+               }
+       }
+}
index 0fb20f42d79e61b394cf46b59e6794e7b42a76fe..31d8bf9cbdf38e8590ce17fc1937bf8130a5f537 100644 (file)
@@ -13,6 +13,7 @@
 //! Offers are a flexible protocol for Lightning payments.
 
 pub mod invoice;
+pub mod invoice_error;
 pub mod invoice_request;
 mod merkle;
 pub mod offer;
index d6a03a88fbe827efc72f6fcd96aa1e928aaa2c17..8ffcec6d17530a7c5674b93e227730da6c097260 100644 (file)
@@ -178,6 +178,9 @@ macro_rules! _get_varint_length_prefixed_tlv_length {
        ($len: expr, $type: expr, $field: expr, (option: $trait: ident $(, $read_arg: expr)?)) => {
                $crate::_get_varint_length_prefixed_tlv_length!($len, $type, $field, option);
        };
+       ($len: expr, $type: expr, $field: expr, (option, encoding: ($fieldty: ty, $encoding: ident))) => {
+               $crate::_get_varint_length_prefixed_tlv_length!($len, $type, $field.map(|f| $encoding(f)), option);
+       };
        ($len: expr, $type: expr, $field: expr, upgradable_required) => {
                $crate::_get_varint_length_prefixed_tlv_length!($len, $type, $field, required);
        };