Allow(unused_imports) on prelude imports
[rust-lightning] / lightning / src / offers / parse.rs
index b722499f251ad5213e1d27bbdb613da2c6aba2f7..00c48d9eae78aac83adc2dc416d18817b3a98ff1 100644 (file)
@@ -16,6 +16,7 @@ use crate::io;
 use crate::ln::msgs::DecodeError;
 use crate::util::ser::SeekReadable;
 
+#[allow(unused_imports)]
 use crate::prelude::*;
 
 #[cfg(not(fuzzing))]
@@ -31,6 +32,7 @@ mod sealed {
        use core::fmt;
        use super::Bolt12ParseError;
 
+       #[allow(unused_imports)]
        use crate::prelude::*;
 
        /// Indicates a message can be encoded using bech32.
@@ -116,7 +118,7 @@ impl<T: SeekReadable> TryFrom<Vec<u8>> for ParsedMessage<T> {
 }
 
 /// Error when parsing a bech32 encoded message using [`str::parse`].
-#[derive(Debug, PartialEq)]
+#[derive(Clone, Debug, PartialEq)]
 pub enum Bolt12ParseError {
        /// The bech32 encoding does not conform to the BOLT 12 requirements for continuing messages
        /// across multiple parts (i.e., '+' followed by whitespace).
@@ -135,7 +137,7 @@ pub enum Bolt12ParseError {
 }
 
 /// Error when interpreting a TLV stream as a specific type.
-#[derive(Debug, PartialEq)]
+#[derive(Clone, Debug, PartialEq)]
 pub enum Bolt12SemanticError {
        /// The current [`std::time::SystemTime`] is past the offer or invoice's expiration.
        AlreadyExpired,
@@ -179,6 +181,8 @@ pub enum Bolt12SemanticError {
        MissingPayerMetadata,
        /// A payer id was expected but was missing.
        MissingPayerId,
+       /// The payment id for a refund or request is already in use.
+       DuplicatePaymentId,
        /// Blinded paths were expected but were missing.
        MissingPaths,
        /// The blinded payinfo given does not match the number of blinded path hops.
@@ -214,3 +218,93 @@ impl From<secp256k1::Error> for Bolt12ParseError {
                Self::InvalidSignature(error)
        }
 }
+
+#[cfg(test)]
+mod bolt12_tests {
+       use super::Bolt12ParseError;
+       use crate::offers::offer::Offer;
+
+       #[test]
+       fn encodes_offer_as_bech32_without_checksum() {
+               let encoded_offer = "lno1pqps7sjqpgtyzm3qv4uxzmtsd3jjqer9wd3hy6tsw35k7msjzfpy7nz5yqcnygrfdej82um5wf5k2uckyypwa3eyt44h6txtxquqh7lz5djge4afgfjn7k4rgrkuag0jsd5xvxg";
+               let offer = dbg!(encoded_offer.parse::<Offer>().unwrap());
+               let reencoded_offer = offer.to_string();
+               dbg!(reencoded_offer.parse::<Offer>().unwrap());
+               assert_eq!(reencoded_offer, encoded_offer);
+       }
+
+       #[test]
+       fn parses_bech32_encoded_offers() {
+               let offers = [
+                       // A complete string is valid
+                       "lno1pqps7sjqpgtyzm3qv4uxzmtsd3jjqer9wd3hy6tsw35k7msjzfpy7nz5yqcnygrfdej82um5wf5k2uckyypwa3eyt44h6txtxquqh7lz5djge4afgfjn7k4rgrkuag0jsd5xvxg",
+
+                       // + can join anywhere
+                       "l+no1pqps7sjqpgtyzm3qv4uxzmtsd3jjqer9wd3hy6tsw35k7msjzfpy7nz5yqcnygrfdej82um5wf5k2uckyypwa3eyt44h6txtxquqh7lz5djge4afgfjn7k4rgrkuag0jsd5xvxg",
+
+                       // Multiple + can join
+                       "lno1pqps7sjqpgt+yzm3qv4uxzmtsd3jjqer9wd3hy6tsw3+5k7msjzfpy7nz5yqcn+ygrfdej82um5wf5k2uckyypwa3eyt44h6txtxquqh7lz5djge4afgfjn7k4rgrkuag0jsd+5xvxg",
+
+                       // + can be followed by whitespace
+                       "lno1pqps7sjqpgt+ yzm3qv4uxzmtsd3jjqer9wd3hy6tsw3+  5k7msjzfpy7nz5yqcn+\nygrfdej82um5wf5k2uckyypwa3eyt44h6txtxquqh7lz5djge4afgfjn7k4rgrkuag0jsd+\r\n 5xvxg",
+               ];
+               for encoded_offer in &offers {
+                       if let Err(e) = encoded_offer.parse::<Offer>() {
+                               panic!("Invalid offer ({:?}): {}", e, encoded_offer);
+                       }
+               }
+       }
+
+       #[test]
+       fn fails_parsing_bech32_encoded_offers_with_invalid_continuations() {
+               let offers = [
+                       // + must be surrounded by bech32 characters
+                       "lno1pqps7sjqpgtyzm3qv4uxzmtsd3jjqer9wd3hy6tsw35k7msjzfpy7nz5yqcnygrfdej82um5wf5k2uckyypwa3eyt44h6txtxquqh7lz5djge4afgfjn7k4rgrkuag0jsd5xvxg+",
+                       "lno1pqps7sjqpgtyzm3qv4uxzmtsd3jjqer9wd3hy6tsw35k7msjzfpy7nz5yqcnygrfdej82um5wf5k2uckyypwa3eyt44h6txtxquqh7lz5djge4afgfjn7k4rgrkuag0jsd5xvxg+ ",
+                       "+lno1pqps7sjqpgtyzm3qv4uxzmtsd3jjqer9wd3hy6tsw35k7msjzfpy7nz5yqcnygrfdej82um5wf5k2uckyypwa3eyt44h6txtxquqh7lz5djge4afgfjn7k4rgrkuag0jsd5xvxg",
+                       "+ lno1pqps7sjqpgtyzm3qv4uxzmtsd3jjqer9wd3hy6tsw35k7msjzfpy7nz5yqcnygrfdej82um5wf5k2uckyypwa3eyt44h6txtxquqh7lz5djge4afgfjn7k4rgrkuag0jsd5xvxg",
+                       "ln++o1pqps7sjqpgtyzm3qv4uxzmtsd3jjqer9wd3hy6tsw35k7msjzfpy7nz5yqcnygrfdej82um5wf5k2uckyypwa3eyt44h6txtxquqh7lz5djge4afgfjn7k4rgrkuag0jsd5xvxg",
+               ];
+               for encoded_offer in &offers {
+                       match encoded_offer.parse::<Offer>() {
+                               Ok(_) => panic!("Valid offer: {}", encoded_offer),
+                               Err(e) => assert_eq!(e, Bolt12ParseError::InvalidContinuation),
+                       }
+               }
+       }
+}
+
+#[cfg(test)]
+mod tests {
+       use super::Bolt12ParseError;
+       use bitcoin::bech32;
+       use crate::ln::msgs::DecodeError;
+       use crate::offers::offer::Offer;
+
+       #[test]
+       fn fails_parsing_bech32_encoded_offer_with_invalid_hrp() {
+               let encoded_offer = "lni1pqps7sjqpgtyzm3qv4uxzmtsd3jjqer9wd3hy6tsw35k7msjzfpy7nz5yqcnygrfdej82um5wf5k2uckyypwa3eyt44h6txtxquqh7lz5djge4afgfjn7k4rgrkuag0jsd5xvxg";
+               match encoded_offer.parse::<Offer>() {
+                       Ok(_) => panic!("Valid offer: {}", encoded_offer),
+                       Err(e) => assert_eq!(e, Bolt12ParseError::InvalidBech32Hrp),
+               }
+       }
+
+       #[test]
+       fn fails_parsing_bech32_encoded_offer_with_invalid_bech32_data() {
+               let encoded_offer = "lno1pqps7sjqpgtyzm3qv4uxzmtsd3jjqer9wd3hy6tsw35k7msjzfpy7nz5yqcnygrfdej82um5wf5k2uckyypwa3eyt44h6txtxquqh7lz5djge4afgfjn7k4rgrkuag0jsd5xvxo";
+               match encoded_offer.parse::<Offer>() {
+                       Ok(_) => panic!("Valid offer: {}", encoded_offer),
+                       Err(e) => assert_eq!(e, Bolt12ParseError::Bech32(bech32::Error::InvalidChar('o'))),
+               }
+       }
+
+       #[test]
+       fn fails_parsing_bech32_encoded_offer_with_invalid_tlv_data() {
+               let encoded_offer = "lno1pqps7sjqpgtyzm3qv4uxzmtsd3jjqer9wd3hy6tsw35k7msjzfpy7nz5yqcnygrfdej82um5wf5k2uckyypwa3eyt44h6txtxquqh7lz5djge4afgfjn7k4rgrkuag0jsd5xvxgqqqqq";
+               match encoded_offer.parse::<Offer>() {
+                       Ok(_) => panic!("Valid offer: {}", encoded_offer),
+                       Err(e) => assert_eq!(e, Bolt12ParseError::Decode(DecodeError::InvalidValue)),
+               }
+       }
+}