WIP: announce and DB better implementation

This commit is contained in:
Naim A 2018-06-12 20:23:19 +03:00
parent dd04df33f6
commit 53eb629f50
No known key found for this signature in database
GPG key ID: FD7948915D9EF8B9
2 changed files with 528 additions and 449 deletions

View file

@ -1,300 +1,357 @@
use std; use std;
use std::net::{SocketAddr, UdpSocket}; use std::net::{SocketAddr, UdpSocket};
use bincode; use bincode;
use serde::{Serialize, Deserialize, Serializer, Deserializer}; use serde::{Serialize, Deserialize};
use tracker; use tracker;
// 2000 should be enough, MTU is usually 1500 // 2000 should be enough, MTU is usually 1500
const MAX_PACKET_SIZE: usize = 2000; const MAX_PACKET_SIZE: usize = 2000;
// protocol contants // protocol contants
const PROTOCOL_ID: u64 = 0x0000041727101980; const PROTOCOL_ID: u64 = 0x0000041727101980;
#[repr(u32)] #[repr(u32)]
#[derive(Serialize, Deserialize)] #[derive(Serialize, Deserialize)]
enum Actions { enum Actions {
Connect = 0, Connect = 0,
Announce = 1, Announce = 1,
Scrape = 2, Scrape = 2,
Error = 3, Error = 3,
} }
#[repr(u32)] #[repr(u32)]
#[derive(Serialize, Deserialize)] #[derive(Serialize, Deserialize, Clone, Copy)]
pub enum Events { pub enum Events {
None = 0, None = 0,
Complete = 1, Complete = 1,
Started = 2, Started = 2,
Stopped = 3, Stopped = 3,
} }
fn pack<T: Serialize>(data: &T) -> Option<Vec<u8>> { fn pack<T: Serialize>(data: &T) -> Option<Vec<u8>> {
let mut bo = bincode::config(); let mut bo = bincode::config();
bo.big_endian(); bo.big_endian();
match bo.serialize(data) { match bo.serialize(data) {
Ok(bytes) => Some(bytes), Ok(bytes) => Some(bytes),
Err(_) => None, Err(_) => None,
} }
} }
fn unpack<'a, T: Deserialize<'a>>(data: &'a [u8]) -> Option<T> { fn unpack<'a, T: Deserialize<'a>>(data: &'a [u8]) -> Option<T> {
let mut bo = bincode::config(); let mut bo = bincode::config();
bo.big_endian(); bo.big_endian();
match bo.deserialize(data) { match bo.deserialize(data) {
Ok(obj) => Some(obj), Ok(obj) => Some(obj),
Err(_) => None, Err(_) => None,
} }
} }
#[derive(Serialize, Deserialize)] #[derive(Serialize, Deserialize)]
struct UDPRequestHeader { struct UDPRequestHeader {
connection_id: u64, connection_id: u64,
action: Actions, action: Actions,
transaction_id: u32, transaction_id: u32,
} }
#[derive(Serialize, Deserialize)] #[derive(Serialize, Deserialize)]
struct UDPResponseHeader { struct UDPResponseHeader {
action: Actions, action: Actions,
transaction_id: u32, transaction_id: u32,
} }
#[derive(Serialize, Deserialize)] #[derive(Serialize, Deserialize)]
struct UDPConnectionResponse { struct UDPConnectionResponse {
header: UDPResponseHeader, header: UDPResponseHeader,
connection_id: u64, connection_id: u64,
} }
#[derive(Serialize, Deserialize)] #[derive(Serialize, Deserialize)]
struct UDPAnnounceRequest { struct UDPAnnounceRequest {
header: UDPRequestHeader, header: UDPRequestHeader,
info_hash: [u8; 20], info_hash: [u8; 20],
peer_id: [u8; 20], peer_id: [u8; 20],
downloaded: u64, downloaded: u64,
left: u64, left: u64,
uploaded: u64, uploaded: u64,
event: Events, event: Events,
ip_address: u32, ip_address: u32,
key: u32, key: u32,
num_want: i32, num_want: i32,
port: u16, port: u16,
} }
#[derive(Serialize, Deserialize)] #[derive(Serialize, Deserialize)]
struct UDPAnnounceResponse { struct UDPAnnounceResponse {
header: UDPResponseHeader, header: UDPResponseHeader,
interval: u32, interval: u32,
leechers: u32, leechers: u32,
seeders: u32, seeders: u32,
} }
pub struct UDPTracker<'a> { pub struct UDPTracker<'a> {
server: std::net::UdpSocket, server: std::net::UdpSocket,
tracker: &'a mut tracker::TorrentTracker, tracker: &'a mut tracker::TorrentTracker,
} }
impl<'a> UDPTracker<'a> { impl<'a> UDPTracker<'a> {
pub fn new<T: std::net::ToSocketAddrs>(bind_address: T, tracker: &mut tracker::TorrentTracker) -> Result<UDPTracker, std::io::Error> { pub fn new<T: std::net::ToSocketAddrs>(bind_address: T, tracker: &mut tracker::TorrentTracker) -> Result<UDPTracker, std::io::Error> {
let server = match UdpSocket::bind(bind_address) { let server = match UdpSocket::bind(bind_address) {
Ok(s) => s, Ok(s) => s,
Err(e) => { Err(e) => {
return Err(e); return Err(e);
} }
}; };
Ok(UDPTracker{ Ok(UDPTracker{
server, server,
tracker, tracker,
}) })
} }
fn handle_packet(&mut self, remote_address: &SocketAddr, payload: &[u8]) { fn handle_packet(&mut self, remote_address: &SocketAddr, payload: &[u8]) {
let header : UDPRequestHeader = match unpack(payload) { let header : UDPRequestHeader = match unpack(payload) {
Some(val) => val, Some(val) => val,
None => { None => {
return; return;
} }
}; };
match header.action { match header.action {
Actions::Connect => self.handle_connect(remote_address, &header, payload), Actions::Connect => self.handle_connect(remote_address, &header, payload),
Actions::Announce => self.handle_announce(remote_address, &header, payload), Actions::Announce => self.handle_announce(remote_address, &header, payload),
Actions::Scrape => self.handle_scrape(remote_address, &header, payload), Actions::Scrape => self.handle_scrape(remote_address, &header, payload),
_ => { _ => {
// someone is playing around... ignore request. // someone is playing around... ignore request.
return; return;
} }
} }
} }
fn handle_connect(&self, remote_addr: &SocketAddr, header: &UDPRequestHeader, _payload: &[u8]) { fn handle_connect(&self, remote_addr: &SocketAddr, header: &UDPRequestHeader, _payload: &[u8]) {
if header.connection_id != PROTOCOL_ID { if header.connection_id != PROTOCOL_ID {
return; return;
} }
// send response... // send response...
let conn_id = self.get_connection_id(remote_addr); let conn_id = self.get_connection_id(remote_addr);
let response = UDPConnectionResponse{ let response = UDPConnectionResponse{
header: UDPResponseHeader{ header: UDPResponseHeader{
transaction_id: header.transaction_id, transaction_id: header.transaction_id,
action: Actions::Connect, action: Actions::Connect,
}, },
connection_id: conn_id, connection_id: conn_id,
}; };
if let Some(payload) = pack(&response) { if let Some(payload) = pack(&response) {
let _ = self.send_packet(remote_addr, payload.as_slice()); let _ = self.send_packet(remote_addr, payload.as_slice());
} }
} }
fn handle_announce(&mut self, remote_addr: &SocketAddr, header: &UDPRequestHeader, payload: &[u8]) { fn handle_announce(&mut self, remote_addr: &SocketAddr, header: &UDPRequestHeader, payload: &[u8]) {
if header.connection_id != self.get_connection_id(remote_addr) { if header.connection_id != self.get_connection_id(remote_addr) {
return; return;
} }
let announce_packet: Option<UDPAnnounceRequest> = unpack(payload); let packet: UDPAnnounceRequest = match unpack(payload) {
match announce_packet { Some(v) => v,
Some(packet) => { None => {
let plen = bincode::serialized_size(&packet).unwrap() as usize; return;
}
println!("payload len={}, announce len={}", payload.len(), plen); };
if payload.len() > plen { let plen = bincode::serialized_size(&packet).unwrap() as usize;
let bep41_payload = &payload[std::mem::size_of::<UDPAnnounceRequest>()..];
println!("bep41: {:?}", bep41_payload); println!("payload len={}, announce len={}", payload.len(), plen);
}
if payload.len() > plen {
if packet.ip_address != 0 { let bep41_payload = &payload[std::mem::size_of::<UDPAnnounceRequest>()..];
// TODO: allow configurability of ip address println!("bep41: {:?}", bep41_payload);
// for now, ignore request. }
return;
} if packet.ip_address != 0 {
// TODO: allow configurability of ip address
let client_addr = SocketAddr::new(remote_addr.ip(), packet.port); // for now, ignore request.
return;
self.tracker.update_torrent_peer(&packet.info_hash, &packet.peer_id, &client_addr, packet.uploaded, packet.downloaded, packet.left, packet.event); }
let stats = self.tracker.get_stats(&packet.info_hash).unwrap_or((0, 0, 0)); let client_addr = SocketAddr::new(remote_addr.ip(), packet.port);
let results = self.tracker.get_peers(&packet.info_hash, &client_addr); match self.tracker.get_torrent(&packet.info_hash, |torrent: &mut tracker::TorrentEntry | -> Result<Vec<u8>, &str> {
if torrent.is_flagged() {
if let Some(mut payload) = pack(&UDPAnnounceResponse{ return Err("Torrent has been flagged.");
header: UDPResponseHeader{ }
action: Actions::Announce, torrent.update_peer(&packet.peer_id, &client_addr, packet.uploaded, packet.downloaded, packet.left, packet.event);
transaction_id: packet.header.transaction_id,
}, let stats = torrent.get_stats();
seeders: stats.0 as u32, let peers = torrent.get_peers(&client_addr);
interval: 20, if let Some(mut payload) = pack(&UDPAnnounceResponse {
leechers: stats.2 as u32, header: UDPResponseHeader {
}) { action: Actions::Announce,
for peer in results { transaction_id: packet.header.transaction_id,
match client_addr { },
SocketAddr::V4(ipv4) => { seeders: stats.0 as u32,
payload.extend(&ipv4.ip().octets()); interval: 20,
}, leechers: stats.2 as u32,
SocketAddr::V6(ipv6) => { }) {
payload.extend(&ipv6.ip().octets()); for peer in peers {
} match peer {
}; SocketAddr::V4(ipv4) => {
payload.extend(&ipv4.ip().octets());
let port_hton = client_addr.port().to_be(); },
payload.extend(&[ (port_hton & 0xff) as u8, ((port_hton >> 8) & 0xff) as u8 ]); SocketAddr::V6(ipv6) => {
} payload.extend(&ipv6.ip().octets());
}
} else { };
self.send_error(&remote_addr, &packet.header, "internal error");
} let port_hton = client_addr.port().to_be();
}, payload.extend(&[(port_hton & 0xff) as u8, ((port_hton >> 8) & 0xff) as u8]);
None => {
return; return Ok(payload);
} }
} }
} return Err("");
}) {
fn handle_scrape(&self, remote_addr: &SocketAddr, header: &UDPRequestHeader, payload: &[u8]) { Some(error_message) => {
if header.connection_id != self.get_connection_id(remote_addr) { match error_message {
return; Err(err) => {
} if err.len() > 0 {
self.send_error(remote_addr, header, err);
self.send_error(remote_addr, header, "scrape not yet implemented"); }
} },
Ok(payload) => {
fn get_connection_id(&self, remote_address: &SocketAddr) -> u64 { // send packet...
match std::time::SystemTime::now().duration_since(std::time::UNIX_EPOCH) { let _ = self.send_packet(remote_addr, payload.as_slice());
Ok(duration) => { }
(duration.as_secs() / 3600) | ((remote_address.port() as u64) << 36) }
}, },
Err(_) => { None => {
0x8000000000000000 self.send_error(remote_addr, header, "Unregistered torrent");
} return;
} }
} };
/*
fn send_packet(&self, remote_addr: &SocketAddr, payload: &[u8]) -> Result<usize, std::io::Error> { if torrent.is_flagged() {
self.server.send_to(payload, remote_addr) error = Some("Torrent was flagged");
} } else {
torrent.update_peer(&packet.peer_id, &client_addr, packet.uploaded, packet.downloaded, packet.left, packet.event);
fn send_error(&self, remote_addr: &SocketAddr, header: &UDPRequestHeader, error_msg: &str) {
if let Some(mut payload) = pack(&UDPResponseHeader{ let stats = torrent.get_stats();
transaction_id: header.transaction_id, let peers = torrent.get_peers(&client_addr);
action: Actions::Error,
}) { if let Some(mut payload) = pack(&UDPAnnounceResponse {
let msg_bytes = Vec::from(error_msg.as_bytes()); header: UDPResponseHeader {
payload.extend(msg_bytes); action: Actions::Announce,
transaction_id: packet.header.transaction_id,
let _ = self.send_packet(remote_addr, payload.as_slice()); },
} seeders: stats.0 as u32,
} interval: 20,
leechers: stats.2 as u32,
pub fn accept_packet(&mut self) -> Result<(), std::io::Error> { }) {
let mut packet = [0u8; MAX_PACKET_SIZE]; for peer in peers {
match self.server.recv_from(&mut packet) { match peer {
Ok((size, remote_address)) => { SocketAddr::V4(ipv4) => {
self.handle_packet(&remote_address, &packet[..size]); payload.extend(&ipv4.ip().octets());
},
Ok(()) SocketAddr::V6(ipv6) => {
}, payload.extend(&ipv6.ip().octets());
Err(e) => Err(e), }
} };
}
} let port_hton = client_addr.port().to_be();
payload.extend(&[(port_hton & 0xff) as u8, ((port_hton >> 8) & 0xff) as u8]);
#[cfg(test)] }
mod tests { }
#[test] }
fn pack() {
let mystruct = super::UDPRequestHeader { if let Some(error_message) = error {
connection_id: 200, self.send_error(remote_addr, &packet.header, error_message);
action: super::Actions::Connect, }*/
transaction_id: 77771, }
};
match super::pack(&mystruct) { fn handle_scrape(&self, remote_addr: &SocketAddr, header: &UDPRequestHeader, payload: &[u8]) {
Some(data) => { if header.connection_id != self.get_connection_id(remote_addr) {
println!("serialized data = [{}, {:?}]", data.len(), data); return;
}, }
None => {
assert!(false); self.send_error(remote_addr, header, "scrape not yet implemented");
} }
};
} fn get_connection_id(&self, remote_address: &SocketAddr) -> u64 {
match std::time::SystemTime::now().duration_since(std::time::UNIX_EPOCH) {
#[test] Ok(duration) => {
fn unpack() { (duration.as_secs() / 3600) | ((remote_address.port() as u64) << 36)
let buf = [0u8, 0, 0, 0, 0, 0, 0, 200, 0, 0, 0, 1, 0, 1, 47, 203]; },
match super::unpack(&buf) { Err(_) => {
Some(obj) => { 0x8000000000000000
let x : super::UDPResponseHeader = obj; }
println!("conn_id={}", x.action as u32); }
}, }
None => {
assert!(false); fn send_packet(&self, remote_addr: &SocketAddr, payload: &[u8]) -> Result<usize, std::io::Error> {
} self.server.send_to(payload, remote_addr)
} }
}
} fn send_error(&self, remote_addr: &SocketAddr, header: &UDPRequestHeader, error_msg: &str) {
if let Some(mut payload) = pack(&UDPResponseHeader{
transaction_id: header.transaction_id,
action: Actions::Error,
}) {
let msg_bytes = Vec::from(error_msg.as_bytes());
payload.extend(msg_bytes);
let _ = self.send_packet(remote_addr, payload.as_slice());
}
}
pub fn accept_packet(&mut self) -> Result<(), std::io::Error> {
let mut packet = [0u8; MAX_PACKET_SIZE];
match self.server.recv_from(&mut packet) {
Ok((size, remote_address)) => {
self.handle_packet(&remote_address, &packet[..size]);
Ok(())
},
Err(e) => Err(e),
}
}
}
#[cfg(test)]
mod tests {
#[test]
fn pack() {
let mystruct = super::UDPRequestHeader {
connection_id: 200,
action: super::Actions::Connect,
transaction_id: 77771,
};
match super::pack(&mystruct) {
Some(data) => {
println!("serialized data = [{}, {:?}]", data.len(), data);
},
None => {
assert!(false);
}
};
}
#[test]
fn unpack() {
let buf = [0u8, 0, 0, 0, 0, 0, 0, 200, 0, 0, 0, 1, 0, 1, 47, 203];
match super::unpack(&buf) {
Some(obj) => {
let x : super::UDPResponseHeader = obj;
println!("conn_id={}", x.action as u32);
},
None => {
assert!(false);
}
}
}
}

View file

@ -1,149 +1,171 @@
use std; use std;
use server::Events; use server::Events;
pub enum TrackerMode { pub enum TrackerMode {
/// In static mode torrents are tracked only if they were added ahead of time. /// In static mode torrents are tracked only if they were added ahead of time.
StaticMode, StaticMode,
/// In dynamic mode, torrents are tracked being added ahead of time. /// In dynamic mode, torrents are tracked being added ahead of time.
DynamicMode, DynamicMode,
/// Tracker will only serve authenticated peers. /// Tracker will only serve authenticated peers.
PrivateMode, PrivateMode,
} }
struct TorrentPeer { struct TorrentPeer {
ip: std::net::SocketAddr, ip: std::net::SocketAddr,
last_connection_id: u64, uploaded: u64,
uploaded: u64, downloaded: u64,
downloaded: u64, left: u64,
left: u64, event: Events,
event: Events, updated: std::time::SystemTime,
updated: std::time::SystemTime, }
}
type PeerId = [u8; 20];
type PeerId = [u8; 20]; type InfoHash = [u8; 20];
type InfoHash = [u8; 20];
pub struct TorrentEntry {
struct TorrentEntry { is_flagged: bool,
is_flagged: bool, peers: std::collections::BTreeMap<PeerId, TorrentPeer>,
peers: std::collections::BTreeMap<PeerId, TorrentPeer>,
} completed: u32,
seeders: u32,
struct TorrentDatabase { }
torrent_peers: std::collections::BTreeMap<InfoHash, TorrentEntry>,
} impl TorrentEntry {
pub fn is_flagged(&self) -> bool {
pub struct TorrentTracker { self.is_flagged
mode: TrackerMode, }
database: TorrentDatabase,
} pub fn update_peer(&mut self, peer_id: &PeerId, remote_address: &std::net::SocketAddr, uploaded: u64, downloaded: u64, left: u64, event: Events) {
let is_seeder = left == 0 && uploaded > 0;
impl TorrentTracker { let mut was_seeder = false;
pub fn new() -> TorrentTracker { let mut is_completed = left == 0 && (event as u32) == (Events::Complete as u32);
TorrentTracker{ if let Some(prev) = self.peers.insert(*peer_id, TorrentPeer{
mode: TrackerMode::DynamicMode, updated: std::time::SystemTime::now(),
database: TorrentDatabase{ left,
torrent_peers: std::collections::BTreeMap::new(), downloaded,
} uploaded,
} ip: *remote_address,
} event,
}) {
/// Adding torrents is not relevant to dynamic trackers. was_seeder = prev.left == 0 && prev.uploaded > 0;
pub fn add_torrent(&mut self, info_hash: &InfoHash) {
use std::collections::BTreeMap; if is_completed && (prev.event as u32) == (Events::Complete as u32) {
self.database.torrent_peers.entry(*info_hash).or_insert(TorrentEntry{ // don't update count again. a torrent should only be updated once per peer.
is_flagged: false, is_completed = false;
peers: std::collections::BTreeMap::new(), }
}); }
}
if is_seeder && !was_seeder {
/// If the torrent is flagged, it will not be removed unless force is set to true. self.seeders += 1;
pub fn remove_torrent(&mut self, info_hash: &InfoHash, force: bool) { } else if was_seeder && !is_seeder {
if !force { self.seeders -= 1;
if let Some(entry) = self.database.torrent_peers.get(info_hash) { }
if entry.is_flagged {
// torrent is flagged, ignore request. if is_completed {
return; self.completed += 1;
} }
} else { }
// torrent not found, no point looking for it again...
return; pub fn get_peers(&self, remote_addr: &std::net::SocketAddr) -> Vec<std::net::SocketAddr> {
} let mut list = Vec::new();
} for (_, peer) in self.peers.iter().filter(|e| e.1.ip.is_ipv4() == remote_addr.is_ipv4()) {
self.database.torrent_peers.remove(info_hash); if peer.ip == *remote_addr {
} continue;
}
/// flagged torrents will result in a tracking error. This is to allow enforcement against piracy.
pub fn set_torrent_flag(&mut self, info_hash: &InfoHash, is_flagged: bool) { list.push(peer.ip);
if let Some(mut entry) = self.database.torrent_peers.get_mut(info_hash) {
if is_flagged && !entry.is_flagged { if list.len() >= 74 {
// empty peer list. // 74 is maximum peers supported by the protocol.
entry.peers.clear(); break;
} }
entry.is_flagged = is_flagged; }
} list
} }
pub fn update_torrent_peer(&mut self, info_hash: &InfoHash, peer_id: &PeerId, remote_address: &std::net::SocketAddr, uploaded: u64, downloaded: u64, left: u64, event: Events) { pub fn get_stats(&self) -> (u32, u32, u32) {
if let Some(mut torrent_entry) = self.database.torrent_peers.get_mut(info_hash) { let leechers = (self.peers.len() as u32) - self.seeders;
torrent_entry.peers.insert(*peer_id, TorrentPeer{ (self.seeders, self.completed, leechers)
updated: std::time::SystemTime::now(), }
left, }
downloaded,
uploaded, struct TorrentDatabase {
ip: *remote_address, torrent_peers: std::collections::BTreeMap<InfoHash, TorrentEntry>,
event, }
last_connection_id: 0,
}); pub struct TorrentTracker {
} mode: TrackerMode,
} database: TorrentDatabase,
}
/// returns a list of peers with the same type of address of the remote_addr (IP v4/v6)
pub fn get_peers(&self, info_hash: &InfoHash, remote_addr: &std::net::SocketAddr) -> Vec<std::net::SocketAddr> { impl TorrentTracker {
let mut list = Vec::new(); pub fn new() -> TorrentTracker {
if let Some(entry) = self.database.torrent_peers.get(info_hash) { TorrentTracker{
for (_, peer) in entry.peers.iter().filter(|e| e.1.ip.is_ipv4() == remote_addr.is_ipv4()) { mode: TrackerMode::DynamicMode,
if peer.ip == *remote_addr { database: TorrentDatabase{
continue; torrent_peers: std::collections::BTreeMap::new(),
} }
}
list.push(peer.ip); }
if list.len() >= 74 { /// Adding torrents is not relevant to dynamic trackers.
// 74 is maximum peers supported by the protocol. pub fn add_torrent(&mut self, info_hash: &InfoHash) {
break; self.database.torrent_peers.entry(*info_hash).or_insert(TorrentEntry{
} is_flagged: false,
} peers: std::collections::BTreeMap::new(),
} seeders: 0,
list completed: 0,
} });
}
pub fn get_stats(&self, info_hash: &InfoHash) -> Option<(i32, i32, i32)> {
if let Some(torrent_entry) = self.database.torrent_peers.get(info_hash) { /// If the torrent is flagged, it will not be removed unless force is set to true.
pub fn remove_torrent(&mut self, info_hash: &InfoHash, force: bool) {
// TODO: store stats in temporary location... if !force {
let mut seeders = 0; if let Some(entry) = self.database.torrent_peers.get(info_hash) {
let mut leechers = 0; if entry.is_flagged {
// torrent is flagged, ignore request.
for (_, peer) in torrent_entry.peers.iter() { return;
if peer.left == 0 { }
seeders += 1; } else {
} // torrent not found, no point looking for it again...
else { return;
leechers += 1; }
} }
} self.database.torrent_peers.remove(info_hash);
}
Some((seeders, -1, leechers))
} else { /// flagged torrents will result in a tracking error. This is to allow enforcement against piracy.
None pub fn set_torrent_flag(&mut self, info_hash: &InfoHash, is_flagged: bool) {
} if let Some(mut entry) = self.database.torrent_peers.get_mut(info_hash) {
} if is_flagged && !entry.is_flagged {
// empty peer list.
pub fn cleanup(&mut self) { entry.peers.clear();
}
} entry.is_flagged = is_flagged;
} }
}
pub fn get_torrent<F, R>(&mut self, info_hash: &InfoHash, action: F) -> Option<R>
where F: Fn(&mut TorrentEntry) -> R
{
if let Some(torrent_entry) = self.database.torrent_peers.get_mut(info_hash) {
Some(action(torrent_entry))
} else {
match self.mode {
TrackerMode::StaticMode => None,
TrackerMode::PrivateMode => None,
TrackerMode::DynamicMode => {
None
}
}
}
}
pub fn cleanup(&mut self) {
}
}