use sip2::*;
use std::env;
use std::sync::Arc;
use std::thread;
use std::time::SystemTime;
const DEFAULT_HOST: &str = "localhost:6001";
const HELP_TEXT: &str = r#"
Send messages to a SIP server and print info to STDOUT.
Synopsis:
sip2-client-cli --sip-user sip-user --sip-pass sip-pass \
--item-barcode 30000017113634 \
--patron-barcode 394902 \
--message-type item-information \
--message-type patron-status \
--message-type patron-information
Parameters:
--sip-host <host:port> [default="localhost:6001"]
--sip-user <username>
--sip-pass <password>
--parallel <count>
Number of parallel threads to run with each thread running
the requested messages.
--repeat <count>
Repeat all requested messages this many times.
--sleep <milliseconds>
Sleep this long between requests to similulate slower traffic.
Sleeps occur after each request minus the opening Login and
SC Status requests.
--print-raw-messages
Include the unformatted SIP message responses in the output.
--quiet
Print only summary information
Message Parameters:
--institution <institution>
--patron-barcode <barcode>
--patron-pass <password>
--item-barcode <barcode>
--pay-amount <amount>
--fee-id <id>
Transaction ID within the ILS.
--transaction-id <id>
Transaction ID provided by the payer.
--message-type <mtype> [Repeatable]
Specify which messages to send to the SIP server.
Options include:
* item-information
* patron-status
* patron-information
* checkout
* checkin
* fee-paid
--patron-summary-index
Specify an index within the Patron Info "summary" section
to set to "Y". E.g. setting position 0 means including
patron hold items in the Patron Info Response
"#;
#[rustfmt::skip]
fn main() {
let options = read_options();
if options.opt_present("help") {
println!("{HELP_TEXT}");
return;
}
let sip_params = setup_params(&options);
let host = options
.opt_str("sip-host")
.unwrap_or(DEFAULT_HOST.to_string());
let quiet = options.opt_present("quiet");
let print_raw = options.opt_present("print-raw-messages");
let repeat = options.opt_get_default("repeat", 1).expect("Valid Repeat Option");
let parallel = options.opt_get_default("parallel", 1).expect("Valid Parallel Option");
let messages = Arc::new(options.opt_strs("message-type"));
let sleep = options.opt_get_default("sleep", 0).expect("Valid Sleep Option");
let mut handles = Vec::new();
let start = SystemTime::now();
for _ in 0..parallel {
let h = host.clone();
let m = messages.clone();
let p = sip_params.clone();
handles.push(thread::spawn(move || run_one_thread(h, m, p, quiet, print_raw, repeat, sleep)));
}
for h in handles {
h.join().unwrap();
}
let duration = start.elapsed().unwrap().as_millis();
let seconds = (duration as f64) / 1000.0;
let count = parallel * repeat * messages.len();
let thput = count as f64 / seconds;
println!("{count} messages processed in {seconds:.3} seconds; ~{thput:.3} reqs / second");
}
fn run_one_thread(
host: String,
messages: Arc<Vec<String>>,
sip_params: ParamSet,
quiet: bool,
print_raw: bool,
repeat: usize,
sleep: u64,
) {
let mut client = Client::new(&host).expect("Cannot Connect");
match client.login(&sip_params).expect("Login Error").ok() {
true => println!("Login OK"),
false => {
eprintln!("Login Failed");
return;
}
}
match client.sc_status().expect("SC Status Error").ok() {
true => println!("SC Status OK"),
false => eprintln!("SC Status Says Offline"),
}
for _ in 0..repeat {
for message in messages.iter() {
let start = SystemTime::now();
let mut resp = match message.as_str() {
"item-information" => client.item_info(&sip_params).expect("Item Info Requested"),
"patron-status" => client
.patron_status(&sip_params)
.expect("Patron Status Requested"),
"patron-information" => client
.patron_info(&sip_params)
.expect("Patron Info Requested"),
"checkout" => client.checkout(&sip_params).expect("Checkout Requested"),
"checkin" => client.checkin(&sip_params).expect("Checkin Requested"),
"fee-paid" => client.fee_paid(&sip_params).expect("Fee Paid Requested"),
_ => panic!("Unsupported message type: {}", message),
};
if print_raw {
println!("\n{}\n", resp.msg().to_sip());
}
resp.msg_mut()
.fields_mut()
.sort_by(|a, b| a.code().cmp(b.code()));
let duration = start.elapsed().unwrap().as_micros();
let millis = (duration as f64) / 1000.0;
let ms = format!("{:0>7}", format!("{:.3}", millis));
if quiet {
println!("{:.<35} {} ms", resp.msg().spec().label, ms);
} else {
println!("{}{} ms\n", resp.msg(), ms);
}
if sleep > 0 {
std::thread::sleep(std::time::Duration::from_millis(sleep));
}
}
}
}
fn read_options() -> getopts::Matches {
let args: Vec<String> = env::args().collect();
let mut opts = getopts::Options::new();
opts.optopt("", "sip-host", "SIP Host", "");
opts.optopt("", "sip-user", "SIP User", "");
opts.optopt("", "sip-pass", "SIP pass", "");
opts.optopt("", "institution", "Institution", "");
opts.optopt("", "terminal-password", "Terminal Password", "");
opts.optopt("", "patron-barcode", "Patron Barcode", "");
opts.optopt("", "patron-password", "Patron Password", "");
opts.optopt("", "item-barcode", "Item Barcode", "");
opts.optopt("", "location-code", "Location Code", "");
opts.optopt("", "repeat", "Repeat Count", "");
opts.optopt("", "sleep", "Sleep Time (millis)", "");
opts.optopt("", "parallel", "Parallel Count", "");
opts.optopt("", "patron-summary-index", "", "");
opts.optopt("", "pay-amount", "Fee Paid Amount", "");
opts.optopt("", "transaction-id", "Fee Paid Transaction ID", "");
opts.optopt("", "fee-id", "Fee Paid Fee ID", "");
opts.optflag("h", "help", "");
opts.optflag("q", "quiet", "");
opts.optflag("", "print-raw-messages", "");
opts.optmulti("", "message-type", "Message Type", "");
opts.parse(&args[1..]) .expect("Error parsing command line options")
}
fn setup_params(options: &getopts::Matches) -> ParamSet {
let mut params = ParamSet::new();
let user = options.opt_str("sip-user").expect("--sip-user required");
let pass = options.opt_str("sip-pass").expect("--sip-pass required");
params.set_sip_user(&user).set_sip_pass(&pass);
if let Some(ref terminal_pwd) = options.opt_str("terminal-password") {
params.set_terminal_pwd(terminal_pwd);
}
if let Some(ref institution) = options.opt_str("institution") {
params.set_institution(institution);
}
if let Some(ref location) = options.opt_str("location-code") {
params.set_location(location);
}
if let Some(ref item_id) = options.opt_str("item-barcode") {
params.set_item_id(item_id);
}
if let Some(ref patron_id) = options.opt_str("patron-barcode") {
params.set_patron_id(patron_id);
}
if let Some(ref patron_pwd) = options.opt_str("patron-password") {
params.set_patron_pwd(patron_pwd);
}
if let Some(ref amount) = options.opt_str("pay-amount") {
params.set_pay_amount(amount);
}
if let Some(ref id) = options.opt_str("fee-id") {
params.set_fee_id(id);
}
if let Some(ref id) = options.opt_str("transaction-id") {
params.set_transaction_id(id);
}
if let Some(ref index) = options.opt_str("patron-summary-index") {
params.set_summary(
index
.parse::<usize>()
.expect("Summary index should be a usize"),
);
}
params
}