sbd 0.3.4

Read and write Iridium Short Burst Data (SBD) messages
Documentation
//! Command line utility for querying and working with Iridium SBD messages.

use std::{io::Write, path::Path, process, str};

use docopt::Docopt;
use sbd::{
    directip::Server,
    mo::{Message, SessionStatus},
    storage::FilesystemStorage,
};
use serde::{Deserialize, Serialize};

const USAGE: &str = "
Iridium Short Burst Data (SBD) message utility.

Usage:
    sbd info <file> [--compact]
    sbd payload <file>
    sbd serve <addr> <directory> [--logfile=<logfile>]
    sbd (-h | --help)
    sbd --version

Options:
    -h --help               Show this information
    --version               Show version
    --logfile=<logfile>     Logfile [default: /var/log/iridiumd.log]
    --compact               Don't pretty-print the JSON
";

#[derive(Debug, Deserialize)]
struct Args {
    cmd_info: bool,
    cmd_payload: bool,
    cmd_serve: bool,
    arg_addr: String,
    arg_directory: String,
    arg_file: String,
    flag_logfile: String,
    flag_compact: bool,
}

struct Logger<P: AsRef<Path>> {
    path: P,
}

#[derive(Debug, Serialize)]
struct ReadableMessage {
    protocol_revision_number: u8,
    cdr_reference: u32,
    imei: String,
    session_status: SessionStatus,
    momsn: u16,
    mtmsn: u16,
    time_of_session: String,
    payload: String,
}

impl<P: AsRef<Path> + Send + Sync> log::Log for Logger<P> {
    fn enabled(&self, metadata: &log::Metadata) -> bool {
        metadata.level() <= log::Level::Debug
    }

    fn log(&self, record: &log::Record) {
        if self.enabled(record.metadata()) {
            let mut output = if Path::new("/dev/stdout") == self.path.as_ref() {
                Box::new(std::io::stdout().lock()) as Box<dyn Write>
            } else {
                Box::new(
                    std::fs::OpenOptions::new()
                        .create(true)
                        .append(true)
                        .open(&self.path)
                        .unwrap(),
                ) as Box<dyn Write>
            };

            output
                .write_all(
                    format!(
                        "({}) {}: {}\n",
                        chrono::Utc::now().format("%Y-%m-%d %H:%M:%S"),
                        record.level(),
                        record.args()
                    )
                    .as_bytes(),
                )
                .unwrap();
        }
    }

    fn flush(&self) {}
}

impl ReadableMessage {
    fn new(_: &Message) -> ReadableMessage {
        unimplemented!()
    }
}

fn main() {
    let args: Args = Docopt::new(USAGE)
        .map(|d| d.version(Some(env!("CARGO_PKG_VERSION").to_string())))
        .and_then(|d| d.deserialize())
        .unwrap_or_else(|e| e.exit());

    if args.cmd_info {
        match Message::from_path(&args.arg_file) {
            Ok(ref message) => {
                let message = &ReadableMessage::new(message);
                if args.flag_compact {
                    println!("{}", serde_json::to_string(message).unwrap());
                } else {
                    println!("{}", serde_json::to_string_pretty(message).unwrap());
                };
            }
            Err(err) => {
                println!("ERROR: Unable to read message: {}", err);
                process::exit(1);
            }
        }
    }
    if args.cmd_payload {
        match Message::from_path(&args.arg_file) {
            Ok(ref message) => match str::from_utf8(message.payload()) {
                Ok(content) => println!("{}", content),
                Err(err) => {
                    println!("ERROR: Unable to parse payload as UTF-8: {}", err);
                    process::exit(1);
                }
            },
            Err(err) => {
                println!("ERROR: Unable to read message: {}", err);
                process::exit(1);
            }
        }
    }
    if args.cmd_serve {
        let logger = Logger {
            path: args.flag_logfile.clone(),
        };
        log::set_boxed_logger(Box::new(logger))
            .map(|()| log::set_max_level(log::LevelFilter::Debug))
            .unwrap_or_else(|e| {
                println!("ERROR: Could not create logger: {}", e);
                process::exit(1);
            });
        let storage = FilesystemStorage::open(args.arg_directory).unwrap_or_else(|e| {
            println!("ERROR: Could not open storage: {}", e);
            process::exit(1);
        });
        let mut server = Server::new(&args.arg_addr[..], storage);
        match server.bind() {
            Ok(()) => server.serve_forever(),
            Err(err) => {
                println!("ERROR: Could not bind to socket: {}", err);
                process::exit(1);
            }
        }
    }
}