add problem 06
This commit is contained in:
Generated
+241
@@ -0,0 +1,241 @@
|
|||||||
|
# This file is automatically @generated by Cargo.
|
||||||
|
# It is not intended for manual editing.
|
||||||
|
version = 4
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "anyhow"
|
||||||
|
version = "1.0.103"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "2a4385e2e34eb35d6b3efe798b9eb88096925d87726c0798709bf56d9ed84af3"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "bitflags"
|
||||||
|
version = "2.13.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b4388bee8683e3d04af747c73422af53102d2bd24d9eadb6cbc100baef4b43f8"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "bytes"
|
||||||
|
version = "1.12.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "fc652a48c352aef3ea3aed32080501cf3ef6ed5da78602a020c991775b0aff04"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "cfg-if"
|
||||||
|
version = "1.0.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "either"
|
||||||
|
version = "1.16.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "91622ff5e7162018101f2fea40d6ebf4a78bbe5a49736a2020649edf9693679e"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "errno"
|
||||||
|
version = "0.3.14"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
"windows-sys",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "itertools"
|
||||||
|
version = "0.15.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8b4baf93f58d4425749ca49a51c50ebab072c5df6994d08fed93541c331481dc"
|
||||||
|
dependencies = [
|
||||||
|
"either",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "libc"
|
||||||
|
version = "0.2.186"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "lock_api"
|
||||||
|
version = "0.4.14"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965"
|
||||||
|
dependencies = [
|
||||||
|
"scopeguard",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "mio"
|
||||||
|
version = "1.2.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "02bd0af71c67b473010cbbc60715ee815645a4dc942899111f494b4b737d6fda"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
"wasi",
|
||||||
|
"windows-sys",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "parking_lot"
|
||||||
|
version = "0.12.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a"
|
||||||
|
dependencies = [
|
||||||
|
"lock_api",
|
||||||
|
"parking_lot_core",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "parking_lot_core"
|
||||||
|
version = "0.9.12"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if",
|
||||||
|
"libc",
|
||||||
|
"redox_syscall",
|
||||||
|
"smallvec",
|
||||||
|
"windows-link",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pin-project-lite"
|
||||||
|
version = "0.2.17"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "problem06"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"anyhow",
|
||||||
|
"itertools",
|
||||||
|
"tokio",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "proc-macro2"
|
||||||
|
version = "1.0.106"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934"
|
||||||
|
dependencies = [
|
||||||
|
"unicode-ident",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "quote"
|
||||||
|
version = "1.0.46"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "dfbc457d0c7a0759a614551b11a6409e5951f6c7537be1f1b7682b9ae9230368"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "redox_syscall"
|
||||||
|
version = "0.5.18"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d"
|
||||||
|
dependencies = [
|
||||||
|
"bitflags",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "scopeguard"
|
||||||
|
version = "1.2.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "signal-hook-registry"
|
||||||
|
version = "1.4.8"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "c4db69cba1110affc0e9f7bcd48bbf87b3f4fc7c61fc9155afd4c469eb3d6c1b"
|
||||||
|
dependencies = [
|
||||||
|
"errno",
|
||||||
|
"libc",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "smallvec"
|
||||||
|
version = "1.15.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8ed6a63f02c8539c91a8685a86f4099661ba3da017932f6ebbea6de3f0fa7c90"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "socket2"
|
||||||
|
version = "0.6.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "52d1cfed4120b4d927bf7c0f86d2087a4a7d6027c906d9f9d525a80573b9be51"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
"windows-sys",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "syn"
|
||||||
|
version = "2.0.118"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1b9ae57f904213ebb649ce6895b8a66c66f0203b9319718f69a5612a065b1422"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"unicode-ident",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tokio"
|
||||||
|
version = "1.52.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8fc7f01b389ac15039e4dc9531aa973a135d7a4135281b12d7c1bc79fd57fffe"
|
||||||
|
dependencies = [
|
||||||
|
"bytes",
|
||||||
|
"libc",
|
||||||
|
"mio",
|
||||||
|
"parking_lot",
|
||||||
|
"pin-project-lite",
|
||||||
|
"signal-hook-registry",
|
||||||
|
"socket2",
|
||||||
|
"tokio-macros",
|
||||||
|
"windows-sys",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tokio-macros"
|
||||||
|
version = "2.7.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "385a6cb71ab9ab790c5fe8d67f1645e6c450a7ce006a33de03daa956cf70a496"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "unicode-ident"
|
||||||
|
version = "1.0.24"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wasi"
|
||||||
|
version = "0.11.1+wasi-snapshot-preview1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows-link"
|
||||||
|
version = "0.2.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows-sys"
|
||||||
|
version = "0.61.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc"
|
||||||
|
dependencies = [
|
||||||
|
"windows-link",
|
||||||
|
]
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
[package]
|
||||||
|
name = "problem06"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2024"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
tokio = { version = "1", features = ["full"] }
|
||||||
|
anyhow = "1"
|
||||||
|
itertools = "0"
|
||||||
@@ -0,0 +1,457 @@
|
|||||||
|
use std::collections::{HashMap, HashSet};
|
||||||
|
use std::sync::{Arc, Mutex, MutexGuard};
|
||||||
|
|
||||||
|
use anyhow::{Result, ensure};
|
||||||
|
use itertools::Itertools;
|
||||||
|
use tokio::io::AsyncWriteExt;
|
||||||
|
use tokio::sync::broadcast::{self, Receiver, Sender};
|
||||||
|
use tokio::{
|
||||||
|
io::AsyncReadExt,
|
||||||
|
net::{TcpListener, TcpStream},
|
||||||
|
};
|
||||||
|
|
||||||
|
type PlateLog = HashMap<String, Vec<(Camera, u32)>>;
|
||||||
|
type TicketLog = HashSet<(String, usize)>;
|
||||||
|
|
||||||
|
#[tokio::main]
|
||||||
|
async fn main() -> Result<()> {
|
||||||
|
let listener = TcpListener::bind("0.0.0.0:10000").await?;
|
||||||
|
|
||||||
|
let plates = Arc::new(Mutex::new(HashMap::new()));
|
||||||
|
let days_with_issued_tickets = Arc::new(Mutex::new(TicketLog::new()));
|
||||||
|
let ticket_queue = Arc::new(Mutex::new(vec![]));
|
||||||
|
let (tx, _) = broadcast::channel(100_000);
|
||||||
|
|
||||||
|
loop {
|
||||||
|
let (socket, _) = listener.accept().await?;
|
||||||
|
let this_plates = plates.clone();
|
||||||
|
let this_tx = tx.clone();
|
||||||
|
let this_queue = ticket_queue.clone();
|
||||||
|
let this_days_tickets = days_with_issued_tickets.clone();
|
||||||
|
|
||||||
|
tokio::spawn(async move {
|
||||||
|
match handle_connection(socket, this_plates, this_queue, this_days_tickets, this_tx)
|
||||||
|
.await
|
||||||
|
{
|
||||||
|
Ok(()) => (),
|
||||||
|
Err(e) => {
|
||||||
|
dbg!(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn handle_connection(
|
||||||
|
mut socket: TcpStream,
|
||||||
|
plates: Arc<Mutex<PlateLog>>,
|
||||||
|
ticket_queue: Arc<Mutex<Vec<Ticket>>>,
|
||||||
|
days_with_issued_tickets: Arc<Mutex<TicketLog>>,
|
||||||
|
tx: Sender<ChannelMessage>,
|
||||||
|
) -> Result<()> {
|
||||||
|
let (mut reader, mut writer) = socket.split();
|
||||||
|
|
||||||
|
let mut heartbeat_interval = None;
|
||||||
|
let mut timer = tokio::time::interval(std::time::Duration::from_hours(100));
|
||||||
|
timer.tick().await;
|
||||||
|
|
||||||
|
loop {
|
||||||
|
tokio::select! {
|
||||||
|
_ = timer.tick() => {
|
||||||
|
writer.write_all(&[0x41]).await?;
|
||||||
|
}
|
||||||
|
byte = reader.read_u8() => {
|
||||||
|
match byte? {
|
||||||
|
0x40 => {
|
||||||
|
if heartbeat_interval.is_some() {
|
||||||
|
send_error_message(&mut writer, "multiple heartbeat requests").await?;
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
let interval = reader.read_u32().await?;
|
||||||
|
heartbeat_interval = Some(interval);
|
||||||
|
if interval > 0 {
|
||||||
|
timer = tokio::time::interval(std::time::Duration::from_millis(
|
||||||
|
100.max((interval as u64 * 100) - 100),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
0x80 => {
|
||||||
|
camera_main_loop(reader, writer, plates, ticket_queue, timer, heartbeat_interval, tx).await?;
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
0x81 => {
|
||||||
|
let rx = tx.subscribe();
|
||||||
|
dispatcher_main_loop(reader, writer, ticket_queue, days_with_issued_tickets, timer, heartbeat_interval, rx)
|
||||||
|
.await?;
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
other => {
|
||||||
|
return send_error_message(
|
||||||
|
&mut writer,
|
||||||
|
&format!("initialized with wrong message type 0x{other:x}"),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn dispatcher_main_loop(
|
||||||
|
mut reader: tokio::net::tcp::ReadHalf<'_>,
|
||||||
|
mut writer: tokio::net::tcp::WriteHalf<'_>,
|
||||||
|
ticket_queue: Arc<Mutex<Vec<Ticket>>>,
|
||||||
|
days_with_issued_tickets: Arc<Mutex<TicketLog>>,
|
||||||
|
mut timer: tokio::time::Interval,
|
||||||
|
mut heartbeat_interval: Option<u32>,
|
||||||
|
mut rx: Receiver<ChannelMessage>,
|
||||||
|
) -> Result<()> {
|
||||||
|
let num_roads = reader.read_u8().await?;
|
||||||
|
let mut roads = vec![];
|
||||||
|
|
||||||
|
for _ in 0..num_roads {
|
||||||
|
roads.push(reader.read_u16().await?);
|
||||||
|
}
|
||||||
|
|
||||||
|
work_ticket_queue(
|
||||||
|
&roads,
|
||||||
|
&mut writer,
|
||||||
|
&ticket_queue,
|
||||||
|
&days_with_issued_tickets,
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
loop {
|
||||||
|
tokio::select! {
|
||||||
|
_ = timer.tick() => {
|
||||||
|
writer.write_all(&[0x41]).await?;
|
||||||
|
}
|
||||||
|
_ = rx.recv() => {
|
||||||
|
work_ticket_queue(&roads, &mut writer, &ticket_queue, &days_with_issued_tickets).await?;
|
||||||
|
}
|
||||||
|
byte = reader.read_u8() => {
|
||||||
|
match byte? {
|
||||||
|
0x40 => {
|
||||||
|
if heartbeat_interval.is_some() {
|
||||||
|
send_error_message(&mut writer, "multiple heartbeat requests").await?;
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
heartbeat_interval = Some(reader.read_u32().await?);
|
||||||
|
if let Some(interval) = heartbeat_interval
|
||||||
|
&& interval > 0
|
||||||
|
{
|
||||||
|
timer = tokio::time::interval(std::time::Duration::from_millis(
|
||||||
|
100.max((interval * 100) as u64 - 100),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
return send_error_message(&mut writer, "received wrong message type").await;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn work_ticket_queue(
|
||||||
|
roads: &[u16],
|
||||||
|
writer: &mut tokio::net::tcp::WriteHalf<'_>,
|
||||||
|
ticket_queue: &Arc<Mutex<Vec<Ticket>>>,
|
||||||
|
days_with_issued_tickets: &Arc<Mutex<TicketLog>>,
|
||||||
|
) -> Result<()> {
|
||||||
|
let messages = generate_ticket_messages(ticket_queue, roads, days_with_issued_tickets).await?;
|
||||||
|
|
||||||
|
for message in messages {
|
||||||
|
writer.write_all(&message).await?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn generate_ticket_messages(
|
||||||
|
queue: &Arc<Mutex<Vec<Ticket>>>,
|
||||||
|
roads: &[u16],
|
||||||
|
days_with_issued_tickets: &Arc<Mutex<TicketLog>>,
|
||||||
|
) -> Result<Vec<Vec<u8>>> {
|
||||||
|
let mut queue = queue.lock().unwrap();
|
||||||
|
|
||||||
|
let mut days = days_with_issued_tickets.lock().unwrap();
|
||||||
|
|
||||||
|
let own_tickets = queue
|
||||||
|
.iter()
|
||||||
|
.positions(|ticket| roads.contains(&ticket.road));
|
||||||
|
|
||||||
|
let mut messages = vec![];
|
||||||
|
let mut to_remove = vec![];
|
||||||
|
|
||||||
|
for idx in own_tickets {
|
||||||
|
let ticket = queue.get(idx).unwrap();
|
||||||
|
to_remove.push(idx);
|
||||||
|
|
||||||
|
let day1 = timestamp_to_day(ticket.timestamp1);
|
||||||
|
let day2 = timestamp_to_day(ticket.timestamp2);
|
||||||
|
|
||||||
|
let key1 = (ticket.plate.clone(), day1);
|
||||||
|
let key2 = (ticket.plate.clone(), day2);
|
||||||
|
|
||||||
|
if days.contains(&key1) || days.contains(&key2) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
days.insert(key1);
|
||||||
|
days.insert(key2);
|
||||||
|
|
||||||
|
let to_wire = ticket_to_vec_u8(ticket);
|
||||||
|
|
||||||
|
messages.push(to_wire);
|
||||||
|
}
|
||||||
|
|
||||||
|
to_remove.sort();
|
||||||
|
|
||||||
|
for idx in to_remove.iter().rev() {
|
||||||
|
queue.remove(*idx);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(messages)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn timestamp_to_day(timestamp: u32) -> usize {
|
||||||
|
(timestamp / 86400) as _
|
||||||
|
}
|
||||||
|
|
||||||
|
fn ticket_to_vec_u8(ticket: &Ticket) -> Vec<u8> {
|
||||||
|
let mut to_wire = vec![0x21];
|
||||||
|
|
||||||
|
// plate
|
||||||
|
to_wire.push(ticket.plate.len() as u8);
|
||||||
|
for byte in ticket.plate.as_bytes() {
|
||||||
|
to_wire.push(*byte);
|
||||||
|
}
|
||||||
|
|
||||||
|
// road
|
||||||
|
for byte in ticket.road.to_be_bytes() {
|
||||||
|
to_wire.push(byte);
|
||||||
|
}
|
||||||
|
|
||||||
|
// mile 1, timestamp 1
|
||||||
|
for byte in ticket.mile1.to_be_bytes() {
|
||||||
|
to_wire.push(byte);
|
||||||
|
}
|
||||||
|
for byte in ticket.timestamp1.to_be_bytes() {
|
||||||
|
to_wire.push(byte);
|
||||||
|
}
|
||||||
|
|
||||||
|
// mile 2, timestamp 2
|
||||||
|
for byte in ticket.mile2.to_be_bytes() {
|
||||||
|
to_wire.push(byte);
|
||||||
|
}
|
||||||
|
for byte in ticket.timestamp2.to_be_bytes() {
|
||||||
|
to_wire.push(byte);
|
||||||
|
}
|
||||||
|
|
||||||
|
// speed
|
||||||
|
for byte in ticket.speed.to_be_bytes() {
|
||||||
|
to_wire.push(byte);
|
||||||
|
}
|
||||||
|
|
||||||
|
to_wire
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn camera_main_loop(
|
||||||
|
mut reader: tokio::net::tcp::ReadHalf<'_>,
|
||||||
|
mut writer: tokio::net::tcp::WriteHalf<'_>,
|
||||||
|
plates: Arc<Mutex<PlateLog>>,
|
||||||
|
ticket_queue: Arc<Mutex<Vec<Ticket>>>,
|
||||||
|
mut timer: tokio::time::Interval,
|
||||||
|
mut heartbeat_interval: Option<u32>,
|
||||||
|
tx: Sender<ChannelMessage>,
|
||||||
|
) -> Result<()> {
|
||||||
|
let myself = Camera {
|
||||||
|
road: reader.read_u16().await?,
|
||||||
|
mile: reader.read_u16().await?,
|
||||||
|
limit: reader.read_u16().await?,
|
||||||
|
};
|
||||||
|
|
||||||
|
loop {
|
||||||
|
tokio::select! {
|
||||||
|
_ = timer.tick() => {
|
||||||
|
writer.write_all(&[0x41]).await?;
|
||||||
|
}
|
||||||
|
byte = reader.read_u8() => {
|
||||||
|
match byte? {
|
||||||
|
|
||||||
|
0x20 => {
|
||||||
|
let plate = read_string(&mut reader).await?;
|
||||||
|
let timestamp = reader.read_u32().await?;
|
||||||
|
{
|
||||||
|
let mut this_plates = plates.lock().unwrap();
|
||||||
|
this_plates
|
||||||
|
.entry(plate.clone())
|
||||||
|
.and_modify(|v| v.push((myself.clone(), timestamp)))
|
||||||
|
.or_insert(vec![(myself.clone(), timestamp)]);
|
||||||
|
|
||||||
|
for ticket in find_possible_tickets(this_plates, &myself, plate, timestamp)
|
||||||
|
{
|
||||||
|
ticket_queue.lock().unwrap().push(ticket);
|
||||||
|
let _ = tx.send(ChannelMessage::TicketIssued);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
0x40 => {
|
||||||
|
if heartbeat_interval.is_some() {
|
||||||
|
send_error_message(&mut writer, "multiple heartbeat requests").await?;
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
heartbeat_interval = Some(reader.read_u32().await?);
|
||||||
|
if let Some(interval) = heartbeat_interval
|
||||||
|
&& interval > 0
|
||||||
|
{
|
||||||
|
timer = tokio::time::interval(std::time::Duration::from_millis(
|
||||||
|
100.max((interval * 100) as u64 - 100),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_ => {
|
||||||
|
return send_error_message(&mut writer, "received wrong message type").await;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn find_possible_tickets(
|
||||||
|
plates: MutexGuard<'_, PlateLog>,
|
||||||
|
this_camera: &Camera,
|
||||||
|
this_plate: String,
|
||||||
|
this_timestamp: u32,
|
||||||
|
) -> Vec<Ticket> {
|
||||||
|
let relevant: Vec<_> = plates
|
||||||
|
.iter()
|
||||||
|
.filter(|(plate, _)| **plate == this_plate)
|
||||||
|
.flat_map(|(_, camera_timestamp_map)| camera_timestamp_map)
|
||||||
|
.filter(|(camera, _)| camera.road == this_camera.road)
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
let violations = relevant
|
||||||
|
.iter()
|
||||||
|
.combinations(2)
|
||||||
|
.filter(|data| {
|
||||||
|
data.iter()
|
||||||
|
.any(|(_, timestamp)| *timestamp == this_timestamp)
|
||||||
|
})
|
||||||
|
.map(|data| (data.clone(), calculate_speed(data).unwrap()))
|
||||||
|
.filter(|(_, speed)| *speed > this_camera.limit * 100);
|
||||||
|
|
||||||
|
let mut result = vec![];
|
||||||
|
|
||||||
|
for violation in violations {
|
||||||
|
let (camera_timestamp_map, speed) = violation;
|
||||||
|
let ticket = Ticket::new(
|
||||||
|
this_plate.clone(),
|
||||||
|
this_camera.road,
|
||||||
|
camera_timestamp_map[0].0.mile,
|
||||||
|
camera_timestamp_map[0].1,
|
||||||
|
camera_timestamp_map[1].0.mile,
|
||||||
|
camera_timestamp_map[1].1,
|
||||||
|
speed,
|
||||||
|
);
|
||||||
|
|
||||||
|
result.push(ticket);
|
||||||
|
}
|
||||||
|
|
||||||
|
result
|
||||||
|
}
|
||||||
|
|
||||||
|
fn calculate_speed(timestamps: Vec<&&(Camera, u32)>) -> Option<u16> {
|
||||||
|
let mut data = timestamps.clone();
|
||||||
|
|
||||||
|
data.sort_by_key(|(_, time)| time);
|
||||||
|
|
||||||
|
let dt = (data[1].1 - data[0].1) as u64;
|
||||||
|
let ds = (data[1].0.mile as i64 - data[0].0.mile as i64).unsigned_abs();
|
||||||
|
|
||||||
|
if dt == 0 {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
Some(((ds * 3600 * 100) / dt) as _)
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn send_error_message(
|
||||||
|
writer: &mut tokio::net::tcp::WriteHalf<'_>,
|
||||||
|
message: &str,
|
||||||
|
) -> Result<()> {
|
||||||
|
let mut to_wire = vec![0x10];
|
||||||
|
|
||||||
|
ensure!(message.len() <= 255, "message too long");
|
||||||
|
to_wire.push(message.len() as u8);
|
||||||
|
|
||||||
|
for byte in message.as_bytes() {
|
||||||
|
to_wire.push(*byte);
|
||||||
|
}
|
||||||
|
|
||||||
|
writer.write_all(&to_wire).await?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn read_string(r: &mut (impl AsyncReadExt + Unpin)) -> Result<String> {
|
||||||
|
let len = r.read_u8().await?;
|
||||||
|
let mut buf = vec![0u8; len as usize];
|
||||||
|
r.read_exact(&mut buf).await?;
|
||||||
|
Ok(String::from_utf8(buf)?)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
struct Camera {
|
||||||
|
road: u16,
|
||||||
|
mile: u16,
|
||||||
|
limit: u16,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
struct Ticket {
|
||||||
|
plate: String,
|
||||||
|
road: u16,
|
||||||
|
mile1: u16,
|
||||||
|
timestamp1: u32,
|
||||||
|
mile2: u16,
|
||||||
|
timestamp2: u32,
|
||||||
|
speed: u16,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Ticket {
|
||||||
|
fn new(
|
||||||
|
plate: String,
|
||||||
|
road: u16,
|
||||||
|
mut mile1: u16,
|
||||||
|
mut timestamp1: u32,
|
||||||
|
mut mile2: u16,
|
||||||
|
mut timestamp2: u32,
|
||||||
|
speed: u16,
|
||||||
|
) -> Self {
|
||||||
|
if timestamp2 < timestamp1 {
|
||||||
|
(mile1, mile2) = (mile2, mile1);
|
||||||
|
(timestamp1, timestamp2) = (timestamp2, timestamp1);
|
||||||
|
}
|
||||||
|
Self {
|
||||||
|
plate,
|
||||||
|
road,
|
||||||
|
mile1,
|
||||||
|
timestamp1,
|
||||||
|
mile2,
|
||||||
|
timestamp2,
|
||||||
|
speed,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
#[non_exhaustive]
|
||||||
|
enum ChannelMessage {
|
||||||
|
TicketIssued,
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user