1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
|
use bitcoin::{hashes::Hash, Txid};
use url::Url;
#[derive(Debug, Clone, Copy, thiserror::Error)]
pub enum DecodeErr {
#[error("Unknown first byte: {0}")]
UnknownFirstByte(u8),
#[error(transparent)]
UriPack(#[from] uri_pack::DecodeErr),
#[error(transparent)]
Hash(#[from] bitcoin::hashes::Error),
#[error("Unexpected end of file")]
UnexpectedEOF,
}
/// Encoded metadata for outgoing transaction
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Info {
Transaction { wtid: [u8; 32], url: Url },
Bounce { bounced: Txid },
}
// We leave a potential special meaning for u8::MAX
const BOUNCE_BYTE: u8 = u8::MAX - 1;
pub fn encode_info(info: &Info) -> Vec<u8> {
let mut buffer = Vec::new();
match info {
Info::Transaction { wtid, url } => {
buffer.push(if url.scheme() == "http" { 1 } else { 0 });
buffer.extend_from_slice(wtid);
let parts = format!("{}{}", url.domain().unwrap_or(""), url.path());
let packed = uri_pack::pack_uri(&parts).unwrap();
buffer.extend_from_slice(&packed);
return buffer;
}
Info::Bounce { bounced: id } => {
buffer.push(BOUNCE_BYTE);
buffer.extend_from_slice(id.as_ref());
}
}
return buffer;
}
pub fn decode_info(bytes: &[u8]) -> Result<Info, DecodeErr> {
if bytes.is_empty() {
return Err(DecodeErr::UnexpectedEOF);
}
match bytes[0] {
0..=1 => {
if bytes.len() < 33 {
return Err(DecodeErr::UnexpectedEOF);
}
let packed = format!(
"http{}://{}",
if bytes[0] == 0 { "s" } else { "" },
uri_pack::unpack_uri(&bytes[33..])?,
);
let url = Url::parse(&packed).unwrap();
Ok(Info::Transaction {
wtid: bytes[1..33].try_into().unwrap(),
url,
})
}
BOUNCE_BYTE => Ok(Info::Bounce {
bounced: Txid::from_slice(&bytes[1..])?,
}),
unknown => Err(DecodeErr::UnknownFirstByte(unknown)),
}
}
#[cfg(test)]
mod test {
use bitcoin::{hashes::Hash, Txid};
use btc_wire::test::rand_key;
use url::Url;
use crate::info::{decode_info, encode_info, Info};
#[test]
fn decode_encode_tx() {
let urls = [
"https://git.taler.net/",
"https://git.taler.net/depolymerization.git/",
"http://git.taler.net/",
"http://git.taler.net/depolymerization.git/",
];
for url in urls {
let wtid = rand_key();
let url = Url::parse(url).unwrap();
let info = Info::Transaction { wtid, url };
let encode = encode_info(&info);
let decoded = decode_info(&encode).unwrap();
assert_eq!(decoded, info);
}
}
#[test]
fn decode_encode_bounce() {
for _ in 0..4 {
let id = rand_key();
let info = Info::Bounce {
bounced: Txid::from_slice(&id).unwrap(),
};
let encode = encode_info(&info);
let decoded = decode_info(&encode).unwrap();
assert_eq!(decoded, info);
}
}
}
|