Skip to content

Commit 50bee6d

Browse files
committed
Improve fuzz test coverage by building a resolver
1 parent 1eb8621 commit 50bee6d

File tree

1 file changed

+82
-4
lines changed

1 file changed

+82
-4
lines changed

fuzz/src/parse.rs

Lines changed: 82 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,14 @@
99

1010
use bitcoin::Network;
1111

12-
use bitcoin_payment_instructions::hrn_resolution::DummyHrnResolver;
12+
use bitcoin_payment_instructions::amount::Amount;
13+
use bitcoin_payment_instructions::hrn::HumanReadableName;
14+
use bitcoin_payment_instructions::hrn_resolution::{
15+
DummyHrnResolver, HrnResolution, HrnResolutionFuture, HrnResolver, LNURLResolutionFuture,
16+
};
1317
use bitcoin_payment_instructions::PaymentInstructions;
1418

19+
use std::sync::Mutex;
1520
use std::future::Future;
1621
use std::pin::Pin;
1722
use std::task::{Context, Poll, RawWaker, RawWakerVTable, Waker};
@@ -25,13 +30,86 @@ fn clone_fn(p: *const ()) -> RawWaker {
2530

2631
fn dummy_fn(_: *const ()) {}
2732

33+
struct Resolver<'a>(
34+
Mutex<(Option<Result<HrnResolution, &'static str>>, Option<Result<&'a [u8], &'static str>>)>,
35+
);
36+
37+
impl HrnResolver for Resolver<'_> {
38+
fn resolve_hrn<'a>(&'a self, _: &'a HumanReadableName) -> HrnResolutionFuture<'a> {
39+
Box::pin(async {
40+
let us = self.0.lock().unwrap();
41+
us.0.take().unwrap()
42+
})
43+
}
44+
45+
fn resolve_lnurl<'a>(&'a self, _: String, _: Amount, _: [u8; 32]) -> LNURLResolutionFuture<'a> {
46+
Box::pin(async {
47+
let us = self.0.lock().unwrap();
48+
us.1.take().unwrap()
49+
})
50+
}
51+
}
52+
2853
#[inline]
29-
pub fn do_test(data: &[u8]) {
54+
pub fn do_test(mut data: &[u8]) {
55+
if data.len() < 2 {
56+
return;
57+
}
58+
59+
let mut bolt11 = None;
60+
61+
let resolution = if (data[0] & 0b1100_0000) == 0b1100_0000 {
62+
Err("HRN resolution failed in fuzzing")
63+
} else if (data[0] & 0b1100_0000) == 0b1000_0000 {
64+
let result_len = (((data[0] & 0b0011_1111) as usize) << 8) | (data[1] as usize);
65+
if data.len() <= result_len + 2 {
66+
return;
67+
}
68+
let result = data[2..result_len + 2].to_vec();
69+
70+
data = &data[result_len + 2..];
71+
HrnResolution::DNSSEC { result, proof: Some(vec![8; 32]) }
72+
} else {
73+
if data.len() <= 16 + 2 {
74+
return;
75+
}
76+
let lnurl_resolution_fails = (data[0] & 0b1100_0000) == 0b0100_0000;
77+
let min = Amount::from_milli_sats(u64::from_le_bytes((&data[..8]).try_into().unwrap()));
78+
data = &data[8..];
79+
let max = Amount::from_milli_sats(u64::from_le_bytes((&data[..8]).try_into().unwrap()));
80+
data = &data[8..];
81+
82+
let bolt11_len = ((data[0] as usize) << 8) | (data[1] as usize);
83+
if data.len() <= bolt11_len + 2 {
84+
return;
85+
}
86+
87+
bolt11 = if lnurl_resolution_fails {
88+
Some(Err("LNURL resolution failed in fuzzing"))
89+
} else {
90+
Some(Ok(&data[2..bolt11_len + 2]))
91+
};
92+
data = &data[bolt11_len + 2..];
93+
94+
let mut expected_description_hash = [0; 32];
95+
expected_description_hash[31] = 42;
96+
97+
HrnResolution::LNURLPay {
98+
min_value: if let Ok(min) = min { min } else { return },
99+
max_value: if let Ok(max) = max { max } else { return },
100+
expected_description_hash,
101+
recipient_description: Some("Payment in fuzzing".to_owned()),
102+
callback: "https://callback.uri/in/fuzzing".to_owned(),
103+
}
104+
};
105+
106+
let resolver = Resolver(Mutex::new((Some(resolution), bolt11)));
107+
30108
if let Ok(s) = std::str::from_utf8(data) {
31109
let waker = unsafe { Waker::from_raw(clone_fn(std::ptr::null())) };
32110

33-
let fut = PaymentInstructions::parse(s, Network::Bitcoin, &DummyHrnResolver, true);
34-
// With a DummyHrnResolver, all instructions should resolve on the first `poll`.
111+
let fut = PaymentInstructions::parse(s, Network::Bitcoin, &resolver, true);
112+
// With our resolver, all instructions should resolve on the first `poll`.
35113
let res = Future::poll(Pin::new(&mut Box::pin(fut)), &mut Context::from_waker(&waker));
36114
assert!(matches!(res, Poll::Ready(_)));
37115

0 commit comments

Comments
 (0)