Skip to content
Snippets Groups Projects
Commit ce054ca5 authored by David Huss's avatar David Huss :speech_balloon:
Browse files

Fix eventhandler behavior

parent e06f5c8f
No related branches found
No related tags found
No related merge requests found
...@@ -1376,7 +1376,7 @@ checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0" ...@@ -1376,7 +1376,7 @@ checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0"
[[package]] [[package]]
name = "sms-gateway" name = "sms-gateway"
version = "0.4.0" version = "0.4.1"
dependencies = [ dependencies = [
"actix-rt", "actix-rt",
"actix-web", "actix-web",
......
...@@ -39,6 +39,7 @@ function build_and_deploy { ...@@ -39,6 +39,7 @@ function build_and_deploy {
echo echo
} }
cargo test
# Build all binaries and deploy them to the remote target, below incantation lists all binary targets and loops over them # Build all binaries and deploy them to the remote target, below incantation lists all binary targets and loops over them
for BINARY in $(cargo run --bin -- 2>&1 | python -c 'import sys; x=sys.stdin.readlines(); print("\n".join([l.strip() for l in x[2:] if l.strip() != ""]))') for BINARY in $(cargo run --bin -- 2>&1 | python -c 'import sys; x=sys.stdin.readlines(); print("\n".join([l.strip() for l in x[2:] if l.strip() != ""]))')
......
...@@ -19,7 +19,7 @@ const BINARY_NAME: &str = env!("CARGO_BIN_NAME"); ...@@ -19,7 +19,7 @@ const BINARY_NAME: &str = env!("CARGO_BIN_NAME");
fn help() { fn help() {
info!("Error: Insufficient number of arguments! Expected two or three arguments."); info!("Error: Insufficient number of arguments! Expected two or three arguments.");
exit(1); exit(142);
} }
...@@ -30,15 +30,15 @@ fn main() -> Result<(), Box<dyn std::error::Error>> { ...@@ -30,15 +30,15 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
let args: Vec<String> = args().collect(); let args: Vec<String> = args().collect();
match args.len() { match args.len() {
2 | 3 => { 3 | 4 => {
let kind = args[1].to_ascii_lowercase(); let kind = args[1].to_ascii_lowercase();
let path = PathBuf::from(&args[2]); let mut path = PathBuf::from(&args[2]);
let content = match std::fs::read_to_string(&path) { let content = match std::fs::read_to_string(&path) {
Ok(s) => s, Ok(s) => s,
Err(e) => { Err(e) => {
error!("Could not read {} SMS from path \"{}\": {}", kind, path.to_string_lossy(), e); error!("Could not read {} SMS from path \"{}\": {}", kind, path.to_string_lossy(), e);
exit(1); exit(130);
} }
}; };
...@@ -54,7 +54,7 @@ fn main() -> Result<(), Box<dyn std::error::Error>> { ...@@ -54,7 +54,7 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
}, },
_ => { _ => {
error!("Error: Unhandled event type: {}", &kind[..]); error!("Error: Unhandled event type: {}", &kind[..]);
exit(1); exit(0);
} }
} }
...@@ -64,6 +64,8 @@ fn main() -> Result<(), Box<dyn std::error::Error>> { ...@@ -64,6 +64,8 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
let mut map = HashMap::new(); let mut map = HashMap::new();
map.insert("kind", kind); map.insert("kind", kind);
map.insert("sms", content.replace("\\n", "\n")); map.insert("sms", content.replace("\\n", "\n"));
path = PathBuf::from(path.file_name().unwrap());
map.insert("path", path.to_string_lossy().to_string());
debug!("Json Payload to be sent: {:#?}", &map); debug!("Json Payload to be sent: {:#?}", &map);
......
use std::{ use std::{
sync::Mutex, sync::Mutex,
thread,
time::Duration, time::Duration,
}; };
...@@ -33,7 +34,7 @@ use build_time::build_time_local; ...@@ -33,7 +34,7 @@ use build_time::build_time_local;
use sms_gateway::{ use sms_gateway::{
event, event,
errors::SmsError, errors::SmsError,
sms::Sms, sms::{Sms, SmsRecord},
event::{Event, EventJson}, event::{Event, EventJson},
history::History, history::History,
PACKAGE_NAME, PACKAGE_NAME,
...@@ -65,34 +66,63 @@ async fn sms_send(info: Json<Sms>, data: Data<Mutex<History>>) -> Result<Json<Sm ...@@ -65,34 +66,63 @@ async fn sms_send(info: Json<Sms>, data: Data<Mutex<History>>) -> Result<Json<Sm
info!("Received a request to send SMS to {}: {}", sms.number, sms.text); info!("Received a request to send SMS to {}: {}", sms.number, sms.text);
// Send the file out // Send the file out
sms.send()?; let path = sms.send()?;
let sms_record = SmsRecord { sms: sms.clone(), state: None, filename: path.clone()};
info!("SMS record: {:#?}", sms_record);
// Unlock the history Mutex and store the number + timestamp // Unlock the history Mutex and store the number + timestamp
{
let mut history = data.lock().unwrap(); let mut history = data.lock().unwrap();
history.store(&sms.number); history.store(sms_record);
info!("SMS within the last minute: {}", history.sms_within_last_minute()); info!("SMS within the last minute: {}", history.sms_within_last_minute());
}
Ok(web::Json(sms)) let mut counter = 0;
let interval = 100;
loop {
{
let history = data.lock().unwrap();
match history.sms.get(&path).unwrap().state {
Some(event::Kind::Sent) => {
return Ok(web::Json(sms))
},
Some(event::Kind::Failed) => {
return Err(SmsError::SendFailed);
},
_ => {}
}
}
if counter > 100 {
error!("Failed to read SMS status within the deadline of {} ms", interval*100);
return Err(SmsError::SendTimeOut);
}
thread::sleep(Duration::from_millis(interval));
counter += 1;
}
} }
#[post("/eventhandler")] #[post("/eventhandler")]
async fn eventhandler(info: Json<EventJson>) -> Result<Json<Event>, SmsError> { async fn eventhandler(info: Json<EventJson>, data: Data<Mutex<History>>) -> Result<Json<Event>, SmsError> {
let parsed_event = Event::try_from(&info.kind, &info.sms)?; let parsed_event = Event::try_from(&info.kind, &info.sms, info.path.clone())?;
info!("Received a \"{:?}\" event from the sms daemon : {:?}", parsed_event.kind, parsed_event.sms); info!("Received a \"{:?}\" event from the sms daemon : {:?}", parsed_event.kind, parsed_event.sms);
let mut history = data.lock().unwrap();
let mut record = history.sms.get_mut(&parsed_event.path).unwrap();
match parsed_event.kind { match parsed_event.kind {
event::Kind::Failed => { event::Kind::Failed => {
info!("Failed to send an SMS to the number \"{}\": \"{}\"", parsed_event.sms.number, parsed_event.sms.text); info!("Failed to send an SMS to the number \"{}\": \"{}\"", parsed_event.sms.number, parsed_event.sms.text.replace("\n", "\\n"));
// todo!("Failed SMS could be marked somehow in history?"); record.state = Some(event::Kind::Failed);
}, },
event::Kind::Sent => { event::Kind::Sent => {
info!("SMS to the number \"{}\" has been sent successfully: \"{}\"", parsed_event.sms.number, parsed_event.sms.text); info!("SMS to the number \"{}\" has been sent successfully: \"{}\"", parsed_event.sms.number, parsed_event.sms.text.replace("\n", "\\n"));
// todo!("Sucessfully sent SMS could be marked somehow in history?"); record.state = Some(event::Kind::Sent);
}, },
event::Kind::Received => { event::Kind::Received => {
info!("Received an SMS from the number \"{}\": \"{}\"", parsed_event.sms.number, parsed_event.sms.text); info!("Received an SMS from the number \"{}\": \"{}\"", parsed_event.sms.number, parsed_event.sms.text.replace("\n", "\\n"));
// todo!("Send email/sms to admin"); record.state = Some(event::Kind::Received);
} }
} }
...@@ -119,23 +149,25 @@ async fn main() -> std::io::Result<()> { ...@@ -119,23 +149,25 @@ async fn main() -> std::io::Result<()> {
} }
info!("Started serving on {}:{} ...", CONFIG.server.host, CONFIG.server.port); info!("Started serving on {}:{} ...", CONFIG.server.host, CONFIG.server.port);
let data = Data::new(Mutex::new(History::new())); let history_data = Data::new(Mutex::new(History::new()));
// Run a cleanup task regularily to delete old data // Run a cleanup task regularily to delete old data
let data_clone = Data::clone(&data); let history_data_clone = Data::clone(&history_data);
spawn(async move { spawn(async move {
let mut interval = time::interval(Duration::from_secs(CONFIG.retention.cleanup_interval_seconds).into()); let mut interval = time::interval(Duration::from_secs(CONFIG.retention.cleanup_interval_seconds).into());
loop { loop {
interval.tick().await; interval.tick().await;
let history = &mut data_clone.lock().unwrap(); let history = &mut history_data_clone.lock().unwrap();
history.clean(); history.clean();
} }
}); });
let history_data = Data::new(Mutex::new(History::new()));
HttpServer::new(move || { HttpServer::new(move || {
App::new() App::new()
.app_data( .app_data(
Data::clone(&data) Data::clone(&history_data)
) )
.wrap(Logger::default()) .wrap(Logger::default())
.service(home) .service(home)
......
...@@ -25,6 +25,10 @@ pub enum SmsError { ...@@ -25,6 +25,10 @@ pub enum SmsError {
NotInWhiteList(String), NotInWhiteList(String),
#[error("The provided number '{0}' as it matched at least one entry of the blacklist")] #[error("The provided number '{0}' as it matched at least one entry of the blacklist")]
InBlackList(String), InBlackList(String),
#[error("Sending SMS failed")]
SendFailed,
#[error("Sending SMS failed, because there was no sent SMS within the given timeout")]
SendTimeOut,
} }
impl SmsError { impl SmsError {
...@@ -40,6 +44,8 @@ impl SmsError { ...@@ -40,6 +44,8 @@ impl SmsError {
SmsError::DeserializationError(_s) => "Internal Server Error".to_string(), SmsError::DeserializationError(_s) => "Internal Server Error".to_string(),
SmsError::NotInWhiteList(_) => "Forbidden".to_string(), SmsError::NotInWhiteList(_) => "Forbidden".to_string(),
SmsError::InBlackList(_) => "Forbidden".to_string(), SmsError::InBlackList(_) => "Forbidden".to_string(),
SmsError::SendFailed => "Service Unavailable".to_string(),
SmsError::SendTimeOut => "Gateway Timeout".to_string(),
} }
} }
} }
...@@ -68,6 +74,8 @@ impl ResponseError for SmsError { ...@@ -68,6 +74,8 @@ impl ResponseError for SmsError {
SmsError::DeserializationError(_) => StatusCode::INTERNAL_SERVER_ERROR, SmsError::DeserializationError(_) => StatusCode::INTERNAL_SERVER_ERROR,
SmsError::NotInWhiteList(_) => StatusCode::FORBIDDEN, SmsError::NotInWhiteList(_) => StatusCode::FORBIDDEN,
SmsError::InBlackList(_) => StatusCode::FORBIDDEN, SmsError::InBlackList(_) => StatusCode::FORBIDDEN,
SmsError::SendFailed => StatusCode::SERVICE_UNAVAILABLE,
SmsError::SendTimeOut => StatusCode::GATEWAY_TIMEOUT,
} }
} }
} }
......
...@@ -2,6 +2,7 @@ use crate::SmsError; ...@@ -2,6 +2,7 @@ use crate::SmsError;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
pub use log::{info, warn, debug, error}; pub use log::{info, warn, debug, error};
use crate::{Sms}; use crate::{Sms};
use std::path::PathBuf;
#[derive(Debug, PartialEq, Deserialize, Serialize)] #[derive(Debug, PartialEq, Deserialize, Serialize)]
...@@ -32,6 +33,7 @@ impl Kind { ...@@ -32,6 +33,7 @@ impl Kind {
pub struct EventJson { pub struct EventJson {
pub kind: String, pub kind: String,
pub sms: String, pub sms: String,
pub path: PathBuf
} }
...@@ -40,19 +42,21 @@ pub struct EventJson { ...@@ -40,19 +42,21 @@ pub struct EventJson {
pub struct Event { pub struct Event {
pub kind: Kind, pub kind: Kind,
pub sms: Sms, pub sms: Sms,
pub path: PathBuf
} }
impl Event { impl Event {
/// Try to create a Sms from the provided number and text /// Try to create a Sms from the provided number and text
/// Return an Error if the text was too long, too short, has /// Return an Error if the text was too long, too short, has
/// invalid characters or if the phonenumber format is invalid /// invalid characters or if the phonenumber format is invalid
pub fn try_from(kind: &str, sms: &str) -> Result<Self, SmsError> { pub fn try_from(kind: &str, sms: &str, path: PathBuf) -> Result<Self, SmsError> {
let kind = Kind::try_from_str(kind)?; let kind = Kind::try_from_str(kind)?;
let sms = Sms::deserialize_from(sms)?; let sms = Sms::deserialize_from(sms)?;
Ok(Event { Ok(Event {
kind, kind,
sms sms,
path,
}) })
} }
} }
......
use std::path::PathBuf;
use std::collections::{VecDeque, HashMap}; use std::collections::{VecDeque, HashMap};
use chrono::prelude::*; use chrono::prelude::*;
use chrono::Duration; use chrono::Duration;
pub use log::{info, warn, debug, error}; pub use log::{info, warn, debug, error};
use crate::CONFIG; use crate::{
CONFIG,
sms::SmsRecord
};
pub struct History { pub struct History {
pub data: HashMap<String, VecDeque<DateTime<Local>>> pub data: HashMap<String, VecDeque<DateTime<Local>>>,
pub sms: HashMap<PathBuf, SmsRecord>
} }
impl History { impl History {
pub fn new() -> Self { pub fn new() -> Self {
let data = HashMap::with_capacity(1200); let data = HashMap::with_capacity(1200);
let sms = HashMap::with_capacity(1200);
History { History {
data data,
sms
} }
} }
/// Store a timestamp of each time a sms has been sent /// Store a timestamp of each time a sms has been sent
/// for a given phone number /// for a given phone number
pub fn store(&mut self, phonenumber: &str) { pub fn store(&mut self, record: SmsRecord) {
let phonenumber = &record.sms.number;
let time: DateTime<Local> = Local::now(); let time: DateTime<Local> = Local::now();
match self.data.get_mut(phonenumber) { match self.data.get_mut(phonenumber.as_str()) {
Some(p) => { Some(p) => {
// phonenumber already exists, add to it // phonenumber already exists, add to it
p.push_back(time); p.push_back(time);
...@@ -36,6 +44,8 @@ impl History { ...@@ -36,6 +44,8 @@ impl History {
debug!("Pushed number {} for the first time", phonenumber); debug!("Pushed number {} for the first time", phonenumber);
} }
} }
self.sms.insert(record.filename.clone(), record);
} }
pub fn sms_within_last_minute(&self) -> usize { pub fn sms_within_last_minute(&self) -> usize {
......
...@@ -59,3 +59,16 @@ pub fn validate_and_normalize_phonenumber(value: &str) -> Result<String, SmsErro ...@@ -59,3 +59,16 @@ pub fn validate_and_normalize_phonenumber(value: &str) -> Result<String, SmsErro
false => return Err(SmsError::InvaldNumber(text)) false => return Err(SmsError::InvaldNumber(text))
} }
} }
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn validate_phonenumbers() {
for p in vec!["4915785315089", "+4915785315089", "004915785315089", "+49 1578-5315089"] {
let clean = validate_and_normalize_phonenumber(p).unwrap();
assert_eq!(clean, "4915785315089");
}
}
}
\ No newline at end of file
...@@ -13,6 +13,7 @@ use crate::{ ...@@ -13,6 +13,7 @@ use crate::{
VALID_CHARACTERS, VALID_CHARACTERS,
CONFIG, CONFIG,
validate_and_normalize_phonenumber, validate_and_normalize_phonenumber,
event::Kind
}; };
...@@ -25,11 +26,19 @@ pub use log::{info, warn, debug, error}; ...@@ -25,11 +26,19 @@ pub use log::{info, warn, debug, error};
#[derive(PartialEq, Debug)]
pub struct SmsRecord {
pub sms: Sms,
pub state: Option<Kind>,
pub filename: PathBuf
}
/// Represents an SMS that is to be sent to a phone number /// Represents an SMS that is to be sent to a phone number
#[derive(Deserialize, Serialize, PartialEq, Debug)] #[derive(Deserialize, Serialize, PartialEq, Debug, Clone)]
pub struct Sms { pub struct Sms {
pub number: String, pub number: String,
pub text: String, pub text: String
} }
impl Sms { impl Sms {
...@@ -59,7 +68,7 @@ impl Sms { ...@@ -59,7 +68,7 @@ impl Sms {
pub fn deserialize_from(body: &str) -> Result<Self, SmsError> { pub fn deserialize_from(body: &str) -> Result<Self, SmsError> {
lazy_static! { lazy_static! {
// flags m: multiline, s: allow . to match \n // flags m: multiline, s: allow . to match \n
static ref SMS_PATTERN: Regex = Regex::new(r"(?ms)^To:\s([0-9+]+)(?:(?:\n\w*:\s?\w+))*?\n\n+(.*)").unwrap(); static ref SMS_PATTERN: Regex = Regex::new(r"(?ms)^To:\s([0-9+]+)(?:.+?)?\n\n(.*)").unwrap();
} }
let captures = match SMS_PATTERN.captures(body) { let captures = match SMS_PATTERN.captures(body) {
...@@ -67,6 +76,8 @@ impl Sms { ...@@ -67,6 +76,8 @@ impl Sms {
None => return Err(SmsError::DeserializationError(body.to_string())) None => return Err(SmsError::DeserializationError(body.to_string()))
}; };
println!("{:#?}", captures);
let number = captures.get(1).map_or("", |m| m.as_str()).trim(); let number = captures.get(1).map_or("", |m| m.as_str()).trim();
let body = captures.get(2).map_or("", |m| m.as_str()).trim(); let body = captures.get(2).map_or("", |m| m.as_str()).trim();
...@@ -86,7 +97,7 @@ impl Sms { ...@@ -86,7 +97,7 @@ impl Sms {
PathBuf::from(format!("{timestamp}-{hash}.sms")) PathBuf::from(format!("{timestamp}-{hash}.sms"))
} }
pub fn send(&self) -> Result<(), SmsError> { pub fn send(&self) -> Result<PathBuf, SmsError> {
let filename = self.get_filename(); let filename = self.get_filename();
// unwrap is okay here, because the program is ended when this // unwrap is okay here, because the program is ended when this
// path does not exist upon startup. // path does not exist upon startup.
...@@ -141,7 +152,7 @@ impl Sms { ...@@ -141,7 +152,7 @@ impl Sms {
return Err(SmsError::WriteError(error_string)) return Err(SmsError::WriteError(error_string))
} }
} }
Ok(()) Ok(PathBuf::from(path.file_name().unwrap()))
} }
} }
...@@ -213,4 +224,15 @@ Häh?"#; ...@@ -213,4 +224,15 @@ Häh?"#;
let sms = maybe_sms.unwrap(); let sms = maybe_sms.unwrap();
assert_eq!(&sms.number[..], "4915785315089"); assert_eq!(&sms.number[..], "4915785315089");
} }
// TODO: Fix failing test
#[test]
fn deserialize_complex_number_failed() {
let text = "To: 4915785315089\nModem: GSM1\nSent: 22-10-28 16:19:09\nSending_time: 6\nMessage_id: 30\nIMSI: 262076013146197\nIMEI: 351596030479374\n\nThis text now contains\n\nmultiple breaks.";
let maybe_sms = Sms::deserialize_from(text);
println!("{:?}", maybe_sms);
assert!(maybe_sms.is_ok());
let sms = maybe_sms.unwrap();
assert_eq!(&sms.number[..], "4915785315089");
}
} }
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment