use tox_crypto::*;
use std::collections::HashMap;
pub const MAX_LINKS_N: u8 = 240;
#[derive(Debug, PartialEq, Clone, Copy)]
pub enum LinkStatus {
Registered,
Online,
}
#[derive(Debug, Clone, Copy)]
pub struct Link {
pub pk: PublicKey,
pub status: LinkStatus,
}
impl Link {
fn new(pk: PublicKey) -> Link {
Link {
pk,
status: LinkStatus::Registered,
}
}
fn downgrade(&mut self) {
self.status = LinkStatus::Registered;
}
fn upgrade(&mut self) {
self.status = LinkStatus::Online;
}
}
pub struct Links {
links: [Option<Link>; MAX_LINKS_N as usize],
pk_to_id: HashMap<PublicKey, u8>,
}
impl Default for Links {
fn default() -> Self {
Links::new()
}
}
impl Links {
pub fn new() -> Links {
Links {
links: [None; 240],
pk_to_id: HashMap::new()
}
}
pub fn insert(&mut self, pk: &PublicKey) -> Option<u8> {
let possible_index = { self.pk_to_id.get(pk).cloned() };
match possible_index {
Some(index) => Some(index), None => {
if let Some(index) = self.links.iter().position(|link| link.is_none()) {
let link = Link::new(*pk);
self.links[index] = Some(link);
self.pk_to_id.insert(*pk, index as u8);
Some(index as u8)
} else {
None
}
}
}
}
pub fn insert_by_id(&mut self, pk: &PublicKey, index: u8) -> bool {
assert!(index < MAX_LINKS_N, "The index {} must be lower than {}", index, MAX_LINKS_N);
if !self.pk_to_id.contains_key(pk) && self.links[index as usize].is_none() {
let link = Link::new(*pk);
self.links[index as usize] = Some(link);
self.pk_to_id.insert(*pk, index);
true
} else {
false
}
}
pub fn by_id(&self, index: u8) -> Option<&Link> {
if index < MAX_LINKS_N {
self.links[index as usize].as_ref()
} else {
None
}
}
pub fn id_by_pk(&self, pk: &PublicKey) -> Option<u8> {
self.pk_to_id.get(pk).cloned()
}
pub fn take(&mut self, index: u8) -> Option<Link> {
if index < MAX_LINKS_N {
if let Some(link) = self.links[index as usize].take() {
self.pk_to_id.remove(&link.pk);
Some(link)
} else {
None
}
} else {
None
}
}
pub fn downgrade(&mut self, index: u8) -> bool {
assert!(index < MAX_LINKS_N, "The index {} must be lower than {}", index, MAX_LINKS_N);
if let Some(ref mut link) = self.links[index as usize] {
link.downgrade();
true
} else {
false
}
}
pub fn upgrade(&mut self, index: u8) -> bool {
assert!(index < MAX_LINKS_N, "The index {} must be lower than {}", index, MAX_LINKS_N);
if let Some(ref mut link) = self.links[index as usize] {
link.upgrade();
true
} else {
false
}
}
pub fn iter_links(&self) -> impl Iterator<Item = Link> + '_ {
self.links.iter().filter_map(|&link| link)
}
pub fn clear(&mut self) {
self.links = [None; 240];
self.pk_to_id.clear();
}
}
#[cfg(test)]
mod tests {
use tox_crypto::*;
use crate::relay::links::*;
#[test]
fn link_new() {
crypto_init().unwrap();
let (pk, _) = gen_keypair();
let link = Link::new(pk);
assert_eq!(LinkStatus::Registered, link.status);
assert_eq!(pk, link.pk);
}
#[test]
fn link_upgrade() {
crypto_init().unwrap();
let (pk, _) = gen_keypair();
let mut link = Link::new(pk);
link.upgrade();
assert_eq!(LinkStatus::Online, link.status);
}
#[test]
fn link_downgrade() {
crypto_init().unwrap();
let (pk, _) = gen_keypair();
let mut link = Link::new(pk);
link.upgrade();
link.downgrade();
assert_eq!(LinkStatus::Registered, link.status);
}
#[test]
fn links_new() {
let links = Links::new();
assert!(links.links.iter().all(|link| link.is_none()));
assert!(links.pk_to_id.is_empty());
}
#[test]
fn links_default() {
let links = Links::default();
assert!(links.links.iter().all(|link| link.is_none()));
assert!(links.pk_to_id.is_empty());
}
#[test]
fn links_insert_240() {
crypto_init().unwrap();
let mut links = Links::new();
for _ in 0..240 {
let (pk, _) = gen_keypair();
let id = links.insert(&pk);
assert!(id.is_some());
}
let (pk, _) = gen_keypair();
let id = links.insert(&pk);
assert!(id.is_none());
}
#[test]
fn links_insert_same_pk() {
crypto_init().unwrap();
let mut links = Links::new();
let (pk, _) = gen_keypair();
let id1 = links.insert(&pk);
let id2 = links.insert(&pk);
assert!(id1.is_some());
assert_eq!(id1, id2);
}
#[test]
fn links_insert_alloc_order() {
crypto_init().unwrap();
let mut links = Links::new();
let (pk1, _) = gen_keypair();
let id1 = links.insert(&pk1).unwrap();
let (pk2, _) = gen_keypair();
let id2 = links.insert(&pk2).unwrap();
assert_eq!(id1, 0);
assert_eq!(id2, 1);
links.take(0);
let (pk3, _) = gen_keypair();
let id3 = links.insert(&pk3).unwrap();
assert_eq!(id3, 0);
}
#[test]
fn links_insert_by_id() {
crypto_init().unwrap();
let mut links = Links::new();
let (pk1, _) = gen_keypair();
let (pk2, _) = gen_keypair();
let id1 = links.insert(&pk1).unwrap();
assert_eq!(links.insert_by_id(&pk1, id1+1), false);
assert_eq!(links.insert_by_id(&pk2, id1), false);
assert_eq!(links.insert_by_id(&pk2, id1+1), true);
assert_eq!(links.by_id(id1+1).unwrap().pk, pk2);
}
#[test]
fn links_by_id() {
crypto_init().unwrap();
let mut links = Links::new();
let (pk, _) = gen_keypair();
let id = links.insert(&pk).unwrap();
assert_eq!(pk, links.by_id(id).unwrap().pk);
}
#[test]
fn links_by_id_nonexistent() {
crypto_init().unwrap();
let links = Links::new();
assert!(links.by_id(MAX_LINKS_N as u8 + 1).is_none());
}
#[test]
fn links_by_pk() {
crypto_init().unwrap();
let mut links = Links::new();
let (pk, _) = gen_keypair();
let id = links.insert(&pk);
assert_eq!(id, links.id_by_pk(&pk));
}
#[test]
fn links_upgrade() {
crypto_init().unwrap();
let mut links = Links::new();
let (pk, _) = gen_keypair();
let id = links.insert(&pk).unwrap();
assert_eq!(LinkStatus::Registered, links.by_id(id).unwrap().status);
assert_eq!(links.upgrade(id), true);
assert_eq!(LinkStatus::Online, links.by_id(id).unwrap().status);
assert_eq!(links.upgrade(id+1), false); }
#[test]
fn links_downgrade() {
crypto_init().unwrap();
let mut links = Links::new();
let (pk, _) = gen_keypair();
let id = links.insert(&pk).unwrap();
assert_eq!(LinkStatus::Registered, links.by_id(id).unwrap().status);
links.upgrade(id);
assert_eq!(links.downgrade(id), true);
assert_eq!(LinkStatus::Registered, links.by_id(id).unwrap().status);
assert_eq!(links.downgrade(id+1), false); }
#[test]
fn links_take() {
crypto_init().unwrap();
let mut links = Links::new();
let (pk, _) = gen_keypair();
let id = links.insert(&pk).unwrap();
assert!(links.by_id(id).is_some());
let link = links.take(id);
assert_eq!(pk, link.unwrap().pk);
assert!(links.by_id(id).is_none());
}
#[test]
fn links_take_nonexistent() {
crypto_init().unwrap();
let mut links = Links::new();
assert!(links.take(MAX_LINKS_N as u8 + 1).is_none());
}
#[test]
fn links_clear() {
crypto_init().unwrap();
let mut links = Links::new();
for _ in 0..240 {
let (pk, _) = gen_keypair();
let id = links.insert(&pk);
assert!(id.is_some());
}
for i in 0..240 {
assert!(links.by_id(i).is_some());
}
links.clear();
for i in 0..240 {
assert!(links.by_id(i).is_none());
}
}
}