Skip to content

Commit 9b3a35a

Browse files
committed
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.
1 parent 1feddae commit 9b3a35a

File tree

3 files changed

+237
-0
lines changed

3 files changed

+237
-0
lines changed

lightning/src/offers/invoice_error.rs

Lines changed: 233 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,233 @@
1+
// This file is Copyright its original authors, visible in version control
2+
// history.
3+
//
4+
// This file is licensed under the Apache License, Version 2.0 <LICENSE-APACHE
5+
// or http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
6+
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your option.
7+
// You may not use this file except in accordance with one or both of these
8+
// licenses.
9+
10+
//! Data structures and encoding for `invoice_error` messages.
11+
12+
use crate::io;
13+
use crate::ln::msgs::DecodeError;
14+
use crate::offers::parse::SemanticError;
15+
use crate::util::ser::{HighZeroBytesDroppedBigSize, Readable, WithoutLength, Writeable, Writer};
16+
use crate::util::string::UntrustedString;
17+
18+
use crate::prelude::*;
19+
20+
/// An error in response to an [`InvoiceRequest`] or an [`Invoice`].
21+
///
22+
/// [`InvoiceRequest`]: crate::offers::invoice_request::InvoiceRequest
23+
/// [`Invoice`]: crate::offers::invoice::Invoice
24+
#[derive(Clone, Debug)]
25+
#[cfg_attr(test, derive(PartialEq))]
26+
pub struct InvoiceError {
27+
/// The field in the [`InvoiceRequest`] or the [`Invoice`] that contained an error.
28+
///
29+
/// [`InvoiceRequest`]: crate::offers::invoice_request::InvoiceRequest
30+
/// [`Invoice`]: crate::offers::invoice::Invoice
31+
pub erroneous_field: Option<ErroneousField>,
32+
33+
/// An explanation of the error.
34+
pub message: UntrustedString,
35+
}
36+
37+
/// The field in the [`InvoiceRequest`] or the [`Invoice`] that contained an error.
38+
///
39+
/// [`InvoiceRequest`]: crate::offers::invoice_request::InvoiceRequest
40+
/// [`Invoice`]: crate::offers::invoice::Invoice
41+
#[derive(Clone, Debug)]
42+
#[cfg_attr(test, derive(PartialEq))]
43+
pub struct ErroneousField {
44+
/// The type number of the TLV field containing the error.
45+
pub tlv_fieldnum: u64,
46+
47+
/// A value to use for the TLV field to avoid the error.
48+
pub suggested_value: Option<Vec<u8>>,
49+
}
50+
51+
impl core::fmt::Display for InvoiceError {
52+
fn fmt(&self, f: &mut core::fmt::Formatter) -> Result<(), core::fmt::Error> {
53+
self.message.fmt(f)
54+
}
55+
}
56+
57+
impl Writeable for InvoiceError {
58+
fn write<W: Writer>(&self, writer: &mut W) -> Result<(), io::Error> {
59+
let tlv_fieldnum = self.erroneous_field.as_ref().map(|f| f.tlv_fieldnum);
60+
let suggested_value =
61+
self.erroneous_field.as_ref().and_then(|f| f.suggested_value.as_ref());
62+
write_tlv_fields!(writer, {
63+
(1, tlv_fieldnum, (option, encoding: (u64, HighZeroBytesDroppedBigSize))),
64+
(3, suggested_value, (option, encoding: (Vec<u8>, WithoutLength))),
65+
(5, WithoutLength(&self.message), required),
66+
});
67+
Ok(())
68+
}
69+
}
70+
71+
impl Readable for InvoiceError {
72+
fn read<R: io::Read>(reader: &mut R) -> Result<Self, DecodeError> {
73+
_init_and_read_tlv_fields!(reader, {
74+
(1, erroneous_field, (option, encoding: (u64, HighZeroBytesDroppedBigSize))),
75+
(3, suggested_value, (option, encoding: (Vec<u8>, WithoutLength))),
76+
(5, error, (option, encoding: (UntrustedString, WithoutLength))),
77+
});
78+
79+
let erroneous_field = match (erroneous_field, suggested_value) {
80+
(None, None) => None,
81+
(None, Some(_)) => return Err(DecodeError::InvalidValue),
82+
(Some(tlv_fieldnum), suggested_value) => {
83+
Some(ErroneousField { tlv_fieldnum, suggested_value })
84+
},
85+
};
86+
87+
let message = match error {
88+
None => return Err(DecodeError::InvalidValue),
89+
Some(error) => error,
90+
};
91+
92+
Ok(InvoiceError { erroneous_field, message })
93+
}
94+
}
95+
96+
impl From<SemanticError> for InvoiceError {
97+
fn from(error: SemanticError) -> Self {
98+
InvoiceError {
99+
erroneous_field: None,
100+
message: UntrustedString(format!("{:?}", error)),
101+
}
102+
}
103+
}
104+
105+
#[cfg(test)]
106+
mod tests {
107+
use super::{ErroneousField, InvoiceError};
108+
109+
use crate::ln::msgs::DecodeError;
110+
use crate::util::ser::{HighZeroBytesDroppedBigSize, Readable, VecWriter, WithoutLength, Writeable};
111+
use crate::util::string::UntrustedString;
112+
113+
#[test]
114+
fn parses_invoice_error_without_erroneous_field() {
115+
let mut writer = VecWriter(Vec::new());
116+
let invoice_error = InvoiceError {
117+
erroneous_field: None,
118+
message: UntrustedString("Invalid value".to_string()),
119+
};
120+
invoice_error.write(&mut writer).unwrap();
121+
122+
let buffer = writer.0;
123+
match InvoiceError::read(&mut &buffer[..]) {
124+
Ok(invoice_error) => {
125+
assert_eq!(invoice_error.message, UntrustedString("Invalid value".to_string()));
126+
assert_eq!(invoice_error.erroneous_field, None);
127+
}
128+
Err(e) => panic!("Unexpected error: {:?}", e),
129+
}
130+
}
131+
132+
#[test]
133+
fn parses_invoice_error_with_erroneous_field() {
134+
let mut writer = VecWriter(Vec::new());
135+
let invoice_error = InvoiceError {
136+
erroneous_field: Some(ErroneousField {
137+
tlv_fieldnum: 42,
138+
suggested_value: Some(vec![42; 32]),
139+
}),
140+
message: UntrustedString("Invalid value".to_string()),
141+
};
142+
invoice_error.write(&mut writer).unwrap();
143+
144+
let buffer = writer.0;
145+
match InvoiceError::read(&mut &buffer[..]) {
146+
Ok(invoice_error) => {
147+
assert_eq!(invoice_error.message, UntrustedString("Invalid value".to_string()));
148+
assert_eq!(
149+
invoice_error.erroneous_field,
150+
Some(ErroneousField { tlv_fieldnum: 42, suggested_value: Some(vec![42; 32]) }),
151+
);
152+
}
153+
Err(e) => panic!("Unexpected error: {:?}", e),
154+
}
155+
}
156+
157+
#[test]
158+
fn parses_invoice_error_without_suggested_value() {
159+
let mut writer = VecWriter(Vec::new());
160+
let invoice_error = InvoiceError {
161+
erroneous_field: Some(ErroneousField {
162+
tlv_fieldnum: 42,
163+
suggested_value: None,
164+
}),
165+
message: UntrustedString("Invalid value".to_string()),
166+
};
167+
invoice_error.write(&mut writer).unwrap();
168+
169+
let buffer = writer.0;
170+
match InvoiceError::read(&mut &buffer[..]) {
171+
Ok(invoice_error) => {
172+
assert_eq!(invoice_error.message, UntrustedString("Invalid value".to_string()));
173+
assert_eq!(
174+
invoice_error.erroneous_field,
175+
Some(ErroneousField { tlv_fieldnum: 42, suggested_value: None }),
176+
);
177+
}
178+
Err(e) => panic!("Unexpected error: {:?}", e),
179+
}
180+
}
181+
182+
#[test]
183+
fn fails_parsing_invoice_error_without_message() {
184+
let tlv_fieldnum: Option<u64> = None;
185+
let suggested_value: Option<&Vec<u8>> = None;
186+
let error: Option<&String> = None;
187+
188+
let mut writer = VecWriter(Vec::new());
189+
let mut write_tlv = || -> Result<(), DecodeError> {
190+
write_tlv_fields!(&mut writer, {
191+
(1, tlv_fieldnum, (option, encoding: (u64, HighZeroBytesDroppedBigSize))),
192+
(3, suggested_value, (option, encoding: (Vec<u8>, WithoutLength))),
193+
(5, error, (option, encoding: (String, WithoutLength))),
194+
});
195+
Ok(())
196+
};
197+
write_tlv().unwrap();
198+
199+
let buffer = writer.0;
200+
match InvoiceError::read(&mut &buffer[..]) {
201+
Ok(_) => panic!("Expected error"),
202+
Err(e) => {
203+
assert_eq!(e, DecodeError::InvalidValue);
204+
},
205+
}
206+
}
207+
208+
#[test]
209+
fn fails_parsing_invoice_error_without_field() {
210+
let tlv_fieldnum: Option<u64> = None;
211+
let suggested_value = vec![42; 32];
212+
let error = "Invalid value".to_string();
213+
214+
let mut writer = VecWriter(Vec::new());
215+
let mut write_tlv = || -> Result<(), DecodeError> {
216+
write_tlv_fields!(&mut writer, {
217+
(1, tlv_fieldnum, (option, encoding: (u64, HighZeroBytesDroppedBigSize))),
218+
(3, Some(&suggested_value), (option, encoding: (Vec<u8>, WithoutLength))),
219+
(5, Some(&error), (option, encoding: (String, WithoutLength))),
220+
});
221+
Ok(())
222+
};
223+
write_tlv().unwrap();
224+
225+
let buffer = writer.0;
226+
match InvoiceError::read(&mut &buffer[..]) {
227+
Ok(_) => panic!("Expected error"),
228+
Err(e) => {
229+
assert_eq!(e, DecodeError::InvalidValue);
230+
},
231+
}
232+
}
233+
}

lightning/src/offers/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
//! Offers are a flexible protocol for Lightning payments.
1414
1515
pub mod invoice;
16+
pub mod invoice_error;
1617
pub mod invoice_request;
1718
mod merkle;
1819
pub mod offer;

lightning/src/util/ser_macros.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -178,6 +178,9 @@ macro_rules! _get_varint_length_prefixed_tlv_length {
178178
($len: expr, $type: expr, $field: expr, (option: $trait: ident $(, $read_arg: expr)?)) => {
179179
$crate::_get_varint_length_prefixed_tlv_length!($len, $type, $field, option);
180180
};
181+
($len: expr, $type: expr, $field: expr, (option, encoding: ($fieldty: ty, $encoding: ident))) => {
182+
$crate::_get_varint_length_prefixed_tlv_length!($len, $type, $field.map(|f| $encoding(f)), option);
183+
};
181184
($len: expr, $type: expr, $field: expr, upgradable_required) => {
182185
$crate::_get_varint_length_prefixed_tlv_length!($len, $type, $field, required);
183186
};

0 commit comments

Comments
 (0)