bittorrent-dht: convert some fields to byte arrays

Some fields that were previously strings are not always value UTF-8
data, instead the protocol specification refers to them as strings of
bytes, so in other words byte arrays.

Currently fields converted are:
- client_version
- info_hash
- response.id
- request.id
- nodes
- token
pull/8113/head
Jason Ish 4 years ago committed by Victor Julien
parent 065f3ab9f1
commit 3cb50592ed

@ -38,8 +38,8 @@ pub struct BitTorrentDHTTransaction {
pub request: Option<BitTorrentDHTRequest>, pub request: Option<BitTorrentDHTRequest>,
pub response: Option<BitTorrentDHTResponse>, pub response: Option<BitTorrentDHTResponse>,
pub error: Option<BitTorrentDHTError>, pub error: Option<BitTorrentDHTError>,
pub transaction_id: String, pub transaction_id: Vec<u8>,
pub client_version: Option<String>, pub client_version: Option<Vec<u8>>,
tx_data: AppLayerTxData, tx_data: AppLayerTxData,
} }
@ -52,7 +52,7 @@ impl BitTorrentDHTTransaction {
request: None, request: None,
response: None, response: None,
error: None, error: None,
transaction_id: String::new(), transaction_id: Vec::new(),
client_version: None, client_version: None,
tx_data: AppLayerTxData::new(), tx_data: AppLayerTxData::new(),
} }

@ -21,9 +21,9 @@ use crate::jsonbuilder::{JsonBuilder, JsonError};
fn log_bittorrent_dht( fn log_bittorrent_dht(
tx: &BitTorrentDHTTransaction, js: &mut JsonBuilder, tx: &BitTorrentDHTTransaction, js: &mut JsonBuilder,
) -> Result<(), JsonError> { ) -> Result<(), JsonError> {
js.set_string("transaction_id", &tx.transaction_id)?; js.set_hex("transaction_id", &tx.transaction_id)?;
if let Some(client_version) = &tx.client_version { if let Some(client_version) = &tx.client_version {
js.set_string("client_version", client_version)?; js.set_hex("client_version", client_version)?;
} }
if let Some(request_type) = &tx.request_type { if let Some(request_type) = &tx.request_type {
js.set_string("request_type", request_type)?; js.set_string("request_type", request_type)?;
@ -36,12 +36,12 @@ fn log_bittorrent_dht(
}; };
if let Some(request) = &tx.request { if let Some(request) = &tx.request {
js.open_object("request")?; js.open_object("request")?;
js.set_string("id", &request.id)?; js.set_hex("id", &request.id)?;
if let Some(target) = &request.target { if let Some(target) = &request.target {
js.set_string("target", target)?; js.set_string("target", target)?;
} }
if let Some(info_hash) = &request.info_hash { if let Some(info_hash) = &request.info_hash {
js.set_string("info_hash", info_hash)?; js.set_hex("info_hash", info_hash)?;
} }
if let Some(token) = &request.token { if let Some(token) = &request.token {
js.set_string("token", token)?; js.set_string("token", token)?;
@ -56,9 +56,9 @@ fn log_bittorrent_dht(
}; };
if let Some(response) = &tx.response { if let Some(response) = &tx.response {
js.open_object("response")?; js.open_object("response")?;
js.set_string("id", &response.id)?; js.set_hex("id", &response.id)?;
if let Some(nodes) = &response.nodes { if let Some(nodes) = &response.nodes {
js.set_string("nodes", nodes)?; js.set_hex("nodes", nodes)?;
} }
if let Some(values) = &response.values { if let Some(values) = &response.values {
js.open_array("values")?; js.open_array("values")?;
@ -68,7 +68,7 @@ fn log_bittorrent_dht(
js.close()?; js.close()?;
} }
if let Some(token) = &response.token { if let Some(token) = &response.token {
js.set_string("token", token)?; js.set_hex("token", token)?;
} }
js.close()?; js.close()?;
}; };

@ -24,11 +24,11 @@ use bendy::decoding::{Decoder, Error, FromBencode, Object, ResultExt};
#[derive(Debug, Eq, PartialEq)] #[derive(Debug, Eq, PartialEq)]
pub struct BitTorrentDHTRequest { pub struct BitTorrentDHTRequest {
/// q = * - 20 byte string, sender's node ID in network byte order /// q = * - 20 byte string, sender's node ID in network byte order
pub id: String, pub id: Vec<u8>,
/// q = find_node - target node ID /// q = find_node - target node ID
pub target: Option<String>, pub target: Option<String>,
/// q = get_peers/announce_peer - 20-byte info hash of target torrent /// q = get_peers/announce_peer - 20-byte info hash of target torrent
pub info_hash: Option<String>, pub info_hash: Option<Vec<u8>>,
/// q = announce_peer - token key received from previous get_peers query /// q = announce_peer - token key received from previous get_peers query
pub token: Option<String>, pub token: Option<String>,
/// q = announce_peer - 0 or 1, if 1 ignore provided port and /// q = announce_peer - 0 or 1, if 1 ignore provided port and
@ -41,15 +41,15 @@ pub struct BitTorrentDHTRequest {
#[derive(Debug, Eq, PartialEq)] #[derive(Debug, Eq, PartialEq)]
pub struct BitTorrentDHTResponse { pub struct BitTorrentDHTResponse {
/// q = * - 20 byte string, receiver's node ID in network byte order /// q = * - 20 byte string, receiver's node ID in network byte order
pub id: String, pub id: Vec<u8>,
/// q = find_node/get_peers - compact node info for target node or /// q = find_node/get_peers - compact node info for target node or
/// K(8) closest good nodes in routing table /// K(8) closest good nodes in routing table
pub nodes: Option<String>, pub nodes: Option<Vec<u8>>,
/// q = get_peers - list of compact peer infos /// q = get_peers - list of compact peer infos
pub values: Option<Vec<String>>, pub values: Option<Vec<String>>,
/// q = get_peers - token key required for sender's future /// q = get_peers - token key required for sender's future
/// announce_peer query /// announce_peer query
pub token: Option<String>, pub token: Option<Vec<u8>>,
} }
#[derive(Debug, Eq, PartialEq)] #[derive(Debug, Eq, PartialEq)]
@ -94,9 +94,7 @@ impl FromBencode for BitTorrentDHTRequest {
while let Some(pair) = dict_dec.next_pair()? { while let Some(pair) = dict_dec.next_pair()? {
match pair { match pair {
(b"id", value) => { (b"id", value) => {
id = String::decode_bencode_object(value) id = value.try_into_bytes().context("id").map(Some)?;
.context("id")
.map(Some)?;
} }
(b"target", value) => { (b"target", value) => {
target = String::decode_bencode_object(value) target = String::decode_bencode_object(value)
@ -104,9 +102,10 @@ impl FromBencode for BitTorrentDHTRequest {
.map(Some)?; .map(Some)?;
} }
(b"info_hash", value) => { (b"info_hash", value) => {
info_hash = String::decode_bencode_object(value) info_hash = value
.try_into_bytes()
.context("info_hash") .context("info_hash")
.map(Some)?; .map(|v| Some(v.to_vec()))?;
} }
(b"token", value) => { (b"token", value) => {
token = String::decode_bencode_object(value) token = String::decode_bencode_object(value)
@ -130,7 +129,7 @@ impl FromBencode for BitTorrentDHTRequest {
let id = id.ok_or_else(|| Error::missing_field("id"))?; let id = id.ok_or_else(|| Error::missing_field("id"))?;
Ok(BitTorrentDHTRequest { Ok(BitTorrentDHTRequest {
id, id: id.to_vec(),
target, target,
info_hash, info_hash,
token, token,
@ -170,14 +169,13 @@ impl FromBencode for BitTorrentDHTResponse {
while let Some(pair) = dict_dec.next_pair()? { while let Some(pair) = dict_dec.next_pair()? {
match pair { match pair {
(b"id", value) => { (b"id", value) => {
id = String::decode_bencode_object(value) id = value.try_into_bytes().context("id").map(Some)?;
.context("id")
.map(Some)?;
} }
(b"nodes", value) => { (b"nodes", value) => {
nodes = String::decode_bencode_object(value) nodes = value
.try_into_bytes()
.context("nodes") .context("nodes")
.map(Some)?; .map(|v| Some(v.to_vec()))?;
} }
(b"values", value) => { (b"values", value) => {
values = Vec::decode_bencode_object(value) values = Vec::decode_bencode_object(value)
@ -185,9 +183,10 @@ impl FromBencode for BitTorrentDHTResponse {
.map(Some)?; .map(Some)?;
} }
(b"token", value) => { (b"token", value) => {
token = String::decode_bencode_object(value) token = value
.try_into_bytes()
.context("token") .context("token")
.map(Some)?; .map(|v| Some(v.to_vec()))?;
} }
(_unknown_field, _) => {} (_unknown_field, _) => {}
} }
@ -196,7 +195,7 @@ impl FromBencode for BitTorrentDHTResponse {
let id = id.ok_or_else(|| Error::missing_field("id"))?; let id = id.ok_or_else(|| Error::missing_field("id"))?;
Ok(BitTorrentDHTResponse { Ok(BitTorrentDHTResponse {
id, id: id.to_vec(),
nodes, nodes,
values, values,
token, token,
@ -311,15 +310,14 @@ pub fn parse_bittorrent_dht_packet(
} }
(b"t", value) => { (b"t", value) => {
// transaction id found // transaction id found
transaction_id = String::decode_bencode_object(value) transaction_id = value.try_into_bytes().context("transaction_id").map(Some)?;
.context("transaction_id")
.map(Some)?;
} }
(b"v", value) => { (b"v", value) => {
// client version string found // client version string found
client_version = String::decode_bencode_object(value) client_version = value
.try_into_bytes()
.context("client_version") .context("client_version")
.map(Some)?; .map(|v| Some(v.to_vec()))?;
} }
(_unknown_field, _) => {} (_unknown_field, _) => {}
} }
@ -347,7 +345,9 @@ pub fn parse_bittorrent_dht_packet(
return Err(Error::missing_field("packet_type")); return Err(Error::missing_field("packet_type"));
} }
tx.transaction_id = transaction_id.ok_or_else(|| Error::missing_field("transaction_id"))?; tx.transaction_id = transaction_id
.ok_or_else(|| Error::missing_field("transaction_id"))?
.to_vec();
// Client version string is an optional field // Client version string is an optional field
tx.client_version = client_version; tx.client_version = client_version;
@ -361,19 +361,19 @@ mod tests {
#[test_case( #[test_case(
b"d2:id20:abcdefghij012345678912:implied_porti1e9:info_hash20:mnopqrstuvwxyz1234564:porti6881e5:token8:aoeusnthe", b"d2:id20:abcdefghij012345678912:implied_porti1e9:info_hash20:mnopqrstuvwxyz1234564:porti6881e5:token8:aoeusnthe",
BitTorrentDHTRequest { id: "abcdefghij0123456789".to_string(), implied_port: Some(1u8), info_hash: Some("mnopqrstuvwxyz123456".to_string()), port: Some(6881u16), token: Some("aoeusnth".to_string()), target: None } ; BitTorrentDHTRequest { id: b"abcdefghij0123456789".to_vec(), implied_port: Some(1u8), info_hash: Some(b"mnopqrstuvwxyz123456".to_vec()), port: Some(6881u16), token: Some("aoeusnth".to_string()), target: None } ;
"test request from bencode 1")] "test request from bencode 1")]
#[test_case( #[test_case(
b"d2:id20:abcdefghij0123456789e", b"d2:id20:abcdefghij0123456789e",
BitTorrentDHTRequest { id: "abcdefghij0123456789".to_string(), implied_port: None, info_hash: None, port: None, token: None, target: None } ; BitTorrentDHTRequest { id: b"abcdefghij0123456789".to_vec(), implied_port: None, info_hash: None, port: None, token: None, target: None } ;
"test request from bencode 2")] "test request from bencode 2")]
#[test_case( #[test_case(
b"d2:id20:abcdefghij01234567896:target20:mnopqrstuvwxyz123456e", b"d2:id20:abcdefghij01234567896:target20:mnopqrstuvwxyz123456e",
BitTorrentDHTRequest { id: "abcdefghij0123456789".to_string(), implied_port: None, info_hash: None, port: None, token: None, target: Some("mnopqrstuvwxyz123456".to_string()) } ; BitTorrentDHTRequest { id: b"abcdefghij0123456789".to_vec(), implied_port: None, info_hash: None, port: None, token: None, target: Some("mnopqrstuvwxyz123456".to_string()) } ;
"test request from bencode 3")] "test request from bencode 3")]
#[test_case( #[test_case(
b"d2:id20:abcdefghij01234567899:info_hash20:mnopqrstuvwxyz123456e", b"d2:id20:abcdefghij01234567899:info_hash20:mnopqrstuvwxyz123456e",
BitTorrentDHTRequest { id: "abcdefghij0123456789".to_string(), implied_port: None, info_hash: Some("mnopqrstuvwxyz123456".to_string()), port: None, token: None, target: None } ; BitTorrentDHTRequest { id: b"abcdefghij0123456789".to_vec(), implied_port: None, info_hash: Some(b"mnopqrstuvwxyz123456".to_vec()), port: None, token: None, target: None } ;
"test request from bencode 4")] "test request from bencode 4")]
fn test_request_from_bencode(encoded: &[u8], expected: BitTorrentDHTRequest) { fn test_request_from_bencode(encoded: &[u8], expected: BitTorrentDHTRequest) {
let decoded = BitTorrentDHTRequest::from_bencode(encoded).unwrap(); let decoded = BitTorrentDHTRequest::from_bencode(encoded).unwrap();
@ -411,27 +411,27 @@ mod tests {
#[test_case( #[test_case(
b"d2:id20:abcdefghij01234567895:token8:aoeusnth6:valueslee", b"d2:id20:abcdefghij01234567895:token8:aoeusnth6:valueslee",
BitTorrentDHTResponse { id: "abcdefghij0123456789".to_string(), token: Some("aoeusnth".to_string()), values: Some(vec![]), nodes: None } ; BitTorrentDHTResponse { id: b"abcdefghij0123456789".to_vec(), token: Some(b"aoeusnth".to_vec()), values: Some(vec![]), nodes: None } ;
"test response from bencode 1")] "test response from bencode 1")]
#[test_case( #[test_case(
b"d2:id20:abcdefghij01234567895:token8:aoeusnth6:valuesl6:axje.uee", b"d2:id20:abcdefghij01234567895:token8:aoeusnth6:valuesl6:axje.uee",
BitTorrentDHTResponse { id: "abcdefghij0123456789".to_string(), token: Some("aoeusnth".to_string()), values: Some(vec!["axje.u".to_string()]), nodes: None } ; BitTorrentDHTResponse { id: b"abcdefghij0123456789".to_vec(), token: Some(b"aoeusnth".to_vec()), values: Some(vec!["axje.u".to_string()]), nodes: None } ;
"test response from bencode 2")] "test response from bencode 2")]
#[test_case( #[test_case(
b"d2:id20:abcdefghij01234567895:token8:aoeusnth6:valuesl6:axje.u6:idhtnmee", b"d2:id20:abcdefghij01234567895:token8:aoeusnth6:valuesl6:axje.u6:idhtnmee",
BitTorrentDHTResponse { id: "abcdefghij0123456789".to_string(), token: Some("aoeusnth".to_string()), values: Some(vec!["axje.u".to_string(), "idhtnm".to_string()]), nodes: None } ; BitTorrentDHTResponse { id: b"abcdefghij0123456789".to_vec(), token: Some(b"aoeusnth".to_vec()), values: Some(vec!["axje.u".to_string(), "idhtnm".to_string()]), nodes: None } ;
"test response from bencode 3")] "test response from bencode 3")]
#[test_case( #[test_case(
b"d2:id20:mnopqrstuvwxyz123456e", b"d2:id20:mnopqrstuvwxyz123456e",
BitTorrentDHTResponse { id: "mnopqrstuvwxyz123456".to_string(), token: None, values: None, nodes: None } ; BitTorrentDHTResponse { id: b"mnopqrstuvwxyz123456".to_vec(), token: None, values: None, nodes: None } ;
"test response from bencode 4")] "test response from bencode 4")]
#[test_case( #[test_case(
b"d2:id20:0123456789abcdefghij5:nodes9:def456...e", b"d2:id20:0123456789abcdefghij5:nodes9:def456...e",
BitTorrentDHTResponse { id: "0123456789abcdefghij".to_string(), token: None, values: None, nodes: Some("def456...".to_string()) } ; BitTorrentDHTResponse { id: b"0123456789abcdefghij".to_vec(), token: None, values: None, nodes: Some(b"def456...".to_vec()) } ;
"test response from bencode 5")] "test response from bencode 5")]
#[test_case( #[test_case(
b"d2:id20:abcdefghij01234567895:nodes9:def456...5:token8:aoeusnthe", b"d2:id20:abcdefghij01234567895:nodes9:def456...5:token8:aoeusnthe",
BitTorrentDHTResponse { id: "abcdefghij0123456789".to_string(), token: Some("aoeusnth".to_string()), values: None, nodes: Some("def456...".to_string()) } ; BitTorrentDHTResponse { id: b"abcdefghij0123456789".to_vec(), token: Some(b"aoeusnth".to_vec()), values: None, nodes: Some(b"def456...".to_vec()) } ;
"test response from bencode 6")] "test response from bencode 6")]
fn test_response_from_bencode(encoded: &[u8], expected: BitTorrentDHTResponse) { fn test_response_from_bencode(encoded: &[u8], expected: BitTorrentDHTResponse) {
let decoded = BitTorrentDHTResponse::from_bencode(encoded).unwrap(); let decoded = BitTorrentDHTResponse::from_bencode(encoded).unwrap();
@ -500,20 +500,20 @@ mod tests {
#[test_case( #[test_case(
b"d1:ad2:id20:abcdefghij0123456789e1:q4:ping1:t2:aa1:v4:UT011:y1:qe", b"d1:ad2:id20:abcdefghij0123456789e1:q4:ping1:t2:aa1:v4:UT011:y1:qe",
Some("ping".to_string()), Some("ping".to_string()),
Some(BitTorrentDHTRequest { id: "abcdefghij0123456789".to_string(), implied_port: None, info_hash: None, port: None, token: None, target: None }), Some(BitTorrentDHTRequest { id: b"abcdefghij0123456789".to_vec(), implied_port: None, info_hash: None, port: None, token: None, target: None }),
None, None,
None, None,
"aa".to_string(), b"aa".to_vec(),
Some("UT01".to_string()) ; Some(b"UT01".to_vec()) ;
"test parse bittorrent dht packet 1" "test parse bittorrent dht packet 1"
)] )]
#[test_case( #[test_case(
b"d1:rd2:id20:abcdefghij01234567895:token8:aoeusnth6:valuesl6:axje.u6:idhtnmee1:t2:aa1:y1:re", b"d1:rd2:id20:abcdefghij01234567895:token8:aoeusnth6:valuesl6:axje.u6:idhtnmee1:t2:aa1:y1:re",
None, None,
None, None,
Some(BitTorrentDHTResponse { id: "abcdefghij0123456789".to_string(), token: Some("aoeusnth".to_string()), values: Some(vec!["axje.u".to_string(), "idhtnm".to_string()]), nodes: None}), Some(BitTorrentDHTResponse { id: b"abcdefghij0123456789".to_vec(), token: Some(b"aoeusnth".to_vec()), values: Some(vec!["axje.u".to_string(), "idhtnm".to_string()]), nodes: None}),
None, None,
"aa".to_string(), b"aa".to_vec(),
None ; None ;
"test parse bittorrent dht packet 2" "test parse bittorrent dht packet 2"
)] )]
@ -523,16 +523,16 @@ mod tests {
None, None,
None, None,
Some(BitTorrentDHTError { num: 201u16, msg: "A Generic Error Ocurred".to_string() }), Some(BitTorrentDHTError { num: 201u16, msg: "A Generic Error Ocurred".to_string() }),
"aa".to_string(), b"aa".to_vec(),
Some("UT01".to_string()) ; Some(b"UT01".to_vec()) ;
"test parse bittorrent dht packet 3" "test parse bittorrent dht packet 3"
)] )]
fn test_parse_bittorrent_dht_packet( fn test_parse_bittorrent_dht_packet(
encoded: &[u8], request_type: Option<String>, encoded: &[u8], request_type: Option<String>,
expected_request: Option<BitTorrentDHTRequest>, expected_request: Option<BitTorrentDHTRequest>,
expected_response: Option<BitTorrentDHTResponse>, expected_response: Option<BitTorrentDHTResponse>,
expected_error: Option<BitTorrentDHTError>, expected_transaction_id: String, expected_error: Option<BitTorrentDHTError>, expected_transaction_id: Vec<u8>,
expected_client_version: Option<String>, expected_client_version: Option<Vec<u8>>,
) { ) {
let mut tx = BitTorrentDHTTransaction::new(); let mut tx = BitTorrentDHTTransaction::new();
parse_bittorrent_dht_packet(encoded, &mut tx).unwrap(); parse_bittorrent_dht_packet(encoded, &mut tx).unwrap();

Loading…
Cancel
Save