challenge: 2

This commit is contained in:
itsscb 2024-12-02 23:37:24 +01:00
parent 480fb7a744
commit 2eb4135c71
8 changed files with 309 additions and 2 deletions

View File

@ -4,7 +4,8 @@ version = "0.1.0"
edition = "2021"
[dependencies]
axum = "0.7.4"
axum = { version = "0.7.4", features = ["query"] }
serde = { version = "1.0.215", features = ["derive"] }
shuttle-axum = "0.49.0"
shuttle-runtime = "0.49.0"
tokio = "1.28.2"

View File

@ -1,7 +1,7 @@
mod routes;
pub use routes::hello_world;
use routes::{hello_bird, minus_one};
use routes::{hello_bird, ipv4_dest, ipv4_key, ipv6_dest, ipv6_key, minus_one};
pub fn router() -> axum::Router {
use axum::routing::get;
@ -10,6 +10,10 @@ pub fn router() -> axum::Router {
Router::new()
.route("/hello_world", get(hello_world))
.route("/-1/seek", get(minus_one))
.route("/2/dest", get(ipv4_dest))
.route("/2/key", get(ipv4_key))
.route("/2/v6/dest", get(ipv6_dest))
.route("/2/v6/key", get(ipv6_key))
.route("/", get(hello_bird))
}
@ -49,4 +53,82 @@ mod test {
response.assert_header("location", "https://www.youtube.com/watch?v=9Gc4QTqslN4");
response.assert_status(StatusCode::FOUND);
}
#[tokio::test]
async fn test_ipv4_dest() {
let server = test_server();
let response = server.get("/2/dest?from=10.0.0.0&key=1.2.3.255").await;
response.assert_status_ok();
response.assert_text("11.2.3.255");
let response = server.get("/2/dest?from=invalid-from&key=1.2.3.255").await;
response.assert_status_bad_request();
let response = server.get("/2/dest?from=10.0.0.0&key=invalid-key").await;
response.assert_status_bad_request();
let response = server
.get("/2/dest?from=128.128.33.0&key=255.0.255.33")
.await;
response.assert_status_ok();
response.assert_text("127.128.32.33");
}
#[tokio::test]
async fn test_ipv4_key() {
let server = test_server();
let response = server.get("/2/key?from=10.0.0.0&to=11.2.3.255").await;
response.assert_status_ok();
response.assert_text("1.2.3.255");
let response = server.get("/2/key?from=invalid-from&to=1.2.3.255").await;
response.assert_status_bad_request();
let response = server.get("/2/key?from=10.0.0.0&to=invalid-to").await;
response.assert_status_bad_request();
let response = server
.get("/2/key?from=128.128.33.0&to=127.128.32.33")
.await;
response.assert_status_ok();
response.assert_text("255.0.255.33");
}
#[tokio::test]
async fn test_ipv6_dest() {
let server = test_server();
let response = server.get("/2/v6/dest?from=fe80::1&key=5:6:7::3333").await;
response.assert_status_ok();
response.assert_text("fe85:6:7::3332");
let response = server
.get("/2/v6/dest?from=invalid-from&key=5:6:7::3333")
.await;
response.assert_status_bad_request();
let response = server.get("/2/v6/dest?from=fe80::1&key=invalid-key").await;
response.assert_status_bad_request();
}
#[tokio::test]
async fn test_ipv6_key() {
let server = test_server();
let response = server
.get("/2/v6/key?from=aaaa::aaaa&to=5555:ffff:c:0:0:c:1234:5555")
.await;
response.assert_status_ok();
response.assert_text("ffff:ffff:c::c:1234:ffff");
let response = server
.get("/2/v6/dest?from=invalid-from&to=5:6:7::3333")
.await;
response.assert_status_bad_request();
let response = server.get("/2/v6/dest?from=fe80::1&to=invalid-to").await;
response.assert_status_bad_request();
}
}

View File

@ -1,7 +1,12 @@
mod hello_bird;
mod hello_world;
mod minus_one;
mod task_two;
pub use hello_bird::hello_bird;
pub use hello_world::hello_world;
pub use minus_one::minus_one;
pub use task_two::ipv4_dest;
pub use task_two::ipv4_key;
pub use task_two::ipv6_dest;
pub use task_two::ipv6_key;

View File

@ -0,0 +1,37 @@
#![allow(dead_code, clippy::unused_async)]
use std::{net::Ipv4Addr, str::FromStr};
use axum::{
extract::Query,
http::StatusCode,
response::{IntoResponse, Response},
};
use super::DestParams;
pub async fn ipv4_dest(params: Query<DestParams>) -> Result<String, Response> {
let params: DestParams = params.0;
let Ok(from) = Ipv4Addr::from_str(&params.from) else {
return Err((StatusCode::BAD_REQUEST, "Invalid from IP address").into_response());
};
let Ok(key) = Ipv4Addr::from_str(&params.key) else {
return Err((StatusCode::BAD_REQUEST, "Invalid key IP address").into_response());
};
Ok(calculate_ipv4_dest(from, key))
}
pub fn calculate_ipv4_dest(from: Ipv4Addr, key: Ipv4Addr) -> String {
let result: Vec<u8> = from
.octets()
.iter()
.zip(key.octets().iter())
.map(|(a, b)| a.overflowing_add(*b).0)
.collect();
result
.iter()
.map(std::string::ToString::to_string)
.collect::<Vec<String>>()
.join(".")
}

View File

@ -0,0 +1,36 @@
#![allow(dead_code, clippy::unused_async)]
use std::{net::Ipv4Addr, str::FromStr};
use axum::{
extract::Query,
http::StatusCode,
response::{IntoResponse, Response},
};
use super::KeyParams;
pub async fn ipv4_key(params: Query<KeyParams>) -> Result<String, Response> {
let params: KeyParams = params.0;
let Ok(from) = Ipv4Addr::from_str(&params.from) else {
return Err((StatusCode::BAD_REQUEST, "Invalid from IP address").into_response());
};
let Ok(to) = Ipv4Addr::from_str(&params.to) else {
return Err((StatusCode::BAD_REQUEST, "Invalid key IP address").into_response());
};
Ok(calculate_ipv4_key(from, to))
}
pub fn calculate_ipv4_key(from: Ipv4Addr, to: Ipv4Addr) -> String {
let result: Vec<u8> = to
.octets()
.iter()
.zip(from.octets().iter())
.map(|(a, b)| a.overflowing_sub(*b).0)
.collect();
result
.iter()
.map(std::string::ToString::to_string)
.collect::<Vec<String>>()
.join(".")
}

View File

@ -0,0 +1,37 @@
#![allow(dead_code, clippy::unused_async)]
use std::{net::Ipv6Addr, str::FromStr};
use axum::{
extract::Query,
http::StatusCode,
response::{IntoResponse, Response},
};
use super::DestParams;
pub async fn ipv6_dest(params: Query<DestParams>) -> Result<String, Response> {
let params: DestParams = params.0;
let Ok(from) = Ipv6Addr::from_str(&params.from) else {
return Err((StatusCode::BAD_REQUEST, "Invalid from IP address").into_response());
};
let Ok(key) = Ipv6Addr::from_str(&params.key) else {
return Err((StatusCode::BAD_REQUEST, "Invalid key IP address").into_response());
};
Ok(calculate_ipv6_dest(from, key))
}
pub fn calculate_ipv6_dest(from: Ipv6Addr, key: Ipv6Addr) -> String {
let result: Vec<String> = from
.segments()
.iter()
.zip(key.segments().iter())
.map(|(a, b)| a ^ b)
.map(|s| format!("{s:x}"))
.collect();
let ip: Ipv6Addr = result.join(":").parse().unwrap();
ip.to_string()
}

View File

@ -0,0 +1,37 @@
#![allow(dead_code, clippy::unused_async)]
use std::{net::Ipv6Addr, str::FromStr};
use axum::{
extract::Query,
http::StatusCode,
response::{IntoResponse, Response},
};
use super::KeyParams;
pub async fn ipv6_key(params: Query<KeyParams>) -> Result<String, Response> {
let params: KeyParams = params.0;
let Ok(from) = Ipv6Addr::from_str(&params.from) else {
return Err((StatusCode::BAD_REQUEST, "Invalid from IP address").into_response());
};
let Ok(to) = Ipv6Addr::from_str(&params.to) else {
return Err((StatusCode::BAD_REQUEST, "Invalid key IP address").into_response());
};
Ok(calculate_ipv6_key(from, to))
}
pub fn calculate_ipv6_key(from: Ipv6Addr, to: Ipv6Addr) -> String {
let result: Vec<String> = to
.segments()
.iter()
.zip(from.segments().iter())
.map(|(a, b)| a ^ b)
.map(|s| format!("{s:x}"))
.collect();
let ip: Ipv6Addr = result.join(":").parse().unwrap();
ip.to_string()
}

View File

@ -0,0 +1,72 @@
#![allow(dead_code, clippy::unused_async)]
mod ipv4_dest;
mod ipv4_key;
mod ipv6_dest;
mod ipv6_key;
pub use ipv4_dest::ipv4_dest;
pub use ipv4_key::ipv4_key;
pub use ipv6_dest::ipv6_dest;
pub use ipv6_key::ipv6_key;
#[derive(serde::Deserialize)]
pub struct DestParams {
from: String,
key: String,
}
#[derive(serde::Deserialize)]
pub struct KeyParams {
from: String,
to: String,
}
#[cfg(test)]
mod test {
use std::net::{Ipv4Addr, Ipv6Addr};
use std::str::FromStr;
use ipv4_dest::calculate_ipv4_dest;
use ipv4_key::calculate_ipv4_key;
use ipv6_dest::calculate_ipv6_dest;
use ipv6_key::calculate_ipv6_key;
use super::*;
#[test]
fn test_calculate_ipv4_dest() {
let from = Ipv4Addr::from_str("10.0.0.0").unwrap();
let key = Ipv4Addr::from_str("1.2.3.255").unwrap();
assert_eq!(calculate_ipv4_dest(from, key), "11.2.3.255");
let from = Ipv4Addr::from_str("128.128.33.0").unwrap();
let key = Ipv4Addr::from_str("255.0.255.33").unwrap();
assert_eq!(calculate_ipv4_dest(from, key), "127.128.32.33");
}
#[test]
fn test_calculate_ipv4_key() {
let from = Ipv4Addr::from_str("10.0.0.0").unwrap();
let to = Ipv4Addr::from_str("11.2.3.255").unwrap();
assert_eq!(calculate_ipv4_key(from, to), "1.2.3.255");
let from = Ipv4Addr::from_str("128.128.33.0").unwrap();
let to = Ipv4Addr::from_str("127.128.32.33").unwrap();
assert_eq!(calculate_ipv4_key(from, to), "255.0.255.33");
}
#[test]
fn test_calculate_ipv6_dest() {
let from = Ipv6Addr::from_str("fe80::1").unwrap();
let key = Ipv6Addr::from_str("5:6:7::3333").unwrap();
assert_eq!(calculate_ipv6_dest(from, key), "fe85:6:7::3332");
}
#[test]
fn test_calculate_ipv6_key() {
let from = Ipv6Addr::from_str("aaaa::aaaa").unwrap();
let to = Ipv6Addr::from_str("5555:ffff:c:0:0:c:1234:5555").unwrap();
assert_eq!(calculate_ipv6_key(from, to), "ffff:ffff:c::c:1234:ffff");
}
}