Created
December 22, 2015 08:13
-
-
Save GGist/19a17c954dac46c6a5da to your computer and use it in GitHub Desktop.
Command Line DHT Search Example (bip_dht)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
[package] | |
name = "testing" | |
version = "0.1.0" | |
[dependencies] | |
bip_dht = "0.2.0" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
extern crate bip_dht; | |
use std::collections::{HashSet}; | |
use std::io::{self, BufRead, Write}; | |
use std::net::{SocketAddr, SocketAddrV4, Ipv4Addr}; | |
use std::thread::{self}; | |
use bip_dht::{Handshaker, Router, DhtBuilder, | |
InfoHash, PeerId, DhtEvent}; | |
// Mock handshaker object. Wont actually listen for TCP connections or initiate connections, | |
// rather, it will print out the unique addresses that our DHT finds for us to connect to. | |
struct MockHandshaker { | |
new_peer_filter: HashSet<SocketAddr> | |
} | |
impl MockHandshaker { | |
fn new() -> MockHandshaker { | |
MockHandshaker{ new_peer_filter: HashSet::new() } | |
} | |
} | |
impl Handshaker for MockHandshaker { | |
type Stream = (); | |
fn id(&self) -> PeerId { | |
[0u8; 20].into() | |
} | |
fn port(&self) -> u16 { | |
// This is the port that will be persisted to the DHT when we announce ourselves. In other words, | |
// this is the port we will receive handshakes on that are initiated by a peer wishing to connect | |
// to us. This port does NOT have to be the same as our DHT source listen port, but it can be. | |
6881 | |
} | |
fn connect(&mut self, _: Option<PeerId>, _: InfoHash, addr: SocketAddr) { | |
// We will only print NEW peers that we see, otherwise we will be printing a lot | |
// of duplicate peers for one, or even multiple searches with the same Handshake object. | |
if self.new_peer_filter.contains(&addr) { | |
return | |
} | |
println!("Found NEW peer: {:?}, Total Peers Found: {:?}", addr, self.new_peer_filter.len()); | |
self.new_peer_filter.insert(addr); | |
} | |
fn filter<F>(&mut self, _: Box<F>) | |
where F: Fn(SocketAddr) -> bool + Send { | |
() | |
} | |
fn stream(&self, _: InfoHash) -> () { | |
() | |
} | |
} | |
fn main() { | |
let src_ip = Ipv4Addr::new(0, 0, 0, 0); | |
let src_addr = SocketAddr::V4(SocketAddrV4::new(src_ip, 6881)); | |
// Starts up the DHT with 3 routers, explicitly sets our source address (not required), | |
// and changes read only from true (default) to false, allowing us to respond to requests | |
// instead of ignoring them (slightly more network traffic). | |
let dht = DhtBuilder::with_router(Router::uTorrent) | |
.add_router(Router::BitTorrent).add_router(Router::Transmission) | |
.set_source_addr(src_addr).set_read_only(false) | |
.start_mainline(MockHandshaker::new()).unwrap(); | |
let dht_events = dht.events(); | |
// Spin up a new thread to listen for user input for InfoHash searches. | |
thread::spawn(move || { | |
let prompt = "Enter an info hash to search for peers for (or nothing to exit): "; | |
let stdin = io::stdin(); | |
let stdin_lock = stdin.lock(); | |
print!("{}", prompt); | |
io::stdout().flush().unwrap(); | |
for line in stdin_lock.lines() { | |
let u_line = line.unwrap(); | |
if u_line.is_empty() { | |
// Exit the thread | |
break; | |
} | |
// Check if a valid hex string (info hash) was entered | |
match hex_string_to_bytes(&u_line) { | |
Some(bytes) => dht.search(bytes.into(), false), | |
None => println!("Entered an invalid info hash...") | |
}; | |
print!("{}", prompt); | |
io::stdout().flush().unwrap(); | |
} | |
// Since we moved the MainlineDht object in this thread, exiting this thread will | |
// cause it to be dropped which will shut down our DHT and since a ShuttingDown | |
// event will get generated, the main thread will exit. | |
}); | |
// In the main thread, report DHT events to us. In particular, the BootstrapComplete | |
// event will tell us that the DHT started up successfully (should take ~20 seconds). | |
// Any tasks executed before the BootstrapComplete event will be queued and executed | |
// after that event. | |
// | |
// If we receivie a ShuttingDown event, it means something went wrong causing the DHT | |
// to shut down. In that case, exit the program. | |
for event in dht_events.iter() { | |
println!("\nReceived a DHT event: {:?}", event); | |
if let DhtEvent::ShuttingDown(_) = event { | |
break; | |
} | |
} | |
} | |
// Convert user inputed hex strings into their byte array equivalent. | |
fn hex_string_to_bytes(hex_string: &str) -> Option<[u8; 20]> { | |
if hex_string.chars().count() != 40 { | |
None | |
} else { | |
let mut index = 0; | |
let mut bytes = [0u8; 20]; | |
while index < 40 { | |
let mut chars = hex_string.chars().skip(index); | |
let (high, low) = (chars.next().unwrap(), chars.next().unwrap()); | |
// Convert the character represenation into its decimal equivalent. | |
let dec_high = if high.is_alphabetic() { | |
(high.to_lowercase().next().unwrap() as u8 - 'a' as u8) + 10 | |
} else { | |
high as u8 - b'0' | |
}; | |
let dec_low = if low.is_alphabetic() { | |
(low.to_lowercase().next().unwrap() as u8 - 'a' as u8) + 10 | |
} else { | |
low as u8 - b'0' | |
}; | |
// Validate the bounds of the hex decimal values and return the byte. | |
let byte = match (dec_high, dec_low) { | |
(0...15, 0...15) => (dec_high << 4) + dec_low, | |
_ => return None | |
}; | |
// Put the bytes into it's place. | |
bytes[index / 2] = byte; | |
index += 2; | |
} | |
Some(bytes) | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment