use ln::features::{ChannelFeatures, InitFeatures, NodeFeatures};
-use std::error::Error;
use std::{cmp, fmt};
use std::io::Read;
use std::result::Result;
use util::events;
-use util::ser::{Readable, Writeable, Writer};
+use util::ser::{Readable, Writeable, Writer, FixedLengthReader, HighZeroBytesDroppedVarInt};
-use ln::channelmanager::{PaymentPreimage, PaymentHash};
+use ln::channelmanager::{PaymentPreimage, PaymentHash, PaymentSecret};
+
+/// 21 million * 10^8 * 1000
+pub(crate) const MAX_VALUE_MSAT: u64 = 21_000_000_0000_0000_000;
/// An error in decoding a message or struct.
#[derive(Debug)]
/// A version byte specified something we don't know how to handle.
/// Includes unknown realm byte in an OnionHopData packet
UnknownVersion,
- /// Unknown feature mandating we fail to parse message
+ /// Unknown feature mandating we fail to parse message (eg TLV with an even, unknown type)
UnknownRequiredFeature,
/// Value was invalid, eg a byte which was supposed to be a bool was something other than a 0
- /// or 1, a public key/private key/signature was invalid, text wasn't UTF-8, etc
+ /// or 1, a public key/private key/signature was invalid, text wasn't UTF-8, TLV was
+ /// syntactically incorrect, etc
InvalidValue,
/// Buffer too short
ShortRead,
- /// node_announcement included more than one address of a given type!
- ExtraAddressesPerType,
/// A length descriptor in the packet didn't describe the later data correctly
BadLengthDescriptor,
/// Error from std::io
&NetAddress::OnionV3 { .. } => { 37 },
}
}
+
+ /// The maximum length of any address descriptor, not including the 1-byte type
+ pub(crate) const MAX_LEN: u16 = 37;
}
impl Writeable for NetAddress {
}
}
-impl<R: ::std::io::Read> Readable<R> for Result<NetAddress, u8> {
- fn read(reader: &mut R) -> Result<Result<NetAddress, u8>, DecodeError> {
- let byte = <u8 as Readable<R>>::read(reader)?;
+impl Readable for Result<NetAddress, u8> {
+ fn read<R: Read>(reader: &mut R) -> Result<Result<NetAddress, u8>, DecodeError> {
+ let byte = <u8 as Readable>::read(reader)?;
match byte {
1 => {
Ok(Ok(NetAddress::IPv4 {
fn handle_htlc_fail_channel_update(&self, update: &HTLCFailChannelUpdate);
/// Gets a subset of the channel announcements and updates required to dump our routing table
/// to a remote node, starting at the short_channel_id indicated by starting_point and
- /// including batch_amount entries.
- fn get_next_channel_announcements(&self, starting_point: u64, batch_amount: u8) -> Vec<(ChannelAnnouncement, ChannelUpdate, ChannelUpdate)>;
+ /// including the batch_amount entries immediately higher in numerical value than starting_point.
+ fn get_next_channel_announcements(&self, starting_point: u64, batch_amount: u8) -> Vec<(ChannelAnnouncement, Option<ChannelUpdate>, Option<ChannelUpdate>)>;
/// Gets a subset of the node announcements required to dump our routing table to a remote node,
- /// starting at the node *after* the provided publickey and including batch_amount entries.
+ /// starting at the node *after* the provided publickey and including batch_amount entries
+ /// immediately higher (as defined by <PublicKey as Ord>::cmp) than starting_point.
/// If None is provided for starting_point, we start at the first node.
fn get_next_node_announcements(&self, starting_point: Option<&PublicKey>, batch_amount: u8) -> Vec<NodeAnnouncement>;
/// Returns whether a full sync should be requested from a peer.
}
mod fuzzy_internal_msgs {
+ use ln::channelmanager::PaymentSecret;
+
// These types aren't intended to be pub, but are exposed for direct fuzzing (as we deserialize
// them from untrusted input):
+ #[derive(Clone)]
+ pub(crate) struct FinalOnionHopData {
+ pub(crate) payment_secret: PaymentSecret,
+ /// The total value, in msat, of the payment as received by the ultimate recipient.
+ /// Message serialization may panic if this value is more than 21 million Bitcoin.
+ pub(crate) total_msat: u64,
+ }
pub(crate) enum OnionHopDataFormat {
- Legacy, // aka Realm-0
- // Some tests expect to be able to generate bogus non-deserializable OnionHopDatas. In the
- // future we can use bogus TLV attributes, but for now we have to expose a "bogus realm"
- // option.
- #[cfg(test)]
- BogusRealm(u8),
+ Legacy { // aka Realm-0
+ short_channel_id: u64,
+ },
+ NonFinalNode {
+ short_channel_id: u64,
+ },
+ FinalNode {
+ payment_data: Option<FinalOnionHopData>,
+ },
}
pub struct OnionHopData {
pub(crate) format: OnionHopDataFormat,
- pub(crate) short_channel_id: u64,
+ /// The value, in msat, of the payment after this hop's fee is deducted.
+ /// Message serialization may panic if this value is more than 21 million Bitcoin.
pub(crate) amt_to_forward: u64,
pub(crate) outgoing_cltv_value: u32,
- // 12 bytes of 0-padding
+ // 12 bytes of 0-padding for Legacy format
}
pub struct DecodedOnionErrorPacket {
pub(crate) data: Vec<u8>,
}
-impl Error for DecodeError {
- fn description(&self) -> &str {
- match *self {
- DecodeError::UnknownVersion => "Unknown realm byte in Onion packet",
- DecodeError::UnknownRequiredFeature => "Unknown required feature preventing decode",
- DecodeError::InvalidValue => "Nonsense bytes didn't map to the type they were interpreted as",
- DecodeError::ShortRead => "Packet extended beyond the provided bytes",
- DecodeError::ExtraAddressesPerType => "More than one address of a single type",
- DecodeError::BadLengthDescriptor => "A length descriptor in the packet didn't describe the later data correctly",
- DecodeError::Io(ref e) => e.description(),
- }
- }
-}
impl fmt::Display for DecodeError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
- f.write_str(self.description())
+ match *self {
+ DecodeError::UnknownVersion => f.write_str("Unknown realm byte in Onion packet"),
+ DecodeError::UnknownRequiredFeature => f.write_str("Unknown required feature preventing decode"),
+ DecodeError::InvalidValue => f.write_str("Nonsense bytes didn't map to the type they were interpreted as"),
+ DecodeError::ShortRead => f.write_str("Packet extended beyond the provided bytes"),
+ DecodeError::BadLengthDescriptor => f.write_str("A length descriptor in the packet didn't describe the later data correctly"),
+ DecodeError::Io(ref e) => e.fmt(f),
+ }
}
}
}
}
-impl<R: Read> Readable<R> for OptionalField<Script> {
- fn read(r: &mut R) -> Result<Self, DecodeError> {
- match <u16 as Readable<R>>::read(r) {
+impl Readable for OptionalField<Script> {
+ fn read<R: Read>(r: &mut R) -> Result<Self, DecodeError> {
+ match <u16 as Readable>::read(r) {
Ok(len) => {
let mut buf = vec![0; len as usize];
r.read_exact(&mut buf)?;
}
}
-impl<R: Read> Readable<R> for ChannelReestablish{
- fn read(r: &mut R) -> Result<Self, DecodeError> {
+impl Readable for ChannelReestablish{
+ fn read<R: Read>(r: &mut R) -> Result<Self, DecodeError> {
Ok(Self {
channel_id: Readable::read(r)?,
next_local_commitment_number: Readable::read(r)?,
next_remote_commitment_number: Readable::read(r)?,
data_loss_protect: {
- match <[u8; 32] as Readable<R>>::read(r) {
+ match <[u8; 32] as Readable>::read(r) {
Ok(your_last_per_commitment_secret) =>
OptionalField::Present(DataLossProtect {
your_last_per_commitment_secret,
}
}
-impl<R: Read> Readable<R> for Init {
- fn read(r: &mut R) -> Result<Self, DecodeError> {
+impl Readable for Init {
+ fn read<R: Read>(r: &mut R) -> Result<Self, DecodeError> {
let global_features: InitFeatures = Readable::read(r)?;
let features: InitFeatures = Readable::read(r)?;
Ok(Init {
}
}
-impl<R: Read> Readable<R> for OnionPacket {
- fn read(r: &mut R) -> Result<Self, DecodeError> {
+impl Readable for OnionPacket {
+ fn read<R: Read>(r: &mut R) -> Result<Self, DecodeError> {
Ok(OnionPacket {
version: Readable::read(r)?,
public_key: {
onion_routing_packet
});
+impl Writeable for FinalOnionHopData {
+ fn write<W: Writer>(&self, w: &mut W) -> Result<(), ::std::io::Error> {
+ w.size_hint(32 + 8 - (self.total_msat.leading_zeros()/8) as usize);
+ self.payment_secret.0.write(w)?;
+ HighZeroBytesDroppedVarInt(self.total_msat).write(w)
+ }
+}
+
+impl Readable for FinalOnionHopData {
+ fn read<R: Read>(r: &mut R) -> Result<Self, DecodeError> {
+ let secret: [u8; 32] = Readable::read(r)?;
+ let amt: HighZeroBytesDroppedVarInt<u64> = Readable::read(r)?;
+ Ok(Self { payment_secret: PaymentSecret(secret), total_msat: amt.0 })
+ }
+}
+
impl Writeable for OnionHopData {
fn write<W: Writer>(&self, w: &mut W) -> Result<(), ::std::io::Error> {
w.size_hint(33);
+ // Note that this should never be reachable if Rust-Lightning generated the message, as we
+ // check values are sane long before we get here, though its possible in the future
+ // user-generated messages may hit this.
+ if self.amt_to_forward > MAX_VALUE_MSAT { panic!("We should never be sending infinite/overflow onion payments"); }
match self.format {
- OnionHopDataFormat::Legacy => 0u8.write(w)?,
- #[cfg(test)]
- OnionHopDataFormat::BogusRealm(v) => v.write(w)?,
+ OnionHopDataFormat::Legacy { short_channel_id } => {
+ 0u8.write(w)?;
+ short_channel_id.write(w)?;
+ self.amt_to_forward.write(w)?;
+ self.outgoing_cltv_value.write(w)?;
+ w.write_all(&[0;12])?;
+ },
+ OnionHopDataFormat::NonFinalNode { short_channel_id } => {
+ encode_varint_length_prefixed_tlv!(w, {
+ (2, HighZeroBytesDroppedVarInt(self.amt_to_forward)),
+ (4, HighZeroBytesDroppedVarInt(self.outgoing_cltv_value)),
+ (6, short_channel_id)
+ });
+ },
+ OnionHopDataFormat::FinalNode { payment_data: Some(ref final_data) } => {
+ if final_data.total_msat > MAX_VALUE_MSAT { panic!("We should never be sending infinite/overflow onion payments"); }
+ encode_varint_length_prefixed_tlv!(w, {
+ (2, HighZeroBytesDroppedVarInt(self.amt_to_forward)),
+ (4, HighZeroBytesDroppedVarInt(self.outgoing_cltv_value)),
+ (8, final_data)
+ });
+ },
+ OnionHopDataFormat::FinalNode { payment_data: None } => {
+ encode_varint_length_prefixed_tlv!(w, {
+ (2, HighZeroBytesDroppedVarInt(self.amt_to_forward)),
+ (4, HighZeroBytesDroppedVarInt(self.outgoing_cltv_value))
+ });
+ },
}
- self.short_channel_id.write(w)?;
- self.amt_to_forward.write(w)?;
- self.outgoing_cltv_value.write(w)?;
- w.write_all(&[0;12])?;
Ok(())
}
}
-impl<R: Read> Readable<R> for OnionHopData {
- fn read(r: &mut R) -> Result<Self, DecodeError> {
- Ok(OnionHopData {
- format: {
- let r: u8 = Readable::read(r)?;
- if r != 0 {
- return Err(DecodeError::UnknownVersion);
+impl Readable for OnionHopData {
+ fn read<R: Read>(mut r: &mut R) -> Result<Self, DecodeError> {
+ use bitcoin::consensus::encode::{Decodable, Error, VarInt};
+ let v: VarInt = Decodable::consensus_decode(&mut r)
+ .map_err(|e| match e {
+ Error::Io(ioe) => DecodeError::from(ioe),
+ _ => DecodeError::InvalidValue
+ })?;
+ const LEGACY_ONION_HOP_FLAG: u64 = 0;
+ let (format, amt, cltv_value) = if v.0 != LEGACY_ONION_HOP_FLAG {
+ let mut rd = FixedLengthReader::new(r, v.0);
+ let mut amt = HighZeroBytesDroppedVarInt(0u64);
+ let mut cltv_value = HighZeroBytesDroppedVarInt(0u32);
+ let mut short_id: Option<u64> = None;
+ let mut payment_data: Option<FinalOnionHopData> = None;
+ decode_tlv!(&mut rd, {
+ (2, amt),
+ (4, cltv_value)
+ }, {
+ (6, short_id),
+ (8, payment_data)
+ });
+ rd.eat_remaining().map_err(|_| DecodeError::ShortRead)?;
+ let format = if let Some(short_channel_id) = short_id {
+ if payment_data.is_some() { return Err(DecodeError::InvalidValue); }
+ OnionHopDataFormat::NonFinalNode {
+ short_channel_id,
}
- OnionHopDataFormat::Legacy
- },
- short_channel_id: Readable::read(r)?,
- amt_to_forward: Readable::read(r)?,
- outgoing_cltv_value: {
- let v: u32 = Readable::read(r)?;
- r.read_exact(&mut [0; 12])?;
- v
- },
+ } else {
+ if let &Some(ref data) = &payment_data {
+ if data.total_msat > MAX_VALUE_MSAT {
+ return Err(DecodeError::InvalidValue);
+ }
+ }
+ OnionHopDataFormat::FinalNode {
+ payment_data
+ }
+ };
+ (format, amt.0, cltv_value.0)
+ } else {
+ let format = OnionHopDataFormat::Legacy {
+ short_channel_id: Readable::read(r)?,
+ };
+ let amt: u64 = Readable::read(r)?;
+ let cltv_value: u32 = Readable::read(r)?;
+ r.read_exact(&mut [0; 12])?;
+ (format, amt, cltv_value)
+ };
+
+ if amt > MAX_VALUE_MSAT {
+ return Err(DecodeError::InvalidValue);
+ }
+ Ok(OnionHopData {
+ format,
+ amt_to_forward: amt,
+ outgoing_cltv_value: cltv_value,
})
}
}
}
}
-impl<R: Read> Readable<R> for Ping {
- fn read(r: &mut R) -> Result<Self, DecodeError> {
+impl Readable for Ping {
+ fn read<R: Read>(r: &mut R) -> Result<Self, DecodeError> {
Ok(Ping {
ponglen: Readable::read(r)?,
byteslen: {
}
}
-impl<R: Read> Readable<R> for Pong {
- fn read(r: &mut R) -> Result<Self, DecodeError> {
+impl Readable for Pong {
+ fn read<R: Read>(r: &mut R) -> Result<Self, DecodeError> {
Ok(Pong {
byteslen: {
let byteslen = Readable::read(r)?;
}
}
-impl<R: Read> Readable<R> for UnsignedChannelAnnouncement {
- fn read(r: &mut R) -> Result<Self, DecodeError> {
+impl Readable for UnsignedChannelAnnouncement {
+ fn read<R: Read>(r: &mut R) -> Result<Self, DecodeError> {
Ok(Self {
features: Readable::read(r)?,
chain_hash: Readable::read(r)?,
}
}
-impl<R: Read> Readable<R> for UnsignedChannelUpdate {
- fn read(r: &mut R) -> Result<Self, DecodeError> {
+impl Readable for UnsignedChannelUpdate {
+ fn read<R: Read>(r: &mut R) -> Result<Self, DecodeError> {
Ok(Self {
chain_hash: Readable::read(r)?,
short_channel_id: Readable::read(r)?,
}
}
-impl<R: Read> Readable<R> for ErrorMessage {
- fn read(r: &mut R) -> Result<Self, DecodeError> {
+impl Readable for ErrorMessage {
+ fn read<R: Read>(r: &mut R) -> Result<Self, DecodeError> {
Ok(Self {
channel_id: Readable::read(r)?,
data: {
- let mut sz: usize = <u16 as Readable<R>>::read(r)? as usize;
+ let mut sz: usize = <u16 as Readable>::read(r)? as usize;
let mut data = vec![];
let data_len = r.read_to_end(&mut data)?;
sz = cmp::min(data_len, sz);
self.alias.write(w)?;
let mut addrs_to_encode = self.addresses.clone();
- addrs_to_encode.sort_unstable_by(|a, b| { a.get_id().cmp(&b.get_id()) });
- addrs_to_encode.dedup_by(|a, b| { a.get_id() == b.get_id() });
+ addrs_to_encode.sort_by(|a, b| { a.get_id().cmp(&b.get_id()) });
let mut addr_len = 0;
for addr in &addrs_to_encode {
addr_len += 1 + addr.len();
}
}
-impl<R: Read> Readable<R> for UnsignedNodeAnnouncement {
- fn read(r: &mut R) -> Result<Self, DecodeError> {
+impl Readable for UnsignedNodeAnnouncement {
+ fn read<R: Read>(r: &mut R) -> Result<Self, DecodeError> {
let features: NodeFeatures = Readable::read(r)?;
let timestamp: u32 = Readable::read(r)?;
let node_id: PublicKey = Readable::read(r)?;
let alias: [u8; 32] = Readable::read(r)?;
let addr_len: u16 = Readable::read(r)?;
- let mut addresses: Vec<NetAddress> = Vec::with_capacity(4);
+ let mut addresses: Vec<NetAddress> = Vec::new();
+ let mut highest_addr_type = 0;
let mut addr_readpos = 0;
let mut excess = false;
let mut excess_byte = 0;
if addr_len <= addr_readpos { break; }
match Readable::read(r) {
Ok(Ok(addr)) => {
- match addr {
- NetAddress::IPv4 { .. } => {
- if addresses.len() > 0 {
- return Err(DecodeError::ExtraAddressesPerType);
- }
- },
- NetAddress::IPv6 { .. } => {
- if addresses.len() > 1 || (addresses.len() == 1 && addresses[0].get_id() != 1) {
- return Err(DecodeError::ExtraAddressesPerType);
- }
- },
- NetAddress::OnionV2 { .. } => {
- if addresses.len() > 2 || (addresses.len() > 0 && addresses.last().unwrap().get_id() > 2) {
- return Err(DecodeError::ExtraAddressesPerType);
- }
- },
- NetAddress::OnionV3 { .. } => {
- if addresses.len() > 3 || (addresses.len() > 0 && addresses.last().unwrap().get_id() > 3) {
- return Err(DecodeError::ExtraAddressesPerType);
- }
- },
+ if addr.get_id() < highest_addr_type {
+ // Addresses must be sorted in increasing order
+ return Err(DecodeError::InvalidValue);
}
+ highest_addr_type = addr.get_id();
if addr_len < addr_readpos + 1 + addr.len() {
return Err(DecodeError::BadLengthDescriptor);
}
impl_writeable_len_match!(NodeAnnouncement, {
{ NodeAnnouncement { contents: UnsignedNodeAnnouncement { ref features, ref addresses, ref excess_address_data, ref excess_data, ..}, .. },
- 64 + 76 + features.byte_count() + addresses.len()*38 + excess_address_data.len() + excess_data.len() }
+ 64 + 76 + features.byte_count() + addresses.len()*(NetAddress::MAX_LEN as usize + 1) + excess_address_data.len() + excess_data.len() }
}, {
signature,
contents
mod tests {
use hex;
use ln::msgs;
- use ln::msgs::{ChannelFeatures, InitFeatures, NodeFeatures, OptionalField, OnionErrorPacket};
- use ln::channelmanager::{PaymentPreimage, PaymentHash};
- use util::ser::Writeable;
+ use ln::msgs::{ChannelFeatures, FinalOnionHopData, InitFeatures, NodeFeatures, OptionalField, OnionErrorPacket, OnionHopDataFormat};
+ use ln::channelmanager::{PaymentPreimage, PaymentHash, PaymentSecret};
+ use util::ser::{Writeable, Readable};
use bitcoin_hashes::sha256d::Hash as Sha256dHash;
use bitcoin_hashes::hex::FromHex;
use secp256k1::key::{PublicKey,SecretKey};
use secp256k1::{Secp256k1, Message};
+ use std::io::Cursor;
+
#[test]
fn encoding_channel_reestablish_no_secret() {
let cr = msgs::ChannelReestablish {
let sig_2 = get_sig_on!(privkey_2, secp_ctx, String::from("01010101010101010101010101010101"));
let sig_3 = get_sig_on!(privkey_3, secp_ctx, String::from("01010101010101010101010101010101"));
let sig_4 = get_sig_on!(privkey_4, secp_ctx, String::from("01010101010101010101010101010101"));
- let mut features = ChannelFeatures::supported();
+ let mut features = ChannelFeatures::known();
if unknown_features_bits {
features = ChannelFeatures::from_le_bytes(vec![0xFF, 0xFF]);
}
let target_value = hex::decode("004000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000").unwrap();
assert_eq!(encoded_value, target_value);
}
+
+ #[test]
+ fn encoding_legacy_onion_hop_data() {
+ let msg = msgs::OnionHopData {
+ format: OnionHopDataFormat::Legacy {
+ short_channel_id: 0xdeadbeef1bad1dea,
+ },
+ amt_to_forward: 0x0badf00d01020304,
+ outgoing_cltv_value: 0xffffffff,
+ };
+ let encoded_value = msg.encode();
+ let target_value = hex::decode("00deadbeef1bad1dea0badf00d01020304ffffffff000000000000000000000000").unwrap();
+ assert_eq!(encoded_value, target_value);
+ }
+
+ #[test]
+ fn encoding_nonfinal_onion_hop_data() {
+ let mut msg = msgs::OnionHopData {
+ format: OnionHopDataFormat::NonFinalNode {
+ short_channel_id: 0xdeadbeef1bad1dea,
+ },
+ amt_to_forward: 0x0badf00d01020304,
+ outgoing_cltv_value: 0xffffffff,
+ };
+ let encoded_value = msg.encode();
+ let target_value = hex::decode("1a02080badf00d010203040404ffffffff0608deadbeef1bad1dea").unwrap();
+ assert_eq!(encoded_value, target_value);
+ msg = Readable::read(&mut Cursor::new(&target_value[..])).unwrap();
+ if let OnionHopDataFormat::NonFinalNode { short_channel_id } = msg.format {
+ assert_eq!(short_channel_id, 0xdeadbeef1bad1dea);
+ } else { panic!(); }
+ assert_eq!(msg.amt_to_forward, 0x0badf00d01020304);
+ assert_eq!(msg.outgoing_cltv_value, 0xffffffff);
+ }
+
+ #[test]
+ fn encoding_final_onion_hop_data() {
+ let mut msg = msgs::OnionHopData {
+ format: OnionHopDataFormat::FinalNode {
+ payment_data: None,
+ },
+ amt_to_forward: 0x0badf00d01020304,
+ outgoing_cltv_value: 0xffffffff,
+ };
+ let encoded_value = msg.encode();
+ let target_value = hex::decode("1002080badf00d010203040404ffffffff").unwrap();
+ assert_eq!(encoded_value, target_value);
+ msg = Readable::read(&mut Cursor::new(&target_value[..])).unwrap();
+ if let OnionHopDataFormat::FinalNode { payment_data: None } = msg.format { } else { panic!(); }
+ assert_eq!(msg.amt_to_forward, 0x0badf00d01020304);
+ assert_eq!(msg.outgoing_cltv_value, 0xffffffff);
+ }
+
+ #[test]
+ fn encoding_final_onion_hop_data_with_secret() {
+ let expected_payment_secret = PaymentSecret([0x42u8; 32]);
+ let mut msg = msgs::OnionHopData {
+ format: OnionHopDataFormat::FinalNode {
+ payment_data: Some(FinalOnionHopData {
+ payment_secret: expected_payment_secret,
+ total_msat: 0x1badca1f
+ }),
+ },
+ amt_to_forward: 0x0badf00d01020304,
+ outgoing_cltv_value: 0xffffffff,
+ };
+ let encoded_value = msg.encode();
+ let target_value = hex::decode("3602080badf00d010203040404ffffffff082442424242424242424242424242424242424242424242424242424242424242421badca1f").unwrap();
+ assert_eq!(encoded_value, target_value);
+ msg = Readable::read(&mut Cursor::new(&target_value[..])).unwrap();
+ if let OnionHopDataFormat::FinalNode {
+ payment_data: Some(FinalOnionHopData {
+ payment_secret,
+ total_msat: 0x1badca1f
+ })
+ } = msg.format {
+ assert_eq!(payment_secret, expected_payment_secret);
+ } else { panic!(); }
+ assert_eq!(msg.amt_to_forward, 0x0badf00d01020304);
+ assert_eq!(msg.outgoing_cltv_value, 0xffffffff);
+ }
}