From: Jeffrey Czyz Date: Thu, 18 May 2023 22:08:46 +0000 (-0500) Subject: Add InvoiceError message X-Git-Tag: v0.0.116-alpha1~11^2~13 X-Git-Url: http://git.bitcoin.ninja/?a=commitdiff_plain;h=9b3a35a133c8f8c5864ab2a748e09ebeaf417525;p=rust-lightning Add InvoiceError message 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. --- diff --git a/lightning/src/offers/invoice_error.rs b/lightning/src/offers/invoice_error.rs new file mode 100644 index 000000000..e843264b4 --- /dev/null +++ b/lightning/src/offers/invoice_error.rs @@ -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 or the MIT license +// , 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, + + /// 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>, +} + +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(&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, WithoutLength))), + (5, WithoutLength(&self.message), required), + }); + Ok(()) + } +} + +impl Readable for InvoiceError { + fn read(reader: &mut R) -> Result { + _init_and_read_tlv_fields!(reader, { + (1, erroneous_field, (option, encoding: (u64, HighZeroBytesDroppedBigSize))), + (3, suggested_value, (option, encoding: (Vec, 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 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 = None; + let suggested_value: Option<&Vec> = 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, 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 = 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, 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); + }, + } + } +} diff --git a/lightning/src/offers/mod.rs b/lightning/src/offers/mod.rs index 0fb20f42d..31d8bf9cb 100644 --- a/lightning/src/offers/mod.rs +++ b/lightning/src/offers/mod.rs @@ -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; diff --git a/lightning/src/util/ser_macros.rs b/lightning/src/util/ser_macros.rs index d6a03a88f..8ffcec6d1 100644 --- a/lightning/src/util/ser_macros.rs +++ b/lightning/src/util/ser_macros.rs @@ -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); };