178 lines
6.0 KiB
Rust
178 lines
6.0 KiB
Rust
use std::collections::BTreeMap;
|
|
|
|
use super::DhtBencodeValue;
|
|
|
|
/// Encodes one DHT bencode value into a byte buffer.
|
|
fn dht_encode_value(value: &DhtBencodeValue, out: &mut Vec<u8>) {
|
|
match value {
|
|
DhtBencodeValue::Int(number) => {
|
|
out.push(b'i');
|
|
out.extend_from_slice(number.to_string().as_bytes());
|
|
out.push(b'e');
|
|
}
|
|
DhtBencodeValue::Bytes(bytes) => {
|
|
out.extend_from_slice(bytes.len().to_string().as_bytes());
|
|
out.push(b':');
|
|
out.extend_from_slice(bytes);
|
|
}
|
|
DhtBencodeValue::List(values) => {
|
|
out.push(b'l');
|
|
for item in values {
|
|
dht_encode_value(item, out);
|
|
}
|
|
out.push(b'e');
|
|
}
|
|
DhtBencodeValue::Dict(values) => {
|
|
out.push(b'd');
|
|
for (key, value) in values {
|
|
out.extend_from_slice(key.len().to_string().as_bytes());
|
|
out.push(b':');
|
|
out.extend_from_slice(key);
|
|
dht_encode_value(value, out);
|
|
}
|
|
out.push(b'e');
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Encodes a DHT dictionary into canonical bencode bytes.
|
|
pub(super) fn dht_encode_dict(dict: &BTreeMap<Vec<u8>, DhtBencodeValue>) -> Vec<u8> {
|
|
let mut out = Vec::new();
|
|
dht_encode_value(&DhtBencodeValue::Dict(dict.clone()), &mut out);
|
|
out
|
|
}
|
|
|
|
/// Parses one DHT bencode value and returns the decoded value plus next index.
|
|
pub(super) fn dht_parse_value(
|
|
input: &[u8],
|
|
index: usize,
|
|
) -> Result<(DhtBencodeValue, usize), String> {
|
|
match input.get(index).copied() {
|
|
Some(b'i') => dht_parse_int(input, index + 1),
|
|
Some(b'l') => dht_parse_list(input, index + 1),
|
|
Some(b'd') => dht_parse_dict(input, index + 1),
|
|
Some(byte) if byte.is_ascii_digit() => dht_parse_bytes(input, index),
|
|
Some(_) => Err("invalid dht bencode value".to_owned()),
|
|
None => Err("unexpected end of dht bencode input".to_owned()),
|
|
}
|
|
}
|
|
|
|
/// Parses one DHT bencode integer starting at `index`.
|
|
fn dht_parse_int(input: &[u8], index: usize) -> Result<(DhtBencodeValue, usize), String> {
|
|
let mut cursor = index;
|
|
while cursor < input.len() && input[cursor] != b'e' {
|
|
cursor += 1;
|
|
}
|
|
if cursor >= input.len() {
|
|
return Err("unterminated dht integer".to_owned());
|
|
}
|
|
let text = std::str::from_utf8(&input[index..cursor])
|
|
.map_err(|_| "invalid dht integer bytes".to_owned())?;
|
|
let value = text
|
|
.parse::<i64>()
|
|
.map_err(|_| "invalid dht integer value".to_owned())?;
|
|
Ok((DhtBencodeValue::Int(value), cursor + 1))
|
|
}
|
|
|
|
/// Parses one DHT bencode list starting at `index`.
|
|
fn dht_parse_list(input: &[u8], index: usize) -> Result<(DhtBencodeValue, usize), String> {
|
|
let mut values = Vec::new();
|
|
let mut cursor = index;
|
|
while cursor < input.len() {
|
|
if input[cursor] == b'e' {
|
|
return Ok((DhtBencodeValue::List(values), cursor + 1));
|
|
}
|
|
let (value, next) = dht_parse_value(input, cursor)?;
|
|
values.push(value);
|
|
cursor = next;
|
|
}
|
|
Err("unterminated dht list".to_owned())
|
|
}
|
|
|
|
/// Parses one DHT bencode dictionary starting at `index`.
|
|
fn dht_parse_dict(input: &[u8], index: usize) -> Result<(DhtBencodeValue, usize), String> {
|
|
let mut map = BTreeMap::new();
|
|
let mut cursor = index;
|
|
while cursor < input.len() {
|
|
if input[cursor] == b'e' {
|
|
return Ok((DhtBencodeValue::Dict(map), cursor + 1));
|
|
}
|
|
let (key, key_end) = dht_parse_bytes_raw(input, cursor)?;
|
|
let (value, value_end) = dht_parse_value(input, key_end)?;
|
|
map.insert(key, value);
|
|
cursor = value_end;
|
|
}
|
|
Err("unterminated dht dictionary".to_owned())
|
|
}
|
|
|
|
/// Parses one DHT bencode byte string starting at `index`.
|
|
fn dht_parse_bytes(input: &[u8], index: usize) -> Result<(DhtBencodeValue, usize), String> {
|
|
let (bytes, next) = dht_parse_bytes_raw(input, index)?;
|
|
Ok((DhtBencodeValue::Bytes(bytes), next))
|
|
}
|
|
|
|
/// Parses one raw DHT bencode byte string and returns its bytes plus next index.
|
|
fn dht_parse_bytes_raw(input: &[u8], index: usize) -> Result<(Vec<u8>, usize), String> {
|
|
let mut cursor = index;
|
|
while cursor < input.len() && input[cursor].is_ascii_digit() {
|
|
cursor += 1;
|
|
}
|
|
if cursor == index || cursor >= input.len() || input[cursor] != b':' {
|
|
return Err("invalid dht byte string".to_owned());
|
|
}
|
|
let length = std::str::from_utf8(&input[index..cursor])
|
|
.map_err(|_| "invalid dht byte string length".to_owned())?
|
|
.parse::<usize>()
|
|
.map_err(|_| "invalid dht byte string length".to_owned())?;
|
|
let start = cursor + 1;
|
|
let end = start.saturating_add(length);
|
|
if end > input.len() {
|
|
return Err("truncated dht byte string".to_owned());
|
|
}
|
|
Ok((input[start..end].to_vec(), end))
|
|
}
|
|
|
|
/// Looks up a raw byte-string field inside a DHT dictionary.
|
|
pub(super) fn dht_dict_get_bytes(
|
|
dict: &BTreeMap<Vec<u8>, DhtBencodeValue>,
|
|
key: &[u8],
|
|
) -> Option<Vec<u8>> {
|
|
match dict.get(key) {
|
|
Some(DhtBencodeValue::Bytes(bytes)) => Some(bytes.clone()),
|
|
_ => None,
|
|
}
|
|
}
|
|
|
|
/// Looks up an integer field inside a DHT dictionary.
|
|
pub(super) fn dht_dict_get_int(
|
|
dict: &BTreeMap<Vec<u8>, DhtBencodeValue>,
|
|
key: &[u8],
|
|
) -> Option<i64> {
|
|
match dict.get(key) {
|
|
Some(DhtBencodeValue::Int(value)) => Some(*value),
|
|
_ => None,
|
|
}
|
|
}
|
|
|
|
/// Looks up a nested dictionary field inside a DHT dictionary.
|
|
pub(super) fn dht_dict_get_dict<'a>(
|
|
dict: &'a BTreeMap<Vec<u8>, DhtBencodeValue>,
|
|
key: &[u8],
|
|
) -> Option<&'a BTreeMap<Vec<u8>, DhtBencodeValue>> {
|
|
match dict.get(key) {
|
|
Some(DhtBencodeValue::Dict(value)) => Some(value),
|
|
_ => None,
|
|
}
|
|
}
|
|
|
|
/// Looks up a list field inside a DHT dictionary.
|
|
pub(super) fn dht_dict_get_list(
|
|
dict: &BTreeMap<Vec<u8>, DhtBencodeValue>,
|
|
key: &[u8],
|
|
) -> Option<Vec<DhtBencodeValue>> {
|
|
match dict.get(key) {
|
|
Some(DhtBencodeValue::List(values)) => Some(values.clone()),
|
|
_ => None,
|
|
}
|
|
}
|