diff --git a/lightning/src/offers/invoice.rs b/lightning/src/offers/invoice.rs index 71a25efecc..a72de04451 100644 --- a/lightning/src/offers/invoice.rs +++ b/lightning/src/offers/invoice.rs @@ -505,7 +505,9 @@ impl UnsignedBolt12Invoice { record.write(&mut bytes).unwrap(); } - let (_, _, _, invoice_tlv_stream, _, _) = contents.as_tlv_stream(); + let (_, _, _, invoice_tlv_stream, _, _, experimental_invoice_tlv_stream) = + contents.as_tlv_stream(); + invoice_tlv_stream.write(&mut bytes).unwrap(); let mut experimental_bytes = Vec::new(); @@ -514,6 +516,8 @@ impl UnsignedBolt12Invoice { record.write(&mut experimental_bytes).unwrap(); } + experimental_invoice_tlv_stream.write(&mut experimental_bytes).unwrap(); + let tlv_stream = TlvStream::new(&bytes).chain(TlvStream::new(&experimental_bytes)); let tagged_hash = TaggedHash::from_tlv_stream(SIGNATURE_TAG, tlv_stream); @@ -871,6 +875,7 @@ impl Bolt12Invoice { let ( payer_tlv_stream, offer_tlv_stream, invoice_request_tlv_stream, invoice_tlv_stream, experimental_offer_tlv_stream, experimental_invoice_request_tlv_stream, + experimental_invoice_tlv_stream, ) = self.contents.as_tlv_stream(); let signature_tlv_stream = SignatureTlvStreamRef { signature: Some(&self.signature), @@ -878,7 +883,7 @@ impl Bolt12Invoice { ( payer_tlv_stream, offer_tlv_stream, invoice_request_tlv_stream, invoice_tlv_stream, signature_tlv_stream, experimental_offer_tlv_stream, - experimental_invoice_request_tlv_stream, + experimental_invoice_request_tlv_stream, experimental_invoice_tlv_stream, ) } @@ -1139,9 +1144,12 @@ impl InvoiceContents { InvoiceContents::ForOffer { invoice_request, .. } => invoice_request.as_tlv_stream(), InvoiceContents::ForRefund { refund, .. } => refund.as_tlv_stream(), }; - let invoice = self.fields().as_tlv_stream(); + let (invoice, experimental_invoice) = self.fields().as_tlv_stream(); - (payer, offer, invoice_request, invoice, experimental_offer, experimental_invoice_request) + ( + payer, offer, invoice_request, invoice, experimental_offer, + experimental_invoice_request, experimental_invoice, + ) } } @@ -1189,24 +1197,27 @@ pub(super) fn filter_fallbacks( } impl InvoiceFields { - fn as_tlv_stream(&self) -> InvoiceTlvStreamRef { + fn as_tlv_stream(&self) -> (InvoiceTlvStreamRef, ExperimentalInvoiceTlvStreamRef) { let features = { if self.features == Bolt12InvoiceFeatures::empty() { None } else { Some(&self.features) } }; - InvoiceTlvStreamRef { - paths: Some(Iterable(self.payment_paths.iter().map(|path| path.inner_blinded_path()))), - blindedpay: Some(Iterable(self.payment_paths.iter().map(|path| &path.payinfo))), - created_at: Some(self.created_at.as_secs()), - relative_expiry: self.relative_expiry.map(|duration| duration.as_secs() as u32), - payment_hash: Some(&self.payment_hash), - amount: Some(self.amount_msats), - fallbacks: self.fallbacks.as_ref(), - features, - node_id: Some(&self.signing_pubkey), - message_paths: None, - } + ( + InvoiceTlvStreamRef { + paths: Some(Iterable(self.payment_paths.iter().map(|path| path.inner_blinded_path()))), + blindedpay: Some(Iterable(self.payment_paths.iter().map(|path| &path.payinfo))), + created_at: Some(self.created_at.as_secs()), + relative_expiry: self.relative_expiry.map(|duration| duration.as_secs() as u32), + payment_hash: Some(&self.payment_hash), + amount: Some(self.amount_msats), + fallbacks: self.fallbacks.as_ref(), + features, + node_id: Some(&self.signing_pubkey), + message_paths: None, + }, + ExperimentalInvoiceTlvStreamRef {}, + ) } } @@ -1244,11 +1255,13 @@ impl TryFrom> for UnsignedBolt12Invoice { let ( payer_tlv_stream, offer_tlv_stream, invoice_request_tlv_stream, invoice_tlv_stream, experimental_offer_tlv_stream, experimental_invoice_request_tlv_stream, + experimental_invoice_tlv_stream, ) = tlv_stream; let contents = InvoiceContents::try_from( ( payer_tlv_stream, offer_tlv_stream, invoice_request_tlv_stream, invoice_tlv_stream, experimental_offer_tlv_stream, experimental_invoice_request_tlv_stream, + experimental_invoice_tlv_stream, ) )?; @@ -1291,6 +1304,13 @@ tlv_stream!(InvoiceTlvStream, InvoiceTlvStreamRef<'a>, INVOICE_TYPES, { (236, message_paths: (Vec, WithoutLength)), }); +/// Valid type range for experimental invoice TLV records. +const EXPERIMENTAL_INVOICE_TYPES: core::ops::RangeFrom = 3_000_000_000..; + +tlv_stream!( + ExperimentalInvoiceTlvStream, ExperimentalInvoiceTlvStreamRef, EXPERIMENTAL_INVOICE_TYPES, {} +); + pub(super) type BlindedPathIter<'a> = core::iter::Map< core::slice::Iter<'a, BlindedPaymentPath>, for<'r> fn(&'r BlindedPaymentPath) -> &'r BlindedPath, @@ -1312,7 +1332,7 @@ impl_writeable!(FallbackAddress, { version, program }); type FullInvoiceTlvStream =( PayerTlvStream, OfferTlvStream, InvoiceRequestTlvStream, InvoiceTlvStream, SignatureTlvStream, - ExperimentalOfferTlvStream, ExperimentalInvoiceRequestTlvStream, + ExperimentalOfferTlvStream, ExperimentalInvoiceRequestTlvStream, ExperimentalInvoiceTlvStream, ); type FullInvoiceTlvStreamRef<'a> = ( @@ -1323,6 +1343,7 @@ type FullInvoiceTlvStreamRef<'a> = ( SignatureTlvStreamRef<'a>, ExperimentalOfferTlvStreamRef, ExperimentalInvoiceRequestTlvStreamRef, + ExperimentalInvoiceTlvStreamRef, ); impl CursorReadable for FullInvoiceTlvStream { @@ -1334,11 +1355,12 @@ impl CursorReadable for FullInvoiceTlvStream { let signature = CursorReadable::read(r)?; let experimental_offer = CursorReadable::read(r)?; let experimental_invoice_request = CursorReadable::read(r)?; + let experimental_invoice = CursorReadable::read(r)?; Ok( ( payer, offer, invoice_request, invoice, signature, experimental_offer, - experimental_invoice_request, + experimental_invoice_request, experimental_invoice, ) ) } @@ -1346,7 +1368,7 @@ impl CursorReadable for FullInvoiceTlvStream { type PartialInvoiceTlvStream = ( PayerTlvStream, OfferTlvStream, InvoiceRequestTlvStream, InvoiceTlvStream, - ExperimentalOfferTlvStream, ExperimentalInvoiceRequestTlvStream, + ExperimentalOfferTlvStream, ExperimentalInvoiceRequestTlvStream, ExperimentalInvoiceTlvStream, ); type PartialInvoiceTlvStreamRef<'a> = ( @@ -1356,6 +1378,7 @@ type PartialInvoiceTlvStreamRef<'a> = ( InvoiceTlvStreamRef<'a>, ExperimentalOfferTlvStreamRef, ExperimentalInvoiceRequestTlvStreamRef, + ExperimentalInvoiceTlvStreamRef, ); impl CursorReadable for PartialInvoiceTlvStream { @@ -1366,11 +1389,12 @@ impl CursorReadable for PartialInvoiceTlvStream { let invoice = CursorReadable::read(r)?; let experimental_offer = CursorReadable::read(r)?; let experimental_invoice_request = CursorReadable::read(r)?; + let experimental_invoice = CursorReadable::read(r)?; Ok( ( payer, offer, invoice_request, invoice, experimental_offer, - experimental_invoice_request, + experimental_invoice_request, experimental_invoice, ) ) } @@ -1386,11 +1410,13 @@ impl TryFrom> for Bolt12Invoice { SignatureTlvStream { signature }, experimental_offer_tlv_stream, experimental_invoice_request_tlv_stream, + experimental_invoice_tlv_stream, ) = tlv_stream; let contents = InvoiceContents::try_from( ( payer_tlv_stream, offer_tlv_stream, invoice_request_tlv_stream, invoice_tlv_stream, experimental_offer_tlv_stream, experimental_invoice_request_tlv_stream, + experimental_invoice_tlv_stream, ) )?; @@ -1419,6 +1445,7 @@ impl TryFrom for InvoiceContents { }, experimental_offer_tlv_stream, experimental_invoice_request_tlv_stream, + ExperimentalInvoiceTlvStream {}, ) = tlv_stream; if message_paths.is_some() { return Err(Bolt12SemanticError::UnexpectedPaths) } @@ -1515,7 +1542,7 @@ pub(super) fn check_invoice_signing_pubkey( #[cfg(test)] mod tests { - use super::{Bolt12Invoice, DEFAULT_RELATIVE_EXPIRY, FallbackAddress, FullInvoiceTlvStreamRef, INVOICE_TYPES, InvoiceTlvStreamRef, SIGNATURE_TAG, UnsignedBolt12Invoice}; + use super::{Bolt12Invoice, DEFAULT_RELATIVE_EXPIRY, ExperimentalInvoiceTlvStreamRef, FallbackAddress, FullInvoiceTlvStreamRef, INVOICE_TYPES, InvoiceTlvStreamRef, SIGNATURE_TAG, UnsignedBolt12Invoice}; use bitcoin::{CompressedPublicKey, WitnessProgram, WitnessVersion}; use bitcoin::constants::ChainHash; @@ -1711,6 +1738,7 @@ mod tests { ExperimentalInvoiceRequestTlvStreamRef { experimental_bar: None, }, + ExperimentalInvoiceTlvStreamRef {}, ), ); @@ -1810,6 +1838,7 @@ mod tests { ExperimentalInvoiceRequestTlvStreamRef { experimental_bar: None, }, + ExperimentalInvoiceTlvStreamRef {}, ), ); @@ -2006,7 +2035,7 @@ mod tests { .relative_expiry(one_hour.as_secs() as u32) .build().unwrap() .sign(recipient_sign).unwrap(); - let (_, _, _, tlv_stream, _, _, _) = invoice.as_tlv_stream(); + let (_, _, _, tlv_stream, _, _, _, _) = invoice.as_tlv_stream(); #[cfg(feature = "std")] assert!(!invoice.is_expired()); assert_eq!(invoice.relative_expiry(), one_hour); @@ -2022,7 +2051,7 @@ mod tests { .relative_expiry(one_hour.as_secs() as u32 - 1) .build().unwrap() .sign(recipient_sign).unwrap(); - let (_, _, _, tlv_stream, _, _, _) = invoice.as_tlv_stream(); + let (_, _, _, tlv_stream, _, _, _, _) = invoice.as_tlv_stream(); #[cfg(feature = "std")] assert!(invoice.is_expired()); assert_eq!(invoice.relative_expiry(), one_hour - Duration::from_secs(1)); @@ -2041,7 +2070,7 @@ mod tests { .respond_with_no_std(payment_paths(), payment_hash(), now()).unwrap() .build().unwrap() .sign(recipient_sign).unwrap(); - let (_, _, _, tlv_stream, _, _, _) = invoice.as_tlv_stream(); + let (_, _, _, tlv_stream, _, _, _, _) = invoice.as_tlv_stream(); assert_eq!(invoice.amount_msats(), 1001); assert_eq!(tlv_stream.amount, Some(1001)); } @@ -2059,7 +2088,7 @@ mod tests { .respond_with_no_std(payment_paths(), payment_hash(), now()).unwrap() .build().unwrap() .sign(recipient_sign).unwrap(); - let (_, _, _, tlv_stream, _, _, _) = invoice.as_tlv_stream(); + let (_, _, _, tlv_stream, _, _, _, _) = invoice.as_tlv_stream(); assert_eq!(invoice.amount_msats(), 2000); assert_eq!(tlv_stream.amount, Some(2000)); @@ -2097,7 +2126,7 @@ mod tests { .fallback_v1_p2tr_tweaked(&tweaked_pubkey) .build().unwrap() .sign(recipient_sign).unwrap(); - let (_, _, _, tlv_stream, _, _, _) = invoice.as_tlv_stream(); + let (_, _, _, tlv_stream, _, _, _, _) = invoice.as_tlv_stream(); assert_eq!( invoice.fallbacks(), vec![ @@ -2140,7 +2169,7 @@ mod tests { .allow_mpp() .build().unwrap() .sign(recipient_sign).unwrap(); - let (_, _, _, tlv_stream, _, _, _) = invoice.as_tlv_stream(); + let (_, _, _, tlv_stream, _, _, _, _) = invoice.as_tlv_stream(); assert_eq!(invoice.invoice_features(), &features); assert_eq!(tlv_stream.features, Some(&features)); } diff --git a/lightning/src/offers/invoice_request.rs b/lightning/src/offers/invoice_request.rs index e3f36536ee..8ddc49e905 100644 --- a/lightning/src/offers/invoice_request.rs +++ b/lightning/src/offers/invoice_request.rs @@ -1539,7 +1539,7 @@ mod tests { let ( payer_tlv_stream, offer_tlv_stream, mut invoice_request_tlv_stream, mut invoice_tlv_stream, mut signature_tlv_stream, experimental_offer_tlv_stream, - experimental_invoice_request_tlv_stream, + experimental_invoice_request_tlv_stream, experimental_invoice_tlv_stream, ) = invoice.as_tlv_stream(); invoice_request_tlv_stream.amount = Some(2000); invoice_tlv_stream.amount = Some(2000); @@ -1548,6 +1548,7 @@ mod tests { (payer_tlv_stream, offer_tlv_stream, invoice_request_tlv_stream, invoice_tlv_stream); let experimental_tlv_stream = ( experimental_offer_tlv_stream, experimental_invoice_request_tlv_stream, + experimental_invoice_tlv_stream, ); let mut bytes = Vec::new(); (&tlv_stream, &experimental_tlv_stream).write(&mut bytes).unwrap(); @@ -1568,7 +1569,7 @@ mod tests { let ( mut payer_tlv_stream, offer_tlv_stream, invoice_request_tlv_stream, invoice_tlv_stream, mut signature_tlv_stream, experimental_offer_tlv_stream, - experimental_invoice_request_tlv_stream, + experimental_invoice_request_tlv_stream, experimental_invoice_tlv_stream, ) = invoice.as_tlv_stream(); let metadata = payer_tlv_stream.metadata.unwrap().iter().copied().rev().collect(); payer_tlv_stream.metadata = Some(&metadata); @@ -1577,6 +1578,7 @@ mod tests { (payer_tlv_stream, offer_tlv_stream, invoice_request_tlv_stream, invoice_tlv_stream); let experimental_tlv_stream = ( experimental_offer_tlv_stream, experimental_invoice_request_tlv_stream, + experimental_invoice_tlv_stream, ); let mut bytes = Vec::new(); (&tlv_stream, &experimental_tlv_stream).write(&mut bytes).unwrap(); @@ -1626,7 +1628,7 @@ mod tests { let ( payer_tlv_stream, offer_tlv_stream, mut invoice_request_tlv_stream, mut invoice_tlv_stream, mut signature_tlv_stream, experimental_offer_tlv_stream, - experimental_invoice_request_tlv_stream, + experimental_invoice_request_tlv_stream, experimental_invoice_tlv_stream, ) = invoice.as_tlv_stream(); invoice_request_tlv_stream.amount = Some(2000); invoice_tlv_stream.amount = Some(2000); @@ -1635,6 +1637,7 @@ mod tests { (payer_tlv_stream, offer_tlv_stream, invoice_request_tlv_stream, invoice_tlv_stream); let experimental_tlv_stream = ( experimental_offer_tlv_stream, experimental_invoice_request_tlv_stream, + experimental_invoice_tlv_stream, ); let mut bytes = Vec::new(); (&tlv_stream, &experimental_tlv_stream).write(&mut bytes).unwrap(); @@ -1657,7 +1660,7 @@ mod tests { let ( payer_tlv_stream, offer_tlv_stream, mut invoice_request_tlv_stream, invoice_tlv_stream, mut signature_tlv_stream, experimental_offer_tlv_stream, - experimental_invoice_request_tlv_stream, + experimental_invoice_request_tlv_stream, experimental_invoice_tlv_stream, ) = invoice.as_tlv_stream(); let payer_id = pubkey(1); invoice_request_tlv_stream.payer_id = Some(&payer_id); @@ -1666,6 +1669,7 @@ mod tests { (payer_tlv_stream, offer_tlv_stream, invoice_request_tlv_stream, invoice_tlv_stream); let experimental_tlv_stream = ( experimental_offer_tlv_stream, experimental_invoice_request_tlv_stream, + experimental_invoice_tlv_stream, ); let mut bytes = Vec::new(); (&tlv_stream, &experimental_tlv_stream).write(&mut bytes).unwrap(); diff --git a/lightning/src/offers/static_invoice.rs b/lightning/src/offers/static_invoice.rs index 1dd78f649c..c0d48fe0bc 100644 --- a/lightning/src/offers/static_invoice.rs +++ b/lightning/src/offers/static_invoice.rs @@ -16,7 +16,8 @@ use crate::ln::features::{Bolt12InvoiceFeatures, OfferFeatures}; use crate::ln::inbound_payment::ExpandedKey; use crate::ln::msgs::DecodeError; use crate::offers::invoice::{ - check_invoice_signing_pubkey, construct_payment_paths, filter_fallbacks, FallbackAddress, + check_invoice_signing_pubkey, construct_payment_paths, filter_fallbacks, + ExperimentalInvoiceTlvStream, ExperimentalInvoiceTlvStreamRef, FallbackAddress, InvoiceTlvStream, InvoiceTlvStreamRef, }; use crate::offers::invoice_macros::{invoice_accessors_common, invoice_builder_methods_common}; @@ -287,7 +288,7 @@ impl UnsignedStaticInvoice { record.write(&mut bytes).unwrap(); } - let (_, invoice_tlv_stream, _) = contents.as_tlv_stream(); + let (_, invoice_tlv_stream, _, experimental_invoice_tlv_stream) = contents.as_tlv_stream(); invoice_tlv_stream.write(&mut bytes).unwrap(); let mut experimental_bytes = Vec::new(); @@ -296,6 +297,8 @@ impl UnsignedStaticInvoice { record.write(&mut experimental_bytes).unwrap(); } + experimental_invoice_tlv_stream.write(&mut experimental_bytes).unwrap(); + let tlv_stream = TlvStream::new(&bytes).chain(TlvStream::new(&experimental_bytes)); let tagged_hash = TaggedHash::from_tlv_stream(SIGNATURE_TAG, tlv_stream); @@ -422,9 +425,11 @@ impl InvoiceContents { payment_hash: None, }; + let experimental_invoice = ExperimentalInvoiceTlvStreamRef {}; + let (offer, experimental_offer) = self.offer.as_tlv_stream(); - (offer, invoice, experimental_offer) + (offer, invoice, experimental_offer, experimental_invoice) } fn chain(&self) -> ChainHash { @@ -521,8 +526,13 @@ impl TryFrom> for StaticInvoice { } } -type FullInvoiceTlvStream = - (OfferTlvStream, InvoiceTlvStream, SignatureTlvStream, ExperimentalOfferTlvStream); +type FullInvoiceTlvStream = ( + OfferTlvStream, + InvoiceTlvStream, + SignatureTlvStream, + ExperimentalOfferTlvStream, + ExperimentalInvoiceTlvStream, +); impl CursorReadable for FullInvoiceTlvStream { fn read>(r: &mut io::Cursor) -> Result { @@ -530,15 +540,21 @@ impl CursorReadable for FullInvoiceTlvStream { let invoice = CursorReadable::read(r)?; let signature = CursorReadable::read(r)?; let experimental_offer = CursorReadable::read(r)?; + let experimental_invoice = CursorReadable::read(r)?; - Ok((offer, invoice, signature, experimental_offer)) + Ok((offer, invoice, signature, experimental_offer, experimental_invoice)) } } -type PartialInvoiceTlvStream = (OfferTlvStream, InvoiceTlvStream, ExperimentalOfferTlvStream); +type PartialInvoiceTlvStream = + (OfferTlvStream, InvoiceTlvStream, ExperimentalOfferTlvStream, ExperimentalInvoiceTlvStream); -type PartialInvoiceTlvStreamRef<'a> = - (OfferTlvStreamRef<'a>, InvoiceTlvStreamRef<'a>, ExperimentalOfferTlvStreamRef); +type PartialInvoiceTlvStreamRef<'a> = ( + OfferTlvStreamRef<'a>, + InvoiceTlvStreamRef<'a>, + ExperimentalOfferTlvStreamRef, + ExperimentalInvoiceTlvStreamRef, +); impl TryFrom> for StaticInvoice { type Error = Bolt12ParseError; @@ -550,11 +566,13 @@ impl TryFrom> for StaticInvoice { invoice_tlv_stream, SignatureTlvStream { signature }, experimental_offer_tlv_stream, + experimental_invoice_tlv_stream, ) = tlv_stream; let contents = InvoiceContents::try_from(( offer_tlv_stream, invoice_tlv_stream, experimental_offer_tlv_stream, + experimental_invoice_tlv_stream, ))?; let signature = match signature { @@ -592,6 +610,7 @@ impl TryFrom for InvoiceContents { amount, }, experimental_offer_tlv_stream, + ExperimentalInvoiceTlvStream {}, ) = tlv_stream; if payment_hash.is_some() { @@ -643,7 +662,7 @@ mod tests { use crate::ln::features::{Bolt12InvoiceFeatures, OfferFeatures}; use crate::ln::inbound_payment::ExpandedKey; use crate::ln::msgs::DecodeError; - use crate::offers::invoice::{InvoiceTlvStreamRef, INVOICE_TYPES}; + use crate::offers::invoice::{ExperimentalInvoiceTlvStreamRef, InvoiceTlvStreamRef, INVOICE_TYPES}; use crate::offers::merkle; use crate::offers::merkle::{SignatureTlvStreamRef, TaggedHash}; use crate::offers::nonce::Nonce; @@ -668,17 +687,23 @@ mod tests { InvoiceTlvStreamRef<'a>, SignatureTlvStreamRef<'a>, ExperimentalOfferTlvStreamRef, + ExperimentalInvoiceTlvStreamRef, ); impl StaticInvoice { fn as_tlv_stream(&self) -> FullInvoiceTlvStreamRef { - let (offer_tlv_stream, invoice_tlv_stream, experimental_offer_tlv_stream) = - self.contents.as_tlv_stream(); + let ( + offer_tlv_stream, + invoice_tlv_stream, + experimental_offer_tlv_stream, + experimental_invoice_tlv_stream, + ) = self.contents.as_tlv_stream(); ( offer_tlv_stream, invoice_tlv_stream, SignatureTlvStreamRef { signature: Some(&self.signature) }, experimental_offer_tlv_stream, + experimental_invoice_tlv_stream, ) } } @@ -689,6 +714,7 @@ mod tests { InvoiceTlvStreamRef, SignatureTlvStreamRef, ExperimentalOfferTlvStreamRef, + ExperimentalInvoiceTlvStreamRef, ), ) -> Vec { let mut buffer = Vec::new(); @@ -696,6 +722,7 @@ mod tests { tlv_stream.1.write(&mut buffer).unwrap(); tlv_stream.2.write(&mut buffer).unwrap(); tlv_stream.3.write(&mut buffer).unwrap(); + tlv_stream.4.write(&mut buffer).unwrap(); buffer } @@ -826,6 +853,7 @@ mod tests { }, SignatureTlvStreamRef { signature: Some(&invoice.signature()) }, ExperimentalOfferTlvStreamRef { experimental_foo: None }, + ExperimentalInvoiceTlvStreamRef {}, ) ); diff --git a/lightning/src/util/ser.rs b/lightning/src/util/ser.rs index a30d10b6ad..98a109c292 100644 --- a/lightning/src/util/ser.rs +++ b/lightning/src/util/ser.rs @@ -1488,6 +1488,30 @@ impl Readable for (A, B, C, D, E, F, G) { + fn read(r: &mut R) -> Result { + let a: A = Readable::read(r)?; + let b: B = Readable::read(r)?; + let c: C = Readable::read(r)?; + let d: D = Readable::read(r)?; + let e: E = Readable::read(r)?; + let f: F = Readable::read(r)?; + let g: G = Readable::read(r)?; + Ok((a, b, c, d, e, f, g)) + } +} +impl Writeable for (A, B, C, D, E, F, G) { + fn write(&self, w: &mut W) -> Result<(), io::Error> { + self.0.write(w)?; + self.1.write(w)?; + self.2.write(w)?; + self.3.write(w)?; + self.4.write(w)?; + self.5.write(w)?; + self.6.write(w) + } +} + impl Writeable for () { fn write(&self, _: &mut W) -> Result<(), io::Error> { Ok(())