Coverage Report

Created: 2025-12-31 06:43

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/suricata7/rust/src/bittorrent_dht/parser.rs
Line
Count
Source
1
/* Copyright (C) 2021 Open Information Security Foundation
2
 *
3
 * You can copy, redistribute or modify this Program under the terms of
4
 * the GNU General Public License version 2 as published by the Free
5
 * Software Foundation.
6
 *
7
 * This program is distributed in the hope that it will be useful,
8
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
9
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
10
 * GNU General Public License for more details.
11
 *
12
 * You should have received a copy of the GNU General Public License
13
 * version 2 along with this program; if not, write to the Free Software
14
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
15
 * 02110-1301, USA.
16
 */
17
18
/*! Parses BitTorrent DHT specification BEP_0005
19
 *  <https://www.bittorrent.org/beps/bep_0005.html> !*/
20
21
// TODO: Custom error type, as we have bencode and nom errors, and may have an our application
22
// specific errors as we finish off this parser.
23
24
use crate::bittorrent_dht::bittorrent_dht::BitTorrentDHTTransaction;
25
use bendy::decoding::{Decoder, Error, FromBencode, Object, ResultExt};
26
use nom7::bytes::complete::take;
27
use nom7::number::complete::be_u16;
28
use nom7::IResult;
29
30
#[derive(Debug, Eq, PartialEq)]
31
pub struct BitTorrentDHTRequest {
32
    /// q = * - 20 byte string, sender's node ID in network byte order
33
    pub id: Vec<u8>,
34
    /// q = find_node - target node ID
35
    pub target: Option<Vec<u8>>,
36
    /// q = get_peers/announce_peer - 20-byte info hash of target torrent
37
    pub info_hash: Option<Vec<u8>>,
38
    /// q = announce_peer - token key received from previous get_peers query
39
    pub token: Option<Vec<u8>>,
40
    /// q = announce_peer - 0 or 1, if 1 ignore provided port and
41
    ///                     use source port of UDP packet
42
    pub implied_port: Option<u8>,
43
    /// q = announce_peer - port on which peer will download torrent
44
    pub port: Option<u16>,
45
}
46
47
#[derive(Debug, Eq, PartialEq)]
48
pub struct BitTorrentDHTResponse {
49
    /// q = * - 20 byte string, receiver's node ID in network byte order
50
    pub id: Vec<u8>,
51
    /// q = find_node/get_peers - compact node info for target node or
52
    ///                           K(8) closest good nodes in routing table
53
    pub nodes: Option<Vec<Node>>,
54
    pub nodes6: Option<Vec<Node>>,
55
    /// q = get_peers - list of compact peer infos
56
    pub values: Option<Vec<Peer>>,
57
    /// q = get_peers - token key required for sender's future
58
    ///                 announce_peer query
59
    pub token: Option<Vec<u8>>,
60
}
61
62
#[derive(Debug, Eq, PartialEq)]
63
pub struct BitTorrentDHTError {
64
    /// integer representing the error code
65
    pub num: u16,
66
    /// string containing the error message
67
    pub msg: String,
68
}
69
70
#[derive(Debug, Eq, PartialEq)]
71
pub struct Node {
72
    pub id: Vec<u8>,
73
    pub ip: Vec<u8>,
74
    pub port: u16,
75
}
76
77
#[derive(Debug, Eq, PartialEq)]
78
pub struct Peer {
79
    pub ip: Vec<u8>,
80
    pub port: u16,
81
}
82
83
/// Parse IPv4 node structures.
84
109k
pub fn parse_node(i: &[u8]) -> IResult<&[u8], Node> {
85
109k
    let (i, id) = take(20usize)(i)?;
86
105k
    let (i, ip) = take(4usize)(i)?;
87
105k
    let (i, port) = be_u16(i)?;
88
105k
    Ok((
89
105k
        i,
90
105k
        Node {
91
105k
            id: id.to_vec(),
92
105k
            ip: ip.to_vec(),
93
105k
            port,
94
105k
        },
95
105k
    ))
96
109k
}
97
98
/// Parse IPv6 node structures.
99
42.7k
pub fn parse_node6(i: &[u8]) -> IResult<&[u8], Node> {
100
42.7k
    let (i, id) = take(20usize)(i)?;
101
42.0k
    let (i, ip) = take(16usize)(i)?;
102
41.8k
    let (i, port) = be_u16(i)?;
103
41.6k
    Ok((
104
41.6k
        i,
105
41.6k
        Node {
106
41.6k
            id: id.to_vec(),
107
41.6k
            ip: ip.to_vec(),
108
41.6k
            port,
109
41.6k
        },
110
41.6k
    ))
111
42.7k
}
112
113
822k
fn parse_peer(i: &[u8]) -> IResult<&[u8], Peer> {
114
822k
    let (i, ip) = if i.len() < 18 {
115
822k
        take(4usize)(i)
116
    } else {
117
246
        take(16usize)(i)
118
12
    }?;
119
822k
    let (i, port) = be_u16(i)?;
120
822k
    Ok((
121
822k
        i,
122
822k
        Peer {
123
822k
            ip: ip.to_vec(),
124
822k
            port,
125
822k
        },
126
822k
    ))
127
822k
}
128
129
impl FromBencode for BitTorrentDHTRequest {
130
    // Try to parse with a `max_depth` of one.
131
    //
132
    // The required max depth of a data structure is calculated as follows:
133
    //  - every potential nesting level encoded as bencode dictionary or
134
    //    list count as +1,
135
    //  - everything else is ignored.
136
    //
137
    // struct BitTorrentDHTRequest {   // encoded as dictionary (+1)
138
    //     id: String,
139
    //     target: Option<String>,
140
    //     info_hash: Option<String>,
141
    //     token: Option<String>,
142
    //     implied_port: Option<u8>,
143
    //     port: Option<u16>,
144
    // }
145
    const EXPECTED_RECURSION_DEPTH: usize = 1;
146
147
17.7k
    fn decode_bencode_object(object: Object) -> Result<Self, Error>
148
17.7k
    where
149
17.7k
        Self: Sized,
150
    {
151
17.7k
        let mut id = None;
152
17.7k
        let mut target = None;
153
17.7k
        let mut info_hash = None;
154
17.7k
        let mut token = None;
155
17.7k
        let mut implied_port = None;
156
17.7k
        let mut port = None;
157
158
17.7k
        let mut dict_dec = object.try_into_dictionary()?;
159
160
61.6k
        while let Some(pair) = dict_dec.next_pair()? {
161
43.8k
            match pair {
162
43.8k
                (b"id", value) => {
163
16.7k
                    id = value.try_into_bytes().context("id").map(Some)?;
164
                }
165
26.4k
                (b"target", value) => {
166
1.24k
                    target = value
167
1.24k
                        .try_into_bytes()
168
1.24k
                        .context("target")
169
1.24k
                        .map(|v| Some(v.to_vec()))?;
170
                }
171
20.4k
                (b"info_hash", value) => {
172
1.64k
                    info_hash = value
173
1.64k
                        .try_into_bytes()
174
1.64k
                        .context("info_hash")
175
1.64k
                        .map(|v| Some(v.to_vec()))?;
176
                }
177
16.3k
                (b"token", value) => {
178
1.82k
                    token = value
179
1.82k
                        .try_into_bytes()
180
1.82k
                        .context("token")
181
1.82k
                        .map(|v| Some(v.to_vec()))?;
182
                }
183
11.3k
                (b"implied_port", value) => {
184
932
                    implied_port = u8::decode_bencode_object(value)
185
932
                        .context("implied_port")
186
932
                        .map(Some)?
187
                }
188
2.43k
                (b"port", value) => {
189
569
                    port = u16::decode_bencode_object(value)
190
569
                        .context("port")
191
569
                        .map(Some)?
192
                }
193
20.9k
                (_unknown_field, _) => {}
194
            }
195
        }
196
197
15.9k
        let id = id.ok_or_else(|| Error::missing_field("id"))?;
198
199
15.8k
        Ok(BitTorrentDHTRequest {
200
15.8k
            id: id.to_vec(),
201
15.8k
            target,
202
15.8k
            info_hash,
203
15.8k
            token,
204
15.8k
            implied_port,
205
15.8k
            port,
206
15.8k
        })
207
17.7k
    }
208
}
209
210
impl FromBencode for BitTorrentDHTResponse {
211
    // Try to parse with a `max_depth` of two.
212
    //
213
    // The required max depth of a data structure is calculated as follows:
214
    //  - every potential nesting level encoded as bencode dictionary or
215
    //    list count as +1,
216
    //  - everything else is ignored.
217
    //
218
    // struct BitTorrentDHTResponse {   // encoded as dictionary (+1)
219
    //     id: String,
220
    //     nodes: Option<String>,
221
    //     values: Option<Vec<String>>, // if present, encoded as list (+1)
222
    //     token: Option<String>,
223
    // }
224
    const EXPECTED_RECURSION_DEPTH: usize = 2;
225
226
13.8k
    fn decode_bencode_object(object: Object) -> Result<Self, Error>
227
13.8k
    where
228
13.8k
        Self: Sized,
229
    {
230
13.8k
        let mut id = None;
231
13.8k
        let mut nodes = None;
232
13.8k
        let mut nodes6 = None;
233
13.8k
        let mut values = vec![];
234
13.8k
        let mut token = None;
235
236
13.8k
        let mut dict_dec = object.try_into_dictionary()?;
237
238
42.2k
        while let Some(pair) = dict_dec.next_pair()? {
239
28.5k
            match pair {
240
28.5k
                (b"id", value) => {
241
12.9k
                    id = value.try_into_bytes().context("id").map(Some)?;
242
                }
243
14.9k
                (b"nodes", value) => {
244
3.90k
                    let (_, decoded_nodes) =
245
3.91k
                        nom7::multi::many0(parse_node)(value.try_into_bytes().context("nodes")?)
246
3.90k
                            .map_err(|_| Error::malformed_content("nodes.node"))?;
247
3.90k
                    if !decoded_nodes.is_empty() {
248
542
                        nodes = Some(decoded_nodes);
249
3.36k
                    }
250
                }
251
6.49k
                (b"nodes6", value) => {
252
1.07k
                    let (_, decoded_nodes) =
253
1.07k
                        nom7::multi::many0(parse_node6)(value.try_into_bytes().context("nodes6")?)
254
1.07k
                            .map_err(|_| Error::malformed_content("nodes6.nodes6"))?;
255
1.07k
                    if !decoded_nodes.is_empty() {
256
479
                        nodes6 = Some(decoded_nodes);
257
594
                    }
258
                }
259
1.52k
                (b"values", value) => {
260
1.52k
                    if let Object::List(mut list) = value {
261
823k
                        while let Some(entry) = list.next_object()? {
262
822k
                            let (_, peer) =
263
822k
                                parse_peer(entry.try_into_bytes().context("values.entry")?)
264
822k
                                    .map_err(|_| Error::malformed_content("values.entry.peer"))?;
265
822k
                            values.push(peer);
266
                        }
267
670
                    }
268
                }
269
1.01k
                (b"token", value) => {
270
1.01k
                    token = value
271
1.01k
                        .try_into_bytes()
272
1.01k
                        .context("token")
273
1.01k
                        .map(|v| Some(v.to_vec()))?;
274
                }
275
8.11k
                (_unknown_field, _) => {}
276
            }
277
        }
278
279
12.1k
        let id = id.ok_or_else(|| Error::missing_field("id"))?;
280
281
        Ok(BitTorrentDHTResponse {
282
12.0k
            id: id.to_vec(),
283
12.0k
            nodes,
284
12.0k
            nodes6,
285
12.0k
            values: if values.is_empty() {
286
11.4k
                None
287
            } else {
288
587
                Some(values)
289
            },
290
12.0k
            token,
291
        })
292
13.8k
    }
293
}
294
295
impl FromBencode for BitTorrentDHTError {
296
    // Try to parse with a `max_depth` of one.
297
    //
298
    // The required max depth of a data structure is calculated as follows:
299
    //  - every potential nesting level encoded as bencode dictionary or
300
    //    list count as +1,
301
    //  - everything else is ignored.
302
    //
303
    // struct BitTorrentDHTError {   // encoded as dictionary (+1)
304
    //     num: u16,
305
    //     msg: String,
306
    // }
307
    const EXPECTED_RECURSION_DEPTH: usize = 1;
308
309
86.8k
    fn decode_bencode_object(object: Object) -> Result<Self, Error>
310
86.8k
    where
311
86.8k
        Self: Sized,
312
    {
313
86.8k
        let mut num = None;
314
86.8k
        let mut msg = None;
315
316
86.8k
        let mut list_dec = object.try_into_list()?;
317
318
717k
        while let Some(object) = list_dec.next_object()? {
319
630k
            match object {
320
                Object::Integer(_) => {
321
88.0k
                    num = u16::decode_bencode_object(object)
322
88.0k
                        .context("num")
323
88.0k
                        .map(Some)?;
324
                }
325
                Object::Bytes(_) => {
326
159k
                    msg = String::decode_bencode_object(object)
327
159k
                        .context("msg")
328
159k
                        .map(Some)?;
329
                }
330
383k
                _ => {}
331
            }
332
        }
333
334
86.0k
        let num = num.ok_or_else(|| Error::missing_field("num"))?;
335
86.0k
        let msg = msg.ok_or_else(|| Error::missing_field("msg"))?;
336
337
86.0k
        Ok(BitTorrentDHTError { num, msg })
338
86.8k
    }
339
}
340
341
109k
pub fn parse_bittorrent_dht_packet(
342
109k
    bytes: &[u8], tx: &mut BitTorrentDHTTransaction,
343
109k
) -> Result<(), Error> {
344
    // Try to parse with a `max_depth` of three.
345
    //
346
    // The required max depth of a data structure is calculated as follows:
347
    //  - every potential nesting level encoded as bencode dictionary or
348
    //    list count as +1,
349
    //  - everything else is ignored.
350
    //
351
    // - Outer packet is a dictionary (+1)
352
    // - Max depth of child within dictionary is a BitTorrentDHTResponse (+2)
353
109k
    let mut decoder = Decoder::new(bytes).with_max_depth(3);
354
109k
    let object = decoder.next_object()?;
355
356
109k
    let mut packet_type = None;
357
109k
    let mut query_type = None;
358
109k
    let mut query_arguments = None;
359
109k
    let mut response = None;
360
109k
    let mut error = None;
361
109k
    let mut transaction_id = None;
362
109k
    let mut client_version = None;
363
364
109k
    let mut dict_dec = object
365
109k
        .ok_or_else(|| Error::unexpected_token("Dict", "EOF"))?
366
109k
        .try_into_dictionary()?;
367
368
439k
    while let Some(pair) = dict_dec.next_pair()? {
369
335k
        match pair {
370
335k
            (b"y", value) => {
371
                // q (query) vs r (response) vs e (error)
372
102k
                packet_type = String::decode_bencode_object(value)
373
102k
                    .context("packet_type")
374
102k
                    .map(Some)?;
375
            }
376
8.16k
            (b"q", value) => {
377
                // query type found
378
8.16k
                query_type = String::decode_bencode_object(value)
379
8.16k
                    .context("query_type")
380
8.16k
                    .map(Some)?;
381
            }
382
17.7k
            (b"a", value) => {
383
                // query arguments found
384
17.7k
                query_arguments = BitTorrentDHTRequest::decode_bencode_object(value)
385
17.7k
                    .context("query_arguments")
386
17.7k
                    .map(Some)?;
387
            }
388
13.8k
            (b"r", value) => {
389
                // response found
390
13.8k
                response = BitTorrentDHTResponse::decode_bencode_object(value)
391
13.8k
                    .context("response")
392
13.8k
                    .map(Some)?;
393
            }
394
86.8k
            (b"e", value) => {
395
                // error found
396
86.8k
                error = BitTorrentDHTError::decode_bencode_object(value)
397
86.8k
                    .context("error")
398
86.8k
                    .map(Some)?;
399
            }
400
102k
            (b"t", value) => {
401
                // transaction id found
402
102k
                transaction_id = value.try_into_bytes().context("transaction_id").map(Some)?;
403
            }
404
1.68k
            (b"v", value) => {
405
                // client version string found
406
1.68k
                client_version = value
407
1.68k
                    .try_into_bytes()
408
1.68k
                    .context("client_version")
409
1.68k
                    .map(|v| Some(v.to_vec()))?;
410
            }
411
1.76k
            (_unknown_field, _) => {}
412
        }
413
    }
414
415
102k
    if let Some(t) = packet_type {
416
102k
        match t.as_str() {
417
102k
            "q" => {
418
5.99k
                tx.request_type =
419
5.99k
                    Some(query_type.ok_or_else(|| Error::missing_field("query_type"))?);
420
5.99k
                tx.request =
421
5.99k
                    Some(query_arguments.ok_or_else(|| Error::missing_field("query_arguments"))?);
422
            }
423
96.2k
            "r" => {
424
11.0k
                tx.response = Some(response.ok_or_else(|| Error::missing_field("response"))?);
425
            }
426
85.2k
            "e" => {
427
85.1k
                tx.error = Some(error.ok_or_else(|| Error::missing_field("error"))?);
428
            }
429
53
            v => {
430
53
                return Err(Error::unexpected_token("packet_type q, r, or e", v));
431
            }
432
        }
433
    } else {
434
46
        return Err(Error::missing_field("packet_type"));
435
    }
436
437
102k
    tx.transaction_id = transaction_id
438
102k
        .ok_or_else(|| Error::missing_field("transaction_id"))?
439
102k
        .to_vec();
440
    // Client version string is an optional field
441
102k
    tx.client_version = client_version;
442
443
102k
    Ok(())
444
109k
}
445
446
#[cfg(test)]
447
#[allow(clippy::unused_unit)]
448
mod tests {
449
    use super::*;
450
    use crate::core::Direction;
451
    use test_case::test_case;
452
453
    #[test_case(
454
        b"d2:id20:abcdefghij0123456789e",
455
        BitTorrentDHTRequest { id: b"abcdefghij0123456789".to_vec(), implied_port: None, info_hash: None, port: None, token: None, target: None } ;
456
        "test request from bencode 2")]
457
    #[test_case(
458
        b"d2:id20:abcdefghij01234567899:info_hash20:mnopqrstuvwxyz123456e",
459
        BitTorrentDHTRequest { id: b"abcdefghij0123456789".to_vec(), implied_port: None, info_hash: Some(b"mnopqrstuvwxyz123456".to_vec()), port: None, token: None, target: None } ;
460
        "test request from bencode 4")]
461
    fn test_request_from_bencode(encoded: &[u8], expected: BitTorrentDHTRequest) {
462
        let decoded = BitTorrentDHTRequest::from_bencode(encoded).unwrap();
463
        assert_eq!(expected, decoded);
464
    }
465
466
    #[test_case(
467
        b"d12:implied_porti1e9:info_hash20:mnopqrstuvwxyz1234564:porti6881e5:token8:aoeusnthe",
468
        "Error: missing field: id" ;
469
        "test request from bencode err 1")]
470
    #[test_case(
471
        b"d2:id20:abcdefghij012345678912:implied_porti9999e9:info_hash20:mnopqrstuvwxyz1234564:porti6881e5:token8:aoeusnthe",
472
        "Error: malformed content discovered in implied_port" ;
473
        "test request from bencode err 2")]
474
    #[test_case(
475
        b"d2:id20:abcdefghij012345678912:implied_porti-1e9:info_hash20:mnopqrstuvwxyz1234564:porti6881e5:token8:aoeusnthe",
476
        "Error: malformed content discovered in implied_port" ;
477
        "test request from bencode err 3")]
478
    #[test_case(
479
        b"d2:id20:abcdefghij012345678912:implied_porti1e9:info_hash20:mnopqrstuvwxyz1234564:porti9999999e5:token8:aoeusnthe",
480
        "Error: malformed content discovered in port" ;
481
        "test request from bencode err 4")]
482
    #[test_case(
483
        b"d2:id20:abcdefghij012345678912:implied_porti1e9:info_hash20:mnopqrstuvwxyz1234564:porti-1e5:token8:aoeusnthe",
484
        "Error: malformed content discovered in port" ;
485
        "test request from bencode err 5")]
486
    #[test_case(
487
        b"i123e",
488
        "Error: discovered Dict but expected Num" ;
489
        "test request from bencode err 6")]
490
    fn test_request_from_bencode_err(encoded: &[u8], expected_error: &str) {
491
        let err = BitTorrentDHTRequest::from_bencode(encoded).unwrap_err();
492
        assert_eq!(expected_error, err.to_string());
493
    }
494
495
    #[test_case(
496
        b"d5:token8:aoeusnth6:valuesl6:axje.u6:idhtnmee",
497
        "Error: missing field: id" ;
498
        "test response from bencode err 1")]
499
    #[test_case(
500
        b"i123e",
501
        "Error: discovered Dict but expected Num" ;
502
        "test response from bencode err 2")]
503
    fn test_response_from_bencode_err(encoded: &[u8], expected_error: &str) {
504
        let err = BitTorrentDHTResponse::from_bencode(encoded).unwrap_err();
505
        assert_eq!(expected_error, err.to_string());
506
    }
507
508
    #[test_case(
509
        b"li201e23:A Generic Error Ocurrede",
510
        BitTorrentDHTError { num: 201u16, msg: "A Generic Error Ocurred".to_string() } ;
511
        "test error from bencode 1")]
512
    #[test_case(
513
        b"li202e12:Server Errore",
514
        BitTorrentDHTError { num: 202u16, msg: "Server Error".to_string() } ;
515
        "test error from bencode 2")]
516
    #[test_case(
517
        b"li203e14:Protocol Errore",
518
        BitTorrentDHTError { num: 203u16, msg: "Protocol Error".to_string() } ;
519
        "test error from bencode 3")]
520
    #[test_case(
521
        b"li204e14:Method Unknowne",
522
        BitTorrentDHTError { num: 204u16, msg: "Method Unknown".to_string() } ;
523
        "test error from bencode 4")]
524
    fn test_error_from_bencode(encoded: &[u8], expected: BitTorrentDHTError) {
525
        let decoded = BitTorrentDHTError::from_bencode(encoded).unwrap();
526
        assert_eq!(expected, decoded);
527
    }
528
529
    #[test_case(
530
        b"l23:A Generic Error Ocurrede",
531
        "Error: missing field: num" ;
532
        "test error from bencode err 1")]
533
    #[test_case(
534
        b"li201ee",
535
        "Error: missing field: msg" ;
536
        "test error from bencode err 2")]
537
    #[test_case(
538
        b"li999999ee",
539
        "Error: malformed content discovered in num" ;
540
        "test error from bencode err 3")]
541
    #[test_case(
542
        b"li-1ee",
543
        "Error: malformed content discovered in num" ;
544
        "test error from bencode err 4")]
545
    #[test_case(
546
        b"i123e",
547
        "Error: discovered List but expected Num" ;
548
        "test error from bencode err 5")]
549
    fn test_error_from_bencode_err(encoded: &[u8], expected_error: &str) {
550
        let err = BitTorrentDHTError::from_bencode(encoded).unwrap_err();
551
        assert_eq!(expected_error, err.to_string());
552
    }
553
554
    #[test_case(
555
        b"d1:ad2:id20:abcdefghij0123456789e1:q4:ping1:t2:aa1:v4:UT011:y1:qe",
556
        Some("ping".to_string()),
557
        Some(BitTorrentDHTRequest { id: b"abcdefghij0123456789".to_vec(), implied_port: None, info_hash: None, port: None, token: None, target: None }),
558
        None,
559
        None,
560
        b"aa".to_vec(),
561
        Some(b"UT01".to_vec()) ;
562
        "test parse bittorrent dht packet 1"
563
    )]
564
    #[test_case(
565
        b"d1:eli201e23:A Generic Error Ocurrede1:t2:aa1:v4:UT011:y1:ee",
566
        None,
567
        None,
568
        None,
569
        Some(BitTorrentDHTError { num: 201u16, msg: "A Generic Error Ocurred".to_string() }),
570
        b"aa".to_vec(),
571
        Some(b"UT01".to_vec()) ;
572
        "test parse bittorrent dht packet 3"
573
    )]
574
    fn test_parse_bittorrent_dht_packet(
575
        encoded: &[u8], request_type: Option<String>,
576
        expected_request: Option<BitTorrentDHTRequest>,
577
        expected_response: Option<BitTorrentDHTResponse>,
578
        expected_error: Option<BitTorrentDHTError>, expected_transaction_id: Vec<u8>,
579
        expected_client_version: Option<Vec<u8>>,
580
    ) {
581
        let mut tx = BitTorrentDHTTransaction::new(Direction::ToServer);
582
        parse_bittorrent_dht_packet(encoded, &mut tx).unwrap();
583
        assert_eq!(request_type, tx.request_type);
584
        assert_eq!(expected_request, tx.request);
585
        assert_eq!(expected_response, tx.response);
586
        assert_eq!(expected_error, tx.error);
587
        assert_eq!(expected_transaction_id, tx.transaction_id);
588
        assert_eq!(expected_client_version, tx.client_version);
589
    }
590
591
    #[test_case(
592
        b"",
593
        "Error: discovered Dict but expected EOF" ;
594
        "test parse bittorrent dht packet err 1"
595
    )]
596
    #[test_case(
597
        b"li2123ei321ee",
598
        "Error: discovered Dict but expected List" ;
599
        "test parse bittorrent dht packet err 2"
600
    )]
601
    #[test_case(
602
        b"d1:ad2:id20:abcdefghij0123456789e1:q4:ping1:t2:aae",
603
        "Error: missing field: packet_type" ;
604
        "test parse bittorrent dht packet err 3"
605
    )]
606
    #[test_case(
607
        b"d1:ad2:id20:abcdefghij0123456789e1:q4:ping1:t2:aa1:y1:Fe",
608
        "Error: discovered packet_type q, r, or e but expected F" ;
609
        "test parse bittorrent dht packet err 4"
610
    )]
611
    #[test_case(
612
        b"d1:ad2:id20:abcdefghij0123456789e1:t2:aa1:y1:qe",
613
        "Error: missing field: query_type" ;
614
        "test parse bittorrent dht packet err 5"
615
    )]
616
    #[test_case(
617
        b"d1:q4:ping1:t2:aa1:y1:qe",
618
        "Error: missing field: query_arguments" ;
619
        "test parse bittorrent dht packet err 6"
620
    )]
621
    #[test_case(
622
        b"d1:t2:aa1:y1:re",
623
        "Error: missing field: response" ;
624
        "test parse bittorrent dht packet err 7"
625
    )]
626
    #[test_case(
627
        b"d1:t2:aa1:y1:ee",
628
        "Error: missing field: error" ;
629
        "test parse bittorrent dht packet err 8"
630
    )]
631
    #[test_case(
632
        b"d1:ade1:q4:ping1:t2:aa1:y1:qe",
633
        "Error: missing field: id in query_arguments" ;
634
        "test parse bittorrent dht packet err 9"
635
    )]
636
    #[test_case(
637
        b"d1:ad2:id20:abcdefghij0123456789e1:q4:ping1:y1:qe",
638
        "Error: missing field: transaction_id" ;
639
        "test parse bittorrent dht packet err 10"
640
    )]
641
    fn test_parse_bittorrent_dht_packet_err(encoded: &[u8], expected_error: &str) {
642
        let mut tx = BitTorrentDHTTransaction::new(Direction::ToServer);
643
        let err = parse_bittorrent_dht_packet(encoded, &mut tx).unwrap_err();
644
        assert_eq!(expected_error, err.to_string());
645
    }
646
647
    #[test]
648
    fn test_parse_node() {
649
        let bytes = b"aaaaaaaaaaaaaaaaaaaa\x00\x00\x00\x00\x00\x01";
650
        let (_rem, node) = parse_node(bytes).unwrap();
651
        assert_eq!(node.id, b"aaaaaaaaaaaaaaaaaaaa");
652
        assert_eq!(node.ip, b"\x00\x00\x00\x00");
653
        assert_eq!(node.port, 1);
654
655
        // Short one byte.
656
        let bytes = b"aaaaaaaaaaaaaaaaaaa\x00\x00\x00\x00\x00\x01";
657
        assert!(parse_node(bytes).is_err());
658
659
        // Has remaining bytes.
660
        let bytes = b"aaaaaaaaaaaaaaaaaaaa\x00\x00\x00\x00\x00\x01bb";
661
        let (rem, _node) = parse_node(bytes).unwrap();
662
        assert_eq!(rem, b"bb");
663
    }
664
}