use chrono::{Duration, Utc}; use lox_library::bridge_table::{BridgeLine, BRIDGE_BYTES, MAX_BRIDGES_PER_BUCKET}; use rdsys_backend::proto::Resource; pub const ACCEPTED_HOURS_OF_FAILURE: i64 = 3; // Parse each resource from rdsys into a Bridgeline as expected by the Lox Bridgetable pub fn parse_into_bridgelines(resources: Vec) -> Vec { let mut bridgelines: Vec = Vec::new(); for resource in resources { let mut ip_bytes: [u8; 16] = [0; 16]; ip_bytes[..resource.address.len()].copy_from_slice(resource.address.as_bytes()); let resource_uid = resource .get_uid() .expect("Unable to get Fingerprint UID of resource"); let infostr: String = format!( "type={} blocked_in={:?} protocol={} fingerprint={:?} or_addresses={:?} distribution={} flags={:?} params={:?}", resource.r#type, resource.blocked_in, resource.protocol, resource.fingerprint, resource.or_addresses, resource.distribution, resource.flags, resource.params, ); let mut info_bytes: [u8; BRIDGE_BYTES - 26] = [0; BRIDGE_BYTES - 26]; info_bytes[..infostr.len()].copy_from_slice(infostr.as_bytes()); bridgelines.push(BridgeLine { addr: ip_bytes, port: resource.port, uid_fingerprint: resource_uid, info: info_bytes, }) } bridgelines } // Allocate each Bridgeline into a bucket that will later be allocated into spare buckets or open invitation buckets // Any leftover buckets from total_bridgelines % MAX_BRIDGES_PER_BUCKET are returned in a separate Vec // TODO: Improve this function to sort bridgelines into buckets in a more intentional manner. This could include // sorting bridgelines with high bandwidth into buckets that are only distributed to more trusted users or sorting // bridgelines by location pub fn parse_into_buckets( mut bridgelines: Vec, ) -> (Vec<[BridgeLine; MAX_BRIDGES_PER_BUCKET]>, Vec) { let mut buckets: Vec<[BridgeLine; MAX_BRIDGES_PER_BUCKET]> = Vec::new(); let mut count = 0; let mut bucket = [BridgeLine::default(); MAX_BRIDGES_PER_BUCKET]; let mut leftovers: Vec = Vec::new(); for bridgeline in bridgelines.clone() { println!("Added bridge with fingerprint: {:?}", bridgeline.uid_fingerprint); if count < MAX_BRIDGES_PER_BUCKET { bucket[count] = bridgeline; count += 1; } else { buckets.push(bucket); count = 0; bucket = [BridgeLine::default(); MAX_BRIDGES_PER_BUCKET]; } } // Handle the extra buckets that were not allocated already if count != 0 { for _ in 0..count { // Assumes that the unallocated bridgelines will be the last x of the passed bridgelines leftovers.push(bridgelines.pop().unwrap()); } } (buckets, leftovers) } // Sort Resources into those that are functional and those that are failing based on the last time // they were passing tests. Before passing them back to the calling function, they are parsed into // BridgeLines pub fn sort_for_parsing(resources: Vec) -> (Vec, Vec) { let mut functional: Vec = Vec::new(); let mut failing: Vec = Vec::new(); for resource in resources { if resource.last_passed + Duration::hours(ACCEPTED_HOURS_OF_FAILURE) >= Utc::now() { functional.push(resource); } else { failing.push(resource); } } let functional_bridgelines = parse_into_bridgelines(functional); let failing_bridgelines = parse_into_bridgelines(failing); (functional_bridgelines, failing_bridgelines) } #[cfg(test)] mod tests { use rdsys_backend::proto::Resource; use std::collections::HashMap; use chrono::{Duration, Utc}; use super::sort_for_parsing; pub fn make_resource( rtype: String, address: String, port: u16, fingerprint: String, last_passed: i64, ) -> Resource { let mut flags = HashMap::new(); flags.insert(String::from("fast"), true); flags.insert(String::from("stable"), true); let mut params = HashMap::new(); params.insert( String::from("password"), String::from("ABCDEFGHIJKLMNOPQRSTUVWXYZ234567"), ); Resource { r#type: String::from(rtype), blocked_in: HashMap::new(), last_passed: Utc::now() - Duration::hours(last_passed), protocol: String::from("tcp"), address: String::from(address), port: port, fingerprint: String::from(fingerprint), or_addresses: None, distribution: String::from("https"), flags: Some(flags), params: Some(params), } } #[test] fn test_sort_for_parsing() { let resource_one = make_resource( "scramblesuit".to_owned(), "123.456.789.100".to_owned(), 3002, "BE84A97D02130470A1C77839954392BA979F7EE1".to_owned(), 2, ); let resource_two = make_resource( "https".to_owned(), "123.222.333.444".to_owned(), 6002, "C56B9EF202130470A1C77839954392BA979F7FF9".to_owned(), 5, ); let resource_three = make_resource( "scramblesuit".to_owned(), "444.888.222.100".to_owned(), 3042, "1A4C8BD902130470A1C77839954392BA979F7B46".to_owned(), 4, ); let resource_four = make_resource( "https".to_owned(), "555.444.212.100".to_owned(), 8022, "FF024DC302130470A1C77839954392BA979F7AE2".to_owned(), 3, ); let resource_five = make_resource( "https".to_owned(), "234.111.212.100".to_owned(), 10432, "7B4DE14CB2130470A1C77839954392BA979F7AE2".to_owned(), 1, ); let mut test_vec: Vec = Vec::new(); test_vec.push(resource_one); test_vec.push(resource_two); test_vec.push(resource_three); test_vec.push(resource_four); test_vec.push(resource_five); let (functional, failing) = sort_for_parsing(test_vec); assert!( functional.len() == 2, "There should be 2 functional bridges" ); assert!(failing.len() == 3, "There should be 3 failing bridges"); } }