Skip to content

Commit bb63559

Browse files
committed
Optimize length-prefixed writes
1 parent b19dcae commit bb63559

File tree

5 files changed

+169
-35
lines changed

5 files changed

+169
-35
lines changed

lightning/src/ln/msgs.rs

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -855,6 +855,7 @@ pub trait RoutingMessageHandler : Send + Sync + MessageSendEventsProvider {
855855

856856
mod fuzzy_internal_msgs {
857857
use ln::channelmanager::PaymentSecret;
858+
use util::ser::Writeable;
858859

859860
// These types aren't intended to be pub, but are exposed for direct fuzzing (as we deserialize
860861
// them from untrusted input):
@@ -887,6 +888,39 @@ mod fuzzy_internal_msgs {
887888
// 12 bytes of 0-padding for Legacy format
888889
}
889890

891+
pub(crate) trait OnionHopDataLength: Writeable {
892+
fn encoding_length(&self) -> usize;
893+
}
894+
895+
impl OnionHopDataLength for OnionHopData {
896+
fn encoding_length(&self) -> usize {
897+
fn high_zero_bytes_dropped_length(data: u64) -> usize {
898+
8 - ((data.leading_zeros()/8) as usize)
899+
}
900+
match self.format {
901+
OnionHopDataFormat::Legacy { .. } => 1 + 8 + 8 + 4 + 12, // 33
902+
OnionHopDataFormat::NonFinalNode { .. } => {
903+
1 +
904+
1 + 1 + high_zero_bytes_dropped_length(self.amt_to_forward) +
905+
1 + 1 + high_zero_bytes_dropped_length(self.outgoing_cltv_value as u64) +
906+
1 + 1 + 8 // short_channel_id
907+
}
908+
OnionHopDataFormat::FinalNode { ref payment_data } => {
909+
1 +
910+
1 + 1 + high_zero_bytes_dropped_length(self.amt_to_forward) +
911+
1 + 1 + high_zero_bytes_dropped_length(self.outgoing_cltv_value as u64) +
912+
match payment_data {
913+
None => 0,
914+
Some(payment_data) =>
915+
1 + 1 + high_zero_bytes_dropped_length(payment_data.total_msat) +
916+
32 // payment_secret
917+
}
918+
}
919+
}
920+
}
921+
922+
}
923+
890924
pub struct DecodedOnionErrorPacket {
891925
pub(crate) hmac: [u8; 32],
892926
pub(crate) failuremsg: Vec<u8>,
@@ -1814,7 +1848,7 @@ impl Writeable for GossipTimestampFilter {
18141848
mod tests {
18151849
use hex;
18161850
use ln::msgs;
1817-
use ln::msgs::{ChannelFeatures, FinalOnionHopData, InitFeatures, NodeFeatures, OptionalField, OnionErrorPacket, OnionHopDataFormat};
1851+
use ln::msgs::{ChannelFeatures, FinalOnionHopData, InitFeatures, NodeFeatures, OptionalField, OnionErrorPacket, OnionHopDataFormat, OnionHopDataLength};
18181852
use ln::channelmanager::{PaymentPreimage, PaymentHash, PaymentSecret};
18191853
use util::ser::{Writeable, Readable};
18201854

@@ -2477,6 +2511,7 @@ mod tests {
24772511
let encoded_value = msg.encode();
24782512
let target_value = hex::decode("00deadbeef1bad1dea0badf00d01020304ffffffff000000000000000000000000").unwrap();
24792513
assert_eq!(encoded_value, target_value);
2514+
assert_eq!(encoded_value.len(), msg.encoding_length())
24802515
}
24812516

24822517
#[test]
@@ -2497,6 +2532,7 @@ mod tests {
24972532
} else { panic!(); }
24982533
assert_eq!(msg.amt_to_forward, 0x0badf00d01020304);
24992534
assert_eq!(msg.outgoing_cltv_value, 0xffffffff);
2535+
assert_eq!(encoded_value.len(), msg.encoding_length())
25002536
}
25012537

25022538
#[test]
@@ -2515,6 +2551,7 @@ mod tests {
25152551
if let OnionHopDataFormat::FinalNode { payment_data: None } = msg.format { } else { panic!(); }
25162552
assert_eq!(msg.amt_to_forward, 0x0badf00d01020304);
25172553
assert_eq!(msg.outgoing_cltv_value, 0xffffffff);
2554+
assert_eq!(encoded_value.len(), msg.encoding_length())
25182555
}
25192556

25202557
#[test]
@@ -2544,6 +2581,7 @@ mod tests {
25442581
} else { panic!(); }
25452582
assert_eq!(msg.amt_to_forward, 0x0badf00d01020304);
25462583
assert_eq!(msg.outgoing_cltv_value, 0xffffffff);
2584+
assert_eq!(encoded_value.len(), msg.encoding_length())
25472585
}
25482586

25492587
#[test]

lightning/src/ln/onion_route_tests.rs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ use ln::onion_utils;
1717
use routing::router::{Route, get_route};
1818
use ln::features::InitFeatures;
1919
use ln::msgs;
20-
use ln::msgs::{ChannelMessageHandler, HTLCFailChannelUpdate, OptionalField};
20+
use ln::msgs::{ChannelMessageHandler, HTLCFailChannelUpdate, OptionalField, OnionHopDataLength};
2121
use util::test_utils;
2222
use util::events::{Event, EventsProvider, MessageSendEvent, MessageSendEventsProvider};
2323
use util::ser::{Writeable, Writer};
@@ -237,6 +237,11 @@ impl Writeable for BogusOnionHopData {
237237
writer.write_all(&self.data[..])
238238
}
239239
}
240+
impl OnionHopDataLength for BogusOnionHopData {
241+
fn encoding_length(&self) -> usize {
242+
self.data.len()
243+
}
244+
}
240245

241246
#[test]
242247
fn test_onion_failure() {

lightning/src/ln/onion_utils.rs

Lines changed: 16 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ use routing::router::RouteHop;
1313
use util::byte_utils;
1414
use util::chacha20::ChaCha20;
1515
use util::errors::{self, APIError};
16-
use util::ser::{Readable, Writeable, Writer};
16+
use util::ser::{Readable, Writeable};
1717
use util::logger::Logger;
1818

1919
use bitcoin::hashes::{Hash, HashEngine};
@@ -28,6 +28,7 @@ use bitcoin::secp256k1;
2828

2929
use std::io::Cursor;
3030
use std::ops::Deref;
31+
use ln::msgs::OnionHopDataLength;
3132

3233
pub(super) struct OnionKeys {
3334
#[cfg(test)]
@@ -183,10 +184,9 @@ fn shift_arr_right(arr: &mut [u8; ONION_DATA_LEN], amt: usize) {
183184
pub(super) fn route_size_insane(payloads: &Vec<msgs::OnionHopData>) -> bool {
184185
let mut len = 0;
185186
for payload in payloads.iter() {
186-
let mut payload_len = Writer::new();
187-
payload.write(&mut payload_len).expect("Failed to calculate length");
188-
assert!(payload_len.len() + 32 < ONION_DATA_LEN);
189-
len += payload_len.len() + 32;
187+
let encoding_length = payload.encoding_length();
188+
assert!(encoding_length + 32 < ONION_DATA_LEN);
189+
len += encoding_length + 32;
190190
if len > ONION_DATA_LEN {
191191
return true;
192192
}
@@ -207,7 +207,7 @@ pub(super) fn construct_onion_packet(payloads: Vec<msgs::OnionHopData>, onion_ke
207207
#[cfg(test)]
208208
// Used in testing to write bogus OnionHopDatas, which is otherwise not representable in
209209
// msgs::OnionHopData.
210-
pub(super) fn construct_onion_packet_bogus_hopdata<HD: Writeable>(payloads: Vec<HD>, onion_keys: Vec<OnionKeys>, prng_seed: [u8; 32], associated_data: &PaymentHash) -> msgs::OnionPacket {
210+
pub(super) fn construct_onion_packet_bogus_hopdata<HD: OnionHopDataLength>(payloads: Vec<HD>, onion_keys: Vec<OnionKeys>, prng_seed: [u8; 32], associated_data: &PaymentHash) -> msgs::OnionPacket {
211211
let mut packet_data = [0; ONION_DATA_LEN];
212212

213213
let mut chacha = ChaCha20::new(&prng_seed, &[0; 8]);
@@ -217,7 +217,7 @@ pub(super) fn construct_onion_packet_bogus_hopdata<HD: Writeable>(payloads: Vec<
217217
}
218218

219219
/// panics if route_size_insane(paylods)
220-
fn construct_onion_packet_with_init_noise<HD: Writeable>(mut payloads: Vec<HD>, onion_keys: Vec<OnionKeys>, mut packet_data: [u8; ONION_DATA_LEN], associated_data: &PaymentHash) -> msgs::OnionPacket {
220+
fn construct_onion_packet_with_init_noise<HD: OnionHopDataLength>(mut payloads: Vec<HD>, onion_keys: Vec<OnionKeys>, mut packet_data: [u8; ONION_DATA_LEN], associated_data: &PaymentHash) -> msgs::OnionPacket {
221221
let filler = {
222222
const ONION_HOP_DATA_LEN: usize = 65; // We may decrease this eventually after TLV is common
223223
let mut res = Vec::with_capacity(ONION_HOP_DATA_LEN * (payloads.len() - 1));
@@ -232,9 +232,8 @@ fn construct_onion_packet_with_init_noise<HD: Writeable>(mut payloads: Vec<HD>,
232232
chacha.process_in_place(&mut dummy); // We don't have a seek function :(
233233
}
234234

235-
let mut payload_len = Writer::new();
236-
payload.write(&mut payload_len).expect("Failed to calculate length");
237-
pos += payload_len.len() + 32;
235+
let payload_len = payload.encoding_length();
236+
pos += payload_len + 32;
238237
assert!(pos <= ONION_DATA_LEN);
239238

240239
res.resize(pos, 0u8);
@@ -245,9 +244,7 @@ fn construct_onion_packet_with_init_noise<HD: Writeable>(mut payloads: Vec<HD>,
245244

246245
let mut hmac_res = [0; 32];
247246
for (i, (payload, keys)) in payloads.iter_mut().zip(onion_keys.iter()).rev().enumerate() {
248-
let mut payload_len = Writer::new();
249-
payload.write(&mut payload_len).expect("Failed to calculate length");
250-
let len = payload_len.len();
247+
let len = payload.encoding_length();
251248
shift_arr_right(&mut packet_data, len + 32);
252249
packet_data[0..len].copy_from_slice(&payload.encode()[..]);
253250
packet_data[len..(len + 32)].copy_from_slice(&hmac_res);
@@ -490,6 +487,7 @@ mod tests {
490487
use bitcoin::secp256k1::key::{PublicKey,SecretKey};
491488

492489
use super::OnionKeys;
490+
use ln::msgs::OnionHopDataLength;
493491

494492
fn build_test_onion_keys() -> Vec<OnionKeys> {
495493
// Keys from BOLT 4, used in both test vector tests
@@ -649,6 +647,11 @@ mod tests {
649647
writer.write_all(&self.data[..])
650648
}
651649
}
650+
impl OnionHopDataLength for RawOnionHopData {
651+
fn encoding_length(&self) -> usize {
652+
self.data.len()
653+
}
654+
}
652655

653656
#[test]
654657
fn variable_length_onion_vectors() {

lightning/src/util/ser.rs

Lines changed: 106 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
//! A very simple serialization framework which is used to serialize/deserialize messages as well
1111
//! as ChannelsManagers and ChannelMonitors.
1212
13-
use std::io::{Read, Write};
13+
use std::io::{Read, Write, Cursor};
1414
use std::collections::HashMap;
1515
use std::hash::Hash;
1616
use std::sync::Mutex;
@@ -62,6 +62,32 @@ impl Writer {
6262
pub fn len(&self) -> usize {
6363
self.0.len()
6464
}
65+
66+
// Write the data with a varint length prefix.
67+
// Use length_adjust to add to the length for special cases, such as writing an Option.
68+
pub(crate) fn write_all_with_length<T: Writeable>(&mut self, data: &T, length_adjust: usize) -> Result<(), ::std::io::Error> {
69+
// Most common case is a single byte length prefix, so leave a space for that
70+
self.0.push(0);
71+
let start = self.len();
72+
data.write(self).expect("No in-memory data may fail to serialize");
73+
self.insert_length(start, length_adjust)
74+
}
75+
76+
fn insert_length(&mut self, slice_start: usize, length_adjust: usize) -> Result<(), ::std::io::Error> {
77+
let slice_end = self.len();
78+
let length = slice_end - slice_start + length_adjust;
79+
let big_size = BigSize(length as u64);
80+
let len_len = big_size.expected_length();
81+
// At this point we have a placeholder for a varint of length 1 just before the start of data
82+
// Shift the slice to make space for the length. If len_len is 1, no shift is needed.
83+
for _ in 1..len_len {
84+
self.0.insert(slice_start, 0);
85+
}
86+
let mut cursor = Cursor::new(&mut self.0);
87+
cursor.set_position((slice_start - 1) as u64);
88+
// Write the length in the space we created before the start of data
89+
BigSize(length as u64).write_write(&mut cursor)
90+
}
6591
}
6692

6793
impl Write for Writer {
@@ -225,27 +251,42 @@ impl Readable for U48 {
225251
/// you're looking for an example of a variable-length integer to use for your own project, move
226252
/// along, this is a rather poor design.
227253
pub(crate) struct BigSize(pub u64);
228-
impl Writeable for BigSize {
229-
#[inline]
230-
fn write(&self, writer: &mut Writer) -> Result<(), ::std::io::Error> {
254+
impl BigSize {
255+
pub fn write_write<W: Write>(&self, write: &mut W) -> Result<(), ::std::io::Error> {
231256
match self.0 {
232257
0...0xFC => {
233-
(self.0 as u8).write(writer)
258+
write.write_all(&[self.0 as u8])
234259
},
235260
0xFD...0xFFFF => {
236-
0xFDu8.write(writer)?;
237-
(self.0 as u16).write(writer)
261+
write.write_all(&[0xFDu8])?;
262+
write.write_all(&be16_to_array(self.0 as u16))
238263
},
239264
0x10000...0xFFFFFFFF => {
240-
0xFEu8.write(writer)?;
241-
(self.0 as u32).write(writer)
265+
write.write_all(&[0xFEu8])?;
266+
write.write_all(&be32_to_array(self.0 as u32))
242267
},
243268
_ => {
244-
0xFFu8.write(writer)?;
245-
(self.0 as u64).write(writer)
269+
write.write_all(&[0xFFu8])?;
270+
write.write_all(&be64_to_array(self.0 as u64))
246271
},
247272
}
248273
}
274+
275+
pub fn expected_length(&self) -> usize {
276+
match self.0 {
277+
0...0xFC => 1,
278+
0xFD...0xFFFF => 3,
279+
0x10000...0xFFFFFFFF => 5,
280+
_ => 9,
281+
}
282+
}
283+
}
284+
285+
impl Writeable for BigSize {
286+
#[inline]
287+
fn write(&self, writer: &mut Writer) -> Result<(), ::std::io::Error> {
288+
self.write_write(writer)
289+
}
249290
}
250291
impl Readable for BigSize {
251292
#[inline]
@@ -609,10 +650,7 @@ impl<T: Writeable> Writeable for Option<T> {
609650
match *self {
610651
None => 0u8.write(w)?,
611652
Some(ref data) => {
612-
let mut len_calc = Writer::new();
613-
data.write(&mut len_calc).expect("No in-memory data may fail to serialize");
614-
BigSize(len_calc.len() as u64 + 1).write(w)?;
615-
data.write(w)?;
653+
w.write_all_with_length(data, 1)?;
616654
}
617655
}
618656
Ok(())
@@ -732,3 +770,56 @@ impl<A: Writeable, B: Writeable> Writeable for (A, B) {
732770
self.1.write(w)
733771
}
734772
}
773+
774+
#[cfg(test)]
775+
mod tests {
776+
use std::io::Cursor;
777+
use util::ser::*;
778+
779+
#[test]
780+
fn big_size() {
781+
for i in vec![0, 0xfc, 0xfd, 0xffff, 0x10000, 0xFFFFFFFF, 0xFFFFFFFFF] {
782+
let mut writer = Writer::new();
783+
let size = BigSize(i);
784+
size.write(&mut writer).unwrap();
785+
assert_eq!(writer.len(), size.expected_length());
786+
let read_size = BigSize::read(&mut Cursor::new(writer.0)).unwrap();
787+
assert_eq!(read_size.0, size.0);
788+
}
789+
}
790+
791+
#[test]
792+
fn length_prefix_zero() {
793+
impl_array!(0);
794+
let mut writer = Writer::new();
795+
writer.write_all_with_length(&[], 0).unwrap();
796+
// Writing a zero-length length-prefixed item should result in just one zero byte
797+
assert_eq!(writer.0, vec![0]);
798+
}
799+
800+
#[test]
801+
fn length_prefix_one() {
802+
impl_array!(1);
803+
let mut writer = Writer::new();
804+
writer.write_all_with_length(&[0x11u8], 0).unwrap();
805+
assert_eq!(writer.0, vec![1, 0x11]);
806+
}
807+
808+
#[test]
809+
fn length_prefix() {
810+
for size in vec![3, 0xfc, 0xfd, 0xffff, 0x10000] {
811+
let mut writer = Writer::new();
812+
let mut data = Vec::new();
813+
// A Vec serializes its own 2 bytes length, so adjust this
814+
data.resize(size-3, 0x11u8);
815+
// Writing an Option is convenient for round-trip checking of length-prefixed writes
816+
Some(data.clone()).write(&mut writer).unwrap();
817+
let mut reader = Cursor::new(&writer.0);
818+
let read_data: Option<Vec<u8>> = Readable::read(&mut reader).unwrap();
819+
assert_eq!(data, read_data.unwrap());
820+
// Ensure everything was consumed
821+
assert_eq!(reader.position() as usize, writer.len());
822+
println!("{}", writer.len())
823+
}
824+
}
825+
}

lightning/src/util/ser_macros.rs

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,13 +9,10 @@
99

1010
macro_rules! encode_tlv {
1111
($stream: expr, {$(($type: expr, $field: expr)),*}) => { {
12-
use util::ser::{BigSize, Writer};
12+
use util::ser::BigSize;
1313
$(
1414
BigSize($type).write($stream)?;
15-
let mut len_calc = Writer::new();
16-
$field.write(&mut len_calc)?;
17-
BigSize(len_calc.len() as u64).write($stream)?;
18-
$field.write($stream)?;
15+
$stream.write_all_with_length(&$field, 0)?;
1916
)*
2017
} }
2118
}

0 commit comments

Comments
 (0)