initial commit

This commit is contained in:
Sebastian Hugentobler 2022-03-12 16:19:54 +01:00
commit 3d9c98eeca
Signed by: shu
GPG Key ID: BB32CF3CA052C2F0
22 changed files with 1075 additions and 0 deletions

1
.gitignore vendored Normal file
View File

@ -0,0 +1 @@
/target

6
.idea/.gitignore vendored Normal file
View File

@ -0,0 +1,6 @@
# Default ignored files
/shelf/
/workspace.xml
# Datasource local storage ignored files
/dataSources/
/dataSources.local.xml

11
.idea/bank-server.iml Normal file
View File

@ -0,0 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="CPP_MODULE" version="4">
<component name="NewModuleRootManager">
<content url="file://$MODULE_DIR$">
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
<excludeFolder url="file://$MODULE_DIR$/target" />
</content>
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>

8
.idea/modules.xml Normal file
View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/.idea/bank-server.iml" filepath="$PROJECT_DIR$/.idea/bank-server.iml" />
</modules>
</component>
</project>

6
.idea/vcs.xml Normal file
View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$" vcs="Git" />
</component>
</project>

252
Cargo.lock generated Normal file
View File

@ -0,0 +1,252 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 3
[[package]]
name = "aho-corasick"
version = "0.7.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e37cfd5e7657ada45f742d6e99ca5788580b5c529dc78faf11ece6dc702656f"
dependencies = [
"memchr",
]
[[package]]
name = "anyhow"
version = "1.0.55"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "159bb86af3a200e19a068f4224eae4c8bb2d0fa054c7e5d1cacd5cef95e684cd"
[[package]]
name = "atty"
version = "0.2.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8"
dependencies = [
"hermit-abi",
"libc",
"winapi",
]
[[package]]
name = "bank-server"
version = "0.1.0"
dependencies = [
"anyhow",
"log",
"pretty_env_logger",
"thiserror",
"uuid",
]
[[package]]
name = "cfg-if"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "env_logger"
version = "0.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "44533bbbb3bb3c1fa17d9f2e4e38bbbaf8396ba82193c4cb1b6445d711445d36"
dependencies = [
"atty",
"humantime",
"log",
"regex",
"termcolor",
]
[[package]]
name = "getrandom"
version = "0.2.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d39cd93900197114fa1fcb7ae84ca742095eed9442088988ae74fa744e930e77"
dependencies = [
"cfg-if",
"libc",
"wasi",
]
[[package]]
name = "hermit-abi"
version = "0.1.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33"
dependencies = [
"libc",
]
[[package]]
name = "humantime"
version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "df004cfca50ef23c36850aaaa59ad52cc70d0e90243c3c7737a4dd32dc7a3c4f"
dependencies = [
"quick-error",
]
[[package]]
name = "libc"
version = "0.2.119"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1bf2e165bb3457c8e098ea76f3e3bc9db55f87aa90d52d0e6be741470916aaa4"
[[package]]
name = "log"
version = "0.4.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710"
dependencies = [
"cfg-if",
]
[[package]]
name = "memchr"
version = "2.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "308cc39be01b73d0d18f82a0e7b2a3df85245f84af96fdddc5d202d27e47b86a"
[[package]]
name = "pretty_env_logger"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "926d36b9553851b8b0005f1275891b392ee4d2d833852c417ed025477350fb9d"
dependencies = [
"env_logger",
"log",
]
[[package]]
name = "proc-macro2"
version = "1.0.36"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c7342d5883fbccae1cc37a2353b09c87c9b0f3afd73f5fb9bba687a1f733b029"
dependencies = [
"unicode-xid",
]
[[package]]
name = "quick-error"
version = "1.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0"
[[package]]
name = "quote"
version = "1.0.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "864d3e96a899863136fc6e99f3d7cae289dafe43bf2c5ac19b70df7210c0a145"
dependencies = [
"proc-macro2",
]
[[package]]
name = "regex"
version = "1.5.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d07a8629359eb56f1e2fb1652bb04212c072a87ba68546a04065d525673ac461"
dependencies = [
"aho-corasick",
"memchr",
"regex-syntax",
]
[[package]]
name = "regex-syntax"
version = "0.6.25"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f497285884f3fcff424ffc933e56d7cbca511def0c9831a7f9b5f6153e3cc89b"
[[package]]
name = "syn"
version = "1.0.86"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8a65b3f4ffa0092e9887669db0eae07941f023991ab58ea44da8fe8e2d511c6b"
dependencies = [
"proc-macro2",
"quote",
"unicode-xid",
]
[[package]]
name = "termcolor"
version = "1.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bab24d30b911b2376f3a13cc2cd443142f0c81dda04c118693e35b3835757755"
dependencies = [
"winapi-util",
]
[[package]]
name = "thiserror"
version = "1.0.30"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "854babe52e4df1653706b98fcfc05843010039b406875930a70e4d9644e5c417"
dependencies = [
"thiserror-impl",
]
[[package]]
name = "thiserror-impl"
version = "1.0.30"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "aa32fd3f627f367fe16f893e2597ae3c05020f8bba2666a4e6ea73d377e5714b"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "unicode-xid"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3"
[[package]]
name = "uuid"
version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bc5cf98d8186244414c848017f0e2676b3fcb46807f6668a97dfe67359a3c4b7"
dependencies = [
"getrandom",
]
[[package]]
name = "wasi"
version = "0.10.2+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fd6fbd9a79829dd1ad0cc20627bf1ed606756a7f77edff7b66b7064f9cb327c6"
[[package]]
name = "winapi"
version = "0.3.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
dependencies = [
"winapi-i686-pc-windows-gnu",
"winapi-x86_64-pc-windows-gnu",
]
[[package]]
name = "winapi-i686-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
[[package]]
name = "winapi-util"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178"
dependencies = [
"winapi",
]
[[package]]
name = "winapi-x86_64-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"

11
Cargo.toml Normal file
View File

@ -0,0 +1,11 @@
[package]
name = "bank-server"
version = "0.1.0"
edition = "2021"
[dependencies]
anyhow = "1.0.55"
log = "0.4.14"
pretty_env_logger = "0.4.0"
thiserror = "1.0.30"
uuid = { version = "0.8.2", features = ["v4"] }

142
src/account.rs Normal file
View File

@ -0,0 +1,142 @@
use std::hash::{Hash, Hasher};
use thiserror::Error;
use anyhow::{bail, Result};
use uuid::Uuid;
use crate::account::AccountError::{Inactive, InvalidAmount, Overdraw};
#[derive(Error, Debug)]
pub enum AccountError {
#[error("can not overdraw account")]
Overdraw(),
#[error("account is inactive")]
Inactive(),
#[error("amount must be > 0")]
InvalidAmount(),
}
#[derive(Debug, Clone)]
pub struct Account {
pub number: String,
pub owner: String,
pub balance: f64,
pub is_active: bool,
}
impl Default for Account {
fn default() -> Self {
Account {
number: Uuid::new_v4().to_string(),
owner: "".into(),
balance: 0_f64,
is_active: true,
}
}
}
impl PartialEq for Account {
fn eq(&self, other: &Self) -> bool {
self.number == other.number
}
}
impl Eq for Account {}
impl Hash for Account {
fn hash<H: Hasher>(&self, state: &mut H) {
self.number.hash(state);
}
}
impl Account {
#[cfg(test)]
pub fn new() -> Self {
Self { ..Default::default() }
}
pub fn deposit(&mut self, amount: f64) -> Result<()> {
self.check_account(amount)?;
self.balance += amount;
Ok(())
}
pub fn withdraw(&mut self, amount: f64) -> Result<()> {
self.check_account(amount)?;
if self.balance - amount < 0 as f64 {
bail!(Overdraw());
}
self.balance -= amount;
Ok(())
}
fn check_account(&self, amount: f64) -> Result<()> {
if !self.is_active {
bail!(Inactive());
}
if amount < 0 as f64 {
bail!(InvalidAmount());
}
Ok(())
}
pub fn passivate(&mut self) -> bool {
let is_passivated = self.balance <= 0 as f64 && self.is_active;
if is_passivated {
self.is_active = false;
}
is_passivated
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn deposits() {
let mut acc = Account::new();
let ok_result = acc.deposit(10.56);
assert!(ok_result.is_ok());
let err_result = acc.deposit(-5.89);
assert!(err_result.is_err());
}
#[test]
fn withdrawals() {
let mut acc = Account::new();
let ok_result1 = acc.deposit(10_f64);
assert!(ok_result1.is_ok());
let ok_result2 = acc.withdraw(5_f64);
assert!(ok_result2.is_ok());
let err_result1 = acc.withdraw(10_f64);
assert!(err_result1.is_err());
let err_result2 = acc.withdraw(-10_f64);
assert!(err_result2.is_err());
}
#[test]
fn passivation() {
let mut acc = Account::new();
let deposit_amount = 100_f64;
acc.deposit(deposit_amount).unwrap();
assert!(!acc.passivate());
acc.withdraw(deposit_amount).unwrap();
assert!(acc.passivate());
assert!(!acc.passivate());
}
}

86
src/bank.rs Normal file
View File

@ -0,0 +1,86 @@
use std::collections::{HashMap, HashSet};
use std::sync::RwLock;
#[cfg(test)]
use anyhow::Result;
use crate::account::Account;
pub struct Bank {
pub accounts: HashMap<String, RwLock<Account>>,
}
impl Bank {
pub fn new() -> Self {
Self { accounts: HashMap::new() }
}
pub fn account_numbers(&self) -> HashSet<String> {
self.accounts.iter()
.filter(|(_, acc)| acc.read().unwrap().is_active)
.map(|(_, acc)| acc.read().unwrap().number.clone())
.collect()
}
pub fn create_account(&mut self, owner: String) -> String {
let acc = Account { owner, ..Default::default() };
let number = acc.number.clone();
self.accounts.insert(acc.number.clone(), RwLock::new(acc));
number
}
#[cfg(test)]
pub fn transfer(&self, from: &mut Account, to: &mut Account, amount: f64) -> Result<()> {
from.withdraw(amount)?;
to.deposit(amount)?;
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn creation() {
let mut bank = Bank::new();
let nr = bank.create_account("Rohaja".into());
assert!(bank.accounts.get(&nr).is_some());
assert!(bank.accounts.get("thisisnotanuuid").is_none());
}
#[test]
fn transfer() {
let mut bank = Bank::new();
let nr1 = bank.create_account("Rohaja".into());
let nr2 = bank.create_account("Hal".into());
{
let mut acc1 = bank.accounts.get(&nr1).unwrap().write().unwrap();
let mut acc2 = bank.accounts.get(&nr2).unwrap().write().unwrap();
let result = bank.transfer(&mut acc1, &mut acc2, 100_f64);
assert!(result.is_err());
acc1.deposit(100_f64).unwrap();
let result = bank.transfer(&mut acc1, &mut acc2, 50_f64);
assert!(result.is_ok());
assert_eq!(acc2.balance, 50_f64);
let result = bank.transfer(&mut acc1, &mut acc2, -50_f64);
assert!(result.is_err());
acc2.withdraw(50_f64).unwrap();
assert!(acc2.passivate());
let result = bank.transfer(&mut acc1, &mut acc2, 50_f64);
assert!(result.is_err());
}
assert_eq!(1, bank.account_numbers().len());
}
}

View File

@ -0,0 +1,32 @@
use std::io::Write;
use std::net::TcpStream;
use std::sync::{Arc, RwLock};
use anyhow::Result;
use crate::bank::Bank;
use crate::commands::Command;
use crate::protocol;
pub struct CloseAccount;
impl Command for CloseAccount {
fn execute(&self, bank: Arc<RwLock<Bank>>, data: &[u8], mut stream: &TcpStream) -> Result<usize> {
debug!("account nr bytes {:?}", data);
let nr = String::from_utf8_lossy(data).to_string();
info!("closing account {}...", nr);
let bank = bank.read().unwrap();
let written = match bank.accounts.get(&nr) {
Some(acc) => {
let mut acc = acc.write().unwrap();
stream.write(&protocol::account_passivate(acc.passivate()))?
}
None => stream.write(&protocol::account_passivate(false))?
};
Ok(written)
}
}

View File

@ -0,0 +1,27 @@
use std::io::Write;
use std::net::TcpStream;
use std::sync::{Arc, RwLock};
use anyhow::Result;
use crate::bank::Bank;
use crate::commands::Command;
use crate::protocol;
pub struct CreateAccount;
impl Command for CreateAccount {
fn execute(&self, bank: Arc<RwLock<Bank>>, data: &[u8], mut stream: &TcpStream) -> Result<usize> {
debug!("owner nr bytes {:?}", data);
let owner = String::from_utf8_lossy(data);
info!("creating new account with owner {}...", owner);
let mut bank = bank.write().unwrap();
let nr = bank.create_account(owner.into());
info!("created account {}", nr);
let written = stream.write(&protocol::account_nr(&nr))?;
Ok(written)
}
}

37
src/commands/deposit.rs Normal file
View File

@ -0,0 +1,37 @@
use std::io::Write;
use std::net::TcpStream;
use std::sync::{Arc, RwLock};
use anyhow::Result;
use crate::bank::Bank;
use crate::commands::Command;
use crate::protocol;
pub struct Deposit;
impl Command for Deposit {
fn execute(&self, bank: Arc<RwLock<Bank>>, data: &[u8], mut stream: &TcpStream) -> Result<usize> {
let value_bytes: [u8; 8] = <[u8; 8]>::try_from(data[..8].to_vec().as_slice())?;
debug!("value bytes {:?}", value_bytes);
let nr_bytes = &data[8..].to_vec();
debug!("nr bytes {:?}", nr_bytes);
let amount = f64::from_be_bytes(value_bytes);
let nr = String::from_utf8_lossy(nr_bytes).to_string();
info!("depositing {} into {}...", amount, nr);
let bank = bank.read().unwrap();
let written = match bank.accounts.get(&nr) {
Some(acc) => {
let mut acc = acc.write().unwrap();
stream.write(&protocol::deposit(acc.deposit(amount)))?
}
None => stream.write(&protocol::error(2))?
};
Ok(written)
}
}

19
src/commands/fail.rs Normal file
View File

@ -0,0 +1,19 @@
use std::io::Write;
use std::net::TcpStream;
use std::sync::{Arc, RwLock};
use anyhow::Result;
use crate::bank::Bank;
use crate::commands::Command;
use crate::protocol;
pub struct Fail;
impl Command for Fail {
fn execute(&self, _: Arc<RwLock<Bank>>, error_code: &[u8], mut stream: &TcpStream) -> Result<usize> {
error!("sending error code {}", error_code[0]);
let written = stream.write(&protocol::error(error_code[0]))?;
Ok(written)
}
}

View File

@ -0,0 +1,32 @@
use std::io::Write;
use std::net::TcpStream;
use std::sync::{Arc, RwLock};
use anyhow::Result;
use crate::bank::Bank;
use crate::commands::Command;
use crate::protocol;
pub struct GetAccount;
impl Command for GetAccount {
fn execute(&self, bank: Arc<RwLock<Bank>>, data: &[u8], mut stream: &TcpStream) -> Result<usize> {
debug!("account nr bytes {:?}", data);
let nr = String::from_utf8_lossy(data).to_string();
info!("getting account {:?}...", nr);
let bank = bank.read().unwrap();
let written = match bank.accounts.get(&nr) {
Some(acc) => {
let acc = acc.read().unwrap();
stream.write(&protocol::account(&acc))?
}
None => stream.write(&protocol::error(2))?
};
Ok(written)
}
}

View File

@ -0,0 +1,26 @@
use std::io::Write;
use std::net::TcpStream;
use std::sync::{Arc, RwLock};
use anyhow::Result;
use crate::bank::Bank;
use crate::commands::Command;
use crate::protocol;
pub struct GetAccountNrs;
impl Command for GetAccountNrs {
fn execute(&self, bank: Arc<RwLock<Bank>>, _: &[u8], mut stream: &TcpStream) -> Result<usize> {
info!("getting account numbers...");
let bank = bank.read().unwrap();
let nrs: Vec<String> = bank.account_numbers()
.into_iter()
.collect();
let written = stream.write(&protocol::account_nrs(&nrs))?;
Ok(written)
}
}

67
src/commands/mod.rs Normal file
View File

@ -0,0 +1,67 @@
use std::collections::HashMap;
use std::net::TcpStream;
use std::sync::{Arc, RwLock};
use anyhow::Result;
use crate::account::AccountError;
use crate::bank::Bank;
use crate::commands::close_account::CloseAccount;
use crate::commands::create_account::CreateAccount;
use crate::commands::deposit::Deposit;
use crate::commands::fail::Fail;
use crate::commands::get_account::GetAccount;
use crate::commands::get_account_nrs::GetAccountNrs;
use crate::commands::pong::Pong;
use crate::commands::withdraw::Withdraw;
mod pong;
mod fail;
mod close_account;
mod create_account;
mod deposit;
mod get_account;
mod get_account_nrs;
mod withdraw;
pub trait Command: Sync + Send {
fn execute(&self, bank: Arc<RwLock<Bank>>, data: &[u8], stream: &TcpStream) -> Result<usize>;
}
pub struct Commands {
cmds: HashMap<u8, Box<dyn Command>>,
}
impl From<&AccountError> for u8 {
fn from(error: &AccountError) -> Self {
match error {
AccountError::Overdraw() => 11,
AccountError::Inactive() => 12,
AccountError::InvalidAmount() => 13,
}
}
}
impl Commands {
pub fn new() -> Self {
let mut cmds = HashMap::new();
cmds.insert(1_u8, Box::new(Pong) as Box<dyn Command>);
cmds.insert(2_u8, Box::new(CreateAccount) as Box<dyn Command>);
cmds.insert(3_u8, Box::new(GetAccount) as Box<dyn Command>);
cmds.insert(4_u8, Box::new(GetAccountNrs) as Box<dyn Command>);
cmds.insert(5_u8, Box::new(CloseAccount) as Box<dyn Command>);
cmds.insert(6_u8, Box::new(Deposit) as Box<dyn Command>);
cmds.insert(7_u8, Box::new(Withdraw) as Box<dyn Command>);
Self {
cmds
}
}
pub fn run(&self, bank: Arc<RwLock<Bank>>, cmd: u8, data: &[u8], stream: &TcpStream) -> Result<usize> {
match self.cmds.get(&cmd) {
None => Fail.execute(bank, &[1_u8], stream),
Some(cmd) => cmd.execute(bank, data, stream)
}
}
}

19
src/commands/pong.rs Normal file
View File

@ -0,0 +1,19 @@
use std::io::Write;
use std::net::TcpStream;
use std::sync::{Arc, RwLock};
use anyhow::Result;
use crate::bank::Bank;
use crate::commands::Command;
use crate::protocol;
pub struct Pong;
impl Command for Pong {
fn execute(&self, _: Arc<RwLock<Bank>>, _: &[u8], mut stream: &TcpStream) -> Result<usize> {
info!("sending 'pong'");
let written = stream.write(&[protocol::PONG])?;
Ok(written)
}
}

37
src/commands/withdraw.rs Normal file
View File

@ -0,0 +1,37 @@
use std::io::Write;
use std::net::TcpStream;
use std::sync::{Arc, RwLock};
use anyhow::Result;
use crate::bank::Bank;
use crate::commands::Command;
use crate::protocol;
pub struct Withdraw;
impl Command for Withdraw {
fn execute(&self, bank: Arc<RwLock<Bank>>, data: &[u8], mut stream: &TcpStream) -> Result<usize> {
let value_bytes: [u8; 8] = <[u8; 8]>::try_from(data[..8].to_vec().as_slice())?;
debug!("value bytes {:?}", value_bytes);
let nr_bytes = &data[8..].to_vec();
debug!("nr bytes {:?}", nr_bytes);
let amount = f64::from_be_bytes(value_bytes);
let nr = String::from_utf8_lossy(nr_bytes).to_string();
info!("withdrawing {} from {}...", amount, nr);
let bank = bank.read().unwrap();
let written = match bank.accounts.get(&nr) {
Some(acc) => {
let mut acc = acc.write().unwrap();
stream.write(&protocol::withdraw(acc.withdraw(amount)))?
}
None => stream.write(&protocol::error(2))?
};
Ok(written)
}
}

17
src/main.rs Normal file
View File

@ -0,0 +1,17 @@
#[macro_use]
extern crate log;
mod account;
mod bank;
mod protocol;
mod server;
mod threadpool;
mod commands;
fn main() {
pretty_env_logger::init();
let host = "127.0.0.1:1234";
info!("running on {}", host);
server::run(host, 8).expect("failed to run server");
}

85
src/protocol.rs Normal file
View File

@ -0,0 +1,85 @@
use crate::account::{Account, AccountError};
use anyhow::Result;
pub const START: [u8; 2] = [0xde, 0xad];
pub const PONG: u8 = 0b0010_0000;
fn to_error_code(error: anyhow::Error, default: u8) -> u8 {
match error.root_cause().downcast_ref::<AccountError>() {
Some(e) => e.into(),
None => default,
}
}
pub fn account_nr(nr: &str) -> Vec<u8> {
let mut response = vec![PONG];
response.append(&mut nr.as_bytes().to_vec());
debug!("account number bytes: {:?}", response);
response
}
pub fn account_passivate(was_passivated: bool) -> Vec<u8> {
let is_active_byte: u8 = if was_passivated { 1 } else { 0 };
vec![PONG | is_active_byte]
}
pub fn deposit(result: Result<()>) -> Vec<u8> {
match result {
Err(e) => error(to_error_code(e, 10)).to_vec(),
Ok(_) => vec![PONG]
}
}
pub fn withdraw(result: Result<()>) -> Vec<u8> {
match result {
Err(e) => error(to_error_code(e, 10)).to_vec(),
Ok(_) => vec![PONG]
}
}
pub fn account_nrs(nrs: &[String]) -> Vec<u8> {
let mut response = vec![PONG];
nrs.iter().for_each(|key| {
let key_bytes = key.as_bytes();
debug!("account number bytes: {:?}", key_bytes);
response.push(key_bytes.len() as u8);
response.append(&mut key_bytes.to_vec());
});
debug!("account numbers bytes: {:?}", response);
response
}
pub fn account(account: &Account) -> Vec<u8> {
let is_active_byte: u8 = if account.is_active { 1 } else { 0 };
let mut response = vec![PONG | is_active_byte];
let nr_bytes = account.number.as_bytes();
let mut nr_bytes_sized = vec![nr_bytes.len() as u8];
nr_bytes_sized.append(&mut nr_bytes.to_vec());
debug!("nr bytes: {:?}", nr_bytes_sized);
let owner_bytes = account.owner.as_bytes();
let mut owner_bytes_sized = vec![owner_bytes.len() as u8];
owner_bytes_sized.append(&mut owner_bytes.to_vec());
debug!("owner bytes: {:?}", owner_bytes_sized);
let balance: [u8; 8] = account.balance.to_be_bytes();
debug!("balance bytes: {:?}", balance);
response.append(&mut nr_bytes_sized);
response.append(&mut owner_bytes_sized);
response.append(&mut balance.to_vec());
debug!("account response: {:?}", response);
response
}
pub fn error(code: u8) -> [u8; 1] {
[0b0100_0000 | code]
}

60
src/server.rs Normal file
View File

@ -0,0 +1,60 @@
use std::io::{Read, Write};
use std::net::{TcpListener, TcpStream};
use std::sync::{Arc, RwLock};
use anyhow::{bail, Result};
use crate::bank::Bank;
use crate::commands::Commands;
use crate::protocol;
use crate::threadpool::ThreadPool;
pub fn run(host: &str, threads: usize) -> Result<()> {
let listener = TcpListener::bind(host)?;
let pool = ThreadPool::new(threads);
let cmds = Arc::new(Commands::new());
let bank = Arc::new(RwLock::new(Bank::new()));
for stream in listener.incoming() {
let cmds = cmds.clone();
let bank = bank.clone();
let stream = stream?;
pool.execute(move || {
if let Err(e) = handle_connection(bank, &cmds, stream) {
error!("{}", e);
}
});
}
Ok(())
}
fn handle_connection(bank: Arc<RwLock<Bank>>, cmds: &Commands, mut stream: TcpStream) -> Result<()> {
const BUF_SIZE: usize = 64;
let mut data: Vec<u8> = vec![];
let mut buffer = [0; BUF_SIZE];
let mut read = stream.read(&mut buffer)?;
while read > 0 {
data.append(&mut buffer[..read].to_vec());
read = stream.read(&mut buffer)?;
}
if data[..2] != protocol::START {
bail!("got {:?} as first bytes, not my problem", &data[..2]);
}
let cmd: u8 = (data[2] & 0b1111_0000) >> 4;
trace!("got command {}", cmd);
cmds.run(bank, cmd, &data[3..], &stream)?;
stream.flush()?;
Ok(())
}

94
src/threadpool.rs Normal file
View File

@ -0,0 +1,94 @@
use std::sync::Arc;
use std::sync::mpsc;
use std::sync::Mutex;
use std::thread;
pub struct ThreadPool {
workers: Vec<Worker>,
sender: mpsc::Sender<Message>,
}
type Job = Box<dyn FnOnce() + Send + 'static>;
enum Message {
NewJob(Job),
Terminate,
}
impl ThreadPool {
pub fn new(size: usize) -> ThreadPool {
assert!(size > 0);
let (sender, receiver) = mpsc::channel();
let receiver = Arc::new(Mutex::new(receiver));
let mut workers = Vec::with_capacity(size);
for id in 0..size {
workers.push(Worker::new(id, Arc::clone(&receiver)));
}
ThreadPool { workers, sender }
}
pub fn execute<F>(&self, f: F)
where
F: FnOnce() + Send + 'static,
{
let job = Box::new(f);
self.sender.send(Message::NewJob(job)).unwrap();
}
}
impl Drop for ThreadPool {
fn drop(&mut self) {
trace!("Sending terminate message to all workers.");
for _ in &self.workers {
self.sender.send(Message::Terminate).unwrap();
}
trace!("Shutting down all workers.");
for worker in &mut self.workers {
trace!("Shutting down worker {}", worker.id);
if let Some(thread) = worker.thread.take() {
thread.join().unwrap();
}
}
}
}
struct Worker {
id: usize,
thread: Option<thread::JoinHandle<()>>,
}
impl Worker {
fn new(id: usize, receiver: Arc<Mutex<mpsc::Receiver<Message>>>) -> Worker {
let thread = thread::spawn(move || loop {
let message = receiver.lock().unwrap().recv().unwrap();
match message {
Message::NewJob(job) => {
trace!("Worker {} got a job; executing.", id);
job();
}
Message::Terminate => {
trace!("Worker {} was told to terminate.", id);
break;
}
}
});
Worker {
id,
thread: Some(thread),
}
}
}