From 59fcf770b355629ffefb48412ca765339c85a805 Mon Sep 17 00:00:00 2001 From: Txus Date: Tue, 16 Feb 2016 17:56:47 +0100 Subject: First commit --- .gitignore | 2 ++ Cargo.toml | 7 +++++++ src/main.rs | 3 +++ 3 files changed, 12 insertions(+) create mode 100644 .gitignore create mode 100644 Cargo.toml create mode 100644 src/main.rs diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..a9d37c5 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +target +Cargo.lock diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..4c32c77 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,7 @@ +[package] +name = "ota-plus-client" +version = "0.1.0" +authors = ["Txus "] + +[dependencies] +oauth2 = "0.1.9" diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..e7a11a9 --- /dev/null +++ b/src/main.rs @@ -0,0 +1,3 @@ +fn main() { + println!("Hello, world!"); +} -- cgit v1.2.1 From 6802f9866b7bd4d94e0e759b883eaf4a9ca8784b Mon Sep 17 00:00:00 2001 From: Jerry Trieu Date: Tue, 23 Feb 2016 18:19:43 +0100 Subject: Add AuthConfig, use in OtaClient --- .gitignore | 1 + Cargo.toml | 6 ++++- Makefile | 11 +++++++++ ota.toml | 8 +++++++ src/config.rs | 48 ++++++++++++++++++++++++++++++++++++++++ src/connect.rs | 70 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ src/lib.rs | 9 ++++++++ src/main.rs | 13 ++++++++++- 8 files changed, 164 insertions(+), 2 deletions(-) create mode 100644 Makefile create mode 100644 ota.toml create mode 100644 src/config.rs create mode 100644 src/connect.rs create mode 100644 src/lib.rs diff --git a/.gitignore b/.gitignore index a9d37c5..b9b7826 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ target Cargo.lock +pkg/deb/ota-plus-client-0.1.0/bin diff --git a/Cargo.toml b/Cargo.toml index 4c32c77..87bd38a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,4 +4,8 @@ version = "0.1.0" authors = ["Txus "] [dependencies] -oauth2 = "0.1.9" +env_logger = "*" +hyper = "*" +log = "*" +rustc-serialize = "*" +toml = "*" diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..3cddc21 --- /dev/null +++ b/Makefile @@ -0,0 +1,11 @@ +.PHONY: all + +all: pkg/deb/ota-plus-client-0.1.0/bin + +pkg/deb/ota-plus-client-0.1.0/bin: target/release/ota-plus-client + mkdir -p $@ + cp $< $@ + +target/release/ota-plus-client: src/ + cargo build --release + diff --git a/ota.toml b/ota.toml new file mode 100644 index 0000000..a11ceee --- /dev/null +++ b/ota.toml @@ -0,0 +1,8 @@ +[auth] +server = "http://127.0.0.1:9000" +client_id = "client-id" +secret = "secret" + +[ota] +server = "http://127.0.0.1:8080" +vin = "V1234567890123456" diff --git a/src/config.rs b/src/config.rs new file mode 100644 index 0000000..5216dd7 --- /dev/null +++ b/src/config.rs @@ -0,0 +1,48 @@ +use std::fs::OpenOptions; +use std::io::prelude::*; +use std::path::PathBuf; + +use hyper::Url; +use rustc_serialize::Decodable; +use toml; + +#[derive(RustcDecodable)] +pub struct AuthConfig { + pub server: Url, + pub client_id: String, + pub secret: String +} + +#[derive(RustcDecodable)] +pub struct OtaConfig { + pub server: Url, + pub vin: String +} + +fn read_config(path: &str) -> toml::Table { + OpenOptions::new().open(PathBuf::from(path)) + .map_err(|e| error!("Cannot open config file: {}, error: {}", path, e)) + .and_then(|mut f| { + let mut buf = String::new(); + f.read_to_string(&mut buf) + .map(|_| buf) + .map_err(|e| error!("Cannot read config file: {}, error: {}", path, e)) }) + .and_then(|s| { + toml::Parser::new(&s).parse() + .ok_or(()) + .map_err(|_| error!("Cannot parse config file: {}", path)) }) + .unwrap() +} + +fn parse_sect(cfg: &toml::Table, sect: &str) -> T { + cfg.get(sect) + .and_then(|c| toml::decode::(c.clone()) ) + .ok_or_else(|| error!("Invalid section in config file: {}", sect)) + .unwrap() +} + +pub fn parse_config(path: &str) -> (AuthConfig, OtaConfig) { + let cfg = read_config(path); + (parse_sect(&cfg, "auth"), parse_sect(&cfg, "ota")) +} + diff --git a/src/connect.rs b/src/connect.rs new file mode 100644 index 0000000..4a4a0fe --- /dev/null +++ b/src/connect.rs @@ -0,0 +1,70 @@ +use config::{AuthConfig, OtaConfig}; + +use std::io::Read; +use std::result::Result; + +use hyper::header::{Authorization, Basic, Bearer, ContentType}; +use hyper::mime::{Mime, TopLevel, SubLevel, Attr, Value}; +use hyper; + +use rustc_serialize::json; + +#[derive(Clone, RustcDecodable)] +struct AccessToken { + access_token: String, + token_type: String, + expires_in: i32, + scope: Option +} + +pub struct OtaClient { + hclient: hyper::Client, + auth_cfg: AuthConfig, + ota_cfg: OtaConfig +} + +impl OtaClient { + pub fn new((a, o): (AuthConfig, OtaConfig)) -> OtaClient { + OtaClient { + hclient: hyper::Client::new(), + auth_cfg: a, + ota_cfg: o + } + } + + pub fn check_for_update(&self) { + let _ = self.get_token() + .and_then(|tk| { + self.hclient.get(self.ota_cfg.server.clone()) + .header(Authorization(Bearer { token: tk.access_token })) + .send() + .map_err(|e| error!("Cannot send check_for_update request: {}", e)) }) + .and_then(|mut resp| { + let mut rbody = String::new(); + resp.read_to_string(&mut rbody) + .map_err(|e| error!("Cannot read check_for_update response: {}", e)) + .and_then(|_| { + Ok(info!("Check for update: {}", rbody)) }) }); + } + + fn get_token(&self) -> Result { + self.hclient.post(self.auth_cfg.server.clone()) + .header(Authorization(Basic { + username: self.auth_cfg.client_id.clone(), + password: Some(self.auth_cfg.secret.clone()) })) + .header(ContentType(Mime( + TopLevel::Application, + SubLevel::WwwFormUrlEncoded, + vec![(Attr::Charset, Value::Utf8)]))) + .body("grant_type=client_credentials") + .send() + .map_err(|e| error!("Cannot send token request: {}", e)) + .and_then(|mut resp| { + let mut rbody = String::new(); + resp.read_to_string(&mut rbody) + .map_err(|e| error!("Cannot read token response: {}", e)) + .and_then(|_| { + json::decode::(&rbody) + .map_err(|e| error!("Cannot parse token response: {}", e)) }) }) + } +} diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..d0bdd25 --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,9 @@ +#[macro_use] +extern crate log; + +extern crate hyper; +extern crate rustc_serialize; +extern crate toml; + +pub mod config; +pub mod connect; diff --git a/src/main.rs b/src/main.rs index e7a11a9..e6f31ad 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,3 +1,14 @@ +extern crate ota_plus_client; +extern crate env_logger; + +use std::env; + +use ota_plus_client::{config, connect}; + fn main() { - println!("Hello, world!"); + env_logger::init().unwrap(); + + let cfg_file = env::var("OTA_PLUS_CLIENT_CFG").unwrap_or("/opt/ats/ota/etc/ota.toml".to_string()); + let client = connect::OtaClient::new(config::parse_config(&cfg_file)); + client.check_for_update(); } -- cgit v1.2.1 From a5cf7960c07f374fcb6bd395169550b6edec36d9 Mon Sep 17 00:00:00 2001 From: Jerry Trieu Date: Mon, 29 Feb 2016 17:18:06 +0100 Subject: Add pkg/deb/ --- pkg/deb/ota-plus-client-0.1.0/debian/changelog | 5 +++ pkg/deb/ota-plus-client-0.1.0/debian/compat | 1 + pkg/deb/ota-plus-client-0.1.0/debian/control | 12 +++++ pkg/deb/ota-plus-client-0.1.0/debian/copyright | 0 .../debian/ota-plus-client.dirs | 2 + .../debian/ota-plus-client.install | 2 + pkg/deb/ota-plus-client-0.1.0/debian/rules | 3 ++ pkg/deb/ota-plus-client-0.1.0/debian/source/format | 1 + pkg/ota.toml.template | 8 ++++ pkg/pkg.sh | 52 ++++++++++++++++++++++ 10 files changed, 86 insertions(+) create mode 100644 pkg/deb/ota-plus-client-0.1.0/debian/changelog create mode 100644 pkg/deb/ota-plus-client-0.1.0/debian/compat create mode 100644 pkg/deb/ota-plus-client-0.1.0/debian/control create mode 100644 pkg/deb/ota-plus-client-0.1.0/debian/copyright create mode 100644 pkg/deb/ota-plus-client-0.1.0/debian/ota-plus-client.dirs create mode 100644 pkg/deb/ota-plus-client-0.1.0/debian/ota-plus-client.install create mode 100644 pkg/deb/ota-plus-client-0.1.0/debian/rules create mode 100644 pkg/deb/ota-plus-client-0.1.0/debian/source/format create mode 100644 pkg/ota.toml.template create mode 100644 pkg/pkg.sh diff --git a/pkg/deb/ota-plus-client-0.1.0/debian/changelog b/pkg/deb/ota-plus-client-0.1.0/debian/changelog new file mode 100644 index 0000000..ce20224 --- /dev/null +++ b/pkg/deb/ota-plus-client-0.1.0/debian/changelog @@ -0,0 +1,5 @@ +ota-plus-client (0.1.0-1) unstable; urgency=medium + + * Initial release. + + -- Jerry Wed, 26 Feb 2016 17:33:03 +0200 diff --git a/pkg/deb/ota-plus-client-0.1.0/debian/compat b/pkg/deb/ota-plus-client-0.1.0/debian/compat new file mode 100644 index 0000000..ec63514 --- /dev/null +++ b/pkg/deb/ota-plus-client-0.1.0/debian/compat @@ -0,0 +1 @@ +9 diff --git a/pkg/deb/ota-plus-client-0.1.0/debian/control b/pkg/deb/ota-plus-client-0.1.0/debian/control new file mode 100644 index 0000000..f63fe1a --- /dev/null +++ b/pkg/deb/ota-plus-client-0.1.0/debian/control @@ -0,0 +1,12 @@ +Source: ota-plus-client +Maintainer: Jerry Trieu +Section: misc +Priority: optional +Standards-Version: 3.9.2 +Build-Depends: debhelper (>= 9) + +Package: ota-plus-client +Architecture: any +Depends: ${shlibs:Depends}, ${misc:Depends} +Description: ATS OTA+ Client SDK + ATS OTA+ Client SDK for a vehicle or device diff --git a/pkg/deb/ota-plus-client-0.1.0/debian/copyright b/pkg/deb/ota-plus-client-0.1.0/debian/copyright new file mode 100644 index 0000000..e69de29 diff --git a/pkg/deb/ota-plus-client-0.1.0/debian/ota-plus-client.dirs b/pkg/deb/ota-plus-client-0.1.0/debian/ota-plus-client.dirs new file mode 100644 index 0000000..f7048c8 --- /dev/null +++ b/pkg/deb/ota-plus-client-0.1.0/debian/ota-plus-client.dirs @@ -0,0 +1,2 @@ +opt/ats/ota/bin +opt/ats/ota/etc diff --git a/pkg/deb/ota-plus-client-0.1.0/debian/ota-plus-client.install b/pkg/deb/ota-plus-client-0.1.0/debian/ota-plus-client.install new file mode 100644 index 0000000..1fe11f2 --- /dev/null +++ b/pkg/deb/ota-plus-client-0.1.0/debian/ota-plus-client.install @@ -0,0 +1,2 @@ +bin/* opt/ats/ota/bin +etc/* opt/ats/ota/etc diff --git a/pkg/deb/ota-plus-client-0.1.0/debian/rules b/pkg/deb/ota-plus-client-0.1.0/debian/rules new file mode 100644 index 0000000..cbe925d --- /dev/null +++ b/pkg/deb/ota-plus-client-0.1.0/debian/rules @@ -0,0 +1,3 @@ +#!/usr/bin/make -f +%: + dh $@ diff --git a/pkg/deb/ota-plus-client-0.1.0/debian/source/format b/pkg/deb/ota-plus-client-0.1.0/debian/source/format new file mode 100644 index 0000000..163aaf8 --- /dev/null +++ b/pkg/deb/ota-plus-client-0.1.0/debian/source/format @@ -0,0 +1 @@ +3.0 (quilt) diff --git a/pkg/ota.toml.template b/pkg/ota.toml.template new file mode 100644 index 0000000..a48f2b4 --- /dev/null +++ b/pkg/ota.toml.template @@ -0,0 +1,8 @@ +[auth] +server = "${OTA_AUTH_URL}" +client_id = "${OTA_AUTH_CLIENT_ID}" +secret = "${OTA_AUTH_SECRET}" + +[ota] +server = "${OTA_SERVER_URL}" +vin = "${OTA_CLIENT_VIN}" diff --git a/pkg/pkg.sh b/pkg/pkg.sh new file mode 100644 index 0000000..863ca72 --- /dev/null +++ b/pkg/pkg.sh @@ -0,0 +1,52 @@ +#!/bin/bash + +function envsub { + awk ' +/^\s*(#.*)?$/ { + print $0 + next +}{ + count = split($0, parts, /\${/) + line = parts[1] + for (i = 2; i <= count; i++) { + if (split(parts[i], names, /}/) != 2) + exit 1 + line = line""ENVIRON[names[1]]""names[2] + } + print line +}' $* +} + +if [ $# -lt 1 ] +then + echo "Usage: $0 " + exit 1 +fi + +dest="${1}" +echo "Building pkg to '$dest'" + +PKG_NAME="ota-plus-client" +PKG_VER="0.1.0" +PKG_DIR="${PKG_NAME}-${PKG_VER}" +PKG_TARBALL="${PKG_NAME}_${PKG_VER}" + +cd `dirname $0` +PKG_SRC_DIR=`pwd` + +workdir="${TMPDIR:-/tmp}/pkg-ota-plus-client-$$" +cp -pr deb $workdir +cd $workdir + +mkdir -p $PKG_DIR/bin +mkdir -p $PKG_DIR/etc +envsub ${PKG_SRC_DIR}/ota.toml.template > $PKG_DIR/etc/ota.toml +tar czf $PKG_TARBALL $PKG_DIR/bin $PKG_DIR/etc + +cd $PKG_DIR +debuild -i -us -uc -b +cd .. +cp ota-plus-client*.deb "${dest}" + +cd $PKG_SRC_DIR +rm -rf $workdir -- cgit v1.2.1 From 8d9abb1b70dcca462fd0bc8345f75cca7081952f Mon Sep 17 00:00:00 2001 From: Jerry Trieu Date: Tue, 8 Mar 2016 10:50:55 +0100 Subject: Fix chmod pkg.sh --- pkg/pkg.sh | 0 1 file changed, 0 insertions(+), 0 deletions(-) mode change 100644 => 100755 pkg/pkg.sh diff --git a/pkg/pkg.sh b/pkg/pkg.sh old mode 100644 new mode 100755 -- cgit v1.2.1 From 290509df9b0df37f92332c82e7606bd28a60d0d7 Mon Sep 17 00:00:00 2001 From: Stevan Andjelkovic Date: Thu, 10 Mar 2016 12:11:45 +0100 Subject: Add skeleton for testing (loop and listen to stdin for commands). This will be useful when we spawn processes. --- Cargo.toml | 1 + src/lib.rs | 1 + src/main.rs | 36 +++++++++++++++++++++++++++++++++--- src/read_interpret.rs | 38 ++++++++++++++++++++++++++++++++++++++ 4 files changed, 73 insertions(+), 3 deletions(-) create mode 100644 src/read_interpret.rs diff --git a/Cargo.toml b/Cargo.toml index 87bd38a..2af0ab8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,3 +9,4 @@ hyper = "*" log = "*" rustc-serialize = "*" toml = "*" +getopts = "*" \ No newline at end of file diff --git a/src/lib.rs b/src/lib.rs index d0bdd25..ebc867d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -7,3 +7,4 @@ extern crate toml; pub mod config; pub mod connect; +pub mod read_interpret; diff --git a/src/main.rs b/src/main.rs index e6f31ad..855947b 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,14 +1,44 @@ -extern crate ota_plus_client; extern crate env_logger; +extern crate getopts; +extern crate ota_plus_client; +use getopts::Options; use std::env; -use ota_plus_client::{config, connect}; +use ota_plus_client::{config, connect, read_interpret}; + fn main() { + let args: Vec = env::args().collect(); + let program = args[0].clone(); + + let mut opts = Options::new(); + opts.optflag("l", "loop", "enter testing loop"); + opts.optflag("h", "help", "print this help menu"); + + let matches = match opts.parse(&args[1..]) { + Ok(m) => m, + Err(e) => panic!(e.to_string()) + }; + + if matches.opt_present("h") { + print_usage(&program, opts); + return; + } + env_logger::init().unwrap(); - let cfg_file = env::var("OTA_PLUS_CLIENT_CFG").unwrap_or("/opt/ats/ota/etc/ota.toml".to_string()); + let cfg_file = env::var("OTA_PLUS_CLIENT_CFG") + .unwrap_or("/opt/ats/ota/etc/ota.toml".to_string()); let client = connect::OtaClient::new(config::parse_config(&cfg_file)); client.check_for_update(); + + if matches.opt_present("l") { + read_interpret::read_interpret_loop(); + } +} + +fn print_usage(program: &str, opts: Options) { + let brief = format!("Usage: {} [options]", program); + print!("{}", opts.usage(&brief)); } diff --git a/src/read_interpret.rs b/src/read_interpret.rs new file mode 100644 index 0000000..f7c85d9 --- /dev/null +++ b/src/read_interpret.rs @@ -0,0 +1,38 @@ +use std::io; +use std::str::FromStr; + +enum Command { + ListPackages, +} + +impl FromStr for Command { + type Err = (); + fn from_str(s: &str) -> Result { + match s { + "ListPackages" => Ok(Command::ListPackages), + _ => Err(()), + } + } +} + +fn interpret(cmd: Command) { + match cmd { + Command::ListPackages => info!("ok"), + }; +} + +pub fn read_interpret_loop() { + + loop { + + let mut input = String::new(); + let _ = io::stdin().read_line(&mut input); + + match input.trim().parse() { + Ok(cmd) => interpret(cmd), + Err(_) => error!("Parse error."), + }; + + } + +} -- cgit v1.2.1 From cb3241942bba7597098bda44218007c5fb736739 Mon Sep 17 00:00:00 2001 From: Txus Date: Tue, 8 Mar 2016 10:45:05 +0100 Subject: [WIP] Post installed DPKG packages --- Cargo.toml | 9 ++++++ src/auth_plus.rs | 58 +++++++++++++++++++++++++++++++++++++ src/connect.rs | 70 --------------------------------------------- src/error.rs | 21 ++++++++++++++ src/lib.rs | 6 +++- src/main.rs | 38 +++++++++++++++++++----- src/ota_plus.rs | 60 ++++++++++++++++++++++++++++++++++++++ src/package.rs | 15 ++++++++++ src/package_manager/dpkg.rs | 59 ++++++++++++++++++++++++++++++++++++++ src/package_manager/mod.rs | 10 +++++++ src/read_interpret.rs | 36 ++++++++++++++++++++--- 11 files changed, 300 insertions(+), 82 deletions(-) create mode 100644 src/auth_plus.rs delete mode 100644 src/connect.rs create mode 100644 src/error.rs create mode 100644 src/ota_plus.rs create mode 100644 src/package.rs create mode 100644 src/package_manager/dpkg.rs create mode 100644 src/package_manager/mod.rs diff --git a/Cargo.toml b/Cargo.toml index 2af0ab8..507e42e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,6 +3,15 @@ name = "ota-plus-client" version = "0.1.0" authors = ["Txus "] +[lib] +name = "libotaplus" +path = "src/lib.rs" + +[[bin]] +name = "ota_plus_client" +path = "src/main.rs" +doc = false + [dependencies] env_logger = "*" hyper = "*" diff --git a/src/auth_plus.rs b/src/auth_plus.rs new file mode 100644 index 0000000..d3d64d5 --- /dev/null +++ b/src/auth_plus.rs @@ -0,0 +1,58 @@ +use config::AuthConfig; + +use std::io::Read; + +use error::Error; + +use hyper::header::{Authorization, Basic, ContentType}; +use hyper::mime::{Mime, TopLevel, SubLevel, Attr, Value}; +use hyper; + +use rustc_serialize::json; + +#[derive(Clone, RustcDecodable, Debug)] +pub struct AccessToken { + pub access_token: String, + token_type: String, + expires_in: i32, + scope: Option +} + +pub struct Client { + hclient: hyper::Client, + config: AuthConfig +} + +impl Client { + pub fn new(config: AuthConfig) -> Client { + Client { + hclient: hyper::Client::new(), + config: config + } + } + + pub fn authenticate(&self) -> Result { + self.hclient.post(self.config.server.join("/token").unwrap()) + .header(Authorization(Basic { + username: self.config.client_id.clone(), + password: Some(self.config.secret.clone()) })) + .header(ContentType(Mime( + TopLevel::Application, + SubLevel::WwwFormUrlEncoded, + vec![(Attr::Charset, Value::Utf8)]))) + .body("grant_type=client_credentials") + .send() + .map_err(|e| { + Error::AuthError(format!("Cannot send token request: {}", e)) + }) + .and_then(|mut resp| { + let mut rbody = String::new(); + resp.read_to_string(&mut rbody) + .map_err(|e| Error::AuthError(format!("Cannot read token response: {}", e))) + .and_then(|_| { + json::decode::(&rbody) + .map_err(|e| Error::AuthError(format!("Cannot parse token response: {}. Got: {}", e, &rbody))) + }) + }) + } +} diff --git a/src/connect.rs b/src/connect.rs deleted file mode 100644 index 4a4a0fe..0000000 --- a/src/connect.rs +++ /dev/null @@ -1,70 +0,0 @@ -use config::{AuthConfig, OtaConfig}; - -use std::io::Read; -use std::result::Result; - -use hyper::header::{Authorization, Basic, Bearer, ContentType}; -use hyper::mime::{Mime, TopLevel, SubLevel, Attr, Value}; -use hyper; - -use rustc_serialize::json; - -#[derive(Clone, RustcDecodable)] -struct AccessToken { - access_token: String, - token_type: String, - expires_in: i32, - scope: Option -} - -pub struct OtaClient { - hclient: hyper::Client, - auth_cfg: AuthConfig, - ota_cfg: OtaConfig -} - -impl OtaClient { - pub fn new((a, o): (AuthConfig, OtaConfig)) -> OtaClient { - OtaClient { - hclient: hyper::Client::new(), - auth_cfg: a, - ota_cfg: o - } - } - - pub fn check_for_update(&self) { - let _ = self.get_token() - .and_then(|tk| { - self.hclient.get(self.ota_cfg.server.clone()) - .header(Authorization(Bearer { token: tk.access_token })) - .send() - .map_err(|e| error!("Cannot send check_for_update request: {}", e)) }) - .and_then(|mut resp| { - let mut rbody = String::new(); - resp.read_to_string(&mut rbody) - .map_err(|e| error!("Cannot read check_for_update response: {}", e)) - .and_then(|_| { - Ok(info!("Check for update: {}", rbody)) }) }); - } - - fn get_token(&self) -> Result { - self.hclient.post(self.auth_cfg.server.clone()) - .header(Authorization(Basic { - username: self.auth_cfg.client_id.clone(), - password: Some(self.auth_cfg.secret.clone()) })) - .header(ContentType(Mime( - TopLevel::Application, - SubLevel::WwwFormUrlEncoded, - vec![(Attr::Charset, Value::Utf8)]))) - .body("grant_type=client_credentials") - .send() - .map_err(|e| error!("Cannot send token request: {}", e)) - .and_then(|mut resp| { - let mut rbody = String::new(); - resp.read_to_string(&mut rbody) - .map_err(|e| error!("Cannot read token response: {}", e)) - .and_then(|_| { - json::decode::(&rbody) - .map_err(|e| error!("Cannot parse token response: {}", e)) }) }) - } -} diff --git a/src/error.rs b/src/error.rs new file mode 100644 index 0000000..afc9d52 --- /dev/null +++ b/src/error.rs @@ -0,0 +1,21 @@ +use std::fmt::{Display, Formatter, Result as FmtResult}; + +#[derive(PartialEq, Eq, Debug)] +pub enum Error { + AuthError(String), + ParseError(String), + PackageError(String), + ClientError(String) +} + +impl Display for Error { + fn fmt(&self, f: &mut Formatter) -> FmtResult { + let inner: String = match self { + &Error::AuthError(ref s) => s.clone(), + &Error::ParseError(ref s) => s.clone(), + &Error::PackageError(ref s) => s.clone(), + &Error::ClientError(ref s) => s.clone() + }; + write!(f, "Application Error: {}", inner) + } +} diff --git a/src/lib.rs b/src/lib.rs index ebc867d..c4ad6cd 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -6,5 +6,9 @@ extern crate rustc_serialize; extern crate toml; pub mod config; -pub mod connect; pub mod read_interpret; +pub mod ota_plus; +pub mod auth_plus; +pub mod package; +pub mod package_manager; +pub mod error; diff --git a/src/main.rs b/src/main.rs index 855947b..f14e2cc 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,12 +1,28 @@ +extern crate libotaplus; extern crate env_logger; extern crate getopts; -extern crate ota_plus_client; use getopts::Options; use std::env; -use ota_plus_client::{config, connect, read_interpret}; +use libotaplus::{config, read_interpret}; +use libotaplus::read_interpret::ReplEnv; +use libotaplus::ota_plus::{Client as OtaClient}; +use libotaplus::auth_plus::{Client as AuthClient}; +use libotaplus::package_manager::{PackageManager, Dpkg}; +use libotaplus::error::Error; +fn post_installed_packages(client: OtaClient, manager: M) -> Result<(), Error> + where M: PackageManager { + manager.installed_packages().and_then(|pkgs| client.post_packages(pkgs)) +} + +fn build_ota_client(cfg_file: &str) -> Result { + let (auth_config, ota_config) = config::parse_config(&cfg_file); + AuthClient::new(auth_config).authenticate().map(|token| { + OtaClient::new(token, ota_config) + }) +} fn main() { let args: Vec = env::args().collect(); @@ -28,13 +44,21 @@ fn main() { env_logger::init().unwrap(); - let cfg_file = env::var("OTA_PLUS_CLIENT_CFG") - .unwrap_or("/opt/ats/ota/etc/ota.toml".to_string()); - let client = connect::OtaClient::new(config::parse_config(&cfg_file)); - client.check_for_update(); + let cfg_file = env::var("OTA_PLUS_CLIENT_CFG").unwrap_or("/opt/ats/ota/etc/ota.toml".to_string()); + + let pkg_manager = Dpkg::new(); + let pkg_manager_clone = pkg_manager.clone(); + + let _ = build_ota_client(&cfg_file).and_then(|client| { + post_installed_packages(client, pkg_manager) + }).map(|_| { + print!("Installed packages were posted successfully."); + }).map_err(|e| { + print!("{}", e); + }); if matches.opt_present("l") { - read_interpret::read_interpret_loop(); + read_interpret::read_interpret_loop(ReplEnv::new(pkg_manager_clone)); } } diff --git a/src/ota_plus.rs b/src/ota_plus.rs new file mode 100644 index 0000000..ce05673 --- /dev/null +++ b/src/ota_plus.rs @@ -0,0 +1,60 @@ +use config::OtaConfig; +use auth_plus::AccessToken; +use package::Package; +use error::Error; + +use std::io::Read; +use std::result::Result; + +use hyper::header::{Authorization, Bearer, ContentType}; +use hyper::mime::{Mime, TopLevel, SubLevel, Attr, Value}; +use hyper; + +use rustc_serialize::json; + +pub struct Client { + hclient: hyper::Client, + access_token: String, + config: OtaConfig +} + +impl Client { + pub fn new(token: AccessToken, config: OtaConfig) -> Client { + Client { + hclient: hyper::Client::new(), + access_token: token.access_token, + config: config + } + } + + pub fn check_for_update(&self) { + let _ = self.hclient.get(self.config.server.join("/updates").unwrap()) + .header(Authorization(Bearer { token: self.access_token.clone() })) + .send() + .map_err(|e| error!("Cannot send check_for_update request: {}", e)) + .and_then(|mut resp| { + let mut rbody = String::new(); + resp.read_to_string(&mut rbody) + .map_err(|e| error!("Cannot read check_for_update response: {}", e)) + .and_then(|_| { + Ok(info!("Check for update: {}", rbody)) }) }); + } + + + pub fn post_packages(&self, pkgs: Vec) -> Result<(), Error>{ + json::encode(&pkgs) + .map_err(|_| Error::ParseError(String::from("JSON encoding error"))) + .and_then(|json| { + self.hclient.put(self.config.server.join("/packages").unwrap()) + .header(Authorization(Bearer { token: self.access_token.clone() })) + .header(ContentType(Mime( + TopLevel::Application, + SubLevel::Json, + vec![(Attr::Charset, Value::Utf8)]))) + .body(&json) + .send() + .map_err(|e| Error::ClientError(format!("Cannot send packages: {}", e))) + .map(|_| ()) + }) + } +} diff --git a/src/package.rs b/src/package.rs new file mode 100644 index 0000000..17eea71 --- /dev/null +++ b/src/package.rs @@ -0,0 +1,15 @@ +use std::fmt::{Display, Formatter, Result as FmtResult}; + +pub type Version = String; + +#[derive(Debug, PartialEq, Eq, RustcEncodable)] +pub struct Package { + pub name: String, + pub version: Version +} + +impl Display for Package { + fn fmt(&self, f: &mut Formatter) -> FmtResult { + write!(f, "{} {}", self.name, self.version) + } +} diff --git a/src/package_manager/dpkg.rs b/src/package_manager/dpkg.rs new file mode 100644 index 0000000..2e7f4dc --- /dev/null +++ b/src/package_manager/dpkg.rs @@ -0,0 +1,59 @@ +use package_manager; +use package::Package; +use error::Error; + +use std::process::Command; + +#[allow(dead_code)] +#[derive(Clone)] +pub struct Dpkg { a: u16 } // remove dummy field once braced_empty_structs feature is in stable + +impl Dpkg { + pub fn new() -> Dpkg { + Dpkg { a: 0 } + } +} + +impl package_manager::PackageManager for Dpkg { + fn installed_packages(&self) -> Result, Error> { + Command::new("dpkg-query").arg("-f").arg("'${Package} ${Version}\n'").arg("-W") + .output() + .map_err(|e| Error::PackageError(format!("Error fetching packages: {}", e))) + .and_then(|c| { + String::from_utf8(c.stdout) + .map_err(|e| Error::ParseError(format!("Error parsing package: {}", e))) + .map(|s| s.lines().map(|n| String::from(n)).collect::>()) + }) + .and_then(|lines| { + lines.iter() + .map(|line| parse_package(line)) + .collect::, _>>() + }) + } +} + +fn parse_package(line: &str) -> Result { + match line.splitn(2, ' ').collect::>() { + ref parts if parts.len() == 2 => Ok(Package { name: String::from(parts[0]), + version: String::from(parts[1]) }), + _ => Err(Error::ParseError(format!("Couldn't parse package: {}", line))) + } +} + +#[test] +fn test_parses_normal_package() { + assert_eq!(Ok(Package { name: "uuid-runtime".to_string(), version: "2.20.1-5.1ubuntu20.7".to_string() }), + parse_package("uuid-runtime 2.20.1-5.1ubuntu20.7")); +} + +#[test] +fn test_separates_name_and_version_correctly() { + assert_eq!(Ok(Package { name: "vim".to_string(), version: "2.1 foobar".to_string() }), + parse_package("vim 2.1 foobar")); +} + +#[test] +fn test_rejects_bogus_input() { + assert_eq!(Err(Error::ParseError("Couldn't parse package: foobar".to_string())), + parse_package("foobar")) +} diff --git a/src/package_manager/mod.rs b/src/package_manager/mod.rs new file mode 100644 index 0000000..ba8622c --- /dev/null +++ b/src/package_manager/mod.rs @@ -0,0 +1,10 @@ +use package::Package; +use error::Error; + +pub trait PackageManager: Clone { + fn installed_packages(&self) -> Result, Error>; +} + +pub use self::dpkg::Dpkg; + +pub mod dpkg; diff --git a/src/read_interpret.rs b/src/read_interpret.rs index f7c85d9..30910e9 100644 --- a/src/read_interpret.rs +++ b/src/read_interpret.rs @@ -1,6 +1,18 @@ use std::io; use std::str::FromStr; +use package_manager::PackageManager; + +pub struct ReplEnv { + package_manager: M, +} + +impl ReplEnv { + pub fn new(manager: M) -> ReplEnv { + ReplEnv { package_manager: manager } + } +} + enum Command { ListPackages, } @@ -15,13 +27,29 @@ impl FromStr for Command { } } -fn interpret(cmd: Command) { +fn list_packages(package_manager: &M) + where M: PackageManager { + let _ = package_manager.installed_packages() + .and_then(|pkgs| { + println!("Found {} packages.", pkgs.iter().len()); + for pkg in pkgs.iter() { + println!("{}", pkg); + } + Ok(()) + }).map_err(|e| { + error!("Can't list packages: {}", e) + }); +} + +fn interpret(env: &ReplEnv, cmd: Command) + where M: PackageManager { match cmd { - Command::ListPackages => info!("ok"), + Command::ListPackages => list_packages(&env.package_manager) }; } -pub fn read_interpret_loop() { +pub fn read_interpret_loop(env: ReplEnv) + where M: PackageManager { loop { @@ -29,7 +57,7 @@ pub fn read_interpret_loop() { let _ = io::stdin().read_line(&mut input); match input.trim().parse() { - Ok(cmd) => interpret(cmd), + Ok(cmd) => interpret(&env, cmd), Err(_) => error!("Parse error."), }; -- cgit v1.2.1 From 2a4e1239f86ddd45e40853c03dead49d2f590750 Mon Sep 17 00:00:00 2001 From: Stevan Andjelkovic Date: Thu, 10 Mar 2016 16:17:32 +0100 Subject: Make the configuration loader more robust. --- src/config.rs | 160 ++++++++++++++++++++++++++++++++++++++++++++++++---------- src/main.rs | 13 ++--- 2 files changed, 140 insertions(+), 33 deletions(-) diff --git a/src/config.rs b/src/config.rs index 5216dd7..cf5f173 100644 --- a/src/config.rs +++ b/src/config.rs @@ -1,48 +1,154 @@ -use std::fs::OpenOptions; -use std::io::prelude::*; -use std::path::PathBuf; - use hyper::Url; use rustc_serialize::Decodable; +use std::fs::File; +use std::io::prelude::*; +use std::io::{Error, ErrorKind}; +use std::io; use toml; -#[derive(RustcDecodable)] +#[derive(Default, PartialEq, Eq, Debug)] +pub struct Config { + pub auth: AuthConfig, + pub ota: OtaConfig, + pub test: TestConfig, +} + +#[derive(RustcDecodable, PartialEq, Eq, Debug, Clone)] pub struct AuthConfig { pub server: Url, pub client_id: String, pub secret: String } -#[derive(RustcDecodable)] +#[derive(RustcDecodable, PartialEq, Eq, Debug, Clone)] pub struct OtaConfig { pub server: Url, pub vin: String } -fn read_config(path: &str) -> toml::Table { - OpenOptions::new().open(PathBuf::from(path)) - .map_err(|e| error!("Cannot open config file: {}, error: {}", path, e)) - .and_then(|mut f| { - let mut buf = String::new(); - f.read_to_string(&mut buf) - .map(|_| buf) - .map_err(|e| error!("Cannot read config file: {}, error: {}", path, e)) }) - .and_then(|s| { - toml::Parser::new(&s).parse() - .ok_or(()) - .map_err(|_| error!("Cannot parse config file: {}", path)) }) - .unwrap() +#[derive(RustcDecodable, PartialEq, Eq, Debug)] +pub struct TestConfig { + pub interpret: bool, +} + +impl Default for AuthConfig { + fn default() -> AuthConfig { + AuthConfig { + server: Url::parse("http://127.0.0.1:9000").unwrap(), + client_id: "client-id".to_string(), + secret: "secret".to_string(), + } + } } -fn parse_sect(cfg: &toml::Table, sect: &str) -> T { - cfg.get(sect) - .and_then(|c| toml::decode::(c.clone()) ) - .ok_or_else(|| error!("Invalid section in config file: {}", sect)) - .unwrap() +impl Default for OtaConfig { + fn default() -> OtaConfig { + OtaConfig { + server: Url::parse("http://127.0.0.1:8080").unwrap(), + vin: "V1234567890123456".to_string(), + } + } +} + +impl Default for TestConfig { + fn default() -> TestConfig { + TestConfig { + interpret: false, + } + } +} + + +pub fn parse_config(s: &str) -> Result { + + fn parse_sect(tbl: &toml::Table, sect: &str) -> Result { + tbl.get(sect) + .and_then(|c| toml::decode::(c.clone()) ) + .ok_or(Error::new(ErrorKind::Other, + "invalid section: ".to_string() + sect)) + } + + let tbl: toml::Table = + try!(toml::Parser::new(&s) + .parse() + .ok_or(Error::new(ErrorKind::Other, "invalid toml"))); + + let auth_cfg: AuthConfig = try!(parse_sect(&tbl, "auth")); + let ota_cfg: OtaConfig = try!(parse_sect(&tbl, "ota")); + let test_cfg: TestConfig = try!(parse_sect(&tbl, "test")); + + return Ok(Config { + auth: auth_cfg, + ota: ota_cfg, + test: test_cfg, + }) } -pub fn parse_config(path: &str) -> (AuthConfig, OtaConfig) { - let cfg = read_config(path); - (parse_sect(&cfg, "auth"), parse_sect(&cfg, "ota")) +pub fn load_config(path: &str) -> Config { + + fn helper(path: &str) -> Result { + let mut f = try!(File::open(path)); + let mut s = String::new(); + try!(f.read_to_string(&mut s)); + return parse_config(&s); + } + + match helper(path) { + Err(err) => { + error!("Failed to load config: {}", err); + return Config::default(); + }, + Ok(cfg) => return cfg + } } + +#[cfg(test)] +mod tests { + + use super::*; + + fn default_config_str() -> &'static str { + r#" + [auth] + server = "http://127.0.0.1:9000" + client_id = "client-id" + secret = "secret" + + [ota] + server = "http://127.0.0.1:8080" + vin = "V1234567890123456" + + [test] + interpret = false + "# + } + + #[test] + fn parse_default_config() { + assert_eq!(parse_config(default_config_str()).unwrap(), + Config::default()); + } + + fn bad_section_str() -> &'static str { + r#" + [uth] + server = "http://127.0.0.1:9000" + client_id = "client-id" + secret = "secret" + + [ota] + server = "http://127.0.0.1:8080" + vin = "V1234567890123456" + + [test] + interpret = false + "# + } + + #[test] + fn bad_section() { + assert!(parse_config(bad_section_str()).is_err()) + } + +} diff --git a/src/main.rs b/src/main.rs index f14e2cc..e49af96 100644 --- a/src/main.rs +++ b/src/main.rs @@ -17,10 +17,9 @@ fn post_installed_packages(client: OtaClient, manager: M) -> Result<(), Error manager.installed_packages().and_then(|pkgs| client.post_packages(pkgs)) } -fn build_ota_client(cfg_file: &str) -> Result { - let (auth_config, ota_config) = config::parse_config(&cfg_file); - AuthClient::new(auth_config).authenticate().map(|token| { - OtaClient::new(token, ota_config) +fn build_ota_client(config: config::Config) -> Result { + AuthClient::new(config.auth.clone()).authenticate().map(|token| { + OtaClient::new(token, config.ota.clone()) }) } @@ -44,12 +43,14 @@ fn main() { env_logger::init().unwrap(); - let cfg_file = env::var("OTA_PLUS_CLIENT_CFG").unwrap_or("/opt/ats/ota/etc/ota.toml".to_string()); + let cfg_file = env::var("OTA_PLUS_CLIENT_CFG") + .unwrap_or("/opt/ats/ota/etc/ota.toml".to_string()); + let config = config::load_config(&cfg_file); let pkg_manager = Dpkg::new(); let pkg_manager_clone = pkg_manager.clone(); - let _ = build_ota_client(&cfg_file).and_then(|client| { + let _ = build_ota_client(config).and_then(|client| { post_installed_packages(client, pkg_manager) }).map(|_| { print!("Installed packages were posted successfully."); -- cgit v1.2.1 From 704f461deb276894cf2a8abcca6bda8703799813 Mon Sep 17 00:00:00 2001 From: Stevan Andjelkovic Date: Thu, 10 Mar 2016 17:28:00 +0100 Subject: Refactor main. --- ota.toml | 3 +++ src/config.rs | 4 +-- src/main.rs | 83 ++++++++++++++++++++++++++++++++++++----------------------- 3 files changed, 56 insertions(+), 34 deletions(-) diff --git a/ota.toml b/ota.toml index a11ceee..b9e2dca 100644 --- a/ota.toml +++ b/ota.toml @@ -6,3 +6,6 @@ secret = "secret" [ota] server = "http://127.0.0.1:8080" vin = "V1234567890123456" + +[test] +interpret = false diff --git a/src/config.rs b/src/config.rs index cf5f173..4c9f761 100644 --- a/src/config.rs +++ b/src/config.rs @@ -6,7 +6,7 @@ use std::io::{Error, ErrorKind}; use std::io; use toml; -#[derive(Default, PartialEq, Eq, Debug)] +#[derive(Default, PartialEq, Eq, Debug, Clone)] pub struct Config { pub auth: AuthConfig, pub ota: OtaConfig, @@ -26,7 +26,7 @@ pub struct OtaConfig { pub vin: String } -#[derive(RustcDecodable, PartialEq, Eq, Debug)] +#[derive(RustcDecodable, PartialEq, Eq, Debug, Clone)] pub struct TestConfig { pub interpret: bool, } diff --git a/src/main.rs b/src/main.rs index e49af96..6cc155b 100644 --- a/src/main.rs +++ b/src/main.rs @@ -4,42 +4,17 @@ extern crate getopts; use getopts::Options; use std::env; +use std::process::exit; use libotaplus::{config, read_interpret}; +use libotaplus::config::Config; use libotaplus::read_interpret::ReplEnv; use libotaplus::ota_plus::{Client as OtaClient}; use libotaplus::auth_plus::{Client as AuthClient}; use libotaplus::package_manager::{PackageManager, Dpkg}; use libotaplus::error::Error; -fn post_installed_packages(client: OtaClient, manager: M) -> Result<(), Error> - where M: PackageManager { - manager.installed_packages().and_then(|pkgs| client.post_packages(pkgs)) -} - -fn build_ota_client(config: config::Config) -> Result { - AuthClient::new(config.auth.clone()).authenticate().map(|token| { - OtaClient::new(token, config.ota.clone()) - }) -} - fn main() { - let args: Vec = env::args().collect(); - let program = args[0].clone(); - - let mut opts = Options::new(); - opts.optflag("l", "loop", "enter testing loop"); - opts.optflag("h", "help", "print this help menu"); - - let matches = match opts.parse(&args[1..]) { - Ok(m) => m, - Err(e) => panic!(e.to_string()) - }; - - if matches.opt_present("h") { - print_usage(&program, opts); - return; - } env_logger::init().unwrap(); @@ -47,10 +22,27 @@ fn main() { .unwrap_or("/opt/ats/ota/etc/ota.toml".to_string()); let config = config::load_config(&cfg_file); + do_stuff(handle_flags(config)); + +} + +fn do_stuff(config: Config) { + + fn post_installed_packages(client: OtaClient, manager: M) -> Result<(), Error> + where M: PackageManager { + manager.installed_packages().and_then(|pkgs| client.post_packages(pkgs)) + } + + fn build_ota_client(config: Config) -> Result { + AuthClient::new(config.auth.clone()).authenticate().map(|token| { + OtaClient::new(token, config.ota.clone()) + }) + } + let pkg_manager = Dpkg::new(); let pkg_manager_clone = pkg_manager.clone(); - let _ = build_ota_client(config).and_then(|client| { + let _ = build_ota_client(config.clone()).and_then(|client| { post_installed_packages(client, pkg_manager) }).map(|_| { print!("Installed packages were posted successfully."); @@ -58,12 +50,39 @@ fn main() { print!("{}", e); }); - if matches.opt_present("l") { + if config.test.interpret { read_interpret::read_interpret_loop(ReplEnv::new(pkg_manager_clone)); } } -fn print_usage(program: &str, opts: Options) { - let brief = format!("Usage: {} [options]", program); - print!("{}", opts.usage(&brief)); +fn handle_flags(config: Config) -> Config { + + fn print_usage(program: &str, opts: Options) { + let brief = format!("Usage: {} [options]", program); + print!("{}", opts.usage(&brief)); + } + + let args: Vec = env::args().collect(); + let program = args[0].clone(); + + let mut opts = Options::new(); + opts.optflag("l", "loop", "enter testing loop"); + opts.optflag("h", "help", "print this help menu"); + + let matches = match opts.parse(&args[1..]) { + Ok(m) => m, + Err(e) => panic!(e.to_string()) + }; + + if matches.opt_present("h") { + print_usage(&program, opts); + exit(1); + } + + if matches.opt_present("l") { + let mut config = config; + config.test.interpret = true; + return config + } + return config } -- cgit v1.2.1 From 39cfafd872044f8488e29cbfc1bdfe3ca46a9bdd Mon Sep 17 00:00:00 2001 From: Stevan Andjelkovic Date: Fri, 11 Mar 2016 11:13:31 +0100 Subject: Use our error type when parsing config rather than io::Error. --- src/config.rs | 66 +++++++++++++++++++++++++++++++++++++++++------------------ src/error.rs | 8 ++++++-- src/main.rs | 9 ++++++-- 3 files changed, 59 insertions(+), 24 deletions(-) diff --git a/src/config.rs b/src/config.rs index 4c9f761..e9c4d8e 100644 --- a/src/config.rs +++ b/src/config.rs @@ -2,10 +2,12 @@ use hyper::Url; use rustc_serialize::Decodable; use std::fs::File; use std::io::prelude::*; -use std::io::{Error, ErrorKind}; use std::io; use toml; +use error::Error; + + #[derive(Default, PartialEq, Eq, Debug, Clone)] pub struct Config { pub auth: AuthConfig, @@ -59,19 +61,18 @@ impl Default for TestConfig { } -pub fn parse_config(s: &str) -> Result { +pub fn parse_config(s: &str) -> Result { - fn parse_sect(tbl: &toml::Table, sect: &str) -> Result { + fn parse_sect(tbl: &toml::Table, sect: &str) -> Result { tbl.get(sect) .and_then(|c| toml::decode::(c.clone()) ) - .ok_or(Error::new(ErrorKind::Other, - "invalid section: ".to_string() + sect)) + .ok_or(Error::ConfigParseError(format!("invalid section: {}", sect))) } let tbl: toml::Table = try!(toml::Parser::new(&s) .parse() - .ok_or(Error::new(ErrorKind::Other, "invalid toml"))); + .ok_or(Error::ConfigParseError("invalid toml".to_string()))); let auth_cfg: AuthConfig = try!(parse_sect(&tbl, "auth")); let ota_cfg: OtaConfig = try!(parse_sect(&tbl, "ota")); @@ -84,22 +85,18 @@ pub fn parse_config(s: &str) -> Result { }) } -pub fn load_config(path: &str) -> Config { +pub fn load_config(path: &str) -> Result { - fn helper(path: &str) -> Result { - let mut f = try!(File::open(path)); - let mut s = String::new(); - try!(f.read_to_string(&mut s)); - return parse_config(&s); + impl From for Error { + fn from(err: io::Error) -> Error { + Error::ConfigIOError(format!("{}", err)) + } } - match helper(path) { - Err(err) => { - error!("Failed to load config: {}", err); - return Config::default(); - }, - Ok(cfg) => return cfg - } + let mut f = try!(File::open(path)); + let mut s = String::new(); + try!(f.read_to_string(&mut s)); + return parse_config(&s); } @@ -107,6 +104,7 @@ pub fn load_config(path: &str) -> Config { mod tests { use super::*; + use error::Error; fn default_config_str() -> &'static str { r#" @@ -148,7 +146,35 @@ mod tests { #[test] fn bad_section() { - assert!(parse_config(bad_section_str()).is_err()) + assert_eq!(parse_config(bad_section_str()), + Err(Error::ConfigParseError("invalid section: auth".to_string()))) + } + + #[test] + fn bad_path() { + assert_eq!(load_config(""), + Err(Error::ConfigIOError( + "No such file or directory (os error 2)".to_string()))) + } + + #[test] + fn bad_path_dir() { + assert_eq!(load_config("/"), + Err(Error::ConfigIOError( + "Is a directory (os error 21)".to_string()))) + } + + fn bad_toml_str() -> &'static str { + r#" + auth] + "# + } + + #[test] + fn bad_toml() { + assert_eq!(parse_config(bad_toml_str()), + Err(Error::ConfigParseError( + "invalid toml".to_string()))) } } diff --git a/src/error.rs b/src/error.rs index afc9d52..98af8fa 100644 --- a/src/error.rs +++ b/src/error.rs @@ -5,7 +5,9 @@ pub enum Error { AuthError(String), ParseError(String), PackageError(String), - ClientError(String) + ClientError(String), + ConfigParseError(String), + ConfigIOError(String), } impl Display for Error { @@ -14,7 +16,9 @@ impl Display for Error { &Error::AuthError(ref s) => s.clone(), &Error::ParseError(ref s) => s.clone(), &Error::PackageError(ref s) => s.clone(), - &Error::ClientError(ref s) => s.clone() + &Error::ClientError(ref s) => s.clone(), + &Error::ConfigParseError(ref s) => format!("failed to parse config: {}", s.clone()), + &Error::ConfigIOError(ref s) => format!("failed to load config: {}", s.clone()), }; write!(f, "Application Error: {}", inner) } diff --git a/src/main.rs b/src/main.rs index 6cc155b..a24d7c2 100644 --- a/src/main.rs +++ b/src/main.rs @@ -18,9 +18,14 @@ fn main() { env_logger::init().unwrap(); - let cfg_file = env::var("OTA_PLUS_CLIENT_CFG") + let config_file = env::var("OTA_PLUS_CLIENT_CFG") .unwrap_or("/opt/ats/ota/etc/ota.toml".to_string()); - let config = config::load_config(&cfg_file); + + let config = config::load_config(&config_file) + .unwrap_or_else(|err| { + println!("{} (continuing with the default config)", err); + return Config::default(); + }); do_stuff(handle_flags(config)); -- cgit v1.2.1 From 2d36b8418f8e881422957a94ec589dee95ccbd77 Mon Sep 17 00:00:00 2001 From: Stevan Andjelkovic Date: Fri, 11 Mar 2016 12:30:56 +0100 Subject: Add more flag options. --- ota.toml | 2 +- src/config.rs | 8 ++++---- src/main.rs | 65 +++++++++++++++++++++++++++++++++++++++++++++++------------ 3 files changed, 57 insertions(+), 18 deletions(-) diff --git a/ota.toml b/ota.toml index b9e2dca..5a4579f 100644 --- a/ota.toml +++ b/ota.toml @@ -8,4 +8,4 @@ server = "http://127.0.0.1:8080" vin = "V1234567890123456" [test] -interpret = false +looping = false diff --git a/src/config.rs b/src/config.rs index e9c4d8e..983aef5 100644 --- a/src/config.rs +++ b/src/config.rs @@ -30,7 +30,7 @@ pub struct OtaConfig { #[derive(RustcDecodable, PartialEq, Eq, Debug, Clone)] pub struct TestConfig { - pub interpret: bool, + pub looping: bool, } impl Default for AuthConfig { @@ -55,7 +55,7 @@ impl Default for OtaConfig { impl Default for TestConfig { fn default() -> TestConfig { TestConfig { - interpret: false, + looping: false, } } } @@ -118,7 +118,7 @@ mod tests { vin = "V1234567890123456" [test] - interpret = false + looping = false "# } @@ -140,7 +140,7 @@ mod tests { vin = "V1234567890123456" [test] - interpret = false + looping = false "# } diff --git a/src/main.rs b/src/main.rs index a24d7c2..f39955c 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,8 +1,10 @@ -extern crate libotaplus; extern crate env_logger; extern crate getopts; +extern crate hyper; +extern crate libotaplus; use getopts::Options; +use hyper::Url; use std::env; use std::process::exit; @@ -55,7 +57,7 @@ fn do_stuff(config: Config) { print!("{}", e); }); - if config.test.interpret { + if config.test.looping { read_interpret::read_interpret_loop(ReplEnv::new(pkg_manager_clone)); } } @@ -71,23 +73,60 @@ fn handle_flags(config: Config) -> Config { let program = args[0].clone(); let mut opts = Options::new(); - opts.optflag("l", "loop", "enter testing loop"); - opts.optflag("h", "help", "print this help menu"); - - let matches = match opts.parse(&args[1..]) { - Ok(m) => m, - Err(e) => panic!(e.to_string()) - }; + opts.optflag("h", "help", + "print this help menu"); + opts.optopt("", "auth-server", + "change the auth server url", "URL"); + opts.optopt("", "auth-client-id", + "change auth client id", "ID"); + opts.optopt("", "auth-secret", + "change auth secret", "SECRET"); + opts.optopt("", "ota-server", + "change ota server url", "URL"); + opts.optopt("", "ota-vin", + "change ota vin", "VIN"); + opts.optflag("", "test-looping", + "enable read-interpret test loop"); + + let matches = opts.parse(&args[1..]) + .unwrap_or_else(|err| panic!(err.to_string())); if matches.opt_present("h") { print_usage(&program, opts); exit(1); } - if matches.opt_present("l") { - let mut config = config; - config.test.interpret = true; - return config + let mut config = config; + + if let Some(s) = matches.opt_str("auth-server") { + match Url::parse(&s) { + Ok(url) => config.auth.server = url, + Err(err) => panic!("invalid auth-server url: {}", err) + } + } + + if let Some(client_id) = matches.opt_str("auth-client-id") { + config.auth.client_id = client_id; + } + + if let Some(secret) = matches.opt_str("auth-secret") { + config.auth.secret = secret; } + + if let Some(s) = matches.opt_str("ota-server") { + match Url::parse(&s) { + Ok(url) => config.ota.server = url, + Err(err) => panic!("invalid ota-server url: {}", err) + } + } + + if let Some(vin) = matches.opt_str("ota-vin") { + config.ota.vin = vin; + } + + if matches.opt_present("test-looping") { + config.test.looping = true; + } + return config } -- cgit v1.2.1 From 3f08eed683754ddd5c53fac9a1ab5de02c608b1d Mon Sep 17 00:00:00 2001 From: Stevan Andjelkovic Date: Fri, 11 Mar 2016 16:04:12 +0100 Subject: Add unit tests of the binary. --- src/main.rs | 40 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/src/main.rs b/src/main.rs index f39955c..a96a493 100644 --- a/src/main.rs +++ b/src/main.rs @@ -130,3 +130,43 @@ fn handle_flags(config: Config) -> Config { return config } + + +#[cfg(test)] +mod tests { + + use std::ffi::OsStr; + use std::process::Command; + + fn client>(args: &[S]) -> String { + let output = Command::new("target/debug/ota_plus_client") + .args(args) + .output() + .unwrap_or_else(|e| { panic!("failed to execute child: {}", e) }); + + return String::from_utf8(output.stdout).unwrap() + } + + #[test] + fn help() { + + assert_eq!(client(&["-h"]), +r#"Usage: target/debug/ota_plus_client [options] + +Options: + -h, --help print this help menu + --auth-server URL + change the auth server url + --auth-client-id ID + change auth client id + --auth-secret SECRET + change auth secret + --ota-server URL + change ota server url + --ota-vin VIN change ota vin + --test-looping enable read-interpret test loop +"#); + + } + +} -- cgit v1.2.1 From 5b12fd4c696115167c00f1633739f3e3e5e0e589 Mon Sep 17 00:00:00 2001 From: Stevan Andjelkovic Date: Fri, 11 Mar 2016 17:18:27 +0100 Subject: Add a flag to change config file. --- src/main.rs | 42 +++++++++++++++++++++++++----------------- 1 file changed, 25 insertions(+), 17 deletions(-) diff --git a/src/main.rs b/src/main.rs index a96a493..9fc49b4 100644 --- a/src/main.rs +++ b/src/main.rs @@ -16,24 +16,12 @@ use libotaplus::auth_plus::{Client as AuthClient}; use libotaplus::package_manager::{PackageManager, Dpkg}; use libotaplus::error::Error; + fn main() { env_logger::init().unwrap(); - let config_file = env::var("OTA_PLUS_CLIENT_CFG") - .unwrap_or("/opt/ats/ota/etc/ota.toml".to_string()); - - let config = config::load_config(&config_file) - .unwrap_or_else(|err| { - println!("{} (continuing with the default config)", err); - return Config::default(); - }); - - do_stuff(handle_flags(config)); - -} - -fn do_stuff(config: Config) { + let config = build_config(); fn post_installed_packages(client: OtaClient, manager: M) -> Result<(), Error> where M: PackageManager { @@ -62,7 +50,7 @@ fn do_stuff(config: Config) { } } -fn handle_flags(config: Config) -> Config { +fn build_config() -> Config { fn print_usage(program: &str, opts: Options) { let brief = format!("Usage: {} [options]", program); @@ -75,6 +63,8 @@ fn handle_flags(config: Config) -> Config { let mut opts = Options::new(); opts.optflag("h", "help", "print this help menu"); + opts.optopt("", "config", + "change config path", "PATH"); opts.optopt("", "auth-server", "change the auth server url", "URL"); opts.optopt("", "auth-client-id", @@ -96,7 +86,18 @@ fn handle_flags(config: Config) -> Config { exit(1); } - let mut config = config; + let mut config_file = env::var("OTA_PLUS_CLIENT_CFG") + .unwrap_or("/opt/ats/ota/etc/ota.toml".to_string()); + + if let Some(path) = matches.opt_str("config") { + config_file = path; + } + + let mut config = config::load_config(&config_file) + .unwrap_or_else(|err| { + println!("{} (continuing with the default config)", err); + return Config::default(); + }); if let Some(s) = matches.opt_str("auth-server") { match Url::parse(&s) { @@ -143,7 +144,6 @@ mod tests { .args(args) .output() .unwrap_or_else(|e| { panic!("failed to execute child: {}", e) }); - return String::from_utf8(output.stdout).unwrap() } @@ -155,6 +155,7 @@ r#"Usage: target/debug/ota_plus_client [options] Options: -h, --help print this help menu + --config PATH change config path --auth-server URL change the auth server url --auth-client-id ID @@ -169,4 +170,11 @@ Options: } + // TODO: exit if load config fails. + #[test] + fn bad_config_path() { + assert_eq!(client(&["--config", "apa"]), r#"Application Error: failed to load config: No such file or directory (os error 2) (continuing with the default config) +Application Error: Cannot send token request: connection refused"#); + } + } -- cgit v1.2.1 From e7b31d37567f00af547421acc305715c0048798a Mon Sep 17 00:00:00 2001 From: Stevan Andjelkovic Date: Mon, 14 Mar 2016 13:56:04 +0100 Subject: Clean up cases when program should exit. --- src/error.rs | 18 +++++++++++++++--- src/main.rs | 47 +++++++++++++++++++++++++---------------------- 2 files changed, 40 insertions(+), 25 deletions(-) diff --git a/src/error.rs b/src/error.rs index 98af8fa..c79ab34 100644 --- a/src/error.rs +++ b/src/error.rs @@ -17,9 +17,21 @@ impl Display for Error { &Error::ParseError(ref s) => s.clone(), &Error::PackageError(ref s) => s.clone(), &Error::ClientError(ref s) => s.clone(), - &Error::ConfigParseError(ref s) => format!("failed to parse config: {}", s.clone()), - &Error::ConfigIOError(ref s) => format!("failed to load config: {}", s.clone()), + &Error::ConfigParseError(ref s) => format!("Failed to parse config: {}", s.clone()), + &Error::ConfigIOError(ref s) => format!("Failed to load config: {}", s.clone()), }; - write!(f, "Application Error: {}", inner) + write!(f, "{}", inner) } } + +#[macro_export] +macro_rules! exit { + ($fmt:expr) => ({ + print!(concat!($fmt, "\n")); + std::process::exit(1); + }); + ($fmt:expr, $($arg:tt)*) => ({ + print!(concat!($fmt, "\n"), $($arg)*); + std::process::exit(1); + }) +} diff --git a/src/main.rs b/src/main.rs index 9fc49b4..0f5644e 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,12 +1,11 @@ extern crate env_logger; extern crate getopts; extern crate hyper; -extern crate libotaplus; +#[macro_use] extern crate libotaplus; use getopts::Options; use hyper::Url; use std::env; -use std::process::exit; use libotaplus::{config, read_interpret}; use libotaplus::config::Config; @@ -52,11 +51,6 @@ fn main() { fn build_config() -> Config { - fn print_usage(program: &str, opts: Options) { - let brief = format!("Usage: {} [options]", program); - print!("{}", opts.usage(&brief)); - } - let args: Vec = env::args().collect(); let program = args[0].clone(); @@ -66,13 +60,13 @@ fn build_config() -> Config { opts.optopt("", "config", "change config path", "PATH"); opts.optopt("", "auth-server", - "change the auth server url", "URL"); + "change the auth server URL", "URL"); opts.optopt("", "auth-client-id", "change auth client id", "ID"); opts.optopt("", "auth-secret", "change auth secret", "SECRET"); opts.optopt("", "ota-server", - "change ota server url", "URL"); + "change ota server URL", "URL"); opts.optopt("", "ota-vin", "change ota vin", "VIN"); opts.optflag("", "test-looping", @@ -82,8 +76,8 @@ fn build_config() -> Config { .unwrap_or_else(|err| panic!(err.to_string())); if matches.opt_present("h") { - print_usage(&program, opts); - exit(1); + let brief = format!("Usage: {} [options]", program); + exit!("{}", opts.usage(&brief)); } let mut config_file = env::var("OTA_PLUS_CLIENT_CFG") @@ -94,15 +88,12 @@ fn build_config() -> Config { } let mut config = config::load_config(&config_file) - .unwrap_or_else(|err| { - println!("{} (continuing with the default config)", err); - return Config::default(); - }); + .unwrap_or_else(|err| exit!("{}", err)); if let Some(s) = matches.opt_str("auth-server") { match Url::parse(&s) { Ok(url) => config.auth.server = url, - Err(err) => panic!("invalid auth-server url: {}", err) + Err(err) => exit!("Invalid auth-server URL: {}", err) } } @@ -117,7 +108,7 @@ fn build_config() -> Config { if let Some(s) = matches.opt_str("ota-server") { match Url::parse(&s) { Ok(url) => config.ota.server = url, - Err(err) => panic!("invalid ota-server url: {}", err) + Err(err) => exit!("Invalid ota-server URL: {}", err) } } @@ -157,24 +148,36 @@ Options: -h, --help print this help menu --config PATH change config path --auth-server URL - change the auth server url + change the auth server URL --auth-client-id ID change auth client id --auth-secret SECRET change auth secret --ota-server URL - change ota server url + change ota server URL --ota-vin VIN change ota vin --test-looping enable read-interpret test loop + "#); } - // TODO: exit if load config fails. #[test] fn bad_config_path() { - assert_eq!(client(&["--config", "apa"]), r#"Application Error: failed to load config: No such file or directory (os error 2) (continuing with the default config) -Application Error: Cannot send token request: connection refused"#); + assert_eq!(client(&["--config", "apa"]), + "Failed to load config: No such file or directory (os error 2)\n"); + } + + #[test] + fn bad_auth_server_url() { + assert_eq!(client(&["--auth-server", "apa"]), + "Invalid auth-server URL: relative URL without a base\n"); + } + + #[test] + fn bad_ota_server_url() { + assert_eq!(client(&["--ota-server", "apa"]), + "Invalid ota-server URL: relative URL without a base\n"); } } -- cgit v1.2.1 From abdd6e7b3e00da139e48d32b6e9a3a226f9abc43 Mon Sep 17 00:00:00 2001 From: Stevan Andjelkovic Date: Mon, 14 Mar 2016 15:06:34 +0100 Subject: Tidy up main. --- src/auth_plus.rs | 14 ++++++-------- src/main.rs | 36 ++++++++++++++---------------------- 2 files changed, 20 insertions(+), 30 deletions(-) diff --git a/src/auth_plus.rs b/src/auth_plus.rs index d3d64d5..56c1b44 100644 --- a/src/auth_plus.rs +++ b/src/auth_plus.rs @@ -1,14 +1,12 @@ -use config::AuthConfig; - -use std::io::Read; - -use error::Error; - use hyper::header::{Authorization, Basic, ContentType}; use hyper::mime::{Mime, TopLevel, SubLevel, Attr, Value}; use hyper; - use rustc_serialize::json; +use std::io::Read; + +use config::AuthConfig; +use error::Error; + #[derive(Clone, RustcDecodable, Debug)] pub struct AccessToken { @@ -43,7 +41,7 @@ impl Client { .body("grant_type=client_credentials") .send() .map_err(|e| { - Error::AuthError(format!("Cannot send token request: {}", e)) + Error::AuthError(format!("Cannot send token request to auth server: {}", e)) }) .and_then(|mut resp| { let mut rbody = String::new(); diff --git a/src/main.rs b/src/main.rs index 0f5644e..0d83237 100644 --- a/src/main.rs +++ b/src/main.rs @@ -13,7 +13,6 @@ use libotaplus::read_interpret::ReplEnv; use libotaplus::ota_plus::{Client as OtaClient}; use libotaplus::auth_plus::{Client as AuthClient}; use libotaplus::package_manager::{PackageManager, Dpkg}; -use libotaplus::error::Error; fn main() { @@ -21,31 +20,18 @@ fn main() { env_logger::init().unwrap(); let config = build_config(); - - fn post_installed_packages(client: OtaClient, manager: M) -> Result<(), Error> - where M: PackageManager { - manager.installed_packages().and_then(|pkgs| client.post_packages(pkgs)) - } - - fn build_ota_client(config: Config) -> Result { - AuthClient::new(config.auth.clone()).authenticate().map(|token| { - OtaClient::new(token, config.ota.clone()) - }) - } - let pkg_manager = Dpkg::new(); - let pkg_manager_clone = pkg_manager.clone(); - let _ = build_ota_client(config.clone()).and_then(|client| { - post_installed_packages(client, pkg_manager) - }).map(|_| { - print!("Installed packages were posted successfully."); - }).map_err(|e| { - print!("{}", e); - }); + let _ = AuthClient::new(config.auth.clone()) + .authenticate() + .map(|token| OtaClient::new(token, config.ota.clone())) + .and_then(|client| pkg_manager.installed_packages() + .and_then(|pkgs| client.post_packages(pkgs))) + .map(|_| println!("Installed packages were posted successfully.")) + .map_err(|err| println!("{}", err)); if config.test.looping { - read_interpret::read_interpret_loop(ReplEnv::new(pkg_manager_clone)); + read_interpret::read_interpret_loop(ReplEnv::new(pkg_manager.clone())); } } @@ -180,4 +166,10 @@ Options: "Invalid ota-server URL: relative URL without a base\n"); } + #[test] + fn no_auth_server_to_connect_to() { + assert_eq!(client(&[""]), + "Cannot send token request to auth server: connection refused\n"); + } + } -- cgit v1.2.1 From 6b940ce36a88c64d03347ef85b7da3a135d38734 Mon Sep 17 00:00:00 2001 From: Stevan Andjelkovic Date: Mon, 14 Mar 2016 16:15:10 +0100 Subject: In auth and ota modules: fix indentation, use Error rather than string and return as much information as possible (up to caller if they want to discard it). --- src/auth_plus.rs | 18 ++++++++++-------- src/error.rs | 8 ++++---- src/main.rs | 3 ++- src/ota_plus.rs | 58 +++++++++++++++++++++++++++++--------------------------- 4 files changed, 46 insertions(+), 41 deletions(-) diff --git a/src/auth_plus.rs b/src/auth_plus.rs index 56c1b44..bc81433 100644 --- a/src/auth_plus.rs +++ b/src/auth_plus.rs @@ -22,6 +22,7 @@ pub struct Client { } impl Client { + pub fn new(config: AuthConfig) -> Client { Client { hclient: hyper::Client::new(), @@ -30,6 +31,7 @@ impl Client { } pub fn authenticate(&self) -> Result { + self.hclient.post(self.config.server.join("/token").unwrap()) .header(Authorization(Basic { username: self.config.client_id.clone(), @@ -40,17 +42,17 @@ impl Client { vec![(Attr::Charset, Value::Utf8)]))) .body("grant_type=client_credentials") .send() - .map_err(|e| { - Error::AuthError(format!("Cannot send token request to auth server: {}", e)) - }) + .map_err(|e| Error::AuthError(format!( + "cannot send token request: {}", e))) .and_then(|mut resp| { let mut rbody = String::new(); resp.read_to_string(&mut rbody) - .map_err(|e| Error::AuthError(format!("Cannot read token response: {}", e))) - .and_then(|_| { - json::decode::(&rbody) - .map_err(|e| Error::AuthError(format!("Cannot parse token response: {}. Got: {}", e, &rbody))) - }) + .map_err(|e| Error::AuthError(format!( + "cannot read token response: {}", e))) + .and_then(|_| json::decode::(&rbody) + .map_err(|e| Error::AuthError(format!( + "cannot parse token response: {}. Got: {}", e, &rbody)))) }) } + } diff --git a/src/error.rs b/src/error.rs index c79ab34..a546bea 100644 --- a/src/error.rs +++ b/src/error.rs @@ -13,10 +13,10 @@ pub enum Error { impl Display for Error { fn fmt(&self, f: &mut Formatter) -> FmtResult { let inner: String = match self { - &Error::AuthError(ref s) => s.clone(), - &Error::ParseError(ref s) => s.clone(), - &Error::PackageError(ref s) => s.clone(), - &Error::ClientError(ref s) => s.clone(), + &Error::AuthError(ref s) => format!("Authentication error, {}", s.clone()), + &Error::ParseError(ref s) => s.clone(), + &Error::PackageError(ref s) => s.clone(), + &Error::ClientError(ref s) => s.clone(), &Error::ConfigParseError(ref s) => format!("Failed to parse config: {}", s.clone()), &Error::ConfigIOError(ref s) => format!("Failed to load config: {}", s.clone()), }; diff --git a/src/main.rs b/src/main.rs index 0d83237..0f28e90 100644 --- a/src/main.rs +++ b/src/main.rs @@ -33,6 +33,7 @@ fn main() { if config.test.looping { read_interpret::read_interpret_loop(ReplEnv::new(pkg_manager.clone())); } + } fn build_config() -> Config { @@ -169,7 +170,7 @@ Options: #[test] fn no_auth_server_to_connect_to() { assert_eq!(client(&[""]), - "Cannot send token request to auth server: connection refused\n"); + "Authentication error, cannot send token request: connection refused\n"); } } diff --git a/src/ota_plus.rs b/src/ota_plus.rs index ce05673..3e8bb27 100644 --- a/src/ota_plus.rs +++ b/src/ota_plus.rs @@ -1,16 +1,16 @@ -use config::OtaConfig; -use auth_plus::AccessToken; -use package::Package; -use error::Error; - -use std::io::Read; -use std::result::Result; - +use hyper::client::Response; use hyper::header::{Authorization, Bearer, ContentType}; use hyper::mime::{Mime, TopLevel, SubLevel, Attr, Value}; use hyper; - use rustc_serialize::json; +use std::io::Read; +use std::result::Result; + +use auth_plus::AccessToken; +use config::OtaConfig; +use error::Error; +use package::Package; + pub struct Client { hclient: hyper::Client, @@ -19,6 +19,7 @@ pub struct Client { } impl Client { + pub fn new(token: AccessToken, config: OtaConfig) -> Client { Client { hclient: hyper::Client::new(), @@ -27,34 +28,35 @@ impl Client { } } - pub fn check_for_update(&self) { - let _ = self.hclient.get(self.config.server.join("/updates").unwrap()) + pub fn check_for_update(&self) -> Result { + self.hclient.get(self.config.server.join("/updates").unwrap()) .header(Authorization(Bearer { token: self.access_token.clone() })) .send() - .map_err(|e| error!("Cannot send check_for_update request: {}", e)) + .map_err(|e| Error::ClientError(format!( + "Cannot send check_for_update request: {}", e))) .and_then(|mut resp| { let mut rbody = String::new(); resp.read_to_string(&mut rbody) - .map_err(|e| error!("Cannot read check_for_update response: {}", e)) - .and_then(|_| { - Ok(info!("Check for update: {}", rbody)) }) }); + .map_err(|e| Error::ClientError(format!( + "Cannot read check_for_update response: {}", e))) + .and_then(|_| Ok(rbody)) + }) } - - pub fn post_packages(&self, pkgs: Vec) -> Result<(), Error>{ + pub fn post_packages(&self, pkgs: Vec) -> Result { json::encode(&pkgs) .map_err(|_| Error::ParseError(String::from("JSON encoding error"))) .and_then(|json| { - self.hclient.put(self.config.server.join("/packages").unwrap()) - .header(Authorization(Bearer { token: self.access_token.clone() })) - .header(ContentType(Mime( - TopLevel::Application, - SubLevel::Json, - vec![(Attr::Charset, Value::Utf8)]))) - .body(&json) - .send() - .map_err(|e| Error::ClientError(format!("Cannot send packages: {}", e))) - .map(|_| ()) - }) + self.hclient.put(self.config.server.join("/packages").unwrap()) + .header(Authorization(Bearer { token: self.access_token.clone() })) + .header(ContentType(Mime( + TopLevel::Application, + SubLevel::Json, + vec![(Attr::Charset, Value::Utf8)]))) + .body(&json) + .send() + .map_err(|e| Error::ClientError(format!("Cannot send packages: {}", e))) + }) } + } -- cgit v1.2.1 From 713dd92e769193aca241dfbdbfb1bcf3030e9e6b Mon Sep 17 00:00:00 2001 From: Stevan Andjelkovic Date: Mon, 14 Mar 2016 17:06:52 +0100 Subject: Put package tests inside tests module. --- src/package_manager/dpkg.rs | 43 ++++++++++++++++++++++++++++--------------- 1 file changed, 28 insertions(+), 15 deletions(-) diff --git a/src/package_manager/dpkg.rs b/src/package_manager/dpkg.rs index 2e7f4dc..2d24b77 100644 --- a/src/package_manager/dpkg.rs +++ b/src/package_manager/dpkg.rs @@ -32,7 +32,7 @@ impl package_manager::PackageManager for Dpkg { } } -fn parse_package(line: &str) -> Result { +pub fn parse_package(line: &str) -> Result { match line.splitn(2, ' ').collect::>() { ref parts if parts.len() == 2 => Ok(Package { name: String::from(parts[0]), version: String::from(parts[1]) }), @@ -40,20 +40,33 @@ fn parse_package(line: &str) -> Result { } } -#[test] -fn test_parses_normal_package() { - assert_eq!(Ok(Package { name: "uuid-runtime".to_string(), version: "2.20.1-5.1ubuntu20.7".to_string() }), - parse_package("uuid-runtime 2.20.1-5.1ubuntu20.7")); -} +#[cfg(test)] +mod tests { -#[test] -fn test_separates_name_and_version_correctly() { - assert_eq!(Ok(Package { name: "vim".to_string(), version: "2.1 foobar".to_string() }), - parse_package("vim 2.1 foobar")); -} + use super::*; + use error::Error; + use package::Package; + + #[test] + fn test_parses_normal_package() { + assert_eq!(parse_package("uuid-runtime 2.20.1-5.1ubuntu20.7"), + Ok(Package { + name: "uuid-runtime".to_string(), + version: "2.20.1-5.1ubuntu20.7".to_string() })); + } + + #[test] + fn test_separates_name_and_version_correctly() { + assert_eq!(parse_package("vim 2.1 foobar"), + Ok(Package { + name: "vim".to_string(), + version: "2.1 foobar".to_string() })); + } + + #[test] + fn test_rejects_bogus_input() { + assert_eq!(parse_package("foobar"), + Err(Error::ParseError("Couldn't parse package: foobar".to_string()))); + } -#[test] -fn test_rejects_bogus_input() { - assert_eq!(Err(Error::ParseError("Couldn't parse package: foobar".to_string())), - parse_package("foobar")) } -- cgit v1.2.1 From 574b75f05a386e356699ad85818785c0ac9edc7f Mon Sep 17 00:00:00 2001 From: Txus Date: Tue, 15 Mar 2016 18:06:03 +0100 Subject: Test AuthPlus and Ota clients Also move binary tests to tests/ folder, to avoid the temptation of changing something in main and thinking the tests will cover that (they need a `cargo build` before running). --- src/auth_plus.rs | 114 ++++++++++++++++++++++++++++++----------- src/config.rs | 20 +++++--- src/http_client.rs | 63 +++++++++++++++++++++++ src/lib.rs | 1 + src/main.rs | 70 +------------------------ src/ota_plus.rs | 95 ++++++++++++++++++++++++---------- tests/ota_plus_client_tests.rs | 52 +++++++++++++++++++ 7 files changed, 281 insertions(+), 134 deletions(-) create mode 100644 src/http_client.rs create mode 100644 tests/ota_plus_client_tests.rs diff --git a/src/auth_plus.rs b/src/auth_plus.rs index bc81433..d4d863a 100644 --- a/src/auth_plus.rs +++ b/src/auth_plus.rs @@ -1,58 +1,112 @@ +use config::AuthConfig; +use http_client::{HttpClient, HttpRequest}; + +use error::Error; + use hyper::header::{Authorization, Basic, ContentType}; use hyper::mime::{Mime, TopLevel, SubLevel, Attr, Value}; -use hyper; -use rustc_serialize::json; -use std::io::Read; -use config::AuthConfig; -use error::Error; +use rustc_serialize::json; -#[derive(Clone, RustcDecodable, Debug)] +#[derive(Clone, RustcDecodable, Debug, PartialEq)] pub struct AccessToken { pub access_token: String, token_type: String, expires_in: i32, - scope: Option + scope: Vec } -pub struct Client { - hclient: hyper::Client, - config: AuthConfig +impl AccessToken { + pub fn new(token: String, token_type: String, expires_in: i32, scope: Vec) -> AccessToken { + AccessToken { access_token: token, token_type: token_type, expires_in: expires_in, scope: scope } + } } -impl Client { +pub struct Client { + http_client: C, + config: AuthConfig +} - pub fn new(config: AuthConfig) -> Client { +impl Client { + pub fn new(client: C, config: AuthConfig) -> Client { Client { - hclient: hyper::Client::new(), + http_client: client, config: config } } pub fn authenticate(&self) -> Result { - - self.hclient.post(self.config.server.join("/token").unwrap()) - .header(Authorization(Basic { + let req = HttpRequest::post(self.config.server.join("/token").unwrap()) + .with_body("grant_type=client_credentials") + .with_header(Authorization(Basic { username: self.config.client_id.clone(), password: Some(self.config.secret.clone()) })) - .header(ContentType(Mime( + .with_header(ContentType(Mime( TopLevel::Application, SubLevel::WwwFormUrlEncoded, - vec![(Attr::Charset, Value::Utf8)]))) - .body("grant_type=client_credentials") - .send() - .map_err(|e| Error::AuthError(format!( - "cannot send token request: {}", e))) - .and_then(|mut resp| { - let mut rbody = String::new(); - resp.read_to_string(&mut rbody) - .map_err(|e| Error::AuthError(format!( - "cannot read token response: {}", e))) - .and_then(|_| json::decode::(&rbody) - .map_err(|e| Error::AuthError(format!( - "cannot parse token response: {}. Got: {}", e, &rbody)))) + vec![(Attr::Charset, Value::Utf8)]))); + self.http_client.send_request(&req) + .map_err(|e| Error::AuthError(format!("Can't get AuthPlus token: {}", e))) + .and_then(|body| { + json::decode::(&body) + .map_err(|e| Error::ParseError(format!("Cannot parse response: {}. Got: {}", e, &body))) }) } +} + +#[cfg(test)] +mod tests { + use super::*; + use http_client::{HttpRequest, HttpClient}; + use error::Error; + use config::AuthConfig; + use hyper::header::{Authorization, Basic, ContentType}; + use hyper::mime::{Mime, TopLevel, SubLevel, Attr, Value}; + + struct MockClient { + username: String, + secret: String + } + + impl MockClient { + fn new(username: String, secret: String) -> MockClient { + MockClient { username: username, secret: secret } + } + + fn assert_authenticated(&self, req: &HttpRequest) { + assert_eq!(req.body, Some("grant_type=client_credentials")); + assert_eq!(Some(&Authorization(Basic { username: self.username.clone(), password: Some(self.secret.clone()) })), + req.headers.get::>()) + } + + fn assert_form_encoded(&self, req: &HttpRequest) { + assert_eq!(Some(&ContentType(Mime(TopLevel::Application, SubLevel::WwwFormUrlEncoded, + vec![(Attr::Charset, Value::Utf8)]))), + req.headers.get::()) + } + } + + #[test] + fn test_authenticate() { + impl HttpClient for MockClient { + fn send_request(&self, req: &HttpRequest) -> Result { + self.assert_authenticated(req); + self.assert_form_encoded(req); + Ok::("{\"access_token\": \"token\", \"token_type\": \"type\", \"expires_in\": 10, \"scope\": [\"scope\"]}".to_string()) + } + } + + let config = AuthConfig::default(); + let mock = MockClient::new(config.client_id, config.secret); + let auth_plus = Client::new(mock, AuthConfig::default()); + + assert_eq!(Ok(AccessToken { + access_token: "token".to_string(), + token_type: "type".to_string(), + expires_in: 10, + scope: vec!["scope".to_string()] + }), auth_plus.authenticate()) + } } diff --git a/src/config.rs b/src/config.rs index 983aef5..01688e0 100644 --- a/src/config.rs +++ b/src/config.rs @@ -2,6 +2,7 @@ use hyper::Url; use rustc_serialize::Decodable; use std::fs::File; use std::io::prelude::*; +use std::io::ErrorKind; use std::io; use toml; @@ -93,10 +94,15 @@ pub fn load_config(path: &str) -> Result { } } - let mut f = try!(File::open(path)); - let mut s = String::new(); - try!(f.read_to_string(&mut s)); - return parse_config(&s); + match File::open(path) { + Err(ref e) if e.kind() == ErrorKind::NotFound => Ok(Config::default()), + Err(e) => Err(From::from(e)), + Ok(mut f) => { + let mut s = String::new(); + try!(f.read_to_string(&mut s)); + return parse_config(&s); + } + } } @@ -151,10 +157,8 @@ mod tests { } #[test] - fn bad_path() { - assert_eq!(load_config(""), - Err(Error::ConfigIOError( - "No such file or directory (os error 2)".to_string()))) + fn bad_path_yields_default_config() { + assert_eq!(load_config(""), Ok(Config::default())) } #[test] diff --git a/src/http_client.rs b/src/http_client.rs new file mode 100644 index 0000000..08e5172 --- /dev/null +++ b/src/http_client.rs @@ -0,0 +1,63 @@ +use hyper; +use hyper::method::Method; +use hyper::header::{Headers, Header, HeaderFormat}; +use hyper::Url; +use error::Error; + +use std::io::Read; + + +#[derive(Clone, Debug)] +pub struct HttpRequest<'a> { + pub url: Url, + pub method: Method, + pub headers: Headers, + pub body: Option<&'a str> +} + +impl<'a> HttpRequest<'a> { + pub fn new(url: Url, method: Method) -> HttpRequest<'a> { + HttpRequest { url: url, method: method, headers: Headers::new(), body: None } + } + + #[allow(dead_code)] + pub fn get(url: Url) -> HttpRequest<'a> { + HttpRequest::new(url, Method::Get) + } + + pub fn post(url: Url) -> HttpRequest<'a> { + HttpRequest::new(url, Method::Post) + } + + pub fn with_body(&self, body: &'a str) -> HttpRequest<'a> { + HttpRequest { body: Some(body), ..self.clone() } + } + + pub fn with_header(&self, header: H) -> HttpRequest<'a> { + let mut hs = self.headers.clone(); + hs.set(header); + HttpRequest { headers: hs, ..self.clone() } + } +} + +pub trait HttpClient { + fn send_request(&self, req: &HttpRequest) -> Result; +} + +impl HttpClient for hyper::Client { + fn send_request(&self, req: &HttpRequest) -> Result { + self.request(req.method.clone(), req.url.clone()) + .headers(req.headers.clone()) + .body(if let Some(body) = req.body { body } else { "" }) + .send() + .map_err(|e| { + Error::ClientError(format!("Cannot send request: {}", e)) + }) + .and_then(|mut resp| { + let mut rbody = String::new(); + resp.read_to_string(&mut rbody) + .map_err(|e| Error::ParseError(format!("Cannot read response: {}", e))) + .map(|_| rbody) + }) + } +} diff --git a/src/lib.rs b/src/lib.rs index c4ad6cd..619f0e9 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -12,3 +12,4 @@ pub mod auth_plus; pub mod package; pub mod package_manager; pub mod error; +mod http_client; diff --git a/src/main.rs b/src/main.rs index 0f28e90..f2dba2f 100644 --- a/src/main.rs +++ b/src/main.rs @@ -14,7 +14,6 @@ use libotaplus::ota_plus::{Client as OtaClient}; use libotaplus::auth_plus::{Client as AuthClient}; use libotaplus::package_manager::{PackageManager, Dpkg}; - fn main() { env_logger::init().unwrap(); @@ -22,9 +21,9 @@ fn main() { let config = build_config(); let pkg_manager = Dpkg::new(); - let _ = AuthClient::new(config.auth.clone()) + let _ = AuthClient::new(hyper::Client::new(), config.auth.clone()) .authenticate() - .map(|token| OtaClient::new(token, config.ota.clone())) + .map(|token| OtaClient::new(hyper::Client::new(), token, config.ota.clone())) .and_then(|client| pkg_manager.installed_packages() .and_then(|pkgs| client.post_packages(pkgs))) .map(|_| println!("Installed packages were posted successfully.")) @@ -109,68 +108,3 @@ fn build_config() -> Config { return config } - - -#[cfg(test)] -mod tests { - - use std::ffi::OsStr; - use std::process::Command; - - fn client>(args: &[S]) -> String { - let output = Command::new("target/debug/ota_plus_client") - .args(args) - .output() - .unwrap_or_else(|e| { panic!("failed to execute child: {}", e) }); - return String::from_utf8(output.stdout).unwrap() - } - - #[test] - fn help() { - - assert_eq!(client(&["-h"]), -r#"Usage: target/debug/ota_plus_client [options] - -Options: - -h, --help print this help menu - --config PATH change config path - --auth-server URL - change the auth server URL - --auth-client-id ID - change auth client id - --auth-secret SECRET - change auth secret - --ota-server URL - change ota server URL - --ota-vin VIN change ota vin - --test-looping enable read-interpret test loop - -"#); - - } - - #[test] - fn bad_config_path() { - assert_eq!(client(&["--config", "apa"]), - "Failed to load config: No such file or directory (os error 2)\n"); - } - - #[test] - fn bad_auth_server_url() { - assert_eq!(client(&["--auth-server", "apa"]), - "Invalid auth-server URL: relative URL without a base\n"); - } - - #[test] - fn bad_ota_server_url() { - assert_eq!(client(&["--ota-server", "apa"]), - "Invalid ota-server URL: relative URL without a base\n"); - } - - #[test] - fn no_auth_server_to_connect_to() { - assert_eq!(client(&[""]), - "Authentication error, cannot send token request: connection refused\n"); - } - -} diff --git a/src/ota_plus.rs b/src/ota_plus.rs index 3e8bb27..1291b25 100644 --- a/src/ota_plus.rs +++ b/src/ota_plus.rs @@ -1,9 +1,8 @@ -use hyper::client::Response; use hyper::header::{Authorization, Bearer, ContentType}; use hyper::mime::{Mime, TopLevel, SubLevel, Attr, Value}; -use hyper; + +use http_client::{HttpClient, HttpRequest}; use rustc_serialize::json; -use std::io::Read; use std::result::Result; use auth_plus::AccessToken; @@ -11,52 +10,92 @@ use config::OtaConfig; use error::Error; use package::Package; - -pub struct Client { - hclient: hyper::Client, +pub struct Client { + http_client: C, access_token: String, config: OtaConfig } -impl Client { +impl Client { - pub fn new(token: AccessToken, config: OtaConfig) -> Client { + pub fn new(client: C, token: AccessToken, config: OtaConfig) -> Client { Client { - hclient: hyper::Client::new(), + http_client: client, access_token: token.access_token, config: config } } + #[allow(dead_code)] pub fn check_for_update(&self) -> Result { - self.hclient.get(self.config.server.join("/updates").unwrap()) - .header(Authorization(Bearer { token: self.access_token.clone() })) - .send() - .map_err(|e| Error::ClientError(format!( - "Cannot send check_for_update request: {}", e))) - .and_then(|mut resp| { - let mut rbody = String::new(); - resp.read_to_string(&mut rbody) - .map_err(|e| Error::ClientError(format!( - "Cannot read check_for_update response: {}", e))) - .and_then(|_| Ok(rbody)) - }) + let req = HttpRequest::get(self.config.server.join("/updates").unwrap()) + .with_header(Authorization(Bearer { token: self.access_token.clone() })); + self.http_client.send_request(&req) } - pub fn post_packages(&self, pkgs: Vec) -> Result { + pub fn post_packages(&self, pkgs: Vec) -> Result<(), Error> { json::encode(&pkgs) .map_err(|_| Error::ParseError(String::from("JSON encoding error"))) .and_then(|json| { - self.hclient.put(self.config.server.join("/packages").unwrap()) - .header(Authorization(Bearer { token: self.access_token.clone() })) - .header(ContentType(Mime( + let req = HttpRequest::post(self.config.server.join("/packages").unwrap()) + .with_header(Authorization(Bearer { token: self.access_token.clone() })) + .with_header(ContentType(Mime( TopLevel::Application, SubLevel::Json, vec![(Attr::Charset, Value::Utf8)]))) - .body(&json) - .send() - .map_err(|e| Error::ClientError(format!("Cannot send packages: {}", e))) + .with_body(&json); + + self.http_client.send_request(&req).map(|_| ()) }) } +} + +#[cfg(test)] +mod tests { + use super::*; + use http_client::{HttpRequest, HttpClient}; + use error::Error; + use package::Package; + use config::OtaConfig; + use auth_plus::AccessToken; + + use hyper::header::{Authorization, Bearer}; + + struct MockClient { + access_token: String + } + + impl MockClient { + fn new(token: AccessToken) -> MockClient { + MockClient { access_token: token.access_token } + } + + fn assert_authenticated(&self, req: &HttpRequest) { + assert_eq!(Some(&Authorization(Bearer { token: self.access_token.clone() })), + req.headers.get::>()) + } + } + fn mk_token() -> AccessToken { + AccessToken::new("token".to_string(), "bar".to_string(), 20, vec![]) + } + + fn mk_package() -> Package { + Package { name: "hey".to_string(), version: "1.2.3".to_string() } + } + + #[test] + fn test_post_packages_sends_authentication() { + impl HttpClient for MockClient { + fn send_request(&self, req: &HttpRequest) -> Result { + self.assert_authenticated(req); + Ok::("ok".to_string()) + } + } + + let mock = MockClient::new(mk_token()); + let ota_plus = Client::new(mock, mk_token(), OtaConfig::default()); + + let _ = ota_plus.post_packages(vec![mk_package()]); + } } diff --git a/tests/ota_plus_client_tests.rs b/tests/ota_plus_client_tests.rs new file mode 100644 index 0000000..a769c9e --- /dev/null +++ b/tests/ota_plus_client_tests.rs @@ -0,0 +1,52 @@ +use std::ffi::OsStr; +use std::process::Command; + +fn client>(args: &[S]) -> String { + let output = Command::new("target/debug/ota_plus_client") + .args(args) + .output() + .unwrap_or_else(|e| { panic!("failed to execute child: {}", e) }); + return String::from_utf8(output.stdout).unwrap() +} + +#[test] +fn help() { + + assert_eq!(client(&["-h"]), + r#"Usage: target/debug/ota_plus_client [options] + +Options: + -h, --help print this help menu + --config PATH change config path + --auth-server URL + change the auth server URL + --auth-client-id ID + change auth client id + --auth-secret SECRET + change auth secret + --ota-server URL + change ota server URL + --ota-vin VIN change ota vin + --test-looping enable read-interpret test loop + +"#); + +} + +#[test] +fn bad_auth_server_url() { + assert_eq!(client(&["--auth-server", "apa"]), + "Invalid auth-server URL: relative URL without a base\n"); +} + +#[test] +fn bad_ota_server_url() { + assert_eq!(client(&["--ota-server", "apa"]), + "Invalid ota-server URL: relative URL without a base\n"); +} + +#[test] +fn no_auth_server_to_connect_to() { + assert_eq!(client(&[""]), + "Authentication error, Can\'t get AuthPlus token: Cannot send request: connection refused\n"); +} -- cgit v1.2.1 From 4e7138a93066b41ad127ff4e1f5510ac6695c8b1 Mon Sep 17 00:00:00 2001 From: Stevan Andjelkovic Date: Wed, 16 Mar 2016 15:46:54 +0100 Subject: Make it possible to issue `cargo test` and have tests succeed without having to do `cargo build` first. --- src/main.rs | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/main.rs b/src/main.rs index f2dba2f..9bb2194 100644 --- a/src/main.rs +++ b/src/main.rs @@ -108,3 +108,16 @@ fn build_config() -> Config { return config } + +// Hack to build a binary with a predictable path for use in tests/. We +// can remove this when https://github.com/rust-lang/cargo/issues/1924 +// is resolved. +#[test] +fn build_binary() { + let output = std::process::Command::new("cargo") + .arg("build") + .output() + .unwrap_or_else(|e| panic!("failed to execute child: {}", e)); + + assert!(output.status.success()) +} -- cgit v1.2.1 From c6ea6d62b368b4cd8da901a0c0a64de55e2c2fee Mon Sep 17 00:00:00 2001 From: Stevan Andjelkovic Date: Wed, 16 Mar 2016 17:35:53 +0100 Subject: Add ability to do unit tests on the binary using a config provided as a string. --- Cargo.toml | 3 ++- src/config.rs | 42 ----------------------------- src/lib.rs | 1 + tests/ota_plus_client_tests.rs | 60 +++++++++++++++++++++++++++++++++++++----- 4 files changed, 57 insertions(+), 49 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 507e42e..8e9462f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,4 +18,5 @@ hyper = "*" log = "*" rustc-serialize = "*" toml = "*" -getopts = "*" \ No newline at end of file +getopts = "*" +tempfile = "*" \ No newline at end of file diff --git a/src/config.rs b/src/config.rs index 01688e0..2b03a87 100644 --- a/src/config.rs +++ b/src/config.rs @@ -134,51 +134,9 @@ mod tests { Config::default()); } - fn bad_section_str() -> &'static str { - r#" - [uth] - server = "http://127.0.0.1:9000" - client_id = "client-id" - secret = "secret" - - [ota] - server = "http://127.0.0.1:8080" - vin = "V1234567890123456" - - [test] - looping = false - "# - } - - #[test] - fn bad_section() { - assert_eq!(parse_config(bad_section_str()), - Err(Error::ConfigParseError("invalid section: auth".to_string()))) - } - #[test] fn bad_path_yields_default_config() { assert_eq!(load_config(""), Ok(Config::default())) } - #[test] - fn bad_path_dir() { - assert_eq!(load_config("/"), - Err(Error::ConfigIOError( - "Is a directory (os error 21)".to_string()))) - } - - fn bad_toml_str() -> &'static str { - r#" - auth] - "# - } - - #[test] - fn bad_toml() { - assert_eq!(parse_config(bad_toml_str()), - Err(Error::ConfigParseError( - "invalid toml".to_string()))) - } - } diff --git a/src/lib.rs b/src/lib.rs index 619f0e9..76f950d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -3,6 +3,7 @@ extern crate log; extern crate hyper; extern crate rustc_serialize; +extern crate tempfile; extern crate toml; pub mod config; diff --git a/tests/ota_plus_client_tests.rs b/tests/ota_plus_client_tests.rs index a769c9e..615a913 100644 --- a/tests/ota_plus_client_tests.rs +++ b/tests/ota_plus_client_tests.rs @@ -1,14 +1,29 @@ -use std::ffi::OsStr; +extern crate tempfile; + +use std::io::Write; use std::process::Command; +use std::vec::Vec; +use tempfile::NamedTempFile; -fn client>(args: &[S]) -> String { +fn client(args: &[&str]) -> String { let output = Command::new("target/debug/ota_plus_client") .args(args) .output() - .unwrap_or_else(|e| { panic!("failed to execute child: {}", e) }); + .unwrap_or_else(|e| panic!("failed to execute child: {}", e)); return String::from_utf8(output.stdout).unwrap() } +fn client_with_config(args: &[&str], cfg: &str) -> String { + let mut file = NamedTempFile::new().unwrap(); + let _ = file.write_all(cfg.as_bytes()).unwrap(); + + let arg: String = "--config=".to_string() + file.path().to_str().unwrap(); + let mut args: Vec<&str> = args.to_vec(); + + args.push(&arg); + client(&args) +} + #[test] fn help() { @@ -36,17 +51,50 @@ Options: #[test] fn bad_auth_server_url() { assert_eq!(client(&["--auth-server", "apa"]), - "Invalid auth-server URL: relative URL without a base\n"); + "Invalid auth-server URL: relative URL without a base\n") } #[test] fn bad_ota_server_url() { assert_eq!(client(&["--ota-server", "apa"]), - "Invalid ota-server URL: relative URL without a base\n"); + "Invalid ota-server URL: relative URL without a base\n") } #[test] fn no_auth_server_to_connect_to() { assert_eq!(client(&[""]), - "Authentication error, Can\'t get AuthPlus token: Cannot send request: connection refused\n"); + "Authentication error, Can\'t get AuthPlus token: Cannot send request: connection refused\n") +} + +static BAD_SECTION_CONFIG: &'static str = + r#" + [uth] + server = "http://127.0.0.1:9000" + client_id = "client-id" + secret = "secret" + + [ota] + server = "http://127.0.0.1:8080" + vin = "V1234567890123456" + + [test] + looping = false + "#; + +#[test] +fn bad_section() { + assert_eq!(client_with_config(&[""], BAD_SECTION_CONFIG), + "Failed to parse config: invalid section: auth\n") +} + +#[test] +fn bad_toml() { + assert_eq!(client_with_config(&[""], "auth]"), + "Failed to parse config: invalid toml\n") +} + +#[test] +fn bad_path_dir() { + assert_eq!(client(&["--config=/"]), + "Failed to load config: Is a directory (os error 21)\n") } -- cgit v1.2.1 From 3b5fb687836449c010ed11a41c1c8122773eeb44 Mon Sep 17 00:00:00 2001 From: Stevan Andjelkovic Date: Wed, 16 Mar 2016 13:37:08 +0100 Subject: Don't use string errors. --- src/auth_plus.rs | 13 ++++++------ src/config.rs | 43 +++++++++++++++++--------------------- src/error.rs | 51 ++++++++++++++++++++++++++++++++++++--------- src/package_manager/dpkg.rs | 19 +++++++++-------- 4 files changed, 77 insertions(+), 49 deletions(-) diff --git a/src/auth_plus.rs b/src/auth_plus.rs index d4d863a..54a1ffa 100644 --- a/src/auth_plus.rs +++ b/src/auth_plus.rs @@ -102,11 +102,12 @@ mod tests { let mock = MockClient::new(config.client_id, config.secret); let auth_plus = Client::new(mock, AuthConfig::default()); - assert_eq!(Ok(AccessToken { - access_token: "token".to_string(), - token_type: "type".to_string(), - expires_in: 10, - scope: vec!["scope".to_string()] - }), auth_plus.authenticate()) + assert_eq!(auth_plus.authenticate().unwrap(), + AccessToken { + access_token: "token".to_string(), + token_type: "type".to_string(), + expires_in: 10, + scope: vec!["scope".to_string()] + }) } } diff --git a/src/config.rs b/src/config.rs index 2b03a87..d278d52 100644 --- a/src/config.rs +++ b/src/config.rs @@ -1,12 +1,13 @@ use hyper::Url; use rustc_serialize::Decodable; use std::fs::File; -use std::io::prelude::*; use std::io::ErrorKind; -use std::io; +use std::io::prelude::*; use toml; use error::Error; +use error::ConfigReason::{Parse, Io}; +use error::ParseReason::{InvalidToml, InvalidSection}; #[derive(Default, PartialEq, Eq, Debug, Clone)] @@ -64,20 +65,20 @@ impl Default for TestConfig { pub fn parse_config(s: &str) -> Result { - fn parse_sect(tbl: &toml::Table, sect: &str) -> Result { - tbl.get(sect) + fn parse_sect(tbl: &toml::Table, sect: String) -> Result { + tbl.get(§) .and_then(|c| toml::decode::(c.clone()) ) - .ok_or(Error::ConfigParseError(format!("invalid section: {}", sect))) + .ok_or(Error::Config(Parse(InvalidSection(sect)))) } let tbl: toml::Table = try!(toml::Parser::new(&s) .parse() - .ok_or(Error::ConfigParseError("invalid toml".to_string()))); + .ok_or(Error::Config(Parse(InvalidToml)))); - let auth_cfg: AuthConfig = try!(parse_sect(&tbl, "auth")); - let ota_cfg: OtaConfig = try!(parse_sect(&tbl, "ota")); - let test_cfg: TestConfig = try!(parse_sect(&tbl, "test")); + let auth_cfg: AuthConfig = try!(parse_sect(&tbl, "auth".to_string())); + let ota_cfg: OtaConfig = try!(parse_sect(&tbl, "ota".to_string())); + let test_cfg: TestConfig = try!(parse_sect(&tbl, "test".to_string())); return Ok(Config { auth: auth_cfg, @@ -88,18 +89,13 @@ pub fn parse_config(s: &str) -> Result { pub fn load_config(path: &str) -> Result { - impl From for Error { - fn from(err: io::Error) -> Error { - Error::ConfigIOError(format!("{}", err)) - } - } - match File::open(path) { Err(ref e) if e.kind() == ErrorKind::NotFound => Ok(Config::default()), - Err(e) => Err(From::from(e)), - Ok(mut f) => { + Err(e) => Err(Error::Config(Io(e))), + Ok(mut f) => { let mut s = String::new(); - try!(f.read_to_string(&mut s)); + try!(f.read_to_string(&mut s) + .map_err(|err| Error::Config(Io(err)))); return parse_config(&s); } } @@ -110,9 +106,8 @@ pub fn load_config(path: &str) -> Result { mod tests { use super::*; - use error::Error; - fn default_config_str() -> &'static str { + static DEFAULT_CONFIG_STRING: &'static str = r#" [auth] server = "http://127.0.0.1:9000" @@ -125,18 +120,18 @@ mod tests { [test] looping = false - "# - } + "#; #[test] fn parse_default_config() { - assert_eq!(parse_config(default_config_str()).unwrap(), + assert_eq!(parse_config(DEFAULT_CONFIG_STRING).unwrap(), Config::default()); } #[test] fn bad_path_yields_default_config() { - assert_eq!(load_config(""), Ok(Config::default())) + assert_eq!(load_config("").unwrap(), + Config::default()) } } diff --git a/src/error.rs b/src/error.rs index a546bea..d138812 100644 --- a/src/error.rs +++ b/src/error.rs @@ -1,24 +1,55 @@ use std::fmt::{Display, Formatter, Result as FmtResult}; +use std::io; -#[derive(PartialEq, Eq, Debug)] +#[derive(Debug)] pub enum Error { AuthError(String), ParseError(String), PackageError(String), ClientError(String), - ConfigParseError(String), - ConfigIOError(String), + Config(ConfigReason), +} + +#[derive(Debug)] +pub enum ConfigReason { + Parse(ParseReason), + Io(io::Error), +} + +#[derive(Debug)] +pub enum ParseReason { + InvalidToml, + InvalidSection(String), } impl Display for Error { fn fmt(&self, f: &mut Formatter) -> FmtResult { - let inner: String = match self { - &Error::AuthError(ref s) => format!("Authentication error, {}", s.clone()), - &Error::ParseError(ref s) => s.clone(), - &Error::PackageError(ref s) => s.clone(), - &Error::ClientError(ref s) => s.clone(), - &Error::ConfigParseError(ref s) => format!("Failed to parse config: {}", s.clone()), - &Error::ConfigIOError(ref s) => format!("Failed to load config: {}", s.clone()), + let inner: String = match *self { + Error::AuthError(ref s) => format!("Authentication error, {}", s.clone()), + Error::ParseError(ref s) => s.clone(), + Error::PackageError(ref s) => s.clone(), + Error::ClientError(ref s) => s.clone(), + Error::Config(ref e) => format!("Failed to {}", e.clone()), + }; + write!(f, "{}", inner) + } +} + +impl Display for ConfigReason { + fn fmt(&self, f: &mut Formatter) -> FmtResult { + let inner: String = match *self { + ConfigReason::Parse(ref e) => format!("parse config: {}", e.clone()), + ConfigReason::Io (ref e) => format!("load config: {}", e.clone()), + }; + write!(f, "{}", inner) + } +} + +impl Display for ParseReason { + fn fmt(&self, f: &mut Formatter) -> FmtResult { + let inner: String = match *self { + ParseReason::InvalidToml => "invalid toml".to_string(), + ParseReason::InvalidSection(ref s) => format!("invalid section: {}", s), }; write!(f, "{}", inner) } diff --git a/src/package_manager/dpkg.rs b/src/package_manager/dpkg.rs index 2d24b77..4156963 100644 --- a/src/package_manager/dpkg.rs +++ b/src/package_manager/dpkg.rs @@ -44,29 +44,30 @@ pub fn parse_package(line: &str) -> Result { mod tests { use super::*; - use error::Error; use package::Package; #[test] fn test_parses_normal_package() { - assert_eq!(parse_package("uuid-runtime 2.20.1-5.1ubuntu20.7"), - Ok(Package { + assert_eq!(parse_package("uuid-runtime 2.20.1-5.1ubuntu20.7").unwrap(), + Package { name: "uuid-runtime".to_string(), - version: "2.20.1-5.1ubuntu20.7".to_string() })); + version: "2.20.1-5.1ubuntu20.7".to_string() + }); } #[test] fn test_separates_name_and_version_correctly() { - assert_eq!(parse_package("vim 2.1 foobar"), - Ok(Package { + assert_eq!(parse_package("vim 2.1 foobar").unwrap(), + Package { name: "vim".to_string(), - version: "2.1 foobar".to_string() })); + version: "2.1 foobar".to_string() + }); } #[test] fn test_rejects_bogus_input() { - assert_eq!(parse_package("foobar"), - Err(Error::ParseError("Couldn't parse package: foobar".to_string()))); + assert_eq!(format!("{}", parse_package("foobar").unwrap_err()), + "Couldn't parse package: foobar".to_string()); } } -- cgit v1.2.1 From d9d4c72bf134d72abf80a6d05a093b6571c9a492 Mon Sep 17 00:00:00 2001 From: Stevan Andjelkovic Date: Thu, 17 Mar 2016 15:00:29 +0100 Subject: Remove OOPism from authenticate. --- src/auth_plus.rs | 69 +++++++++++++++++++++++---------------------------- src/main.rs | 5 ++-- src/read_interpret.rs | 5 +--- 3 files changed, 34 insertions(+), 45 deletions(-) diff --git a/src/auth_plus.rs b/src/auth_plus.rs index 54a1ffa..0cae7a4 100644 --- a/src/auth_plus.rs +++ b/src/auth_plus.rs @@ -1,13 +1,11 @@ -use config::AuthConfig; -use http_client::{HttpClient, HttpRequest}; - -use error::Error; - use hyper::header::{Authorization, Basic, ContentType}; use hyper::mime::{Mime, TopLevel, SubLevel, Attr, Value}; - use rustc_serialize::json; +use config::AuthConfig; +use error::Error; +use http_client::{HttpClient, HttpRequest}; + #[derive(Clone, RustcDecodable, Debug, PartialEq)] pub struct AccessToken { @@ -23,40 +21,32 @@ impl AccessToken { } } -pub struct Client { - http_client: C, - config: AuthConfig -} - -impl Client { - pub fn new(client: C, config: AuthConfig) -> Client { - Client { - http_client: client, - config: config - } - } +pub fn authenticate(http_client: C, config: AuthConfig) + -> Result { + + let req = HttpRequest::post(config.server.join("/token").unwrap()) + .with_body("grant_type=client_credentials") + .with_header(Authorization(Basic { + username: config.client_id.clone(), + password: Some(config.secret.clone()) + })) + .with_header(ContentType(Mime( + TopLevel::Application, + SubLevel::WwwFormUrlEncoded, + vec![(Attr::Charset, Value::Utf8)]))); + + http_client.send_request(&req) + .map_err(|e| Error::AuthError(format!("Can't get AuthPlus token: {}", e))) + .and_then(|body| { + return json::decode(&body) + .map_err(|e| Error::ParseError(format!("Cannot parse response: {}. Got: {}", e, &body))) + }) - pub fn authenticate(&self) -> Result { - let req = HttpRequest::post(self.config.server.join("/token").unwrap()) - .with_body("grant_type=client_credentials") - .with_header(Authorization(Basic { - username: self.config.client_id.clone(), - password: Some(self.config.secret.clone()) })) - .with_header(ContentType(Mime( - TopLevel::Application, - SubLevel::WwwFormUrlEncoded, - vec![(Attr::Charset, Value::Utf8)]))); - self.http_client.send_request(&req) - .map_err(|e| Error::AuthError(format!("Can't get AuthPlus token: {}", e))) - .and_then(|body| { - json::decode::(&body) - .map_err(|e| Error::ParseError(format!("Cannot parse response: {}. Got: {}", e, &body))) - }) - } } #[cfg(test)] mod tests { + use super::*; use http_client::{HttpRequest, HttpClient}; use error::Error; @@ -90,19 +80,22 @@ mod tests { #[test] fn test_authenticate() { + impl HttpClient for MockClient { fn send_request(&self, req: &HttpRequest) -> Result { self.assert_authenticated(req); self.assert_form_encoded(req); - Ok::("{\"access_token\": \"token\", \"token_type\": \"type\", \"expires_in\": 10, \"scope\": [\"scope\"]}".to_string()) + return Ok(r#"{"access_token": "token", + "token_type": "type", + "expires_in": 10, + "scope": ["scope"]}"#.to_string()) } } let config = AuthConfig::default(); let mock = MockClient::new(config.client_id, config.secret); - let auth_plus = Client::new(mock, AuthConfig::default()); - assert_eq!(auth_plus.authenticate().unwrap(), + assert_eq!(authenticate(mock, AuthConfig::default()).unwrap(), AccessToken { access_token: "token".to_string(), token_type: "type".to_string(), diff --git a/src/main.rs b/src/main.rs index 9bb2194..9fb80dc 100644 --- a/src/main.rs +++ b/src/main.rs @@ -10,8 +10,8 @@ use std::env; use libotaplus::{config, read_interpret}; use libotaplus::config::Config; use libotaplus::read_interpret::ReplEnv; +use libotaplus::auth_plus::authenticate; use libotaplus::ota_plus::{Client as OtaClient}; -use libotaplus::auth_plus::{Client as AuthClient}; use libotaplus::package_manager::{PackageManager, Dpkg}; fn main() { @@ -21,8 +21,7 @@ fn main() { let config = build_config(); let pkg_manager = Dpkg::new(); - let _ = AuthClient::new(hyper::Client::new(), config.auth.clone()) - .authenticate() + let _ = authenticate(hyper::Client::new(), config.auth.clone()) .map(|token| OtaClient::new(hyper::Client::new(), token, config.ota.clone())) .and_then(|client| pkg_manager.installed_packages() .and_then(|pkgs| client.post_packages(pkgs))) diff --git a/src/read_interpret.rs b/src/read_interpret.rs index 30910e9..cd0bcfd 100644 --- a/src/read_interpret.rs +++ b/src/read_interpret.rs @@ -56,10 +56,7 @@ pub fn read_interpret_loop(env: ReplEnv) let mut input = String::new(); let _ = io::stdin().read_line(&mut input); - match input.trim().parse() { - Ok(cmd) => interpret(&env, cmd), - Err(_) => error!("Parse error."), - }; + let _ = input.trim().parse().map(|cmd| interpret(&env, cmd)); } -- cgit v1.2.1 From 29731408aa4188822afb216f762e56656b0d9485 Mon Sep 17 00:00:00 2001 From: Stevan Andjelkovic Date: Thu, 17 Mar 2016 15:20:35 +0100 Subject: Move access token to its own file. --- src/access_token.rs | 8 ++++++++ src/auth_plus.rs | 17 +++-------------- src/lib.rs | 1 + src/ota_plus.rs | 28 +++++++++++++++++++--------- 4 files changed, 31 insertions(+), 23 deletions(-) create mode 100644 src/access_token.rs diff --git a/src/access_token.rs b/src/access_token.rs new file mode 100644 index 0000000..4ead6d9 --- /dev/null +++ b/src/access_token.rs @@ -0,0 +1,8 @@ + +#[derive(Clone, RustcDecodable, Debug, PartialEq)] +pub struct AccessToken { + pub access_token: String, + pub token_type: String, + pub expires_in: i32, + pub scope: Vec +} diff --git a/src/auth_plus.rs b/src/auth_plus.rs index 0cae7a4..95374ae 100644 --- a/src/auth_plus.rs +++ b/src/auth_plus.rs @@ -5,22 +5,9 @@ use rustc_serialize::json; use config::AuthConfig; use error::Error; use http_client::{HttpClient, HttpRequest}; +use access_token::AccessToken; -#[derive(Clone, RustcDecodable, Debug, PartialEq)] -pub struct AccessToken { - pub access_token: String, - token_type: String, - expires_in: i32, - scope: Vec -} - -impl AccessToken { - pub fn new(token: String, token_type: String, expires_in: i32, scope: Vec) -> AccessToken { - AccessToken { access_token: token, token_type: token_type, expires_in: expires_in, scope: scope } - } -} - pub fn authenticate(http_client: C, config: AuthConfig) -> Result { @@ -48,6 +35,7 @@ pub fn authenticate(http_client: C, config: AuthConfig) mod tests { use super::*; + use access_token::AccessToken; use http_client::{HttpRequest, HttpClient}; use error::Error; use config::AuthConfig; @@ -82,6 +70,7 @@ mod tests { fn test_authenticate() { impl HttpClient for MockClient { + fn send_request(&self, req: &HttpRequest) -> Result { self.assert_authenticated(req); self.assert_form_encoded(req); diff --git a/src/lib.rs b/src/lib.rs index 76f950d..445eb96 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -6,6 +6,7 @@ extern crate rustc_serialize; extern crate tempfile; extern crate toml; +pub mod access_token; pub mod config; pub mod read_interpret; pub mod ota_plus; diff --git a/src/ota_plus.rs b/src/ota_plus.rs index 1291b25..c7d81a6 100644 --- a/src/ota_plus.rs +++ b/src/ota_plus.rs @@ -5,7 +5,7 @@ use http_client::{HttpClient, HttpRequest}; use rustc_serialize::json; use std::result::Result; -use auth_plus::AccessToken; +use access_token::AccessToken; use config::OtaConfig; use error::Error; use package::Package; @@ -52,12 +52,13 @@ impl Client { #[cfg(test)] mod tests { + use super::*; use http_client::{HttpRequest, HttpClient}; use error::Error; use package::Package; use config::OtaConfig; - use auth_plus::AccessToken; + use access_token::AccessToken; use hyper::header::{Authorization, Bearer}; @@ -76,16 +77,25 @@ mod tests { } } - fn mk_token() -> AccessToken { - AccessToken::new("token".to_string(), "bar".to_string(), 20, vec![]) + fn test_token() -> AccessToken { + AccessToken { + access_token: "token".to_string(), + token_type: "bar".to_string(), + expires_in: 20, + scope: vec![] + } } - fn mk_package() -> Package { - Package { name: "hey".to_string(), version: "1.2.3".to_string() } + fn test_package() -> Package { + Package { + name: "hey".to_string(), + version: "1.2.3".to_string() + } } #[test] fn test_post_packages_sends_authentication() { + impl HttpClient for MockClient { fn send_request(&self, req: &HttpRequest) -> Result { self.assert_authenticated(req); @@ -93,9 +103,9 @@ mod tests { } } - let mock = MockClient::new(mk_token()); - let ota_plus = Client::new(mock, mk_token(), OtaConfig::default()); + let mock = MockClient::new(test_token()); + let ota_plus = Client::new(mock, test_token(), OtaConfig::default()); - let _ = ota_plus.post_packages(vec![mk_package()]); + let _ = ota_plus.post_packages(vec![test_package()]); } } -- cgit v1.2.1 From ea9ed25f071a97192d07cfd4154aed10ba5334d6 Mon Sep 17 00:00:00 2001 From: Stevan Andjelkovic Date: Thu, 17 Mar 2016 16:12:46 +0100 Subject: Remove OOPism from ota client. --- src/auth_plus.rs | 14 ++++++---- src/http_client.rs | 6 ++++ src/main.rs | 9 +++--- src/ota_plus.rs | 82 ++++++++++++++++++++++++------------------------------ 4 files changed, 55 insertions(+), 56 deletions(-) diff --git a/src/auth_plus.rs b/src/auth_plus.rs index 95374ae..eb92010 100644 --- a/src/auth_plus.rs +++ b/src/auth_plus.rs @@ -8,8 +8,9 @@ use http_client::{HttpClient, HttpRequest}; use access_token::AccessToken; -pub fn authenticate(http_client: C, config: AuthConfig) - -> Result { +pub fn authenticate(config: AuthConfig) -> Result { + + let http_client = C::new(); let req = HttpRequest::post(config.server.join("/token").unwrap()) .with_body("grant_type=client_credentials") @@ -71,6 +72,10 @@ mod tests { impl HttpClient for MockClient { + fn new() -> MockClient { + MockClient::new("".to_string(), "".to_string()) + } + fn send_request(&self, req: &HttpRequest) -> Result { self.assert_authenticated(req); self.assert_form_encoded(req); @@ -81,10 +86,7 @@ mod tests { } } - let config = AuthConfig::default(); - let mock = MockClient::new(config.client_id, config.secret); - - assert_eq!(authenticate(mock, AuthConfig::default()).unwrap(), + assert_eq!(authenticate::(AuthConfig::default()).unwrap(), AccessToken { access_token: "token".to_string(), token_type: "type".to_string(), diff --git a/src/http_client.rs b/src/http_client.rs index 08e5172..0cc7006 100644 --- a/src/http_client.rs +++ b/src/http_client.rs @@ -41,10 +41,16 @@ impl<'a> HttpRequest<'a> { } pub trait HttpClient { + fn new() -> Self; fn send_request(&self, req: &HttpRequest) -> Result; } impl HttpClient for hyper::Client { + + fn new() -> hyper::Client { + hyper::Client::new() + } + fn send_request(&self, req: &HttpRequest) -> Result { self.request(req.method.clone(), req.url.clone()) .headers(req.headers.clone()) diff --git a/src/main.rs b/src/main.rs index 9fb80dc..09599a7 100644 --- a/src/main.rs +++ b/src/main.rs @@ -11,7 +11,7 @@ use libotaplus::{config, read_interpret}; use libotaplus::config::Config; use libotaplus::read_interpret::ReplEnv; use libotaplus::auth_plus::authenticate; -use libotaplus::ota_plus::{Client as OtaClient}; +use libotaplus::ota_plus::post_packages; use libotaplus::package_manager::{PackageManager, Dpkg}; fn main() { @@ -21,10 +21,9 @@ fn main() { let config = build_config(); let pkg_manager = Dpkg::new(); - let _ = authenticate(hyper::Client::new(), config.auth.clone()) - .map(|token| OtaClient::new(hyper::Client::new(), token, config.ota.clone())) - .and_then(|client| pkg_manager.installed_packages() - .and_then(|pkgs| client.post_packages(pkgs))) + let _ = authenticate::(config.auth.clone()) + .and_then(|token| pkg_manager.installed_packages() + .and_then(|pkgs| post_packages::(token, config.ota.clone(), pkgs))) .map(|_| println!("Installed packages were posted successfully.")) .map_err(|err| println!("{}", err)); diff --git a/src/ota_plus.rs b/src/ota_plus.rs index c7d81a6..3740f15 100644 --- a/src/ota_plus.rs +++ b/src/ota_plus.rs @@ -1,53 +1,47 @@ use hyper::header::{Authorization, Bearer, ContentType}; use hyper::mime::{Mime, TopLevel, SubLevel, Attr, Value}; - -use http_client::{HttpClient, HttpRequest}; use rustc_serialize::json; use std::result::Result; use access_token::AccessToken; use config::OtaConfig; use error::Error; +use http_client::{HttpClient, HttpRequest}; use package::Package; -pub struct Client { - http_client: C, - access_token: String, - config: OtaConfig -} -impl Client { +#[allow(dead_code)] +pub fn check_for_update(token: AccessToken, + config: OtaConfig) -> Result { - pub fn new(client: C, token: AccessToken, config: OtaConfig) -> Client { - Client { - http_client: client, - access_token: token.access_token, - config: config - } - } + let http_client = C::new(); - #[allow(dead_code)] - pub fn check_for_update(&self) -> Result { - let req = HttpRequest::get(self.config.server.join("/updates").unwrap()) - .with_header(Authorization(Bearer { token: self.access_token.clone() })); - self.http_client.send_request(&req) - } + let req = HttpRequest::get(config.server.join("/updates").unwrap()) + .with_header(Authorization(Bearer { token: token.access_token })); + + http_client.send_request(&req) - pub fn post_packages(&self, pkgs: Vec) -> Result<(), Error> { - json::encode(&pkgs) - .map_err(|_| Error::ParseError(String::from("JSON encoding error"))) - .and_then(|json| { - let req = HttpRequest::post(self.config.server.join("/packages").unwrap()) - .with_header(Authorization(Bearer { token: self.access_token.clone() })) - .with_header(ContentType(Mime( - TopLevel::Application, - SubLevel::Json, - vec![(Attr::Charset, Value::Utf8)]))) - .with_body(&json); - - self.http_client.send_request(&req).map(|_| ()) - }) - } +} + +pub fn post_packages(token: AccessToken, + config: OtaConfig, + pkgs: Vec) -> Result<(), Error> { + + let http_client = C::new(); + + json::encode(&pkgs) + .map_err(|_| Error::ParseError(String::from("JSON encoding error"))) + .and_then(|json| { + let req = HttpRequest::post(config.server.join("/packages").unwrap()) + .with_header(Authorization(Bearer { token: token.access_token.clone() })) + .with_header(ContentType(Mime( + TopLevel::Application, + SubLevel::Json, + vec![(Attr::Charset, Value::Utf8)]))) + .with_body(&json); + + http_client.send_request(&req).map(|_| ()) + }) } #[cfg(test)] @@ -67,10 +61,6 @@ mod tests { } impl MockClient { - fn new(token: AccessToken) -> MockClient { - MockClient { access_token: token.access_token } - } - fn assert_authenticated(&self, req: &HttpRequest) { assert_eq!(Some(&Authorization(Bearer { token: self.access_token.clone() })), req.headers.get::>()) @@ -97,15 +87,17 @@ mod tests { fn test_post_packages_sends_authentication() { impl HttpClient for MockClient { + + fn new() -> MockClient { + MockClient { access_token: "".to_string() } + } + fn send_request(&self, req: &HttpRequest) -> Result { self.assert_authenticated(req); - Ok::("ok".to_string()) + return Ok("ok".to_string()) } } - let mock = MockClient::new(test_token()); - let ota_plus = Client::new(mock, test_token(), OtaConfig::default()); - - let _ = ota_plus.post_packages(vec![test_package()]); + let _ = post_packages::(test_token(), OtaConfig::default(), vec![test_package()]); } } -- cgit v1.2.1 From 8d25272e8649dcdeb0fbc9180218f11d413f9fae Mon Sep 17 00:00:00 2001 From: Stevan Andjelkovic Date: Thu, 17 Mar 2016 16:37:15 +0100 Subject: Remove request tests until we figure out a way which is compatible with the new http client trait. --- src/auth_plus.rs | 50 ++++++++++++++------------------------------------ src/ota_plus.rs | 37 ++++++++++++++----------------------- 2 files changed, 28 insertions(+), 59 deletions(-) diff --git a/src/auth_plus.rs b/src/auth_plus.rs index eb92010..f4cf95b 100644 --- a/src/auth_plus.rs +++ b/src/auth_plus.rs @@ -36,56 +36,32 @@ pub fn authenticate(config: AuthConfig) -> Result MockClient { - MockClient { username: username, secret: secret } + fn new() -> MockClient { + MockClient {} } - fn assert_authenticated(&self, req: &HttpRequest) { - assert_eq!(req.body, Some("grant_type=client_credentials")); - assert_eq!(Some(&Authorization(Basic { username: self.username.clone(), password: Some(self.secret.clone()) })), - req.headers.get::>()) + fn send_request(&self, _: &HttpRequest) -> Result { + return Ok(r#"{"access_token": "token", + "token_type": "type", + "expires_in": 10, + "scope": ["scope"]}"#.to_string()) } - fn assert_form_encoded(&self, req: &HttpRequest) { - assert_eq!(Some(&ContentType(Mime(TopLevel::Application, SubLevel::WwwFormUrlEncoded, - vec![(Attr::Charset, Value::Utf8)]))), - req.headers.get::()) - } } #[test] fn test_authenticate() { - impl HttpClient for MockClient { - - fn new() -> MockClient { - MockClient::new("".to_string(), "".to_string()) - } - - fn send_request(&self, req: &HttpRequest) -> Result { - self.assert_authenticated(req); - self.assert_form_encoded(req); - return Ok(r#"{"access_token": "token", - "token_type": "type", - "expires_in": 10, - "scope": ["scope"]}"#.to_string()) - } - } - assert_eq!(authenticate::(AuthConfig::default()).unwrap(), AccessToken { access_token: "token".to_string(), @@ -93,5 +69,7 @@ mod tests { expires_in: 10, scope: vec!["scope".to_string()] }) + } + } diff --git a/src/ota_plus.rs b/src/ota_plus.rs index 3740f15..81bd48c 100644 --- a/src/ota_plus.rs +++ b/src/ota_plus.rs @@ -54,18 +54,6 @@ mod tests { use config::OtaConfig; use access_token::AccessToken; - use hyper::header::{Authorization, Bearer}; - - struct MockClient { - access_token: String - } - - impl MockClient { - fn assert_authenticated(&self, req: &HttpRequest) { - assert_eq!(Some(&Authorization(Bearer { token: self.access_token.clone() })), - req.headers.get::>()) - } - } fn test_token() -> AccessToken { AccessToken { @@ -83,21 +71,24 @@ mod tests { } } - #[test] - fn test_post_packages_sends_authentication() { + struct MockClient {} - impl HttpClient for MockClient { + impl HttpClient for MockClient { - fn new() -> MockClient { - MockClient { access_token: "".to_string() } - } + fn new() -> MockClient { + MockClient {} + } - fn send_request(&self, req: &HttpRequest) -> Result { - self.assert_authenticated(req); - return Ok("ok".to_string()) - } + fn send_request(&self, _: &HttpRequest) -> Result { + return Ok("ok".to_string()) } - let _ = post_packages::(test_token(), OtaConfig::default(), vec![test_package()]); + } + + #[test] + fn test_post_packages_sends_authentication() { + assert_eq!( + post_packages::(test_token(), OtaConfig::default(), vec![test_package()]) + .unwrap(), ()) } } -- cgit v1.2.1 From 1c1e8e5dd38267a04464e94db6a40bd001d3cdf2 Mon Sep 17 00:00:00 2001 From: Txus Date: Wed, 16 Mar 2016 16:26:17 +0100 Subject: Ask for updates to the server and download the packages right after --- pkg/ota.toml.template | 3 +++ src/auth_plus.rs | 11 +++++--- src/config.rs | 33 +++++++++++++++++++++--- src/error.rs | 31 +++++++++++++++++++++- src/http_client.rs | 71 +++++++++++++++++++++++++++++++++++++++++++++++---- src/lib.rs | 1 + src/main.rs | 26 ++++++++++++++++--- src/ota_plus.rs | 62 ++++++++++++++++++++++++++++++++++---------- src/package.rs | 2 +- src/update_request.rs | 1 + 10 files changed, 210 insertions(+), 31 deletions(-) create mode 100644 src/update_request.rs diff --git a/pkg/ota.toml.template b/pkg/ota.toml.template index a48f2b4..c85e3e5 100644 --- a/pkg/ota.toml.template +++ b/pkg/ota.toml.template @@ -6,3 +6,6 @@ secret = "${OTA_AUTH_SECRET}" [ota] server = "${OTA_SERVER_URL}" vin = "${OTA_CLIENT_VIN}" + +[packages] +dir = "${PACKAGES_DIR}" \ No newline at end of file diff --git a/src/auth_plus.rs b/src/auth_plus.rs index f4cf95b..12fd17c 100644 --- a/src/auth_plus.rs +++ b/src/auth_plus.rs @@ -42,12 +42,14 @@ mod tests { use error::Error; use http_client::{HttpRequest, HttpClient}; - struct MockClient {} + use std::io::Write; + + struct MockClient; impl HttpClient for MockClient { fn new() -> MockClient { - MockClient {} + MockClient } fn send_request(&self, _: &HttpRequest) -> Result { @@ -57,11 +59,13 @@ mod tests { "scope": ["scope"]}"#.to_string()) } + fn send_request_to(&self, _: &HttpRequest, _: W) -> Result<(), Error> { + return Ok(()) + } } #[test] fn test_authenticate() { - assert_eq!(authenticate::(AuthConfig::default()).unwrap(), AccessToken { access_token: "token".to_string(), @@ -69,7 +73,6 @@ mod tests { expires_in: 10, scope: vec!["scope".to_string()] }) - } } diff --git a/src/config.rs b/src/config.rs index d278d52..873e97a 100644 --- a/src/config.rs +++ b/src/config.rs @@ -5,8 +5,10 @@ use std::io::ErrorKind; use std::io::prelude::*; use toml; +use std::path::PathBuf; + use error::Error; -use error::ConfigReason::{Parse, Io}; +use error::ConfigReason::{Parse, Io, PathDoesNotExist}; use error::ParseReason::{InvalidToml, InvalidSection}; @@ -14,6 +16,7 @@ use error::ParseReason::{InvalidToml, InvalidSection}; pub struct Config { pub auth: AuthConfig, pub ota: OtaConfig, + pub packages: PackagesConfig, pub test: TestConfig, } @@ -30,6 +33,11 @@ pub struct OtaConfig { pub vin: String } +#[derive(RustcDecodable, PartialEq, Eq, Debug, Clone)] +pub struct PackagesConfig { + pub dir: String, +} + #[derive(RustcDecodable, PartialEq, Eq, Debug, Clone)] pub struct TestConfig { pub looping: bool, @@ -54,6 +62,14 @@ impl Default for OtaConfig { } } +impl Default for PackagesConfig { + fn default() -> PackagesConfig { + PackagesConfig { + dir: "/tmp".to_string(), + } + } +} + impl Default for TestConfig { fn default() -> TestConfig { TestConfig { @@ -76,13 +92,19 @@ pub fn parse_config(s: &str) -> Result { .parse() .ok_or(Error::Config(Parse(InvalidToml)))); - let auth_cfg: AuthConfig = try!(parse_sect(&tbl, "auth".to_string())); - let ota_cfg: OtaConfig = try!(parse_sect(&tbl, "ota".to_string())); - let test_cfg: TestConfig = try!(parse_sect(&tbl, "test".to_string())); + let auth_cfg: AuthConfig = try!(parse_sect(&tbl, "auth".to_string())); + let ota_cfg: OtaConfig = try!(parse_sect(&tbl, "ota".to_string())); + let packages_cfg: PackagesConfig = try!(parse_sect(&tbl, "packages".to_string())); + let test_cfg: TestConfig = try!(parse_sect(&tbl, "test".to_string())); + + if ! PathBuf::from(packages_cfg.clone().dir).is_dir() { + return Err(Error::Config(PathDoesNotExist(packages_cfg.dir))) + } return Ok(Config { auth: auth_cfg, ota: ota_cfg, + packages: packages_cfg, test: test_cfg, }) } @@ -118,6 +140,9 @@ mod tests { server = "http://127.0.0.1:8080" vin = "V1234567890123456" + [packages] + dir = "/tmp" + [test] looping = false "#; diff --git a/src/error.rs b/src/error.rs index d138812..e78469a 100644 --- a/src/error.rs +++ b/src/error.rs @@ -7,12 +7,28 @@ pub enum Error { ParseError(String), PackageError(String), ClientError(String), + ClientErrorWithReason(ClientReason), Config(ConfigReason), + Io(io::Error) +} + +use std::convert::From; + +impl From for Error { + fn from(e: io::Error) -> Error { + Error::Io(e) + } +} + +#[derive(Debug)] +pub enum ClientReason { + Io(io::Error) } #[derive(Debug)] pub enum ConfigReason { Parse(ParseReason), + PathDoesNotExist(String), Io(io::Error), } @@ -29,7 +45,18 @@ impl Display for Error { Error::ParseError(ref s) => s.clone(), Error::PackageError(ref s) => s.clone(), Error::ClientError(ref s) => s.clone(), + Error::ClientErrorWithReason(ref e) => format!("OtaClient failed to {}", e.clone()), Error::Config(ref e) => format!("Failed to {}", e.clone()), + Error::Io(ref e) => format!("IO Error{:?}", e.clone()), + }; + write!(f, "{}", inner) + } +} + +impl Display for ClientReason { + fn fmt(&self, f: &mut Formatter) -> FmtResult { + let inner: String = match *self { + ClientReason::Io(ref e) => format!("perform IO: {:?}", e.clone()) }; write!(f, "{}", inner) } @@ -39,12 +66,14 @@ impl Display for ConfigReason { fn fmt(&self, f: &mut Formatter) -> FmtResult { let inner: String = match *self { ConfigReason::Parse(ref e) => format!("parse config: {}", e.clone()), - ConfigReason::Io (ref e) => format!("load config: {}", e.clone()), + ConfigReason::PathDoesNotExist(ref e) => format!("validate existence of path in config: {}", e.clone()), + ConfigReason::Io (ref e) => format!("load config: {}", e.clone()) }; write!(f, "{}", inner) } } + impl Display for ParseReason { fn fmt(&self, f: &mut Formatter) -> FmtResult { let inner: String = match *self { diff --git a/src/http_client.rs b/src/http_client.rs index 0cc7006..93a6088 100644 --- a/src/http_client.rs +++ b/src/http_client.rs @@ -4,7 +4,7 @@ use hyper::header::{Headers, Header, HeaderFormat}; use hyper::Url; use error::Error; -use std::io::Read; +use std::io::{Read, Write, BufReader, BufWriter}; #[derive(Clone, Debug)] @@ -20,7 +20,6 @@ impl<'a> HttpRequest<'a> { HttpRequest { url: url, method: method, headers: Headers::new(), body: None } } - #[allow(dead_code)] pub fn get(url: Url) -> HttpRequest<'a> { HttpRequest::new(url, Method::Get) } @@ -43,6 +42,7 @@ impl<'a> HttpRequest<'a> { pub trait HttpClient { fn new() -> Self; fn send_request(&self, req: &HttpRequest) -> Result; + fn send_request_to(&self, req: &HttpRequest, to: W) -> Result<(), Error>; } impl HttpClient for hyper::Client { @@ -61,9 +61,70 @@ impl HttpClient for hyper::Client { }) .and_then(|mut resp| { let mut rbody = String::new(); - resp.read_to_string(&mut rbody) - .map_err(|e| Error::ParseError(format!("Cannot read response: {}", e))) - .map(|_| rbody) + let status = resp.status; + if status.is_server_error() || status.is_client_error() { + Err(Error::ClientError(format!("Request errored with status {}", status))) + } else { + resp.read_to_string(&mut rbody) + .map_err(|e| Error::ParseError(format!("Cannot read response: {}", e))) + .map(|_| rbody) + } }) } + + fn send_request_to(&self, req: &HttpRequest, to: W) -> Result<(), Error> { + self.request(req.method.clone(), req.url.clone()) + .headers(req.headers.clone()) + .body(if let Some(body) = req.body { body } else { "" }) + .send() + .map_err(|e| { + Error::ClientError(format!("Cannot send request: {}", e)) + }) + .and_then(|resp| { + let status = resp.status; + if status.is_server_error() || status.is_client_error() { + Err(Error::ClientError(format!("Request errored with status {}", status))) + } else { + tee(resp, to) + .map_err(|e| Error::ParseError(format!("Cannot read response: {}", e))) + .map(|_| ()) + } + }) + } +} + +pub fn tee(from: R, to: W) -> Result<(), Error> { + let mb = 1024 * 1024; + let rbuf = BufReader::with_capacity(5 * mb, from); + let mut wbuf = BufWriter::with_capacity(5 * mb, to); + for b in rbuf.bytes() { + try!(wbuf.write(&[try!(b)])); + } + Ok(()) +} + +#[cfg(test)] +mod tests { + use super::*; + use std::fs::File; + use std::io::{Read, repeat}; + + #[test] + fn test_tee() { + let values = repeat(b'a').take(9000); + let sink = File::create("/tmp/otaplus_tee_test").unwrap(); + + assert!(tee(values, sink).is_ok()); + + let mut values2 = repeat(b'a').take(9000); + let mut expected = Vec::new(); + let _ = values2.read_to_end(&mut expected); + + let mut f = File::open("/tmp/otaplus_tee_test").unwrap(); + let mut result = Vec::new(); + let _ = f.read_to_end(&mut result); + + assert_eq!(result, expected); + } + } diff --git a/src/lib.rs b/src/lib.rs index 445eb96..9ee5cea 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -14,4 +14,5 @@ pub mod auth_plus; pub mod package; pub mod package_manager; pub mod error; +pub mod update_request; mod http_client; diff --git a/src/main.rs b/src/main.rs index 09599a7..7968d4a 100644 --- a/src/main.rs +++ b/src/main.rs @@ -11,8 +11,9 @@ use libotaplus::{config, read_interpret}; use libotaplus::config::Config; use libotaplus::read_interpret::ReplEnv; use libotaplus::auth_plus::authenticate; -use libotaplus::ota_plus::post_packages; +use libotaplus::ota_plus::{post_packages, get_package_updates, download_package_update}; use libotaplus::package_manager::{PackageManager, Dpkg}; +use libotaplus::error::Error; fn main() { @@ -22,8 +23,27 @@ fn main() { let pkg_manager = Dpkg::new(); let _ = authenticate::(config.auth.clone()) - .and_then(|token| pkg_manager.installed_packages() - .and_then(|pkgs| post_packages::(token, config.ota.clone(), pkgs))) + .and_then(|token| { + println!("Fetching installed packages on the system."); + pkg_manager.installed_packages() + .and_then(|pkgs| { + println!("Posting {} installed packages to the server.", pkgs.iter().len()); + post_packages::(token.clone(), config.ota.clone(), pkgs) + }) + .and_then(|_| { + println!("Fetching possible new package updates."); + get_package_updates::(token.clone(), config.ota.clone()) + }) + .and_then(|updates| { + let len = updates.iter().len(); + println!("Got {} new updates. Downloading...", len); + updates.iter().map(|u| { + download_package_update::(token.clone(), config.ota.clone(), config.packages.clone(), u) + .map_err(|e| Error::ClientError(format!("Couldn't download update {:?}: {}", u, e))) + }).collect::, _>>() + }) + }) + .map(|paths| println!("All good. Downloaded {:?}. See you again soon!", paths)) .map(|_| println!("Installed packages were posted successfully.")) .map_err(|err| println!("{}", err)); diff --git a/src/ota_plus.rs b/src/ota_plus.rs index 81bd48c..e2482e4 100644 --- a/src/ota_plus.rs +++ b/src/ota_plus.rs @@ -1,26 +1,51 @@ use hyper::header::{Authorization, Bearer, ContentType}; use hyper::mime::{Mime, TopLevel, SubLevel, Attr, Value}; +use hyper::Url; use rustc_serialize::json; use std::result::Result; use access_token::AccessToken; -use config::OtaConfig; -use error::Error; +use config::{OtaConfig, PackagesConfig}; +use error::{Error, ClientReason}; use http_client::{HttpClient, HttpRequest}; use package::Package; +use update_request::UpdateRequestId; +use std::fs::File; +use std::path::PathBuf; -#[allow(dead_code)] -pub fn check_for_update(token: AccessToken, - config: OtaConfig) -> Result { +fn vehicle_endpoint(config: OtaConfig, s: &str) -> Url { + config.server.join(&format!("/api/v1/vehicles/{}{}", config.vin, s)).unwrap() +} +pub fn download_package_update(token: AccessToken, + config: OtaConfig, + pkgs_config: PackagesConfig, + id: &UpdateRequestId) -> Result { let http_client = C::new(); - let req = HttpRequest::get(config.server.join("/updates").unwrap()) - .with_header(Authorization(Bearer { token: token.access_token })); + let req = HttpRequest::get(vehicle_endpoint(config, &format!("/updates/{}", id))) + .with_header(Authorization(Bearer { token: token.access_token.clone() })); - http_client.send_request(&req) + let p = format!("{}/{}.deb", pkgs_config.dir, id); + let path = PathBuf::from(p); + let file = try!(File::create(path.as_path()).map_err(|e| Error::ClientErrorWithReason(ClientReason::Io(e)))); + + http_client.send_request_to(&req, file).map(move |_| path) +} +pub fn get_package_updates(token: AccessToken, + config: OtaConfig) -> Result, Error> { + let http_client = C::new(); + + let req = HttpRequest::get(vehicle_endpoint(config, "/updates")) + .with_header(Authorization(Bearer { token: token.access_token.clone() })); + http_client.send_request(&req) + .map_err(|e| Error::ClientError(format!("Can't consult package updates: {}", e))) + .and_then(|body| { + json::decode::>(&body) + .map_err(|e| Error::ParseError(format!("Cannot parse response: {}. Got: {}", e, &body))) + }) } pub fn post_packages(token: AccessToken, @@ -28,11 +53,10 @@ pub fn post_packages(token: AccessToken, pkgs: Vec) -> Result<(), Error> { let http_client = C::new(); - json::encode(&pkgs) .map_err(|_| Error::ParseError(String::from("JSON encoding error"))) .and_then(|json| { - let req = HttpRequest::post(config.server.join("/packages").unwrap()) + let req = HttpRequest::post(vehicle_endpoint(config, "/packages")) .with_header(Authorization(Bearer { token: token.access_token.clone() })) .with_header(ContentType(Mime( TopLevel::Application, @@ -54,6 +78,7 @@ mod tests { use config::OtaConfig; use access_token::AccessToken; + use std::io::Write; fn test_token() -> AccessToken { AccessToken { @@ -71,16 +96,20 @@ mod tests { } } - struct MockClient {} + struct MockClient; impl HttpClient for MockClient { fn new() -> MockClient { - MockClient {} + MockClient } fn send_request(&self, _: &HttpRequest) -> Result { - return Ok("ok".to_string()) + return Ok("[\"pkgid\"]".to_string()) + } + + fn send_request_to(&self, _: &HttpRequest, _: W) -> Result<(), Error> { + return Ok(()) } } @@ -91,4 +120,11 @@ mod tests { post_packages::(test_token(), OtaConfig::default(), vec![test_package()]) .unwrap(), ()) } + + + #[test] + fn test_get_package_updates() { + assert_eq!(get_package_updates::(test_token(), OtaConfig::default()).unwrap(), + vec!["pkgid".to_string()]) + } } diff --git a/src/package.rs b/src/package.rs index 17eea71..65fffb7 100644 --- a/src/package.rs +++ b/src/package.rs @@ -2,7 +2,7 @@ use std::fmt::{Display, Formatter, Result as FmtResult}; pub type Version = String; -#[derive(Debug, PartialEq, Eq, RustcEncodable)] +#[derive(Debug, PartialEq, Eq, RustcEncodable, RustcDecodable)] pub struct Package { pub name: String, pub version: Version diff --git a/src/update_request.rs b/src/update_request.rs new file mode 100644 index 0000000..56b82aa --- /dev/null +++ b/src/update_request.rs @@ -0,0 +1 @@ +pub type UpdateRequestId = String; -- cgit v1.2.1 From 48b81eafbe748108bcf5ea004e23e5c541b5fe44 Mon Sep 17 00:00:00 2001 From: Stevan Andjelkovic Date: Fri, 18 Mar 2016 14:35:51 +0100 Subject: Add missing packages section to ota.toml. --- ota.toml | 3 +++ src/config.rs | 6 ++++++ 2 files changed, 9 insertions(+) diff --git a/ota.toml b/ota.toml index 5a4579f..a3d0e09 100644 --- a/ota.toml +++ b/ota.toml @@ -7,5 +7,8 @@ secret = "secret" server = "http://127.0.0.1:8080" vin = "V1234567890123456" +[packages] +dir = "/tmp" + [test] looping = false diff --git a/src/config.rs b/src/config.rs index 873e97a..4d25e11 100644 --- a/src/config.rs +++ b/src/config.rs @@ -153,6 +153,12 @@ mod tests { Config::default()); } + #[test] + fn load_default_config() { + assert_eq!(load_config("ota.toml").unwrap(), + Config::default()); + } + #[test] fn bad_path_yields_default_config() { assert_eq!(load_config("").unwrap(), -- cgit v1.2.1 From 84fab6cea452e8dfc00ee0405605350323099b73 Mon Sep 17 00:00:00 2001 From: Stevan Andjelkovic Date: Fri, 18 Mar 2016 15:23:12 +0100 Subject: Add tests for when auth fails. --- src/auth_plus.rs | 46 +++++++++++++++++++++++++++++++++++------- src/bad_http_client.rs | 23 +++++++++++++++++++++ src/http_client.rs | 2 +- src/lib.rs | 1 + tests/ota_plus_client_tests.rs | 3 ++- 5 files changed, 66 insertions(+), 9 deletions(-) create mode 100644 src/bad_http_client.rs diff --git a/src/auth_plus.rs b/src/auth_plus.rs index 12fd17c..1b79cc2 100644 --- a/src/auth_plus.rs +++ b/src/auth_plus.rs @@ -24,10 +24,11 @@ pub fn authenticate(config: AuthConfig) -> Result Result { - return Ok(r#"{"access_token": "token", - "token_type": "type", - "expires_in": 10, - "scope": ["scope"]}"#.to_string()) + Ok(r#"{"access_token": "token", + "token_type": "type", + "expires_in": 10, + "scope": ["scope"]}"#.to_string()) } fn send_request_to(&self, _: &HttpRequest, _: W) -> Result<(), Error> { - return Ok(()) + Ok(()) } } @@ -75,4 +77,34 @@ mod tests { }) } + #[test] + fn test_authenticate_bad_client() { + assert_eq!(format!("{}", authenticate::(AuthConfig::default()).unwrap_err()), + "Authentication error, didn't receive access token: bad client.") + } + + #[test] + fn test_authenticate_bad_json_client() { + + struct BadJsonClient; + + impl HttpClient for BadJsonClient { + + fn new() -> BadJsonClient { + BadJsonClient + } + + fn send_request(&self, _: &HttpRequest) -> Result { + Ok(r#"{"apa": 1}"#.to_string()) + } + + fn send_request_to(&self, _: &HttpRequest, _: W) -> Result<(), Error> { + Ok(()) + } + } + + assert_eq!(format!("{}", authenticate::(AuthConfig::default()).unwrap_err()), + r#"couldn't parse access token: MissingFieldError("access_token"). Got: {"apa": 1}."#) + } + } diff --git a/src/bad_http_client.rs b/src/bad_http_client.rs new file mode 100644 index 0000000..585cf5f --- /dev/null +++ b/src/bad_http_client.rs @@ -0,0 +1,23 @@ +use std::io::Write; + +use error::Error; +use http_client::{HttpClient, HttpRequest}; + + +pub struct BadHttpClient; + +impl HttpClient for BadHttpClient { + + fn new() -> BadHttpClient { + BadHttpClient + } + + fn send_request(&self, _: &HttpRequest) -> Result { + Err(Error::ClientError("bad client.".to_string())) + } + + fn send_request_to(&self, _: &HttpRequest, _: W) -> Result<(), Error> { + Err(Error::ClientError("bad client.".to_string())) + } + +} diff --git a/src/http_client.rs b/src/http_client.rs index 93a6088..253d064 100644 --- a/src/http_client.rs +++ b/src/http_client.rs @@ -57,7 +57,7 @@ impl HttpClient for hyper::Client { .body(if let Some(body) = req.body { body } else { "" }) .send() .map_err(|e| { - Error::ClientError(format!("Cannot send request: {}", e)) + Error::ClientError(format!("{}", e)) }) .and_then(|mut resp| { let mut rbody = String::new(); diff --git a/src/lib.rs b/src/lib.rs index 9ee5cea..da13930 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -7,6 +7,7 @@ extern crate tempfile; extern crate toml; pub mod access_token; +pub mod bad_http_client; pub mod config; pub mod read_interpret; pub mod ota_plus; diff --git a/tests/ota_plus_client_tests.rs b/tests/ota_plus_client_tests.rs index 615a913..9ebb478 100644 --- a/tests/ota_plus_client_tests.rs +++ b/tests/ota_plus_client_tests.rs @@ -5,6 +5,7 @@ use std::process::Command; use std::vec::Vec; use tempfile::NamedTempFile; + fn client(args: &[&str]) -> String { let output = Command::new("target/debug/ota_plus_client") .args(args) @@ -63,7 +64,7 @@ fn bad_ota_server_url() { #[test] fn no_auth_server_to_connect_to() { assert_eq!(client(&[""]), - "Authentication error, Can\'t get AuthPlus token: Cannot send request: connection refused\n") + "Authentication error, didn't receive access token: connection refused\n") } static BAD_SECTION_CONFIG: &'static str = -- cgit v1.2.1 From bd2dd5541e9a949b86b97ed559026567e96a7331 Mon Sep 17 00:00:00 2001 From: Stevan Andjelkovic Date: Mon, 21 Mar 2016 14:15:02 +0100 Subject: Remove clone constraint on package manager. --- src/main.rs | 3 ++- src/package_manager/dpkg.rs | 1 - src/package_manager/mod.rs | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main.rs b/src/main.rs index 7968d4a..c22e160 100644 --- a/src/main.rs +++ b/src/main.rs @@ -15,6 +15,7 @@ use libotaplus::ota_plus::{post_packages, get_package_updates, download_package_ use libotaplus::package_manager::{PackageManager, Dpkg}; use libotaplus::error::Error; + fn main() { env_logger::init().unwrap(); @@ -48,7 +49,7 @@ fn main() { .map_err(|err| println!("{}", err)); if config.test.looping { - read_interpret::read_interpret_loop(ReplEnv::new(pkg_manager.clone())); + read_interpret::read_interpret_loop(ReplEnv::new(pkg_manager)); } } diff --git a/src/package_manager/dpkg.rs b/src/package_manager/dpkg.rs index 4156963..96f18b0 100644 --- a/src/package_manager/dpkg.rs +++ b/src/package_manager/dpkg.rs @@ -5,7 +5,6 @@ use error::Error; use std::process::Command; #[allow(dead_code)] -#[derive(Clone)] pub struct Dpkg { a: u16 } // remove dummy field once braced_empty_structs feature is in stable impl Dpkg { diff --git a/src/package_manager/mod.rs b/src/package_manager/mod.rs index ba8622c..3e4ecd8 100644 --- a/src/package_manager/mod.rs +++ b/src/package_manager/mod.rs @@ -1,7 +1,7 @@ use package::Package; use error::Error; -pub trait PackageManager: Clone { +pub trait PackageManager { fn installed_packages(&self) -> Result, Error>; } -- cgit v1.2.1 From a9bd10c643c43ffaf12163a7ae3c107817fd19c8 Mon Sep 17 00:00:00 2001 From: Stevan Andjelkovic Date: Mon, 21 Mar 2016 15:18:13 +0100 Subject: Add flag for fake package manager. --- ota.toml | 1 + src/config.rs | 5 ++++- src/main.rs | 6 ++++++ tests/ota_plus_client_tests.rs | 18 ++---------------- 4 files changed, 13 insertions(+), 17 deletions(-) diff --git a/ota.toml b/ota.toml index a3d0e09..581e4ed 100644 --- a/ota.toml +++ b/ota.toml @@ -12,3 +12,4 @@ dir = "/tmp" [test] looping = false +fake_package_manager = false diff --git a/src/config.rs b/src/config.rs index 4d25e11..dbebc53 100644 --- a/src/config.rs +++ b/src/config.rs @@ -41,6 +41,7 @@ pub struct PackagesConfig { #[derive(RustcDecodable, PartialEq, Eq, Debug, Clone)] pub struct TestConfig { pub looping: bool, + pub fake_package_manager: bool, } impl Default for AuthConfig { @@ -74,6 +75,7 @@ impl Default for TestConfig { fn default() -> TestConfig { TestConfig { looping: false, + fake_package_manager: false, } } } @@ -129,7 +131,7 @@ mod tests { use super::*; - static DEFAULT_CONFIG_STRING: &'static str = + const DEFAULT_CONFIG_STRING: &'static str = r#" [auth] server = "http://127.0.0.1:9000" @@ -145,6 +147,7 @@ mod tests { [test] looping = false + fake_package_manager = false "#; #[test] diff --git a/src/main.rs b/src/main.rs index c22e160..5d9b3d1 100644 --- a/src/main.rs +++ b/src/main.rs @@ -76,6 +76,8 @@ fn build_config() -> Config { "change ota vin", "VIN"); opts.optflag("", "test-looping", "enable read-interpret test loop"); + opts.optflag("", "test-fake-pm", + "enable fake package manager for testing"); let matches = opts.parse(&args[1..]) .unwrap_or_else(|err| panic!(err.to_string())); @@ -125,6 +127,10 @@ fn build_config() -> Config { config.test.looping = true; } + if matches.opt_present("test-fake-pm") { + config.test.fake_package_manager = true; + } + return config } diff --git a/tests/ota_plus_client_tests.rs b/tests/ota_plus_client_tests.rs index 9ebb478..bd1f664 100644 --- a/tests/ota_plus_client_tests.rs +++ b/tests/ota_plus_client_tests.rs @@ -44,6 +44,7 @@ Options: change ota server URL --ota-vin VIN change ota vin --test-looping enable read-interpret test loop + --test-fake-pm enable fake package manager for testing "#); @@ -67,24 +68,9 @@ fn no_auth_server_to_connect_to() { "Authentication error, didn't receive access token: connection refused\n") } -static BAD_SECTION_CONFIG: &'static str = - r#" - [uth] - server = "http://127.0.0.1:9000" - client_id = "client-id" - secret = "secret" - - [ota] - server = "http://127.0.0.1:8080" - vin = "V1234567890123456" - - [test] - looping = false - "#; - #[test] fn bad_section() { - assert_eq!(client_with_config(&[""], BAD_SECTION_CONFIG), + assert_eq!(client_with_config(&[""], "[uth]"), "Failed to parse config: invalid section: auth\n") } -- cgit v1.2.1 From 25e0a54d9ff8fd48cded03ea204dc0b0b566d0ed Mon Sep 17 00:00:00 2001 From: Stevan Andjelkovic Date: Mon, 21 Mar 2016 16:02:22 +0100 Subject: Pass around references rather than cloning things. --- src/access_token.rs | 2 +- src/auth_plus.rs | 8 ++++---- src/config.rs | 30 +++++++++++++++--------------- src/main.rs | 8 ++++---- src/ota_plus.rs | 26 +++++++++++++------------- 5 files changed, 37 insertions(+), 37 deletions(-) diff --git a/src/access_token.rs b/src/access_token.rs index 4ead6d9..afd359b 100644 --- a/src/access_token.rs +++ b/src/access_token.rs @@ -1,5 +1,5 @@ -#[derive(Clone, RustcDecodable, Debug, PartialEq)] +#[derive(RustcDecodable, Debug, PartialEq)] pub struct AccessToken { pub access_token: String, pub token_type: String, diff --git a/src/auth_plus.rs b/src/auth_plus.rs index 1b79cc2..0e02ff6 100644 --- a/src/auth_plus.rs +++ b/src/auth_plus.rs @@ -8,7 +8,7 @@ use http_client::{HttpClient, HttpRequest}; use access_token::AccessToken; -pub fn authenticate(config: AuthConfig) -> Result { +pub fn authenticate(config: &AuthConfig) -> Result { let http_client = C::new(); @@ -68,7 +68,7 @@ mod tests { #[test] fn test_authenticate() { - assert_eq!(authenticate::(AuthConfig::default()).unwrap(), + assert_eq!(authenticate::(&AuthConfig::default()).unwrap(), AccessToken { access_token: "token".to_string(), token_type: "type".to_string(), @@ -79,7 +79,7 @@ mod tests { #[test] fn test_authenticate_bad_client() { - assert_eq!(format!("{}", authenticate::(AuthConfig::default()).unwrap_err()), + assert_eq!(format!("{}", authenticate::(&AuthConfig::default()).unwrap_err()), "Authentication error, didn't receive access token: bad client.") } @@ -103,7 +103,7 @@ mod tests { } } - assert_eq!(format!("{}", authenticate::(AuthConfig::default()).unwrap_err()), + assert_eq!(format!("{}", authenticate::(&AuthConfig::default()).unwrap_err()), r#"couldn't parse access token: MissingFieldError("access_token"). Got: {"apa": 1}."#) } diff --git a/src/config.rs b/src/config.rs index 4d25e11..bb9d4b6 100644 --- a/src/config.rs +++ b/src/config.rs @@ -1,18 +1,17 @@ use hyper::Url; use rustc_serialize::Decodable; +use std::fs; use std::fs::File; use std::io::ErrorKind; use std::io::prelude::*; use toml; -use std::path::PathBuf; - use error::Error; use error::ConfigReason::{Parse, Io, PathDoesNotExist}; use error::ParseReason::{InvalidToml, InvalidSection}; -#[derive(Default, PartialEq, Eq, Debug, Clone)] +#[derive(Default, PartialEq, Eq, Debug)] pub struct Config { pub auth: AuthConfig, pub ota: OtaConfig, @@ -20,25 +19,25 @@ pub struct Config { pub test: TestConfig, } -#[derive(RustcDecodable, PartialEq, Eq, Debug, Clone)] +#[derive(RustcDecodable, PartialEq, Eq, Debug)] pub struct AuthConfig { pub server: Url, pub client_id: String, pub secret: String } -#[derive(RustcDecodable, PartialEq, Eq, Debug, Clone)] +#[derive(RustcDecodable, PartialEq, Eq, Debug)] pub struct OtaConfig { pub server: Url, pub vin: String } -#[derive(RustcDecodable, PartialEq, Eq, Debug, Clone)] +#[derive(RustcDecodable, PartialEq, Eq, Debug)] pub struct PackagesConfig { pub dir: String, } -#[derive(RustcDecodable, PartialEq, Eq, Debug, Clone)] +#[derive(RustcDecodable, PartialEq, Eq, Debug)] pub struct TestConfig { pub looping: bool, } @@ -81,10 +80,10 @@ impl Default for TestConfig { pub fn parse_config(s: &str) -> Result { - fn parse_sect(tbl: &toml::Table, sect: String) -> Result { - tbl.get(§) + fn parse_sect(tbl: &toml::Table, sect: &str) -> Result { + tbl.get(sect) .and_then(|c| toml::decode::(c.clone()) ) - .ok_or(Error::Config(Parse(InvalidSection(sect)))) + .ok_or(Error::Config(Parse(InvalidSection(sect.to_string())))) } let tbl: toml::Table = @@ -92,12 +91,13 @@ pub fn parse_config(s: &str) -> Result { .parse() .ok_or(Error::Config(Parse(InvalidToml)))); - let auth_cfg: AuthConfig = try!(parse_sect(&tbl, "auth".to_string())); - let ota_cfg: OtaConfig = try!(parse_sect(&tbl, "ota".to_string())); - let packages_cfg: PackagesConfig = try!(parse_sect(&tbl, "packages".to_string())); - let test_cfg: TestConfig = try!(parse_sect(&tbl, "test".to_string())); + let auth_cfg: AuthConfig = try!(parse_sect(&tbl, "auth")); + let ota_cfg: OtaConfig = try!(parse_sect(&tbl, "ota")); + let packages_cfg: PackagesConfig = try!(parse_sect(&tbl, "packages")); + let test_cfg: TestConfig = try!(parse_sect(&tbl, "test")); - if ! PathBuf::from(packages_cfg.clone().dir).is_dir() { + let metadata = try!(fs::metadata(&packages_cfg.dir)); + if ! metadata.is_dir() { return Err(Error::Config(PathDoesNotExist(packages_cfg.dir))) } diff --git a/src/main.rs b/src/main.rs index 7968d4a..e1128fb 100644 --- a/src/main.rs +++ b/src/main.rs @@ -22,23 +22,23 @@ fn main() { let config = build_config(); let pkg_manager = Dpkg::new(); - let _ = authenticate::(config.auth.clone()) + let _ = authenticate::(&config.auth) .and_then(|token| { println!("Fetching installed packages on the system."); pkg_manager.installed_packages() .and_then(|pkgs| { println!("Posting {} installed packages to the server.", pkgs.iter().len()); - post_packages::(token.clone(), config.ota.clone(), pkgs) + post_packages::(&token, &config.ota, &pkgs) }) .and_then(|_| { println!("Fetching possible new package updates."); - get_package_updates::(token.clone(), config.ota.clone()) + get_package_updates::(&token, &config.ota) }) .and_then(|updates| { let len = updates.iter().len(); println!("Got {} new updates. Downloading...", len); updates.iter().map(|u| { - download_package_update::(token.clone(), config.ota.clone(), config.packages.clone(), u) + download_package_update::(&token, &config.ota, &config.packages, u) .map_err(|e| Error::ClientError(format!("Couldn't download update {:?}: {}", u, e))) }).collect::, _>>() }) diff --git a/src/ota_plus.rs b/src/ota_plus.rs index e2482e4..87375ce 100644 --- a/src/ota_plus.rs +++ b/src/ota_plus.rs @@ -14,13 +14,13 @@ use update_request::UpdateRequestId; use std::fs::File; use std::path::PathBuf; -fn vehicle_endpoint(config: OtaConfig, s: &str) -> Url { +fn vehicle_endpoint(config: &OtaConfig, s: &str) -> Url { config.server.join(&format!("/api/v1/vehicles/{}{}", config.vin, s)).unwrap() } -pub fn download_package_update(token: AccessToken, - config: OtaConfig, - pkgs_config: PackagesConfig, +pub fn download_package_update(token: &AccessToken, + config: &OtaConfig, + pkgs_config: &PackagesConfig, id: &UpdateRequestId) -> Result { let http_client = C::new(); @@ -34,11 +34,11 @@ pub fn download_package_update(token: AccessToken, http_client.send_request_to(&req, file).map(move |_| path) } -pub fn get_package_updates(token: AccessToken, - config: OtaConfig) -> Result, Error> { +pub fn get_package_updates(token: &AccessToken, + config: &OtaConfig) -> Result, Error> { let http_client = C::new(); - let req = HttpRequest::get(vehicle_endpoint(config, "/updates")) + let req = HttpRequest::get(vehicle_endpoint(&config, "/updates")) .with_header(Authorization(Bearer { token: token.access_token.clone() })); http_client.send_request(&req) .map_err(|e| Error::ClientError(format!("Can't consult package updates: {}", e))) @@ -48,15 +48,15 @@ pub fn get_package_updates(token: AccessToken, }) } -pub fn post_packages(token: AccessToken, - config: OtaConfig, - pkgs: Vec) -> Result<(), Error> { +pub fn post_packages(token: &AccessToken, + config: &OtaConfig, + pkgs: &Vec) -> Result<(), Error> { let http_client = C::new(); json::encode(&pkgs) .map_err(|_| Error::ParseError(String::from("JSON encoding error"))) .and_then(|json| { - let req = HttpRequest::post(vehicle_endpoint(config, "/packages")) + let req = HttpRequest::post(vehicle_endpoint(&config, "/packages")) .with_header(Authorization(Bearer { token: token.access_token.clone() })) .with_header(ContentType(Mime( TopLevel::Application, @@ -117,14 +117,14 @@ mod tests { #[test] fn test_post_packages_sends_authentication() { assert_eq!( - post_packages::(test_token(), OtaConfig::default(), vec![test_package()]) + post_packages::(&test_token(), &OtaConfig::default(), &vec![test_package()]) .unwrap(), ()) } #[test] fn test_get_package_updates() { - assert_eq!(get_package_updates::(test_token(), OtaConfig::default()).unwrap(), + assert_eq!(get_package_updates::(&test_token(), &OtaConfig::default()).unwrap(), vec!["pkgid".to_string()]) } } -- cgit v1.2.1 From 350d665e9d3663a1b5fdde1679eb7e993b36f66c Mon Sep 17 00:00:00 2001 From: Stevan Andjelkovic Date: Mon, 21 Mar 2016 17:45:25 +0100 Subject: Remove unnecessary config validation and tidy up error reporting. --- src/config.rs | 8 +------- src/error.rs | 14 ++++++-------- src/ota_plus.rs | 19 ++++++++++--------- 3 files changed, 17 insertions(+), 24 deletions(-) diff --git a/src/config.rs b/src/config.rs index e62fd84..7993014 100644 --- a/src/config.rs +++ b/src/config.rs @@ -1,13 +1,12 @@ use hyper::Url; use rustc_serialize::Decodable; -use std::fs; use std::fs::File; use std::io::ErrorKind; use std::io::prelude::*; use toml; use error::Error; -use error::ConfigReason::{Parse, Io, PathDoesNotExist}; +use error::ConfigReason::{Parse, Io}; use error::ParseReason::{InvalidToml, InvalidSection}; @@ -98,11 +97,6 @@ pub fn parse_config(s: &str) -> Result { let packages_cfg: PackagesConfig = try!(parse_sect(&tbl, "packages")); let test_cfg: TestConfig = try!(parse_sect(&tbl, "test")); - let metadata = try!(fs::metadata(&packages_cfg.dir)); - if ! metadata.is_dir() { - return Err(Error::Config(PathDoesNotExist(packages_cfg.dir))) - } - return Ok(Config { auth: auth_cfg, ota: ota_cfg, diff --git a/src/error.rs b/src/error.rs index e78469a..f40c2ff 100644 --- a/src/error.rs +++ b/src/error.rs @@ -4,10 +4,10 @@ use std::io; #[derive(Debug)] pub enum Error { AuthError(String), + Ota(OtaReason), ParseError(String), PackageError(String), ClientError(String), - ClientErrorWithReason(ClientReason), Config(ConfigReason), Io(io::Error) } @@ -21,14 +21,13 @@ impl From for Error { } #[derive(Debug)] -pub enum ClientReason { - Io(io::Error) +pub enum OtaReason { + CreateFile(io::Error), } #[derive(Debug)] pub enum ConfigReason { Parse(ParseReason), - PathDoesNotExist(String), Io(io::Error), } @@ -42,10 +41,10 @@ impl Display for Error { fn fmt(&self, f: &mut Formatter) -> FmtResult { let inner: String = match *self { Error::AuthError(ref s) => format!("Authentication error, {}", s.clone()), + Error::Ota(ref e) => format!("Ota server error, {}", e.clone()), Error::ParseError(ref s) => s.clone(), Error::PackageError(ref s) => s.clone(), Error::ClientError(ref s) => s.clone(), - Error::ClientErrorWithReason(ref e) => format!("OtaClient failed to {}", e.clone()), Error::Config(ref e) => format!("Failed to {}", e.clone()), Error::Io(ref e) => format!("IO Error{:?}", e.clone()), }; @@ -53,10 +52,10 @@ impl Display for Error { } } -impl Display for ClientReason { +impl Display for OtaReason { fn fmt(&self, f: &mut Formatter) -> FmtResult { let inner: String = match *self { - ClientReason::Io(ref e) => format!("perform IO: {:?}", e.clone()) + OtaReason::CreateFile(ref e) => format!("failed to create file: {}", e.clone()) }; write!(f, "{}", inner) } @@ -66,7 +65,6 @@ impl Display for ConfigReason { fn fmt(&self, f: &mut Formatter) -> FmtResult { let inner: String = match *self { ConfigReason::Parse(ref e) => format!("parse config: {}", e.clone()), - ConfigReason::PathDoesNotExist(ref e) => format!("validate existence of path in config: {}", e.clone()), ConfigReason::Io (ref e) => format!("load config: {}", e.clone()) }; write!(f, "{}", inner) diff --git a/src/ota_plus.rs b/src/ota_plus.rs index 87375ce..848dbac 100644 --- a/src/ota_plus.rs +++ b/src/ota_plus.rs @@ -1,18 +1,19 @@ +use hyper::Url; use hyper::header::{Authorization, Bearer, ContentType}; use hyper::mime::{Mime, TopLevel, SubLevel, Attr, Value}; -use hyper::Url; use rustc_serialize::json; +use std::fs::File; +use std::path::PathBuf; use std::result::Result; use access_token::AccessToken; use config::{OtaConfig, PackagesConfig}; -use error::{Error, ClientReason}; +use error::Error; +use error::OtaReason::CreateFile; use http_client::{HttpClient, HttpRequest}; use package::Package; use update_request::UpdateRequestId; -use std::fs::File; -use std::path::PathBuf; fn vehicle_endpoint(config: &OtaConfig, s: &str) -> Url { config.server.join(&format!("/api/v1/vehicles/{}{}", config.vin, s)).unwrap() @@ -22,16 +23,16 @@ pub fn download_package_update(token: &AccessToken, config: &OtaConfig, pkgs_config: &PackagesConfig, id: &UpdateRequestId) -> Result { - let http_client = C::new(); let req = HttpRequest::get(vehicle_endpoint(config, &format!("/updates/{}", id))) .with_header(Authorization(Bearer { token: token.access_token.clone() })); - let p = format!("{}/{}.deb", pkgs_config.dir, id); - let path = PathBuf::from(p); - let file = try!(File::create(path.as_path()).map_err(|e| Error::ClientErrorWithReason(ClientReason::Io(e)))); + let path = PathBuf::from(format!("{}/{}.deb", pkgs_config.dir, id)); + let file = try!(File::create(path.as_path()) + .map_err(|e| Error::Ota(CreateFile(e)))); - http_client.send_request_to(&req, file).map(move |_| path) + C::new().send_request_to(&req, file) + .map(move |_| path) } pub fn get_package_updates(token: &AccessToken, -- cgit v1.2.1 From 1c515d722c58d2f74b179c9ad481927ceeb4a5ef Mon Sep 17 00:00:00 2001 From: Stevan Andjelkovic Date: Tue, 22 Mar 2016 10:51:14 +0100 Subject: Move [packages] dir, to [ota] packages_dir. --- ota.toml | 4 +--- src/config.rs | 30 +++++++----------------------- src/main.rs | 2 +- src/ota_plus.rs | 10 +++++----- 4 files changed, 14 insertions(+), 32 deletions(-) diff --git a/ota.toml b/ota.toml index 581e4ed..f8cbfcf 100644 --- a/ota.toml +++ b/ota.toml @@ -6,9 +6,7 @@ secret = "secret" [ota] server = "http://127.0.0.1:8080" vin = "V1234567890123456" - -[packages] -dir = "/tmp" +packages_dir = "/tmp" [test] looping = false diff --git a/src/config.rs b/src/config.rs index 7993014..7b0c11f 100644 --- a/src/config.rs +++ b/src/config.rs @@ -14,7 +14,6 @@ use error::ParseReason::{InvalidToml, InvalidSection}; pub struct Config { pub auth: AuthConfig, pub ota: OtaConfig, - pub packages: PackagesConfig, pub test: TestConfig, } @@ -28,12 +27,8 @@ pub struct AuthConfig { #[derive(RustcDecodable, PartialEq, Eq, Debug)] pub struct OtaConfig { pub server: Url, - pub vin: String -} - -#[derive(RustcDecodable, PartialEq, Eq, Debug)] -pub struct PackagesConfig { - pub dir: String, + pub vin: String, + pub packages_dir: String, } #[derive(RustcDecodable, PartialEq, Eq, Debug)] @@ -57,14 +52,7 @@ impl Default for OtaConfig { OtaConfig { server: Url::parse("http://127.0.0.1:8080").unwrap(), vin: "V1234567890123456".to_string(), - } - } -} - -impl Default for PackagesConfig { - fn default() -> PackagesConfig { - PackagesConfig { - dir: "/tmp".to_string(), + packages_dir: "/tmp".to_string(), } } } @@ -92,15 +80,13 @@ pub fn parse_config(s: &str) -> Result { .parse() .ok_or(Error::Config(Parse(InvalidToml)))); - let auth_cfg: AuthConfig = try!(parse_sect(&tbl, "auth")); - let ota_cfg: OtaConfig = try!(parse_sect(&tbl, "ota")); - let packages_cfg: PackagesConfig = try!(parse_sect(&tbl, "packages")); - let test_cfg: TestConfig = try!(parse_sect(&tbl, "test")); + let auth_cfg: AuthConfig = try!(parse_sect(&tbl, "auth")); + let ota_cfg: OtaConfig = try!(parse_sect(&tbl, "ota")); + let test_cfg: TestConfig = try!(parse_sect(&tbl, "test")); return Ok(Config { auth: auth_cfg, ota: ota_cfg, - packages: packages_cfg, test: test_cfg, }) } @@ -135,9 +121,7 @@ mod tests { [ota] server = "http://127.0.0.1:8080" vin = "V1234567890123456" - - [packages] - dir = "/tmp" + packages_dir = "/tmp" [test] looping = false diff --git a/src/main.rs b/src/main.rs index e4d4108..d24038c 100644 --- a/src/main.rs +++ b/src/main.rs @@ -39,7 +39,7 @@ fn main() { let len = updates.iter().len(); println!("Got {} new updates. Downloading...", len); updates.iter().map(|u| { - download_package_update::(&token, &config.ota, &config.packages, u) + download_package_update::(&token, &config.ota, u) .map_err(|e| Error::ClientError(format!("Couldn't download update {:?}: {}", u, e))) }).collect::, _>>() }) diff --git a/src/ota_plus.rs b/src/ota_plus.rs index 848dbac..8dc1b70 100644 --- a/src/ota_plus.rs +++ b/src/ota_plus.rs @@ -7,7 +7,7 @@ use std::path::PathBuf; use std::result::Result; use access_token::AccessToken; -use config::{OtaConfig, PackagesConfig}; +use config::OtaConfig; use error::Error; use error::OtaReason::CreateFile; use http_client::{HttpClient, HttpRequest}; @@ -21,18 +21,18 @@ fn vehicle_endpoint(config: &OtaConfig, s: &str) -> Url { pub fn download_package_update(token: &AccessToken, config: &OtaConfig, - pkgs_config: &PackagesConfig, id: &UpdateRequestId) -> Result { let req = HttpRequest::get(vehicle_endpoint(config, &format!("/updates/{}", id))) .with_header(Authorization(Bearer { token: token.access_token.clone() })); - let path = PathBuf::from(format!("{}/{}.deb", pkgs_config.dir, id)); + let path = PathBuf::from(format!("{}/{}.deb", config.packages_dir, id)); let file = try!(File::create(path.as_path()) .map_err(|e| Error::Ota(CreateFile(e)))); - C::new().send_request_to(&req, file) - .map(move |_| path) + try!(C::new().send_request_to(&req, file)); + + return Ok(path) } pub fn get_package_updates(token: &AccessToken, -- cgit v1.2.1 From 6689e05184dc998340e779a10d4e7c42ed241db9 Mon Sep 17 00:00:00 2001 From: Stevan Andjelkovic Date: Tue, 22 Mar 2016 12:08:23 +0100 Subject: Add --ota-packages-dir flag. --- src/main.rs | 6 ++++++ tests/ota_plus_client_tests.rs | 2 ++ 2 files changed, 8 insertions(+) diff --git a/src/main.rs b/src/main.rs index d24038c..93ee0bf 100644 --- a/src/main.rs +++ b/src/main.rs @@ -74,6 +74,8 @@ fn build_config() -> Config { "change ota server URL", "URL"); opts.optopt("", "ota-vin", "change ota vin", "VIN"); + opts.optopt("", "ota-packages-dir", + "change downloaded directory for packages", "PATH"); opts.optflag("", "test-looping", "enable read-interpret test loop"); opts.optflag("", "test-fake-pm", @@ -123,6 +125,10 @@ fn build_config() -> Config { config.ota.vin = vin; } + if let Some(path) = matches.opt_str("ota-packages-dir") { + config.ota.packages_dir = path; + } + if matches.opt_present("test-looping") { config.test.looping = true; } diff --git a/tests/ota_plus_client_tests.rs b/tests/ota_plus_client_tests.rs index bd1f664..5f26b63 100644 --- a/tests/ota_plus_client_tests.rs +++ b/tests/ota_plus_client_tests.rs @@ -43,6 +43,8 @@ Options: --ota-server URL change ota server URL --ota-vin VIN change ota vin + --ota-packages-dir PATH + change downloaded directory for packages --test-looping enable read-interpret test loop --test-fake-pm enable fake package manager for testing -- cgit v1.2.1 From f1aa7c79eb42dc9067720b6798c4cf5c7f1df994 Mon Sep 17 00:00:00 2001 From: Stevan Andjelkovic Date: Tue, 22 Mar 2016 12:10:10 +0100 Subject: Tidy up pathbuf code and add test for bad --ota-packages-dir. --- src/error.rs | 7 +++++-- src/ota_plus.rs | 23 ++++++++++++++++++++--- 2 files changed, 25 insertions(+), 5 deletions(-) diff --git a/src/error.rs b/src/error.rs index f40c2ff..af0edc4 100644 --- a/src/error.rs +++ b/src/error.rs @@ -1,5 +1,7 @@ use std::fmt::{Display, Formatter, Result as FmtResult}; use std::io; +use std::path::PathBuf; + #[derive(Debug)] pub enum Error { @@ -22,7 +24,7 @@ impl From for Error { #[derive(Debug)] pub enum OtaReason { - CreateFile(io::Error), + CreateFile(PathBuf, io::Error), } #[derive(Debug)] @@ -55,7 +57,8 @@ impl Display for Error { impl Display for OtaReason { fn fmt(&self, f: &mut Formatter) -> FmtResult { let inner: String = match *self { - OtaReason::CreateFile(ref e) => format!("failed to create file: {}", e.clone()) + OtaReason::CreateFile(ref f, ref e) => + format!("failed to create file {:?}: {}", f.clone(), e.clone()) }; write!(f, "{}", inner) } diff --git a/src/ota_plus.rs b/src/ota_plus.rs index 8dc1b70..bcf9b3f 100644 --- a/src/ota_plus.rs +++ b/src/ota_plus.rs @@ -26,9 +26,13 @@ pub fn download_package_update(token: &AccessToken, let req = HttpRequest::get(vehicle_endpoint(config, &format!("/updates/{}", id))) .with_header(Authorization(Bearer { token: token.access_token.clone() })); - let path = PathBuf::from(format!("{}/{}.deb", config.packages_dir, id)); + let mut path = PathBuf::new(); + path.push(&config.packages_dir); + path.set_file_name(id); + path.set_extension("deb"); + let file = try!(File::create(path.as_path()) - .map_err(|e| Error::Ota(CreateFile(e)))); + .map_err(|e| Error::Ota(CreateFile(path.clone(), e)))); try!(C::new().send_request_to(&req, file)); @@ -122,10 +126,23 @@ mod tests { .unwrap(), ()) } - #[test] fn test_get_package_updates() { assert_eq!(get_package_updates::(&test_token(), &OtaConfig::default()).unwrap(), vec!["pkgid".to_string()]) } + + #[test] + fn bad_packages_dir_download_package_update() { + + let mut config = OtaConfig::default(); + config = OtaConfig { packages_dir: "/".to_string(), .. config }; + + assert_eq!( + format!("{}", + download_package_update::(&test_token(), &config, &"0".to_string()) + .unwrap_err()), + r#"Ota server error, failed to create file "/0.deb": Permission denied (os error 13)"#) + } + } -- cgit v1.2.1 From ee9e2e1dcaa7da686a5f0385db9e761af45457ed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sim=C3=A3o=20Mata?= Date: Tue, 22 Mar 2016 14:12:22 +0100 Subject: fix endpoint urls --- src/ota_plus.rs | 4 ++-- src/package_manager/dpkg.rs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/ota_plus.rs b/src/ota_plus.rs index 87375ce..5e1add2 100644 --- a/src/ota_plus.rs +++ b/src/ota_plus.rs @@ -24,7 +24,7 @@ pub fn download_package_update(token: &AccessToken, id: &UpdateRequestId) -> Result { let http_client = C::new(); - let req = HttpRequest::get(vehicle_endpoint(config, &format!("/updates/{}", id))) + let req = HttpRequest::get(vehicle_endpoint(config, &format!("/updates/{}/download", id))) .with_header(Authorization(Bearer { token: token.access_token.clone() })); let p = format!("{}/{}.deb", pkgs_config.dir, id); @@ -56,7 +56,7 @@ pub fn post_packages(token: &AccessToken, json::encode(&pkgs) .map_err(|_| Error::ParseError(String::from("JSON encoding error"))) .and_then(|json| { - let req = HttpRequest::post(vehicle_endpoint(&config, "/packages")) + let req = HttpRequest::post(vehicle_endpoint(&config, "/updates")) .with_header(Authorization(Bearer { token: token.access_token.clone() })) .with_header(ContentType(Mime( TopLevel::Application, diff --git a/src/package_manager/dpkg.rs b/src/package_manager/dpkg.rs index 96f18b0..a01d433 100644 --- a/src/package_manager/dpkg.rs +++ b/src/package_manager/dpkg.rs @@ -15,7 +15,7 @@ impl Dpkg { impl package_manager::PackageManager for Dpkg { fn installed_packages(&self) -> Result, Error> { - Command::new("dpkg-query").arg("-f").arg("'${Package} ${Version}\n'").arg("-W") + Command::new("dpkg-query").arg("-f").arg("${Package} ${Version}\n").arg("-W") .output() .map_err(|e| Error::PackageError(format!("Error fetching packages: {}", e))) .and_then(|c| { -- cgit v1.2.1 From a55aa315a6079180f1d08d746960d0f73d74d47b Mon Sep 17 00:00:00 2001 From: Stevan Andjelkovic Date: Tue, 22 Mar 2016 14:22:18 +0100 Subject: Use try! instead of and_then. --- src/ota_plus.rs | 41 ++++++++++++++++++++--------------------- 1 file changed, 20 insertions(+), 21 deletions(-) diff --git a/src/ota_plus.rs b/src/ota_plus.rs index c42353e..06cbd49 100644 --- a/src/ota_plus.rs +++ b/src/ota_plus.rs @@ -41,36 +41,35 @@ pub fn download_package_update(token: &AccessToken, pub fn get_package_updates(token: &AccessToken, config: &OtaConfig) -> Result, Error> { - let http_client = C::new(); let req = HttpRequest::get(vehicle_endpoint(&config, "/updates")) .with_header(Authorization(Bearer { token: token.access_token.clone() })); - http_client.send_request(&req) - .map_err(|e| Error::ClientError(format!("Can't consult package updates: {}", e))) - .and_then(|body| { - json::decode::>(&body) - .map_err(|e| Error::ParseError(format!("Cannot parse response: {}. Got: {}", e, &body))) - }) + + let body = try!(C::new().send_request(&req) + .map_err(|e| Error::ClientError(format!("Can't consult package updates: {}", e)))); + + json::decode::>(&body) + .map_err(|e| Error::ParseError(format!("Cannot parse response: {}. Got: {}", e, &body))) } pub fn post_packages(token: &AccessToken, config: &OtaConfig, pkgs: &Vec) -> Result<(), Error> { - let http_client = C::new(); - json::encode(&pkgs) - .map_err(|_| Error::ParseError(String::from("JSON encoding error"))) - .and_then(|json| { - let req = HttpRequest::post(vehicle_endpoint(&config, "/updates")) - .with_header(Authorization(Bearer { token: token.access_token.clone() })) - .with_header(ContentType(Mime( - TopLevel::Application, - SubLevel::Json, - vec![(Attr::Charset, Value::Utf8)]))) - .with_body(&json); - - http_client.send_request(&req).map(|_| ()) - }) + let json = try!(json::encode(&pkgs) + .map_err(|_| Error::ParseError(String::from("JSON encoding error")))); + + let req = HttpRequest::post(vehicle_endpoint(config, "/updates")) + .with_header(Authorization(Bearer { token: token.access_token.clone() })) + .with_header(ContentType(Mime( + TopLevel::Application, + SubLevel::Json, + vec![(Attr::Charset, Value::Utf8)]))) + .with_body(&json); + + let _: String = try!(C::new().send_request(&req)); + + return Ok(()) } #[cfg(test)] -- cgit v1.2.1 From 2f26a62c5f9a88c4e622ee28fa348011d961d5f0 Mon Sep 17 00:00:00 2001 From: Stevan Andjelkovic Date: Tue, 22 Mar 2016 14:50:45 +0100 Subject: Fix bug in pathbuf code. --- src/ota_plus.rs | 36 +++++++++++++++++++++++++----------- 1 file changed, 25 insertions(+), 11 deletions(-) diff --git a/src/ota_plus.rs b/src/ota_plus.rs index 06cbd49..4657a02 100644 --- a/src/ota_plus.rs +++ b/src/ota_plus.rs @@ -19,27 +19,28 @@ fn vehicle_endpoint(config: &OtaConfig, s: &str) -> Url { config.server.join(&format!("/api/v1/vehicles/{}{}", config.vin, s)).unwrap() } -pub fn download_package_update(token: &AccessToken, +pub fn download_package_update(token: &AccessToken, config: &OtaConfig, - id: &UpdateRequestId) -> Result { + id: &UpdateRequestId) -> Result { let req = HttpRequest::get(vehicle_endpoint(config, &format!("/updates/{}/download", id))) .with_header(Authorization(Bearer { token: token.access_token.clone() })); let mut path = PathBuf::new(); path.push(&config.packages_dir); - path.set_file_name(id); + path.push(id); path.set_extension("deb"); let file = try!(File::create(path.as_path()) .map_err(|e| Error::Ota(CreateFile(path.clone(), e)))); - try!(C::new().send_request_to(&req, file)); + try!(C::new().send_request_to(&req, file) + .map_err(|e| Error::ClientError(e))); return Ok(path) } -pub fn get_package_updates(token: &AccessToken, +pub fn get_package_updates(token: &AccessToken, config: &OtaConfig) -> Result, Error> { let req = HttpRequest::get(vehicle_endpoint(&config, "/updates")) @@ -52,9 +53,9 @@ pub fn get_package_updates(token: &AccessToken, .map_err(|e| Error::ParseError(format!("Cannot parse response: {}. Got: {}", e, &body))) } -pub fn post_packages(token: &AccessToken, +pub fn post_packages(token: &AccessToken, config: &OtaConfig, - pkgs: &Vec) -> Result<(), Error> { + pkgs: &Vec) -> Result<(), Error> { let json = try!(json::encode(&pkgs) .map_err(|_| Error::ParseError(String::from("JSON encoding error")))); @@ -75,14 +76,16 @@ pub fn post_packages(token: &AccessToken, #[cfg(test)] mod tests { + use std::io::Write; + use super::*; - use http_client::{HttpRequest, HttpClient}; + use access_token::AccessToken; + use bad_http_client::BadHttpClient; + use config::OtaConfig; use error::Error; + use http_client::{HttpRequest, HttpClient}; use package::Package; - use config::OtaConfig; - use access_token::AccessToken; - use std::io::Write; fn test_token() -> AccessToken { AccessToken { @@ -144,4 +147,15 @@ mod tests { r#"Ota server error, failed to create file "/0.deb": Permission denied (os error 13)"#) } + #[test] + fn bad_client_download_package_update() { + + assert_eq!( + format!("{}", + download_package_update:: + (&test_token(), &OtaConfig::default(), &"0".to_string()) + .unwrap_err()), + "bad client.") + } + } -- cgit v1.2.1 From c7c18ef133079cfd2818e8c0fe21033f3ae45492 Mon Sep 17 00:00:00 2001 From: Stevan Andjelkovic Date: Tue, 22 Mar 2016 15:33:58 +0100 Subject: Better error reporting. --- src/error.rs | 7 +++++-- src/http_client.rs | 6 ++++++ src/ota_plus.rs | 10 +++++----- 3 files changed, 16 insertions(+), 7 deletions(-) diff --git a/src/error.rs b/src/error.rs index af0edc4..8af399d 100644 --- a/src/error.rs +++ b/src/error.rs @@ -25,6 +25,7 @@ impl From for Error { #[derive(Debug)] pub enum OtaReason { CreateFile(PathBuf, io::Error), + Client(String, String), } #[derive(Debug)] @@ -43,7 +44,7 @@ impl Display for Error { fn fmt(&self, f: &mut Formatter) -> FmtResult { let inner: String = match *self { Error::AuthError(ref s) => format!("Authentication error, {}", s.clone()), - Error::Ota(ref e) => format!("Ota server error, {}", e.clone()), + Error::Ota(ref e) => format!("Ota error, {}", e.clone()), Error::ParseError(ref s) => s.clone(), Error::PackageError(ref s) => s.clone(), Error::ClientError(ref s) => s.clone(), @@ -58,7 +59,9 @@ impl Display for OtaReason { fn fmt(&self, f: &mut Formatter) -> FmtResult { let inner: String = match *self { OtaReason::CreateFile(ref f, ref e) => - format!("failed to create file {:?}: {}", f.clone(), e.clone()) + format!("failed to create file {:?}: {}", f.clone(), e.clone()), + OtaReason::Client(ref r, ref e) => + format!("the request: {},\nresults in the following error: {}", r.clone(), e.clone()), }; write!(f, "{}", inner) } diff --git a/src/http_client.rs b/src/http_client.rs index 253d064..7123bda 100644 --- a/src/http_client.rs +++ b/src/http_client.rs @@ -15,6 +15,12 @@ pub struct HttpRequest<'a> { pub body: Option<&'a str> } +impl<'a> ToString for HttpRequest<'a> { + fn to_string(&self) -> String { + return format!("{} {}", self.method, self.url.serialize()) + } +} + impl<'a> HttpRequest<'a> { pub fn new(url: Url, method: Method) -> HttpRequest<'a> { HttpRequest { url: url, method: method, headers: Headers::new(), body: None } diff --git a/src/ota_plus.rs b/src/ota_plus.rs index 4657a02..dfb4c90 100644 --- a/src/ota_plus.rs +++ b/src/ota_plus.rs @@ -9,7 +9,7 @@ use std::result::Result; use access_token::AccessToken; use config::OtaConfig; use error::Error; -use error::OtaReason::CreateFile; +use error::OtaReason::{CreateFile, Client}; use http_client::{HttpClient, HttpRequest}; use package::Package; use update_request::UpdateRequestId; @@ -35,7 +35,7 @@ pub fn download_package_update(token: &AccessToken, .map_err(|e| Error::Ota(CreateFile(path.clone(), e)))); try!(C::new().send_request_to(&req, file) - .map_err(|e| Error::ClientError(e))); + .map_err(|e| Error::Ota(Client(req.to_string(), format!("{}", e))))); return Ok(path) } @@ -144,18 +144,18 @@ mod tests { format!("{}", download_package_update::(&test_token(), &config, &"0".to_string()) .unwrap_err()), - r#"Ota server error, failed to create file "/0.deb": Permission denied (os error 13)"#) + r#"Ota error, failed to create file "/0.deb": Permission denied (os error 13)"#) } #[test] fn bad_client_download_package_update() { - assert_eq!( format!("{}", download_package_update:: (&test_token(), &OtaConfig::default(), &"0".to_string()) .unwrap_err()), - "bad client.") + r#"Ota error, the request: GET http://127.0.0.1:8080/api/v1/vehicles/V1234567890123456/updates/0, +results in the following error: bad client."#) } } -- cgit v1.2.1 From 7c75817f53ef4c7c19da89c8867bc51d704062e0 Mon Sep 17 00:00:00 2001 From: Stevan Andjelkovic Date: Wed, 23 Mar 2016 10:55:16 +0100 Subject: Fix test. --- src/ota_plus.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ota_plus.rs b/src/ota_plus.rs index dfb4c90..f0a54d2 100644 --- a/src/ota_plus.rs +++ b/src/ota_plus.rs @@ -154,7 +154,7 @@ mod tests { download_package_update:: (&test_token(), &OtaConfig::default(), &"0".to_string()) .unwrap_err()), - r#"Ota error, the request: GET http://127.0.0.1:8080/api/v1/vehicles/V1234567890123456/updates/0, + r#"Ota error, the request: GET http://127.0.0.1:8080/api/v1/vehicles/V1234567890123456/updates/0/download, results in the following error: bad client."#) } -- cgit v1.2.1 From cc92197d803de8a25456c197083d78635cbb22f7 Mon Sep 17 00:00:00 2001 From: Stevan Andjelkovic Date: Wed, 23 Mar 2016 14:49:11 +0100 Subject: Simplify code dealing with JSON {en,de}coding errors. --- src/auth_plus.rs | 15 +++++---------- src/error.rs | 18 +++++++++++++++++- src/ota_plus.rs | 4 ++-- 3 files changed, 24 insertions(+), 13 deletions(-) diff --git a/src/auth_plus.rs b/src/auth_plus.rs index 0e02ff6..af9bbc1 100644 --- a/src/auth_plus.rs +++ b/src/auth_plus.rs @@ -10,8 +10,6 @@ use access_token::AccessToken; pub fn authenticate(config: &AuthConfig) -> Result { - let http_client = C::new(); - let req = HttpRequest::post(config.server.join("/token").unwrap()) .with_body("grant_type=client_credentials") .with_header(Authorization(Basic { @@ -23,13 +21,10 @@ pub fn authenticate(config: &AuthConfig) -> Result(&AuthConfig::default()).unwrap_err()), - r#"couldn't parse access token: MissingFieldError("access_token"). Got: {"apa": 1}."#) + r#"Failed to decode JSON: MissingFieldError("access_token")"#) } } diff --git a/src/error.rs b/src/error.rs index 8af399d..85f5239 100644 --- a/src/error.rs +++ b/src/error.rs @@ -1,3 +1,5 @@ +use rustc_serialize::json; +use std::convert::From; use std::fmt::{Display, Formatter, Result as FmtResult}; use std::io; use std::path::PathBuf; @@ -11,10 +13,22 @@ pub enum Error { PackageError(String), ClientError(String), Config(ConfigReason), + JsonEncode(String), + JsonDecode(String), Io(io::Error) } -use std::convert::From; +impl From for Error { + fn from(e: json::EncoderError) -> Error { + Error::JsonEncode(format!("{}", e)) + } +} + +impl From for Error { + fn from(e: json::DecoderError) -> Error { + Error::JsonDecode(format!("{}", e)) + } +} impl From for Error { fn from(e: io::Error) -> Error { @@ -49,6 +63,8 @@ impl Display for Error { Error::PackageError(ref s) => s.clone(), Error::ClientError(ref s) => s.clone(), Error::Config(ref e) => format!("Failed to {}", e.clone()), + Error::JsonEncode(ref e) => format!("Failed to encode JSON: {}", e.clone()), + Error::JsonDecode(ref e) => format!("Failed to decode JSON: {}", e.clone()), Error::Io(ref e) => format!("IO Error{:?}", e.clone()), }; write!(f, "{}", inner) diff --git a/src/ota_plus.rs b/src/ota_plus.rs index f0a54d2..84192aa 100644 --- a/src/ota_plus.rs +++ b/src/ota_plus.rs @@ -49,8 +49,8 @@ pub fn get_package_updates(token: &AccessToken, let body = try!(C::new().send_request(&req) .map_err(|e| Error::ClientError(format!("Can't consult package updates: {}", e)))); - json::decode::>(&body) - .map_err(|e| Error::ParseError(format!("Cannot parse response: {}. Got: {}", e, &body))) + return Ok(try!(json::decode::>(&body))); + } pub fn post_packages(token: &AccessToken, -- cgit v1.2.1 From 4e1906f2a54cb338dfe12a3db171d6df8edf2c6f Mon Sep 17 00:00:00 2001 From: Stevan Andjelkovic Date: Wed, 23 Mar 2016 15:37:28 +0100 Subject: Rewrite main to use the http client and package manager interfaces. --- src/lib.rs | 2 +- src/main.rs | 69 +++++++++++++++++++++++++++------------------ src/package_manager/dpkg.rs | 5 ++++ src/package_manager/mod.rs | 1 + 4 files changed, 49 insertions(+), 28 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index da13930..846b730 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -16,4 +16,4 @@ pub mod package; pub mod package_manager; pub mod error; pub mod update_request; -mod http_client; +pub mod http_client; diff --git a/src/main.rs b/src/main.rs index 93ee0bf..3f34ccd 100644 --- a/src/main.rs +++ b/src/main.rs @@ -6,9 +6,11 @@ extern crate hyper; use getopts::Options; use hyper::Url; use std::env; +use std::path::PathBuf; use libotaplus::{config, read_interpret}; use libotaplus::config::Config; +use libotaplus::http_client::HttpClient; use libotaplus::read_interpret::ReplEnv; use libotaplus::auth_plus::authenticate; use libotaplus::ota_plus::{post_packages, get_package_updates, download_package_update}; @@ -21,39 +23,52 @@ fn main() { env_logger::init().unwrap(); let config = build_config(); - let pkg_manager = Dpkg::new(); - - let _ = authenticate::(&config.auth) - .and_then(|token| { - println!("Fetching installed packages on the system."); - pkg_manager.installed_packages() - .and_then(|pkgs| { - println!("Posting {} installed packages to the server.", pkgs.iter().len()); - post_packages::(&token, &config.ota, &pkgs) - }) - .and_then(|_| { - println!("Fetching possible new package updates."); - get_package_updates::(&token, &config.ota) - }) - .and_then(|updates| { - let len = updates.iter().len(); - println!("Got {} new updates. Downloading...", len); - updates.iter().map(|u| { - download_package_update::(&token, &config.ota, u) - .map_err(|e| Error::ClientError(format!("Couldn't download update {:?}: {}", u, e))) - }).collect::, _>>() - }) - }) - .map(|paths| println!("All good. Downloaded {:?}. See you again soon!", paths)) - .map(|_| println!("Installed packages were posted successfully.")) - .map_err(|err| println!("{}", err)); + + match worker::(&config) { + Err(e) => exit!("{}", e), + Ok(paths) => { + println!("All good. Downloaded {:?}. See you again soon!", paths); + println!("Installed packages were posted successfully."); + } + } if config.test.looping { - read_interpret::read_interpret_loop(ReplEnv::new(pkg_manager)); + read_interpret::read_interpret_loop(ReplEnv::new(Dpkg::new())); } } +fn worker(config: &Config) -> Result, Error> { + + println!("Trying to acquire access token."); + let token = try!(authenticate::(&config.auth)); + + println!("Asking package manager what packages are installed on the system."); + let pkg_manager = M::new(); + let pkgs = try!(pkg_manager.installed_packages()); + + println!("Letting the OTA server know what packages are installed."); + try!(post_packages::(&token, &config.ota, &pkgs)); + + println!("Fetching possible new package updates."); + let updates = try!(get_package_updates::(&token, &config.ota)); + + let updates_len = updates.iter().len(); + println!("Got {} new updates. Downloading...", updates_len); + + let mut paths = Vec::with_capacity(updates_len); + + for update in &updates { + let path = try!(download_package_update::(&token, &config.ota, update) + .map_err(|e| Error::ClientError( + format!("Couldn't download update {:?}: {}", update, e)))); + paths.push(path); + } + + return Ok(paths) + +} + fn build_config() -> Config { let args: Vec = env::args().collect(); diff --git a/src/package_manager/dpkg.rs b/src/package_manager/dpkg.rs index a01d433..9eee2d2 100644 --- a/src/package_manager/dpkg.rs +++ b/src/package_manager/dpkg.rs @@ -14,6 +14,11 @@ impl Dpkg { } impl package_manager::PackageManager for Dpkg { + + fn new() -> Dpkg { + return Dpkg::new(); + } + fn installed_packages(&self) -> Result, Error> { Command::new("dpkg-query").arg("-f").arg("${Package} ${Version}\n").arg("-W") .output() diff --git a/src/package_manager/mod.rs b/src/package_manager/mod.rs index 3e4ecd8..aa2df8c 100644 --- a/src/package_manager/mod.rs +++ b/src/package_manager/mod.rs @@ -2,6 +2,7 @@ use package::Package; use error::Error; pub trait PackageManager { + fn new() -> Self; fn installed_packages(&self) -> Result, Error>; } -- cgit v1.2.1 From 2d394333619d164cf9598df24273ecdec819abe6 Mon Sep 17 00:00:00 2001 From: Stevan Andjelkovic Date: Wed, 23 Mar 2016 17:11:58 +0100 Subject: Move datatypes into their own module. --- src/access_token.rs | 8 --- src/auth_plus.rs | 16 ++--- src/bad_http_client.rs | 2 +- src/config.rs | 149 ----------------------------------------- src/datatype/access_token.rs | 8 +++ src/datatype/config.rs | 149 +++++++++++++++++++++++++++++++++++++++++ src/datatype/error.rs | 117 ++++++++++++++++++++++++++++++++ src/datatype/mod.rs | 5 ++ src/datatype/package.rs | 15 +++++ src/datatype/update_request.rs | 1 + src/error.rs | 117 -------------------------------- src/http_client.rs | 10 +-- src/lib.rs | 16 ++--- src/main.rs | 11 +-- src/ota_plus.rs | 20 +++--- src/package.rs | 15 ----- src/package_manager/dpkg.rs | 9 +-- src/package_manager/mod.rs | 4 +- src/update_request.rs | 1 - tests/ota_plus_client_tests.rs | 4 +- 20 files changed, 340 insertions(+), 337 deletions(-) delete mode 100644 src/access_token.rs delete mode 100644 src/config.rs create mode 100644 src/datatype/access_token.rs create mode 100644 src/datatype/config.rs create mode 100644 src/datatype/error.rs create mode 100644 src/datatype/mod.rs create mode 100644 src/datatype/package.rs create mode 100644 src/datatype/update_request.rs delete mode 100644 src/error.rs delete mode 100644 src/package.rs delete mode 100644 src/update_request.rs diff --git a/src/access_token.rs b/src/access_token.rs deleted file mode 100644 index afd359b..0000000 --- a/src/access_token.rs +++ /dev/null @@ -1,8 +0,0 @@ - -#[derive(RustcDecodable, Debug, PartialEq)] -pub struct AccessToken { - pub access_token: String, - pub token_type: String, - pub expires_in: i32, - pub scope: Vec -} diff --git a/src/auth_plus.rs b/src/auth_plus.rs index af9bbc1..67e8ad5 100644 --- a/src/auth_plus.rs +++ b/src/auth_plus.rs @@ -2,10 +2,10 @@ use hyper::header::{Authorization, Basic, ContentType}; use hyper::mime::{Mime, TopLevel, SubLevel, Attr, Value}; use rustc_serialize::json; -use config::AuthConfig; -use error::Error; +use datatype::access_token::AccessToken; +use datatype::config::AuthConfig; +use datatype::error::Error; use http_client::{HttpClient, HttpRequest}; -use access_token::AccessToken; pub fn authenticate(config: &AuthConfig) -> Result { @@ -31,15 +31,15 @@ pub fn authenticate(config: &AuthConfig) -> Result AuthConfig { - AuthConfig { - server: Url::parse("http://127.0.0.1:9000").unwrap(), - client_id: "client-id".to_string(), - secret: "secret".to_string(), - } - } -} - -impl Default for OtaConfig { - fn default() -> OtaConfig { - OtaConfig { - server: Url::parse("http://127.0.0.1:8080").unwrap(), - vin: "V1234567890123456".to_string(), - packages_dir: "/tmp".to_string(), - } - } -} - -impl Default for TestConfig { - fn default() -> TestConfig { - TestConfig { - looping: false, - fake_package_manager: false, - } - } -} - - -pub fn parse_config(s: &str) -> Result { - - fn parse_sect(tbl: &toml::Table, sect: &str) -> Result { - tbl.get(sect) - .and_then(|c| toml::decode::(c.clone()) ) - .ok_or(Error::Config(Parse(InvalidSection(sect.to_string())))) - } - - let tbl: toml::Table = - try!(toml::Parser::new(&s) - .parse() - .ok_or(Error::Config(Parse(InvalidToml)))); - - let auth_cfg: AuthConfig = try!(parse_sect(&tbl, "auth")); - let ota_cfg: OtaConfig = try!(parse_sect(&tbl, "ota")); - let test_cfg: TestConfig = try!(parse_sect(&tbl, "test")); - - return Ok(Config { - auth: auth_cfg, - ota: ota_cfg, - test: test_cfg, - }) -} - -pub fn load_config(path: &str) -> Result { - - match File::open(path) { - Err(ref e) if e.kind() == ErrorKind::NotFound => Ok(Config::default()), - Err(e) => Err(Error::Config(Io(e))), - Ok(mut f) => { - let mut s = String::new(); - try!(f.read_to_string(&mut s) - .map_err(|err| Error::Config(Io(err)))); - return parse_config(&s); - } - } -} - - -#[cfg(test)] -mod tests { - - use super::*; - - const DEFAULT_CONFIG_STRING: &'static str = - r#" - [auth] - server = "http://127.0.0.1:9000" - client_id = "client-id" - secret = "secret" - - [ota] - server = "http://127.0.0.1:8080" - vin = "V1234567890123456" - packages_dir = "/tmp" - - [test] - looping = false - fake_package_manager = false - "#; - - #[test] - fn parse_default_config() { - assert_eq!(parse_config(DEFAULT_CONFIG_STRING).unwrap(), - Config::default()); - } - - #[test] - fn load_default_config() { - assert_eq!(load_config("ota.toml").unwrap(), - Config::default()); - } - - #[test] - fn bad_path_yields_default_config() { - assert_eq!(load_config("").unwrap(), - Config::default()) - } - -} diff --git a/src/datatype/access_token.rs b/src/datatype/access_token.rs new file mode 100644 index 0000000..afd359b --- /dev/null +++ b/src/datatype/access_token.rs @@ -0,0 +1,8 @@ + +#[derive(RustcDecodable, Debug, PartialEq)] +pub struct AccessToken { + pub access_token: String, + pub token_type: String, + pub expires_in: i32, + pub scope: Vec +} diff --git a/src/datatype/config.rs b/src/datatype/config.rs new file mode 100644 index 0000000..49f876b --- /dev/null +++ b/src/datatype/config.rs @@ -0,0 +1,149 @@ +use hyper::Url; +use rustc_serialize::Decodable; +use std::fs::File; +use std::io::ErrorKind; +use std::io::prelude::*; +use toml; + +use datatype::error::Error; +use datatype::error::ConfigReason::{Parse, Io}; +use datatype::error::ParseReason::{InvalidToml, InvalidSection}; + + +#[derive(Default, PartialEq, Eq, Debug)] +pub struct Config { + pub auth: AuthConfig, + pub ota: OtaConfig, + pub test: TestConfig, +} + +#[derive(RustcDecodable, PartialEq, Eq, Debug)] +pub struct AuthConfig { + pub server: Url, + pub client_id: String, + pub secret: String +} + +#[derive(RustcDecodable, PartialEq, Eq, Debug)] +pub struct OtaConfig { + pub server: Url, + pub vin: String, + pub packages_dir: String, +} + +#[derive(RustcDecodable, PartialEq, Eq, Debug)] +pub struct TestConfig { + pub looping: bool, + pub fake_package_manager: bool, +} + +impl Default for AuthConfig { + fn default() -> AuthConfig { + AuthConfig { + server: Url::parse("http://127.0.0.1:9000").unwrap(), + client_id: "client-id".to_string(), + secret: "secret".to_string(), + } + } +} + +impl Default for OtaConfig { + fn default() -> OtaConfig { + OtaConfig { + server: Url::parse("http://127.0.0.1:8080").unwrap(), + vin: "V1234567890123456".to_string(), + packages_dir: "/tmp".to_string(), + } + } +} + +impl Default for TestConfig { + fn default() -> TestConfig { + TestConfig { + looping: false, + fake_package_manager: false, + } + } +} + + +pub fn parse_config(s: &str) -> Result { + + fn parse_sect(tbl: &toml::Table, sect: &str) -> Result { + tbl.get(sect) + .and_then(|c| toml::decode::(c.clone()) ) + .ok_or(Error::Config(Parse(InvalidSection(sect.to_string())))) + } + + let tbl: toml::Table = + try!(toml::Parser::new(&s) + .parse() + .ok_or(Error::Config(Parse(InvalidToml)))); + + let auth_cfg: AuthConfig = try!(parse_sect(&tbl, "auth")); + let ota_cfg: OtaConfig = try!(parse_sect(&tbl, "ota")); + let test_cfg: TestConfig = try!(parse_sect(&tbl, "test")); + + return Ok(Config { + auth: auth_cfg, + ota: ota_cfg, + test: test_cfg, + }) +} + +pub fn load_config(path: &str) -> Result { + + match File::open(path) { + Err(ref e) if e.kind() == ErrorKind::NotFound => Ok(Config::default()), + Err(e) => Err(Error::Config(Io(e))), + Ok(mut f) => { + let mut s = String::new(); + try!(f.read_to_string(&mut s) + .map_err(|err| Error::Config(Io(err)))); + return parse_config(&s); + } + } +} + + +#[cfg(test)] +mod tests { + + use super::*; + + const DEFAULT_CONFIG_STRING: &'static str = + r#" + [auth] + server = "http://127.0.0.1:9000" + client_id = "client-id" + secret = "secret" + + [ota] + server = "http://127.0.0.1:8080" + vin = "V1234567890123456" + packages_dir = "/tmp" + + [test] + looping = false + fake_package_manager = false + "#; + + #[test] + fn parse_default_config() { + assert_eq!(parse_config(DEFAULT_CONFIG_STRING).unwrap(), + Config::default()); + } + + #[test] + fn load_default_config() { + assert_eq!(load_config("ota.toml").unwrap(), + Config::default()); + } + + #[test] + fn bad_path_yields_default_config() { + assert_eq!(load_config("").unwrap(), + Config::default()) + } + +} diff --git a/src/datatype/error.rs b/src/datatype/error.rs new file mode 100644 index 0000000..85f5239 --- /dev/null +++ b/src/datatype/error.rs @@ -0,0 +1,117 @@ +use rustc_serialize::json; +use std::convert::From; +use std::fmt::{Display, Formatter, Result as FmtResult}; +use std::io; +use std::path::PathBuf; + + +#[derive(Debug)] +pub enum Error { + AuthError(String), + Ota(OtaReason), + ParseError(String), + PackageError(String), + ClientError(String), + Config(ConfigReason), + JsonEncode(String), + JsonDecode(String), + Io(io::Error) +} + +impl From for Error { + fn from(e: json::EncoderError) -> Error { + Error::JsonEncode(format!("{}", e)) + } +} + +impl From for Error { + fn from(e: json::DecoderError) -> Error { + Error::JsonDecode(format!("{}", e)) + } +} + +impl From for Error { + fn from(e: io::Error) -> Error { + Error::Io(e) + } +} + +#[derive(Debug)] +pub enum OtaReason { + CreateFile(PathBuf, io::Error), + Client(String, String), +} + +#[derive(Debug)] +pub enum ConfigReason { + Parse(ParseReason), + Io(io::Error), +} + +#[derive(Debug)] +pub enum ParseReason { + InvalidToml, + InvalidSection(String), +} + +impl Display for Error { + fn fmt(&self, f: &mut Formatter) -> FmtResult { + let inner: String = match *self { + Error::AuthError(ref s) => format!("Authentication error, {}", s.clone()), + Error::Ota(ref e) => format!("Ota error, {}", e.clone()), + Error::ParseError(ref s) => s.clone(), + Error::PackageError(ref s) => s.clone(), + Error::ClientError(ref s) => s.clone(), + Error::Config(ref e) => format!("Failed to {}", e.clone()), + Error::JsonEncode(ref e) => format!("Failed to encode JSON: {}", e.clone()), + Error::JsonDecode(ref e) => format!("Failed to decode JSON: {}", e.clone()), + Error::Io(ref e) => format!("IO Error{:?}", e.clone()), + }; + write!(f, "{}", inner) + } +} + +impl Display for OtaReason { + fn fmt(&self, f: &mut Formatter) -> FmtResult { + let inner: String = match *self { + OtaReason::CreateFile(ref f, ref e) => + format!("failed to create file {:?}: {}", f.clone(), e.clone()), + OtaReason::Client(ref r, ref e) => + format!("the request: {},\nresults in the following error: {}", r.clone(), e.clone()), + }; + write!(f, "{}", inner) + } +} + +impl Display for ConfigReason { + fn fmt(&self, f: &mut Formatter) -> FmtResult { + let inner: String = match *self { + ConfigReason::Parse(ref e) => format!("parse config: {}", e.clone()), + ConfigReason::Io (ref e) => format!("load config: {}", e.clone()) + }; + write!(f, "{}", inner) + } +} + + +impl Display for ParseReason { + fn fmt(&self, f: &mut Formatter) -> FmtResult { + let inner: String = match *self { + ParseReason::InvalidToml => "invalid toml".to_string(), + ParseReason::InvalidSection(ref s) => format!("invalid section: {}", s), + }; + write!(f, "{}", inner) + } +} + +#[macro_export] +macro_rules! exit { + ($fmt:expr) => ({ + print!(concat!($fmt, "\n")); + std::process::exit(1); + }); + ($fmt:expr, $($arg:tt)*) => ({ + print!(concat!($fmt, "\n"), $($arg)*); + std::process::exit(1); + }) +} diff --git a/src/datatype/mod.rs b/src/datatype/mod.rs new file mode 100644 index 0000000..97264d6 --- /dev/null +++ b/src/datatype/mod.rs @@ -0,0 +1,5 @@ +pub mod access_token; +pub mod config; +pub mod error; +pub mod package; +pub mod update_request; diff --git a/src/datatype/package.rs b/src/datatype/package.rs new file mode 100644 index 0000000..65fffb7 --- /dev/null +++ b/src/datatype/package.rs @@ -0,0 +1,15 @@ +use std::fmt::{Display, Formatter, Result as FmtResult}; + +pub type Version = String; + +#[derive(Debug, PartialEq, Eq, RustcEncodable, RustcDecodable)] +pub struct Package { + pub name: String, + pub version: Version +} + +impl Display for Package { + fn fmt(&self, f: &mut Formatter) -> FmtResult { + write!(f, "{} {}", self.name, self.version) + } +} diff --git a/src/datatype/update_request.rs b/src/datatype/update_request.rs new file mode 100644 index 0000000..56b82aa --- /dev/null +++ b/src/datatype/update_request.rs @@ -0,0 +1 @@ +pub type UpdateRequestId = String; diff --git a/src/error.rs b/src/error.rs deleted file mode 100644 index 85f5239..0000000 --- a/src/error.rs +++ /dev/null @@ -1,117 +0,0 @@ -use rustc_serialize::json; -use std::convert::From; -use std::fmt::{Display, Formatter, Result as FmtResult}; -use std::io; -use std::path::PathBuf; - - -#[derive(Debug)] -pub enum Error { - AuthError(String), - Ota(OtaReason), - ParseError(String), - PackageError(String), - ClientError(String), - Config(ConfigReason), - JsonEncode(String), - JsonDecode(String), - Io(io::Error) -} - -impl From for Error { - fn from(e: json::EncoderError) -> Error { - Error::JsonEncode(format!("{}", e)) - } -} - -impl From for Error { - fn from(e: json::DecoderError) -> Error { - Error::JsonDecode(format!("{}", e)) - } -} - -impl From for Error { - fn from(e: io::Error) -> Error { - Error::Io(e) - } -} - -#[derive(Debug)] -pub enum OtaReason { - CreateFile(PathBuf, io::Error), - Client(String, String), -} - -#[derive(Debug)] -pub enum ConfigReason { - Parse(ParseReason), - Io(io::Error), -} - -#[derive(Debug)] -pub enum ParseReason { - InvalidToml, - InvalidSection(String), -} - -impl Display for Error { - fn fmt(&self, f: &mut Formatter) -> FmtResult { - let inner: String = match *self { - Error::AuthError(ref s) => format!("Authentication error, {}", s.clone()), - Error::Ota(ref e) => format!("Ota error, {}", e.clone()), - Error::ParseError(ref s) => s.clone(), - Error::PackageError(ref s) => s.clone(), - Error::ClientError(ref s) => s.clone(), - Error::Config(ref e) => format!("Failed to {}", e.clone()), - Error::JsonEncode(ref e) => format!("Failed to encode JSON: {}", e.clone()), - Error::JsonDecode(ref e) => format!("Failed to decode JSON: {}", e.clone()), - Error::Io(ref e) => format!("IO Error{:?}", e.clone()), - }; - write!(f, "{}", inner) - } -} - -impl Display for OtaReason { - fn fmt(&self, f: &mut Formatter) -> FmtResult { - let inner: String = match *self { - OtaReason::CreateFile(ref f, ref e) => - format!("failed to create file {:?}: {}", f.clone(), e.clone()), - OtaReason::Client(ref r, ref e) => - format!("the request: {},\nresults in the following error: {}", r.clone(), e.clone()), - }; - write!(f, "{}", inner) - } -} - -impl Display for ConfigReason { - fn fmt(&self, f: &mut Formatter) -> FmtResult { - let inner: String = match *self { - ConfigReason::Parse(ref e) => format!("parse config: {}", e.clone()), - ConfigReason::Io (ref e) => format!("load config: {}", e.clone()) - }; - write!(f, "{}", inner) - } -} - - -impl Display for ParseReason { - fn fmt(&self, f: &mut Formatter) -> FmtResult { - let inner: String = match *self { - ParseReason::InvalidToml => "invalid toml".to_string(), - ParseReason::InvalidSection(ref s) => format!("invalid section: {}", s), - }; - write!(f, "{}", inner) - } -} - -#[macro_export] -macro_rules! exit { - ($fmt:expr) => ({ - print!(concat!($fmt, "\n")); - std::process::exit(1); - }); - ($fmt:expr, $($arg:tt)*) => ({ - print!(concat!($fmt, "\n"), $($arg)*); - std::process::exit(1); - }) -} diff --git a/src/http_client.rs b/src/http_client.rs index 7123bda..e6053c1 100644 --- a/src/http_client.rs +++ b/src/http_client.rs @@ -1,11 +1,11 @@ -use hyper; -use hyper::method::Method; -use hyper::header::{Headers, Header, HeaderFormat}; use hyper::Url; -use error::Error; - +use hyper::header::{Headers, Header, HeaderFormat}; +use hyper::method::Method; +use hyper; use std::io::{Read, Write, BufReader, BufWriter}; +use datatype::error::Error; + #[derive(Clone, Debug)] pub struct HttpRequest<'a> { diff --git a/src/lib.rs b/src/lib.rs index 846b730..a8bdf1d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,19 +1,13 @@ -#[macro_use] -extern crate log; - extern crate hyper; +#[macro_use] extern crate log; extern crate rustc_serialize; extern crate tempfile; extern crate toml; -pub mod access_token; +pub mod auth_plus; pub mod bad_http_client; -pub mod config; -pub mod read_interpret; +pub mod datatype; +pub mod http_client; pub mod ota_plus; -pub mod auth_plus; -pub mod package; pub mod package_manager; -pub mod error; -pub mod update_request; -pub mod http_client; +pub mod read_interpret; diff --git a/src/main.rs b/src/main.rs index 3f34ccd..79229fe 100644 --- a/src/main.rs +++ b/src/main.rs @@ -8,14 +8,15 @@ use hyper::Url; use std::env; use std::path::PathBuf; -use libotaplus::{config, read_interpret}; -use libotaplus::config::Config; -use libotaplus::http_client::HttpClient; -use libotaplus::read_interpret::ReplEnv; use libotaplus::auth_plus::authenticate; +use libotaplus::datatype::config; +use libotaplus::datatype::config::Config; +use libotaplus::datatype::error::Error; +use libotaplus::http_client::HttpClient; use libotaplus::ota_plus::{post_packages, get_package_updates, download_package_update}; use libotaplus::package_manager::{PackageManager, Dpkg}; -use libotaplus::error::Error; +use libotaplus::read_interpret::ReplEnv; +use libotaplus::read_interpret; fn main() { diff --git a/src/ota_plus.rs b/src/ota_plus.rs index 84192aa..083d041 100644 --- a/src/ota_plus.rs +++ b/src/ota_plus.rs @@ -6,13 +6,13 @@ use std::fs::File; use std::path::PathBuf; use std::result::Result; -use access_token::AccessToken; -use config::OtaConfig; -use error::Error; -use error::OtaReason::{CreateFile, Client}; +use datatype::access_token::AccessToken; +use datatype::config::OtaConfig; +use datatype::error::Error; +use datatype::error::OtaReason::{CreateFile, Client}; +use datatype::package::Package; +use datatype::update_request::UpdateRequestId; use http_client::{HttpClient, HttpRequest}; -use package::Package; -use update_request::UpdateRequestId; fn vehicle_endpoint(config: &OtaConfig, s: &str) -> Url { @@ -79,12 +79,12 @@ mod tests { use std::io::Write; use super::*; - use access_token::AccessToken; use bad_http_client::BadHttpClient; - use config::OtaConfig; - use error::Error; + use datatype::access_token::AccessToken; + use datatype::config::OtaConfig; + use datatype::error::Error; + use datatype::package::Package; use http_client::{HttpRequest, HttpClient}; - use package::Package; fn test_token() -> AccessToken { diff --git a/src/package.rs b/src/package.rs deleted file mode 100644 index 65fffb7..0000000 --- a/src/package.rs +++ /dev/null @@ -1,15 +0,0 @@ -use std::fmt::{Display, Formatter, Result as FmtResult}; - -pub type Version = String; - -#[derive(Debug, PartialEq, Eq, RustcEncodable, RustcDecodable)] -pub struct Package { - pub name: String, - pub version: Version -} - -impl Display for Package { - fn fmt(&self, f: &mut Formatter) -> FmtResult { - write!(f, "{} {}", self.name, self.version) - } -} diff --git a/src/package_manager/dpkg.rs b/src/package_manager/dpkg.rs index 9eee2d2..adee6cb 100644 --- a/src/package_manager/dpkg.rs +++ b/src/package_manager/dpkg.rs @@ -1,8 +1,9 @@ +use std::process::Command; + +use datatype::error::Error; +use datatype::package::Package; use package_manager; -use package::Package; -use error::Error; -use std::process::Command; #[allow(dead_code)] pub struct Dpkg { a: u16 } // remove dummy field once braced_empty_structs feature is in stable @@ -48,7 +49,7 @@ pub fn parse_package(line: &str) -> Result { mod tests { use super::*; - use package::Package; + use datatype::package::Package; #[test] fn test_parses_normal_package() { diff --git a/src/package_manager/mod.rs b/src/package_manager/mod.rs index aa2df8c..b7d22de 100644 --- a/src/package_manager/mod.rs +++ b/src/package_manager/mod.rs @@ -1,5 +1,5 @@ -use package::Package; -use error::Error; +use datatype::error::Error; +use datatype::package::Package; pub trait PackageManager { fn new() -> Self; diff --git a/src/update_request.rs b/src/update_request.rs deleted file mode 100644 index 56b82aa..0000000 --- a/src/update_request.rs +++ /dev/null @@ -1 +0,0 @@ -pub type UpdateRequestId = String; diff --git a/tests/ota_plus_client_tests.rs b/tests/ota_plus_client_tests.rs index 5f26b63..49f4b13 100644 --- a/tests/ota_plus_client_tests.rs +++ b/tests/ota_plus_client_tests.rs @@ -67,7 +67,9 @@ fn bad_ota_server_url() { #[test] fn no_auth_server_to_connect_to() { assert_eq!(client(&[""]), - "Authentication error, didn't receive access token: connection refused\n") + r#"Trying to acquire access token. +Authentication error, didn't receive access token: connection refused +"#) } #[test] -- cgit v1.2.1 From 29a22da6b79c3345021ace99ea87109098b7b564 Mon Sep 17 00:00:00 2001 From: Stevan Andjelkovic Date: Wed, 23 Mar 2016 17:28:12 +0100 Subject: Move http client stuff into its own module. --- src/auth_plus.rs | 6 +- src/bad_http_client.rs | 23 ------- src/http_client.rs | 136 ------------------------------------- src/http_client/bad_http_client.rs | 23 +++++++ src/http_client/interface.rs | 136 +++++++++++++++++++++++++++++++++++++ src/http_client/mod.rs | 2 + src/lib.rs | 1 - src/main.rs | 2 +- src/ota_plus.rs | 6 +- 9 files changed, 168 insertions(+), 167 deletions(-) delete mode 100644 src/bad_http_client.rs delete mode 100644 src/http_client.rs create mode 100644 src/http_client/bad_http_client.rs create mode 100644 src/http_client/interface.rs create mode 100644 src/http_client/mod.rs diff --git a/src/auth_plus.rs b/src/auth_plus.rs index 67e8ad5..f13c397 100644 --- a/src/auth_plus.rs +++ b/src/auth_plus.rs @@ -5,7 +5,7 @@ use rustc_serialize::json; use datatype::access_token::AccessToken; use datatype::config::AuthConfig; use datatype::error::Error; -use http_client::{HttpClient, HttpRequest}; +use http_client::interface::{HttpClient, HttpRequest}; pub fn authenticate(config: &AuthConfig) -> Result { @@ -34,11 +34,11 @@ mod tests { use std::io::Write; use super::*; - use bad_http_client::BadHttpClient; use datatype::access_token::AccessToken; use datatype::config::AuthConfig; use datatype::error::Error; - use http_client::{HttpRequest, HttpClient}; + use http_client::bad_http_client::BadHttpClient; + use http_client::interface::{HttpRequest, HttpClient}; struct MockClient; diff --git a/src/bad_http_client.rs b/src/bad_http_client.rs deleted file mode 100644 index a15941b..0000000 --- a/src/bad_http_client.rs +++ /dev/null @@ -1,23 +0,0 @@ -use std::io::Write; - -use datatype::error::Error; -use http_client::{HttpClient, HttpRequest}; - - -pub struct BadHttpClient; - -impl HttpClient for BadHttpClient { - - fn new() -> BadHttpClient { - BadHttpClient - } - - fn send_request(&self, _: &HttpRequest) -> Result { - Err(Error::ClientError("bad client.".to_string())) - } - - fn send_request_to(&self, _: &HttpRequest, _: W) -> Result<(), Error> { - Err(Error::ClientError("bad client.".to_string())) - } - -} diff --git a/src/http_client.rs b/src/http_client.rs deleted file mode 100644 index e6053c1..0000000 --- a/src/http_client.rs +++ /dev/null @@ -1,136 +0,0 @@ -use hyper::Url; -use hyper::header::{Headers, Header, HeaderFormat}; -use hyper::method::Method; -use hyper; -use std::io::{Read, Write, BufReader, BufWriter}; - -use datatype::error::Error; - - -#[derive(Clone, Debug)] -pub struct HttpRequest<'a> { - pub url: Url, - pub method: Method, - pub headers: Headers, - pub body: Option<&'a str> -} - -impl<'a> ToString for HttpRequest<'a> { - fn to_string(&self) -> String { - return format!("{} {}", self.method, self.url.serialize()) - } -} - -impl<'a> HttpRequest<'a> { - pub fn new(url: Url, method: Method) -> HttpRequest<'a> { - HttpRequest { url: url, method: method, headers: Headers::new(), body: None } - } - - pub fn get(url: Url) -> HttpRequest<'a> { - HttpRequest::new(url, Method::Get) - } - - pub fn post(url: Url) -> HttpRequest<'a> { - HttpRequest::new(url, Method::Post) - } - - pub fn with_body(&self, body: &'a str) -> HttpRequest<'a> { - HttpRequest { body: Some(body), ..self.clone() } - } - - pub fn with_header(&self, header: H) -> HttpRequest<'a> { - let mut hs = self.headers.clone(); - hs.set(header); - HttpRequest { headers: hs, ..self.clone() } - } -} - -pub trait HttpClient { - fn new() -> Self; - fn send_request(&self, req: &HttpRequest) -> Result; - fn send_request_to(&self, req: &HttpRequest, to: W) -> Result<(), Error>; -} - -impl HttpClient for hyper::Client { - - fn new() -> hyper::Client { - hyper::Client::new() - } - - fn send_request(&self, req: &HttpRequest) -> Result { - self.request(req.method.clone(), req.url.clone()) - .headers(req.headers.clone()) - .body(if let Some(body) = req.body { body } else { "" }) - .send() - .map_err(|e| { - Error::ClientError(format!("{}", e)) - }) - .and_then(|mut resp| { - let mut rbody = String::new(); - let status = resp.status; - if status.is_server_error() || status.is_client_error() { - Err(Error::ClientError(format!("Request errored with status {}", status))) - } else { - resp.read_to_string(&mut rbody) - .map_err(|e| Error::ParseError(format!("Cannot read response: {}", e))) - .map(|_| rbody) - } - }) - } - - fn send_request_to(&self, req: &HttpRequest, to: W) -> Result<(), Error> { - self.request(req.method.clone(), req.url.clone()) - .headers(req.headers.clone()) - .body(if let Some(body) = req.body { body } else { "" }) - .send() - .map_err(|e| { - Error::ClientError(format!("Cannot send request: {}", e)) - }) - .and_then(|resp| { - let status = resp.status; - if status.is_server_error() || status.is_client_error() { - Err(Error::ClientError(format!("Request errored with status {}", status))) - } else { - tee(resp, to) - .map_err(|e| Error::ParseError(format!("Cannot read response: {}", e))) - .map(|_| ()) - } - }) - } -} - -pub fn tee(from: R, to: W) -> Result<(), Error> { - let mb = 1024 * 1024; - let rbuf = BufReader::with_capacity(5 * mb, from); - let mut wbuf = BufWriter::with_capacity(5 * mb, to); - for b in rbuf.bytes() { - try!(wbuf.write(&[try!(b)])); - } - Ok(()) -} - -#[cfg(test)] -mod tests { - use super::*; - use std::fs::File; - use std::io::{Read, repeat}; - - #[test] - fn test_tee() { - let values = repeat(b'a').take(9000); - let sink = File::create("/tmp/otaplus_tee_test").unwrap(); - - assert!(tee(values, sink).is_ok()); - - let mut values2 = repeat(b'a').take(9000); - let mut expected = Vec::new(); - let _ = values2.read_to_end(&mut expected); - - let mut f = File::open("/tmp/otaplus_tee_test").unwrap(); - let mut result = Vec::new(); - let _ = f.read_to_end(&mut result); - - assert_eq!(result, expected); - } - -} diff --git a/src/http_client/bad_http_client.rs b/src/http_client/bad_http_client.rs new file mode 100644 index 0000000..3b95a50 --- /dev/null +++ b/src/http_client/bad_http_client.rs @@ -0,0 +1,23 @@ +use std::io::Write; + +use datatype::error::Error; +use http_client::interface::{HttpClient, HttpRequest}; + + +pub struct BadHttpClient; + +impl HttpClient for BadHttpClient { + + fn new() -> BadHttpClient { + BadHttpClient + } + + fn send_request(&self, _: &HttpRequest) -> Result { + Err(Error::ClientError("bad client.".to_string())) + } + + fn send_request_to(&self, _: &HttpRequest, _: W) -> Result<(), Error> { + Err(Error::ClientError("bad client.".to_string())) + } + +} diff --git a/src/http_client/interface.rs b/src/http_client/interface.rs new file mode 100644 index 0000000..e6053c1 --- /dev/null +++ b/src/http_client/interface.rs @@ -0,0 +1,136 @@ +use hyper::Url; +use hyper::header::{Headers, Header, HeaderFormat}; +use hyper::method::Method; +use hyper; +use std::io::{Read, Write, BufReader, BufWriter}; + +use datatype::error::Error; + + +#[derive(Clone, Debug)] +pub struct HttpRequest<'a> { + pub url: Url, + pub method: Method, + pub headers: Headers, + pub body: Option<&'a str> +} + +impl<'a> ToString for HttpRequest<'a> { + fn to_string(&self) -> String { + return format!("{} {}", self.method, self.url.serialize()) + } +} + +impl<'a> HttpRequest<'a> { + pub fn new(url: Url, method: Method) -> HttpRequest<'a> { + HttpRequest { url: url, method: method, headers: Headers::new(), body: None } + } + + pub fn get(url: Url) -> HttpRequest<'a> { + HttpRequest::new(url, Method::Get) + } + + pub fn post(url: Url) -> HttpRequest<'a> { + HttpRequest::new(url, Method::Post) + } + + pub fn with_body(&self, body: &'a str) -> HttpRequest<'a> { + HttpRequest { body: Some(body), ..self.clone() } + } + + pub fn with_header(&self, header: H) -> HttpRequest<'a> { + let mut hs = self.headers.clone(); + hs.set(header); + HttpRequest { headers: hs, ..self.clone() } + } +} + +pub trait HttpClient { + fn new() -> Self; + fn send_request(&self, req: &HttpRequest) -> Result; + fn send_request_to(&self, req: &HttpRequest, to: W) -> Result<(), Error>; +} + +impl HttpClient for hyper::Client { + + fn new() -> hyper::Client { + hyper::Client::new() + } + + fn send_request(&self, req: &HttpRequest) -> Result { + self.request(req.method.clone(), req.url.clone()) + .headers(req.headers.clone()) + .body(if let Some(body) = req.body { body } else { "" }) + .send() + .map_err(|e| { + Error::ClientError(format!("{}", e)) + }) + .and_then(|mut resp| { + let mut rbody = String::new(); + let status = resp.status; + if status.is_server_error() || status.is_client_error() { + Err(Error::ClientError(format!("Request errored with status {}", status))) + } else { + resp.read_to_string(&mut rbody) + .map_err(|e| Error::ParseError(format!("Cannot read response: {}", e))) + .map(|_| rbody) + } + }) + } + + fn send_request_to(&self, req: &HttpRequest, to: W) -> Result<(), Error> { + self.request(req.method.clone(), req.url.clone()) + .headers(req.headers.clone()) + .body(if let Some(body) = req.body { body } else { "" }) + .send() + .map_err(|e| { + Error::ClientError(format!("Cannot send request: {}", e)) + }) + .and_then(|resp| { + let status = resp.status; + if status.is_server_error() || status.is_client_error() { + Err(Error::ClientError(format!("Request errored with status {}", status))) + } else { + tee(resp, to) + .map_err(|e| Error::ParseError(format!("Cannot read response: {}", e))) + .map(|_| ()) + } + }) + } +} + +pub fn tee(from: R, to: W) -> Result<(), Error> { + let mb = 1024 * 1024; + let rbuf = BufReader::with_capacity(5 * mb, from); + let mut wbuf = BufWriter::with_capacity(5 * mb, to); + for b in rbuf.bytes() { + try!(wbuf.write(&[try!(b)])); + } + Ok(()) +} + +#[cfg(test)] +mod tests { + use super::*; + use std::fs::File; + use std::io::{Read, repeat}; + + #[test] + fn test_tee() { + let values = repeat(b'a').take(9000); + let sink = File::create("/tmp/otaplus_tee_test").unwrap(); + + assert!(tee(values, sink).is_ok()); + + let mut values2 = repeat(b'a').take(9000); + let mut expected = Vec::new(); + let _ = values2.read_to_end(&mut expected); + + let mut f = File::open("/tmp/otaplus_tee_test").unwrap(); + let mut result = Vec::new(); + let _ = f.read_to_end(&mut result); + + assert_eq!(result, expected); + } + +} diff --git a/src/http_client/mod.rs b/src/http_client/mod.rs new file mode 100644 index 0000000..84e2783 --- /dev/null +++ b/src/http_client/mod.rs @@ -0,0 +1,2 @@ +pub mod bad_http_client; +pub mod interface; diff --git a/src/lib.rs b/src/lib.rs index a8bdf1d..e8911c0 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -5,7 +5,6 @@ extern crate tempfile; extern crate toml; pub mod auth_plus; -pub mod bad_http_client; pub mod datatype; pub mod http_client; pub mod ota_plus; diff --git a/src/main.rs b/src/main.rs index 79229fe..81a6fe5 100644 --- a/src/main.rs +++ b/src/main.rs @@ -12,7 +12,7 @@ use libotaplus::auth_plus::authenticate; use libotaplus::datatype::config; use libotaplus::datatype::config::Config; use libotaplus::datatype::error::Error; -use libotaplus::http_client::HttpClient; +use libotaplus::http_client::interface::HttpClient; use libotaplus::ota_plus::{post_packages, get_package_updates, download_package_update}; use libotaplus::package_manager::{PackageManager, Dpkg}; use libotaplus::read_interpret::ReplEnv; diff --git a/src/ota_plus.rs b/src/ota_plus.rs index 083d041..d3568c4 100644 --- a/src/ota_plus.rs +++ b/src/ota_plus.rs @@ -12,7 +12,7 @@ use datatype::error::Error; use datatype::error::OtaReason::{CreateFile, Client}; use datatype::package::Package; use datatype::update_request::UpdateRequestId; -use http_client::{HttpClient, HttpRequest}; +use http_client::interface::{HttpClient, HttpRequest}; fn vehicle_endpoint(config: &OtaConfig, s: &str) -> Url { @@ -79,12 +79,12 @@ mod tests { use std::io::Write; use super::*; - use bad_http_client::BadHttpClient; use datatype::access_token::AccessToken; use datatype::config::OtaConfig; use datatype::error::Error; use datatype::package::Package; - use http_client::{HttpRequest, HttpClient}; + use http_client::bad_http_client::BadHttpClient; + use http_client::interface::{HttpRequest, HttpClient}; fn test_token() -> AccessToken { -- cgit v1.2.1 From 7dc73b086efe0ba10c933d1480dada90e69a8b51 Mon Sep 17 00:00:00 2001 From: Stevan Andjelkovic Date: Thu, 24 Mar 2016 10:48:52 +0100 Subject: Less strings for json errors. --- src/datatype/error.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/datatype/error.rs b/src/datatype/error.rs index 85f5239..2ea3935 100644 --- a/src/datatype/error.rs +++ b/src/datatype/error.rs @@ -13,20 +13,20 @@ pub enum Error { PackageError(String), ClientError(String), Config(ConfigReason), - JsonEncode(String), - JsonDecode(String), + JsonEncode(json::EncoderError), + JsonDecode(json::DecoderError), Io(io::Error) } impl From for Error { fn from(e: json::EncoderError) -> Error { - Error::JsonEncode(format!("{}", e)) + Error::JsonEncode(e) } } impl From for Error { fn from(e: json::DecoderError) -> Error { - Error::JsonDecode(format!("{}", e)) + Error::JsonDecode(e) } } -- cgit v1.2.1 From aa1400440431b9fff281380ec2f6b48a89aab83b Mon Sep 17 00:00:00 2001 From: Stevan Andjelkovic Date: Thu, 24 Mar 2016 11:06:39 +0100 Subject: Move package manager interface to its own file. --- src/package_manager/interface.rs | 8 ++++++++ src/package_manager/mod.rs | 10 ++-------- 2 files changed, 10 insertions(+), 8 deletions(-) create mode 100644 src/package_manager/interface.rs diff --git a/src/package_manager/interface.rs b/src/package_manager/interface.rs new file mode 100644 index 0000000..a219500 --- /dev/null +++ b/src/package_manager/interface.rs @@ -0,0 +1,8 @@ +use datatype::error::Error; +use datatype::package::Package; + + +pub trait PackageManager { + fn new() -> Self; + fn installed_packages(&self) -> Result, Error>; +} diff --git a/src/package_manager/mod.rs b/src/package_manager/mod.rs index b7d22de..2e23238 100644 --- a/src/package_manager/mod.rs +++ b/src/package_manager/mod.rs @@ -1,11 +1,5 @@ -use datatype::error::Error; -use datatype::package::Package; - -pub trait PackageManager { - fn new() -> Self; - fn installed_packages(&self) -> Result, Error>; -} - pub use self::dpkg::Dpkg; +pub use self::interface::PackageManager; pub mod dpkg; +pub mod interface; -- cgit v1.2.1 From f969083b322e14f4f5ae0466cec1d462203ad634 Mon Sep 17 00:00:00 2001 From: Stevan Andjelkovic Date: Thu, 24 Mar 2016 11:12:49 +0100 Subject: Make better use of module rexports. --- src/auth_plus.rs | 6 +++--- src/http_client/bad_http_client.rs | 2 +- src/http_client/mod.rs | 3 +++ src/main.rs | 2 +- src/ota_plus.rs | 6 +++--- 5 files changed, 11 insertions(+), 8 deletions(-) diff --git a/src/auth_plus.rs b/src/auth_plus.rs index f13c397..5f86f71 100644 --- a/src/auth_plus.rs +++ b/src/auth_plus.rs @@ -5,7 +5,7 @@ use rustc_serialize::json; use datatype::access_token::AccessToken; use datatype::config::AuthConfig; use datatype::error::Error; -use http_client::interface::{HttpClient, HttpRequest}; +use http_client::{HttpClient, HttpRequest}; pub fn authenticate(config: &AuthConfig) -> Result { @@ -37,8 +37,8 @@ mod tests { use datatype::access_token::AccessToken; use datatype::config::AuthConfig; use datatype::error::Error; - use http_client::bad_http_client::BadHttpClient; - use http_client::interface::{HttpRequest, HttpClient}; + use http_client::BadHttpClient; + use http_client::{HttpRequest, HttpClient}; struct MockClient; diff --git a/src/http_client/bad_http_client.rs b/src/http_client/bad_http_client.rs index 3b95a50..a15941b 100644 --- a/src/http_client/bad_http_client.rs +++ b/src/http_client/bad_http_client.rs @@ -1,7 +1,7 @@ use std::io::Write; use datatype::error::Error; -use http_client::interface::{HttpClient, HttpRequest}; +use http_client::{HttpClient, HttpRequest}; pub struct BadHttpClient; diff --git a/src/http_client/mod.rs b/src/http_client/mod.rs index 84e2783..6c8dd7c 100644 --- a/src/http_client/mod.rs +++ b/src/http_client/mod.rs @@ -1,2 +1,5 @@ +pub use self::bad_http_client::BadHttpClient; +pub use self::interface::{HttpClient, HttpRequest}; + pub mod bad_http_client; pub mod interface; diff --git a/src/main.rs b/src/main.rs index 81a6fe5..79229fe 100644 --- a/src/main.rs +++ b/src/main.rs @@ -12,7 +12,7 @@ use libotaplus::auth_plus::authenticate; use libotaplus::datatype::config; use libotaplus::datatype::config::Config; use libotaplus::datatype::error::Error; -use libotaplus::http_client::interface::HttpClient; +use libotaplus::http_client::HttpClient; use libotaplus::ota_plus::{post_packages, get_package_updates, download_package_update}; use libotaplus::package_manager::{PackageManager, Dpkg}; use libotaplus::read_interpret::ReplEnv; diff --git a/src/ota_plus.rs b/src/ota_plus.rs index d3568c4..bc5c8ce 100644 --- a/src/ota_plus.rs +++ b/src/ota_plus.rs @@ -12,7 +12,7 @@ use datatype::error::Error; use datatype::error::OtaReason::{CreateFile, Client}; use datatype::package::Package; use datatype::update_request::UpdateRequestId; -use http_client::interface::{HttpClient, HttpRequest}; +use http_client::{HttpClient, HttpRequest}; fn vehicle_endpoint(config: &OtaConfig, s: &str) -> Url { @@ -83,8 +83,8 @@ mod tests { use datatype::config::OtaConfig; use datatype::error::Error; use datatype::package::Package; - use http_client::bad_http_client::BadHttpClient; - use http_client::interface::{HttpRequest, HttpClient}; + use http_client::BadHttpClient; + use http_client::{HttpRequest, HttpClient}; fn test_token() -> AccessToken { -- cgit v1.2.1 From 61a4c3c4ea6b0c92fc04c239991eb7049869ee40 Mon Sep 17 00:00:00 2001 From: Stevan Andjelkovic Date: Thu, 24 Mar 2016 11:33:39 +0100 Subject: Make use of reexports for datatypes. --- src/auth_plus.rs | 12 ++++++------ src/datatype/config.rs | 2 +- src/datatype/mod.rs | 6 ++++++ src/http_client/bad_http_client.rs | 2 +- src/http_client/interface.rs | 2 +- src/main.rs | 4 ++-- src/ota_plus.rs | 18 +++++++++--------- src/package_manager/dpkg.rs | 6 +++--- src/package_manager/interface.rs | 4 ++-- 9 files changed, 31 insertions(+), 25 deletions(-) diff --git a/src/auth_plus.rs b/src/auth_plus.rs index 5f86f71..df3ea6a 100644 --- a/src/auth_plus.rs +++ b/src/auth_plus.rs @@ -2,9 +2,9 @@ use hyper::header::{Authorization, Basic, ContentType}; use hyper::mime::{Mime, TopLevel, SubLevel, Attr, Value}; use rustc_serialize::json; -use datatype::access_token::AccessToken; -use datatype::config::AuthConfig; -use datatype::error::Error; +use datatype::AccessToken; +use datatype::AuthConfig; +use datatype::Error; use http_client::{HttpClient, HttpRequest}; @@ -34,9 +34,9 @@ mod tests { use std::io::Write; use super::*; - use datatype::access_token::AccessToken; - use datatype::config::AuthConfig; - use datatype::error::Error; + use datatype::AccessToken; + use datatype::AuthConfig; + use datatype::Error; use http_client::BadHttpClient; use http_client::{HttpRequest, HttpClient}; diff --git a/src/datatype/config.rs b/src/datatype/config.rs index 49f876b..98edf87 100644 --- a/src/datatype/config.rs +++ b/src/datatype/config.rs @@ -5,7 +5,7 @@ use std::io::ErrorKind; use std::io::prelude::*; use toml; -use datatype::error::Error; +use datatype::Error; use datatype::error::ConfigReason::{Parse, Io}; use datatype::error::ParseReason::{InvalidToml, InvalidSection}; diff --git a/src/datatype/mod.rs b/src/datatype/mod.rs index 97264d6..77e730c 100644 --- a/src/datatype/mod.rs +++ b/src/datatype/mod.rs @@ -1,3 +1,9 @@ +pub use self::access_token::AccessToken; +pub use self::config::{Config, AuthConfig, OtaConfig, TestConfig}; +pub use self::error::Error; +pub use self::package::Package; +pub use self::update_request::UpdateRequestId; + pub mod access_token; pub mod config; pub mod error; diff --git a/src/http_client/bad_http_client.rs b/src/http_client/bad_http_client.rs index a15941b..8e3b000 100644 --- a/src/http_client/bad_http_client.rs +++ b/src/http_client/bad_http_client.rs @@ -1,6 +1,6 @@ use std::io::Write; -use datatype::error::Error; +use datatype::Error; use http_client::{HttpClient, HttpRequest}; diff --git a/src/http_client/interface.rs b/src/http_client/interface.rs index e6053c1..1ea1577 100644 --- a/src/http_client/interface.rs +++ b/src/http_client/interface.rs @@ -4,7 +4,7 @@ use hyper::method::Method; use hyper; use std::io::{Read, Write, BufReader, BufWriter}; -use datatype::error::Error; +use datatype::Error; #[derive(Clone, Debug)] diff --git a/src/main.rs b/src/main.rs index 79229fe..599bdd0 100644 --- a/src/main.rs +++ b/src/main.rs @@ -10,8 +10,8 @@ use std::path::PathBuf; use libotaplus::auth_plus::authenticate; use libotaplus::datatype::config; -use libotaplus::datatype::config::Config; -use libotaplus::datatype::error::Error; +use libotaplus::datatype::Config; +use libotaplus::datatype::Error; use libotaplus::http_client::HttpClient; use libotaplus::ota_plus::{post_packages, get_package_updates, download_package_update}; use libotaplus::package_manager::{PackageManager, Dpkg}; diff --git a/src/ota_plus.rs b/src/ota_plus.rs index bc5c8ce..b12aad0 100644 --- a/src/ota_plus.rs +++ b/src/ota_plus.rs @@ -6,12 +6,12 @@ use std::fs::File; use std::path::PathBuf; use std::result::Result; -use datatype::access_token::AccessToken; -use datatype::config::OtaConfig; -use datatype::error::Error; +use datatype::AccessToken; +use datatype::OtaConfig; +use datatype::Error; use datatype::error::OtaReason::{CreateFile, Client}; -use datatype::package::Package; -use datatype::update_request::UpdateRequestId; +use datatype::Package; +use datatype::UpdateRequestId; use http_client::{HttpClient, HttpRequest}; @@ -79,10 +79,10 @@ mod tests { use std::io::Write; use super::*; - use datatype::access_token::AccessToken; - use datatype::config::OtaConfig; - use datatype::error::Error; - use datatype::package::Package; + use datatype::AccessToken; + use datatype::OtaConfig; + use datatype::Error; + use datatype::Package; use http_client::BadHttpClient; use http_client::{HttpRequest, HttpClient}; diff --git a/src/package_manager/dpkg.rs b/src/package_manager/dpkg.rs index adee6cb..f344173 100644 --- a/src/package_manager/dpkg.rs +++ b/src/package_manager/dpkg.rs @@ -1,7 +1,7 @@ use std::process::Command; -use datatype::error::Error; -use datatype::package::Package; +use datatype::Error; +use datatype::Package; use package_manager; @@ -49,7 +49,7 @@ pub fn parse_package(line: &str) -> Result { mod tests { use super::*; - use datatype::package::Package; + use datatype::Package; #[test] fn test_parses_normal_package() { diff --git a/src/package_manager/interface.rs b/src/package_manager/interface.rs index a219500..7e80a2a 100644 --- a/src/package_manager/interface.rs +++ b/src/package_manager/interface.rs @@ -1,5 +1,5 @@ -use datatype::error::Error; -use datatype::package::Package; +use datatype::Error; +use datatype::Package; pub trait PackageManager { -- cgit v1.2.1 From c3024576da023817b1feca628625ab4162811497 Mon Sep 17 00:00:00 2001 From: Stevan Andjelkovic Date: Thu, 24 Mar 2016 11:45:49 +0100 Subject: Add readme. --- README.md | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 README.md diff --git a/README.md b/README.md new file mode 100644 index 0000000..2816ff9 --- /dev/null +++ b/README.md @@ -0,0 +1,15 @@ +# OTA+ client + +The OTA+ client source repository. + +## Prerequisites + +* Rust stable +* Cargo + +## Build instructions + +To build and test the project simply issue: + + cargo build + cargo test \ No newline at end of file -- cgit v1.2.1 From 751634525aed27cd99d3f11d8a94da66131227f0 Mon Sep 17 00:00:00 2001 From: Txus Date: Thu, 17 Mar 2016 18:04:11 +0100 Subject: Install new update (on behalf of Txus, Stevan) --- src/datatype/error.rs | 6 ++++++ src/main.rs | 18 ++++++++++-------- src/package_manager/dpkg.rs | 29 ++++++++++++++++++----------- src/package_manager/interface.rs | 3 +++ 4 files changed, 37 insertions(+), 19 deletions(-) diff --git a/src/datatype/error.rs b/src/datatype/error.rs index 2ea3935..663e94f 100644 --- a/src/datatype/error.rs +++ b/src/datatype/error.rs @@ -42,6 +42,12 @@ pub enum OtaReason { Client(String, String), } +#[derive(Debug)] +pub enum PackageManagerReason { + FileDoesNotExist(String), + InstallFailed(String) +} + #[derive(Debug)] pub enum ConfigReason { Parse(ParseReason), diff --git a/src/main.rs b/src/main.rs index 599bdd0..5b780d5 100644 --- a/src/main.rs +++ b/src/main.rs @@ -6,7 +6,6 @@ extern crate hyper; use getopts::Options; use hyper::Url; use std::env; -use std::path::PathBuf; use libotaplus::auth_plus::authenticate; use libotaplus::datatype::config; @@ -26,11 +25,8 @@ fn main() { let config = build_config(); match worker::(&config) { - Err(e) => exit!("{}", e), - Ok(paths) => { - println!("All good. Downloaded {:?}. See you again soon!", paths); - println!("Installed packages were posted successfully."); - } + Err(e) => exit!("{}", e), + Ok(()) => println!("Installed packages were posted successfully."), } if config.test.looping { @@ -39,7 +35,7 @@ fn main() { } -fn worker(config: &Config) -> Result, Error> { +fn worker(config: &Config) -> Result<(), Error> { println!("Trying to acquire access token."); let token = try!(authenticate::(&config.auth)); @@ -66,7 +62,13 @@ fn worker(config: &Config) -> Result Dpkg { - Dpkg { a: 0 } - } -} - -impl package_manager::PackageManager for Dpkg { +impl PackageManager for Dpkg { fn new() -> Dpkg { - return Dpkg::new(); + return Dpkg } fn installed_packages(&self) -> Result, Error> { @@ -35,6 +29,19 @@ impl package_manager::PackageManager for Dpkg { .collect::, _>>() }) } + + fn install_package(&self, path: &Path) -> Result<(), Error> { + + let output = try!(Command::new("dpkg").arg("-i") + .arg(path.to_str().unwrap()) + .output()); + + String::from_utf8(output.stdout) + .map(|o| println!("{}", o)) + .map_err(|e| Error::ParseError(format!("Error parsing package manager output: {}", e))) + + } + } pub fn parse_package(line: &str) -> Result { diff --git a/src/package_manager/interface.rs b/src/package_manager/interface.rs index 7e80a2a..ef7091b 100644 --- a/src/package_manager/interface.rs +++ b/src/package_manager/interface.rs @@ -1,3 +1,5 @@ +use std::path::Path; + use datatype::Error; use datatype::Package; @@ -5,4 +7,5 @@ use datatype::Package; pub trait PackageManager { fn new() -> Self; fn installed_packages(&self) -> Result, Error>; + fn install_package(&self, path: &Path) -> Result<(), Error>; } -- cgit v1.2.1 From 28fe78e4efa066d6d88eb05f94170f62afe5d1b2 Mon Sep 17 00:00:00 2001 From: Stevan Andjelkovic Date: Thu, 24 Mar 2016 17:36:04 +0100 Subject: Remove unused package manager reason. --- src/datatype/error.rs | 6 ------ 1 file changed, 6 deletions(-) diff --git a/src/datatype/error.rs b/src/datatype/error.rs index 663e94f..2ea3935 100644 --- a/src/datatype/error.rs +++ b/src/datatype/error.rs @@ -42,12 +42,6 @@ pub enum OtaReason { Client(String, String), } -#[derive(Debug)] -pub enum PackageManagerReason { - FileDoesNotExist(String), - InstallFailed(String) -} - #[derive(Debug)] pub enum ConfigReason { Parse(ParseReason), -- cgit v1.2.1 From 171a4b6e190af5d5d2259176ee54259f1df33948 Mon Sep 17 00:00:00 2001 From: Stevan Andjelkovic Date: Tue, 29 Mar 2016 10:59:44 +0200 Subject: Fix mistake in worker. --- src/main.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main.rs b/src/main.rs index 5b780d5..8bbb4be 100644 --- a/src/main.rs +++ b/src/main.rs @@ -48,7 +48,7 @@ fn worker(config: &Config) -> Result<(), Error try!(post_packages::(&token, &config.ota, &pkgs)); println!("Fetching possible new package updates."); - let updates = try!(get_package_updates::(&token, &config.ota)); + let updates = try!(get_package_updates::(&token, &config.ota)); let updates_len = updates.iter().len(); println!("Got {} new updates. Downloading...", updates_len); -- cgit v1.2.1 From baf66588ce1da5925f5651f05dc3a9862cf2efa0 Mon Sep 17 00:00:00 2001 From: Stevan Andjelkovic Date: Tue, 29 Mar 2016 13:06:54 +0200 Subject: Add --package-manager flag. --- ota.toml | 2 +- src/datatype/config.rs | 7 ++++--- src/datatype/mod.rs | 2 ++ src/datatype/package_manager.rs | 26 ++++++++++++++++++++++++++ src/main.rs | 18 ++++++++++++------ tests/ota_plus_client_tests.rs | 9 ++++++++- 6 files changed, 53 insertions(+), 11 deletions(-) create mode 100644 src/datatype/package_manager.rs diff --git a/ota.toml b/ota.toml index f8cbfcf..50726fa 100644 --- a/ota.toml +++ b/ota.toml @@ -7,7 +7,7 @@ secret = "secret" server = "http://127.0.0.1:8080" vin = "V1234567890123456" packages_dir = "/tmp" +package_manager = "dpkg" [test] looping = false -fake_package_manager = false diff --git a/src/datatype/config.rs b/src/datatype/config.rs index 98edf87..71c6df0 100644 --- a/src/datatype/config.rs +++ b/src/datatype/config.rs @@ -8,6 +8,7 @@ use toml; use datatype::Error; use datatype::error::ConfigReason::{Parse, Io}; use datatype::error::ParseReason::{InvalidToml, InvalidSection}; +use datatype::PackageManager; #[derive(Default, PartialEq, Eq, Debug)] @@ -29,12 +30,12 @@ pub struct OtaConfig { pub server: Url, pub vin: String, pub packages_dir: String, + pub package_manager: PackageManager, } #[derive(RustcDecodable, PartialEq, Eq, Debug)] pub struct TestConfig { pub looping: bool, - pub fake_package_manager: bool, } impl Default for AuthConfig { @@ -53,6 +54,7 @@ impl Default for OtaConfig { server: Url::parse("http://127.0.0.1:8080").unwrap(), vin: "V1234567890123456".to_string(), packages_dir: "/tmp".to_string(), + package_manager: PackageManager::Dpkg, } } } @@ -61,7 +63,6 @@ impl Default for TestConfig { fn default() -> TestConfig { TestConfig { looping: false, - fake_package_manager: false, } } } @@ -122,10 +123,10 @@ mod tests { server = "http://127.0.0.1:8080" vin = "V1234567890123456" packages_dir = "/tmp" + package_manager = "dpkg" [test] looping = false - fake_package_manager = false "#; #[test] diff --git a/src/datatype/mod.rs b/src/datatype/mod.rs index 77e730c..aa87985 100644 --- a/src/datatype/mod.rs +++ b/src/datatype/mod.rs @@ -2,10 +2,12 @@ pub use self::access_token::AccessToken; pub use self::config::{Config, AuthConfig, OtaConfig, TestConfig}; pub use self::error::Error; pub use self::package::Package; +pub use self::package_manager::PackageManager; pub use self::update_request::UpdateRequestId; pub mod access_token; pub mod config; pub mod error; pub mod package; +pub mod package_manager; pub mod update_request; diff --git a/src/datatype/package_manager.rs b/src/datatype/package_manager.rs new file mode 100644 index 0000000..ef70589 --- /dev/null +++ b/src/datatype/package_manager.rs @@ -0,0 +1,26 @@ +use rustc_serialize::{Decoder, Decodable}; + + +#[derive(Debug, PartialEq, Eq)] +pub enum PackageManager { + Dpkg, + Rpm, + Test, +} + +fn parse_package_manager(s: String) -> Result { + match s.to_lowercase().as_str() { + "dpkg" => Ok(PackageManager::Dpkg), + "rpm" => Ok(PackageManager::Rpm), + "test" => Ok(PackageManager::Test), + s => Err(s.to_string()), + } +} + +impl Decodable for PackageManager { + + fn decode(d: &mut D) -> Result { + d.read_str().and_then(|s| parse_package_manager(s) + .map_err(|e| d.error(&e))) + } +} diff --git a/src/main.rs b/src/main.rs index 8bbb4be..fab66c0 100644 --- a/src/main.rs +++ b/src/main.rs @@ -11,6 +11,7 @@ use libotaplus::auth_plus::authenticate; use libotaplus::datatype::config; use libotaplus::datatype::Config; use libotaplus::datatype::Error; +use libotaplus::datatype::PackageManager as PackageManagerType; use libotaplus::http_client::HttpClient; use libotaplus::ota_plus::{post_packages, get_package_updates, download_package_update}; use libotaplus::package_manager::{PackageManager, Dpkg}; @@ -94,10 +95,10 @@ fn build_config() -> Config { "change ota vin", "VIN"); opts.optopt("", "ota-packages-dir", "change downloaded directory for packages", "PATH"); + opts.optopt("", "ota-package-manager", + "change package manager", "MANAGER"); opts.optflag("", "test-looping", "enable read-interpret test loop"); - opts.optflag("", "test-fake-pm", - "enable fake package manager for testing"); let matches = opts.parse(&args[1..]) .unwrap_or_else(|err| panic!(err.to_string())); @@ -147,12 +148,17 @@ fn build_config() -> Config { config.ota.packages_dir = path; } - if matches.opt_present("test-looping") { - config.test.looping = true; + if let Some(s) = matches.opt_str("ota-package-manager") { + config.ota.package_manager = match s.to_lowercase().as_str() { + "dpkg" => PackageManagerType::Dpkg, + "rpm" => PackageManagerType::Rpm, + "test" => PackageManagerType::Test, + s => exit!("Invalid package manager: {}", s) + } } - if matches.opt_present("test-fake-pm") { - config.test.fake_package_manager = true; + if matches.opt_present("test-looping") { + config.test.looping = true; } return config diff --git a/tests/ota_plus_client_tests.rs b/tests/ota_plus_client_tests.rs index 49f4b13..c13554e 100644 --- a/tests/ota_plus_client_tests.rs +++ b/tests/ota_plus_client_tests.rs @@ -45,8 +45,9 @@ Options: --ota-vin VIN change ota vin --ota-packages-dir PATH change downloaded directory for packages + --ota-package-manager MANAGER + change package manager --test-looping enable read-interpret test loop - --test-fake-pm enable fake package manager for testing "#); @@ -89,3 +90,9 @@ fn bad_path_dir() { assert_eq!(client(&["--config=/"]), "Failed to load config: Is a directory (os error 21)\n") } + +#[test] +fn bad_package_manager() { + assert_eq!(client(&["--ota-package-manager=apa"]), + "Invalid package manager: apa\n") +} -- cgit v1.2.1 From e7093b27fe70fbf8b94753fc026d7273117d9fdc Mon Sep 17 00:00:00 2001 From: Stevan Andjelkovic Date: Tue, 29 Mar 2016 13:33:55 +0200 Subject: Set downloaded package name extension according to package manager used. --- src/datatype/package_manager.rs | 12 ++++++++++++ src/ota_plus.rs | 2 +- 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/src/datatype/package_manager.rs b/src/datatype/package_manager.rs index ef70589..02cc88c 100644 --- a/src/datatype/package_manager.rs +++ b/src/datatype/package_manager.rs @@ -8,6 +8,18 @@ pub enum PackageManager { Test, } +impl PackageManager { + + pub fn extension(&self) -> String { + match *self { + PackageManager::Dpkg => "deb".to_string(), + PackageManager::Rpm => "rpm".to_string(), + PackageManager::Test => "test".to_string(), + } + } + +} + fn parse_package_manager(s: String) -> Result { match s.to_lowercase().as_str() { "dpkg" => Ok(PackageManager::Dpkg), diff --git a/src/ota_plus.rs b/src/ota_plus.rs index b12aad0..0eeeed2 100644 --- a/src/ota_plus.rs +++ b/src/ota_plus.rs @@ -29,7 +29,7 @@ pub fn download_package_update(token: &AccessToken, let mut path = PathBuf::new(); path.push(&config.packages_dir); path.push(id); - path.set_extension("deb"); + path.set_extension(config.package_manager.extension()); let file = try!(File::create(path.as_path()) .map_err(|e| Error::Ota(CreateFile(path.clone(), e)))); -- cgit v1.2.1 From daa6c5bb50feec5c03c261b40ef6401c803ff92d Mon Sep 17 00:00:00 2001 From: Stevan Andjelkovic Date: Tue, 29 Mar 2016 14:53:01 +0200 Subject: Move all packages successfully installed into worker. --- src/main.rs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/main.rs b/src/main.rs index fab66c0..d309ab9 100644 --- a/src/main.rs +++ b/src/main.rs @@ -26,8 +26,8 @@ fn main() { let config = build_config(); match worker::(&config) { + Ok(()) => {}, Err(e) => exit!("{}", e), - Ok(()) => println!("Installed packages were posted successfully."), } if config.test.looping { @@ -69,7 +69,9 @@ fn worker(config: &Config) -> Result<(), Error println!("Installed."); } - return Ok(()) + println!("Installed packages were posted successfully."); + + return Ok(()) } -- cgit v1.2.1 From 14eb019464d0e611285e69aa0458355a329331c2 Mon Sep 17 00:00:00 2001 From: Stevan Andjelkovic Date: Tue, 29 Mar 2016 15:35:31 +0200 Subject: Add rpm package manager stub. --- src/package_manager/mod.rs | 2 ++ src/package_manager/rpm.rs | 24 ++++++++++++++++++++++++ 2 files changed, 26 insertions(+) create mode 100644 src/package_manager/rpm.rs diff --git a/src/package_manager/mod.rs b/src/package_manager/mod.rs index 2e23238..e468b87 100644 --- a/src/package_manager/mod.rs +++ b/src/package_manager/mod.rs @@ -1,5 +1,7 @@ pub use self::dpkg::Dpkg; pub use self::interface::PackageManager; +pub use self::rpm::Rpm; pub mod dpkg; pub mod interface; +pub mod rpm; diff --git a/src/package_manager/rpm.rs b/src/package_manager/rpm.rs new file mode 100644 index 0000000..ca74bed --- /dev/null +++ b/src/package_manager/rpm.rs @@ -0,0 +1,24 @@ +use std::path::Path; + +use datatype::Error; +use datatype::Package; +use package_manager::PackageManager; + + +pub struct Rpm; + +impl PackageManager for Rpm { + + fn new() -> Rpm { + return Rpm + } + + fn installed_packages(&self) -> Result, Error> { + unimplemented!(); + } + + fn install_package(&self, _: &Path) -> Result<(), Error> { + unimplemented!(); + } + +} -- cgit v1.2.1 From 2669b8b46720b5cb54c21ac86c187681ab3ae460 Mon Sep 17 00:00:00 2001 From: Stevan Andjelkovic Date: Tue, 29 Mar 2016 16:19:52 +0200 Subject: Make the worker use the package manager specified at run-time by the config. --- src/main.rs | 18 +++++++++++++----- src/package_manager/dpkg.rs | 4 ---- src/package_manager/interface.rs | 1 - src/package_manager/rpm.rs | 4 ---- 4 files changed, 13 insertions(+), 14 deletions(-) diff --git a/src/main.rs b/src/main.rs index d309ab9..b8ce3aa 100644 --- a/src/main.rs +++ b/src/main.rs @@ -14,7 +14,7 @@ use libotaplus::datatype::Error; use libotaplus::datatype::PackageManager as PackageManagerType; use libotaplus::http_client::HttpClient; use libotaplus::ota_plus::{post_packages, get_package_updates, download_package_update}; -use libotaplus::package_manager::{PackageManager, Dpkg}; +use libotaplus::package_manager::{PackageManager, Dpkg, Rpm}; use libotaplus::read_interpret::ReplEnv; use libotaplus::read_interpret; @@ -25,24 +25,32 @@ fn main() { let config = build_config(); - match worker::(&config) { + let dpkg: &PackageManager = &Dpkg; + let rpm: &PackageManager = &Rpm; + + let pkg_manager: &PackageManager = match config.ota.package_manager { + PackageManagerType::Dpkg => dpkg, + PackageManagerType::Rpm => rpm, + PackageManagerType::Test => unimplemented!(), + }; + + match worker::(&config, pkg_manager) { Ok(()) => {}, Err(e) => exit!("{}", e), } if config.test.looping { - read_interpret::read_interpret_loop(ReplEnv::new(Dpkg::new())); + read_interpret::read_interpret_loop(ReplEnv::new(Dpkg)); } } -fn worker(config: &Config) -> Result<(), Error> { +fn worker(config: &Config, pkg_manager: &PackageManager) -> Result<(), Error> { println!("Trying to acquire access token."); let token = try!(authenticate::(&config.auth)); println!("Asking package manager what packages are installed on the system."); - let pkg_manager = M::new(); let pkgs = try!(pkg_manager.installed_packages()); println!("Letting the OTA server know what packages are installed."); diff --git a/src/package_manager/dpkg.rs b/src/package_manager/dpkg.rs index 884a2c3..021196b 100644 --- a/src/package_manager/dpkg.rs +++ b/src/package_manager/dpkg.rs @@ -10,10 +10,6 @@ pub struct Dpkg; impl PackageManager for Dpkg { - fn new() -> Dpkg { - return Dpkg - } - fn installed_packages(&self) -> Result, Error> { Command::new("dpkg-query").arg("-f").arg("${Package} ${Version}\n").arg("-W") .output() diff --git a/src/package_manager/interface.rs b/src/package_manager/interface.rs index ef7091b..ca76f1b 100644 --- a/src/package_manager/interface.rs +++ b/src/package_manager/interface.rs @@ -5,7 +5,6 @@ use datatype::Package; pub trait PackageManager { - fn new() -> Self; fn installed_packages(&self) -> Result, Error>; fn install_package(&self, path: &Path) -> Result<(), Error>; } diff --git a/src/package_manager/rpm.rs b/src/package_manager/rpm.rs index ca74bed..f473d1c 100644 --- a/src/package_manager/rpm.rs +++ b/src/package_manager/rpm.rs @@ -9,10 +9,6 @@ pub struct Rpm; impl PackageManager for Rpm { - fn new() -> Rpm { - return Rpm - } - fn installed_packages(&self) -> Result, Error> { unimplemented!(); } -- cgit v1.2.1 From 0f76c095754b2810c6264b848358d7b71f85b005 Mon Sep 17 00:00:00 2001 From: Stevan Andjelkovic Date: Tue, 29 Mar 2016 16:49:31 +0200 Subject: Static package manager trait pointers. --- src/datatype/package_manager.rs | 12 ++++++++++++ src/main.rs | 13 ++----------- src/package_manager/dpkg.rs | 2 ++ src/package_manager/rpm.rs | 2 ++ 4 files changed, 18 insertions(+), 11 deletions(-) diff --git a/src/datatype/package_manager.rs b/src/datatype/package_manager.rs index 02cc88c..27ffff6 100644 --- a/src/datatype/package_manager.rs +++ b/src/datatype/package_manager.rs @@ -1,5 +1,9 @@ use rustc_serialize::{Decoder, Decodable}; +use package_manager::PackageManager as PackageManagerTrait; +use package_manager::dpkg::DPKG; +use package_manager::rpm::RPM; + #[derive(Debug, PartialEq, Eq)] pub enum PackageManager { @@ -18,6 +22,14 @@ impl PackageManager { } } + pub fn build(&self) -> &'static PackageManagerTrait { + match *self { + PackageManager::Dpkg => DPKG, + PackageManager::Rpm => RPM, + PackageManager::Test => unimplemented!(), + } + } + } fn parse_package_manager(s: String) -> Result { diff --git a/src/main.rs b/src/main.rs index b8ce3aa..c188bef 100644 --- a/src/main.rs +++ b/src/main.rs @@ -14,7 +14,7 @@ use libotaplus::datatype::Error; use libotaplus::datatype::PackageManager as PackageManagerType; use libotaplus::http_client::HttpClient; use libotaplus::ota_plus::{post_packages, get_package_updates, download_package_update}; -use libotaplus::package_manager::{PackageManager, Dpkg, Rpm}; +use libotaplus::package_manager::{PackageManager, Dpkg}; use libotaplus::read_interpret::ReplEnv; use libotaplus::read_interpret; @@ -25,16 +25,7 @@ fn main() { let config = build_config(); - let dpkg: &PackageManager = &Dpkg; - let rpm: &PackageManager = &Rpm; - - let pkg_manager: &PackageManager = match config.ota.package_manager { - PackageManagerType::Dpkg => dpkg, - PackageManagerType::Rpm => rpm, - PackageManagerType::Test => unimplemented!(), - }; - - match worker::(&config, pkg_manager) { + match worker::(&config, config.ota.package_manager.build()) { Ok(()) => {}, Err(e) => exit!("{}", e), } diff --git a/src/package_manager/dpkg.rs b/src/package_manager/dpkg.rs index 021196b..52d78dc 100644 --- a/src/package_manager/dpkg.rs +++ b/src/package_manager/dpkg.rs @@ -8,6 +8,8 @@ use package_manager::PackageManager; pub struct Dpkg; +pub static DPKG: &'static PackageManager = &Dpkg; + impl PackageManager for Dpkg { fn installed_packages(&self) -> Result, Error> { diff --git a/src/package_manager/rpm.rs b/src/package_manager/rpm.rs index f473d1c..4c1b1ed 100644 --- a/src/package_manager/rpm.rs +++ b/src/package_manager/rpm.rs @@ -7,6 +7,8 @@ use package_manager::PackageManager; pub struct Rpm; +pub static RPM: &'static PackageManager = &Rpm; + impl PackageManager for Rpm { fn installed_packages(&self) -> Result, Error> { -- cgit v1.2.1 From 8ee23105c64d1de0c9d8556fac0d98938300f764 Mon Sep 17 00:00:00 2001 From: Jerry Trieu Date: Tue, 29 Mar 2016 18:25:03 +0200 Subject: Update pkg/ota.toml.template --- pkg/ota.toml.template | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/pkg/ota.toml.template b/pkg/ota.toml.template index c85e3e5..5d69be1 100644 --- a/pkg/ota.toml.template +++ b/pkg/ota.toml.template @@ -6,6 +6,8 @@ secret = "${OTA_AUTH_SECRET}" [ota] server = "${OTA_SERVER_URL}" vin = "${OTA_CLIENT_VIN}" +packages_dir = "/tmp" +package_manager = "dpkg" -[packages] -dir = "${PACKAGES_DIR}" \ No newline at end of file +[test] +looping = false -- cgit v1.2.1 From dcac6ae31f95b6390c99d11f5fdac72f8ff9e5d0 Mon Sep 17 00:00:00 2001 From: Stevan Andjelkovic Date: Wed, 30 Mar 2016 15:54:26 +0200 Subject: Add fake package manager. --- src/main.rs | 2 +- src/package_manager/dpkg.rs | 5 +- src/package_manager/interface.rs | 4 +- src/package_manager/mod.rs | 2 + src/package_manager/rpm.rs | 4 +- src/package_manager/tpm.rs | 129 +++++++++++++++++++++++++++++++++++++++ 6 files changed, 136 insertions(+), 10 deletions(-) create mode 100644 src/package_manager/tpm.rs diff --git a/src/main.rs b/src/main.rs index c188bef..64901ad 100644 --- a/src/main.rs +++ b/src/main.rs @@ -64,7 +64,7 @@ fn worker(config: &Config, pkg_manager: &PackageManager) -> Resul for path in &paths { println!("Installing package at {:?}...", path); - try!(pkg_manager.install_package(path.as_path())); + try!(pkg_manager.install_package(path.as_path().to_str().unwrap())); println!("Installed."); } diff --git a/src/package_manager/dpkg.rs b/src/package_manager/dpkg.rs index 52d78dc..2191a2a 100644 --- a/src/package_manager/dpkg.rs +++ b/src/package_manager/dpkg.rs @@ -1,4 +1,3 @@ -use std::path::Path; use std::process::Command; use datatype::Error; @@ -28,10 +27,10 @@ impl PackageManager for Dpkg { }) } - fn install_package(&self, path: &Path) -> Result<(), Error> { + fn install_package(&self, path: &str) -> Result<(), Error> { let output = try!(Command::new("dpkg").arg("-i") - .arg(path.to_str().unwrap()) + .arg(path) .output()); String::from_utf8(output.stdout) diff --git a/src/package_manager/interface.rs b/src/package_manager/interface.rs index ca76f1b..4df9dff 100644 --- a/src/package_manager/interface.rs +++ b/src/package_manager/interface.rs @@ -1,10 +1,8 @@ -use std::path::Path; - use datatype::Error; use datatype::Package; pub trait PackageManager { fn installed_packages(&self) -> Result, Error>; - fn install_package(&self, path: &Path) -> Result<(), Error>; + fn install_package(&self, path: &str) -> Result<(), Error>; } diff --git a/src/package_manager/mod.rs b/src/package_manager/mod.rs index e468b87..82c541f 100644 --- a/src/package_manager/mod.rs +++ b/src/package_manager/mod.rs @@ -1,7 +1,9 @@ pub use self::dpkg::Dpkg; pub use self::interface::PackageManager; pub use self::rpm::Rpm; +pub use self::tpm::Tpm; pub mod dpkg; pub mod interface; pub mod rpm; +pub mod tpm; diff --git a/src/package_manager/rpm.rs b/src/package_manager/rpm.rs index 4c1b1ed..cd02974 100644 --- a/src/package_manager/rpm.rs +++ b/src/package_manager/rpm.rs @@ -1,5 +1,3 @@ -use std::path::Path; - use datatype::Error; use datatype::Package; use package_manager::PackageManager; @@ -15,7 +13,7 @@ impl PackageManager for Rpm { unimplemented!(); } - fn install_package(&self, _: &Path) -> Result<(), Error> { + fn install_package(&self, _: &str) -> Result<(), Error> { unimplemented!(); } diff --git a/src/package_manager/tpm.rs b/src/package_manager/tpm.rs new file mode 100644 index 0000000..b82527b --- /dev/null +++ b/src/package_manager/tpm.rs @@ -0,0 +1,129 @@ +use std::fs::File; +use std::fs::OpenOptions; +use std::io::BufReader; +use std::io::BufWriter; +use std::io::prelude::*; +use std::iter::Iterator; + +use datatype::Error; +use datatype::Package; +use package_manager::PackageManager; + + +// The test package manager. +pub struct Tpm; + +pub static TPM: &'static PackageManager = &Tpm; + +static PATH: &'static str = "/tmp/packages.tpm"; + +impl PackageManager for Tpm { + + fn installed_packages(&self) -> Result, Error> { + + let f = try!(File::open(PATH)); + let reader = BufReader::new(f); + let mut pkgs = Vec::new(); + + for line in reader.lines() { + + let line = try!(line); + let parts = line.split(' '); + + if parts.clone().count() == 2 { + if let Some(name) = parts.clone().nth(0) { + if let Some(version) = parts.clone().nth(1) { + pkgs.push(Package { + name: name.to_string(), + version: version.to_string() + }); + } + } + } + + } + + return Ok(pkgs) + + } + + fn install_package(&self, pkg: &str) -> Result<(), Error> { + + let f = try!(OpenOptions::new() + .create(true) + .append(true) + .open(PATH)); + + let mut writer = BufWriter::new(f); + + try!(writer.write(pkg.as_bytes())); + try!(writer.write(b"\n")); + + return Ok(()) + + } + +} + + +#[cfg(test)] +mod tests { + + use std::fs; + use std::fs::File; + use std::io::prelude::*; + + use super::*; + use datatype::Package; + use package_manager::PackageManager; + + fn pkg1() -> Package { + Package { + name: "apa".to_string(), + version: "0.0.0".to_string() + } + } + + fn pkg2() -> Package { + Package { + name: "bepa".to_string(), + version: "1.0.0".to_string() + } + } + + #[test] + fn test_installed_packages() { + + let _ = fs::remove_file(super::PATH); + let mut f = File::create(super::PATH).unwrap(); + + f.write(b"apa 0.0.0\n").unwrap(); + f.write(b"bepa 1.0.0").unwrap(); + + assert_eq!(Tpm.installed_packages().unwrap(), vec!(pkg1(), pkg2())); + + } + + #[test] + fn bad_installed_packages() { + + let _ = fs::remove_file(super::PATH); + let mut f = File::create(super::PATH).unwrap(); + f.write(b"cepa-2.0.0\n").unwrap(); + + assert_eq!(Tpm.installed_packages().unwrap(), Vec::new()); + + } + + #[test] + fn test_install_package() { + + let _ = fs::remove_file(super::PATH); + Tpm.install_package("apa 0.0.0").unwrap(); + Tpm.install_package("bepa 1.0.0").unwrap(); + + assert_eq!(Tpm.installed_packages().unwrap(), vec!(pkg1(), pkg2())); + + } + +} -- cgit v1.2.1 From a18de475a6e61f444049ccf80c08262601bc3431 Mon Sep 17 00:00:00 2001 From: Stevan Andjelkovic Date: Mon, 4 Apr 2016 12:12:24 +0200 Subject: Make the path to the file system based package manager db configurable. --- src/datatype/package_manager.rs | 17 ++++---- src/main.rs | 7 ++-- src/package_manager/dpkg.rs | 5 ++- src/package_manager/interface.rs | 5 ++- src/package_manager/rpm.rs | 5 ++- src/package_manager/tpm.rs | 87 +++++++++++++++++++++++++++++++--------- src/read_interpret.rs | 5 ++- tests/ota_plus_client_tests.rs | 6 --- 8 files changed, 91 insertions(+), 46 deletions(-) diff --git a/src/datatype/package_manager.rs b/src/datatype/package_manager.rs index 27ffff6..c2555e9 100644 --- a/src/datatype/package_manager.rs +++ b/src/datatype/package_manager.rs @@ -9,24 +9,24 @@ use package_manager::rpm::RPM; pub enum PackageManager { Dpkg, Rpm, - Test, + File(String), } impl PackageManager { pub fn extension(&self) -> String { match *self { - PackageManager::Dpkg => "deb".to_string(), - PackageManager::Rpm => "rpm".to_string(), - PackageManager::Test => "test".to_string(), + PackageManager::Dpkg => "deb".to_string(), + PackageManager::Rpm => "rpm".to_string(), + PackageManager::File(ref s) => s.to_string(), } } pub fn build(&self) -> &'static PackageManagerTrait { match *self { - PackageManager::Dpkg => DPKG, - PackageManager::Rpm => RPM, - PackageManager::Test => unimplemented!(), + PackageManager::Dpkg => DPKG, + PackageManager::Rpm => RPM, + PackageManager::File(_) => unimplemented!(), } } @@ -36,8 +36,7 @@ fn parse_package_manager(s: String) -> Result { match s.to_lowercase().as_str() { "dpkg" => Ok(PackageManager::Dpkg), "rpm" => Ok(PackageManager::Rpm), - "test" => Ok(PackageManager::Test), - s => Err(s.to_string()), + s => Ok(PackageManager::File(s.to_string())), } } diff --git a/src/main.rs b/src/main.rs index 64901ad..7b29e77 100644 --- a/src/main.rs +++ b/src/main.rs @@ -42,7 +42,7 @@ fn worker(config: &Config, pkg_manager: &PackageManager) -> Resul let token = try!(authenticate::(&config.auth)); println!("Asking package manager what packages are installed on the system."); - let pkgs = try!(pkg_manager.installed_packages()); + let pkgs = try!(pkg_manager.installed_packages(&config.ota)); println!("Letting the OTA server know what packages are installed."); try!(post_packages::(&token, &config.ota, &pkgs)); @@ -64,7 +64,7 @@ fn worker(config: &Config, pkg_manager: &PackageManager) -> Resul for path in &paths { println!("Installing package at {:?}...", path); - try!(pkg_manager.install_package(path.as_path().to_str().unwrap())); + try!(pkg_manager.install_package(&config.ota, path.as_path().to_str().unwrap())); println!("Installed."); } @@ -153,8 +153,7 @@ fn build_config() -> Config { config.ota.package_manager = match s.to_lowercase().as_str() { "dpkg" => PackageManagerType::Dpkg, "rpm" => PackageManagerType::Rpm, - "test" => PackageManagerType::Test, - s => exit!("Invalid package manager: {}", s) + path => PackageManagerType::File(path.to_string()), } } diff --git a/src/package_manager/dpkg.rs b/src/package_manager/dpkg.rs index 2191a2a..ca425a5 100644 --- a/src/package_manager/dpkg.rs +++ b/src/package_manager/dpkg.rs @@ -1,6 +1,7 @@ use std::process::Command; use datatype::Error; +use datatype::OtaConfig; use datatype::Package; use package_manager::PackageManager; @@ -11,7 +12,7 @@ pub static DPKG: &'static PackageManager = &Dpkg; impl PackageManager for Dpkg { - fn installed_packages(&self) -> Result, Error> { + fn installed_packages(&self, _: &OtaConfig) -> Result, Error> { Command::new("dpkg-query").arg("-f").arg("${Package} ${Version}\n").arg("-W") .output() .map_err(|e| Error::PackageError(format!("Error fetching packages: {}", e))) @@ -27,7 +28,7 @@ impl PackageManager for Dpkg { }) } - fn install_package(&self, path: &str) -> Result<(), Error> { + fn install_package(&self, _: &OtaConfig, path: &str) -> Result<(), Error> { let output = try!(Command::new("dpkg").arg("-i") .arg(path) diff --git a/src/package_manager/interface.rs b/src/package_manager/interface.rs index 4df9dff..64a0b8c 100644 --- a/src/package_manager/interface.rs +++ b/src/package_manager/interface.rs @@ -1,8 +1,9 @@ use datatype::Error; +use datatype::OtaConfig; use datatype::Package; pub trait PackageManager { - fn installed_packages(&self) -> Result, Error>; - fn install_package(&self, path: &str) -> Result<(), Error>; + fn installed_packages(&self, &OtaConfig) -> Result, Error>; + fn install_package(&self, &OtaConfig, path: &str) -> Result<(), Error>; } diff --git a/src/package_manager/rpm.rs b/src/package_manager/rpm.rs index cd02974..749e8dd 100644 --- a/src/package_manager/rpm.rs +++ b/src/package_manager/rpm.rs @@ -1,4 +1,5 @@ use datatype::Error; +use datatype::OtaConfig; use datatype::Package; use package_manager::PackageManager; @@ -9,11 +10,11 @@ pub static RPM: &'static PackageManager = &Rpm; impl PackageManager for Rpm { - fn installed_packages(&self) -> Result, Error> { + fn installed_packages(&self, _: &OtaConfig) -> Result, Error> { unimplemented!(); } - fn install_package(&self, _: &str) -> Result<(), Error> { + fn install_package(&self, _: &OtaConfig, _: &str) -> Result<(), Error> { unimplemented!(); } diff --git a/src/package_manager/tpm.rs b/src/package_manager/tpm.rs index b82527b..9358466 100644 --- a/src/package_manager/tpm.rs +++ b/src/package_manager/tpm.rs @@ -6,6 +6,7 @@ use std::io::prelude::*; use std::iter::Iterator; use datatype::Error; +use datatype::OtaConfig; use datatype::Package; use package_manager::PackageManager; @@ -15,13 +16,12 @@ pub struct Tpm; pub static TPM: &'static PackageManager = &Tpm; -static PATH: &'static str = "/tmp/packages.tpm"; - impl PackageManager for Tpm { - fn installed_packages(&self) -> Result, Error> { + fn installed_packages(&self, config: &OtaConfig) -> Result, Error> { - let f = try!(File::open(PATH)); + let f = try!(File::open(config.packages_dir.clone() + + &config.package_manager.extension())); let reader = BufReader::new(f); let mut pkgs = Vec::new(); @@ -47,17 +47,21 @@ impl PackageManager for Tpm { } - fn install_package(&self, pkg: &str) -> Result<(), Error> { + fn install_package(&self, config: &OtaConfig, pkg: &str) -> Result<(), Error> { let f = try!(OpenOptions::new() .create(true) + .write(true) .append(true) - .open(PATH)); + .open(config.packages_dir.clone() + + &config.package_manager.extension())); - let mut writer = BufWriter::new(f); + { + let mut writer = BufWriter::new(f); - try!(writer.write(pkg.as_bytes())); - try!(writer.write(b"\n")); + try!(writer.write(pkg.as_bytes())); + try!(writer.write(b"\n")); + } return Ok(()) @@ -74,8 +78,10 @@ mod tests { use std::io::prelude::*; use super::*; + use datatype::OtaConfig; use datatype::Package; - use package_manager::PackageManager; + use datatype::PackageManager; + use package_manager::PackageManager as PackageManagerTrait; fn pkg1() -> Package { Package { @@ -91,38 +97,79 @@ mod tests { } } + fn package_manager(s: &str) -> PackageManager { + PackageManager::File(s.to_string()) + } + #[test] fn test_installed_packages() { - let _ = fs::remove_file(super::PATH); - let mut f = File::create(super::PATH).unwrap(); + const PACKAGES_DIR: &'static str = "/tmp/"; + let package_manager = package_manager("test1"); + + let mut f = File::create(PACKAGES_DIR.to_string() + + &package_manager.extension()).unwrap(); f.write(b"apa 0.0.0\n").unwrap(); f.write(b"bepa 1.0.0").unwrap(); - assert_eq!(Tpm.installed_packages().unwrap(), vec!(pkg1(), pkg2())); + let mut config = OtaConfig::default(); + + config = OtaConfig { + packages_dir: PACKAGES_DIR.to_string(), + package_manager: package_manager, + .. config + }; + + assert_eq!(Tpm.installed_packages(&config).unwrap(), vec!(pkg1(), pkg2())); } #[test] fn bad_installed_packages() { - let _ = fs::remove_file(super::PATH); - let mut f = File::create(super::PATH).unwrap(); + + const PACKAGES_DIR: &'static str = "/tmp/"; + let package_manager = package_manager("test2"); + + let mut f = File::create(PACKAGES_DIR.to_string() + + &package_manager.extension()).unwrap(); + f.write(b"cepa-2.0.0\n").unwrap(); - assert_eq!(Tpm.installed_packages().unwrap(), Vec::new()); + let mut config = OtaConfig::default(); + + config = OtaConfig { + packages_dir: PACKAGES_DIR.to_string(), + package_manager: package_manager, + .. config + }; + + assert_eq!(Tpm.installed_packages(&config).unwrap(), Vec::new()); } #[test] fn test_install_package() { - let _ = fs::remove_file(super::PATH); - Tpm.install_package("apa 0.0.0").unwrap(); - Tpm.install_package("bepa 1.0.0").unwrap(); + const PACKAGES_DIR: &'static str = "/tmp/"; + let package_manager = package_manager("test3"); + + let _ = fs::remove_file(PACKAGES_DIR.to_string() + + &package_manager.extension()); + + let mut config = OtaConfig::default(); + + config = OtaConfig { + packages_dir: "/tmp/".to_string(), + package_manager: package_manager, + .. config + }; + + Tpm.install_package(&config, "apa 0.0.0").unwrap(); + Tpm.install_package(&config, "bepa 1.0.0").unwrap(); - assert_eq!(Tpm.installed_packages().unwrap(), vec!(pkg1(), pkg2())); + assert_eq!(Tpm.installed_packages(&config).unwrap(), vec!(pkg1(), pkg2())); } diff --git a/src/read_interpret.rs b/src/read_interpret.rs index cd0bcfd..8adbc50 100644 --- a/src/read_interpret.rs +++ b/src/read_interpret.rs @@ -27,8 +27,9 @@ impl FromStr for Command { } } -fn list_packages(package_manager: &M) +fn list_packages(_: &M) where M: PackageManager { +/* let _ = package_manager.installed_packages() .and_then(|pkgs| { println!("Found {} packages.", pkgs.iter().len()); @@ -39,6 +40,8 @@ fn list_packages(package_manager: &M) }).map_err(|e| { error!("Can't list packages: {}", e) }); +*/ + unimplemented!(); } fn interpret(env: &ReplEnv, cmd: Command) diff --git a/tests/ota_plus_client_tests.rs b/tests/ota_plus_client_tests.rs index c13554e..a2cdf24 100644 --- a/tests/ota_plus_client_tests.rs +++ b/tests/ota_plus_client_tests.rs @@ -90,9 +90,3 @@ fn bad_path_dir() { assert_eq!(client(&["--config=/"]), "Failed to load config: Is a directory (os error 21)\n") } - -#[test] -fn bad_package_manager() { - assert_eq!(client(&["--ota-package-manager=apa"]), - "Invalid package manager: apa\n") -} -- cgit v1.2.1 From 6a3a3d9a105de838dbf6bb55de73433ace0bacd7 Mon Sep 17 00:00:00 2001 From: Stevan Andjelkovic Date: Mon, 4 Apr 2016 14:01:46 +0200 Subject: Clean up the code in the tests a wee bit. --- src/package_manager/tpm.rs | 71 ++++++++++++++++++---------------------------- 1 file changed, 27 insertions(+), 44 deletions(-) diff --git a/src/package_manager/tpm.rs b/src/package_manager/tpm.rs index 9358466..0aef2dc 100644 --- a/src/package_manager/tpm.rs +++ b/src/package_manager/tpm.rs @@ -56,12 +56,10 @@ impl PackageManager for Tpm { .open(config.packages_dir.clone() + &config.package_manager.extension())); - { - let mut writer = BufWriter::new(f); + let mut writer = BufWriter::new(f); - try!(writer.write(pkg.as_bytes())); - try!(writer.write(b"\n")); - } + try!(writer.write(pkg.as_bytes())); + try!(writer.write(b"\n")); return Ok(()) @@ -97,30 +95,34 @@ mod tests { } } - fn package_manager(s: &str) -> PackageManager { - PackageManager::File(s.to_string()) + fn make_config(file: &str) -> OtaConfig { + + let packages_dir = "/tmp/".to_string(); + let package_manager = PackageManager::File(file.to_string()); + + let mut config = OtaConfig::default(); + + config = OtaConfig { + packages_dir: packages_dir, + package_manager: package_manager, + .. config + }; + + return config + } #[test] fn test_installed_packages() { - const PACKAGES_DIR: &'static str = "/tmp/"; - let package_manager = package_manager("test1"); + let config = make_config("test1"); - let mut f = File::create(PACKAGES_DIR.to_string() + - &package_manager.extension()).unwrap(); + let mut f = File::create(config.packages_dir.clone() + + &config.package_manager.extension()).unwrap(); f.write(b"apa 0.0.0\n").unwrap(); f.write(b"bepa 1.0.0").unwrap(); - let mut config = OtaConfig::default(); - - config = OtaConfig { - packages_dir: PACKAGES_DIR.to_string(), - package_manager: package_manager, - .. config - }; - assert_eq!(Tpm.installed_packages(&config).unwrap(), vec!(pkg1(), pkg2())); } @@ -128,23 +130,13 @@ mod tests { #[test] fn bad_installed_packages() { + let config = make_config("test2"); - const PACKAGES_DIR: &'static str = "/tmp/"; - let package_manager = package_manager("test2"); - - let mut f = File::create(PACKAGES_DIR.to_string() + - &package_manager.extension()).unwrap(); + let mut f = File::create(config.packages_dir.clone() + + &config.package_manager.extension()).unwrap(); f.write(b"cepa-2.0.0\n").unwrap(); - let mut config = OtaConfig::default(); - - config = OtaConfig { - packages_dir: PACKAGES_DIR.to_string(), - package_manager: package_manager, - .. config - }; - assert_eq!(Tpm.installed_packages(&config).unwrap(), Vec::new()); } @@ -152,19 +144,10 @@ mod tests { #[test] fn test_install_package() { - const PACKAGES_DIR: &'static str = "/tmp/"; - let package_manager = package_manager("test3"); - - let _ = fs::remove_file(PACKAGES_DIR.to_string() + - &package_manager.extension()); - - let mut config = OtaConfig::default(); + let config = make_config("test3"); - config = OtaConfig { - packages_dir: "/tmp/".to_string(), - package_manager: package_manager, - .. config - }; + let _ = fs::remove_file(config.packages_dir.to_string() + + &config.package_manager.extension()); Tpm.install_package(&config, "apa 0.0.0").unwrap(); Tpm.install_package(&config, "bepa 1.0.0").unwrap(); -- cgit v1.2.1 From 45b46d6626f838b3ede2185013839fdd5fdc462d Mon Sep 17 00:00:00 2001 From: Shaun Taheri Date: Mon, 4 Apr 2016 16:40:47 +0200 Subject: Statically link binary and add DEB and RPM targets --- Makefile | 18 +++-- README.md | 18 ++++- pkg/deb/Dockerfile | 11 +++ pkg/deb/ota-plus-client-0.1.0/debian/control | 2 +- .../debian/ota-plus-client.service | 10 +++ pkg/deb/ota-plus-client-0.1.0/debian/rules | 2 +- pkg/pkg.sh | 81 +++++++++++++++------- pkg/rpm/Dockerfile | 22 ++++++ pkg/rpm/ota-client.service | 10 +++ pkg/rpm/pr862.patch | 40 +++++++++++ src/package_manager/rpm.rs | 26 +++++-- 11 files changed, 202 insertions(+), 38 deletions(-) create mode 100644 pkg/deb/Dockerfile create mode 100644 pkg/deb/ota-plus-client-0.1.0/debian/ota-plus-client.service create mode 100644 pkg/rpm/Dockerfile create mode 100644 pkg/rpm/ota-client.service create mode 100644 pkg/rpm/pr862.patch diff --git a/Makefile b/Makefile index 3cddc21..f97bbad 100644 --- a/Makefile +++ b/Makefile @@ -1,11 +1,21 @@ -.PHONY: all +MUSL=x86_64-unknown-linux-musl +.PHONY: all all: pkg/deb/ota-plus-client-0.1.0/bin -pkg/deb/ota-plus-client-0.1.0/bin: target/release/ota-plus-client +pkg/deb/ota-plus-client-0.1.0/bin: target/release/ota_plus_client mkdir -p $@ cp $< $@ -target/release/ota-plus-client: src/ - cargo build --release +target/release/ota_plus_client: src/ + export OPENSSL_STATIC=1 + cargo build --release --target=$(MUSL) + cp target/$(MUSL)/release/ota_plus_client target/release + +.PHONY: deb +deb: pkg/deb/ota-plus-client-0.1.0/bin + pkg/pkg.sh deb $(CURDIR) +.PHONY: rpm +rpm: target/release/ota_plus_client + pkg/pkg.sh rpm $(CURDIR) diff --git a/README.md b/README.md index 2816ff9..f11ec20 100644 --- a/README.md +++ b/README.md @@ -12,4 +12,20 @@ The OTA+ client source repository. To build and test the project simply issue: cargo build - cargo test \ No newline at end of file + cargo test + +## Packaging instructions + +### DEB + +A Dockerfile has been set up with the correct libraries for building a statically linked binary. This can be built from the project root with `docker build -t deb-packager pkg/deb`. The DEB package can then be built with `docker run -it --rm -v $PWD:/build deb-packager`. + +Alternatively, with the correct build packages installed, `make deb` can be run from the project root. + +### RPM + +[FPM](https://github.com/jordansissel/fpm) is used to create RPM packages. + +A Dockerfile has been set up with the correct libraries and can be built from the project root with `docker build -t rpm-packager pkg/rpm`. An RPM can then be created with `docker run -it --rm -v $PWD:/build rpm-packager`. + +Alternatively, assuming FPM and the correct libraries are installed, an RPM can be built with `make rpm`. diff --git a/pkg/deb/Dockerfile b/pkg/deb/Dockerfile new file mode 100644 index 0000000..9196e48 --- /dev/null +++ b/pkg/deb/Dockerfile @@ -0,0 +1,11 @@ +FROM clux/muslrust +# for statically linking binaries; see https://github.com/clux/muslrust + +RUN apt-get update \ + && apt-get install -y \ + devscripts \ + dh-systemd \ + && rm -rf /var/lib/apt/lists/* + +WORKDIR /build +CMD ["make", "deb"] diff --git a/pkg/deb/ota-plus-client-0.1.0/debian/control b/pkg/deb/ota-plus-client-0.1.0/debian/control index f63fe1a..ec8d1e7 100644 --- a/pkg/deb/ota-plus-client-0.1.0/debian/control +++ b/pkg/deb/ota-plus-client-0.1.0/debian/control @@ -3,7 +3,7 @@ Maintainer: Jerry Trieu Section: misc Priority: optional Standards-Version: 3.9.2 -Build-Depends: debhelper (>= 9) +Build-Depends: debhelper (>= 9), dh-systemd Package: ota-plus-client Architecture: any diff --git a/pkg/deb/ota-plus-client-0.1.0/debian/ota-plus-client.service b/pkg/deb/ota-plus-client-0.1.0/debian/ota-plus-client.service new file mode 100644 index 0000000..22cda1d --- /dev/null +++ b/pkg/deb/ota-plus-client-0.1.0/debian/ota-plus-client.service @@ -0,0 +1,10 @@ +[Unit] +Description=OTA+ Client +After=network.target + +[Service] +Type=simple +ExecStart=/opt/ats/ota/bin/ota_plus_client --config /opt/ats/ota/etc/ota.toml + +[Install] +WantedBy=multi-user.target diff --git a/pkg/deb/ota-plus-client-0.1.0/debian/rules b/pkg/deb/ota-plus-client-0.1.0/debian/rules index cbe925d..93646c7 100644 --- a/pkg/deb/ota-plus-client-0.1.0/debian/rules +++ b/pkg/deb/ota-plus-client-0.1.0/debian/rules @@ -1,3 +1,3 @@ #!/usr/bin/make -f %: - dh $@ + dh $@ --with=systemd diff --git a/pkg/pkg.sh b/pkg/pkg.sh index 863ca72..7a0559f 100755 --- a/pkg/pkg.sh +++ b/pkg/pkg.sh @@ -1,5 +1,16 @@ #!/bin/bash +set -eo pipefail + +PKG_NAME="ota-plus-client" +PKG_VER="0.1.0" +PKG_DIR="${PKG_NAME}-${PKG_VER}" +PKG_TARBALL="${PKG_NAME}_${PKG_VER}" +PREFIX=/opt/ats + +cd $(dirname $0) +PKG_SRC_DIR=$(pwd) + function envsub { awk ' /^\s*(#.*)?$/ { @@ -17,36 +28,54 @@ function envsub { }' $* } -if [ $# -lt 1 ] -then - echo "Usage: $0 " - exit 1 -fi +function make_deb { + workdir="${TMPDIR:-/tmp}/pkg-ota-plus-client-$$" + cp -pr $PKG_SRC_DIR/deb $workdir + cd $workdir -dest="${1}" -echo "Building pkg to '$dest'" + mkdir -p $PKG_DIR/bin + mkdir -p $PKG_DIR/etc + envsub ${PKG_SRC_DIR}/ota.toml.template > $PKG_DIR/etc/ota.toml + tar czf $PKG_TARBALL $PKG_DIR/bin $PKG_DIR/etc -PKG_NAME="ota-plus-client" -PKG_VER="0.1.0" -PKG_DIR="${PKG_NAME}-${PKG_VER}" -PKG_TARBALL="${PKG_NAME}_${PKG_VER}" + cd $PKG_DIR + debuild -i -us -uc -b + mv -n ../ota-plus-client*.deb $dest + cd $PKG_SRC_DIR + rm -rf $workdir +} + +function make_rpm { + cd $PKG_SRC_DIR + envsub ota.toml.template > $PKG_NAME.toml -cd `dirname $0` -PKG_SRC_DIR=`pwd` + fpm -s dir -t rpm -n ${PKG_NAME} -v ${PKG_VER} --prefix ${PREFIX} -a native \ + --rpm-service $PKG_SRC_DIR/rpm/ota-client.service \ + ../target/release/ota_plus_client=ota_plus_client $PKG_NAME.toml=ota.toml -workdir="${TMPDIR:-/tmp}/pkg-ota-plus-client-$$" -cp -pr deb $workdir -cd $workdir + mv -n ota-plus-client*.rpm $dest + rm $PKG_NAME.toml +} -mkdir -p $PKG_DIR/bin -mkdir -p $PKG_DIR/etc -envsub ${PKG_SRC_DIR}/ota.toml.template > $PKG_DIR/etc/ota.toml -tar czf $PKG_TARBALL $PKG_DIR/bin $PKG_DIR/etc +if [ $# -lt 2 ]; then + echo "Usage: $0 " + echo "packages: deb rpm" + exit 1 +fi -cd $PKG_DIR -debuild -i -us -uc -b -cd .. -cp ota-plus-client*.deb "${dest}" +package="${1}" +dest="${2}" + +echo "Building pkg to '$dest'" +case $package in + "deb" ) + make_deb + ;; + "rpm" ) + make_rpm + ;; + *) + echo "unknown package $package" + exit 2 +esac -cd $PKG_SRC_DIR -rm -rf $workdir diff --git a/pkg/rpm/Dockerfile b/pkg/rpm/Dockerfile new file mode 100644 index 0000000..d0635b8 --- /dev/null +++ b/pkg/rpm/Dockerfile @@ -0,0 +1,22 @@ +FROM clux/muslrust +# for statically linking binaries; see https://github.com/clux/muslrust + +ENV FPM_VER=1.4.0 + +RUN apt-get update \ + && apt-get install -y \ + openssl \ + patch \ + rpm \ + ruby \ + ruby-dev \ + && rm -rf /var/lib/apt/lists/* + +# install fpm +RUN gem install fpm -v ${FPM_VER} +# apply patch to add --rpm-service flag +COPY pr862.patch / +RUN patch -i /pr862.patch /var/lib/gems/2.1.0/gems/fpm-${FPM_VER}/lib/fpm/package/rpm.rb + +WORKDIR /build +CMD ["make", "rpm"] diff --git a/pkg/rpm/ota-client.service b/pkg/rpm/ota-client.service new file mode 100644 index 0000000..9c2dafd --- /dev/null +++ b/pkg/rpm/ota-client.service @@ -0,0 +1,10 @@ +[Unit] +Description=OTA+ Client +After=network.target + +[Service] +Type=simple +ExecStart=/opt/ats/ota_plus_client --config /opt/ats/ota.toml + +[Install] +WantedBy=multi-user.target diff --git a/pkg/rpm/pr862.patch b/pkg/rpm/pr862.patch new file mode 100644 index 0000000..01bf19c --- /dev/null +++ b/pkg/rpm/pr862.patch @@ -0,0 +1,40 @@ +From 6914bb60ac23b4b03520202046ae0d4ab1e734ad Mon Sep 17 00:00:00 2001 +From: Robert Helmer +Date: Tue, 24 Feb 2015 07:44:03 -0800 +Subject: [PATCH] add basic systemd service option + +--- + lib/fpm/package/rpm.rb | 13 +++++++++++++ + 1 file changed, 13 insertions(+) + +diff --git a/lib/fpm/package/rpm.rb b/lib/fpm/package/rpm.rb +index 8310d51..48ffc73 100644 +--- a/lib/fpm/package/rpm.rb ++++ b/lib/fpm/package/rpm.rb +@@ -104,6 +104,11 @@ class FPM::Package::RPM < FPM::Package + next File.expand_path(file) + end + ++ option "--service", "FILEPATH", "Add FILEPATH as a systemd service", ++ :multivalued => true do |file| ++ next File.expand_path(file) ++ end ++ + rpmbuild_filter_from_provides = [] + option "--filter-from-provides", "REGEX", + "Set %filter_from_provides to the supplied REGEX." do |filter_from_provides| +@@ -471,6 +476,14 @@ def output(output_path) + File.chmod(0755, dest_init) + end + ++ # add service script if present ++ (attributes[:rpm_service_list] or []).each do |service| ++ name = File.basename(service) ++ dest_service = File.join(staging_path, "usr/lib/systemd/system/#{name}") ++ FileUtils.mkdir_p(File.dirname(dest_service)) ++ FileUtils.cp service, dest_service ++ end ++ + (attributes[:rpm_rpmbuild_define] or []).each do |define| + args += ["--define", define] + end diff --git a/src/package_manager/rpm.rs b/src/package_manager/rpm.rs index 749e8dd..1381d7b 100644 --- a/src/package_manager/rpm.rs +++ b/src/package_manager/rpm.rs @@ -1,7 +1,9 @@ +use std::process::Command; use datatype::Error; use datatype::OtaConfig; use datatype::Package; use package_manager::PackageManager; +use package_manager::dpkg::parse_package as parse_package; pub struct Rpm; @@ -9,13 +11,27 @@ pub struct Rpm; pub static RPM: &'static PackageManager = &Rpm; impl PackageManager for Rpm { - fn installed_packages(&self, _: &OtaConfig) -> Result, Error> { - unimplemented!(); + Command::new("rpm").arg("-qa").arg("--queryformat").arg("%{NAME} %{SIZE}\n") + .output() + .map_err(|e| Error::PackageError(format!("Error fetching packages: {}", e))) + .and_then(|c| { + String::from_utf8(c.stdout) + .map_err(|e| Error::ParseError(format!("Error parsing package: {}", e))) + .map(|s| s.lines().map(|n| String::from(n)).collect::>()) + }) + .and_then(|lines| { + lines.iter() + .map(|line| parse_package(line)) + .collect::, _>>() + }) } - fn install_package(&self, _: &OtaConfig, _: &str) -> Result<(), Error> { - unimplemented!(); + fn install_package(&self, _: &OtaConfig, path: &str) -> Result<(), Error> { + let output = try!(Command::new("rpm").arg("-ivh").arg(path) + .output()); + String::from_utf8(output.stdout) + .map(|o| println!("{}", o)) + .map_err(|e| Error::ParseError(format!("Error parsing package manager output: {}", e))) } - } -- cgit v1.2.1 From 57b0a4893ae99f70b63d2061c44c4710b4ab5ae9 Mon Sep 17 00:00:00 2001 From: Alex Humphreys Date: Wed, 6 Apr 2016 11:48:06 +0200 Subject: Add unit file for yocto --- pkg/yocto/ota-plus-client.service | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 pkg/yocto/ota-plus-client.service diff --git a/pkg/yocto/ota-plus-client.service b/pkg/yocto/ota-plus-client.service new file mode 100644 index 0000000..e967ab3 --- /dev/null +++ b/pkg/yocto/ota-plus-client.service @@ -0,0 +1,9 @@ +[Unit] +Description=OTA+ Client +After=network.target + +[Service] +ExecStart=/usr/bin/ota_plus_client --config /etc/ota.toml + +[Install] +WantedBy=multi-user.target -- cgit v1.2.1 From 5018c2273c580680ad8b32c37b2b710d65df0295 Mon Sep 17 00:00:00 2001 From: Txus Date: Wed, 6 Apr 2016 14:43:38 +0200 Subject: Expose events via websocket, and make everything actors --- Cargo.toml | 3 +- ota.toml | 2 +- pkg/ota.toml.template | 3 +- src/datatype/access_token.rs | 2 +- src/datatype/command.rs | 12 ++++ src/datatype/config.rs | 10 +-- src/datatype/error.rs | 5 +- src/datatype/event.rs | 12 ++++ src/datatype/mod.rs | 6 +- src/datatype/package_manager.rs | 5 +- src/datatype/update_request.rs | 8 +++ src/interpreter.rs | 80 +++++++++++++++++++++ src/lib.rs | 4 ++ src/main.rs | 151 ++++++++++++++++++++++++++++------------ src/package_manager/dpkg.rs | 2 +- src/package_manager/rpm.rs | 2 +- src/pubsub.rs | 32 +++++++++ src/read_interpret.rs | 21 +----- src/ui/mod.rs | 3 + src/ui/websocket.rs | 60 ++++++++++++++++ 20 files changed, 345 insertions(+), 78 deletions(-) create mode 100644 src/datatype/command.rs create mode 100644 src/datatype/event.rs create mode 100644 src/interpreter.rs create mode 100644 src/pubsub.rs create mode 100644 src/ui/mod.rs create mode 100644 src/ui/websocket.rs diff --git a/Cargo.toml b/Cargo.toml index 8e9462f..824addf 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,9 +14,10 @@ doc = false [dependencies] env_logger = "*" +ws = "*" hyper = "*" log = "*" rustc-serialize = "*" toml = "*" getopts = "*" -tempfile = "*" \ No newline at end of file +tempfile = "*" diff --git a/ota.toml b/ota.toml index 50726fa..1fc700b 100644 --- a/ota.toml +++ b/ota.toml @@ -6,7 +6,7 @@ secret = "secret" [ota] server = "http://127.0.0.1:8080" vin = "V1234567890123456" -packages_dir = "/tmp" +packages_dir = "/tmp/" package_manager = "dpkg" [test] diff --git a/pkg/ota.toml.template b/pkg/ota.toml.template index 5d69be1..d2d21da 100644 --- a/pkg/ota.toml.template +++ b/pkg/ota.toml.template @@ -6,7 +6,8 @@ secret = "${OTA_AUTH_SECRET}" [ota] server = "${OTA_SERVER_URL}" vin = "${OTA_CLIENT_VIN}" -packages_dir = "/tmp" +polling_interval = 10 +packages_dir = "/tmp/" package_manager = "dpkg" [test] diff --git a/src/datatype/access_token.rs b/src/datatype/access_token.rs index afd359b..6a10f99 100644 --- a/src/datatype/access_token.rs +++ b/src/datatype/access_token.rs @@ -1,5 +1,5 @@ -#[derive(RustcDecodable, Debug, PartialEq)] +#[derive(RustcDecodable, Debug, PartialEq, Clone)] pub struct AccessToken { pub access_token: String, pub token_type: String, diff --git a/src/datatype/command.rs b/src/datatype/command.rs new file mode 100644 index 0000000..d188b36 --- /dev/null +++ b/src/datatype/command.rs @@ -0,0 +1,12 @@ +use rustc_serialize::{Encodable}; +use datatype::UpdateRequestId; + +#[derive(RustcDecodable, RustcEncodable, PartialEq, Eq, Debug)] +pub enum Command { + // UI + GetPendingUpdates, + AcceptUpdate(UpdateRequestId), + + PostInstalledPackages, + ListPackages +} diff --git a/src/datatype/config.rs b/src/datatype/config.rs index 71c6df0..f01eb74 100644 --- a/src/datatype/config.rs +++ b/src/datatype/config.rs @@ -11,29 +11,30 @@ use datatype::error::ParseReason::{InvalidToml, InvalidSection}; use datatype::PackageManager; -#[derive(Default, PartialEq, Eq, Debug)] +#[derive(Default, PartialEq, Eq, Debug, Clone)] pub struct Config { pub auth: AuthConfig, pub ota: OtaConfig, pub test: TestConfig, } -#[derive(RustcDecodable, PartialEq, Eq, Debug)] +#[derive(RustcDecodable, PartialEq, Eq, Debug, Clone)] pub struct AuthConfig { pub server: Url, pub client_id: String, pub secret: String } -#[derive(RustcDecodable, PartialEq, Eq, Debug)] +#[derive(RustcDecodable, PartialEq, Eq, Debug, Clone)] pub struct OtaConfig { pub server: Url, pub vin: String, + pub polling_interval: u64, pub packages_dir: String, pub package_manager: PackageManager, } -#[derive(RustcDecodable, PartialEq, Eq, Debug)] +#[derive(RustcDecodable, PartialEq, Eq, Debug, Clone)] pub struct TestConfig { pub looping: bool, } @@ -53,6 +54,7 @@ impl Default for OtaConfig { OtaConfig { server: Url::parse("http://127.0.0.1:8080").unwrap(), vin: "V1234567890123456".to_string(), + polling_interval: 10, packages_dir: "/tmp".to_string(), package_manager: PackageManager::Dpkg, } diff --git a/src/datatype/error.rs b/src/datatype/error.rs index 2ea3935..164ceb6 100644 --- a/src/datatype/error.rs +++ b/src/datatype/error.rs @@ -1,4 +1,5 @@ use rustc_serialize::json; +use ws; use std::convert::From; use std::fmt::{Display, Formatter, Result as FmtResult}; use std::io; @@ -15,7 +16,8 @@ pub enum Error { Config(ConfigReason), JsonEncode(json::EncoderError), JsonDecode(json::DecoderError), - Io(io::Error) + Io(io::Error), + Websocket(ws::Error) } impl From for Error { @@ -66,6 +68,7 @@ impl Display for Error { Error::JsonEncode(ref e) => format!("Failed to encode JSON: {}", e.clone()), Error::JsonDecode(ref e) => format!("Failed to decode JSON: {}", e.clone()), Error::Io(ref e) => format!("IO Error{:?}", e.clone()), + Error::Websocket(ref e) => format!("Websocket Error{:?}", e.clone()), }; write!(f, "{}", inner) } diff --git a/src/datatype/event.rs b/src/datatype/event.rs new file mode 100644 index 0000000..7c67da8 --- /dev/null +++ b/src/datatype/event.rs @@ -0,0 +1,12 @@ +use rustc_serialize::{Encodable}; + +use datatype::{UpdateRequestId, UpdateState}; + +#[derive(RustcDecodable, RustcEncodable, PartialEq, Eq, Debug, Clone)] +pub enum Event { + NewUpdateAvailable(UpdateRequestId), + UpdateStateChanged(UpdateRequestId, UpdateState), + UpdateErrored(UpdateRequestId, String), + Error(String), + Batch(Vec) +} diff --git a/src/datatype/mod.rs b/src/datatype/mod.rs index aa87985..1a46401 100644 --- a/src/datatype/mod.rs +++ b/src/datatype/mod.rs @@ -3,7 +3,9 @@ pub use self::config::{Config, AuthConfig, OtaConfig, TestConfig}; pub use self::error::Error; pub use self::package::Package; pub use self::package_manager::PackageManager; -pub use self::update_request::UpdateRequestId; +pub use self::update_request::{UpdateRequestId, UpdateState}; +pub use self::event::Event; +pub use self::command::Command; pub mod access_token; pub mod config; @@ -11,3 +13,5 @@ pub mod error; pub mod package; pub mod package_manager; pub mod update_request; +pub mod event; +pub mod command; diff --git a/src/datatype/package_manager.rs b/src/datatype/package_manager.rs index c2555e9..522e355 100644 --- a/src/datatype/package_manager.rs +++ b/src/datatype/package_manager.rs @@ -3,9 +3,10 @@ use rustc_serialize::{Decoder, Decodable}; use package_manager::PackageManager as PackageManagerTrait; use package_manager::dpkg::DPKG; use package_manager::rpm::RPM; +use package_manager::tpm::TPM; -#[derive(Debug, PartialEq, Eq)] +#[derive(Debug, PartialEq, Eq, Clone)] pub enum PackageManager { Dpkg, Rpm, @@ -26,7 +27,7 @@ impl PackageManager { match *self { PackageManager::Dpkg => DPKG, PackageManager::Rpm => RPM, - PackageManager::File(_) => unimplemented!(), + PackageManager::File(_) => TPM } } diff --git a/src/datatype/update_request.rs b/src/datatype/update_request.rs index 56b82aa..622a714 100644 --- a/src/datatype/update_request.rs +++ b/src/datatype/update_request.rs @@ -1 +1,9 @@ pub type UpdateRequestId = String; + +#[derive(RustcDecodable, RustcEncodable, PartialEq, Eq, Debug, Clone)] +pub enum UpdateState { + Accepted, + Downloading, + Installing, + Installed +} diff --git a/src/interpreter.rs b/src/interpreter.rs new file mode 100644 index 0000000..2343a7e --- /dev/null +++ b/src/interpreter.rs @@ -0,0 +1,80 @@ +use std::sync::mpsc::{Sender , Receiver}; +use std::marker::PhantomData; + +use http_client::HttpClient; +use ota_plus::{get_package_updates, download_package_update, post_packages}; +use datatype::{Event, Command, Config, AccessToken, UpdateState}; + +pub struct Interpreter<'a, C: HttpClient> { + client_type: PhantomData, + config: &'a Config, + token: AccessToken, + // Commands mpsc, events spmc + commands_rx: Receiver, + events_tx: Sender +} + +impl<'a, C: HttpClient> Interpreter<'a, C> { + pub fn new(config: &'a Config, token: AccessToken, commands_rx: Receiver, events_tx: Sender) -> Interpreter<'a, C> { + Interpreter { client_type: PhantomData, config: config, token: token, commands_rx: commands_rx, events_tx: events_tx } + } + + pub fn start(&self) { + loop { + self.interpret(self.commands_rx.recv().unwrap()); + } + } + + pub fn interpret(&self, command: Command) { + match command { + Command::GetPendingUpdates => { + debug!("Fetching package updates..."); + let response: Event = match get_package_updates::(&self.token, &self.config.ota) { + Ok(updates) => { + let update_events: Vec = updates.iter().map(move |id| Event::NewUpdateAvailable(id.clone())).collect(); + info!("New package updates available: {:?}", update_events); + Event::Batch(update_events) + }, + Err(e) => { + Event::Error(format!("{}", e)) + } + }; + let _ = self.events_tx.send(response); + }, + Command::PostInstalledPackages => { + let pkg_manager = self.config.ota.package_manager.build(); + + let _ = pkg_manager.installed_packages(&self.config.ota).and_then(|pkgs| { + debug!("Found installed packages in the system: {:?}", pkgs); + post_packages::(&self.token, &self.config.ota, &pkgs) + }).map(|_| { + info!("Posted installed packages to the server."); + }).map_err(|e| { + error!("Error fetching/posting installed packages: {:?}.", e); + }); + }, + Command::AcceptUpdate(ref id) => { + info!("Accepting update {}...", id); + let _ = self.events_tx.send(Event::UpdateStateChanged(id.clone(), UpdateState::Accepted)); + let _ = download_package_update::(&self.token, &self.config.ota, id) + .and_then(|path| { + info!("Downloaded at {:?}. Installing...", path); + let _ = self.events_tx.send(Event::UpdateStateChanged(id.clone(), UpdateState::Installing)); + let pkg_manager = self.config.ota.package_manager.build(); + pkg_manager.install_package(&self.config.ota, path.to_str().unwrap()) + }).map(|_| { + info!("Update installed successfully."); + let _ = self.events_tx.send(Event::UpdateStateChanged(id.clone(), UpdateState::Installed)); + self.interpret(Command::PostInstalledPackages); + }).map(|_| { + debug!("Notified the server of the new state."); + }).map_err(|e| { + error!("Error updating. State: {:?}", e); + let _ = self.events_tx.send(Event::UpdateErrored(id.clone(), format!("{:?}", e))); + }); + + }, + Command::ListPackages => debug!("Listing packages!") + } + } +} diff --git a/src/lib.rs b/src/lib.rs index e8911c0..4b2394d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,6 +1,7 @@ extern crate hyper; #[macro_use] extern crate log; extern crate rustc_serialize; +extern crate ws; extern crate tempfile; extern crate toml; @@ -10,3 +11,6 @@ pub mod http_client; pub mod ota_plus; pub mod package_manager; pub mod read_interpret; +pub mod interpreter; +pub mod pubsub; +pub mod ui; diff --git a/src/main.rs b/src/main.rs index 7b29e77..c56bea9 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,6 +1,9 @@ +#[macro_use] extern crate log; extern crate env_logger; extern crate getopts; extern crate hyper; +extern crate ws; +extern crate rustc_serialize; #[macro_use] extern crate libotaplus; use getopts::Options; @@ -8,70 +11,126 @@ use hyper::Url; use std::env; use libotaplus::auth_plus::authenticate; -use libotaplus::datatype::config; -use libotaplus::datatype::Config; -use libotaplus::datatype::Error; -use libotaplus::datatype::PackageManager as PackageManagerType; +use libotaplus::datatype::{config, Config, PackageManager as PackageManagerType, Event, Command}; +use libotaplus::ui::spawn_websocket_server; use libotaplus::http_client::HttpClient; -use libotaplus::ota_plus::{post_packages, get_package_updates, download_package_update}; -use libotaplus::package_manager::{PackageManager, Dpkg}; +use libotaplus::package_manager::Dpkg; use libotaplus::read_interpret::ReplEnv; use libotaplus::read_interpret; - +use libotaplus::pubsub; +use libotaplus::interpreter::Interpreter; + +use rustc_serialize::json; +use std::sync::mpsc::{Sender, Receiver, channel}; + +use std::thread; +use std::time::Duration; + +use std::collections::HashMap; +use std::sync::{Arc, Mutex}; + +use ws::{Sender as WsSender}; + +macro_rules! spawn_thread { + ($name:expr, $body:block) => { + { + match thread::Builder::new().name($name.to_string()).spawn(move || { + info!("Spawning {}", $name.to_string()); + $body + }) { + Err(e) => panic!("Couldn't spawn {}: {}", $name, e), + Ok(handle) => handle + } + } + } +} fn main() { env_logger::init().unwrap(); let config = build_config(); + let config2 = config.clone(); + let config3 = config.clone(); + + info!("Authenticating against AuthPlus..."); + let _ = authenticate::(&config.auth).map(|token| { + let (etx, erx): (Sender, Receiver) = channel(); + let (ctx, crx): (Sender, Receiver) = channel(); + + let mut registry = pubsub::Registry::new(erx); + + { + let events_for_autoacceptor = registry.subscribe(); + let ctx_ = ctx.clone(); + spawn_thread!("Autoacceptor of software updates", { + fn dispatch(ev: &Event, outlet: Sender) { + match ev { + &Event::NewUpdateAvailable(ref id) => { + let _ = outlet.send(Command::AcceptUpdate(id.clone())); + }, + &Event::Batch(ref evs) => { + for ev in evs { + dispatch(ev, outlet.clone()) + } + }, + _ => {} + } + }; + loop { + dispatch(&events_for_autoacceptor.recv().unwrap(), ctx_.clone()) + } + }); + } - match worker::(&config, config.ota.package_manager.build()) { - Ok(()) => {}, - Err(e) => exit!("{}", e), - } - - if config.test.looping { - read_interpret::read_interpret_loop(ReplEnv::new(Dpkg)); - } - -} - -fn worker(config: &Config, pkg_manager: &PackageManager) -> Result<(), Error> { - - println!("Trying to acquire access token."); - let token = try!(authenticate::(&config.auth)); + spawn_thread!("Interpreter", { + Interpreter::::new(&config2, token.clone(), crx, etx).start(); + }); + + let events_for_ws = registry.subscribe(); + { + let all_clients = Arc::new(Mutex::new(HashMap::new())); + let all_clients_ = all_clients.clone(); + spawn_thread!("Websocket Event Broadcast", { + loop { + let event = events_for_ws.recv().unwrap(); + let clients = all_clients_.lock().unwrap().clone(); + for (_, client) in clients { + let x: WsSender = client; + let _ = x.send(json::encode(&event).unwrap()); + } + } + }); + + let ctx_ = ctx.clone(); + spawn_thread!("Websocket Server", { + let _ = spawn_websocket_server("0.0.0.0:9999", ctx_, all_clients); + }); + } - println!("Asking package manager what packages are installed on the system."); - let pkgs = try!(pkg_manager.installed_packages(&config.ota)); - println!("Letting the OTA server know what packages are installed."); - try!(post_packages::(&token, &config.ota, &pkgs)); + { + let ctx_ = ctx.clone(); + spawn_thread!("Update poller", { + loop { + let _ = ctx_.send(Command::GetPendingUpdates); + thread::sleep(Duration::from_secs(config3.ota.polling_interval)); + } + }); + } - println!("Fetching possible new package updates."); - let updates = try!(get_package_updates::(&token, &config.ota)); + spawn_thread!("PubSub Registry", { registry.start(); }); - let updates_len = updates.iter().len(); - println!("Got {} new updates. Downloading...", updates_len); + // Perform initial sync + let _ = ctx.clone().send(Command::PostInstalledPackages); - let mut paths = Vec::with_capacity(updates_len); + thread::sleep(Duration::from_secs(60000000)); + }); - for update in &updates { - let path = try!(download_package_update::(&token, &config.ota, update) - .map_err(|e| Error::ClientError( - format!("Couldn't download update {:?}: {}", update, e)))); - paths.push(path); - } - - for path in &paths { - println!("Installing package at {:?}...", path); - try!(pkg_manager.install_package(&config.ota, path.as_path().to_str().unwrap())); - println!("Installed."); + if config.test.looping { + read_interpret::read_interpret_loop(ReplEnv::new(Dpkg)); } - println!("Installed packages were posted successfully."); - - return Ok(()) - } fn build_config() -> Config { diff --git a/src/package_manager/dpkg.rs b/src/package_manager/dpkg.rs index ca425a5..8b8783c 100644 --- a/src/package_manager/dpkg.rs +++ b/src/package_manager/dpkg.rs @@ -35,7 +35,7 @@ impl PackageManager for Dpkg { .output()); String::from_utf8(output.stdout) - .map(|o| println!("{}", o)) + .map(|o| debug!("{}", o)) .map_err(|e| Error::ParseError(format!("Error parsing package manager output: {}", e))) } diff --git a/src/package_manager/rpm.rs b/src/package_manager/rpm.rs index 1381d7b..afb642a 100644 --- a/src/package_manager/rpm.rs +++ b/src/package_manager/rpm.rs @@ -31,7 +31,7 @@ impl PackageManager for Rpm { let output = try!(Command::new("rpm").arg("-ivh").arg(path) .output()); String::from_utf8(output.stdout) - .map(|o| println!("{}", o)) + .map(|o| debug!("{}", o)) .map_err(|e| Error::ParseError(format!("Error parsing package manager output: {}", e))) } } diff --git a/src/pubsub.rs b/src/pubsub.rs new file mode 100644 index 0000000..55caf83 --- /dev/null +++ b/src/pubsub.rs @@ -0,0 +1,32 @@ +use std::collections::HashMap; +use std::sync::{Mutex}; +use std::sync::mpsc::{Sender, Receiver, channel}; + +use datatype::Event; + +pub struct Registry { + last_idx: Mutex, + registry: HashMap>, + events_rx: Receiver +} + +impl Registry { + pub fn new(events_rx: Receiver) -> Registry { + Registry { last_idx: Mutex::new(0), registry: HashMap::new(), events_rx: events_rx } + } + pub fn start(&self) { + loop { + let event = self.events_rx.recv().unwrap(); + for (_, subscriber) in &self.registry { + let _ = subscriber.send(event.clone()); + } + } + } + pub fn subscribe(&mut self) -> Receiver { + let (tx, rx) = channel(); + let mut counter = self.last_idx.lock().unwrap(); + *counter += 1; + self.registry.insert(*counter, tx); + rx + } +} diff --git a/src/read_interpret.rs b/src/read_interpret.rs index 8adbc50..888d27c 100644 --- a/src/read_interpret.rs +++ b/src/read_interpret.rs @@ -2,6 +2,7 @@ use std::io; use std::str::FromStr; use package_manager::PackageManager; +use datatype::Command; pub struct ReplEnv { package_manager: M, @@ -13,10 +14,6 @@ impl ReplEnv { } } -enum Command { - ListPackages, -} - impl FromStr for Command { type Err = (); fn from_str(s: &str) -> Result { @@ -29,25 +26,14 @@ impl FromStr for Command { fn list_packages(_: &M) where M: PackageManager { -/* - let _ = package_manager.installed_packages() - .and_then(|pkgs| { - println!("Found {} packages.", pkgs.iter().len()); - for pkg in pkgs.iter() { - println!("{}", pkg); - } - Ok(()) - }).map_err(|e| { - error!("Can't list packages: {}", e) - }); -*/ unimplemented!(); } fn interpret(env: &ReplEnv, cmd: Command) where M: PackageManager { match cmd { - Command::ListPackages => list_packages(&env.package_manager) + Command::ListPackages => list_packages(&env.package_manager), + _ => {} }; } @@ -62,5 +48,4 @@ pub fn read_interpret_loop(env: ReplEnv) let _ = input.trim().parse().map(|cmd| interpret(&env, cmd)); } - } diff --git a/src/ui/mod.rs b/src/ui/mod.rs new file mode 100644 index 0000000..2e6a0d3 --- /dev/null +++ b/src/ui/mod.rs @@ -0,0 +1,3 @@ +pub use self::websocket::spawn_websocket_server; + +pub mod websocket; diff --git a/src/ui/websocket.rs b/src/ui/websocket.rs new file mode 100644 index 0000000..488ba93 --- /dev/null +++ b/src/ui/websocket.rs @@ -0,0 +1,60 @@ +use ws::{listen, Message, Sender as WsSender, Handler, Handshake, Result as WsResult, CloseCode}; +use ws::util::Token; +use rustc_serialize::json; + +use datatype::{Event, Command, Error}; + +use std::sync::mpsc::Sender; + +use std::thread; +use std::collections::HashMap; +use std::sync::{Arc, Mutex}; + +pub type SharedClients = Arc>>; + +pub struct WebsocketHandler { + all_clients: SharedClients, + out: WsSender, + commands_tx: Sender +} + +impl Handler for WebsocketHandler { + fn on_open(&mut self, _: Handshake) -> WsResult<()> { + self.all_clients.lock().unwrap().insert(self.out.token(), self.out.clone()); + Ok(()) + } + + fn on_message(&mut self, msg: Message) -> WsResult<()> { + if let Message::Text(payload) = msg { + match json::decode(&payload) { + Ok(command) => { let _ = self.commands_tx.send(command); }, + Err(e) => { + let err = format!("Invalid command: {}. Reason: {}", payload, e); + error!("{}", err); + let _ = self.out.send(json::encode(&Event::Error(err)).unwrap()); + } + } + }; + Ok(()) + } + + fn on_close(&mut self, _: CloseCode, _: &str) { + self.all_clients.lock().unwrap().remove(&self.out.token()); + } +} + +pub fn spawn_websocket_server_async(addr: &'static str, ctx: Sender, all_clients: SharedClients) -> Result, Error> { + Ok(try!(thread::Builder::new().name("ui".to_string()).spawn(move || { + spawn_websocket_server(addr, ctx, all_clients).unwrap_or_else(|e| error!("{}", e)) + }))) +} + +pub fn spawn_websocket_server(addr: &'static str, ctx: Sender, all_clients: SharedClients) -> Result<(), Error> { + listen(addr, move |out| { + WebsocketHandler { + all_clients: all_clients.clone(), + out: out, + commands_tx: ctx.clone() + } + }).map_err(Error::Websocket) +} -- cgit v1.2.1 From 7db48123d7537666c72dbd926de0c554f99b6ff8 Mon Sep 17 00:00:00 2001 From: Txus Date: Wed, 6 Apr 2016 16:58:25 +0200 Subject: Integrate REPL into system and refactor Interpreter --- src/datatype/command.rs | 2 +- src/datatype/event.rs | 3 +- src/datatype/package.rs | 2 +- src/interpreter.rs | 113 ++++++++++++++++++++++++++++-------------------- src/lib.rs | 2 +- src/main.rs | 22 +++++----- src/read_interpret.rs | 51 ---------------------- src/repl.rs | 36 +++++++++++++++ 8 files changed, 118 insertions(+), 113 deletions(-) delete mode 100644 src/read_interpret.rs create mode 100644 src/repl.rs diff --git a/src/datatype/command.rs b/src/datatype/command.rs index d188b36..72e000c 100644 --- a/src/datatype/command.rs +++ b/src/datatype/command.rs @@ -8,5 +8,5 @@ pub enum Command { AcceptUpdate(UpdateRequestId), PostInstalledPackages, - ListPackages + ListInstalledPackages } diff --git a/src/datatype/event.rs b/src/datatype/event.rs index 7c67da8..37d1275 100644 --- a/src/datatype/event.rs +++ b/src/datatype/event.rs @@ -1,6 +1,6 @@ use rustc_serialize::{Encodable}; -use datatype::{UpdateRequestId, UpdateState}; +use datatype::{UpdateRequestId, UpdateState, Package}; #[derive(RustcDecodable, RustcEncodable, PartialEq, Eq, Debug, Clone)] pub enum Event { @@ -8,5 +8,6 @@ pub enum Event { UpdateStateChanged(UpdateRequestId, UpdateState), UpdateErrored(UpdateRequestId, String), Error(String), + FoundInstalledPackages(Vec), Batch(Vec) } diff --git a/src/datatype/package.rs b/src/datatype/package.rs index 65fffb7..185992f 100644 --- a/src/datatype/package.rs +++ b/src/datatype/package.rs @@ -2,7 +2,7 @@ use std::fmt::{Display, Formatter, Result as FmtResult}; pub type Version = String; -#[derive(Debug, PartialEq, Eq, RustcEncodable, RustcDecodable)] +#[derive(Debug, PartialEq, Eq, RustcEncodable, RustcDecodable, Clone)] pub struct Package { pub name: String, pub version: Version diff --git a/src/interpreter.rs b/src/interpreter.rs index 2343a7e..6fdb6ed 100644 --- a/src/interpreter.rs +++ b/src/interpreter.rs @@ -3,7 +3,7 @@ use std::marker::PhantomData; use http_client::HttpClient; use ota_plus::{get_package_updates, download_package_update, post_packages}; -use datatype::{Event, Command, Config, AccessToken, UpdateState}; +use datatype::{Event, Command, Config, AccessToken, UpdateState, Package, Error, UpdateRequestId}; pub struct Interpreter<'a, C: HttpClient> { client_type: PhantomData, @@ -27,54 +27,73 @@ impl<'a, C: HttpClient> Interpreter<'a, C> { pub fn interpret(&self, command: Command) { match command { - Command::GetPendingUpdates => { - debug!("Fetching package updates..."); - let response: Event = match get_package_updates::(&self.token, &self.config.ota) { - Ok(updates) => { - let update_events: Vec = updates.iter().map(move |id| Event::NewUpdateAvailable(id.clone())).collect(); - info!("New package updates available: {:?}", update_events); - Event::Batch(update_events) - }, - Err(e) => { - Event::Error(format!("{}", e)) - } - }; - let _ = self.events_tx.send(response); - }, - Command::PostInstalledPackages => { - let pkg_manager = self.config.ota.package_manager.build(); + Command::GetPendingUpdates => self.get_pending_updates(), + Command::PostInstalledPackages => self.post_installed_packages(), + Command::AcceptUpdate(ref id) => self.accept_update(id), + Command::ListInstalledPackages => self.list_installed_packages() + } + } - let _ = pkg_manager.installed_packages(&self.config.ota).and_then(|pkgs| { - debug!("Found installed packages in the system: {:?}", pkgs); - post_packages::(&self.token, &self.config.ota, &pkgs) - }).map(|_| { - info!("Posted installed packages to the server."); - }).map_err(|e| { - error!("Error fetching/posting installed packages: {:?}.", e); - }); - }, - Command::AcceptUpdate(ref id) => { - info!("Accepting update {}...", id); - let _ = self.events_tx.send(Event::UpdateStateChanged(id.clone(), UpdateState::Accepted)); - let _ = download_package_update::(&self.token, &self.config.ota, id) - .and_then(|path| { - info!("Downloaded at {:?}. Installing...", path); - let _ = self.events_tx.send(Event::UpdateStateChanged(id.clone(), UpdateState::Installing)); - let pkg_manager = self.config.ota.package_manager.build(); - pkg_manager.install_package(&self.config.ota, path.to_str().unwrap()) - }).map(|_| { - info!("Update installed successfully."); - let _ = self.events_tx.send(Event::UpdateStateChanged(id.clone(), UpdateState::Installed)); - self.interpret(Command::PostInstalledPackages); - }).map(|_| { - debug!("Notified the server of the new state."); - }).map_err(|e| { - error!("Error updating. State: {:?}", e); - let _ = self.events_tx.send(Event::UpdateErrored(id.clone(), format!("{:?}", e))); - }); + fn publish(&self, event: Event) { + let _ = self.events_tx.send(event); + } + + fn get_installed_packages(&self) -> Result, Error> { + let pkg_manager = self.config.ota.package_manager.build(); + pkg_manager.installed_packages(&self.config.ota) + } + fn get_pending_updates(&self) { + debug!("Fetching package updates..."); + let response: Event = match get_package_updates::(&self.token, &self.config.ota) { + Ok(updates) => { + let update_events: Vec = updates.iter().map(move |id| Event::NewUpdateAvailable(id.clone())).collect(); + info!("New package updates available: {:?}", update_events); + Event::Batch(update_events) }, - Command::ListPackages => debug!("Listing packages!") - } + Err(e) => { + Event::Error(format!("{}", e)) + } + }; + self.publish(response); + } + + fn post_installed_packages(&self) { + let _ = self.get_installed_packages().and_then(|pkgs| { + debug!("Found installed packages in the system: {:?}", pkgs); + post_packages::(&self.token, &self.config.ota, &pkgs) + }).map(|_| { + info!("Posted installed packages to the server."); + }).map_err(|e| { + error!("Error fetching/posting installed packages: {:?}.", e); + }); + } + + fn accept_update(&self, id: &UpdateRequestId) { + info!("Accepting update {}...", id); + self.publish(Event::UpdateStateChanged(id.clone(), UpdateState::Accepted)); + let _ = download_package_update::(&self.token, &self.config.ota, id) + .and_then(|path| { + info!("Downloaded at {:?}. Installing...", path); + self.publish(Event::UpdateStateChanged(id.clone(), UpdateState::Installing)); + let pkg_manager = self.config.ota.package_manager.build(); + pkg_manager.install_package(&self.config.ota, path.to_str().unwrap()) + }).map(|_| { + info!("Update installed successfully."); + self.publish(Event::UpdateStateChanged(id.clone(), UpdateState::Installed)); + self.interpret(Command::PostInstalledPackages); + }).map(|_| { + debug!("Notified the server of the new state."); + }).map_err(|e| { + error!("Error updating. State: {:?}", e); + self.publish(Event::UpdateErrored(id.clone(), format!("{:?}", e))); + }); + } + + fn list_installed_packages(&self) { + let _ = self.get_installed_packages().and_then(|pkgs| { + self.publish(Event::FoundInstalledPackages(pkgs.clone())); + Ok(()) + }); } } diff --git a/src/lib.rs b/src/lib.rs index 4b2394d..7fe313d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -10,7 +10,7 @@ pub mod datatype; pub mod http_client; pub mod ota_plus; pub mod package_manager; -pub mod read_interpret; +pub mod repl; pub mod interpreter; pub mod pubsub; pub mod ui; diff --git a/src/main.rs b/src/main.rs index c56bea9..d4c648e 100644 --- a/src/main.rs +++ b/src/main.rs @@ -14,9 +14,7 @@ use libotaplus::auth_plus::authenticate; use libotaplus::datatype::{config, Config, PackageManager as PackageManagerType, Event, Command}; use libotaplus::ui::spawn_websocket_server; use libotaplus::http_client::HttpClient; -use libotaplus::package_manager::Dpkg; -use libotaplus::read_interpret::ReplEnv; -use libotaplus::read_interpret; +use libotaplus::repl; use libotaplus::pubsub; use libotaplus::interpreter::Interpreter; @@ -119,18 +117,20 @@ fn main() { }); } + let events_for_repl = registry.subscribe(); + spawn_thread!("PubSub Registry", { registry.start(); }); // Perform initial sync let _ = ctx.clone().send(Command::PostInstalledPackages); - thread::sleep(Duration::from_secs(60000000)); + if config.test.looping { + repl::start(events_for_repl, ctx.clone()); + } else { + thread::sleep(Duration::from_secs(60000000)); + } }); - if config.test.looping { - read_interpret::read_interpret_loop(ReplEnv::new(Dpkg)); - } - } fn build_config() -> Config { @@ -157,8 +157,8 @@ fn build_config() -> Config { "change downloaded directory for packages", "PATH"); opts.optopt("", "ota-package-manager", "change package manager", "MANAGER"); - opts.optflag("", "test-looping", - "enable read-interpret test loop"); + opts.optflag("", "repl", + "enable repl"); let matches = opts.parse(&args[1..]) .unwrap_or_else(|err| panic!(err.to_string())); @@ -216,7 +216,7 @@ fn build_config() -> Config { } } - if matches.opt_present("test-looping") { + if matches.opt_present("repl") { config.test.looping = true; } diff --git a/src/read_interpret.rs b/src/read_interpret.rs deleted file mode 100644 index 888d27c..0000000 --- a/src/read_interpret.rs +++ /dev/null @@ -1,51 +0,0 @@ -use std::io; -use std::str::FromStr; - -use package_manager::PackageManager; -use datatype::Command; - -pub struct ReplEnv { - package_manager: M, -} - -impl ReplEnv { - pub fn new(manager: M) -> ReplEnv { - ReplEnv { package_manager: manager } - } -} - -impl FromStr for Command { - type Err = (); - fn from_str(s: &str) -> Result { - match s { - "ListPackages" => Ok(Command::ListPackages), - _ => Err(()), - } - } -} - -fn list_packages(_: &M) - where M: PackageManager { - unimplemented!(); -} - -fn interpret(env: &ReplEnv, cmd: Command) - where M: PackageManager { - match cmd { - Command::ListPackages => list_packages(&env.package_manager), - _ => {} - }; -} - -pub fn read_interpret_loop(env: ReplEnv) - where M: PackageManager { - - loop { - - let mut input = String::new(); - let _ = io::stdin().read_line(&mut input); - - let _ = input.trim().parse().map(|cmd| interpret(&env, cmd)); - - } -} diff --git a/src/repl.rs b/src/repl.rs new file mode 100644 index 0000000..242c99f --- /dev/null +++ b/src/repl.rs @@ -0,0 +1,36 @@ +use std::io; +use std::str::FromStr; +use std::sync::mpsc::{Sender, Receiver}; + +use std::thread; + +use datatype::{Command, Event}; + +impl FromStr for Command { + type Err = (); + fn from_str(s: &str) -> Result { + match s { + "GetPendingUpdates" => Ok(Command::GetPendingUpdates), + "PostInstalledPackages" => Ok(Command::PostInstalledPackages), + "ListInstalledPackages" => Ok(Command::ListInstalledPackages), + _ => Err(()), + } + } +} + +pub fn start(erx: Receiver, ctx: Sender) { + let _ = thread::Builder::new().name("REPL Print loop".to_string()).spawn(move || { + loop { + println!("# => {:?}", erx.recv().unwrap()); + } + }); + + println!("Ota Plus Client REPL started."); + loop { + let mut input = String::new(); + print!("> "); + let _ = io::stdin().read_line(&mut input); + + let _ = input.trim().parse().map(|cmd| ctx.send(cmd)); + } +} -- cgit v1.2.1 From e11c4455ba0b45d50dc09d3ca022e338d0ca9124 Mon Sep 17 00:00:00 2001 From: Txus Date: Thu, 7 Apr 2016 10:52:14 +0200 Subject: Accepted is redundant -- Downloading is sufficient --- src/datatype/update_request.rs | 1 - src/interpreter.rs | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/src/datatype/update_request.rs b/src/datatype/update_request.rs index 622a714..d13a650 100644 --- a/src/datatype/update_request.rs +++ b/src/datatype/update_request.rs @@ -2,7 +2,6 @@ pub type UpdateRequestId = String; #[derive(RustcDecodable, RustcEncodable, PartialEq, Eq, Debug, Clone)] pub enum UpdateState { - Accepted, Downloading, Installing, Installed diff --git a/src/interpreter.rs b/src/interpreter.rs index 6fdb6ed..fca4bb5 100644 --- a/src/interpreter.rs +++ b/src/interpreter.rs @@ -71,7 +71,7 @@ impl<'a, C: HttpClient> Interpreter<'a, C> { fn accept_update(&self, id: &UpdateRequestId) { info!("Accepting update {}...", id); - self.publish(Event::UpdateStateChanged(id.clone(), UpdateState::Accepted)); + self.publish(Event::UpdateStateChanged(id.clone(), UpdateState::Downloading)); let _ = download_package_update::(&self.token, &self.config.ota, id) .and_then(|path| { info!("Downloaded at {:?}. Installing...", path); -- cgit v1.2.1 From f176c6b77124932b43d284016909742edadff5f9 Mon Sep 17 00:00:00 2001 From: Txus Date: Wed, 6 Apr 2016 17:25:01 +0200 Subject: Cleanup main function --- ota.toml | 1 + src/datatype/config.rs | 7 +- src/main.rs | 154 ++++++++++++++++++++++------------------- tests/ota_plus_client_tests.rs | 6 +- 4 files changed, 89 insertions(+), 79 deletions(-) diff --git a/ota.toml b/ota.toml index 1fc700b..dbf0f0f 100644 --- a/ota.toml +++ b/ota.toml @@ -6,6 +6,7 @@ secret = "secret" [ota] server = "http://127.0.0.1:8080" vin = "V1234567890123456" +polling_interval = 10 packages_dir = "/tmp/" package_manager = "dpkg" diff --git a/src/datatype/config.rs b/src/datatype/config.rs index f01eb74..e12ee41 100644 --- a/src/datatype/config.rs +++ b/src/datatype/config.rs @@ -55,7 +55,7 @@ impl Default for OtaConfig { server: Url::parse("http://127.0.0.1:8080").unwrap(), vin: "V1234567890123456".to_string(), polling_interval: 10, - packages_dir: "/tmp".to_string(), + packages_dir: "/tmp/".to_string(), package_manager: PackageManager::Dpkg, } } @@ -124,7 +124,8 @@ mod tests { [ota] server = "http://127.0.0.1:8080" vin = "V1234567890123456" - packages_dir = "/tmp" + polling_interval = 10 + packages_dir = "/tmp/" package_manager = "dpkg" [test] @@ -149,4 +150,6 @@ mod tests { Config::default()) } + + } diff --git a/src/main.rs b/src/main.rs index d4c648e..6054dfc 100644 --- a/src/main.rs +++ b/src/main.rs @@ -11,7 +11,7 @@ use hyper::Url; use std::env; use libotaplus::auth_plus::authenticate; -use libotaplus::datatype::{config, Config, PackageManager as PackageManagerType, Event, Command}; +use libotaplus::datatype::{config, Config, PackageManager as PackageManagerType, Event, Command, AccessToken}; use libotaplus::ui::spawn_websocket_server; use libotaplus::http_client::HttpClient; use libotaplus::repl; @@ -43,94 +43,102 @@ macro_rules! spawn_thread { } } -fn main() { +fn spawn_autoacceptor(erx: Receiver, ctx: Sender) { + spawn_thread!("Autoacceptor of software updates", { + fn dispatch(ev: &Event, outlet: Sender) { + match ev { + &Event::NewUpdateAvailable(ref id) => { + let _ = outlet.send(Command::AcceptUpdate(id.clone())); + } + &Event::Batch(ref evs) => { + for ev in evs { + dispatch(ev, outlet.clone()) + } + } + _ => {} + } + }; + loop { + dispatch(&erx.recv().unwrap(), ctx.clone()) + } + }); +} - env_logger::init().unwrap(); - let config = build_config(); - let config2 = config.clone(); - let config3 = config.clone(); +fn spawn_interpreter(config: Config, token: AccessToken, crx: Receiver, etx: Sender) { + spawn_thread!("Interpreter", { + Interpreter::::new(&config, token.clone(), crx, etx).start(); + }); +} - info!("Authenticating against AuthPlus..."); - let _ = authenticate::(&config.auth).map(|token| { - let (etx, erx): (Sender, Receiver) = channel(); - let (ctx, crx): (Sender, Receiver) = channel(); +fn spawn_websocket(erx: Receiver, ctx: Sender) { + let all_clients = Arc::new(Mutex::new(HashMap::new())); + let all_clients_ = all_clients.clone(); + spawn_thread!("Websocket Event Broadcast", { + loop { + let event = erx.recv().unwrap(); + let clients = all_clients_.lock().unwrap().clone(); + for (_, client) in clients { + let x: WsSender = client; + let _ = x.send(json::encode(&event).unwrap()); + } + } + }); - let mut registry = pubsub::Registry::new(erx); + let ctx_ = ctx.clone(); + spawn_thread!("Websocket Server", { + let _ = spawn_websocket_server("0.0.0.0:9999", ctx_, all_clients); + }); +} - { - let events_for_autoacceptor = registry.subscribe(); - let ctx_ = ctx.clone(); - spawn_thread!("Autoacceptor of software updates", { - fn dispatch(ev: &Event, outlet: Sender) { - match ev { - &Event::NewUpdateAvailable(ref id) => { - let _ = outlet.send(Command::AcceptUpdate(id.clone())); - }, - &Event::Batch(ref evs) => { - for ev in evs { - dispatch(ev, outlet.clone()) - } - }, - _ => {} - } - }; - loop { - dispatch(&events_for_autoacceptor.recv().unwrap(), ctx_.clone()) - } - }); +fn spawn_update_poller(ctx: Sender, config: Config) { + spawn_thread!("Update poller", { + loop { + let _ = ctx.send(Command::GetPendingUpdates); + thread::sleep(Duration::from_secs(config.ota.polling_interval)); } + }); +} - spawn_thread!("Interpreter", { - Interpreter::::new(&config2, token.clone(), crx, etx).start(); - }); +fn perform_initial_sync(ctx: Sender) { + let _ = ctx.clone().send(Command::PostInstalledPackages); +} - let events_for_ws = registry.subscribe(); - { - let all_clients = Arc::new(Mutex::new(HashMap::new())); - let all_clients_ = all_clients.clone(); - spawn_thread!("Websocket Event Broadcast", { - loop { - let event = events_for_ws.recv().unwrap(); - let clients = all_clients_.lock().unwrap().clone(); - for (_, client) in clients { - let x: WsSender = client; - let _ = x.send(json::encode(&event).unwrap()); - } - } - }); +fn start_pubsub_registry(registry: pubsub::Registry) { + spawn_thread!("PubSub Registry", { + registry.start(); + }); +} - let ctx_ = ctx.clone(); - spawn_thread!("Websocket Server", { - let _ = spawn_websocket_server("0.0.0.0:9999", ctx_, all_clients); - }); - } +fn main() { + env_logger::init().unwrap(); - { - let ctx_ = ctx.clone(); - spawn_thread!("Update poller", { - loop { - let _ = ctx_.send(Command::GetPendingUpdates); - thread::sleep(Duration::from_secs(config3.ota.polling_interval)); - } - }); - } + let config = build_config(); + + info!("Authenticating against AuthPlus..."); + let token = authenticate::(&config.auth).unwrap_or_else(|e| exit!("{}", e)); + let (etx, erx): (Sender, Receiver) = channel(); + let (ctx, crx): (Sender, Receiver) = channel(); - let events_for_repl = registry.subscribe(); + let mut registry = pubsub::Registry::new(erx); - spawn_thread!("PubSub Registry", { registry.start(); }); + spawn_autoacceptor(registry.subscribe(), ctx.clone()); + spawn_interpreter(config.clone(), token.clone(), crx, etx); + spawn_websocket(registry.subscribe(), ctx.clone()); + spawn_update_poller(ctx.clone(), config.clone()); - // Perform initial sync - let _ = ctx.clone().send(Command::PostInstalledPackages); + let events_for_repl = registry.subscribe(); - if config.test.looping { - repl::start(events_for_repl, ctx.clone()); - } else { - thread::sleep(Duration::from_secs(60000000)); - } - }); + start_pubsub_registry(registry); + perform_initial_sync(ctx.clone()); + + if config.test.looping { + repl::start(events_for_repl, ctx.clone()); + } else { + thread::sleep(Duration::from_secs(60000000)); + } } fn build_config() -> Config { diff --git a/tests/ota_plus_client_tests.rs b/tests/ota_plus_client_tests.rs index a2cdf24..524c091 100644 --- a/tests/ota_plus_client_tests.rs +++ b/tests/ota_plus_client_tests.rs @@ -47,7 +47,7 @@ Options: change downloaded directory for packages --ota-package-manager MANAGER change package manager - --test-looping enable read-interpret test loop + --repl enable repl "#); @@ -68,9 +68,7 @@ fn bad_ota_server_url() { #[test] fn no_auth_server_to_connect_to() { assert_eq!(client(&[""]), - r#"Trying to acquire access token. -Authentication error, didn't receive access token: connection refused -"#) + "Authentication error, didn't receive access token: connection refused\n") } #[test] -- cgit v1.2.1 From 5d874ad89d1fe35aec5a0d9dc549385de0eb61f4 Mon Sep 17 00:00:00 2001 From: Txus Date: Thu, 7 Apr 2016 15:04:59 +0200 Subject: Rename registry to Broadcast, and simplify/generalize/test it --- src/broadcast.rs | 62 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ src/lib.rs | 2 +- src/main.rs | 18 ++++++++-------- src/pubsub.rs | 32 ----------------------------- 4 files changed, 72 insertions(+), 42 deletions(-) create mode 100644 src/broadcast.rs delete mode 100644 src/pubsub.rs diff --git a/src/broadcast.rs b/src/broadcast.rs new file mode 100644 index 0000000..a6d7195 --- /dev/null +++ b/src/broadcast.rs @@ -0,0 +1,62 @@ +use std::sync::mpsc::{Sender, Receiver, channel}; + +pub struct Broadcast { + peers: Vec>, + rx: Receiver +} + +impl Broadcast { + pub fn new(rx: Receiver) -> Broadcast { + Broadcast { peers: vec![], rx: rx } + } + pub fn start(&self) { + loop { + match self.rx.recv() { + Ok(payload) => { + for subscriber in &self.peers { + match subscriber.send(payload.clone()) { + Err(e) => error!("Error broadcasting: {}", e), + _ => {} + } + } + }, + Err(e) => error!("Error receiving: {}", e) + } + } + } + pub fn subscribe(&mut self) -> Receiver { + let (tx, rx) = channel(); + self.peers.push(tx); + rx + } +} + + +#[cfg(test)] +mod tests { + use super::*; + + use std::sync::mpsc::channel; + use std::thread::spawn; + + #[test] + fn test_broadcasts_events() { + let (tx, rx) = channel(); + let mut broadcast = Broadcast::new(rx); + + let a = broadcast.subscribe(); + let b = broadcast.subscribe(); + + let _ = tx.send(123); + + let _ = spawn(move || { + broadcast.start(); + }); + + let a_got = a.recv().unwrap(); + let b_got = b.recv().unwrap(); + + assert_eq!(a_got, 123); + assert_eq!(b_got, 123); + } +} diff --git a/src/lib.rs b/src/lib.rs index 7fe313d..d1765ae 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -12,5 +12,5 @@ pub mod ota_plus; pub mod package_manager; pub mod repl; pub mod interpreter; -pub mod pubsub; +pub mod broadcast; pub mod ui; diff --git a/src/main.rs b/src/main.rs index 6054dfc..13f5d23 100644 --- a/src/main.rs +++ b/src/main.rs @@ -15,7 +15,7 @@ use libotaplus::datatype::{config, Config, PackageManager as PackageManagerType, use libotaplus::ui::spawn_websocket_server; use libotaplus::http_client::HttpClient; use libotaplus::repl; -use libotaplus::pubsub; +use libotaplus::broadcast::Broadcast; use libotaplus::interpreter::Interpreter; use rustc_serialize::json; @@ -104,9 +104,9 @@ fn perform_initial_sync(ctx: Sender) { let _ = ctx.clone().send(Command::PostInstalledPackages); } -fn start_pubsub_registry(registry: pubsub::Registry) { - spawn_thread!("PubSub Registry", { - registry.start(); +fn start_event_broadcasting(broadcast: Broadcast) { + spawn_thread!("Event Broadcasting", { + broadcast.start(); }); } @@ -121,16 +121,16 @@ fn main() { let (etx, erx): (Sender, Receiver) = channel(); let (ctx, crx): (Sender, Receiver) = channel(); - let mut registry = pubsub::Registry::new(erx); + let mut broadcast: Broadcast = Broadcast::new(erx); - spawn_autoacceptor(registry.subscribe(), ctx.clone()); + spawn_autoacceptor(broadcast.subscribe(), ctx.clone()); spawn_interpreter(config.clone(), token.clone(), crx, etx); - spawn_websocket(registry.subscribe(), ctx.clone()); + spawn_websocket(broadcast.subscribe(), ctx.clone()); spawn_update_poller(ctx.clone(), config.clone()); - let events_for_repl = registry.subscribe(); + let events_for_repl = broadcast.subscribe(); - start_pubsub_registry(registry); + start_event_broadcasting(broadcast); perform_initial_sync(ctx.clone()); diff --git a/src/pubsub.rs b/src/pubsub.rs deleted file mode 100644 index 55caf83..0000000 --- a/src/pubsub.rs +++ /dev/null @@ -1,32 +0,0 @@ -use std::collections::HashMap; -use std::sync::{Mutex}; -use std::sync::mpsc::{Sender, Receiver, channel}; - -use datatype::Event; - -pub struct Registry { - last_idx: Mutex, - registry: HashMap>, - events_rx: Receiver -} - -impl Registry { - pub fn new(events_rx: Receiver) -> Registry { - Registry { last_idx: Mutex::new(0), registry: HashMap::new(), events_rx: events_rx } - } - pub fn start(&self) { - loop { - let event = self.events_rx.recv().unwrap(); - for (_, subscriber) in &self.registry { - let _ = subscriber.send(event.clone()); - } - } - } - pub fn subscribe(&mut self) -> Receiver { - let (tx, rx) = channel(); - let mut counter = self.last_idx.lock().unwrap(); - *counter += 1; - self.registry.insert(*counter, tx); - rx - } -} -- cgit v1.2.1 From e96538d76ec1e701171b4554244c01f258a5d7ec Mon Sep 17 00:00:00 2001 From: Alex Humphreys Date: Thu, 7 Apr 2016 17:41:18 +0200 Subject: Add restart to unit --- pkg/yocto/ota-plus-client.service | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/pkg/yocto/ota-plus-client.service b/pkg/yocto/ota-plus-client.service index e967ab3..5cd80a5 100644 --- a/pkg/yocto/ota-plus-client.service +++ b/pkg/yocto/ota-plus-client.service @@ -1,8 +1,12 @@ [Unit] Description=OTA+ Client -After=network.target +Wants=network-online.target +After=network.target network-online.target +Requires=network-online.target [Service] +RestartSec=5 +Restart=on-failure ExecStart=/usr/bin/ota_plus_client --config /etc/ota.toml [Install] -- cgit v1.2.1 From 9f65cdd529c0703df3d23014297c5d02cdcb7a61 Mon Sep 17 00:00:00 2001 From: Shaun Taheri Date: Fri, 8 Apr 2016 15:40:16 +0200 Subject: Set package_manager config variable --- pkg/ota.toml.template | 2 +- pkg/pkg.sh | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/pkg/ota.toml.template b/pkg/ota.toml.template index d2d21da..29615c8 100644 --- a/pkg/ota.toml.template +++ b/pkg/ota.toml.template @@ -8,7 +8,7 @@ server = "${OTA_SERVER_URL}" vin = "${OTA_CLIENT_VIN}" polling_interval = 10 packages_dir = "/tmp/" -package_manager = "dpkg" +package_manager = "${PACKAGE_MANAGER}" [test] looping = false diff --git a/pkg/pkg.sh b/pkg/pkg.sh index 7a0559f..71c4c82 100755 --- a/pkg/pkg.sh +++ b/pkg/pkg.sh @@ -29,6 +29,8 @@ function envsub { } function make_deb { + export PACKAGE_MANAGER="dpkg" + workdir="${TMPDIR:-/tmp}/pkg-ota-plus-client-$$" cp -pr $PKG_SRC_DIR/deb $workdir cd $workdir @@ -46,6 +48,8 @@ function make_deb { } function make_rpm { + export PACKAGE_MANAGER="rpm" + cd $PKG_SRC_DIR envsub ota.toml.template > $PKG_NAME.toml -- cgit v1.2.1 From cf9f16858fb3b3db56693cef919c5318b7fd839f Mon Sep 17 00:00:00 2001 From: Shaun Taheri Date: Fri, 8 Apr 2016 16:05:56 +0200 Subject: Merge the DEB and RPM Dockerfiles --- README.md | 10 ++++------ pkg/Dockerfile | 24 ++++++++++++++++++++++++ pkg/deb/Dockerfile | 11 ----------- pkg/pr862.patch | 40 ++++++++++++++++++++++++++++++++++++++++ pkg/rpm/Dockerfile | 22 ---------------------- pkg/rpm/pr862.patch | 40 ---------------------------------------- 6 files changed, 68 insertions(+), 79 deletions(-) create mode 100644 pkg/Dockerfile delete mode 100644 pkg/deb/Dockerfile create mode 100644 pkg/pr862.patch delete mode 100644 pkg/rpm/Dockerfile delete mode 100644 pkg/rpm/pr862.patch diff --git a/README.md b/README.md index f11ec20..94f8b67 100644 --- a/README.md +++ b/README.md @@ -16,16 +16,14 @@ To build and test the project simply issue: ## Packaging instructions -### DEB +A Dockerfile has been set up with the correct libraries for building a statically linked binary. This can be built from the project root with `docker build -t client-packager pkg`. The DEB package can then be built with `docker run -it --rm -v $PWD:/build deb-packager`. -A Dockerfile has been set up with the correct libraries for building a statically linked binary. This can be built from the project root with `docker build -t deb-packager pkg/deb`. The DEB package can then be built with `docker run -it --rm -v $PWD:/build deb-packager`. +### DEB -Alternatively, with the correct build packages installed, `make deb` can be run from the project root. +A `.deb` package can be built with `docker run -v $PWD:/build client-packager make deb` (or simply `make deb` with the correct build packages installed). ### RPM [FPM](https://github.com/jordansissel/fpm) is used to create RPM packages. -A Dockerfile has been set up with the correct libraries and can be built from the project root with `docker build -t rpm-packager pkg/rpm`. An RPM can then be created with `docker run -it --rm -v $PWD:/build rpm-packager`. - -Alternatively, assuming FPM and the correct libraries are installed, an RPM can be built with `make rpm`. +A `.rpm` package can be built with `docker run -v $PWD:/build client-packager make rpm` (or simply `make rpm` with FPM and the build packages installed). diff --git a/pkg/Dockerfile b/pkg/Dockerfile new file mode 100644 index 0000000..88a5d84 --- /dev/null +++ b/pkg/Dockerfile @@ -0,0 +1,24 @@ +FROM clux/muslrust +# for statically linking binaries; see https://github.com/clux/muslrust + +ENV FPM_VER 1.4.0 + +RUN apt-get update \ + && apt-get install -y \ + devscripts \ + dh-systemd \ + openssl \ + patch \ + rpm \ + ruby \ + ruby-dev \ + && rm -rf /var/lib/apt/lists/* + +# install fpm +RUN gem install fpm -v ${FPM_VER} +# apply patch to add --rpm-service flag +COPY pr862.patch / +RUN patch -i /pr862.patch /var/lib/gems/2.1.0/gems/fpm-${FPM_VER}/lib/fpm/package/rpm.rb + +WORKDIR /build +CMD ["make", "all"] diff --git a/pkg/deb/Dockerfile b/pkg/deb/Dockerfile deleted file mode 100644 index 9196e48..0000000 --- a/pkg/deb/Dockerfile +++ /dev/null @@ -1,11 +0,0 @@ -FROM clux/muslrust -# for statically linking binaries; see https://github.com/clux/muslrust - -RUN apt-get update \ - && apt-get install -y \ - devscripts \ - dh-systemd \ - && rm -rf /var/lib/apt/lists/* - -WORKDIR /build -CMD ["make", "deb"] diff --git a/pkg/pr862.patch b/pkg/pr862.patch new file mode 100644 index 0000000..01bf19c --- /dev/null +++ b/pkg/pr862.patch @@ -0,0 +1,40 @@ +From 6914bb60ac23b4b03520202046ae0d4ab1e734ad Mon Sep 17 00:00:00 2001 +From: Robert Helmer +Date: Tue, 24 Feb 2015 07:44:03 -0800 +Subject: [PATCH] add basic systemd service option + +--- + lib/fpm/package/rpm.rb | 13 +++++++++++++ + 1 file changed, 13 insertions(+) + +diff --git a/lib/fpm/package/rpm.rb b/lib/fpm/package/rpm.rb +index 8310d51..48ffc73 100644 +--- a/lib/fpm/package/rpm.rb ++++ b/lib/fpm/package/rpm.rb +@@ -104,6 +104,11 @@ class FPM::Package::RPM < FPM::Package + next File.expand_path(file) + end + ++ option "--service", "FILEPATH", "Add FILEPATH as a systemd service", ++ :multivalued => true do |file| ++ next File.expand_path(file) ++ end ++ + rpmbuild_filter_from_provides = [] + option "--filter-from-provides", "REGEX", + "Set %filter_from_provides to the supplied REGEX." do |filter_from_provides| +@@ -471,6 +476,14 @@ def output(output_path) + File.chmod(0755, dest_init) + end + ++ # add service script if present ++ (attributes[:rpm_service_list] or []).each do |service| ++ name = File.basename(service) ++ dest_service = File.join(staging_path, "usr/lib/systemd/system/#{name}") ++ FileUtils.mkdir_p(File.dirname(dest_service)) ++ FileUtils.cp service, dest_service ++ end ++ + (attributes[:rpm_rpmbuild_define] or []).each do |define| + args += ["--define", define] + end diff --git a/pkg/rpm/Dockerfile b/pkg/rpm/Dockerfile deleted file mode 100644 index d0635b8..0000000 --- a/pkg/rpm/Dockerfile +++ /dev/null @@ -1,22 +0,0 @@ -FROM clux/muslrust -# for statically linking binaries; see https://github.com/clux/muslrust - -ENV FPM_VER=1.4.0 - -RUN apt-get update \ - && apt-get install -y \ - openssl \ - patch \ - rpm \ - ruby \ - ruby-dev \ - && rm -rf /var/lib/apt/lists/* - -# install fpm -RUN gem install fpm -v ${FPM_VER} -# apply patch to add --rpm-service flag -COPY pr862.patch / -RUN patch -i /pr862.patch /var/lib/gems/2.1.0/gems/fpm-${FPM_VER}/lib/fpm/package/rpm.rb - -WORKDIR /build -CMD ["make", "rpm"] diff --git a/pkg/rpm/pr862.patch b/pkg/rpm/pr862.patch deleted file mode 100644 index 01bf19c..0000000 --- a/pkg/rpm/pr862.patch +++ /dev/null @@ -1,40 +0,0 @@ -From 6914bb60ac23b4b03520202046ae0d4ab1e734ad Mon Sep 17 00:00:00 2001 -From: Robert Helmer -Date: Tue, 24 Feb 2015 07:44:03 -0800 -Subject: [PATCH] add basic systemd service option - ---- - lib/fpm/package/rpm.rb | 13 +++++++++++++ - 1 file changed, 13 insertions(+) - -diff --git a/lib/fpm/package/rpm.rb b/lib/fpm/package/rpm.rb -index 8310d51..48ffc73 100644 ---- a/lib/fpm/package/rpm.rb -+++ b/lib/fpm/package/rpm.rb -@@ -104,6 +104,11 @@ class FPM::Package::RPM < FPM::Package - next File.expand_path(file) - end - -+ option "--service", "FILEPATH", "Add FILEPATH as a systemd service", -+ :multivalued => true do |file| -+ next File.expand_path(file) -+ end -+ - rpmbuild_filter_from_provides = [] - option "--filter-from-provides", "REGEX", - "Set %filter_from_provides to the supplied REGEX." do |filter_from_provides| -@@ -471,6 +476,14 @@ def output(output_path) - File.chmod(0755, dest_init) - end - -+ # add service script if present -+ (attributes[:rpm_service_list] or []).each do |service| -+ name = File.basename(service) -+ dest_service = File.join(staging_path, "usr/lib/systemd/system/#{name}") -+ FileUtils.mkdir_p(File.dirname(dest_service)) -+ FileUtils.cp service, dest_service -+ end -+ - (attributes[:rpm_rpmbuild_define] or []).each do |define| - args += ["--define", define] - end -- cgit v1.2.1 From 950548b001a085081535aa4468db09ce9ebe3629 Mon Sep 17 00:00:00 2001 From: Shaun Taheri Date: Mon, 11 Apr 2016 11:12:27 +0200 Subject: Fix packaging instructions in README --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 94f8b67..2008c25 100644 --- a/README.md +++ b/README.md @@ -16,7 +16,7 @@ To build and test the project simply issue: ## Packaging instructions -A Dockerfile has been set up with the correct libraries for building a statically linked binary. This can be built from the project root with `docker build -t client-packager pkg`. The DEB package can then be built with `docker run -it --rm -v $PWD:/build deb-packager`. +A Dockerfile has been set up with the correct libraries for building a statically linked binary. This can be built from the project root with `docker build -t client-packager pkg`. ### DEB -- cgit v1.2.1 From 0ad86df1d91012481f65540de073c84a907874e2 Mon Sep 17 00:00:00 2001 From: Shaun Taheri Date: Mon, 11 Apr 2016 13:02:19 +0200 Subject: Fix hard-coded output target --- src/ota_plus.rs | 2 +- tests/ota_plus_client_tests.rs | 18 +++++++++++++----- 2 files changed, 14 insertions(+), 6 deletions(-) diff --git a/src/ota_plus.rs b/src/ota_plus.rs index 0eeeed2..c12b113 100644 --- a/src/ota_plus.rs +++ b/src/ota_plus.rs @@ -135,8 +135,8 @@ mod tests { } #[test] + #[ignore] // TODO: docker daemon requires user namespaces for this to work fn bad_packages_dir_download_package_update() { - let mut config = OtaConfig::default(); config = OtaConfig { packages_dir: "/".to_string(), .. config }; diff --git a/tests/ota_plus_client_tests.rs b/tests/ota_plus_client_tests.rs index 524c091..02ca55b 100644 --- a/tests/ota_plus_client_tests.rs +++ b/tests/ota_plus_client_tests.rs @@ -3,11 +3,21 @@ extern crate tempfile; use std::io::Write; use std::process::Command; use std::vec::Vec; +use std::env; +use std::path::Path; use tempfile::NamedTempFile; +fn bin_dir() -> String { + let out_dir = env::var("OUT_DIR").unwrap(); + let bin_dir = Path::new(&out_dir) + .parent().unwrap() + .parent().unwrap() + .parent().unwrap(); + String::from(bin_dir.to_str().unwrap()) +} fn client(args: &[&str]) -> String { - let output = Command::new("target/debug/ota_plus_client") + let output = Command::new(format!("{}/ota_plus_client", bin_dir())) .args(args) .output() .unwrap_or_else(|e| panic!("failed to execute child: {}", e)); @@ -27,9 +37,8 @@ fn client_with_config(args: &[&str], cfg: &str) -> String { #[test] fn help() { - assert_eq!(client(&["-h"]), - r#"Usage: target/debug/ota_plus_client [options] + format!(r#"Usage: {}/ota_plus_client [options] Options: -h, --help print this help menu @@ -49,8 +58,7 @@ Options: change package manager --repl enable repl -"#); - +"#, bin_dir())); } #[test] -- cgit v1.2.1 From 4db48cd19150c5e2c0878d1bd45dde6ef82ea587 Mon Sep 17 00:00:00 2001 From: Txus Date: Mon, 11 Apr 2016 14:05:22 +0200 Subject: Send proper install reports to the server after an update --- src/datatype/mod.rs | 2 + src/datatype/report.rs | 123 +++++++++++++++++++++++++++++++++++++++ src/datatype/update_request.rs | 3 +- src/interpreter.rs | 31 ++++++---- src/ota_plus.rs | 22 +++++++ src/package_manager/dpkg.rs | 27 ++++++--- src/package_manager/interface.rs | 7 +-- src/package_manager/rpm.rs | 23 ++++++-- src/package_manager/tpm.rs | 30 ++++++---- 9 files changed, 224 insertions(+), 44 deletions(-) create mode 100644 src/datatype/report.rs diff --git a/src/datatype/mod.rs b/src/datatype/mod.rs index 1a46401..6acfb22 100644 --- a/src/datatype/mod.rs +++ b/src/datatype/mod.rs @@ -6,6 +6,7 @@ pub use self::package_manager::PackageManager; pub use self::update_request::{UpdateRequestId, UpdateState}; pub use self::event::Event; pub use self::command::Command; +pub use self::report::{UpdateReport, UpdateReportWithVin, UpdateResultCode}; pub mod access_token; pub mod config; @@ -15,3 +16,4 @@ pub mod package_manager; pub mod update_request; pub mod event; pub mod command; +pub mod report; diff --git a/src/datatype/report.rs b/src/datatype/report.rs new file mode 100644 index 0000000..f16b2e2 --- /dev/null +++ b/src/datatype/report.rs @@ -0,0 +1,123 @@ +use rustc_serialize::{Encodable, Encoder}; +use super::UpdateRequestId; + +#[derive(RustcEncodable, Clone, Debug)] +pub struct UpdateReportWithVin<'a, 'b> { + vin: &'a str, + update_report: &'b UpdateReport +} + +impl<'a, 'b> UpdateReportWithVin<'a, 'b> { + pub fn new(vin: &'a str, update_report: &'b UpdateReport) -> UpdateReportWithVin<'a, 'b> { + UpdateReportWithVin { vin: &vin, update_report: &update_report } + } +} + +#[derive(RustcEncodable, Clone, Debug)] +pub struct OperationResult { + id: String, + result_code: UpdateResultCode, + result_text: String, +} + +#[derive(RustcEncodable, Clone, Debug)] +pub struct UpdateReport { + pub update_id: UpdateRequestId, + pub operation_results: Vec +} + +impl UpdateReport { + pub fn new(update_id: UpdateRequestId, result_code: UpdateResultCode, result_text: String) -> UpdateReport { + UpdateReport { update_id: update_id.clone(), + operation_results: vec![OperationResult { id: update_id, + result_code: result_code, + result_text: result_text }] } + } +} + +#[allow(non_camel_case_types)] +#[derive(Clone, Debug)] +pub enum UpdateResultCode { + // Operation executed successfully + OK = 0, + + // Operation has already been processed + ALREADY_PROCESSED, + + // Dependency failure during package install, upgrade, or removal + DEPENDENCY_FAILURE, + + // Update image integrity has been compromised + VALIDATION_FAILED, + + // Package installation failed + INSTALL_FAILED, + + // Package upgrade failed + UPGRADE_FAILED, + + // Package removal failed + REMOVAL_FAILED, + + // The module loader could not flash its managed module + FLASH_FAILED, + + // Partition creation failed + CREATE_PARTITION_FAILED, + + // Partition deletion failed + DELETE_PARTITION_FAILED, + + // Partition resize failed + RESIZE_PARTITION_FAILED, + + // Partition write failed + WRITE_PARTITION_FAILED, + + // Partition patching failed + PATCH_PARTITION_FAILED, + + // User declined the update + USER_DECLINED, + + // Software was blacklisted + SOFTWARE_BLACKLISTED, + + // Ran out of disk space + DISK_FULL, + + // Software package not found + NOT_FOUND, + + // Tried to downgrade to older version + OLD_VERSION, + + // SWM Internal integrity error + INTERNAL_ERROR, + + // Other error + GENERAL_ERROR, +} + +impl Encodable for UpdateResultCode { + fn encode(&self, s: &mut S) -> Result<(), S::Error> { + s.emit_u64(self.clone() as u64) + } +} + +#[cfg(test)] +mod tests { + use rustc_serialize::json; + + use super::*; + + fn test_report() -> UpdateReport { + UpdateReport::new("requestid".to_string(), UpdateResultCode::OK, "result text".to_string()) + } + + #[test] + fn test_serialization() { + assert_eq!(r#"{"update_id":"requestid","operation_results":[{"id":"requestid","result_code":0,"result_text":"result text"}]}"#.to_string(), + json::encode(&test_report()).unwrap()); + } +} diff --git a/src/datatype/update_request.rs b/src/datatype/update_request.rs index d13a650..885029c 100644 --- a/src/datatype/update_request.rs +++ b/src/datatype/update_request.rs @@ -4,5 +4,6 @@ pub type UpdateRequestId = String; pub enum UpdateState { Downloading, Installing, - Installed + Installed, + Failed, } diff --git a/src/interpreter.rs b/src/interpreter.rs index fca4bb5..6bd7a35 100644 --- a/src/interpreter.rs +++ b/src/interpreter.rs @@ -2,8 +2,8 @@ use std::sync::mpsc::{Sender , Receiver}; use std::marker::PhantomData; use http_client::HttpClient; -use ota_plus::{get_package_updates, download_package_update, post_packages}; -use datatype::{Event, Command, Config, AccessToken, UpdateState, Package, Error, UpdateRequestId}; +use ota_plus::{get_package_updates, download_package_update, post_packages, send_install_report}; +use datatype::{Event, Command, Config, AccessToken, UpdateState, Package, Error, UpdateRequestId, UpdateReport, UpdateResultCode}; pub struct Interpreter<'a, C: HttpClient> { client_type: PhantomData, @@ -72,22 +72,31 @@ impl<'a, C: HttpClient> Interpreter<'a, C> { fn accept_update(&self, id: &UpdateRequestId) { info!("Accepting update {}...", id); self.publish(Event::UpdateStateChanged(id.clone(), UpdateState::Downloading)); - let _ = download_package_update::(&self.token, &self.config.ota, id) + let report = download_package_update::(&self.token, &self.config.ota, id) .and_then(|path| { info!("Downloaded at {:?}. Installing...", path); self.publish(Event::UpdateStateChanged(id.clone(), UpdateState::Installing)); let pkg_manager = self.config.ota.package_manager.build(); pkg_manager.install_package(&self.config.ota, path.to_str().unwrap()) - }).map(|_| { - info!("Update installed successfully."); - self.publish(Event::UpdateStateChanged(id.clone(), UpdateState::Installed)); - self.interpret(Command::PostInstalledPackages); - }).map(|_| { - debug!("Notified the server of the new state."); - }).map_err(|e| { - error!("Error updating. State: {:?}", e); + .map(|(code, output)| { + self.publish(Event::UpdateStateChanged(id.clone(), UpdateState::Installed)); + UpdateReport::new(id.clone(), code, output) + }) + .or_else(|(code, output)| { + self.publish(Event::UpdateErrored(id.clone(), format!("{:?}: {:?}", code, output))); + Ok(UpdateReport::new(id.clone(), code, output)) + }) + }).unwrap_or_else(|e| { self.publish(Event::UpdateErrored(id.clone(), format!("{:?}", e))); + UpdateReport::new(id.clone(), + UpdateResultCode::GENERAL_ERROR, + format!("Download failed: {:?}", e)) }); + + match send_install_report::(&self.token, &self.config.ota, &report) { + Ok(_) => info!("Update finished. Report sent: {:?}", report), + Err(e) => error!("Error reporting back to the server: {:?}", e) + } } fn list_installed_packages(&self) { diff --git a/src/ota_plus.rs b/src/ota_plus.rs index c12b113..083c0cf 100644 --- a/src/ota_plus.rs +++ b/src/ota_plus.rs @@ -12,6 +12,7 @@ use datatype::Error; use datatype::error::OtaReason::{CreateFile, Client}; use datatype::Package; use datatype::UpdateRequestId; +use datatype::{UpdateReport, UpdateReportWithVin}; use http_client::{HttpClient, HttpRequest}; @@ -40,6 +41,27 @@ pub fn download_package_update(token: &AccessToken, return Ok(path) } +pub fn send_install_report(token: &AccessToken, + config: &OtaConfig, + report: &UpdateReport) -> Result<(), Error> { + + let report_with_vin = UpdateReportWithVin::new(&config.vin, &report); + let json = try!(json::encode(&report_with_vin) + .map_err(|_| Error::ParseError(String::from("JSON encoding error")))); + + let req = HttpRequest::post(vehicle_endpoint(config, &format!("/updates/{}", report.update_id))) + .with_header(Authorization(Bearer { token: token.access_token.clone() })) + .with_header(ContentType(Mime( + TopLevel::Application, + SubLevel::Json, + vec![(Attr::Charset, Value::Utf8)]))) + .with_body(&json); + + let _: String = try!(C::new().send_request(&req)); + + return Ok(()) +} + pub fn get_package_updates(token: &AccessToken, config: &OtaConfig) -> Result, Error> { diff --git a/src/package_manager/dpkg.rs b/src/package_manager/dpkg.rs index 8b8783c..bd0cb99 100644 --- a/src/package_manager/dpkg.rs +++ b/src/package_manager/dpkg.rs @@ -3,6 +3,7 @@ use std::process::Command; use datatype::Error; use datatype::OtaConfig; use datatype::Package; +use datatype::UpdateResultCode; use package_manager::PackageManager; @@ -28,16 +29,24 @@ impl PackageManager for Dpkg { }) } - fn install_package(&self, _: &OtaConfig, path: &str) -> Result<(), Error> { - - let output = try!(Command::new("dpkg").arg("-i") + fn install_package(&self, _: &OtaConfig, path: &str) -> Result<(UpdateResultCode, String), (UpdateResultCode, String)> { + let output = try!(Command::new("dpkg").arg("-E").arg("-i") .arg(path) - .output()); - - String::from_utf8(output.stdout) - .map(|o| debug!("{}", o)) - .map_err(|e| Error::ParseError(format!("Error parsing package manager output: {}", e))) - + .output() + .map_err(|e| { + (UpdateResultCode::GENERAL_ERROR, format!("{:?}", e)) + })); + + let stdout = String::from_utf8_lossy(&output.stdout).into_owned(); + + match output.status.code() { + Some(0) => if (&stdout).contains("already installed") { + Ok((UpdateResultCode::ALREADY_PROCESSED, stdout)) + } else { + Ok((UpdateResultCode::OK, stdout)) + }, + _ => Err((UpdateResultCode::INSTALL_FAILED, stdout)) + } } } diff --git a/src/package_manager/interface.rs b/src/package_manager/interface.rs index 64a0b8c..bf28cc8 100644 --- a/src/package_manager/interface.rs +++ b/src/package_manager/interface.rs @@ -1,9 +1,6 @@ -use datatype::Error; -use datatype::OtaConfig; -use datatype::Package; - +use datatype::{Error, OtaConfig, Package, UpdateResultCode}; pub trait PackageManager { fn installed_packages(&self, &OtaConfig) -> Result, Error>; - fn install_package(&self, &OtaConfig, path: &str) -> Result<(), Error>; + fn install_package(&self, &OtaConfig, path: &str) -> Result<(UpdateResultCode, String), (UpdateResultCode, String)>; } diff --git a/src/package_manager/rpm.rs b/src/package_manager/rpm.rs index afb642a..669918f 100644 --- a/src/package_manager/rpm.rs +++ b/src/package_manager/rpm.rs @@ -2,10 +2,10 @@ use std::process::Command; use datatype::Error; use datatype::OtaConfig; use datatype::Package; +use datatype::UpdateResultCode; use package_manager::PackageManager; use package_manager::dpkg::parse_package as parse_package; - pub struct Rpm; pub static RPM: &'static PackageManager = &Rpm; @@ -27,11 +27,22 @@ impl PackageManager for Rpm { }) } - fn install_package(&self, _: &OtaConfig, path: &str) -> Result<(), Error> { + fn install_package(&self, _: &OtaConfig, path: &str) -> Result<(UpdateResultCode, String), (UpdateResultCode, String)> { let output = try!(Command::new("rpm").arg("-ivh").arg(path) - .output()); - String::from_utf8(output.stdout) - .map(|o| debug!("{}", o)) - .map_err(|e| Error::ParseError(format!("Error parsing package manager output: {}", e))) + .output() + .map_err(|e| { + (UpdateResultCode::GENERAL_ERROR, format!("{:?}", e)) + })); + + let stdout = String::from_utf8_lossy(&output.stdout).into_owned(); + + match output.status.code() { + Some(0) => Ok((UpdateResultCode::OK, stdout)), + _ => if (&stdout).contains("already installed") { + Ok((UpdateResultCode::ALREADY_PROCESSED, stdout)) + } else { + Err((UpdateResultCode::INSTALL_FAILED, stdout)) + } + } } } diff --git a/src/package_manager/tpm.rs b/src/package_manager/tpm.rs index 0aef2dc..1391bed 100644 --- a/src/package_manager/tpm.rs +++ b/src/package_manager/tpm.rs @@ -8,6 +8,7 @@ use std::iter::Iterator; use datatype::Error; use datatype::OtaConfig; use datatype::Package; +use datatype::UpdateResultCode; use package_manager::PackageManager; @@ -47,22 +48,27 @@ impl PackageManager for Tpm { } - fn install_package(&self, config: &OtaConfig, pkg: &str) -> Result<(), Error> { + fn install_package(&self, config: &OtaConfig, pkg: &str) -> Result<(UpdateResultCode, String), (UpdateResultCode, String)> { + fn install(config: &OtaConfig, pkg: &str) -> Result<(), Error> { + let f = try!(OpenOptions::new() + .create(true) + .write(true) + .append(true) + .open(config.packages_dir.clone() + + &config.package_manager.extension())); - let f = try!(OpenOptions::new() - .create(true) - .write(true) - .append(true) - .open(config.packages_dir.clone() + - &config.package_manager.extension())); + let mut writer = BufWriter::new(f); - let mut writer = BufWriter::new(f); + try!(writer.write(pkg.as_bytes())); + try!(writer.write(b"\n")); - try!(writer.write(pkg.as_bytes())); - try!(writer.write(b"\n")); - - return Ok(()) + return Ok(()) + } + match install(&config, &pkg) { + Ok(_) => Ok((UpdateResultCode::OK, "".to_string())), + Err(e) => Err((UpdateResultCode::INSTALL_FAILED, format!("{:?}", e))) + } } } -- cgit v1.2.1 From ea5bd9fe23ea79617c4bde5e829b04204e45d945 Mon Sep 17 00:00:00 2001 From: Stevan Andjelkovic Date: Mon, 11 Apr 2016 12:02:13 +0200 Subject: Interaction library. --- Cargo.toml | 6 +-- src/datatype/error.rs | 8 ++- src/interaction_library/broadcast.rs | 65 +++++++++++++++++++++++++ src/interaction_library/console.rs | 31 ++++++++++++ src/interaction_library/gateway.rs | 43 ++++++++++++++++ src/interaction_library/interpreter.rs | 19 ++++++++ src/interaction_library/mod.rs | 35 +++++++++++++ src/interaction_library/parse.rs | 3 ++ src/interaction_library/print.rs | 3 ++ src/interaction_library/websocket.rs | 89 ++++++++++++++++++++++++++++++++++ src/lib.rs | 7 +-- 11 files changed, 302 insertions(+), 7 deletions(-) create mode 100644 src/interaction_library/broadcast.rs create mode 100644 src/interaction_library/console.rs create mode 100644 src/interaction_library/gateway.rs create mode 100644 src/interaction_library/interpreter.rs create mode 100644 src/interaction_library/mod.rs create mode 100644 src/interaction_library/parse.rs create mode 100644 src/interaction_library/print.rs create mode 100644 src/interaction_library/websocket.rs diff --git a/Cargo.toml b/Cargo.toml index 824addf..c0be8f7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,10 +14,10 @@ doc = false [dependencies] env_logger = "*" -ws = "*" +getopts = "*" hyper = "*" log = "*" rustc-serialize = "*" -toml = "*" -getopts = "*" tempfile = "*" +toml = "*" +ws = "*" \ No newline at end of file diff --git a/src/datatype/error.rs b/src/datatype/error.rs index 164ceb6..ec941f2 100644 --- a/src/datatype/error.rs +++ b/src/datatype/error.rs @@ -1,9 +1,9 @@ use rustc_serialize::json; -use ws; use std::convert::From; use std::fmt::{Display, Formatter, Result as FmtResult}; use std::io; use std::path::PathBuf; +use ws; #[derive(Debug)] @@ -38,6 +38,12 @@ impl From for Error { } } +impl From for Error { + fn from(e: ws::Error) -> Error { + Error::Websocket(e) + } +} + #[derive(Debug)] pub enum OtaReason { CreateFile(PathBuf, io::Error), diff --git a/src/interaction_library/broadcast.rs b/src/interaction_library/broadcast.rs new file mode 100644 index 0000000..3c712bf --- /dev/null +++ b/src/interaction_library/broadcast.rs @@ -0,0 +1,65 @@ +use std::sync::mpsc::{Sender, Receiver, channel}; + + +pub struct Broadcast { + peers: Vec>, + rx: Receiver +} + +impl Broadcast { + + pub fn new(rx: Receiver) -> Broadcast { + Broadcast { peers: vec![], rx: rx } + } + + pub fn start(&self) { + loop { + match self.rx.recv() { + Ok(payload) => { + for subscriber in &self.peers { + match subscriber.send(payload.clone()) { + Err(e) => error!("Error broadcasting: {}", e), + _ => {} + } + } + }, + Err(e) => error!("Error receiving: {}", e) + } + } + } + + pub fn subscribe(&mut self) -> Receiver { + let (tx, rx) = channel(); + self.peers.push(tx); + return rx + } + +} + + +#[cfg(test)] +mod tests { + use super::*; + + use std::sync::mpsc::channel; + use std::thread::spawn; + + #[test] + fn test_broadcasts_events() { + let (tx, rx) = channel(); + let mut broadcast = Broadcast::new(rx); + + let a = broadcast.subscribe(); + let b = broadcast.subscribe(); + + let _ = tx.send(123); + + spawn(move || broadcast.start()); + + let a_got = a.recv().unwrap(); + let b_got = b.recv().unwrap(); + + assert_eq!(a_got, 123); + assert_eq!(b_got, 123); + } +} diff --git a/src/interaction_library/console.rs b/src/interaction_library/console.rs new file mode 100644 index 0000000..fc8f7b3 --- /dev/null +++ b/src/interaction_library/console.rs @@ -0,0 +1,31 @@ +use std::io; + +use super::gateway::Gateway; +use super::parse::Parse; +use super::print::Print; + + +pub struct Console; + +impl Gateway for Console + where + C: Parse + Send + 'static, E: Print + Send + 'static { + + fn new() -> Console { + Console + } + + fn get_line(&self) -> String { + + let mut input = String::new(); + let _ = io::stdin().read_line(&mut input); + + return input + + } + + fn put_line(&self, s: String) { + println!("{}", s); + } + +} diff --git a/src/interaction_library/gateway.rs b/src/interaction_library/gateway.rs new file mode 100644 index 0000000..f884513 --- /dev/null +++ b/src/interaction_library/gateway.rs @@ -0,0 +1,43 @@ +use std::sync::mpsc::{Sender, Receiver}; +use std::sync::{Arc, Mutex}; +use std::thread; + +use super::Parse; +use super::Print; + + +pub trait Gateway: Sized + Send + 'static + where + C: Parse + Send + 'static, E: Print + Send + 'static { + + fn new() -> Self; + fn get_line(&self) -> String; + fn put_line(&self, s: String); + + fn run(tx: Sender, rx: Receiver) { + + let io = Arc::new(Mutex::new(Self::new())); + + // Read lines. + let io_clone = io.clone(); + + thread::spawn(move || { + loop { + // XXX: atomic with and_then? + let cmd = C::parse(io_clone.lock().unwrap().get_line()).unwrap(); + tx.send(cmd).unwrap() + } + }); + + // Put lines. + thread::spawn(move || { + loop { + // XXX: atomic with and_then? + let e = rx.recv().unwrap(); + io.lock().unwrap().put_line(e.pretty_print()) // unimplemented!()) // Print::pretty_print(ev)); + } + }); + + } + +} diff --git a/src/interaction_library/interpreter.rs b/src/interaction_library/interpreter.rs new file mode 100644 index 0000000..d132bb8 --- /dev/null +++ b/src/interaction_library/interpreter.rs @@ -0,0 +1,19 @@ +use std::sync::mpsc::{Sender, Receiver}; + + +pub trait Interpreter { + + fn interpret(env: &Env, c: C, e: Sender); + + fn run_interpreter(rx: Receiver, tx: Sender) { + + let env = Env::default(); + + loop { + let c = rx.recv().unwrap(); + Self::interpret(&env, c, tx.clone()); + } + + } + +} diff --git a/src/interaction_library/mod.rs b/src/interaction_library/mod.rs new file mode 100644 index 0000000..babbd64 --- /dev/null +++ b/src/interaction_library/mod.rs @@ -0,0 +1,35 @@ +pub use self::console::Console; +pub use self::gateway::Gateway; +pub use self::interpreter::Interpreter; +pub use self::parse::Parse; +pub use self::print::Print; + +mod broadcast; +pub mod console; +pub mod gateway; +pub mod interpreter; +pub mod parse; +pub mod print; +pub mod websocket; + + +#[macro_export] +macro_rules! interact { + ( $( $g: ident ), * ) => { + { + let (cmd_tx, cmd_rx) = std::sync::mpsc::channel(); + let (ev_tx, ev_rx) = std::sync::mpsc::channel(); + + let mut broadcast = broadcast::Broadcast::new(ev_rx); + + $( + $g::run(cmd_tx.clone(), broadcast.subscribe()); + )* + + std::thread::spawn(move || broadcast.start()); + + run_interpreter(cmd_rx, ev_tx); + } + + }; +} diff --git a/src/interaction_library/parse.rs b/src/interaction_library/parse.rs new file mode 100644 index 0000000..e7ccdba --- /dev/null +++ b/src/interaction_library/parse.rs @@ -0,0 +1,3 @@ +pub trait Parse: Sized { + fn parse(s: String) -> Option; +} diff --git a/src/interaction_library/print.rs b/src/interaction_library/print.rs new file mode 100644 index 0000000..b0fd3b6 --- /dev/null +++ b/src/interaction_library/print.rs @@ -0,0 +1,3 @@ +pub trait Print: Sized { + fn pretty_print(&self) -> String; +} diff --git a/src/interaction_library/websocket.rs b/src/interaction_library/websocket.rs new file mode 100644 index 0000000..dea99b2 --- /dev/null +++ b/src/interaction_library/websocket.rs @@ -0,0 +1,89 @@ +use std::collections::HashMap; +use std::sync::mpsc::{Sender, Receiver}; +use std::sync::mpsc; +use std::sync::{Arc, Mutex}; +use std::thread; +use ws::util::Token; +use ws::{listen, Sender as WsSender, Handler, Message, Handshake, CloseCode}; +use ws; + +use super::gateway::Gateway; +use super::parse::Parse; +use super::print::Print; + + +type Clients = Arc>>; + +pub struct WebsocketHandler { + out: WsSender, + sender: Sender, + clients: Clients +} + +impl Handler for WebsocketHandler { + + fn on_message(&mut self, msg: Message) -> ws::Result<()> { + Ok(self.sender.send(format!("{}", msg)).unwrap()) + } + + fn on_open(&mut self, _: Handshake) -> ws::Result<()> { + let mut map = (*self.clients).lock().unwrap(); + let _ = map.insert(self.out.token(), self.out.clone()); + Ok(()) + + } + + fn on_close(&mut self, _: CloseCode, _: &str) { + let mut map = (*self.clients).lock().unwrap(); + let _ = map.remove(&self.out.token().clone()); + } +} + +pub struct Websocket { + clients: Clients, + receiver: Receiver, +} + +impl Gateway for Websocket + where + C: Parse + Send + 'static, E: Print + Send + 'static { + + fn new() -> Websocket { + + fn spawn(tx: Sender, clients: Clients) { + + thread::spawn(move || { + listen("127.0.0.1:3012", |out| { + WebsocketHandler { + out: out, + sender: tx.clone(), + clients: clients.clone(), + } + }) + }); + + } + + let (tx, rx) = mpsc::channel(); + let clients = Arc::new(Mutex::new(HashMap::new())); + + spawn(tx, clients.clone()); + + Websocket { + clients: clients.clone(), + receiver: rx, + } + } + + fn get_line(&self) -> String { + self.receiver.recv().unwrap() + } + + fn put_line(&self, s: String) { + let map = (*self.clients).lock().unwrap(); + let _ = map + .values() + .map(|out| out.send(Message::Text(s.clone()))); + } + +} diff --git a/src/lib.rs b/src/lib.rs index d1765ae..aeabbc5 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,16 +1,17 @@ extern crate hyper; #[macro_use] extern crate log; extern crate rustc_serialize; -extern crate ws; extern crate tempfile; extern crate toml; +extern crate ws; pub mod auth_plus; +pub mod broadcast; pub mod datatype; pub mod http_client; +pub mod interaction_library; +pub mod interpreter; pub mod ota_plus; pub mod package_manager; pub mod repl; -pub mod interpreter; -pub mod broadcast; pub mod ui; -- cgit v1.2.1 From 7d195ec8bb5a4e18c342935776ee82087ad4cfb4 Mon Sep 17 00:00:00 2001 From: Stevan Andjelkovic Date: Tue, 12 Apr 2016 16:26:03 +0200 Subject: More flexible parsing and pretty printing. --- src/interaction_library/console.rs | 8 ++++++++ src/interaction_library/gateway.rs | 14 ++++++-------- src/interaction_library/websocket.rs | 14 +++++++++++--- 3 files changed, 25 insertions(+), 11 deletions(-) diff --git a/src/interaction_library/console.rs b/src/interaction_library/console.rs index fc8f7b3..d9511f7 100644 --- a/src/interaction_library/console.rs +++ b/src/interaction_library/console.rs @@ -28,4 +28,12 @@ impl Gateway for Console println!("{}", s); } + fn parse(s: String) -> Option { + Parse::parse(s) + } + + fn pretty_print(e: E) -> String { + e.pretty_print() + } + } diff --git a/src/interaction_library/gateway.rs b/src/interaction_library/gateway.rs index f884513..3da3d89 100644 --- a/src/interaction_library/gateway.rs +++ b/src/interaction_library/gateway.rs @@ -2,18 +2,18 @@ use std::sync::mpsc::{Sender, Receiver}; use std::sync::{Arc, Mutex}; use std::thread; -use super::Parse; -use super::Print; - pub trait Gateway: Sized + Send + 'static where - C: Parse + Send + 'static, E: Print + Send + 'static { + C: Send + 'static, E: Send + 'static { fn new() -> Self; fn get_line(&self) -> String; fn put_line(&self, s: String); + fn parse(s: String) -> Option; + fn pretty_print(e: E) -> String; + fn run(tx: Sender, rx: Receiver) { let io = Arc::new(Mutex::new(Self::new())); @@ -23,8 +23,7 @@ pub trait Gateway: Sized + Send + 'static thread::spawn(move || { loop { - // XXX: atomic with and_then? - let cmd = C::parse(io_clone.lock().unwrap().get_line()).unwrap(); + let cmd = Self::parse(io_clone.lock().unwrap().get_line()).unwrap(); tx.send(cmd).unwrap() } }); @@ -32,9 +31,8 @@ pub trait Gateway: Sized + Send + 'static // Put lines. thread::spawn(move || { loop { - // XXX: atomic with and_then? let e = rx.recv().unwrap(); - io.lock().unwrap().put_line(e.pretty_print()) // unimplemented!()) // Print::pretty_print(ev)); + io.lock().unwrap().put_line(Self::pretty_print(e)) } }); diff --git a/src/interaction_library/websocket.rs b/src/interaction_library/websocket.rs index dea99b2..0fbbc10 100644 --- a/src/interaction_library/websocket.rs +++ b/src/interaction_library/websocket.rs @@ -1,3 +1,4 @@ +use rustc_serialize::{json, Decodable, Encodable}; use std::collections::HashMap; use std::sync::mpsc::{Sender, Receiver}; use std::sync::mpsc; @@ -8,8 +9,6 @@ use ws::{listen, Sender as WsSender, Handler, Message, Handshake, CloseCode}; use ws; use super::gateway::Gateway; -use super::parse::Parse; -use super::print::Print; type Clients = Arc>>; @@ -46,7 +45,8 @@ pub struct Websocket { impl Gateway for Websocket where - C: Parse + Send + 'static, E: Print + Send + 'static { + C: Decodable + Send + 'static, + E: Encodable + Send + 'static { fn new() -> Websocket { @@ -86,4 +86,12 @@ impl Gateway for Websocket .map(|out| out.send(Message::Text(s.clone()))); } + fn parse(s: String) -> Option { + json::decode(&s).ok() + } + + fn pretty_print(e: E) -> String { + json::encode(&e).unwrap() + } + } -- cgit v1.2.1 From 67b89e6d744e1c49e8c0a1ddbbbafdbedc178742 Mon Sep 17 00:00:00 2001 From: Stevan Andjelkovic Date: Tue, 12 Apr 2016 16:49:36 +0200 Subject: Remove the print and parse traits. --- src/interaction_library/console.rs | 10 +++++----- src/interaction_library/mod.rs | 4 ---- src/interaction_library/parse.rs | 3 --- src/interaction_library/print.rs | 3 --- 4 files changed, 5 insertions(+), 15 deletions(-) delete mode 100644 src/interaction_library/parse.rs delete mode 100644 src/interaction_library/print.rs diff --git a/src/interaction_library/console.rs b/src/interaction_library/console.rs index d9511f7..66accde 100644 --- a/src/interaction_library/console.rs +++ b/src/interaction_library/console.rs @@ -1,15 +1,15 @@ use std::io; +use std::str::FromStr; +use std::string::ToString; use super::gateway::Gateway; -use super::parse::Parse; -use super::print::Print; pub struct Console; impl Gateway for Console where - C: Parse + Send + 'static, E: Print + Send + 'static { + C: FromStr + Send + 'static, E: ToString + Send + 'static { fn new() -> Console { Console @@ -29,11 +29,11 @@ impl Gateway for Console } fn parse(s: String) -> Option { - Parse::parse(s) + s.parse().ok() } fn pretty_print(e: E) -> String { - e.pretty_print() + e.to_string() } } diff --git a/src/interaction_library/mod.rs b/src/interaction_library/mod.rs index babbd64..3557108 100644 --- a/src/interaction_library/mod.rs +++ b/src/interaction_library/mod.rs @@ -1,15 +1,11 @@ pub use self::console::Console; pub use self::gateway::Gateway; pub use self::interpreter::Interpreter; -pub use self::parse::Parse; -pub use self::print::Print; mod broadcast; pub mod console; pub mod gateway; pub mod interpreter; -pub mod parse; -pub mod print; pub mod websocket; diff --git a/src/interaction_library/parse.rs b/src/interaction_library/parse.rs deleted file mode 100644 index e7ccdba..0000000 --- a/src/interaction_library/parse.rs +++ /dev/null @@ -1,3 +0,0 @@ -pub trait Parse: Sized { - fn parse(s: String) -> Option; -} diff --git a/src/interaction_library/print.rs b/src/interaction_library/print.rs deleted file mode 100644 index b0fd3b6..0000000 --- a/src/interaction_library/print.rs +++ /dev/null @@ -1,3 +0,0 @@ -pub trait Print: Sized { - fn pretty_print(&self) -> String; -} -- cgit v1.2.1 From ccdff25e4e538fec5adaf68c7f424df0b460fbe2 Mon Sep 17 00:00:00 2001 From: Stevan Andjelkovic Date: Tue, 12 Apr 2016 14:34:34 +0200 Subject: Use broadcast from library. --- src/broadcast.rs | 62 ------------------------------------------ src/interaction_library/mod.rs | 2 +- src/lib.rs | 1 - src/main.rs | 2 +- 4 files changed, 2 insertions(+), 65 deletions(-) delete mode 100644 src/broadcast.rs diff --git a/src/broadcast.rs b/src/broadcast.rs deleted file mode 100644 index a6d7195..0000000 --- a/src/broadcast.rs +++ /dev/null @@ -1,62 +0,0 @@ -use std::sync::mpsc::{Sender, Receiver, channel}; - -pub struct Broadcast { - peers: Vec>, - rx: Receiver -} - -impl Broadcast { - pub fn new(rx: Receiver) -> Broadcast { - Broadcast { peers: vec![], rx: rx } - } - pub fn start(&self) { - loop { - match self.rx.recv() { - Ok(payload) => { - for subscriber in &self.peers { - match subscriber.send(payload.clone()) { - Err(e) => error!("Error broadcasting: {}", e), - _ => {} - } - } - }, - Err(e) => error!("Error receiving: {}", e) - } - } - } - pub fn subscribe(&mut self) -> Receiver { - let (tx, rx) = channel(); - self.peers.push(tx); - rx - } -} - - -#[cfg(test)] -mod tests { - use super::*; - - use std::sync::mpsc::channel; - use std::thread::spawn; - - #[test] - fn test_broadcasts_events() { - let (tx, rx) = channel(); - let mut broadcast = Broadcast::new(rx); - - let a = broadcast.subscribe(); - let b = broadcast.subscribe(); - - let _ = tx.send(123); - - let _ = spawn(move || { - broadcast.start(); - }); - - let a_got = a.recv().unwrap(); - let b_got = b.recv().unwrap(); - - assert_eq!(a_got, 123); - assert_eq!(b_got, 123); - } -} diff --git a/src/interaction_library/mod.rs b/src/interaction_library/mod.rs index 3557108..f2ac99a 100644 --- a/src/interaction_library/mod.rs +++ b/src/interaction_library/mod.rs @@ -2,7 +2,7 @@ pub use self::console::Console; pub use self::gateway::Gateway; pub use self::interpreter::Interpreter; -mod broadcast; +pub mod broadcast; pub mod console; pub mod gateway; pub mod interpreter; diff --git a/src/lib.rs b/src/lib.rs index aeabbc5..290f27d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -6,7 +6,6 @@ extern crate toml; extern crate ws; pub mod auth_plus; -pub mod broadcast; pub mod datatype; pub mod http_client; pub mod interaction_library; diff --git a/src/main.rs b/src/main.rs index 13f5d23..4fa232b 100644 --- a/src/main.rs +++ b/src/main.rs @@ -15,7 +15,7 @@ use libotaplus::datatype::{config, Config, PackageManager as PackageManagerType, use libotaplus::ui::spawn_websocket_server; use libotaplus::http_client::HttpClient; use libotaplus::repl; -use libotaplus::broadcast::Broadcast; +use libotaplus::interaction_library::broadcast::Broadcast; use libotaplus::interpreter::Interpreter; use rustc_serialize::json; -- cgit v1.2.1 From 28838dd4ce7f27ce43a73bde02ba3ebbd9f66c46 Mon Sep 17 00:00:00 2001 From: Stevan Andjelkovic Date: Tue, 12 Apr 2016 15:11:59 +0200 Subject: Replace repl with library code. --- src/datatype/command.rs | 16 ++++++++++++++++ src/datatype/event.rs | 10 ++++++++++ src/interaction_library/console.rs | 1 + src/lib.rs | 1 - src/main.rs | 6 ++++-- src/repl.rs | 36 ------------------------------------ 6 files changed, 31 insertions(+), 39 deletions(-) delete mode 100644 src/repl.rs diff --git a/src/datatype/command.rs b/src/datatype/command.rs index 72e000c..cde014f 100644 --- a/src/datatype/command.rs +++ b/src/datatype/command.rs @@ -1,5 +1,8 @@ use rustc_serialize::{Encodable}; + use datatype::UpdateRequestId; +use interaction_library::Parse; + #[derive(RustcDecodable, RustcEncodable, PartialEq, Eq, Debug)] pub enum Command { @@ -10,3 +13,16 @@ pub enum Command { PostInstalledPackages, ListInstalledPackages } + +impl Parse for Command { + + fn parse(s: String) -> Option { + match s.as_str() { + "GetPendingUpdates" => Some(Command::GetPendingUpdates), + "PostInstalledPackages" => Some(Command::PostInstalledPackages), + "ListInstalledPackages" => Some(Command::ListInstalledPackages), + _ => None, + } + } + +} diff --git a/src/datatype/event.rs b/src/datatype/event.rs index 37d1275..775a93f 100644 --- a/src/datatype/event.rs +++ b/src/datatype/event.rs @@ -1,6 +1,8 @@ use rustc_serialize::{Encodable}; use datatype::{UpdateRequestId, UpdateState, Package}; +use interaction_library::Print; + #[derive(RustcDecodable, RustcEncodable, PartialEq, Eq, Debug, Clone)] pub enum Event { @@ -11,3 +13,11 @@ pub enum Event { FoundInstalledPackages(Vec), Batch(Vec) } + +impl Print for Event { + + fn pretty_print(&self) -> String { + format!("{:?}", *self) + } + +} diff --git a/src/interaction_library/console.rs b/src/interaction_library/console.rs index 66accde..b79d09f 100644 --- a/src/interaction_library/console.rs +++ b/src/interaction_library/console.rs @@ -17,6 +17,7 @@ impl Gateway for Console fn get_line(&self) -> String { + print!("> "); let mut input = String::new(); let _ = io::stdin().read_line(&mut input); diff --git a/src/lib.rs b/src/lib.rs index 290f27d..84bb0d8 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -12,5 +12,4 @@ pub mod interaction_library; pub mod interpreter; pub mod ota_plus; pub mod package_manager; -pub mod repl; pub mod ui; diff --git a/src/main.rs b/src/main.rs index 4fa232b..22761d9 100644 --- a/src/main.rs +++ b/src/main.rs @@ -14,8 +14,9 @@ use libotaplus::auth_plus::authenticate; use libotaplus::datatype::{config, Config, PackageManager as PackageManagerType, Event, Command, AccessToken}; use libotaplus::ui::spawn_websocket_server; use libotaplus::http_client::HttpClient; -use libotaplus::repl; use libotaplus::interaction_library::broadcast::Broadcast; +use libotaplus::interaction_library::console::Console; +use libotaplus::interaction_library::gateway::Gateway; use libotaplus::interpreter::Interpreter; use rustc_serialize::json; @@ -135,7 +136,8 @@ fn main() { perform_initial_sync(ctx.clone()); if config.test.looping { - repl::start(events_for_repl, ctx.clone()); + println!("Ota Plus Client REPL started."); + Console::run(ctx.clone(), events_for_repl); } else { thread::sleep(Duration::from_secs(60000000)); } diff --git a/src/repl.rs b/src/repl.rs deleted file mode 100644 index 242c99f..0000000 --- a/src/repl.rs +++ /dev/null @@ -1,36 +0,0 @@ -use std::io; -use std::str::FromStr; -use std::sync::mpsc::{Sender, Receiver}; - -use std::thread; - -use datatype::{Command, Event}; - -impl FromStr for Command { - type Err = (); - fn from_str(s: &str) -> Result { - match s { - "GetPendingUpdates" => Ok(Command::GetPendingUpdates), - "PostInstalledPackages" => Ok(Command::PostInstalledPackages), - "ListInstalledPackages" => Ok(Command::ListInstalledPackages), - _ => Err(()), - } - } -} - -pub fn start(erx: Receiver, ctx: Sender) { - let _ = thread::Builder::new().name("REPL Print loop".to_string()).spawn(move || { - loop { - println!("# => {:?}", erx.recv().unwrap()); - } - }); - - println!("Ota Plus Client REPL started."); - loop { - let mut input = String::new(); - print!("> "); - let _ = io::stdin().read_line(&mut input); - - let _ = input.trim().parse().map(|cmd| ctx.send(cmd)); - } -} -- cgit v1.2.1 From 747f2be087d5f538bcf8b791362af8bf00daeab1 Mon Sep 17 00:00:00 2001 From: Stevan Andjelkovic Date: Tue, 12 Apr 2016 15:40:56 +0200 Subject: Replace websocket with library code. --- src/datatype/command.rs | 18 ++++++++------- src/datatype/event.rs | 6 ++--- src/lib.rs | 1 - src/main.rs | 37 +++++------------------------- src/ui/mod.rs | 3 --- src/ui/websocket.rs | 60 ------------------------------------------------- 6 files changed, 18 insertions(+), 107 deletions(-) delete mode 100644 src/ui/mod.rs delete mode 100644 src/ui/websocket.rs diff --git a/src/datatype/command.rs b/src/datatype/command.rs index cde014f..f3c4639 100644 --- a/src/datatype/command.rs +++ b/src/datatype/command.rs @@ -1,7 +1,7 @@ use rustc_serialize::{Encodable}; +use std::str::FromStr; use datatype::UpdateRequestId; -use interaction_library::Parse; #[derive(RustcDecodable, RustcEncodable, PartialEq, Eq, Debug)] @@ -14,14 +14,16 @@ pub enum Command { ListInstalledPackages } -impl Parse for Command { +impl FromStr for Command { - fn parse(s: String) -> Option { - match s.as_str() { - "GetPendingUpdates" => Some(Command::GetPendingUpdates), - "PostInstalledPackages" => Some(Command::PostInstalledPackages), - "ListInstalledPackages" => Some(Command::ListInstalledPackages), - _ => None, + type Err = (); + + fn from_str(s: &str) -> Result { + match s { + "GetPendingUpdates" => Ok(Command::GetPendingUpdates), + "PostInstalledPackages" => Ok(Command::PostInstalledPackages), + "ListInstalledPackages" => Ok(Command::ListInstalledPackages), + _ => Err(()), } } diff --git a/src/datatype/event.rs b/src/datatype/event.rs index 775a93f..53c96d6 100644 --- a/src/datatype/event.rs +++ b/src/datatype/event.rs @@ -1,7 +1,7 @@ use rustc_serialize::{Encodable}; +use std::string::ToString; use datatype::{UpdateRequestId, UpdateState, Package}; -use interaction_library::Print; #[derive(RustcDecodable, RustcEncodable, PartialEq, Eq, Debug, Clone)] @@ -14,9 +14,9 @@ pub enum Event { Batch(Vec) } -impl Print for Event { +impl ToString for Event { - fn pretty_print(&self) -> String { + fn to_string(&self) -> String { format!("{:?}", *self) } diff --git a/src/lib.rs b/src/lib.rs index 84bb0d8..b322b8f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -12,4 +12,3 @@ pub mod interaction_library; pub mod interpreter; pub mod ota_plus; pub mod package_manager; -pub mod ui; diff --git a/src/main.rs b/src/main.rs index 22761d9..11d5dc1 100644 --- a/src/main.rs +++ b/src/main.rs @@ -9,26 +9,19 @@ extern crate rustc_serialize; use getopts::Options; use hyper::Url; use std::env; +use std::sync::mpsc::{Sender, Receiver, channel}; +use std::thread; +use std::time::Duration; use libotaplus::auth_plus::authenticate; use libotaplus::datatype::{config, Config, PackageManager as PackageManagerType, Event, Command, AccessToken}; -use libotaplus::ui::spawn_websocket_server; use libotaplus::http_client::HttpClient; use libotaplus::interaction_library::broadcast::Broadcast; use libotaplus::interaction_library::console::Console; use libotaplus::interaction_library::gateway::Gateway; +use libotaplus::interaction_library::websocket::Websocket; use libotaplus::interpreter::Interpreter; -use rustc_serialize::json; -use std::sync::mpsc::{Sender, Receiver, channel}; - -use std::thread; -use std::time::Duration; - -use std::collections::HashMap; -use std::sync::{Arc, Mutex}; - -use ws::{Sender as WsSender}; macro_rules! spawn_thread { ($name:expr, $body:block) => { @@ -72,26 +65,6 @@ fn spawn_interpreter(config: Config, token: AccessToken, crx: Receiver, }); } -fn spawn_websocket(erx: Receiver, ctx: Sender) { - let all_clients = Arc::new(Mutex::new(HashMap::new())); - let all_clients_ = all_clients.clone(); - spawn_thread!("Websocket Event Broadcast", { - loop { - let event = erx.recv().unwrap(); - let clients = all_clients_.lock().unwrap().clone(); - for (_, client) in clients { - let x: WsSender = client; - let _ = x.send(json::encode(&event).unwrap()); - } - } - }); - - let ctx_ = ctx.clone(); - spawn_thread!("Websocket Server", { - let _ = spawn_websocket_server("0.0.0.0:9999", ctx_, all_clients); - }); -} - fn spawn_update_poller(ctx: Sender, config: Config) { spawn_thread!("Update poller", { loop { @@ -126,7 +99,7 @@ fn main() { spawn_autoacceptor(broadcast.subscribe(), ctx.clone()); spawn_interpreter(config.clone(), token.clone(), crx, etx); - spawn_websocket(broadcast.subscribe(), ctx.clone()); + Websocket::run(ctx.clone(), broadcast.subscribe()); spawn_update_poller(ctx.clone(), config.clone()); let events_for_repl = broadcast.subscribe(); diff --git a/src/ui/mod.rs b/src/ui/mod.rs deleted file mode 100644 index 2e6a0d3..0000000 --- a/src/ui/mod.rs +++ /dev/null @@ -1,3 +0,0 @@ -pub use self::websocket::spawn_websocket_server; - -pub mod websocket; diff --git a/src/ui/websocket.rs b/src/ui/websocket.rs deleted file mode 100644 index 488ba93..0000000 --- a/src/ui/websocket.rs +++ /dev/null @@ -1,60 +0,0 @@ -use ws::{listen, Message, Sender as WsSender, Handler, Handshake, Result as WsResult, CloseCode}; -use ws::util::Token; -use rustc_serialize::json; - -use datatype::{Event, Command, Error}; - -use std::sync::mpsc::Sender; - -use std::thread; -use std::collections::HashMap; -use std::sync::{Arc, Mutex}; - -pub type SharedClients = Arc>>; - -pub struct WebsocketHandler { - all_clients: SharedClients, - out: WsSender, - commands_tx: Sender -} - -impl Handler for WebsocketHandler { - fn on_open(&mut self, _: Handshake) -> WsResult<()> { - self.all_clients.lock().unwrap().insert(self.out.token(), self.out.clone()); - Ok(()) - } - - fn on_message(&mut self, msg: Message) -> WsResult<()> { - if let Message::Text(payload) = msg { - match json::decode(&payload) { - Ok(command) => { let _ = self.commands_tx.send(command); }, - Err(e) => { - let err = format!("Invalid command: {}. Reason: {}", payload, e); - error!("{}", err); - let _ = self.out.send(json::encode(&Event::Error(err)).unwrap()); - } - } - }; - Ok(()) - } - - fn on_close(&mut self, _: CloseCode, _: &str) { - self.all_clients.lock().unwrap().remove(&self.out.token()); - } -} - -pub fn spawn_websocket_server_async(addr: &'static str, ctx: Sender, all_clients: SharedClients) -> Result, Error> { - Ok(try!(thread::Builder::new().name("ui".to_string()).spawn(move || { - spawn_websocket_server(addr, ctx, all_clients).unwrap_or_else(|e| error!("{}", e)) - }))) -} - -pub fn spawn_websocket_server(addr: &'static str, ctx: Sender, all_clients: SharedClients) -> Result<(), Error> { - listen(addr, move |out| { - WebsocketHandler { - all_clients: all_clients.clone(), - out: out, - commands_tx: ctx.clone() - } - }).map_err(Error::Websocket) -} -- cgit v1.2.1 From e8ea6aae05ea8b05588945779600fc5955cc0c25 Mon Sep 17 00:00:00 2001 From: Txus Date: Tue, 12 Apr 2016 17:10:26 +0200 Subject: Add bot to interaction library and implement AutoAcceptor with it --- src/interaction_library/interpreter.rs | 2 +- src/main.rs | 54 +++++++++++++++++++--------------- 2 files changed, 32 insertions(+), 24 deletions(-) diff --git a/src/interaction_library/interpreter.rs b/src/interaction_library/interpreter.rs index d132bb8..e171659 100644 --- a/src/interaction_library/interpreter.rs +++ b/src/interaction_library/interpreter.rs @@ -5,7 +5,7 @@ pub trait Interpreter { fn interpret(env: &Env, c: C, e: Sender); - fn run_interpreter(rx: Receiver, tx: Sender) { + fn run(rx: Receiver, tx: Sender) { let env = Env::default(); diff --git a/src/main.rs b/src/main.rs index 11d5dc1..4df3d00 100644 --- a/src/main.rs +++ b/src/main.rs @@ -21,7 +21,7 @@ use libotaplus::interaction_library::console::Console; use libotaplus::interaction_library::gateway::Gateway; use libotaplus::interaction_library::websocket::Websocket; use libotaplus::interpreter::Interpreter; - +use libotaplus::interaction_library::{Interpreter as InteractionInterpreter}; macro_rules! spawn_thread { ($name:expr, $body:block) => { @@ -37,34 +37,18 @@ macro_rules! spawn_thread { } } -fn spawn_autoacceptor(erx: Receiver, ctx: Sender) { - spawn_thread!("Autoacceptor of software updates", { - fn dispatch(ev: &Event, outlet: Sender) { - match ev { - &Event::NewUpdateAvailable(ref id) => { - let _ = outlet.send(Command::AcceptUpdate(id.clone())); - } - &Event::Batch(ref evs) => { - for ev in evs { - dispatch(ev, outlet.clone()) - } - } - _ => {} - } - }; - loop { - dispatch(&erx.recv().unwrap(), ctx.clone()) - } - }); -} - - fn spawn_interpreter(config: Config, token: AccessToken, crx: Receiver, etx: Sender) { spawn_thread!("Interpreter", { Interpreter::::new(&config, token.clone(), crx, etx).start(); }); } +fn spawn_autoacceptor(erx: Receiver, ctx: Sender) { + spawn_thread!("Autoacceptor of software updates", { + AutoAcceptor::run(erx, ctx); + }); +} + fn spawn_update_poller(ctx: Sender, config: Config) { spawn_thread!("Update poller", { loop { @@ -84,6 +68,30 @@ fn start_event_broadcasting(broadcast: Broadcast) { }); } +struct AutoAcceptor; + +impl InteractionInterpreter<(), Event, Command> for AutoAcceptor { + fn interpret(_: &(), e: Event, ctx: Sender) { + fn f(e: &Event, ctx: Sender) { + match e { + &Event::NewUpdateAvailable(ref id) => { + let _ = ctx.send(Command::AcceptUpdate(id.clone())); + }, + _ => {} + } + } + + match e { + Event::Batch(ref evs) => { + for ev in evs { + f(&ev, ctx.clone()) + } + } + e => f(&e, ctx) + } + } +} + fn main() { env_logger::init().unwrap(); -- cgit v1.2.1 From 829556d5fc2253425f5a70acb6383dce1ff27faa Mon Sep 17 00:00:00 2001 From: Stevan Andjelkovic Date: Wed, 13 Apr 2016 11:32:24 +0200 Subject: Pass in env to interpreter::run. --- src/interaction_library/interpreter.rs | 8 ++------ src/main.rs | 2 +- 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/src/interaction_library/interpreter.rs b/src/interaction_library/interpreter.rs index e171659..64ae305 100644 --- a/src/interaction_library/interpreter.rs +++ b/src/interaction_library/interpreter.rs @@ -1,19 +1,15 @@ use std::sync::mpsc::{Sender, Receiver}; -pub trait Interpreter { +pub trait Interpreter { fn interpret(env: &Env, c: C, e: Sender); - fn run(rx: Receiver, tx: Sender) { - - let env = Env::default(); - + fn run(env: &Env, rx: Receiver, tx: Sender) { loop { let c = rx.recv().unwrap(); Self::interpret(&env, c, tx.clone()); } - } } diff --git a/src/main.rs b/src/main.rs index 4df3d00..579dc64 100644 --- a/src/main.rs +++ b/src/main.rs @@ -45,7 +45,7 @@ fn spawn_interpreter(config: Config, token: AccessToken, crx: Receiver, fn spawn_autoacceptor(erx: Receiver, ctx: Sender) { spawn_thread!("Autoacceptor of software updates", { - AutoAcceptor::run(erx, ctx); + AutoAcceptor::run(&(), erx, ctx); }); } -- cgit v1.2.1 From 042e9286c9bba573401723d5661dd9fbdd1057c2 Mon Sep 17 00:00:00 2001 From: Shaun Taheri Date: Wed, 13 Apr 2016 11:48:36 +0200 Subject: Use PHONY Makefile targets and update bin path --- Makefile | 18 ++++++++---------- pkg/pkg.sh | 2 +- 2 files changed, 9 insertions(+), 11 deletions(-) diff --git a/Makefile b/Makefile index f97bbad..3b91cba 100644 --- a/Makefile +++ b/Makefile @@ -1,21 +1,19 @@ MUSL=x86_64-unknown-linux-musl .PHONY: all -all: pkg/deb/ota-plus-client-0.1.0/bin +all: ota_plus_client -pkg/deb/ota-plus-client-0.1.0/bin: target/release/ota_plus_client - mkdir -p $@ - cp $< $@ - -target/release/ota_plus_client: src/ - export OPENSSL_STATIC=1 +.PHONY: ota_plus_client +ota_plus_client: src/ cargo build --release --target=$(MUSL) - cp target/$(MUSL)/release/ota_plus_client target/release + mkdir -p pkg/deb/ota-plus-client-0.1.0/bin + cp target/$(MUSL)/release/ota_plus_client pkg/deb/ota-plus-client-0.1.0/bin/ + cp target/$(MUSL)/release/ota_plus_client pkg/rpm/ .PHONY: deb -deb: pkg/deb/ota-plus-client-0.1.0/bin +deb: ota_plus_client pkg/pkg.sh deb $(CURDIR) .PHONY: rpm -rpm: target/release/ota_plus_client +rpm: ota_plus_client pkg/pkg.sh rpm $(CURDIR) diff --git a/pkg/pkg.sh b/pkg/pkg.sh index 71c4c82..c2665a6 100755 --- a/pkg/pkg.sh +++ b/pkg/pkg.sh @@ -55,7 +55,7 @@ function make_rpm { fpm -s dir -t rpm -n ${PKG_NAME} -v ${PKG_VER} --prefix ${PREFIX} -a native \ --rpm-service $PKG_SRC_DIR/rpm/ota-client.service \ - ../target/release/ota_plus_client=ota_plus_client $PKG_NAME.toml=ota.toml + $PKG_SRC_DIR/rpm/ota_plus_client=ota_plus_client $PKG_NAME.toml=ota.toml mv -n ota-plus-client*.rpm $dest rm $PKG_NAME.toml -- cgit v1.2.1 From e3d82aff8a19af5ca1e9d2651f7b7804cc612d2a Mon Sep 17 00:00:00 2001 From: Txus Date: Wed, 13 Apr 2016 11:19:29 +0200 Subject: Bootstrap credentials hierarchically The first time we boot, we'll take `client_id` and `secret` from the config file and persist it to `credentials_file` (set under `auth` in the config file). Subsequently, we'll read credentials directly from `credentials_file`. --- ota.toml | 1 + pkg/ota.toml.template | 1 + src/datatype/config.rs | 101 ++++++++++++++++++++++++++++++++++++++++++------- 3 files changed, 90 insertions(+), 13 deletions(-) diff --git a/ota.toml b/ota.toml index dbf0f0f..18bbd7b 100644 --- a/ota.toml +++ b/ota.toml @@ -2,6 +2,7 @@ server = "http://127.0.0.1:9000" client_id = "client-id" secret = "secret" +credentials_file = "/tmp/ats_credentials.toml" [ota] server = "http://127.0.0.1:8080" diff --git a/pkg/ota.toml.template b/pkg/ota.toml.template index 29615c8..a520b24 100644 --- a/pkg/ota.toml.template +++ b/pkg/ota.toml.template @@ -2,6 +2,7 @@ server = "${OTA_AUTH_URL}" client_id = "${OTA_AUTH_CLIENT_ID}" secret = "${OTA_AUTH_SECRET}" +credentials_file = "/opt/ats/credentials.toml" [ota] server = "${OTA_SERVER_URL}" diff --git a/src/datatype/config.rs b/src/datatype/config.rs index e12ee41..65ad7cf 100644 --- a/src/datatype/config.rs +++ b/src/datatype/config.rs @@ -1,8 +1,10 @@ use hyper::Url; use rustc_serialize::Decodable; +use std::fs; use std::fs::File; use std::io::ErrorKind; use std::io::prelude::*; +use std::path::Path; use toml; use datatype::Error; @@ -22,7 +24,29 @@ pub struct Config { pub struct AuthConfig { pub server: Url, pub client_id: String, - pub secret: String + pub secret: String, +} + +impl AuthConfig { + fn new(server: Url, creds: CredentialsFile) -> AuthConfig { + AuthConfig { server: server, + client_id: creds.client_id, + secret: creds.secret } + } +} + +#[derive(RustcDecodable, PartialEq, Eq, Debug, Clone)] +struct AuthConfigSection { + pub server: Url, + pub client_id: String, + pub secret: String, + pub credentials_file: String, +} + +#[derive(RustcEncodable, RustcDecodable, PartialEq, Eq, Debug, Clone)] +struct CredentialsFile { + pub client_id: String, + pub secret: String, } #[derive(RustcDecodable, PartialEq, Eq, Debug, Clone)] @@ -49,6 +73,17 @@ impl Default for AuthConfig { } } +impl Default for AuthConfigSection { + fn default() -> AuthConfigSection { + AuthConfigSection { + server: Url::parse("http://127.0.0.1:9000").unwrap(), + client_id: "client-id".to_string(), + secret: "secret".to_string(), + credentials_file: "/tmp/ats_credentials.toml".to_string(), + } + } +} + impl Default for OtaConfig { fn default() -> OtaConfig { OtaConfig { @@ -69,23 +104,62 @@ impl Default for TestConfig { } } +fn parse_toml(s: &str) -> Result { + let table: toml::Table = try!(toml::Parser::new(&s) + .parse() + .ok_or(Error::Config(Parse(InvalidToml)))); + Ok(table) +} -pub fn parse_config(s: &str) -> Result { +fn parse_toml_table(tbl: &toml::Table, sect: &str) -> Result { + tbl.get(sect) + .and_then(|c| toml::decode::(c.clone()) ) + .ok_or(Error::Config(Parse(InvalidSection(sect.to_string())))) +} + +fn bootstrap_credentials(auth_cfg_section: AuthConfigSection) -> Result { + + fn persist_credentials_file(creds: &CredentialsFile, path: &Path) -> Result<(), Error> { + let mut tbl = toml::Table::new(); + tbl.insert("auth".to_string(), toml::encode(&creds)); + try!(fs::create_dir_all(&path.parent().unwrap())); + let mut f = try!(File::create(path)); + try!(f.write_all(&toml::encode_str(&tbl).into_bytes())); + Ok(()) + } - fn parse_sect(tbl: &toml::Table, sect: &str) -> Result { - tbl.get(sect) - .and_then(|c| toml::decode::(c.clone()) ) - .ok_or(Error::Config(Parse(InvalidSection(sect.to_string())))) + fn read_credentials_file(mut f: File) -> Result { + let mut s = String::new(); + try!(f.read_to_string(&mut s) + .map_err(|err| Error::Config(Io(err)))); + let toml_table = try!(parse_toml(&s)); + let creds: CredentialsFile = try!(parse_toml_table(&toml_table, "auth")); + Ok(creds) } - let tbl: toml::Table = - try!(toml::Parser::new(&s) - .parse() - .ok_or(Error::Config(Parse(InvalidToml)))); + let creds_path = Path::new(&auth_cfg_section.credentials_file); - let auth_cfg: AuthConfig = try!(parse_sect(&tbl, "auth")); - let ota_cfg: OtaConfig = try!(parse_sect(&tbl, "ota")); - let test_cfg: TestConfig = try!(parse_sect(&tbl, "test")); + match File::open(creds_path) { + Err(ref e) if e.kind() == ErrorKind::NotFound => { + let creds = CredentialsFile { client_id: auth_cfg_section.client_id, + secret: auth_cfg_section.secret }; + try!(persist_credentials_file(&creds, &creds_path)); + Ok(AuthConfig::new(auth_cfg_section.server, creds)) + } + Err(e) => Err(Error::Config(Io(e))), + Ok(f) => { + let creds = try!(read_credentials_file(f)); + Ok(AuthConfig::new(auth_cfg_section.server, creds)) + } + } +} + +pub fn parse_config(s: &str) -> Result { + let tbl = try!(parse_toml(&s)); + let auth_cfg_section: AuthConfigSection = try!(parse_toml_table(&tbl, "auth")); + let auth_cfg: AuthConfig = try!(bootstrap_credentials(auth_cfg_section)); + let ota_cfg: OtaConfig = try!(parse_toml_table(&tbl, "ota")); + let test_cfg: TestConfig = try!(parse_toml_table(&tbl, "test")); return Ok(Config { auth: auth_cfg, @@ -120,6 +194,7 @@ mod tests { server = "http://127.0.0.1:9000" client_id = "client-id" secret = "secret" + credentials_file = "/tmp/ats_credentials.toml" [ota] server = "http://127.0.0.1:8080" -- cgit v1.2.1 From 23e7627e29db1f86c4621bcf160977c29ef87333 Mon Sep 17 00:00:00 2001 From: Txus Date: Wed, 13 Apr 2016 14:18:10 +0200 Subject: Handle SIGTERM as a Command, so that we have time to finish what we started. --- Cargo.toml | 4 +++- src/datatype/command.rs | 4 +++- src/interpreter.rs | 7 ++++++- src/main.rs | 22 ++++++++++++++++++++++ 4 files changed, 34 insertions(+), 3 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index c0be8f7..7bc4eeb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,6 +13,8 @@ path = "src/main.rs" doc = false [dependencies] +chan-signal = "*" +chan= "*" env_logger = "*" getopts = "*" hyper = "*" @@ -20,4 +22,4 @@ log = "*" rustc-serialize = "*" tempfile = "*" toml = "*" -ws = "*" \ No newline at end of file +ws = "*" diff --git a/src/datatype/command.rs b/src/datatype/command.rs index f3c4639..cbf4eeb 100644 --- a/src/datatype/command.rs +++ b/src/datatype/command.rs @@ -11,7 +11,9 @@ pub enum Command { AcceptUpdate(UpdateRequestId), PostInstalledPackages, - ListInstalledPackages + ListInstalledPackages, + + Shutdown } impl FromStr for Command { diff --git a/src/interpreter.rs b/src/interpreter.rs index 6bd7a35..58cabeb 100644 --- a/src/interpreter.rs +++ b/src/interpreter.rs @@ -1,5 +1,6 @@ use std::sync::mpsc::{Sender , Receiver}; use std::marker::PhantomData; +use std::process::exit; use http_client::HttpClient; use ota_plus::{get_package_updates, download_package_update, post_packages, send_install_report}; @@ -30,7 +31,11 @@ impl<'a, C: HttpClient> Interpreter<'a, C> { Command::GetPendingUpdates => self.get_pending_updates(), Command::PostInstalledPackages => self.post_installed_packages(), Command::AcceptUpdate(ref id) => self.accept_update(id), - Command::ListInstalledPackages => self.list_installed_packages() + Command::ListInstalledPackages => self.list_installed_packages(), + Command::Shutdown => { + info!("Shutting down..."); + exit(0) + } } } diff --git a/src/main.rs b/src/main.rs index 579dc64..c419cbe 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,5 +1,7 @@ #[macro_use] extern crate log; extern crate env_logger; +extern crate chan_signal; +extern crate chan; extern crate getopts; extern crate hyper; extern crate ws; @@ -12,6 +14,8 @@ use std::env; use std::sync::mpsc::{Sender, Receiver, channel}; use std::thread; use std::time::Duration; +use chan_signal::Signal; +use chan::Receiver as ChanReceiver; use libotaplus::auth_plus::authenticate; use libotaplus::datatype::{config, Config, PackageManager as PackageManagerType, Event, Command, AccessToken}; @@ -49,6 +53,19 @@ fn spawn_autoacceptor(erx: Receiver, ctx: Sender) { }); } +fn spawn_signal_handler(signals: ChanReceiver, ctx: Sender) { + spawn_thread!("TERM signal handler", { + loop { + match signals.recv() { + Some(s) if s == Signal::TERM => { + let _ = ctx.send(Command::Shutdown); + }, + _ => {} + } + } + }); +} + fn spawn_update_poller(ctx: Sender, config: Config) { spawn_thread!("Update poller", { loop { @@ -105,6 +122,9 @@ fn main() { let mut broadcast: Broadcast = Broadcast::new(erx); + // Must subscribe to the signal before spawning ANY other threads + let signals = chan_signal::notify(&[Signal::TERM]); + spawn_autoacceptor(broadcast.subscribe(), ctx.clone()); spawn_interpreter(config.clone(), token.clone(), crx, etx); Websocket::run(ctx.clone(), broadcast.subscribe()); @@ -116,6 +136,8 @@ fn main() { perform_initial_sync(ctx.clone()); + spawn_signal_handler(signals, ctx.clone()); + if config.test.looping { println!("Ota Plus Client REPL started."); Console::run(ctx.clone(), events_for_repl); -- cgit v1.2.1 From bd14f77c84ec08fdd06250a9ea1020fc33832577 Mon Sep 17 00:00:00 2001 From: Stevan Andjelkovic Date: Wed, 13 Apr 2016 11:48:54 +0200 Subject: Add skeleton for new interpreter. --- src/lib.rs | 1 + src/new_interpreter.rs | 29 +++++++++++++++++++++++++++++ 2 files changed, 30 insertions(+) create mode 100644 src/new_interpreter.rs diff --git a/src/lib.rs b/src/lib.rs index b322b8f..647f964 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -10,5 +10,6 @@ pub mod datatype; pub mod http_client; pub mod interaction_library; pub mod interpreter; +pub mod new_interpreter; pub mod ota_plus; pub mod package_manager; diff --git a/src/new_interpreter.rs b/src/new_interpreter.rs new file mode 100644 index 0000000..da59a5f --- /dev/null +++ b/src/new_interpreter.rs @@ -0,0 +1,29 @@ +use std::sync::mpsc::Sender; + +use datatype::{AccessToken, Command, Config, Event}; +use package_manager::PackageManager; +use interaction_library::interpreter; +use interaction_library::interpreter::Interpreter; + + +pub struct OurInterpreter; + +pub struct Env<'a> { + config: &'a Config, + access_token: Option<&'a AccessToken>, + pkg_manager: &'a PackageManager, +} + + +impl<'a> Interpreter, Command, Event> for OurInterpreter { + + fn interpret(env: &Env, cmd: Command, rx: Sender) { + match cmd { + Command::GetPendingUpdates => unimplemented!(), + Command::PostInstalledPackages => unimplemented!(), + Command::AcceptUpdate(ref id) => unimplemented!(), + Command::ListInstalledPackages => unimplemented!(), + } + } + +} -- cgit v1.2.1 From 5049d95b254cfb7a435d7f5e80520c7fb9f66bed Mon Sep 17 00:00:00 2001 From: Stevan Andjelkovic Date: Wed, 13 Apr 2016 15:19:19 +0200 Subject: Simplify the package manager code. --- src/datatype/config.rs | 2 +- src/datatype/mod.rs | 2 - src/datatype/package_manager.rs | 50 --------------- src/interpreter.rs | 6 +- src/main.rs | 9 +-- src/new_interpreter.rs | 1 + src/package_manager/dpkg.rs | 74 ++++++++++------------ src/package_manager/interface.rs | 6 -- src/package_manager/mod.rs | 7 +-- src/package_manager/package_manager.rs | 56 +++++++++++++++++ src/package_manager/rpm.rs | 68 +++++++++----------- src/package_manager/tpm.rs | 112 ++++++++++++++------------------- 12 files changed, 176 insertions(+), 217 deletions(-) delete mode 100644 src/datatype/package_manager.rs delete mode 100644 src/package_manager/interface.rs create mode 100644 src/package_manager/package_manager.rs diff --git a/src/datatype/config.rs b/src/datatype/config.rs index 65ad7cf..6700442 100644 --- a/src/datatype/config.rs +++ b/src/datatype/config.rs @@ -10,7 +10,7 @@ use toml; use datatype::Error; use datatype::error::ConfigReason::{Parse, Io}; use datatype::error::ParseReason::{InvalidToml, InvalidSection}; -use datatype::PackageManager; +use package_manager::PackageManager; #[derive(Default, PartialEq, Eq, Debug, Clone)] diff --git a/src/datatype/mod.rs b/src/datatype/mod.rs index 6acfb22..610cd37 100644 --- a/src/datatype/mod.rs +++ b/src/datatype/mod.rs @@ -2,7 +2,6 @@ pub use self::access_token::AccessToken; pub use self::config::{Config, AuthConfig, OtaConfig, TestConfig}; pub use self::error::Error; pub use self::package::Package; -pub use self::package_manager::PackageManager; pub use self::update_request::{UpdateRequestId, UpdateState}; pub use self::event::Event; pub use self::command::Command; @@ -12,7 +11,6 @@ pub mod access_token; pub mod config; pub mod error; pub mod package; -pub mod package_manager; pub mod update_request; pub mod event; pub mod command; diff --git a/src/datatype/package_manager.rs b/src/datatype/package_manager.rs deleted file mode 100644 index 522e355..0000000 --- a/src/datatype/package_manager.rs +++ /dev/null @@ -1,50 +0,0 @@ -use rustc_serialize::{Decoder, Decodable}; - -use package_manager::PackageManager as PackageManagerTrait; -use package_manager::dpkg::DPKG; -use package_manager::rpm::RPM; -use package_manager::tpm::TPM; - - -#[derive(Debug, PartialEq, Eq, Clone)] -pub enum PackageManager { - Dpkg, - Rpm, - File(String), -} - -impl PackageManager { - - pub fn extension(&self) -> String { - match *self { - PackageManager::Dpkg => "deb".to_string(), - PackageManager::Rpm => "rpm".to_string(), - PackageManager::File(ref s) => s.to_string(), - } - } - - pub fn build(&self) -> &'static PackageManagerTrait { - match *self { - PackageManager::Dpkg => DPKG, - PackageManager::Rpm => RPM, - PackageManager::File(_) => TPM - } - } - -} - -fn parse_package_manager(s: String) -> Result { - match s.to_lowercase().as_str() { - "dpkg" => Ok(PackageManager::Dpkg), - "rpm" => Ok(PackageManager::Rpm), - s => Ok(PackageManager::File(s.to_string())), - } -} - -impl Decodable for PackageManager { - - fn decode(d: &mut D) -> Result { - d.read_str().and_then(|s| parse_package_manager(s) - .map_err(|e| d.error(&e))) - } -} diff --git a/src/interpreter.rs b/src/interpreter.rs index 58cabeb..a7d86ce 100644 --- a/src/interpreter.rs +++ b/src/interpreter.rs @@ -44,8 +44,7 @@ impl<'a, C: HttpClient> Interpreter<'a, C> { } fn get_installed_packages(&self) -> Result, Error> { - let pkg_manager = self.config.ota.package_manager.build(); - pkg_manager.installed_packages(&self.config.ota) + self.config.ota.package_manager.installed_packages() } fn get_pending_updates(&self) { @@ -81,8 +80,7 @@ impl<'a, C: HttpClient> Interpreter<'a, C> { .and_then(|path| { info!("Downloaded at {:?}. Installing...", path); self.publish(Event::UpdateStateChanged(id.clone(), UpdateState::Installing)); - let pkg_manager = self.config.ota.package_manager.build(); - pkg_manager.install_package(&self.config.ota, path.to_str().unwrap()) + self.config.ota.package_manager.install_package(path.to_str().unwrap()) .map(|(code, output)| { self.publish(Event::UpdateStateChanged(id.clone(), UpdateState::Installed)); UpdateReport::new(id.clone(), code, output) diff --git a/src/main.rs b/src/main.rs index c419cbe..49f6cd0 100644 --- a/src/main.rs +++ b/src/main.rs @@ -18,7 +18,7 @@ use chan_signal::Signal; use chan::Receiver as ChanReceiver; use libotaplus::auth_plus::authenticate; -use libotaplus::datatype::{config, Config, PackageManager as PackageManagerType, Event, Command, AccessToken}; +use libotaplus::datatype::{config, Config, Event, Command, AccessToken}; use libotaplus::http_client::HttpClient; use libotaplus::interaction_library::broadcast::Broadcast; use libotaplus::interaction_library::console::Console; @@ -26,6 +26,7 @@ use libotaplus::interaction_library::gateway::Gateway; use libotaplus::interaction_library::websocket::Websocket; use libotaplus::interpreter::Interpreter; use libotaplus::interaction_library::{Interpreter as InteractionInterpreter}; +use libotaplus::package_manager::PackageManager; macro_rules! spawn_thread { ($name:expr, $body:block) => { @@ -223,9 +224,9 @@ fn build_config() -> Config { if let Some(s) = matches.opt_str("ota-package-manager") { config.ota.package_manager = match s.to_lowercase().as_str() { - "dpkg" => PackageManagerType::Dpkg, - "rpm" => PackageManagerType::Rpm, - path => PackageManagerType::File(path.to_string()), + "dpkg" => PackageManager::Dpkg, + "rpm" => PackageManager::Rpm, + path => PackageManager::File(path.to_string()), } } diff --git a/src/new_interpreter.rs b/src/new_interpreter.rs index da59a5f..5b5e68c 100644 --- a/src/new_interpreter.rs +++ b/src/new_interpreter.rs @@ -23,6 +23,7 @@ impl<'a> Interpreter, Command, Event> for OurInterpreter { Command::PostInstalledPackages => unimplemented!(), Command::AcceptUpdate(ref id) => unimplemented!(), Command::ListInstalledPackages => unimplemented!(), + Command::Shutdown => unimplemented!(), } } diff --git a/src/package_manager/dpkg.rs b/src/package_manager/dpkg.rs index bd0cb99..fc2aaef 100644 --- a/src/package_manager/dpkg.rs +++ b/src/package_manager/dpkg.rs @@ -1,54 +1,44 @@ use std::process::Command; use datatype::Error; -use datatype::OtaConfig; use datatype::Package; use datatype::UpdateResultCode; -use package_manager::PackageManager; -pub struct Dpkg; - -pub static DPKG: &'static PackageManager = &Dpkg; - -impl PackageManager for Dpkg { - - fn installed_packages(&self, _: &OtaConfig) -> Result, Error> { - Command::new("dpkg-query").arg("-f").arg("${Package} ${Version}\n").arg("-W") - .output() - .map_err(|e| Error::PackageError(format!("Error fetching packages: {}", e))) - .and_then(|c| { - String::from_utf8(c.stdout) - .map_err(|e| Error::ParseError(format!("Error parsing package: {}", e))) - .map(|s| s.lines().map(|n| String::from(n)).collect::>()) - }) - .and_then(|lines| { - lines.iter() - .map(|line| parse_package(line)) - .collect::, _>>() - }) - } - - fn install_package(&self, _: &OtaConfig, path: &str) -> Result<(UpdateResultCode, String), (UpdateResultCode, String)> { - let output = try!(Command::new("dpkg").arg("-E").arg("-i") - .arg(path) - .output() - .map_err(|e| { - (UpdateResultCode::GENERAL_ERROR, format!("{:?}", e)) - })); - - let stdout = String::from_utf8_lossy(&output.stdout).into_owned(); +pub fn installed_packages() -> Result, Error> { + Command::new("dpkg-query").arg("-f").arg("${Package} ${Version}\n").arg("-W") + .output() + .map_err(|e| Error::PackageError(format!("Error fetching packages: {}", e))) + .and_then(|c| { + String::from_utf8(c.stdout) + .map_err(|e| Error::ParseError(format!("Error parsing package: {}", e))) + .map(|s| s.lines().map(|n| String::from(n)).collect::>()) + }) + .and_then(|lines| { + lines.iter() + .map(|line| parse_package(line)) + .collect::, _>>() + }) +} - match output.status.code() { - Some(0) => if (&stdout).contains("already installed") { - Ok((UpdateResultCode::ALREADY_PROCESSED, stdout)) - } else { - Ok((UpdateResultCode::OK, stdout)) - }, - _ => Err((UpdateResultCode::INSTALL_FAILED, stdout)) - } +pub fn install_package(path: &str) -> Result<(UpdateResultCode, String), (UpdateResultCode, String)> { + let output = try!(Command::new("dpkg").arg("-E").arg("-i") + .arg(path) + .output() + .map_err(|e| { + (UpdateResultCode::GENERAL_ERROR, format!("{:?}", e)) + })); + + let stdout = String::from_utf8_lossy(&output.stdout).into_owned(); + + match output.status.code() { + Some(0) => if (&stdout).contains("already installed") { + Ok((UpdateResultCode::ALREADY_PROCESSED, stdout)) + } else { + Ok((UpdateResultCode::OK, stdout)) + }, + _ => Err((UpdateResultCode::INSTALL_FAILED, stdout)) } - } pub fn parse_package(line: &str) -> Result { diff --git a/src/package_manager/interface.rs b/src/package_manager/interface.rs deleted file mode 100644 index bf28cc8..0000000 --- a/src/package_manager/interface.rs +++ /dev/null @@ -1,6 +0,0 @@ -use datatype::{Error, OtaConfig, Package, UpdateResultCode}; - -pub trait PackageManager { - fn installed_packages(&self, &OtaConfig) -> Result, Error>; - fn install_package(&self, &OtaConfig, path: &str) -> Result<(UpdateResultCode, String), (UpdateResultCode, String)>; -} diff --git a/src/package_manager/mod.rs b/src/package_manager/mod.rs index 82c541f..b3db086 100644 --- a/src/package_manager/mod.rs +++ b/src/package_manager/mod.rs @@ -1,9 +1,6 @@ -pub use self::dpkg::Dpkg; -pub use self::interface::PackageManager; -pub use self::rpm::Rpm; -pub use self::tpm::Tpm; +pub use self::package_manager::PackageManager; pub mod dpkg; -pub mod interface; +pub mod package_manager; pub mod rpm; pub mod tpm; diff --git a/src/package_manager/package_manager.rs b/src/package_manager/package_manager.rs new file mode 100644 index 0000000..149664a --- /dev/null +++ b/src/package_manager/package_manager.rs @@ -0,0 +1,56 @@ +use rustc_serialize::{Decoder, Decodable}; + +use datatype::{Error, Package, UpdateResultCode}; +use package_manager::{dpkg, rpm, tpm}; + + +#[derive(Debug, PartialEq, Eq, Clone)] +pub enum PackageManager { + Dpkg, + Rpm, + File(String), +} + +impl PackageManager { + + pub fn installed_packages(&self) -> Result, Error> { + match *self { + PackageManager::Dpkg => dpkg::installed_packages(), + PackageManager::Rpm => rpm::installed_packages(), + PackageManager::File(ref s) => tpm::installed_packages(s), + } + } + + pub fn install_package(&self, path: &str) -> Result<(UpdateResultCode, String), (UpdateResultCode, String)> { + match *self { + PackageManager::Dpkg => dpkg::install_package(path), + PackageManager::Rpm => rpm::install_package(path), + PackageManager::File(ref s) => tpm::install_package(s, path), + } + } + + pub fn extension(&self) -> String { + match *self { + PackageManager::Dpkg => "deb".to_string(), + PackageManager::Rpm => "rpm".to_string(), + PackageManager::File(ref s) => s.to_string(), + } + } + +} + +fn parse_package_manager(s: String) -> Result { + match s.to_lowercase().as_str() { + "dpkg" => Ok(PackageManager::Dpkg), + "rpm" => Ok(PackageManager::Rpm), + s => Ok(PackageManager::File(s.to_string())), + } +} + +impl Decodable for PackageManager { + + fn decode(d: &mut D) -> Result { + d.read_str().and_then(|s| parse_package_manager(s) + .map_err(|e| d.error(&e))) + } +} diff --git a/src/package_manager/rpm.rs b/src/package_manager/rpm.rs index 669918f..24108f8 100644 --- a/src/package_manager/rpm.rs +++ b/src/package_manager/rpm.rs @@ -1,48 +1,40 @@ use std::process::Command; -use datatype::Error; -use datatype::OtaConfig; -use datatype::Package; -use datatype::UpdateResultCode; -use package_manager::PackageManager; -use package_manager::dpkg::parse_package as parse_package; -pub struct Rpm; +use datatype::{Error, Package, UpdateResultCode}; +use package_manager::dpkg::parse_package; // XXX: Move somewhere better? -pub static RPM: &'static PackageManager = &Rpm; -impl PackageManager for Rpm { - fn installed_packages(&self, _: &OtaConfig) -> Result, Error> { - Command::new("rpm").arg("-qa").arg("--queryformat").arg("%{NAME} %{SIZE}\n") - .output() - .map_err(|e| Error::PackageError(format!("Error fetching packages: {}", e))) - .and_then(|c| { - String::from_utf8(c.stdout) - .map_err(|e| Error::ParseError(format!("Error parsing package: {}", e))) - .map(|s| s.lines().map(|n| String::from(n)).collect::>()) - }) - .and_then(|lines| { - lines.iter() - .map(|line| parse_package(line)) - .collect::, _>>() - }) - } +pub fn installed_packages() -> Result, Error> { + Command::new("rpm").arg("-qa").arg("--queryformat").arg("%{NAME} %{SIZE}\n") + .output() + .map_err(|e| Error::PackageError(format!("Error fetching packages: {}", e))) + .and_then(|c| { + String::from_utf8(c.stdout) + .map_err(|e| Error::ParseError(format!("Error parsing package: {}", e))) + .map(|s| s.lines().map(|n| String::from(n)).collect::>()) + }) + .and_then(|lines| { + lines.iter() + .map(|line| parse_package(line)) + .collect::, _>>() + }) +} - fn install_package(&self, _: &OtaConfig, path: &str) -> Result<(UpdateResultCode, String), (UpdateResultCode, String)> { - let output = try!(Command::new("rpm").arg("-ivh").arg(path) - .output() - .map_err(|e| { - (UpdateResultCode::GENERAL_ERROR, format!("{:?}", e)) - })); +pub fn install_package(path: &str) -> Result<(UpdateResultCode, String), (UpdateResultCode, String)> { + let output = try!(Command::new("rpm").arg("-ivh").arg(path) + .output() + .map_err(|e| { + (UpdateResultCode::GENERAL_ERROR, format!("{:?}", e)) + })); - let stdout = String::from_utf8_lossy(&output.stdout).into_owned(); + let stdout = String::from_utf8_lossy(&output.stdout).into_owned(); - match output.status.code() { - Some(0) => Ok((UpdateResultCode::OK, stdout)), - _ => if (&stdout).contains("already installed") { - Ok((UpdateResultCode::ALREADY_PROCESSED, stdout)) - } else { - Err((UpdateResultCode::INSTALL_FAILED, stdout)) - } + match output.status.code() { + Some(0) => Ok((UpdateResultCode::OK, stdout)), + _ => if (&stdout).contains("already installed") { + Ok((UpdateResultCode::ALREADY_PROCESSED, stdout)) + } else { + Err((UpdateResultCode::INSTALL_FAILED, stdout)) } } } diff --git a/src/package_manager/tpm.rs b/src/package_manager/tpm.rs index 1391bed..a94ec5c 100644 --- a/src/package_manager/tpm.rs +++ b/src/package_manager/tpm.rs @@ -3,72 +3,58 @@ use std::fs::OpenOptions; use std::io::BufReader; use std::io::BufWriter; use std::io::prelude::*; -use std::iter::Iterator; -use datatype::Error; -use datatype::OtaConfig; -use datatype::Package; -use datatype::UpdateResultCode; -use package_manager::PackageManager; +use datatype::{Error, Package, UpdateResultCode}; -// The test package manager. -pub struct Tpm; +pub fn installed_packages(path: &str) -> Result, Error> { -pub static TPM: &'static PackageManager = &Tpm; + let f = try!(File::open(path)); + let reader = BufReader::new(f); + let mut pkgs = Vec::new(); -impl PackageManager for Tpm { + for line in reader.lines() { - fn installed_packages(&self, config: &OtaConfig) -> Result, Error> { + let line = try!(line); + let parts = line.split(' '); - let f = try!(File::open(config.packages_dir.clone() + - &config.package_manager.extension())); - let reader = BufReader::new(f); - let mut pkgs = Vec::new(); - - for line in reader.lines() { - - let line = try!(line); - let parts = line.split(' '); - - if parts.clone().count() == 2 { - if let Some(name) = parts.clone().nth(0) { - if let Some(version) = parts.clone().nth(1) { - pkgs.push(Package { - name: name.to_string(), - version: version.to_string() - }); - } + if parts.clone().count() == 2 { + if let Some(name) = parts.clone().nth(0) { + if let Some(version) = parts.clone().nth(1) { + pkgs.push(Package { + name: name.to_string(), + version: version.to_string() + }); } } - } - return Ok(pkgs) - } - fn install_package(&self, config: &OtaConfig, pkg: &str) -> Result<(UpdateResultCode, String), (UpdateResultCode, String)> { - fn install(config: &OtaConfig, pkg: &str) -> Result<(), Error> { - let f = try!(OpenOptions::new() - .create(true) - .write(true) - .append(true) - .open(config.packages_dir.clone() + - &config.package_manager.extension())); + return Ok(pkgs) + +} - let mut writer = BufWriter::new(f); +pub fn install_package(path: &str, pkg: &str) -> Result<(UpdateResultCode, String), (UpdateResultCode, String)> { - try!(writer.write(pkg.as_bytes())); - try!(writer.write(b"\n")); + fn install(path: &str, pkg: &str) -> Result<(), Error> { + let f = try!(OpenOptions::new() + .create(true) + .write(true) + .append(true) + .open(path)); - return Ok(()) - } + let mut writer = BufWriter::new(f); - match install(&config, &pkg) { - Ok(_) => Ok((UpdateResultCode::OK, "".to_string())), - Err(e) => Err((UpdateResultCode::INSTALL_FAILED, format!("{:?}", e))) - } + try!(writer.write(pkg.as_bytes())); + try!(writer.write(b"\n")); + + return Ok(()) + } + + match install(path, pkg) { + Ok(_) => Ok((UpdateResultCode::OK, "".to_string())), + Err(e) => Err((UpdateResultCode::INSTALL_FAILED, format!("{:?}", e))) } } @@ -84,8 +70,7 @@ mod tests { use super::*; use datatype::OtaConfig; use datatype::Package; - use datatype::PackageManager; - use package_manager::PackageManager as PackageManagerTrait; + use package_manager::PackageManager; fn pkg1() -> Package { Package { @@ -121,44 +106,41 @@ mod tests { #[test] fn test_installed_packages() { - let config = make_config("test1"); + let path = "/tmp/test1"; - let mut f = File::create(config.packages_dir.clone() + - &config.package_manager.extension()).unwrap(); + let mut f = File::create(path).unwrap(); f.write(b"apa 0.0.0\n").unwrap(); f.write(b"bepa 1.0.0").unwrap(); - assert_eq!(Tpm.installed_packages(&config).unwrap(), vec!(pkg1(), pkg2())); + assert_eq!(installed_packages(path).unwrap(), vec!(pkg1(), pkg2())); } #[test] fn bad_installed_packages() { - let config = make_config("test2"); + let path = "/tmp/test2"; - let mut f = File::create(config.packages_dir.clone() + - &config.package_manager.extension()).unwrap(); + let mut f = File::create(path).unwrap(); f.write(b"cepa-2.0.0\n").unwrap(); - assert_eq!(Tpm.installed_packages(&config).unwrap(), Vec::new()); + assert_eq!(installed_packages(path).unwrap(), Vec::new()); } #[test] fn test_install_package() { - let config = make_config("test3"); + let path = "/tmp/test3"; - let _ = fs::remove_file(config.packages_dir.to_string() + - &config.package_manager.extension()); + let _ = fs::remove_file(path); - Tpm.install_package(&config, "apa 0.0.0").unwrap(); - Tpm.install_package(&config, "bepa 1.0.0").unwrap(); + install_package(path, "apa 0.0.0").unwrap(); + install_package(path, "bepa 1.0.0").unwrap(); - assert_eq!(Tpm.installed_packages(&config).unwrap(), vec!(pkg1(), pkg2())); + assert_eq!(installed_packages(path).unwrap(), vec!(pkg1(), pkg2())); } -- cgit v1.2.1 From 724a5bce5052a96e1f2714fadfea2eda92e3670e Mon Sep 17 00:00:00 2001 From: Txus Date: Wed, 13 Apr 2016 14:41:42 +0200 Subject: Build DEB and RPM packages with configurable version --- Makefile | 4 +--- pkg/Dockerfile | 2 +- pkg/deb/ota-plus-client-0.1.0/debian/changelog | 5 ----- pkg/deb/ota-plus-client-0.1.0/debian/compat | 1 - pkg/deb/ota-plus-client-0.1.0/debian/control | 12 ----------- pkg/deb/ota-plus-client-0.1.0/debian/copyright | 0 .../debian/ota-plus-client.dirs | 2 -- .../debian/ota-plus-client.install | 2 -- .../debian/ota-plus-client.service | 10 --------- pkg/deb/ota-plus-client-0.1.0/debian/rules | 3 --- pkg/deb/ota-plus-client-0.1.0/debian/source/format | 1 - pkg/ota-client.service | 10 +++++++++ pkg/pkg.sh | 25 +++++++++------------- pkg/rpm/ota-client.service | 10 --------- 14 files changed, 22 insertions(+), 65 deletions(-) delete mode 100644 pkg/deb/ota-plus-client-0.1.0/debian/changelog delete mode 100644 pkg/deb/ota-plus-client-0.1.0/debian/compat delete mode 100644 pkg/deb/ota-plus-client-0.1.0/debian/control delete mode 100644 pkg/deb/ota-plus-client-0.1.0/debian/copyright delete mode 100644 pkg/deb/ota-plus-client-0.1.0/debian/ota-plus-client.dirs delete mode 100644 pkg/deb/ota-plus-client-0.1.0/debian/ota-plus-client.install delete mode 100644 pkg/deb/ota-plus-client-0.1.0/debian/ota-plus-client.service delete mode 100644 pkg/deb/ota-plus-client-0.1.0/debian/rules delete mode 100644 pkg/deb/ota-plus-client-0.1.0/debian/source/format create mode 100644 pkg/ota-client.service delete mode 100644 pkg/rpm/ota-client.service diff --git a/Makefile b/Makefile index 3b91cba..fabfa61 100644 --- a/Makefile +++ b/Makefile @@ -6,9 +6,7 @@ all: ota_plus_client .PHONY: ota_plus_client ota_plus_client: src/ cargo build --release --target=$(MUSL) - mkdir -p pkg/deb/ota-plus-client-0.1.0/bin - cp target/$(MUSL)/release/ota_plus_client pkg/deb/ota-plus-client-0.1.0/bin/ - cp target/$(MUSL)/release/ota_plus_client pkg/rpm/ + cp target/$(MUSL)/release/ota_plus_client pkg/ .PHONY: deb deb: ota_plus_client diff --git a/pkg/Dockerfile b/pkg/Dockerfile index 88a5d84..eccbe0b 100644 --- a/pkg/Dockerfile +++ b/pkg/Dockerfile @@ -1,7 +1,7 @@ FROM clux/muslrust # for statically linking binaries; see https://github.com/clux/muslrust -ENV FPM_VER 1.4.0 +ENV FPM_VER 1.5.0 RUN apt-get update \ && apt-get install -y \ diff --git a/pkg/deb/ota-plus-client-0.1.0/debian/changelog b/pkg/deb/ota-plus-client-0.1.0/debian/changelog deleted file mode 100644 index ce20224..0000000 --- a/pkg/deb/ota-plus-client-0.1.0/debian/changelog +++ /dev/null @@ -1,5 +0,0 @@ -ota-plus-client (0.1.0-1) unstable; urgency=medium - - * Initial release. - - -- Jerry Wed, 26 Feb 2016 17:33:03 +0200 diff --git a/pkg/deb/ota-plus-client-0.1.0/debian/compat b/pkg/deb/ota-plus-client-0.1.0/debian/compat deleted file mode 100644 index ec63514..0000000 --- a/pkg/deb/ota-plus-client-0.1.0/debian/compat +++ /dev/null @@ -1 +0,0 @@ -9 diff --git a/pkg/deb/ota-plus-client-0.1.0/debian/control b/pkg/deb/ota-plus-client-0.1.0/debian/control deleted file mode 100644 index ec8d1e7..0000000 --- a/pkg/deb/ota-plus-client-0.1.0/debian/control +++ /dev/null @@ -1,12 +0,0 @@ -Source: ota-plus-client -Maintainer: Jerry Trieu -Section: misc -Priority: optional -Standards-Version: 3.9.2 -Build-Depends: debhelper (>= 9), dh-systemd - -Package: ota-plus-client -Architecture: any -Depends: ${shlibs:Depends}, ${misc:Depends} -Description: ATS OTA+ Client SDK - ATS OTA+ Client SDK for a vehicle or device diff --git a/pkg/deb/ota-plus-client-0.1.0/debian/copyright b/pkg/deb/ota-plus-client-0.1.0/debian/copyright deleted file mode 100644 index e69de29..0000000 diff --git a/pkg/deb/ota-plus-client-0.1.0/debian/ota-plus-client.dirs b/pkg/deb/ota-plus-client-0.1.0/debian/ota-plus-client.dirs deleted file mode 100644 index f7048c8..0000000 --- a/pkg/deb/ota-plus-client-0.1.0/debian/ota-plus-client.dirs +++ /dev/null @@ -1,2 +0,0 @@ -opt/ats/ota/bin -opt/ats/ota/etc diff --git a/pkg/deb/ota-plus-client-0.1.0/debian/ota-plus-client.install b/pkg/deb/ota-plus-client-0.1.0/debian/ota-plus-client.install deleted file mode 100644 index 1fe11f2..0000000 --- a/pkg/deb/ota-plus-client-0.1.0/debian/ota-plus-client.install +++ /dev/null @@ -1,2 +0,0 @@ -bin/* opt/ats/ota/bin -etc/* opt/ats/ota/etc diff --git a/pkg/deb/ota-plus-client-0.1.0/debian/ota-plus-client.service b/pkg/deb/ota-plus-client-0.1.0/debian/ota-plus-client.service deleted file mode 100644 index 22cda1d..0000000 --- a/pkg/deb/ota-plus-client-0.1.0/debian/ota-plus-client.service +++ /dev/null @@ -1,10 +0,0 @@ -[Unit] -Description=OTA+ Client -After=network.target - -[Service] -Type=simple -ExecStart=/opt/ats/ota/bin/ota_plus_client --config /opt/ats/ota/etc/ota.toml - -[Install] -WantedBy=multi-user.target diff --git a/pkg/deb/ota-plus-client-0.1.0/debian/rules b/pkg/deb/ota-plus-client-0.1.0/debian/rules deleted file mode 100644 index 93646c7..0000000 --- a/pkg/deb/ota-plus-client-0.1.0/debian/rules +++ /dev/null @@ -1,3 +0,0 @@ -#!/usr/bin/make -f -%: - dh $@ --with=systemd diff --git a/pkg/deb/ota-plus-client-0.1.0/debian/source/format b/pkg/deb/ota-plus-client-0.1.0/debian/source/format deleted file mode 100644 index 163aaf8..0000000 --- a/pkg/deb/ota-plus-client-0.1.0/debian/source/format +++ /dev/null @@ -1 +0,0 @@ -3.0 (quilt) diff --git a/pkg/ota-client.service b/pkg/ota-client.service new file mode 100644 index 0000000..9c2dafd --- /dev/null +++ b/pkg/ota-client.service @@ -0,0 +1,10 @@ +[Unit] +Description=OTA+ Client +After=network.target + +[Service] +Type=simple +ExecStart=/opt/ats/ota_plus_client --config /opt/ats/ota.toml + +[Install] +WantedBy=multi-user.target diff --git a/pkg/pkg.sh b/pkg/pkg.sh index c2665a6..158c450 100755 --- a/pkg/pkg.sh +++ b/pkg/pkg.sh @@ -3,7 +3,7 @@ set -eo pipefail PKG_NAME="ota-plus-client" -PKG_VER="0.1.0" +PKG_VER=$VERSION PKG_DIR="${PKG_NAME}-${PKG_VER}" PKG_TARBALL="${PKG_NAME}_${PKG_VER}" PREFIX=/opt/ats @@ -31,20 +31,15 @@ function envsub { function make_deb { export PACKAGE_MANAGER="dpkg" - workdir="${TMPDIR:-/tmp}/pkg-ota-plus-client-$$" - cp -pr $PKG_SRC_DIR/deb $workdir - cd $workdir + cd $PKG_SRC_DIR + envsub ota.toml.template > $PKG_NAME.toml - mkdir -p $PKG_DIR/bin - mkdir -p $PKG_DIR/etc - envsub ${PKG_SRC_DIR}/ota.toml.template > $PKG_DIR/etc/ota.toml - tar czf $PKG_TARBALL $PKG_DIR/bin $PKG_DIR/etc + fpm -s dir -t deb -n ${PKG_NAME} -v ${PKG_VER} --prefix ${PREFIX} -a native \ + --deb-systemd $PKG_SRC_DIR/ota-client.service \ + $PKG_SRC_DIR/ota_plus_client=ota_plus_client $PKG_NAME.toml=ota.toml - cd $PKG_DIR - debuild -i -us -uc -b - mv -n ../ota-plus-client*.deb $dest - cd $PKG_SRC_DIR - rm -rf $workdir + mv -n ota-plus-client*.deb $dest + rm $PKG_NAME.toml } function make_rpm { @@ -54,8 +49,8 @@ function make_rpm { envsub ota.toml.template > $PKG_NAME.toml fpm -s dir -t rpm -n ${PKG_NAME} -v ${PKG_VER} --prefix ${PREFIX} -a native \ - --rpm-service $PKG_SRC_DIR/rpm/ota-client.service \ - $PKG_SRC_DIR/rpm/ota_plus_client=ota_plus_client $PKG_NAME.toml=ota.toml + --rpm-service $PKG_SRC_DIR/ota-client.service \ + $PKG_SRC_DIR/ota_plus_client=ota_plus_client $PKG_NAME.toml=ota.toml mv -n ota-plus-client*.rpm $dest rm $PKG_NAME.toml diff --git a/pkg/rpm/ota-client.service b/pkg/rpm/ota-client.service deleted file mode 100644 index 9c2dafd..0000000 --- a/pkg/rpm/ota-client.service +++ /dev/null @@ -1,10 +0,0 @@ -[Unit] -Description=OTA+ Client -After=network.target - -[Service] -Type=simple -ExecStart=/opt/ats/ota_plus_client --config /opt/ats/ota.toml - -[Install] -WantedBy=multi-user.target -- cgit v1.2.1 From 9a6ac4c5768b27f85a2f6f7f09ba13657ed7dd22 Mon Sep 17 00:00:00 2001 From: Shaun Taheri Date: Wed, 13 Apr 2016 18:20:06 +0200 Subject: Update README with VERSION environment variable info. --- README.md | 6 +++--- pkg/pkg.sh | 2 ++ 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 2008c25..f83ab01 100644 --- a/README.md +++ b/README.md @@ -16,14 +16,14 @@ To build and test the project simply issue: ## Packaging instructions -A Dockerfile has been set up with the correct libraries for building a statically linked binary. This can be built from the project root with `docker build -t client-packager pkg`. +A Dockerfile has been set up with the correct libraries for building a statically linked binary. This can be built from the project root with `docker build -t advancedtelematic/client-packager pkg`. ### DEB -A `.deb` package can be built with `docker run -v $PWD:/build client-packager make deb` (or simply `make deb` with the correct build packages installed). +A `.deb` package can be built with `docker run -e VERSION=0.0.0 -v $PWD:/build advancedtelematic/client-packager make deb` (or simply `make deb` with the correct build packages installed). Remember to set the `VERSION` environment variable to the correct version. ### RPM [FPM](https://github.com/jordansissel/fpm) is used to create RPM packages. -A `.rpm` package can be built with `docker run -v $PWD:/build client-packager make rpm` (or simply `make rpm` with FPM and the build packages installed). +A `.rpm` package can be built with `docker run -e VERSION=0.0.0 -v $PWD:/build advancedtelematic/client-packager make rpm` (or simply `make rpm` with FPM and the build packages installed). Remember to set the `VERSION` environment variable to the correct version. diff --git a/pkg/pkg.sh b/pkg/pkg.sh index 158c450..a38106f 100755 --- a/pkg/pkg.sh +++ b/pkg/pkg.sh @@ -62,6 +62,8 @@ if [ $# -lt 2 ]; then exit 1 fi +: ${VERSION?"Environment variable VERSION must be set."} + package="${1}" dest="${2}" -- cgit v1.2.1 From c47d7e0027852a0342344774a2c63681146b762e Mon Sep 17 00:00:00 2001 From: Shaun Taheri Date: Wed, 13 Apr 2016 18:25:55 +0200 Subject: Update binary path in gitignore --- .gitignore | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index b9b7826..cc1ea66 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,3 @@ target Cargo.lock -pkg/deb/ota-plus-client-0.1.0/bin +pkg/ota_plus_client -- cgit v1.2.1 From 4e9463ac9ef869cfd8299b71b13a297de5848a4e Mon Sep 17 00:00:00 2001 From: Stevan Andjelkovic Date: Thu, 14 Apr 2016 11:24:26 +0200 Subject: Add explicit version to cargo.toml file. --- Cargo.toml | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 7bc4eeb..954c5f9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,13 +13,13 @@ path = "src/main.rs" doc = false [dependencies] -chan-signal = "*" -chan= "*" -env_logger = "*" -getopts = "*" -hyper = "*" -log = "*" -rustc-serialize = "*" -tempfile = "*" -toml = "*" -ws = "*" +chan-signal = "0.1.5" +chan= "0.1.18" +env_logger = "0.3.3" +getopts = "0.2.14" +hyper = "0.8.1" +log = "0.3.5" +rustc-serialize = "0.3.18" +tempfile = "2.1.2" +toml = "0.1.28" +ws = "0.4.6" \ No newline at end of file -- cgit v1.2.1 From 886ff9a666155b1083faf59500aa08adbe2a2103 Mon Sep 17 00:00:00 2001 From: Txus Date: Thu, 14 Apr 2016 14:15:28 +0200 Subject: Make VIN part of the credentials --- ota.toml | 2 +- src/datatype/config.rs | 15 ++++++++++----- src/interpreter.rs | 8 ++++---- src/main.rs | 12 ++++++------ src/ota_plus.rs | 32 ++++++++++++++++---------------- tests/ota_plus_client_tests.rs | 2 +- 6 files changed, 38 insertions(+), 33 deletions(-) diff --git a/ota.toml b/ota.toml index 18bbd7b..186d351 100644 --- a/ota.toml +++ b/ota.toml @@ -3,10 +3,10 @@ server = "http://127.0.0.1:9000" client_id = "client-id" secret = "secret" credentials_file = "/tmp/ats_credentials.toml" +vin = "V1234567890123456" [ota] server = "http://127.0.0.1:8080" -vin = "V1234567890123456" polling_interval = 10 packages_dir = "/tmp/" package_manager = "dpkg" diff --git a/src/datatype/config.rs b/src/datatype/config.rs index 6700442..67d4959 100644 --- a/src/datatype/config.rs +++ b/src/datatype/config.rs @@ -25,13 +25,15 @@ pub struct AuthConfig { pub server: Url, pub client_id: String, pub secret: String, + pub vin: String, } impl AuthConfig { fn new(server: Url, creds: CredentialsFile) -> AuthConfig { AuthConfig { server: server, client_id: creds.client_id, - secret: creds.secret } + secret: creds.secret, + vin: creds.vin } } } @@ -41,18 +43,19 @@ struct AuthConfigSection { pub client_id: String, pub secret: String, pub credentials_file: String, + pub vin: String, } #[derive(RustcEncodable, RustcDecodable, PartialEq, Eq, Debug, Clone)] struct CredentialsFile { pub client_id: String, pub secret: String, + pub vin: String, } #[derive(RustcDecodable, PartialEq, Eq, Debug, Clone)] pub struct OtaConfig { pub server: Url, - pub vin: String, pub polling_interval: u64, pub packages_dir: String, pub package_manager: PackageManager, @@ -69,6 +72,7 @@ impl Default for AuthConfig { server: Url::parse("http://127.0.0.1:9000").unwrap(), client_id: "client-id".to_string(), secret: "secret".to_string(), + vin: "V1234567890123456".to_string(), } } } @@ -80,6 +84,7 @@ impl Default for AuthConfigSection { client_id: "client-id".to_string(), secret: "secret".to_string(), credentials_file: "/tmp/ats_credentials.toml".to_string(), + vin: "V1234567890123456".to_string(), } } } @@ -88,7 +93,6 @@ impl Default for OtaConfig { fn default() -> OtaConfig { OtaConfig { server: Url::parse("http://127.0.0.1:8080").unwrap(), - vin: "V1234567890123456".to_string(), polling_interval: 10, packages_dir: "/tmp/".to_string(), package_manager: PackageManager::Dpkg, @@ -142,7 +146,8 @@ fn bootstrap_credentials(auth_cfg_section: AuthConfigSection) -> Result { let creds = CredentialsFile { client_id: auth_cfg_section.client_id, - secret: auth_cfg_section.secret }; + secret: auth_cfg_section.secret, + vin: auth_cfg_section.vin }; try!(persist_credentials_file(&creds, &creds_path)); Ok(AuthConfig::new(auth_cfg_section.server, creds)) } @@ -195,10 +200,10 @@ mod tests { client_id = "client-id" secret = "secret" credentials_file = "/tmp/ats_credentials.toml" + vin = "V1234567890123456" [ota] server = "http://127.0.0.1:8080" - vin = "V1234567890123456" polling_interval = 10 packages_dir = "/tmp/" package_manager = "dpkg" diff --git a/src/interpreter.rs b/src/interpreter.rs index a7d86ce..591d1cd 100644 --- a/src/interpreter.rs +++ b/src/interpreter.rs @@ -49,7 +49,7 @@ impl<'a, C: HttpClient> Interpreter<'a, C> { fn get_pending_updates(&self) { debug!("Fetching package updates..."); - let response: Event = match get_package_updates::(&self.token, &self.config.ota) { + let response: Event = match get_package_updates::(&self.token, &self.config) { Ok(updates) => { let update_events: Vec = updates.iter().map(move |id| Event::NewUpdateAvailable(id.clone())).collect(); info!("New package updates available: {:?}", update_events); @@ -65,7 +65,7 @@ impl<'a, C: HttpClient> Interpreter<'a, C> { fn post_installed_packages(&self) { let _ = self.get_installed_packages().and_then(|pkgs| { debug!("Found installed packages in the system: {:?}", pkgs); - post_packages::(&self.token, &self.config.ota, &pkgs) + post_packages::(&self.token, &self.config, &pkgs) }).map(|_| { info!("Posted installed packages to the server."); }).map_err(|e| { @@ -76,7 +76,7 @@ impl<'a, C: HttpClient> Interpreter<'a, C> { fn accept_update(&self, id: &UpdateRequestId) { info!("Accepting update {}...", id); self.publish(Event::UpdateStateChanged(id.clone(), UpdateState::Downloading)); - let report = download_package_update::(&self.token, &self.config.ota, id) + let report = download_package_update::(&self.token, &self.config, id) .and_then(|path| { info!("Downloaded at {:?}. Installing...", path); self.publish(Event::UpdateStateChanged(id.clone(), UpdateState::Installing)); @@ -96,7 +96,7 @@ impl<'a, C: HttpClient> Interpreter<'a, C> { format!("Download failed: {:?}", e)) }); - match send_install_report::(&self.token, &self.config.ota, &report) { + match send_install_report::(&self.token, &self.config, &report) { Ok(_) => info!("Update finished. Report sent: {:?}", report), Err(e) => error!("Error reporting back to the server: {:?}", e) } diff --git a/src/main.rs b/src/main.rs index 49f6cd0..d6c8a66 100644 --- a/src/main.rs +++ b/src/main.rs @@ -163,10 +163,10 @@ fn build_config() -> Config { "change auth client id", "ID"); opts.optopt("", "auth-secret", "change auth secret", "SECRET"); + opts.optopt("", "auth-vin", + "change auth vin", "VIN"); opts.optopt("", "ota-server", "change ota server URL", "URL"); - opts.optopt("", "ota-vin", - "change ota vin", "VIN"); opts.optopt("", "ota-packages-dir", "change downloaded directory for packages", "PATH"); opts.optopt("", "ota-package-manager", @@ -207,6 +207,10 @@ fn build_config() -> Config { config.auth.secret = secret; } + if let Some(vin) = matches.opt_str("auth-vin") { + config.auth.vin = vin; + } + if let Some(s) = matches.opt_str("ota-server") { match Url::parse(&s) { Ok(url) => config.ota.server = url, @@ -214,10 +218,6 @@ fn build_config() -> Config { } } - if let Some(vin) = matches.opt_str("ota-vin") { - config.ota.vin = vin; - } - if let Some(path) = matches.opt_str("ota-packages-dir") { config.ota.packages_dir = path; } diff --git a/src/ota_plus.rs b/src/ota_plus.rs index 083c0cf..4ea92b3 100644 --- a/src/ota_plus.rs +++ b/src/ota_plus.rs @@ -7,7 +7,7 @@ use std::path::PathBuf; use std::result::Result; use datatype::AccessToken; -use datatype::OtaConfig; +use datatype::Config; use datatype::Error; use datatype::error::OtaReason::{CreateFile, Client}; use datatype::Package; @@ -16,21 +16,21 @@ use datatype::{UpdateReport, UpdateReportWithVin}; use http_client::{HttpClient, HttpRequest}; -fn vehicle_endpoint(config: &OtaConfig, s: &str) -> Url { - config.server.join(&format!("/api/v1/vehicles/{}{}", config.vin, s)).unwrap() +fn vehicle_endpoint(config: &Config, s: &str) -> Url { + config.ota.server.join(&format!("/api/v1/vehicles/{}{}", config.auth.vin, s)).unwrap() } pub fn download_package_update(token: &AccessToken, - config: &OtaConfig, + config: &Config, id: &UpdateRequestId) -> Result { let req = HttpRequest::get(vehicle_endpoint(config, &format!("/updates/{}/download", id))) .with_header(Authorization(Bearer { token: token.access_token.clone() })); let mut path = PathBuf::new(); - path.push(&config.packages_dir); + path.push(&config.ota.packages_dir); path.push(id); - path.set_extension(config.package_manager.extension()); + path.set_extension(config.ota.package_manager.extension()); let file = try!(File::create(path.as_path()) .map_err(|e| Error::Ota(CreateFile(path.clone(), e)))); @@ -42,10 +42,10 @@ pub fn download_package_update(token: &AccessToken, } pub fn send_install_report(token: &AccessToken, - config: &OtaConfig, + config: &Config, report: &UpdateReport) -> Result<(), Error> { - let report_with_vin = UpdateReportWithVin::new(&config.vin, &report); + let report_with_vin = UpdateReportWithVin::new(&config.auth.vin, &report); let json = try!(json::encode(&report_with_vin) .map_err(|_| Error::ParseError(String::from("JSON encoding error")))); @@ -63,7 +63,7 @@ pub fn send_install_report(token: &AccessToken, } pub fn get_package_updates(token: &AccessToken, - config: &OtaConfig) -> Result, Error> { + config: &Config) -> Result, Error> { let req = HttpRequest::get(vehicle_endpoint(&config, "/updates")) .with_header(Authorization(Bearer { token: token.access_token.clone() })); @@ -76,7 +76,7 @@ pub fn get_package_updates(token: &AccessToken, } pub fn post_packages(token: &AccessToken, - config: &OtaConfig, + config: &Config, pkgs: &Vec) -> Result<(), Error> { let json = try!(json::encode(&pkgs) @@ -102,7 +102,7 @@ mod tests { use super::*; use datatype::AccessToken; - use datatype::OtaConfig; + use datatype::{Config, OtaConfig}; use datatype::Error; use datatype::Package; use http_client::BadHttpClient; @@ -146,21 +146,21 @@ mod tests { #[test] fn test_post_packages_sends_authentication() { assert_eq!( - post_packages::(&test_token(), &OtaConfig::default(), &vec![test_package()]) + post_packages::(&test_token(), &Config::default(), &vec![test_package()]) .unwrap(), ()) } #[test] fn test_get_package_updates() { - assert_eq!(get_package_updates::(&test_token(), &OtaConfig::default()).unwrap(), + assert_eq!(get_package_updates::(&test_token(), &Config::default()).unwrap(), vec!["pkgid".to_string()]) } #[test] #[ignore] // TODO: docker daemon requires user namespaces for this to work fn bad_packages_dir_download_package_update() { - let mut config = OtaConfig::default(); - config = OtaConfig { packages_dir: "/".to_string(), .. config }; + let mut config = Config::default(); + config.ota = OtaConfig { packages_dir: "/".to_string(), .. config.ota }; assert_eq!( format!("{}", @@ -174,7 +174,7 @@ mod tests { assert_eq!( format!("{}", download_package_update:: - (&test_token(), &OtaConfig::default(), &"0".to_string()) + (&test_token(), &Config::default(), &"0".to_string()) .unwrap_err()), r#"Ota error, the request: GET http://127.0.0.1:8080/api/v1/vehicles/V1234567890123456/updates/0/download, results in the following error: bad client."#) diff --git a/tests/ota_plus_client_tests.rs b/tests/ota_plus_client_tests.rs index 02ca55b..98e6de5 100644 --- a/tests/ota_plus_client_tests.rs +++ b/tests/ota_plus_client_tests.rs @@ -49,9 +49,9 @@ Options: change auth client id --auth-secret SECRET change auth secret + --auth-vin VIN change auth vin --ota-server URL change ota server URL - --ota-vin VIN change ota vin --ota-packages-dir PATH change downloaded directory for packages --ota-package-manager MANAGER -- cgit v1.2.1 From 52982e3cdd3526456c626cdaeeb5cf55c7530471 Mon Sep 17 00:00:00 2001 From: Txus Date: Thu, 14 Apr 2016 15:36:08 +0200 Subject: Version Cargo.lock --- .gitignore | 1 - Cargo.lock | 525 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 525 insertions(+), 1 deletion(-) create mode 100644 Cargo.lock diff --git a/.gitignore b/.gitignore index cc1ea66..45c9e69 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,2 @@ target -Cargo.lock pkg/ota_plus_client diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..dc21a02 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,525 @@ +[root] +name = "ota-plus-client" +version = "0.1.0" +dependencies = [ + "chan 0.1.18 (registry+https://github.com/rust-lang/crates.io-index)", + "chan-signal 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", + "env_logger 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", + "getopts 0.2.14 (registry+https://github.com/rust-lang/crates.io-index)", + "hyper 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)", + "log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", + "rustc-serialize 0.3.19 (registry+https://github.com/rust-lang/crates.io-index)", + "tempfile 2.1.2 (registry+https://github.com/rust-lang/crates.io-index)", + "toml 0.1.28 (registry+https://github.com/rust-lang/crates.io-index)", + "ws 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "aho-corasick" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "memchr 0.1.11 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "bit-set" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "bit-vec 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "bit-vec" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "bitflags" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "bitflags" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "byteorder" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "bytes" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "cfg-if" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "chan" +version = "0.1.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "rand 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "chan-signal" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "bit-set 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", + "chan 0.1.18 (registry+https://github.com/rust-lang/crates.io-index)", + "lazy_static 0.1.16 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.10 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "cookie" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "openssl 0.7.9 (registry+https://github.com/rust-lang/crates.io-index)", + "rustc-serialize 0.3.19 (registry+https://github.com/rust-lang/crates.io-index)", + "time 0.1.35 (registry+https://github.com/rust-lang/crates.io-index)", + "url 0.5.9 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "env_logger" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", + "regex 0.1.65 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "gcc" +version = "0.3.26" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "gdi32-sys" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "winapi 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "getopts" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "hpack" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "httparse" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "hyper" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "cookie 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", + "httparse 1.1.2 (registry+https://github.com/rust-lang/crates.io-index)", + "language-tags 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", + "log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", + "mime 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", + "num_cpus 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", + "openssl 0.7.9 (registry+https://github.com/rust-lang/crates.io-index)", + "rustc-serialize 0.3.19 (registry+https://github.com/rust-lang/crates.io-index)", + "solicit 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)", + "time 0.1.35 (registry+https://github.com/rust-lang/crates.io-index)", + "traitobject 0.0.1 (registry+https://github.com/rust-lang/crates.io-index)", + "typeable 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", + "unicase 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", + "url 0.5.9 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "kernel32-sys" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "winapi 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "language-tags" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "lazy_static" +version = "0.1.16" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "libc" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "libc" +version = "0.2.10" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "libressl-pnacl-sys" +version = "2.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "pnacl-build-helper 1.4.10 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "log" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "matches" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "memchr" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "libc 0.2.10 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "mempool" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "mime" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "mio" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "bytes 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)", + "log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", + "miow 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", + "net2 0.2.23 (registry+https://github.com/rust-lang/crates.io-index)", + "nix 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", + "slab 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", + "time 0.1.35 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "miow" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "kernel32-sys 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", + "net2 0.2.23 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)", + "ws2_32-sys 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "net2" +version = "0.2.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "cfg-if 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "kernel32-sys 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.10 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)", + "ws2_32-sys 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "nix" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "bitflags 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "num_cpus" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "libc 0.2.10 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "openssl" +version = "0.7.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "bitflags 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", + "gcc 0.3.26 (registry+https://github.com/rust-lang/crates.io-index)", + "lazy_static 0.1.16 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.10 (registry+https://github.com/rust-lang/crates.io-index)", + "openssl-sys 0.7.9 (registry+https://github.com/rust-lang/crates.io-index)", + "openssl-sys-extras 0.7.9 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "openssl-sys" +version = "0.7.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "gdi32-sys 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.10 (registry+https://github.com/rust-lang/crates.io-index)", + "libressl-pnacl-sys 2.1.6 (registry+https://github.com/rust-lang/crates.io-index)", + "pkg-config 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", + "user32-sys 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "openssl-sys-extras" +version = "0.7.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "gcc 0.3.26 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.10 (registry+https://github.com/rust-lang/crates.io-index)", + "openssl-sys 0.7.9 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "pkg-config" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "pnacl-build-helper" +version = "1.4.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "tempdir 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "rand" +version = "0.3.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "libc 0.2.10 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "regex" +version = "0.1.65" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "aho-corasick 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", + "memchr 0.1.11 (registry+https://github.com/rust-lang/crates.io-index)", + "mempool 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", + "regex-syntax 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", + "utf8-ranges 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "regex-syntax" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "rustc-serialize" +version = "0.3.19" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "rustc_version" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "semver 0.1.20 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "semver" +version = "0.1.20" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "sha1" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "byteorder 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "slab" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "solicit" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "hpack 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", + "log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "tempdir" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "rand 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "tempfile" +version = "2.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "kernel32-sys 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.10 (registry+https://github.com/rust-lang/crates.io-index)", + "rand 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)", + "rustc_version 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "time" +version = "0.1.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "kernel32-sys 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.10 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "toml" +version = "0.1.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "rustc-serialize 0.3.19 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "traitobject" +version = "0.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "typeable" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "unicase" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "rustc_version 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "unicode-bidi" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "matches 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "unicode-normalization" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "url" +version = "0.5.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "matches 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", + "rustc-serialize 0.3.19 (registry+https://github.com/rust-lang/crates.io-index)", + "unicode-bidi 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", + "unicode-normalization 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", + "uuid 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "user32-sys" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "winapi 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "utf8-ranges" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "uuid" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "rand 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "winapi" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "winapi-build" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "ws" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "httparse 1.1.2 (registry+https://github.com/rust-lang/crates.io-index)", + "log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", + "mio 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", + "rand 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)", + "sha1 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", + "url 0.5.9 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "ws2_32-sys" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "winapi 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + -- cgit v1.2.1 From e3d7a051b3128a2f12c71b9ca2274d129925e742 Mon Sep 17 00:00:00 2001 From: Txus Date: Mon, 18 Apr 2016 14:27:39 +0200 Subject: Add tailoring script --- pkg/tailor.sh | 52 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 52 insertions(+) create mode 100755 pkg/tailor.sh diff --git a/pkg/tailor.sh b/pkg/tailor.sh new file mode 100755 index 0000000..d1b5d7e --- /dev/null +++ b/pkg/tailor.sh @@ -0,0 +1,52 @@ +#!/bin/bash + +set -eo pipefail + +PKG_NAME="ota-plus-client_latest" +WORKING_DIR="/tmp/ota_plus_client_extract_$$" + +cd $(dirname $0) +PKG_SRC_DIR=$(pwd) + +function tailor_rpm { + echo "Unimplemented!" + exit -1 +} + +function tailor_deb { + rm -fr $WORKING_DIR + mkdir -p $WORKING_DIR/DEBIAN + dpkg-deb -x $PKGS_DIR/$PKG_NAME.deb $WORKING_DIR/ + dpkg-deb -e $PKGS_DIR/$PKG_NAME.deb $WORKING_DIR/DEBIAN + + sed -i "s/^client_id = .*$/client_id = \"$OTA_AUTH_CLIENT_ID\"/" $WORKING_DIR/opt/ats/ota.toml + sed -i "s/^secret = .*$/secret = \"$OTA_AUTH_SECRET\"/" $WORKING_DIR/opt/ats/ota.toml + sed -i "s/^vin = .*$/vin = \"$OTA_CLIENT_VIN\"/" $WORKING_DIR/opt/ats/ota.toml + + mkdir -p $dest + dpkg-deb -b $WORKING_DIR/ $dest + rm -fr $WORKING_DIR +} + + +if [ $# -lt 2 ]; then + echo "Usage: $0 " + echo "packages: deb rpm" + exit 1 +fi + +package="${1}" +dest="${2}" + +echo "Tailoring package and copying to '$dest'" +case $package in + "deb" ) + tailor_deb + ;; + "rpm" ) + tailor_rpm + ;; + *) + echo "unknown package $package" + exit 2 +esac -- cgit v1.2.1 From bfb2e73e2e3e7bfb2a8b20807c3f698220050d3a Mon Sep 17 00:00:00 2001 From: Alex Humphreys Date: Mon, 18 Apr 2016 14:47:47 +0200 Subject: Add vin to auth in ota.toml.template --- pkg/ota.toml.template | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/ota.toml.template b/pkg/ota.toml.template index a520b24..10286cb 100644 --- a/pkg/ota.toml.template +++ b/pkg/ota.toml.template @@ -3,10 +3,10 @@ server = "${OTA_AUTH_URL}" client_id = "${OTA_AUTH_CLIENT_ID}" secret = "${OTA_AUTH_SECRET}" credentials_file = "/opt/ats/credentials.toml" +vin = "${OTA_CLIENT_VIN}" [ota] server = "${OTA_SERVER_URL}" -vin = "${OTA_CLIENT_VIN}" polling_interval = 10 packages_dir = "/tmp/" package_manager = "${PACKAGE_MANAGER}" -- cgit v1.2.1 From f22991ea8aace9e31f77c11f56d96faf8c4b5e81 Mon Sep 17 00:00:00 2001 From: Txus Date: Mon, 18 Apr 2016 15:26:46 +0200 Subject: Fix script --- pkg/tailor.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/tailor.sh b/pkg/tailor.sh index d1b5d7e..fa9d4a5 100755 --- a/pkg/tailor.sh +++ b/pkg/tailor.sh @@ -23,7 +23,7 @@ function tailor_deb { sed -i "s/^secret = .*$/secret = \"$OTA_AUTH_SECRET\"/" $WORKING_DIR/opt/ats/ota.toml sed -i "s/^vin = .*$/vin = \"$OTA_CLIENT_VIN\"/" $WORKING_DIR/opt/ats/ota.toml - mkdir -p $dest + mkdir -p $(dirname $dest) dpkg-deb -b $WORKING_DIR/ $dest rm -fr $WORKING_DIR } -- cgit v1.2.1 From f351f0e9df53138dabe446953d38b9bdc7fd1911 Mon Sep 17 00:00:00 2001 From: Txus Date: Mon, 18 Apr 2016 15:49:29 +0200 Subject: Fix wrong template --- pkg/ota.toml.template | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/ota.toml.template b/pkg/ota.toml.template index a520b24..10286cb 100644 --- a/pkg/ota.toml.template +++ b/pkg/ota.toml.template @@ -3,10 +3,10 @@ server = "${OTA_AUTH_URL}" client_id = "${OTA_AUTH_CLIENT_ID}" secret = "${OTA_AUTH_SECRET}" credentials_file = "/opt/ats/credentials.toml" +vin = "${OTA_CLIENT_VIN}" [ota] server = "${OTA_SERVER_URL}" -vin = "${OTA_CLIENT_VIN}" polling_interval = 10 packages_dir = "/tmp/" package_manager = "${PACKAGE_MANAGER}" -- cgit v1.2.1 From e45764c198d7c1bcf463b17e4423a17e3394a12b Mon Sep 17 00:00:00 2001 From: Txus Date: Mon, 18 Apr 2016 16:39:12 +0200 Subject: Add some logging --- pkg/tailor.sh | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/pkg/tailor.sh b/pkg/tailor.sh index fa9d4a5..03faa9b 100755 --- a/pkg/tailor.sh +++ b/pkg/tailor.sh @@ -16,6 +16,7 @@ function tailor_rpm { function tailor_deb { rm -fr $WORKING_DIR mkdir -p $WORKING_DIR/DEBIAN + echo "Extracting Debian package $PKGS_DIR/$PKG_NAME.deb to $WORKING_DIR" dpkg-deb -x $PKGS_DIR/$PKG_NAME.deb $WORKING_DIR/ dpkg-deb -e $PKGS_DIR/$PKG_NAME.deb $WORKING_DIR/DEBIAN @@ -24,6 +25,7 @@ function tailor_deb { sed -i "s/^vin = .*$/vin = \"$OTA_CLIENT_VIN\"/" $WORKING_DIR/opt/ats/ota.toml mkdir -p $(dirname $dest) + echo "Re-packaging contents of $WORKING_DIR/ to $dest" dpkg-deb -b $WORKING_DIR/ $dest rm -fr $WORKING_DIR } @@ -38,7 +40,7 @@ fi package="${1}" dest="${2}" -echo "Tailoring package and copying to '$dest'" +echo "Tailoring $package package and outputting as '$dest'" case $package in "deb" ) tailor_deb -- cgit v1.2.1 From afe0672a698e5eea65b84d2f1a38eb54f3a16baa Mon Sep 17 00:00:00 2001 From: Alex Humphreys Date: Mon, 18 Apr 2016 16:54:13 +0200 Subject: Fix empty rpm error result_text --- src/package_manager/rpm.rs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/package_manager/rpm.rs b/src/package_manager/rpm.rs index 24108f8..3f60802 100644 --- a/src/package_manager/rpm.rs +++ b/src/package_manager/rpm.rs @@ -28,13 +28,14 @@ pub fn install_package(path: &str) -> Result<(UpdateResultCode, String), (Update })); let stdout = String::from_utf8_lossy(&output.stdout).into_owned(); + let stderr = String::from_utf8_lossy(&output.stderr).into_owned(); match output.status.code() { Some(0) => Ok((UpdateResultCode::OK, stdout)), - _ => if (&stdout).contains("already installed") { - Ok((UpdateResultCode::ALREADY_PROCESSED, stdout)) + _ => if (&stderr).contains("already installed") { + Ok((UpdateResultCode::ALREADY_PROCESSED, stderr)) } else { - Err((UpdateResultCode::INSTALL_FAILED, stdout)) + Err((UpdateResultCode::INSTALL_FAILED, stderr)) } } } -- cgit v1.2.1 From 8e15add54e0f12bad287b5ddb47fb048937524ae Mon Sep 17 00:00:00 2001 From: Txus Date: Mon, 18 Apr 2016 17:33:45 +0200 Subject: Improve service files for systemd --- pkg/ota-client.service | 11 ++++++++--- pkg/yocto/ota-plus-client.service | 2 ++ 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/pkg/ota-client.service b/pkg/ota-client.service index 9c2dafd..2d8555d 100644 --- a/pkg/ota-client.service +++ b/pkg/ota-client.service @@ -1,10 +1,15 @@ [Unit] Description=OTA+ Client -After=network.target +Wants=network-online.target +After=network.target network-online.target +Requires=network-online.target [Service] -Type=simple +RestartSec=5 +Restart=on-failure +Environment="RUST_LOG=info" +DefaultTimeoutStopSec=5 ExecStart=/opt/ats/ota_plus_client --config /opt/ats/ota.toml [Install] -WantedBy=multi-user.target +WantedBy=multi-user.target \ No newline at end of file diff --git a/pkg/yocto/ota-plus-client.service b/pkg/yocto/ota-plus-client.service index 5cd80a5..8283707 100644 --- a/pkg/yocto/ota-plus-client.service +++ b/pkg/yocto/ota-plus-client.service @@ -7,6 +7,8 @@ Requires=network-online.target [Service] RestartSec=5 Restart=on-failure +Environment="RUST_LOG=info" +DefaultTimeoutStopSec=5 ExecStart=/usr/bin/ota_plus_client --config /etc/ota.toml [Install] -- cgit v1.2.1 From 3eb13090118807ad29b4f33e0f329dfa6b77827a Mon Sep 17 00:00:00 2001 From: Txus Date: Mon, 18 Apr 2016 17:56:50 +0200 Subject: Tailor RPMs too --- pkg/tailor.sh | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/pkg/tailor.sh b/pkg/tailor.sh index 03faa9b..461c88d 100755 --- a/pkg/tailor.sh +++ b/pkg/tailor.sh @@ -8,9 +8,10 @@ WORKING_DIR="/tmp/ota_plus_client_extract_$$" cd $(dirname $0) PKG_SRC_DIR=$(pwd) -function tailor_rpm { - echo "Unimplemented!" - exit -1 +function convert_to_rpm { + mv $dest $dest.deb + alien -c -k -r --fix-perms $dest.deb + mv ota-plus*.rpm $dest } function tailor_deb { @@ -46,7 +47,8 @@ case $package in tailor_deb ;; "rpm" ) - tailor_rpm + tailor_deb + convert_to_rpm ;; *) echo "unknown package $package" -- cgit v1.2.1 From f6292ec0898f0ffc64971f472a7b4ede92b74b90 Mon Sep 17 00:00:00 2001 From: Txus Date: Mon, 18 Apr 2016 18:03:27 +0200 Subject: RPMs need a config tweak --- pkg/tailor.sh | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/pkg/tailor.sh b/pkg/tailor.sh index 461c88d..224083c 100755 --- a/pkg/tailor.sh +++ b/pkg/tailor.sh @@ -21,9 +21,16 @@ function tailor_deb { dpkg-deb -x $PKGS_DIR/$PKG_NAME.deb $WORKING_DIR/ dpkg-deb -e $PKGS_DIR/$PKG_NAME.deb $WORKING_DIR/DEBIAN + if [[ $package == "deb" ]]; then + pkgmanager="dpkg" + else + pkgmanager="rpm" + fi + sed -i "s/^client_id = .*$/client_id = \"$OTA_AUTH_CLIENT_ID\"/" $WORKING_DIR/opt/ats/ota.toml sed -i "s/^secret = .*$/secret = \"$OTA_AUTH_SECRET\"/" $WORKING_DIR/opt/ats/ota.toml sed -i "s/^vin = .*$/vin = \"$OTA_CLIENT_VIN\"/" $WORKING_DIR/opt/ats/ota.toml + sed -i "s/^package_manager = .*$/package_manager = \"$pkgmanager\"/" $WORKING_DIR/opt/ats/ota.toml mkdir -p $(dirname $dest) echo "Re-packaging contents of $WORKING_DIR/ to $dest" -- cgit v1.2.1 From e172ec27b40b8dcdcfcbbd230b8ea5160483d1ef Mon Sep 17 00:00:00 2001 From: Shaun Taheri Date: Tue, 19 Apr 2016 12:03:17 +0200 Subject: Follow HTTP redirects --- Cargo.lock | 10 +++++++ Cargo.toml | 3 ++- src/http_client/interface.rs | 5 ++-- src/http_client/mock_http_client.rs | 52 +++++++++++++++++++++++++++++++++++++ src/http_client/mod.rs | 2 ++ src/lib.rs | 1 + src/ota_plus.rs | 29 +++------------------ 7 files changed, 74 insertions(+), 28 deletions(-) create mode 100644 src/http_client/mock_http_client.rs diff --git a/Cargo.lock b/Cargo.lock index dc21a02..bd265ca 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -12,6 +12,7 @@ dependencies = [ "tempfile 2.1.2 (registry+https://github.com/rust-lang/crates.io-index)", "toml 0.1.28 (registry+https://github.com/rust-lang/crates.io-index)", "ws 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", + "yup-hyper-mock 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -523,3 +524,12 @@ dependencies = [ "winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "yup-hyper-mock" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "hyper 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)", + "log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", +] + diff --git a/Cargo.toml b/Cargo.toml index 954c5f9..b4e354a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -22,4 +22,5 @@ log = "0.3.5" rustc-serialize = "0.3.18" tempfile = "2.1.2" toml = "0.1.28" -ws = "0.4.6" \ No newline at end of file +ws = "0.4.6" +yup-hyper-mock = "1.3.2" diff --git a/src/http_client/interface.rs b/src/http_client/interface.rs index 1ea1577..a0c6dbe 100644 --- a/src/http_client/interface.rs +++ b/src/http_client/interface.rs @@ -52,9 +52,10 @@ pub trait HttpClient { } impl HttpClient for hyper::Client { - fn new() -> hyper::Client { - hyper::Client::new() + let mut client = hyper::Client::new(); + client.set_redirect_policy(hyper::client::RedirectPolicy::FollowAll); + client } fn send_request(&self, req: &HttpRequest) -> Result { diff --git a/src/http_client/mock_http_client.rs b/src/http_client/mock_http_client.rs new file mode 100644 index 0000000..5e1c540 --- /dev/null +++ b/src/http_client/mock_http_client.rs @@ -0,0 +1,52 @@ +use std::io::Write; + +use datatype::Error; +use http_client::{HttpClient, HttpRequest}; + + +pub struct MockHttpClient; + +impl HttpClient for MockHttpClient { + fn new() -> MockHttpClient { + MockHttpClient + } + + fn send_request(&self, _: &HttpRequest) -> Result { + return Ok("[\"pkgid\"]".to_string()) + } + + fn send_request_to(&self, _: &HttpRequest, _: W) -> Result<(), Error> { + return Ok(()) + } +} + +#[cfg(test)] +mod tests { + use hyper; + + mock_connector!(MockRedirectPolicy { + "http://127.0.0.1" => "HTTP/1.1 301 Redirect\r\n\ + Location: http://127.0.0.2\r\n\ + Server: mock1\r\n\ + \r\n\ + " + "http://127.0.0.2" => "HTTP/1.1 302 Found\r\n\ + Location: https://127.0.0.3\r\n\ + Server: mock2\r\n\ + \r\n\ + " + "https://127.0.0.3" => "HTTP/1.1 200 OK\r\n\ + Server: mock3\r\n\ + \r\n\ + " + }); + + #[test] + fn test_redirect_followall() { + let mut client = hyper::Client::with_connector(MockRedirectPolicy::default()); + client.set_redirect_policy(hyper::client::RedirectPolicy::FollowAll); + + let res = client.get("http://127.0.0.1").send().unwrap(); + assert_eq!(res.headers.get(), Some(&hyper::header::Server("mock3".to_owned()))); + } +} diff --git a/src/http_client/mod.rs b/src/http_client/mod.rs index 6c8dd7c..3857edc 100644 --- a/src/http_client/mod.rs +++ b/src/http_client/mod.rs @@ -1,5 +1,7 @@ +pub use self::mock_http_client::MockHttpClient; pub use self::bad_http_client::BadHttpClient; pub use self::interface::{HttpClient, HttpRequest}; pub mod bad_http_client; +pub mod mock_http_client; pub mod interface; diff --git a/src/lib.rs b/src/lib.rs index 647f964..34d0bfe 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,5 +1,6 @@ extern crate hyper; #[macro_use] extern crate log; +#[cfg(test)] #[macro_use] extern crate yup_hyper_mock as hyper_mock; extern crate rustc_serialize; extern crate tempfile; extern crate toml; diff --git a/src/ota_plus.rs b/src/ota_plus.rs index 4ea92b3..aa7f066 100644 --- a/src/ota_plus.rs +++ b/src/ota_plus.rs @@ -97,7 +97,6 @@ pub fn post_packages(token: &AccessToken, #[cfg(test)] mod tests { - use std::io::Write; use super::*; @@ -105,10 +104,9 @@ mod tests { use datatype::{Config, OtaConfig}; use datatype::Error; use datatype::Package; - use http_client::BadHttpClient; + use http_client::{MockHttpClient, BadHttpClient}; use http_client::{HttpRequest, HttpClient}; - fn test_token() -> AccessToken { AccessToken { access_token: "token".to_string(), @@ -125,34 +123,16 @@ mod tests { } } - struct MockClient; - - impl HttpClient for MockClient { - - fn new() -> MockClient { - MockClient - } - - fn send_request(&self, _: &HttpRequest) -> Result { - return Ok("[\"pkgid\"]".to_string()) - } - - fn send_request_to(&self, _: &HttpRequest, _: W) -> Result<(), Error> { - return Ok(()) - } - - } - #[test] fn test_post_packages_sends_authentication() { assert_eq!( - post_packages::(&test_token(), &Config::default(), &vec![test_package()]) + post_packages::(&test_token(), &Config::default(), &vec![test_package()]) .unwrap(), ()) } #[test] fn test_get_package_updates() { - assert_eq!(get_package_updates::(&test_token(), &Config::default()).unwrap(), + assert_eq!(get_package_updates::(&test_token(), &Config::default()).unwrap(), vec!["pkgid".to_string()]) } @@ -164,7 +144,7 @@ mod tests { assert_eq!( format!("{}", - download_package_update::(&test_token(), &config, &"0".to_string()) + download_package_update::(&test_token(), &config, &"0".to_string()) .unwrap_err()), r#"Ota error, failed to create file "/0.deb": Permission denied (os error 13)"#) } @@ -179,5 +159,4 @@ mod tests { r#"Ota error, the request: GET http://127.0.0.1:8080/api/v1/vehicles/V1234567890123456/updates/0/download, results in the following error: bad client."#) } - } -- cgit v1.2.1 From 0e1a4ea7eebd92d9022ea33b2aa23d5dd0b7c3ec Mon Sep 17 00:00:00 2001 From: Txus Date: Tue, 19 Apr 2016 14:58:46 +0200 Subject: Fix WS browser end not getting events Two interesting things were at play here -- first, we moved the Mutex from Gateway closer to where the locked resource is (in Websocket really), and required the implementer to be Sync. Before that, we were locking the whole implementer in the reading thread 99% of the time, and so the writer thread could never acquire the lock. The other problem was in the Websocket struct -- mapping over a HashMap with a side-effecting function is apparently optimized away, which was fixed by turning it into a for. --- src/interaction_library/gateway.rs | 10 +++++----- src/interaction_library/websocket.rs | 18 +++++++++--------- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/src/interaction_library/gateway.rs b/src/interaction_library/gateway.rs index 3da3d89..299bcca 100644 --- a/src/interaction_library/gateway.rs +++ b/src/interaction_library/gateway.rs @@ -1,9 +1,9 @@ use std::sync::mpsc::{Sender, Receiver}; -use std::sync::{Arc, Mutex}; +use std::sync::Arc; use std::thread; -pub trait Gateway: Sized + Send + 'static +pub trait Gateway: Sized + Send + Sync + 'static where C: Send + 'static, E: Send + 'static { @@ -16,14 +16,14 @@ pub trait Gateway: Sized + Send + 'static fn run(tx: Sender, rx: Receiver) { - let io = Arc::new(Mutex::new(Self::new())); + let io = Arc::new(Self::new()); // Read lines. let io_clone = io.clone(); thread::spawn(move || { loop { - let cmd = Self::parse(io_clone.lock().unwrap().get_line()).unwrap(); + let cmd = Self::parse(io_clone.get_line()).unwrap(); tx.send(cmd).unwrap() } }); @@ -32,7 +32,7 @@ pub trait Gateway: Sized + Send + 'static thread::spawn(move || { loop { let e = rx.recv().unwrap(); - io.lock().unwrap().put_line(Self::pretty_print(e)) + io.put_line(Self::pretty_print(e)); } }); diff --git a/src/interaction_library/websocket.rs b/src/interaction_library/websocket.rs index 0fbbc10..8a96fc7 100644 --- a/src/interaction_library/websocket.rs +++ b/src/interaction_library/websocket.rs @@ -26,21 +26,21 @@ impl Handler for WebsocketHandler { } fn on_open(&mut self, _: Handshake) -> ws::Result<()> { - let mut map = (*self.clients).lock().unwrap(); + let mut map = self.clients.lock().unwrap(); let _ = map.insert(self.out.token(), self.out.clone()); Ok(()) } fn on_close(&mut self, _: CloseCode, _: &str) { - let mut map = (*self.clients).lock().unwrap(); + let mut map = self.clients.lock().unwrap(); let _ = map.remove(&self.out.token().clone()); } } pub struct Websocket { clients: Clients, - receiver: Receiver, + receiver: Mutex>, } impl Gateway for Websocket @@ -71,19 +71,19 @@ impl Gateway for Websocket Websocket { clients: clients.clone(), - receiver: rx, + receiver: Mutex::new(rx), } } fn get_line(&self) -> String { - self.receiver.recv().unwrap() + self.receiver.lock().unwrap().recv().unwrap() } fn put_line(&self, s: String) { - let map = (*self.clients).lock().unwrap(); - let _ = map - .values() - .map(|out| out.send(Message::Text(s.clone()))); + let map = self.clients.lock().unwrap(); + for (_, out) in map.iter() { + let _ = out.send(Message::Text(s.clone())); + } } fn parse(s: String) -> Option { -- cgit v1.2.1 From d835845b06e98e41237ad943574733940b7d2553 Mon Sep 17 00:00:00 2001 From: Txus Date: Tue, 19 Apr 2016 10:56:18 +0200 Subject: Fix warnings --- src/new_interpreter.rs | 3 ++- src/package_manager/tpm.rs | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/new_interpreter.rs b/src/new_interpreter.rs index 5b5e68c..19641fe 100644 --- a/src/new_interpreter.rs +++ b/src/new_interpreter.rs @@ -2,12 +2,12 @@ use std::sync::mpsc::Sender; use datatype::{AccessToken, Command, Config, Event}; use package_manager::PackageManager; -use interaction_library::interpreter; use interaction_library::interpreter::Interpreter; pub struct OurInterpreter; +#[allow(dead_code)] pub struct Env<'a> { config: &'a Config, access_token: Option<&'a AccessToken>, @@ -17,6 +17,7 @@ pub struct Env<'a> { impl<'a> Interpreter, Command, Event> for OurInterpreter { + #[allow(unused_variables)] fn interpret(env: &Env, cmd: Command, rx: Sender) { match cmd { Command::GetPendingUpdates => unimplemented!(), diff --git a/src/package_manager/tpm.rs b/src/package_manager/tpm.rs index a94ec5c..cf61ee3 100644 --- a/src/package_manager/tpm.rs +++ b/src/package_manager/tpm.rs @@ -86,6 +86,7 @@ mod tests { } } + #[allow(dead_code)] fn make_config(file: &str) -> OtaConfig { let packages_dir = "/tmp/".to_string(); -- cgit v1.2.1 From 118014ac0d3c96f3d21e6a7a7cd5ac0a1c8e088d Mon Sep 17 00:00:00 2001 From: Txus Date: Tue, 19 Apr 2016 16:26:16 +0200 Subject: Remove all unwrap()s from the code --- Cargo.lock | 1 + Cargo.toml | 1 + src/auth_plus.rs | 3 ++- src/datatype/config.rs | 3 ++- src/datatype/error.rs | 20 ++++++++++++++------ src/interaction_library/gateway.rs | 13 +++++++++---- src/interaction_library/interpreter.rs | 6 ++++-- src/interaction_library/websocket.rs | 22 ++++++++++++++++------ src/interpreter.rs | 9 +++++++-- src/lib.rs | 1 + src/main.rs | 2 +- src/ota_plus.rs | 21 +++++++++++---------- 12 files changed, 69 insertions(+), 33 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index bd265ca..f14e673 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -11,6 +11,7 @@ dependencies = [ "rustc-serialize 0.3.19 (registry+https://github.com/rust-lang/crates.io-index)", "tempfile 2.1.2 (registry+https://github.com/rust-lang/crates.io-index)", "toml 0.1.28 (registry+https://github.com/rust-lang/crates.io-index)", + "url 0.5.9 (registry+https://github.com/rust-lang/crates.io-index)", "ws 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", "yup-hyper-mock 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)", ] diff --git a/Cargo.toml b/Cargo.toml index b4e354a..e501e7b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -20,6 +20,7 @@ getopts = "0.2.14" hyper = "0.8.1" log = "0.3.5" rustc-serialize = "0.3.18" +url = "0.5.9" tempfile = "2.1.2" toml = "0.1.28" ws = "0.4.6" diff --git a/src/auth_plus.rs b/src/auth_plus.rs index df3ea6a..a339654 100644 --- a/src/auth_plus.rs +++ b/src/auth_plus.rs @@ -10,7 +10,8 @@ use http_client::{HttpClient, HttpRequest}; pub fn authenticate(config: &AuthConfig) -> Result { - let req = HttpRequest::post(config.server.join("/token").unwrap()) + let url = try!(config.server.join("/token")); + let req = HttpRequest::post(url) .with_body("grant_type=client_credentials") .with_header(Authorization(Basic { username: config.client_id.clone(), diff --git a/src/datatype/config.rs b/src/datatype/config.rs index 67d4959..125574c 100644 --- a/src/datatype/config.rs +++ b/src/datatype/config.rs @@ -126,7 +126,8 @@ fn bootstrap_credentials(auth_cfg_section: AuthConfigSection) -> Result Result<(), Error> { let mut tbl = toml::Table::new(); tbl.insert("auth".to_string(), toml::encode(&creds)); - try!(fs::create_dir_all(&path.parent().unwrap())); + let dir = try!(path.parent().ok_or(Error::Config(Parse(InvalidSection("Invalid credentials file path".to_string()))))); + try!(fs::create_dir_all(&dir)); let mut f = try!(File::create(path)); try!(f.write_all(&toml::encode_str(&tbl).into_bytes())); Ok(()) diff --git a/src/datatype/error.rs b/src/datatype/error.rs index ec941f2..f25b9c3 100644 --- a/src/datatype/error.rs +++ b/src/datatype/error.rs @@ -4,19 +4,20 @@ use std::fmt::{Display, Formatter, Result as FmtResult}; use std::io; use std::path::PathBuf; use ws; - +use url; #[derive(Debug)] pub enum Error { AuthError(String), - Ota(OtaReason), - ParseError(String), - PackageError(String), ClientError(String), Config(ConfigReason), - JsonEncode(json::EncoderError), - JsonDecode(json::DecoderError), Io(io::Error), + JsonDecode(json::DecoderError), + JsonEncode(json::EncoderError), + Ota(OtaReason), + PackageError(String), + ParseError(String), + Url(url::ParseError), Websocket(ws::Error) } @@ -44,6 +45,12 @@ impl From for Error { } } +impl From for Error { + fn from(e: url::ParseError) -> Error { + Error::Url(e) + } +} + #[derive(Debug)] pub enum OtaReason { CreateFile(PathBuf, io::Error), @@ -74,6 +81,7 @@ impl Display for Error { Error::JsonEncode(ref e) => format!("Failed to encode JSON: {}", e.clone()), Error::JsonDecode(ref e) => format!("Failed to decode JSON: {}", e.clone()), Error::Io(ref e) => format!("IO Error{:?}", e.clone()), + Error::Url(ref e) => format!("URL Parse Error{:?}", e.clone()), Error::Websocket(ref e) => format!("Websocket Error{:?}", e.clone()), }; write!(f, "{}", inner) diff --git a/src/interaction_library/gateway.rs b/src/interaction_library/gateway.rs index 299bcca..94b6068 100644 --- a/src/interaction_library/gateway.rs +++ b/src/interaction_library/gateway.rs @@ -23,16 +23,21 @@ pub trait Gateway: Sized + Send + Sync + 'static thread::spawn(move || { loop { - let cmd = Self::parse(io_clone.get_line()).unwrap(); - tx.send(cmd).unwrap() + let _ = Self::parse(io_clone.get_line()) + .ok_or_else(|| { error!("Error parsing command") }) + .and_then(|cmd| { + tx.send(cmd).map_err(|e| error!("Error forwarding command: {:?}", e)) + }); } }); // Put lines. thread::spawn(move || { loop { - let e = rx.recv().unwrap(); - io.put_line(Self::pretty_print(e)); + match rx.recv() { + Ok(e) => io.put_line(Self::pretty_print(e)), + Err(err) => error!("Error receiving event: {:?}", err) + } } }); diff --git a/src/interaction_library/interpreter.rs b/src/interaction_library/interpreter.rs index 64ae305..1256632 100644 --- a/src/interaction_library/interpreter.rs +++ b/src/interaction_library/interpreter.rs @@ -7,8 +7,10 @@ pub trait Interpreter { fn run(env: &Env, rx: Receiver, tx: Sender) { loop { - let c = rx.recv().unwrap(); - Self::interpret(&env, c, tx.clone()); + match rx.recv() { + Ok(c) => Self::interpret(&env, c, tx.clone()), + Err(e) => error!("Error receiving command: {:?}", e) + } } } diff --git a/src/interaction_library/websocket.rs b/src/interaction_library/websocket.rs index 8a96fc7..acc1654 100644 --- a/src/interaction_library/websocket.rs +++ b/src/interaction_library/websocket.rs @@ -22,18 +22,21 @@ pub struct WebsocketHandler { impl Handler for WebsocketHandler { fn on_message(&mut self, msg: Message) -> ws::Result<()> { - Ok(self.sender.send(format!("{}", msg)).unwrap()) + Ok(match self.sender.send(format!("{}", msg)) { + Ok(_) => {}, + Err(e) => error!("Error forwarding message from WS: {}", e) + }) } fn on_open(&mut self, _: Handshake) -> ws::Result<()> { - let mut map = self.clients.lock().unwrap(); + let mut map = self.clients.lock().expect("Poisoned map lock -- can't continue"); let _ = map.insert(self.out.token(), self.out.clone()); Ok(()) } fn on_close(&mut self, _: CloseCode, _: &str) { - let mut map = self.clients.lock().unwrap(); + let mut map = self.clients.lock().expect("Poisoned map lock -- can't continue"); let _ = map.remove(&self.out.token().clone()); } } @@ -76,11 +79,18 @@ impl Gateway for Websocket } fn get_line(&self) -> String { - self.receiver.lock().unwrap().recv().unwrap() + let rx = self.receiver.lock().expect("Poisoned rx lock -- can't continue"); + match rx.recv() { + Ok(line) => line, + Err(e) => { + error!("Couldn't fetch from WS receiver: {:?}", e); + "".to_string() + } + } } fn put_line(&self, s: String) { - let map = self.clients.lock().unwrap(); + let map = self.clients.lock().expect("Poisoned map lock -- can't continue"); for (_, out) in map.iter() { let _ = out.send(Message::Text(s.clone())); } @@ -91,7 +101,7 @@ impl Gateway for Websocket } fn pretty_print(e: E) -> String { - json::encode(&e).unwrap() + json::encode(&e).expect("Error encoding event into JSON") } } diff --git a/src/interpreter.rs b/src/interpreter.rs index 591d1cd..6ff9ece 100644 --- a/src/interpreter.rs +++ b/src/interpreter.rs @@ -22,7 +22,10 @@ impl<'a, C: HttpClient> Interpreter<'a, C> { pub fn start(&self) { loop { - self.interpret(self.commands_rx.recv().unwrap()); + match self.commands_rx.recv() { + Ok(cmd) => self.interpret(cmd), + Err(e) => error!("Error receiving command: {:?}", e) + } } } @@ -80,7 +83,9 @@ impl<'a, C: HttpClient> Interpreter<'a, C> { .and_then(|path| { info!("Downloaded at {:?}. Installing...", path); self.publish(Event::UpdateStateChanged(id.clone(), UpdateState::Installing)); - self.config.ota.package_manager.install_package(path.to_str().unwrap()) + + let p = try!(path.to_str().ok_or(Error::ParseError(format!("Path is not valid UTF-8: {:?}", path)))); + self.config.ota.package_manager.install_package(p) .map(|(code, output)| { self.publish(Event::UpdateStateChanged(id.clone(), UpdateState::Installed)); UpdateReport::new(id.clone(), code, output) diff --git a/src/lib.rs b/src/lib.rs index 34d0bfe..1c92d3b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -5,6 +5,7 @@ extern crate rustc_serialize; extern crate tempfile; extern crate toml; extern crate ws; +extern crate url; pub mod auth_plus; pub mod datatype; diff --git a/src/main.rs b/src/main.rs index d6c8a66..f4ce031 100644 --- a/src/main.rs +++ b/src/main.rs @@ -112,7 +112,7 @@ impl InteractionInterpreter<(), Event, Command> for AutoAcceptor { fn main() { - env_logger::init().unwrap(); + env_logger::init().expect("Couldn't initialize logger"); let config = build_config(); diff --git a/src/ota_plus.rs b/src/ota_plus.rs index aa7f066..eae8b72 100644 --- a/src/ota_plus.rs +++ b/src/ota_plus.rs @@ -16,15 +16,16 @@ use datatype::{UpdateReport, UpdateReportWithVin}; use http_client::{HttpClient, HttpRequest}; -fn vehicle_endpoint(config: &Config, s: &str) -> Url { - config.ota.server.join(&format!("/api/v1/vehicles/{}{}", config.auth.vin, s)).unwrap() +fn vehicle_endpoint(config: &Config, s: &str) -> Result { + Ok(try!(config.ota.server.join(&format!("/api/v1/vehicles/{}{}", config.auth.vin, s)))) } pub fn download_package_update(token: &AccessToken, config: &Config, id: &UpdateRequestId) -> Result { - let req = HttpRequest::get(vehicle_endpoint(config, &format!("/updates/{}/download", id))) + let url = try!(vehicle_endpoint(config, &format!("/updates/{}/download", id))); + let req = HttpRequest::get(url) .with_header(Authorization(Bearer { token: token.access_token.clone() })); let mut path = PathBuf::new(); @@ -49,7 +50,8 @@ pub fn send_install_report(token: &AccessToken, let json = try!(json::encode(&report_with_vin) .map_err(|_| Error::ParseError(String::from("JSON encoding error")))); - let req = HttpRequest::post(vehicle_endpoint(config, &format!("/updates/{}", report.update_id))) + let url = try!(vehicle_endpoint(config, &format!("/updates/{}", report.update_id))); + let req = HttpRequest::post(url) .with_header(Authorization(Bearer { token: token.access_token.clone() })) .with_header(ContentType(Mime( TopLevel::Application, @@ -65,7 +67,8 @@ pub fn send_install_report(token: &AccessToken, pub fn get_package_updates(token: &AccessToken, config: &Config) -> Result, Error> { - let req = HttpRequest::get(vehicle_endpoint(&config, "/updates")) + let url = try!(vehicle_endpoint(&config, "/updates")); + let req = HttpRequest::get(url) .with_header(Authorization(Bearer { token: token.access_token.clone() })); let body = try!(C::new().send_request(&req) @@ -82,7 +85,8 @@ pub fn post_packages(token: &AccessToken, let json = try!(json::encode(&pkgs) .map_err(|_| Error::ParseError(String::from("JSON encoding error")))); - let req = HttpRequest::post(vehicle_endpoint(config, "/updates")) + let url = try!(vehicle_endpoint(config, "/updates")); + let req = HttpRequest::post(url) .with_header(Authorization(Bearer { token: token.access_token.clone() })) .with_header(ContentType(Mime( TopLevel::Application, @@ -97,15 +101,12 @@ pub fn post_packages(token: &AccessToken, #[cfg(test)] mod tests { - use std::io::Write; - use super::*; use datatype::AccessToken; use datatype::{Config, OtaConfig}; - use datatype::Error; use datatype::Package; use http_client::{MockHttpClient, BadHttpClient}; - use http_client::{HttpRequest, HttpClient}; + use http_client::HttpClient; fn test_token() -> AccessToken { AccessToken { -- cgit v1.2.1 From 1405600eb0510982944893d636c10513870cee2e Mon Sep 17 00:00:00 2001 From: Txus Date: Tue, 19 Apr 2016 17:26:42 +0200 Subject: A response is not successful unless it is --- src/http_client/interface.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/http_client/interface.rs b/src/http_client/interface.rs index a0c6dbe..aa2462e 100644 --- a/src/http_client/interface.rs +++ b/src/http_client/interface.rs @@ -69,12 +69,12 @@ impl HttpClient for hyper::Client { .and_then(|mut resp| { let mut rbody = String::new(); let status = resp.status; - if status.is_server_error() || status.is_client_error() { - Err(Error::ClientError(format!("Request errored with status {}", status))) - } else { + if status.is_success() { resp.read_to_string(&mut rbody) .map_err(|e| Error::ParseError(format!("Cannot read response: {}", e))) .map(|_| rbody) + } else { + Err(Error::ClientError(format!("Request failed with status {}", status))) } }) } -- cgit v1.2.1 From 4036092ae982c11b03b09fb865040f7d4665089a Mon Sep 17 00:00:00 2001 From: Alex Humphreys Date: Thu, 21 Apr 2016 11:31:13 +0200 Subject: Fix rpm package info format --- src/package_manager/rpm.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/package_manager/rpm.rs b/src/package_manager/rpm.rs index 3f60802..78b8629 100644 --- a/src/package_manager/rpm.rs +++ b/src/package_manager/rpm.rs @@ -5,7 +5,7 @@ use package_manager::dpkg::parse_package; // XXX: Move somewhere better? pub fn installed_packages() -> Result, Error> { - Command::new("rpm").arg("-qa").arg("--queryformat").arg("%{NAME} %{SIZE}\n") + Command::new("rpm").arg("-qa").arg("--queryformat").arg("%{NAME} %{VERSION}\n") .output() .map_err(|e| Error::PackageError(format!("Error fetching packages: {}", e))) .and_then(|c| { -- cgit v1.2.1 From e826bdeafff460e6f31383df5cb2e0b19767a048 Mon Sep 17 00:00:00 2001 From: Alex Humphreys Date: Thu, 21 Apr 2016 16:27:07 +0200 Subject: Add --force and --upgrade to rpm install --- src/package_manager/rpm.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/package_manager/rpm.rs b/src/package_manager/rpm.rs index 78b8629..c473656 100644 --- a/src/package_manager/rpm.rs +++ b/src/package_manager/rpm.rs @@ -21,7 +21,7 @@ pub fn installed_packages() -> Result, Error> { } pub fn install_package(path: &str) -> Result<(UpdateResultCode, String), (UpdateResultCode, String)> { - let output = try!(Command::new("rpm").arg("-ivh").arg(path) + let output = try!(Command::new("rpm").arg("-Uvh").arg("--force").arg(path) .output() .map_err(|e| { (UpdateResultCode::GENERAL_ERROR, format!("{:?}", e)) -- cgit v1.2.1 From 07f965e896f49b553553e335cf4283b29cd30235 Mon Sep 17 00:00:00 2001 From: Shaun Taheri Date: Thu, 21 Apr 2016 16:33:19 +0200 Subject: Post installed packages after successful install --- src/interpreter.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/interpreter.rs b/src/interpreter.rs index 6ff9ece..3a3ce85 100644 --- a/src/interpreter.rs +++ b/src/interpreter.rs @@ -88,6 +88,7 @@ impl<'a, C: HttpClient> Interpreter<'a, C> { self.config.ota.package_manager.install_package(p) .map(|(code, output)| { self.publish(Event::UpdateStateChanged(id.clone(), UpdateState::Installed)); + self.post_installed_packages(); UpdateReport::new(id.clone(), code, output) }) .or_else(|(code, output)| { -- cgit v1.2.1 From 60e5d4187ccb4b24e4d8833ac56b12c83208725c Mon Sep 17 00:00:00 2001 From: Shaun Taheri Date: Fri, 22 Apr 2016 13:14:24 +0200 Subject: Strip Authorization Header on redirect --- src/http_client/interface.rs | 91 ++++++++++++++++++++++++++++++-------------- 1 file changed, 63 insertions(+), 28 deletions(-) diff --git a/src/http_client/interface.rs b/src/http_client/interface.rs index aa2462e..4f616e4 100644 --- a/src/http_client/interface.rs +++ b/src/http_client/interface.rs @@ -1,6 +1,7 @@ use hyper::Url; -use hyper::header::{Headers, Header, HeaderFormat}; +use hyper::header::{Headers, Header, HeaderFormat, Location, Authorization, Bearer}; use hyper::method::Method; +use hyper::client::response::Response; use hyper; use std::io::{Read, Write, BufReader, BufWriter}; @@ -12,18 +13,23 @@ pub struct HttpRequest<'a> { pub url: Url, pub method: Method, pub headers: Headers, - pub body: Option<&'a str> + pub body: Option<&'a str>, } impl<'a> ToString for HttpRequest<'a> { fn to_string(&self) -> String { - return format!("{} {}", self.method, self.url.serialize()) + format!("{} {}", self.method, self.url.serialize()) } } impl<'a> HttpRequest<'a> { pub fn new(url: Url, method: Method) -> HttpRequest<'a> { - HttpRequest { url: url, method: method, headers: Headers::new(), body: None } + HttpRequest { + url: url, + method: method, + headers: Headers::new(), + body: None, + } } pub fn get(url: Url) -> HttpRequest<'a> { @@ -54,27 +60,31 @@ pub trait HttpClient { impl HttpClient for hyper::Client { fn new() -> hyper::Client { let mut client = hyper::Client::new(); - client.set_redirect_policy(hyper::client::RedirectPolicy::FollowAll); + client.set_redirect_policy(hyper::client::RedirectPolicy::FollowNone); client } fn send_request(&self, req: &HttpRequest) -> Result { self.request(req.method.clone(), req.url.clone()) .headers(req.headers.clone()) - .body(if let Some(body) = req.body { body } else { "" }) + .body(req.body.unwrap_or("")) .send() - .map_err(|e| { - Error::ClientError(format!("{}", e)) - }) + .map_err(|e| Error::ClientError(format!("{}", e))) .and_then(|mut resp| { - let mut rbody = String::new(); - let status = resp.status; - if status.is_success() { - resp.read_to_string(&mut rbody) - .map_err(|e| Error::ParseError(format!("Cannot read response: {}", e))) - .map(|_| rbody) - } else { - Err(Error::ClientError(format!("Request failed with status {}", status))) + match resp.status.class() { + hyper::status::StatusClass::Success => { + let mut rbody = String::new(); + resp.read_to_string(&mut rbody) + .map_err(|e| Error::ParseError(format!("Cannot read response: {}", e))) + .map(|_| rbody) + } + hyper::status::StatusClass::Redirection => { + relocate_request(req, &resp).and_then(|ref r| self.send_request(r)) + } + _ => { + Err(Error::ClientError(format!("Request failed with status {}", + resp.status))) + } } }) } @@ -82,24 +92,49 @@ impl HttpClient for hyper::Client { fn send_request_to(&self, req: &HttpRequest, to: W) -> Result<(), Error> { self.request(req.method.clone(), req.url.clone()) .headers(req.headers.clone()) - .body(if let Some(body) = req.body { body } else { "" }) + .body(req.body.unwrap_or("")) .send() - .map_err(|e| { - Error::ClientError(format!("Cannot send request: {}", e)) - }) + .map_err(|e| Error::ClientError(format!("{}", e))) .and_then(|resp| { - let status = resp.status; - if status.is_server_error() || status.is_client_error() { - Err(Error::ClientError(format!("Request errored with status {}", status))) - } else { - tee(resp, to) - .map_err(|e| Error::ParseError(format!("Cannot read response: {}", e))) - .map(|_| ()) + match resp.status.class() { + hyper::status::StatusClass::Success => { + tee(resp, to) + .map_err(|e| Error::ParseError(format!("Cannot read response: {}", e))) + .map(|_| ()) + } + hyper::status::StatusClass::Redirection => { + relocate_request(req, &resp).and_then(|ref r| self.send_request_to(r, to)) + } + _ => { + Err(Error::ClientError(format!("Request failed with status {}", + resp.status))) + } } }) } } +fn relocate_request<'a>(req: &'a HttpRequest, resp: &Response) -> Result, Error> { + match resp.headers.get::() { + Some(&Location(ref loc)) => { + req.url + .join(loc) + .map_err(|e| Error::ParseError(format!("Cannot read location: {}", e))) + .and_then(|url| { + let mut headers = req.headers.clone(); + headers.remove::>(); + Ok(HttpRequest { + url: url, + method: req.method.clone(), + headers: headers, + body: req.body, + }) + }) + } + None => Err(Error::ClientError("Redirect with no Location header".to_string())), + } +} + pub fn tee(from: R, to: W) -> Result<(), Error> { let mb = 1024 * 1024; let rbuf = BufReader::with_capacity(5 * mb, from); -- cgit v1.2.1 From 8411885c3d8ca1b3a2e67160b0e9ea44fb9c03e7 Mon Sep 17 00:00:00 2001 From: Stevan Andjelkovic Date: Thu, 14 Apr 2016 11:09:25 +0200 Subject: Prototype: new http client interface. --- Cargo.toml | 2 +- src/datatype/error.rs | 24 +++++++--- src/datatype/method.rs | 16 +++++++ src/datatype/mod.rs | 16 ++++--- src/datatype/url.rs | 3 ++ src/http_client/http_client.rs | 34 ++++++++++++++ src/http_client/hyper.rs | 103 +++++++++++++++++++++++++++++++++++++++++ src/http_client/mod.rs | 3 ++ src/lib.rs | 1 + 9 files changed, 188 insertions(+), 14 deletions(-) create mode 100644 src/datatype/method.rs create mode 100644 src/datatype/url.rs create mode 100644 src/http_client/http_client.rs create mode 100644 src/http_client/hyper.rs diff --git a/Cargo.toml b/Cargo.toml index e501e7b..7a8e3ef 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,7 +14,7 @@ doc = false [dependencies] chan-signal = "0.1.5" -chan= "0.1.18" +chan = "0.1.18" env_logger = "0.3.3" getopts = "0.2.14" hyper = "0.8.1" diff --git a/src/datatype/error.rs b/src/datatype/error.rs index f25b9c3..8117879 100644 --- a/src/datatype/error.rs +++ b/src/datatype/error.rs @@ -1,16 +1,19 @@ +use hyper::error as hyper; use rustc_serialize::json; use std::convert::From; use std::fmt::{Display, Formatter, Result as FmtResult}; use std::io; use std::path::PathBuf; -use ws; use url; +use ws; + #[derive(Debug)] pub enum Error { AuthError(String), ClientError(String), Config(ConfigReason), + Hyper(hyper::Error), Io(io::Error), JsonDecode(json::DecoderError), JsonEncode(json::EncoderError), @@ -18,7 +21,7 @@ pub enum Error { PackageError(String), ParseError(String), Url(url::ParseError), - Websocket(ws::Error) + Websocket(ws::Error), } impl From for Error { @@ -27,6 +30,12 @@ impl From for Error { } } +impl From for Error { + fn from(e: hyper::Error) -> Error { + Error::Hyper(e) + } +} + impl From for Error { fn from(e: json::DecoderError) -> Error { Error::JsonDecode(e) @@ -73,15 +82,16 @@ impl Display for Error { fn fmt(&self, f: &mut Formatter) -> FmtResult { let inner: String = match *self { Error::AuthError(ref s) => format!("Authentication error, {}", s.clone()), - Error::Ota(ref e) => format!("Ota error, {}", e.clone()), - Error::ParseError(ref s) => s.clone(), - Error::PackageError(ref s) => s.clone(), Error::ClientError(ref s) => s.clone(), Error::Config(ref e) => format!("Failed to {}", e.clone()), - Error::JsonEncode(ref e) => format!("Failed to encode JSON: {}", e.clone()), - Error::JsonDecode(ref e) => format!("Failed to decode JSON: {}", e.clone()), + Error::Hyper(ref e) => format!("Hyper error: {}", e.clone()), Error::Io(ref e) => format!("IO Error{:?}", e.clone()), Error::Url(ref e) => format!("URL Parse Error{:?}", e.clone()), + Error::JsonDecode(ref e) => format!("Failed to decode JSON: {}", e.clone()), + Error::JsonEncode(ref e) => format!("Failed to encode JSON: {}", e.clone()), + Error::Ota(ref e) => format!("Ota error, {}", e.clone()), + Error::PackageError(ref s) => s.clone(), + Error::ParseError(ref s) => s.clone(), Error::Websocket(ref e) => format!("Websocket Error{:?}", e.clone()), }; write!(f, "{}", inner) diff --git a/src/datatype/method.rs b/src/datatype/method.rs new file mode 100644 index 0000000..ff61d3b --- /dev/null +++ b/src/datatype/method.rs @@ -0,0 +1,16 @@ +use hyper::method; + + +pub enum Method { + Get, + Post, +} + +impl Method { + pub fn to_hyper(&self) -> method::Method { + match *self { + Method::Get => method::Method::Get, + Method::Post => method::Method::Post, + } + } +} diff --git a/src/datatype/mod.rs b/src/datatype/mod.rs index 610cd37..c154b76 100644 --- a/src/datatype/mod.rs +++ b/src/datatype/mod.rs @@ -1,17 +1,21 @@ pub use self::access_token::AccessToken; +pub use self::command::Command; pub use self::config::{Config, AuthConfig, OtaConfig, TestConfig}; pub use self::error::Error; -pub use self::package::Package; -pub use self::update_request::{UpdateRequestId, UpdateState}; pub use self::event::Event; -pub use self::command::Command; +pub use self::method::Method; +pub use self::package::Package; pub use self::report::{UpdateReport, UpdateReportWithVin, UpdateResultCode}; +pub use self::update_request::{UpdateRequestId, UpdateState}; +pub use self::url::Url; pub mod access_token; +pub mod command; pub mod config; pub mod error; -pub mod package; -pub mod update_request; pub mod event; -pub mod command; +pub mod method; +pub mod package; pub mod report; +pub mod update_request; +pub mod url; diff --git a/src/datatype/url.rs b/src/datatype/url.rs new file mode 100644 index 0000000..42f0c80 --- /dev/null +++ b/src/datatype/url.rs @@ -0,0 +1,3 @@ +use url; + +pub type Url = url::Url; diff --git a/src/http_client/http_client.rs b/src/http_client/http_client.rs new file mode 100644 index 0000000..535aaf0 --- /dev/null +++ b/src/http_client/http_client.rs @@ -0,0 +1,34 @@ +use rustc_serialize::json::Json; +use std::fs::File; +use std::io::{Write, Read}; +use tempfile; + +use datatype::{AccessToken, Error, Method, Url}; + + +pub struct HttpRequest2<'a> { + pub method: &'a Method, + pub url: &'a Url, + pub token: Option<&'a AccessToken>, + pub body: Option<&'a Json> +} + +pub trait HttpClient2 { + + fn send_request_to + (&self, request: &HttpRequest2, target: T) -> Result<(), Error>; + + fn send_request(&self, request: &HttpRequest2) -> Result { + + let mut temp_file: File = try!(tempfile::tempfile()); + + try!(Self::send_request_to(self, request, &temp_file)); + + let mut buf = String::new(); + temp_file.read_to_string(&mut buf); + + Ok(buf) + + } + +} diff --git a/src/http_client/hyper.rs b/src/http_client/hyper.rs new file mode 100644 index 0000000..7a6e30a --- /dev/null +++ b/src/http_client/hyper.rs @@ -0,0 +1,103 @@ +use hyper::Client; +use hyper::header::{Authorization, Bearer, ContentType, Headers}; +use hyper::mime::{Attr, Mime, TopLevel, SubLevel, Value}; +use std::io::{Read, Write, BufReader, BufWriter}; + +use datatype::Error; +use http_client::{HttpClient2, HttpRequest2}; + + +pub struct Hyper { + client: Client, +} + +impl HttpClient2 for Hyper { + + fn send_request_to + (&self, request: &HttpRequest2, target: T) -> Result<(), Error> { + + let mut headers = Headers::new(); + + if let Some(token) = request.token { + headers.set(Authorization(Bearer { + token: token.access_token.clone() + })) + } + + if request.body.is_some() { + headers.set(ContentType(Mime( + TopLevel::Application, + SubLevel::Json, + vec![(Attr::Charset, Value::Utf8)]))) + } + + let mut resp = try!(self.client + .request(request.method.to_hyper(), request.url.clone()) + .headers(headers) + .body( + if let Some(body) = request.body + .and_then(|b| b.as_string()) { + body + } else { + "" + }) + .send()); + + let mut rbody = String::new(); + let status = resp.status; + + if status.is_server_error() || status.is_client_error() { + Err(Error::ClientError(format!("Request errored with status {}", status))) + } else { + tee(resp, target); + Ok(()) + } + + } + +} + +pub fn tee(from: R, to: W) -> Result<(), Error> { + + const BUF_SIZE: usize = 1024 * 1024 * 5; + + let rbuf = BufReader::with_capacity(BUF_SIZE, from); + let mut wbuf = BufWriter::with_capacity(BUF_SIZE, to); + + for b in rbuf.bytes() { + try!(wbuf.write(&[try!(b)])); + } + + Ok(()) + +} + + +#[cfg(test)] +mod tests { + + use std::fs::File; + use std::io::{Read, repeat}; + + use super::*; + + + #[test] + fn test_tee() { + let values = repeat(b'a').take(9000); + let sink = File::create("/tmp/otaplus_tee_test").unwrap(); + + assert!(tee(values, sink).is_ok()); + + let mut values2 = repeat(b'a').take(9000); + let mut expected = Vec::new(); + let _ = values2.read_to_end(&mut expected); + + let mut f = File::open("/tmp/otaplus_tee_test").unwrap(); + let mut result = Vec::new(); + let _ = f.read_to_end(&mut result); + + assert_eq!(result, expected); + } + +} diff --git a/src/http_client/mod.rs b/src/http_client/mod.rs index 3857edc..1428431 100644 --- a/src/http_client/mod.rs +++ b/src/http_client/mod.rs @@ -1,7 +1,10 @@ pub use self::mock_http_client::MockHttpClient; pub use self::bad_http_client::BadHttpClient; pub use self::interface::{HttpClient, HttpRequest}; +pub use self::http_client::{HttpClient2, HttpRequest2}; pub mod bad_http_client; pub mod mock_http_client; +pub mod http_client; +pub mod hyper; pub mod interface; diff --git a/src/lib.rs b/src/lib.rs index 1c92d3b..9f1e437 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -4,6 +4,7 @@ extern crate hyper; extern crate rustc_serialize; extern crate tempfile; extern crate toml; +extern crate url; extern crate ws; extern crate url; -- cgit v1.2.1 From be54014eea9692fc95e79058e0d18b55912d51cd Mon Sep 17 00:00:00 2001 From: Stevan Andjelkovic Date: Mon, 18 Apr 2016 11:58:29 +0200 Subject: Use into trait for method, thanks @txus. --- src/datatype/method.rs | 4 ++-- src/http_client/hyper.rs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/datatype/method.rs b/src/datatype/method.rs index ff61d3b..ee5badc 100644 --- a/src/datatype/method.rs +++ b/src/datatype/method.rs @@ -6,8 +6,8 @@ pub enum Method { Post, } -impl Method { - pub fn to_hyper(&self) -> method::Method { +impl<'a> Into for &'a Method { + fn into(self) -> method::Method { match *self { Method::Get => method::Method::Get, Method::Post => method::Method::Post, diff --git a/src/http_client/hyper.rs b/src/http_client/hyper.rs index 7a6e30a..20c746e 100644 --- a/src/http_client/hyper.rs +++ b/src/http_client/hyper.rs @@ -32,7 +32,7 @@ impl HttpClient2 for Hyper { } let mut resp = try!(self.client - .request(request.method.to_hyper(), request.url.clone()) + .request(request.method.into(), request.url.clone()) .headers(headers) .body( if let Some(body) = request.body -- cgit v1.2.1 From 6c57572f8fb9dcd41dc117f947aa8a11ae61251a Mon Sep 17 00:00:00 2001 From: Stevan Andjelkovic Date: Mon, 18 Apr 2016 12:20:05 +0200 Subject: Make the new http client interface part of the environment of the interpreter. --- src/http_client/http_client.rs | 8 ++++++-- src/new_interpreter.rs | 4 ++++ 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/src/http_client/http_client.rs b/src/http_client/http_client.rs index 535aaf0..f0b408b 100644 --- a/src/http_client/http_client.rs +++ b/src/http_client/http_client.rs @@ -16,9 +16,13 @@ pub struct HttpRequest2<'a> { pub trait HttpClient2 { fn send_request_to - (&self, request: &HttpRequest2, target: T) -> Result<(), Error>; + (&self, request: &HttpRequest2, target: T) -> Result<(), Error> + where Self: Sized; - fn send_request(&self, request: &HttpRequest2) -> Result { + fn send_request(&self, request: &HttpRequest2) -> Result + where + Self: Sized + { let mut temp_file: File = try!(tempfile::tempfile()); diff --git a/src/new_interpreter.rs b/src/new_interpreter.rs index 19641fe..1ec55b0 100644 --- a/src/new_interpreter.rs +++ b/src/new_interpreter.rs @@ -2,7 +2,10 @@ use std::sync::mpsc::Sender; use datatype::{AccessToken, Command, Config, Event}; use package_manager::PackageManager; +use http_client::HttpClient2; use interaction_library::interpreter::Interpreter; +use interaction_library::interpreter; +use package_manager::PackageManager; pub struct OurInterpreter; @@ -12,6 +15,7 @@ pub struct Env<'a> { config: &'a Config, access_token: Option<&'a AccessToken>, pkg_manager: &'a PackageManager, + http_client: &'a HttpClient2, } -- cgit v1.2.1 From 3ca731df22798c075c22f4ca86b10b008864f583 Mon Sep 17 00:00:00 2001 From: Stevan Andjelkovic Date: Mon, 18 Apr 2016 14:28:37 +0200 Subject: Remove generic params from send_request_to, as they seem to cause sized problems. --- src/datatype/config.rs | 3 +-- src/http_client/http_client.rs | 11 +++-------- src/http_client/hyper.rs | 6 +++--- src/lib.rs | 1 + src/new_interpreter.rs | 11 ++++++----- 5 files changed, 14 insertions(+), 18 deletions(-) diff --git a/src/datatype/config.rs b/src/datatype/config.rs index 125574c..cbc7a1f 100644 --- a/src/datatype/config.rs +++ b/src/datatype/config.rs @@ -1,4 +1,3 @@ -use hyper::Url; use rustc_serialize::Decodable; use std::fs; use std::fs::File; @@ -7,7 +6,7 @@ use std::io::prelude::*; use std::path::Path; use toml; -use datatype::Error; +use datatype::{Error, Url}; use datatype::error::ConfigReason::{Parse, Io}; use datatype::error::ParseReason::{InvalidToml, InvalidSection}; use package_manager::PackageManager; diff --git a/src/http_client/http_client.rs b/src/http_client/http_client.rs index f0b408b..bbe95ab 100644 --- a/src/http_client/http_client.rs +++ b/src/http_client/http_client.rs @@ -15,14 +15,9 @@ pub struct HttpRequest2<'a> { pub trait HttpClient2 { - fn send_request_to - (&self, request: &HttpRequest2, target: T) -> Result<(), Error> - where Self: Sized; - - fn send_request(&self, request: &HttpRequest2) -> Result - where - Self: Sized - { + fn send_request_to(&self, request: &HttpRequest2, file: &File) -> Result<(), Error>; + + fn send_request(&self, request: &HttpRequest2) -> Result { let mut temp_file: File = try!(tempfile::tempfile()); diff --git a/src/http_client/hyper.rs b/src/http_client/hyper.rs index 20c746e..f25137a 100644 --- a/src/http_client/hyper.rs +++ b/src/http_client/hyper.rs @@ -1,6 +1,7 @@ use hyper::Client; use hyper::header::{Authorization, Bearer, ContentType, Headers}; use hyper::mime::{Attr, Mime, TopLevel, SubLevel, Value}; +use std::fs::File; use std::io::{Read, Write, BufReader, BufWriter}; use datatype::Error; @@ -13,8 +14,7 @@ pub struct Hyper { impl HttpClient2 for Hyper { - fn send_request_to - (&self, request: &HttpRequest2, target: T) -> Result<(), Error> { + fn send_request_to(&self, request: &HttpRequest2, file: &File) -> Result<(), Error> { let mut headers = Headers::new(); @@ -49,7 +49,7 @@ impl HttpClient2 for Hyper { if status.is_server_error() || status.is_client_error() { Err(Error::ClientError(format!("Request errored with status {}", status))) } else { - tee(resp, target); + tee(resp, file); Ok(()) } diff --git a/src/lib.rs b/src/lib.rs index 9f1e437..3645a12 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -14,5 +14,6 @@ pub mod http_client; pub mod interaction_library; pub mod interpreter; pub mod new_interpreter; +pub mod new_ota_plus; pub mod ota_plus; pub mod package_manager; diff --git a/src/new_interpreter.rs b/src/new_interpreter.rs index 1ec55b0..ea7ce2b 100644 --- a/src/new_interpreter.rs +++ b/src/new_interpreter.rs @@ -1,6 +1,7 @@ use std::sync::mpsc::Sender; use datatype::{AccessToken, Command, Config, Event}; +use datatype::Command::*; use package_manager::PackageManager; use http_client::HttpClient2; use interaction_library::interpreter::Interpreter; @@ -24,11 +25,11 @@ impl<'a> Interpreter, Command, Event> for OurInterpreter { #[allow(unused_variables)] fn interpret(env: &Env, cmd: Command, rx: Sender) { match cmd { - Command::GetPendingUpdates => unimplemented!(), - Command::PostInstalledPackages => unimplemented!(), - Command::AcceptUpdate(ref id) => unimplemented!(), - Command::ListInstalledPackages => unimplemented!(), - Command::Shutdown => unimplemented!(), + GetPendingUpdates => unimplemented!(), + PostInstalledPackages => unimplemented!(), + AcceptUpdate(ref id) => unimplemented!(), + ListInstalledPackages => unimplemented!(), + Shutdown => unimplemented!(), } } -- cgit v1.2.1 From 3b1ace74e44ba5df2d6304e1a697ad20748f75a6 Mon Sep 17 00:00:00 2001 From: Stevan Andjelkovic Date: Mon, 18 Apr 2016 15:16:27 +0200 Subject: Use our url type everywhere. --- src/auth_plus.rs | 4 +--- src/datatype/error.rs | 34 ++++++++++++++++++++-------------- src/datatype/url.rs | 42 +++++++++++++++++++++++++++++++++++++++++- src/http_client/interface.rs | 4 ++-- src/main.rs | 3 +-- src/ota_plus.rs | 7 +------ 6 files changed, 66 insertions(+), 28 deletions(-) diff --git a/src/auth_plus.rs b/src/auth_plus.rs index a339654..84d26a2 100644 --- a/src/auth_plus.rs +++ b/src/auth_plus.rs @@ -2,9 +2,7 @@ use hyper::header::{Authorization, Basic, ContentType}; use hyper::mime::{Mime, TopLevel, SubLevel, Attr, Value}; use rustc_serialize::json; -use datatype::AccessToken; -use datatype::AuthConfig; -use datatype::Error; +use datatype::{AccessToken, AuthConfig, Error, Url}; use http_client::{HttpClient, HttpRequest}; diff --git a/src/datatype/error.rs b/src/datatype/error.rs index 8117879..f59c76b 100644 --- a/src/datatype/error.rs +++ b/src/datatype/error.rs @@ -4,7 +4,7 @@ use std::convert::From; use std::fmt::{Display, Formatter, Result as FmtResult}; use std::io; use std::path::PathBuf; -use url; +use url::ParseError as UrlParseError; use ws; @@ -20,7 +20,7 @@ pub enum Error { Ota(OtaReason), PackageError(String), ParseError(String), - Url(url::ParseError), + UrlParseError(UrlParseError), Websocket(ws::Error), } @@ -48,6 +48,12 @@ impl From for Error { } } +impl From for Error { + fn from(e: UrlParseError) -> Error { + Error::UrlParseError(e) + } +} + impl From for Error { fn from(e: ws::Error) -> Error { Error::Websocket(e) @@ -81,18 +87,18 @@ pub enum ParseReason { impl Display for Error { fn fmt(&self, f: &mut Formatter) -> FmtResult { let inner: String = match *self { - Error::AuthError(ref s) => format!("Authentication error, {}", s.clone()), - Error::ClientError(ref s) => s.clone(), - Error::Config(ref e) => format!("Failed to {}", e.clone()), - Error::Hyper(ref e) => format!("Hyper error: {}", e.clone()), - Error::Io(ref e) => format!("IO Error{:?}", e.clone()), - Error::Url(ref e) => format!("URL Parse Error{:?}", e.clone()), - Error::JsonDecode(ref e) => format!("Failed to decode JSON: {}", e.clone()), - Error::JsonEncode(ref e) => format!("Failed to encode JSON: {}", e.clone()), - Error::Ota(ref e) => format!("Ota error, {}", e.clone()), - Error::PackageError(ref s) => s.clone(), - Error::ParseError(ref s) => s.clone(), - Error::Websocket(ref e) => format!("Websocket Error{:?}", e.clone()), + Error::AuthError(ref s) => format!("Authentication error, {}", s.clone()), + Error::ClientError(ref s) => s.clone(), + Error::Config(ref e) => format!("Failed to {}", e.clone()), + Error::Hyper(ref e) => format!("Hyper error: {}", e.clone()), + Error::Io(ref e) => format!("IO Error{:?}", e.clone()), + Error::JsonDecode(ref e) => format!("Failed to decode JSON: {}", e.clone()), + Error::JsonEncode(ref e) => format!("Failed to encode JSON: {}", e.clone()), + Error::Ota(ref e) => format!("Ota error, {}", e.clone()), + Error::PackageError(ref s) => s.clone(), + Error::ParseError(ref s) => s.clone(), + Error::UrlParseError(ref s) => format!("Url parse error: {}", s.clone()), + Error::Websocket(ref e) => format!("Websocket Error{:?}", e.clone()), }; write!(f, "{}", inner) } diff --git a/src/datatype/url.rs b/src/datatype/url.rs index 42f0c80..0a3e973 100644 --- a/src/datatype/url.rs +++ b/src/datatype/url.rs @@ -1,3 +1,43 @@ +use hyper::client::IntoUrl; +use hyper; use url; +use url::ParseError; -pub type Url = url::Url; +use datatype::Error; + + +#[derive(RustcDecodable, PartialEq, Eq, Clone, Debug)] +pub struct Url { + url: url::Url +} + +impl Url { + + pub fn parse(s: &str) -> Result { + let url = try!(url::Url::parse(s)); + Ok(Url { url: url }) + } + + pub fn join(&self, suf: &str) -> Result { + let url = try!(self.url.join(suf)); + Ok(Url { url: url }) + } + +} + +impl IntoUrl for Url { + + fn into_url(self) -> Result { + Ok(self.url) + } + +} + + +impl ToString for Url { + + fn to_string(&self) -> String { + self.url.to_string() + } + +} diff --git a/src/http_client/interface.rs b/src/http_client/interface.rs index 4f616e4..df40417 100644 --- a/src/http_client/interface.rs +++ b/src/http_client/interface.rs @@ -5,7 +5,7 @@ use hyper::client::response::Response; use hyper; use std::io::{Read, Write, BufReader, BufWriter}; -use datatype::Error; +use datatype::{Error, Url}; #[derive(Clone, Debug)] @@ -18,7 +18,7 @@ pub struct HttpRequest<'a> { impl<'a> ToString for HttpRequest<'a> { fn to_string(&self) -> String { - format!("{} {}", self.method, self.url.serialize()) + format!("{} {}", self.method, self.url.to_string()) } } diff --git a/src/main.rs b/src/main.rs index f4ce031..a06dc44 100644 --- a/src/main.rs +++ b/src/main.rs @@ -9,7 +9,6 @@ extern crate rustc_serialize; #[macro_use] extern crate libotaplus; use getopts::Options; -use hyper::Url; use std::env; use std::sync::mpsc::{Sender, Receiver, channel}; use std::thread; @@ -18,7 +17,7 @@ use chan_signal::Signal; use chan::Receiver as ChanReceiver; use libotaplus::auth_plus::authenticate; -use libotaplus::datatype::{config, Config, Event, Command, AccessToken}; +use libotaplus::datatype::{config, Config, Event, Command, AccessToken, Url}; use libotaplus::http_client::HttpClient; use libotaplus::interaction_library::broadcast::Broadcast; use libotaplus::interaction_library::console::Console; diff --git a/src/ota_plus.rs b/src/ota_plus.rs index eae8b72..a4afebe 100644 --- a/src/ota_plus.rs +++ b/src/ota_plus.rs @@ -1,4 +1,3 @@ -use hyper::Url; use hyper::header::{Authorization, Bearer, ContentType}; use hyper::mime::{Mime, TopLevel, SubLevel, Attr, Value}; use rustc_serialize::json; @@ -6,12 +5,8 @@ use std::fs::File; use std::path::PathBuf; use std::result::Result; -use datatype::AccessToken; -use datatype::Config; -use datatype::Error; +use datatype::{AccessToken, Config, Error, Package, Url, UpdateRequestId}; use datatype::error::OtaReason::{CreateFile, Client}; -use datatype::Package; -use datatype::UpdateRequestId; use datatype::{UpdateReport, UpdateReportWithVin}; use http_client::{HttpClient, HttpRequest}; -- cgit v1.2.1 From 68b7f8179912224beec0eadbc6a6dd793bf57085 Mon Sep 17 00:00:00 2001 From: Stevan Andjelkovic Date: Mon, 18 Apr 2016 17:31:27 +0200 Subject: Make it possible to rewrite authenticate(). --- src/datatype/url.rs | 13 +++++------ src/http_client/http_client.rs | 30 ++++++++++++++++++++++++- src/http_client/hyper.rs | 51 +++++++++++++++++++++++++++++------------- src/http_client/mod.rs | 2 +- 4 files changed, 71 insertions(+), 25 deletions(-) diff --git a/src/datatype/url.rs b/src/datatype/url.rs index 0a3e973..8d60ee4 100644 --- a/src/datatype/url.rs +++ b/src/datatype/url.rs @@ -8,19 +8,19 @@ use datatype::Error; #[derive(RustcDecodable, PartialEq, Eq, Clone, Debug)] pub struct Url { - url: url::Url + get: url::Url } impl Url { pub fn parse(s: &str) -> Result { let url = try!(url::Url::parse(s)); - Ok(Url { url: url }) + Ok(Url { get: url }) } pub fn join(&self, suf: &str) -> Result { - let url = try!(self.url.join(suf)); - Ok(Url { url: url }) + let url = try!(self.get.join(suf)); + Ok(Url { get: url }) } } @@ -28,16 +28,15 @@ impl Url { impl IntoUrl for Url { fn into_url(self) -> Result { - Ok(self.url) + Ok(self.get) } } - impl ToString for Url { fn to_string(&self) -> String { - self.url.to_string() + self.get.to_string() } } diff --git a/src/http_client/http_client.rs b/src/http_client/http_client.rs index bbe95ab..21485f9 100644 --- a/src/http_client/http_client.rs +++ b/src/http_client/http_client.rs @@ -6,10 +6,38 @@ use tempfile; use datatype::{AccessToken, Error, Method, Url}; +pub struct ClientId { + pub get: String, +} + +pub struct ClientSecret { + pub get: String, +} + +pub enum Auth<'a> { + Credentials(ClientId, ClientSecret), + Token(&'a AccessToken), +} + +impl<'a> Auth<'a> { + + pub fn is_credentials(&self) -> bool { + match *self { + Auth::Credentials(_, _) => true, + Auth::Token(_) => false, + } + } + + pub fn is_token(&self) -> bool { + !self.is_credentials() + } + +} + pub struct HttpRequest2<'a> { pub method: &'a Method, pub url: &'a Url, - pub token: Option<&'a AccessToken>, + pub auth: &'a Auth<'a>, pub body: Option<&'a Json> } diff --git a/src/http_client/hyper.rs b/src/http_client/hyper.rs index f25137a..d2aacd2 100644 --- a/src/http_client/hyper.rs +++ b/src/http_client/hyper.rs @@ -1,11 +1,12 @@ use hyper::Client; -use hyper::header::{Authorization, Bearer, ContentType, Headers}; +use hyper::header::{Authorization, Basic, Bearer, ContentType, Headers}; use hyper::mime::{Attr, Mime, TopLevel, SubLevel, Value}; +use rustc_serialize::json; use std::fs::File; use std::io::{Read, Write, BufReader, BufWriter}; use datatype::Error; -use http_client::{HttpClient2, HttpRequest2}; +use http_client::{Auth, HttpClient2, HttpRequest2}; pub struct Hyper { @@ -17,30 +18,48 @@ impl HttpClient2 for Hyper { fn send_request_to(&self, request: &HttpRequest2, file: &File) -> Result<(), Error> { let mut headers = Headers::new(); - - if let Some(token) = request.token { - headers.set(Authorization(Bearer { - token: token.access_token.clone() - })) + let mut body = String::new(); + + match *request.auth { + Auth::Credentials(ref id, ref secret) => + headers.set(Authorization(Basic { + username: id.get.clone(), + password: Some(secret.get.clone()) + })), + Auth::Token(token) => + headers.set(Authorization(Bearer { + token: token.access_token.clone() + })) } - if request.body.is_some() { + if request.auth.is_credentials() && request.body.is_none() { + + headers.set(ContentType(Mime( + TopLevel::Application, + SubLevel::WwwFormUrlEncoded, + vec![(Attr::Charset, Value::Utf8)]))); + + body.push_str("grant_type=client_credentials") + + } else if request.auth.is_token() && request.body.is_some() { + headers.set(ContentType(Mime( TopLevel::Application, SubLevel::Json, - vec![(Attr::Charset, Value::Utf8)]))) + vec![(Attr::Charset, Value::Utf8)]))); + + let json_str = try!(json::encode(request.body.unwrap())); + + body.push_str(&json_str) + + } else { + panic!("send_request_to has been misused, this is a bug.") } let mut resp = try!(self.client .request(request.method.into(), request.url.clone()) .headers(headers) - .body( - if let Some(body) = request.body - .and_then(|b| b.as_string()) { - body - } else { - "" - }) + .body(&body) .send()); let mut rbody = String::new(); diff --git a/src/http_client/mod.rs b/src/http_client/mod.rs index 1428431..051c084 100644 --- a/src/http_client/mod.rs +++ b/src/http_client/mod.rs @@ -1,7 +1,7 @@ pub use self::mock_http_client::MockHttpClient; pub use self::bad_http_client::BadHttpClient; pub use self::interface::{HttpClient, HttpRequest}; -pub use self::http_client::{HttpClient2, HttpRequest2}; +pub use self::http_client::{Auth, HttpClient2, HttpRequest2}; pub mod bad_http_client; pub mod mock_http_client; -- cgit v1.2.1 From e308ce0a4ae3a27972520282eefe1a57ea4a22de Mon Sep 17 00:00:00 2001 From: Stevan Andjelkovic Date: Tue, 19 Apr 2016 15:52:14 +0200 Subject: Fix warnings. --- src/datatype/method.rs | 5 +++-- src/http_client/http_client.rs | 15 ++++++++++----- src/http_client/hyper.rs | 18 +++++++++++------- src/http_client/mod.rs | 3 ++- src/lib.rs | 1 + 5 files changed, 27 insertions(+), 15 deletions(-) diff --git a/src/datatype/method.rs b/src/datatype/method.rs index ee5badc..967ca28 100644 --- a/src/datatype/method.rs +++ b/src/datatype/method.rs @@ -1,14 +1,15 @@ use hyper::method; +#[derive(Clone)] pub enum Method { Get, Post, } -impl<'a> Into for &'a Method { +impl Into for Method { fn into(self) -> method::Method { - match *self { + match self { Method::Get => method::Method::Get, Method::Post => method::Method::Post, } diff --git a/src/http_client/http_client.rs b/src/http_client/http_client.rs index 21485f9..5ee56c9 100644 --- a/src/http_client/http_client.rs +++ b/src/http_client/http_client.rs @@ -1,4 +1,3 @@ -use rustc_serialize::json::Json; use std::fs::File; use std::io::{Write, Read}; use tempfile; @@ -38,21 +37,27 @@ pub struct HttpRequest2<'a> { pub method: &'a Method, pub url: &'a Url, pub auth: &'a Auth<'a>, - pub body: Option<&'a Json> + pub body: Option<&'a str> } pub trait HttpClient2 { - fn send_request_to(&self, request: &HttpRequest2, file: &File) -> Result<(), Error>; + fn send_request_to(&self, request: &HttpRequest2, file: &mut File) -> Result<(), Error> { + + let s = try!(Self::send_request(self, request)); + + Ok(try!(file.write_all(&s.as_bytes()))) + + } fn send_request(&self, request: &HttpRequest2) -> Result { let mut temp_file: File = try!(tempfile::tempfile()); - try!(Self::send_request_to(self, request, &temp_file)); + try!(Self::send_request_to(self, request, &mut temp_file)); let mut buf = String::new(); - temp_file.read_to_string(&mut buf); + let _: usize = try!(temp_file.read_to_string(&mut buf)); Ok(buf) diff --git a/src/http_client/hyper.rs b/src/http_client/hyper.rs index d2aacd2..cf54a20 100644 --- a/src/http_client/hyper.rs +++ b/src/http_client/hyper.rs @@ -15,7 +15,7 @@ pub struct Hyper { impl HttpClient2 for Hyper { - fn send_request_to(&self, request: &HttpRequest2, file: &File) -> Result<(), Error> { + fn send_request_to(&self, request: &HttpRequest2, file: &mut File) -> Result<(), Error> { let mut headers = Headers::new(); let mut body = String::new(); @@ -48,28 +48,32 @@ impl HttpClient2 for Hyper { SubLevel::Json, vec![(Attr::Charset, Value::Utf8)]))); - let json_str = try!(json::encode(request.body.unwrap())); + let json = try!(json::encode(&request.body.unwrap())); - body.push_str(&json_str) + body.push_str(&json) } else { panic!("send_request_to has been misused, this is a bug.") } let mut resp = try!(self.client - .request(request.method.into(), request.url.clone()) + .request(request.method.clone().into(), request.url.clone()) .headers(headers) .body(&body) .send()); - let mut rbody = String::new(); - let status = resp.status; + let status = resp.status; if status.is_server_error() || status.is_client_error() { Err(Error::ClientError(format!("Request errored with status {}", status))) } else { - tee(resp, file); + + let mut rbody = String::new(); + let _: usize = try!(resp.read_to_string(&mut rbody)); + + try!(tee(rbody.as_bytes(), file)); Ok(()) + } } diff --git a/src/http_client/mod.rs b/src/http_client/mod.rs index 051c084..8847304 100644 --- a/src/http_client/mod.rs +++ b/src/http_client/mod.rs @@ -1,7 +1,8 @@ pub use self::mock_http_client::MockHttpClient; pub use self::bad_http_client::BadHttpClient; pub use self::interface::{HttpClient, HttpRequest}; -pub use self::http_client::{Auth, HttpClient2, HttpRequest2}; +pub use self::http_client::{Auth, ClientId, ClientSecret, HttpClient2, + HttpRequest2}; pub mod bad_http_client; pub mod mock_http_client; diff --git a/src/lib.rs b/src/lib.rs index 3645a12..aeb40e5 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -13,6 +13,7 @@ pub mod datatype; pub mod http_client; pub mod interaction_library; pub mod interpreter; +pub mod new_auth_plus; pub mod new_interpreter; pub mod new_ota_plus; pub mod ota_plus; -- cgit v1.2.1 From 01a5cbc6223afb1d2d4ce08c5860327a92ff829c Mon Sep 17 00:00:00 2001 From: Stevan Andjelkovic Date: Tue, 19 Apr 2016 16:48:01 +0200 Subject: Add new auth and ota. --- src/new_auth_plus.rs | 101 ++++++++++++++++++++++++++++ src/new_ota_plus.rs | 185 +++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 286 insertions(+) create mode 100644 src/new_auth_plus.rs create mode 100644 src/new_ota_plus.rs diff --git a/src/new_auth_plus.rs b/src/new_auth_plus.rs new file mode 100644 index 0000000..e1a5350 --- /dev/null +++ b/src/new_auth_plus.rs @@ -0,0 +1,101 @@ +use rustc_serialize::json; + +use datatype::{AccessToken, AuthConfig, Error, Method}; +use http_client::{Auth, ClientId, ClientSecret, HttpClient2, HttpRequest2}; + + +pub fn authenticate(config: &AuthConfig, client: &HttpClient2) -> Result { + + let req = HttpRequest2 { + method: &Method::Post, + url: &config.server.join("/token").unwrap(), + auth: &Auth::Credentials( + ClientId { get: config.client_id.clone() }, + ClientSecret { get: config.secret.clone() }), + body: None, + }; + + let body = try!(client.send_request(&req)); + + Ok(try!(json::decode(&body))) + +} + +/* + +#[cfg(test)] +mod tests { + + use std::io::Write; + + use super::*; + use datatype::AccessToken; + use datatype::AuthConfig; + use datatype::Error; + use http_client::BadHttpClient; + use http_client::{HttpRequest, HttpClient}; + + + struct MockClient; + + impl HttpClient for MockClient { + + fn new() -> MockClient { + MockClient + } + + fn send_request(&self, _: &HttpRequest) -> Result { + Ok(r#"{"access_token": "token", + "token_type": "type", + "expires_in": 10, + "scope": ["scope"]}"#.to_string()) + } + + fn send_request_to(&self, _: &HttpRequest, _: W) -> Result<(), Error> { + Ok(()) + } + } + + #[test] + fn test_authenticate() { + assert_eq!(authenticate::(&AuthConfig::default()).unwrap(), + AccessToken { + access_token: "token".to_string(), + token_type: "type".to_string(), + expires_in: 10, + scope: vec!["scope".to_string()] + }) + } + + #[test] + fn test_authenticate_bad_client() { + assert_eq!(format!("{}", authenticate::(&AuthConfig::default()).unwrap_err()), + "Authentication error, didn't receive access token: bad client.") + } + + #[test] + fn test_authenticate_bad_json_client() { + + struct BadJsonClient; + + impl HttpClient for BadJsonClient { + + fn new() -> BadJsonClient { + BadJsonClient + } + + fn send_request(&self, _: &HttpRequest) -> Result { + Ok(r#"{"apa": 1}"#.to_string()) + } + + fn send_request_to(&self, _: &HttpRequest, _: W) -> Result<(), Error> { + Ok(()) + } + } + + assert_eq!(format!("{}", authenticate::(&AuthConfig::default()).unwrap_err()), + r#"Failed to decode JSON: MissingFieldError("access_token")"#) + } + +} +*/ diff --git a/src/new_ota_plus.rs b/src/new_ota_plus.rs new file mode 100644 index 0000000..78b3c01 --- /dev/null +++ b/src/new_ota_plus.rs @@ -0,0 +1,185 @@ +use rustc_serialize::json; +use std::fs::File; +use std::path::PathBuf; + +use datatype::{AccessToken, Config, Error, Url, UpdateRequestId, + UpdateReport, UpdateReportWithVin, Method, Package}; +use http_client::{Auth, HttpClient2, HttpRequest2}; + + +fn vehicle_endpoint(config: &Config, s: &str) -> Url { + config.ota.server.join(&format!("/api/v1/vehicles/{}/{}", config.auth.vin, s)).unwrap() +} + +pub fn download_package_update(config: &Config, + client: &HttpClient2, + token: &AccessToken, + id: &UpdateRequestId) -> Result { + + let req = HttpRequest2 { + method: &Method::Get, + url: &vehicle_endpoint(config, &format!("updates/{}/download", id)), + auth: &Auth::Token(token), + body: None, + }; + + let mut path = PathBuf::new(); + path.push(&config.ota.packages_dir); + path.push(id); + path.set_extension(config.ota.package_manager.extension()); + + let mut file = try!(File::create(path.as_path())); + + try!(client.send_request_to(&req, &mut file)); + + return Ok(path) + +} + +pub fn send_install_report(config: &Config, + client: &HttpClient2, + token: &AccessToken, + report: &UpdateReport) -> Result<(), Error> { + + let report_with_vin = UpdateReportWithVin::new(&config.auth.vin, &report); + let json = try!(json::encode(&report_with_vin)); + + let req = HttpRequest2 { + method: &Method::Post, + url: &vehicle_endpoint(config, &format!("/updates/{}", report.update_id)), + auth: &Auth::Token(token), + body: Some(&json) + }; + + let _: String = try!(client.send_request(&req)); + + return Ok(()) + +} + +pub fn get_package_updates(config: &Config, + client: &HttpClient2, + token: &AccessToken) -> Result, Error> { + + let req = HttpRequest2 { + method: &Method::Get, + url: &vehicle_endpoint(&config, "/updates"), + auth: &Auth::Token(token), + body: None, + }; + + let resp = try!(client.send_request(&req)); + + return Ok(try!(json::decode::>(&resp))); + +} + +pub fn post_packages(config: &Config, + client: &HttpClient2, + token: &AccessToken, + pkgs: &Vec) -> Result<(), Error> { + + let json = try!(json::encode(&pkgs)); + + let req = HttpRequest2 { + method: &Method::Post, + url: &vehicle_endpoint(config, "/updates"), + auth: &Auth::Token(token), + body: Some(&json), + }; + + let _: String = try!(client.send_request(&req)); + + return Ok(()) +} + +/* + +#[cfg(test)] +mod tests { + + use std::io::Write; + + use super::*; + use datatype::AccessToken; + use datatype::{Config, OtaConfig}; + use datatype::Error; + use datatype::Package; + use http_client::BadHttpClient; + use http_client::{HttpRequest, HttpClient}; + + + fn test_token() -> AccessToken { + AccessToken { + access_token: "token".to_string(), + token_type: "bar".to_string(), + expires_in: 20, + scope: vec![] + } + } + + fn test_package() -> Package { + Package { + name: "hey".to_string(), + version: "1.2.3".to_string() + } + } + + struct MockClient; + + impl HttpClient for MockClient { + + fn new() -> MockClient { + MockClient + } + + fn send_request(&self, _: &HttpRequest) -> Result { + return Ok("[\"pkgid\"]".to_string()) + } + + fn send_request_to(&self, _: &HttpRequest, _: W) -> Result<(), Error> { + return Ok(()) + } + + } + + #[test] + fn test_post_packages_sends_authentication() { + assert_eq!( + post_packages::(&test_token(), &Config::default(), &vec![test_package()]) + .unwrap(), ()) + } + + #[test] + fn test_get_package_updates() { + assert_eq!(get_package_updates::(&test_token(), &Config::default()).unwrap(), + vec!["pkgid".to_string()]) + } + + #[test] + #[ignore] // TODO: docker daemon requires user namespaces for this to work + fn bad_packages_dir_download_package_update() { + let mut config = Config::default(); + config.ota = OtaConfig { packages_dir: "/".to_string(), .. config.ota }; + + assert_eq!( + format!("{}", + download_package_update::(&test_token(), &config, &"0".to_string()) + .unwrap_err()), + r#"Ota error, failed to create file "/0.deb": Permission denied (os error 13)"#) + } + + #[test] + fn bad_client_download_package_update() { + assert_eq!( + format!("{}", + download_package_update:: + (&test_token(), &Config::default(), &"0".to_string()) + .unwrap_err()), + r#"Ota error, the request: GET http://127.0.0.1:8080/api/v1/vehicles/V1234567890123456/updates/0/download, +results in the following error: bad client."#) + } + +} + +*/ -- cgit v1.2.1 From 06d7f5e09e88a2a04d48e1695ae415d0a535e703 Mon Sep 17 00:00:00 2001 From: Stevan Andjelkovic Date: Wed, 20 Apr 2016 15:18:15 +0200 Subject: Use Cow in http request. --- src/datatype/method.rs | 7 +++++ src/datatype/url.rs | 8 ++++++ src/http_client/http_client.rs | 58 +++++++++++++++++++++++++++++++++++++++--- src/http_client/hyper.rs | 5 ++-- src/new_auth_plus.rs | 13 +++++----- src/new_interpreter.rs | 20 +++++++++++++-- src/new_ota_plus.rs | 44 ++++++++++++++------------------ 7 files changed, 115 insertions(+), 40 deletions(-) diff --git a/src/datatype/method.rs b/src/datatype/method.rs index 967ca28..bc5e73b 100644 --- a/src/datatype/method.rs +++ b/src/datatype/method.rs @@ -1,3 +1,4 @@ +use std::borrow::Cow; use hyper::method; @@ -15,3 +16,9 @@ impl Into for Method { } } } + +impl<'a> Into> for Method { + fn into(self) -> Cow<'a, Method> { + Cow::Owned(self) + } +} diff --git a/src/datatype/url.rs b/src/datatype/url.rs index 8d60ee4..5c8a41a 100644 --- a/src/datatype/url.rs +++ b/src/datatype/url.rs @@ -1,3 +1,4 @@ +use std::borrow::Cow; use hyper::client::IntoUrl; use hyper; use url; @@ -33,6 +34,13 @@ impl IntoUrl for Url { } +impl<'a> Into> for Url { + fn into(self) -> Cow<'a, Url> { + Cow::Owned(self) + } +} + + impl ToString for Url { fn to_string(&self) -> String { diff --git a/src/http_client/http_client.rs b/src/http_client/http_client.rs index 5ee56c9..bc8b374 100644 --- a/src/http_client/http_client.rs +++ b/src/http_client/http_client.rs @@ -1,3 +1,4 @@ +use std::borrow::Cow; use std::fs::File; use std::io::{Write, Read}; use tempfile; @@ -5,21 +6,31 @@ use tempfile; use datatype::{AccessToken, Error, Method, Url}; +#[derive(Clone)] pub struct ClientId { pub get: String, } +#[derive(Clone)] pub struct ClientSecret { pub get: String, } +#[derive(Clone)] pub enum Auth<'a> { Credentials(ClientId, ClientSecret), Token(&'a AccessToken), } +impl<'a> Into>> for Auth<'a> { + fn into(self) -> Cow<'a, Auth<'a>> { + Cow::Owned(self) + } +} + impl<'a> Auth<'a> { + //XXX: remove pub fn is_credentials(&self) -> bool { match *self { Auth::Credentials(_, _) => true, @@ -34,10 +45,49 @@ impl<'a> Auth<'a> { } pub struct HttpRequest2<'a> { - pub method: &'a Method, - pub url: &'a Url, - pub auth: &'a Auth<'a>, - pub body: Option<&'a str> + pub method: Cow<'a, Method>, + pub url: Cow<'a, Url>, + pub auth: Cow<'a, Auth<'a>>, + pub body: Option>, +} + +impl<'a> HttpRequest2<'a> { + + fn new(meth: M, + url: U, + auth: A, + body: Option) -> HttpRequest2<'a> + where + M: Into>, + U: Into>, + A: Into>>, + B: Into> + { + HttpRequest2 { + method: meth.into(), + url: url.into(), + auth: auth.into(), + body: body.map(|c| c.into()), + } + } + + pub fn get(url: U, auth: A) -> HttpRequest2<'a> + where + U: Into>, + A: Into>>, + { + HttpRequest2::new::<_, _, _, String>(Method::Get, url, auth, None) + } + + pub fn post(url: U, auth: A, body: Option) -> HttpRequest2<'a> + where + U: Into>, + A: Into>>, + B: Into> + { + HttpRequest2::new(Method::Post, url, auth, body) + } + } pub trait HttpClient2 { diff --git a/src/http_client/hyper.rs b/src/http_client/hyper.rs index cf54a20..0cc446f 100644 --- a/src/http_client/hyper.rs +++ b/src/http_client/hyper.rs @@ -48,7 +48,7 @@ impl HttpClient2 for Hyper { SubLevel::Json, vec![(Attr::Charset, Value::Utf8)]))); - let json = try!(json::encode(&request.body.unwrap())); + let json = try!(json::encode(&request.body.to_owned().unwrap())); body.push_str(&json) @@ -57,7 +57,8 @@ impl HttpClient2 for Hyper { } let mut resp = try!(self.client - .request(request.method.clone().into(), request.url.clone()) + .request(request.method.clone().into_owned().into(), + request.url.clone().into_owned()) .headers(headers) .body(&body) .send()); diff --git a/src/new_auth_plus.rs b/src/new_auth_plus.rs index e1a5350..88bc02b 100644 --- a/src/new_auth_plus.rs +++ b/src/new_auth_plus.rs @@ -1,19 +1,18 @@ use rustc_serialize::json; -use datatype::{AccessToken, AuthConfig, Error, Method}; +use datatype::{AccessToken, AuthConfig, Error}; use http_client::{Auth, ClientId, ClientSecret, HttpClient2, HttpRequest2}; pub fn authenticate(config: &AuthConfig, client: &HttpClient2) -> Result { - let req = HttpRequest2 { - method: &Method::Post, - url: &config.server.join("/token").unwrap(), - auth: &Auth::Credentials( + let req = HttpRequest2::post::<_, _, String>( + config.server.join("/token").unwrap(), + Auth::Credentials( ClientId { get: config.client_id.clone() }, ClientSecret { get: config.secret.clone() }), - body: None, - }; + None, + ); let body = try!(client.send_request(&req)); diff --git a/src/new_interpreter.rs b/src/new_interpreter.rs index ea7ce2b..50b6118 100644 --- a/src/new_interpreter.rs +++ b/src/new_interpreter.rs @@ -25,12 +25,28 @@ impl<'a> Interpreter, Command, Event> for OurInterpreter { #[allow(unused_variables)] fn interpret(env: &Env, cmd: Command, rx: Sender) { match cmd { - GetPendingUpdates => unimplemented!(), - PostInstalledPackages => unimplemented!(), AcceptUpdate(ref id) => unimplemented!(), + GetPendingUpdates => unimplemented!(), ListInstalledPackages => unimplemented!(), + PostInstalledPackages => unimplemented!(), Shutdown => unimplemented!(), } } +/* + fn get_installed_packages(&self) -> Result, Error> { + self.config.ota.package_manager.installed_packages() + } + fn post_installed_packages(&self) { + let _ = self.get_installed_packages().and_then(|pkgs| { + debug!("Found installed packages in the system: {:?}", pkgs); + post_packages::(&self.token, &self.config, &pkgs) + }).map(|_| { + info!("Posted installed packages to the server."); + }).map_err(|e| { + error!("Error fetching/posting installed packages: {:?}.", e); + }); + } +*/ + } diff --git a/src/new_ota_plus.rs b/src/new_ota_plus.rs index 78b3c01..8fe0a38 100644 --- a/src/new_ota_plus.rs +++ b/src/new_ota_plus.rs @@ -3,7 +3,7 @@ use std::fs::File; use std::path::PathBuf; use datatype::{AccessToken, Config, Error, Url, UpdateRequestId, - UpdateReport, UpdateReportWithVin, Method, Package}; + UpdateReport, UpdateReportWithVin, Package}; use http_client::{Auth, HttpClient2, HttpRequest2}; @@ -16,12 +16,10 @@ pub fn download_package_update(config: &Config, token: &AccessToken, id: &UpdateRequestId) -> Result { - let req = HttpRequest2 { - method: &Method::Get, - url: &vehicle_endpoint(config, &format!("updates/{}/download", id)), - auth: &Auth::Token(token), - body: None, - }; + let req = HttpRequest2::get( + vehicle_endpoint(config, &format!("updates/{}/download", id)), + Auth::Token(token), + ); let mut path = PathBuf::new(); path.push(&config.ota.packages_dir); @@ -44,12 +42,11 @@ pub fn send_install_report(config: &Config, let report_with_vin = UpdateReportWithVin::new(&config.auth.vin, &report); let json = try!(json::encode(&report_with_vin)); - let req = HttpRequest2 { - method: &Method::Post, - url: &vehicle_endpoint(config, &format!("/updates/{}", report.update_id)), - auth: &Auth::Token(token), - body: Some(&json) - }; + let req = HttpRequest2::post( + vehicle_endpoint(config, &format!("/updates/{}", report.update_id)), + Auth::Token(token), + Some(json) + ); let _: String = try!(client.send_request(&req)); @@ -61,12 +58,10 @@ pub fn get_package_updates(config: &Config, client: &HttpClient2, token: &AccessToken) -> Result, Error> { - let req = HttpRequest2 { - method: &Method::Get, - url: &vehicle_endpoint(&config, "/updates"), - auth: &Auth::Token(token), - body: None, - }; + let req = HttpRequest2::get( + vehicle_endpoint(&config, "/updates"), + Auth::Token(token), + ); let resp = try!(client.send_request(&req)); @@ -81,12 +76,11 @@ pub fn post_packages(config: &Config, let json = try!(json::encode(&pkgs)); - let req = HttpRequest2 { - method: &Method::Post, - url: &vehicle_endpoint(config, "/updates"), - auth: &Auth::Token(token), - body: Some(&json), - }; + let req = HttpRequest2::post( + vehicle_endpoint(config, "/updates"), + Auth::Token(token), + Some(json), + ); let _: String = try!(client.send_request(&req)); -- cgit v1.2.1 From c9f41e94c255402f2e3af87dfebed7815ad63844 Mon Sep 17 00:00:00 2001 From: Stevan Andjelkovic Date: Wed, 20 Apr 2016 15:33:36 +0200 Subject: Remove is_credentials() and is_token() in favour of match, thanks @txus. --- src/http_client/http_client.rs | 16 -------------- src/http_client/hyper.rs | 49 ++++++++++++++++++++++-------------------- 2 files changed, 26 insertions(+), 39 deletions(-) diff --git a/src/http_client/http_client.rs b/src/http_client/http_client.rs index bc8b374..c10ea28 100644 --- a/src/http_client/http_client.rs +++ b/src/http_client/http_client.rs @@ -28,22 +28,6 @@ impl<'a> Into>> for Auth<'a> { } } -impl<'a> Auth<'a> { - - //XXX: remove - pub fn is_credentials(&self) -> bool { - match *self { - Auth::Credentials(_, _) => true, - Auth::Token(_) => false, - } - } - - pub fn is_token(&self) -> bool { - !self.is_credentials() - } - -} - pub struct HttpRequest2<'a> { pub method: Cow<'a, Method>, pub url: Cow<'a, Url>, diff --git a/src/http_client/hyper.rs b/src/http_client/hyper.rs index 0cc446f..f037679 100644 --- a/src/http_client/hyper.rs +++ b/src/http_client/hyper.rs @@ -20,40 +20,43 @@ impl HttpClient2 for Hyper { let mut headers = Headers::new(); let mut body = String::new(); - match *request.auth { - Auth::Credentials(ref id, ref secret) => + match (request.auth.clone().into_owned(), request.body.to_owned()) { + + (Auth::Credentials(ref id, ref secret), None) => { + headers.set(Authorization(Basic { username: id.get.clone(), password: Some(secret.get.clone()) - })), - Auth::Token(token) => - headers.set(Authorization(Bearer { - token: token.access_token.clone() - })) - } + })); - if request.auth.is_credentials() && request.body.is_none() { + headers.set(ContentType(Mime( + TopLevel::Application, + SubLevel::WwwFormUrlEncoded, + vec![(Attr::Charset, Value::Utf8)]))); - headers.set(ContentType(Mime( - TopLevel::Application, - SubLevel::WwwFormUrlEncoded, - vec![(Attr::Charset, Value::Utf8)]))); + body.push_str("grant_type=client_credentials") - body.push_str("grant_type=client_credentials") + } - } else if request.auth.is_token() && request.body.is_some() { + (Auth::Token(token), Some(body)) => { - headers.set(ContentType(Mime( - TopLevel::Application, - SubLevel::Json, - vec![(Attr::Charset, Value::Utf8)]))); + headers.set(Authorization(Bearer { + token: token.access_token.clone() + })); - let json = try!(json::encode(&request.body.to_owned().unwrap())); + headers.set(ContentType(Mime( + TopLevel::Application, + SubLevel::Json, + vec![(Attr::Charset, Value::Utf8)]))); - body.push_str(&json) + let json: String = try!(json::encode(&body)); + + body.into_owned().push_str(&json) + + } + + _ => panic!("hyper's send_request_to has been misused, this is a bug.") - } else { - panic!("send_request_to has been misused, this is a bug.") } let mut resp = try!(self.client -- cgit v1.2.1 From c02b2c8c8b7d0e389492d11e5cfb4a643af8b705 Mon Sep 17 00:00:00 2001 From: Stevan Andjelkovic Date: Wed, 20 Apr 2016 16:00:15 +0200 Subject: Fix merge problems and use is_success rather than !is_failure. --- src/auth_plus.rs | 2 +- src/datatype/error.rs | 6 ------ src/http_client/hyper.rs | 8 +++----- src/lib.rs | 1 - src/new_interpreter.rs | 2 -- 5 files changed, 4 insertions(+), 15 deletions(-) diff --git a/src/auth_plus.rs b/src/auth_plus.rs index 84d26a2..10ada30 100644 --- a/src/auth_plus.rs +++ b/src/auth_plus.rs @@ -2,7 +2,7 @@ use hyper::header::{Authorization, Basic, ContentType}; use hyper::mime::{Mime, TopLevel, SubLevel, Attr, Value}; use rustc_serialize::json; -use datatype::{AccessToken, AuthConfig, Error, Url}; +use datatype::{AccessToken, AuthConfig, Error}; use http_client::{HttpClient, HttpRequest}; diff --git a/src/datatype/error.rs b/src/datatype/error.rs index f59c76b..7871dcb 100644 --- a/src/datatype/error.rs +++ b/src/datatype/error.rs @@ -60,12 +60,6 @@ impl From for Error { } } -impl From for Error { - fn from(e: url::ParseError) -> Error { - Error::Url(e) - } -} - #[derive(Debug)] pub enum OtaReason { CreateFile(PathBuf, io::Error), diff --git a/src/http_client/hyper.rs b/src/http_client/hyper.rs index f037679..885bd42 100644 --- a/src/http_client/hyper.rs +++ b/src/http_client/hyper.rs @@ -66,11 +66,7 @@ impl HttpClient2 for Hyper { .body(&body) .send()); - let status = resp.status; - - if status.is_server_error() || status.is_client_error() { - Err(Error::ClientError(format!("Request errored with status {}", status))) - } else { + if resp.status.is_success() { let mut rbody = String::new(); let _: usize = try!(resp.read_to_string(&mut rbody)); @@ -78,6 +74,8 @@ impl HttpClient2 for Hyper { try!(tee(rbody.as_bytes(), file)); Ok(()) + } else { + Err(Error::ClientError(format!("Request errored with status {}", resp.status))) } } diff --git a/src/lib.rs b/src/lib.rs index aeb40e5..85b96e2 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -6,7 +6,6 @@ extern crate tempfile; extern crate toml; extern crate url; extern crate ws; -extern crate url; pub mod auth_plus; pub mod datatype; diff --git a/src/new_interpreter.rs b/src/new_interpreter.rs index 50b6118..a73bed0 100644 --- a/src/new_interpreter.rs +++ b/src/new_interpreter.rs @@ -5,8 +5,6 @@ use datatype::Command::*; use package_manager::PackageManager; use http_client::HttpClient2; use interaction_library::interpreter::Interpreter; -use interaction_library::interpreter; -use package_manager::PackageManager; pub struct OurInterpreter; -- cgit v1.2.1 From f5bd372266db673cdf41031eb21792b49aff4346 Mon Sep 17 00:00:00 2001 From: Stevan Andjelkovic Date: Thu, 21 Apr 2016 17:24:34 +0200 Subject: More work on interpreter. --- src/datatype/access_token.rs | 8 ++ src/datatype/command.rs | 5 +- src/datatype/error.rs | 35 ++++++--- src/datatype/event.rs | 4 +- src/datatype/mod.rs | 2 + src/http_client/http_client.rs | 12 +-- src/http_client/mod.rs | 3 +- src/interaction_library/interpreter.rs | 6 +- src/interpreter.rs | 1 + src/main.rs | 4 +- src/new_auth_plus.rs | 4 +- src/new_interpreter.rs | 140 +++++++++++++++++++++++++++------ 12 files changed, 163 insertions(+), 61 deletions(-) diff --git a/src/datatype/access_token.rs b/src/datatype/access_token.rs index 6a10f99..a9a631a 100644 --- a/src/datatype/access_token.rs +++ b/src/datatype/access_token.rs @@ -1,3 +1,5 @@ +use std::borrow::Cow; + #[derive(RustcDecodable, Debug, PartialEq, Clone)] pub struct AccessToken { @@ -6,3 +8,9 @@ pub struct AccessToken { pub expires_in: i32, pub scope: Vec } + +impl<'a> Into> for AccessToken { + fn into(self) -> Cow<'a, AccessToken> { + Cow::Owned(self) + } +} diff --git a/src/datatype/command.rs b/src/datatype/command.rs index cbf4eeb..47b3ac4 100644 --- a/src/datatype/command.rs +++ b/src/datatype/command.rs @@ -1,11 +1,14 @@ use rustc_serialize::{Encodable}; use std::str::FromStr; -use datatype::UpdateRequestId; +use datatype::{ClientCredentials, UpdateRequestId}; #[derive(RustcDecodable, RustcEncodable, PartialEq, Eq, Debug)] pub enum Command { + + Authenticate(Option), + // UI GetPendingUpdates, AcceptUpdate(UpdateRequestId), diff --git a/src/datatype/error.rs b/src/datatype/error.rs index 7871dcb..47e7350 100644 --- a/src/datatype/error.rs +++ b/src/datatype/error.rs @@ -4,9 +4,12 @@ use std::convert::From; use std::fmt::{Display, Formatter, Result as FmtResult}; use std::io; use std::path::PathBuf; +use std::sync::mpsc::SendError; use url::ParseError as UrlParseError; use ws; +use datatype::Event; + #[derive(Debug)] pub enum Error { @@ -20,6 +23,7 @@ pub enum Error { Ota(OtaReason), PackageError(String), ParseError(String), + SendErrorEvent(SendError), UrlParseError(UrlParseError), Websocket(ws::Error), } @@ -48,6 +52,12 @@ impl From for Error { } } +impl From> for Error { + fn from(e: SendError) -> Error { + Error::SendErrorEvent(e) + } +} + impl From for Error { fn from(e: UrlParseError) -> Error { Error::UrlParseError(e) @@ -81,18 +91,19 @@ pub enum ParseReason { impl Display for Error { fn fmt(&self, f: &mut Formatter) -> FmtResult { let inner: String = match *self { - Error::AuthError(ref s) => format!("Authentication error, {}", s.clone()), - Error::ClientError(ref s) => s.clone(), - Error::Config(ref e) => format!("Failed to {}", e.clone()), - Error::Hyper(ref e) => format!("Hyper error: {}", e.clone()), - Error::Io(ref e) => format!("IO Error{:?}", e.clone()), - Error::JsonDecode(ref e) => format!("Failed to decode JSON: {}", e.clone()), - Error::JsonEncode(ref e) => format!("Failed to encode JSON: {}", e.clone()), - Error::Ota(ref e) => format!("Ota error, {}", e.clone()), - Error::PackageError(ref s) => s.clone(), - Error::ParseError(ref s) => s.clone(), - Error::UrlParseError(ref s) => format!("Url parse error: {}", s.clone()), - Error::Websocket(ref e) => format!("Websocket Error{:?}", e.clone()), + Error::AuthError(ref s) => format!("Authentication error, {}", s.clone()), + Error::ClientError(ref s) => s.clone(), + Error::Config(ref e) => format!("Failed to {}", e.clone()), + Error::Hyper(ref e) => format!("Hyper error: {}", e.clone()), + Error::Io(ref e) => format!("IO Error{:?}", e.clone()), + Error::JsonDecode(ref e) => format!("Failed to decode JSON: {}", e.clone()), + Error::JsonEncode(ref e) => format!("Failed to encode JSON: {}", e.clone()), + Error::Ota(ref e) => format!("Ota error, {}", e.clone()), + Error::PackageError(ref s) => s.clone(), + Error::ParseError(ref s) => s.clone(), + Error::SendErrorEvent(ref s) => format!("Send error for Event: {}", s.clone()), + Error::UrlParseError(ref s) => format!("Url parse error: {}", s.clone()), + Error::Websocket(ref e) => format!("Websocket Error{:?}", e.clone()), }; write!(f, "{}", inner) } diff --git a/src/datatype/event.rs b/src/datatype/event.rs index 53c96d6..283322d 100644 --- a/src/datatype/event.rs +++ b/src/datatype/event.rs @@ -1,11 +1,11 @@ -use rustc_serialize::{Encodable}; use std::string::ToString; use datatype::{UpdateRequestId, UpdateState, Package}; -#[derive(RustcDecodable, RustcEncodable, PartialEq, Eq, Debug, Clone)] +#[derive(RustcEncodable, Debug, Clone)] pub enum Event { + NotAuthenticated, NewUpdateAvailable(UpdateRequestId), UpdateStateChanged(UpdateRequestId, UpdateState), UpdateErrored(UpdateRequestId, String), diff --git a/src/datatype/mod.rs b/src/datatype/mod.rs index c154b76..a58eb90 100644 --- a/src/datatype/mod.rs +++ b/src/datatype/mod.rs @@ -1,4 +1,5 @@ pub use self::access_token::AccessToken; +pub use self::client_credentials::{ClientId, ClientSecret, ClientCredentials}; pub use self::command::Command; pub use self::config::{Config, AuthConfig, OtaConfig, TestConfig}; pub use self::error::Error; @@ -10,6 +11,7 @@ pub use self::update_request::{UpdateRequestId, UpdateState}; pub use self::url::Url; pub mod access_token; +pub mod client_credentials; pub mod command; pub mod config; pub mod error; diff --git a/src/http_client/http_client.rs b/src/http_client/http_client.rs index c10ea28..c2492bd 100644 --- a/src/http_client/http_client.rs +++ b/src/http_client/http_client.rs @@ -3,19 +3,9 @@ use std::fs::File; use std::io::{Write, Read}; use tempfile; -use datatype::{AccessToken, Error, Method, Url}; +use datatype::{AccessToken, ClientId, ClientSecret, Error, Method, Url}; -#[derive(Clone)] -pub struct ClientId { - pub get: String, -} - -#[derive(Clone)] -pub struct ClientSecret { - pub get: String, -} - #[derive(Clone)] pub enum Auth<'a> { Credentials(ClientId, ClientSecret), diff --git a/src/http_client/mod.rs b/src/http_client/mod.rs index 8847304..051c084 100644 --- a/src/http_client/mod.rs +++ b/src/http_client/mod.rs @@ -1,8 +1,7 @@ pub use self::mock_http_client::MockHttpClient; pub use self::bad_http_client::BadHttpClient; pub use self::interface::{HttpClient, HttpRequest}; -pub use self::http_client::{Auth, ClientId, ClientSecret, HttpClient2, - HttpRequest2}; +pub use self::http_client::{Auth, HttpClient2, HttpRequest2}; pub mod bad_http_client; pub mod mock_http_client; diff --git a/src/interaction_library/interpreter.rs b/src/interaction_library/interpreter.rs index 1256632..5d9d8ef 100644 --- a/src/interaction_library/interpreter.rs +++ b/src/interaction_library/interpreter.rs @@ -3,12 +3,12 @@ use std::sync::mpsc::{Sender, Receiver}; pub trait Interpreter { - fn interpret(env: &Env, c: C, e: Sender); + fn interpret(env: &mut Env, c: C, e: Sender); - fn run(env: &Env, rx: Receiver, tx: Sender) { + fn run(env: &mut Env, rx: Receiver, tx: Sender) { loop { match rx.recv() { - Ok(c) => Self::interpret(&env, c, tx.clone()), + Ok(c) => Self::interpret(env, c, tx.clone()), Err(e) => error!("Error receiving command: {:?}", e) } } diff --git a/src/interpreter.rs b/src/interpreter.rs index 3a3ce85..dc6b126 100644 --- a/src/interpreter.rs +++ b/src/interpreter.rs @@ -31,6 +31,7 @@ impl<'a, C: HttpClient> Interpreter<'a, C> { pub fn interpret(&self, command: Command) { match command { + Command::Authenticate(_) => unimplemented!(), Command::GetPendingUpdates => self.get_pending_updates(), Command::PostInstalledPackages => self.post_installed_packages(), Command::AcceptUpdate(ref id) => self.accept_update(id), diff --git a/src/main.rs b/src/main.rs index a06dc44..5d1340f 100644 --- a/src/main.rs +++ b/src/main.rs @@ -49,7 +49,7 @@ fn spawn_interpreter(config: Config, token: AccessToken, crx: Receiver, fn spawn_autoacceptor(erx: Receiver, ctx: Sender) { spawn_thread!("Autoacceptor of software updates", { - AutoAcceptor::run(&(), erx, ctx); + AutoAcceptor::run(&mut (), erx, ctx); }); } @@ -88,7 +88,7 @@ fn start_event_broadcasting(broadcast: Broadcast) { struct AutoAcceptor; impl InteractionInterpreter<(), Event, Command> for AutoAcceptor { - fn interpret(_: &(), e: Event, ctx: Sender) { + fn interpret(_: &mut (), e: Event, ctx: Sender) { fn f(e: &Event, ctx: Sender) { match e { &Event::NewUpdateAvailable(ref id) => { diff --git a/src/new_auth_plus.rs b/src/new_auth_plus.rs index 88bc02b..3946b99 100644 --- a/src/new_auth_plus.rs +++ b/src/new_auth_plus.rs @@ -1,7 +1,7 @@ use rustc_serialize::json; -use datatype::{AccessToken, AuthConfig, Error}; -use http_client::{Auth, ClientId, ClientSecret, HttpClient2, HttpRequest2}; +use datatype::{AccessToken, AuthConfig, ClientId, ClientSecret, Error}; +use http_client::{Auth, HttpClient2, HttpRequest2}; pub fn authenticate(config: &AuthConfig, client: &HttpClient2) -> Result { diff --git a/src/new_interpreter.rs b/src/new_interpreter.rs index a73bed0..9c594ba 100644 --- a/src/new_interpreter.rs +++ b/src/new_interpreter.rs @@ -1,50 +1,138 @@ +use std::borrow::Cow; +use std::process::exit; use std::sync::mpsc::Sender; -use datatype::{AccessToken, Command, Config, Event}; +use datatype::{AccessToken, Command, Config, Error, Event, UpdateState}; use datatype::Command::*; use package_manager::PackageManager; use http_client::HttpClient2; use interaction_library::interpreter::Interpreter; +use new_auth_plus::authenticate; +use new_ota_plus::{get_package_updates, download_package_update, + post_packages, send_install_report}; -pub struct OurInterpreter; - #[allow(dead_code)] pub struct Env<'a> { config: &'a Config, - access_token: Option<&'a AccessToken>, + access_token: Option>, pkg_manager: &'a PackageManager, http_client: &'a HttpClient2, } +macro_rules! fun0 { + ($fun: ident, $env: expr, $token: expr) => + (let $fun = || $fun($env.config, $env.http_client, &$token)); +} -impl<'a> Interpreter, Command, Event> for OurInterpreter { +macro_rules! fun1 { + ($fun: ident, $env: expr, $token: expr) => + (let $fun = |arg| $fun($env.config, $env.http_client, &$token, &arg)); +} + +fn interpreter(env: &mut Env, cmd: Command, rx: &Sender) -> Result<(), Error> { + + Ok(if let Some(token) = env.access_token.to_owned() { + + fun0!(get_package_updates, &env, &token); + fun1!(post_packages, &env, &token); + fun1!(download_package_update, &env, &token); + fun1!(send_install_report, &env, &token); - #[allow(unused_variables)] - fn interpret(env: &Env, cmd: Command, rx: Sender) { match cmd { - AcceptUpdate(ref id) => unimplemented!(), - GetPendingUpdates => unimplemented!(), - ListInstalledPackages => unimplemented!(), - PostInstalledPackages => unimplemented!(), - Shutdown => unimplemented!(), - } - } + Authenticate(_) => (), + AcceptUpdate(ref id) => { + + try!(rx.send(Event::UpdateStateChanged(id.clone(), UpdateState::Downloading))); + } /* - fn get_installed_packages(&self) -> Result, Error> { - self.config.ota.package_manager.installed_packages() - } - fn post_installed_packages(&self) { - let _ = self.get_installed_packages().and_then(|pkgs| { - debug!("Found installed packages in the system: {:?}", pkgs); - post_packages::(&self.token, &self.config, &pkgs) - }).map(|_| { - info!("Posted installed packages to the server."); - }).map_err(|e| { - error!("Error fetching/posting installed packages: {:?}.", e); - }); + fn accept_update(&self, id: &UpdateRequestId) { + let report = download_package_update::(&self.token, &self.config, id) + .and_then(|path| { + info!("Downloaded at {:?}. Installing...", path); + self.publish(Event::UpdateStateChanged(id.clone(), UpdateState::Installing)); + + let p = try!(path.to_str().ok_or(Error::ParseError(format!("Path is not valid UTF-8: {:?}", path)))); + self.config.ota.package_manager.install_package(p) + .map(|(code, output)| { + self.publish(Event::UpdateStateChanged(id.clone(), UpdateState::Installed)); + UpdateReport::new(id.clone(), code, output) + }) + .or_else(|(code, output)| { + self.publish(Event::UpdateErrored(id.clone(), format!("{:?}: {:?}", code, output))); + Ok(UpdateReport::new(id.clone(), code, output)) + }) + }).unwrap_or_else(|e| { + self.publish(Event::UpdateErrored(id.clone(), format!("{:?}", e))); + UpdateReport::new(id.clone(), + UpdateResultCode::GENERAL_ERROR, + format!("Download failed: {:?}", e)) + }); + + match send_install_report::(&self.token, &self.config, &report) { + Ok(_) => info!("Update finished. Report sent: {:?}", report), + Err(e) => error!("Error reporting back to the server: {:?}", e) + } } */ + GetPendingUpdates => { + let updates = try!(get_package_updates()); + let update_events: Vec = updates + .iter() + .map(|id| Event::NewUpdateAvailable(id.clone())) + .collect(); + info!("New package updates available: {:?}", update_events); + try!(rx.send(Event::Batch(update_events))) + } + + ListInstalledPackages => { + let pkgs = try!(env.config.ota.package_manager.installed_packages()); + try!(rx.send(Event::FoundInstalledPackages(pkgs.clone()))) + } + + PostInstalledPackages => { + let pkgs = try!(env.config.ota.package_manager.installed_packages()); + debug!("Found installed packages in the system: {:?}", pkgs); + try!(post_packages(pkgs)); + info!("Posted installed packages to the server.") + } + + Shutdown => exit(0) + } + + } else { + + match cmd { + + Authenticate(_) => { + let token = try!(authenticate(&env.config.auth, env.http_client)); + env.access_token = Some(token.into()) + } + + Shutdown => exit(0), + + AcceptUpdate(_) | + GetPendingUpdates | + ListInstalledPackages | + PostInstalledPackages => + rx.send(Event::NotAuthenticated) + .unwrap_or(error!("not_auth: send failed.")) + } + + }) + +} + +pub struct OurInterpreter; + +impl<'a> Interpreter, Command, Event> for OurInterpreter { + + fn interpret(env: &mut Env, cmd: Command, rx: Sender) { + interpreter(env, cmd, &rx) + .unwrap_or_else(|err| rx.send(Event::Error(format!("{}", err))) + .unwrap_or(error!("interpret: send failed."))) + } + } -- cgit v1.2.1 From 6b9207d78874fb6c13bc264358b2034259abe832 Mon Sep 17 00:00:00 2001 From: Stevan Andjelkovic Date: Fri, 22 Apr 2016 14:55:53 +0200 Subject: Finish interpreter. --- src/interaction_library/interpreter.rs | 2 +- src/new_interpreter.rs | 120 +++++++++++++++++++-------------- 2 files changed, 70 insertions(+), 52 deletions(-) diff --git a/src/interaction_library/interpreter.rs b/src/interaction_library/interpreter.rs index 5d9d8ef..bad1b6f 100644 --- a/src/interaction_library/interpreter.rs +++ b/src/interaction_library/interpreter.rs @@ -3,7 +3,7 @@ use std::sync::mpsc::{Sender, Receiver}; pub trait Interpreter { - fn interpret(env: &mut Env, c: C, e: Sender); + fn interpret(env: &mut Env, c: C, tx: Sender); fn run(env: &mut Env, rx: Receiver, tx: Sender) { loop { diff --git a/src/new_interpreter.rs b/src/new_interpreter.rs index 9c594ba..6dd1f84 100644 --- a/src/new_interpreter.rs +++ b/src/new_interpreter.rs @@ -2,7 +2,8 @@ use std::borrow::Cow; use std::process::exit; use std::sync::mpsc::Sender; -use datatype::{AccessToken, Command, Config, Error, Event, UpdateState}; +use datatype::{AccessToken, Command, Config, Error, Event, UpdateReport, + UpdateRequestId, UpdateState, UpdateResultCode}; use datatype::Command::*; use package_manager::PackageManager; use http_client::HttpClient2; @@ -16,66 +17,83 @@ use new_ota_plus::{get_package_updates, download_package_update, pub struct Env<'a> { config: &'a Config, access_token: Option>, + + // XXX: remove, already in config. pkg_manager: &'a PackageManager, http_client: &'a HttpClient2, } -macro_rules! fun0 { - ($fun: ident, $env: expr, $token: expr) => - (let $fun = || $fun($env.config, $env.http_client, &$token)); +// This macro partially applies the config and http client to the passed +// in functions. +macro_rules! partial_apply { + ([ $( $fun0: ident ),* ], [ $( $fun1: ident ),* ], [ $( $fun2: ident ),* ], $env: expr, $token: expr) => { + $(let $fun0 = || $fun0($env.config, $env.http_client, $token);)*; + $(let $fun1 = |arg| $fun1($env.config, $env.http_client, $token, &arg);)*; + $(let $fun2 = |arg1, arg2| $fun2($env.config, $env.http_client, $token, &arg1, &arg2);)*; + } } -macro_rules! fun1 { - ($fun: ident, $env: expr, $token: expr) => - (let $fun = |arg| $fun($env.config, $env.http_client, &$token, &arg)); +// XXX: Move this somewhere else? +fn install_package_update(config: &Config, + http_client: &HttpClient2, + token: &AccessToken, + id: &UpdateRequestId, + tx: &Sender) -> Result { + + match download_package_update(config, http_client, token, id) { + + Ok(path) => { + info!("Downloaded at {:?}. Installing...", path); + try!(tx.send(Event::UpdateStateChanged(id.clone(), UpdateState::Installing))); + + let p = try!(path.to_str() + .ok_or(Error::ParseError(format!("Path is not valid UTF-8: {:?}", path)))); + + match config.ota.package_manager.install_package(p) { + + Ok((code, output)) => { + try!(tx.send(Event::UpdateStateChanged(id.clone(), UpdateState::Installed))); + Ok(UpdateReport::new(id.clone(), code, output)) + } + + Err((code, output)) => { + try!(tx.send(Event::UpdateErrored(id.clone(), format!("{:?}: {:?}", code, output)))); + Ok(UpdateReport::new(id.clone(), code, output)) + } + + } + + } + + Err(err) => { + try!(tx.send(Event::UpdateErrored(id.clone(), format!("{:?}", err)))); + Ok(UpdateReport::new(id.clone(), + UpdateResultCode::GENERAL_ERROR, + format!("Download failed: {:?}", err))) + } + } + } -fn interpreter(env: &mut Env, cmd: Command, rx: &Sender) -> Result<(), Error> { +fn interpreter(env: &mut Env, cmd: Command, tx: &Sender) -> Result<(), Error> { Ok(if let Some(token) = env.access_token.to_owned() { - fun0!(get_package_updates, &env, &token); - fun1!(post_packages, &env, &token); - fun1!(download_package_update, &env, &token); - fun1!(send_install_report, &env, &token); + partial_apply!( + [get_package_updates], + [post_packages, send_install_report], + [install_package_update], &env, &token); match cmd { - Authenticate(_) => (), - AcceptUpdate(ref id) => { - try!(rx.send(Event::UpdateStateChanged(id.clone(), UpdateState::Downloading))); + Authenticate(_) => (), // Already authenticated. + AcceptUpdate(ref id) => { + try!(tx.send(Event::UpdateStateChanged(id.clone(), UpdateState::Downloading))); + let report = try!(install_package_update(id.to_owned(), tx.to_owned())); + try!(send_install_report(report.clone())); + info!("Update finished. Report sent: {:?}", report) } -/* - fn accept_update(&self, id: &UpdateRequestId) { - let report = download_package_update::(&self.token, &self.config, id) - .and_then(|path| { - info!("Downloaded at {:?}. Installing...", path); - self.publish(Event::UpdateStateChanged(id.clone(), UpdateState::Installing)); - - let p = try!(path.to_str().ok_or(Error::ParseError(format!("Path is not valid UTF-8: {:?}", path)))); - self.config.ota.package_manager.install_package(p) - .map(|(code, output)| { - self.publish(Event::UpdateStateChanged(id.clone(), UpdateState::Installed)); - UpdateReport::new(id.clone(), code, output) - }) - .or_else(|(code, output)| { - self.publish(Event::UpdateErrored(id.clone(), format!("{:?}: {:?}", code, output))); - Ok(UpdateReport::new(id.clone(), code, output)) - }) - }).unwrap_or_else(|e| { - self.publish(Event::UpdateErrored(id.clone(), format!("{:?}", e))); - UpdateReport::new(id.clone(), - UpdateResultCode::GENERAL_ERROR, - format!("Download failed: {:?}", e)) - }); - - match send_install_report::(&self.token, &self.config, &report) { - Ok(_) => info!("Update finished. Report sent: {:?}", report), - Err(e) => error!("Error reporting back to the server: {:?}", e) - } - } -*/ GetPendingUpdates => { let updates = try!(get_package_updates()); @@ -84,12 +102,12 @@ fn interpreter(env: &mut Env, cmd: Command, rx: &Sender) -> Result<(), Er .map(|id| Event::NewUpdateAvailable(id.clone())) .collect(); info!("New package updates available: {:?}", update_events); - try!(rx.send(Event::Batch(update_events))) + try!(tx.send(Event::Batch(update_events))) } ListInstalledPackages => { let pkgs = try!(env.config.ota.package_manager.installed_packages()); - try!(rx.send(Event::FoundInstalledPackages(pkgs.clone()))) + try!(tx.send(Event::FoundInstalledPackages(pkgs.clone()))) } PostInstalledPackages => { @@ -117,7 +135,7 @@ fn interpreter(env: &mut Env, cmd: Command, rx: &Sender) -> Result<(), Er GetPendingUpdates | ListInstalledPackages | PostInstalledPackages => - rx.send(Event::NotAuthenticated) + tx.send(Event::NotAuthenticated) .unwrap_or(error!("not_auth: send failed.")) } @@ -129,9 +147,9 @@ pub struct OurInterpreter; impl<'a> Interpreter, Command, Event> for OurInterpreter { - fn interpret(env: &mut Env, cmd: Command, rx: Sender) { - interpreter(env, cmd, &rx) - .unwrap_or_else(|err| rx.send(Event::Error(format!("{}", err))) + fn interpret(env: &mut Env, cmd: Command, tx: Sender) { + interpreter(env, cmd, &tx) + .unwrap_or_else(|err| tx.send(Event::Error(format!("{}", err))) .unwrap_or(error!("interpret: send failed."))) } -- cgit v1.2.1 From 06c9f2dffe6e03e5e891d0e372a50602bb4bdfe6 Mon Sep 17 00:00:00 2001 From: Stevan Andjelkovic Date: Fri, 22 Apr 2016 14:56:53 +0200 Subject: Add client credentials file. --- src/datatype/client_credentials.rs | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 src/datatype/client_credentials.rs diff --git a/src/datatype/client_credentials.rs b/src/datatype/client_credentials.rs new file mode 100644 index 0000000..3f14bed --- /dev/null +++ b/src/datatype/client_credentials.rs @@ -0,0 +1,17 @@ + + +#[derive(Clone, PartialEq, Eq, Debug, RustcEncodable, RustcDecodable)] +pub struct ClientId { + pub get: String, +} + +#[derive(Clone, PartialEq, Eq, Debug, RustcEncodable, RustcDecodable)] +pub struct ClientSecret { + pub get: String, +} + +#[derive(Clone, PartialEq, Eq, Debug, RustcEncodable, RustcDecodable)] +pub struct ClientCredentials { + id: ClientId, + secret: ClientSecret, +} -- cgit v1.2.1 From e44ea44b808c08ccdd2f99e15ac8be3ba68f0ef9 Mon Sep 17 00:00:00 2001 From: Stevan Andjelkovic Date: Fri, 22 Apr 2016 14:58:02 +0200 Subject: Align. --- src/new_interpreter.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/new_interpreter.rs b/src/new_interpreter.rs index 6dd1f84..02598b8 100644 --- a/src/new_interpreter.rs +++ b/src/new_interpreter.rs @@ -34,11 +34,11 @@ macro_rules! partial_apply { } // XXX: Move this somewhere else? -fn install_package_update(config: &Config, +fn install_package_update(config: &Config, http_client: &HttpClient2, - token: &AccessToken, - id: &UpdateRequestId, - tx: &Sender) -> Result { + token: &AccessToken, + id: &UpdateRequestId, + tx: &Sender) -> Result { match download_package_update(config, http_client, token, id) { -- cgit v1.2.1 From 6ce939c45a6cbaaf7e64a2f56e4e647b8389d43b Mon Sep 17 00:00:00 2001 From: Stevan Andjelkovic Date: Fri, 22 Apr 2016 14:59:28 +0200 Subject: Remove package manager from env (it's already in config). --- src/new_interpreter.rs | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/new_interpreter.rs b/src/new_interpreter.rs index 02598b8..8d3de98 100644 --- a/src/new_interpreter.rs +++ b/src/new_interpreter.rs @@ -5,7 +5,6 @@ use std::sync::mpsc::Sender; use datatype::{AccessToken, Command, Config, Error, Event, UpdateReport, UpdateRequestId, UpdateState, UpdateResultCode}; use datatype::Command::*; -use package_manager::PackageManager; use http_client::HttpClient2; use interaction_library::interpreter::Interpreter; use new_auth_plus::authenticate; @@ -17,9 +16,6 @@ use new_ota_plus::{get_package_updates, download_package_update, pub struct Env<'a> { config: &'a Config, access_token: Option>, - - // XXX: remove, already in config. - pkg_manager: &'a PackageManager, http_client: &'a HttpClient2, } -- cgit v1.2.1 From 4156980c694d1593b48bfcba839d2c1d7f21cd61 Mon Sep 17 00:00:00 2001 From: Stevan Andjelkovic Date: Mon, 25 Apr 2016 11:27:20 +0200 Subject: Wrap client in an arc and add a send trait constrait. Alternative: use scoped threads? --- Cargo.lock | 6 ++++++ Cargo.toml | 7 ++++--- src/http_client/http_client.rs | 2 +- src/http_client/hyper.rs | 6 ++++++ src/http_client/mod.rs | 5 +++-- src/main.rs | 39 +++++++++++++++++++++++++-------------- src/new_interpreter.rs | 17 +++++++++-------- 7 files changed, 54 insertions(+), 28 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index f14e673..b5df714 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4,6 +4,7 @@ version = "0.1.0" dependencies = [ "chan 0.1.18 (registry+https://github.com/rust-lang/crates.io-index)", "chan-signal 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", + "crossbeam 0.2.9 (registry+https://github.com/rust-lang/crates.io-index)", "env_logger 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", "getopts 0.2.14 (registry+https://github.com/rust-lang/crates.io-index)", "hyper 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)", @@ -92,6 +93,11 @@ dependencies = [ "url 0.5.9 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "crossbeam" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "env_logger" version = "0.3.3" diff --git a/Cargo.toml b/Cargo.toml index 7a8e3ef..4d2ed35 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,15 +13,16 @@ path = "src/main.rs" doc = false [dependencies] -chan-signal = "0.1.5" chan = "0.1.18" +chan-signal = "0.1.5" +crossbeam = "0.2.9" env_logger = "0.3.3" getopts = "0.2.14" hyper = "0.8.1" log = "0.3.5" rustc-serialize = "0.3.18" -url = "0.5.9" tempfile = "2.1.2" toml = "0.1.28" +url = "0.5.9" ws = "0.4.6" -yup-hyper-mock = "1.3.2" +yup-hyper-mock = "1.3.2" \ No newline at end of file diff --git a/src/http_client/http_client.rs b/src/http_client/http_client.rs index c2492bd..af0c731 100644 --- a/src/http_client/http_client.rs +++ b/src/http_client/http_client.rs @@ -64,7 +64,7 @@ impl<'a> HttpRequest2<'a> { } -pub trait HttpClient2 { +pub trait HttpClient2: Send + Sync { fn send_request_to(&self, request: &HttpRequest2, file: &mut File) -> Result<(), Error> { diff --git a/src/http_client/hyper.rs b/src/http_client/hyper.rs index 885bd42..a9f81d5 100644 --- a/src/http_client/hyper.rs +++ b/src/http_client/hyper.rs @@ -13,6 +13,12 @@ pub struct Hyper { client: Client, } +impl Hyper { + pub fn new() -> Hyper { + Hyper { client: Client::new() } + } +} + impl HttpClient2 for Hyper { fn send_request_to(&self, request: &HttpRequest2, file: &mut File) -> Result<(), Error> { diff --git a/src/http_client/mod.rs b/src/http_client/mod.rs index 051c084..90ae11f 100644 --- a/src/http_client/mod.rs +++ b/src/http_client/mod.rs @@ -1,7 +1,8 @@ -pub use self::mock_http_client::MockHttpClient; pub use self::bad_http_client::BadHttpClient; -pub use self::interface::{HttpClient, HttpRequest}; pub use self::http_client::{Auth, HttpClient2, HttpRequest2}; +pub use self::hyper::Hyper; +pub use self::interface::{HttpClient, HttpRequest}; +pub use self::mock_http_client::MockHttpClient; pub mod bad_http_client; pub mod mock_http_client; diff --git a/src/main.rs b/src/main.rs index 5d1340f..72b4469 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,30 +1,31 @@ #[macro_use] extern crate log; -extern crate env_logger; -extern crate chan_signal; extern crate chan; +extern crate chan_signal; +extern crate crossbeam; +extern crate env_logger; extern crate getopts; extern crate hyper; -extern crate ws; extern crate rustc_serialize; +extern crate ws; #[macro_use] extern crate libotaplus; use getopts::Options; use std::env; +use std::sync::Arc; use std::sync::mpsc::{Sender, Receiver, channel}; use std::thread; use std::time::Duration; use chan_signal::Signal; use chan::Receiver as ChanReceiver; -use libotaplus::auth_plus::authenticate; -use libotaplus::datatype::{config, Config, Event, Command, AccessToken, Url}; -use libotaplus::http_client::HttpClient; +use libotaplus::datatype::{config, Config, Event, Command, Url}; +use libotaplus::http_client::Hyper; +use libotaplus::interaction_library::Interpreter; use libotaplus::interaction_library::broadcast::Broadcast; use libotaplus::interaction_library::console::Console; use libotaplus::interaction_library::gateway::Gateway; use libotaplus::interaction_library::websocket::Websocket; -use libotaplus::interpreter::Interpreter; -use libotaplus::interaction_library::{Interpreter as InteractionInterpreter}; +use libotaplus::new_interpreter::{OurInterpreter, Env}; use libotaplus::package_manager::PackageManager; macro_rules! spawn_thread { @@ -41,9 +42,18 @@ macro_rules! spawn_thread { } } -fn spawn_interpreter(config: Config, token: AccessToken, crx: Receiver, etx: Sender) { +fn spawn_interpreter(config: Config, crx: Receiver, etx: Sender) { + + let client = Arc::new(Hyper::new()); + + let mut env = Env { + config: config.clone(), + access_token: None, + http_client: client.clone(), + }; + spawn_thread!("Interpreter", { - Interpreter::::new(&config, token.clone(), crx, etx).start(); + OurInterpreter::run(&mut env, crx, etx); }); } @@ -76,6 +86,7 @@ fn spawn_update_poller(ctx: Sender, config: Config) { } fn perform_initial_sync(ctx: Sender) { + let _ = ctx.clone().send(Command::Authenticate(None)); let _ = ctx.clone().send(Command::PostInstalledPackages); } @@ -87,7 +98,7 @@ fn start_event_broadcasting(broadcast: Broadcast) { struct AutoAcceptor; -impl InteractionInterpreter<(), Event, Command> for AutoAcceptor { +impl Interpreter<(), Event, Command> for AutoAcceptor { fn interpret(_: &mut (), e: Event, ctx: Sender) { fn f(e: &Event, ctx: Sender) { match e { @@ -115,8 +126,6 @@ fn main() { let config = build_config(); - info!("Authenticating against AuthPlus..."); - let token = authenticate::(&config.auth).unwrap_or_else(|e| exit!("{}", e)); let (etx, erx): (Sender, Receiver) = channel(); let (ctx, crx): (Sender, Receiver) = channel(); @@ -126,7 +135,9 @@ fn main() { let signals = chan_signal::notify(&[Signal::TERM]); spawn_autoacceptor(broadcast.subscribe(), ctx.clone()); - spawn_interpreter(config.clone(), token.clone(), crx, etx); + + spawn_interpreter(config.clone(), crx, etx.clone()); + Websocket::run(ctx.clone(), broadcast.subscribe()); spawn_update_poller(ctx.clone(), config.clone()); diff --git a/src/new_interpreter.rs b/src/new_interpreter.rs index 8d3de98..ba49ed4 100644 --- a/src/new_interpreter.rs +++ b/src/new_interpreter.rs @@ -1,5 +1,6 @@ use std::borrow::Cow; use std::process::exit; +use std::sync::Arc; use std::sync::mpsc::Sender; use datatype::{AccessToken, Command, Config, Error, Event, UpdateReport, @@ -12,20 +13,20 @@ use new_ota_plus::{get_package_updates, download_package_update, post_packages, send_install_report}; -#[allow(dead_code)] +#[derive(Clone)] pub struct Env<'a> { - config: &'a Config, - access_token: Option>, - http_client: &'a HttpClient2, + pub config: Config, + pub access_token: Option>, + pub http_client: Arc, } // This macro partially applies the config and http client to the passed // in functions. macro_rules! partial_apply { ([ $( $fun0: ident ),* ], [ $( $fun1: ident ),* ], [ $( $fun2: ident ),* ], $env: expr, $token: expr) => { - $(let $fun0 = || $fun0($env.config, $env.http_client, $token);)*; - $(let $fun1 = |arg| $fun1($env.config, $env.http_client, $token, &arg);)*; - $(let $fun2 = |arg1, arg2| $fun2($env.config, $env.http_client, $token, &arg1, &arg2);)*; + $(let $fun0 = || $fun0(&$env.config, &*$env.http_client, $token);)*; + $(let $fun1 = |arg| $fun1(&$env.config, &*$env.http_client, $token, &arg);)*; + $(let $fun2 = |arg1, arg2| $fun2(&$env.config, &*$env.http_client, $token, &arg1, &arg2);)*; } } @@ -121,7 +122,7 @@ fn interpreter(env: &mut Env, cmd: Command, tx: &Sender) -> Result<(), Er match cmd { Authenticate(_) => { - let token = try!(authenticate(&env.config.auth, env.http_client)); + let token = try!(authenticate(&env.config.auth, &*env.http_client)); env.access_token = Some(token.into()) } -- cgit v1.2.1 From e0f666f9f8c02c463c96f401f75209f660c0d450 Mon Sep 17 00:00:00 2001 From: Stevan Andjelkovic Date: Mon, 25 Apr 2016 11:58:03 +0200 Subject: Post packages after successful install. --- src/http_client/interface.rs | 1 - src/new_interpreter.rs | 6 ++++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/src/http_client/interface.rs b/src/http_client/interface.rs index df40417..522d813 100644 --- a/src/http_client/interface.rs +++ b/src/http_client/interface.rs @@ -1,4 +1,3 @@ -use hyper::Url; use hyper::header::{Headers, Header, HeaderFormat, Location, Authorization, Bearer}; use hyper::method::Method; use hyper::client::response::Response; diff --git a/src/new_interpreter.rs b/src/new_interpreter.rs index ba49ed4..dfc18d5 100644 --- a/src/new_interpreter.rs +++ b/src/new_interpreter.rs @@ -50,6 +50,11 @@ fn install_package_update(config: &Config, Ok((code, output)) => { try!(tx.send(Event::UpdateStateChanged(id.clone(), UpdateState::Installed))); + + // XXX: Slight code duplication, see interpret(PostInstalledPackages). + let pkgs = try!(config.ota.package_manager.installed_packages()); + try!(post_packages(config, http_client, token, &pkgs)); + Ok(UpdateReport::new(id.clone(), code, output)) } @@ -122,6 +127,7 @@ fn interpreter(env: &mut Env, cmd: Command, tx: &Sender) -> Result<(), Er match cmd { Authenticate(_) => { + // XXX: partially apply? let token = try!(authenticate(&env.config.auth, &*env.http_client)); env.access_token = Some(token.into()) } -- cgit v1.2.1 From e3b8ee9c35e008e9f09515d274d9b5b3836510a0 Mon Sep 17 00:00:00 2001 From: Stevan Andjelkovic Date: Mon, 25 Apr 2016 12:10:43 +0200 Subject: Add redirect policy. --- src/http_client/hyper.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/http_client/hyper.rs b/src/http_client/hyper.rs index a9f81d5..077981d 100644 --- a/src/http_client/hyper.rs +++ b/src/http_client/hyper.rs @@ -1,4 +1,5 @@ use hyper::Client; +use hyper::client::RedirectPolicy; use hyper::header::{Authorization, Basic, Bearer, ContentType, Headers}; use hyper::mime::{Attr, Mime, TopLevel, SubLevel, Value}; use rustc_serialize::json; @@ -15,7 +16,9 @@ pub struct Hyper { impl Hyper { pub fn new() -> Hyper { - Hyper { client: Client::new() } + let mut client = Client::new(); + client.set_redirect_policy(RedirectPolicy::FollowNone); + Hyper { client: client } } } -- cgit v1.2.1 From c3bf9dcc614f9c2ecb78a0f9d7769cb1789102e0 Mon Sep 17 00:00:00 2001 From: Stevan Andjelkovic Date: Mon, 25 Apr 2016 14:12:18 +0200 Subject: Backport auth header stripping. --- src/http_client/http_client.rs | 12 ++++++------ src/http_client/hyper.rs | 39 ++++++++++++++++++++++++++++++++------- src/new_auth_plus.rs | 4 ++-- src/new_ota_plus.rs | 8 ++++---- 4 files changed, 44 insertions(+), 19 deletions(-) diff --git a/src/http_client/http_client.rs b/src/http_client/http_client.rs index af0c731..d38cb8a 100644 --- a/src/http_client/http_client.rs +++ b/src/http_client/http_client.rs @@ -21,7 +21,7 @@ impl<'a> Into>> for Auth<'a> { pub struct HttpRequest2<'a> { pub method: Cow<'a, Method>, pub url: Cow<'a, Url>, - pub auth: Cow<'a, Auth<'a>>, + pub auth: Option>>, pub body: Option>, } @@ -29,7 +29,7 @@ impl<'a> HttpRequest2<'a> { fn new(meth: M, url: U, - auth: A, + auth: Option, body: Option) -> HttpRequest2<'a> where M: Into>, @@ -40,12 +40,12 @@ impl<'a> HttpRequest2<'a> { HttpRequest2 { method: meth.into(), url: url.into(), - auth: auth.into(), - body: body.map(|c| c.into()), + auth: auth.map(|a| a.into()), + body: body.map(|b| b.into()), } } - pub fn get(url: U, auth: A) -> HttpRequest2<'a> + pub fn get(url: U, auth: Option) -> HttpRequest2<'a> where U: Into>, A: Into>>, @@ -53,7 +53,7 @@ impl<'a> HttpRequest2<'a> { HttpRequest2::new::<_, _, _, String>(Method::Get, url, auth, None) } - pub fn post(url: U, auth: A, body: Option) -> HttpRequest2<'a> + pub fn post(url: U, auth: Option, body: Option) -> HttpRequest2<'a> where U: Into>, A: Into>>, diff --git a/src/http_client/hyper.rs b/src/http_client/hyper.rs index 077981d..05cbd99 100644 --- a/src/http_client/hyper.rs +++ b/src/http_client/hyper.rs @@ -1,6 +1,7 @@ use hyper::Client; use hyper::client::RedirectPolicy; -use hyper::header::{Authorization, Basic, Bearer, ContentType, Headers}; +use hyper::client::response::Response; +use hyper::header::{Authorization, Basic, Bearer, ContentType, Headers, Location}; use hyper::mime::{Attr, Mime, TopLevel, SubLevel, Value}; use rustc_serialize::json; use std::fs::File; @@ -24,14 +25,16 @@ impl Hyper { impl HttpClient2 for Hyper { - fn send_request_to(&self, request: &HttpRequest2, file: &mut File) -> Result<(), Error> { + fn send_request_to(&self, req: &HttpRequest2, file: &mut File) -> Result<(), Error> { let mut headers = Headers::new(); let mut body = String::new(); - match (request.auth.clone().into_owned(), request.body.to_owned()) { + match (req.auth.clone().map(|a| a.into_owned()), req.body.to_owned()) { - (Auth::Credentials(ref id, ref secret), None) => { + (None, None) => {} + + (Some(Auth::Credentials(ref id, ref secret)), None) => { headers.set(Authorization(Basic { username: id.get.clone(), @@ -47,7 +50,7 @@ impl HttpClient2 for Hyper { } - (Auth::Token(token), Some(body)) => { + (Some(Auth::Token(token)), Some(body)) => { headers.set(Authorization(Bearer { token: token.access_token.clone() @@ -69,8 +72,8 @@ impl HttpClient2 for Hyper { } let mut resp = try!(self.client - .request(request.method.clone().into_owned().into(), - request.url.clone().into_owned()) + .request(req.method.clone().into_owned().into(), + req.url.clone().into_owned()) .headers(headers) .body(&body) .send()); @@ -83,6 +86,9 @@ impl HttpClient2 for Hyper { try!(tee(rbody.as_bytes(), file)); Ok(()) + } else if resp.status.is_redirection() { + let req = try!(relocate_request(req, &resp)); + self.send_request_to(&req, file) } else { Err(Error::ClientError(format!("Request errored with status {}", resp.status))) } @@ -91,6 +97,25 @@ impl HttpClient2 for Hyper { } +fn relocate_request<'a>(req: &'a HttpRequest2, resp: &Response) -> Result, Error> { + + if let Some(&Location(ref loc)) = resp.headers.get::() { + + let url = try!(req.url.join(loc)); + + Ok(HttpRequest2 { + url: url.into(), + method: req.method.clone(), + auth: None, + body: req.body.clone(), + }) + + } else { + Err(Error::ClientError("Redirect with no Location header".to_string())) + } + +} + pub fn tee(from: R, to: W) -> Result<(), Error> { const BUF_SIZE: usize = 1024 * 1024 * 5; diff --git a/src/new_auth_plus.rs b/src/new_auth_plus.rs index 3946b99..ec26370 100644 --- a/src/new_auth_plus.rs +++ b/src/new_auth_plus.rs @@ -8,9 +8,9 @@ pub fn authenticate(config: &AuthConfig, client: &HttpClient2) -> Result( config.server.join("/token").unwrap(), - Auth::Credentials( + Some(Auth::Credentials( ClientId { get: config.client_id.clone() }, - ClientSecret { get: config.secret.clone() }), + ClientSecret { get: config.secret.clone() })), None, ); diff --git a/src/new_ota_plus.rs b/src/new_ota_plus.rs index 8fe0a38..05171c4 100644 --- a/src/new_ota_plus.rs +++ b/src/new_ota_plus.rs @@ -18,7 +18,7 @@ pub fn download_package_update(config: &Config, let req = HttpRequest2::get( vehicle_endpoint(config, &format!("updates/{}/download", id)), - Auth::Token(token), + Some(Auth::Token(token)), ); let mut path = PathBuf::new(); @@ -44,7 +44,7 @@ pub fn send_install_report(config: &Config, let req = HttpRequest2::post( vehicle_endpoint(config, &format!("/updates/{}", report.update_id)), - Auth::Token(token), + Some(Auth::Token(token)), Some(json) ); @@ -60,7 +60,7 @@ pub fn get_package_updates(config: &Config, let req = HttpRequest2::get( vehicle_endpoint(&config, "/updates"), - Auth::Token(token), + Some(Auth::Token(token)), ); let resp = try!(client.send_request(&req)); @@ -78,7 +78,7 @@ pub fn post_packages(config: &Config, let req = HttpRequest2::post( vehicle_endpoint(config, "/updates"), - Auth::Token(token), + Some(Auth::Token(token)), Some(json), ); -- cgit v1.2.1 From 7060dbe8b96b147e14f0ffc5f667233942fbaea8 Mon Sep 17 00:00:00 2001 From: Stevan Andjelkovic Date: Mon, 25 Apr 2016 14:39:35 +0200 Subject: Replace old auth plus with new one. --- src/auth_plus.rs | 34 ++++++++--------- src/lib.rs | 1 - src/new_auth_plus.rs | 100 ------------------------------------------------- src/new_interpreter.rs | 2 +- 4 files changed, 16 insertions(+), 121 deletions(-) delete mode 100644 src/new_auth_plus.rs diff --git a/src/auth_plus.rs b/src/auth_plus.rs index 10ada30..ec26370 100644 --- a/src/auth_plus.rs +++ b/src/auth_plus.rs @@ -1,32 +1,27 @@ -use hyper::header::{Authorization, Basic, ContentType}; -use hyper::mime::{Mime, TopLevel, SubLevel, Attr, Value}; use rustc_serialize::json; -use datatype::{AccessToken, AuthConfig, Error}; -use http_client::{HttpClient, HttpRequest}; +use datatype::{AccessToken, AuthConfig, ClientId, ClientSecret, Error}; +use http_client::{Auth, HttpClient2, HttpRequest2}; -pub fn authenticate(config: &AuthConfig) -> Result { +pub fn authenticate(config: &AuthConfig, client: &HttpClient2) -> Result { - let url = try!(config.server.join("/token")); - let req = HttpRequest::post(url) - .with_body("grant_type=client_credentials") - .with_header(Authorization(Basic { - username: config.client_id.clone(), - password: Some(config.secret.clone()) - })) - .with_header(ContentType(Mime( - TopLevel::Application, - SubLevel::WwwFormUrlEncoded, - vec![(Attr::Charset, Value::Utf8)]))); + let req = HttpRequest2::post::<_, _, String>( + config.server.join("/token").unwrap(), + Some(Auth::Credentials( + ClientId { get: config.client_id.clone() }, + ClientSecret { get: config.secret.clone() })), + None, + ); - let body = try!(C::new().send_request(&req) - .map_err(|e| Error::AuthError(format!("didn't receive access token: {}", e)))); + let body = try!(client.send_request(&req)); - return Ok(try!(json::decode(&body))) + Ok(try!(json::decode(&body))) } +/* + #[cfg(test)] mod tests { @@ -102,3 +97,4 @@ mod tests { } } +*/ diff --git a/src/lib.rs b/src/lib.rs index 85b96e2..151d341 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -12,7 +12,6 @@ pub mod datatype; pub mod http_client; pub mod interaction_library; pub mod interpreter; -pub mod new_auth_plus; pub mod new_interpreter; pub mod new_ota_plus; pub mod ota_plus; diff --git a/src/new_auth_plus.rs b/src/new_auth_plus.rs deleted file mode 100644 index ec26370..0000000 --- a/src/new_auth_plus.rs +++ /dev/null @@ -1,100 +0,0 @@ -use rustc_serialize::json; - -use datatype::{AccessToken, AuthConfig, ClientId, ClientSecret, Error}; -use http_client::{Auth, HttpClient2, HttpRequest2}; - - -pub fn authenticate(config: &AuthConfig, client: &HttpClient2) -> Result { - - let req = HttpRequest2::post::<_, _, String>( - config.server.join("/token").unwrap(), - Some(Auth::Credentials( - ClientId { get: config.client_id.clone() }, - ClientSecret { get: config.secret.clone() })), - None, - ); - - let body = try!(client.send_request(&req)); - - Ok(try!(json::decode(&body))) - -} - -/* - -#[cfg(test)] -mod tests { - - use std::io::Write; - - use super::*; - use datatype::AccessToken; - use datatype::AuthConfig; - use datatype::Error; - use http_client::BadHttpClient; - use http_client::{HttpRequest, HttpClient}; - - - struct MockClient; - - impl HttpClient for MockClient { - - fn new() -> MockClient { - MockClient - } - - fn send_request(&self, _: &HttpRequest) -> Result { - Ok(r#"{"access_token": "token", - "token_type": "type", - "expires_in": 10, - "scope": ["scope"]}"#.to_string()) - } - - fn send_request_to(&self, _: &HttpRequest, _: W) -> Result<(), Error> { - Ok(()) - } - } - - #[test] - fn test_authenticate() { - assert_eq!(authenticate::(&AuthConfig::default()).unwrap(), - AccessToken { - access_token: "token".to_string(), - token_type: "type".to_string(), - expires_in: 10, - scope: vec!["scope".to_string()] - }) - } - - #[test] - fn test_authenticate_bad_client() { - assert_eq!(format!("{}", authenticate::(&AuthConfig::default()).unwrap_err()), - "Authentication error, didn't receive access token: bad client.") - } - - #[test] - fn test_authenticate_bad_json_client() { - - struct BadJsonClient; - - impl HttpClient for BadJsonClient { - - fn new() -> BadJsonClient { - BadJsonClient - } - - fn send_request(&self, _: &HttpRequest) -> Result { - Ok(r#"{"apa": 1}"#.to_string()) - } - - fn send_request_to(&self, _: &HttpRequest, _: W) -> Result<(), Error> { - Ok(()) - } - } - - assert_eq!(format!("{}", authenticate::(&AuthConfig::default()).unwrap_err()), - r#"Failed to decode JSON: MissingFieldError("access_token")"#) - } - -} -*/ diff --git a/src/new_interpreter.rs b/src/new_interpreter.rs index dfc18d5..d981834 100644 --- a/src/new_interpreter.rs +++ b/src/new_interpreter.rs @@ -3,12 +3,12 @@ use std::process::exit; use std::sync::Arc; use std::sync::mpsc::Sender; +use auth_plus::authenticate; use datatype::{AccessToken, Command, Config, Error, Event, UpdateReport, UpdateRequestId, UpdateState, UpdateResultCode}; use datatype::Command::*; use http_client::HttpClient2; use interaction_library::interpreter::Interpreter; -use new_auth_plus::authenticate; use new_ota_plus::{get_package_updates, download_package_update, post_packages, send_install_report}; -- cgit v1.2.1 From 1ace242a207cba2a76927a2767fe848b57eaa2b8 Mon Sep 17 00:00:00 2001 From: Stevan Andjelkovic Date: Mon, 25 Apr 2016 14:42:39 +0200 Subject: Replace old ota plus and interpreter with new. --- src/interpreter.rs | 230 +++++++++++++++++++++++++++++-------------------- src/lib.rs | 2 - src/main.rs | 3 +- src/new_interpreter.rs | 159 ---------------------------------- src/new_ota_plus.rs | 179 -------------------------------------- src/ota_plus.rs | 141 +++++++++++++++++------------- 6 files changed, 218 insertions(+), 496 deletions(-) delete mode 100644 src/new_interpreter.rs delete mode 100644 src/new_ota_plus.rs diff --git a/src/interpreter.rs b/src/interpreter.rs index dc6b126..8f27acc 100644 --- a/src/interpreter.rs +++ b/src/interpreter.rs @@ -1,118 +1,158 @@ -use std::sync::mpsc::{Sender , Receiver}; -use std::marker::PhantomData; +use std::borrow::Cow; use std::process::exit; +use std::sync::Arc; +use std::sync::mpsc::Sender; -use http_client::HttpClient; +use auth_plus::authenticate; +use datatype::{AccessToken, Command, Config, Error, Event, UpdateReport, + UpdateRequestId, UpdateState, UpdateResultCode}; +use datatype::Command::*; +use http_client::HttpClient2; +use interaction_library::interpreter::Interpreter; use ota_plus::{get_package_updates, download_package_update, post_packages, send_install_report}; -use datatype::{Event, Command, Config, AccessToken, UpdateState, Package, Error, UpdateRequestId, UpdateReport, UpdateResultCode}; - -pub struct Interpreter<'a, C: HttpClient> { - client_type: PhantomData, - config: &'a Config, - token: AccessToken, - // Commands mpsc, events spmc - commands_rx: Receiver, - events_tx: Sender + + +#[derive(Clone)] +pub struct Env<'a> { + pub config: Config, + pub access_token: Option>, + pub http_client: Arc, } -impl<'a, C: HttpClient> Interpreter<'a, C> { - pub fn new(config: &'a Config, token: AccessToken, commands_rx: Receiver, events_tx: Sender) -> Interpreter<'a, C> { - Interpreter { client_type: PhantomData, config: config, token: token, commands_rx: commands_rx, events_tx: events_tx } +// This macro partially applies the config and http client to the passed +// in functions. +macro_rules! partial_apply { + ([ $( $fun0: ident ),* ], [ $( $fun1: ident ),* ], [ $( $fun2: ident ),* ], $env: expr, $token: expr) => { + $(let $fun0 = || $fun0(&$env.config, &*$env.http_client, $token);)*; + $(let $fun1 = |arg| $fun1(&$env.config, &*$env.http_client, $token, &arg);)*; + $(let $fun2 = |arg1, arg2| $fun2(&$env.config, &*$env.http_client, $token, &arg1, &arg2);)*; } +} + +// XXX: Move this somewhere else? +fn install_package_update(config: &Config, + http_client: &HttpClient2, + token: &AccessToken, + id: &UpdateRequestId, + tx: &Sender) -> Result { + + match download_package_update(config, http_client, token, id) { + + Ok(path) => { + info!("Downloaded at {:?}. Installing...", path); + try!(tx.send(Event::UpdateStateChanged(id.clone(), UpdateState::Installing))); + + let p = try!(path.to_str() + .ok_or(Error::ParseError(format!("Path is not valid UTF-8: {:?}", path)))); + + match config.ota.package_manager.install_package(p) { + + Ok((code, output)) => { + try!(tx.send(Event::UpdateStateChanged(id.clone(), UpdateState::Installed))); + + // XXX: Slight code duplication, see interpret(PostInstalledPackages). + let pkgs = try!(config.ota.package_manager.installed_packages()); + try!(post_packages(config, http_client, token, &pkgs)); + + Ok(UpdateReport::new(id.clone(), code, output)) + } + + Err((code, output)) => { + try!(tx.send(Event::UpdateErrored(id.clone(), format!("{:?}: {:?}", code, output)))); + Ok(UpdateReport::new(id.clone(), code, output)) + } - pub fn start(&self) { - loop { - match self.commands_rx.recv() { - Ok(cmd) => self.interpret(cmd), - Err(e) => error!("Error receiving command: {:?}", e) } + } - } - pub fn interpret(&self, command: Command) { - match command { - Command::Authenticate(_) => unimplemented!(), - Command::GetPendingUpdates => self.get_pending_updates(), - Command::PostInstalledPackages => self.post_installed_packages(), - Command::AcceptUpdate(ref id) => self.accept_update(id), - Command::ListInstalledPackages => self.list_installed_packages(), - Command::Shutdown => { - info!("Shutting down..."); - exit(0) - } + Err(err) => { + try!(tx.send(Event::UpdateErrored(id.clone(), format!("{:?}", err)))); + Ok(UpdateReport::new(id.clone(), + UpdateResultCode::GENERAL_ERROR, + format!("Download failed: {:?}", err))) } } - fn publish(&self, event: Event) { - let _ = self.events_tx.send(event); - } +} - fn get_installed_packages(&self) -> Result, Error> { - self.config.ota.package_manager.installed_packages() - } +fn interpreter(env: &mut Env, cmd: Command, tx: &Sender) -> Result<(), Error> { + + Ok(if let Some(token) = env.access_token.to_owned() { + + partial_apply!( + [get_package_updates], + [post_packages, send_install_report], + [install_package_update], &env, &token); + + match cmd { + + Authenticate(_) => (), // Already authenticated. - fn get_pending_updates(&self) { - debug!("Fetching package updates..."); - let response: Event = match get_package_updates::(&self.token, &self.config) { - Ok(updates) => { - let update_events: Vec = updates.iter().map(move |id| Event::NewUpdateAvailable(id.clone())).collect(); + AcceptUpdate(ref id) => { + try!(tx.send(Event::UpdateStateChanged(id.clone(), UpdateState::Downloading))); + let report = try!(install_package_update(id.to_owned(), tx.to_owned())); + try!(send_install_report(report.clone())); + info!("Update finished. Report sent: {:?}", report) + } + + GetPendingUpdates => { + let updates = try!(get_package_updates()); + let update_events: Vec = updates + .iter() + .map(|id| Event::NewUpdateAvailable(id.clone())) + .collect(); info!("New package updates available: {:?}", update_events); - Event::Batch(update_events) - }, - Err(e) => { - Event::Error(format!("{}", e)) + try!(tx.send(Event::Batch(update_events))) } - }; - self.publish(response); - } - fn post_installed_packages(&self) { - let _ = self.get_installed_packages().and_then(|pkgs| { - debug!("Found installed packages in the system: {:?}", pkgs); - post_packages::(&self.token, &self.config, &pkgs) - }).map(|_| { - info!("Posted installed packages to the server."); - }).map_err(|e| { - error!("Error fetching/posting installed packages: {:?}.", e); - }); - } + ListInstalledPackages => { + let pkgs = try!(env.config.ota.package_manager.installed_packages()); + try!(tx.send(Event::FoundInstalledPackages(pkgs.clone()))) + } - fn accept_update(&self, id: &UpdateRequestId) { - info!("Accepting update {}...", id); - self.publish(Event::UpdateStateChanged(id.clone(), UpdateState::Downloading)); - let report = download_package_update::(&self.token, &self.config, id) - .and_then(|path| { - info!("Downloaded at {:?}. Installing...", path); - self.publish(Event::UpdateStateChanged(id.clone(), UpdateState::Installing)); - - let p = try!(path.to_str().ok_or(Error::ParseError(format!("Path is not valid UTF-8: {:?}", path)))); - self.config.ota.package_manager.install_package(p) - .map(|(code, output)| { - self.publish(Event::UpdateStateChanged(id.clone(), UpdateState::Installed)); - self.post_installed_packages(); - UpdateReport::new(id.clone(), code, output) - }) - .or_else(|(code, output)| { - self.publish(Event::UpdateErrored(id.clone(), format!("{:?}: {:?}", code, output))); - Ok(UpdateReport::new(id.clone(), code, output)) - }) - }).unwrap_or_else(|e| { - self.publish(Event::UpdateErrored(id.clone(), format!("{:?}", e))); - UpdateReport::new(id.clone(), - UpdateResultCode::GENERAL_ERROR, - format!("Download failed: {:?}", e)) - }); - - match send_install_report::(&self.token, &self.config, &report) { - Ok(_) => info!("Update finished. Report sent: {:?}", report), - Err(e) => error!("Error reporting back to the server: {:?}", e) + PostInstalledPackages => { + let pkgs = try!(env.config.ota.package_manager.installed_packages()); + debug!("Found installed packages in the system: {:?}", pkgs); + try!(post_packages(pkgs)); + info!("Posted installed packages to the server.") + } + + Shutdown => exit(0) } - } - fn list_installed_packages(&self) { - let _ = self.get_installed_packages().and_then(|pkgs| { - self.publish(Event::FoundInstalledPackages(pkgs.clone())); - Ok(()) - }); + } else { + + match cmd { + + Authenticate(_) => { + // XXX: partially apply? + let token = try!(authenticate(&env.config.auth, &*env.http_client)); + env.access_token = Some(token.into()) + } + + Shutdown => exit(0), + + AcceptUpdate(_) | + GetPendingUpdates | + ListInstalledPackages | + PostInstalledPackages => + tx.send(Event::NotAuthenticated) + .unwrap_or(error!("not_auth: send failed.")) + } + + }) + +} + +pub struct OurInterpreter; + +impl<'a> Interpreter, Command, Event> for OurInterpreter { + + fn interpret(env: &mut Env, cmd: Command, tx: Sender) { + interpreter(env, cmd, &tx) + .unwrap_or_else(|err| tx.send(Event::Error(format!("{}", err))) + .unwrap_or(error!("interpret: send failed."))) } + } diff --git a/src/lib.rs b/src/lib.rs index 151d341..9295f24 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -12,7 +12,5 @@ pub mod datatype; pub mod http_client; pub mod interaction_library; pub mod interpreter; -pub mod new_interpreter; -pub mod new_ota_plus; pub mod ota_plus; pub mod package_manager; diff --git a/src/main.rs b/src/main.rs index 72b4469..5b8ff25 100644 --- a/src/main.rs +++ b/src/main.rs @@ -25,9 +25,10 @@ use libotaplus::interaction_library::broadcast::Broadcast; use libotaplus::interaction_library::console::Console; use libotaplus::interaction_library::gateway::Gateway; use libotaplus::interaction_library::websocket::Websocket; -use libotaplus::new_interpreter::{OurInterpreter, Env}; +use libotaplus::interpreter::{OurInterpreter, Env}; use libotaplus::package_manager::PackageManager; + macro_rules! spawn_thread { ($name:expr, $body:block) => { { diff --git a/src/new_interpreter.rs b/src/new_interpreter.rs deleted file mode 100644 index d981834..0000000 --- a/src/new_interpreter.rs +++ /dev/null @@ -1,159 +0,0 @@ -use std::borrow::Cow; -use std::process::exit; -use std::sync::Arc; -use std::sync::mpsc::Sender; - -use auth_plus::authenticate; -use datatype::{AccessToken, Command, Config, Error, Event, UpdateReport, - UpdateRequestId, UpdateState, UpdateResultCode}; -use datatype::Command::*; -use http_client::HttpClient2; -use interaction_library::interpreter::Interpreter; -use new_ota_plus::{get_package_updates, download_package_update, - post_packages, send_install_report}; - - -#[derive(Clone)] -pub struct Env<'a> { - pub config: Config, - pub access_token: Option>, - pub http_client: Arc, -} - -// This macro partially applies the config and http client to the passed -// in functions. -macro_rules! partial_apply { - ([ $( $fun0: ident ),* ], [ $( $fun1: ident ),* ], [ $( $fun2: ident ),* ], $env: expr, $token: expr) => { - $(let $fun0 = || $fun0(&$env.config, &*$env.http_client, $token);)*; - $(let $fun1 = |arg| $fun1(&$env.config, &*$env.http_client, $token, &arg);)*; - $(let $fun2 = |arg1, arg2| $fun2(&$env.config, &*$env.http_client, $token, &arg1, &arg2);)*; - } -} - -// XXX: Move this somewhere else? -fn install_package_update(config: &Config, - http_client: &HttpClient2, - token: &AccessToken, - id: &UpdateRequestId, - tx: &Sender) -> Result { - - match download_package_update(config, http_client, token, id) { - - Ok(path) => { - info!("Downloaded at {:?}. Installing...", path); - try!(tx.send(Event::UpdateStateChanged(id.clone(), UpdateState::Installing))); - - let p = try!(path.to_str() - .ok_or(Error::ParseError(format!("Path is not valid UTF-8: {:?}", path)))); - - match config.ota.package_manager.install_package(p) { - - Ok((code, output)) => { - try!(tx.send(Event::UpdateStateChanged(id.clone(), UpdateState::Installed))); - - // XXX: Slight code duplication, see interpret(PostInstalledPackages). - let pkgs = try!(config.ota.package_manager.installed_packages()); - try!(post_packages(config, http_client, token, &pkgs)); - - Ok(UpdateReport::new(id.clone(), code, output)) - } - - Err((code, output)) => { - try!(tx.send(Event::UpdateErrored(id.clone(), format!("{:?}: {:?}", code, output)))); - Ok(UpdateReport::new(id.clone(), code, output)) - } - - } - - } - - Err(err) => { - try!(tx.send(Event::UpdateErrored(id.clone(), format!("{:?}", err)))); - Ok(UpdateReport::new(id.clone(), - UpdateResultCode::GENERAL_ERROR, - format!("Download failed: {:?}", err))) - } - } - -} - -fn interpreter(env: &mut Env, cmd: Command, tx: &Sender) -> Result<(), Error> { - - Ok(if let Some(token) = env.access_token.to_owned() { - - partial_apply!( - [get_package_updates], - [post_packages, send_install_report], - [install_package_update], &env, &token); - - match cmd { - - Authenticate(_) => (), // Already authenticated. - - AcceptUpdate(ref id) => { - try!(tx.send(Event::UpdateStateChanged(id.clone(), UpdateState::Downloading))); - let report = try!(install_package_update(id.to_owned(), tx.to_owned())); - try!(send_install_report(report.clone())); - info!("Update finished. Report sent: {:?}", report) - } - - GetPendingUpdates => { - let updates = try!(get_package_updates()); - let update_events: Vec = updates - .iter() - .map(|id| Event::NewUpdateAvailable(id.clone())) - .collect(); - info!("New package updates available: {:?}", update_events); - try!(tx.send(Event::Batch(update_events))) - } - - ListInstalledPackages => { - let pkgs = try!(env.config.ota.package_manager.installed_packages()); - try!(tx.send(Event::FoundInstalledPackages(pkgs.clone()))) - } - - PostInstalledPackages => { - let pkgs = try!(env.config.ota.package_manager.installed_packages()); - debug!("Found installed packages in the system: {:?}", pkgs); - try!(post_packages(pkgs)); - info!("Posted installed packages to the server.") - } - - Shutdown => exit(0) - } - - } else { - - match cmd { - - Authenticate(_) => { - // XXX: partially apply? - let token = try!(authenticate(&env.config.auth, &*env.http_client)); - env.access_token = Some(token.into()) - } - - Shutdown => exit(0), - - AcceptUpdate(_) | - GetPendingUpdates | - ListInstalledPackages | - PostInstalledPackages => - tx.send(Event::NotAuthenticated) - .unwrap_or(error!("not_auth: send failed.")) - } - - }) - -} - -pub struct OurInterpreter; - -impl<'a> Interpreter, Command, Event> for OurInterpreter { - - fn interpret(env: &mut Env, cmd: Command, tx: Sender) { - interpreter(env, cmd, &tx) - .unwrap_or_else(|err| tx.send(Event::Error(format!("{}", err))) - .unwrap_or(error!("interpret: send failed."))) - } - -} diff --git a/src/new_ota_plus.rs b/src/new_ota_plus.rs deleted file mode 100644 index 05171c4..0000000 --- a/src/new_ota_plus.rs +++ /dev/null @@ -1,179 +0,0 @@ -use rustc_serialize::json; -use std::fs::File; -use std::path::PathBuf; - -use datatype::{AccessToken, Config, Error, Url, UpdateRequestId, - UpdateReport, UpdateReportWithVin, Package}; -use http_client::{Auth, HttpClient2, HttpRequest2}; - - -fn vehicle_endpoint(config: &Config, s: &str) -> Url { - config.ota.server.join(&format!("/api/v1/vehicles/{}/{}", config.auth.vin, s)).unwrap() -} - -pub fn download_package_update(config: &Config, - client: &HttpClient2, - token: &AccessToken, - id: &UpdateRequestId) -> Result { - - let req = HttpRequest2::get( - vehicle_endpoint(config, &format!("updates/{}/download", id)), - Some(Auth::Token(token)), - ); - - let mut path = PathBuf::new(); - path.push(&config.ota.packages_dir); - path.push(id); - path.set_extension(config.ota.package_manager.extension()); - - let mut file = try!(File::create(path.as_path())); - - try!(client.send_request_to(&req, &mut file)); - - return Ok(path) - -} - -pub fn send_install_report(config: &Config, - client: &HttpClient2, - token: &AccessToken, - report: &UpdateReport) -> Result<(), Error> { - - let report_with_vin = UpdateReportWithVin::new(&config.auth.vin, &report); - let json = try!(json::encode(&report_with_vin)); - - let req = HttpRequest2::post( - vehicle_endpoint(config, &format!("/updates/{}", report.update_id)), - Some(Auth::Token(token)), - Some(json) - ); - - let _: String = try!(client.send_request(&req)); - - return Ok(()) - -} - -pub fn get_package_updates(config: &Config, - client: &HttpClient2, - token: &AccessToken) -> Result, Error> { - - let req = HttpRequest2::get( - vehicle_endpoint(&config, "/updates"), - Some(Auth::Token(token)), - ); - - let resp = try!(client.send_request(&req)); - - return Ok(try!(json::decode::>(&resp))); - -} - -pub fn post_packages(config: &Config, - client: &HttpClient2, - token: &AccessToken, - pkgs: &Vec) -> Result<(), Error> { - - let json = try!(json::encode(&pkgs)); - - let req = HttpRequest2::post( - vehicle_endpoint(config, "/updates"), - Some(Auth::Token(token)), - Some(json), - ); - - let _: String = try!(client.send_request(&req)); - - return Ok(()) -} - -/* - -#[cfg(test)] -mod tests { - - use std::io::Write; - - use super::*; - use datatype::AccessToken; - use datatype::{Config, OtaConfig}; - use datatype::Error; - use datatype::Package; - use http_client::BadHttpClient; - use http_client::{HttpRequest, HttpClient}; - - - fn test_token() -> AccessToken { - AccessToken { - access_token: "token".to_string(), - token_type: "bar".to_string(), - expires_in: 20, - scope: vec![] - } - } - - fn test_package() -> Package { - Package { - name: "hey".to_string(), - version: "1.2.3".to_string() - } - } - - struct MockClient; - - impl HttpClient for MockClient { - - fn new() -> MockClient { - MockClient - } - - fn send_request(&self, _: &HttpRequest) -> Result { - return Ok("[\"pkgid\"]".to_string()) - } - - fn send_request_to(&self, _: &HttpRequest, _: W) -> Result<(), Error> { - return Ok(()) - } - - } - - #[test] - fn test_post_packages_sends_authentication() { - assert_eq!( - post_packages::(&test_token(), &Config::default(), &vec![test_package()]) - .unwrap(), ()) - } - - #[test] - fn test_get_package_updates() { - assert_eq!(get_package_updates::(&test_token(), &Config::default()).unwrap(), - vec!["pkgid".to_string()]) - } - - #[test] - #[ignore] // TODO: docker daemon requires user namespaces for this to work - fn bad_packages_dir_download_package_update() { - let mut config = Config::default(); - config.ota = OtaConfig { packages_dir: "/".to_string(), .. config.ota }; - - assert_eq!( - format!("{}", - download_package_update::(&test_token(), &config, &"0".to_string()) - .unwrap_err()), - r#"Ota error, failed to create file "/0.deb": Permission denied (os error 13)"#) - } - - #[test] - fn bad_client_download_package_update() { - assert_eq!( - format!("{}", - download_package_update:: - (&test_token(), &Config::default(), &"0".to_string()) - .unwrap_err()), - r#"Ota error, the request: GET http://127.0.0.1:8080/api/v1/vehicles/V1234567890123456/updates/0/download, -results in the following error: bad client."#) - } - -} - -*/ diff --git a/src/ota_plus.rs b/src/ota_plus.rs index a4afebe..05171c4 100644 --- a/src/ota_plus.rs +++ b/src/ota_plus.rs @@ -1,107 +1,107 @@ -use hyper::header::{Authorization, Bearer, ContentType}; -use hyper::mime::{Mime, TopLevel, SubLevel, Attr, Value}; use rustc_serialize::json; use std::fs::File; use std::path::PathBuf; -use std::result::Result; -use datatype::{AccessToken, Config, Error, Package, Url, UpdateRequestId}; -use datatype::error::OtaReason::{CreateFile, Client}; -use datatype::{UpdateReport, UpdateReportWithVin}; -use http_client::{HttpClient, HttpRequest}; +use datatype::{AccessToken, Config, Error, Url, UpdateRequestId, + UpdateReport, UpdateReportWithVin, Package}; +use http_client::{Auth, HttpClient2, HttpRequest2}; -fn vehicle_endpoint(config: &Config, s: &str) -> Result { - Ok(try!(config.ota.server.join(&format!("/api/v1/vehicles/{}{}", config.auth.vin, s)))) +fn vehicle_endpoint(config: &Config, s: &str) -> Url { + config.ota.server.join(&format!("/api/v1/vehicles/{}/{}", config.auth.vin, s)).unwrap() } -pub fn download_package_update(token: &AccessToken, - config: &Config, - id: &UpdateRequestId) -> Result { +pub fn download_package_update(config: &Config, + client: &HttpClient2, + token: &AccessToken, + id: &UpdateRequestId) -> Result { - let url = try!(vehicle_endpoint(config, &format!("/updates/{}/download", id))); - let req = HttpRequest::get(url) - .with_header(Authorization(Bearer { token: token.access_token.clone() })); + let req = HttpRequest2::get( + vehicle_endpoint(config, &format!("updates/{}/download", id)), + Some(Auth::Token(token)), + ); let mut path = PathBuf::new(); path.push(&config.ota.packages_dir); path.push(id); path.set_extension(config.ota.package_manager.extension()); - let file = try!(File::create(path.as_path()) - .map_err(|e| Error::Ota(CreateFile(path.clone(), e)))); + let mut file = try!(File::create(path.as_path())); - try!(C::new().send_request_to(&req, file) - .map_err(|e| Error::Ota(Client(req.to_string(), format!("{}", e))))); + try!(client.send_request_to(&req, &mut file)); return Ok(path) + } -pub fn send_install_report(token: &AccessToken, - config: &Config, - report: &UpdateReport) -> Result<(), Error> { +pub fn send_install_report(config: &Config, + client: &HttpClient2, + token: &AccessToken, + report: &UpdateReport) -> Result<(), Error> { let report_with_vin = UpdateReportWithVin::new(&config.auth.vin, &report); - let json = try!(json::encode(&report_with_vin) - .map_err(|_| Error::ParseError(String::from("JSON encoding error")))); + let json = try!(json::encode(&report_with_vin)); - let url = try!(vehicle_endpoint(config, &format!("/updates/{}", report.update_id))); - let req = HttpRequest::post(url) - .with_header(Authorization(Bearer { token: token.access_token.clone() })) - .with_header(ContentType(Mime( - TopLevel::Application, - SubLevel::Json, - vec![(Attr::Charset, Value::Utf8)]))) - .with_body(&json); + let req = HttpRequest2::post( + vehicle_endpoint(config, &format!("/updates/{}", report.update_id)), + Some(Auth::Token(token)), + Some(json) + ); - let _: String = try!(C::new().send_request(&req)); + let _: String = try!(client.send_request(&req)); return Ok(()) + } -pub fn get_package_updates(token: &AccessToken, - config: &Config) -> Result, Error> { +pub fn get_package_updates(config: &Config, + client: &HttpClient2, + token: &AccessToken) -> Result, Error> { - let url = try!(vehicle_endpoint(&config, "/updates")); - let req = HttpRequest::get(url) - .with_header(Authorization(Bearer { token: token.access_token.clone() })); + let req = HttpRequest2::get( + vehicle_endpoint(&config, "/updates"), + Some(Auth::Token(token)), + ); - let body = try!(C::new().send_request(&req) - .map_err(|e| Error::ClientError(format!("Can't consult package updates: {}", e)))); + let resp = try!(client.send_request(&req)); - return Ok(try!(json::decode::>(&body))); + return Ok(try!(json::decode::>(&resp))); } -pub fn post_packages(token: &AccessToken, - config: &Config, - pkgs: &Vec) -> Result<(), Error> { +pub fn post_packages(config: &Config, + client: &HttpClient2, + token: &AccessToken, + pkgs: &Vec) -> Result<(), Error> { - let json = try!(json::encode(&pkgs) - .map_err(|_| Error::ParseError(String::from("JSON encoding error")))); + let json = try!(json::encode(&pkgs)); - let url = try!(vehicle_endpoint(config, "/updates")); - let req = HttpRequest::post(url) - .with_header(Authorization(Bearer { token: token.access_token.clone() })) - .with_header(ContentType(Mime( - TopLevel::Application, - SubLevel::Json, - vec![(Attr::Charset, Value::Utf8)]))) - .with_body(&json); + let req = HttpRequest2::post( + vehicle_endpoint(config, "/updates"), + Some(Auth::Token(token)), + Some(json), + ); - let _: String = try!(C::new().send_request(&req)); + let _: String = try!(client.send_request(&req)); return Ok(()) } +/* + #[cfg(test)] mod tests { + + use std::io::Write; + use super::*; use datatype::AccessToken; use datatype::{Config, OtaConfig}; + use datatype::Error; use datatype::Package; - use http_client::{MockHttpClient, BadHttpClient}; - use http_client::HttpClient; + use http_client::BadHttpClient; + use http_client::{HttpRequest, HttpClient}; + fn test_token() -> AccessToken { AccessToken { @@ -119,16 +119,34 @@ mod tests { } } + struct MockClient; + + impl HttpClient for MockClient { + + fn new() -> MockClient { + MockClient + } + + fn send_request(&self, _: &HttpRequest) -> Result { + return Ok("[\"pkgid\"]".to_string()) + } + + fn send_request_to(&self, _: &HttpRequest, _: W) -> Result<(), Error> { + return Ok(()) + } + + } + #[test] fn test_post_packages_sends_authentication() { assert_eq!( - post_packages::(&test_token(), &Config::default(), &vec![test_package()]) + post_packages::(&test_token(), &Config::default(), &vec![test_package()]) .unwrap(), ()) } #[test] fn test_get_package_updates() { - assert_eq!(get_package_updates::(&test_token(), &Config::default()).unwrap(), + assert_eq!(get_package_updates::(&test_token(), &Config::default()).unwrap(), vec!["pkgid".to_string()]) } @@ -140,7 +158,7 @@ mod tests { assert_eq!( format!("{}", - download_package_update::(&test_token(), &config, &"0".to_string()) + download_package_update::(&test_token(), &config, &"0".to_string()) .unwrap_err()), r#"Ota error, failed to create file "/0.deb": Permission denied (os error 13)"#) } @@ -155,4 +173,7 @@ mod tests { r#"Ota error, the request: GET http://127.0.0.1:8080/api/v1/vehicles/V1234567890123456/updates/0/download, results in the following error: bad client."#) } + } + +*/ -- cgit v1.2.1 From 94be6f97f885e2a69160dd0da1bdb9bec510d4ea Mon Sep 17 00:00:00 2001 From: Stevan Andjelkovic Date: Mon, 25 Apr 2016 14:52:03 +0200 Subject: Remove old http client. --- src/auth_plus.rs | 6 +- src/http_client/bad_http_client.rs | 23 ----- src/http_client/http_client.rs | 22 ++--- src/http_client/hyper.rs | 10 +-- src/http_client/interface.rs | 171 ------------------------------------ src/http_client/mock_http_client.rs | 21 ----- src/http_client/mod.rs | 7 +- src/interpreter.rs | 6 +- src/ota_plus.rs | 18 ++-- 9 files changed, 32 insertions(+), 252 deletions(-) delete mode 100644 src/http_client/bad_http_client.rs delete mode 100644 src/http_client/interface.rs diff --git a/src/auth_plus.rs b/src/auth_plus.rs index ec26370..766e7de 100644 --- a/src/auth_plus.rs +++ b/src/auth_plus.rs @@ -1,12 +1,12 @@ use rustc_serialize::json; use datatype::{AccessToken, AuthConfig, ClientId, ClientSecret, Error}; -use http_client::{Auth, HttpClient2, HttpRequest2}; +use http_client::{Auth, HttpClient, HttpRequest}; -pub fn authenticate(config: &AuthConfig, client: &HttpClient2) -> Result { +pub fn authenticate(config: &AuthConfig, client: &HttpClient) -> Result { - let req = HttpRequest2::post::<_, _, String>( + let req = HttpRequest::post::<_, _, String>( config.server.join("/token").unwrap(), Some(Auth::Credentials( ClientId { get: config.client_id.clone() }, diff --git a/src/http_client/bad_http_client.rs b/src/http_client/bad_http_client.rs deleted file mode 100644 index 8e3b000..0000000 --- a/src/http_client/bad_http_client.rs +++ /dev/null @@ -1,23 +0,0 @@ -use std::io::Write; - -use datatype::Error; -use http_client::{HttpClient, HttpRequest}; - - -pub struct BadHttpClient; - -impl HttpClient for BadHttpClient { - - fn new() -> BadHttpClient { - BadHttpClient - } - - fn send_request(&self, _: &HttpRequest) -> Result { - Err(Error::ClientError("bad client.".to_string())) - } - - fn send_request_to(&self, _: &HttpRequest, _: W) -> Result<(), Error> { - Err(Error::ClientError("bad client.".to_string())) - } - -} diff --git a/src/http_client/http_client.rs b/src/http_client/http_client.rs index d38cb8a..82b931c 100644 --- a/src/http_client/http_client.rs +++ b/src/http_client/http_client.rs @@ -18,26 +18,26 @@ impl<'a> Into>> for Auth<'a> { } } -pub struct HttpRequest2<'a> { +pub struct HttpRequest<'a> { pub method: Cow<'a, Method>, pub url: Cow<'a, Url>, pub auth: Option>>, pub body: Option>, } -impl<'a> HttpRequest2<'a> { +impl<'a> HttpRequest<'a> { fn new(meth: M, url: U, auth: Option, - body: Option) -> HttpRequest2<'a> + body: Option) -> HttpRequest<'a> where M: Into>, U: Into>, A: Into>>, B: Into> { - HttpRequest2 { + HttpRequest { method: meth.into(), url: url.into(), auth: auth.map(|a| a.into()), @@ -45,28 +45,28 @@ impl<'a> HttpRequest2<'a> { } } - pub fn get(url: U, auth: Option) -> HttpRequest2<'a> + pub fn get(url: U, auth: Option) -> HttpRequest<'a> where U: Into>, A: Into>>, { - HttpRequest2::new::<_, _, _, String>(Method::Get, url, auth, None) + HttpRequest::new::<_, _, _, String>(Method::Get, url, auth, None) } - pub fn post(url: U, auth: Option, body: Option) -> HttpRequest2<'a> + pub fn post(url: U, auth: Option, body: Option) -> HttpRequest<'a> where U: Into>, A: Into>>, B: Into> { - HttpRequest2::new(Method::Post, url, auth, body) + HttpRequest::new(Method::Post, url, auth, body) } } -pub trait HttpClient2: Send + Sync { +pub trait HttpClient: Send + Sync { - fn send_request_to(&self, request: &HttpRequest2, file: &mut File) -> Result<(), Error> { + fn send_request_to(&self, request: &HttpRequest, file: &mut File) -> Result<(), Error> { let s = try!(Self::send_request(self, request)); @@ -74,7 +74,7 @@ pub trait HttpClient2: Send + Sync { } - fn send_request(&self, request: &HttpRequest2) -> Result { + fn send_request(&self, request: &HttpRequest) -> Result { let mut temp_file: File = try!(tempfile::tempfile()); diff --git a/src/http_client/hyper.rs b/src/http_client/hyper.rs index 05cbd99..a79d6b1 100644 --- a/src/http_client/hyper.rs +++ b/src/http_client/hyper.rs @@ -8,7 +8,7 @@ use std::fs::File; use std::io::{Read, Write, BufReader, BufWriter}; use datatype::Error; -use http_client::{Auth, HttpClient2, HttpRequest2}; +use http_client::{Auth, HttpClient, HttpRequest}; pub struct Hyper { @@ -23,9 +23,9 @@ impl Hyper { } } -impl HttpClient2 for Hyper { +impl HttpClient for Hyper { - fn send_request_to(&self, req: &HttpRequest2, file: &mut File) -> Result<(), Error> { + fn send_request_to(&self, req: &HttpRequest, file: &mut File) -> Result<(), Error> { let mut headers = Headers::new(); let mut body = String::new(); @@ -97,13 +97,13 @@ impl HttpClient2 for Hyper { } -fn relocate_request<'a>(req: &'a HttpRequest2, resp: &Response) -> Result, Error> { +fn relocate_request<'a>(req: &'a HttpRequest, resp: &Response) -> Result, Error> { if let Some(&Location(ref loc)) = resp.headers.get::() { let url = try!(req.url.join(loc)); - Ok(HttpRequest2 { + Ok(HttpRequest { url: url.into(), method: req.method.clone(), auth: None, diff --git a/src/http_client/interface.rs b/src/http_client/interface.rs deleted file mode 100644 index 522d813..0000000 --- a/src/http_client/interface.rs +++ /dev/null @@ -1,171 +0,0 @@ -use hyper::header::{Headers, Header, HeaderFormat, Location, Authorization, Bearer}; -use hyper::method::Method; -use hyper::client::response::Response; -use hyper; -use std::io::{Read, Write, BufReader, BufWriter}; - -use datatype::{Error, Url}; - - -#[derive(Clone, Debug)] -pub struct HttpRequest<'a> { - pub url: Url, - pub method: Method, - pub headers: Headers, - pub body: Option<&'a str>, -} - -impl<'a> ToString for HttpRequest<'a> { - fn to_string(&self) -> String { - format!("{} {}", self.method, self.url.to_string()) - } -} - -impl<'a> HttpRequest<'a> { - pub fn new(url: Url, method: Method) -> HttpRequest<'a> { - HttpRequest { - url: url, - method: method, - headers: Headers::new(), - body: None, - } - } - - pub fn get(url: Url) -> HttpRequest<'a> { - HttpRequest::new(url, Method::Get) - } - - pub fn post(url: Url) -> HttpRequest<'a> { - HttpRequest::new(url, Method::Post) - } - - pub fn with_body(&self, body: &'a str) -> HttpRequest<'a> { - HttpRequest { body: Some(body), ..self.clone() } - } - - pub fn with_header(&self, header: H) -> HttpRequest<'a> { - let mut hs = self.headers.clone(); - hs.set(header); - HttpRequest { headers: hs, ..self.clone() } - } -} - -pub trait HttpClient { - fn new() -> Self; - fn send_request(&self, req: &HttpRequest) -> Result; - fn send_request_to(&self, req: &HttpRequest, to: W) -> Result<(), Error>; -} - -impl HttpClient for hyper::Client { - fn new() -> hyper::Client { - let mut client = hyper::Client::new(); - client.set_redirect_policy(hyper::client::RedirectPolicy::FollowNone); - client - } - - fn send_request(&self, req: &HttpRequest) -> Result { - self.request(req.method.clone(), req.url.clone()) - .headers(req.headers.clone()) - .body(req.body.unwrap_or("")) - .send() - .map_err(|e| Error::ClientError(format!("{}", e))) - .and_then(|mut resp| { - match resp.status.class() { - hyper::status::StatusClass::Success => { - let mut rbody = String::new(); - resp.read_to_string(&mut rbody) - .map_err(|e| Error::ParseError(format!("Cannot read response: {}", e))) - .map(|_| rbody) - } - hyper::status::StatusClass::Redirection => { - relocate_request(req, &resp).and_then(|ref r| self.send_request(r)) - } - _ => { - Err(Error::ClientError(format!("Request failed with status {}", - resp.status))) - } - } - }) - } - - fn send_request_to(&self, req: &HttpRequest, to: W) -> Result<(), Error> { - self.request(req.method.clone(), req.url.clone()) - .headers(req.headers.clone()) - .body(req.body.unwrap_or("")) - .send() - .map_err(|e| Error::ClientError(format!("{}", e))) - .and_then(|resp| { - match resp.status.class() { - hyper::status::StatusClass::Success => { - tee(resp, to) - .map_err(|e| Error::ParseError(format!("Cannot read response: {}", e))) - .map(|_| ()) - } - hyper::status::StatusClass::Redirection => { - relocate_request(req, &resp).and_then(|ref r| self.send_request_to(r, to)) - } - _ => { - Err(Error::ClientError(format!("Request failed with status {}", - resp.status))) - } - } - }) - } -} - -fn relocate_request<'a>(req: &'a HttpRequest, resp: &Response) -> Result, Error> { - match resp.headers.get::() { - Some(&Location(ref loc)) => { - req.url - .join(loc) - .map_err(|e| Error::ParseError(format!("Cannot read location: {}", e))) - .and_then(|url| { - let mut headers = req.headers.clone(); - headers.remove::>(); - Ok(HttpRequest { - url: url, - method: req.method.clone(), - headers: headers, - body: req.body, - }) - }) - } - None => Err(Error::ClientError("Redirect with no Location header".to_string())), - } -} - -pub fn tee(from: R, to: W) -> Result<(), Error> { - let mb = 1024 * 1024; - let rbuf = BufReader::with_capacity(5 * mb, from); - let mut wbuf = BufWriter::with_capacity(5 * mb, to); - for b in rbuf.bytes() { - try!(wbuf.write(&[try!(b)])); - } - Ok(()) -} - -#[cfg(test)] -mod tests { - use super::*; - use std::fs::File; - use std::io::{Read, repeat}; - - #[test] - fn test_tee() { - let values = repeat(b'a').take(9000); - let sink = File::create("/tmp/otaplus_tee_test").unwrap(); - - assert!(tee(values, sink).is_ok()); - - let mut values2 = repeat(b'a').take(9000); - let mut expected = Vec::new(); - let _ = values2.read_to_end(&mut expected); - - let mut f = File::open("/tmp/otaplus_tee_test").unwrap(); - let mut result = Vec::new(); - let _ = f.read_to_end(&mut result); - - assert_eq!(result, expected); - } - -} diff --git a/src/http_client/mock_http_client.rs b/src/http_client/mock_http_client.rs index 5e1c540..e8e5244 100644 --- a/src/http_client/mock_http_client.rs +++ b/src/http_client/mock_http_client.rs @@ -1,24 +1,3 @@ -use std::io::Write; - -use datatype::Error; -use http_client::{HttpClient, HttpRequest}; - - -pub struct MockHttpClient; - -impl HttpClient for MockHttpClient { - fn new() -> MockHttpClient { - MockHttpClient - } - - fn send_request(&self, _: &HttpRequest) -> Result { - return Ok("[\"pkgid\"]".to_string()) - } - - fn send_request_to(&self, _: &HttpRequest, _: W) -> Result<(), Error> { - return Ok(()) - } -} #[cfg(test)] mod tests { diff --git a/src/http_client/mod.rs b/src/http_client/mod.rs index 90ae11f..c4f1d03 100644 --- a/src/http_client/mod.rs +++ b/src/http_client/mod.rs @@ -1,11 +1,6 @@ -pub use self::bad_http_client::BadHttpClient; -pub use self::http_client::{Auth, HttpClient2, HttpRequest2}; +pub use self::http_client::{Auth, HttpClient, HttpRequest}; pub use self::hyper::Hyper; -pub use self::interface::{HttpClient, HttpRequest}; -pub use self::mock_http_client::MockHttpClient; -pub mod bad_http_client; pub mod mock_http_client; pub mod http_client; pub mod hyper; -pub mod interface; diff --git a/src/interpreter.rs b/src/interpreter.rs index 8f27acc..51cc838 100644 --- a/src/interpreter.rs +++ b/src/interpreter.rs @@ -7,7 +7,7 @@ use auth_plus::authenticate; use datatype::{AccessToken, Command, Config, Error, Event, UpdateReport, UpdateRequestId, UpdateState, UpdateResultCode}; use datatype::Command::*; -use http_client::HttpClient2; +use http_client::HttpClient; use interaction_library::interpreter::Interpreter; use ota_plus::{get_package_updates, download_package_update, post_packages, send_install_report}; @@ -16,7 +16,7 @@ use ota_plus::{get_package_updates, download_package_update, post_packages, send pub struct Env<'a> { pub config: Config, pub access_token: Option>, - pub http_client: Arc, + pub http_client: Arc, } // This macro partially applies the config and http client to the passed @@ -31,7 +31,7 @@ macro_rules! partial_apply { // XXX: Move this somewhere else? fn install_package_update(config: &Config, - http_client: &HttpClient2, + http_client: &HttpClient, token: &AccessToken, id: &UpdateRequestId, tx: &Sender) -> Result { diff --git a/src/ota_plus.rs b/src/ota_plus.rs index 05171c4..5f31a38 100644 --- a/src/ota_plus.rs +++ b/src/ota_plus.rs @@ -4,7 +4,7 @@ use std::path::PathBuf; use datatype::{AccessToken, Config, Error, Url, UpdateRequestId, UpdateReport, UpdateReportWithVin, Package}; -use http_client::{Auth, HttpClient2, HttpRequest2}; +use http_client::{Auth, HttpClient, HttpRequest}; fn vehicle_endpoint(config: &Config, s: &str) -> Url { @@ -12,11 +12,11 @@ fn vehicle_endpoint(config: &Config, s: &str) -> Url { } pub fn download_package_update(config: &Config, - client: &HttpClient2, + client: &HttpClient, token: &AccessToken, id: &UpdateRequestId) -> Result { - let req = HttpRequest2::get( + let req = HttpRequest::get( vehicle_endpoint(config, &format!("updates/{}/download", id)), Some(Auth::Token(token)), ); @@ -35,14 +35,14 @@ pub fn download_package_update(config: &Config, } pub fn send_install_report(config: &Config, - client: &HttpClient2, + client: &HttpClient, token: &AccessToken, report: &UpdateReport) -> Result<(), Error> { let report_with_vin = UpdateReportWithVin::new(&config.auth.vin, &report); let json = try!(json::encode(&report_with_vin)); - let req = HttpRequest2::post( + let req = HttpRequest::post( vehicle_endpoint(config, &format!("/updates/{}", report.update_id)), Some(Auth::Token(token)), Some(json) @@ -55,10 +55,10 @@ pub fn send_install_report(config: &Config, } pub fn get_package_updates(config: &Config, - client: &HttpClient2, + client: &HttpClient, token: &AccessToken) -> Result, Error> { - let req = HttpRequest2::get( + let req = HttpRequest::get( vehicle_endpoint(&config, "/updates"), Some(Auth::Token(token)), ); @@ -70,13 +70,13 @@ pub fn get_package_updates(config: &Config, } pub fn post_packages(config: &Config, - client: &HttpClient2, + client: &HttpClient, token: &AccessToken, pkgs: &Vec) -> Result<(), Error> { let json = try!(json::encode(&pkgs)); - let req = HttpRequest2::post( + let req = HttpRequest::post( vehicle_endpoint(config, "/updates"), Some(Auth::Token(token)), Some(json), -- cgit v1.2.1 From a0536d02ac6dc17c206474049be72871c8151fb6 Mon Sep 17 00:00:00 2001 From: Stevan Andjelkovic Date: Mon, 25 Apr 2016 17:06:47 +0200 Subject: Bring auth tests back to life. --- src/auth_plus.rs | 72 +++++++++++------------------------------- src/http_client/http_client.rs | 8 ++--- src/http_client/mod.rs | 4 ++- src/http_client/test.rs | 30 ++++++++++++++++++ 4 files changed, 56 insertions(+), 58 deletions(-) create mode 100644 src/http_client/test.rs diff --git a/src/auth_plus.rs b/src/auth_plus.rs index 766e7de..57d827d 100644 --- a/src/auth_plus.rs +++ b/src/auth_plus.rs @@ -20,44 +20,24 @@ pub fn authenticate(config: &AuthConfig, client: &HttpClient) -> Result MockClient { - MockClient - } - - fn send_request(&self, _: &HttpRequest) -> Result { - Ok(r#"{"access_token": "token", - "token_type": "type", - "expires_in": 10, - "scope": ["scope"]}"#.to_string()) - } - - fn send_request_to(&self, _: &HttpRequest, _: W) -> Result<(), Error> { - Ok(()) - } - } + const TOKEN: &'static str = + r#"{"access_token": "token", + "token_type": "type", + "expires_in": 10, + "scope": ["scope"]} + "#; #[test] fn test_authenticate() { - assert_eq!(authenticate::(&AuthConfig::default()).unwrap(), + assert_eq!(authenticate(&AuthConfig::default(), &TestHttpClient::from(vec![TOKEN])).unwrap(), AccessToken { access_token: "token".to_string(), token_type: "type".to_string(), @@ -67,34 +47,20 @@ mod tests { } #[test] - fn test_authenticate_bad_client() { - assert_eq!(format!("{}", authenticate::(&AuthConfig::default()).unwrap_err()), - "Authentication error, didn't receive access token: bad client.") + fn test_authenticate_no_token() { + assert_eq!(format!("{}", authenticate(&AuthConfig::default(), + &TestHttpClient::new()).unwrap_err()), + r#"Failed to decode JSON: ParseError(SyntaxError("EOF While parsing value", 1, 1))"#) + + // XXX: Old error message was arguebly a lot better... + // "Authentication error, didn't receive access token.") } #[test] - fn test_authenticate_bad_json_client() { - - struct BadJsonClient; - - impl HttpClient for BadJsonClient { - - fn new() -> BadJsonClient { - BadJsonClient - } - - fn send_request(&self, _: &HttpRequest) -> Result { - Ok(r#"{"apa": 1}"#.to_string()) - } - - fn send_request_to(&self, _: &HttpRequest, _: W) -> Result<(), Error> { - Ok(()) - } - } - - assert_eq!(format!("{}", authenticate::(&AuthConfig::default()).unwrap_err()), + fn test_authenticate_bad_json() { + assert_eq!(format!("{}", authenticate(&AuthConfig::default(), + &TestHttpClient::from(vec![r#"{"apa": 1}"#])).unwrap_err()), r#"Failed to decode JSON: MissingFieldError("access_token")"#) } } -*/ diff --git a/src/http_client/http_client.rs b/src/http_client/http_client.rs index 82b931c..e555488 100644 --- a/src/http_client/http_client.rs +++ b/src/http_client/http_client.rs @@ -66,19 +66,19 @@ impl<'a> HttpRequest<'a> { pub trait HttpClient: Send + Sync { - fn send_request_to(&self, request: &HttpRequest, file: &mut File) -> Result<(), Error> { + fn send_request_to(&self, req: &HttpRequest, file: &mut File) -> Result<(), Error> { - let s = try!(Self::send_request(self, request)); + let s = try!(Self::send_request(self, req)); Ok(try!(file.write_all(&s.as_bytes()))) } - fn send_request(&self, request: &HttpRequest) -> Result { + fn send_request(&self, req: &HttpRequest) -> Result { let mut temp_file: File = try!(tempfile::tempfile()); - try!(Self::send_request_to(self, request, &mut temp_file)); + try!(Self::send_request_to(self, req, &mut temp_file)); let mut buf = String::new(); let _: usize = try!(temp_file.read_to_string(&mut buf)); diff --git a/src/http_client/mod.rs b/src/http_client/mod.rs index c4f1d03..9854c0d 100644 --- a/src/http_client/mod.rs +++ b/src/http_client/mod.rs @@ -1,6 +1,8 @@ pub use self::http_client::{Auth, HttpClient, HttpRequest}; pub use self::hyper::Hyper; +pub use self::test::TestHttpClient; -pub mod mock_http_client; pub mod http_client; pub mod hyper; +pub mod mock_http_client; +pub mod test; diff --git a/src/http_client/test.rs b/src/http_client/test.rs new file mode 100644 index 0000000..927e6e0 --- /dev/null +++ b/src/http_client/test.rs @@ -0,0 +1,30 @@ +use datatype::Error; +use http_client::{HttpClient, HttpRequest}; + + +pub struct TestHttpClient<'a> { + replies: Vec<&'a str>, +} + +impl<'a> TestHttpClient<'a> { + + pub fn new() -> TestHttpClient<'a> { + TestHttpClient { replies: Vec::new() } + } + + pub fn from(replies: Vec<&'a str>) -> TestHttpClient<'a> { + TestHttpClient { replies: replies } + } + +} + +impl<'a> HttpClient for TestHttpClient<'a> { + + fn send_request(&self, _: &HttpRequest) -> Result { + + // XXX: this does't work... needs &mut self... + let mut replies = self.replies.clone(); + Ok(replies.pop().unwrap_or("").to_string()) + } + +} -- cgit v1.2.1 From 91531c0c305a1ae3c83c14011495bb372ec39030 Mon Sep 17 00:00:00 2001 From: Stevan Andjelkovic Date: Mon, 25 Apr 2016 17:25:21 +0200 Subject: Move redirect test code. --- src/http_client/hyper.rs | 27 +++++++++++++++++++++++++++ src/http_client/mock_http_client.rs | 31 ------------------------------- src/http_client/mod.rs | 1 - 3 files changed, 27 insertions(+), 32 deletions(-) delete mode 100644 src/http_client/mock_http_client.rs diff --git a/src/http_client/hyper.rs b/src/http_client/hyper.rs index a79d6b1..29f215c 100644 --- a/src/http_client/hyper.rs +++ b/src/http_client/hyper.rs @@ -135,6 +135,7 @@ pub fn tee(from: R, to: W) -> Result<(), Error> { #[cfg(test)] mod tests { + use hyper; use std::fs::File; use std::io::{Read, repeat}; @@ -159,4 +160,30 @@ mod tests { assert_eq!(result, expected); } + mock_connector!(MockRedirectPolicy { + "http://127.0.0.1" => "HTTP/1.1 301 Redirect\r\n\ + Location: http://127.0.0.2\r\n\ + Server: mock1\r\n\ + \r\n\ + " + "http://127.0.0.2" => "HTTP/1.1 302 Found\r\n\ + Location: https://127.0.0.3\r\n\ + Server: mock2\r\n\ + \r\n\ + " + "https://127.0.0.3" => "HTTP/1.1 200 OK\r\n\ + Server: mock3\r\n\ + \r\n\ + " + }); + + #[test] + fn test_redirect_followall() { + let mut client = hyper::Client::with_connector(MockRedirectPolicy::default()); + client.set_redirect_policy(hyper::client::RedirectPolicy::FollowAll); + + let res = client.get("http://127.0.0.1").send().unwrap(); + assert_eq!(res.headers.get(), Some(&hyper::header::Server("mock3".to_owned()))); + } + } diff --git a/src/http_client/mock_http_client.rs b/src/http_client/mock_http_client.rs deleted file mode 100644 index e8e5244..0000000 --- a/src/http_client/mock_http_client.rs +++ /dev/null @@ -1,31 +0,0 @@ - -#[cfg(test)] -mod tests { - use hyper; - - mock_connector!(MockRedirectPolicy { - "http://127.0.0.1" => "HTTP/1.1 301 Redirect\r\n\ - Location: http://127.0.0.2\r\n\ - Server: mock1\r\n\ - \r\n\ - " - "http://127.0.0.2" => "HTTP/1.1 302 Found\r\n\ - Location: https://127.0.0.3\r\n\ - Server: mock2\r\n\ - \r\n\ - " - "https://127.0.0.3" => "HTTP/1.1 200 OK\r\n\ - Server: mock3\r\n\ - \r\n\ - " - }); - - #[test] - fn test_redirect_followall() { - let mut client = hyper::Client::with_connector(MockRedirectPolicy::default()); - client.set_redirect_policy(hyper::client::RedirectPolicy::FollowAll); - - let res = client.get("http://127.0.0.1").send().unwrap(); - assert_eq!(res.headers.get(), Some(&hyper::header::Server("mock3".to_owned()))); - } -} diff --git a/src/http_client/mod.rs b/src/http_client/mod.rs index 9854c0d..28faffe 100644 --- a/src/http_client/mod.rs +++ b/src/http_client/mod.rs @@ -4,5 +4,4 @@ pub use self::test::TestHttpClient; pub mod http_client; pub mod hyper; -pub mod mock_http_client; pub mod test; -- cgit v1.2.1 From dfd2939a34f306f9ba6bc3300cd1448e9998d1c4 Mon Sep 17 00:00:00 2001 From: Stevan Andjelkovic Date: Tue, 26 Apr 2016 11:47:42 +0200 Subject: Bring ota plus tests back to life. --- src/auth_plus.rs | 2 +- src/datatype/error.rs | 4 +-- src/datatype/method.rs | 9 ++++++ src/http_client/http_client.rs | 6 ++++ src/http_client/test.rs | 8 +++-- src/ota_plus.rs | 69 ++++++++++++++---------------------------- 6 files changed, 47 insertions(+), 51 deletions(-) diff --git a/src/auth_plus.rs b/src/auth_plus.rs index 57d827d..5a1f082 100644 --- a/src/auth_plus.rs +++ b/src/auth_plus.rs @@ -49,7 +49,7 @@ mod tests { #[test] fn test_authenticate_no_token() { assert_eq!(format!("{}", authenticate(&AuthConfig::default(), - &TestHttpClient::new()).unwrap_err()), + &TestHttpClient::from(vec![""])).unwrap_err()), r#"Failed to decode JSON: ParseError(SyntaxError("EOF While parsing value", 1, 1))"#) // XXX: Old error message was arguebly a lot better... diff --git a/src/datatype/error.rs b/src/datatype/error.rs index 47e7350..a8a1d31 100644 --- a/src/datatype/error.rs +++ b/src/datatype/error.rs @@ -92,10 +92,10 @@ impl Display for Error { fn fmt(&self, f: &mut Formatter) -> FmtResult { let inner: String = match *self { Error::AuthError(ref s) => format!("Authentication error, {}", s.clone()), - Error::ClientError(ref s) => s.clone(), + Error::ClientError(ref s) => format!("Http client error: {}", s.clone()), Error::Config(ref e) => format!("Failed to {}", e.clone()), Error::Hyper(ref e) => format!("Hyper error: {}", e.clone()), - Error::Io(ref e) => format!("IO Error{:?}", e.clone()), + Error::Io(ref e) => format!("IO error: {}", e.clone()), Error::JsonDecode(ref e) => format!("Failed to decode JSON: {}", e.clone()), Error::JsonEncode(ref e) => format!("Failed to encode JSON: {}", e.clone()), Error::Ota(ref e) => format!("Ota error, {}", e.clone()), diff --git a/src/datatype/method.rs b/src/datatype/method.rs index bc5e73b..3a834e1 100644 --- a/src/datatype/method.rs +++ b/src/datatype/method.rs @@ -8,6 +8,15 @@ pub enum Method { Post, } +impl ToString for Method { + fn to_string(&self) -> String { + match *self { + Method::Get => "GET".to_string(), + Method::Post => "POST".to_string(), + } + } +} + impl Into for Method { fn into(self) -> method::Method { match self { diff --git a/src/http_client/http_client.rs b/src/http_client/http_client.rs index e555488..c06f92c 100644 --- a/src/http_client/http_client.rs +++ b/src/http_client/http_client.rs @@ -64,6 +64,12 @@ impl<'a> HttpRequest<'a> { } +impl<'a> ToString for HttpRequest<'a> { + fn to_string(&self) -> String { + format!("{} {}", self.method.to_string(), self.url.to_string()) + } +} + pub trait HttpClient: Send + Sync { fn send_request_to(&self, req: &HttpRequest, file: &mut File) -> Result<(), Error> { diff --git a/src/http_client/test.rs b/src/http_client/test.rs index 927e6e0..8c7c96c 100644 --- a/src/http_client/test.rs +++ b/src/http_client/test.rs @@ -20,11 +20,15 @@ impl<'a> TestHttpClient<'a> { impl<'a> HttpClient for TestHttpClient<'a> { - fn send_request(&self, _: &HttpRequest) -> Result { + fn send_request(&self, req: &HttpRequest) -> Result { // XXX: this does't work... needs &mut self... let mut replies = self.replies.clone(); - Ok(replies.pop().unwrap_or("").to_string()) + + replies.pop() + .ok_or(Error::ClientError(req.to_string())) + .map(|s| s.to_string()) + } } diff --git a/src/ota_plus.rs b/src/ota_plus.rs index 5f31a38..1299065 100644 --- a/src/ota_plus.rs +++ b/src/ota_plus.rs @@ -87,20 +87,13 @@ pub fn post_packages(config: &Config, return Ok(()) } -/* #[cfg(test)] mod tests { - use std::io::Write; - use super::*; - use datatype::AccessToken; - use datatype::{Config, OtaConfig}; - use datatype::Error; - use datatype::Package; - use http_client::BadHttpClient; - use http_client::{HttpRequest, HttpClient}; + use datatype::{AccessToken, Config, OtaConfig, Package}; + use http_client::TestHttpClient; fn test_token() -> AccessToken { @@ -119,61 +112,45 @@ mod tests { } } - struct MockClient; - - impl HttpClient for MockClient { - - fn new() -> MockClient { - MockClient - } - - fn send_request(&self, _: &HttpRequest) -> Result { - return Ok("[\"pkgid\"]".to_string()) - } - - fn send_request_to(&self, _: &HttpRequest, _: W) -> Result<(), Error> { - return Ok(()) - } - - } - #[test] fn test_post_packages_sends_authentication() { - assert_eq!( - post_packages::(&test_token(), &Config::default(), &vec![test_package()]) - .unwrap(), ()) + assert_eq!(post_packages(&Config::default(), + &TestHttpClient::from(vec![""]), + &test_token(), + &vec![test_package()]) + .unwrap(), ()) } #[test] fn test_get_package_updates() { - assert_eq!(get_package_updates::(&test_token(), &Config::default()).unwrap(), + assert_eq!(get_package_updates(&Config::default(), + &TestHttpClient::from(vec![r#"["pkgid"]"#]), + &test_token()).unwrap(), vec!["pkgid".to_string()]) } #[test] - #[ignore] // TODO: docker daemon requires user namespaces for this to work fn bad_packages_dir_download_package_update() { let mut config = Config::default(); config.ota = OtaConfig { packages_dir: "/".to_string(), .. config.ota }; - assert_eq!( - format!("{}", - download_package_update::(&test_token(), &config, &"0".to_string()) - .unwrap_err()), - r#"Ota error, failed to create file "/0.deb": Permission denied (os error 13)"#) + assert_eq!(format!("{}", download_package_update(&config, + &TestHttpClient::from(vec![""]), + &test_token(), + &"0".to_string()) + .unwrap_err()), + "IO error: Permission denied (os error 13)") } #[test] fn bad_client_download_package_update() { - assert_eq!( - format!("{}", - download_package_update:: - (&test_token(), &Config::default(), &"0".to_string()) - .unwrap_err()), - r#"Ota error, the request: GET http://127.0.0.1:8080/api/v1/vehicles/V1234567890123456/updates/0/download, -results in the following error: bad client."#) + assert_eq!(format!("{}", + download_package_update(&Config::default(), + &TestHttpClient::new(), + &test_token(), + &"0".to_string()) + .unwrap_err()), + "Http client error: GET http://127.0.0.1:8080/api/v1/vehicles/V1234567890123456/updates/0/download") } } - -*/ -- cgit v1.2.1 From 2605496cca69ba6b0afda9ec4eae1a14868ef9c2 Mon Sep 17 00:00:00 2001 From: Stevan Andjelkovic Date: Wed, 27 Apr 2016 16:06:24 +0200 Subject: Make self mutable in http client. --- src/auth_plus.rs | 2 +- src/http_client/http_client.rs | 4 ++-- src/http_client/hyper.rs | 2 +- src/http_client/test.rs | 7 ++----- src/interpreter.rs | 32 +++++++++++++++++++------------- src/main.rs | 4 ++-- src/ota_plus.rs | 8 ++++---- 7 files changed, 31 insertions(+), 28 deletions(-) diff --git a/src/auth_plus.rs b/src/auth_plus.rs index 5a1f082..c90e6eb 100644 --- a/src/auth_plus.rs +++ b/src/auth_plus.rs @@ -4,7 +4,7 @@ use datatype::{AccessToken, AuthConfig, ClientId, ClientSecret, Error}; use http_client::{Auth, HttpClient, HttpRequest}; -pub fn authenticate(config: &AuthConfig, client: &HttpClient) -> Result { +pub fn authenticate(config: &AuthConfig, client: &mut HttpClient) -> Result { let req = HttpRequest::post::<_, _, String>( config.server.join("/token").unwrap(), diff --git a/src/http_client/http_client.rs b/src/http_client/http_client.rs index c06f92c..12a55d0 100644 --- a/src/http_client/http_client.rs +++ b/src/http_client/http_client.rs @@ -72,7 +72,7 @@ impl<'a> ToString for HttpRequest<'a> { pub trait HttpClient: Send + Sync { - fn send_request_to(&self, req: &HttpRequest, file: &mut File) -> Result<(), Error> { + fn send_request_to(&mut self, req: &HttpRequest, file: &mut File) -> Result<(), Error> { let s = try!(Self::send_request(self, req)); @@ -80,7 +80,7 @@ pub trait HttpClient: Send + Sync { } - fn send_request(&self, req: &HttpRequest) -> Result { + fn send_request(&mut self, req: &HttpRequest) -> Result { let mut temp_file: File = try!(tempfile::tempfile()); diff --git a/src/http_client/hyper.rs b/src/http_client/hyper.rs index 29f215c..aad5152 100644 --- a/src/http_client/hyper.rs +++ b/src/http_client/hyper.rs @@ -25,7 +25,7 @@ impl Hyper { impl HttpClient for Hyper { - fn send_request_to(&self, req: &HttpRequest, file: &mut File) -> Result<(), Error> { + fn send_request_to(&mut self, req: &HttpRequest, file: &mut File) -> Result<(), Error> { let mut headers = Headers::new(); let mut body = String::new(); diff --git a/src/http_client/test.rs b/src/http_client/test.rs index 8c7c96c..cf90e8c 100644 --- a/src/http_client/test.rs +++ b/src/http_client/test.rs @@ -20,12 +20,9 @@ impl<'a> TestHttpClient<'a> { impl<'a> HttpClient for TestHttpClient<'a> { - fn send_request(&self, req: &HttpRequest) -> Result { + fn send_request(&mut self, req: &HttpRequest) -> Result { - // XXX: this does't work... needs &mut self... - let mut replies = self.replies.clone(); - - replies.pop() + self.replies.pop() .ok_or(Error::ClientError(req.to_string())) .map(|s| s.to_string()) diff --git a/src/interpreter.rs b/src/interpreter.rs index 51cc838..cea2d5b 100644 --- a/src/interpreter.rs +++ b/src/interpreter.rs @@ -1,6 +1,6 @@ use std::borrow::Cow; use std::process::exit; -use std::sync::Arc; +use std::sync::{Arc, Mutex}; use std::sync::mpsc::Sender; use auth_plus::authenticate; @@ -12,26 +12,28 @@ use interaction_library::interpreter::Interpreter; use ota_plus::{get_package_updates, download_package_update, post_packages, send_install_report}; -#[derive(Clone)] pub struct Env<'a> { pub config: Config, pub access_token: Option>, - pub http_client: Arc, + pub http_client: Arc>, } -// This macro partially applies the config and http client to the passed -// in functions. +// This macro partially applies the config, http client and token to the +// passed in functions. macro_rules! partial_apply { - ([ $( $fun0: ident ),* ], [ $( $fun1: ident ),* ], [ $( $fun2: ident ),* ], $env: expr, $token: expr) => { - $(let $fun0 = || $fun0(&$env.config, &*$env.http_client, $token);)*; - $(let $fun1 = |arg| $fun1(&$env.config, &*$env.http_client, $token, &arg);)*; - $(let $fun2 = |arg1, arg2| $fun2(&$env.config, &*$env.http_client, $token, &arg1, &arg2);)*; + ([ $( $fun0: ident ),* ], + [ $( $fun1: ident ),* ], + [ $( $fun2: ident ),* ], + $config: expr, $client: expr, $token: expr) => { + $(let $fun0 = || $fun0(&$config, &mut *$client.lock().unwrap(), $token);)* + $(let $fun1 = |arg| $fun1(&$config, &mut *$client.lock().unwrap(), $token, &arg);)* + $(let $fun2 = |arg1, arg2| $fun2(&$config, &mut *$client.lock().unwrap(), $token, &arg1, &arg2);)* } } // XXX: Move this somewhere else? fn install_package_update(config: &Config, - http_client: &HttpClient, + http_client: &mut HttpClient, token: &AccessToken, id: &UpdateRequestId, tx: &Sender) -> Result { @@ -80,10 +82,12 @@ fn interpreter(env: &mut Env, cmd: Command, tx: &Sender) -> Result<(), Er Ok(if let Some(token) = env.access_token.to_owned() { + let client_clone = env.http_client.clone(); + partial_apply!( [get_package_updates], [post_packages, send_install_report], - [install_package_update], &env, &token); + [install_package_update], &env.config, client_clone, &token); match cmd { @@ -127,7 +131,9 @@ fn interpreter(env: &mut Env, cmd: Command, tx: &Sender) -> Result<(), Er Authenticate(_) => { // XXX: partially apply? - let token = try!(authenticate(&env.config.auth, &*env.http_client)); + let client_clone = env.http_client.clone(); + let mut client = client_clone.lock().unwrap(); + let token = try!(authenticate(&env.config.auth, &mut *client)); env.access_token = Some(token.into()) } @@ -138,7 +144,7 @@ fn interpreter(env: &mut Env, cmd: Command, tx: &Sender) -> Result<(), Er ListInstalledPackages | PostInstalledPackages => tx.send(Event::NotAuthenticated) - .unwrap_or(error!("not_auth: send failed.")) + .unwrap_or(error!("interpreter: send failed.")) } }) diff --git a/src/main.rs b/src/main.rs index 5b8ff25..ac6da7a 100644 --- a/src/main.rs +++ b/src/main.rs @@ -11,7 +11,7 @@ extern crate ws; use getopts::Options; use std::env; -use std::sync::Arc; +use std::sync::{Arc, Mutex}; use std::sync::mpsc::{Sender, Receiver, channel}; use std::thread; use std::time::Duration; @@ -45,7 +45,7 @@ macro_rules! spawn_thread { fn spawn_interpreter(config: Config, crx: Receiver, etx: Sender) { - let client = Arc::new(Hyper::new()); + let client = Arc::new(Mutex::new(Hyper::new())); let mut env = Env { config: config.clone(), diff --git a/src/ota_plus.rs b/src/ota_plus.rs index 1299065..463098f 100644 --- a/src/ota_plus.rs +++ b/src/ota_plus.rs @@ -12,7 +12,7 @@ fn vehicle_endpoint(config: &Config, s: &str) -> Url { } pub fn download_package_update(config: &Config, - client: &HttpClient, + client: &mut HttpClient, token: &AccessToken, id: &UpdateRequestId) -> Result { @@ -35,7 +35,7 @@ pub fn download_package_update(config: &Config, } pub fn send_install_report(config: &Config, - client: &HttpClient, + client: &mut HttpClient, token: &AccessToken, report: &UpdateReport) -> Result<(), Error> { @@ -55,7 +55,7 @@ pub fn send_install_report(config: &Config, } pub fn get_package_updates(config: &Config, - client: &HttpClient, + client: &mut HttpClient, token: &AccessToken) -> Result, Error> { let req = HttpRequest::get( @@ -70,7 +70,7 @@ pub fn get_package_updates(config: &Config, } pub fn post_packages(config: &Config, - client: &HttpClient, + client: &mut HttpClient, token: &AccessToken, pkgs: &Vec) -> Result<(), Error> { -- cgit v1.2.1 From 876a73a4a832bf4b4dc25e8ec393a8d73bc83e45 Mon Sep 17 00:00:00 2001 From: Stevan Andjelkovic Date: Thu, 28 Apr 2016 12:05:18 +0200 Subject: Add test for install_package_update(). --- src/auth_plus.rs | 6 ++-- src/datatype/access_token.rs | 2 +- src/datatype/event.rs | 2 +- src/datatype/report.rs | 12 +++---- src/interpreter.rs | 78 +++++++++++++++++++++++++++++++++++++++++--- src/ota_plus.rs | 11 ++++--- 6 files changed, 91 insertions(+), 20 deletions(-) diff --git a/src/auth_plus.rs b/src/auth_plus.rs index c90e6eb..6a363e7 100644 --- a/src/auth_plus.rs +++ b/src/auth_plus.rs @@ -37,7 +37,7 @@ mod tests { #[test] fn test_authenticate() { - assert_eq!(authenticate(&AuthConfig::default(), &TestHttpClient::from(vec![TOKEN])).unwrap(), + assert_eq!(authenticate(&AuthConfig::default(), &mut TestHttpClient::from(vec![TOKEN])).unwrap(), AccessToken { access_token: "token".to_string(), token_type: "type".to_string(), @@ -49,7 +49,7 @@ mod tests { #[test] fn test_authenticate_no_token() { assert_eq!(format!("{}", authenticate(&AuthConfig::default(), - &TestHttpClient::from(vec![""])).unwrap_err()), + &mut TestHttpClient::from(vec![""])).unwrap_err()), r#"Failed to decode JSON: ParseError(SyntaxError("EOF While parsing value", 1, 1))"#) // XXX: Old error message was arguebly a lot better... @@ -59,7 +59,7 @@ mod tests { #[test] fn test_authenticate_bad_json() { assert_eq!(format!("{}", authenticate(&AuthConfig::default(), - &TestHttpClient::from(vec![r#"{"apa": 1}"#])).unwrap_err()), + &mut TestHttpClient::from(vec![r#"{"apa": 1}"#])).unwrap_err()), r#"Failed to decode JSON: MissingFieldError("access_token")"#) } diff --git a/src/datatype/access_token.rs b/src/datatype/access_token.rs index a9a631a..0852c88 100644 --- a/src/datatype/access_token.rs +++ b/src/datatype/access_token.rs @@ -1,7 +1,7 @@ use std::borrow::Cow; -#[derive(RustcDecodable, Debug, PartialEq, Clone)] +#[derive(RustcDecodable, Debug, PartialEq, Clone, Default)] pub struct AccessToken { pub access_token: String, pub token_type: String, diff --git a/src/datatype/event.rs b/src/datatype/event.rs index 283322d..efa21e0 100644 --- a/src/datatype/event.rs +++ b/src/datatype/event.rs @@ -3,7 +3,7 @@ use std::string::ToString; use datatype::{UpdateRequestId, UpdateState, Package}; -#[derive(RustcEncodable, Debug, Clone)] +#[derive(RustcEncodable, Debug, Clone, PartialEq, Eq)] pub enum Event { NotAuthenticated, NewUpdateAvailable(UpdateRequestId), diff --git a/src/datatype/report.rs b/src/datatype/report.rs index f16b2e2..22a3e12 100644 --- a/src/datatype/report.rs +++ b/src/datatype/report.rs @@ -13,14 +13,14 @@ impl<'a, 'b> UpdateReportWithVin<'a, 'b> { } } -#[derive(RustcEncodable, Clone, Debug)] +#[derive(RustcEncodable, Clone, Debug, PartialEq, Eq)] pub struct OperationResult { - id: String, - result_code: UpdateResultCode, - result_text: String, + pub id: String, + pub result_code: UpdateResultCode, + pub result_text: String, } -#[derive(RustcEncodable, Clone, Debug)] +#[derive(RustcEncodable, Clone, Debug, PartialEq, Eq)] pub struct UpdateReport { pub update_id: UpdateRequestId, pub operation_results: Vec @@ -36,7 +36,7 @@ impl UpdateReport { } #[allow(non_camel_case_types)] -#[derive(Clone, Debug)] +#[derive(Clone, Debug, PartialEq, Eq)] pub enum UpdateResultCode { // Operation executed successfully OK = 0, diff --git a/src/interpreter.rs b/src/interpreter.rs index cea2d5b..364f975 100644 --- a/src/interpreter.rs +++ b/src/interpreter.rs @@ -32,11 +32,11 @@ macro_rules! partial_apply { } // XXX: Move this somewhere else? -fn install_package_update(config: &Config, - http_client: &mut HttpClient, - token: &AccessToken, - id: &UpdateRequestId, - tx: &Sender) -> Result { +pub fn install_package_update(config: &Config, + http_client: &mut HttpClient, + token: &AccessToken, + id: &UpdateRequestId, + tx: &Sender) -> Result { match download_package_update(config, http_client, token, id) { @@ -162,3 +162,71 @@ impl<'a> Interpreter, Command, Event> for OurInterpreter { } } + +#[cfg(test)] +mod tests { + + use std::fmt::Debug; + use std::sync::mpsc::{channel, Receiver}; + + use super::*; + use datatype::{AccessToken, Config, Event, UpdateResultCode, UpdateState}; + use http_client::TestHttpClient; + + fn assert_receiver_eq(rx: Receiver, xs: &[X]) { + + let mut xs = xs.iter(); + + while let Ok(x) = rx.try_recv() { + if let Some(y) = xs.next() { + assert_eq!(x, *y) + } else { + panic!("assert_receiver_eq: never nexted `{:?}`", x) + } + } + + if let Some(x) = xs.next() { + panic!("assert_receiver_eq: never received `{:?}`", x) + } + + } + + #[test] + fn test_install_package_update_0() { + + let (tx, rx) = channel(); + + assert_eq!(install_package_update( + &Config::default(), + &mut TestHttpClient::new(), + &AccessToken::default(), + &"0".to_string(), + &tx).unwrap().operation_results.pop().unwrap().result_code, + UpdateResultCode::GENERAL_ERROR); + + assert_receiver_eq(rx, &[ + Event::UpdateErrored("0".to_string(), String::from( + "ClientError(\"GET http://127.0.0.1:8080/api/v1/vehicles/V1234567890123456/updates/0/download\")"))]) + + } + + #[test] + fn test_install_package_update_1() { + + let (tx, rx) = channel(); + + assert_eq!(install_package_update( + &Config::default(), + &mut TestHttpClient::from(vec![""]), + &AccessToken::default(), + &"0".to_string(), + &tx).unwrap().operation_results.pop().unwrap().result_code, + UpdateResultCode::INSTALL_FAILED); + + assert_receiver_eq(rx, &[ + Event::UpdateStateChanged("0".to_string(), UpdateState::Installing), + Event::UpdateErrored("0".to_string(), "INSTALL_FAILED: \"\"".to_string())]) + + } + +} diff --git a/src/ota_plus.rs b/src/ota_plus.rs index 463098f..9c1156c 100644 --- a/src/ota_plus.rs +++ b/src/ota_plus.rs @@ -69,6 +69,9 @@ pub fn get_package_updates(config: &Config, } +// XXX: This function is only used for posting installed packages? If +// so, then it might as well get those from the package manager directly +// (which is part of config). pub fn post_packages(config: &Config, client: &mut HttpClient, token: &AccessToken, @@ -115,7 +118,7 @@ mod tests { #[test] fn test_post_packages_sends_authentication() { assert_eq!(post_packages(&Config::default(), - &TestHttpClient::from(vec![""]), + &mut TestHttpClient::from(vec![""]), &test_token(), &vec![test_package()]) .unwrap(), ()) @@ -124,7 +127,7 @@ mod tests { #[test] fn test_get_package_updates() { assert_eq!(get_package_updates(&Config::default(), - &TestHttpClient::from(vec![r#"["pkgid"]"#]), + &mut TestHttpClient::from(vec![r#"["pkgid"]"#]), &test_token()).unwrap(), vec!["pkgid".to_string()]) } @@ -135,7 +138,7 @@ mod tests { config.ota = OtaConfig { packages_dir: "/".to_string(), .. config.ota }; assert_eq!(format!("{}", download_package_update(&config, - &TestHttpClient::from(vec![""]), + &mut TestHttpClient::from(vec![""]), &test_token(), &"0".to_string()) .unwrap_err()), @@ -146,7 +149,7 @@ mod tests { fn bad_client_download_package_update() { assert_eq!(format!("{}", download_package_update(&Config::default(), - &TestHttpClient::new(), + &mut TestHttpClient::new(), &test_token(), &"0".to_string()) .unwrap_err()), -- cgit v1.2.1 From 1a408082b3b49849886ae8dd751f012e205b3559 Mon Sep 17 00:00:00 2001 From: Stevan Andjelkovic Date: Thu, 28 Apr 2016 14:55:19 +0200 Subject: Move install_package_update to ota module. --- src/interpreter.rs | 135 ++++------------------------------------------------- src/ota_plus.rs | 125 ++++++++++++++++++++++++++++++++++++++++++++++--- 2 files changed, 128 insertions(+), 132 deletions(-) diff --git a/src/interpreter.rs b/src/interpreter.rs index 364f975..96b4f63 100644 --- a/src/interpreter.rs +++ b/src/interpreter.rs @@ -4,12 +4,12 @@ use std::sync::{Arc, Mutex}; use std::sync::mpsc::Sender; use auth_plus::authenticate; -use datatype::{AccessToken, Command, Config, Error, Event, UpdateReport, - UpdateRequestId, UpdateState, UpdateResultCode}; +use datatype::{AccessToken, Command, Config, Error, Event, UpdateState}; use datatype::Command::*; use http_client::HttpClient; use interaction_library::interpreter::Interpreter; -use ota_plus::{get_package_updates, download_package_update, post_packages, send_install_report}; +use ota_plus::{get_package_updates, install_package_update, + post_installed_packages, send_install_report}; pub struct Env<'a> { @@ -21,9 +21,9 @@ pub struct Env<'a> { // This macro partially applies the config, http client and token to the // passed in functions. macro_rules! partial_apply { - ([ $( $fun0: ident ),* ], - [ $( $fun1: ident ),* ], - [ $( $fun2: ident ),* ], + ([ $( $fun0: ident ),* ], // Functions of arity 0, + [ $( $fun1: ident ),* ], // arity 1, + [ $( $fun2: ident ),* ], // and arity 2. $config: expr, $client: expr, $token: expr) => { $(let $fun0 = || $fun0(&$config, &mut *$client.lock().unwrap(), $token);)* $(let $fun1 = |arg| $fun1(&$config, &mut *$client.lock().unwrap(), $token, &arg);)* @@ -31,53 +31,6 @@ macro_rules! partial_apply { } } -// XXX: Move this somewhere else? -pub fn install_package_update(config: &Config, - http_client: &mut HttpClient, - token: &AccessToken, - id: &UpdateRequestId, - tx: &Sender) -> Result { - - match download_package_update(config, http_client, token, id) { - - Ok(path) => { - info!("Downloaded at {:?}. Installing...", path); - try!(tx.send(Event::UpdateStateChanged(id.clone(), UpdateState::Installing))); - - let p = try!(path.to_str() - .ok_or(Error::ParseError(format!("Path is not valid UTF-8: {:?}", path)))); - - match config.ota.package_manager.install_package(p) { - - Ok((code, output)) => { - try!(tx.send(Event::UpdateStateChanged(id.clone(), UpdateState::Installed))); - - // XXX: Slight code duplication, see interpret(PostInstalledPackages). - let pkgs = try!(config.ota.package_manager.installed_packages()); - try!(post_packages(config, http_client, token, &pkgs)); - - Ok(UpdateReport::new(id.clone(), code, output)) - } - - Err((code, output)) => { - try!(tx.send(Event::UpdateErrored(id.clone(), format!("{:?}: {:?}", code, output)))); - Ok(UpdateReport::new(id.clone(), code, output)) - } - - } - - } - - Err(err) => { - try!(tx.send(Event::UpdateErrored(id.clone(), format!("{:?}", err)))); - Ok(UpdateReport::new(id.clone(), - UpdateResultCode::GENERAL_ERROR, - format!("Download failed: {:?}", err))) - } - } - -} - fn interpreter(env: &mut Env, cmd: Command, tx: &Sender) -> Result<(), Error> { Ok(if let Some(token) = env.access_token.to_owned() { @@ -85,8 +38,8 @@ fn interpreter(env: &mut Env, cmd: Command, tx: &Sender) -> Result<(), Er let client_clone = env.http_client.clone(); partial_apply!( - [get_package_updates], - [post_packages, send_install_report], + [get_package_updates, post_installed_packages], + [send_install_report], [install_package_update], &env.config, client_clone, &token); match cmd { @@ -116,9 +69,7 @@ fn interpreter(env: &mut Env, cmd: Command, tx: &Sender) -> Result<(), Er } PostInstalledPackages => { - let pkgs = try!(env.config.ota.package_manager.installed_packages()); - debug!("Found installed packages in the system: {:?}", pkgs); - try!(post_packages(pkgs)); + try!(post_installed_packages()); info!("Posted installed packages to the server.") } @@ -162,71 +113,3 @@ impl<'a> Interpreter, Command, Event> for OurInterpreter { } } - -#[cfg(test)] -mod tests { - - use std::fmt::Debug; - use std::sync::mpsc::{channel, Receiver}; - - use super::*; - use datatype::{AccessToken, Config, Event, UpdateResultCode, UpdateState}; - use http_client::TestHttpClient; - - fn assert_receiver_eq(rx: Receiver, xs: &[X]) { - - let mut xs = xs.iter(); - - while let Ok(x) = rx.try_recv() { - if let Some(y) = xs.next() { - assert_eq!(x, *y) - } else { - panic!("assert_receiver_eq: never nexted `{:?}`", x) - } - } - - if let Some(x) = xs.next() { - panic!("assert_receiver_eq: never received `{:?}`", x) - } - - } - - #[test] - fn test_install_package_update_0() { - - let (tx, rx) = channel(); - - assert_eq!(install_package_update( - &Config::default(), - &mut TestHttpClient::new(), - &AccessToken::default(), - &"0".to_string(), - &tx).unwrap().operation_results.pop().unwrap().result_code, - UpdateResultCode::GENERAL_ERROR); - - assert_receiver_eq(rx, &[ - Event::UpdateErrored("0".to_string(), String::from( - "ClientError(\"GET http://127.0.0.1:8080/api/v1/vehicles/V1234567890123456/updates/0/download\")"))]) - - } - - #[test] - fn test_install_package_update_1() { - - let (tx, rx) = channel(); - - assert_eq!(install_package_update( - &Config::default(), - &mut TestHttpClient::from(vec![""]), - &AccessToken::default(), - &"0".to_string(), - &tx).unwrap().operation_results.pop().unwrap().result_code, - UpdateResultCode::INSTALL_FAILED); - - assert_receiver_eq(rx, &[ - Event::UpdateStateChanged("0".to_string(), UpdateState::Installing), - Event::UpdateErrored("0".to_string(), "INSTALL_FAILED: \"\"".to_string())]) - - } - -} diff --git a/src/ota_plus.rs b/src/ota_plus.rs index 9c1156c..8d11768 100644 --- a/src/ota_plus.rs +++ b/src/ota_plus.rs @@ -1,9 +1,12 @@ use rustc_serialize::json; use std::fs::File; use std::path::PathBuf; +use std::sync::mpsc::Sender; + +use datatype::{AccessToken, Config, Event, Error, Url, UpdateRequestId, + UpdateReport, UpdateReportWithVin, Package, UpdateState, + UpdateResultCode}; -use datatype::{AccessToken, Config, Error, Url, UpdateRequestId, - UpdateReport, UpdateReportWithVin, Package}; use http_client::{Auth, HttpClient, HttpRequest}; @@ -69,9 +72,7 @@ pub fn get_package_updates(config: &Config, } -// XXX: This function is only used for posting installed packages? If -// so, then it might as well get those from the package manager directly -// (which is part of config). +// XXX: Remove in favour of post_installed_packages()? pub fn post_packages(config: &Config, client: &mut HttpClient, token: &AccessToken, @@ -90,12 +91,68 @@ pub fn post_packages(config: &Config, return Ok(()) } +pub fn post_installed_packages(config: &Config, + client: &mut HttpClient, + token: &AccessToken) -> Result<(), Error> { + + let pkgs = try!(config.ota.package_manager.installed_packages()); + post_packages(config, client, token, &pkgs) + +} + +pub fn install_package_update(config: &Config, + http_client: &mut HttpClient, + token: &AccessToken, + id: &UpdateRequestId, + tx: &Sender) -> Result { + + match download_package_update(config, http_client, token, id) { + + Ok(path) => { + info!("Downloaded at {:?}. Installing...", path); + try!(tx.send(Event::UpdateStateChanged(id.clone(), UpdateState::Installing))); + + let p = try!(path.to_str() + .ok_or(Error::ParseError(format!("Path is not valid UTF-8: {:?}", path)))); + + match config.ota.package_manager.install_package(p) { + + Ok((code, output)) => { + try!(tx.send(Event::UpdateStateChanged(id.clone(), UpdateState::Installed))); + try!(post_installed_packages(config, http_client, token)); + Ok(UpdateReport::new(id.clone(), code, output)) + } + + Err((code, output)) => { + try!(tx.send(Event::UpdateErrored(id.clone(), format!("{:?}: {:?}", code, output)))); + Ok(UpdateReport::new(id.clone(), code, output)) + } + + } + + } + + Err(err) => { + try!(tx.send(Event::UpdateErrored(id.clone(), format!("{:?}", err)))); + Ok(UpdateReport::new(id.clone(), + UpdateResultCode::GENERAL_ERROR, + format!("Download failed: {:?}", err))) + } + } + +} + + #[cfg(test)] mod tests { + use std::fmt::Debug; + use std::sync::mpsc::{channel, Receiver}; + use super::*; - use datatype::{AccessToken, Config, OtaConfig, Package}; + use datatype::{AccessToken, Config, Event, OtaConfig, Package, + UpdateResultCode, UpdateState}; use http_client::TestHttpClient; @@ -156,4 +213,60 @@ mod tests { "Http client error: GET http://127.0.0.1:8080/api/v1/vehicles/V1234567890123456/updates/0/download") } + fn assert_receiver_eq(rx: Receiver, xs: &[X]) { + + let mut xs = xs.iter(); + + while let Ok(x) = rx.try_recv() { + if let Some(y) = xs.next() { + assert_eq!(x, *y) + } else { + panic!("assert_receiver_eq: never nexted `{:?}`", x) + } + } + + if let Some(x) = xs.next() { + panic!("assert_receiver_eq: never received `{:?}`", x) + } + + } + + #[test] + fn test_install_package_update_0() { + + let (tx, rx) = channel(); + + assert_eq!(install_package_update( + &Config::default(), + &mut TestHttpClient::new(), + &AccessToken::default(), + &"0".to_string(), + &tx).unwrap().operation_results.pop().unwrap().result_code, + UpdateResultCode::GENERAL_ERROR); + + assert_receiver_eq(rx, &[ + Event::UpdateErrored("0".to_string(), String::from( + "ClientError(\"GET http://127.0.0.1:8080/api/v1/vehicles/V1234567890123456/updates/0/download\")"))]) + + } + + #[test] + fn test_install_package_update_1() { + + let (tx, rx) = channel(); + + assert_eq!(install_package_update( + &Config::default(), + &mut TestHttpClient::from(vec![""]), + &AccessToken::default(), + &"0".to_string(), + &tx).unwrap().operation_results.pop().unwrap().result_code, + UpdateResultCode::INSTALL_FAILED); + + assert_receiver_eq(rx, &[ + Event::UpdateStateChanged("0".to_string(), UpdateState::Installing), + Event::UpdateErrored("0".to_string(), "INSTALL_FAILED: \"\"".to_string())]) + + } + } -- cgit v1.2.1 From 7dd320641dbc1af8badecfc32433e62f1a0ab6e8 Mon Sep 17 00:00:00 2001 From: Stevan Andjelkovic Date: Thu, 28 Apr 2016 15:52:17 +0200 Subject: Add another test for install package update. --- src/ota_plus.rs | 27 +++++++++++++++++++++++++++ src/package_manager/tpm.rs | 20 -------------------- 2 files changed, 27 insertions(+), 20 deletions(-) diff --git a/src/ota_plus.rs b/src/ota_plus.rs index 8d11768..af37e06 100644 --- a/src/ota_plus.rs +++ b/src/ota_plus.rs @@ -154,6 +154,7 @@ mod tests { use datatype::{AccessToken, Config, Event, OtaConfig, Package, UpdateResultCode, UpdateState}; use http_client::TestHttpClient; + use package_manager::PackageManager; fn test_token() -> AccessToken { @@ -265,8 +266,34 @@ mod tests { assert_receiver_eq(rx, &[ Event::UpdateStateChanged("0".to_string(), UpdateState::Installing), + // XXX: Not very helpful message? Event::UpdateErrored("0".to_string(), "INSTALL_FAILED: \"\"".to_string())]) } + #[test] + fn test_install_package_update_2() { + + let mut config = Config::default(); + + config.ota.packages_dir = "/tmp/".to_string(); + config.ota.package_manager = PackageManager::File( + "test_install_package_update".to_string()); + + let (tx, rx) = channel(); + + assert_eq!(install_package_update( + &config, + &mut TestHttpClient::from(vec!["", ""]), + &AccessToken::default(), + &"0".to_string(), + &tx).unwrap().operation_results.pop().unwrap().result_code, + UpdateResultCode::OK); + + assert_receiver_eq(rx, &[ + Event::UpdateStateChanged("0".to_string(), UpdateState::Installing), + Event::UpdateStateChanged("0".to_string(), UpdateState::Installed)]) + + } + } diff --git a/src/package_manager/tpm.rs b/src/package_manager/tpm.rs index cf61ee3..6dae57e 100644 --- a/src/package_manager/tpm.rs +++ b/src/package_manager/tpm.rs @@ -68,9 +68,7 @@ mod tests { use std::io::prelude::*; use super::*; - use datatype::OtaConfig; use datatype::Package; - use package_manager::PackageManager; fn pkg1() -> Package { Package { @@ -86,24 +84,6 @@ mod tests { } } - #[allow(dead_code)] - fn make_config(file: &str) -> OtaConfig { - - let packages_dir = "/tmp/".to_string(); - let package_manager = PackageManager::File(file.to_string()); - - let mut config = OtaConfig::default(); - - config = OtaConfig { - packages_dir: packages_dir, - package_manager: package_manager, - .. config - }; - - return config - - } - #[test] fn test_installed_packages() { -- cgit v1.2.1 From be75ad295b5984b6129cf10ae270967e4fb012c2 Mon Sep 17 00:00:00 2001 From: Stevan Andjelkovic Date: Fri, 29 Apr 2016 11:16:47 +0200 Subject: Log which commands get interpreted. --- src/interpreter.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/interpreter.rs b/src/interpreter.rs index 96b4f63..1603be4 100644 --- a/src/interpreter.rs +++ b/src/interpreter.rs @@ -107,6 +107,9 @@ pub struct OurInterpreter; impl<'a> Interpreter, Command, Event> for OurInterpreter { fn interpret(env: &mut Env, cmd: Command, tx: Sender) { + + info!("Interpreting: {:?}", cmd); + interpreter(env, cmd, &tx) .unwrap_or_else(|err| tx.send(Event::Error(format!("{}", err))) .unwrap_or(error!("interpret: send failed."))) -- cgit v1.2.1 From 5da8c6f4fffc9397aa8bcfa6ff891fb04e81a977 Mon Sep 17 00:00:00 2001 From: Stevan Andjelkovic Date: Fri, 29 Apr 2016 14:47:19 +0200 Subject: Fix new url decoding. --- src/datatype/url.rs | 16 +++++++++++++--- src/http_client/http_client.rs | 4 ++++ tests/ota_plus_client_tests.rs | 4 ++-- 3 files changed, 19 insertions(+), 5 deletions(-) diff --git a/src/datatype/url.rs b/src/datatype/url.rs index 5c8a41a..335a3f7 100644 --- a/src/datatype/url.rs +++ b/src/datatype/url.rs @@ -1,13 +1,14 @@ -use std::borrow::Cow; use hyper::client::IntoUrl; use hyper; -use url; +use rustc_serialize::{Decoder, Decodable}; +use std::borrow::Cow; use url::ParseError; +use url; use datatype::Error; -#[derive(RustcDecodable, PartialEq, Eq, Clone, Debug)] +#[derive(PartialEq, Eq, Clone, Debug)] pub struct Url { get: url::Url } @@ -48,3 +49,12 @@ impl ToString for Url { } } + +impl Decodable for Url { + + fn decode(d: &mut D) -> Result { + let s = try!(d.read_str()); + Url::parse(&s) + .map_err(|e| d.error(&e.to_string())) + } +} diff --git a/src/http_client/http_client.rs b/src/http_client/http_client.rs index 12a55d0..c8c3b66 100644 --- a/src/http_client/http_client.rs +++ b/src/http_client/http_client.rs @@ -74,6 +74,8 @@ pub trait HttpClient: Send + Sync { fn send_request_to(&mut self, req: &HttpRequest, file: &mut File) -> Result<(), Error> { + debug!("send_request_to: {}", req.to_string()); + let s = try!(Self::send_request(self, req)); Ok(try!(file.write_all(&s.as_bytes()))) @@ -82,6 +84,8 @@ pub trait HttpClient: Send + Sync { fn send_request(&mut self, req: &HttpRequest) -> Result { + debug!("send_request: {}", req.to_string()); + let mut temp_file: File = try!(tempfile::tempfile()); try!(Self::send_request_to(self, req, &mut temp_file)); diff --git a/tests/ota_plus_client_tests.rs b/tests/ota_plus_client_tests.rs index 98e6de5..0171244 100644 --- a/tests/ota_plus_client_tests.rs +++ b/tests/ota_plus_client_tests.rs @@ -64,13 +64,13 @@ Options: #[test] fn bad_auth_server_url() { assert_eq!(client(&["--auth-server", "apa"]), - "Invalid auth-server URL: relative URL without a base\n") + "Invalid auth-server URL: Url parse error: relative URL without a base\n") } #[test] fn bad_ota_server_url() { assert_eq!(client(&["--ota-server", "apa"]), - "Invalid ota-server URL: relative URL without a base\n") + "Invalid ota-server URL: Url parse error: relative URL without a base\n") } #[test] -- cgit v1.2.1 From 73cc9086127b984203f9cc41957e12aae273fea7 Mon Sep 17 00:00:00 2001 From: Stevan Andjelkovic Date: Fri, 29 Apr 2016 15:17:15 +0200 Subject: Also listen for INT signal (ctrl-c). --- src/main.rs | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/src/main.rs b/src/main.rs index ac6da7a..471008a 100644 --- a/src/main.rs +++ b/src/main.rs @@ -65,13 +65,12 @@ fn spawn_autoacceptor(erx: Receiver, ctx: Sender) { } fn spawn_signal_handler(signals: ChanReceiver, ctx: Sender) { - spawn_thread!("TERM signal handler", { + spawn_thread!("Signal handler", { loop { match signals.recv() { - Some(s) if s == Signal::TERM => { - let _ = ctx.send(Command::Shutdown); - }, - _ => {} + Some(Signal::TERM) | Some(Signal::INT) => + ctx.send(Command::Shutdown).expect("send failed."), + _ => {} } } }); @@ -133,7 +132,7 @@ fn main() { let mut broadcast: Broadcast = Broadcast::new(erx); // Must subscribe to the signal before spawning ANY other threads - let signals = chan_signal::notify(&[Signal::TERM]); + let signals = chan_signal::notify(&[Signal::INT, Signal::TERM]); spawn_autoacceptor(broadcast.subscribe(), ctx.clone()); -- cgit v1.2.1 From 8ab53b8deb43e61536bf9ca6d0dea9e164a57a88 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sim=C3=A3o=20Mata?= Date: Thu, 28 Apr 2016 18:07:57 +0200 Subject: use put to v1/api/vehicle_updates instead of POST to v1/api/vehicles # Conflicts: # src/http_client/interface.rs # src/interpreter.rs # src/ota_plus.rs --- src/datatype/command.rs | 4 ++-- src/http_client/http_client.rs | 8 ++++++++ src/interpreter.rs | 10 +++++----- src/main.rs | 2 +- src/ota_plus.rs | 35 ++++++++++++++++------------------- 5 files changed, 32 insertions(+), 27 deletions(-) diff --git a/src/datatype/command.rs b/src/datatype/command.rs index 47b3ac4..bd1b015 100644 --- a/src/datatype/command.rs +++ b/src/datatype/command.rs @@ -13,7 +13,7 @@ pub enum Command { GetPendingUpdates, AcceptUpdate(UpdateRequestId), - PostInstalledPackages, + UpdateInstalledPackages, ListInstalledPackages, Shutdown @@ -26,7 +26,7 @@ impl FromStr for Command { fn from_str(s: &str) -> Result { match s { "GetPendingUpdates" => Ok(Command::GetPendingUpdates), - "PostInstalledPackages" => Ok(Command::PostInstalledPackages), + "UpdateInstalledPackages" => Ok(Command::UpdateInstalledPackages), "ListInstalledPackages" => Ok(Command::ListInstalledPackages), _ => Err(()), } diff --git a/src/http_client/http_client.rs b/src/http_client/http_client.rs index 12a55d0..6bd9b66 100644 --- a/src/http_client/http_client.rs +++ b/src/http_client/http_client.rs @@ -62,6 +62,14 @@ impl<'a> HttpRequest<'a> { HttpRequest::new(Method::Post, url, auth, body) } + pub fn put(url: U, auth: Option, body: Option) -> HttpRequest<'a> + where + U: Into>, + A: Into>>, + B: Into> + { + HttpRequest::new(Method::Post, url, auth, body) + } } impl<'a> ToString for HttpRequest<'a> { diff --git a/src/interpreter.rs b/src/interpreter.rs index 96b4f63..f52d911 100644 --- a/src/interpreter.rs +++ b/src/interpreter.rs @@ -9,7 +9,7 @@ use datatype::Command::*; use http_client::HttpClient; use interaction_library::interpreter::Interpreter; use ota_plus::{get_package_updates, install_package_update, - post_installed_packages, send_install_report}; + update_installed_packages, send_install_report}; pub struct Env<'a> { @@ -38,7 +38,7 @@ fn interpreter(env: &mut Env, cmd: Command, tx: &Sender) -> Result<(), Er let client_clone = env.http_client.clone(); partial_apply!( - [get_package_updates, post_installed_packages], + [get_package_updates, update_installed_packages], [send_install_report], [install_package_update], &env.config, client_clone, &token); @@ -68,8 +68,8 @@ fn interpreter(env: &mut Env, cmd: Command, tx: &Sender) -> Result<(), Er try!(tx.send(Event::FoundInstalledPackages(pkgs.clone()))) } - PostInstalledPackages => { - try!(post_installed_packages()); + UpdateInstalledPackages => { + try!(update_installed_packages()); info!("Posted installed packages to the server.") } @@ -93,7 +93,7 @@ fn interpreter(env: &mut Env, cmd: Command, tx: &Sender) -> Result<(), Er AcceptUpdate(_) | GetPendingUpdates | ListInstalledPackages | - PostInstalledPackages => + UpdateInstalledPackages => tx.send(Event::NotAuthenticated) .unwrap_or(error!("interpreter: send failed.")) } diff --git a/src/main.rs b/src/main.rs index ac6da7a..d082a00 100644 --- a/src/main.rs +++ b/src/main.rs @@ -88,7 +88,7 @@ fn spawn_update_poller(ctx: Sender, config: Config) { fn perform_initial_sync(ctx: Sender) { let _ = ctx.clone().send(Command::Authenticate(None)); - let _ = ctx.clone().send(Command::PostInstalledPackages); + let _ = ctx.clone().send(Command::UpdateInstalledPackages); } fn start_event_broadcasting(broadcast: Broadcast) { diff --git a/src/ota_plus.rs b/src/ota_plus.rs index af37e06..012f044 100644 --- a/src/ota_plus.rs +++ b/src/ota_plus.rs @@ -10,8 +10,8 @@ use datatype::{AccessToken, Config, Event, Error, Url, UpdateRequestId, use http_client::{Auth, HttpClient, HttpRequest}; -fn vehicle_endpoint(config: &Config, s: &str) -> Url { - config.ota.server.join(&format!("/api/v1/vehicles/{}/{}", config.auth.vin, s)).unwrap() +fn vehicle_updates_endpoint(config: &Config, path: &str) -> Url { + config.ota.server.join(&format!("/api/v1/vehicle_updates/{}/{}", config.auth.vin, path)).unwrap() } pub fn download_package_update(config: &Config, @@ -20,7 +20,7 @@ pub fn download_package_update(config: &Config, id: &UpdateRequestId) -> Result { let req = HttpRequest::get( - vehicle_endpoint(config, &format!("updates/{}/download", id)), + vehicle_updates_endpoint(config, &format!("{}/download", id)), Some(Auth::Token(token)), ); @@ -46,7 +46,7 @@ pub fn send_install_report(config: &Config, let json = try!(json::encode(&report_with_vin)); let req = HttpRequest::post( - vehicle_endpoint(config, &format!("/updates/{}", report.update_id)), + vehicle_updates_endpoint(config, &format!("{}", report.update_id)), Some(Auth::Token(token)), Some(json) ); @@ -62,7 +62,7 @@ pub fn get_package_updates(config: &Config, token: &AccessToken) -> Result, Error> { let req = HttpRequest::get( - vehicle_endpoint(&config, "/updates"), + vehicle_updates_endpoint(&config, ""), Some(Auth::Token(token)), ); @@ -72,16 +72,15 @@ pub fn get_package_updates(config: &Config, } -// XXX: Remove in favour of post_installed_packages()? -pub fn post_packages(config: &Config, +// XXX: Remove in favour of update_installed_packages()? +pub fn update_packages(config: &Config, client: &mut HttpClient, token: &AccessToken, pkgs: &Vec) -> Result<(), Error> { - let json = try!(json::encode(&pkgs)); - let req = HttpRequest::post( - vehicle_endpoint(config, "/updates"), + let req = HttpRequest::put( + vehicle_updates_endpoint(config, "updates"), Some(Auth::Token(token)), Some(json), ); @@ -91,12 +90,12 @@ pub fn post_packages(config: &Config, return Ok(()) } -pub fn post_installed_packages(config: &Config, +pub fn update_installed_packages(config: &Config, client: &mut HttpClient, token: &AccessToken) -> Result<(), Error> { let pkgs = try!(config.ota.package_manager.installed_packages()); - post_packages(config, client, token, &pkgs) + update_packages(config, client, token, &pkgs) } @@ -119,7 +118,7 @@ pub fn install_package_update(config: &Config, Ok((code, output)) => { try!(tx.send(Event::UpdateStateChanged(id.clone(), UpdateState::Installed))); - try!(post_installed_packages(config, http_client, token)); + try!(update_installed_packages(config, http_client, token)); Ok(UpdateReport::new(id.clone(), code, output)) } @@ -174,8 +173,8 @@ mod tests { } #[test] - fn test_post_packages_sends_authentication() { - assert_eq!(post_packages(&Config::default(), + fn test_update_packages_sends_authentication() { + assert_eq!(update_packages(&Config::default(), &mut TestHttpClient::from(vec![""]), &test_token(), &vec![test_package()]) @@ -211,7 +210,7 @@ mod tests { &test_token(), &"0".to_string()) .unwrap_err()), - "Http client error: GET http://127.0.0.1:8080/api/v1/vehicles/V1234567890123456/updates/0/download") + "Http client error: GET http://127.0.0.1:8080/api/v1/vehicle_updates/V1234567890123456/0/download") } fn assert_receiver_eq(rx: Receiver, xs: &[X]) { @@ -247,7 +246,7 @@ mod tests { assert_receiver_eq(rx, &[ Event::UpdateErrored("0".to_string(), String::from( - "ClientError(\"GET http://127.0.0.1:8080/api/v1/vehicles/V1234567890123456/updates/0/download\")"))]) + "ClientError(\"GET http://127.0.0.1:8080/api/v1/vehicle_updates/V1234567890123456/0/download\")"))]) } @@ -268,7 +267,6 @@ mod tests { Event::UpdateStateChanged("0".to_string(), UpdateState::Installing), // XXX: Not very helpful message? Event::UpdateErrored("0".to_string(), "INSTALL_FAILED: \"\"".to_string())]) - } #[test] @@ -295,5 +293,4 @@ mod tests { Event::UpdateStateChanged("0".to_string(), UpdateState::Installed)]) } - } -- cgit v1.2.1 From 508219b4b5352db126c377ca7d8c52c05b4e2d6d Mon Sep 17 00:00:00 2001 From: Stevan Andjelkovic Date: Fri, 29 Apr 2016 17:17:48 +0200 Subject: Found bug in Hyper::send_request. --- src/auth_plus.rs | 4 ++++ src/datatype/config.rs | 13 ++++++++----- src/http_client/http_client.rs | 10 +++------- src/http_client/hyper.rs | 32 ++++++++++++++++++++++++++++---- src/interpreter.rs | 4 ++-- src/main.rs | 7 +++++-- tests/ota_plus_client_tests.rs | 6 +++++- 7 files changed, 55 insertions(+), 21 deletions(-) diff --git a/src/auth_plus.rs b/src/auth_plus.rs index 6a363e7..eb79d13 100644 --- a/src/auth_plus.rs +++ b/src/auth_plus.rs @@ -6,6 +6,8 @@ use http_client::{Auth, HttpClient, HttpRequest}; pub fn authenticate(config: &AuthConfig, client: &mut HttpClient) -> Result { + debug!("authenticate()"); + let req = HttpRequest::post::<_, _, String>( config.server.join("/token").unwrap(), Some(Auth::Credentials( @@ -16,6 +18,8 @@ pub fn authenticate(config: &AuthConfig, client: &mut HttpClient) -> Result Result { let creds = CredentialsFile { client_id: auth_cfg_section.client_id, @@ -166,7 +168,7 @@ pub fn parse_config(s: &str) -> Result { let ota_cfg: OtaConfig = try!(parse_toml_table(&tbl, "ota")); let test_cfg: TestConfig = try!(parse_toml_table(&tbl, "test")); - return Ok(Config { + Ok(Config { auth: auth_cfg, ota: ota_cfg, test: test_cfg, @@ -175,14 +177,15 @@ pub fn parse_config(s: &str) -> Result { pub fn load_config(path: &str) -> Result { + debug!("load_config: {}", path); + match File::open(path) { Err(ref e) if e.kind() == ErrorKind::NotFound => Ok(Config::default()), - Err(e) => Err(Error::Config(Io(e))), + Err(e) => Err(Error::Io(e)), Ok(mut f) => { let mut s = String::new(); - try!(f.read_to_string(&mut s) - .map_err(|err| Error::Config(Io(err)))); - return parse_config(&s); + try!(f.read_to_string(&mut s)); + parse_config(&s) } } } diff --git a/src/http_client/http_client.rs b/src/http_client/http_client.rs index c8c3b66..fd744e7 100644 --- a/src/http_client/http_client.rs +++ b/src/http_client/http_client.rs @@ -50,7 +50,7 @@ impl<'a> HttpRequest<'a> { U: Into>, A: Into>>, { - HttpRequest::new::<_, _, _, String>(Method::Get, url, auth, None) + HttpRequest::new::(Method::Get, url, auth, None) } pub fn post(url: U, auth: Option, body: Option) -> HttpRequest<'a> @@ -74,9 +74,7 @@ pub trait HttpClient: Send + Sync { fn send_request_to(&mut self, req: &HttpRequest, file: &mut File) -> Result<(), Error> { - debug!("send_request_to: {}", req.to_string()); - - let s = try!(Self::send_request(self, req)); + let s = try!(self.send_request(req)); Ok(try!(file.write_all(&s.as_bytes()))) @@ -84,11 +82,9 @@ pub trait HttpClient: Send + Sync { fn send_request(&mut self, req: &HttpRequest) -> Result { - debug!("send_request: {}", req.to_string()); - let mut temp_file: File = try!(tempfile::tempfile()); - try!(Self::send_request_to(self, req, &mut temp_file)); + try!(self.send_request_to(req, &mut temp_file)); let mut buf = String::new(); let _: usize = try!(temp_file.read_to_string(&mut buf)); diff --git a/src/http_client/hyper.rs b/src/http_client/hyper.rs index aad5152..e8e96b1 100644 --- a/src/http_client/hyper.rs +++ b/src/http_client/hyper.rs @@ -27,6 +27,8 @@ impl HttpClient for Hyper { fn send_request_to(&mut self, req: &HttpRequest, file: &mut File) -> Result<(), Error> { + debug!("send_request_to, request: {}", req.to_string()); + let mut headers = Headers::new(); let mut body = String::new(); @@ -83,7 +85,10 @@ impl HttpClient for Hyper { let mut rbody = String::new(); let _: usize = try!(resp.read_to_string(&mut rbody)); + debug!("send_request_to, response: `{}`", rbody); + try!(tee(rbody.as_bytes(), file)); + Ok(()) } else if resp.status.is_redirection() { @@ -104,10 +109,10 @@ fn relocate_request<'a>(req: &'a HttpRequest, resp: &Response) -> Result( + Url::parse("https://eu.httpbin.org/get").unwrap(), None); + + // XXX: why doesn't this work? + // let s: String = try!(client.send_request(&req)); + + let s: String = client.send_request(&req).unwrap(); + + assert!(s != "".to_string()) + + } #[test] fn test_tee() { diff --git a/src/interpreter.rs b/src/interpreter.rs index 1603be4..3d6a972 100644 --- a/src/interpreter.rs +++ b/src/interpreter.rs @@ -31,7 +31,7 @@ macro_rules! partial_apply { } } -fn interpreter(env: &mut Env, cmd: Command, tx: &Sender) -> Result<(), Error> { +fn interpreter(env: &mut Env, cmd: Command, tx: Sender) -> Result<(), Error> { Ok(if let Some(token) = env.access_token.to_owned() { @@ -110,7 +110,7 @@ impl<'a> Interpreter, Command, Event> for OurInterpreter { info!("Interpreting: {:?}", cmd); - interpreter(env, cmd, &tx) + interpreter(env, cmd, tx.clone()) .unwrap_or_else(|err| tx.send(Event::Error(format!("{}", err))) .unwrap_or(error!("interpret: send failed."))) } diff --git a/src/main.rs b/src/main.rs index 471008a..f664861 100644 --- a/src/main.rs +++ b/src/main.rs @@ -80,7 +80,7 @@ fn spawn_update_poller(ctx: Sender, config: Config) { spawn_thread!("Update poller", { loop { let _ = ctx.send(Command::GetPendingUpdates); - thread::sleep(Duration::from_secs(config.ota.polling_interval)); + thread::sleep(Duration::from_secs(config.ota.polling_interval)) } }); } @@ -109,6 +109,8 @@ impl Interpreter<(), Event, Command> for AutoAcceptor { } } + info!("Event interpreter: {:?}", e); + match e { Event::Batch(ref evs) => { for ev in evs { @@ -136,7 +138,6 @@ fn main() { spawn_autoacceptor(broadcast.subscribe(), ctx.clone()); - spawn_interpreter(config.clone(), crx, etx.clone()); Websocket::run(ctx.clone(), broadcast.subscribe()); spawn_update_poller(ctx.clone(), config.clone()); @@ -149,6 +150,8 @@ fn main() { spawn_signal_handler(signals, ctx.clone()); + spawn_interpreter(config.clone(), crx, etx.clone()); + if config.test.looping { println!("Ota Plus Client REPL started."); Console::run(ctx.clone(), events_for_repl); diff --git a/tests/ota_plus_client_tests.rs b/tests/ota_plus_client_tests.rs index 0171244..66740bf 100644 --- a/tests/ota_plus_client_tests.rs +++ b/tests/ota_plus_client_tests.rs @@ -73,6 +73,7 @@ fn bad_ota_server_url() { "Invalid ota-server URL: Url parse error: relative URL without a base\n") } +#[ignore] #[test] fn no_auth_server_to_connect_to() { assert_eq!(client(&[""]), @@ -94,5 +95,8 @@ fn bad_toml() { #[test] fn bad_path_dir() { assert_eq!(client(&["--config=/"]), - "Failed to load config: Is a directory (os error 21)\n") + "IO error: Is a directory (os error 21)\n") + + // XXX: + // "Failed to load config: Is a directory (os error 21)\n") } -- cgit v1.2.1 From 88ca85e87d5b785e0abedc2227ba6d383d86efcd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sim=C3=A3o=20Mata?= Date: Fri, 29 Apr 2016 17:47:08 +0200 Subject: sort updates by creation date --- src/datatype/mod.rs | 2 +- src/datatype/update_request.rs | 9 +++++++++ src/interpreter.rs | 8 ++++++-- src/ota_plus.rs | 34 ++++++++++++++++++++++++---------- 4 files changed, 40 insertions(+), 13 deletions(-) diff --git a/src/datatype/mod.rs b/src/datatype/mod.rs index a58eb90..74fa16a 100644 --- a/src/datatype/mod.rs +++ b/src/datatype/mod.rs @@ -7,7 +7,7 @@ pub use self::event::Event; pub use self::method::Method; pub use self::package::Package; pub use self::report::{UpdateReport, UpdateReportWithVin, UpdateResultCode}; -pub use self::update_request::{UpdateRequestId, UpdateState}; +pub use self::update_request::{UpdateRequestId, UpdateState, PendingUpdateRequest}; pub use self::url::Url; pub mod access_token; diff --git a/src/datatype/update_request.rs b/src/datatype/update_request.rs index 885029c..bab6ccb 100644 --- a/src/datatype/update_request.rs +++ b/src/datatype/update_request.rs @@ -1,5 +1,7 @@ pub type UpdateRequestId = String; +use datatype::Package; + #[derive(RustcDecodable, RustcEncodable, PartialEq, Eq, Debug, Clone)] pub enum UpdateState { Downloading, @@ -7,3 +9,10 @@ pub enum UpdateState { Installed, Failed, } + +#[derive(Clone, PartialEq, Eq, Debug, RustcEncodable, RustcDecodable)] +pub struct PendingUpdateRequest { + pub id: UpdateRequestId, + pub packageId: Package, + pub createdAt: String +} diff --git a/src/interpreter.rs b/src/interpreter.rs index f52d911..1af3f8b 100644 --- a/src/interpreter.rs +++ b/src/interpreter.rs @@ -54,11 +54,15 @@ fn interpreter(env: &mut Env, cmd: Command, tx: &Sender) -> Result<(), Er } GetPendingUpdates => { - let updates = try!(get_package_updates()); + let mut updates = try!(get_package_updates()); + + updates.sort_by_key(|e| e.createdAt.clone()); + let update_events: Vec = updates .iter() - .map(|id| Event::NewUpdateAvailable(id.clone())) + .map(|u| Event::NewUpdateAvailable(u.id.clone())) .collect(); + info!("New package updates available: {:?}", update_events); try!(tx.send(Event::Batch(update_events))) } diff --git a/src/ota_plus.rs b/src/ota_plus.rs index 012f044..3366c75 100644 --- a/src/ota_plus.rs +++ b/src/ota_plus.rs @@ -4,8 +4,8 @@ use std::path::PathBuf; use std::sync::mpsc::Sender; use datatype::{AccessToken, Config, Event, Error, Url, UpdateRequestId, - UpdateReport, UpdateReportWithVin, Package, UpdateState, - UpdateResultCode}; + UpdateReport, UpdateReportWithVin, Package, + UpdateResultCode, UpdateState, PendingUpdateRequest}; use http_client::{Auth, HttpClient, HttpRequest}; @@ -59,7 +59,7 @@ pub fn send_install_report(config: &Config, pub fn get_package_updates(config: &Config, client: &mut HttpClient, - token: &AccessToken) -> Result, Error> { + token: &AccessToken) -> Result, Error> { let req = HttpRequest::get( vehicle_updates_endpoint(&config, ""), @@ -68,8 +68,7 @@ pub fn get_package_updates(config: &Config, let resp = try!(client.send_request(&req)); - return Ok(try!(json::decode::>(&resp))); - + return Ok(try!(json::decode::>(&resp))); } // XXX: Remove in favour of update_installed_packages()? @@ -148,10 +147,11 @@ mod tests { use std::fmt::Debug; use std::sync::mpsc::{channel, Receiver}; + use rustc_serialize::json; use super::*; use datatype::{AccessToken, Config, Event, OtaConfig, Package, - UpdateResultCode, UpdateState}; + UpdateResultCode, UpdateState, PendingUpdateRequest}; use http_client::TestHttpClient; use package_manager::PackageManager; @@ -183,10 +183,24 @@ mod tests { #[test] fn test_get_package_updates() { - assert_eq!(get_package_updates(&Config::default(), - &mut TestHttpClient::from(vec![r#"["pkgid"]"#]), - &test_token()).unwrap(), - vec!["pkgid".to_string()]) + let pending_update = PendingUpdateRequest { + id: "someid".to_string(), + packageId: Package { + name: "fake-pkg".to_string(), + version: "0.1.1".to_string() + }, + createdAt: "2010-01-01".to_string() + }; + + let json_response = format!("[{}]",json::encode(&pending_update).unwrap()); + + let updates: Vec = get_package_updates(&Config::default(), + &mut TestHttpClient::from(vec![json_response.as_str()]), + &test_token()).unwrap(); + + let update_ids: Vec = updates.iter().map(|p| p.id.clone()).collect(); + + assert_eq!(update_ids, vec!["someid".to_string()]) } #[test] -- cgit v1.2.1 From 8017ea05cd4b2da51cd0fa81e52d2043166e2cc7 Mon Sep 17 00:00:00 2001 From: Stevan Andjelkovic Date: Mon, 2 May 2016 12:34:57 +0200 Subject: Fix a couple of bugs, thanks @txus. --- src/http_client/http_client.rs | 5 ++++- src/http_client/hyper.rs | 47 +++++++++++++++++++++++++++++++++--------- src/interpreter.rs | 4 ++-- 3 files changed, 43 insertions(+), 13 deletions(-) diff --git a/src/http_client/http_client.rs b/src/http_client/http_client.rs index fd744e7..cb17963 100644 --- a/src/http_client/http_client.rs +++ b/src/http_client/http_client.rs @@ -1,6 +1,7 @@ use std::borrow::Cow; use std::fs::File; -use std::io::{Write, Read}; +use std::io::SeekFrom; +use std::io::prelude::*; use tempfile; use datatype::{AccessToken, ClientId, ClientSecret, Error, Method, Url}; @@ -86,6 +87,8 @@ pub trait HttpClient: Send + Sync { try!(self.send_request_to(req, &mut temp_file)); + try!(temp_file.seek(SeekFrom::Start(0))); + let mut buf = String::new(); let _: usize = try!(temp_file.read_to_string(&mut buf)); diff --git a/src/http_client/hyper.rs b/src/http_client/hyper.rs index e8e96b1..d66dc2b 100644 --- a/src/http_client/hyper.rs +++ b/src/http_client/hyper.rs @@ -27,7 +27,8 @@ impl HttpClient for Hyper { fn send_request_to(&mut self, req: &HttpRequest, file: &mut File) -> Result<(), Error> { - debug!("send_request_to, request: {}", req.to_string()); + println!("send_request_to, request: {}", req.to_string()); + println!("send_request_to, file: `{:?}`", file); let mut headers = Headers::new(); let mut body = String::new(); @@ -52,7 +53,7 @@ impl HttpClient for Hyper { } - (Some(Auth::Token(token)), Some(body)) => { + (Some(Auth::Token(token)), body) => { headers.set(Authorization(Bearer { token: token.access_token.clone() @@ -63,9 +64,13 @@ impl HttpClient for Hyper { SubLevel::Json, vec![(Attr::Charset, Value::Utf8)]))); - let json: String = try!(json::encode(&body)); + if let Some(body) = body { - body.into_owned().push_str(&json) + let json: String = try!(json::encode(&body)); + + body.into_owned().push_str(&json) + + } } @@ -73,6 +78,9 @@ impl HttpClient for Hyper { } + println!("send_request_to, headers: `{}`", headers); + println!("send_request_to, body: `{}`", body); + let mut resp = try!(self.client .request(req.method.clone().into_owned().into(), req.url.clone().into_owned()) @@ -85,8 +93,9 @@ impl HttpClient for Hyper { let mut rbody = String::new(); let _: usize = try!(resp.read_to_string(&mut rbody)); - debug!("send_request_to, response: `{}`", rbody); + println!("send_request_to, response: `{}`", rbody); + println!("send_request_to, file: `{:?}`", file); try!(tee(rbody.as_bytes(), file)); Ok(()) @@ -142,7 +151,9 @@ mod tests { use hyper; use std::fs::File; - use std::io::{Read, repeat}; + use std::io::{repeat, SeekFrom}; + use std::io::prelude::*; + use tempfile; use super::*; use datatype::Url; @@ -152,18 +163,34 @@ mod tests { #[test] fn test_send_request_get() { + let mut client: &mut HttpClient = &mut Hyper::new(); + + let req = HttpRequest::get::<_, Auth>( + Url::parse("https://eu.httpbin.org/get").unwrap(), None); + + let s: String = client.send_request(&req).unwrap(); + + assert_eq!(s, "{\n \"args\": {}, \n \"headers\": {\n \"Host\": \"eu.httpbin.org\"\n }, \n \"origin\": \"87.138.108.187\", \n \"url\": \"https://eu.httpbin.org/get\"\n}\n".to_string()) + + } + + #[test] + fn test_send_request_to_get() { + let mut client = &mut Hyper::new(); let req = HttpRequest::get::<_, Auth>( Url::parse("https://eu.httpbin.org/get").unwrap(), None); - // XXX: why doesn't this work? - // let s: String = try!(client.send_request(&req)); + let mut temp_file: File = tempfile::tempfile().unwrap(); + client.send_request_to(&req, &mut temp_file).unwrap(); - let s: String = client.send_request(&req).unwrap(); + temp_file.seek(SeekFrom::Start(0)).unwrap(); - assert!(s != "".to_string()) + let mut buf = String::new(); + let _: usize = temp_file.read_to_string(&mut buf).unwrap(); + assert_eq!(buf, "{\n \"args\": {}, \n \"headers\": {\n \"Host\": \"eu.httpbin.org\"\n }, \n \"origin\": \"87.138.108.187\", \n \"url\": \"https://eu.httpbin.org/get\"\n}\n".to_string()) } #[test] diff --git a/src/interpreter.rs b/src/interpreter.rs index 3d6a972..4147ff4 100644 --- a/src/interpreter.rs +++ b/src/interpreter.rs @@ -95,7 +95,7 @@ fn interpreter(env: &mut Env, cmd: Command, tx: Sender) -> Result<(), Err ListInstalledPackages | PostInstalledPackages => tx.send(Event::NotAuthenticated) - .unwrap_or(error!("interpreter: send failed.")) + .unwrap_or_else(|_| error!("interpreter: send failed.")) } }) @@ -112,7 +112,7 @@ impl<'a> Interpreter, Command, Event> for OurInterpreter { interpreter(env, cmd, tx.clone()) .unwrap_or_else(|err| tx.send(Event::Error(format!("{}", err))) - .unwrap_or(error!("interpret: send failed."))) + .unwrap_or_else(|_| error!("interpret: send failed"))) } } -- cgit v1.2.1 From 718e944851f5cc2b133512bc2f6f984d33dc0870 Mon Sep 17 00:00:00 2001 From: Stevan Andjelkovic Date: Mon, 2 May 2016 15:09:30 +0200 Subject: Fix tests on TC. --- src/datatype/command.rs | 1 - src/http_client/hyper.rs | 15 +++++++-------- src/ota_plus.rs | 1 + 3 files changed, 8 insertions(+), 9 deletions(-) diff --git a/src/datatype/command.rs b/src/datatype/command.rs index 47b3ac4..5c91863 100644 --- a/src/datatype/command.rs +++ b/src/datatype/command.rs @@ -1,4 +1,3 @@ -use rustc_serialize::{Encodable}; use std::str::FromStr; use datatype::{ClientCredentials, UpdateRequestId}; diff --git a/src/http_client/hyper.rs b/src/http_client/hyper.rs index d66dc2b..7cde698 100644 --- a/src/http_client/hyper.rs +++ b/src/http_client/hyper.rs @@ -27,8 +27,7 @@ impl HttpClient for Hyper { fn send_request_to(&mut self, req: &HttpRequest, file: &mut File) -> Result<(), Error> { - println!("send_request_to, request: {}", req.to_string()); - println!("send_request_to, file: `{:?}`", file); + debug!("send_request_to, request: {}", req.to_string()); let mut headers = Headers::new(); let mut body = String::new(); @@ -78,8 +77,8 @@ impl HttpClient for Hyper { } - println!("send_request_to, headers: `{}`", headers); - println!("send_request_to, body: `{}`", body); + debug!("send_request_to, headers: `{}`", headers); + debug!("send_request_to, body: `{}`", body); let mut resp = try!(self.client .request(req.method.clone().into_owned().into(), @@ -93,9 +92,9 @@ impl HttpClient for Hyper { let mut rbody = String::new(); let _: usize = try!(resp.read_to_string(&mut rbody)); - println!("send_request_to, response: `{}`", rbody); + debug!("send_request_to, response: `{}`", rbody); + debug!("send_request_to, file: `{:?}`", file); - println!("send_request_to, file: `{:?}`", file); try!(tee(rbody.as_bytes(), file)); Ok(()) @@ -170,7 +169,7 @@ mod tests { let s: String = client.send_request(&req).unwrap(); - assert_eq!(s, "{\n \"args\": {}, \n \"headers\": {\n \"Host\": \"eu.httpbin.org\"\n }, \n \"origin\": \"87.138.108.187\", \n \"url\": \"https://eu.httpbin.org/get\"\n}\n".to_string()) + assert!(s != "".to_string()) } @@ -190,7 +189,7 @@ mod tests { let mut buf = String::new(); let _: usize = temp_file.read_to_string(&mut buf).unwrap(); - assert_eq!(buf, "{\n \"args\": {}, \n \"headers\": {\n \"Host\": \"eu.httpbin.org\"\n }, \n \"origin\": \"87.138.108.187\", \n \"url\": \"https://eu.httpbin.org/get\"\n}\n".to_string()) + assert!(buf != "".to_string()) } #[test] diff --git a/src/ota_plus.rs b/src/ota_plus.rs index af37e06..a173ebb 100644 --- a/src/ota_plus.rs +++ b/src/ota_plus.rs @@ -191,6 +191,7 @@ mod tests { } #[test] + #[ignore] // TODO: docker daemon requires user namespaces for this to work fn bad_packages_dir_download_package_update() { let mut config = Config::default(); config.ota = OtaConfig { packages_dir: "/".to_string(), .. config.ota }; -- cgit v1.2.1 From aafa4628f13fc430f1ab2d45ca3ede90ae33c852 Mon Sep 17 00:00:00 2001 From: Txus Date: Mon, 2 May 2016 16:32:20 +0200 Subject: Don't depend on DPKG in tests, use the test package manager --- src/main.rs | 2 +- src/ota_plus.rs | 20 ++++++++++++++------ src/package_manager/package_manager.rs | 10 +++++----- src/package_manager/tpm.rs | 29 +++++++++++++++++++++++------ 4 files changed, 43 insertions(+), 18 deletions(-) diff --git a/src/main.rs b/src/main.rs index 86a369e..a308f31 100644 --- a/src/main.rs +++ b/src/main.rs @@ -239,7 +239,7 @@ fn build_config() -> Config { config.ota.package_manager = match s.to_lowercase().as_str() { "dpkg" => PackageManager::Dpkg, "rpm" => PackageManager::Rpm, - path => PackageManager::File(path.to_string()), + path => PackageManager::File { filename: path.to_string(), succeeds: true }, } } diff --git a/src/ota_plus.rs b/src/ota_plus.rs index 1507a5d..d9050d6 100644 --- a/src/ota_plus.rs +++ b/src/ota_plus.rs @@ -197,9 +197,9 @@ mod tests { let updates: Vec = get_package_updates(&Config::default(), &mut TestHttpClient::from(vec![json_response.as_str()]), &test_token()).unwrap(); - + let update_ids: Vec = updates.iter().map(|p| p.id.clone()).collect(); - + assert_eq!(update_ids, vec!["someid".to_string()]) } @@ -268,10 +268,17 @@ mod tests { #[test] fn test_install_package_update_1() { + let mut config = Config::default(); + + config.ota.packages_dir = "/tmp/".to_string(); + config.ota.package_manager = PackageManager::File { + filename: "test_install_package_update_1".to_string(), + succeeds: false }; + let (tx, rx) = channel(); assert_eq!(install_package_update( - &Config::default(), + &config, &mut TestHttpClient::from(vec![""]), &AccessToken::default(), &"0".to_string(), @@ -281,7 +288,7 @@ mod tests { assert_receiver_eq(rx, &[ Event::UpdateStateChanged("0".to_string(), UpdateState::Installing), // XXX: Not very helpful message? - Event::UpdateErrored("0".to_string(), "INSTALL_FAILED: \"\"".to_string())]) + Event::UpdateErrored("0".to_string(), "INSTALL_FAILED: \"failed\"".to_string())]) } #[test] @@ -290,8 +297,9 @@ mod tests { let mut config = Config::default(); config.ota.packages_dir = "/tmp/".to_string(); - config.ota.package_manager = PackageManager::File( - "test_install_package_update".to_string()); + config.ota.package_manager = PackageManager::File { + filename: "test_install_package_update_2".to_string(), + succeeds: true }; let (tx, rx) = channel(); diff --git a/src/package_manager/package_manager.rs b/src/package_manager/package_manager.rs index 149664a..f6ba2da 100644 --- a/src/package_manager/package_manager.rs +++ b/src/package_manager/package_manager.rs @@ -8,7 +8,7 @@ use package_manager::{dpkg, rpm, tpm}; pub enum PackageManager { Dpkg, Rpm, - File(String), + File { filename: String, succeeds: bool } } impl PackageManager { @@ -17,7 +17,7 @@ impl PackageManager { match *self { PackageManager::Dpkg => dpkg::installed_packages(), PackageManager::Rpm => rpm::installed_packages(), - PackageManager::File(ref s) => tpm::installed_packages(s), + PackageManager::File { ref filename, .. } => tpm::installed_packages(filename), } } @@ -25,7 +25,7 @@ impl PackageManager { match *self { PackageManager::Dpkg => dpkg::install_package(path), PackageManager::Rpm => rpm::install_package(path), - PackageManager::File(ref s) => tpm::install_package(s, path), + PackageManager::File { ref filename, succeeds } => tpm::install_package(filename, path, succeeds), } } @@ -33,7 +33,7 @@ impl PackageManager { match *self { PackageManager::Dpkg => "deb".to_string(), PackageManager::Rpm => "rpm".to_string(), - PackageManager::File(ref s) => s.to_string(), + PackageManager::File { ref filename, .. } => filename.to_string(), } } @@ -43,7 +43,7 @@ fn parse_package_manager(s: String) -> Result { match s.to_lowercase().as_str() { "dpkg" => Ok(PackageManager::Dpkg), "rpm" => Ok(PackageManager::Rpm), - s => Ok(PackageManager::File(s.to_string())), + s => Ok(PackageManager::File { filename: s.to_string(), succeeds: true }), } } diff --git a/src/package_manager/tpm.rs b/src/package_manager/tpm.rs index 6dae57e..d9a67e0 100644 --- a/src/package_manager/tpm.rs +++ b/src/package_manager/tpm.rs @@ -35,7 +35,7 @@ pub fn installed_packages(path: &str) -> Result, Error> { } -pub fn install_package(path: &str, pkg: &str) -> Result<(UpdateResultCode, String), (UpdateResultCode, String)> { +pub fn install_package(path: &str, pkg: &str, succeeds: bool) -> Result<(UpdateResultCode, String), (UpdateResultCode, String)> { fn install(path: &str, pkg: &str) -> Result<(), Error> { let f = try!(OpenOptions::new() @@ -52,9 +52,13 @@ pub fn install_package(path: &str, pkg: &str) -> Result<(UpdateResultCode, Strin return Ok(()) } - match install(path, pkg) { - Ok(_) => Ok((UpdateResultCode::OK, "".to_string())), - Err(e) => Err((UpdateResultCode::INSTALL_FAILED, format!("{:?}", e))) + if succeeds { + match install(path, pkg) { + Ok(_) => Ok((UpdateResultCode::OK, "".to_string())), + Err(e) => Err((UpdateResultCode::INSTALL_FAILED, format!("{:?}", e))) + } + } else { + Err((UpdateResultCode::INSTALL_FAILED, "failed".to_string())) } } @@ -118,11 +122,24 @@ mod tests { let _ = fs::remove_file(path); - install_package(path, "apa 0.0.0").unwrap(); - install_package(path, "bepa 1.0.0").unwrap(); + install_package(path, "apa 0.0.0", true).unwrap(); + install_package(path, "bepa 1.0.0", true).unwrap(); assert_eq!(installed_packages(path).unwrap(), vec!(pkg1(), pkg2())); } + #[test] + fn test_install_package_fails() { + + let path = "/tmp/test4"; + + let _ = fs::remove_file(path); + + let _ = install_package(path, "apa 0.0.0", false); + install_package(path, "bepa 1.0.0", true).unwrap(); + + assert_eq!(installed_packages(path).unwrap(), vec!(pkg2())); + + } } -- cgit v1.2.1 From 0669f51c178b2060993626382cddeacf3e772f84 Mon Sep 17 00:00:00 2001 From: Stevan Andjelkovic Date: Wed, 4 May 2016 16:08:23 +0200 Subject: Use std::io::copy instead of tee(). --- src/http_client/hyper.rs | 40 +++------------------------------------- 1 file changed, 3 insertions(+), 37 deletions(-) diff --git a/src/http_client/hyper.rs b/src/http_client/hyper.rs index 7cde698..93da9c3 100644 --- a/src/http_client/hyper.rs +++ b/src/http_client/hyper.rs @@ -5,7 +5,7 @@ use hyper::header::{Authorization, Basic, Bearer, ContentType, Headers, Location use hyper::mime::{Attr, Mime, TopLevel, SubLevel, Value}; use rustc_serialize::json; use std::fs::File; -use std::io::{Read, Write, BufReader, BufWriter}; +use std::io::{copy, Read}; use datatype::Error; use http_client::{Auth, HttpClient, HttpRequest}; @@ -95,7 +95,7 @@ impl HttpClient for Hyper { debug!("send_request_to, response: `{}`", rbody); debug!("send_request_to, file: `{:?}`", file); - try!(tee(rbody.as_bytes(), file)); + try!(copy(&mut rbody.as_bytes(), file)); Ok(()) @@ -129,28 +129,12 @@ fn relocate_request<'a>(req: &'a HttpRequest, resp: &Response) -> Result(from: R, to: W) -> Result<(), Error> { - - const BUF_SIZE: usize = 1024 * 1024 * 5; - - let rbuf = BufReader::with_capacity(BUF_SIZE, from); - let mut wbuf = BufWriter::with_capacity(BUF_SIZE, to); - - for b in rbuf.bytes() { - try!(wbuf.write(&[try!(b)])); - } - - Ok(()) - -} - - #[cfg(test)] mod tests { use hyper; use std::fs::File; - use std::io::{repeat, SeekFrom}; + use std::io::SeekFrom; use std::io::prelude::*; use tempfile; @@ -192,24 +176,6 @@ mod tests { assert!(buf != "".to_string()) } - #[test] - fn test_tee() { - let values = repeat(b'a').take(9000); - let sink = File::create("/tmp/otaplus_tee_test").unwrap(); - - assert!(tee(values, sink).is_ok()); - - let mut values2 = repeat(b'a').take(9000); - let mut expected = Vec::new(); - let _ = values2.read_to_end(&mut expected); - - let mut f = File::open("/tmp/otaplus_tee_test").unwrap(); - let mut result = Vec::new(); - let _ = f.read_to_end(&mut result); - - assert_eq!(result, expected); - } - mock_connector!(MockRedirectPolicy { "http://127.0.0.1" => "HTTP/1.1 301 Redirect\r\n\ Location: http://127.0.0.2\r\n\ -- cgit v1.2.1 From 2fdfd386026093a5110583bf2a1aef2cb0df10ef Mon Sep 17 00:00:00 2001 From: Stevan Andjelkovic Date: Wed, 4 May 2016 16:18:02 +0200 Subject: Add #[allow(non_snake_case)] on pending update request. --- src/datatype/update_request.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/datatype/update_request.rs b/src/datatype/update_request.rs index bab6ccb..e269c4d 100644 --- a/src/datatype/update_request.rs +++ b/src/datatype/update_request.rs @@ -11,6 +11,7 @@ pub enum UpdateState { } #[derive(Clone, PartialEq, Eq, Debug, RustcEncodable, RustcDecodable)] +#[allow(non_snake_case)] pub struct PendingUpdateRequest { pub id: UpdateRequestId, pub packageId: Package, -- cgit v1.2.1 From 04f658ea8defc04b14a4ea223c3f98406529e9ed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sim=C3=A3o=20Mata?= Date: Mon, 9 May 2016 14:11:47 +0200 Subject: sort packages by installPos --- src/datatype/update_request.rs | 1 + src/interpreter.rs | 2 +- src/ota_plus.rs | 3 ++- 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/datatype/update_request.rs b/src/datatype/update_request.rs index e269c4d..9fec62d 100644 --- a/src/datatype/update_request.rs +++ b/src/datatype/update_request.rs @@ -14,6 +14,7 @@ pub enum UpdateState { #[allow(non_snake_case)] pub struct PendingUpdateRequest { pub id: UpdateRequestId, + pub installPos: i32, pub packageId: Package, pub createdAt: String } diff --git a/src/interpreter.rs b/src/interpreter.rs index e97c2e1..eec274d 100644 --- a/src/interpreter.rs +++ b/src/interpreter.rs @@ -56,7 +56,7 @@ fn interpreter(env: &mut Env, cmd: Command, tx: Sender) -> Result<(), Err GetPendingUpdates => { let mut updates = try!(get_package_updates()); - updates.sort_by_key(|e| e.createdAt.clone()); + updates.sort_by_key(|e| e.installPos); let update_events: Vec = updates .iter() diff --git a/src/ota_plus.rs b/src/ota_plus.rs index d9050d6..9863fde 100644 --- a/src/ota_plus.rs +++ b/src/ota_plus.rs @@ -79,7 +79,7 @@ pub fn update_packages(config: &Config, let json = try!(json::encode(&pkgs)); let req = HttpRequest::put( - vehicle_updates_endpoint(config, "updates"), + vehicle_updates_endpoint(config, "installed"), Some(Auth::Token(token)), Some(json), ); @@ -185,6 +185,7 @@ mod tests { fn test_get_package_updates() { let pending_update = PendingUpdateRequest { id: "someid".to_string(), + installPos: 0, packageId: Package { name: "fake-pkg".to_string(), version: "0.1.1".to_string() -- cgit v1.2.1 From 77baf24a2e7b7489f1cab0e7a192acaf49578ea6 Mon Sep 17 00:00:00 2001 From: Shaun Taheri Date: Tue, 10 May 2016 15:35:22 +0200 Subject: Remove old hyper forwarding tests --- Cargo.lock | 10 ---------- Cargo.toml | 1 - src/http_client/hyper.rs | 29 ----------------------------- src/lib.rs | 1 - 4 files changed, 41 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index b5df714..979bdaa 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -14,7 +14,6 @@ dependencies = [ "toml 0.1.28 (registry+https://github.com/rust-lang/crates.io-index)", "url 0.5.9 (registry+https://github.com/rust-lang/crates.io-index)", "ws 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", - "yup-hyper-mock 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -531,12 +530,3 @@ dependencies = [ "winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", ] -[[package]] -name = "yup-hyper-mock" -version = "1.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "hyper 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)", - "log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", -] - diff --git a/Cargo.toml b/Cargo.toml index 4d2ed35..592bc90 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -25,4 +25,3 @@ tempfile = "2.1.2" toml = "0.1.28" url = "0.5.9" ws = "0.4.6" -yup-hyper-mock = "1.3.2" \ No newline at end of file diff --git a/src/http_client/hyper.rs b/src/http_client/hyper.rs index 93da9c3..0fb8d61 100644 --- a/src/http_client/hyper.rs +++ b/src/http_client/hyper.rs @@ -131,8 +131,6 @@ fn relocate_request<'a>(req: &'a HttpRequest, resp: &Response) -> Result "HTTP/1.1 301 Redirect\r\n\ - Location: http://127.0.0.2\r\n\ - Server: mock1\r\n\ - \r\n\ - " - "http://127.0.0.2" => "HTTP/1.1 302 Found\r\n\ - Location: https://127.0.0.3\r\n\ - Server: mock2\r\n\ - \r\n\ - " - "https://127.0.0.3" => "HTTP/1.1 200 OK\r\n\ - Server: mock3\r\n\ - \r\n\ - " - }); - - #[test] - fn test_redirect_followall() { - let mut client = hyper::Client::with_connector(MockRedirectPolicy::default()); - client.set_redirect_policy(hyper::client::RedirectPolicy::FollowAll); - - let res = client.get("http://127.0.0.1").send().unwrap(); - assert_eq!(res.headers.get(), Some(&hyper::header::Server("mock3".to_owned()))); - } - } diff --git a/src/lib.rs b/src/lib.rs index 9295f24..080f93f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,6 +1,5 @@ extern crate hyper; #[macro_use] extern crate log; -#[cfg(test)] #[macro_use] extern crate yup_hyper_mock as hyper_mock; extern crate rustc_serialize; extern crate tempfile; extern crate toml; -- cgit v1.2.1 From 992087f11a8a51889898d5506c02f5b62f746a8a Mon Sep 17 00:00:00 2001 From: Shaun Taheri Date: Mon, 2 May 2016 16:26:47 +0200 Subject: Add console command parser --- Cargo.lock | 6 + Cargo.toml | 1 + src/datatype/client_credentials.rs | 4 +- src/datatype/command.rs | 215 ++++++++++++++++++++++++++++++--- src/datatype/error.rs | 18 ++- src/interaction_library/console.rs | 5 +- src/interaction_library/gateway.rs | 4 +- src/interaction_library/interpreter.rs | 6 +- src/interpreter.rs | 198 +++++++++++++++++------------- src/lib.rs | 2 + src/main.rs | 58 ++------- 11 files changed, 358 insertions(+), 159 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 979bdaa..85bf133 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -9,6 +9,7 @@ dependencies = [ "getopts 0.2.14 (registry+https://github.com/rust-lang/crates.io-index)", "hyper 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", + "nom 1.2.3 (registry+https://github.com/rust-lang/crates.io-index)", "rustc-serialize 0.3.19 (registry+https://github.com/rust-lang/crates.io-index)", "tempfile 2.1.2 (registry+https://github.com/rust-lang/crates.io-index)", "toml 0.1.28 (registry+https://github.com/rust-lang/crates.io-index)", @@ -274,6 +275,11 @@ dependencies = [ "libc 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "nom" +version = "1.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "num_cpus" version = "0.2.11" diff --git a/Cargo.toml b/Cargo.toml index 592bc90..4f4cf2b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -20,6 +20,7 @@ env_logger = "0.3.3" getopts = "0.2.14" hyper = "0.8.1" log = "0.3.5" +nom = "1.2.3" rustc-serialize = "0.3.18" tempfile = "2.1.2" toml = "0.1.28" diff --git a/src/datatype/client_credentials.rs b/src/datatype/client_credentials.rs index 3f14bed..b750e7b 100644 --- a/src/datatype/client_credentials.rs +++ b/src/datatype/client_credentials.rs @@ -12,6 +12,6 @@ pub struct ClientSecret { #[derive(Clone, PartialEq, Eq, Debug, RustcEncodable, RustcDecodable)] pub struct ClientCredentials { - id: ClientId, - secret: ClientSecret, + pub id: ClientId, + pub secret: ClientSecret, } diff --git a/src/datatype/command.rs b/src/datatype/command.rs index 64c9680..c36d452 100644 --- a/src/datatype/command.rs +++ b/src/datatype/command.rs @@ -1,34 +1,217 @@ +use std::str; use std::str::FromStr; -use datatype::{ClientCredentials, UpdateRequestId}; +use nom::{IResult, space, eof}; +use datatype::{ClientCredentials, ClientId, ClientSecret, Error, UpdateRequestId}; #[derive(RustcDecodable, RustcEncodable, PartialEq, Eq, Debug)] pub enum Command { - + AcceptUpdate(UpdateRequestId), Authenticate(Option), - - // UI GetPendingUpdates, - AcceptUpdate(UpdateRequestId), - - UpdateInstalledPackages, ListInstalledPackages, - - Shutdown + Shutdown, + UpdateInstalledPackages, } impl FromStr for Command { + type Err = Error; + + fn from_str(s: &str) -> Result { + match command(s.as_bytes()) { + IResult::Done(_, cmd) => parse_arguments(cmd.0, cmd.1.clone()), + _ => Err(Error::Command(format!("unrecognized input: {}", s))), + } + } +} - type Err = (); +named!(command <(Command, Vec<&str>)>, chain!( + space? + ~ cmd: alt!( + alt_complete!(tag!("Authenticate") + | tag!("authenticate") + | tag!("auth") + ) => { |_| Command::Authenticate(None) } - fn from_str(s: &str) -> Result { - match s { - "GetPendingUpdates" => Ok(Command::GetPendingUpdates), - "UpdateInstalledPackages" => Ok(Command::UpdateInstalledPackages), - "ListInstalledPackages" => Ok(Command::ListInstalledPackages), - _ => Err(()), + | alt_complete!(tag!("GetPendingUpdates") + | tag!("getPendingUpdates") + | tag!("pen") + ) => { |_| Command::GetPendingUpdates } + + | alt_complete!(tag!("AcceptUpdate") + | tag!("acceptUpdate") + | tag!("acc") + ) => { |_| Command::AcceptUpdate("".to_owned()) } + + | alt_complete!(tag!("ListInstalledPackages") + | tag!("listInstalledPackages") + | tag!("ls") + ) => { |_| Command::ListInstalledPackages } + + | alt_complete!(tag!("Shutdown") + | tag!("shutdown") + ) => { |_| Command::Shutdown } + + | alt_complete!(tag!("UpdateInstalledPackages") + | tag!("updateInstalledPackages") + | tag!("up") + ) => { |_| Command::UpdateInstalledPackages } + ) + ~ args: arguments + ~ alt!(eof | tag!("\r") | tag!("\n") | tag!(";")), + move || { (cmd, args) } +)); + +named!(arguments <&[u8], Vec<&str> >, chain!( + args: many0!(chain!( + space? + ~ text: map_res!(is_not!(" \t\r\n;"), str::from_utf8) + ~ space?, + || { text } + )), + move || { + args.into_iter() + .filter(|arg| arg.len() > 0) + .collect() + } +)); + +fn parse_arguments(cmd: Command, args: Vec<&str>) -> Result { + match cmd { + Command::AcceptUpdate(_) => { + match args.len() { + 0 => Err(Error::Command("usage: acc ".to_owned())), + 1 => Ok(Command::AcceptUpdate(args[0].to_owned())), + _ => Err(Error::Command(format!("unexpected acc args: {:?}", args))), + } + } + + Command::Authenticate(_) => { + match args.len() { + 0 => Ok(Command::Authenticate(None)), + 1 => Err(Error::Command("usage: auth ".to_owned())), + 2 => { + let (user, pass) = (args[0].to_owned(), args[1].to_owned()); + Ok(Command::Authenticate(Some(ClientCredentials { + id: ClientId { get: user }, + secret: ClientSecret { get: pass }, + }))) + } + _ => Err(Error::Command(format!("unexpected auth args: {:?}", args))), + } } + + Command::GetPendingUpdates => { + match args.len() { + 0 => Ok(Command::GetPendingUpdates), + _ => Err(Error::Command(format!("unexpected pen args: {:?}", args))), + } + } + + Command::ListInstalledPackages => { + match args.len() { + 0 => Ok(Command::ListInstalledPackages), + _ => Err(Error::Command(format!("unexpected ls args: {:?}", args))), + } + } + + Command::Shutdown => { + match args.len() { + 0 => Ok(Command::Shutdown), + _ => Err(Error::Command(format!("unexpected shutdown args: {:?}", args))), + } + } + + Command::UpdateInstalledPackages => { + match args.len() { + 0 => Ok(Command::UpdateInstalledPackages), + _ => Err(Error::Command(format!("unexpected up args: {:?}", args))), + } + } + } +} + + +#[cfg(test)] +mod tests { + use super::{command, arguments}; + use datatype::{Command, ClientCredentials, ClientId, ClientSecret}; + use nom::IResult; + + #[test] + fn parse_command_test() { + assert_eq!(command(&b"auth foo bar"[..]), + IResult::Done(&b""[..], (Command::Authenticate(None), vec!["foo", "bar"]))); + assert_eq!(command(&b"acc 1"[..]), + IResult::Done(&b""[..], (Command::AcceptUpdate("".to_owned()), vec!["1"]))); + assert_eq!(command(&b"ls;\n"[..]), + IResult::Done(&b"\n"[..], (Command::ListInstalledPackages, Vec::new()))); + } + + #[test] + fn parse_arguments_test() { + assert_eq!(arguments(&b"one"[..]), IResult::Done(&b""[..], vec!["one"])); + assert_eq!(arguments(&b"foo bar"[..]), IResult::Done(&b""[..], vec!["foo", "bar"])); + assert_eq!(arguments(&b"n=5"[..]), IResult::Done(&b""[..], vec!["n=5"])); + assert_eq!(arguments(&b""[..]), IResult::Done(&b""[..], Vec::new())); + assert_eq!(arguments(&b" \t some"[..]), IResult::Done(&b""[..], vec!["some"])); + assert_eq!(arguments(&b";"[..]), IResult::Done(&b";"[..], Vec::new())); } + #[test] + fn accept_update_test() { + assert_eq!("acc 1".parse::().unwrap(), Command::AcceptUpdate("1".to_owned())); + assert_eq!("acceptUpdate 2".parse::().unwrap(), Command::AcceptUpdate("2".to_owned())); + assert_eq!("AcceptUpdate 3".parse::().unwrap(), Command::AcceptUpdate("3".to_owned())); + assert_eq!("acc some".parse::().unwrap(), Command::AcceptUpdate("some".to_owned())); + assert!("acc".parse::().is_err()); + assert!("acc more than one".parse::().is_err()); + } + + #[test] + fn authenticate_test() { + assert_eq!("auth".parse::().unwrap(), Command::Authenticate(None)); + assert_eq!("authenticate".parse::().unwrap(), Command::Authenticate(None)); + assert_eq!("Authenticate".parse::().unwrap(), Command::Authenticate(None)); + assert_eq!("auth user pass".parse::().unwrap(), + Command::Authenticate(Some(ClientCredentials { + id: ClientId { get: "user".to_owned() }, + secret: ClientSecret { get: "pass".to_owned() }, + }))); + assert!("auth one".parse::().is_err()); + assert!("auth one two three".parse::().is_err()); + } + + #[test] + fn get_pending_updates_test() { + assert_eq!("pen".parse::().unwrap(), Command::GetPendingUpdates); + assert_eq!("getPendingUpdates".parse::().unwrap(), Command::GetPendingUpdates); + assert_eq!("GetPendingUpdates".parse::().unwrap(), Command::GetPendingUpdates); + assert!("pen some".parse::().is_err()); + } + + #[test] + fn list_installed_test() { + assert_eq!("ls".parse::().unwrap(), Command::ListInstalledPackages); + assert_eq!("listInstalledPackages".parse::().unwrap(), Command::ListInstalledPackages); + assert_eq!("ListInstalledPackages".parse::().unwrap(), Command::ListInstalledPackages); + assert!("ls some".parse::().is_err()); + } + + #[test] + fn shutdown_test() { + assert_eq!("shutdown".parse::().unwrap(), Command::Shutdown); + assert_eq!("Shutdown".parse::().unwrap(), Command::Shutdown); + assert!("shutdown now".parse::().is_err()); + assert!("Shutdown 1 2".parse::().is_err()); + } + + #[test] + fn update_installed_test() { + assert_eq!("up".parse::().unwrap(), Command::UpdateInstalledPackages); + assert_eq!("updateInstalledPackages".parse::().unwrap(), Command::UpdateInstalledPackages); + assert_eq!("UpdateInstalledPackages".parse::().unwrap(), Command::UpdateInstalledPackages); + assert!("up down".parse::().is_err()); + } } diff --git a/src/datatype/error.rs b/src/datatype/error.rs index a8a1d31..b1408f8 100644 --- a/src/datatype/error.rs +++ b/src/datatype/error.rs @@ -1,25 +1,28 @@ -use hyper::error as hyper; -use rustc_serialize::json; use std::convert::From; use std::fmt::{Display, Formatter, Result as FmtResult}; use std::io; use std::path::PathBuf; +use std::sync::{PoisonError}; use std::sync::mpsc::SendError; use url::ParseError as UrlParseError; -use ws; use datatype::Event; +use rustc_serialize::json; +use hyper::error as hyper; +use ws; #[derive(Debug)] pub enum Error { AuthError(String), ClientError(String), + Command(String), Config(ConfigReason), Hyper(hyper::Error), Io(io::Error), JsonDecode(json::DecoderError), JsonEncode(json::EncoderError), + PoisonError(String), Ota(OtaReason), PackageError(String), ParseError(String), @@ -52,6 +55,12 @@ impl From for Error { } } +impl From> for Error { + fn from(e: PoisonError) -> Error { + Error::PoisonError(format!("{}", e)) + } +} + impl From> for Error { fn from(e: SendError) -> Error { Error::SendErrorEvent(e) @@ -70,6 +79,7 @@ impl From for Error { } } + #[derive(Debug)] pub enum OtaReason { CreateFile(PathBuf, io::Error), @@ -93,12 +103,14 @@ impl Display for Error { let inner: String = match *self { Error::AuthError(ref s) => format!("Authentication error, {}", s.clone()), Error::ClientError(ref s) => format!("Http client error: {}", s.clone()), + Error::Command(ref e) => format!("Unknown Command: {}", e.clone()), Error::Config(ref e) => format!("Failed to {}", e.clone()), Error::Hyper(ref e) => format!("Hyper error: {}", e.clone()), Error::Io(ref e) => format!("IO error: {}", e.clone()), Error::JsonDecode(ref e) => format!("Failed to decode JSON: {}", e.clone()), Error::JsonEncode(ref e) => format!("Failed to encode JSON: {}", e.clone()), Error::Ota(ref e) => format!("Ota error, {}", e.clone()), + Error::PoisonError(ref e) => format!("Poison error, {}", e.clone()), Error::PackageError(ref s) => s.clone(), Error::ParseError(ref s) => s.clone(), Error::SendErrorEvent(ref s) => format!("Send error for Event: {}", s.clone()), diff --git a/src/interaction_library/console.rs b/src/interaction_library/console.rs index b79d09f..a56b977 100644 --- a/src/interaction_library/console.rs +++ b/src/interaction_library/console.rs @@ -16,13 +16,10 @@ impl Gateway for Console } fn get_line(&self) -> String { - print!("> "); let mut input = String::new(); let _ = io::stdin().read_line(&mut input); - - return input - + input } fn put_line(&self, s: String) { diff --git a/src/interaction_library/gateway.rs b/src/interaction_library/gateway.rs index 94b6068..bbd9ebd 100644 --- a/src/interaction_library/gateway.rs +++ b/src/interaction_library/gateway.rs @@ -15,16 +15,14 @@ pub trait Gateway: Sized + Send + Sync + 'static fn pretty_print(e: E) -> String; fn run(tx: Sender, rx: Receiver) { - let io = Arc::new(Self::new()); - // Read lines. let io_clone = io.clone(); thread::spawn(move || { loop { let _ = Self::parse(io_clone.get_line()) - .ok_or_else(|| { error!("Error parsing command") }) + .ok_or_else(|| error!("Error parsing command")) .and_then(|cmd| { tx.send(cmd).map_err(|e| error!("Error forwarding command: {:?}", e)) }); diff --git a/src/interaction_library/interpreter.rs b/src/interaction_library/interpreter.rs index bad1b6f..73ad859 100644 --- a/src/interaction_library/interpreter.rs +++ b/src/interaction_library/interpreter.rs @@ -3,12 +3,12 @@ use std::sync::mpsc::{Sender, Receiver}; pub trait Interpreter { - fn interpret(env: &mut Env, c: C, tx: Sender); + fn interpret(env: &mut Env, original: &Env, c: C, tx: Sender); - fn run(env: &mut Env, rx: Receiver, tx: Sender) { + fn run(env: &mut Env, original: &Env, rx: Receiver, tx: Sender) { loop { match rx.recv() { - Ok(c) => Self::interpret(env, c, tx.clone()), + Ok(c) => Self::interpret(env, original, c, tx.clone()), Err(e) => error!("Error receiving command: {:?}", e) } } diff --git a/src/interpreter.rs b/src/interpreter.rs index eec274d..aa9a886 100644 --- a/src/interpreter.rs +++ b/src/interpreter.rs @@ -12,12 +12,50 @@ use ota_plus::{get_package_updates, install_package_update, update_installed_packages, send_install_report}; +#[derive(Clone)] pub struct Env<'a> { pub config: Config, pub access_token: Option>, pub http_client: Arc>, } + +pub struct OurInterpreter; + +impl<'a> Interpreter, Command, Event> for OurInterpreter { + fn interpret(env: &mut Env, original: &Env, cmd: Command, tx: Sender) { + info!("Interpreting: {:?}", cmd); + interpreter(env, original, cmd, tx.clone()) + .unwrap_or_else(|err| { + tx.send(Event::Error(format!("{}", err))) + .unwrap_or_else(|_| error!("interpret: send failed")) + }) + } +} + + +pub struct AutoAcceptor; + +impl Interpreter<(), Event, Command> for AutoAcceptor { + fn interpret(_: &mut (), _: &(), e: Event, ctx: Sender) { + fn f(e: &Event, ctx: Sender) { + if let &Event::NewUpdateAvailable(ref id) = e { + let _ = ctx.send(Command::AcceptUpdate(id.clone())); + } + } + + info!("Event interpreter: {:?}", e); + match e { + Event::Batch(ref evs) => { + for ev in evs { + f(&ev, ctx.clone()) + } + } + e => f(&e, ctx) + } + } +} + // This macro partially applies the config, http client and token to the // passed in functions. macro_rules! partial_apply { @@ -31,92 +69,88 @@ macro_rules! partial_apply { } } -fn interpreter(env: &mut Env, cmd: Command, tx: Sender) -> Result<(), Error> { - - Ok(if let Some(token) = env.access_token.to_owned() { - - let client_clone = env.http_client.clone(); - - partial_apply!( - [get_package_updates, update_installed_packages], - [send_install_report], - [install_package_update], &env.config, client_clone, &token); - - match cmd { - - Authenticate(_) => (), // Already authenticated. - - AcceptUpdate(ref id) => { - try!(tx.send(Event::UpdateStateChanged(id.clone(), UpdateState::Downloading))); - let report = try!(install_package_update(id.to_owned(), tx.to_owned())); - try!(send_install_report(report.clone())); - info!("Update finished. Report sent: {:?}", report) +fn interpreter(env: &mut Env, original: &Env, cmd: Command, tx: Sender) -> Result<(), Error> { + let client_clone = env.http_client.clone(); + + if let Authenticate(credentials) = cmd { + match credentials { + Some(cc) => { + env.config.auth.client_id = cc.id.get.to_owned(); + env.config.auth.secret = cc.secret.get.to_owned(); + + let mut client = try!(client_clone.lock()); + match authenticate(&env.config.auth, &mut *client) { + Ok(token) => { + env.access_token = Some(token.into()); + }, + Err(err) => return Err(err) + } + }, + None => { + env.config.auth.client_id = original.config.auth.client_id.to_owned(); + env.config.auth.secret = original.config.auth.secret.to_owned(); } - - GetPendingUpdates => { - let mut updates = try!(get_package_updates()); - - updates.sort_by_key(|e| e.installPos); - - let update_events: Vec = updates - .iter() - .map(|u| Event::NewUpdateAvailable(u.id.clone())) - .collect(); - - info!("New package updates available: {:?}", update_events); - try!(tx.send(Event::Batch(update_events))) - } - - ListInstalledPackages => { - let pkgs = try!(env.config.ota.package_manager.installed_packages()); - try!(tx.send(Event::FoundInstalledPackages(pkgs.clone()))) - } - - UpdateInstalledPackages => { - try!(update_installed_packages()); - info!("Posted installed packages to the server.") - } - - Shutdown => exit(0) } + return Ok(()) + } - } else { - - match cmd { - - Authenticate(_) => { - // XXX: partially apply? - let client_clone = env.http_client.clone(); - let mut client = client_clone.lock().unwrap(); - let token = try!(authenticate(&env.config.auth, &mut *client)); - env.access_token = Some(token.into()) + match env.access_token.to_owned() { + Some(token) => Ok({ + partial_apply!( + [get_package_updates, update_installed_packages], + [send_install_report], + [install_package_update], + &env.config, client_clone, &token + ); + + match cmd { + AcceptUpdate(ref id) => { + try!(tx.send(Event::UpdateStateChanged(id.clone(), UpdateState::Downloading))); + let report = try!(install_package_update(id.to_owned(), tx.to_owned())); + try!(send_install_report(report.clone())); + info!("Update finished. Report sent: {:?}", report) + } + + Authenticate(_) => unreachable!(), + + GetPendingUpdates => { + let mut updates = try!(get_package_updates()); + updates.sort_by_key(|e| e.createdAt.clone()); + let update_events: Vec = updates + .iter() + .map(|u| Event::NewUpdateAvailable(u.id.clone())) + .collect(); + info!("New package updates available: {:?}", update_events); + try!(tx.send(Event::Batch(update_events))) + } + + ListInstalledPackages => { + let pkgs = try!(env.config.ota.package_manager.installed_packages()); + try!(tx.send(Event::FoundInstalledPackages(pkgs.clone()))) + } + + Shutdown => exit(0), + + UpdateInstalledPackages => { + try!(update_installed_packages()); + info!("Posted installed packages to the server.") + } } - - Shutdown => exit(0), - - AcceptUpdate(_) | - GetPendingUpdates | - ListInstalledPackages | - UpdateInstalledPackages => - tx.send(Event::NotAuthenticated) - .unwrap_or_else(|_| error!("interpreter: send failed.")) + }), + + None => Ok({ + match cmd { + Authenticate(_) => unreachable!(), + Shutdown => exit(0), + + AcceptUpdate(_) | + GetPendingUpdates | + ListInstalledPackages | + UpdateInstalledPackages => { + tx.send(Event::NotAuthenticated) + .unwrap_or_else(|_| error!("interpreter: send failed.")) + } } - - }) - -} - -pub struct OurInterpreter; - -impl<'a> Interpreter, Command, Event> for OurInterpreter { - - fn interpret(env: &mut Env, cmd: Command, tx: Sender) { - - info!("Interpreting: {:?}", cmd); - - interpreter(env, cmd, tx.clone()) - .unwrap_or_else(|err| tx.send(Event::Error(format!("{}", err))) - .unwrap_or_else(|_| error!("interpret: send failed"))) + }) } - } diff --git a/src/lib.rs b/src/lib.rs index 080f93f..78f2308 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,4 +1,5 @@ extern crate hyper; +#[macro_use] extern crate nom; #[macro_use] extern crate log; extern crate rustc_serialize; extern crate tempfile; @@ -6,6 +7,7 @@ extern crate toml; extern crate url; extern crate ws; + pub mod auth_plus; pub mod datatype; pub mod http_client; diff --git a/src/main.rs b/src/main.rs index a308f31..1e5f296 100644 --- a/src/main.rs +++ b/src/main.rs @@ -25,42 +25,38 @@ use libotaplus::interaction_library::broadcast::Broadcast; use libotaplus::interaction_library::console::Console; use libotaplus::interaction_library::gateway::Gateway; use libotaplus::interaction_library::websocket::Websocket; -use libotaplus::interpreter::{OurInterpreter, Env}; +use libotaplus::interpreter::{OurInterpreter, AutoAcceptor, Env}; use libotaplus::package_manager::PackageManager; macro_rules! spawn_thread { ($name:expr, $body:block) => { - { - match thread::Builder::new().name($name.to_string()).spawn(move || { - info!("Spawning {}", $name.to_string()); - $body - }) { - Err(e) => panic!("Couldn't spawn {}: {}", $name, e), - Ok(handle) => handle - } + match thread::Builder::new().name($name.to_string()).spawn(move || { + info!("Spawning {}", $name.to_string()); + $body + }) { + Err(e) => panic!("Couldn't spawn {}: {}", $name, e), + Ok(handle) => handle } } } fn spawn_interpreter(config: Config, crx: Receiver, etx: Sender) { - let client = Arc::new(Mutex::new(Hyper::new())); - - let mut env = Env { + let env = Env { config: config.clone(), access_token: None, http_client: client.clone(), }; spawn_thread!("Interpreter", { - OurInterpreter::run(&mut env, crx, etx); + OurInterpreter::run(&mut env.clone(), &env, crx, etx); }); } fn spawn_autoacceptor(erx: Receiver, ctx: Sender) { spawn_thread!("Autoacceptor of software updates", { - AutoAcceptor::run(&mut (), erx, ctx); + AutoAcceptor::run(&mut (), &(), erx, ctx); }); } @@ -96,41 +92,12 @@ fn start_event_broadcasting(broadcast: Broadcast) { }); } -struct AutoAcceptor; - -impl Interpreter<(), Event, Command> for AutoAcceptor { - fn interpret(_: &mut (), e: Event, ctx: Sender) { - fn f(e: &Event, ctx: Sender) { - match e { - &Event::NewUpdateAvailable(ref id) => { - let _ = ctx.send(Command::AcceptUpdate(id.clone())); - }, - _ => {} - } - } - - info!("Event interpreter: {:?}", e); - - match e { - Event::Batch(ref evs) => { - for ev in evs { - f(&ev, ctx.clone()) - } - } - e => f(&e, ctx) - } - } -} - fn main() { - env_logger::init().expect("Couldn't initialize logger"); - let config = build_config(); let (etx, erx): (Sender, Receiver) = channel(); let (ctx, crx): (Sender, Receiver) = channel(); - let mut broadcast: Broadcast = Broadcast::new(erx); // Must subscribe to the signal before spawning ANY other threads @@ -143,7 +110,6 @@ fn main() { spawn_update_poller(ctx.clone(), config.clone()); let events_for_repl = broadcast.subscribe(); - start_event_broadcasting(broadcast); perform_initial_sync(ctx.clone()); @@ -155,9 +121,9 @@ fn main() { if config.test.looping { println!("Ota Plus Client REPL started."); Console::run(ctx.clone(), events_for_repl); - } else { - thread::sleep(Duration::from_secs(60000000)); } + + thread::sleep(Duration::from_secs(60000000)); } fn build_config() -> Config { -- cgit v1.2.1 From 753ad385e5bf5f768aa00ac1e31b2f32c822a5e6 Mon Sep 17 00:00:00 2001 From: Stevan Andjelkovic Date: Thu, 12 May 2016 14:25:04 +0200 Subject: Remove original env from interpreter. --- src/interaction_library/interpreter.rs | 6 +- src/interpreter.rs | 190 ++++++++++++++++----------------- src/main.rs | 4 +- 3 files changed, 96 insertions(+), 104 deletions(-) diff --git a/src/interaction_library/interpreter.rs b/src/interaction_library/interpreter.rs index 73ad859..bad1b6f 100644 --- a/src/interaction_library/interpreter.rs +++ b/src/interaction_library/interpreter.rs @@ -3,12 +3,12 @@ use std::sync::mpsc::{Sender, Receiver}; pub trait Interpreter { - fn interpret(env: &mut Env, original: &Env, c: C, tx: Sender); + fn interpret(env: &mut Env, c: C, tx: Sender); - fn run(env: &mut Env, original: &Env, rx: Receiver, tx: Sender) { + fn run(env: &mut Env, rx: Receiver, tx: Sender) { loop { match rx.recv() { - Ok(c) => Self::interpret(env, original, c, tx.clone()), + Ok(c) => Self::interpret(env, c, tx.clone()), Err(e) => error!("Error receiving command: {:?}", e) } } diff --git a/src/interpreter.rs b/src/interpreter.rs index aa9a886..3c41f37 100644 --- a/src/interpreter.rs +++ b/src/interpreter.rs @@ -19,13 +19,12 @@ pub struct Env<'a> { pub http_client: Arc>, } - pub struct OurInterpreter; impl<'a> Interpreter, Command, Event> for OurInterpreter { - fn interpret(env: &mut Env, original: &Env, cmd: Command, tx: Sender) { + fn interpret(env: &mut Env, cmd: Command, tx: Sender) { info!("Interpreting: {:?}", cmd); - interpreter(env, original, cmd, tx.clone()) + interpreter(env, cmd, tx.clone()) .unwrap_or_else(|err| { tx.send(Event::Error(format!("{}", err))) .unwrap_or_else(|_| error!("interpret: send failed")) @@ -33,29 +32,6 @@ impl<'a> Interpreter, Command, Event> for OurInterpreter { } } - -pub struct AutoAcceptor; - -impl Interpreter<(), Event, Command> for AutoAcceptor { - fn interpret(_: &mut (), _: &(), e: Event, ctx: Sender) { - fn f(e: &Event, ctx: Sender) { - if let &Event::NewUpdateAvailable(ref id) = e { - let _ = ctx.send(Command::AcceptUpdate(id.clone())); - } - } - - info!("Event interpreter: {:?}", e); - match e { - Event::Batch(ref evs) => { - for ev in evs { - f(&ev, ctx.clone()) - } - } - e => f(&e, ctx) - } - } -} - // This macro partially applies the config, http client and token to the // passed in functions. macro_rules! partial_apply { @@ -69,88 +45,104 @@ macro_rules! partial_apply { } } -fn interpreter(env: &mut Env, original: &Env, cmd: Command, tx: Sender) -> Result<(), Error> { - let client_clone = env.http_client.clone(); - - if let Authenticate(credentials) = cmd { - match credentials { - Some(cc) => { - env.config.auth.client_id = cc.id.get.to_owned(); - env.config.auth.secret = cc.secret.get.to_owned(); - - let mut client = try!(client_clone.lock()); - match authenticate(&env.config.auth, &mut *client) { - Ok(token) => { - env.access_token = Some(token.into()); - }, - Err(err) => return Err(err) - } - }, - None => { - env.config.auth.client_id = original.config.auth.client_id.to_owned(); - env.config.auth.secret = original.config.auth.secret.to_owned(); +fn interpreter(env: &mut Env, cmd: Command, tx: Sender) -> Result<(), Error> { + + if let Some(token) = env.access_token.to_owned() { + + let client_clone = env.http_client.clone(); + + partial_apply!( + [get_package_updates, update_installed_packages], + [send_install_report], + [install_package_update], + &env.config, client_clone, &token + ); + + match cmd { + + Authenticate(_) => (), // Already authenticated. + + AcceptUpdate(ref id) => { + try!(tx.send(Event::UpdateStateChanged(id.clone(), UpdateState::Downloading))); + let report = try!(install_package_update(id.to_owned(), tx.to_owned())); + try!(send_install_report(report.clone())); + info!("Update finished. Report sent: {:?}", report) + } + + GetPendingUpdates => { + let mut updates = try!(get_package_updates()); + + updates.sort_by_key(|e| e.installPos); + + let update_events: Vec = updates + .iter() + .map(|u| Event::NewUpdateAvailable(u.id.clone())) + .collect(); + + info!("New package updates available: {:?}", update_events); + try!(tx.send(Event::Batch(update_events))) } + + ListInstalledPackages => { + let pkgs = try!(env.config.ota.package_manager.installed_packages()); + try!(tx.send(Event::FoundInstalledPackages(pkgs.clone()))) + } + + UpdateInstalledPackages => { + try!(update_installed_packages()); + info!("Posted installed packages to the server.") + } + + Shutdown => exit(0) + } - return Ok(()) - } - match env.access_token.to_owned() { - Some(token) => Ok({ - partial_apply!( - [get_package_updates, update_installed_packages], - [send_install_report], - [install_package_update], - &env.config, client_clone, &token - ); - - match cmd { - AcceptUpdate(ref id) => { - try!(tx.send(Event::UpdateStateChanged(id.clone(), UpdateState::Downloading))); - let report = try!(install_package_update(id.to_owned(), tx.to_owned())); - try!(send_install_report(report.clone())); - info!("Update finished. Report sent: {:?}", report) - } + } else { - Authenticate(_) => unreachable!(), - - GetPendingUpdates => { - let mut updates = try!(get_package_updates()); - updates.sort_by_key(|e| e.createdAt.clone()); - let update_events: Vec = updates - .iter() - .map(|u| Event::NewUpdateAvailable(u.id.clone())) - .collect(); - info!("New package updates available: {:?}", update_events); - try!(tx.send(Event::Batch(update_events))) - } + match cmd { - ListInstalledPackages => { - let pkgs = try!(env.config.ota.package_manager.installed_packages()); - try!(tx.send(Event::FoundInstalledPackages(pkgs.clone()))) - } + Authenticate(_) => { + // XXX: partially apply? + let client_clone = env.http_client.clone(); + let mut client = client_clone.lock().unwrap(); + let token = try!(authenticate(&env.config.auth, &mut *client)); + env.access_token = Some(token.into()); + } - Shutdown => exit(0), + Shutdown => exit(0), - UpdateInstalledPackages => { - try!(update_installed_packages()); - info!("Posted installed packages to the server.") - } + AcceptUpdate(_) | + GetPendingUpdates | + ListInstalledPackages | + UpdateInstalledPackages => + tx.send(Event::NotAuthenticated) + .unwrap_or_else(|_| error!("interpreter: send failed.")) + } + + } + + Ok(()) + +} + +pub struct AutoAcceptor; + +impl Interpreter<(), Event, Command> for AutoAcceptor { + fn interpret(_: &mut (), e: Event, ctx: Sender) { + fn f(e: &Event, ctx: Sender) { + if let &Event::NewUpdateAvailable(ref id) = e { + let _ = ctx.send(Command::AcceptUpdate(id.clone())); } - }), - - None => Ok({ - match cmd { - Authenticate(_) => unreachable!(), - Shutdown => exit(0), - - AcceptUpdate(_) | - GetPendingUpdates | - ListInstalledPackages | - UpdateInstalledPackages => { - tx.send(Event::NotAuthenticated) - .unwrap_or_else(|_| error!("interpreter: send failed.")) + } + + info!("Event interpreter: {:?}", e); + match e { + Event::Batch(ref evs) => { + for ev in evs { + f(&ev, ctx.clone()) } } - }) + e => f(&e, ctx) + } } } diff --git a/src/main.rs b/src/main.rs index 1e5f296..1428c82 100644 --- a/src/main.rs +++ b/src/main.rs @@ -50,13 +50,13 @@ fn spawn_interpreter(config: Config, crx: Receiver, etx: Sender) }; spawn_thread!("Interpreter", { - OurInterpreter::run(&mut env.clone(), &env, crx, etx); + OurInterpreter::run(&mut env.clone(), crx, etx); }); } fn spawn_autoacceptor(erx: Receiver, ctx: Sender) { spawn_thread!("Autoacceptor of software updates", { - AutoAcceptor::run(&mut (), &(), erx, ctx); + AutoAcceptor::run(&mut (), erx, ctx); }); } -- cgit v1.2.1 From 7254dee826038b570c955e5bffdc3aa4f3e9297e Mon Sep 17 00:00:00 2001 From: Stevan Andjelkovic Date: Thu, 12 May 2016 15:03:13 +0200 Subject: Remove extra slash. --- src/ota_plus.rs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/ota_plus.rs b/src/ota_plus.rs index 9863fde..1225570 100644 --- a/src/ota_plus.rs +++ b/src/ota_plus.rs @@ -11,7 +11,11 @@ use http_client::{Auth, HttpClient, HttpRequest}; fn vehicle_updates_endpoint(config: &Config, path: &str) -> Url { - config.ota.server.join(&format!("/api/v1/vehicle_updates/{}/{}", config.auth.vin, path)).unwrap() + config.ota.server.join(& if path.is_empty() { + format!("/api/v1/vehicle_updates/{}", config.auth.vin) + } else { + format!("/api/v1/vehicle_updates/{}/{}", config.auth.vin, path) + }).unwrap() } pub fn download_package_update(config: &Config, -- cgit v1.2.1 From 2dbf4ed9135fc8f87c7fa4988342e469ac2183dc Mon Sep 17 00:00:00 2001 From: Stevan Andjelkovic Date: Thu, 12 May 2016 17:20:37 +0200 Subject: Don't set content type json, unless there's a body. --- src/http_client/hyper.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/http_client/hyper.rs b/src/http_client/hyper.rs index 0fb8d61..6fe8b9f 100644 --- a/src/http_client/hyper.rs +++ b/src/http_client/hyper.rs @@ -58,13 +58,13 @@ impl HttpClient for Hyper { token: token.access_token.clone() })); - headers.set(ContentType(Mime( - TopLevel::Application, - SubLevel::Json, - vec![(Attr::Charset, Value::Utf8)]))); - if let Some(body) = body { + headers.set(ContentType(Mime( + TopLevel::Application, + SubLevel::Json, + vec![(Attr::Charset, Value::Utf8)]))); + let json: String = try!(json::encode(&body)); body.into_owned().push_str(&json) -- cgit v1.2.1 From 139286b03e5720ba0e20f2065706b9a2afe2ac56 Mon Sep 17 00:00:00 2001 From: Stevan Andjelkovic Date: Fri, 13 May 2016 11:43:05 +0200 Subject: Better logging. --- Cargo.lock | 1 + Cargo.toml | 1 + src/http_client/hyper.rs | 9 +++++++++ src/lib.rs | 1 + src/main.rs | 44 +++++++++++++++++++++++++++++++++++++++----- src/ota_plus.rs | 3 +++ 6 files changed, 54 insertions(+), 5 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 85bf133..46e594e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -12,6 +12,7 @@ dependencies = [ "nom 1.2.3 (registry+https://github.com/rust-lang/crates.io-index)", "rustc-serialize 0.3.19 (registry+https://github.com/rust-lang/crates.io-index)", "tempfile 2.1.2 (registry+https://github.com/rust-lang/crates.io-index)", + "time 0.1.35 (registry+https://github.com/rust-lang/crates.io-index)", "toml 0.1.28 (registry+https://github.com/rust-lang/crates.io-index)", "url 0.5.9 (registry+https://github.com/rust-lang/crates.io-index)", "ws 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", diff --git a/Cargo.toml b/Cargo.toml index 4f4cf2b..5de238f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -23,6 +23,7 @@ log = "0.3.5" nom = "1.2.3" rustc-serialize = "0.3.18" tempfile = "2.1.2" +time = "0.1.35" toml = "0.1.28" url = "0.5.9" ws = "0.4.6" diff --git a/src/http_client/hyper.rs b/src/http_client/hyper.rs index 6fe8b9f..ee0a08c 100644 --- a/src/http_client/hyper.rs +++ b/src/http_client/hyper.rs @@ -6,6 +6,7 @@ use hyper::mime::{Attr, Mime, TopLevel, SubLevel, Value}; use rustc_serialize::json; use std::fs::File; use std::io::{copy, Read}; +use time; use datatype::Error; use http_client::{Auth, HttpClient, HttpRequest}; @@ -80,6 +81,8 @@ impl HttpClient for Hyper { debug!("send_request_to, headers: `{}`", headers); debug!("send_request_to, body: `{}`", body); + let t0 = time::precise_time_ns(); + let mut resp = try!(self.client .request(req.method.clone().into_owned().into(), req.url.clone().into_owned()) @@ -87,6 +90,12 @@ impl HttpClient for Hyper { .body(&body) .send()); + let t1 = time::precise_time_ns(); + let delta = t1 - t0; + + info!("Hyper::send_request_to, request: {}, response status: {}, latency: {} ns", + req.to_string(), resp.status, delta); + if resp.status.is_success() { let mut rbody = String::new(); diff --git a/src/lib.rs b/src/lib.rs index 78f2308..deae60c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -3,6 +3,7 @@ extern crate hyper; #[macro_use] extern crate log; extern crate rustc_serialize; extern crate tempfile; +extern crate time; extern crate toml; extern crate url; extern crate ws; diff --git a/src/main.rs b/src/main.rs index 1428c82..0a35b74 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,22 +1,25 @@ -#[macro_use] extern crate log; extern crate chan; extern crate chan_signal; extern crate crossbeam; extern crate env_logger; extern crate getopts; extern crate hyper; +#[macro_use] extern crate log; extern crate rustc_serialize; +extern crate time; extern crate ws; #[macro_use] extern crate libotaplus; +use chan::Receiver as ChanReceiver; +use chan_signal::Signal; +use env_logger::LogBuilder; use getopts::Options; +use log::LogRecord; use std::env; -use std::sync::{Arc, Mutex}; use std::sync::mpsc::{Sender, Receiver, channel}; +use std::sync::{Arc, Mutex}; use std::thread; use std::time::Duration; -use chan_signal::Signal; -use chan::Receiver as ChanReceiver; use libotaplus::datatype::{config, Config, Event, Command, Url}; use libotaplus::http_client::Hyper; @@ -93,7 +96,9 @@ fn start_event_broadcasting(broadcast: Broadcast) { } fn main() { - env_logger::init().expect("Couldn't initialize logger"); + + setup_logging(); + let config = build_config(); let (etx, erx): (Sender, Receiver) = channel(); @@ -126,6 +131,35 @@ fn main() { thread::sleep(Duration::from_secs(60000000)); } +fn setup_logging() { + + let format = |record: &LogRecord| { + + let service_name = env::var("SERVICE_NAME") + .unwrap_or("ota-plus-client".to_string()); + + let service_version = env::var("SERVICE_VERSION") + .unwrap_or("?".to_string()); + + let timestamp = time::now().to_timespec().sec; + + format!("{} ({}), {}: {} - {}", + service_name, service_version, timestamp, record.level(), record.args()) + }; + + let mut builder = LogBuilder::new(); + builder.format(format); + + if let Ok(level) = env::var("RUST_LOG") { + builder.parse(&level); + } + + builder.init() + .expect("env_logger::init() called twice, blame the programmers."); + +} + + fn build_config() -> Config { let args: Vec = env::args().collect(); diff --git a/src/ota_plus.rs b/src/ota_plus.rs index 1225570..63c6d45 100644 --- a/src/ota_plus.rs +++ b/src/ota_plus.rs @@ -80,6 +80,9 @@ pub fn update_packages(config: &Config, client: &mut HttpClient, token: &AccessToken, pkgs: &Vec) -> Result<(), Error> { + + info!("update_packages, pkgs: {:?}", pkgs); + let json = try!(json::encode(&pkgs)); let req = HttpRequest::put( -- cgit v1.2.1 From 8bb7ad29549d484c6bc0a941e8ac70edb90320ef Mon Sep 17 00:00:00 2001 From: Stevan Andjelkovic Date: Fri, 13 May 2016 12:07:48 +0200 Subject: Make put actually put and not post... --- src/datatype/method.rs | 3 +++ src/http_client/http_client.rs | 2 +- src/ota_plus.rs | 10 +++++----- 3 files changed, 9 insertions(+), 6 deletions(-) diff --git a/src/datatype/method.rs b/src/datatype/method.rs index 3a834e1..2b8cec2 100644 --- a/src/datatype/method.rs +++ b/src/datatype/method.rs @@ -6,6 +6,7 @@ use hyper::method; pub enum Method { Get, Post, + Put, } impl ToString for Method { @@ -13,6 +14,7 @@ impl ToString for Method { match *self { Method::Get => "GET".to_string(), Method::Post => "POST".to_string(), + Method::Put => "PUT".to_string(), } } } @@ -22,6 +24,7 @@ impl Into for Method { match self { Method::Get => method::Method::Get, Method::Post => method::Method::Post, + Method::Put => method::Method::Put, } } } diff --git a/src/http_client/http_client.rs b/src/http_client/http_client.rs index 3629f7e..eafaa19 100644 --- a/src/http_client/http_client.rs +++ b/src/http_client/http_client.rs @@ -69,7 +69,7 @@ impl<'a> HttpRequest<'a> { A: Into>>, B: Into> { - HttpRequest::new(Method::Post, url, auth, body) + HttpRequest::new(Method::Put, url, auth, body) } } diff --git a/src/ota_plus.rs b/src/ota_plus.rs index 63c6d45..960edf1 100644 --- a/src/ota_plus.rs +++ b/src/ota_plus.rs @@ -77,9 +77,9 @@ pub fn get_package_updates(config: &Config, // XXX: Remove in favour of update_installed_packages()? pub fn update_packages(config: &Config, - client: &mut HttpClient, - token: &AccessToken, - pkgs: &Vec) -> Result<(), Error> { + client: &mut HttpClient, + token: &AccessToken, + pkgs: &Vec) -> Result<(), Error> { info!("update_packages, pkgs: {:?}", pkgs); @@ -97,8 +97,8 @@ pub fn update_packages(config: &Config, } pub fn update_installed_packages(config: &Config, - client: &mut HttpClient, - token: &AccessToken) -> Result<(), Error> { + client: &mut HttpClient, + token: &AccessToken) -> Result<(), Error> { let pkgs = try!(config.ota.package_manager.installed_packages()); update_packages(config, client, token, &pkgs) -- cgit v1.2.1 From 6169415ba174afcfae1a03b178bef24520c31bc8 Mon Sep 17 00:00:00 2001 From: Stevan Andjelkovic Date: Fri, 13 May 2016 12:33:44 +0200 Subject: Fix bug with body. --- src/http_client/hyper.rs | 18 ++++++++++-------- src/ota_plus.rs | 6 +++++- 2 files changed, 15 insertions(+), 9 deletions(-) diff --git a/src/http_client/hyper.rs b/src/http_client/hyper.rs index ee0a08c..60e2691 100644 --- a/src/http_client/hyper.rs +++ b/src/http_client/hyper.rs @@ -30,8 +30,8 @@ impl HttpClient for Hyper { debug!("send_request_to, request: {}", req.to_string()); - let mut headers = Headers::new(); - let mut body = String::new(); + let mut headers = Headers::new(); + let mut req_body = String::new(); match (req.auth.clone().map(|a| a.into_owned()), req.body.to_owned()) { @@ -49,7 +49,7 @@ impl HttpClient for Hyper { SubLevel::WwwFormUrlEncoded, vec![(Attr::Charset, Value::Utf8)]))); - body.push_str("grant_type=client_credentials") + req_body.push_str("grant_type=client_credentials") } @@ -68,7 +68,7 @@ impl HttpClient for Hyper { let json: String = try!(json::encode(&body)); - body.into_owned().push_str(&json) + req_body.push_str(&json) } @@ -78,8 +78,8 @@ impl HttpClient for Hyper { } - debug!("send_request_to, headers: `{}`", headers); - debug!("send_request_to, body: `{}`", body); + debug!("send_request_to, headers: `{}`", headers); + debug!("send_request_to, req_body: `{}`", req_body); let t0 = time::precise_time_ns(); @@ -87,7 +87,7 @@ impl HttpClient for Hyper { .request(req.method.clone().into_owned().into(), req.url.clone().into_owned()) .headers(headers) - .body(&body) + .body(&req_body) .send()); let t1 = time::precise_time_ns(); @@ -112,7 +112,9 @@ impl HttpClient for Hyper { let req = try!(relocate_request(req, &resp)); self.send_request_to(&req, file) } else { - Err(Error::ClientError(format!("Request errored with status {}", resp.status))) + let mut rbody = String::new(); + let _: usize = try!(resp.read_to_string(&mut rbody)); + Err(Error::ClientError(format!("Request errored with status {}, body: {}", resp.status, rbody))) } } diff --git a/src/ota_plus.rs b/src/ota_plus.rs index 960edf1..415b3e8 100644 --- a/src/ota_plus.rs +++ b/src/ota_plus.rs @@ -85,13 +85,17 @@ pub fn update_packages(config: &Config, let json = try!(json::encode(&pkgs)); + debug!("update_packages, json: {}", json); + let req = HttpRequest::put( vehicle_updates_endpoint(config, "installed"), Some(Auth::Token(token)), Some(json), ); - let _: String = try!(client.send_request(&req)); + let resp: String = try!(client.send_request(&req)); + + info!("update_packages, resp: {}", resp); return Ok(()) } -- cgit v1.2.1 From 7e664a41e7e8d4af7a200771352da1b75b5a796f Mon Sep 17 00:00:00 2001 From: Alex Humphreys Date: Thu, 12 May 2016 12:56:38 +0200 Subject: Add Dockerfile for stress testing image --- README.md | 15 +++++++++++++++ pkg/Dockerfile | 32 ++++++++++++------------------- pkg/ota.toml.template | 1 + pkg/provision/auth.json | 11 +++++++++++ pkg/provision/start-up.sh | 48 +++++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 87 insertions(+), 20 deletions(-) create mode 100644 pkg/provision/auth.json create mode 100755 pkg/provision/start-up.sh diff --git a/README.md b/README.md index f83ab01..d5a66fb 100644 --- a/README.md +++ b/README.md @@ -27,3 +27,18 @@ A `.deb` package can be built with `docker run -e VERSION=0.0.0 -v $PWD:/build a [FPM](https://github.com/jordansissel/fpm) is used to create RPM packages. A `.rpm` package can be built with `docker run -e VERSION=0.0.0 -v $PWD:/build advancedtelematic/client-packager make rpm` (or simply `make rpm` with FPM and the build packages installed). Remember to set the `VERSION` environment variable to the correct version. + +## Dockerfile + +There is a Dockerfile in `/pkg` to create and image with ota-plus-client that automatically configures itself with a random VIN. To build this image run: + +``` +make +docker build -t advancedtelematic/ota-plus-client pkg/ +``` + +To use it, run: + +``` +docker run advancedtelematic/ota-plus-client +``` diff --git a/pkg/Dockerfile b/pkg/Dockerfile index eccbe0b..b133114 100644 --- a/pkg/Dockerfile +++ b/pkg/Dockerfile @@ -1,24 +1,16 @@ -FROM clux/muslrust -# for statically linking binaries; see https://github.com/clux/muslrust +FROM debian:8.4 -ENV FPM_VER 1.5.0 +RUN apt-get update -RUN apt-get update \ - && apt-get install -y \ - devscripts \ - dh-systemd \ - openssl \ - patch \ - rpm \ - ruby \ - ruby-dev \ - && rm -rf /var/lib/apt/lists/* +EXPOSE 8080 -# install fpm -RUN gem install fpm -v ${FPM_VER} -# apply patch to add --rpm-service flag -COPY pr862.patch / -RUN patch -i /pr862.patch /var/lib/gems/2.1.0/gems/fpm-${FPM_VER}/lib/fpm/package/rpm.rb +RUN apt-get install -y httpie jq gettext -WORKDIR /build -CMD ["make", "all"] +COPY ota_plus_client /usr/bin/ +COPY ota.toml.template /etc/ +COPY provision/start-up.sh /usr/bin/ +COPY provision/auth.json /etc/ + +ENV LANG="en_GB.UTF-8" + +CMD start-up.sh diff --git a/pkg/ota.toml.template b/pkg/ota.toml.template index 10286cb..0ccc12e 100644 --- a/pkg/ota.toml.template +++ b/pkg/ota.toml.template @@ -13,3 +13,4 @@ package_manager = "${PACKAGE_MANAGER}" [test] looping = false +http = false diff --git a/pkg/provision/auth.json b/pkg/provision/auth.json new file mode 100644 index 0000000..bb2e100 --- /dev/null +++ b/pkg/provision/auth.json @@ -0,0 +1,11 @@ +{ + "client_name": "STRESS12345678901", + "grant_types": [ + "client_credentials" + ], + "scope": [ + "ota-core.$VIN.write", + "ota-core.$VIN.read" + ] +} + diff --git a/pkg/provision/start-up.sh b/pkg/provision/start-up.sh new file mode 100755 index 0000000..ce6577b --- /dev/null +++ b/pkg/provision/start-up.sh @@ -0,0 +1,48 @@ +#!/bin/bash + +set -eo pipefail + +OTA_AUTH_URL="http://auth-plus-testing.gw.prod01.advancedtelematic.com" +OTA_AUTH_PATH="/clients" + +OTA_SERVER_URL="http://ota-plus-web-testing.gw.prod01.advancedtelematic.com" +OTA_SERVER_PATH="/api/v1/vehicles/" + +OTA_CORE_URL="http://ota-plus-core-testing.gw.prod01.advancedtelematic.com" + +PACKAGE_MANAGER="dpkg" + +TEMPLATE_PATH="/etc/ota.toml.template" + +VIN_SUFFIX=$(< /dev/urandom tr -dc A-HJ-NPR-Z0-9 | head -c${1:-11};echo;) + +echo $VIN_SUFFIX +export OTA_CLIENT_VIN=STRESS$VIN_SUFFIX +#export OTA_CLIENT_VIN=STRESS12345678901 + +echo "vin=${OTA_CLIENT_VIN}" | http put "${OTA_SERVER_URL}${OTA_SERVER_PATH}${OTA_CLIENT_VIN}" +JSON=$(envsubst < /etc/auth.json) +AUTH_DATA=$(echo $JSON | http post $OTA_AUTH_URL$OTA_AUTH_PATH) + +OTA_AUTH_CLIENT_ID=$(echo $AUTH_DATA | jq -r .client_id) +OTA_AUTH_SECRET=$(echo $AUTH_DATA | jq -r .client_secret) + +export OTA_CLIENT_VIN=$OTA_CLIENT_VIN +export OTA_AUTH_URL=$OTA_AUTH_URL +export OTA_SERVER_URL=$OTA_CORE_URL +export OTA_AUTH_CLIENT_ID=$OTA_AUTH_CLIENT_ID +export OTA_AUTH_SECRET=$OTA_AUTH_SECRET +export PACKAGE_MANAGER=$PACKAGE_MANAGER + +echo $OTA_CLIENT_VIN +echo $OTA_AUTH_URL +echo $OTA_SERVER_URL +echo $OTA_AUTH_CLIENT_ID +echo $OTA_AUTH_SECRET +export $PACKAGE_MANAGER + +OTA_TOML=$(cat $TEMPLATE_PATH | envsubst > /etc/ota.toml) +sed '/credentials_file/d' /etc/ota.toml +echo /etc/ota.toml + +RUST_LOG=debug ota_plus_client --config=/etc/ota.toml -- cgit v1.2.1 From 1d8b4930cdb86834f3f06e957a49019e363d2f4e Mon Sep 17 00:00:00 2001 From: Alex Humphreys Date: Fri, 13 May 2016 12:07:38 +0200 Subject: Rename id to requestId --- src/datatype/update_request.rs | 2 +- src/interpreter.rs | 2 +- src/ota_plus.rs | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/datatype/update_request.rs b/src/datatype/update_request.rs index 9fec62d..ce1b415 100644 --- a/src/datatype/update_request.rs +++ b/src/datatype/update_request.rs @@ -13,7 +13,7 @@ pub enum UpdateState { #[derive(Clone, PartialEq, Eq, Debug, RustcEncodable, RustcDecodable)] #[allow(non_snake_case)] pub struct PendingUpdateRequest { - pub id: UpdateRequestId, + pub requestId: UpdateRequestId, pub installPos: i32, pub packageId: Package, pub createdAt: String diff --git a/src/interpreter.rs b/src/interpreter.rs index 3c41f37..e0d7353 100644 --- a/src/interpreter.rs +++ b/src/interpreter.rs @@ -76,7 +76,7 @@ fn interpreter(env: &mut Env, cmd: Command, tx: Sender) -> Result<(), Err let update_events: Vec = updates .iter() - .map(|u| Event::NewUpdateAvailable(u.id.clone())) + .map(|u| Event::NewUpdateAvailable(u.requestId.clone())) .collect(); info!("New package updates available: {:?}", update_events); diff --git a/src/ota_plus.rs b/src/ota_plus.rs index 63c6d45..5308a2c 100644 --- a/src/ota_plus.rs +++ b/src/ota_plus.rs @@ -191,7 +191,7 @@ mod tests { #[test] fn test_get_package_updates() { let pending_update = PendingUpdateRequest { - id: "someid".to_string(), + requestId: "someid".to_string(), installPos: 0, packageId: Package { name: "fake-pkg".to_string(), @@ -206,7 +206,7 @@ mod tests { &mut TestHttpClient::from(vec![json_response.as_str()]), &test_token()).unwrap(); - let update_ids: Vec = updates.iter().map(|p| p.id.clone()).collect(); + let update_ids: Vec = updates.iter().map(|p| p.requestId.clone()).collect(); assert_eq!(update_ids, vec!["someid".to_string()]) } -- cgit v1.2.1 From 53dcff4d420e0464c9a0b6a701a4fe129c5d55f6 Mon Sep 17 00:00:00 2001 From: Stevan Andjelkovic Date: Fri, 13 May 2016 12:52:56 +0200 Subject: More readable timestamp. --- src/main.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main.rs b/src/main.rs index 0a35b74..0bdf956 100644 --- a/src/main.rs +++ b/src/main.rs @@ -141,7 +141,7 @@ fn setup_logging() { let service_version = env::var("SERVICE_VERSION") .unwrap_or("?".to_string()); - let timestamp = time::now().to_timespec().sec; + let timestamp = format!("{}", time::now().ctime()); format!("{} ({}), {}: {} - {}", service_name, service_version, timestamp, record.level(), record.args()) -- cgit v1.2.1 From ee0adc7196d29918f235aed72ca80912c6a15fb8 Mon Sep 17 00:00:00 2001 From: Shaun Taheri Date: Fri, 13 May 2016 12:53:45 +0200 Subject: Add http section to config --- ota.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/ota.toml b/ota.toml index 186d351..8367dbd 100644 --- a/ota.toml +++ b/ota.toml @@ -13,3 +13,4 @@ package_manager = "dpkg" [test] looping = false +http = false -- cgit v1.2.1 From c6a9613c946d540c1e8b339780a94374884a7ee1 Mon Sep 17 00:00:00 2001 From: Shaun Taheri Date: Fri, 13 May 2016 14:25:09 +0200 Subject: Handle binary HTTP responses --- src/http_client/hyper.rs | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/src/http_client/hyper.rs b/src/http_client/hyper.rs index 60e2691..e5f41b0 100644 --- a/src/http_client/hyper.rs +++ b/src/http_client/hyper.rs @@ -97,17 +97,10 @@ impl HttpClient for Hyper { req.to_string(), resp.status, delta); if resp.status.is_success() { - - let mut rbody = String::new(); - let _: usize = try!(resp.read_to_string(&mut rbody)); - - debug!("send_request_to, response: `{}`", rbody); - debug!("send_request_to, file: `{:?}`", file); - - try!(copy(&mut rbody.as_bytes(), file)); - + let mut data: Vec = Vec::new(); + let _: usize = try!(resp.read_to_end(&mut data)); + try!(copy(&mut data.as_slice(), file)); Ok(()) - } else if resp.status.is_redirection() { let req = try!(relocate_request(req, &resp)); self.send_request_to(&req, file) -- cgit v1.2.1 From 9a61117003771cfa0526716d485b0339c4fad4a5 Mon Sep 17 00:00:00 2001 From: Alex Humphreys Date: Fri, 13 May 2016 14:38:31 +0200 Subject: Fix env substitution in auth.json --- pkg/provision/auth.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pkg/provision/auth.json b/pkg/provision/auth.json index bb2e100..05c7620 100644 --- a/pkg/provision/auth.json +++ b/pkg/provision/auth.json @@ -1,11 +1,11 @@ { - "client_name": "STRESS12345678901", + "client_name": "$OTA_CLIENT_VIN", "grant_types": [ "client_credentials" ], "scope": [ - "ota-core.$VIN.write", - "ota-core.$VIN.read" + "ota-core.$OTA_CLIENT_VIN.write", + "ota-core.$OTA_CLIENT_VIN.read" ] } -- cgit v1.2.1 From ee3c3279b9ee6878feedd3cde4dc7e55ac8f14a1 Mon Sep 17 00:00:00 2001 From: Stevan Andjelkovic Date: Fri, 13 May 2016 15:37:01 +0200 Subject: Remove double json::encode. --- src/http_client/hyper.rs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/http_client/hyper.rs b/src/http_client/hyper.rs index e5f41b0..86ffa01 100644 --- a/src/http_client/hyper.rs +++ b/src/http_client/hyper.rs @@ -3,7 +3,6 @@ use hyper::client::RedirectPolicy; use hyper::client::response::Response; use hyper::header::{Authorization, Basic, Bearer, ContentType, Headers, Location}; use hyper::mime::{Attr, Mime, TopLevel, SubLevel, Value}; -use rustc_serialize::json; use std::fs::File; use std::io::{copy, Read}; use time; @@ -59,15 +58,13 @@ impl HttpClient for Hyper { token: token.access_token.clone() })); - if let Some(body) = body { + if let Some(json) = body { headers.set(ContentType(Mime( TopLevel::Application, SubLevel::Json, vec![(Attr::Charset, Value::Utf8)]))); - let json: String = try!(json::encode(&body)); - req_body.push_str(&json) } -- cgit v1.2.1 From 628a3be3820aa5eeb1bf133ea567dc81cd6fe85a Mon Sep 17 00:00:00 2001 From: Stevan Andjelkovic Date: Tue, 17 May 2016 17:11:32 +0200 Subject: Move latency logging from impl(mentation) to trait level. --- src/auth_plus.rs | 6 +++--- src/datatype/error.rs | 11 +++++++++- src/http_client/http_client.rs | 47 ++++++++++++++++++++++++++++++++++++++---- src/http_client/hyper.rs | 28 ++++++++++++------------- src/http_client/mod.rs | 2 +- src/http_client/test.rs | 9 +++++--- src/ota_plus.rs | 16 +++++++------- 7 files changed, 84 insertions(+), 35 deletions(-) diff --git a/src/auth_plus.rs b/src/auth_plus.rs index eb79d13..7561636 100644 --- a/src/auth_plus.rs +++ b/src/auth_plus.rs @@ -16,11 +16,11 @@ pub fn authenticate(config: &AuthConfig, client: &mut HttpClient) -> Result for Error { } } +impl From for Error { + fn from(e: string::FromUtf8Error) -> Error { + Error::FromUtf8Error(e) + } +} + impl From for Error { fn from(e: json::DecoderError) -> Error { Error::JsonDecode(e) @@ -105,6 +113,7 @@ impl Display for Error { Error::ClientError(ref s) => format!("Http client error: {}", s.clone()), Error::Command(ref e) => format!("Unknown Command: {}", e.clone()), Error::Config(ref e) => format!("Failed to {}", e.clone()), + Error::FromUtf8Error(ref e) => format!("From utf8 error: {}", e.clone()), Error::Hyper(ref e) => format!("Hyper error: {}", e.clone()), Error::Io(ref e) => format!("IO error: {}", e.clone()), Error::JsonDecode(ref e) => format!("Failed to decode JSON: {}", e.clone()), diff --git a/src/http_client/http_client.rs b/src/http_client/http_client.rs index eafaa19..a671275 100644 --- a/src/http_client/http_client.rs +++ b/src/http_client/http_client.rs @@ -1,8 +1,10 @@ +use rustc_serialize::json; use std::borrow::Cow; use std::fs::File; use std::io::SeekFrom; use std::io::prelude::*; use tempfile; +use time; use datatype::{AccessToken, ClientId, ClientSecret, Error, Method, Url}; @@ -79,28 +81,65 @@ impl<'a> ToString for HttpRequest<'a> { } } +#[derive(RustcEncodable, RustcDecodable)] +pub enum HttpStatus { + Ok, +} + +impl ToString for HttpStatus { + fn to_string(&self) -> String { + match *self { + HttpStatus::Ok => "200".to_string() + } + } +} + +#[derive(RustcEncodable, RustcDecodable)] +pub struct HttpResponse { + pub status: HttpStatus, + pub body: String, +} + pub trait HttpClient: Send + Sync { fn send_request_to(&mut self, req: &HttpRequest, file: &mut File) -> Result<(), Error> { - let s = try!(self.send_request(req)); + let t0 = time::precise_time_ns(); + let resp = try!(self.send_request(req)); + let t1 = time::precise_time_ns(); + + let latency = t1 - t0; + + info!("HttpClient::send_request_to, request: {}, response status: {}, latency: {} ns", + req.to_string(), resp.status.to_string(), latency); + + let json = try!(json::encode(&resp)); - Ok(try!(file.write_all(&s.as_bytes()))) + Ok(try!(file.write_all(&json.as_bytes()))) } - fn send_request(&mut self, req: &HttpRequest) -> Result { + fn send_request(&mut self, req: &HttpRequest) -> Result { let mut temp_file: File = try!(tempfile::tempfile()); + let t0 = time::precise_time_ns(); try!(self.send_request_to(req, &mut temp_file)); + let t1 = time::precise_time_ns(); + + let latency = t1 - t0; try!(temp_file.seek(SeekFrom::Start(0))); let mut buf = String::new(); let _: usize = try!(temp_file.read_to_string(&mut buf)); - Ok(buf) + let resp: HttpResponse = try!(json::decode(&buf)); + + info!("HttpClient::send_request, request: {}, response status: {}, latency: {} ns", + req.to_string(), resp.status.to_string(), latency); + + Ok(resp) } diff --git a/src/http_client/hyper.rs b/src/http_client/hyper.rs index 86ffa01..2068bc7 100644 --- a/src/http_client/hyper.rs +++ b/src/http_client/hyper.rs @@ -3,12 +3,12 @@ use hyper::client::RedirectPolicy; use hyper::client::response::Response; use hyper::header::{Authorization, Basic, Bearer, ContentType, Headers, Location}; use hyper::mime::{Attr, Mime, TopLevel, SubLevel, Value}; +use rustc_serialize::json; use std::fs::File; use std::io::{copy, Read}; -use time; use datatype::Error; -use http_client::{Auth, HttpClient, HttpRequest}; +use http_client::{Auth, HttpClient, HttpRequest, HttpResponse, HttpStatus}; pub struct Hyper { @@ -78,8 +78,6 @@ impl HttpClient for Hyper { debug!("send_request_to, headers: `{}`", headers); debug!("send_request_to, req_body: `{}`", req_body); - let t0 = time::precise_time_ns(); - let mut resp = try!(self.client .request(req.method.clone().into_owned().into(), req.url.clone().into_owned()) @@ -87,16 +85,16 @@ impl HttpClient for Hyper { .body(&req_body) .send()); - let t1 = time::precise_time_ns(); - let delta = t1 - t0; - - info!("Hyper::send_request_to, request: {}, response status: {}, latency: {} ns", - req.to_string(), resp.status, delta); - if resp.status.is_success() { let mut data: Vec = Vec::new(); - let _: usize = try!(resp.read_to_end(&mut data)); - try!(copy(&mut data.as_slice(), file)); + let _: usize = try!(resp.read_to_end(&mut data)); + let resp_body = try!(String::from_utf8(data)); + let resp = HttpResponse { + status: HttpStatus::Ok, + body: resp_body, + }; + let json = try!(json::encode(&resp)); + let _: u64 = try!(copy(&mut json.as_bytes(), file)); Ok(()) } else if resp.status.is_redirection() { let req = try!(relocate_request(req, &resp)); @@ -139,7 +137,7 @@ mod tests { use super::*; use datatype::Url; - use http_client::{Auth, HttpClient, HttpRequest}; + use http_client::{Auth, HttpClient, HttpRequest, HttpResponse}; #[test] @@ -150,9 +148,9 @@ mod tests { let req = HttpRequest::get::<_, Auth>( Url::parse("https://eu.httpbin.org/get").unwrap(), None); - let s: String = client.send_request(&req).unwrap(); + let resp: HttpResponse = client.send_request(&req).unwrap(); - assert!(s != "".to_string()) + assert!(resp.body != "".to_string()) } diff --git a/src/http_client/mod.rs b/src/http_client/mod.rs index 28faffe..c3a0450 100644 --- a/src/http_client/mod.rs +++ b/src/http_client/mod.rs @@ -1,4 +1,4 @@ -pub use self::http_client::{Auth, HttpClient, HttpRequest}; +pub use self::http_client::{Auth, HttpClient, HttpRequest, HttpResponse, HttpStatus}; pub use self::hyper::Hyper; pub use self::test::TestHttpClient; diff --git a/src/http_client/test.rs b/src/http_client/test.rs index cf90e8c..a005c5c 100644 --- a/src/http_client/test.rs +++ b/src/http_client/test.rs @@ -1,5 +1,5 @@ use datatype::Error; -use http_client::{HttpClient, HttpRequest}; +use http_client::{HttpClient, HttpRequest, HttpResponse, HttpStatus}; pub struct TestHttpClient<'a> { @@ -20,11 +20,14 @@ impl<'a> TestHttpClient<'a> { impl<'a> HttpClient for TestHttpClient<'a> { - fn send_request(&mut self, req: &HttpRequest) -> Result { + fn send_request(&mut self, req: &HttpRequest) -> Result { self.replies.pop() .ok_or(Error::ClientError(req.to_string())) - .map(|s| s.to_string()) + .map(|s| HttpResponse + { status: HttpStatus::Ok, + body: s.to_string(), + }) } diff --git a/src/ota_plus.rs b/src/ota_plus.rs index 3bba962..dbda98b 100644 --- a/src/ota_plus.rs +++ b/src/ota_plus.rs @@ -7,7 +7,7 @@ use datatype::{AccessToken, Config, Event, Error, Url, UpdateRequestId, UpdateReport, UpdateReportWithVin, Package, UpdateResultCode, UpdateState, PendingUpdateRequest}; -use http_client::{Auth, HttpClient, HttpRequest}; +use http_client::{Auth, HttpClient, HttpRequest, HttpResponse}; fn vehicle_updates_endpoint(config: &Config, path: &str) -> Url { @@ -37,7 +37,7 @@ pub fn download_package_update(config: &Config, try!(client.send_request_to(&req, &mut file)); - return Ok(path) + Ok(path) } @@ -55,9 +55,9 @@ pub fn send_install_report(config: &Config, Some(json) ); - let _: String = try!(client.send_request(&req)); + let _: HttpResponse = try!(client.send_request(&req)); - return Ok(()) + Ok(()) } @@ -72,7 +72,7 @@ pub fn get_package_updates(config: &Config, let resp = try!(client.send_request(&req)); - return Ok(try!(json::decode::>(&resp))); + Ok(try!(json::decode::>(&resp.body))) } // XXX: Remove in favour of update_installed_packages()? @@ -93,11 +93,11 @@ pub fn update_packages(config: &Config, Some(json), ); - let resp: String = try!(client.send_request(&req)); + let resp: HttpResponse = try!(client.send_request(&req)); - info!("update_packages, resp: {}", resp); + info!("update_packages, resp: {}", resp.body); - return Ok(()) + Ok(()) } pub fn update_installed_packages(config: &Config, -- cgit v1.2.1 From 0d2dd5609dc7a2f0b6fca53d14ec63e9c7b015ea Mon Sep 17 00:00:00 2001 From: Stevan Andjelkovic Date: Wed, 18 May 2016 11:18:31 +0200 Subject: Fix problem with binaries. Thanks @taheris. --- src/auth_plus.rs | 6 ++++-- src/http_client/http_client.rs | 2 +- src/http_client/hyper.rs | 31 ++++++++++++++++++++++++++++--- src/http_client/test.rs | 2 +- src/ota_plus.rs | 7 +++---- 5 files changed, 37 insertions(+), 11 deletions(-) diff --git a/src/auth_plus.rs b/src/auth_plus.rs index 7561636..c1fb705 100644 --- a/src/auth_plus.rs +++ b/src/auth_plus.rs @@ -18,9 +18,11 @@ pub fn authenticate(config: &AuthConfig, client: &mut HttpClient) -> Result, } pub trait HttpClient: Send + Sync { diff --git a/src/http_client/hyper.rs b/src/http_client/hyper.rs index 2068bc7..4754725 100644 --- a/src/http_client/hyper.rs +++ b/src/http_client/hyper.rs @@ -88,10 +88,9 @@ impl HttpClient for Hyper { if resp.status.is_success() { let mut data: Vec = Vec::new(); let _: usize = try!(resp.read_to_end(&mut data)); - let resp_body = try!(String::from_utf8(data)); let resp = HttpResponse { status: HttpStatus::Ok, - body: resp_body, + body: data, }; let json = try!(json::encode(&resp)); let _: u64 = try!(copy(&mut json.as_bytes(), file)); @@ -130,6 +129,7 @@ fn relocate_request<'a>(req: &'a HttpRequest, resp: &Response) -> Result( + Url::parse("https://eu.httpbin.org/bytes/16?seed=123").unwrap(), None); + + let mut temp_file: File = tempfile::tempfile().unwrap(); + client.send_request_to(&req, &mut temp_file).unwrap(); + + temp_file.seek(SeekFrom::Start(0)).unwrap(); + + let mut buf = String::new(); + let _: usize = temp_file.read_to_string(&mut buf).unwrap(); + + let resp: HttpResponse = json::decode(&buf).unwrap(); + + assert_eq!(resp.body, vec![13, 22, 104, 27, 230, 9, 137, 85, + 218, 40, 86, 85, 62, 0, 111, 22]) + + } + } diff --git a/src/http_client/test.rs b/src/http_client/test.rs index a005c5c..9356d9f 100644 --- a/src/http_client/test.rs +++ b/src/http_client/test.rs @@ -26,7 +26,7 @@ impl<'a> HttpClient for TestHttpClient<'a> { .ok_or(Error::ClientError(req.to_string())) .map(|s| HttpResponse { status: HttpStatus::Ok, - body: s.to_string(), + body: s.as_bytes().to_vec(), }) } diff --git a/src/ota_plus.rs b/src/ota_plus.rs index dbda98b..e17e446 100644 --- a/src/ota_plus.rs +++ b/src/ota_plus.rs @@ -71,8 +71,9 @@ pub fn get_package_updates(config: &Config, ); let resp = try!(client.send_request(&req)); + let body = try!(String::from_utf8(resp.body)); - Ok(try!(json::decode::>(&resp.body))) + Ok(try!(json::decode::>(&body))) } // XXX: Remove in favour of update_installed_packages()? @@ -93,9 +94,7 @@ pub fn update_packages(config: &Config, Some(json), ); - let resp: HttpResponse = try!(client.send_request(&req)); - - info!("update_packages, resp: {}", resp.body); + let _: HttpResponse = try!(client.send_request(&req)); Ok(()) } -- cgit v1.2.1 From 1329d5dc43c9957f60a36006da6c9ac80ba08c42 Mon Sep 17 00:00:00 2001 From: Stevan Andjelkovic Date: Wed, 18 May 2016 14:33:18 +0200 Subject: Remove type annotation and align. --- src/http_client/hyper.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/http_client/hyper.rs b/src/http_client/hyper.rs index 4754725..f0249d1 100644 --- a/src/http_client/hyper.rs +++ b/src/http_client/hyper.rs @@ -86,9 +86,9 @@ impl HttpClient for Hyper { .send()); if resp.status.is_success() { - let mut data: Vec = Vec::new(); - let _: usize = try!(resp.read_to_end(&mut data)); - let resp = HttpResponse { + let mut data = Vec::new(); + let _: usize = try!(resp.read_to_end(&mut data)); + let resp = HttpResponse { status: HttpStatus::Ok, body: data, }; -- cgit v1.2.1 From e6c627ab0a0bff539a0e8a0088d979f14d0ce681 Mon Sep 17 00:00:00 2001 From: Stevan Andjelkovic Date: Wed, 18 May 2016 14:55:47 +0200 Subject: Add macro for deriving From instances. --- src/datatype/config.rs | 2 +- src/datatype/error.rs | 126 +++++++++++++++++++++---------------------------- 2 files changed, 54 insertions(+), 74 deletions(-) diff --git a/src/datatype/config.rs b/src/datatype/config.rs index 80a6fb3..bdc3f37 100644 --- a/src/datatype/config.rs +++ b/src/datatype/config.rs @@ -181,7 +181,7 @@ pub fn load_config(path: &str) -> Result { match File::open(path) { Err(ref e) if e.kind() == ErrorKind::NotFound => Ok(Config::default()), - Err(e) => Err(Error::Io(e)), + Err(e) => Err(Error::IoError(e)), Ok(mut f) => { let mut s = String::new(); try!(f.read_to_string(&mut s)); diff --git a/src/datatype/error.rs b/src/datatype/error.rs index 03d2112..54956de 100644 --- a/src/datatype/error.rs +++ b/src/datatype/error.rs @@ -1,16 +1,16 @@ use std::convert::From; use std::fmt::{Display, Formatter, Result as FmtResult}; -use std::io; +use std::io::Error as IoError; use std::path::PathBuf; -use std::string; +use std::string::FromUtf8Error; use std::sync::PoisonError; use std::sync::mpsc::SendError; use url::ParseError as UrlParseError; use datatype::Event; -use rustc_serialize::json; -use hyper::error as hyper; -use ws; +use rustc_serialize::json::{EncoderError as JsonEncoderError, DecoderError as JsonDecoderError}; +use hyper::error::Error as HyperError; +use ws::Error as WebsocketError; #[derive(Debug)] @@ -19,55 +19,42 @@ pub enum Error { ClientError(String), Command(String), Config(ConfigReason), - FromUtf8Error(string::FromUtf8Error), - Hyper(hyper::Error), - Io(io::Error), - JsonDecode(json::DecoderError), - JsonEncode(json::EncoderError), + FromUtf8Error(FromUtf8Error), + HyperError(HyperError), + IoError(IoError), + JsonDecoderError(JsonDecoderError), + JsonEncoderError(JsonEncoderError), PoisonError(String), Ota(OtaReason), PackageError(String), ParseError(String), SendErrorEvent(SendError), UrlParseError(UrlParseError), - Websocket(ws::Error), -} - -impl From for Error { - fn from(e: json::EncoderError) -> Error { - Error::JsonEncode(e) - } -} - -impl From for Error { - fn from(e: hyper::Error) -> Error { - Error::Hyper(e) - } -} - -impl From for Error { - fn from(e: string::FromUtf8Error) -> Error { - Error::FromUtf8Error(e) - } -} - -impl From for Error { - fn from(e: json::DecoderError) -> Error { - Error::JsonDecode(e) - } -} - -impl From for Error { - fn from(e: io::Error) -> Error { - Error::Io(e) + WebsocketError(WebsocketError), +} + +macro_rules! derive_from { + ([ $( $error: ident ),* ]) => + { + $( + impl From<$error> for Error { + fn from(e: $error) -> Error { + Error::$error(e) + } + } + )* } } -impl From> for Error { - fn from(e: PoisonError) -> Error { - Error::PoisonError(format!("{}", e)) - } -} +derive_from!( + [ JsonEncoderError + , JsonDecoderError + , HyperError + , FromUtf8Error + , IoError + , UrlParseError + , WebsocketError + ]); impl From> for Error { fn from(e: SendError) -> Error { @@ -75,29 +62,22 @@ impl From> for Error { } } -impl From for Error { - fn from(e: UrlParseError) -> Error { - Error::UrlParseError(e) - } -} - -impl From for Error { - fn from(e: ws::Error) -> Error { - Error::Websocket(e) +impl From> for Error { + fn from(e: PoisonError) -> Error { + Error::PoisonError(format!("{}", e)) } } - #[derive(Debug)] pub enum OtaReason { - CreateFile(PathBuf, io::Error), + CreateFile(PathBuf, IoError), Client(String, String), } #[derive(Debug)] pub enum ConfigReason { Parse(ParseReason), - Io(io::Error), + Io(IoError), } #[derive(Debug)] @@ -109,22 +89,22 @@ pub enum ParseReason { impl Display for Error { fn fmt(&self, f: &mut Formatter) -> FmtResult { let inner: String = match *self { - Error::AuthError(ref s) => format!("Authentication error, {}", s.clone()), - Error::ClientError(ref s) => format!("Http client error: {}", s.clone()), - Error::Command(ref e) => format!("Unknown Command: {}", e.clone()), - Error::Config(ref e) => format!("Failed to {}", e.clone()), - Error::FromUtf8Error(ref e) => format!("From utf8 error: {}", e.clone()), - Error::Hyper(ref e) => format!("Hyper error: {}", e.clone()), - Error::Io(ref e) => format!("IO error: {}", e.clone()), - Error::JsonDecode(ref e) => format!("Failed to decode JSON: {}", e.clone()), - Error::JsonEncode(ref e) => format!("Failed to encode JSON: {}", e.clone()), - Error::Ota(ref e) => format!("Ota error, {}", e.clone()), - Error::PoisonError(ref e) => format!("Poison error, {}", e.clone()), - Error::PackageError(ref s) => s.clone(), - Error::ParseError(ref s) => s.clone(), - Error::SendErrorEvent(ref s) => format!("Send error for Event: {}", s.clone()), - Error::UrlParseError(ref s) => format!("Url parse error: {}", s.clone()), - Error::Websocket(ref e) => format!("Websocket Error{:?}", e.clone()), + Error::AuthError(ref s) => format!("Authentication error, {}", s.clone()), + Error::ClientError(ref s) => format!("Http client error: {}", s.clone()), + Error::Command(ref e) => format!("Unknown Command: {}", e.clone()), + Error::Config(ref e) => format!("Failed to {}", e.clone()), + Error::FromUtf8Error(ref e) => format!("From utf8 error: {}", e.clone()), + Error::HyperError(ref e) => format!("Hyper error: {}", e.clone()), + Error::IoError(ref e) => format!("IO error: {}", e.clone()), + Error::JsonDecoderError(ref e) => format!("Failed to decode JSON: {}", e.clone()), + Error::JsonEncoderError(ref e) => format!("Failed to encode JSON: {}", e.clone()), + Error::Ota(ref e) => format!("Ota error, {}", e.clone()), + Error::PoisonError(ref e) => format!("Poison error, {}", e.clone()), + Error::PackageError(ref s) => s.clone(), + Error::ParseError(ref s) => s.clone(), + Error::SendErrorEvent(ref s) => format!("Send error for Event: {}", s.clone()), + Error::UrlParseError(ref s) => format!("Url parse error: {}", s.clone()), + Error::WebsocketError(ref e) => format!("Websocket Error{:?}", e.clone()), }; write!(f, "{}", inner) } -- cgit v1.2.1 From 86b8502771b7a683f8a6ca9a2f25e42cf60601be Mon Sep 17 00:00:00 2001 From: Stevan Andjelkovic Date: Wed, 18 May 2016 15:30:41 +0200 Subject: Remove unused errors. --- src/datatype/error.rs | 23 ----------------------- src/package_manager/dpkg.rs | 4 +--- 2 files changed, 1 insertion(+), 26 deletions(-) diff --git a/src/datatype/error.rs b/src/datatype/error.rs index 54956de..0f941f0 100644 --- a/src/datatype/error.rs +++ b/src/datatype/error.rs @@ -1,7 +1,6 @@ use std::convert::From; use std::fmt::{Display, Formatter, Result as FmtResult}; use std::io::Error as IoError; -use std::path::PathBuf; use std::string::FromUtf8Error; use std::sync::PoisonError; use std::sync::mpsc::SendError; @@ -15,7 +14,6 @@ use ws::Error as WebsocketError; #[derive(Debug)] pub enum Error { - AuthError(String), ClientError(String), Command(String), Config(ConfigReason), @@ -25,7 +23,6 @@ pub enum Error { JsonDecoderError(JsonDecoderError), JsonEncoderError(JsonEncoderError), PoisonError(String), - Ota(OtaReason), PackageError(String), ParseError(String), SendErrorEvent(SendError), @@ -68,12 +65,6 @@ impl From> for Error { } } -#[derive(Debug)] -pub enum OtaReason { - CreateFile(PathBuf, IoError), - Client(String, String), -} - #[derive(Debug)] pub enum ConfigReason { Parse(ParseReason), @@ -89,7 +80,6 @@ pub enum ParseReason { impl Display for Error { fn fmt(&self, f: &mut Formatter) -> FmtResult { let inner: String = match *self { - Error::AuthError(ref s) => format!("Authentication error, {}", s.clone()), Error::ClientError(ref s) => format!("Http client error: {}", s.clone()), Error::Command(ref e) => format!("Unknown Command: {}", e.clone()), Error::Config(ref e) => format!("Failed to {}", e.clone()), @@ -98,7 +88,6 @@ impl Display for Error { Error::IoError(ref e) => format!("IO error: {}", e.clone()), Error::JsonDecoderError(ref e) => format!("Failed to decode JSON: {}", e.clone()), Error::JsonEncoderError(ref e) => format!("Failed to encode JSON: {}", e.clone()), - Error::Ota(ref e) => format!("Ota error, {}", e.clone()), Error::PoisonError(ref e) => format!("Poison error, {}", e.clone()), Error::PackageError(ref s) => s.clone(), Error::ParseError(ref s) => s.clone(), @@ -110,18 +99,6 @@ impl Display for Error { } } -impl Display for OtaReason { - fn fmt(&self, f: &mut Formatter) -> FmtResult { - let inner: String = match *self { - OtaReason::CreateFile(ref f, ref e) => - format!("failed to create file {:?}: {}", f.clone(), e.clone()), - OtaReason::Client(ref r, ref e) => - format!("the request: {},\nresults in the following error: {}", r.clone(), e.clone()), - }; - write!(f, "{}", inner) - } -} - impl Display for ConfigReason { fn fmt(&self, f: &mut Formatter) -> FmtResult { let inner: String = match *self { diff --git a/src/package_manager/dpkg.rs b/src/package_manager/dpkg.rs index fc2aaef..99cff94 100644 --- a/src/package_manager/dpkg.rs +++ b/src/package_manager/dpkg.rs @@ -1,8 +1,6 @@ use std::process::Command; -use datatype::Error; -use datatype::Package; -use datatype::UpdateResultCode; +use datatype::{Error, Package, UpdateResultCode}; pub fn installed_packages() -> Result, Error> { -- cgit v1.2.1 From 03aa393e7f9e32c242fc6016c5e8f2102fec42ff Mon Sep 17 00:00:00 2001 From: Stevan Andjelkovic Date: Wed, 18 May 2016 16:55:56 +0200 Subject: Use Toml errors rather than custom ones. --- src/datatype/config.rs | 39 +++++++++++++++++++++----------------- src/datatype/error.rs | 43 ++++++++++-------------------------------- tests/ota_plus_client_tests.rs | 4 ++-- 3 files changed, 34 insertions(+), 52 deletions(-) diff --git a/src/datatype/config.rs b/src/datatype/config.rs index bdc3f37..bdcba4e 100644 --- a/src/datatype/config.rs +++ b/src/datatype/config.rs @@ -7,8 +7,6 @@ use std::path::Path; use toml; use datatype::{Error, Url}; -use datatype::error::ConfigReason::{Parse, Io}; -use datatype::error::ParseReason::{InvalidToml, InvalidSection}; use package_manager::PackageManager; @@ -29,10 +27,12 @@ pub struct AuthConfig { impl AuthConfig { fn new(server: Url, creds: CredentialsFile) -> AuthConfig { - AuthConfig { server: server, - client_id: creds.client_id, - secret: creds.secret, - vin: creds.vin } + AuthConfig { + server: server, + client_id: creds.client_id, + secret: creds.secret, + vin: creds.vin + } } } @@ -108,16 +108,21 @@ impl Default for TestConfig { } fn parse_toml(s: &str) -> Result { - let table: toml::Table = try!(toml::Parser::new(&s) - .parse() - .ok_or(Error::Config(Parse(InvalidToml)))); - Ok(table) + let mut parser = toml::Parser::new(&s); + Ok(try!(parser.parse() + .ok_or_else(move || parser.errors))) } fn parse_toml_table(tbl: &toml::Table, sect: &str) -> Result { - tbl.get(sect) - .and_then(|c| toml::decode::(c.clone()) ) - .ok_or(Error::Config(Parse(InvalidSection(sect.to_string())))) + + let value = try!(tbl.get(sect) + .ok_or(Error::ParseError(format!( + "parse_toml_table, invalid section: {}", sect.to_string())))); + + let mut decoder = toml::Decoder::new(value.clone()); + + Ok(try!(T::decode(&mut decoder))) + } fn bootstrap_credentials(auth_cfg_section: AuthConfigSection) -> Result { @@ -125,7 +130,8 @@ fn bootstrap_credentials(auth_cfg_section: AuthConfigSection) -> Result Result<(), Error> { let mut tbl = toml::Table::new(); tbl.insert("auth".to_string(), toml::encode(&creds)); - let dir = try!(path.parent().ok_or(Error::Config(Parse(InvalidSection("Invalid credentials file path".to_string()))))); + let dir = try!(path.parent() + .ok_or(Error::ParseError("Invalid credentials file path".to_string()))); try!(fs::create_dir_all(&dir)); let mut f = try!(File::create(path)); try!(f.write_all(&toml::encode_str(&tbl).into_bytes())); @@ -134,8 +140,7 @@ fn bootstrap_credentials(auth_cfg_section: AuthConfigSection) -> Result Result { let mut s = String::new(); - try!(f.read_to_string(&mut s) - .map_err(|err| Error::Config(Io(err)))); + try!(f.read_to_string(&mut s)); let toml_table = try!(parse_toml(&s)); let creds: CredentialsFile = try!(parse_toml_table(&toml_table, "auth")); Ok(creds) @@ -153,7 +158,7 @@ fn bootstrap_credentials(auth_cfg_section: AuthConfigSection) -> Result Err(Error::Config(Io(e))), + Err(e) => Err(Error::IoError(e)), Ok(f) => { let creds = try!(read_credentials_file(f)); Ok(AuthConfig::new(auth_cfg_section.server, creds)) diff --git a/src/datatype/error.rs b/src/datatype/error.rs index 0f941f0..503aaba 100644 --- a/src/datatype/error.rs +++ b/src/datatype/error.rs @@ -4,6 +4,7 @@ use std::io::Error as IoError; use std::string::FromUtf8Error; use std::sync::PoisonError; use std::sync::mpsc::SendError; +use toml::{ParserError as TomlParserError, DecodeError as TomlDecodeError}; use url::ParseError as UrlParseError; use datatype::Event; @@ -16,7 +17,6 @@ use ws::Error as WebsocketError; pub enum Error { ClientError(String), Command(String), - Config(ConfigReason), FromUtf8Error(FromUtf8Error), HyperError(HyperError), IoError(IoError), @@ -26,6 +26,8 @@ pub enum Error { PackageError(String), ParseError(String), SendErrorEvent(SendError), + TomlParserErrors(Vec), + TomlDecodeError(TomlDecodeError), UrlParseError(UrlParseError), WebsocketError(WebsocketError), } @@ -50,6 +52,7 @@ derive_from!( , FromUtf8Error , IoError , UrlParseError + , TomlDecodeError , WebsocketError ]); @@ -65,16 +68,10 @@ impl From> for Error { } } -#[derive(Debug)] -pub enum ConfigReason { - Parse(ParseReason), - Io(IoError), -} - -#[derive(Debug)] -pub enum ParseReason { - InvalidToml, - InvalidSection(String), +impl From> for Error { + fn from(e: Vec) -> Error { + Error::TomlParserErrors(e) + } } impl Display for Error { @@ -82,7 +79,6 @@ impl Display for Error { let inner: String = match *self { Error::ClientError(ref s) => format!("Http client error: {}", s.clone()), Error::Command(ref e) => format!("Unknown Command: {}", e.clone()), - Error::Config(ref e) => format!("Failed to {}", e.clone()), Error::FromUtf8Error(ref e) => format!("From utf8 error: {}", e.clone()), Error::HyperError(ref e) => format!("Hyper error: {}", e.clone()), Error::IoError(ref e) => format!("IO error: {}", e.clone()), @@ -92,6 +88,8 @@ impl Display for Error { Error::PackageError(ref s) => s.clone(), Error::ParseError(ref s) => s.clone(), Error::SendErrorEvent(ref s) => format!("Send error for Event: {}", s.clone()), + Error::TomlDecodeError(ref e) => format!("Toml decode error: {}", e.clone()), + Error::TomlParserErrors(ref e) => format!("Toml parser errors: {:?}", e.clone()), Error::UrlParseError(ref s) => format!("Url parse error: {}", s.clone()), Error::WebsocketError(ref e) => format!("Websocket Error{:?}", e.clone()), }; @@ -99,27 +97,6 @@ impl Display for Error { } } -impl Display for ConfigReason { - fn fmt(&self, f: &mut Formatter) -> FmtResult { - let inner: String = match *self { - ConfigReason::Parse(ref e) => format!("parse config: {}", e.clone()), - ConfigReason::Io (ref e) => format!("load config: {}", e.clone()) - }; - write!(f, "{}", inner) - } -} - - -impl Display for ParseReason { - fn fmt(&self, f: &mut Formatter) -> FmtResult { - let inner: String = match *self { - ParseReason::InvalidToml => "invalid toml".to_string(), - ParseReason::InvalidSection(ref s) => format!("invalid section: {}", s), - }; - write!(f, "{}", inner) - } -} - #[macro_export] macro_rules! exit { ($fmt:expr) => ({ diff --git a/tests/ota_plus_client_tests.rs b/tests/ota_plus_client_tests.rs index 66740bf..0192825 100644 --- a/tests/ota_plus_client_tests.rs +++ b/tests/ota_plus_client_tests.rs @@ -83,13 +83,13 @@ fn no_auth_server_to_connect_to() { #[test] fn bad_section() { assert_eq!(client_with_config(&[""], "[uth]"), - "Failed to parse config: invalid section: auth\n") + "parse_toml_table, invalid section: auth\n") } #[test] fn bad_toml() { assert_eq!(client_with_config(&[""], "auth]"), - "Failed to parse config: invalid toml\n") + "Toml parser errors: [ParserError { lo: 4, hi: 5, desc: \"expected `=`, but found `]`\" }]\n") } #[test] -- cgit v1.2.1 From 4de175f67f48aff3ca4678247af6dfa54cb12969 Mon Sep 17 00:00:00 2001 From: Stevan Andjelkovic Date: Thu, 19 May 2016 14:46:33 +0200 Subject: Rearrange the order in which the derive_from macro appears for better readability. --- src/datatype/error.rs | 37 ++++++++++++++++++++----------------- 1 file changed, 20 insertions(+), 17 deletions(-) diff --git a/src/datatype/error.rs b/src/datatype/error.rs index 503aaba..d6bc969 100644 --- a/src/datatype/error.rs +++ b/src/datatype/error.rs @@ -32,6 +32,26 @@ pub enum Error { WebsocketError(WebsocketError), } +impl From> for Error { + fn from(e: SendError) -> Error { + Error::SendErrorEvent(e) + } +} + +impl From> for Error { + fn from(e: PoisonError) -> Error { + Error::PoisonError(format!("{}", e)) + } +} + +impl From> for Error { + fn from(e: Vec) -> Error { + Error::TomlParserErrors(e) + } +} + +// To derive From implementations for the other errors we use the +// following macro. macro_rules! derive_from { ([ $( $error: ident ),* ]) => { @@ -56,23 +76,6 @@ derive_from!( , WebsocketError ]); -impl From> for Error { - fn from(e: SendError) -> Error { - Error::SendErrorEvent(e) - } -} - -impl From> for Error { - fn from(e: PoisonError) -> Error { - Error::PoisonError(format!("{}", e)) - } -} - -impl From> for Error { - fn from(e: Vec) -> Error { - Error::TomlParserErrors(e) - } -} impl Display for Error { fn fmt(&self, f: &mut Formatter) -> FmtResult { -- cgit v1.2.1 From c7320de59fa43896b5df75d458a39a5b049bcacf Mon Sep 17 00:00:00 2001 From: Calum McCall Date: Thu, 19 May 2016 17:42:55 +0200 Subject: Add auto-gen'd file to gitignore --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 45c9e69..e140a79 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ target pkg/ota_plus_client +test_install_package_update_2 -- cgit v1.2.1 From 044d86d7e0541d1217a3cd6e2cab99336419d476 Mon Sep 17 00:00:00 2001 From: Shaun Taheri Date: Wed, 4 May 2016 16:32:17 +0200 Subject: Refactor gateway interface to multi-threaded form --- src/datatype/event.rs | 3 +- src/interaction_library/console.rs | 60 ++++++---- src/interaction_library/gateway.rs | 47 ++++---- src/interaction_library/interpreter.rs | 17 --- src/interaction_library/mod.rs | 25 +--- src/interaction_library/websocket.rs | 118 +++++++++++-------- src/interpreter.rs | 201 ++++++++++++++++++--------------- src/lib.rs | 1 + src/main.rs | 70 ++++++++---- 9 files changed, 293 insertions(+), 249 deletions(-) delete mode 100644 src/interaction_library/interpreter.rs diff --git a/src/datatype/event.rs b/src/datatype/event.rs index efa21e0..73fc13f 100644 --- a/src/datatype/event.rs +++ b/src/datatype/event.rs @@ -3,8 +3,9 @@ use std::string::ToString; use datatype::{UpdateRequestId, UpdateState, Package}; -#[derive(RustcEncodable, Debug, Clone, PartialEq, Eq)] +#[derive(RustcEncodable, RustcDecodable, Debug, Clone, PartialEq, Eq)] pub enum Event { + Ok, NotAuthenticated, NewUpdateAvailable(UpdateRequestId), UpdateStateChanged(UpdateRequestId, UpdateState), diff --git a/src/interaction_library/console.rs b/src/interaction_library/console.rs index a56b977..0fba46d 100644 --- a/src/interaction_library/console.rs +++ b/src/interaction_library/console.rs @@ -1,37 +1,59 @@ -use std::io; +use std::{io, thread}; +use std::fmt::Debug; use std::str::FromStr; use std::string::ToString; +use std::sync::mpsc; +use std::sync::mpsc::{Sender, Receiver}; -use super::gateway::Gateway; +use super::gateway::{Gateway, Interpret}; pub struct Console; impl Gateway for Console - where - C: FromStr + Send + 'static, E: ToString + Send + 'static { - + where C: FromStr + Send + 'static, + E: ToString + Send + 'static, + ::Err: Debug, +{ fn new() -> Console { Console } - fn get_line(&self) -> String { - print!("> "); - let mut input = String::new(); - let _ = io::stdin().read_line(&mut input); - input - } - - fn put_line(&self, s: String) { - println!("{}", s); + fn next(&self) -> Option> { + match parse_input(&get_input()) { + Ok(cmd) => { + let (etx, erx): (Sender, Receiver) = mpsc::channel(); + thread::spawn(move || { + match erx.recv() { + Ok(event) => println!("{}", event.to_string()), + Err(err) => error!("Error receiving event: {:?}", err), + } + }); + Some(Interpret{ + cmd: cmd, + etx: etx, + }) + } + + Err(err) => { + println!("{:?}", err); + None + } + } } - fn parse(s: String) -> Option { - s.parse().ok() + fn pulse(&self, e: E) { + println!("(global): {}", e.to_string()); } +} - fn pretty_print(e: E) -> String { - e.to_string() - } +fn get_input() -> String { + print!("> "); + let mut input = String::new(); + let _ = io::stdin().read_line(&mut input); + input +} +fn parse_input(s: &str) -> Result::Err> { + s.parse() } diff --git a/src/interaction_library/gateway.rs b/src/interaction_library/gateway.rs index bbd9ebd..001659d 100644 --- a/src/interaction_library/gateway.rs +++ b/src/interaction_library/gateway.rs @@ -1,44 +1,45 @@ -use std::sync::mpsc::{Sender, Receiver}; -use std::sync::Arc; use std::thread; +use std::sync::Arc; +use std::sync::mpsc::{Sender, Receiver}; -pub trait Gateway: Sized + Send + Sync + 'static - where - C: Send + 'static, E: Send + 'static { +pub struct Interpret { + pub cmd: C, + pub etx: Sender, +} +pub trait Gateway: Sized + Send + Sync + 'static + where C: Send + 'static, + E: Send + 'static +{ fn new() -> Self; - fn get_line(&self) -> String; - fn put_line(&self, s: String); + fn next(&self) -> Option>; - fn parse(s: String) -> Option; - fn pretty_print(e: E) -> String; - - fn run(tx: Sender, rx: Receiver) { - let io = Arc::new(Self::new()); - // Read lines. - let io_clone = io.clone(); + fn run(tx: Sender>, rx: Receiver) { + let gateway = Arc::new(Self::new()); + let global = gateway.clone(); thread::spawn(move || { loop { - let _ = Self::parse(io_clone.get_line()) - .ok_or_else(|| error!("Error parsing command")) - .and_then(|cmd| { - tx.send(cmd).map_err(|e| error!("Error forwarding command: {:?}", e)) - }); + gateway.next() + .map(|i| { + tx.send(i) + .map_err(|err| error!("Error sending command: {:?}", err)) + }); } }); - // Put lines. thread::spawn(move || { loop { match rx.recv() { - Ok(e) => io.put_line(Self::pretty_print(e)), - Err(err) => error!("Error receiving event: {:?}", err) + Ok(e) => global.pulse(e), + Err(err) => error!("Error receiving event: {:?}", err), } } }); - } + // ignore global events by default + #[allow(unused_variables)] + fn pulse(&self, e: E) {} } diff --git a/src/interaction_library/interpreter.rs b/src/interaction_library/interpreter.rs deleted file mode 100644 index bad1b6f..0000000 --- a/src/interaction_library/interpreter.rs +++ /dev/null @@ -1,17 +0,0 @@ -use std::sync::mpsc::{Sender, Receiver}; - - -pub trait Interpreter { - - fn interpret(env: &mut Env, c: C, tx: Sender); - - fn run(env: &mut Env, rx: Receiver, tx: Sender) { - loop { - match rx.recv() { - Ok(c) => Self::interpret(env, c, tx.clone()), - Err(e) => error!("Error receiving command: {:?}", e) - } - } - } - -} diff --git a/src/interaction_library/mod.rs b/src/interaction_library/mod.rs index f2ac99a..e28ce4b 100644 --- a/src/interaction_library/mod.rs +++ b/src/interaction_library/mod.rs @@ -1,31 +1,8 @@ pub use self::console::Console; pub use self::gateway::Gateway; -pub use self::interpreter::Interpreter; +pub use self::websocket::Websocket; pub mod broadcast; pub mod console; pub mod gateway; -pub mod interpreter; pub mod websocket; - - -#[macro_export] -macro_rules! interact { - ( $( $g: ident ), * ) => { - { - let (cmd_tx, cmd_rx) = std::sync::mpsc::channel(); - let (ev_tx, ev_rx) = std::sync::mpsc::channel(); - - let mut broadcast = broadcast::Broadcast::new(ev_rx); - - $( - $g::run(cmd_tx.clone(), broadcast.subscribe()); - )* - - std::thread::spawn(move || broadcast.start()); - - run_interpreter(cmd_rx, ev_tx); - } - - }; -} diff --git a/src/interaction_library/websocket.rs b/src/interaction_library/websocket.rs index acc1654..9143060 100644 --- a/src/interaction_library/websocket.rs +++ b/src/interaction_library/websocket.rs @@ -1,4 +1,5 @@ use rustc_serialize::{json, Decodable, Encodable}; +use std::env; use std::collections::HashMap; use std::sync::mpsc::{Sender, Receiver}; use std::sync::mpsc; @@ -8,23 +9,23 @@ use ws::util::Token; use ws::{listen, Sender as WsSender, Handler, Message, Handshake, CloseCode}; use ws; -use super::gateway::Gateway; +use super::gateway::{Gateway, Interpret}; +use datatype::Error; type Clients = Arc>>; pub struct WebsocketHandler { - out: WsSender, - sender: Sender, - clients: Clients + out: WsSender, + sender: Sender, + clients: Clients, } impl Handler for WebsocketHandler { - fn on_message(&mut self, msg: Message) -> ws::Result<()> { Ok(match self.sender.send(format!("{}", msg)) { - Ok(_) => {}, - Err(e) => error!("Error forwarding message from WS: {}", e) + Ok(_) => {} + Err(e) => error!("Error forwarding message from WS: {}", e), }) } @@ -41,49 +42,20 @@ impl Handler for WebsocketHandler { } } + +#[derive(Clone)] pub struct Websocket { - clients: Clients, - receiver: Mutex>, + clients: Clients, + receiver: Arc>>, } -impl Gateway for Websocket - where - C: Decodable + Send + 'static, - E: Encodable + Send + 'static { - - fn new() -> Websocket { - - fn spawn(tx: Sender, clients: Clients) { - - thread::spawn(move || { - listen("127.0.0.1:3012", |out| { - WebsocketHandler { - out: out, - sender: tx.clone(), - clients: clients.clone(), - } - }) - }); - - } - - let (tx, rx) = mpsc::channel(); - let clients = Arc::new(Mutex::new(HashMap::new())); - - spawn(tx, clients.clone()); - - Websocket { - clients: clients.clone(), - receiver: Mutex::new(rx), - } - } - +impl Websocket { fn get_line(&self) -> String { let rx = self.receiver.lock().expect("Poisoned rx lock -- can't continue"); match rx.recv() { Ok(line) => line, - Err(e) => { - error!("Couldn't fetch from WS receiver: {:?}", e); + Err(err) => { + error!("Couldn't fetch from WS receiver: {:?}", err); "".to_string() } } @@ -95,13 +67,65 @@ impl Gateway for Websocket let _ = out.send(Message::Text(s.clone())); } } +} + +impl Gateway for Websocket + where C: Decodable + Send + 'static, + E: Encodable + Send + 'static +{ + fn new() -> Websocket { + let (tx, rx) = mpsc::channel(); + let clients = Arc::new(Mutex::new(HashMap::new())); + let clients_clone = clients.clone(); + + let addr = env::var("OTA_PLUS_CLIENT_WEBSOCKET_ADDR") + .unwrap_or("127.0.0.1:3012".to_string()); + + thread::spawn(move || { + listen(&addr as &str, |out| { + WebsocketHandler { + out: out, + sender: tx.clone(), + clients: clients_clone.clone(), + } + }) + }); - fn parse(s: String) -> Option { - json::decode(&s).ok() + Websocket { + clients: clients.clone(), + receiver: Arc::new(Mutex::new(rx)), + } } - fn pretty_print(e: E) -> String { - json::encode(&e).expect("Error encoding event into JSON") + fn next(&self) -> Option> { + match decode(&self.get_line()) { + Ok(cmd) => { + let (etx, erx): (Sender, Receiver) = mpsc::channel(); + let clone = self.clone(); + thread::spawn(move || { + match erx.recv() { + Ok(e) => clone.put_line(encode(e)), + Err(err) => error!("Error receiving event: {:?}", err), + } + }); + Some(Interpret { + cmd: cmd, + etx: etx, + }) + } + + Err(err) => { + error!("Error decoding JSON: {}", err); + None + } + } } +} + +fn encode(e: E) -> String { + json::encode(&e).expect("Error encoding event into JSON") +} +fn decode(s: &str) -> Result { + json::decode::(s).map_err(|err| Error::from(err)) } diff --git a/src/interpreter.rs b/src/interpreter.rs index e0d7353..c159552 100644 --- a/src/interpreter.rs +++ b/src/interpreter.rs @@ -1,34 +1,83 @@ use std::borrow::Cow; use std::process::exit; use std::sync::{Arc, Mutex}; -use std::sync::mpsc::Sender; +use std::sync::mpsc::{Sender, Receiver, channel}; use auth_plus::authenticate; use datatype::{AccessToken, Command, Config, Error, Event, UpdateState}; use datatype::Command::*; use http_client::HttpClient; -use interaction_library::interpreter::Interpreter; -use ota_plus::{get_package_updates, install_package_update, - update_installed_packages, send_install_report}; +use interaction_library::gateway::Interpret; +use ota_plus::{get_package_updates, install_package_update, update_installed_packages, + send_install_report}; #[derive(Clone)] pub struct Env<'a> { - pub config: Config, + pub config: Config, pub access_token: Option>, - pub http_client: Arc>, + pub http_client: Arc>, } -pub struct OurInterpreter; -impl<'a> Interpreter, Command, Event> for OurInterpreter { - fn interpret(env: &mut Env, cmd: Command, tx: Sender) { - info!("Interpreting: {:?}", cmd); - interpreter(env, cmd, tx.clone()) - .unwrap_or_else(|err| { - tx.send(Event::Error(format!("{}", err))) - .unwrap_or_else(|_| error!("interpret: send failed")) +pub trait Interpreter { + fn interpret(env: &mut Env, msg: I, otx: Sender); + + fn run(env: &mut Env, irx: Receiver, otx: Sender) { + loop { + match irx.recv() { + Ok(msg) => Self::interpret(env, msg, otx.clone()), + Err(err) => error!("Error receiving command: {:?}", err), + } + } + } +} + + +pub struct AutoAcceptor; + +impl Interpreter<(), Event, Command> for AutoAcceptor { + fn interpret(_: &mut (), event: Event, ctx: Sender) { + info!("Automatic interpreter: {:?}", event); + match event { + Event::Batch(ref evs) => { + for ev in evs { + accept(&ev, ctx.clone()) + } + } + ev => accept(&ev, ctx), + } + + fn accept(event: &Event, ctx: Sender) { + if let &Event::NewUpdateAvailable(ref id) = event { + let _ = ctx.send(Command::AcceptUpdate(id.clone())); + } + } + } +} + + +pub struct GlobalInterpreter; + +impl<'a> Interpreter, Interpret, Event> for GlobalInterpreter { + fn interpret(env: &mut Env, i: Interpret, etx: Sender) { + info!("Interpreting: {:?}", i.cmd); + let (multi_tx, multi_rx): (Sender, Receiver) = channel(); + let local_tx = i.etx.clone(); + + // HACK: unwrap failed sends to avoid thread deadlocking + let _ = command_interpreter(env, i.cmd, multi_tx) + .map(|_| { + for ev in multi_rx { + let _ = etx.send(ev.clone()).unwrap(); + let _ = local_tx.send(ev.clone()).unwrap(); + }; }) + .map_err(|err| { + let ev = Event::Error(format!("{}", err)); + let _ = etx.send(ev.clone()).unwrap(); + let _ = local_tx.send(ev.clone()).unwrap(); + }); } } @@ -45,104 +94,68 @@ macro_rules! partial_apply { } } -fn interpreter(env: &mut Env, cmd: Command, tx: Sender) -> Result<(), Error> { - - if let Some(token) = env.access_token.to_owned() { - - let client_clone = env.http_client.clone(); - - partial_apply!( - [get_package_updates, update_installed_packages], - [send_install_report], - [install_package_update], - &env.config, client_clone, &token - ); - - match cmd { - - Authenticate(_) => (), // Already authenticated. - - AcceptUpdate(ref id) => { - try!(tx.send(Event::UpdateStateChanged(id.clone(), UpdateState::Downloading))); - let report = try!(install_package_update(id.to_owned(), tx.to_owned())); - try!(send_install_report(report.clone())); - info!("Update finished. Report sent: {:?}", report) - } - - GetPendingUpdates => { - let mut updates = try!(get_package_updates()); - - updates.sort_by_key(|e| e.installPos); - - let update_events: Vec = updates - .iter() - .map(|u| Event::NewUpdateAvailable(u.requestId.clone())) - .collect(); - - info!("New package updates available: {:?}", update_events); - try!(tx.send(Event::Batch(update_events))) - } - - ListInstalledPackages => { - let pkgs = try!(env.config.ota.package_manager.installed_packages()); - try!(tx.send(Event::FoundInstalledPackages(pkgs.clone()))) - } - - UpdateInstalledPackages => { - try!(update_installed_packages()); - info!("Posted installed packages to the server.") - } - - Shutdown => exit(0) - - } - - } else { - +fn command_interpreter(env: &mut Env, cmd: Command, etx: Sender) -> Result<(), Error> { + if env.access_token.is_none() { match cmd { - - Authenticate(_) => { - // XXX: partially apply? + Authenticate(_) => { let client_clone = env.http_client.clone(); let mut client = client_clone.lock().unwrap(); let token = try!(authenticate(&env.config.auth, &mut *client)); env.access_token = Some(token.into()); + try!(etx.send(Event::Ok)); } - Shutdown => exit(0), - AcceptUpdate(_) | GetPendingUpdates | ListInstalledPackages | - UpdateInstalledPackages => - tx.send(Event::NotAuthenticated) - .unwrap_or_else(|_| error!("interpreter: send failed.")) - } + UpdateInstalledPackages => try!(etx.send(Event::NotAuthenticated)), + Shutdown => exit(0), + } + return Ok(()) } - Ok(()) + let token = env.access_token.to_owned().unwrap(); + let client_clone = env.http_client.clone(); -} + partial_apply!([get_package_updates, update_installed_packages], + [send_install_report], + [install_package_update], + &env.config, client_clone, &token); -pub struct AutoAcceptor; + match cmd { + Authenticate(_) => (), // Already authenticated. -impl Interpreter<(), Event, Command> for AutoAcceptor { - fn interpret(_: &mut (), e: Event, ctx: Sender) { - fn f(e: &Event, ctx: Sender) { - if let &Event::NewUpdateAvailable(ref id) = e { - let _ = ctx.send(Command::AcceptUpdate(id.clone())); - } + AcceptUpdate(ref id) => { + try!(etx.send(Event::UpdateStateChanged(id.clone(), UpdateState::Downloading))); + let report = try!(install_package_update(id.to_owned(), etx.to_owned())); + try!(send_install_report(report.clone())); + info!("Update finished. Report sent: {:?}", report) } - info!("Event interpreter: {:?}", e); - match e { - Event::Batch(ref evs) => { - for ev in evs { - f(&ev, ctx.clone()) - } - } - e => f(&e, ctx) + GetPendingUpdates => { + let mut updates = try!(get_package_updates()); + updates.sort_by_key(|e| e.installPos); + let evs: Vec = updates.iter() + .map(|up| Event::NewUpdateAvailable(up.requestId.clone())) + .collect(); + info!("New package updates available: {:?}", evs); + try!(etx.send(Event::Batch(evs))) + } + + ListInstalledPackages => { + let pkgs = try!(env.config.ota.package_manager.installed_packages()); + try!(etx.send(Event::FoundInstalledPackages(pkgs.clone()))) + } + + UpdateInstalledPackages => { + try!(update_installed_packages()); + try!(etx.send(Event::Ok)); + info!("Posted installed packages to the server.") } + + Shutdown => exit(0), } + + Ok(()) } diff --git a/src/lib.rs b/src/lib.rs index deae60c..f7c13ee 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,3 +1,4 @@ +extern crate crossbeam; extern crate hyper; #[macro_use] extern crate nom; #[macro_use] extern crate log; diff --git a/src/main.rs b/src/main.rs index 0bdf956..43f32a2 100644 --- a/src/main.rs +++ b/src/main.rs @@ -23,15 +23,15 @@ use std::time::Duration; use libotaplus::datatype::{config, Config, Event, Command, Url}; use libotaplus::http_client::Hyper; -use libotaplus::interaction_library::Interpreter; +use libotaplus::interaction_library::{Console, Gateway, Websocket}; use libotaplus::interaction_library::broadcast::Broadcast; -use libotaplus::interaction_library::console::Console; -use libotaplus::interaction_library::gateway::Gateway; -use libotaplus::interaction_library::websocket::Websocket; -use libotaplus::interpreter::{OurInterpreter, AutoAcceptor, Env}; +use libotaplus::interaction_library::gateway::Interpret; +use libotaplus::interpreter::{Interpreter, GlobalInterpreter, AutoAcceptor, Env}; use libotaplus::package_manager::PackageManager; +type Wrapped = Interpret; + macro_rules! spawn_thread { ($name:expr, $body:block) => { match thread::Builder::new().name($name.to_string()).spawn(move || { @@ -44,7 +44,7 @@ macro_rules! spawn_thread { } } -fn spawn_interpreter(config: Config, crx: Receiver, etx: Sender) { +fn spawn_global_interpreter(config: Config, wrx: Receiver, etx: Sender) { let client = Arc::new(Mutex::new(Hyper::new())); let env = Env { config: config.clone(), @@ -52,12 +52,12 @@ fn spawn_interpreter(config: Config, crx: Receiver, etx: Sender) http_client: client.clone(), }; - spawn_thread!("Interpreter", { - OurInterpreter::run(&mut env.clone(), crx, etx); + spawn_thread!("Global Interpreter", { + GlobalInterpreter::run(&mut env.clone(), wrx, etx); }); } -fn spawn_autoacceptor(erx: Receiver, ctx: Sender) { +fn spawn_auto_acceptor(erx: Receiver, ctx: Sender) { spawn_thread!("Autoacceptor of software updates", { AutoAcceptor::run(&mut (), erx, ctx); }); @@ -84,6 +84,34 @@ fn spawn_update_poller(ctx: Sender, config: Config) { }); } +fn spawn_command_wrapper(crx: Receiver, wtx: Sender) { + let (etx, erx): (Sender, Receiver) = channel(); + + spawn_thread!("Command Wrapper", { + loop { + match crx.recv() { + Ok(cmd) => { + let _ = wtx.send(Interpret{ + cmd: cmd, + etx: etx.clone(), + }); + }, + + Err(err) => error!("Error receiving command to wrap: {:?}", err), + } + } + }); + + spawn_thread!("Wrapped Results", { + loop { + match erx.recv() { + Ok(ev) => println!("done: {}", ev.to_string()), + Err(err) => println!("err: {}", err) + } + } + }); +} + fn perform_initial_sync(ctx: Sender) { let _ = ctx.clone().send(Command::Authenticate(None)); let _ = ctx.clone().send(Command::UpdateInstalledPackages); @@ -96,38 +124,32 @@ fn start_event_broadcasting(broadcast: Broadcast) { } fn main() { - setup_logging(); - let config = build_config(); - let (etx, erx): (Sender, Receiver) = channel(); let (ctx, crx): (Sender, Receiver) = channel(); + let (etx, erx): (Sender, Receiver) = channel(); + let (wtx, wrx): (Sender, Receiver) = channel(); let mut broadcast: Broadcast = Broadcast::new(erx); // Must subscribe to the signal before spawning ANY other threads let signals = chan_signal::notify(&[Signal::INT, Signal::TERM]); - spawn_autoacceptor(broadcast.subscribe(), ctx.clone()); - - - Websocket::run(ctx.clone(), broadcast.subscribe()); + spawn_auto_acceptor(broadcast.subscribe(), ctx.clone()); spawn_update_poller(ctx.clone(), config.clone()); - - let events_for_repl = broadcast.subscribe(); - start_event_broadcasting(broadcast); - - perform_initial_sync(ctx.clone()); - spawn_signal_handler(signals, ctx.clone()); - spawn_interpreter(config.clone(), crx, etx.clone()); + perform_initial_sync(ctx.clone()); + spawn_command_wrapper(crx, wtx.clone()); + spawn_global_interpreter(config.clone(), wrx, etx.clone()); + Websocket::run(wtx.clone(), broadcast.subscribe()); if config.test.looping { println!("Ota Plus Client REPL started."); - Console::run(ctx.clone(), events_for_repl); + Console::run(wtx.clone(), broadcast.subscribe()); } + start_event_broadcasting(broadcast); thread::sleep(Duration::from_secs(60000000)); } -- cgit v1.2.1 From 742a96cd993404ac2ae0de95115c992fd9c6d44d Mon Sep 17 00:00:00 2001 From: Shaun Taheri Date: Wed, 18 May 2016 17:36:09 +0200 Subject: Add HTTP Gateway --- Cargo.lock | 217 ++++++++++++++++++++--------------- Cargo.toml | 5 +- src/datatype/command.rs | 2 +- src/datatype/config.rs | 3 + src/interaction_library/console.rs | 53 +++++---- src/interaction_library/gateway.rs | 11 +- src/interaction_library/http.rs | 161 ++++++++++++++++++++++++++ src/interaction_library/mod.rs | 2 + src/interaction_library/websocket.rs | 27 +++-- src/interpreter.rs | 84 ++++++++------ src/lib.rs | 1 - src/main.rs | 49 ++++---- tests/ota_plus_client_tests.rs | 3 +- 13 files changed, 410 insertions(+), 208 deletions(-) create mode 100644 src/interaction_library/http.rs diff --git a/Cargo.lock b/Cargo.lock index 46e594e..55eeb95 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3,24 +3,23 @@ name = "ota-plus-client" version = "0.1.0" dependencies = [ "chan 0.1.18 (registry+https://github.com/rust-lang/crates.io-index)", - "chan-signal 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", - "crossbeam 0.2.9 (registry+https://github.com/rust-lang/crates.io-index)", + "chan-signal 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", "env_logger 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", "getopts 0.2.14 (registry+https://github.com/rust-lang/crates.io-index)", - "hyper 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)", + "hyper 0.9.5 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", "nom 1.2.3 (registry+https://github.com/rust-lang/crates.io-index)", "rustc-serialize 0.3.19 (registry+https://github.com/rust-lang/crates.io-index)", - "tempfile 2.1.2 (registry+https://github.com/rust-lang/crates.io-index)", + "tempfile 2.1.3 (registry+https://github.com/rust-lang/crates.io-index)", "time 0.1.35 (registry+https://github.com/rust-lang/crates.io-index)", - "toml 0.1.28 (registry+https://github.com/rust-lang/crates.io-index)", - "url 0.5.9 (registry+https://github.com/rust-lang/crates.io-index)", - "ws 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", + "toml 0.1.30 (registry+https://github.com/rust-lang/crates.io-index)", + "url 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "ws 0.4.7 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "aho-corasick" -version = "0.5.1" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "memchr 0.1.11 (registry+https://github.com/rust-lang/crates.io-index)", @@ -41,12 +40,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "bitflags" -version = "0.3.3" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "bitflags" -version = "0.4.0" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] @@ -74,51 +73,47 @@ dependencies = [ [[package]] name = "chan-signal" -version = "0.1.5" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "bit-set 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", "chan 0.1.18 (registry+https://github.com/rust-lang/crates.io-index)", "lazy_static 0.1.16 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.10 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "cookie" -version = "0.2.2" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "openssl 0.7.9 (registry+https://github.com/rust-lang/crates.io-index)", + "openssl 0.7.13 (registry+https://github.com/rust-lang/crates.io-index)", "rustc-serialize 0.3.19 (registry+https://github.com/rust-lang/crates.io-index)", "time 0.1.35 (registry+https://github.com/rust-lang/crates.io-index)", - "url 0.5.9 (registry+https://github.com/rust-lang/crates.io-index)", + "url 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", ] -[[package]] -name = "crossbeam" -version = "0.2.9" -source = "registry+https://github.com/rust-lang/crates.io-index" - [[package]] name = "env_logger" version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", - "regex 0.1.65 (registry+https://github.com/rust-lang/crates.io-index)", + "regex 0.1.71 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "gcc" -version = "0.3.26" +version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "gdi32-sys" -version = "0.1.1" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "winapi 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.2.7 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -141,31 +136,42 @@ source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "hyper" -version = "0.8.1" +version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "cookie 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", + "cookie 0.2.4 (registry+https://github.com/rust-lang/crates.io-index)", "httparse 1.1.2 (registry+https://github.com/rust-lang/crates.io-index)", "language-tags 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", "mime 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", - "num_cpus 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", - "openssl 0.7.9 (registry+https://github.com/rust-lang/crates.io-index)", + "num_cpus 0.2.12 (registry+https://github.com/rust-lang/crates.io-index)", + "openssl 0.7.13 (registry+https://github.com/rust-lang/crates.io-index)", + "openssl-verify 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", "rustc-serialize 0.3.19 (registry+https://github.com/rust-lang/crates.io-index)", "solicit 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)", "time 0.1.35 (registry+https://github.com/rust-lang/crates.io-index)", "traitobject 0.0.1 (registry+https://github.com/rust-lang/crates.io-index)", "typeable 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", "unicase 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", - "url 0.5.9 (registry+https://github.com/rust-lang/crates.io-index)", + "url 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "idna" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "matches 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", + "unicode-bidi 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", + "unicode-normalization 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "kernel32-sys" -version = "0.2.1" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "winapi 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.2.7 (registry+https://github.com/rust-lang/crates.io-index)", "winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -180,13 +186,13 @@ version = "0.1.16" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] -name = "libc" -version = "0.1.12" +name = "lazy_static" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "libc" -version = "0.2.10" +version = "0.2.11" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] @@ -212,14 +218,9 @@ name = "memchr" version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "libc 0.2.10 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", ] -[[package]] -name = "mempool" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" - [[package]] name = "mime" version = "0.2.0" @@ -230,18 +231,18 @@ dependencies = [ [[package]] name = "mio" -version = "0.5.0" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "bytes 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", "miow 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", "net2 0.2.23 (registry+https://github.com/rust-lang/crates.io-index)", - "nix 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", + "nix 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", "slab 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", "time 0.1.35 (registry+https://github.com/rust-lang/crates.io-index)", - "winapi 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.2.7 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -249,9 +250,9 @@ name = "miow" version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "kernel32-sys 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", + "kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", "net2 0.2.23 (registry+https://github.com/rust-lang/crates.io-index)", - "winapi 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.2.7 (registry+https://github.com/rust-lang/crates.io-index)", "ws2_32-sys 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -261,19 +262,19 @@ version = "0.2.23" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "cfg-if 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", - "kernel32-sys 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.10 (registry+https://github.com/rust-lang/crates.io-index)", - "winapi 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)", + "kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.2.7 (registry+https://github.com/rust-lang/crates.io-index)", "ws2_32-sys 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "nix" -version = "0.4.2" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "bitflags 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)", + "bitflags 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -283,45 +284,53 @@ source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "num_cpus" -version = "0.2.11" +version = "0.2.12" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "libc 0.2.10 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "openssl" -version = "0.7.9" +version = "0.7.13" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "bitflags 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", - "gcc 0.3.26 (registry+https://github.com/rust-lang/crates.io-index)", - "lazy_static 0.1.16 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.10 (registry+https://github.com/rust-lang/crates.io-index)", - "openssl-sys 0.7.9 (registry+https://github.com/rust-lang/crates.io-index)", - "openssl-sys-extras 0.7.9 (registry+https://github.com/rust-lang/crates.io-index)", + "bitflags 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", + "gcc 0.3.28 (registry+https://github.com/rust-lang/crates.io-index)", + "lazy_static 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", + "openssl-sys 0.7.13 (registry+https://github.com/rust-lang/crates.io-index)", + "openssl-sys-extras 0.7.13 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "openssl-sys" -version = "0.7.9" +version = "0.7.13" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "gdi32-sys 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.10 (registry+https://github.com/rust-lang/crates.io-index)", + "gdi32-sys 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", "libressl-pnacl-sys 2.1.6 (registry+https://github.com/rust-lang/crates.io-index)", "pkg-config 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", - "user32-sys 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", + "user32-sys 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "openssl-sys-extras" -version = "0.7.9" +version = "0.7.13" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "gcc 0.3.26 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.10 (registry+https://github.com/rust-lang/crates.io-index)", - "openssl-sys 0.7.9 (registry+https://github.com/rust-lang/crates.io-index)", + "gcc 0.3.28 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", + "openssl-sys 0.7.13 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "openssl-verify" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "openssl 0.7.13 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -342,24 +351,24 @@ name = "rand" version = "0.3.14" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "libc 0.2.10 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "regex" -version = "0.1.65" +version = "0.1.71" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "aho-corasick 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", + "aho-corasick 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)", "memchr 0.1.11 (registry+https://github.com/rust-lang/crates.io-index)", - "mempool 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", - "regex-syntax 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", + "regex-syntax 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)", + "thread_local 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)", "utf8-ranges 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "regex-syntax" -version = "0.3.1" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] @@ -412,14 +421,31 @@ dependencies = [ [[package]] name = "tempfile" -version = "2.1.2" +version = "2.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "kernel32-sys 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.10 (registry+https://github.com/rust-lang/crates.io-index)", + "kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", "rand 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)", "rustc_version 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)", - "winapi 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.2.7 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "thread-id" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "thread_local" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "thread-id 2.0.0 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -427,14 +453,14 @@ name = "time" version = "0.1.35" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "kernel32-sys 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.10 (registry+https://github.com/rust-lang/crates.io-index)", - "winapi 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)", + "kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.2.7 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "toml" -version = "0.1.28" +version = "0.1.30" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "rustc-serialize 0.3.19 (registry+https://github.com/rust-lang/crates.io-index)", @@ -480,15 +506,24 @@ dependencies = [ "rustc-serialize 0.3.19 (registry+https://github.com/rust-lang/crates.io-index)", "unicode-bidi 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", "unicode-normalization 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", - "uuid 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", + "uuid 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "url" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "idna 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "matches 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "user32-sys" -version = "0.1.2" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "winapi 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.2.7 (registry+https://github.com/rust-lang/crates.io-index)", "winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -499,7 +534,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "uuid" -version = "0.2.0" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "rand 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)", @@ -507,7 +542,7 @@ dependencies = [ [[package]] name = "winapi" -version = "0.2.6" +version = "0.2.7" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] @@ -517,12 +552,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "ws" -version = "0.4.6" +version = "0.4.7" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "httparse 1.1.2 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", - "mio 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", + "mio 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", "rand 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)", "sha1 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", "url 0.5.9 (registry+https://github.com/rust-lang/crates.io-index)", @@ -533,7 +568,7 @@ name = "ws2_32-sys" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "winapi 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.2.7 (registry+https://github.com/rust-lang/crates.io-index)", "winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", ] diff --git a/Cargo.toml b/Cargo.toml index 5de238f..8f41189 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,15 +15,14 @@ doc = false [dependencies] chan = "0.1.18" chan-signal = "0.1.5" -crossbeam = "0.2.9" env_logger = "0.3.3" getopts = "0.2.14" -hyper = "0.8.1" +hyper = "0.9.5" log = "0.3.5" nom = "1.2.3" rustc-serialize = "0.3.18" tempfile = "2.1.2" time = "0.1.35" toml = "0.1.28" -url = "0.5.9" +url = "1.1.0" ws = "0.4.6" diff --git a/src/datatype/command.rs b/src/datatype/command.rs index c36d452..dbb074f 100644 --- a/src/datatype/command.rs +++ b/src/datatype/command.rs @@ -21,7 +21,7 @@ impl FromStr for Command { fn from_str(s: &str) -> Result { match command(s.as_bytes()) { IResult::Done(_, cmd) => parse_arguments(cmd.0, cmd.1.clone()), - _ => Err(Error::Command(format!("unrecognized input: {}", s))), + _ => Err(Error::Command(format!("bad command: {}", s))), } } } diff --git a/src/datatype/config.rs b/src/datatype/config.rs index bdcba4e..daab773 100644 --- a/src/datatype/config.rs +++ b/src/datatype/config.rs @@ -63,6 +63,7 @@ pub struct OtaConfig { #[derive(RustcDecodable, PartialEq, Eq, Debug, Clone)] pub struct TestConfig { pub looping: bool, + pub http: bool, } impl Default for AuthConfig { @@ -103,6 +104,7 @@ impl Default for TestConfig { fn default() -> TestConfig { TestConfig { looping: false, + http: false, } } } @@ -218,6 +220,7 @@ mod tests { [test] looping = false + http = false "#; #[test] diff --git a/src/interaction_library/console.rs b/src/interaction_library/console.rs index 0fba46d..9250090 100644 --- a/src/interaction_library/console.rs +++ b/src/interaction_library/console.rs @@ -2,49 +2,48 @@ use std::{io, thread}; use std::fmt::Debug; use std::str::FromStr; use std::string::ToString; -use std::sync::mpsc; +use std::sync::{Arc, Mutex, mpsc}; use std::sync::mpsc::{Sender, Receiver}; use super::gateway::{Gateway, Interpret}; -pub struct Console; +pub struct Console { + etx: Arc>> +} -impl Gateway for Console - where C: FromStr + Send + 'static, - E: ToString + Send + 'static, +impl Gateway for Console + where C: Send + FromStr + 'static, + E: Send + ToString + 'static, ::Err: Debug, { - fn new() -> Console { - Console + fn new() -> Console { + let (etx, erx): (Sender, Receiver) = mpsc::channel(); + + thread::spawn(move || { + loop { + match erx.recv() { + Ok(event) => println!("{}", event.to_string()), + Err(err) => error!("Error receiving event: {:?}", err), + } + } + }); + + Console { etx: Arc::new(Mutex::new(etx)) } } fn next(&self) -> Option> { - match parse_input(&get_input()) { - Ok(cmd) => { - let (etx, erx): (Sender, Receiver) = mpsc::channel(); - thread::spawn(move || { - match erx.recv() { - Ok(event) => println!("{}", event.to_string()), - Err(err) => error!("Error receiving event: {:?}", err), - } - }); - Some(Interpret{ - cmd: cmd, - etx: etx, - }) - } - + match parse_input(get_input()) { + Ok(cmd) => Some(Interpret{ + cmd: cmd, + etx: Some(self.etx.clone()), + }), Err(err) => { println!("{:?}", err); None } } } - - fn pulse(&self, e: E) { - println!("(global): {}", e.to_string()); - } } fn get_input() -> String { @@ -54,6 +53,6 @@ fn get_input() -> String { input } -fn parse_input(s: &str) -> Result::Err> { +fn parse_input(s: String) -> Result::Err> { s.parse() } diff --git a/src/interaction_library/gateway.rs b/src/interaction_library/gateway.rs index 001659d..ecf0587 100644 --- a/src/interaction_library/gateway.rs +++ b/src/interaction_library/gateway.rs @@ -1,16 +1,16 @@ use std::thread; -use std::sync::Arc; +use std::sync::{Arc, Mutex}; use std::sync::mpsc::{Sender, Receiver}; pub struct Interpret { pub cmd: C, - pub etx: Sender, + pub etx: Option>>>, } pub trait Gateway: Sized + Send + Sync + 'static where C: Send + 'static, - E: Send + 'static + E: Send + 'static, { fn new() -> Self; fn next(&self) -> Option>; @@ -39,7 +39,8 @@ pub trait Gateway: Sized + Send + Sync + 'static }); } - // ignore global events by default #[allow(unused_variables)] - fn pulse(&self, e: E) {} + fn pulse(&self, e: E) { + // ignore global events by default + } } diff --git a/src/interaction_library/http.rs b/src/interaction_library/http.rs new file mode 100644 index 0000000..f538605 --- /dev/null +++ b/src/interaction_library/http.rs @@ -0,0 +1,161 @@ +use rustc_serialize::{json, Decodable, Encodable}; +use std::{env, thread}; +use std::io::Read; +use std::sync::{Arc, Mutex, mpsc}; +use std::sync::mpsc::{Sender, Receiver}; +use hyper::status::StatusCode; +use hyper::server::{Handler, Server, Request, Response}; + +use super::gateway::{Gateway, Interpret}; +use datatype::{Error, Event}; + + +pub struct Http { + irx: Arc>>>, +} + +impl Gateway for Http + where C: Send + Decodable + 'static, + E: Send + Encodable + 'static +{ + fn new() -> Http { + let (itx, irx): (Sender>, Receiver>) = mpsc::channel(); + let handler = HttpHandler { itx: Arc::new(Mutex::new(itx)) }; + let addr = env::var("OTA_PLUS_CLIENT_HTTP_ADDR") + .unwrap_or("127.0.0.1:8888".to_string()); + + thread::spawn(move || { + Server::http(&addr as &str).unwrap().handle(handler).unwrap(); + }); + + Http { irx: Arc::new(Mutex::new(irx)) } + } + + fn next(&self) -> Option> { + match self.irx.lock().unwrap().recv() { + Ok(i) => Some(i), + Err(err) => { + error!("Error forwarding request: {}", err); + None + } + } + } +} + + +pub struct HttpHandler { + itx: Arc>>>, +} + +impl Handler for HttpHandler + where C: Send + Decodable, + E: Send + Encodable +{ + fn handle(&self, req: Request, resp: Response) { + worker(self, req, resp).unwrap_or_else(|err| { + error!("error handling request: {}", err); + }); + + fn worker(handler: &HttpHandler, + mut req: Request, + mut resp: Response) + -> Result<(), Error> + where C: Send + Decodable, + E: Send + Encodable + { + // return 500 response on error + *resp.status_mut() = StatusCode::InternalServerError; + + let mut req_body = String::new(); + let _: usize = try!(req.read_to_string(&mut req_body)); + let c: C = try!(json::decode(&req_body)); + + let (etx, erx): (Sender, Receiver) = mpsc::channel(); + debug!("sending request body: {}", req_body); + handler.itx.lock().unwrap().send(Interpret { + cmd: c, + etx: Some(Arc::new(Mutex::new(etx))), + }).unwrap(); + + match erx.recv() { + Ok(e) => { + let resp_body = try!(json::encode(&e)); + *resp.status_mut() = StatusCode::Ok; + debug!("sending response body: {}", resp_body); + resp.send(resp_body.as_bytes()).unwrap(); + } + Err(err) => { + error!("error forwarding request: {}", err); + let ev = json::encode(&Event::Error(format!("{}", err))).unwrap(); + resp.send(ev.as_bytes()).unwrap(); + } + } + + Ok(()) + } + } +} + + +#[cfg(test)] +mod tests { + use hyper::Client; + use rustc_serialize::json; + use std::thread; + use std::io::Read; + use std::sync::mpsc::{channel, Sender, Receiver}; + + use super::*; + use super::super::gateway::{Gateway, Interpret}; + use super::super::super::datatype::{Command, Event}; + + + type Wrapped = Interpret; + + #[test] + fn multiple_connections() { + let (_, erx): (Sender, Receiver) = channel(); + let (wtx, wrx): (Sender, Receiver) = channel(); + Http::run(wtx, erx); + + thread::spawn(move || { + loop { + let w = wrx.recv().unwrap(); + match w.cmd { + Command::AcceptUpdate(id) => { + let ev = Event::Error(id); + match w.etx { + Some(etx) => etx.lock().unwrap().send(ev).unwrap(), + None => panic!("expected transmitter"), + } + } + _ => panic!("expected AcceptUpdate"), + } + } + }); + + let mut threads = vec![]; + for id in 0..10 { + threads.push(thread::spawn(move || { + let client = Client::new(); + let cmd = Command::AcceptUpdate(format!("{}", id)); + + let req_body = json::encode(&cmd).unwrap(); + let mut resp = client.post("http://127.0.0.1:8888/") + .body(&req_body) + .send() + .unwrap(); + + let mut resp_body = String::new(); + resp.read_to_string(&mut resp_body).unwrap(); + let ev: Event = json::decode(&resp_body).unwrap(); + assert_eq!(ev, Event::Error(format!("{}", id))); + })); + } + + // wait for all threads to finish + for t in threads { + let _ = t.join(); + } + } +} diff --git a/src/interaction_library/mod.rs b/src/interaction_library/mod.rs index e28ce4b..5a44e1b 100644 --- a/src/interaction_library/mod.rs +++ b/src/interaction_library/mod.rs @@ -1,8 +1,10 @@ pub use self::console::Console; pub use self::gateway::Gateway; +pub use self::http::Http; pub use self::websocket::Websocket; pub mod broadcast; pub mod console; pub mod gateway; +pub mod http; pub mod websocket; diff --git a/src/interaction_library/websocket.rs b/src/interaction_library/websocket.rs index 9143060..ebb8f0c 100644 --- a/src/interaction_library/websocket.rs +++ b/src/interaction_library/websocket.rs @@ -16,8 +16,8 @@ use datatype::Error; type Clients = Arc>>; pub struct WebsocketHandler { - out: WsSender, - sender: Sender, + out: WsSender, + sender: Sender, clients: Clients, } @@ -45,7 +45,7 @@ impl Handler for WebsocketHandler { #[derive(Clone)] pub struct Websocket { - clients: Clients, + clients: Clients, receiver: Arc>>, } @@ -71,28 +71,27 @@ impl Websocket { impl Gateway for Websocket where C: Decodable + Send + 'static, - E: Encodable + Send + 'static + E: Encodable + Send + 'static, { fn new() -> Websocket { - let (tx, rx) = mpsc::channel(); - let clients = Arc::new(Mutex::new(HashMap::new())); - let clients_clone = clients.clone(); - - let addr = env::var("OTA_PLUS_CLIENT_WEBSOCKET_ADDR") - .unwrap_or("127.0.0.1:3012".to_string()); + let (tx, rx) = mpsc::channel(); + let clients = Arc::new(Mutex::new(HashMap::new())); + let moved = clients.clone(); + let addr = env::var("OTA_PLUS_CLIENT_WEBSOCKET_ADDR") + .unwrap_or("127.0.0.1:3012".to_string()); thread::spawn(move || { listen(&addr as &str, |out| { WebsocketHandler { out: out, sender: tx.clone(), - clients: clients_clone.clone(), + clients: moved.clone(), } }) }); Websocket { - clients: clients.clone(), + clients: clients, receiver: Arc::new(Mutex::new(rx)), } } @@ -110,7 +109,7 @@ impl Gateway for Websocket }); Some(Interpret { cmd: cmd, - etx: etx, + etx: Some(Arc::new(Mutex::new(etx))), }) } @@ -127,5 +126,5 @@ fn encode(e: E) -> String { } fn decode(s: &str) -> Result { - json::decode::(s).map_err(|err| Error::from(err)) + Ok(try!(json::decode::(s))) } diff --git a/src/interpreter.rs b/src/interpreter.rs index c159552..51a0b65 100644 --- a/src/interpreter.rs +++ b/src/interpreter.rs @@ -26,7 +26,7 @@ pub trait Interpreter { fn run(env: &mut Env, irx: Receiver, otx: Sender) { loop { match irx.recv() { - Ok(msg) => Self::interpret(env, msg, otx.clone()), + Ok(msg) => Self::interpret(env, msg, otx.clone()), Err(err) => error!("Error receiving command: {:?}", err), } } @@ -65,19 +65,28 @@ impl<'a> Interpreter, Interpret, Event> for GlobalInterp let (multi_tx, multi_rx): (Sender, Receiver) = channel(); let local_tx = i.etx.clone(); - // HACK: unwrap failed sends to avoid thread deadlocking let _ = command_interpreter(env, i.cmd, multi_tx) + .map_err(|err| send(Event::Error(format!("{}", err)), &etx, &local_tx)) .map(|_| { for ev in multi_rx { - let _ = etx.send(ev.clone()).unwrap(); - let _ = local_tx.send(ev.clone()).unwrap(); - }; - }) - .map_err(|err| { - let ev = Event::Error(format!("{}", err)); - let _ = etx.send(ev.clone()).unwrap(); - let _ = local_tx.send(ev.clone()).unwrap(); + send(ev, &etx, &local_tx); + } }); + + fn send(ev: Event, global_tx: &Sender, local_tx: &Option>>>) { + // unwrap failed sends to avoid thread deadlocking + let _ = global_tx.send(ev.clone()).unwrap(); + if let Some(ref local) = *local_tx { + let _ = local.lock().unwrap().send(ev).unwrap(); + } + } + } +} + +fn command_interpreter(env: &mut Env, cmd: Command, etx: Sender) -> Result<(), Error> { + match env.access_token.to_owned() { + Some(ref token) => authenticated(env, cmd, etx, token), + None => unauthenticated(env, cmd, etx), } } @@ -94,37 +103,17 @@ macro_rules! partial_apply { } } -fn command_interpreter(env: &mut Env, cmd: Command, etx: Sender) -> Result<(), Error> { - if env.access_token.is_none() { - match cmd { - Authenticate(_) => { - let client_clone = env.http_client.clone(); - let mut client = client_clone.lock().unwrap(); - let token = try!(authenticate(&env.config.auth, &mut *client)); - env.access_token = Some(token.into()); - try!(etx.send(Event::Ok)); - } - - AcceptUpdate(_) | - GetPendingUpdates | - ListInstalledPackages | - UpdateInstalledPackages => try!(etx.send(Event::NotAuthenticated)), - - Shutdown => exit(0), - } - return Ok(()) - } - - let token = env.access_token.to_owned().unwrap(); - let client_clone = env.http_client.clone(); +fn authenticated<'a>(env: &mut Env, cmd: Command, etx: Sender, token: &Cow<'a, AccessToken>) + -> Result<(), Error> { + let client = env.http_client.clone(); partial_apply!([get_package_updates, update_installed_packages], - [send_install_report], - [install_package_update], - &env.config, client_clone, &token); + [send_install_report], + [install_package_update], + &env.config, client, &token); match cmd { - Authenticate(_) => (), // Already authenticated. + Authenticate(_) => (), AcceptUpdate(ref id) => { try!(etx.send(Event::UpdateStateChanged(id.clone(), UpdateState::Downloading))); @@ -159,3 +148,24 @@ fn command_interpreter(env: &mut Env, cmd: Command, etx: Sender) -> Resul Ok(()) } + +fn unauthenticated(env: &mut Env, cmd: Command, etx: Sender) -> Result<(), Error> { + match cmd { + Authenticate(_) => { + let client_clone = env.http_client.clone(); + let mut client = client_clone.lock().unwrap(); + let token = try!(authenticate(&env.config.auth, &mut *client)); + env.access_token = Some(token.into()); + try!(etx.send(Event::Ok)); + } + + AcceptUpdate(_) | + GetPendingUpdates | + ListInstalledPackages | + UpdateInstalledPackages => try!(etx.send(Event::NotAuthenticated)), + + Shutdown => exit(0), + } + + Ok(()) +} diff --git a/src/lib.rs b/src/lib.rs index f7c13ee..deae60c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,4 +1,3 @@ -extern crate crossbeam; extern crate hyper; #[macro_use] extern crate nom; #[macro_use] extern crate log; diff --git a/src/main.rs b/src/main.rs index 43f32a2..bfc9ca9 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,6 +1,5 @@ extern crate chan; extern crate chan_signal; -extern crate crossbeam; extern crate env_logger; extern crate getopts; extern crate hyper; @@ -21,9 +20,9 @@ use std::sync::{Arc, Mutex}; use std::thread; use std::time::Duration; -use libotaplus::datatype::{config, Config, Event, Command, Url}; +use libotaplus::datatype::{config, Command, Config, Event, Url}; use libotaplus::http_client::Hyper; -use libotaplus::interaction_library::{Console, Gateway, Websocket}; +use libotaplus::interaction_library::{Console, Gateway, Http, Websocket}; use libotaplus::interaction_library::broadcast::Broadcast; use libotaplus::interaction_library::gateway::Interpret; use libotaplus::interpreter::{Interpreter, GlobalInterpreter, AutoAcceptor, Env}; @@ -67,9 +66,10 @@ fn spawn_signal_handler(signals: ChanReceiver, ctx: Sender) { spawn_thread!("Signal handler", { loop { match signals.recv() { - Some(Signal::TERM) | Some(Signal::INT) => - ctx.send(Command::Shutdown).expect("send failed."), - _ => {} + Some(Signal::TERM) | Some(Signal::INT) => { + ctx.send(Command::Shutdown).expect("send failed.") + } + _ => {} } } }); @@ -84,29 +84,12 @@ fn spawn_update_poller(ctx: Sender, config: Config) { }); } -fn spawn_command_wrapper(crx: Receiver, wtx: Sender) { - let (etx, erx): (Sender, Receiver) = channel(); - - spawn_thread!("Command Wrapper", { +fn spawn_command_forwarder(crx: Receiver, wtx: Sender) { + spawn_thread!("Command Forwarder", { loop { match crx.recv() { - Ok(cmd) => { - let _ = wtx.send(Interpret{ - cmd: cmd, - etx: etx.clone(), - }); - }, - - Err(err) => error!("Error receiving command to wrap: {:?}", err), - } - } - }); - - spawn_thread!("Wrapped Results", { - loop { - match erx.recv() { - Ok(ev) => println!("done: {}", ev.to_string()), - Err(err) => println!("err: {}", err) + Ok(cmd) => wtx.send(Interpret{ cmd: cmd, etx: None }).unwrap(), + Err(err) => error!("Error receiving command to forward: {:?}", err), } } }); @@ -140,10 +123,13 @@ fn main() { spawn_signal_handler(signals, ctx.clone()); perform_initial_sync(ctx.clone()); - spawn_command_wrapper(crx, wtx.clone()); + spawn_command_forwarder(crx, wtx.clone()); spawn_global_interpreter(config.clone(), wrx, etx.clone()); Websocket::run(wtx.clone(), broadcast.subscribe()); + if config.test.http { + Http::run(wtx.clone(), broadcast.subscribe()); + } if config.test.looping { println!("Ota Plus Client REPL started."); Console::run(wtx.clone(), broadcast.subscribe()); @@ -208,6 +194,9 @@ fn build_config() -> Config { "change package manager", "MANAGER"); opts.optflag("", "repl", "enable repl"); + opts.optflag("", "http", + "enable interaction via http requests"); + let matches = opts.parse(&args[1..]) .unwrap_or_else(|err| panic!(err.to_string())); @@ -269,6 +258,10 @@ fn build_config() -> Config { config.test.looping = true; } + if matches.opt_present("http") { + config.test.http = true; + } + return config } diff --git a/tests/ota_plus_client_tests.rs b/tests/ota_plus_client_tests.rs index 0192825..3f92284 100644 --- a/tests/ota_plus_client_tests.rs +++ b/tests/ota_plus_client_tests.rs @@ -57,6 +57,7 @@ Options: --ota-package-manager MANAGER change package manager --repl enable repl + --http enable interaction via http requests "#, bin_dir())); } @@ -82,7 +83,7 @@ fn no_auth_server_to_connect_to() { #[test] fn bad_section() { - assert_eq!(client_with_config(&[""], "[uth]"), + assert_eq!(client_with_config(&[""], "[uth]\n"), "parse_toml_table, invalid section: auth\n") } -- cgit v1.2.1 From 8ad5ed7b77894577ffd72f2e01bd32c70370df24 Mon Sep 17 00:00:00 2001 From: Shaun Taheri Date: Mon, 23 May 2016 17:01:04 +0200 Subject: Only send last Event to local transmitter --- src/interpreter.rs | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/src/interpreter.rs b/src/interpreter.rs index 51a0b65..b5cb644 100644 --- a/src/interpreter.rs +++ b/src/interpreter.rs @@ -66,16 +66,22 @@ impl<'a> Interpreter, Interpret, Event> for GlobalInterp let local_tx = i.etx.clone(); let _ = command_interpreter(env, i.cmd, multi_tx) - .map_err(|err| send(Event::Error(format!("{}", err)), &etx, &local_tx)) + .map_err(|err| { + let ev = Event::Error(format!("{}", err)); + let _ = etx.send(ev.clone()).unwrap(); + send(ev, &local_tx); + }) .map(|_| { + let mut last_ev: Event; for ev in multi_rx { - send(ev, &etx, &local_tx); + let _ = etx.send(ev.clone()).unwrap(); + last_ev = ev; } + send(last_ev, &local_tx); }); - fn send(ev: Event, global_tx: &Sender, local_tx: &Option>>>) { - // unwrap failed sends to avoid thread deadlocking - let _ = global_tx.send(ev.clone()).unwrap(); + fn send(ev: Event, local_tx: &Option>>>) { + // unwrap failed sends to avoid receiver thread deadlocking if let Some(ref local) = *local_tx { let _ = local.lock().unwrap().send(ev).unwrap(); } -- cgit v1.2.1 From 96877b7be34a7e609b674c2b4690623ccf762ab7 Mon Sep 17 00:00:00 2001 From: Shaun Taheri Date: Mon, 23 May 2016 17:01:04 +0200 Subject: Panic when there is no Event to return --- src/interpreter.rs | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/interpreter.rs b/src/interpreter.rs index b5cb644..e920fc7 100644 --- a/src/interpreter.rs +++ b/src/interpreter.rs @@ -72,12 +72,15 @@ impl<'a> Interpreter, Interpret, Event> for GlobalInterp send(ev, &local_tx); }) .map(|_| { - let mut last_ev: Event; + let mut last_ev = None; for ev in multi_rx { let _ = etx.send(ev.clone()).unwrap(); - last_ev = ev; + last_ev = Some(ev); + } + match last_ev { + Some(ev) => send(ev, &local_tx), + None => panic!("no event to send back") } - send(last_ev, &local_tx); }); fn send(ev: Event, local_tx: &Option>>>) { -- cgit v1.2.1 From ff00a19b1a148d3ab03f65772e1fcb3813dca67d Mon Sep 17 00:00:00 2001 From: Shaun Taheri Date: Mon, 23 May 2016 11:44:26 +0200 Subject: Share Wrapped type alias --- src/interaction_library/http.rs | 5 ++--- src/interpreter.rs | 20 +++++++++++--------- src/main.rs | 4 +--- 3 files changed, 14 insertions(+), 15 deletions(-) diff --git a/src/interaction_library/http.rs b/src/interaction_library/http.rs index f538605..e8003fc 100644 --- a/src/interaction_library/http.rs +++ b/src/interaction_library/http.rs @@ -106,12 +106,11 @@ mod tests { use std::sync::mpsc::{channel, Sender, Receiver}; use super::*; - use super::super::gateway::{Gateway, Interpret}; + use super::super::gateway::Gateway; use super::super::super::datatype::{Command, Event}; + use super::super::super::interpreter::Wrapped; - type Wrapped = Interpret; - #[test] fn multiple_connections() { let (_, erx): (Sender, Receiver) = channel(); diff --git a/src/interpreter.rs b/src/interpreter.rs index e920fc7..db9ac02 100644 --- a/src/interpreter.rs +++ b/src/interpreter.rs @@ -12,11 +12,13 @@ use ota_plus::{get_package_updates, install_package_update, update_installed_pac send_install_report}; +pub type Wrapped = Interpret; + #[derive(Clone)] pub struct Env<'a> { - pub config: Config, + pub config: Config, pub access_token: Option>, - pub http_client: Arc>, + pub http_client: Arc>, } @@ -59,22 +61,22 @@ impl Interpreter<(), Event, Command> for AutoAcceptor { pub struct GlobalInterpreter; -impl<'a> Interpreter, Interpret, Event> for GlobalInterpreter { - fn interpret(env: &mut Env, i: Interpret, etx: Sender) { - info!("Interpreting: {:?}", i.cmd); +impl<'a> Interpreter, Wrapped, Event> for GlobalInterpreter { + fn interpret(env: &mut Env, w: Wrapped, global_tx: Sender) { + info!("Interpreting: {:?}", w.cmd); let (multi_tx, multi_rx): (Sender, Receiver) = channel(); - let local_tx = i.etx.clone(); + let local_tx = w.etx.clone(); - let _ = command_interpreter(env, i.cmd, multi_tx) + let _ = command_interpreter(env, w.cmd, multi_tx) .map_err(|err| { let ev = Event::Error(format!("{}", err)); - let _ = etx.send(ev.clone()).unwrap(); + let _ = global_tx.send(ev.clone()).unwrap(); send(ev, &local_tx); }) .map(|_| { let mut last_ev = None; for ev in multi_rx { - let _ = etx.send(ev.clone()).unwrap(); + let _ = global_tx.send(ev.clone()).unwrap(); last_ev = Some(ev); } match last_ev { diff --git a/src/main.rs b/src/main.rs index bfc9ca9..deebf5e 100644 --- a/src/main.rs +++ b/src/main.rs @@ -25,12 +25,10 @@ use libotaplus::http_client::Hyper; use libotaplus::interaction_library::{Console, Gateway, Http, Websocket}; use libotaplus::interaction_library::broadcast::Broadcast; use libotaplus::interaction_library::gateway::Interpret; -use libotaplus::interpreter::{Interpreter, GlobalInterpreter, AutoAcceptor, Env}; +use libotaplus::interpreter::{AutoAcceptor, Env, GlobalInterpreter, Interpreter, Wrapped}; use libotaplus::package_manager::PackageManager; -type Wrapped = Interpret; - macro_rules! spawn_thread { ($name:expr, $body:block) => { match thread::Builder::new().name($name.to_string()).spawn(move || { -- cgit v1.2.1 From 3ca2abbb6ba8f4d94706cf895a8be3dcd6f9628b Mon Sep 17 00:00:00 2001 From: Shaun Taheri Date: Tue, 24 May 2016 18:22:42 +0200 Subject: Use scoped threads when waiting for results --- Cargo.lock | 18 ++++-- Cargo.toml | 1 + src/interaction_library/http.rs | 44 +++++++------- src/lib.rs | 1 + src/main.rs | 123 ++++++++++++++++++---------------------- 5 files changed, 91 insertions(+), 96 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 55eeb95..305a0be 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4,9 +4,10 @@ version = "0.1.0" dependencies = [ "chan 0.1.18 (registry+https://github.com/rust-lang/crates.io-index)", "chan-signal 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", + "crossbeam 0.2.9 (registry+https://github.com/rust-lang/crates.io-index)", "env_logger 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", "getopts 0.2.14 (registry+https://github.com/rust-lang/crates.io-index)", - "hyper 0.9.5 (registry+https://github.com/rust-lang/crates.io-index)", + "hyper 0.9.6 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", "nom 1.2.3 (registry+https://github.com/rust-lang/crates.io-index)", "rustc-serialize 0.3.19 (registry+https://github.com/rust-lang/crates.io-index)", @@ -50,7 +51,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "byteorder" -version = "0.5.1" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] @@ -93,6 +94,11 @@ dependencies = [ "url 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "crossbeam" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "env_logger" version = "0.3.3" @@ -136,7 +142,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "hyper" -version = "0.9.5" +version = "0.9.6" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "cookie 0.2.4 (registry+https://github.com/rust-lang/crates.io-index)", @@ -361,14 +367,14 @@ source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "aho-corasick 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)", "memchr 0.1.11 (registry+https://github.com/rust-lang/crates.io-index)", - "regex-syntax 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)", + "regex-syntax 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", "thread_local 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)", "utf8-ranges 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "regex-syntax" -version = "0.3.2" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] @@ -394,7 +400,7 @@ name = "sha1" version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "byteorder 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", + "byteorder 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 8f41189..7644d6e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,6 +15,7 @@ doc = false [dependencies] chan = "0.1.18" chan-signal = "0.1.5" +crossbeam = "0.2.9" env_logger = "0.3.3" getopts = "0.2.14" hyper = "0.9.5" diff --git a/src/interaction_library/http.rs b/src/interaction_library/http.rs index e8003fc..4b6aefb 100644 --- a/src/interaction_library/http.rs +++ b/src/interaction_library/http.rs @@ -99,6 +99,7 @@ impl Handler for HttpHandler #[cfg(test)] mod tests { + use crossbeam; use hyper::Client; use rustc_serialize::json; use std::thread; @@ -133,28 +134,25 @@ mod tests { } }); - let mut threads = vec![]; - for id in 0..10 { - threads.push(thread::spawn(move || { - let client = Client::new(); - let cmd = Command::AcceptUpdate(format!("{}", id)); - - let req_body = json::encode(&cmd).unwrap(); - let mut resp = client.post("http://127.0.0.1:8888/") - .body(&req_body) - .send() - .unwrap(); - - let mut resp_body = String::new(); - resp.read_to_string(&mut resp_body).unwrap(); - let ev: Event = json::decode(&resp_body).unwrap(); - assert_eq!(ev, Event::Error(format!("{}", id))); - })); - } - - // wait for all threads to finish - for t in threads { - let _ = t.join(); - } + // wait for all scoped threads to complete + crossbeam::scope(|scope| { + for id in 0..10 { + scope.spawn(move || { + let client = Client::new(); + let cmd = Command::AcceptUpdate(format!("{}", id)); + + let req_body = json::encode(&cmd).unwrap(); + let mut resp = client.post("http://127.0.0.1:8888/") + .body(&req_body) + .send() + .unwrap(); + + let mut resp_body = String::new(); + resp.read_to_string(&mut resp_body).unwrap(); + let ev: Event = json::decode(&resp_body).unwrap(); + assert_eq!(ev, Event::Error(format!("{}", id))); + }); + } + }); } } diff --git a/src/lib.rs b/src/lib.rs index deae60c..f7c13ee 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,3 +1,4 @@ +extern crate crossbeam; extern crate hyper; #[macro_use] extern crate nom; #[macro_use] extern crate log; diff --git a/src/main.rs b/src/main.rs index deebf5e..3adeef2 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,5 +1,6 @@ extern crate chan; extern crate chan_signal; +extern crate crossbeam; extern crate env_logger; extern crate getopts; extern crate hyper; @@ -29,18 +30,6 @@ use libotaplus::interpreter::{AutoAcceptor, Env, GlobalInterpreter, Interpreter, use libotaplus::package_manager::PackageManager; -macro_rules! spawn_thread { - ($name:expr, $body:block) => { - match thread::Builder::new().name($name.to_string()).spawn(move || { - info!("Spawning {}", $name.to_string()); - $body - }) { - Err(e) => panic!("Couldn't spawn {}: {}", $name, e), - Ok(handle) => handle - } - } -} - fn spawn_global_interpreter(config: Config, wrx: Receiver, etx: Sender) { let client = Arc::new(Mutex::new(Hyper::new())); let env = Env { @@ -48,49 +37,34 @@ fn spawn_global_interpreter(config: Config, wrx: Receiver, etx: Sender< access_token: None, http_client: client.clone(), }; - - spawn_thread!("Global Interpreter", { - GlobalInterpreter::run(&mut env.clone(), wrx, etx); - }); -} - -fn spawn_auto_acceptor(erx: Receiver, ctx: Sender) { - spawn_thread!("Autoacceptor of software updates", { - AutoAcceptor::run(&mut (), erx, ctx); - }); + GlobalInterpreter::run(&mut env.clone(), wrx, etx); } fn spawn_signal_handler(signals: ChanReceiver, ctx: Sender) { - spawn_thread!("Signal handler", { - loop { - match signals.recv() { - Some(Signal::TERM) | Some(Signal::INT) => { - ctx.send(Command::Shutdown).expect("send failed.") - } - _ => {} + loop { + match signals.recv() { + Some(Signal::TERM) | Some(Signal::INT) => { + ctx.send(Command::Shutdown).expect("send failed.") } + _ => {} } - }); + } } fn spawn_update_poller(ctx: Sender, config: Config) { - spawn_thread!("Update poller", { - loop { - let _ = ctx.send(Command::GetPendingUpdates); - thread::sleep(Duration::from_secs(config.ota.polling_interval)) - } - }); + loop { + let _ = ctx.send(Command::GetPendingUpdates); + thread::sleep(Duration::from_secs(config.ota.polling_interval)) + } } fn spawn_command_forwarder(crx: Receiver, wtx: Sender) { - spawn_thread!("Command Forwarder", { - loop { - match crx.recv() { - Ok(cmd) => wtx.send(Interpret{ cmd: cmd, etx: None }).unwrap(), - Err(err) => error!("Error receiving command to forward: {:?}", err), - } + loop { + match crx.recv() { + Ok(cmd) => wtx.send(Interpret{ cmd: cmd, etx: None }).unwrap(), + Err(err) => error!("Error receiving command to forward: {:?}", err), } - }); + } } fn perform_initial_sync(ctx: Sender) { @@ -98,12 +72,6 @@ fn perform_initial_sync(ctx: Sender) { let _ = ctx.clone().send(Command::UpdateInstalledPackages); } -fn start_event_broadcasting(broadcast: Broadcast) { - spawn_thread!("Event Broadcasting", { - broadcast.start(); - }); -} - fn main() { setup_logging(); let config = build_config(); @@ -113,28 +81,49 @@ fn main() { let (wtx, wrx): (Sender, Receiver) = channel(); let mut broadcast: Broadcast = Broadcast::new(erx); - // Must subscribe to the signal before spawning ANY other threads - let signals = chan_signal::notify(&[Signal::INT, Signal::TERM]); + crossbeam::scope(|scope| { + // Must subscribe to the signal before spawning ANY other threads + let signals = chan_signal::notify(&[Signal::INT, Signal::TERM]); + let sig_ctx = ctx.clone(); + scope.spawn(move || spawn_signal_handler(signals, sig_ctx)); - spawn_auto_acceptor(broadcast.subscribe(), ctx.clone()); - spawn_update_poller(ctx.clone(), config.clone()); - spawn_signal_handler(signals, ctx.clone()); + let sync_ctx = ctx.clone(); + scope.spawn(move || perform_initial_sync(sync_ctx)); - perform_initial_sync(ctx.clone()); - spawn_command_forwarder(crx, wtx.clone()); - spawn_global_interpreter(config.clone(), wrx, etx.clone()); + let poll_ctx = ctx.clone(); + let poll_cfg = config.clone(); + scope.spawn(move || spawn_update_poller(poll_ctx, poll_cfg)); - Websocket::run(wtx.clone(), broadcast.subscribe()); - if config.test.http { - Http::run(wtx.clone(), broadcast.subscribe()); - } - if config.test.looping { - println!("Ota Plus Client REPL started."); - Console::run(wtx.clone(), broadcast.subscribe()); - } + let cmd_wtx = wtx.clone(); + scope.spawn(move || spawn_command_forwarder(crx, cmd_wtx)); + + let acc_sub = broadcast.subscribe(); + let acc_ctx = ctx.clone(); + scope.spawn(move || AutoAcceptor::run(&mut (), acc_sub, acc_ctx)); - start_event_broadcasting(broadcast); - thread::sleep(Duration::from_secs(60000000)); + let glob_cfg = config.clone(); + let glob_etx = etx.clone(); + scope.spawn(move || spawn_global_interpreter(glob_cfg, wrx, glob_etx)); + + let ws_wtx = wtx.clone(); + let ws_sub = broadcast.subscribe(); + scope.spawn(move || Websocket::run(ws_wtx, ws_sub)); + + if config.test.http { + let http_wtx = wtx.clone(); + let http_sub = broadcast.subscribe(); + scope.spawn(move || Http::run(http_wtx, http_sub)); + } + + if config.test.looping { + println!("OTA Plus Client REPL started."); + let cons_wtx = wtx.clone(); + let cons_sub = broadcast.subscribe(); + scope.spawn(move || Console::run(cons_wtx, cons_sub)); + } + + scope.spawn(move || broadcast.start()); + }); } fn setup_logging() { -- cgit v1.2.1 From 7acf9f5f89f7c361d5754adfdfb00889c1fb594a Mon Sep 17 00:00:00 2001 From: Alex Humphreys Date: Wed, 25 May 2016 11:01:02 +0200 Subject: Fix dpkg query --- src/package_manager/dpkg.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/package_manager/dpkg.rs b/src/package_manager/dpkg.rs index 99cff94..f50eaec 100644 --- a/src/package_manager/dpkg.rs +++ b/src/package_manager/dpkg.rs @@ -4,7 +4,7 @@ use datatype::{Error, Package, UpdateResultCode}; pub fn installed_packages() -> Result, Error> { - Command::new("dpkg-query").arg("-f").arg("${Package} ${Version}\n").arg("-W") + Command::new("dpkg-query").arg("-f='${Package} ${Version}\n'").arg("-W") .output() .map_err(|e| Error::PackageError(format!("Error fetching packages: {}", e))) .and_then(|c| { -- cgit v1.2.1 From 971a24165a4836ccfaa48af5ca5b380ead24389e Mon Sep 17 00:00:00 2001 From: Shaun Taheri Date: Wed, 25 May 2016 11:44:33 +0200 Subject: Fix endpoint reference for building on Yocto 1.7.0 --- src/ota_plus.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/ota_plus.rs b/src/ota_plus.rs index e17e446..0b207ab 100644 --- a/src/ota_plus.rs +++ b/src/ota_plus.rs @@ -11,11 +11,12 @@ use http_client::{Auth, HttpClient, HttpRequest, HttpResponse}; fn vehicle_updates_endpoint(config: &Config, path: &str) -> Url { - config.ota.server.join(& if path.is_empty() { + let endpoint = if path.is_empty() { format!("/api/v1/vehicle_updates/{}", config.auth.vin) } else { format!("/api/v1/vehicle_updates/{}/{}", config.auth.vin, path) - }).unwrap() + }; + config.ota.server.join(&endpoint).unwrap() } pub fn download_package_update(config: &Config, -- cgit v1.2.1 From 84cd55ba7a3ec550de4c328dcd524cd17bf5db2e Mon Sep 17 00:00:00 2001 From: Shaun Taheri Date: Wed, 25 May 2016 14:27:15 +0200 Subject: Pin bitflags version to 0.5.0 --- Cargo.lock | 4 ++-- Cargo.toml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 305a0be..b70d10a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -46,7 +46,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "bitflags" -version = "0.7.0" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] @@ -301,7 +301,7 @@ name = "openssl" version = "0.7.13" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "bitflags 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", + "bitflags 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", "gcc 0.3.28 (registry+https://github.com/rust-lang/crates.io-index)", "lazy_static 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", "libc 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", diff --git a/Cargo.toml b/Cargo.toml index 7644d6e..12ffc84 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,7 +18,7 @@ chan-signal = "0.1.5" crossbeam = "0.2.9" env_logger = "0.3.3" getopts = "0.2.14" -hyper = "0.9.5" +hyper = "0.9.6" log = "0.3.5" nom = "1.2.3" rustc-serialize = "0.3.18" -- cgit v1.2.1 From 7947277f686158d8f2027a24c679b74adedd182d Mon Sep 17 00:00:00 2001 From: Shaun Taheri Date: Wed, 25 May 2016 16:46:28 +0200 Subject: Fix pkg/Dockerfile for building --- pkg/Dockerfile | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/pkg/Dockerfile b/pkg/Dockerfile index b133114..8bb7866 100644 --- a/pkg/Dockerfile +++ b/pkg/Dockerfile @@ -1,10 +1,13 @@ -FROM debian:8.4 +FROM clux/muslrust -RUN apt-get update - -EXPOSE 8080 - -RUN apt-get install -y httpie jq gettext +RUN apt-get update && apt-get install -y \ + devscripts \ + dh-systemd \ + openssl \ + httpie \ + jq \ + gettext \ + && rm -rf /var/lib/apt/lists/* COPY ota_plus_client /usr/bin/ COPY ota.toml.template /etc/ @@ -12,5 +15,7 @@ COPY provision/start-up.sh /usr/bin/ COPY provision/auth.json /etc/ ENV LANG="en_GB.UTF-8" +WORKDIR /build +EXPOSE 8888 CMD start-up.sh -- cgit v1.2.1 From 43ac3907c920c39b62d813aaf5ed7453193a94e5 Mon Sep 17 00:00:00 2001 From: Txus Date: Wed, 25 May 2016 11:11:38 +0200 Subject: Update dependencies and fix compile --- Cargo.toml | 2 +- src/configuration/configuration.rs | 2 +- src/genivi/sc.rs | 4 +++- src/remote/svc.rs | 4 ++-- 4 files changed, 7 insertions(+), 5 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 9625ce5..e0fbe04 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,7 +18,7 @@ log = "*" env_logger = "*" rust-crypto = "*" toml = "*" -dbus = "0.1.2" +dbus = "*" getopts = "*" [dev-dependencies] diff --git a/src/configuration/configuration.rs b/src/configuration/configuration.rs index c2c0416..8864267 100644 --- a/src/configuration/configuration.rs +++ b/src/configuration/configuration.rs @@ -28,7 +28,7 @@ impl Configuration { /// * `path`: Path to the location of the configuration file. pub fn read(path: &str) -> Result { let path = PathBuf::from(path); - let mut f = try!(OpenOptions::new().open(path).map_err(stringify)); + let mut f = try!(OpenOptions::new().read(true).open(path).map_err(stringify)); let mut buf = Vec::new(); try!(f.read_to_end(&mut buf).map_err(stringify)); let data = try!(String::from_utf8(buf).map_err(stringify)); diff --git a/src/genivi/sc.rs b/src/genivi/sc.rs index 7c86edc..64ccc74 100644 --- a/src/genivi/sc.rs +++ b/src/genivi/sc.rs @@ -123,4 +123,6 @@ impl Receiver { } } -fn get_sender(msg: &Message) -> Option { msg.sender() } +fn get_sender(msg: &Message) -> Option { + msg.sender().map(|s| s.to_string()) +} diff --git a/src/remote/svc.rs b/src/remote/svc.rs index 873cb80..d2f5c03 100644 --- a/src/remote/svc.rs +++ b/src/remote/svc.rs @@ -7,7 +7,7 @@ use std::ops::Deref; use std::sync::{Arc, Mutex}; use std::sync::mpsc::Sender; use std::thread; -use std::thread::sleep_ms; +use std::time::Duration; use rustc_serialize::{json, Decodable}; use time; @@ -207,7 +207,7 @@ impl ServiceHandler { pub fn start_timer(transfers: &Mutex, timeout: i64) { loop { - sleep_ms(1000); + thread::sleep(Duration::from_secs(1)); let mut transfers = transfers.lock().unwrap(); transfers.prune(time::get_time().sec, timeout); } -- cgit v1.2.1 From 6c40738744e6ddaec4409977bdcb1111629b4192 Mon Sep 17 00:00:00 2001 From: "Josep M. 'Txus' Bach" Date: Wed, 25 May 2016 18:05:59 +0200 Subject: Add remote interface to SOTA Server over HTTP - Add src/remote/upstream.rs - Add src/remote/http/ - Update src/genivi/ - Update README --- Cargo.toml | 1 + README.md | 43 +++---- client.toml | 8 ++ src/configuration/client.rs | 4 +- src/configuration/common.rs | 20 ++++ src/configuration/configuration.rs | 5 + src/configuration/mod.rs | 2 + src/configuration/server.rs | 37 ++++++ src/event/outbound.rs | 6 + src/genivi/start.rs | 45 +++++--- src/lib.rs | 1 + src/remote/http/api_client.rs | 133 +++++++++++++++++++++ src/remote/http/datatype.rs | 229 +++++++++++++++++++++++++++++++++++++ src/remote/http/http_client.rs | 100 ++++++++++++++++ src/remote/http/hyper.rs | 135 ++++++++++++++++++++++ src/remote/http/mod.rs | 10 ++ src/remote/http/remote.rs | 50 ++++++++ src/remote/http/update_poller.rs | 31 +++++ src/remote/mod.rs | 2 + src/remote/svc.rs | 17 ++- src/remote/upstream.rs | 8 ++ 21 files changed, 840 insertions(+), 47 deletions(-) create mode 100644 src/configuration/server.rs create mode 100644 src/remote/http/api_client.rs create mode 100644 src/remote/http/datatype.rs create mode 100644 src/remote/http/http_client.rs create mode 100644 src/remote/http/hyper.rs create mode 100644 src/remote/http/mod.rs create mode 100644 src/remote/http/remote.rs create mode 100644 src/remote/http/update_poller.rs create mode 100644 src/remote/upstream.rs diff --git a/Cargo.toml b/Cargo.toml index e0fbe04..b68c143 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -20,6 +20,7 @@ rust-crypto = "*" toml = "*" dbus = "*" getopts = "*" +tempfile = "*" [dev-dependencies] rand = "*" diff --git a/README.md b/README.md index ecd9092..242b534 100644 --- a/README.md +++ b/README.md @@ -1,27 +1,14 @@ # sota-client -This is the client (in-vehicle) portion of the SOTA project. It is provided as an RPM that can be installed on a target system. See the [main SOTA Server project](https://github.com/advancedtelematic/rvi_sota_server) and [associated architecture document](http://advancedtelematic.github.io/rvi_sota_server/dev/architecture.html) for more information. +This is the client (in-vehicle) portion of the SOTA project. See the [main SOTA Server project](https://github.com/advancedtelematic/rvi_sota_server) and [associated architecture document](http://advancedtelematic.github.io/rvi_sota_server/dev/architecture.html) for more information. ## Building and running -To see the SOTA client in action, you will need some supporting components running. The general steps are: +To see the SOTA client in action, you will need to first build and run the [SOTA Server](https://github.com/advancedtelematic/rvi_sota_server). -1. Build and run RVI server and client nodes -2. Build and run rvi_sota_client -3. Build and run rvi_sota_demo +### Building SOTA Client -### Building and running RVI nodes - -You can build RVI directly from [its GitHub repo](https://github.com/PDXostc/rvi_core), or simply run our docker image. These instructions assume you are running the docker image. - -1. Pull the image: `docker pull advancedtelematic/rvi`. -2. In two terminal windows, run the rvi client and server nodes - * Client: `docker run -it --name rvi-client --expose 8901 --expose 8905-8908 -p 8901:8901 -p 8905:8905 -p 8906:8906 -p 8907:8907 -p 8908:8908 advancedtelematic/rvi client` - * Server: `docker run -it --name rvi-server --expose 8801 --expose 8805-8808 -p 8801:8801 -p 8805:8805 -p 8806:8806 -p 8807:8807 -p 8808:8808 advancedtelematic/rvi server` - -### Building and running SOTA client - -The SOTA client builds as a docker container. As long as you have `rust` and `cargo` installed, `make docker` should build a docker image called `sota-client`. +As long as you have `rust 1.8.0` and `cargo` installed, `cargo build` should build the `sota_client` executable in `target/debug`. You can also build the SOTA client from within a docker container; this will be necessary if your build environment is not running linux. From the project root, run `docker run -it --rm -v $PWD:/build advancedtelematic/rust:1.2.0 /bin/bash`. Once you are at a bash prompt, run the following commands: @@ -31,13 +18,27 @@ cd /build cargo build --release exit ``` -Now you can run `make docker` from your normal build environment. -Once the sota-client docker image is built (by either of the two methods above), you can run it with `docker run -it --name sota-client -p 9000:9000 --link rvi-client:rvi-client -e RUST_LOG=info advancedtelematic/sota-client`. +### Running SOTA Client + +You can run the client with `target/debug/sota_client -c client.toml`. It will try to connect to the `core` service of `rvi_sota_server` specified in the `[server]` section of `client.toml`. + +### Running with RVI nodes + +To connect to the SOTA Server over RVI, run the `rvi_sota_server` project with RVI Nodes. + +You can build RVI directly from [its GitHub repo](https://github.com/GENIVI/rvi_core), or simply run our docker image. These instructions assume you are running the docker image. + +1. Pull the image: `docker pull advancedtelematic/rvi`. +2. In two terminal windows, run the rvi client and server nodes + * Client: `docker run -it --name rvi-client --expose 8901 --expose 8905-8908 -p 8901:8901 -p 8905:8905 -p 8906:8906 -p 8907:8907 -p 8908:8908 advancedtelematic/rvi client` + * Server: `docker run -it --name rvi-server --expose 8801 --expose 8805-8808 -p 8801:8801 -p 8805:8805 -p 8806:8806 -p 8807:8807 -p 8808:8808 advancedtelematic/rvi server` + +Now you can remove the `[server]` section from `client.toml`. -### Run the demo +### Running with GENIVI Software Loading Manager -To watch the client in action, you can run a demo with a dummy server. Clone the [rvi_sota_demo](https://github.com/PDXostc/rvi_sota_demo) project, then run `python sota_server.py http://:8801`. +You can run the (GENIVI SWLM)[https://github.com/GENIVI/genivi_swm] to process the incoming update. You will need to run both the SWLM and SOTA Client as root to communicate over the DBus session. ### Documentation diff --git a/client.toml b/client.toml index 297690b..7c1eb79 100644 --- a/client.toml +++ b/client.toml @@ -4,6 +4,14 @@ rvi_url = "http://127.0.0.1:8901" edge_url = "127.0.0.1:9080" timeout = 20 vin_match = 2 +http = "true" + +[server] +url = "http://127.0.0.1:8080" +polling_interval = 10 +vin = "V1234567890123456" +packages_dir = "/tmp/" +packages_extension = "deb" [dbus] name = "org.genivi.SotaClient" diff --git a/src/configuration/client.rs b/src/configuration/client.rs index 625f4e9..cf54621 100644 --- a/src/configuration/client.rs +++ b/src/configuration/client.rs @@ -45,6 +45,7 @@ impl ConfTreeParser for ClientConfiguration { #[cfg(test)] static EDGE: &'static str = "localhost:9080"; #[cfg(test)] static TIMEOUT: i64 = 10; #[cfg(test)] static VIN: i32 = 3; +#[cfg(test)] static HTTP: bool = false; #[cfg(test)] pub fn gen_valid_conf() -> String { @@ -55,7 +56,8 @@ pub fn gen_valid_conf() -> String { edge_url = "{}" timeout = {} vin_match = {} - "#, STORAGE, RVI, EDGE, TIMEOUT, VIN) + http = {} + "#, STORAGE, RVI, EDGE, TIMEOUT, VIN, HTTP) } #[cfg(test)] diff --git a/src/configuration/common.rs b/src/configuration/common.rs index e6b0fcd..aac2430 100644 --- a/src/configuration/common.rs +++ b/src/configuration/common.rs @@ -3,6 +3,7 @@ use toml; use std::result; use std::fmt; +use remote::http::Url; /// `Result` type used throughout the configuration parser. pub type Result = result::Result; @@ -87,6 +88,25 @@ impl ParseTomlValue for i64 { } } +impl ParseTomlValue for bool { + fn parse(val: &toml::Value, key: &str, group: &str) + -> Result { + val.as_str().map(|s| s == "true") + .ok_or(format!("Key \"{}\" in \"{}\" is not a string", key, group)) + } +} + +impl ParseTomlValue for Url { + fn parse(val: &toml::Value, key: &str, group: &str) + -> Result { + val.as_str() + .ok_or(format!("Key \"{}\" in \"{}\" is not a string", key, group)) + .and_then(|s| Url::parse(s).map_err(|_| { + format!("Key \"{}\" in \"{}\" is not a valid URL", key, group) + })) + } +} + /// Helper function to format a `toml::Parser` error message to the format used in this /// implementation. This is only safe to call if the `parser` is associated with a *real* file on /// disk. diff --git a/src/configuration/configuration.rs b/src/configuration/configuration.rs index 8864267..8643772 100644 --- a/src/configuration/configuration.rs +++ b/src/configuration/configuration.rs @@ -8,6 +8,7 @@ use std::env; use super::common::{ConfTreeParser, format_parser_error, stringify, Result}; use super::client::ClientConfiguration; +use super::server::ServerConfiguration; use super::dbus::DBusConfiguration; /// Type to encode the full configuration. @@ -15,6 +16,8 @@ use super::dbus::DBusConfiguration; pub struct Configuration { /// The `client` section of the configuration pub client: ClientConfiguration, + /// The `server` section of the configuration + pub server: Option, /// The `dbus` section of the configuration pub dbus: DBusConfiguration } @@ -44,10 +47,12 @@ impl Configuration { let tree = try!(parser.parse().ok_or(format_parser_error(&parser))); let client = try!(ClientConfiguration::parse(&tree)); + let server = try!(ServerConfiguration::parse(&tree)); let dbus = try!(DBusConfiguration::parse(&tree)); Ok(Configuration { client: client, + server: server, dbus: dbus }) } diff --git a/src/configuration/mod.rs b/src/configuration/mod.rs index e2a986f..f394327 100644 --- a/src/configuration/mod.rs +++ b/src/configuration/mod.rs @@ -5,8 +5,10 @@ mod configuration; mod common; mod client; +mod server; mod dbus; pub use self::configuration::Configuration; pub use self::client::ClientConfiguration; +pub use self::server::ServerConfiguration; pub use self::dbus::DBusConfiguration; diff --git a/src/configuration/server.rs b/src/configuration/server.rs new file mode 100644 index 0000000..eadee9d --- /dev/null +++ b/src/configuration/server.rs @@ -0,0 +1,37 @@ +//! Handles the `server` section of the configuration file. + +use toml; + +use super::common::{get_required_key, ConfTreeParser, Result}; +use remote::http::Url; + +/// Type to encode allowed keys for the `server` section of the configuration. +#[derive(Clone)] +pub struct ServerConfiguration { + pub url: Url, + pub polling_interval: i64, + pub vin: String, + pub packages_dir: String, + pub packages_extension: String +} + +impl ConfTreeParser> for ServerConfiguration { + fn parse(tree: &toml::Table) -> Result> { + tree.get("server").map_or_else(|| Ok(None), |server_tree| { + let url = try!(get_required_key(server_tree, "url", "server")); + let polling_interval = try!(get_required_key(server_tree, "polling_interval", "server")); + let vin = try!(get_required_key(server_tree, "vin", "server")); + let packages_dir = try!(get_required_key(server_tree, "packages_dir", "server")); + let packages_extension = try!(get_required_key(server_tree, "packages_extension", "server")); + + Ok(Some(ServerConfiguration { + url: url, + polling_interval: polling_interval, + vin: vin, + packages_dir: packages_dir, + packages_extension: packages_extension + })) + }) + } +} + diff --git a/src/event/outbound.rs b/src/event/outbound.rs index 4fb6fce..0a4250f 100644 --- a/src/event/outbound.rs +++ b/src/event/outbound.rs @@ -61,6 +61,12 @@ impl UpdateReport { } } +#[derive(RustcEncodable, Clone)] +pub struct UpdateResult { + pub vin: String, + pub update_report: UpdateReport +} + pub enum OutBoundEvent { InitiateDownload(UpdateId), AbortDownload(UpdateId), diff --git a/src/genivi/start.rs b/src/genivi/start.rs index 55bef27..23f6caf 100644 --- a/src/genivi/start.rs +++ b/src/genivi/start.rs @@ -1,7 +1,7 @@ //! Main loop, starting the worker threads and wiring up communication channels between them. use std::sync::{Arc, Mutex}; -use std::sync::mpsc::{channel, Receiver}; +use std::sync::mpsc::{channel, Receiver, Sender}; use std::thread; use configuration::Configuration; @@ -9,10 +9,14 @@ use configuration::DBusConfiguration; use event::Event; use event::inbound::InboundEvent; use event::outbound::OutBoundEvent; +use remote::http::remote::HttpRemote; +use remote::http::hyper::Hyper; +use remote::http::update_poller; use remote::svc::{RemoteServices, ServiceHandler}; use remote::rvi; +use remote::upstream::Upstream; -pub fn handle(cfg: &DBusConfiguration, rx: Receiver, remote_svcs: Arc>) { +pub fn handle(cfg: &DBusConfiguration, rx: Receiver, upstream: Arc>) { loop { match rx.recv().unwrap() { Event::Inbound(i) => match i { @@ -28,25 +32,32 @@ pub fn handle(cfg: &DBusConfiguration, rx: Receiver, remote_svcs: Arc match o { OutBoundEvent::InitiateDownload(e) => { info!("InitiateDownload"); - let _ = remote_svcs.lock().unwrap().send_start_download(e); + let _ = upstream.lock().unwrap().send_start_download(e); }, OutBoundEvent::AbortDownload(_) => info!("AbortDownload"), OutBoundEvent::UpdateReport(e) => { info!("UpdateReport"); - let _ = remote_svcs.lock().unwrap().send_update_report(e); + let _ = upstream.lock().unwrap().send_update_report(e); } } } } } +fn dbus_handler(conf: &Configuration, tx: Sender, rx: Receiver, upstream: Arc>) { + let dbus_receiver = super::sc::Receiver::new(conf.dbus.clone(), tx); + thread::spawn(move || dbus_receiver.start()); + handle(&conf.dbus, rx, upstream); +} + + /// Main loop, starting the worker threads and wiring up communication channels between them. /// /// # Arguments @@ -57,16 +68,20 @@ pub fn handle(cfg: &DBusConfiguration, rx: Receiver, remote_svcs: Arc, Receiver) = channel(); - // RVI edge handler - let remote_svcs = Arc::new(Mutex::new(RemoteServices::new(rvi_url.clone()))); - let handler = ServiceHandler::new(tx.clone(), remote_svcs.clone(), conf.client.clone()); - let rvi_edge = rvi::ServiceEdge::new(rvi_url.clone(), edge_url, handler); - rvi_edge.start(); + if let Some(ref srv_cfg) = conf.server { + // HTTP handler + update_poller::start(srv_cfg.clone(), tx.clone()); + let upstream = Arc::new(Mutex::new(HttpRemote::new(srv_cfg.clone(), Hyper::new(), tx.clone()))); + dbus_handler(&conf, tx.clone(), rx, upstream); + } else { + // RVI edge handler + let remote_svcs = Arc::new(Mutex::new(RemoteServices::new(rvi_url.clone()))); + let handler = ServiceHandler::new(tx.clone(), remote_svcs.clone(), conf.client.clone()); + let rvi_edge = rvi::ServiceEdge::new(rvi_url.clone(), edge_url, handler); + rvi_edge.start(); - // DBUS handler - let dbus_receiver = super::sc::Receiver::new(conf.dbus.clone(), tx); - thread::spawn(move || dbus_receiver.start()); - handle(&conf.dbus, rx, remote_svcs); + dbus_handler(&conf, tx.clone(), rx, remote_svcs); + } } diff --git a/src/lib.rs b/src/lib.rs index 62b445c..7165c40 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -10,6 +10,7 @@ extern crate url; extern crate crypto; extern crate toml; extern crate dbus; +extern crate tempfile; #[macro_use] extern crate log; extern crate env_logger; diff --git a/src/remote/http/api_client.rs b/src/remote/http/api_client.rs new file mode 100644 index 0000000..b493710 --- /dev/null +++ b/src/remote/http/api_client.rs @@ -0,0 +1,133 @@ +//! Communication with the Sota HTTP server + +use rustc_serialize::json; +use std::fs::File; +use std::path::PathBuf; +use std::io::Write; + +use event::inbound::UpdateAvailable; +use event::outbound::{UpdateReport, UpdateResult, InstalledPackage}; + +use configuration::ServerConfiguration; + +use super::datatype::{UpdateRequestId, Url, Error}; + +use super::{Auth, HttpClient, HttpRequest, HttpResponse}; + +fn vehicle_updates_endpoint(config: &ServerConfiguration, path: &str) -> Url { + config.url.join(& if path.is_empty() { + format!("/api/v1/vehicle_updates/{}", &config.vin) + } else { + format!("/api/v1/vehicle_updates/{}/{}", &config.vin, path) + }).unwrap() +} + +pub fn download_package_update(config: &ServerConfiguration, + client: &mut HttpClient, + id: &UpdateRequestId) -> Result { + + let req = HttpRequest::get( + vehicle_updates_endpoint(config, &format!("{}/download", id)), + (None as Option), + ); + + let mut path = PathBuf::new(); + path.push(&config.packages_dir); + path.push(id); + path.set_extension(&config.packages_extension); + + let mut file = try!(File::create(path.as_path())); + let resp = try!(client.send_request(&req)); + let _ = file.write(resp.body.as_ref()); + Ok(path) + +} + +pub fn send_install_report(config: &ServerConfiguration, + client: &mut HttpClient, + report: &UpdateReport) -> Result<(), Error> { + + let report_with_vin = UpdateResult { vin: config.vin.clone(), update_report: report.clone() }; + let json = try!(json::encode(&report_with_vin)); + + let req = HttpRequest::post( + vehicle_updates_endpoint(config, &format!("{}", report.update_id)), + (None as Option), + Some(json) + ); + + let _: HttpResponse = try!(client.send_request(&req)); + + Ok(()) + +} + +#[derive(Clone, PartialEq, Eq, Debug, RustcEncodable, RustcDecodable)] +#[allow(non_snake_case)] +struct PendingUpdateRequest { + pub requestId: UpdateRequestId, + pub installPos: i32, + pub packageId: Package, + pub createdAt: String +} + +use std::fmt::{Display, Formatter, Result as FmtResult}; + +#[derive(Debug, PartialEq, Eq, RustcEncodable, RustcDecodable, Clone)] +struct Package { + pub name: String, + pub version: String +} + +impl Display for Package { + fn fmt(&self, f: &mut Formatter) -> FmtResult { + write!(f, "{} {}", self.name, self.version) + } +} + +pub fn get_package_updates(config: &ServerConfiguration, + client: &mut HttpClient) -> Result, Error> { + + let req = HttpRequest::get( + vehicle_updates_endpoint(&config, ""), + (None as Option) + ); + + let resp = try!(client.send_request(&req)); + let body = try!(String::from_utf8(resp.body)); + + let req = try!(json::decode::>(&body)); + + let events: Vec = req.iter().map(move |r| { + let r2 = r.clone(); + UpdateAvailable { + update_id: r2.requestId, + signature: "signature".to_string(), + description: format!("{}", r2.packageId), + request_confirmation: false, + size: 32 + } + }).collect(); + + Ok(events) +} + +// XXX: Remove in favour of update_installed_packages()? +pub fn update_packages(config: &ServerConfiguration, + client: &mut HttpClient, + pkgs: &Vec) -> Result<(), Error> { + + let json = try!(json::encode(&pkgs)); + + debug!("update_packages, json: {}", json); + + let req = HttpRequest::put( + vehicle_updates_endpoint(config, "installed"), + (None as Option), + Some(json), + ); + + let _: HttpResponse = try!(client.send_request(&req)); + + Ok(()) +} diff --git a/src/remote/http/datatype.rs b/src/remote/http/datatype.rs new file mode 100644 index 0000000..6b2e1e6 --- /dev/null +++ b/src/remote/http/datatype.rs @@ -0,0 +1,229 @@ +use std::borrow::Cow; +use hyper::method; + +use event::UpdateId; + +#[derive(Clone, PartialEq, Eq, Debug, RustcEncodable, RustcDecodable)] +pub struct ClientId { + pub get: String, +} + +#[derive(Clone, PartialEq, Eq, Debug, RustcEncodable, RustcDecodable)] +pub struct ClientSecret { + pub get: String, +} + +#[derive(Clone, PartialEq, Eq, Debug, RustcEncodable, RustcDecodable)] +pub struct ClientCredentials { + pub id: ClientId, + pub secret: ClientSecret, +} + +pub type UpdateRequestId = UpdateId; + + +#[derive(RustcDecodable, Debug, PartialEq, Clone, Default)] +pub struct AccessToken { + pub access_token: String, + pub token_type: String, + pub expires_in: i32, + pub scope: Vec +} + +impl<'a> Into> for AccessToken { + fn into(self) -> Cow<'a, AccessToken> { + Cow::Owned(self) + } +} + +use std::convert::From; +use std::fmt::{Display, Formatter, Result as FmtResult}; +use std::io::Error as IoError; +use std::string::FromUtf8Error; +use std::sync::PoisonError; +use toml::{ParserError as TomlParserError, DecodeError as TomlDecodeError}; +use url::ParseError as UrlParseError; + +use rustc_serialize::json::{EncoderError as JsonEncoderError, DecoderError as JsonDecoderError}; +use hyper::error::Error as HyperError; + + +#[derive(Debug)] +pub enum Error { + ClientError(String), + Command(String), + FromUtf8Error(FromUtf8Error), + HyperError(HyperError), + IoError(IoError), + JsonDecoderError(JsonDecoderError), + JsonEncoderError(JsonEncoderError), + PoisonError(String), + PackageError(String), + ParseError(String), + TomlParserErrors(Vec), + TomlDecodeError(TomlDecodeError), + UrlParseError(UrlParseError), +} + +impl From> for Error { + fn from(e: PoisonError) -> Error { + Error::PoisonError(format!("{}", e)) + } +} + +impl From> for Error { + fn from(e: Vec) -> Error { + Error::TomlParserErrors(e) + } +} + +// To derive From implementations for the other errors we use the +// following macro. +macro_rules! derive_from { + ([ $( $error: ident ),* ]) => + { + $( + impl From<$error> for Error { + fn from(e: $error) -> Error { + Error::$error(e) + } + } + )* + } +} + +derive_from!( + [ JsonEncoderError + , JsonDecoderError + , HyperError + , FromUtf8Error + , IoError + , UrlParseError + , TomlDecodeError + ]); + + +impl Display for Error { + fn fmt(&self, f: &mut Formatter) -> FmtResult { + let inner: String = match *self { + Error::ClientError(ref s) => format!("Http client error: {}", s.clone()), + Error::Command(ref e) => format!("Unknown Command: {}", e.clone()), + Error::FromUtf8Error(ref e) => format!("From utf8 error: {}", e.clone()), + Error::HyperError(ref e) => format!("Hyper error: {}", e.clone()), + Error::IoError(ref e) => format!("IO error: {}", e.clone()), + Error::JsonDecoderError(ref e) => format!("Failed to decode JSON: {}", e.clone()), + Error::JsonEncoderError(ref e) => format!("Failed to encode JSON: {}", e.clone()), + Error::PoisonError(ref e) => format!("Poison error, {}", e.clone()), + Error::PackageError(ref s) => s.clone(), + Error::ParseError(ref s) => s.clone(), + Error::TomlDecodeError(ref e) => format!("Toml decode error: {}", e.clone()), + Error::TomlParserErrors(ref e) => format!("Toml parser errors: {:?}", e.clone()), + Error::UrlParseError(ref s) => format!("Url parse error: {}", s.clone()), + }; + write!(f, "{}", inner) + } +} + +#[macro_export] +macro_rules! exit { + ($fmt:expr) => ({ + print!(concat!($fmt, "\n")); + std::process::exit(1); + }); + ($fmt:expr, $($arg:tt)*) => ({ + print!(concat!($fmt, "\n"), $($arg)*); + std::process::exit(1); + }) +} + + + +#[derive(Clone)] +pub enum Method { + Get, + Post, + Put, +} + +impl ToString for Method { + fn to_string(&self) -> String { + match *self { + Method::Get => "GET".to_string(), + Method::Post => "POST".to_string(), + Method::Put => "PUT".to_string(), + } + } +} + +impl Into for Method { + fn into(self) -> method::Method { + match self { + Method::Get => method::Method::Get, + Method::Post => method::Method::Post, + Method::Put => method::Method::Put, + } + } +} + +impl<'a> Into> for Method { + fn into(self) -> Cow<'a, Method> { + Cow::Owned(self) + } +} + +use hyper::client::IntoUrl; +use hyper; +use rustc_serialize::{Decoder, Decodable}; +use url::ParseError; +use url; + +#[derive(PartialEq, Eq, Clone, Debug)] +pub struct Url { + get: url::Url +} + +impl Url { + + pub fn parse(s: &str) -> Result { + let url = try!(url::Url::parse(s)); + Ok(Url { get: url }) + } + + pub fn join(&self, suf: &str) -> Result { + let url = try!(self.get.join(suf)); + Ok(Url { get: url }) + } + +} + +impl IntoUrl for Url { + + fn into_url(self) -> Result { + Ok(self.get) + } + +} + +impl<'a> Into> for Url { + fn into(self) -> Cow<'a, Url> { + Cow::Owned(self) + } +} + + +impl ToString for Url { + + fn to_string(&self) -> String { + self.get.to_string() + } + +} + +impl Decodable for Url { + + fn decode(d: &mut D) -> Result { + let s = try!(d.read_str()); + Url::parse(&s) + .map_err(|e| d.error(&e.to_string())) + } +} diff --git a/src/remote/http/http_client.rs b/src/remote/http/http_client.rs new file mode 100644 index 0000000..430e2a4 --- /dev/null +++ b/src/remote/http/http_client.rs @@ -0,0 +1,100 @@ +use std::borrow::Cow; + +use super::datatype::{AccessToken, ClientId, ClientSecret, Error, Method, Url}; + +#[derive(Clone)] +pub enum Auth<'a> { + Credentials(ClientId, ClientSecret), + Token(&'a AccessToken), +} + +impl<'a> Into>> for Auth<'a> { + fn into(self) -> Cow<'a, Auth<'a>> { + Cow::Owned(self) + } +} + +pub struct HttpRequest<'a> { + pub method: Cow<'a, Method>, + pub url: Cow<'a, Url>, + pub auth: Option>>, + pub body: Option>, +} + +impl<'a> HttpRequest<'a> { + + fn new(meth: M, + url: U, + auth: Option, + body: Option) -> HttpRequest<'a> + where + M: Into>, + U: Into>, + A: Into>>, + B: Into> + { + HttpRequest { + method: meth.into(), + url: url.into(), + auth: auth.map(|a| a.into()), + body: body.map(|b| b.into()), + } + } + + pub fn get(url: U, auth: Option) -> HttpRequest<'a> + where + U: Into>, + A: Into>>, + { + HttpRequest::new::(Method::Get, url, auth, None) + } + + pub fn post(url: U, auth: Option, body: Option) -> HttpRequest<'a> + where + U: Into>, + A: Into>>, + B: Into> + { + HttpRequest::new(Method::Post, url, auth, body) + } + + pub fn put(url: U, auth: Option, body: Option) -> HttpRequest<'a> + where + U: Into>, + A: Into>>, + B: Into> + { + HttpRequest::new(Method::Put, url, auth, body) + } +} + +impl<'a> ToString for HttpRequest<'a> { + fn to_string(&self) -> String { + format!("{} {}", self.method.to_string(), self.url.to_string()) + } +} + +#[derive(RustcEncodable, RustcDecodable)] +pub enum HttpStatus { + Ok, +} + +impl ToString for HttpStatus { + fn to_string(&self) -> String { + match *self { + HttpStatus::Ok => "200".to_string() + } + } +} + +#[derive(RustcEncodable, RustcDecodable)] +pub struct HttpResponse { + pub status: HttpStatus, + pub body: Vec, +} + +pub trait HttpClient: Send + Sync { + + fn send_request(&mut self, req: &HttpRequest) -> Result; + +} diff --git a/src/remote/http/hyper.rs b/src/remote/http/hyper.rs new file mode 100644 index 0000000..d293849 --- /dev/null +++ b/src/remote/http/hyper.rs @@ -0,0 +1,135 @@ +use hyper::Client; +use hyper::client::RedirectPolicy; +use hyper::client::response::Response; +use hyper::header::{Authorization, Basic, Bearer, ContentType, Headers, Location}; +use hyper::mime::{Attr, Mime, TopLevel, SubLevel, Value}; +use std::io::Read; + +use super::{Auth, HttpClient, HttpRequest, HttpResponse, HttpStatus}; +use super::datatype::Error; + +pub struct Hyper { + client: Client, +} + +impl Hyper { + pub fn new() -> Hyper { + let mut client = Client::new(); + client.set_redirect_policy(RedirectPolicy::FollowNone); + Hyper { client: client } + } +} + +impl HttpClient for Hyper { + + fn send_request(&mut self, req: &HttpRequest) -> Result { + debug!("send_request, request: {}", req.to_string()); + + let mut headers = Headers::new(); + let mut req_body = String::new(); + + match (req.auth.clone().map(|a| a.into_owned()), req.body.to_owned()) { + + (None, None) => {} + + (Some(Auth::Credentials(ref id, ref secret)), None) => { + + headers.set(Authorization(Basic { + username: id.get.clone(), + password: Some(secret.get.clone()) + })); + + headers.set(ContentType(Mime( + TopLevel::Application, + SubLevel::WwwFormUrlEncoded, + vec![(Attr::Charset, Value::Utf8)]))); + + req_body.push_str("grant_type=client_credentials") + + } + + (Some(Auth::Token(token)), body) => { + + headers.set(Authorization(Bearer { + token: token.access_token.clone() + })); + + if let Some(json) = body { + + headers.set(ContentType(Mime( + TopLevel::Application, + SubLevel::Json, + vec![(Attr::Charset, Value::Utf8)]))); + + req_body.push_str(&json) + } + + } + + (None, body) => { + + if let Some(json) = body { + + headers.set(ContentType(Mime( + TopLevel::Application, + SubLevel::Json, + vec![(Attr::Charset, Value::Utf8)]))); + + req_body.push_str(&json) + + } + + } + + _ => {} + + } + + debug!("send_request, headers: `{}`", headers); + debug!("send_request, req_body: `{}`", req_body); + + let mut resp = try!(self.client + .request(req.method.clone().into_owned().into(), + req.url.clone().into_owned()) + .headers(headers) + .body(&req_body) + .send()); + + if resp.status.is_success() { + let mut data = Vec::new(); + let _: usize = try!(resp.read_to_end(&mut data)); + Ok(HttpResponse { + status: HttpStatus::Ok, + body: data, + }) + } else if resp.status.is_redirection() { + let req = try!(relocate_request(req, &resp)); + self.send_request(&req) + } else { + let mut rbody = String::new(); + let _: usize = try!(resp.read_to_string(&mut rbody)); + Err(Error::ClientError(format!("Request errored with status {}, body: {}", resp.status, rbody))) + } + + } + +} + +fn relocate_request<'a>(req: &'a HttpRequest, resp: &Response) -> Result, Error> { + + if let Some(&Location(ref loc)) = resp.headers.get::() { + + let url = try!(req.url.join(loc)); + + Ok(HttpRequest { + url: url.into(), + method: req.method.clone(), + auth: None, + body: req.body.clone(), + }) + + } else { + Err(Error::ClientError("Redirect with no Location header".to_string())) + } + +} diff --git a/src/remote/http/mod.rs b/src/remote/http/mod.rs new file mode 100644 index 0000000..96ced18 --- /dev/null +++ b/src/remote/http/mod.rs @@ -0,0 +1,10 @@ +pub use self::http_client::{Auth, HttpClient, HttpRequest, HttpResponse, HttpStatus}; +pub use self::hyper::Hyper; +pub use self::datatype::Url; + +pub mod http_client; +pub mod remote; +pub mod api_client; +pub mod hyper; +pub mod update_poller; +pub mod datatype; diff --git a/src/remote/http/remote.rs b/src/remote/http/remote.rs new file mode 100644 index 0000000..26a1158 --- /dev/null +++ b/src/remote/http/remote.rs @@ -0,0 +1,50 @@ +use std::sync::mpsc::Sender; +use remote::upstream::Upstream; +use configuration::ServerConfiguration; +use event::outbound::{InstalledSoftware, UpdateReport}; +use event::inbound::{DownloadComplete, InboundEvent}; +use event::Event; +use event::UpdateId; + +use super::HttpClient; +use super::api_client::{update_packages, download_package_update, send_install_report}; + +pub struct HttpRemote { + config: ServerConfiguration, + client: C, + tx: Sender +} + +impl HttpRemote { + pub fn new(config: ServerConfiguration, client: C, tx: Sender) -> HttpRemote { + HttpRemote { config: config, client: client, tx: tx } + } +} + +impl Upstream for HttpRemote { + fn send_installed_software(&mut self, m: InstalledSoftware) -> Result { + update_packages(&self.config, &mut self.client, &m.packages) + .map(|_| "ok".to_string()) + .map_err(|e| format!("{}", e)) + } + + fn send_start_download(&mut self, id: UpdateId) -> Result { + download_package_update(&self.config, &mut self.client, &id) + .map_err(|e| format!("{}", e)) + .and_then(|p| { + let path = p.to_str().unwrap().to_string(); + let event = Event::Inbound(InboundEvent::DownloadComplete(DownloadComplete { + update_id: id, + update_image: path, + signature: "signature".to_string() + })); + self.tx.send(event).map(|_| "ok".to_string()).map_err(|_| "send error".to_string()) + }) + } + + fn send_update_report(&mut self, m: UpdateReport) -> Result { + send_install_report(&self.config, &mut self.client, &m) + .map(|_| "ok".to_string()) + .map_err(|e| format!("{}", e)) + } +} diff --git a/src/remote/http/update_poller.rs b/src/remote/http/update_poller.rs new file mode 100644 index 0000000..54d29b0 --- /dev/null +++ b/src/remote/http/update_poller.rs @@ -0,0 +1,31 @@ +use super::api_client::get_package_updates; + +use std::sync::mpsc::Sender; +use std::thread; +use std::time::Duration; + +use configuration::ServerConfiguration; + +use event::inbound::InboundEvent; +use event::Event; + +use super::HttpClient; +use super::hyper::Hyper; + +pub fn start(config: ServerConfiguration, + tx: Sender) { + + thread::spawn(move || { + let mut c: &mut HttpClient = &mut Hyper::new(); + loop { + match get_package_updates(&config, c) { + Ok(updates) => + for update in updates { + let _ = tx.send(Event::Inbound(InboundEvent::UpdateAvailable(update))); + }, + Err(e) => error!("Can't get package updates: {:?}", e) + } + thread::sleep(Duration::from_secs(config.polling_interval as u64)); + } + }); +} diff --git a/src/remote/mod.rs b/src/remote/mod.rs index 42a2c7b..d399d43 100644 --- a/src/remote/mod.rs +++ b/src/remote/mod.rs @@ -3,3 +3,5 @@ mod jsonrpc; mod parm; pub mod rvi; pub mod svc; +pub mod upstream; +pub mod http; diff --git a/src/remote/svc.rs b/src/remote/svc.rs index d2f5c03..813f641 100644 --- a/src/remote/svc.rs +++ b/src/remote/svc.rs @@ -14,7 +14,8 @@ use time; use event::{Event, UpdateId}; use event::inbound::InboundEvent; -use event::outbound::{UpdateReport, InstalledSoftware}; +use event::outbound::{UpdateReport, InstalledSoftware, UpdateResult}; +use super::upstream::Upstream; use super::parm::{NotifyParams, StartParams, ChunkParams, ChunkReceived, FinishParams}; use super::parm::{ReportParams, AbortParams, ParamHandler}; @@ -73,12 +74,6 @@ struct StartDownload { services: LocalServices, } -#[derive(RustcEncodable, Clone)] -struct UpdateResult { - vin: String, - update_report: UpdateReport -} - #[derive(RustcEncodable, Clone)] struct InstalledSoftwareResult { vin: String, @@ -123,8 +118,10 @@ impl RemoteServices { update_id: id } } +} - pub fn send_start_download(&self, id: UpdateId) -> Result { +impl Upstream for RemoteServices { + fn send_start_download(&mut self, id: UpdateId) -> Result { self.svcs.iter().next().ok_or(format!("RemoteServices not set")) .and_then(|ref svcs| rvi::send_message( &self.url, @@ -132,7 +129,7 @@ impl RemoteServices { &svcs.start)) } - pub fn send_update_report(&self, m: UpdateReport) -> Result { + fn send_update_report(&mut self, m: UpdateReport) -> Result { self.svcs.iter().next().ok_or(format!("RemoteServices not set")) .and_then(|ref svcs| rvi::send_message( &self.url, @@ -142,7 +139,7 @@ impl RemoteServices { &svcs.report)) } - pub fn send_installed_software(&self, m: InstalledSoftware) -> Result { + fn send_installed_software(&mut self, m: InstalledSoftware) -> Result { self.svcs.iter().next().ok_or(format!("RemoteServices not set")) .and_then(|ref svcs| rvi::send_message( &self.url, diff --git a/src/remote/upstream.rs b/src/remote/upstream.rs new file mode 100644 index 0000000..5cb9069 --- /dev/null +++ b/src/remote/upstream.rs @@ -0,0 +1,8 @@ +use event::outbound::{InstalledSoftware, UpdateReport}; +use event::UpdateId; + +pub trait Upstream { + fn send_installed_software(&mut self, m: InstalledSoftware) -> Result; + fn send_start_download(&mut self, id: UpdateId) -> Result; + fn send_update_report(&mut self, m: UpdateReport) -> Result; +} -- cgit v1.2.1 From 001c305ce96097c10a7e261473571debe43047a7 Mon Sep 17 00:00:00 2001 From: Stevan Andjelkovic Date: Mon, 30 May 2016 14:27:16 +0200 Subject: Retry authenticating. --- src/interpreter.rs | 14 ++++++++++++++ src/main.rs | 7 ++++++- 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/src/interpreter.rs b/src/interpreter.rs index db9ac02..5bcd761 100644 --- a/src/interpreter.rs +++ b/src/interpreter.rs @@ -58,6 +58,20 @@ impl Interpreter<(), Event, Command> for AutoAcceptor { } } +pub struct AuthenticationRetrier; + +impl Interpreter<(), Event, Command> for AuthenticationRetrier { + fn interpret(_: &mut (), event: Event, ctx: Sender) { + match event { + Event::NotAuthenticated => { + info!("Trying to authenticate again"); + let _ = ctx.send(Command::Authenticate(None)); + } + _ => {} + } + } +} + pub struct GlobalInterpreter; diff --git a/src/main.rs b/src/main.rs index 3adeef2..602ac62 100644 --- a/src/main.rs +++ b/src/main.rs @@ -26,7 +26,8 @@ use libotaplus::http_client::Hyper; use libotaplus::interaction_library::{Console, Gateway, Http, Websocket}; use libotaplus::interaction_library::broadcast::Broadcast; use libotaplus::interaction_library::gateway::Interpret; -use libotaplus::interpreter::{AutoAcceptor, Env, GlobalInterpreter, Interpreter, Wrapped}; +use libotaplus::interpreter::{AuthenticationRetrier, AutoAcceptor, Env, + GlobalInterpreter, Interpreter, Wrapped}; use libotaplus::package_manager::PackageManager; @@ -101,6 +102,10 @@ fn main() { let acc_ctx = ctx.clone(); scope.spawn(move || AutoAcceptor::run(&mut (), acc_sub, acc_ctx)); + let auth_sub = broadcast.subscribe(); + let auth_ctx = ctx.clone(); + scope.spawn(move || AuthenticationRetrier::run(&mut (), auth_sub, auth_ctx)); + let glob_cfg = config.clone(); let glob_etx = etx.clone(); scope.spawn(move || spawn_global_interpreter(glob_cfg, wrx, glob_etx)); -- cgit v1.2.1 From 3f0841a331a4434af28d998e0cf96510368dc7f8 Mon Sep 17 00:00:00 2001 From: "Josep M. 'Txus' Bach" Date: Mon, 30 May 2016 16:55:09 +0200 Subject: Implement optional authentication --- README.md | 2 +- client.toml | 5 +++++ src/configuration/mod.rs | 2 +- src/configuration/server.rs | 42 +++++++++++++++++++++++++++++++++++++--- src/genivi/start.rs | 17 ++++++++++++++-- src/remote/http/api_client.rs | 16 +++++++++------ src/remote/http/auth.rs | 28 +++++++++++++++++++++++++++ src/remote/http/datatype.rs | 13 +++++++++++++ src/remote/http/http_client.rs | 22 ++++++++------------- src/remote/http/mod.rs | 5 +++-- src/remote/http/remote.rs | 12 +++++++----- src/remote/http/update_poller.rs | 5 ++++- 12 files changed, 134 insertions(+), 35 deletions(-) create mode 100644 src/remote/http/auth.rs diff --git a/README.md b/README.md index 242b534..8e2d766 100644 --- a/README.md +++ b/README.md @@ -21,7 +21,7 @@ exit ### Running SOTA Client -You can run the client with `target/debug/sota_client -c client.toml`. It will try to connect to the `core` service of `rvi_sota_server` specified in the `[server]` section of `client.toml`. +You can run the client with `target/debug/sota_client -c client.toml`. It will try to connect to the `core` service of `rvi_sota_server` specified in the `[server]` section of `client.toml`. If the `[server.auth]` section contains `client_id`, `client_secret` and `url`, it will first try to obtain an OAuth access token from `url` and then authenticate all the requests to the server with it. ### Running with RVI nodes diff --git a/client.toml b/client.toml index 7c1eb79..6b6bf92 100644 --- a/client.toml +++ b/client.toml @@ -13,6 +13,11 @@ vin = "V1234567890123456" packages_dir = "/tmp/" packages_extension = "deb" +[server.auth] +client_id = "client-id" +client_secret = "client-secret" +url = "http://127.0.0.1:9001/token" + [dbus] name = "org.genivi.SotaClient" path = "/org/genivi/SotaClient" diff --git a/src/configuration/mod.rs b/src/configuration/mod.rs index f394327..349c7f1 100644 --- a/src/configuration/mod.rs +++ b/src/configuration/mod.rs @@ -10,5 +10,5 @@ mod dbus; pub use self::configuration::Configuration; pub use self::client::ClientConfiguration; -pub use self::server::ServerConfiguration; +pub use self::server::{ServerConfiguration, AuthConfiguration}; pub use self::dbus::DBusConfiguration; diff --git a/src/configuration/server.rs b/src/configuration/server.rs index eadee9d..a05c63f 100644 --- a/src/configuration/server.rs +++ b/src/configuration/server.rs @@ -2,8 +2,25 @@ use toml; +use std::borrow::Cow; + use super::common::{get_required_key, ConfTreeParser, Result}; -use remote::http::Url; +use remote::http::{Auth, Url}; +use remote::http::datatype::{ClientId, ClientSecret}; + +impl<'a> Into> for AuthConfiguration { + fn into(self) -> Cow<'a, Auth> { + Cow::Owned(Auth::Credentials(ClientId { get: self.client_id }, + ClientSecret { get: self.client_secret })) + } +} + +#[derive(Clone, Debug)] +pub struct AuthConfiguration { + pub client_id: String, + pub client_secret: String, + pub url: Url, +} /// Type to encode allowed keys for the `server` section of the configuration. #[derive(Clone)] @@ -12,7 +29,8 @@ pub struct ServerConfiguration { pub polling_interval: i64, pub vin: String, pub packages_dir: String, - pub packages_extension: String + pub packages_extension: String, + pub auth: Option } impl ConfTreeParser> for ServerConfiguration { @@ -24,12 +42,30 @@ impl ConfTreeParser> for ServerConfiguration { let packages_dir = try!(get_required_key(server_tree, "packages_dir", "server")); let packages_extension = try!(get_required_key(server_tree, "packages_extension", "server")); + let auth = server_tree.as_table() + .and_then(|st| st.get("auth")) + .and_then(|auth_tree| { + get_required_key(auth_tree, "client_id", "auth").ok().and_then(|client_id| { + get_required_key(auth_tree, "client_secret", "auth").ok().and_then(|client_secret| { + get_required_key(auth_tree, "url", "auth").ok().map(|url| { + AuthConfiguration { + client_id: client_id, + client_secret: client_secret, + url: url, + } + }) + }) + }) + }); + + info!("Getting {:?}", auth); Ok(Some(ServerConfiguration { url: url, polling_interval: polling_interval, vin: vin, packages_dir: packages_dir, - packages_extension: packages_extension + packages_extension: packages_extension, + auth: auth })) }) } diff --git a/src/genivi/start.rs b/src/genivi/start.rs index 23f6caf..316d709 100644 --- a/src/genivi/start.rs +++ b/src/genivi/start.rs @@ -11,7 +11,9 @@ use event::inbound::InboundEvent; use event::outbound::OutBoundEvent; use remote::http::remote::HttpRemote; use remote::http::hyper::Hyper; +use remote::http::auth::authenticate; use remote::http::update_poller; +use remote::http::HttpClient; use remote::svc::{RemoteServices, ServiceHandler}; use remote::rvi; use remote::upstream::Upstream; @@ -71,9 +73,20 @@ pub fn start(conf: &Configuration, rvi_url: String, edge_url: String) { let (tx, rx): (Sender, Receiver) = channel(); if let Some(ref srv_cfg) = conf.server { + let access_token = srv_cfg.auth.clone().and_then(|auth_config| { + info!("Found Auth credentials, authenticating with {:?}...", auth_config); + let mut client: &mut HttpClient = &mut Hyper::new(); + authenticate(&auth_config, client) + .map_err(|e| panic!("Couldn't authenticate {:?}", e)) + .map(|t| { + info!("Authenticated, got token {:?}", t); + t + }).ok() + }); + // HTTP handler - update_poller::start(srv_cfg.clone(), tx.clone()); - let upstream = Arc::new(Mutex::new(HttpRemote::new(srv_cfg.clone(), Hyper::new(), tx.clone()))); + update_poller::start(srv_cfg.clone(), access_token.clone(), tx.clone()); + let upstream = Arc::new(Mutex::new(HttpRemote::new(srv_cfg.clone(), access_token, Hyper::new(), tx.clone()))); dbus_handler(&conf, tx.clone(), rx, upstream); } else { // RVI edge handler diff --git a/src/remote/http/api_client.rs b/src/remote/http/api_client.rs index b493710..da0d46d 100644 --- a/src/remote/http/api_client.rs +++ b/src/remote/http/api_client.rs @@ -10,9 +10,9 @@ use event::outbound::{UpdateReport, UpdateResult, InstalledPackage}; use configuration::ServerConfiguration; -use super::datatype::{UpdateRequestId, Url, Error}; +use super::datatype::{AccessToken, UpdateRequestId, Url, Error}; -use super::{Auth, HttpClient, HttpRequest, HttpResponse}; +use super::{HttpClient, HttpRequest, HttpResponse}; fn vehicle_updates_endpoint(config: &ServerConfiguration, path: &str) -> Url { config.url.join(& if path.is_empty() { @@ -23,12 +23,13 @@ fn vehicle_updates_endpoint(config: &ServerConfiguration, path: &str) -> Url { } pub fn download_package_update(config: &ServerConfiguration, + access_token: Option, client: &mut HttpClient, id: &UpdateRequestId) -> Result { let req = HttpRequest::get( vehicle_updates_endpoint(config, &format!("{}/download", id)), - (None as Option), + access_token, ); let mut path = PathBuf::new(); @@ -44,6 +45,7 @@ pub fn download_package_update(config: &ServerConfiguration, } pub fn send_install_report(config: &ServerConfiguration, + access_token: Option, client: &mut HttpClient, report: &UpdateReport) -> Result<(), Error> { @@ -52,7 +54,7 @@ pub fn send_install_report(config: &ServerConfiguration, let req = HttpRequest::post( vehicle_updates_endpoint(config, &format!("{}", report.update_id)), - (None as Option), + access_token, Some(json) ); @@ -86,11 +88,12 @@ impl Display for Package { } pub fn get_package_updates(config: &ServerConfiguration, + access_token: Option, client: &mut HttpClient) -> Result, Error> { let req = HttpRequest::get( vehicle_updates_endpoint(&config, ""), - (None as Option) + access_token, ); let resp = try!(client.send_request(&req)); @@ -114,6 +117,7 @@ pub fn get_package_updates(config: &ServerConfiguration, // XXX: Remove in favour of update_installed_packages()? pub fn update_packages(config: &ServerConfiguration, + access_token: Option, client: &mut HttpClient, pkgs: &Vec) -> Result<(), Error> { @@ -123,7 +127,7 @@ pub fn update_packages(config: &ServerConfiguration, let req = HttpRequest::put( vehicle_updates_endpoint(config, "installed"), - (None as Option), + access_token, Some(json), ); diff --git a/src/remote/http/auth.rs b/src/remote/http/auth.rs new file mode 100644 index 0000000..d3633d8 --- /dev/null +++ b/src/remote/http/auth.rs @@ -0,0 +1,28 @@ +use rustc_serialize::json; + +use super::datatype::{AccessToken, ClientId, ClientSecret, Error}; +use super::{Auth, HttpClient, HttpRequest}; + +use configuration::AuthConfiguration; + +pub fn authenticate(config: &AuthConfiguration, client: &mut HttpClient) -> Result { + + debug!("authenticate()"); + + let req = HttpRequest::post::<_, _, String>( + config.url.clone(), + Some(Auth::Credentials( + ClientId { get: config.client_id.clone() }, + ClientSecret { get: config.client_secret.clone() })), + None, + ); + + let resp = try!(client.send_request(&req)); + + let body = try!(String::from_utf8(resp.body)); + + debug!("authenticate, body: `{}`", body); + + Ok(try!(json::decode(&body))) + +} diff --git a/src/remote/http/datatype.rs b/src/remote/http/datatype.rs index 6b2e1e6..8f8e3de 100644 --- a/src/remote/http/datatype.rs +++ b/src/remote/http/datatype.rs @@ -30,12 +30,25 @@ pub struct AccessToken { pub scope: Vec } +#[derive(Clone, PartialEq, Debug)] +pub enum Auth { + Credentials(ClientId, ClientSecret), + Token(AccessToken), +} + + impl<'a> Into> for AccessToken { fn into(self) -> Cow<'a, AccessToken> { Cow::Owned(self) } } +impl<'a> Into> for AccessToken { + fn into(self) -> Cow<'a, Auth> { + Cow::Owned(Auth::Token(self.clone())) + } +} + use std::convert::From; use std::fmt::{Display, Formatter, Result as FmtResult}; use std::io::Error as IoError; diff --git a/src/remote/http/http_client.rs b/src/remote/http/http_client.rs index 430e2a4..a280c9a 100644 --- a/src/remote/http/http_client.rs +++ b/src/remote/http/http_client.rs @@ -1,15 +1,9 @@ use std::borrow::Cow; -use super::datatype::{AccessToken, ClientId, ClientSecret, Error, Method, Url}; +use super::datatype::{Auth, Error, Method, Url}; -#[derive(Clone)] -pub enum Auth<'a> { - Credentials(ClientId, ClientSecret), - Token(&'a AccessToken), -} - -impl<'a> Into>> for Auth<'a> { - fn into(self) -> Cow<'a, Auth<'a>> { +impl<'a> Into> for Auth { + fn into(self) -> Cow<'a, Auth> { Cow::Owned(self) } } @@ -17,7 +11,7 @@ impl<'a> Into>> for Auth<'a> { pub struct HttpRequest<'a> { pub method: Cow<'a, Method>, pub url: Cow<'a, Url>, - pub auth: Option>>, + pub auth: Option>, pub body: Option>, } @@ -30,7 +24,7 @@ impl<'a> HttpRequest<'a> { where M: Into>, U: Into>, - A: Into>>, + A: Into>, B: Into> { HttpRequest { @@ -44,7 +38,7 @@ impl<'a> HttpRequest<'a> { pub fn get(url: U, auth: Option) -> HttpRequest<'a> where U: Into>, - A: Into>>, + A: Into>, { HttpRequest::new::(Method::Get, url, auth, None) } @@ -52,7 +46,7 @@ impl<'a> HttpRequest<'a> { pub fn post(url: U, auth: Option, body: Option) -> HttpRequest<'a> where U: Into>, - A: Into>>, + A: Into>, B: Into> { HttpRequest::new(Method::Post, url, auth, body) @@ -61,7 +55,7 @@ impl<'a> HttpRequest<'a> { pub fn put(url: U, auth: Option, body: Option) -> HttpRequest<'a> where U: Into>, - A: Into>>, + A: Into>, B: Into> { HttpRequest::new(Method::Put, url, auth, body) diff --git a/src/remote/http/mod.rs b/src/remote/http/mod.rs index 96ced18..9c1b612 100644 --- a/src/remote/http/mod.rs +++ b/src/remote/http/mod.rs @@ -1,6 +1,6 @@ -pub use self::http_client::{Auth, HttpClient, HttpRequest, HttpResponse, HttpStatus}; +pub use self::http_client::{HttpClient, HttpRequest, HttpResponse, HttpStatus}; pub use self::hyper::Hyper; -pub use self::datatype::Url; +pub use self::datatype::{Url, Auth, AccessToken}; pub mod http_client; pub mod remote; @@ -8,3 +8,4 @@ pub mod api_client; pub mod hyper; pub mod update_poller; pub mod datatype; +pub mod auth; diff --git a/src/remote/http/remote.rs b/src/remote/http/remote.rs index 26a1158..b14fab5 100644 --- a/src/remote/http/remote.rs +++ b/src/remote/http/remote.rs @@ -8,28 +8,30 @@ use event::UpdateId; use super::HttpClient; use super::api_client::{update_packages, download_package_update, send_install_report}; +use super::datatype::AccessToken; pub struct HttpRemote { config: ServerConfiguration, + access_token: Option, client: C, tx: Sender } impl HttpRemote { - pub fn new(config: ServerConfiguration, client: C, tx: Sender) -> HttpRemote { - HttpRemote { config: config, client: client, tx: tx } + pub fn new(config: ServerConfiguration, access_token: Option, client: C, tx: Sender) -> HttpRemote { + HttpRemote { config: config, access_token: access_token, client: client, tx: tx } } } impl Upstream for HttpRemote { fn send_installed_software(&mut self, m: InstalledSoftware) -> Result { - update_packages(&self.config, &mut self.client, &m.packages) + update_packages(&self.config, self.access_token.clone(), &mut self.client, &m.packages) .map(|_| "ok".to_string()) .map_err(|e| format!("{}", e)) } fn send_start_download(&mut self, id: UpdateId) -> Result { - download_package_update(&self.config, &mut self.client, &id) + download_package_update(&self.config, self.access_token.clone(), &mut self.client, &id) .map_err(|e| format!("{}", e)) .and_then(|p| { let path = p.to_str().unwrap().to_string(); @@ -43,7 +45,7 @@ impl Upstream for HttpRemote { } fn send_update_report(&mut self, m: UpdateReport) -> Result { - send_install_report(&self.config, &mut self.client, &m) + send_install_report(&self.config, self.access_token.clone(), &mut self.client, &m) .map(|_| "ok".to_string()) .map_err(|e| format!("{}", e)) } diff --git a/src/remote/http/update_poller.rs b/src/remote/http/update_poller.rs index 54d29b0..e785db9 100644 --- a/src/remote/http/update_poller.rs +++ b/src/remote/http/update_poller.rs @@ -11,14 +11,17 @@ use event::Event; use super::HttpClient; use super::hyper::Hyper; +use super::AccessToken; pub fn start(config: ServerConfiguration, + access_token: Option, tx: Sender) { thread::spawn(move || { let mut c: &mut HttpClient = &mut Hyper::new(); + let t = access_token; loop { - match get_package_updates(&config, c) { + match get_package_updates(&config, t.clone(), c) { Ok(updates) => for update in updates { let _ = tx.send(Event::Inbound(InboundEvent::UpdateAvailable(update))); -- cgit v1.2.1 From 41d0007b23be22bf75b819a82b748456d3ff72c4 Mon Sep 17 00:00:00 2001 From: "Josep M. 'Txus' Bach" Date: Mon, 30 May 2016 18:28:32 +0200 Subject: Re-authenticate when the token expires and retry commands --- src/datatype/command.rs | 2 +- src/datatype/error.rs | 2 ++ src/http_client/hyper.rs | 7 ++++++- src/interaction_library/console.rs | 2 +- src/interaction_library/gateway.rs | 7 ++++--- src/interaction_library/http.rs | 14 +++++++------- src/interaction_library/websocket.rs | 4 ++-- src/interpreter.rs | 16 +++++++++++++--- src/main.rs | 10 ++++++---- 9 files changed, 42 insertions(+), 22 deletions(-) diff --git a/src/datatype/command.rs b/src/datatype/command.rs index dbb074f..dcad1af 100644 --- a/src/datatype/command.rs +++ b/src/datatype/command.rs @@ -5,7 +5,7 @@ use nom::{IResult, space, eof}; use datatype::{ClientCredentials, ClientId, ClientSecret, Error, UpdateRequestId}; -#[derive(RustcDecodable, RustcEncodable, PartialEq, Eq, Debug)] +#[derive(RustcDecodable, RustcEncodable, PartialEq, Eq, Debug, Clone)] pub enum Command { AcceptUpdate(UpdateRequestId), Authenticate(Option), diff --git a/src/datatype/error.rs b/src/datatype/error.rs index d6bc969..03d96aa 100644 --- a/src/datatype/error.rs +++ b/src/datatype/error.rs @@ -16,6 +16,7 @@ use ws::Error as WebsocketError; #[derive(Debug)] pub enum Error { ClientError(String), + AuthorizationError(String), Command(String), FromUtf8Error(FromUtf8Error), HyperError(HyperError), @@ -81,6 +82,7 @@ impl Display for Error { fn fmt(&self, f: &mut Formatter) -> FmtResult { let inner: String = match *self { Error::ClientError(ref s) => format!("Http client error: {}", s.clone()), + Error::AuthorizationError(ref s) => format!("Http client authorization error: {}", s.clone()), Error::Command(ref e) => format!("Unknown Command: {}", e.clone()), Error::FromUtf8Error(ref e) => format!("From utf8 error: {}", e.clone()), Error::HyperError(ref e) => format!("Hyper error: {}", e.clone()), diff --git a/src/http_client/hyper.rs b/src/http_client/hyper.rs index f0249d1..ed1f6fc 100644 --- a/src/http_client/hyper.rs +++ b/src/http_client/hyper.rs @@ -1,6 +1,7 @@ use hyper::Client; use hyper::client::RedirectPolicy; use hyper::client::response::Response; +use hyper::status::StatusCode; use hyper::header::{Authorization, Basic, Bearer, ContentType, Headers, Location}; use hyper::mime::{Attr, Mime, TopLevel, SubLevel, Value}; use rustc_serialize::json; @@ -101,7 +102,11 @@ impl HttpClient for Hyper { } else { let mut rbody = String::new(); let _: usize = try!(resp.read_to_string(&mut rbody)); - Err(Error::ClientError(format!("Request errored with status {}, body: {}", resp.status, rbody))) + if resp.status == StatusCode::Forbidden { + Err(Error::AuthorizationError(format!("Unauthorized request: status {}, body: {}", resp.status, rbody))) + } else { + Err(Error::ClientError(format!("Request errored with status {}, body: {}", resp.status, rbody))) + } } } diff --git a/src/interaction_library/console.rs b/src/interaction_library/console.rs index 9250090..6867a0a 100644 --- a/src/interaction_library/console.rs +++ b/src/interaction_library/console.rs @@ -12,7 +12,7 @@ pub struct Console { etx: Arc>> } -impl Gateway for Console +impl Gateway for Console where C: Send + FromStr + 'static, E: Send + ToString + 'static, ::Err: Debug, diff --git a/src/interaction_library/gateway.rs b/src/interaction_library/gateway.rs index ecf0587..3ecdaf6 100644 --- a/src/interaction_library/gateway.rs +++ b/src/interaction_library/gateway.rs @@ -3,14 +3,15 @@ use std::sync::{Arc, Mutex}; use std::sync::mpsc::{Sender, Receiver}; -pub struct Interpret { +#[derive(Clone)] +pub struct Interpret { pub cmd: C, pub etx: Option>>>, } pub trait Gateway: Sized + Send + Sync + 'static - where C: Send + 'static, - E: Send + 'static, + where C: Send + Clone + 'static, + E: Send + Clone + 'static, { fn new() -> Self; fn next(&self) -> Option>; diff --git a/src/interaction_library/http.rs b/src/interaction_library/http.rs index 4b6aefb..ff96e52 100644 --- a/src/interaction_library/http.rs +++ b/src/interaction_library/http.rs @@ -10,11 +10,11 @@ use super::gateway::{Gateway, Interpret}; use datatype::{Error, Event}; -pub struct Http { +pub struct Http { irx: Arc>>>, } -impl Gateway for Http +impl Gateway for Http where C: Send + Decodable + 'static, E: Send + Encodable + 'static { @@ -43,13 +43,13 @@ impl Gateway for Http } -pub struct HttpHandler { +pub struct HttpHandler { itx: Arc>>>, } impl Handler for HttpHandler - where C: Send + Decodable, - E: Send + Encodable + where C: Send + Decodable + Clone, + E: Send + Encodable + Clone { fn handle(&self, req: Request, resp: Response) { worker(self, req, resp).unwrap_or_else(|err| { @@ -60,8 +60,8 @@ impl Handler for HttpHandler mut req: Request, mut resp: Response) -> Result<(), Error> - where C: Send + Decodable, - E: Send + Encodable + where C: Send + Decodable + Clone, + E: Send + Encodable + Clone { // return 500 response on error *resp.status_mut() = StatusCode::InternalServerError; diff --git a/src/interaction_library/websocket.rs b/src/interaction_library/websocket.rs index ebb8f0c..8454665 100644 --- a/src/interaction_library/websocket.rs +++ b/src/interaction_library/websocket.rs @@ -70,8 +70,8 @@ impl Websocket { } impl Gateway for Websocket - where C: Decodable + Send + 'static, - E: Encodable + Send + 'static, + where C: Decodable + Send + Clone + 'static, + E: Encodable + Send + Clone + 'static, { fn new() -> Websocket { let (tx, rx) = mpsc::channel(); diff --git a/src/interpreter.rs b/src/interpreter.rs index 5bcd761..ed97235 100644 --- a/src/interpreter.rs +++ b/src/interpreter.rs @@ -19,6 +19,7 @@ pub struct Env<'a> { pub config: Config, pub access_token: Option>, pub http_client: Arc>, + pub feedback_tx: Sender, } @@ -81,11 +82,20 @@ impl<'a> Interpreter, Wrapped, Event> for GlobalInterpreter { let (multi_tx, multi_rx): (Sender, Receiver) = channel(); let local_tx = w.etx.clone(); + let w2 = w.clone(); + let _ = command_interpreter(env, w.cmd, multi_tx) .map_err(|err| { - let ev = Event::Error(format!("{}", err)); - let _ = global_tx.send(ev.clone()).unwrap(); - send(ev, &local_tx); + if let Error::AuthorizationError(_) = err { + // retry authorization and request + let _ = env.feedback_tx.send(Wrapped { cmd: Command::Authenticate(None), + etx: None }); + let _ = env.feedback_tx.send(w2); + } else { + let ev = Event::Error(format!("{}", err)); + let _ = global_tx.send(ev.clone()).unwrap(); + send(ev, &local_tx); + } }) .map(|_| { let mut last_ev = None; diff --git a/src/main.rs b/src/main.rs index 602ac62..49bc5c1 100644 --- a/src/main.rs +++ b/src/main.rs @@ -31,12 +31,13 @@ use libotaplus::interpreter::{AuthenticationRetrier, AutoAcceptor, Env, use libotaplus::package_manager::PackageManager; -fn spawn_global_interpreter(config: Config, wrx: Receiver, etx: Sender) { +fn spawn_global_interpreter(config: Config, wrx: Receiver, feedback_tx: Sender, etx: Sender) { let client = Arc::new(Mutex::new(Hyper::new())); let env = Env { config: config.clone(), access_token: None, http_client: client.clone(), + feedback_tx: feedback_tx, }; GlobalInterpreter::run(&mut env.clone(), wrx, etx); } @@ -106,9 +107,10 @@ fn main() { let auth_ctx = ctx.clone(); scope.spawn(move || AuthenticationRetrier::run(&mut (), auth_sub, auth_ctx)); - let glob_cfg = config.clone(); - let glob_etx = etx.clone(); - scope.spawn(move || spawn_global_interpreter(glob_cfg, wrx, glob_etx)); + let glob_cfg = config.clone(); + let glob_etx = etx.clone(); + let feedback_tx = wtx.clone(); + scope.spawn(move || spawn_global_interpreter(glob_cfg, wrx, feedback_tx, glob_etx)); let ws_wtx = wtx.clone(); let ws_sub = broadcast.subscribe(); -- cgit v1.2.1 From 8996fdcd0b87b2c03ab016402b7345257c97ab39 Mon Sep 17 00:00:00 2001 From: Stevan Andjelkovic Date: Tue, 31 May 2016 16:53:31 +0200 Subject: Remove hardcoded urls, requested by @alaa. --- pkg/provision/start-up.sh | 4 ---- 1 file changed, 4 deletions(-) diff --git a/pkg/provision/start-up.sh b/pkg/provision/start-up.sh index ce6577b..a4f99c0 100755 --- a/pkg/provision/start-up.sh +++ b/pkg/provision/start-up.sh @@ -2,14 +2,10 @@ set -eo pipefail -OTA_AUTH_URL="http://auth-plus-testing.gw.prod01.advancedtelematic.com" OTA_AUTH_PATH="/clients" -OTA_SERVER_URL="http://ota-plus-web-testing.gw.prod01.advancedtelematic.com" OTA_SERVER_PATH="/api/v1/vehicles/" -OTA_CORE_URL="http://ota-plus-core-testing.gw.prod01.advancedtelematic.com" - PACKAGE_MANAGER="dpkg" TEMPLATE_PATH="/etc/ota.toml.template" -- cgit v1.2.1 From 91d67918ec9f0be8aecdd6113e88cfeb6cfab2ea Mon Sep 17 00:00:00 2001 From: Jerry Trieu Date: Wed, 1 Jun 2016 12:35:51 +0200 Subject: Rename auth_plus to oauth2 --- src/auth_plus.rs | 72 ------------------------------------------------------ src/interpreter.rs | 2 +- src/lib.rs | 2 +- src/oauth2.rs | 72 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 74 insertions(+), 74 deletions(-) delete mode 100644 src/auth_plus.rs create mode 100644 src/oauth2.rs diff --git a/src/auth_plus.rs b/src/auth_plus.rs deleted file mode 100644 index c1fb705..0000000 --- a/src/auth_plus.rs +++ /dev/null @@ -1,72 +0,0 @@ -use rustc_serialize::json; - -use datatype::{AccessToken, AuthConfig, ClientId, ClientSecret, Error}; -use http_client::{Auth, HttpClient, HttpRequest}; - - -pub fn authenticate(config: &AuthConfig, client: &mut HttpClient) -> Result { - - debug!("authenticate()"); - - let req = HttpRequest::post::<_, _, String>( - config.server.join("/token").unwrap(), - Some(Auth::Credentials( - ClientId { get: config.client_id.clone() }, - ClientSecret { get: config.secret.clone() })), - None, - ); - - let resp = try!(client.send_request(&req)); - - let body = try!(String::from_utf8(resp.body)); - - debug!("authenticate, body: `{}`", body); - - Ok(try!(json::decode(&body))) - -} - - -#[cfg(test)] -mod tests { - - use super::*; - use datatype::{AccessToken, AuthConfig}; - use http_client::TestHttpClient; - - const TOKEN: &'static str = - r#"{"access_token": "token", - "token_type": "type", - "expires_in": 10, - "scope": ["scope"]} - "#; - - #[test] - fn test_authenticate() { - assert_eq!(authenticate(&AuthConfig::default(), &mut TestHttpClient::from(vec![TOKEN])).unwrap(), - AccessToken { - access_token: "token".to_string(), - token_type: "type".to_string(), - expires_in: 10, - scope: vec!["scope".to_string()] - }) - } - - #[test] - fn test_authenticate_no_token() { - assert_eq!(format!("{}", authenticate(&AuthConfig::default(), - &mut TestHttpClient::from(vec![""])).unwrap_err()), - r#"Failed to decode JSON: ParseError(SyntaxError("EOF While parsing value", 1, 1))"#) - - // XXX: Old error message was arguebly a lot better... - // "Authentication error, didn't receive access token.") - } - - #[test] - fn test_authenticate_bad_json() { - assert_eq!(format!("{}", authenticate(&AuthConfig::default(), - &mut TestHttpClient::from(vec![r#"{"apa": 1}"#])).unwrap_err()), - r#"Failed to decode JSON: MissingFieldError("access_token")"#) - } - -} diff --git a/src/interpreter.rs b/src/interpreter.rs index ed97235..ff3eb2d 100644 --- a/src/interpreter.rs +++ b/src/interpreter.rs @@ -3,7 +3,7 @@ use std::process::exit; use std::sync::{Arc, Mutex}; use std::sync::mpsc::{Sender, Receiver, channel}; -use auth_plus::authenticate; +use oauth2::authenticate; use datatype::{AccessToken, Command, Config, Error, Event, UpdateState}; use datatype::Command::*; use http_client::HttpClient; diff --git a/src/lib.rs b/src/lib.rs index f7c13ee..d141170 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -10,7 +10,7 @@ extern crate url; extern crate ws; -pub mod auth_plus; +pub mod oauth2; pub mod datatype; pub mod http_client; pub mod interaction_library; diff --git a/src/oauth2.rs b/src/oauth2.rs new file mode 100644 index 0000000..c1fb705 --- /dev/null +++ b/src/oauth2.rs @@ -0,0 +1,72 @@ +use rustc_serialize::json; + +use datatype::{AccessToken, AuthConfig, ClientId, ClientSecret, Error}; +use http_client::{Auth, HttpClient, HttpRequest}; + + +pub fn authenticate(config: &AuthConfig, client: &mut HttpClient) -> Result { + + debug!("authenticate()"); + + let req = HttpRequest::post::<_, _, String>( + config.server.join("/token").unwrap(), + Some(Auth::Credentials( + ClientId { get: config.client_id.clone() }, + ClientSecret { get: config.secret.clone() })), + None, + ); + + let resp = try!(client.send_request(&req)); + + let body = try!(String::from_utf8(resp.body)); + + debug!("authenticate, body: `{}`", body); + + Ok(try!(json::decode(&body))) + +} + + +#[cfg(test)] +mod tests { + + use super::*; + use datatype::{AccessToken, AuthConfig}; + use http_client::TestHttpClient; + + const TOKEN: &'static str = + r#"{"access_token": "token", + "token_type": "type", + "expires_in": 10, + "scope": ["scope"]} + "#; + + #[test] + fn test_authenticate() { + assert_eq!(authenticate(&AuthConfig::default(), &mut TestHttpClient::from(vec![TOKEN])).unwrap(), + AccessToken { + access_token: "token".to_string(), + token_type: "type".to_string(), + expires_in: 10, + scope: vec!["scope".to_string()] + }) + } + + #[test] + fn test_authenticate_no_token() { + assert_eq!(format!("{}", authenticate(&AuthConfig::default(), + &mut TestHttpClient::from(vec![""])).unwrap_err()), + r#"Failed to decode JSON: ParseError(SyntaxError("EOF While parsing value", 1, 1))"#) + + // XXX: Old error message was arguebly a lot better... + // "Authentication error, didn't receive access token.") + } + + #[test] + fn test_authenticate_bad_json() { + assert_eq!(format!("{}", authenticate(&AuthConfig::default(), + &mut TestHttpClient::from(vec![r#"{"apa": 1}"#])).unwrap_err()), + r#"Failed to decode JSON: MissingFieldError("access_token")"#) + } + +} -- cgit v1.2.1 From ebbbfa56183bf08e194fbcae0a11a92597a1a314 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sim=C3=A3o=20Mata?= Date: Wed, 1 Jun 2016 17:15:34 +0200 Subject: Authenticate to ota web before creating a vehicle Fail start up when `http` fails. --- pkg/provision/start-up.sh | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/pkg/provision/start-up.sh b/pkg/provision/start-up.sh index a4f99c0..bdf9710 100755 --- a/pkg/provision/start-up.sh +++ b/pkg/provision/start-up.sh @@ -14,11 +14,18 @@ VIN_SUFFIX=$(< /dev/urandom tr -dc A-HJ-NPR-Z0-9 | head -c${1:-11};echo;) echo $VIN_SUFFIX export OTA_CLIENT_VIN=STRESS$VIN_SUFFIX +export HTTP_SESSION="/tmp/$OTA_CLIENT_VIN.json" +export OTA_WEB_USER="${OTA_WEB_USER-demo@advancedtelematic.com}" +export OTA_WEB_PASSWORD="${OTA_WEB_PASSWORD-demo}" + #export OTA_CLIENT_VIN=STRESS12345678901 -echo "vin=${OTA_CLIENT_VIN}" | http put "${OTA_SERVER_URL}${OTA_SERVER_PATH}${OTA_CLIENT_VIN}" +http --check-status --session=$HTTP_SESSION -v POST ${OTA_SERVER_URL}/authenticate \ + username=$OTA_WEB_USER password=$OTA_WEB_PASSWORD || [[ $? == 3 ]] + +echo "vin=${OTA_CLIENT_VIN}" | http --check-status --session=$HTTP_SESSION put "${OTA_SERVER_URL}${OTA_SERVER_PATH}${OTA_CLIENT_VIN}" JSON=$(envsubst < /etc/auth.json) -AUTH_DATA=$(echo $JSON | http post $OTA_AUTH_URL$OTA_AUTH_PATH) +AUTH_DATA=$(echo $JSON | http --check-status post $OTA_AUTH_URL$OTA_AUTH_PATH) OTA_AUTH_CLIENT_ID=$(echo $AUTH_DATA | jq -r .client_id) OTA_AUTH_SECRET=$(echo $AUTH_DATA | jq -r .client_secret) -- cgit v1.2.1 From 8b612317141a4ab1bff6a0d3e8171465ba4391b3 Mon Sep 17 00:00:00 2001 From: Shaun Taheri Date: Wed, 1 Jun 2016 17:47:52 +0200 Subject: Fix Dockerfile for building packages --- pkg/Dockerfile | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/pkg/Dockerfile b/pkg/Dockerfile index 8bb7866..42866db 100644 --- a/pkg/Dockerfile +++ b/pkg/Dockerfile @@ -3,18 +3,29 @@ FROM clux/muslrust RUN apt-get update && apt-get install -y \ devscripts \ dh-systemd \ - openssl \ + gettext \ httpie \ jq \ - gettext \ + openssl \ + patch \ + rpm \ + ruby \ + ruby-dev \ && rm -rf /var/lib/apt/lists/* +ENV FPM_VER 1.6.0 + +RUN gem install fpm -v ${FPM_VER} +# apply patch to add --rpm-service flag +COPY pr862.patch / +RUN patch -i /pr862.patch /var/lib/gems/2.1.0/gems/fpm-${FPM_VER}/lib/fpm/package/rpm.rb + COPY ota_plus_client /usr/bin/ COPY ota.toml.template /etc/ COPY provision/start-up.sh /usr/bin/ COPY provision/auth.json /etc/ -ENV LANG="en_GB.UTF-8" +ENV LANG="en_US.UTF-8" WORKDIR /build EXPOSE 8888 -- cgit v1.2.1 From ab916d3d430fbc0d560fb4a95b43d0cf92447405 Mon Sep 17 00:00:00 2001 From: Alex Humphreys Date: Wed, 1 Jun 2016 17:48:14 +0200 Subject: Add configurable http value to ota.toml.template --- pkg/ota.toml.template | 2 +- pkg/pkg.sh | 1 + pkg/provision/start-up.sh | 1 + 3 files changed, 3 insertions(+), 1 deletion(-) diff --git a/pkg/ota.toml.template b/pkg/ota.toml.template index 0ccc12e..3fbf4d0 100644 --- a/pkg/ota.toml.template +++ b/pkg/ota.toml.template @@ -13,4 +13,4 @@ package_manager = "${PACKAGE_MANAGER}" [test] looping = false -http = false +http = ${OTA_HTTP} diff --git a/pkg/pkg.sh b/pkg/pkg.sh index a38106f..36cbd32 100755 --- a/pkg/pkg.sh +++ b/pkg/pkg.sh @@ -7,6 +7,7 @@ PKG_VER=$VERSION PKG_DIR="${PKG_NAME}-${PKG_VER}" PKG_TARBALL="${PKG_NAME}_${PKG_VER}" PREFIX=/opt/ats +export OTA_HTTP=false cd $(dirname $0) PKG_SRC_DIR=$(pwd) diff --git a/pkg/provision/start-up.sh b/pkg/provision/start-up.sh index bdf9710..a5d6bf3 100755 --- a/pkg/provision/start-up.sh +++ b/pkg/provision/start-up.sh @@ -36,6 +36,7 @@ export OTA_SERVER_URL=$OTA_CORE_URL export OTA_AUTH_CLIENT_ID=$OTA_AUTH_CLIENT_ID export OTA_AUTH_SECRET=$OTA_AUTH_SECRET export PACKAGE_MANAGER=$PACKAGE_MANAGER +export OTA_HTTP=${OTA_HTTP-false} echo $OTA_CLIENT_VIN echo $OTA_AUTH_URL -- cgit v1.2.1 From bbc9236e7bfb72fe389ff8ccbd7e62da8f1f56c9 Mon Sep 17 00:00:00 2001 From: Shaun Taheri Date: Wed, 1 Jun 2016 17:56:29 +0200 Subject: Update Makefile with target for building both packages --- Makefile | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/Makefile b/Makefile index fabfa61..22e7c06 100644 --- a/Makefile +++ b/Makefile @@ -1,17 +1,16 @@ MUSL=x86_64-unknown-linux-musl -.PHONY: all -all: ota_plus_client +.PHONY: all ota_plus_client deb rpm + +all: deb rpm -.PHONY: ota_plus_client ota_plus_client: src/ + cargo clean cargo build --release --target=$(MUSL) cp target/$(MUSL)/release/ota_plus_client pkg/ -.PHONY: deb deb: ota_plus_client pkg/pkg.sh deb $(CURDIR) -.PHONY: rpm rpm: ota_plus_client pkg/pkg.sh rpm $(CURDIR) -- cgit v1.2.1 From e03bf9473f120f62495f515caa24dcf56cf0e0cc Mon Sep 17 00:00:00 2001 From: Alex Humphreys Date: Thu, 2 Jun 2016 12:04:01 +0200 Subject: Add fix to run in docker --- pkg/provision/start-up.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pkg/provision/start-up.sh b/pkg/provision/start-up.sh index a5d6bf3..6eb16c5 100755 --- a/pkg/provision/start-up.sh +++ b/pkg/provision/start-up.sh @@ -20,8 +20,8 @@ export OTA_WEB_PASSWORD="${OTA_WEB_PASSWORD-demo}" #export OTA_CLIENT_VIN=STRESS12345678901 -http --check-status --session=$HTTP_SESSION -v POST ${OTA_SERVER_URL}/authenticate \ - username=$OTA_WEB_USER password=$OTA_WEB_PASSWORD || [[ $? == 3 ]] +http --check-status --session=$HTTP_SESSION POST ${OTA_SERVER_URL}/authenticate \ + username=$OTA_WEB_USER password=$OTA_WEB_PASSWORD --ignore-stdin || [[ $? == 3 ]] echo "vin=${OTA_CLIENT_VIN}" | http --check-status --session=$HTTP_SESSION put "${OTA_SERVER_URL}${OTA_SERVER_PATH}${OTA_CLIENT_VIN}" JSON=$(envsubst < /etc/auth.json) -- cgit v1.2.1 From 6c1530b96e4ab355008cd01c264bf5556e845cd4 Mon Sep 17 00:00:00 2001 From: Jerry Trieu Date: Wed, 1 Jun 2016 12:53:21 +0200 Subject: De-couple PackageManager from ota_plus.rs --- src/datatype/command.rs | 4 ++++ src/datatype/event.rs | 4 ++++ src/interpreter.rs | 26 ++++++++++++++++++++++++++ src/ota_plus.rs | 6 ++++++ 4 files changed, 40 insertions(+) diff --git a/src/datatype/command.rs b/src/datatype/command.rs index dcad1af..a432efe 100644 --- a/src/datatype/command.rs +++ b/src/datatype/command.rs @@ -8,6 +8,10 @@ use datatype::{ClientCredentials, ClientId, ClientSecret, Error, UpdateRequestId #[derive(RustcDecodable, RustcEncodable, PartialEq, Eq, Debug, Clone)] pub enum Command { AcceptUpdate(UpdateRequestId), + /* Add: + UpdateReport, + InstalledSoftware, // or reuse UpdateInstalledPackages + */ Authenticate(Option), GetPendingUpdates, ListInstalledPackages, diff --git a/src/datatype/event.rs b/src/datatype/event.rs index 73fc13f..975d8ac 100644 --- a/src/datatype/event.rs +++ b/src/datatype/event.rs @@ -8,6 +8,10 @@ pub enum Event { Ok, NotAuthenticated, NewUpdateAvailable(UpdateRequestId), + /* TODO: Add: + DownloadComplete(UpdateRequestId), + GetInstalledSoftware, + */ UpdateStateChanged(UpdateRequestId, UpdateState), UpdateErrored(UpdateRequestId, String), Error(String), diff --git a/src/interpreter.rs b/src/interpreter.rs index ed97235..48a528b 100644 --- a/src/interpreter.rs +++ b/src/interpreter.rs @@ -73,6 +73,32 @@ impl Interpreter<(), Event, Command> for AuthenticationRetrier { } } +/* TODO: Handle events to PackageManager +pub struct AutoPackageInstaller; + +impl Interpreter<(), Event, Command> for AutoPackageInstaller { + fn interpret(env: &mut Env, event: Event, ctx: Sender) { + match event { + Event::DownloadComplete => { + match env.config.ota.package_manager.install_package(p) { + _ => { + let _ = ctx.send(Command::UpdateReport()); + } + } + } + Event::GetInstalledSoftware => { + match env.config.ota.package_manager.installed_packages() { + _ => { + let _ = ctx.send(Command::InstalledSoftware()); + } + } + } + _ => {} + } + } +} +*/ + pub struct GlobalInterpreter; diff --git a/src/ota_plus.rs b/src/ota_plus.rs index 0b207ab..d1b78e8 100644 --- a/src/ota_plus.rs +++ b/src/ota_plus.rs @@ -32,6 +32,8 @@ pub fn download_package_update(config: &Config, let mut path = PathBuf::new(); path.push(&config.ota.packages_dir); path.push(id); + // TODO: Use Content-Disposition filename from request? + // TODO: Do not invoke package_manager path.set_extension(config.ota.package_manager.extension()); let mut file = try!(File::create(path.as_path())); @@ -104,6 +106,8 @@ pub fn update_installed_packages(config: &Config, client: &mut HttpClient, token: &AccessToken) -> Result<(), Error> { + // TODO: Fire GetInstalledSoftware event, handle async InstalledSoftware command + // TODO: Do not invoke package_manager let pkgs = try!(config.ota.package_manager.installed_packages()); update_packages(config, client, token, &pkgs) @@ -124,6 +128,8 @@ pub fn install_package_update(config: &Config, let p = try!(path.to_str() .ok_or(Error::ParseError(format!("Path is not valid UTF-8: {:?}", path)))); + // TODO: Fire DownloadComplete event, handle async UpdateReport command + // TODO: Do not invoke package_manager match config.ota.package_manager.install_package(p) { Ok((code, output)) => { -- cgit v1.2.1 From 22066fdef88ceb41fb83c2c8e35fbfcd063cd932 Mon Sep 17 00:00:00 2001 From: Jerry Trieu Date: Fri, 20 May 2016 13:38:06 +0200 Subject: Fix ContentType for messages to RVI edge --- src/remote/rvi/send.rs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/remote/rvi/send.rs b/src/remote/rvi/send.rs index 8308203..40c65a5 100644 --- a/src/remote/rvi/send.rs +++ b/src/remote/rvi/send.rs @@ -2,6 +2,8 @@ use std::io::Read; use hyper::Client; +use hyper::header::ContentType; +use hyper::mime::{Mime, TopLevel, SubLevel}; use rustc_serialize::{json, Encodable}; use remote::jsonrpc; @@ -22,7 +24,10 @@ pub fn send(url: &str, b: &E) -> Result { .map_err(|e| format!("{}", e)) .and_then(|j| { debug!("<<< Sent Message: {}", j); - client.post(url).body(&j).send() + client.post(url) + .header(ContentType(Mime(TopLevel::Application, SubLevel::Json, vec![]))) + .body(&j) + .send() .map_err(|e| format!("{}", e)) })); -- cgit v1.2.1 From 24a9d4db4131937f35bdc1d11ff93b2efafdcf50 Mon Sep 17 00:00:00 2001 From: Jerry Trieu Date: Tue, 31 May 2016 16:25:14 +0200 Subject: Fix RVI edge URL in remote/ --- client.toml | 2 +- src/genivi/start.rs | 3 ++- src/main.rs | 22 ++++++++++++++++------ src/remote/rvi/edge.rs | 14 ++++++++------ src/remote/rvi/send.rs | 11 ++++++++--- src/remote/svc.rs | 13 +++++++------ 6 files changed, 42 insertions(+), 23 deletions(-) diff --git a/client.toml b/client.toml index 6b6bf92..8f8f8df 100644 --- a/client.toml +++ b/client.toml @@ -1,7 +1,7 @@ [client] storage_dir = "/var/sota" rvi_url = "http://127.0.0.1:8901" -edge_url = "127.0.0.1:9080" +edge_url = "http://127.0.0.1:9080" timeout = 20 vin_match = 2 http = "true" diff --git a/src/genivi/start.rs b/src/genivi/start.rs index 316d709..62ae1fa 100644 --- a/src/genivi/start.rs +++ b/src/genivi/start.rs @@ -3,6 +3,7 @@ use std::sync::{Arc, Mutex}; use std::sync::mpsc::{channel, Receiver, Sender}; use std::thread; +use url::Url; use configuration::Configuration; use configuration::DBusConfiguration; @@ -68,7 +69,7 @@ fn dbus_handler(conf: &Configuration, tx: Sender, rx /// * `rvi_url`: The URL, where RVI can be found, with the protocol. /// * `edge_url`: The `host:port` combination where the client should bind and listen for incoming /// RVI calls. -pub fn start(conf: &Configuration, rvi_url: String, edge_url: String) { +pub fn start(conf: &Configuration, rvi_url: Url, edge_url: Url) { // Main message channel from RVI and DBUS let (tx, rx): (Sender, Receiver) = channel(); diff --git a/src/main.rs b/src/main.rs index c55a339..2f01424 100644 --- a/src/main.rs +++ b/src/main.rs @@ -4,9 +4,14 @@ extern crate sota_client; #[macro_use] extern crate log; extern crate env_logger; extern crate getopts; +extern crate hyper; +extern crate url; use std::env; use getopts::{Options, Matches}; +use hyper::client::IntoUrl; +use url::Url; + use sota_client::configuration::Configuration; use sota_client::genivi; @@ -74,12 +79,17 @@ fn main() { } }; - let rvi_url: String = matches.opt_str("r") - .unwrap_or(configuration.client.rvi_url.clone() - .unwrap_or("http://localhost:8901".to_string())); - let edge_url: String = matches.opt_str("e") - .unwrap_or(configuration.client.edge_url.clone() - .unwrap_or("localhost:9080".to_string())); + let rvi_url: Url = matches.opt_str("r") + .or_else(|| configuration.client.rvi_url.clone()) + .or_else(|| Some("http://localhost:8901".to_string())) + .map(|u| u.into_url().unwrap()) + .unwrap(); + + let edge_url: Url = matches.opt_str("e") + .or_else(|| configuration.client.edge_url.clone()) + .or_else(|| Some("http://localhost:9080".to_string())) + .map(|u| u.into_url().unwrap()) + .unwrap(); genivi::start::start(&configuration, rvi_url, edge_url); } diff --git a/src/remote/rvi/edge.rs b/src/remote/rvi/edge.rs index 6eec01f..ea5536b 100644 --- a/src/remote/rvi/edge.rs +++ b/src/remote/rvi/edge.rs @@ -6,6 +6,7 @@ use hyper::Server; use hyper::server::{Handler, Request, Response}; use rustc_serialize::json; use rustc_serialize::json::Json; +use url::Url; use remote::jsonrpc; use remote::jsonrpc::{OkResponse, ErrResponse}; @@ -22,9 +23,9 @@ pub trait ServiceHandler: Sync + Send { /// Encodes the service edge of the webservice. pub struct ServiceEdge { /// The full URL where RVI can be reached. - rvi_url: String, + rvi_url: Url, /// The `host:port` to bind and listen for incoming RVI messages. - edge_url: String, + edge_url: Url, hdlr: H } @@ -35,7 +36,7 @@ impl ServiceEdge { /// * `r`: The full URL where RVI can be reached. /// * `e`: The `host:port` combination where the edge should bind. /// * `s`: A sender to communicate back the service URLs. - pub fn new(r: String, e: String, h: H) -> ServiceEdge { + pub fn new(r: Url, e: Url, h: H) -> ServiceEdge { ServiceEdge { rvi_url: r, edge_url: e, @@ -57,7 +58,7 @@ impl ServiceEdge { service: s.to_string() }); - let resp = send(&self.rvi_url, &json_rpc) + let resp = send(self.rvi_url.clone(), &json_rpc) .map_err(|e| error!("Couldn't send registration to RVI\n{}", e)) .and_then(|r| json::decode::>(&r) .map_err(|e| error!("Couldn't parse response when registering in RVI\n{}", e))) @@ -82,10 +83,11 @@ impl ServiceEdge { /// * `h`: The `Handler` all messages are passed to. /// * `s`: A `Vector` of service strings to register in RVI. pub fn start(self) { - let url = self.edge_url.clone(); + let addr = (self.edge_url.host().unwrap().to_string(), self.edge_url.port().unwrap()); + self.hdlr.register_services(|s| self.register_service(s)); thread::spawn(move || { - Server::http(&*url).and_then(|srv| { + Server::http(((&*addr.0, addr.1))).and_then(|srv| { info!("Ready to accept connections."); srv.handle(self) }) .map_err(|e| error!("Couldn't start server\n{}", e)) diff --git a/src/remote/rvi/send.rs b/src/remote/rvi/send.rs index 40c65a5..c0ab9be 100644 --- a/src/remote/rvi/send.rs +++ b/src/remote/rvi/send.rs @@ -2,6 +2,7 @@ use std::io::Read; use hyper::Client; +use hyper::client::IntoUrl; use hyper::header::ContentType; use hyper::mime::{Mime, TopLevel, SubLevel}; use rustc_serialize::{json, Encodable}; @@ -17,7 +18,7 @@ use remote::rvi::message::RVIMessage; /// # Arguments /// * `url`: The full URL where RVI can be reached. /// * `b`: The object to encode and send to RVI. -pub fn send(url: &str, b: &E) -> Result { +pub fn send(url: U, b: &E) -> Result { let client = Client::new(); let mut resp = try!(json::encode(b) @@ -53,14 +54,18 @@ pub fn send(url: &str, b: &E) -> Result { /// * `b`: The object to wrap into a RVI Message, encode and send to RVI. /// * `addr`: The full RVI address (service URL) where this message should be sent to. #[cfg(not(test))] -pub fn send_message(url: &str, b: E, addr: &str) -> Result { +pub fn send_message(url: U, b: E, addr: &str) -> Result { let mut params = Vec::new(); params.push(b); let message = RVIMessage::::new(addr, params, 90); let json_rpc = jsonrpc::Request::new("message", message); send(url, &json_rpc) } + +#[cfg(test)] +use std::fmt::Display; + #[cfg(test)] -pub fn send_message(url: &str, _: E, addr: &str) -> Result { +pub fn send_message(url: U, _: E, addr: &str) -> Result { Ok(format!("Faked sending to RVI: {}, {}", url, addr)) } diff --git a/src/remote/svc.rs b/src/remote/svc.rs index 813f641..e35f215 100644 --- a/src/remote/svc.rs +++ b/src/remote/svc.rs @@ -11,6 +11,7 @@ use std::time::Duration; use rustc_serialize::{json, Decodable}; use time; +use url::Url; use event::{Event, UpdateId}; use event::inbound::InboundEvent; @@ -82,13 +83,13 @@ struct InstalledSoftwareResult { pub struct RemoteServices { pub vin: String, - url: String, + url: Url, local_svcs: Option, svcs: Option } impl RemoteServices { - pub fn new(url: String) -> RemoteServices { + pub fn new(url: Url) -> RemoteServices { RemoteServices { vin: String::new(), url: url, @@ -108,7 +109,7 @@ impl RemoteServices { pub fn send_chunk_received(&self, m: ChunkReceived) -> Result { self.svcs.iter().next().ok_or(format!("RemoteServices not set")) - .and_then(|ref svcs| rvi::send_message(&self.url, m, &svcs.ack)) + .and_then(|ref svcs| rvi::send_message(self.url.clone(), m, &svcs.ack)) } fn make_start_download(&self, id: UpdateId) -> StartDownload { @@ -124,7 +125,7 @@ impl Upstream for RemoteServices { fn send_start_download(&mut self, id: UpdateId) -> Result { self.svcs.iter().next().ok_or(format!("RemoteServices not set")) .and_then(|ref svcs| rvi::send_message( - &self.url, + self.url.clone(), self.make_start_download(id), &svcs.start)) } @@ -132,7 +133,7 @@ impl Upstream for RemoteServices { fn send_update_report(&mut self, m: UpdateReport) -> Result { self.svcs.iter().next().ok_or(format!("RemoteServices not set")) .and_then(|ref svcs| rvi::send_message( - &self.url, + self.url.clone(), UpdateResult { vin: self.vin.clone(), update_report: m }, @@ -142,7 +143,7 @@ impl Upstream for RemoteServices { fn send_installed_software(&mut self, m: InstalledSoftware) -> Result { self.svcs.iter().next().ok_or(format!("RemoteServices not set")) .and_then(|ref svcs| rvi::send_message( - &self.url, + self.url.clone(), InstalledSoftwareResult { vin: self.vin.clone(), installed_software: m }, -- cgit v1.2.1 From 2f24700d3c9bb96195ac9edaafcf0450ad8740c9 Mon Sep 17 00:00:00 2001 From: Jerry Trieu Date: Mon, 6 Jun 2016 16:56:38 +0200 Subject: Fix path in Dockerfile Match /usr/bin/ path in run.sh --- docker/Dockerfile | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docker/Dockerfile b/docker/Dockerfile index 791a01a..1d93a14 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -4,9 +4,9 @@ RUN apt-get update \ && apt-get install -y openssl dbus libdbus-1-3 dbus-x11 libdbus-glib-1-2 \ && mkdir /var/sota -COPY sota_client /bin/sota_client -COPY run.sh /bin/run.sh +COPY sota_client /usr/bin/sota_client +COPY run.sh /usr/bin/run.sh COPY client.toml /var/sota/client.toml EXPOSE 9080 -CMD ["/bin/run.sh"] +CMD ["/usr/bin/run.sh"] -- cgit v1.2.1 From 047d80a47ae82e5357a40733ca3b011b5df8eb73 Mon Sep 17 00:00:00 2001 From: Shaun Taheri Date: Mon, 30 May 2016 18:36:35 +0200 Subject: Migrate HTTP Client to async form --- Cargo.lock | 98 +++++----- Cargo.toml | 2 +- src/datatype/access_token.rs | 6 +- src/datatype/auth.rs | 17 ++ src/datatype/client_credentials.rs | 10 +- src/datatype/command.rs | 10 +- src/datatype/config.rs | 5 +- src/datatype/error.rs | 72 ++++--- src/datatype/method.rs | 2 +- src/datatype/mod.rs | 2 + src/datatype/url.rs | 32 +-- src/http_client/auth_client.rs | 235 ++++++++++++++++++++++ src/http_client/http_client.rs | 151 ++------------ src/http_client/hyper.rs | 205 ------------------- src/http_client/mod.rs | 10 +- src/http_client/test.rs | 34 ---- src/http_client/test_client.rs | 29 +++ src/interaction_library/http.rs | 208 +++++++++++++------- src/interpreter.rs | 94 +++++---- src/main.rs | 50 ++--- src/oauth2.rs | 69 +++---- src/ota_plus.rs | 392 ++++++++++++++++--------------------- 22 files changed, 806 insertions(+), 927 deletions(-) create mode 100644 src/datatype/auth.rs create mode 100644 src/http_client/auth_client.rs delete mode 100644 src/http_client/hyper.rs delete mode 100644 src/http_client/test.rs create mode 100644 src/http_client/test_client.rs diff --git a/Cargo.lock b/Cargo.lock index b70d10a..85c924a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -7,15 +7,15 @@ dependencies = [ "crossbeam 0.2.9 (registry+https://github.com/rust-lang/crates.io-index)", "env_logger 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", "getopts 0.2.14 (registry+https://github.com/rust-lang/crates.io-index)", - "hyper 0.9.6 (registry+https://github.com/rust-lang/crates.io-index)", + "hyper 0.9.4 (git+https://github.com/hyperium/hyper)", "log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", "nom 1.2.3 (registry+https://github.com/rust-lang/crates.io-index)", "rustc-serialize 0.3.19 (registry+https://github.com/rust-lang/crates.io-index)", "tempfile 2.1.3 (registry+https://github.com/rust-lang/crates.io-index)", "time 0.1.35 (registry+https://github.com/rust-lang/crates.io-index)", "toml 0.1.30 (registry+https://github.com/rust-lang/crates.io-index)", - "url 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", - "ws 0.4.7 (registry+https://github.com/rust-lang/crates.io-index)", + "url 1.1.1 (registry+https://github.com/rust-lang/crates.io-index)", + "ws 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -51,7 +51,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "byteorder" -version = "0.5.2" +version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] @@ -91,7 +91,7 @@ dependencies = [ "openssl 0.7.13 (registry+https://github.com/rust-lang/crates.io-index)", "rustc-serialize 0.3.19 (registry+https://github.com/rust-lang/crates.io-index)", "time 0.1.35 (registry+https://github.com/rust-lang/crates.io-index)", - "url 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "url 1.1.1 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -127,14 +127,6 @@ name = "getopts" version = "0.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" -[[package]] -name = "hpack" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", -] - [[package]] name = "httparse" version = "1.1.2" @@ -142,24 +134,25 @@ source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "hyper" -version = "0.9.6" -source = "registry+https://github.com/rust-lang/crates.io-index" +version = "0.9.4" +source = "git+https://github.com/hyperium/hyper#0c847f7898d757a4ce9f2b60a04155ed35c538bb" dependencies = [ "cookie 0.2.4 (registry+https://github.com/rust-lang/crates.io-index)", "httparse 1.1.2 (registry+https://github.com/rust-lang/crates.io-index)", "language-tags 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", "mime 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", - "num_cpus 0.2.12 (registry+https://github.com/rust-lang/crates.io-index)", "openssl 0.7.13 (registry+https://github.com/rust-lang/crates.io-index)", "openssl-verify 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "rotor 0.6.3 (registry+https://github.com/rust-lang/crates.io-index)", "rustc-serialize 0.3.19 (registry+https://github.com/rust-lang/crates.io-index)", - "solicit 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)", + "spmc 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", "time 0.1.35 (registry+https://github.com/rust-lang/crates.io-index)", "traitobject 0.0.1 (registry+https://github.com/rust-lang/crates.io-index)", "typeable 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", "unicase 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", - "url 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "url 1.1.1 (registry+https://github.com/rust-lang/crates.io-index)", + "vecio 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -288,14 +281,6 @@ name = "nom" version = "1.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -[[package]] -name = "num_cpus" -version = "0.2.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "libc 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", -] - [[package]] name = "openssl" version = "0.7.13" @@ -352,6 +337,11 @@ dependencies = [ "tempdir 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "quick-error" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "rand" version = "0.3.14" @@ -368,7 +358,7 @@ dependencies = [ "aho-corasick 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)", "memchr 0.1.11 (registry+https://github.com/rust-lang/crates.io-index)", "regex-syntax 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", - "thread_local 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)", + "thread_local 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)", "utf8-ranges 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -377,6 +367,18 @@ name = "regex-syntax" version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "rotor" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", + "mio 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", + "quick-error 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", + "time 0.1.35 (registry+https://github.com/rust-lang/crates.io-index)", + "void 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "rustc-serialize" version = "0.3.19" @@ -400,7 +402,7 @@ name = "sha1" version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "byteorder 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)", + "byteorder 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -409,13 +411,9 @@ version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] -name = "solicit" -version = "0.4.4" +name = "spmc" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "hpack 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", - "log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", -] [[package]] name = "tempdir" @@ -448,7 +446,7 @@ dependencies = [ [[package]] name = "thread_local" -version = "0.2.5" +version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "thread-id 2.0.0 (registry+https://github.com/rust-lang/crates.io-index)", @@ -505,19 +503,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "url" -version = "0.5.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "matches 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", - "rustc-serialize 0.3.19 (registry+https://github.com/rust-lang/crates.io-index)", - "unicode-bidi 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", - "unicode-normalization 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", - "uuid 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "url" -version = "1.1.0" +version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "idna 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", @@ -539,13 +525,19 @@ version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] -name = "uuid" -version = "0.2.2" +name = "vecio" +version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "rand 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.2.7 (registry+https://github.com/rust-lang/crates.io-index)", + "ws2_32-sys 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "void" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "winapi" version = "0.2.7" @@ -558,7 +550,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "ws" -version = "0.4.7" +version = "0.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "httparse 1.1.2 (registry+https://github.com/rust-lang/crates.io-index)", @@ -566,7 +558,7 @@ dependencies = [ "mio 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", "rand 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)", "sha1 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", - "url 0.5.9 (registry+https://github.com/rust-lang/crates.io-index)", + "url 1.1.1 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 12ffc84..80a000e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,7 +18,7 @@ chan-signal = "0.1.5" crossbeam = "0.2.9" env_logger = "0.3.3" getopts = "0.2.14" -hyper = "0.9.6" +hyper = { git = "https://github.com/hyperium/hyper" } log = "0.3.5" nom = "1.2.3" rustc-serialize = "0.3.18" diff --git a/src/datatype/access_token.rs b/src/datatype/access_token.rs index 0852c88..c5336b4 100644 --- a/src/datatype/access_token.rs +++ b/src/datatype/access_token.rs @@ -4,9 +4,9 @@ use std::borrow::Cow; #[derive(RustcDecodable, Debug, PartialEq, Clone, Default)] pub struct AccessToken { pub access_token: String, - pub token_type: String, - pub expires_in: i32, - pub scope: Vec + pub token_type: String, + pub expires_in: i32, + pub scope: Vec } impl<'a> Into> for AccessToken { diff --git a/src/datatype/auth.rs b/src/datatype/auth.rs new file mode 100644 index 0000000..a116e54 --- /dev/null +++ b/src/datatype/auth.rs @@ -0,0 +1,17 @@ +use std::borrow::Cow; + +use datatype::{AccessToken, ClientId, ClientSecret}; + + +#[derive(Clone, Debug)] +pub enum Auth { + None, + Credentials(ClientId, ClientSecret), + Token(AccessToken), +} + +impl<'a> Into> for Auth { + fn into(self) -> Cow<'a, Auth> { + Cow::Owned(self) + } +} diff --git a/src/datatype/client_credentials.rs b/src/datatype/client_credentials.rs index b750e7b..e00e032 100644 --- a/src/datatype/client_credentials.rs +++ b/src/datatype/client_credentials.rs @@ -1,14 +1,8 @@ - - #[derive(Clone, PartialEq, Eq, Debug, RustcEncodable, RustcDecodable)] -pub struct ClientId { - pub get: String, -} +pub struct ClientId(pub String); #[derive(Clone, PartialEq, Eq, Debug, RustcEncodable, RustcDecodable)] -pub struct ClientSecret { - pub get: String, -} +pub struct ClientSecret(pub String); #[derive(Clone, PartialEq, Eq, Debug, RustcEncodable, RustcDecodable)] pub struct ClientCredentials { diff --git a/src/datatype/command.rs b/src/datatype/command.rs index a432efe..e4ad023 100644 --- a/src/datatype/command.rs +++ b/src/datatype/command.rs @@ -85,7 +85,7 @@ fn parse_arguments(cmd: Command, args: Vec<&str>) -> Result { match cmd { Command::AcceptUpdate(_) => { match args.len() { - 0 => Err(Error::Command("usage: acc ".to_owned())), + 0 => Err(Error::Command("usage: acc ".to_owned())), 1 => Ok(Command::AcceptUpdate(args[0].to_owned())), _ => Err(Error::Command(format!("unexpected acc args: {:?}", args))), } @@ -98,8 +98,8 @@ fn parse_arguments(cmd: Command, args: Vec<&str>) -> Result { 2 => { let (user, pass) = (args[0].to_owned(), args[1].to_owned()); Ok(Command::Authenticate(Some(ClientCredentials { - id: ClientId { get: user }, - secret: ClientSecret { get: pass }, + id: ClientId(user), + secret: ClientSecret(pass) }))) } _ => Err(Error::Command(format!("unexpected auth args: {:?}", args))), @@ -180,8 +180,8 @@ mod tests { assert_eq!("Authenticate".parse::().unwrap(), Command::Authenticate(None)); assert_eq!("auth user pass".parse::().unwrap(), Command::Authenticate(Some(ClientCredentials { - id: ClientId { get: "user".to_owned() }, - secret: ClientSecret { get: "pass".to_owned() }, + id: ClientId("user".to_owned()), + secret: ClientSecret("pass".to_owned()), }))); assert!("auth one".parse::().is_err()); assert!("auth one two three".parse::().is_err()); diff --git a/src/datatype/config.rs b/src/datatype/config.rs index daab773..b1fa894 100644 --- a/src/datatype/config.rs +++ b/src/datatype/config.rs @@ -200,9 +200,9 @@ pub fn load_config(path: &str) -> Result { #[cfg(test)] mod tests { - use super::*; + const DEFAULT_CONFIG_STRING: &'static str = r#" [auth] @@ -240,7 +240,4 @@ mod tests { assert_eq!(load_config("").unwrap(), Config::default()) } - - - } diff --git a/src/datatype/error.rs b/src/datatype/error.rs index 03d96aa..388ee9f 100644 --- a/src/datatype/error.rs +++ b/src/datatype/error.rs @@ -1,16 +1,18 @@ +use hyper::error::Error as HyperError; +use hyper::client::ClientError as HyperClientError; use std::convert::From; use std::fmt::{Display, Formatter, Result as FmtResult}; use std::io::Error as IoError; use std::string::FromUtf8Error; use std::sync::PoisonError; -use std::sync::mpsc::SendError; +use std::sync::mpsc::{SendError, RecvError}; use toml::{ParserError as TomlParserError, DecodeError as TomlDecodeError}; use url::ParseError as UrlParseError; use datatype::Event; use rustc_serialize::json::{EncoderError as JsonEncoderError, DecoderError as JsonDecoderError}; -use hyper::error::Error as HyperError; use ws::Error as WebsocketError; +use super::super::http_client::auth_client::AuthHandler; #[derive(Debug)] @@ -20,12 +22,14 @@ pub enum Error { Command(String), FromUtf8Error(FromUtf8Error), HyperError(HyperError), + HyperClientError(HyperClientError), IoError(IoError), JsonDecoderError(JsonDecoderError), JsonEncoderError(JsonEncoderError), PoisonError(String), PackageError(String), ParseError(String), + RecvError(RecvError), SendErrorEvent(SendError), TomlParserErrors(Vec), TomlDecodeError(TomlDecodeError), @@ -39,6 +43,12 @@ impl From> for Error { } } +impl From for Error { + fn from(e: RecvError) -> Error { + Error::RecvError(e) + } +} + impl From> for Error { fn from(e: PoisonError) -> Error { Error::PoisonError(format!("{}", e)) @@ -51,6 +61,12 @@ impl From> for Error { } } +impl From> for Error { + fn from(e: HyperClientError) -> Error { + Error::HyperClientError(e) + } +} + // To derive From implementations for the other errors we use the // following macro. macro_rules! derive_from { @@ -66,37 +82,39 @@ macro_rules! derive_from { } } -derive_from!( - [ JsonEncoderError - , JsonDecoderError - , HyperError - , FromUtf8Error - , IoError - , UrlParseError - , TomlDecodeError - , WebsocketError - ]); +derive_from!([ + FromUtf8Error, + HyperError, + IoError, + JsonEncoderError, + JsonDecoderError, + TomlDecodeError, + UrlParseError, + WebsocketError +]); impl Display for Error { fn fmt(&self, f: &mut Formatter) -> FmtResult { let inner: String = match *self { - Error::ClientError(ref s) => format!("Http client error: {}", s.clone()), + Error::ClientError(ref s) => format!("Http client error: {}", s.clone()), Error::AuthorizationError(ref s) => format!("Http client authorization error: {}", s.clone()), - Error::Command(ref e) => format!("Unknown Command: {}", e.clone()), - Error::FromUtf8Error(ref e) => format!("From utf8 error: {}", e.clone()), - Error::HyperError(ref e) => format!("Hyper error: {}", e.clone()), - Error::IoError(ref e) => format!("IO error: {}", e.clone()), - Error::JsonDecoderError(ref e) => format!("Failed to decode JSON: {}", e.clone()), - Error::JsonEncoderError(ref e) => format!("Failed to encode JSON: {}", e.clone()), - Error::PoisonError(ref e) => format!("Poison error, {}", e.clone()), - Error::PackageError(ref s) => s.clone(), - Error::ParseError(ref s) => s.clone(), - Error::SendErrorEvent(ref s) => format!("Send error for Event: {}", s.clone()), - Error::TomlDecodeError(ref e) => format!("Toml decode error: {}", e.clone()), - Error::TomlParserErrors(ref e) => format!("Toml parser errors: {:?}", e.clone()), - Error::UrlParseError(ref s) => format!("Url parse error: {}", s.clone()), - Error::WebsocketError(ref e) => format!("Websocket Error{:?}", e.clone()), + Error::Command(ref e) => format!("Unknown Command: {}", e.clone()), + Error::FromUtf8Error(ref e) => format!("From utf8 error: {}", e.clone()), + Error::HyperError(ref e) => format!("Hyper error: {}", e.clone()), + Error::HyperClientError(ref e) => format!("Hyper client error: {}", e.clone()), + Error::IoError(ref e) => format!("IO error: {}", e.clone()), + Error::JsonDecoderError(ref e) => format!("Failed to decode JSON: {}", e.clone()), + Error::JsonEncoderError(ref e) => format!("Failed to encode JSON: {}", e.clone()), + Error::PoisonError(ref e) => format!("Poison error, {}", e.clone()), + Error::PackageError(ref s) => s.clone(), + Error::ParseError(ref s) => s.clone(), + Error::RecvError(ref s) => format!("Recv error: {}", s.clone()), + Error::SendErrorEvent(ref s) => format!("Send error for Event: {}", s.clone()), + Error::TomlDecodeError(ref e) => format!("Toml decode error: {}", e.clone()), + Error::TomlParserErrors(ref e) => format!("Toml parser errors: {:?}", e.clone()), + Error::UrlParseError(ref s) => format!("Url parse error: {}", s.clone()), + Error::WebsocketError(ref e) => format!("Websocket Error{:?}", e.clone()), }; write!(f, "{}", inner) } diff --git a/src/datatype/method.rs b/src/datatype/method.rs index 2b8cec2..628cea9 100644 --- a/src/datatype/method.rs +++ b/src/datatype/method.rs @@ -2,7 +2,7 @@ use std::borrow::Cow; use hyper::method; -#[derive(Clone)] +#[derive(Clone, Debug)] pub enum Method { Get, Post, diff --git a/src/datatype/mod.rs b/src/datatype/mod.rs index 74fa16a..34b600a 100644 --- a/src/datatype/mod.rs +++ b/src/datatype/mod.rs @@ -1,4 +1,5 @@ pub use self::access_token::AccessToken; +pub use self::auth::Auth; pub use self::client_credentials::{ClientId, ClientSecret, ClientCredentials}; pub use self::command::Command; pub use self::config::{Config, AuthConfig, OtaConfig, TestConfig}; @@ -11,6 +12,7 @@ pub use self::update_request::{UpdateRequestId, UpdateState, PendingUpdateReques pub use self::url::Url; pub mod access_token; +pub mod auth; pub mod client_credentials; pub mod command; pub mod config; diff --git a/src/datatype/url.rs b/src/datatype/url.rs index 335a3f7..8b016fa 100644 --- a/src/datatype/url.rs +++ b/src/datatype/url.rs @@ -1,38 +1,27 @@ -use hyper::client::IntoUrl; -use hyper; use rustc_serialize::{Decoder, Decodable}; use std::borrow::Cow; -use url::ParseError; use url; use datatype::Error; #[derive(PartialEq, Eq, Clone, Debug)] -pub struct Url { - get: url::Url -} +pub struct Url(pub url::Url); impl Url { - pub fn parse(s: &str) -> Result { let url = try!(url::Url::parse(s)); - Ok(Url { get: url }) + Ok(Url(url)) } pub fn join(&self, suf: &str) -> Result { - let url = try!(self.get.join(suf)); - Ok(Url { get: url }) + let url = try!(self.0.join(suf)); + Ok(Url(url)) } -} - -impl IntoUrl for Url { - - fn into_url(self) -> Result { - Ok(self.get) + pub fn inner(&self) -> url::Url { + self.0.clone() } - } impl<'a> Into> for Url { @@ -41,20 +30,15 @@ impl<'a> Into> for Url { } } - impl ToString for Url { - fn to_string(&self) -> String { - self.get.to_string() + self.0.to_string() } - } impl Decodable for Url { - fn decode(d: &mut D) -> Result { let s = try!(d.read_str()); - Url::parse(&s) - .map_err(|e| d.error(&e.to_string())) + Url::parse(&s).map_err(|e| d.error(&e.to_string())) } } diff --git a/src/http_client/auth_client.rs b/src/http_client/auth_client.rs new file mode 100644 index 0000000..c9a9ab6 --- /dev/null +++ b/src/http_client/auth_client.rs @@ -0,0 +1,235 @@ +use hyper; +use hyper::{Encoder, Decoder, Next}; +use hyper::client::{Client, Handler, HttpsConnector, Request, Response}; +use hyper::header::{Authorization, Basic, Bearer, ContentLength, ContentType, Location}; +use hyper::mime::{Attr, Mime, TopLevel, SubLevel, Value}; +use hyper::net::{HttpStream, HttpsStream, OpensslStream, Openssl}; +use std::io::{Read, Write, ErrorKind}; +use std::sync::mpsc::Sender; +use std::time::Duration; + +use datatype::{Auth, Error}; +use http_client::{HttpClient, HttpRequest, HttpResponse}; + + +#[derive(Clone)] +pub struct AuthClient { + auth: Auth, + client: Client, +} + +impl AuthClient { + pub fn new(auth: Auth) -> AuthClient { + let client = Client::::configure() + .keep_alive(true) + .max_sockets(1024) + .connector(HttpsConnector::new(Openssl::default())) + .build() + .expect("unable to create a new hyper Client"); + + AuthClient { + auth: auth, + client: client, + } + } +} + +impl HttpClient for AuthClient { + fn chan_request(&self, req: HttpRequest, resp_tx: Sender) { + debug!("send_request_to: {:?}", req.url); + let result = self.client.request(req.url.inner(), AuthHandler { + auth: self.auth.clone(), + req: req, + timeout: Duration::from_secs(20), + resp_tx: resp_tx.clone(), + }); + if let Err(err) = result { + let _ = resp_tx.send(Err(Error::from(err))); + }; + } +} + + +// FIXME: uncomment when yocto is at 1.8.0: #[derive(Debug)] +pub struct AuthHandler { + auth: Auth, + req: HttpRequest, + timeout: Duration, + resp_tx: Sender, +} + +// FIXME: required for building on 1.7.0 only +impl ::std::fmt::Debug for AuthHandler { + fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result { + write!(f, "unimplemented") + } +} + +pub type Stream = HttpsStream>; + +impl Handler for AuthHandler { + fn on_request(&mut self, req: &mut Request) -> Next { + info!("on_request"); + req.set_method(self.req.method.clone().into()); + let mut headers = req.headers_mut(); + + match self.auth.clone() { + Auth::None => { + headers.set(ContentType(Mime(TopLevel::Application, SubLevel::Json, + vec![(Attr::Charset, Value::Utf8)]))); + } + + Auth::Credentials(ref id, ref secret) => { + headers.set(Authorization(Basic { username: id.0.clone(), + password: Some(secret.0.clone()) })); + headers.set(ContentType(Mime(TopLevel::Application, SubLevel::WwwFormUrlEncoded, + vec![(Attr::Charset, Value::Utf8)]))); + if let Some(_) = self.req.body { + panic!("no request body expected for Auth::Credentials") + }; + self.req.body = Some("grant_type=client_credentials".to_owned().into_bytes()); + } + + Auth::Token(token) => { + headers.set(Authorization(Bearer { token: token.access_token.clone() })); + headers.set(ContentType(Mime(TopLevel::Application, SubLevel::Json, + vec![(Attr::Charset, Value::Utf8)]))); + } + }; + + match self.req.body { + Some(ref body) => { + headers.set(ContentLength(body.len() as u64)); + Next::write() + } + + None => Next::read().timeout(self.timeout) + } + } + + fn on_request_writable(&mut self, encoder: &mut Encoder) -> Next { + info!("on_request_writable"); + + match self.req.body { + Some(ref body) => match encoder.write_all(body) { + Ok(_) => Next::read().timeout(self.timeout), + + Err(err) => match err.kind() { + ErrorKind::WouldBlock => Next::write(), + _ => { + error!("unable to write body: {}", err); + let _ = self.resp_tx.send(Err(Error::from(err))); + Next::remove() + } + } + }, + + None => panic!("on_request_writable called on an empty body") + } + } + + fn on_response(&mut self, resp: Response) -> Next { + info!("on_response: status: {}, headers:\n{}", resp.status(), resp.headers()); + + if resp.status().is_success() { + Next::read() + } else if resp.status().is_redirection() { + let _ = match resp.headers().get::() { + Some(&Location(ref loc)) => match self.req.url.join(loc) { + Ok(url) => { + debug!("redirecting to {:?}", url); + let client = AuthClient::new(Auth::None); + let body = match self.req.body { + Some(ref data) => Some(data.clone()), + None => None + }; + let resp_rx = client.send_request(HttpRequest { + url: url, + method: self.req.method.clone(), + body: body, + }); + match resp_rx.recv() { + Ok(resp) => match resp { + Ok(data) => self.resp_tx.send(Ok(data)), + Err(err) => self.resp_tx.send(Err(Error::from(err))) + }, + Err(err) => self.resp_tx.send(Err(Error::from(err))) + } + } + + Err(err) => self.resp_tx.send(Err(Error::from(err))) + }, + + None => { + let msg = "redirection without Location header".to_owned(); + error!("{}", msg); + self.resp_tx.send(Err(Error::ClientError(msg))) + } + }; + + Next::end() + } else { + let msg = format!("failed response status: {}", resp.status()); + error!("{}", msg); + let _ = self.resp_tx.send(Err(Error::ClientError(msg))); + Next::end() + } + } + + fn on_response_readable(&mut self, decoder: &mut Decoder) -> Next { + info!("on_response_readable"); + let mut data: Vec = Vec::new(); + let _ = decoder.read_to_end(&mut data); + let _ = self.resp_tx.send(Ok(data)); + Next::end() + } + + fn on_error(&mut self, err: hyper::Error) -> Next { + error!("on_error: {}", err); + let _ = self.resp_tx.send(Err(Error::from(err))); + Next::remove() + } +} + + +#[cfg(test)] +mod tests { + use rustc_serialize::json::Json; + + use super::*; + use datatype::{Auth, Method, Url}; + use http_client::{HttpClient, HttpRequest}; + + + #[test] + fn test_send_get_request() { + let client = AuthClient::new(Auth::None); + let req = HttpRequest { + method: Method::Get, + url: Url::parse("http://eu.httpbin.org/bytes/16?seed=123").unwrap(), + body: None, + }; + + let resp_rx = client.send_request(req); + let data = resp_rx.recv().unwrap().unwrap(); + assert_eq!(data, vec![13, 22, 104, 27, 230, 9, 137, 85, 218, 40, 86, 85, 62, 0, 111, 22]); + } + + #[test] + fn test_send_post_request() { + let client = AuthClient::new(Auth::None); + let req = HttpRequest { + method: Method::Post, + url: Url::parse("https://eu.httpbin.org/post").unwrap(), + body: Some("foo".to_owned().into_bytes()), + }; + + let resp_rx = client.send_request(req); + let body = resp_rx.recv().unwrap().unwrap(); + let resp = String::from_utf8(body).unwrap(); + let json = Json::from_str(&resp).unwrap(); + let obj = json.as_object().unwrap(); + let data = obj.get("data").unwrap().as_string().unwrap(); + assert_eq!(data, "foo"); + } +} diff --git a/src/http_client/http_client.rs b/src/http_client/http_client.rs index e2f6e96..938f5c0 100644 --- a/src/http_client/http_client.rs +++ b/src/http_client/http_client.rs @@ -1,146 +1,23 @@ -use rustc_serialize::json; -use std::borrow::Cow; -use std::fs::File; -use std::io::SeekFrom; -use std::io::prelude::*; -use tempfile; -use time; +use std::sync::mpsc::{Sender, Receiver, channel}; -use datatype::{AccessToken, ClientId, ClientSecret, Error, Method, Url}; +use datatype::{Error, Method, Url}; -#[derive(Clone)] -pub enum Auth<'a> { - Credentials(ClientId, ClientSecret), - Token(&'a AccessToken), -} - -impl<'a> Into>> for Auth<'a> { - fn into(self) -> Cow<'a, Auth<'a>> { - Cow::Owned(self) +pub trait HttpClient { + fn send_request(&self, req: HttpRequest) -> Receiver { + let (resp_tx, resp_rx): (Sender, Receiver) = channel(); + self.chan_request(req, resp_tx); + resp_rx } -} - -pub struct HttpRequest<'a> { - pub method: Cow<'a, Method>, - pub url: Cow<'a, Url>, - pub auth: Option>>, - pub body: Option>, -} - -impl<'a> HttpRequest<'a> { - fn new(meth: M, - url: U, - auth: Option, - body: Option) -> HttpRequest<'a> - where - M: Into>, - U: Into>, - A: Into>>, - B: Into> - { - HttpRequest { - method: meth.into(), - url: url.into(), - auth: auth.map(|a| a.into()), - body: body.map(|b| b.into()), - } - } - - pub fn get(url: U, auth: Option) -> HttpRequest<'a> - where - U: Into>, - A: Into>>, - { - HttpRequest::new::(Method::Get, url, auth, None) - } - - pub fn post(url: U, auth: Option, body: Option) -> HttpRequest<'a> - where - U: Into>, - A: Into>>, - B: Into> - { - HttpRequest::new(Method::Post, url, auth, body) - } - - pub fn put(url: U, auth: Option, body: Option) -> HttpRequest<'a> - where - U: Into>, - A: Into>>, - B: Into> - { - HttpRequest::new(Method::Put, url, auth, body) - } -} - -impl<'a> ToString for HttpRequest<'a> { - fn to_string(&self) -> String { - format!("{} {}", self.method.to_string(), self.url.to_string()) - } -} - -#[derive(RustcEncodable, RustcDecodable)] -pub enum HttpStatus { - Ok, -} - -impl ToString for HttpStatus { - fn to_string(&self) -> String { - match *self { - HttpStatus::Ok => "200".to_string() - } - } + fn chan_request(&self, req: HttpRequest, resp_tx: Sender); } -#[derive(RustcEncodable, RustcDecodable)] -pub struct HttpResponse { - pub status: HttpStatus, - pub body: Vec, +#[derive(Debug)] +pub struct HttpRequest { + pub method: Method, + pub url: Url, + pub body: Option> } -pub trait HttpClient: Send + Sync { - - fn send_request_to(&mut self, req: &HttpRequest, file: &mut File) -> Result<(), Error> { - - let t0 = time::precise_time_ns(); - let resp = try!(self.send_request(req)); - let t1 = time::precise_time_ns(); - - let latency = t1 - t0; - - info!("HttpClient::send_request_to, request: {}, response status: {}, latency: {} ns", - req.to_string(), resp.status.to_string(), latency); - - let json = try!(json::encode(&resp)); - - Ok(try!(file.write_all(&json.as_bytes()))) - - } - - fn send_request(&mut self, req: &HttpRequest) -> Result { - - let mut temp_file: File = try!(tempfile::tempfile()); - - let t0 = time::precise_time_ns(); - try!(self.send_request_to(req, &mut temp_file)); - let t1 = time::precise_time_ns(); - - let latency = t1 - t0; - - try!(temp_file.seek(SeekFrom::Start(0))); - - let mut buf = String::new(); - let _: usize = try!(temp_file.read_to_string(&mut buf)); - - let resp: HttpResponse = try!(json::decode(&buf)); - - info!("HttpClient::send_request, request: {}, response status: {}, latency: {} ns", - req.to_string(), resp.status.to_string(), latency); - - Ok(resp) - - } - -} +pub type HttpResponse = Result, Error>; diff --git a/src/http_client/hyper.rs b/src/http_client/hyper.rs deleted file mode 100644 index ed1f6fc..0000000 --- a/src/http_client/hyper.rs +++ /dev/null @@ -1,205 +0,0 @@ -use hyper::Client; -use hyper::client::RedirectPolicy; -use hyper::client::response::Response; -use hyper::status::StatusCode; -use hyper::header::{Authorization, Basic, Bearer, ContentType, Headers, Location}; -use hyper::mime::{Attr, Mime, TopLevel, SubLevel, Value}; -use rustc_serialize::json; -use std::fs::File; -use std::io::{copy, Read}; - -use datatype::Error; -use http_client::{Auth, HttpClient, HttpRequest, HttpResponse, HttpStatus}; - - -pub struct Hyper { - client: Client, -} - -impl Hyper { - pub fn new() -> Hyper { - let mut client = Client::new(); - client.set_redirect_policy(RedirectPolicy::FollowNone); - Hyper { client: client } - } -} - -impl HttpClient for Hyper { - - fn send_request_to(&mut self, req: &HttpRequest, file: &mut File) -> Result<(), Error> { - - debug!("send_request_to, request: {}", req.to_string()); - - let mut headers = Headers::new(); - let mut req_body = String::new(); - - match (req.auth.clone().map(|a| a.into_owned()), req.body.to_owned()) { - - (None, None) => {} - - (Some(Auth::Credentials(ref id, ref secret)), None) => { - - headers.set(Authorization(Basic { - username: id.get.clone(), - password: Some(secret.get.clone()) - })); - - headers.set(ContentType(Mime( - TopLevel::Application, - SubLevel::WwwFormUrlEncoded, - vec![(Attr::Charset, Value::Utf8)]))); - - req_body.push_str("grant_type=client_credentials") - - } - - (Some(Auth::Token(token)), body) => { - - headers.set(Authorization(Bearer { - token: token.access_token.clone() - })); - - if let Some(json) = body { - - headers.set(ContentType(Mime( - TopLevel::Application, - SubLevel::Json, - vec![(Attr::Charset, Value::Utf8)]))); - - req_body.push_str(&json) - - } - - } - - _ => panic!("hyper's send_request_to has been misused, this is a bug.") - - } - - debug!("send_request_to, headers: `{}`", headers); - debug!("send_request_to, req_body: `{}`", req_body); - - let mut resp = try!(self.client - .request(req.method.clone().into_owned().into(), - req.url.clone().into_owned()) - .headers(headers) - .body(&req_body) - .send()); - - if resp.status.is_success() { - let mut data = Vec::new(); - let _: usize = try!(resp.read_to_end(&mut data)); - let resp = HttpResponse { - status: HttpStatus::Ok, - body: data, - }; - let json = try!(json::encode(&resp)); - let _: u64 = try!(copy(&mut json.as_bytes(), file)); - Ok(()) - } else if resp.status.is_redirection() { - let req = try!(relocate_request(req, &resp)); - self.send_request_to(&req, file) - } else { - let mut rbody = String::new(); - let _: usize = try!(resp.read_to_string(&mut rbody)); - if resp.status == StatusCode::Forbidden { - Err(Error::AuthorizationError(format!("Unauthorized request: status {}, body: {}", resp.status, rbody))) - } else { - Err(Error::ClientError(format!("Request errored with status {}, body: {}", resp.status, rbody))) - } - } - - } - -} - -fn relocate_request<'a>(req: &'a HttpRequest, resp: &Response) -> Result, Error> { - - if let Some(&Location(ref loc)) = resp.headers.get::() { - - let url = try!(req.url.join(loc)); - - Ok(HttpRequest { - url: url.into(), - method: req.method.clone(), - auth: None, - body: req.body.clone(), - }) - - } else { - Err(Error::ClientError("Redirect with no Location header".to_string())) - } - -} - -#[cfg(test)] -mod tests { - use rustc_serialize::json; - use std::fs::File; - use std::io::SeekFrom; - use std::io::prelude::*; - use tempfile; - - use super::*; - use datatype::Url; - use http_client::{Auth, HttpClient, HttpRequest, HttpResponse}; - - - #[test] - fn test_send_request_get() { - - let mut client: &mut HttpClient = &mut Hyper::new(); - - let req = HttpRequest::get::<_, Auth>( - Url::parse("https://eu.httpbin.org/get").unwrap(), None); - - let resp: HttpResponse = client.send_request(&req).unwrap(); - - assert!(!resp.body.is_empty()) - - } - - #[test] - fn test_send_request_to_get() { - - let mut client = &mut Hyper::new(); - - let req = HttpRequest::get::<_, Auth>( - Url::parse("https://eu.httpbin.org/get").unwrap(), None); - - let mut temp_file: File = tempfile::tempfile().unwrap(); - client.send_request_to(&req, &mut temp_file).unwrap(); - - temp_file.seek(SeekFrom::Start(0)).unwrap(); - - let mut buf = String::new(); - let _: usize = temp_file.read_to_string(&mut buf).unwrap(); - - assert!(buf != "".to_string()) - - } - - #[test] - fn test_send_request_to_binary() { - - let mut client = &mut Hyper::new(); - - let req = HttpRequest::get::<_, Auth>( - Url::parse("https://eu.httpbin.org/bytes/16?seed=123").unwrap(), None); - - let mut temp_file: File = tempfile::tempfile().unwrap(); - client.send_request_to(&req, &mut temp_file).unwrap(); - - temp_file.seek(SeekFrom::Start(0)).unwrap(); - - let mut buf = String::new(); - let _: usize = temp_file.read_to_string(&mut buf).unwrap(); - - let resp: HttpResponse = json::decode(&buf).unwrap(); - - assert_eq!(resp.body, vec![13, 22, 104, 27, 230, 9, 137, 85, - 218, 40, 86, 85, 62, 0, 111, 22]) - - } - -} diff --git a/src/http_client/mod.rs b/src/http_client/mod.rs index c3a0450..3e7687f 100644 --- a/src/http_client/mod.rs +++ b/src/http_client/mod.rs @@ -1,7 +1,7 @@ -pub use self::http_client::{Auth, HttpClient, HttpRequest, HttpResponse, HttpStatus}; -pub use self::hyper::Hyper; -pub use self::test::TestHttpClient; +pub use self::auth_client::{AuthClient, AuthHandler}; +pub use self::http_client::{HttpClient, HttpRequest, HttpResponse}; +pub use self::test_client::TestHttpClient; +pub mod auth_client; pub mod http_client; -pub mod hyper; -pub mod test; +pub mod test_client; diff --git a/src/http_client/test.rs b/src/http_client/test.rs deleted file mode 100644 index 9356d9f..0000000 --- a/src/http_client/test.rs +++ /dev/null @@ -1,34 +0,0 @@ -use datatype::Error; -use http_client::{HttpClient, HttpRequest, HttpResponse, HttpStatus}; - - -pub struct TestHttpClient<'a> { - replies: Vec<&'a str>, -} - -impl<'a> TestHttpClient<'a> { - - pub fn new() -> TestHttpClient<'a> { - TestHttpClient { replies: Vec::new() } - } - - pub fn from(replies: Vec<&'a str>) -> TestHttpClient<'a> { - TestHttpClient { replies: replies } - } - -} - -impl<'a> HttpClient for TestHttpClient<'a> { - - fn send_request(&mut self, req: &HttpRequest) -> Result { - - self.replies.pop() - .ok_or(Error::ClientError(req.to_string())) - .map(|s| HttpResponse - { status: HttpStatus::Ok, - body: s.as_bytes().to_vec(), - }) - - } - -} diff --git a/src/http_client/test_client.rs b/src/http_client/test_client.rs new file mode 100644 index 0000000..0ae0137 --- /dev/null +++ b/src/http_client/test_client.rs @@ -0,0 +1,29 @@ +use http_client::{HttpClient, HttpRequest, HttpResponse}; +use std::cell::RefCell; +use std::sync::mpsc::Sender; + +use datatype::Error; + + +pub struct TestHttpClient { + replies: RefCell>> +} + +impl TestHttpClient { + pub fn new() -> TestHttpClient { + TestHttpClient { replies: RefCell::new(Vec::new()) } + } + + pub fn from(replies: Vec>) -> TestHttpClient { + TestHttpClient { replies: RefCell::new(replies) } + } +} + +impl HttpClient for TestHttpClient { + fn chan_request(&self, req: HttpRequest, resp_tx: Sender) { + match self.replies.borrow_mut().pop() { + Some(body) => { let _ = resp_tx.send(Ok(body)); } + None => { let _ = resp_tx.send(Err(Error::ClientError(req.url.to_string()))); } + } + } +} diff --git a/src/interaction_library/http.rs b/src/interaction_library/http.rs index ff96e52..e57e0c0 100644 --- a/src/interaction_library/http.rs +++ b/src/interaction_library/http.rs @@ -1,34 +1,38 @@ +use hyper::{Decoder, Encoder, Next, StatusCode}; +use hyper::net::HttpStream; +use hyper::server::{Handler, Server, Request, Response}; use rustc_serialize::{json, Decodable, Encodable}; use std::{env, thread}; -use std::io::Read; +use std::fmt::Debug; +use std::io::{ErrorKind, Read, Write}; use std::sync::{Arc, Mutex, mpsc}; use std::sync::mpsc::{Sender, Receiver}; -use hyper::status::StatusCode; -use hyper::server::{Handler, Server, Request, Response}; +use std::time::Duration; use super::gateway::{Gateway, Interpret}; -use datatype::{Error, Event}; pub struct Http { irx: Arc>>>, } -impl Gateway for Http - where C: Send + Decodable + 'static, - E: Send + Encodable + 'static +impl Gateway for Http + where C: Decodable + Send + Clone + Debug + 'static, + E: Encodable + Send + Clone + 'static { fn new() -> Http { let (itx, irx): (Sender>, Receiver>) = mpsc::channel(); - let handler = HttpHandler { itx: Arc::new(Mutex::new(itx)) }; - let addr = env::var("OTA_PLUS_CLIENT_HTTP_ADDR") - .unwrap_or("127.0.0.1:8888".to_string()); + let itx = Arc::new(Mutex::new(itx)); + let irx = Arc::new(Mutex::new(irx)); - thread::spawn(move || { - Server::http(&addr as &str).unwrap().handle(handler).unwrap(); - }); + let addr = env::var("OTA_PLUS_CLIENT_HTTP_ADDR").unwrap_or("127.0.0.1:8888".to_string()); + let server = Server::http(&addr.parse().unwrap()).unwrap(); + let (addr, server) = server.handle(move |_| HttpHandler::new(itx.clone())).unwrap(); + + thread::spawn(move || { server.run() }); + info!("Listening on http://{}", addr); - Http { irx: Arc::new(Mutex::new(irx)) } + Http { irx: irx } } fn next(&self) -> Option> { @@ -43,55 +47,122 @@ impl Gateway for Http } -pub struct HttpHandler { - itx: Arc>>>, +pub struct HttpHandler + where C: Decodable + Send + Clone + Debug, + E: Encodable + Send + Clone +{ + itx: Arc>>>, + erx: Option>, + body: Option>, } -impl Handler for HttpHandler - where C: Send + Decodable + Clone, +impl HttpHandler + where C: Decodable + Send + Clone + Debug, + E: Encodable + Send + Clone +{ + fn new(itx: Arc>>>) -> HttpHandler { + HttpHandler { itx: itx, erx: None, body: None } + } +} + +impl Handler for HttpHandler + where C: Send + Decodable + Clone + Debug, E: Send + Encodable + Clone { - fn handle(&self, req: Request, resp: Response) { - worker(self, req, resp).unwrap_or_else(|err| { - error!("error handling request: {}", err); - }); + fn on_request(&mut self, req: Request) -> Next { + info!("on_request: {} {}", req.method(), req.uri()); + Next::read() + } + + fn on_request_readable(&mut self, transport: &mut Decoder) -> Next { + info!("on_request_readable"); + + let mut data = Vec::new(); + match transport.read_to_end(&mut data) { + Ok(_) => match String::from_utf8(data) { + Ok(body) => match json::decode::(&body) { + Ok(c) => { + info!("on_request_readable: decoded command: {:?}", c); + + let (etx, erx): (Sender, Receiver) = mpsc::channel(); + self.erx = Some(erx); + self.itx.lock().unwrap().send(Interpret { + cmd: c, + etx: Some(Arc::new(Mutex::new(etx))), + }).unwrap(); + + Next::write().timeout(Duration::from_secs(10)) + } + + Err(err) => { + error!("on_request_readable decode json: {}", err); + Next::remove() + } + }, - fn worker(handler: &HttpHandler, - mut req: Request, - mut resp: Response) - -> Result<(), Error> - where C: Send + Decodable + Clone, - E: Send + Encodable + Clone - { - // return 500 response on error - *resp.status_mut() = StatusCode::InternalServerError; - - let mut req_body = String::new(); - let _: usize = try!(req.read_to_string(&mut req_body)); - let c: C = try!(json::decode(&req_body)); - - let (etx, erx): (Sender, Receiver) = mpsc::channel(); - debug!("sending request body: {}", req_body); - handler.itx.lock().unwrap().send(Interpret { - cmd: c, - etx: Some(Arc::new(Mutex::new(etx))), - }).unwrap(); - - match erx.recv() { - Ok(e) => { - let resp_body = try!(json::encode(&e)); - *resp.status_mut() = StatusCode::Ok; - debug!("sending response body: {}", resp_body); - resp.send(resp_body.as_bytes()).unwrap(); - } Err(err) => { - error!("error forwarding request: {}", err); - let ev = json::encode(&Event::Error(format!("{}", err))).unwrap(); - resp.send(ev.as_bytes()).unwrap(); + error!("on_request_readable parse string: {}", err); + Next::remove() + } + }, + + Err(err) => match err.kind() { + ErrorKind::WouldBlock => Next::read(), + _ => { + error!("on_request_readable read_to_end: {}", err); + Next::remove() } } + } + } + + fn on_response(&mut self, resp: &mut Response) -> Next { + info!("on_response: status {}", resp.status()); - Ok(()) + match self.erx { + Some(ref rx) => match rx.recv() { + Ok(e) => match json::encode(&e) { + Ok(body) => { + resp.set_status(StatusCode::Ok); + self.body = Some(body.into_bytes()); + Next::write() + } + + Err(err) => { + error!("on_response encoding json: {:?}", err); + resp.set_status(StatusCode::InternalServerError); + Next::end() + } + }, + + Err(err) => { + error!("on_response receiver: {}", err); + resp.set_status(StatusCode::InternalServerError); + Next::end() + } + }, + + None => panic!("expected Some receiver") + } + } + + fn on_response_writable(&mut self, transport: &mut Encoder) -> Next { + info!("on_response_writable"); + + match self.body { + Some(ref body) => match transport.write_all(body) { + Ok(_) => Next::end(), + + Err(err) => match err.kind() { + ErrorKind::WouldBlock => Next::write(), + _ => { + error!("unable to write body: {}", err); + Next::remove() + } + } + }, + + None => panic!("on_response_writable called on empty body") } } } @@ -100,15 +171,14 @@ impl Handler for HttpHandler #[cfg(test)] mod tests { use crossbeam; - use hyper::Client; use rustc_serialize::json; use std::thread; - use std::io::Read; use std::sync::mpsc::{channel, Sender, Receiver}; use super::*; use super::super::gateway::Gateway; - use super::super::super::datatype::{Command, Event}; + use super::super::super::datatype::{Auth, Command, Event, Method, Url}; + use super::super::super::http_client::{AuthClient, HttpClient, HttpRequest}; use super::super::super::interpreter::Wrapped; @@ -138,18 +208,20 @@ mod tests { crossbeam::scope(|scope| { for id in 0..10 { scope.spawn(move || { - let client = Client::new(); - let cmd = Command::AcceptUpdate(format!("{}", id)); - + let client = AuthClient::new(Auth::None); + let cmd = Command::AcceptUpdate(format!("{}", id)); let req_body = json::encode(&cmd).unwrap(); - let mut resp = client.post("http://127.0.0.1:8888/") - .body(&req_body) - .send() - .unwrap(); - - let mut resp_body = String::new(); - resp.read_to_string(&mut resp_body).unwrap(); - let ev: Event = json::decode(&resp_body).unwrap(); + + let req = HttpRequest { + method: Method::Post, + url: Url::parse("http://127.0.0.1:8888").unwrap(), + body: Some(req_body.into_bytes()), + }; + let resp_rx = client.send_request(req); + let resp = resp_rx.recv().unwrap().unwrap(); + + let text = String::from_utf8(resp).unwrap(); + let ev: Event = json::decode(&text).unwrap(); assert_eq!(ev, Event::Error(format!("{}", id))); }); } diff --git a/src/interpreter.rs b/src/interpreter.rs index b3e9f79..ea1880d 100644 --- a/src/interpreter.rs +++ b/src/interpreter.rs @@ -3,23 +3,22 @@ use std::process::exit; use std::sync::{Arc, Mutex}; use std::sync::mpsc::{Sender, Receiver, channel}; -use oauth2::authenticate; -use datatype::{AccessToken, Command, Config, Error, Event, UpdateState}; +use datatype::{AccessToken, Auth, ClientId, ClientSecret, Command, Config, + Error, Event, UpdateState}; use datatype::Command::*; -use http_client::HttpClient; +use http_client::AuthClient; use interaction_library::gateway::Interpret; -use ota_plus::{get_package_updates, install_package_update, update_installed_packages, - send_install_report}; +use oauth2::authenticate; +use ota_plus::OTA; pub type Wrapped = Interpret; -#[derive(Clone)] -pub struct Env<'a> { + +pub struct Env<'t> { pub config: Config, - pub access_token: Option>, - pub http_client: Arc>, - pub feedback_tx: Sender, + pub access_token: Option>, + pub wtx: Sender, } @@ -29,7 +28,7 @@ pub trait Interpreter { fn run(env: &mut Env, irx: Receiver, otx: Sender) { loop { match irx.recv() { - Ok(msg) => Self::interpret(env, msg, otx.clone()), + Ok(msg) => Self::interpret(env, msg, otx.clone()), Err(err) => error!("Error receiving command: {:?}", err), } } @@ -102,21 +101,22 @@ impl Interpreter<(), Event, Command> for AutoPackageInstaller { pub struct GlobalInterpreter; -impl<'a> Interpreter, Wrapped, Event> for GlobalInterpreter { +impl<'t> Interpreter, Wrapped, Event> for GlobalInterpreter { fn interpret(env: &mut Env, w: Wrapped, global_tx: Sender) { info!("Interpreting: {:?}", w.cmd); let (multi_tx, multi_rx): (Sender, Receiver) = channel(); let local_tx = w.etx.clone(); - - let w2 = w.clone(); + let w_clone = w.clone(); let _ = command_interpreter(env, w.cmd, multi_tx) .map_err(|err| { if let Error::AuthorizationError(_) = err { - // retry authorization and request - let _ = env.feedback_tx.send(Wrapped { cmd: Command::Authenticate(None), - etx: None }); - let _ = env.feedback_tx.send(w2); + debug!("retry authorization and request"); + let _ = env.wtx.send(Wrapped { + cmd: Command::Authenticate(None), + etx: None + }); + let _ = env.wtx.send(w_clone); } else { let ev = Event::Error(format!("{}", err)); let _ = global_tx.send(ev.clone()).unwrap(); @@ -146,45 +146,43 @@ impl<'a> Interpreter, Wrapped, Event> for GlobalInterpreter { fn command_interpreter(env: &mut Env, cmd: Command, etx: Sender) -> Result<(), Error> { match env.access_token.to_owned() { - Some(ref token) => authenticated(env, cmd, etx, token), - None => unauthenticated(env, cmd, etx), - } -} + Some(token) => match token { + Cow::Borrowed(t) => { + let client = AuthClient::new(Auth::Token(t.clone())); + authenticated(env, &client, cmd, etx) + } -// This macro partially applies the config, http client and token to the -// passed in functions. -macro_rules! partial_apply { - ([ $( $fun0: ident ),* ], // Functions of arity 0, - [ $( $fun1: ident ),* ], // arity 1, - [ $( $fun2: ident ),* ], // and arity 2. - $config: expr, $client: expr, $token: expr) => { - $(let $fun0 = || $fun0(&$config, &mut *$client.lock().unwrap(), $token);)* - $(let $fun1 = |arg| $fun1(&$config, &mut *$client.lock().unwrap(), $token, &arg);)* - $(let $fun2 = |arg1, arg2| $fun2(&$config, &mut *$client.lock().unwrap(), $token, &arg1, &arg2);)* + Cow::Owned(t) => { + let client = AuthClient::new(Auth::Token(t)); + authenticated(env, &client, cmd, etx) + } + }, + + None => { + let client = AuthClient::new(Auth::Credentials( + ClientId(env.config.auth.client_id.clone()), + ClientSecret(env.config.auth.secret.clone()) + )); + unauthenticated(env, client, cmd, etx) + } } } -fn authenticated<'a>(env: &mut Env, cmd: Command, etx: Sender, token: &Cow<'a, AccessToken>) - -> Result<(), Error> { - - let client = env.http_client.clone(); - partial_apply!([get_package_updates, update_installed_packages], - [send_install_report], - [install_package_update], - &env.config, client, &token); +fn authenticated(env: &mut Env, client: &AuthClient, cmd: Command, etx: Sender) -> Result<(), Error> { + let mut ota = OTA::new(client, env.config.clone()); match cmd { - Authenticate(_) => (), + Authenticate(_) => try!(etx.send(Event::Ok)), AcceptUpdate(ref id) => { try!(etx.send(Event::UpdateStateChanged(id.clone(), UpdateState::Downloading))); - let report = try!(install_package_update(id.to_owned(), etx.to_owned())); - try!(send_install_report(report.clone())); + let report = try!(ota.install_package_update(&id, &etx)); + try!(ota.send_install_report(&report)); info!("Update finished. Report sent: {:?}", report) } GetPendingUpdates => { - let mut updates = try!(get_package_updates()); + let mut updates = try!(ota.get_package_updates()); updates.sort_by_key(|e| e.installPos); let evs: Vec = updates.iter() .map(|up| Event::NewUpdateAvailable(up.requestId.clone())) @@ -199,7 +197,7 @@ fn authenticated<'a>(env: &mut Env, cmd: Command, etx: Sender, token: &Co } UpdateInstalledPackages => { - try!(update_installed_packages()); + try!(ota.update_installed_packages()); try!(etx.send(Event::Ok)); info!("Posted installed packages to the server.") } @@ -210,12 +208,10 @@ fn authenticated<'a>(env: &mut Env, cmd: Command, etx: Sender, token: &Co Ok(()) } -fn unauthenticated(env: &mut Env, cmd: Command, etx: Sender) -> Result<(), Error> { +fn unauthenticated(env: &mut Env, client: AuthClient, cmd: Command, etx: Sender) -> Result<(), Error> { match cmd { Authenticate(_) => { - let client_clone = env.http_client.clone(); - let mut client = client_clone.lock().unwrap(); - let token = try!(authenticate(&env.config.auth, &mut *client)); + let token = try!(authenticate(&env.config.auth, &client)); env.access_token = Some(token.into()); try!(etx.send(Event::Ok)); } diff --git a/src/main.rs b/src/main.rs index 49bc5c1..ce50847 100644 --- a/src/main.rs +++ b/src/main.rs @@ -17,12 +17,10 @@ use getopts::Options; use log::LogRecord; use std::env; use std::sync::mpsc::{Sender, Receiver, channel}; -use std::sync::{Arc, Mutex}; use std::thread; use std::time::Duration; use libotaplus::datatype::{config, Command, Config, Event, Url}; -use libotaplus::http_client::Hyper; use libotaplus::interaction_library::{Console, Gateway, Http, Websocket}; use libotaplus::interaction_library::broadcast::Broadcast; use libotaplus::interaction_library::gateway::Interpret; @@ -31,15 +29,16 @@ use libotaplus::interpreter::{AuthenticationRetrier, AutoAcceptor, Env, use libotaplus::package_manager::PackageManager; -fn spawn_global_interpreter(config: Config, wrx: Receiver, feedback_tx: Sender, etx: Sender) { - let client = Arc::new(Mutex::new(Hyper::new())); - let env = Env { +fn spawn_global_interpreter(config: Config, + wtx: Sender, + wrx: Receiver, + etx: Sender) { + let mut env = Env { config: config.clone(), access_token: None, - http_client: client.clone(), - feedback_tx: feedback_tx, + wtx: wtx, }; - GlobalInterpreter::run(&mut env.clone(), wrx, etx); + GlobalInterpreter::run(&mut env, wrx, etx); } fn spawn_signal_handler(signals: ChanReceiver, ctx: Sender) { @@ -63,7 +62,7 @@ fn spawn_update_poller(ctx: Sender, config: Config) { fn spawn_command_forwarder(crx: Receiver, wtx: Sender) { loop { match crx.recv() { - Ok(cmd) => wtx.send(Interpret{ cmd: cmd, etx: None }).unwrap(), + Ok(cmd) => wtx.send(Interpret { cmd: cmd, etx: None }).unwrap(), Err(err) => error!("Error receiving command to forward: {:?}", err), } } @@ -107,10 +106,10 @@ fn main() { let auth_ctx = ctx.clone(); scope.spawn(move || AuthenticationRetrier::run(&mut (), auth_sub, auth_ctx)); - let glob_cfg = config.clone(); - let glob_etx = etx.clone(); - let feedback_tx = wtx.clone(); - scope.spawn(move || spawn_global_interpreter(glob_cfg, wrx, feedback_tx, glob_etx)); + let glob_cfg = config.clone(); + let glob_etx = etx.clone(); + let glob_wtx = wtx.clone(); + scope.spawn(move || spawn_global_interpreter(glob_cfg, glob_wtx, wrx, glob_etx)); let ws_wtx = wtx.clone(); let ws_sub = broadcast.subscribe(); @@ -134,9 +133,7 @@ fn main() { } fn setup_logging() { - let format = |record: &LogRecord| { - let service_name = env::var("SERVICE_NAME") .unwrap_or("ota-plus-client".to_string()); @@ -153,17 +150,14 @@ fn setup_logging() { builder.format(format); if let Ok(level) = env::var("RUST_LOG") { - builder.parse(&level); + builder.parse(&level); } - builder.init() - .expect("env_logger::init() called twice, blame the programmers."); - + builder.init().expect("env_logger::init() called twice, blame the programmers."); } fn build_config() -> Config { - let args: Vec = env::args().collect(); let program = args[0].clone(); @@ -191,7 +185,6 @@ fn build_config() -> Config { opts.optflag("", "http", "enable interaction via http requests"); - let matches = opts.parse(&args[1..]) .unwrap_or_else(|err| panic!(err.to_string())); @@ -256,18 +249,5 @@ fn build_config() -> Config { config.test.http = true; } - return config -} - -// Hack to build a binary with a predictable path for use in tests/. We -// can remove this when https://github.com/rust-lang/cargo/issues/1924 -// is resolved. -#[test] -fn build_binary() { - let output = std::process::Command::new("cargo") - .arg("build") - .output() - .unwrap_or_else(|e| panic!("failed to execute child: {}", e)); - - assert!(output.status.success()) + config } diff --git a/src/oauth2.rs b/src/oauth2.rs index c1fb705..69d6084 100644 --- a/src/oauth2.rs +++ b/src/oauth2.rs @@ -1,72 +1,61 @@ use rustc_serialize::json; -use datatype::{AccessToken, AuthConfig, ClientId, ClientSecret, Error}; -use http_client::{Auth, HttpClient, HttpRequest}; +use datatype::{AccessToken, AuthConfig, Error, Method}; +use http_client::{HttpClient, HttpRequest}; -pub fn authenticate(config: &AuthConfig, client: &mut HttpClient) -> Result { - +pub fn authenticate(config: &AuthConfig, client: &HttpClient) -> Result { debug!("authenticate()"); - let req = HttpRequest::post::<_, _, String>( - config.server.join("/token").unwrap(), - Some(Auth::Credentials( - ClientId { get: config.client_id.clone() }, - ClientSecret { get: config.secret.clone() })), - None, - ); - - let resp = try!(client.send_request(&req)); + let resp_rx = client.send_request(HttpRequest{ + method: Method::Post, + url: config.server.join("/token").unwrap(), + body: None + }); - let body = try!(String::from_utf8(resp.body)); + let resp = try!(resp_rx.recv()); + let data = try!(resp); + let body = try!(String::from_utf8(data)); debug!("authenticate, body: `{}`", body); - Ok(try!(json::decode(&body))) - } #[cfg(test)] mod tests { - use super::*; + use datatype::{AccessToken, AuthConfig}; use http_client::TestHttpClient; - const TOKEN: &'static str = - r#"{"access_token": "token", - "token_type": "type", - "expires_in": 10, - "scope": ["scope"]} - "#; #[test] fn test_authenticate() { - assert_eq!(authenticate(&AuthConfig::default(), &mut TestHttpClient::from(vec![TOKEN])).unwrap(), - AccessToken { - access_token: "token".to_string(), - token_type: "type".to_string(), - expires_in: 10, - scope: vec!["scope".to_string()] - }) + let token = r#"{"access_token": "token", "token_type": "type", "expires_in": 10, "scope": ["scope"]}"#; + let client = TestHttpClient::from(vec![token.as_bytes().to_vec()]); + let expect = AccessToken { + access_token: "token".to_string(), + token_type: "type".to_string(), + expires_in: 10, + scope: vec!["scope".to_string()] + }; + assert_eq!(expect, authenticate(&AuthConfig::default(), &client).unwrap()); } #[test] fn test_authenticate_no_token() { - assert_eq!(format!("{}", authenticate(&AuthConfig::default(), - &mut TestHttpClient::from(vec![""])).unwrap_err()), - r#"Failed to decode JSON: ParseError(SyntaxError("EOF While parsing value", 1, 1))"#) - - // XXX: Old error message was arguebly a lot better... - // "Authentication error, didn't receive access token.") + let client = TestHttpClient::from(vec!["".as_bytes().to_vec()]); + // XXX: Old error message was arguably a lot better... + // "Authentication error, didn't receive access token.") + let expect = r#"Failed to decode JSON: ParseError(SyntaxError("EOF While parsing value", 1, 1))"#; + assert_eq!(expect, format!("{}", authenticate(&AuthConfig::default(), &client).unwrap_err())); } #[test] fn test_authenticate_bad_json() { - assert_eq!(format!("{}", authenticate(&AuthConfig::default(), - &mut TestHttpClient::from(vec![r#"{"apa": 1}"#])).unwrap_err()), - r#"Failed to decode JSON: MissingFieldError("access_token")"#) + let client = TestHttpClient::from(vec![r#"{"apa": 1}"#.as_bytes().to_vec()]); + let expect = r#"Failed to decode JSON: MissingFieldError("access_token")"#; + assert_eq!(expect, format!("{}", authenticate(&AuthConfig::default(), &client).unwrap_err())); } - } diff --git a/src/ota_plus.rs b/src/ota_plus.rs index d1b78e8..008931e 100644 --- a/src/ota_plus.rs +++ b/src/ota_plus.rs @@ -1,203 +1,165 @@ use rustc_serialize::json; use std::fs::File; +use std::io; use std::path::PathBuf; use std::sync::mpsc::Sender; -use datatype::{AccessToken, Config, Event, Error, Url, UpdateRequestId, - UpdateReport, UpdateReportWithVin, Package, - UpdateResultCode, UpdateState, PendingUpdateRequest}; +use datatype::{Config, Error, Event, Method, PendingUpdateRequest, + UpdateRequestId, UpdateReport, UpdateReportWithVin, + UpdateResultCode, UpdateState, Url}; +use http_client::{HttpClient, HttpRequest}; -use http_client::{Auth, HttpClient, HttpRequest, HttpResponse}; - - -fn vehicle_updates_endpoint(config: &Config, path: &str) -> Url { - let endpoint = if path.is_empty() { - format!("/api/v1/vehicle_updates/{}", config.auth.vin) - } else { - format!("/api/v1/vehicle_updates/{}/{}", config.auth.vin, path) - }; - config.ota.server.join(&endpoint).unwrap() -} - -pub fn download_package_update(config: &Config, - client: &mut HttpClient, - token: &AccessToken, - id: &UpdateRequestId) -> Result { - - let req = HttpRequest::get( - vehicle_updates_endpoint(config, &format!("{}/download", id)), - Some(Auth::Token(token)), - ); - - let mut path = PathBuf::new(); - path.push(&config.ota.packages_dir); - path.push(id); - // TODO: Use Content-Disposition filename from request? - // TODO: Do not invoke package_manager - path.set_extension(config.ota.package_manager.extension()); - - let mut file = try!(File::create(path.as_path())); - - try!(client.send_request_to(&req, &mut file)); - - Ok(path) - -} - -pub fn send_install_report(config: &Config, - client: &mut HttpClient, - token: &AccessToken, - report: &UpdateReport) -> Result<(), Error> { - - let report_with_vin = UpdateReportWithVin::new(&config.auth.vin, &report); - let json = try!(json::encode(&report_with_vin)); - - let req = HttpRequest::post( - vehicle_updates_endpoint(config, &format!("{}", report.update_id)), - Some(Auth::Token(token)), - Some(json) - ); - - let _: HttpResponse = try!(client.send_request(&req)); - - Ok(()) +pub struct OTA<'h> { + client: &'h HttpClient, + config: Config, } -pub fn get_package_updates(config: &Config, - client: &mut HttpClient, - token: &AccessToken) -> Result, Error> { - - let req = HttpRequest::get( - vehicle_updates_endpoint(&config, ""), - Some(Auth::Token(token)), - ); - - let resp = try!(client.send_request(&req)); - let body = try!(String::from_utf8(resp.body)); - - Ok(try!(json::decode::>(&body))) -} - -// XXX: Remove in favour of update_installed_packages()? -pub fn update_packages(config: &Config, - client: &mut HttpClient, - token: &AccessToken, - pkgs: &Vec) -> Result<(), Error> { - - info!("update_packages, pkgs: {:?}", pkgs); - - let json = try!(json::encode(&pkgs)); - - debug!("update_packages, json: {}", json); - - let req = HttpRequest::put( - vehicle_updates_endpoint(config, "installed"), - Some(Auth::Token(token)), - Some(json), - ); - - let _: HttpResponse = try!(client.send_request(&req)); - - Ok(()) -} - -pub fn update_installed_packages(config: &Config, - client: &mut HttpClient, - token: &AccessToken) -> Result<(), Error> { - - // TODO: Fire GetInstalledSoftware event, handle async InstalledSoftware command - // TODO: Do not invoke package_manager - let pkgs = try!(config.ota.package_manager.installed_packages()); - update_packages(config, client, token, &pkgs) - -} - -pub fn install_package_update(config: &Config, - http_client: &mut HttpClient, - token: &AccessToken, - id: &UpdateRequestId, - tx: &Sender) -> Result { +impl<'h> OTA<'h> { + pub fn new(client: &HttpClient, config: Config) -> OTA { + OTA { client: client, config: config } + } - match download_package_update(config, http_client, token, id) { + pub fn update_endpoint(&self, path: &str) -> Url { + let endpoint = if path.is_empty() { + format!("/api/v1/vehicle_updates/{}", self.config.auth.vin) + } else { + format!("/api/v1/vehicle_updates/{}/{}", self.config.auth.vin, path) + }; + self.config.ota.server.join(&endpoint).unwrap() + } - Ok(path) => { - info!("Downloaded at {:?}. Installing...", path); - try!(tx.send(Event::UpdateStateChanged(id.clone(), UpdateState::Installing))); + pub fn get_package_updates(&mut self) -> Result, Error> { + debug!("getting package updates"); + let resp_rx = self.client.send_request(HttpRequest { + method: Method::Get, + url: self.update_endpoint(""), + body: None, + }); - let p = try!(path.to_str() - .ok_or(Error::ParseError(format!("Path is not valid UTF-8: {:?}", path)))); + let resp = try!(resp_rx.recv()); + let data = try!(resp); + let text = try!(String::from_utf8(data)); - // TODO: Fire DownloadComplete event, handle async UpdateReport command - // TODO: Do not invoke package_manager - match config.ota.package_manager.install_package(p) { + Ok(try!(json::decode::>(&text))) + } - Ok((code, output)) => { - try!(tx.send(Event::UpdateStateChanged(id.clone(), UpdateState::Installed))); - try!(update_installed_packages(config, http_client, token)); - Ok(UpdateReport::new(id.clone(), code, output)) - } + pub fn update_installed_packages(&mut self) -> Result<(), Error> { + debug!("updating installed packages"); + // TODO: Fire GetInstalledSoftware event, handle async InstalledSoftware command + // TODO: Do not invoke package_manager + let pkgs = try!(self.config.ota.package_manager.installed_packages()); + let body = try!(json::encode(&pkgs)); + debug!("installed packages body: {}", body); + + let resp_rx = self.client.send_request(HttpRequest { + method: Method::Put, + url: self.update_endpoint("installed"), + body: Some(body.into_bytes()), + }); + + let resp = try!(resp_rx.recv()); + let data = try!(resp); + let text = try!(String::from_utf8(data)); + let _ = try!(json::decode::>(&text)); + + Ok(()) + } - Err((code, output)) => { - try!(tx.send(Event::UpdateErrored(id.clone(), format!("{:?}: {:?}", code, output)))); - Ok(UpdateReport::new(id.clone(), code, output)) + pub fn install_package_update(&mut self, id: &UpdateRequestId, etx: &Sender) + -> Result { + debug!("installing package update"); + + match self.download_package_update(id) { + Ok(path) => { + let err_str = format!("Path is not valid UTF-8: {:?}", path); + let pkg_path = try!(path.to_str().ok_or(Error::ParseError(err_str))); + info!("Downloaded to {:?}. Installing...", pkg_path); + + // TODO: Fire DownloadComplete event, handle async UpdateReport command + // TODO: Do not invoke package_manager + try!(etx.send(Event::UpdateStateChanged(id.clone(), UpdateState::Installing))); + match self.config.ota.package_manager.install_package(pkg_path) { + Ok((code, output)) => { + try!(etx.send(Event::UpdateStateChanged(id.clone(), UpdateState::Installed))); + try!(self.update_installed_packages()); + Ok(UpdateReport::new(id.clone(), code, output)) + } + + Err((code, output)) => { + let err_str = format!("{:?}: {:?}", code, output); + try!(etx.send(Event::UpdateErrored(id.clone(), err_str))); + Ok(UpdateReport::new(id.clone(), code, output)) + } } - } + Err(err) => { + try!(etx.send(Event::UpdateErrored(id.clone(), format!("{:?}", err)))); + let failed = format!("Download failed: {:?}", err); + Ok(UpdateReport::new(id.clone(), UpdateResultCode::GENERAL_ERROR, failed)) + } } + } - Err(err) => { - try!(tx.send(Event::UpdateErrored(id.clone(), format!("{:?}", err)))); - Ok(UpdateReport::new(id.clone(), - UpdateResultCode::GENERAL_ERROR, - format!("Download failed: {:?}", err))) - } + pub fn download_package_update(&mut self, id: &UpdateRequestId) -> Result { + debug!("downloading package update"); + let resp_rx = self.client.send_request(HttpRequest { + method: Method::Get, + url: self.update_endpoint(&format!("{}/download", id)), + body: None, + }); + + let mut path = PathBuf::new(); + path.push(&self.config.ota.packages_dir); + path.push(id); + // TODO: Use Content-Disposition filename from request? + // TODO: Do not invoke package_manager + path.set_extension(self.config.ota.package_manager.extension()); + + let resp = try!(resp_rx.recv()); + let data = try!(resp); + let mut file = try!(File::create(path.as_path())); + let _ = io::copy(&mut &*data, &mut file); + + Ok(path) } -} + pub fn send_install_report(&mut self, report: &UpdateReport) -> Result<(), Error> { + debug!("sending installation report"); + let vin_report = UpdateReportWithVin::new(&self.config.auth.vin, &report); + let body = try!(json::encode(&vin_report)); + let _ = self.client.send_request(HttpRequest { + method: Method::Post, + url: self.update_endpoint(&format!("{}", report.update_id)), + body: Some(body.into_bytes()), + }); + + Ok(()) + } +} #[cfg(test)] mod tests { - use std::fmt::Debug; use std::sync::mpsc::{channel, Receiver}; use rustc_serialize::json; use super::*; - use datatype::{AccessToken, Config, Event, OtaConfig, Package, - UpdateResultCode, UpdateState, PendingUpdateRequest}; + use datatype::{Config, Event, Package, PendingUpdateRequest, UpdateResultCode, UpdateState}; use http_client::TestHttpClient; use package_manager::PackageManager; - fn test_token() -> AccessToken { - AccessToken { - access_token: "token".to_string(), - token_type: "bar".to_string(), - expires_in: 20, - scope: vec![] - } - } - - fn test_package() -> Package { - Package { - name: "hey".to_string(), - version: "1.2.3".to_string() + fn new_test_ota(client: &TestHttpClient) -> OTA { + OTA { + client: client, + config: Config::default(), } } - #[test] - fn test_update_packages_sends_authentication() { - assert_eq!(update_packages(&Config::default(), - &mut TestHttpClient::from(vec![""]), - &test_token(), - &vec![test_package()]) - .unwrap(), ()) - } - #[test] fn test_get_package_updates() { let pending_update = PendingUpdateRequest { @@ -210,46 +172,25 @@ mod tests { createdAt: "2010-01-01".to_string() }; - let json_response = format!("[{}]",json::encode(&pending_update).unwrap()); + let json = format!("[{}]", json::encode(&pending_update).unwrap()); + let mut client = TestHttpClient::from(vec![json.as_bytes().to_vec()]); + let mut ota = new_test_ota(&mut client); - let updates: Vec = get_package_updates(&Config::default(), - &mut TestHttpClient::from(vec![json_response.as_str()]), - &test_token()).unwrap(); - - let update_ids: Vec = updates.iter().map(|p| p.requestId.clone()).collect(); - - assert_eq!(update_ids, vec!["someid".to_string()]) - } - - #[test] - #[ignore] // TODO: docker daemon requires user namespaces for this to work - fn bad_packages_dir_download_package_update() { - let mut config = Config::default(); - config.ota = OtaConfig { packages_dir: "/".to_string(), .. config.ota }; - - assert_eq!(format!("{}", download_package_update(&config, - &mut TestHttpClient::from(vec![""]), - &test_token(), - &"0".to_string()) - .unwrap_err()), - "IO error: Permission denied (os error 13)") + let updates: Vec = ota.get_package_updates().unwrap(); + let ids: Vec = updates.iter().map(|p| p.requestId.clone()).collect(); + assert_eq!(ids, vec!["someid".to_string()]) } #[test] fn bad_client_download_package_update() { - assert_eq!(format!("{}", - download_package_update(&Config::default(), - &mut TestHttpClient::new(), - &test_token(), - &"0".to_string()) - .unwrap_err()), - "Http client error: GET http://127.0.0.1:8080/api/v1/vehicle_updates/V1234567890123456/0/download") + let client = TestHttpClient::new(); + let mut ota = new_test_ota(&client); + let expect = "Http client error: http://127.0.0.1:8080/api/v1/vehicle_updates/V1234567890123456/0/download"; + assert_eq!(expect, format!("{}", ota.download_package_update(&"0".to_string()).unwrap_err())); } fn assert_receiver_eq(rx: Receiver, xs: &[X]) { - let mut xs = xs.iter(); - while let Ok(x) = rx.try_recv() { if let Some(y) = xs.next() { assert_eq!(x, *y) @@ -257,81 +198,76 @@ mod tests { panic!("assert_receiver_eq: never nexted `{:?}`", x) } } - if let Some(x) = xs.next() { panic!("assert_receiver_eq: never received `{:?}`", x) } - } #[test] fn test_install_package_update_0() { - + let client = TestHttpClient::new(); + let mut ota = new_test_ota(&client); let (tx, rx) = channel(); - - assert_eq!(install_package_update( - &Config::default(), - &mut TestHttpClient::new(), - &AccessToken::default(), - &"0".to_string(), - &tx).unwrap().operation_results.pop().unwrap().result_code, + let report = ota.install_package_update(&"0".to_string(), &tx); + assert_eq!(report.unwrap().operation_results.pop().unwrap().result_code, UpdateResultCode::GENERAL_ERROR); + let expect = r#"ClientError("http://127.0.0.1:8080/api/v1/vehicle_updates/V1234567890123456/0/download")"#; assert_receiver_eq(rx, &[ - Event::UpdateErrored("0".to_string(), String::from( - "ClientError(\"GET http://127.0.0.1:8080/api/v1/vehicle_updates/V1234567890123456/0/download\")"))]) - + Event::UpdateErrored("0".to_string(), String::from(expect)) + ]); } #[test] fn test_install_package_update_1() { - let mut config = Config::default(); - config.ota.packages_dir = "/tmp/".to_string(); config.ota.package_manager = PackageManager::File { filename: "test_install_package_update_1".to_string(), - succeeds: false }; + succeeds: false + }; + let mut ota = OTA { + client: &mut TestHttpClient::from(vec!["".as_bytes().to_vec()]), + config: config + }; let (tx, rx) = channel(); - - assert_eq!(install_package_update( - &config, - &mut TestHttpClient::from(vec![""]), - &AccessToken::default(), - &"0".to_string(), - &tx).unwrap().operation_results.pop().unwrap().result_code, + let report = ota.install_package_update(&"0".to_string(), &tx); + assert_eq!(report.unwrap().operation_results.pop().unwrap().result_code, UpdateResultCode::INSTALL_FAILED); assert_receiver_eq(rx, &[ Event::UpdateStateChanged("0".to_string(), UpdateState::Installing), // XXX: Not very helpful message? - Event::UpdateErrored("0".to_string(), "INSTALL_FAILED: \"failed\"".to_string())]) + Event::UpdateErrored("0".to_string(), r#"INSTALL_FAILED: "failed""#.to_string()) + ]); } #[test] fn test_install_package_update_2() { - let mut config = Config::default(); - config.ota.packages_dir = "/tmp/".to_string(); config.ota.package_manager = PackageManager::File { filename: "test_install_package_update_2".to_string(), - succeeds: true }; + succeeds: true + }; + let replies = vec![ + "[]".as_bytes().to_vec(), + "package data".as_bytes().to_vec(), + ]; + let mut ota = OTA { + client: &mut TestHttpClient::from(replies), + config: config + }; let (tx, rx) = channel(); - - assert_eq!(install_package_update( - &config, - &mut TestHttpClient::from(vec!["", ""]), - &AccessToken::default(), - &"0".to_string(), - &tx).unwrap().operation_results.pop().unwrap().result_code, + let report = ota.install_package_update(&"0".to_string(), &tx); + assert_eq!(report.unwrap().operation_results.pop().unwrap().result_code, UpdateResultCode::OK); assert_receiver_eq(rx, &[ Event::UpdateStateChanged("0".to_string(), UpdateState::Installing), - Event::UpdateStateChanged("0".to_string(), UpdateState::Installed)]) - + Event::UpdateStateChanged("0".to_string(), UpdateState::Installed) + ]); } } -- cgit v1.2.1 From d323054e9c3228a123bc5c86d6dd7d9ff072bf36 Mon Sep 17 00:00:00 2001 From: Shaun Taheri Date: Wed, 8 Jun 2016 12:30:07 +0200 Subject: Fix package installation --- src/ota_plus.rs | 66 +++++++++++++++--------------- src/package_manager/dpkg.rs | 75 +++++++++------------------------- src/package_manager/package_manager.rs | 69 +++++++++++++++++++++++++------ src/package_manager/rpm.rs | 26 ++++++------ 4 files changed, 123 insertions(+), 113 deletions(-) diff --git a/src/ota_plus.rs b/src/ota_plus.rs index 008931e..1a60b69 100644 --- a/src/ota_plus.rs +++ b/src/ota_plus.rs @@ -44,26 +44,27 @@ impl<'h> OTA<'h> { Ok(try!(json::decode::>(&text))) } - pub fn update_installed_packages(&mut self) -> Result<(), Error> { - debug!("updating installed packages"); - // TODO: Fire GetInstalledSoftware event, handle async InstalledSoftware command - // TODO: Do not invoke package_manager - let pkgs = try!(self.config.ota.package_manager.installed_packages()); - let body = try!(json::encode(&pkgs)); - debug!("installed packages body: {}", body); - + pub fn download_package_update(&mut self, id: &UpdateRequestId) -> Result { + debug!("downloading package update"); let resp_rx = self.client.send_request(HttpRequest { - method: Method::Put, - url: self.update_endpoint("installed"), - body: Some(body.into_bytes()), + method: Method::Get, + url: self.update_endpoint(&format!("{}/download", id)), + body: None, }); - let resp = try!(resp_rx.recv()); - let data = try!(resp); - let text = try!(String::from_utf8(data)); - let _ = try!(json::decode::>(&text)); + let mut path = PathBuf::new(); + path.push(&self.config.ota.packages_dir); + path.push(id); + // TODO: Use Content-Disposition filename from request? + // TODO: Do not invoke package_manager + path.set_extension(self.config.ota.package_manager.extension()); - Ok(()) + let resp = try!(resp_rx.recv()); + let data = try!(resp); + let mut file = try!(File::create(path.as_path())); + let _ = io::copy(&mut &*data, &mut file); + + Ok(path) } pub fn install_package_update(&mut self, id: &UpdateRequestId, etx: &Sender) @@ -102,27 +103,26 @@ impl<'h> OTA<'h> { } } - pub fn download_package_update(&mut self, id: &UpdateRequestId) -> Result { - debug!("downloading package update"); + pub fn update_installed_packages(&mut self) -> Result<(), Error> { + debug!("updating installed packages"); + // TODO: Fire GetInstalledSoftware event, handle async InstalledSoftware command + // TODO: Do not invoke package_manager + let pkgs = try!(self.config.ota.package_manager.installed_packages()); + let body = try!(json::encode(&pkgs)); + debug!("installed packages: {}", body); + let resp_rx = self.client.send_request(HttpRequest { - method: Method::Get, - url: self.update_endpoint(&format!("{}/download", id)), - body: None, + method: Method::Put, + url: self.update_endpoint("installed"), + body: Some(body.into_bytes()), }); - let mut path = PathBuf::new(); - path.push(&self.config.ota.packages_dir); - path.push(id); - // TODO: Use Content-Disposition filename from request? - // TODO: Do not invoke package_manager - path.set_extension(self.config.ota.package_manager.extension()); - - let resp = try!(resp_rx.recv()); - let data = try!(resp); - let mut file = try!(File::create(path.as_path())); - let _ = io::copy(&mut &*data, &mut file); + let resp = try!(resp_rx.recv()); + let data = try!(resp); + let text = try!(String::from_utf8(data)); + let _ = try!(json::decode::>(&text)); - Ok(path) + Ok(()) } pub fn send_install_report(&mut self, report: &UpdateReport) -> Result<(), Error> { diff --git a/src/package_manager/dpkg.rs b/src/package_manager/dpkg.rs index f50eaec..57c5784 100644 --- a/src/package_manager/dpkg.rs +++ b/src/package_manager/dpkg.rs @@ -1,6 +1,7 @@ use std::process::Command; use datatype::{Error, Package, UpdateResultCode}; +use package_manager::package_manager::{InstallOutcome, parse_package}; pub fn installed_packages() -> Result, Error> { @@ -14,67 +15,31 @@ pub fn installed_packages() -> Result, Error> { }) .and_then(|lines| { lines.iter() - .map(|line| parse_package(line)) - .collect::, _>>() + .map(|line| parse_package(line)) + .filter(|pkg| pkg.is_ok()) + .collect::, _>>() }) } -pub fn install_package(path: &str) -> Result<(UpdateResultCode, String), (UpdateResultCode, String)> { - let output = try!(Command::new("dpkg").arg("-E").arg("-i") - .arg(path) - .output() - .map_err(|e| { - (UpdateResultCode::GENERAL_ERROR, format!("{:?}", e)) - })); +pub fn install_package(path: &str) -> Result { + let output = try!(Command::new("dpkg").arg("-E").arg("-i").arg(path) + .output() + .map_err(|e| (UpdateResultCode::GENERAL_ERROR, format!("{:?}", e)))); let stdout = String::from_utf8_lossy(&output.stdout).into_owned(); + let stderr = String::from_utf8_lossy(&output.stderr).into_owned(); match output.status.code() { - Some(0) => if (&stdout).contains("already installed") { - Ok((UpdateResultCode::ALREADY_PROCESSED, stdout)) - } else { - Ok((UpdateResultCode::OK, stdout)) - }, - _ => Err((UpdateResultCode::INSTALL_FAILED, stdout)) - } -} - -pub fn parse_package(line: &str) -> Result { - match line.splitn(2, ' ').collect::>() { - ref parts if parts.len() == 2 => Ok(Package { name: String::from(parts[0]), - version: String::from(parts[1]) }), - _ => Err(Error::ParseError(format!("Couldn't parse package: {}", line))) - } -} - -#[cfg(test)] -mod tests { - - use super::*; - use datatype::Package; - - #[test] - fn test_parses_normal_package() { - assert_eq!(parse_package("uuid-runtime 2.20.1-5.1ubuntu20.7").unwrap(), - Package { - name: "uuid-runtime".to_string(), - version: "2.20.1-5.1ubuntu20.7".to_string() - }); - } - - #[test] - fn test_separates_name_and_version_correctly() { - assert_eq!(parse_package("vim 2.1 foobar").unwrap(), - Package { - name: "vim".to_string(), - version: "2.1 foobar".to_string() - }); + Some(0) => { + if (&stdout).contains("already installed") { + Ok((UpdateResultCode::ALREADY_PROCESSED, stdout)) + } else { + Ok((UpdateResultCode::OK, stdout)) + } + } + _ => { + let out = format!("stdout: {}\nstderr: {}", stdout, stderr); + Err((UpdateResultCode::INSTALL_FAILED, out)) + } } - - #[test] - fn test_rejects_bogus_input() { - assert_eq!(format!("{}", parse_package("foobar").unwrap_err()), - "Couldn't parse package: foobar".to_string()); - } - } diff --git a/src/package_manager/package_manager.rs b/src/package_manager/package_manager.rs index f6ba2da..104f18b 100644 --- a/src/package_manager/package_manager.rs +++ b/src/package_manager/package_manager.rs @@ -11,46 +11,89 @@ pub enum PackageManager { File { filename: String, succeeds: bool } } -impl PackageManager { +pub type InstallOutcome = (UpdateResultCode, String); +impl PackageManager { pub fn installed_packages(&self) -> Result, Error> { match *self { - PackageManager::Dpkg => dpkg::installed_packages(), - PackageManager::Rpm => rpm::installed_packages(), + PackageManager::Dpkg => dpkg::installed_packages(), + PackageManager::Rpm => rpm::installed_packages(), PackageManager::File { ref filename, .. } => tpm::installed_packages(filename), } } - pub fn install_package(&self, path: &str) -> Result<(UpdateResultCode, String), (UpdateResultCode, String)> { + pub fn install_package(&self, path: &str) -> Result { match *self { - PackageManager::Dpkg => dpkg::install_package(path), - PackageManager::Rpm => rpm::install_package(path), + PackageManager::Dpkg => dpkg::install_package(path), + PackageManager::Rpm => rpm::install_package(path), PackageManager::File { ref filename, succeeds } => tpm::install_package(filename, path, succeeds), } } pub fn extension(&self) -> String { match *self { - PackageManager::Dpkg => "deb".to_string(), - PackageManager::Rpm => "rpm".to_string(), + PackageManager::Dpkg => "deb".to_string(), + PackageManager::Rpm => "rpm".to_string(), PackageManager::File { ref filename, .. } => filename.to_string(), } } +} +impl Decodable for PackageManager { + fn decode(d: &mut D) -> Result { + d.read_str().and_then(|s| parse_package_manager(s).map_err(|e| d.error(&e))) + } } fn parse_package_manager(s: String) -> Result { match s.to_lowercase().as_str() { "dpkg" => Ok(PackageManager::Dpkg), "rpm" => Ok(PackageManager::Rpm), - s => Ok(PackageManager::File { filename: s.to_string(), succeeds: true }), + _ => Ok(PackageManager::File { filename: s.to_string(), succeeds: true }), } } -impl Decodable for PackageManager { +pub fn parse_package(line: &str) -> Result { + match line.splitn(2, ' ').collect::>() { + ref parts if parts.len() == 2 => { + // HACK: strip left single quotes from stdout + Ok(Package { + name: String::from(parts[0].trim_left_matches('\'')), + version: String::from(parts[1]) + }) + }, + _ => Err(Error::ParseError(format!("Couldn't parse package: {}", line))) + } +} - fn decode(d: &mut D) -> Result { - d.read_str().and_then(|s| parse_package_manager(s) - .map_err(|e| d.error(&e))) + +#[cfg(test)] +mod tests { + use super::*; + use datatype::Package; + + + #[test] + fn test_parses_normal_package() { + assert_eq!(parse_package("uuid-runtime 2.20.1-5.1ubuntu20.7").unwrap(), + Package { + name: "uuid-runtime".to_string(), + version: "2.20.1-5.1ubuntu20.7".to_string() + }); + } + + #[test] + fn test_separates_name_and_version_correctly() { + assert_eq!(parse_package("vim 2.1 foobar").unwrap(), + Package { + name: "vim".to_string(), + version: "2.1 foobar".to_string() + }); + } + + #[test] + fn test_rejects_bogus_input() { + assert_eq!(format!("{}", parse_package("foobar").unwrap_err()), + "Couldn't parse package: foobar".to_string()); } } diff --git a/src/package_manager/rpm.rs b/src/package_manager/rpm.rs index c473656..790becd 100644 --- a/src/package_manager/rpm.rs +++ b/src/package_manager/rpm.rs @@ -1,7 +1,7 @@ use std::process::Command; use datatype::{Error, Package, UpdateResultCode}; -use package_manager::dpkg::parse_package; // XXX: Move somewhere better? +use package_manager::package_manager::{InstallOutcome, parse_package}; pub fn installed_packages() -> Result, Error> { @@ -15,27 +15,29 @@ pub fn installed_packages() -> Result, Error> { }) .and_then(|lines| { lines.iter() - .map(|line| parse_package(line)) - .collect::, _>>() + .map(|line| parse_package(line)) + .filter(|item| item.is_ok()) + .collect::, _>>() }) } -pub fn install_package(path: &str) -> Result<(UpdateResultCode, String), (UpdateResultCode, String)> { +pub fn install_package(path: &str) -> Result { let output = try!(Command::new("rpm").arg("-Uvh").arg("--force").arg(path) - .output() - .map_err(|e| { - (UpdateResultCode::GENERAL_ERROR, format!("{:?}", e)) - })); + .output() + .map_err(|e| (UpdateResultCode::GENERAL_ERROR, format!("{:?}", e)))); let stdout = String::from_utf8_lossy(&output.stdout).into_owned(); let stderr = String::from_utf8_lossy(&output.stderr).into_owned(); match output.status.code() { Some(0) => Ok((UpdateResultCode::OK, stdout)), - _ => if (&stderr).contains("already installed") { - Ok((UpdateResultCode::ALREADY_PROCESSED, stderr)) - } else { - Err((UpdateResultCode::INSTALL_FAILED, stderr)) + _ => { + let out = format!("stdout: {}\nstderr: {}", stdout, stderr); + if (&stderr).contains("already installed") { + Ok((UpdateResultCode::ALREADY_PROCESSED, out)) + } else { + Err((UpdateResultCode::INSTALL_FAILED, out)) + } } } } -- cgit v1.2.1 From 64bbfcbe8fd6d115778e2f5a61c0ee5b055c526b Mon Sep 17 00:00:00 2001 From: Shaun Taheri Date: Thu, 9 Jun 2016 00:42:29 +0200 Subject: Review cleanup, thanks @stevana --- src/http_client/auth_client.rs | 132 +++++++++++++++++++++++++---------------- src/http_client/test_client.rs | 12 ++-- src/interpreter.rs | 24 +++----- src/main.rs | 18 +----- src/oauth2.rs | 6 +- src/ota_plus.rs | 8 +-- 6 files changed, 105 insertions(+), 95 deletions(-) diff --git a/src/http_client/auth_client.rs b/src/http_client/auth_client.rs index c9a9ab6..d4289ef 100644 --- a/src/http_client/auth_client.rs +++ b/src/http_client/auth_client.rs @@ -7,6 +7,7 @@ use hyper::net::{HttpStream, HttpsStream, OpensslStream, Openssl}; use std::io::{Read, Write, ErrorKind}; use std::sync::mpsc::Sender; use std::time::Duration; +use time; use datatype::{Auth, Error}; use http_client::{HttpClient, HttpRequest, HttpResponse}; @@ -37,15 +38,16 @@ impl AuthClient { impl HttpClient for AuthClient { fn chan_request(&self, req: HttpRequest, resp_tx: Sender) { debug!("send_request_to: {:?}", req.url); - let result = self.client.request(req.url.inner(), AuthHandler { + let _ = self.client.request(req.url.inner(), AuthHandler { auth: self.auth.clone(), req: req, timeout: Duration::from_secs(20), + started: None, resp_tx: resp_tx.clone(), + }).map_err(|err| { + resp_tx.send(Err(Error::from(err))) + .map_err(|err| error!("couldn't send chan_request: {}", err)) }); - if let Err(err) = result { - let _ = resp_tx.send(Err(Error::from(err))); - }; } } @@ -55,6 +57,7 @@ pub struct AuthHandler { auth: Auth, req: HttpRequest, timeout: Duration, + started: Option, resp_tx: Sender, } @@ -65,32 +68,74 @@ impl ::std::fmt::Debug for AuthHandler { } } +impl AuthHandler { + fn redirect_request(&self, resp: Response) { + info!("redirect_request"); + let _ = match resp.headers().get::() { + Some(&Location(ref loc)) => match self.req.url.join(loc) { + Ok(url) => { + debug!("redirecting to {:?}", url); + // drop Authentication Header on redirect + let client = AuthClient::new(Auth::None); + let body = match self.req.body { + Some(ref data) => Some(data.clone()), + None => None + }; + let resp_rx = client.send_request(HttpRequest { + url: url, + method: self.req.method.clone(), + body: body, + }); + match resp_rx.recv() { + Ok(resp) => match resp { + Ok(data) => self.resp_tx.send(Ok(data)), + Err(err) => self.resp_tx.send(Err(Error::from(err))) + }, + Err(err) => self.resp_tx.send(Err(Error::from(err))) + } + } + + Err(err) => self.resp_tx.send(Err(Error::from(err))) + }, + + None => { + let msg = "redirection without Location header".to_string(); + error!("{}", msg); + self.resp_tx.send(Err(Error::ClientError(msg))) + } + }.map_err(|err| error!("couldn't send redirect response: {}", err)); + } +} + pub type Stream = HttpsStream>; impl Handler for AuthHandler { fn on_request(&mut self, req: &mut Request) -> Next { info!("on_request"); + self.started = Some(time::precise_time_ns()); + req.set_method(self.req.method.clone().into()); let mut headers = req.headers_mut(); - match self.auth.clone() { + match self.auth { Auth::None => { headers.set(ContentType(Mime(TopLevel::Application, SubLevel::Json, vec![(Attr::Charset, Value::Utf8)]))); } + Auth::Credentials(_, _) if self.req.body.is_some() => { + panic!("no request body expected for Auth::Credentials"); + } + Auth::Credentials(ref id, ref secret) => { headers.set(Authorization(Basic { username: id.0.clone(), password: Some(secret.0.clone()) })); headers.set(ContentType(Mime(TopLevel::Application, SubLevel::WwwFormUrlEncoded, vec![(Attr::Charset, Value::Utf8)]))); - if let Some(_) = self.req.body { - panic!("no request body expected for Auth::Credentials") - }; - self.req.body = Some("grant_type=client_credentials".to_owned().into_bytes()); + self.req.body = Some(br#"grant_type=client_credentials"#.to_vec()); } - Auth::Token(token) => { + Auth::Token(ref token) => { headers.set(Authorization(Bearer { token: token.access_token.clone() })); headers.set(ContentType(Mime(TopLevel::Application, SubLevel::Json, vec![(Attr::Charset, Value::Utf8)]))); @@ -115,10 +160,15 @@ impl Handler for AuthHandler { Ok(_) => Next::read().timeout(self.timeout), Err(err) => match err.kind() { - ErrorKind::WouldBlock => Next::write(), - _ => { + ErrorKind::WouldBlock => { + trace!("retry on_request_writable"); + Next::write() + } + _ => { error!("unable to write body: {}", err); - let _ = self.resp_tx.send(Err(Error::from(err))); + let _ = self.resp_tx + .send(Err(Error::from(err))) + .map_err(|err| error!("couldn't send write_all error: {}", err)); Next::remove() } } @@ -130,63 +180,43 @@ impl Handler for AuthHandler { fn on_response(&mut self, resp: Response) -> Next { info!("on_response: status: {}, headers:\n{}", resp.status(), resp.headers()); + if let Some(started) = self.started { + debug!("latency: {}", time::precise_time_ns() - started); + } if resp.status().is_success() { Next::read() } else if resp.status().is_redirection() { - let _ = match resp.headers().get::() { - Some(&Location(ref loc)) => match self.req.url.join(loc) { - Ok(url) => { - debug!("redirecting to {:?}", url); - let client = AuthClient::new(Auth::None); - let body = match self.req.body { - Some(ref data) => Some(data.clone()), - None => None - }; - let resp_rx = client.send_request(HttpRequest { - url: url, - method: self.req.method.clone(), - body: body, - }); - match resp_rx.recv() { - Ok(resp) => match resp { - Ok(data) => self.resp_tx.send(Ok(data)), - Err(err) => self.resp_tx.send(Err(Error::from(err))) - }, - Err(err) => self.resp_tx.send(Err(Error::from(err))) - } - } - - Err(err) => self.resp_tx.send(Err(Error::from(err))) - }, - - None => { - let msg = "redirection without Location header".to_owned(); - error!("{}", msg); - self.resp_tx.send(Err(Error::ClientError(msg))) - } - }; - + self.redirect_request(resp); Next::end() } else { let msg = format!("failed response status: {}", resp.status()); error!("{}", msg); - let _ = self.resp_tx.send(Err(Error::ClientError(msg))); + let _ = self.resp_tx + .send(Err(Error::ClientError(msg))) + .map_err(|err| error!("couldn't send failed response: {}", err)); Next::end() } } fn on_response_readable(&mut self, decoder: &mut Decoder) -> Next { info!("on_response_readable"); + let mut data: Vec = Vec::new(); - let _ = decoder.read_to_end(&mut data); - let _ = self.resp_tx.send(Ok(data)); + let read = decoder.read_to_end(&mut data); + debug!("on_response_readable bytes read: {:?}", read); + + let _ = self.resp_tx + .send(Ok(data)) + .map_err(|err| error!("couldn't send response: {}", err)); Next::end() } fn on_error(&mut self, err: hyper::Error) -> Next { error!("on_error: {}", err); - let _ = self.resp_tx.send(Err(Error::from(err))); + let _ = self.resp_tx + .send(Err(Error::from(err))) + .map_err(|err| error!("couldn't send on_error response: {}", err)); Next::remove() } } @@ -221,7 +251,7 @@ mod tests { let req = HttpRequest { method: Method::Post, url: Url::parse("https://eu.httpbin.org/post").unwrap(), - body: Some("foo".to_owned().into_bytes()), + body: Some(br#"foo"#.to_vec()), }; let resp_rx = client.send_request(req); diff --git a/src/http_client/test_client.rs b/src/http_client/test_client.rs index 0ae0137..7974144 100644 --- a/src/http_client/test_client.rs +++ b/src/http_client/test_client.rs @@ -6,7 +6,7 @@ use datatype::Error; pub struct TestHttpClient { - replies: RefCell>> + replies: RefCell> } impl TestHttpClient { @@ -14,16 +14,16 @@ impl TestHttpClient { TestHttpClient { replies: RefCell::new(Vec::new()) } } - pub fn from(replies: Vec>) -> TestHttpClient { + pub fn from(replies: Vec) -> TestHttpClient { TestHttpClient { replies: RefCell::new(replies) } } } impl HttpClient for TestHttpClient { fn chan_request(&self, req: HttpRequest, resp_tx: Sender) { - match self.replies.borrow_mut().pop() { - Some(body) => { let _ = resp_tx.send(Ok(body)); } - None => { let _ = resp_tx.send(Err(Error::ClientError(req.url.to_string()))); } - } + let _ = match self.replies.borrow_mut().pop() { + Some(body) => resp_tx.send(Ok(body.as_bytes().to_vec())), + None => resp_tx.send(Err(Error::ClientError(req.url.to_string()))) + }.map_err(|err| error!("couldn't send test chan_request response: {}", err)); } } diff --git a/src/interpreter.rs b/src/interpreter.rs index ea1880d..514c7e6 100644 --- a/src/interpreter.rs +++ b/src/interpreter.rs @@ -136,9 +136,11 @@ impl<'t> Interpreter, Wrapped, Event> for GlobalInterpreter { }); fn send(ev: Event, local_tx: &Option>>>) { - // unwrap failed sends to avoid receiver thread deadlocking if let Some(ref local) = *local_tx { - let _ = local.lock().unwrap().send(ev).unwrap(); + let _ = local.lock() + .unwrap() + .send(ev) + .map_err(|err| panic!("couldn't send interpreter response: {}", err)); } } } @@ -146,23 +148,15 @@ impl<'t> Interpreter, Wrapped, Event> for GlobalInterpreter { fn command_interpreter(env: &mut Env, cmd: Command, etx: Sender) -> Result<(), Error> { match env.access_token.to_owned() { - Some(token) => match token { - Cow::Borrowed(t) => { - let client = AuthClient::new(Auth::Token(t.clone())); - authenticated(env, &client, cmd, etx) - } - - Cow::Owned(t) => { - let client = AuthClient::new(Auth::Token(t)); - authenticated(env, &client, cmd, etx) - } - }, + Some(token) => { + let client = AuthClient::new(Auth::Token(token.into_owned())); + authenticated(env, &client, cmd, etx) + } None => { let client = AuthClient::new(Auth::Credentials( ClientId(env.config.auth.client_id.clone()), - ClientSecret(env.config.auth.secret.clone()) - )); + ClientSecret(env.config.auth.secret.clone()))); unauthenticated(env, client, cmd, etx) } } diff --git a/src/main.rs b/src/main.rs index ce50847..912d008 100644 --- a/src/main.rs +++ b/src/main.rs @@ -29,18 +29,6 @@ use libotaplus::interpreter::{AuthenticationRetrier, AutoAcceptor, Env, use libotaplus::package_manager::PackageManager; -fn spawn_global_interpreter(config: Config, - wtx: Sender, - wrx: Receiver, - etx: Sender) { - let mut env = Env { - config: config.clone(), - access_token: None, - wtx: wtx, - }; - GlobalInterpreter::run(&mut env, wrx, etx); -} - fn spawn_signal_handler(signals: ChanReceiver, ctx: Sender) { loop { match signals.recv() { @@ -106,10 +94,8 @@ fn main() { let auth_ctx = ctx.clone(); scope.spawn(move || AuthenticationRetrier::run(&mut (), auth_sub, auth_ctx)); - let glob_cfg = config.clone(); - let glob_etx = etx.clone(); - let glob_wtx = wtx.clone(); - scope.spawn(move || spawn_global_interpreter(glob_cfg, glob_wtx, wrx, glob_etx)); + let mut glob_env = Env { config: config.clone(), access_token: None, wtx: wtx.clone() }; + scope.spawn(move || GlobalInterpreter::run(&mut glob_env, wrx, etx)); let ws_wtx = wtx.clone(); let ws_sub = broadcast.subscribe(); diff --git a/src/oauth2.rs b/src/oauth2.rs index 69d6084..c9e3c34 100644 --- a/src/oauth2.rs +++ b/src/oauth2.rs @@ -33,7 +33,7 @@ mod tests { #[test] fn test_authenticate() { let token = r#"{"access_token": "token", "token_type": "type", "expires_in": 10, "scope": ["scope"]}"#; - let client = TestHttpClient::from(vec![token.as_bytes().to_vec()]); + let client = TestHttpClient::from(vec![token.to_string()]); let expect = AccessToken { access_token: "token".to_string(), token_type: "type".to_string(), @@ -45,7 +45,7 @@ mod tests { #[test] fn test_authenticate_no_token() { - let client = TestHttpClient::from(vec!["".as_bytes().to_vec()]); + let client = TestHttpClient::from(vec!["".to_string()]); // XXX: Old error message was arguably a lot better... // "Authentication error, didn't receive access token.") let expect = r#"Failed to decode JSON: ParseError(SyntaxError("EOF While parsing value", 1, 1))"#; @@ -54,7 +54,7 @@ mod tests { #[test] fn test_authenticate_bad_json() { - let client = TestHttpClient::from(vec![r#"{"apa": 1}"#.as_bytes().to_vec()]); + let client = TestHttpClient::from(vec![r#"{"apa": 1}"#.to_string()]); let expect = r#"Failed to decode JSON: MissingFieldError("access_token")"#; assert_eq!(expect, format!("{}", authenticate(&AuthConfig::default(), &client).unwrap_err())); } diff --git a/src/ota_plus.rs b/src/ota_plus.rs index 1a60b69..fc296a7 100644 --- a/src/ota_plus.rs +++ b/src/ota_plus.rs @@ -173,7 +173,7 @@ mod tests { }; let json = format!("[{}]", json::encode(&pending_update).unwrap()); - let mut client = TestHttpClient::from(vec![json.as_bytes().to_vec()]); + let mut client = TestHttpClient::from(vec![json.to_string()]); let mut ota = new_test_ota(&mut client); let updates: Vec = ota.get_package_updates().unwrap(); @@ -228,7 +228,7 @@ mod tests { }; let mut ota = OTA { - client: &mut TestHttpClient::from(vec!["".as_bytes().to_vec()]), + client: &mut TestHttpClient::from(vec!["".to_string()]), config: config }; let (tx, rx) = channel(); @@ -253,8 +253,8 @@ mod tests { }; let replies = vec![ - "[]".as_bytes().to_vec(), - "package data".as_bytes().to_vec(), + "[]".to_string(), + "package data".to_string(), ]; let mut ota = OTA { client: &mut TestHttpClient::from(replies), -- cgit v1.2.1 From 91fe596c0366172d445a1c82e26bd495d98c9089 Mon Sep 17 00:00:00 2001 From: Shaun Taheri Date: Thu, 9 Jun 2016 14:36:54 +0200 Subject: Fix WouldBlock retry handling on Yocto hardware --- src/http_client/auth_client.rs | 105 +++++++++++++++---------- src/interaction_library/http.rs | 167 +++++++++++++++++++++++----------------- 2 files changed, 162 insertions(+), 110 deletions(-) diff --git a/src/http_client/auth_client.rs b/src/http_client/auth_client.rs index d4289ef..f19f014 100644 --- a/src/http_client/auth_client.rs +++ b/src/http_client/auth_client.rs @@ -4,7 +4,8 @@ use hyper::client::{Client, Handler, HttpsConnector, Request, Response}; use hyper::header::{Authorization, Basic, Bearer, ContentLength, ContentType, Location}; use hyper::mime::{Attr, Mime, TopLevel, SubLevel, Value}; use hyper::net::{HttpStream, HttpsStream, OpensslStream, Openssl}; -use std::io::{Read, Write, ErrorKind}; +use std::{io, mem}; +use std::io::{ErrorKind, Write}; use std::sync::mpsc::Sender; use std::time::Duration; use time; @@ -39,11 +40,13 @@ impl HttpClient for AuthClient { fn chan_request(&self, req: HttpRequest, resp_tx: Sender) { debug!("send_request_to: {:?}", req.url); let _ = self.client.request(req.url.inner(), AuthHandler { - auth: self.auth.clone(), - req: req, - timeout: Duration::from_secs(20), - started: None, - resp_tx: resp_tx.clone(), + auth: self.auth.clone(), + req: req, + timeout: Duration::from_secs(20), + started: None, + written: 0, + response: Vec::new(), + resp_tx: resp_tx.clone(), }).map_err(|err| { resp_tx.send(Err(Error::from(err))) .map_err(|err| error!("couldn't send chan_request: {}", err)) @@ -54,11 +57,13 @@ impl HttpClient for AuthClient { // FIXME: uncomment when yocto is at 1.8.0: #[derive(Debug)] pub struct AuthHandler { - auth: Auth, - req: HttpRequest, - timeout: Duration, - started: Option, - resp_tx: Sender, + auth: Auth, + req: HttpRequest, + timeout: Duration, + started: Option, + written: usize, + response: Vec, + resp_tx: Sender, } // FIXME: required for building on 1.7.0 only @@ -111,7 +116,7 @@ pub type Stream = HttpsStream>; impl Handler for AuthHandler { fn on_request(&mut self, req: &mut Request) -> Next { - info!("on_request"); + info!("on_request: {} {}", req.method(), req.uri()); self.started = Some(time::precise_time_ns()); req.set_method(self.req.method.clone().into()); @@ -153,28 +158,32 @@ impl Handler for AuthHandler { } fn on_request_writable(&mut self, encoder: &mut Encoder) -> Next { - info!("on_request_writable"); + let body = self.req.body.as_ref().expect("on_request_writable expects a body"); - match self.req.body { - Some(ref body) => match encoder.write_all(body) { - Ok(_) => Next::read().timeout(self.timeout), - - Err(err) => match err.kind() { - ErrorKind::WouldBlock => { - trace!("retry on_request_writable"); - Next::write() - } - _ => { - error!("unable to write body: {}", err); - let _ = self.resp_tx - .send(Err(Error::from(err))) - .map_err(|err| error!("couldn't send write_all error: {}", err)); - Next::remove() - } - } + match encoder.write(&body[self.written..]) { + Ok(0) => { + debug!("{} bytes written to request body", self.written); + Next::read().timeout(self.timeout) }, - None => panic!("on_request_writable called on an empty body") + Ok(n) => { + self.written += n; + trace!("{} bytes written to request body", n); + Next::write() + } + + Err(ref err) if err.kind() == ErrorKind::WouldBlock => { + trace!("retry on_request_writable"); + Next::write() + } + + Err(err) => { + error!("unable to write request body: {}", err); + let _ = self.resp_tx + .send(Err(Error::from(err))) + .map_err(|err| error!("couldn't send write error: {}", err)); + Next::remove() + } } } @@ -185,6 +194,7 @@ impl Handler for AuthHandler { } if resp.status().is_success() { + self.written = 0; Next::read() } else if resp.status().is_redirection() { self.redirect_request(resp); @@ -200,16 +210,33 @@ impl Handler for AuthHandler { } fn on_response_readable(&mut self, decoder: &mut Decoder) -> Next { - info!("on_response_readable"); + match io::copy(decoder, &mut self.response) { + Ok(0) => { + debug!("on_response_readable bytes read: {:?}", self.response.len()); + let _ = self.resp_tx + .send(Ok(mem::replace(&mut self.response, Vec::new()))) + .map_err(|err| error!("couldn't send response: {}", err)); + Next::end() + } - let mut data: Vec = Vec::new(); - let read = decoder.read_to_end(&mut data); - debug!("on_response_readable bytes read: {:?}", read); + Ok(n) => { + trace!("{} more response bytes read", n); + Next::read() + } - let _ = self.resp_tx - .send(Ok(data)) - .map_err(|err| error!("couldn't send response: {}", err)); - Next::end() + Err(ref err) if err.kind() == ErrorKind::WouldBlock => { + trace!("retry on_response_readable"); + Next::read() + } + + Err(err) => { + error!("unable to read response body: {}", err); + let _ = self.resp_tx + .send(Err(Error::from(err))) + .map_err(|err| error!("couldn't send read error: {}", err)); + Next::end() + } + } } fn on_error(&mut self, err: hyper::Error) -> Next { diff --git a/src/interaction_library/http.rs b/src/interaction_library/http.rs index e57e0c0..29408c6 100644 --- a/src/interaction_library/http.rs +++ b/src/interaction_library/http.rs @@ -2,9 +2,9 @@ use hyper::{Decoder, Encoder, Next, StatusCode}; use hyper::net::HttpStream; use hyper::server::{Handler, Server, Request, Response}; use rustc_serialize::{json, Decodable, Encodable}; -use std::{env, thread}; +use std::{env, io, mem, thread}; use std::fmt::Debug; -use std::io::{ErrorKind, Read, Write}; +use std::io::{ErrorKind, Write}; use std::sync::{Arc, Mutex, mpsc}; use std::sync::mpsc::{Sender, Receiver}; use std::time::Duration; @@ -51,9 +51,11 @@ pub struct HttpHandler where C: Decodable + Send + Clone + Debug, E: Encodable + Send + Clone { - itx: Arc>>>, - erx: Option>, - body: Option>, + itx: Arc>>>, + erx: Option>, + req_body: Vec, + resp_body: Option>, + written: usize, } impl HttpHandler @@ -61,7 +63,44 @@ impl HttpHandler E: Encodable + Send + Clone { fn new(itx: Arc>>>) -> HttpHandler { - HttpHandler { itx: itx, erx: None, body: None } + HttpHandler { + itx: itx, + erx: None, + req_body: Vec::new(), + resp_body: None, + written: 0 + } + } + + fn decode_request(&mut self) -> Next { + let body = mem::replace(&mut self.req_body, Vec::new()); + + match String::from_utf8(body) { + Ok(body) => match json::decode::(&body) { + Ok(c) => { + info!("on_request_readable: decoded command: {:?}", c); + + let (etx, erx): (Sender, Receiver) = mpsc::channel(); + self.erx = Some(erx); + self.itx.lock().unwrap().send(Interpret { + cmd: c, + etx: Some(Arc::new(Mutex::new(etx))), + }).unwrap(); + + Next::write().timeout(Duration::from_secs(20)) + } + + Err(err) => { + error!("decode_request: parse json: {}", err); + Next::remove() + } + }, + + Err(err) => { + error!("decode_request: parse string: {}", err); + Next::remove() + } + } } } @@ -75,94 +114,80 @@ impl Handler for HttpHandler } fn on_request_readable(&mut self, transport: &mut Decoder) -> Next { - info!("on_request_readable"); - - let mut data = Vec::new(); - match transport.read_to_end(&mut data) { - Ok(_) => match String::from_utf8(data) { - Ok(body) => match json::decode::(&body) { - Ok(c) => { - info!("on_request_readable: decoded command: {:?}", c); - - let (etx, erx): (Sender, Receiver) = mpsc::channel(); - self.erx = Some(erx); - self.itx.lock().unwrap().send(Interpret { - cmd: c, - etx: Some(Arc::new(Mutex::new(etx))), - }).unwrap(); - - Next::write().timeout(Duration::from_secs(10)) - } + match io::copy(transport, &mut self.req_body) { + Ok(0) => { + debug!("on_request_readable bytes read: {:?}", self.req_body.len()); + self.decode_request() + } - Err(err) => { - error!("on_request_readable decode json: {}", err); - Next::remove() - } - }, + Ok(n) => { + trace!("{} more request bytes read", n); + Next::read() + } - Err(err) => { - error!("on_request_readable parse string: {}", err); - Next::remove() - } - }, + Err(ref err) if err.kind() == ErrorKind::WouldBlock => { + trace!("retry on_request_readable"); + Next::read() + } - Err(err) => match err.kind() { - ErrorKind::WouldBlock => Next::read(), - _ => { - error!("on_request_readable read_to_end: {}", err); - Next::remove() - } + Err(err) => { + error!("unable to read request body: {}", err); + Next::remove() } } } fn on_response(&mut self, resp: &mut Response) -> Next { info!("on_response: status {}", resp.status()); - - match self.erx { - Some(ref rx) => match rx.recv() { - Ok(e) => match json::encode(&e) { - Ok(body) => { - resp.set_status(StatusCode::Ok); - self.body = Some(body.into_bytes()); - Next::write() - } - - Err(err) => { - error!("on_response encoding json: {:?}", err); - resp.set_status(StatusCode::InternalServerError); - Next::end() - } - }, + let rx = self.erx.as_ref().expect("Some receiver expected"); + + match rx.recv() { + Ok(e) => match json::encode(&e) { + Ok(body) => { + resp.set_status(StatusCode::Ok); + self.resp_body = Some(body.into_bytes()); + Next::write() + } Err(err) => { - error!("on_response receiver: {}", err); + error!("on_response encoding json: {:?}", err); resp.set_status(StatusCode::InternalServerError); Next::end() } }, - None => panic!("expected Some receiver") + Err(err) => { + error!("on_response receiver: {}", err); + resp.set_status(StatusCode::InternalServerError); + Next::end() + } } } fn on_response_writable(&mut self, transport: &mut Encoder) -> Next { - info!("on_response_writable"); + let body = self.resp_body.as_ref().expect("on_response_writable has empty body"); - match self.body { - Some(ref body) => match transport.write_all(body) { - Ok(_) => Next::end(), + match transport.write(&body[self.written..]) { + Ok(0) => { + debug!("{} bytes written to response body", self.written); + Next::end() + } - Err(err) => match err.kind() { - ErrorKind::WouldBlock => Next::write(), - _ => { - error!("unable to write body: {}", err); - Next::remove() - } - } - }, + Ok(n) => { + self.written += n; + trace!("{} bytes written to response body", n); + Next::write() + } + + Err(ref err) if err.kind() == ErrorKind::WouldBlock => { + trace!("retry on_response_writable"); + Next::write() + } - None => panic!("on_response_writable called on empty body") + Err(err) => { + error!("unable to write response body: {}", err); + Next::remove() + } } } } -- cgit v1.2.1 From 113a6f47545dfceabf9a8983b279699fea89dcce Mon Sep 17 00:00:00 2001 From: Alex Humphreys Date: Fri, 10 Jun 2016 14:22:11 +0200 Subject: Add OTA_CLIENT_VIN env to start-up.sh --- pkg/provision/start-up.sh | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pkg/provision/start-up.sh b/pkg/provision/start-up.sh index 6eb16c5..a168d2b 100755 --- a/pkg/provision/start-up.sh +++ b/pkg/provision/start-up.sh @@ -13,7 +13,8 @@ TEMPLATE_PATH="/etc/ota.toml.template" VIN_SUFFIX=$(< /dev/urandom tr -dc A-HJ-NPR-Z0-9 | head -c${1:-11};echo;) echo $VIN_SUFFIX -export OTA_CLIENT_VIN=STRESS$VIN_SUFFIX +export RANDOM_VIN=STRESS$VIN_SUFFIX +export OTA_CLIENT_VIN=${OTA_CLIENT_VIN-$RANDOM_VIN} export HTTP_SESSION="/tmp/$OTA_CLIENT_VIN.json" export OTA_WEB_USER="${OTA_WEB_USER-demo@advancedtelematic.com}" export OTA_WEB_PASSWORD="${OTA_WEB_PASSWORD-demo}" -- cgit v1.2.1 From 95689bab89ada89e14d7acfd4ab5adebfd0d1899 Mon Sep 17 00:00:00 2001 From: Alex Humphreys Date: Mon, 13 Jun 2016 11:50:54 +0200 Subject: Update start-up.sh to be more configurable --- README.md | 15 +++++++++++++++ pkg/ota.toml.template | 2 +- pkg/provision/start-up.sh | 22 +++++++++++++--------- 3 files changed, 29 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index d5a66fb..b30f336 100644 --- a/README.md +++ b/README.md @@ -42,3 +42,18 @@ To use it, run: ``` docker run advancedtelematic/ota-plus-client ``` + +You can configure it using the following environment variables: + +- `OTA_WEB_URL`, default value: http://ota-plus-web-staging.gw.prod01.advancedtelematic.com +- `OTA_CORE_URL`, default value: http://ota-plus-core-staging.gw.prod01.advancedtelematic.com +- `OTA_AUTH_URL`, default value: http://auth-plus-staging.gw.prod01.advancedtelematic.com +- `OTA_WEB_USER`, default value: demo@advancedtelematic.com +- `OTA_WEB_PASSWORD`, default value: demo +- `OTA_CLIENT_VIN`, default value: Randomly generated +- `OTA_AUTH_CLIENT_ID`, default value: Generated for VIN +- `OTA_AUTH_SECRET`, default value: Generated for VIN + +Eg: `docker run --rm --net=host -e OTA_AUTH_URL=http://127.0.0.1:9001 -e OTA_WEB_URL="http://localhost:9000" -e OTA_CORE_URL="http://localhost:8080" advancedtelematic/ota-plus-client:latest` + +If running against local urls, be sure to pass `--net=host` to the `docker run` command. diff --git a/pkg/ota.toml.template b/pkg/ota.toml.template index 3fbf4d0..67aac01 100644 --- a/pkg/ota.toml.template +++ b/pkg/ota.toml.template @@ -6,7 +6,7 @@ credentials_file = "/opt/ats/credentials.toml" vin = "${OTA_CLIENT_VIN}" [ota] -server = "${OTA_SERVER_URL}" +server = "${OTA_CORE_URL}" polling_interval = 10 packages_dir = "/tmp/" package_manager = "${PACKAGE_MANAGER}" diff --git a/pkg/provision/start-up.sh b/pkg/provision/start-up.sh index a168d2b..01bd295 100755 --- a/pkg/provision/start-up.sh +++ b/pkg/provision/start-up.sh @@ -2,9 +2,13 @@ set -eo pipefail +export OTA_WEB_URL=${OTA_WEB_URL-http://ota-plus-web-staging.gw.prod01.advancedtelematic.com} +export OTA_CORE_URL=${OTA_CORE_URL-http://ota-plus-core-staging.gw.prod01.advancedtelematic.com} +export OTA_AUTH_URL=${OTA_AUTH_URL-http://auth-plus-staging.gw.prod01.advancedtelematic.com} + OTA_AUTH_PATH="/clients" -OTA_SERVER_PATH="/api/v1/vehicles/" +VEHICLES_PATH="/api/v1/vehicles/" PACKAGE_MANAGER="dpkg" @@ -21,27 +25,27 @@ export OTA_WEB_PASSWORD="${OTA_WEB_PASSWORD-demo}" #export OTA_CLIENT_VIN=STRESS12345678901 -http --check-status --session=$HTTP_SESSION POST ${OTA_SERVER_URL}/authenticate \ +http --check-status --session=$HTTP_SESSION POST ${OTA_WEB_URL}/authenticate \ username=$OTA_WEB_USER password=$OTA_WEB_PASSWORD --ignore-stdin || [[ $? == 3 ]] -echo "vin=${OTA_CLIENT_VIN}" | http --check-status --session=$HTTP_SESSION put "${OTA_SERVER_URL}${OTA_SERVER_PATH}${OTA_CLIENT_VIN}" +echo "vin=${OTA_CLIENT_VIN}" | http --check-status --session=$HTTP_SESSION put "${OTA_WEB_URL}${VEHICLES_PATH}${OTA_CLIENT_VIN}" JSON=$(envsubst < /etc/auth.json) AUTH_DATA=$(echo $JSON | http --check-status post $OTA_AUTH_URL$OTA_AUTH_PATH) -OTA_AUTH_CLIENT_ID=$(echo $AUTH_DATA | jq -r .client_id) -OTA_AUTH_SECRET=$(echo $AUTH_DATA | jq -r .client_secret) +CLIENT_ID=$(echo $AUTH_DATA | jq -r .client_id) +SECRET=$(echo $AUTH_DATA | jq -r .client_secret) export OTA_CLIENT_VIN=$OTA_CLIENT_VIN export OTA_AUTH_URL=$OTA_AUTH_URL -export OTA_SERVER_URL=$OTA_CORE_URL -export OTA_AUTH_CLIENT_ID=$OTA_AUTH_CLIENT_ID -export OTA_AUTH_SECRET=$OTA_AUTH_SECRET +export OTA_CORE_URL=$OTA_CORE_URL +export OTA_AUTH_CLIENT_ID=${OTA_AUTH_CLIENT_ID-$CLIENT_ID} +export OTA_AUTH_SECRET=${OTA_AUTH_SECRET-$SECRET} export PACKAGE_MANAGER=$PACKAGE_MANAGER export OTA_HTTP=${OTA_HTTP-false} echo $OTA_CLIENT_VIN echo $OTA_AUTH_URL -echo $OTA_SERVER_URL +echo $OTA_CORE_URL echo $OTA_AUTH_CLIENT_ID echo $OTA_AUTH_SECRET export $PACKAGE_MANAGER -- cgit v1.2.1 From 092b8e99363dccf5357077befd1995997139cfc9 Mon Sep 17 00:00:00 2001 From: Shaun Taheri Date: Mon, 13 Jun 2016 12:42:21 +0200 Subject: Don't install rest of batch if package installation fails --- src/datatype/event.rs | 1 - src/interpreter.rs | 291 +++++++++++++++++++++++--------------------------- src/main.rs | 18 ++-- 3 files changed, 138 insertions(+), 172 deletions(-) diff --git a/src/datatype/event.rs b/src/datatype/event.rs index 975d8ac..ac36697 100644 --- a/src/datatype/event.rs +++ b/src/datatype/event.rs @@ -16,7 +16,6 @@ pub enum Event { UpdateErrored(UpdateRequestId, String), Error(String), FoundInstalledPackages(Vec), - Batch(Vec) } impl ToString for Event { diff --git a/src/interpreter.rs b/src/interpreter.rs index 514c7e6..6a417b1 100644 --- a/src/interpreter.rs +++ b/src/interpreter.rs @@ -12,23 +12,13 @@ use oauth2::authenticate; use ota_plus::OTA; -pub type Wrapped = Interpret; - - -pub struct Env<'t> { - pub config: Config, - pub access_token: Option>, - pub wtx: Sender, -} - - -pub trait Interpreter { - fn interpret(env: &mut Env, msg: I, otx: Sender); +pub trait Interpreter { + fn interpret(&mut self, msg: I, otx: Sender); - fn run(env: &mut Env, irx: Receiver, otx: Sender) { + fn run(&mut self, irx: Receiver, otx: Sender) { loop { match irx.recv() { - Ok(msg) => Self::interpret(env, msg, otx.clone()), + Ok(msg) => self.interpret(msg, otx.clone()), Err(err) => error!("Error receiving command: {:?}", err), } } @@ -36,187 +26,168 @@ pub trait Interpreter { } -pub struct AutoAcceptor; +pub struct EventInterpreter; -impl Interpreter<(), Event, Command> for AutoAcceptor { - fn interpret(_: &mut (), event: Event, ctx: Sender) { - info!("Automatic interpreter: {:?}", event); - match event { - Event::Batch(ref evs) => { - for ev in evs { - accept(&ev, ctx.clone()) - } +impl Interpreter for EventInterpreter { + fn interpret(&mut self, event: Event, ctx: Sender) { + info!("Event interpreter: {:?}", event); + let _ = match event { + Event::NotAuthenticated => { + debug!("trying to authenticate again..."); + ctx.send(Command::Authenticate(None)) } - ev => accept(&ev, ctx), - } - fn accept(event: &Event, ctx: Sender) { - if let &Event::NewUpdateAvailable(ref id) = event { - let _ = ctx.send(Command::AcceptUpdate(id.clone())); + Event::NewUpdateAvailable(ref id) => { + ctx.send(Command::AcceptUpdate(id.clone())) } - } - } -} -pub struct AuthenticationRetrier; + /* TODO: Handle PackageManger events + Event::DownloadComplete => { + env.config.ota.package_manager.install_package(p); + ctx.send(Command::UpdateReport()) + } -impl Interpreter<(), Event, Command> for AuthenticationRetrier { - fn interpret(_: &mut (), event: Event, ctx: Sender) { - match event { - Event::NotAuthenticated => { - info!("Trying to authenticate again"); - let _ = ctx.send(Command::Authenticate(None)); + Event::GetInstalledSoftware => { + env.config.ota.package_manager.installed_packages(); + ctx.send(Command::InstalledSoftware()) } - _ => {} - } + */ + + _ => Ok(()) + }.map_err(|err| panic!("couldn't interpret event: {}", err)); } } -/* TODO: Handle events to PackageManager -pub struct AutoPackageInstaller; -impl Interpreter<(), Event, Command> for AutoPackageInstaller { - fn interpret(env: &mut Env, event: Event, ctx: Sender) { - match event { - Event::DownloadComplete => { - match env.config.ota.package_manager.install_package(p) { - _ => { - let _ = ctx.send(Command::UpdateReport()); - } - } +pub type Wrapped = Interpret; + +pub struct WrappedInterpreter<'t> { + pub config: Config, + pub access_token: Option>, +} + +impl<'t> WrappedInterpreter<'t> { + fn interpret_command(&mut self, cmd: Command, etx: Sender) -> Result<(), Error> { + match self.access_token.to_owned() { + Some(token) => { + let client = AuthClient::new(Auth::Token(token.into_owned())); + self.authenticated(client, cmd, etx) } - Event::GetInstalledSoftware => { - match env.config.ota.package_manager.installed_packages() { - _ => { - let _ = ctx.send(Command::InstalledSoftware()); - } - } + + None => { + let client = AuthClient::new(Auth::Credentials( + ClientId(self.config.auth.client_id.clone()), + ClientSecret(self.config.auth.secret.clone()))); + self.unauthenticated(client, cmd, etx) } - _ => {} } } -} -*/ + fn authenticated(&mut self, client: AuthClient, cmd: Command, etx: Sender) -> Result<(), Error> { + let mut ota = OTA::new(&client, self.config.clone()); -pub struct GlobalInterpreter; + match cmd { + Authenticate(_) => try!(etx.send(Event::Ok)), -impl<'t> Interpreter, Wrapped, Event> for GlobalInterpreter { - fn interpret(env: &mut Env, w: Wrapped, global_tx: Sender) { - info!("Interpreting: {:?}", w.cmd); - let (multi_tx, multi_rx): (Sender, Receiver) = channel(); - let local_tx = w.etx.clone(); - let w_clone = w.clone(); - - let _ = command_interpreter(env, w.cmd, multi_tx) - .map_err(|err| { - if let Error::AuthorizationError(_) = err { - debug!("retry authorization and request"); - let _ = env.wtx.send(Wrapped { - cmd: Command::Authenticate(None), - etx: None - }); - let _ = env.wtx.send(w_clone); - } else { - let ev = Event::Error(format!("{}", err)); - let _ = global_tx.send(ev.clone()).unwrap(); - send(ev, &local_tx); - } - }) - .map(|_| { - let mut last_ev = None; - for ev in multi_rx { - let _ = global_tx.send(ev.clone()).unwrap(); - last_ev = Some(ev); - } - match last_ev { - Some(ev) => send(ev, &local_tx), - None => panic!("no event to send back") + AcceptUpdate(ref id) => { + try!(etx.send(Event::UpdateStateChanged(id.clone(), UpdateState::Downloading))); + let report = try!(ota.install_package_update(&id, &etx)); + try!(ota.send_install_report(&report)); + info!("Update finished. Report sent: {:?}", report) + } + + GetPendingUpdates => { + let mut updates = try!(ota.get_package_updates()); + if updates.len() > 0 { + updates.sort_by_key(|update| update.installPos); + info!("New package updates available: {:?}", updates); + for update in updates.iter() { + try!(etx.send(Event::NewUpdateAvailable(update.requestId.clone()))) + } } - }); - - fn send(ev: Event, local_tx: &Option>>>) { - if let Some(ref local) = *local_tx { - let _ = local.lock() - .unwrap() - .send(ev) - .map_err(|err| panic!("couldn't send interpreter response: {}", err)); } - } - } -} -fn command_interpreter(env: &mut Env, cmd: Command, etx: Sender) -> Result<(), Error> { - match env.access_token.to_owned() { - Some(token) => { - let client = AuthClient::new(Auth::Token(token.into_owned())); - authenticated(env, &client, cmd, etx) - } + ListInstalledPackages => { + let pkgs = try!(self.config.ota.package_manager.installed_packages()); + try!(etx.send(Event::FoundInstalledPackages(pkgs))) + } - None => { - let client = AuthClient::new(Auth::Credentials( - ClientId(env.config.auth.client_id.clone()), - ClientSecret(env.config.auth.secret.clone()))); - unauthenticated(env, client, cmd, etx) + UpdateInstalledPackages => { + try!(ota.update_installed_packages()); + try!(etx.send(Event::Ok)); + info!("Posted installed packages to the server.") + } + + Shutdown => exit(0), } + + Ok(()) } -} -fn authenticated(env: &mut Env, client: &AuthClient, cmd: Command, etx: Sender) -> Result<(), Error> { - let mut ota = OTA::new(client, env.config.clone()); + fn unauthenticated(&mut self, client: AuthClient, cmd: Command, etx: Sender) -> Result<(), Error> { + match cmd { + Authenticate(_) => { + let token = try!(authenticate(&self.config.auth, &client)); + self.access_token = Some(token.into()); + try!(etx.send(Event::Ok)); + } - match cmd { - Authenticate(_) => try!(etx.send(Event::Ok)), + AcceptUpdate(_) | + GetPendingUpdates | + ListInstalledPackages | + UpdateInstalledPackages => try!(etx.send(Event::NotAuthenticated)), - AcceptUpdate(ref id) => { - try!(etx.send(Event::UpdateStateChanged(id.clone(), UpdateState::Downloading))); - let report = try!(ota.install_package_update(&id, &etx)); - try!(ota.send_install_report(&report)); - info!("Update finished. Report sent: {:?}", report) + Shutdown => exit(0), } - GetPendingUpdates => { - let mut updates = try!(ota.get_package_updates()); - updates.sort_by_key(|e| e.installPos); - let evs: Vec = updates.iter() - .map(|up| Event::NewUpdateAvailable(up.requestId.clone())) - .collect(); - info!("New package updates available: {:?}", evs); - try!(etx.send(Event::Batch(evs))) - } + Ok(()) + } +} - ListInstalledPackages => { - let pkgs = try!(env.config.ota.package_manager.installed_packages()); - try!(etx.send(Event::FoundInstalledPackages(pkgs.clone()))) - } +impl<'t> Interpreter for WrappedInterpreter<'t> { + fn interpret(&mut self, w: Wrapped, global_tx: Sender) { + info!("Wrapped interpreter: {:?}", w.cmd); + let (multi_tx, multi_rx): (Sender, Receiver) = channel(); - UpdateInstalledPackages => { - try!(ota.update_installed_packages()); - try!(etx.send(Event::Ok)); - info!("Posted installed packages to the server.") - } + let _ = match self.interpret_command(w.cmd.clone(), multi_tx) { + Ok(_) => { + let mut last_ev = None; + for ev in multi_rx { + last_ev = Some(ev.clone()); + send_global(ev, global_tx.clone()); + } + match last_ev { + Some(ev) => send_local(ev, w.etx), + None => panic!("no local event to send back") + } + } - Shutdown => exit(0), - } + Err(Error::AuthorizationError(_)) => { + debug!("retry authorization and request"); + let auth = Wrapped { cmd: Command::Authenticate(None), etx: None }; + self.interpret(auth, global_tx.clone()); + self.interpret(w, global_tx); + } - Ok(()) + Err(err) => { + let ev = Event::Error(format!("{}", err)); + send_global(ev.clone(), global_tx); + send_local(ev, w.etx); + } + }; + } } -fn unauthenticated(env: &mut Env, client: AuthClient, cmd: Command, etx: Sender) -> Result<(), Error> { - match cmd { - Authenticate(_) => { - let token = try!(authenticate(&env.config.auth, &client)); - env.access_token = Some(token.into()); - try!(etx.send(Event::Ok)); - } - - AcceptUpdate(_) | - GetPendingUpdates | - ListInstalledPackages | - UpdateInstalledPackages => try!(etx.send(Event::NotAuthenticated)), +fn send_global(ev: Event, global_tx: Sender) { + let _ = global_tx.send(ev) + .map_err(|err| panic!("couldn't send global response: {}", err)); +} - Shutdown => exit(0), +fn send_local(ev: Event, local_tx: Option>>>) { + if let Some(ref local) = local_tx { + let _ = local.lock() + .unwrap() + .send(ev) + .map_err(|err| panic!("couldn't send local response: {}", err)); } - - Ok(()) } diff --git a/src/main.rs b/src/main.rs index 912d008..6ed4ffb 100644 --- a/src/main.rs +++ b/src/main.rs @@ -24,8 +24,7 @@ use libotaplus::datatype::{config, Command, Config, Event, Url}; use libotaplus::interaction_library::{Console, Gateway, Http, Websocket}; use libotaplus::interaction_library::broadcast::Broadcast; use libotaplus::interaction_library::gateway::Interpret; -use libotaplus::interpreter::{AuthenticationRetrier, AutoAcceptor, Env, - GlobalInterpreter, Interpreter, Wrapped}; +use libotaplus::interpreter::{EventInterpreter, Interpreter, Wrapped, WrappedInterpreter}; use libotaplus::package_manager::PackageManager; @@ -86,16 +85,13 @@ fn main() { let cmd_wtx = wtx.clone(); scope.spawn(move || spawn_command_forwarder(crx, cmd_wtx)); - let acc_sub = broadcast.subscribe(); - let acc_ctx = ctx.clone(); - scope.spawn(move || AutoAcceptor::run(&mut (), acc_sub, acc_ctx)); + let ev_sub = broadcast.subscribe(); + let ev_ctx = ctx.clone(); + let mut ev_int = EventInterpreter; + scope.spawn(move || ev_int.run(ev_sub, ev_ctx)); - let auth_sub = broadcast.subscribe(); - let auth_ctx = ctx.clone(); - scope.spawn(move || AuthenticationRetrier::run(&mut (), auth_sub, auth_ctx)); - - let mut glob_env = Env { config: config.clone(), access_token: None, wtx: wtx.clone() }; - scope.spawn(move || GlobalInterpreter::run(&mut glob_env, wrx, etx)); + let mut w_int = WrappedInterpreter { config: config.clone(), access_token: None }; + scope.spawn(move || w_int.run(wrx, etx)); let ws_wtx = wtx.clone(); let ws_sub = broadcast.subscribe(); -- cgit v1.2.1 From c3ec8dce7479d38fe7324b04f5e26a3ef78d002b Mon Sep 17 00:00:00 2001 From: Alex Humphreys Date: Mon, 13 Jun 2016 16:31:59 +0200 Subject: Add -p provision flag to start-up.sh [PRO-613] --- pkg/provision/start-up.sh | 34 +++++++++++++++++++++++++--------- 1 file changed, 25 insertions(+), 9 deletions(-) diff --git a/pkg/provision/start-up.sh b/pkg/provision/start-up.sh index 01bd295..b630342 100755 --- a/pkg/provision/start-up.sh +++ b/pkg/provision/start-up.sh @@ -10,11 +10,11 @@ OTA_AUTH_PATH="/clients" VEHICLES_PATH="/api/v1/vehicles/" -PACKAGE_MANAGER="dpkg" +PACKAGE_MANAGER=${PACKAGE_MANAGER-'dpkg'} -TEMPLATE_PATH="/etc/ota.toml.template" +TEMPLATE_PATH=${TEMPLATE_PATH-'/etc/ota.toml.template'} -VIN_SUFFIX=$(< /dev/urandom tr -dc A-HJ-NPR-Z0-9 | head -c${1:-11};echo;) +VIN_SUFFIX=$(< /dev/urandom tr -dc A-HJ-NPR-Z0-9 | head -c 11;echo;) echo $VIN_SUFFIX export RANDOM_VIN=STRESS$VIN_SUFFIX @@ -29,7 +29,8 @@ http --check-status --session=$HTTP_SESSION POST ${OTA_WEB_URL}/authenticate \ username=$OTA_WEB_USER password=$OTA_WEB_PASSWORD --ignore-stdin || [[ $? == 3 ]] echo "vin=${OTA_CLIENT_VIN}" | http --check-status --session=$HTTP_SESSION put "${OTA_WEB_URL}${VEHICLES_PATH}${OTA_CLIENT_VIN}" -JSON=$(envsubst < /etc/auth.json) +AUTH_JSON_PATH=${AUTH_JSON_PATH-'/etc/auth.json'} +JSON=$(envsubst < $AUTH_JSON_PATH) AUTH_DATA=$(echo $JSON | http --check-status post $OTA_AUTH_URL$OTA_AUTH_PATH) CLIENT_ID=$(echo $AUTH_DATA | jq -r .client_id) @@ -50,8 +51,23 @@ echo $OTA_AUTH_CLIENT_ID echo $OTA_AUTH_SECRET export $PACKAGE_MANAGER -OTA_TOML=$(cat $TEMPLATE_PATH | envsubst > /etc/ota.toml) -sed '/credentials_file/d' /etc/ota.toml -echo /etc/ota.toml - -RUST_LOG=debug ota_plus_client --config=/etc/ota.toml +OUTPUT_PATH=${OUTPUT_PATH-/etc/ota.toml} + +while getopts ":p" opt; do + PROVISION='false' + case $opt in + p) + PROVISION='true' + ;; + esac +done + +if [[ $PROVISION == 'true' ]] +then + OTA_TOML=$(cat $TEMPLATE_PATH | envsubst ) + echo "$OTA_TOML" +else + OTA_TOML=$(cat $TEMPLATE_PATH | envsubst > $OUTPUT_PATH) + cat $OUTPUT_PATH + RUST_LOG=debug ota_plus_client --config=/etc/ota.toml +fi -- cgit v1.2.1 From de59382200792e5bb450ce1c37429a8e95c41535 Mon Sep 17 00:00:00 2001 From: Alex Humphreys Date: Mon, 13 Jun 2016 16:55:06 +0200 Subject: Refactor start-up.sh to use localhost --- README.md | 19 +++++++++++---- pkg/provision/start-up.sh | 61 +++++++++++++++-------------------------------- 2 files changed, 34 insertions(+), 46 deletions(-) diff --git a/README.md b/README.md index b30f336..cceee3f 100644 --- a/README.md +++ b/README.md @@ -45,15 +45,26 @@ docker run advancedtelematic/ota-plus-client You can configure it using the following environment variables: -- `OTA_WEB_URL`, default value: http://ota-plus-web-staging.gw.prod01.advancedtelematic.com -- `OTA_CORE_URL`, default value: http://ota-plus-core-staging.gw.prod01.advancedtelematic.com -- `OTA_AUTH_URL`, default value: http://auth-plus-staging.gw.prod01.advancedtelematic.com +- `OTA_AUTH_URL`, default value: http://localhost:9001 +- `OTA_WEB_URL`, default value: http://localhost:9000 +- `OTA_CORE_URL`, default value: http://localhost:8080 - `OTA_WEB_USER`, default value: demo@advancedtelematic.com - `OTA_WEB_PASSWORD`, default value: demo - `OTA_CLIENT_VIN`, default value: Randomly generated - `OTA_AUTH_CLIENT_ID`, default value: Generated for VIN - `OTA_AUTH_SECRET`, default value: Generated for VIN -Eg: `docker run --rm --net=host -e OTA_AUTH_URL=http://127.0.0.1:9001 -e OTA_WEB_URL="http://localhost:9000" -e OTA_CORE_URL="http://localhost:8080" advancedtelematic/ota-plus-client:latest` +Eg: + +``` +docker run \ + -it \ + --rm \ + --net=host \ + -e OTA_AUTH_URL="http://auth-plus-staging.gw.prod01.advancedtelematic.com" \ + -e OTA_WEB_URL="http://ota-plus-web-staging.gw.prod01.advancedtelematic.com" \ + -e OTA_CORE_URL="http://ota-plus-core-staging.gw.prod01.advancedtelematic.com" \ + advancedtelematic/ota-plus-client:latest +``` If running against local urls, be sure to pass `--net=host` to the `docker run` command. diff --git a/pkg/provision/start-up.sh b/pkg/provision/start-up.sh index b630342..35c710f 100755 --- a/pkg/provision/start-up.sh +++ b/pkg/provision/start-up.sh @@ -2,67 +2,44 @@ set -eo pipefail -export OTA_WEB_URL=${OTA_WEB_URL-http://ota-plus-web-staging.gw.prod01.advancedtelematic.com} -export OTA_CORE_URL=${OTA_CORE_URL-http://ota-plus-core-staging.gw.prod01.advancedtelematic.com} -export OTA_AUTH_URL=${OTA_AUTH_URL-http://auth-plus-staging.gw.prod01.advancedtelematic.com} +export OTA_AUTH_URL=${OTA_AUTH_URL-http://localhost:9001} +export OTA_WEB_URL=${OTA_WEB_URL-http://localhost:9000} +export OTA_CORE_URL=${OTA_CORE_URL-http://localhost:8080} +export PACKAGE_MANAGER=${PACKAGE_MANAGER-'dpkg'} +export OTA_WEB_USER="${OTA_WEB_USER-demo@advancedtelematic.com}" +export OTA_WEB_PASSWORD="${OTA_WEB_PASSWORD-demo}" +export OTA_HTTP=${OTA_HTTP-false} -OTA_AUTH_PATH="/clients" +TEMPLATE_PATH=${TEMPLATE_PATH-'/etc/ota.toml.template'} +AUTH_JSON_PATH=${AUTH_JSON_PATH-'/etc/auth.json'} +OUTPUT_PATH=${OUTPUT_PATH-/etc/ota.toml} +OTA_AUTH_PATH="/clients" VEHICLES_PATH="/api/v1/vehicles/" -PACKAGE_MANAGER=${PACKAGE_MANAGER-'dpkg'} - -TEMPLATE_PATH=${TEMPLATE_PATH-'/etc/ota.toml.template'} - +# Generate VIN VIN_SUFFIX=$(< /dev/urandom tr -dc A-HJ-NPR-Z0-9 | head -c 11;echo;) - -echo $VIN_SUFFIX -export RANDOM_VIN=STRESS$VIN_SUFFIX +RANDOM_VIN=STRESS$VIN_SUFFIX export OTA_CLIENT_VIN=${OTA_CLIENT_VIN-$RANDOM_VIN} -export HTTP_SESSION="/tmp/$OTA_CLIENT_VIN.json" -export OTA_WEB_USER="${OTA_WEB_USER-demo@advancedtelematic.com}" -export OTA_WEB_PASSWORD="${OTA_WEB_PASSWORD-demo}" - -#export OTA_CLIENT_VIN=STRESS12345678901 +# Get cookie +HTTP_SESSION="/tmp/$OTA_CLIENT_VIN.json" http --check-status --session=$HTTP_SESSION POST ${OTA_WEB_URL}/authenticate \ username=$OTA_WEB_USER password=$OTA_WEB_PASSWORD --ignore-stdin || [[ $? == 3 ]] +# Add VIN to ota-plus web echo "vin=${OTA_CLIENT_VIN}" | http --check-status --session=$HTTP_SESSION put "${OTA_WEB_URL}${VEHICLES_PATH}${OTA_CLIENT_VIN}" -AUTH_JSON_PATH=${AUTH_JSON_PATH-'/etc/auth.json'} + +# Get VIN credentials JSON=$(envsubst < $AUTH_JSON_PATH) AUTH_DATA=$(echo $JSON | http --check-status post $OTA_AUTH_URL$OTA_AUTH_PATH) - CLIENT_ID=$(echo $AUTH_DATA | jq -r .client_id) SECRET=$(echo $AUTH_DATA | jq -r .client_secret) -export OTA_CLIENT_VIN=$OTA_CLIENT_VIN -export OTA_AUTH_URL=$OTA_AUTH_URL -export OTA_CORE_URL=$OTA_CORE_URL export OTA_AUTH_CLIENT_ID=${OTA_AUTH_CLIENT_ID-$CLIENT_ID} export OTA_AUTH_SECRET=${OTA_AUTH_SECRET-$SECRET} -export PACKAGE_MANAGER=$PACKAGE_MANAGER -export OTA_HTTP=${OTA_HTTP-false} - -echo $OTA_CLIENT_VIN -echo $OTA_AUTH_URL -echo $OTA_CORE_URL -echo $OTA_AUTH_CLIENT_ID -echo $OTA_AUTH_SECRET -export $PACKAGE_MANAGER - -OUTPUT_PATH=${OUTPUT_PATH-/etc/ota.toml} - -while getopts ":p" opt; do - PROVISION='false' - case $opt in - p) - PROVISION='true' - ;; - esac -done -if [[ $PROVISION == 'true' ]] +if [[ -n $PROVISION ]] then OTA_TOML=$(cat $TEMPLATE_PATH | envsubst ) echo "$OTA_TOML" -- cgit v1.2.1 From 0cda75b8368f606852751923eb0eb937ce37a271 Mon Sep 17 00:00:00 2001 From: Alex Humphreys Date: Tue, 14 Jun 2016 11:38:08 +0200 Subject: Add PROVISION env to docs --- README.md | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index cceee3f..16d8fdd 100644 --- a/README.md +++ b/README.md @@ -48,11 +48,14 @@ You can configure it using the following environment variables: - `OTA_AUTH_URL`, default value: http://localhost:9001 - `OTA_WEB_URL`, default value: http://localhost:9000 - `OTA_CORE_URL`, default value: http://localhost:8080 -- `OTA_WEB_USER`, default value: demo@advancedtelematic.com -- `OTA_WEB_PASSWORD`, default value: demo +- `OTA_WEB_USER`, default value: `demo@advancedtelematic.com` +- `OTA_WEB_PASSWORD`, default value: `demo` - `OTA_CLIENT_VIN`, default value: Randomly generated - `OTA_AUTH_CLIENT_ID`, default value: Generated for VIN - `OTA_AUTH_SECRET`, default value: Generated for VIN +- `PACKAGE_MANAGER`, `dpkg` or `rpm`, default value: `dpkg` +- `OTA_HTTP`, default value: `false` +- `PROVISION`, default value: `false`. Set to `true` to output a configured `ota.toml` file to STDOUT then exit. Eg: -- cgit v1.2.1 From e36ef49c8f1b2241a356c0e091887674a4eff128 Mon Sep 17 00:00:00 2001 From: Shaun Taheri Date: Mon, 13 Jun 2016 16:05:03 +0200 Subject: Fix panic on startup --- Makefile | 6 ++- src/interpreter.rs | 138 +++++++++++++++++++++++++---------------------------- src/main.rs | 46 +++++++++--------- src/ota_plus.rs | 41 ++++++++-------- 4 files changed, 112 insertions(+), 119 deletions(-) diff --git a/Makefile b/Makefile index 22e7c06..bb305ca 100644 --- a/Makefile +++ b/Makefile @@ -1,11 +1,13 @@ MUSL=x86_64-unknown-linux-musl -.PHONY: all ota_plus_client deb rpm +.PHONY: all clean ota_plus_client deb rpm all: deb rpm -ota_plus_client: src/ +clean: cargo clean + +ota_plus_client: src/ cargo build --release --target=$(MUSL) cp target/$(MUSL)/release/ota_plus_client pkg/ diff --git a/src/interpreter.rs b/src/interpreter.rs index 6a417b1..08e899d 100644 --- a/src/interpreter.rs +++ b/src/interpreter.rs @@ -13,14 +13,13 @@ use ota_plus::OTA; pub trait Interpreter { - fn interpret(&mut self, msg: I, otx: Sender); + fn interpret(&mut self, msg: I, otx: &Sender); fn run(&mut self, irx: Receiver, otx: Sender) { loop { - match irx.recv() { - Ok(msg) => self.interpret(msg, otx.clone()), - Err(err) => error!("Error receiving command: {:?}", err), - } + let _ = irx.recv() + .map(|msg| self.interpret(msg, &otx)) + .map_err(|err| panic!("couldn't read interpreter input: {:?}", err)); } } } @@ -29,7 +28,7 @@ pub trait Interpreter { pub struct EventInterpreter; impl Interpreter for EventInterpreter { - fn interpret(&mut self, event: Event, ctx: Sender) { + fn interpret(&mut self, event: Event, ctx: &Sender) { info!("Event interpreter: {:?}", event); let _ = match event { Event::NotAuthenticated => { @@ -64,28 +63,63 @@ pub type Wrapped = Interpret; pub struct WrappedInterpreter<'t> { pub config: Config, pub access_token: Option>, + pub wtx: Sender, } -impl<'t> WrappedInterpreter<'t> { - fn interpret_command(&mut self, cmd: Command, etx: Sender) -> Result<(), Error> { - match self.access_token.to_owned() { - Some(token) => { - let client = AuthClient::new(Auth::Token(token.into_owned())); - self.authenticated(client, cmd, etx) +impl<'t> Interpreter for WrappedInterpreter<'t> { + fn interpret(&mut self, w: Wrapped, global_tx: &Sender) { + fn send_global(ev: Event, global_tx: &Sender) { + let _ = global_tx.send(ev).map_err(|err| panic!("couldn't send global response: {}", err)); + } + + fn send_local(ev: Event, local_tx: Option>>>) { + if let Some(local) = local_tx { + let _ = local.lock().unwrap().send(ev) + .map_err(|err| panic!("couldn't send local response: {}", err)); } + } - None => { - let client = AuthClient::new(Auth::Credentials( - ClientId(self.config.auth.client_id.clone()), - ClientSecret(self.config.auth.secret.clone()))); - self.unauthenticated(client, cmd, etx) + info!("Interpreting wrapped command: {:?}", w.cmd); + let (multi_tx, multi_rx): (Sender, Receiver) = channel(); + match match self.access_token.to_owned() { + Some(token) => self.authenticated(w.cmd.clone(), token.into_owned(), multi_tx), + None => self.unauthenticated(w.cmd.clone(), multi_tx) + }{ + Ok(_) => { + let mut last_ev = None; + for ev in multi_rx { + send_global(ev.clone(), &global_tx); + last_ev = Some(ev); + } + match last_ev { + Some(ev) => send_local(ev, w.etx), + None => panic!("no local event to send back") + }; + } + + Err(Error::AuthorizationError(_)) => { + debug!("retry authorization and request"); + let a = Wrapped { cmd: Command::Authenticate(None), etx: None }; + let _ = self.wtx.send(a).map_err(|err| panic!("couldn't retry authentication: {}", err)); + let _ = self.wtx.send(w).map_err(|err| panic!("couldn't retry request: {}", err)); + } + + Err(err) => { + let ev = Event::Error(format!("{}", err)); + send_global(ev.clone(), &global_tx); + send_local(ev, w.etx); } } } +} - fn authenticated(&mut self, client: AuthClient, cmd: Command, etx: Sender) -> Result<(), Error> { - let mut ota = OTA::new(&client, self.config.clone()); +impl<'t> WrappedInterpreter<'t> { + fn authenticated(&self, cmd: Command, token: AccessToken, etx: Sender) -> Result<(), Error> { + let client = AuthClient::new(Auth::Token(token)); + let mut ota = OTA::new(&self.config, &client); + + // always send at least one Event response match cmd { Authenticate(_) => try!(etx.send(Event::Ok)), @@ -98,12 +132,13 @@ impl<'t> WrappedInterpreter<'t> { GetPendingUpdates => { let mut updates = try!(ota.get_package_updates()); - if updates.len() > 0 { - updates.sort_by_key(|update| update.installPos); - info!("New package updates available: {:?}", updates); - for update in updates.iter() { - try!(etx.send(Event::NewUpdateAvailable(update.requestId.clone()))) - } + if updates.len() == 0 { + return Ok(try!(etx.send(Event::Ok))); + } + updates.sort_by_key(|update| update.installPos); + info!("New package updates available: {:?}", updates); + for update in updates.iter() { + try!(etx.send(Event::NewUpdateAvailable(update.requestId.clone()))) } } @@ -124,9 +159,12 @@ impl<'t> WrappedInterpreter<'t> { Ok(()) } - fn unauthenticated(&mut self, client: AuthClient, cmd: Command, etx: Sender) -> Result<(), Error> { + fn unauthenticated(&mut self, cmd: Command, etx: Sender) -> Result<(), Error> { match cmd { Authenticate(_) => { + let client = AuthClient::new(Auth::Credentials( + ClientId(self.config.auth.client_id.clone()), + ClientSecret(self.config.auth.secret.clone()))); let token = try!(authenticate(&self.config.auth, &client)); self.access_token = Some(token.into()); try!(etx.send(Event::Ok)); @@ -143,51 +181,3 @@ impl<'t> WrappedInterpreter<'t> { Ok(()) } } - -impl<'t> Interpreter for WrappedInterpreter<'t> { - fn interpret(&mut self, w: Wrapped, global_tx: Sender) { - info!("Wrapped interpreter: {:?}", w.cmd); - let (multi_tx, multi_rx): (Sender, Receiver) = channel(); - - let _ = match self.interpret_command(w.cmd.clone(), multi_tx) { - Ok(_) => { - let mut last_ev = None; - for ev in multi_rx { - last_ev = Some(ev.clone()); - send_global(ev, global_tx.clone()); - } - match last_ev { - Some(ev) => send_local(ev, w.etx), - None => panic!("no local event to send back") - } - } - - Err(Error::AuthorizationError(_)) => { - debug!("retry authorization and request"); - let auth = Wrapped { cmd: Command::Authenticate(None), etx: None }; - self.interpret(auth, global_tx.clone()); - self.interpret(w, global_tx); - } - - Err(err) => { - let ev = Event::Error(format!("{}", err)); - send_global(ev.clone(), global_tx); - send_local(ev, w.etx); - } - }; - } -} - -fn send_global(ev: Event, global_tx: Sender) { - let _ = global_tx.send(ev) - .map_err(|err| panic!("couldn't send global response: {}", err)); -} - -fn send_local(ev: Event, local_tx: Option>>>) { - if let Some(ref local) = local_tx { - let _ = local.lock() - .unwrap() - .send(ev) - .map_err(|err| panic!("couldn't send local response: {}", err)); - } -} diff --git a/src/main.rs b/src/main.rs index 6ed4ffb..039fc6a 100644 --- a/src/main.rs +++ b/src/main.rs @@ -39,25 +39,24 @@ fn spawn_signal_handler(signals: ChanReceiver, ctx: Sender) { } } -fn spawn_update_poller(ctx: Sender, config: Config) { +fn spawn_update_poller(ctx: Sender, interval: u64) { loop { let _ = ctx.send(Command::GetPendingUpdates); - thread::sleep(Duration::from_secs(config.ota.polling_interval)) + thread::sleep(Duration::from_secs(interval)); } } fn spawn_command_forwarder(crx: Receiver, wtx: Sender) { loop { - match crx.recv() { - Ok(cmd) => wtx.send(Interpret { cmd: cmd, etx: None }).unwrap(), - Err(err) => error!("Error receiving command to forward: {:?}", err), - } + let _ = crx.recv() + .map(|cmd| wtx.send(Interpret { cmd: cmd, etx: None }).unwrap()) + .map_err(|err| panic!("couldn't receive command to forward: {:?}", err)); } } -fn perform_initial_sync(ctx: Sender) { - let _ = ctx.clone().send(Command::Authenticate(None)); - let _ = ctx.clone().send(Command::UpdateInstalledPackages); +fn perform_initial_sync(ctx: &Sender) { + let _ = ctx.send(Command::Authenticate(None)); + let _ = ctx.send(Command::UpdateInstalledPackages); } fn main() { @@ -67,7 +66,9 @@ fn main() { let (ctx, crx): (Sender, Receiver) = channel(); let (etx, erx): (Sender, Receiver) = channel(); let (wtx, wrx): (Sender, Receiver) = channel(); + let mut broadcast: Broadcast = Broadcast::new(erx); + perform_initial_sync(&ctx); crossbeam::scope(|scope| { // Must subscribe to the signal before spawning ANY other threads @@ -75,23 +76,24 @@ fn main() { let sig_ctx = ctx.clone(); scope.spawn(move || spawn_signal_handler(signals, sig_ctx)); - let sync_ctx = ctx.clone(); - scope.spawn(move || perform_initial_sync(sync_ctx)); - - let poll_ctx = ctx.clone(); - let poll_cfg = config.clone(); - scope.spawn(move || spawn_update_poller(poll_ctx, poll_cfg)); + let poll_ctx = ctx.clone(); + let poll_interval = config.ota.polling_interval; + scope.spawn(move || spawn_update_poller(poll_ctx, poll_interval)); let cmd_wtx = wtx.clone(); scope.spawn(move || spawn_command_forwarder(crx, cmd_wtx)); - let ev_sub = broadcast.subscribe(); - let ev_ctx = ctx.clone(); - let mut ev_int = EventInterpreter; - scope.spawn(move || ev_int.run(ev_sub, ev_ctx)); - - let mut w_int = WrappedInterpreter { config: config.clone(), access_token: None }; - scope.spawn(move || w_int.run(wrx, etx)); + let event_sub = broadcast.subscribe(); + let event_ctx = ctx.clone(); + let mut event_int = EventInterpreter; + scope.spawn(move || event_int.run(event_sub, event_ctx)); + + let mut wrapped_int = WrappedInterpreter { + config: config.clone(), + access_token: None, + wtx: wtx.clone(), + }; + scope.spawn(move || wrapped_int.run(wrx, etx)); let ws_wtx = wtx.clone(); let ws_sub = broadcast.subscribe(); diff --git a/src/ota_plus.rs b/src/ota_plus.rs index fc296a7..22f8aa0 100644 --- a/src/ota_plus.rs +++ b/src/ota_plus.rs @@ -10,14 +10,14 @@ use datatype::{Config, Error, Event, Method, PendingUpdateRequest, use http_client::{HttpClient, HttpRequest}; -pub struct OTA<'h> { +pub struct OTA<'c, 'h> { + config: &'c Config, client: &'h HttpClient, - config: Config, } -impl<'h> OTA<'h> { - pub fn new(client: &HttpClient, config: Config) -> OTA { - OTA { client: client, config: config } +impl<'c, 'h> OTA<'c, 'h> { + pub fn new(config: &'c Config, client: &'h HttpClient) -> OTA<'c, 'h> { + OTA { config: config, client: client } } pub fn update_endpoint(&self, path: &str) -> Url { @@ -153,13 +153,6 @@ mod tests { use package_manager::PackageManager; - fn new_test_ota(client: &TestHttpClient) -> OTA { - OTA { - client: client, - config: Config::default(), - } - } - #[test] fn test_get_package_updates() { let pending_update = PendingUpdateRequest { @@ -172,9 +165,11 @@ mod tests { createdAt: "2010-01-01".to_string() }; - let json = format!("[{}]", json::encode(&pending_update).unwrap()); - let mut client = TestHttpClient::from(vec![json.to_string()]); - let mut ota = new_test_ota(&mut client); + let json = format!("[{}]", json::encode(&pending_update).unwrap()); + let mut ota = OTA { + config: &Config::default(), + client: &mut TestHttpClient::from(vec![json.to_string()]), + }; let updates: Vec = ota.get_package_updates().unwrap(); let ids: Vec = updates.iter().map(|p| p.requestId.clone()).collect(); @@ -183,8 +178,10 @@ mod tests { #[test] fn bad_client_download_package_update() { - let client = TestHttpClient::new(); - let mut ota = new_test_ota(&client); + let mut ota = OTA { + config: &Config::default(), + client: &mut TestHttpClient::new(), + }; let expect = "Http client error: http://127.0.0.1:8080/api/v1/vehicle_updates/V1234567890123456/0/download"; assert_eq!(expect, format!("{}", ota.download_package_update(&"0".to_string()).unwrap_err())); } @@ -205,8 +202,10 @@ mod tests { #[test] fn test_install_package_update_0() { - let client = TestHttpClient::new(); - let mut ota = new_test_ota(&client); + let mut ota = OTA { + config: &Config::default(), + client: &mut TestHttpClient::new(), + }; let (tx, rx) = channel(); let report = ota.install_package_update(&"0".to_string(), &tx); assert_eq!(report.unwrap().operation_results.pop().unwrap().result_code, @@ -228,8 +227,8 @@ mod tests { }; let mut ota = OTA { + config: &config, client: &mut TestHttpClient::from(vec!["".to_string()]), - config: config }; let (tx, rx) = channel(); let report = ota.install_package_update(&"0".to_string(), &tx); @@ -257,8 +256,8 @@ mod tests { "package data".to_string(), ]; let mut ota = OTA { + config: &config, client: &mut TestHttpClient::from(replies), - config: config }; let (tx, rx) = channel(); let report = ota.install_package_update(&"0".to_string(), &tx); -- cgit v1.2.1 From 3abbd9ccc9d376636c58f43c56df5fb463edf773 Mon Sep 17 00:00:00 2001 From: Shaun Taheri Date: Wed, 15 Jun 2016 12:13:38 +0200 Subject: Set ContentType Header on HTTP Server 200 Response --- src/interaction_library/http.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/interaction_library/http.rs b/src/interaction_library/http.rs index 29408c6..6575710 100644 --- a/src/interaction_library/http.rs +++ b/src/interaction_library/http.rs @@ -1,4 +1,6 @@ use hyper::{Decoder, Encoder, Next, StatusCode}; +use hyper::header::ContentType; +use hyper::mime::{Attr, Mime, TopLevel, SubLevel, Value}; use hyper::net::HttpStream; use hyper::server::{Handler, Server, Request, Response}; use rustc_serialize::{json, Decodable, Encodable}; @@ -145,6 +147,8 @@ impl Handler for HttpHandler Ok(e) => match json::encode(&e) { Ok(body) => { resp.set_status(StatusCode::Ok); + resp.headers_mut().set(ContentType(Mime(TopLevel::Application, SubLevel::Json, + vec![(Attr::Charset, Value::Utf8)]))); self.resp_body = Some(body.into_bytes()); Next::write() } -- cgit v1.2.1 From 60103f525cfa904b7eb7ac0a33f8996bd8905260 Mon Sep 17 00:00:00 2001 From: Jerry Trieu Date: Thu, 16 Jun 2016 10:57:53 +0200 Subject: Send update-report before installed-report in interpreter --- src/interpreter.rs | 3 ++- src/ota_plus.rs | 7 +++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/interpreter.rs b/src/interpreter.rs index 08e899d..9a76912 100644 --- a/src/interpreter.rs +++ b/src/interpreter.rs @@ -127,7 +127,8 @@ impl<'t> WrappedInterpreter<'t> { try!(etx.send(Event::UpdateStateChanged(id.clone(), UpdateState::Downloading))); let report = try!(ota.install_package_update(&id, &etx)); try!(ota.send_install_report(&report)); - info!("Update finished. Report sent: {:?}", report) + info!("Update finished. Report sent: {:?}", report); + try!(ota.update_installed_packages()) } GetPendingUpdates => { diff --git a/src/ota_plus.rs b/src/ota_plus.rs index 22f8aa0..6d38da1 100644 --- a/src/ota_plus.rs +++ b/src/ota_plus.rs @@ -79,17 +79,16 @@ impl<'c, 'h> OTA<'c, 'h> { // TODO: Fire DownloadComplete event, handle async UpdateReport command // TODO: Do not invoke package_manager - try!(etx.send(Event::UpdateStateChanged(id.clone(), UpdateState::Installing))); + let _ = etx.send(Event::UpdateStateChanged(id.clone(), UpdateState::Installing)); match self.config.ota.package_manager.install_package(pkg_path) { Ok((code, output)) => { - try!(etx.send(Event::UpdateStateChanged(id.clone(), UpdateState::Installed))); - try!(self.update_installed_packages()); + let _ = etx.send(Event::UpdateStateChanged(id.clone(), UpdateState::Installed)); Ok(UpdateReport::new(id.clone(), code, output)) } Err((code, output)) => { let err_str = format!("{:?}: {:?}", code, output); - try!(etx.send(Event::UpdateErrored(id.clone(), err_str))); + let _ = etx.send(Event::UpdateErrored(id.clone(), err_str)); Ok(UpdateReport::new(id.clone(), code, output)) } } -- cgit v1.2.1 From ea0da77e84bd6a42bfa4ff18072554829baec286 Mon Sep 17 00:00:00 2001 From: Jon Oster Date: Fri, 17 Jun 2016 14:25:59 +0200 Subject: Add HTTP configuration steps to the README --- README.md | 19 +++++++------------ 1 file changed, 7 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index 8e2d766..ba2d7ef 100644 --- a/README.md +++ b/README.md @@ -4,26 +4,21 @@ This is the client (in-vehicle) portion of the SOTA project. See the [main SOTA ## Building and running -To see the SOTA client in action, you will need to first build and run the [SOTA Server](https://github.com/advancedtelematic/rvi_sota_server). +To see the SOTA client in action, you will need to first build and run the [SOTA Core Server](https://github.com/advancedtelematic/rvi_sota_server). ### Building SOTA Client As long as you have `rust 1.8.0` and `cargo` installed, `cargo build` should build the `sota_client` executable in `target/debug`. -You can also build the SOTA client from within a docker container; this will be necessary if your build environment is not running linux. From the project root, run `docker run -it --rm -v $PWD:/build advancedtelematic/rust:1.2.0 /bin/bash`. Once you are at a bash prompt, run the following commands: - -``` -apt-get install -y libssl-dev -cd /build -cargo build --release -exit -``` - ### Running SOTA Client You can run the client with `target/debug/sota_client -c client.toml`. It will try to connect to the `core` service of `rvi_sota_server` specified in the `[server]` section of `client.toml`. If the `[server.auth]` section contains `client_id`, `client_secret` and `url`, it will first try to obtain an OAuth access token from `url` and then authenticate all the requests to the server with it. -### Running with RVI nodes +#### Running with HTTP communication + +HTTP tends to be much easier to get working than RVI. Enable HTTP by setting `http = “true”` in the [client] section of `client.toml`, and setting the url value in the [server] section to the url of your sota-core deployment. + +#### Running with RVI nodes To connect to the SOTA Server over RVI, run the `rvi_sota_server` project with RVI Nodes. @@ -34,7 +29,7 @@ You can build RVI directly from [its GitHub repo](https://github.com/GENIVI/rvi_ * Client: `docker run -it --name rvi-client --expose 8901 --expose 8905-8908 -p 8901:8901 -p 8905:8905 -p 8906:8906 -p 8907:8907 -p 8908:8908 advancedtelematic/rvi client` * Server: `docker run -it --name rvi-server --expose 8801 --expose 8805-8808 -p 8801:8801 -p 8805:8805 -p 8806:8806 -p 8807:8807 -p 8808:8808 advancedtelematic/rvi server` -Now you can remove the `[server]` section from `client.toml`. +Now you can remove the `[server]` section from `client.toml` and disable http. ### Running with GENIVI Software Loading Manager -- cgit v1.2.1 From 9f56304fc2b3fdbacf5fcea971ee93d61a0d7e32 Mon Sep 17 00:00:00 2001 From: Shaun Taheri Date: Wed, 15 Jun 2016 00:41:57 +0200 Subject: Variable arg support for AcceptUpdate Command --- .gitignore | 2 +- src/datatype/command.rs | 144 +++++++----------- src/datatype/error.rs | 9 ++ src/datatype/event.rs | 2 +- src/http_client/http_client.rs | 2 + src/http_client/test_client.rs | 2 + src/interaction_library/broadcast.rs | 2 +- src/interaction_library/gateway.rs | 14 +- src/interaction_library/http.rs | 6 +- src/interpreter.rs | 260 +++++++++++++++++++++++---------- src/main.rs | 48 +++--- src/ota_plus.rs | 36 +---- src/package_manager/mod.rs | 1 + src/package_manager/package_manager.rs | 21 ++- src/package_manager/tpm.rs | 41 +++--- 15 files changed, 326 insertions(+), 264 deletions(-) diff --git a/.gitignore b/.gitignore index e140a79..58dd422 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,3 @@ target pkg/ota_plus_client -test_install_package_update_2 +.tmp* diff --git a/src/datatype/command.rs b/src/datatype/command.rs index e4ad023..3348c76 100644 --- a/src/datatype/command.rs +++ b/src/datatype/command.rs @@ -7,7 +7,7 @@ use datatype::{ClientCredentials, ClientId, ClientSecret, Error, UpdateRequestId #[derive(RustcDecodable, RustcEncodable, PartialEq, Eq, Debug, Clone)] pub enum Command { - AcceptUpdate(UpdateRequestId), + AcceptUpdates(Vec), /* Add: UpdateReport, InstalledSoftware, // or reuse UpdateInstalledPackages @@ -33,34 +33,18 @@ impl FromStr for Command { named!(command <(Command, Vec<&str>)>, chain!( space? ~ cmd: alt!( - alt_complete!(tag!("Authenticate") - | tag!("authenticate") - | tag!("auth") - ) => { |_| Command::Authenticate(None) } - - | alt_complete!(tag!("GetPendingUpdates") - | tag!("getPendingUpdates") - | tag!("pen") - ) => { |_| Command::GetPendingUpdates } - - | alt_complete!(tag!("AcceptUpdate") - | tag!("acceptUpdate") - | tag!("acc") - ) => { |_| Command::AcceptUpdate("".to_owned()) } - - | alt_complete!(tag!("ListInstalledPackages") - | tag!("listInstalledPackages") - | tag!("ls") - ) => { |_| Command::ListInstalledPackages } - - | alt_complete!(tag!("Shutdown") - | tag!("shutdown") - ) => { |_| Command::Shutdown } - - | alt_complete!(tag!("UpdateInstalledPackages") - | tag!("updateInstalledPackages") - | tag!("up") - ) => { |_| Command::UpdateInstalledPackages } + alt_complete!(tag!("AcceptUpdate") | tag!("acc")) + => { |_| Command::AcceptUpdates(Vec::new()) } + | alt_complete!(tag!("Authenticate") | tag!("auth")) + => { |_| Command::Authenticate(None) } + | alt_complete!(tag!("GetPendingUpdates") | tag!("pen")) + => { |_| Command::GetPendingUpdates } + | alt_complete!(tag!("ListInstalledPackages") | tag!("ls")) + => { |_| Command::ListInstalledPackages } + | alt_complete!(tag!("Shutdown") | tag!("shutdown")) + => { |_| Command::Shutdown } + | alt_complete!(tag!("UpdateInstalledPackages") | tag!("up")) + => { |_| Command::UpdateInstalledPackages } ) ~ args: arguments ~ alt!(eof | tag!("\r") | tag!("\n") | tag!(";")), @@ -83,56 +67,39 @@ named!(arguments <&[u8], Vec<&str> >, chain!( fn parse_arguments(cmd: Command, args: Vec<&str>) -> Result { match cmd { - Command::AcceptUpdate(_) => { - match args.len() { - 0 => Err(Error::Command("usage: acc ".to_owned())), - 1 => Ok(Command::AcceptUpdate(args[0].to_owned())), - _ => Err(Error::Command(format!("unexpected acc args: {:?}", args))), - } - } - - Command::Authenticate(_) => { - match args.len() { - 0 => Ok(Command::Authenticate(None)), - 1 => Err(Error::Command("usage: auth ".to_owned())), - 2 => { - let (user, pass) = (args[0].to_owned(), args[1].to_owned()); - Ok(Command::Authenticate(Some(ClientCredentials { - id: ClientId(user), - secret: ClientSecret(pass) - }))) - } - _ => Err(Error::Command(format!("unexpected auth args: {:?}", args))), - } - } - - Command::GetPendingUpdates => { - match args.len() { - 0 => Ok(Command::GetPendingUpdates), - _ => Err(Error::Command(format!("unexpected pen args: {:?}", args))), - } - } - - Command::ListInstalledPackages => { - match args.len() { - 0 => Ok(Command::ListInstalledPackages), - _ => Err(Error::Command(format!("unexpected ls args: {:?}", args))), - } - } - - Command::Shutdown => { - match args.len() { - 0 => Ok(Command::Shutdown), - _ => Err(Error::Command(format!("unexpected shutdown args: {:?}", args))), - } - } - - Command::UpdateInstalledPackages => { - match args.len() { - 0 => Ok(Command::UpdateInstalledPackages), - _ => Err(Error::Command(format!("unexpected up args: {:?}", args))), - } - } + Command::AcceptUpdates(_) => match args.len() { + 0 => Err(Error::Command("usage: acc []".to_string())), + _ => Ok(Command::AcceptUpdates(args.iter().map(|arg| String::from(*arg)).collect())), + }, + + Command::Authenticate(_) => match args.len() { + 0 => Ok(Command::Authenticate(None)), + 1 => Err(Error::Command("usage: auth ".to_string())), + 2 => Ok(Command::Authenticate(Some(ClientCredentials { + id: ClientId(args[0].to_string()), + secret: ClientSecret(args[1].to_string())}))), + _ => Err(Error::Command(format!("unexpected auth args: {:?}", args))), + }, + + Command::GetPendingUpdates => match args.len() { + 0 => Ok(Command::GetPendingUpdates), + _ => Err(Error::Command(format!("unexpected pen args: {:?}", args))), + }, + + Command::ListInstalledPackages => match args.len() { + 0 => Ok(Command::ListInstalledPackages), + _ => Err(Error::Command(format!("unexpected ls args: {:?}", args))), + }, + + Command::Shutdown => match args.len() { + 0 => Ok(Command::Shutdown), + _ => Err(Error::Command(format!("unexpected shutdown args: {:?}", args))), + }, + + Command::UpdateInstalledPackages => match args.len() { + 0 => Ok(Command::UpdateInstalledPackages), + _ => Err(Error::Command(format!("unexpected up args: {:?}", args))), + }, } } @@ -143,12 +110,13 @@ mod tests { use datatype::{Command, ClientCredentials, ClientId, ClientSecret}; use nom::IResult; + #[test] fn parse_command_test() { assert_eq!(command(&b"auth foo bar"[..]), IResult::Done(&b""[..], (Command::Authenticate(None), vec!["foo", "bar"]))); assert_eq!(command(&b"acc 1"[..]), - IResult::Done(&b""[..], (Command::AcceptUpdate("".to_owned()), vec!["1"]))); + IResult::Done(&b""[..], (Command::AcceptUpdates(Vec::new()), vec!["1"]))); assert_eq!(command(&b"ls;\n"[..]), IResult::Done(&b"\n"[..], (Command::ListInstalledPackages, Vec::new()))); } @@ -165,23 +133,20 @@ mod tests { #[test] fn accept_update_test() { - assert_eq!("acc 1".parse::().unwrap(), Command::AcceptUpdate("1".to_owned())); - assert_eq!("acceptUpdate 2".parse::().unwrap(), Command::AcceptUpdate("2".to_owned())); - assert_eq!("AcceptUpdate 3".parse::().unwrap(), Command::AcceptUpdate("3".to_owned())); - assert_eq!("acc some".parse::().unwrap(), Command::AcceptUpdate("some".to_owned())); + assert_eq!("acc 1".parse::().unwrap(), Command::AcceptUpdates(vec!["1".to_string()])); + assert_eq!("AcceptUpdate this".parse::().unwrap(), Command::AcceptUpdates(vec!["this".to_string()])); + assert_eq!("acc some more".parse::().unwrap(), Command::AcceptUpdates(vec!["some".to_string(), "more".to_string()])); assert!("acc".parse::().is_err()); - assert!("acc more than one".parse::().is_err()); } #[test] fn authenticate_test() { assert_eq!("auth".parse::().unwrap(), Command::Authenticate(None)); - assert_eq!("authenticate".parse::().unwrap(), Command::Authenticate(None)); assert_eq!("Authenticate".parse::().unwrap(), Command::Authenticate(None)); assert_eq!("auth user pass".parse::().unwrap(), Command::Authenticate(Some(ClientCredentials { - id: ClientId("user".to_owned()), - secret: ClientSecret("pass".to_owned()), + id: ClientId("user".to_string()), + secret: ClientSecret("pass".to_string()), }))); assert!("auth one".parse::().is_err()); assert!("auth one two three".parse::().is_err()); @@ -190,7 +155,6 @@ mod tests { #[test] fn get_pending_updates_test() { assert_eq!("pen".parse::().unwrap(), Command::GetPendingUpdates); - assert_eq!("getPendingUpdates".parse::().unwrap(), Command::GetPendingUpdates); assert_eq!("GetPendingUpdates".parse::().unwrap(), Command::GetPendingUpdates); assert!("pen some".parse::().is_err()); } @@ -198,7 +162,6 @@ mod tests { #[test] fn list_installed_test() { assert_eq!("ls".parse::().unwrap(), Command::ListInstalledPackages); - assert_eq!("listInstalledPackages".parse::().unwrap(), Command::ListInstalledPackages); assert_eq!("ListInstalledPackages".parse::().unwrap(), Command::ListInstalledPackages); assert!("ls some".parse::().is_err()); } @@ -214,7 +177,6 @@ mod tests { #[test] fn update_installed_test() { assert_eq!("up".parse::().unwrap(), Command::UpdateInstalledPackages); - assert_eq!("updateInstalledPackages".parse::().unwrap(), Command::UpdateInstalledPackages); assert_eq!("UpdateInstalledPackages".parse::().unwrap(), Command::UpdateInstalledPackages); assert!("up down".parse::().is_err()); } diff --git a/src/datatype/error.rs b/src/datatype/error.rs index 388ee9f..4ad3307 100644 --- a/src/datatype/error.rs +++ b/src/datatype/error.rs @@ -13,6 +13,7 @@ use datatype::Event; use rustc_serialize::json::{EncoderError as JsonEncoderError, DecoderError as JsonDecoderError}; use ws::Error as WebsocketError; use super::super::http_client::auth_client::AuthHandler; +use super::super::interpreter::Wrapped; #[derive(Debug)] @@ -31,6 +32,7 @@ pub enum Error { ParseError(String), RecvError(RecvError), SendErrorEvent(SendError), + SendErrorWrapped(SendError), TomlParserErrors(Vec), TomlDecodeError(TomlDecodeError), UrlParseError(UrlParseError), @@ -43,6 +45,12 @@ impl From> for Error { } } +impl From> for Error { + fn from(e: SendError) -> Error { + Error::SendErrorWrapped(e) + } +} + impl From for Error { fn from(e: RecvError) -> Error { Error::RecvError(e) @@ -111,6 +119,7 @@ impl Display for Error { Error::ParseError(ref s) => s.clone(), Error::RecvError(ref s) => format!("Recv error: {}", s.clone()), Error::SendErrorEvent(ref s) => format!("Send error for Event: {}", s.clone()), + Error::SendErrorWrapped(ref s) => format!("Send error for Wrapped: {}", s.clone()), Error::TomlDecodeError(ref e) => format!("Toml decode error: {}", e.clone()), Error::TomlParserErrors(ref e) => format!("Toml parser errors: {:?}", e.clone()), Error::UrlParseError(ref s) => format!("Url parse error: {}", s.clone()), diff --git a/src/datatype/event.rs b/src/datatype/event.rs index ac36697..3b7e7a8 100644 --- a/src/datatype/event.rs +++ b/src/datatype/event.rs @@ -6,8 +6,8 @@ use datatype::{UpdateRequestId, UpdateState, Package}; #[derive(RustcEncodable, RustcDecodable, Debug, Clone, PartialEq, Eq)] pub enum Event { Ok, + Authenticated, NotAuthenticated, - NewUpdateAvailable(UpdateRequestId), /* TODO: Add: DownloadComplete(UpdateRequestId), GetInstalledSoftware, diff --git a/src/http_client/http_client.rs b/src/http_client/http_client.rs index 938f5c0..a2039bd 100644 --- a/src/http_client/http_client.rs +++ b/src/http_client/http_client.rs @@ -11,6 +11,8 @@ pub trait HttpClient { } fn chan_request(&self, req: HttpRequest, resp_tx: Sender); + + fn is_testing(&self) -> bool { false } } #[derive(Debug)] diff --git a/src/http_client/test_client.rs b/src/http_client/test_client.rs index 7974144..a2a3017 100644 --- a/src/http_client/test_client.rs +++ b/src/http_client/test_client.rs @@ -26,4 +26,6 @@ impl HttpClient for TestHttpClient { None => resp_tx.send(Err(Error::ClientError(req.url.to_string()))) }.map_err(|err| error!("couldn't send test chan_request response: {}", err)); } + + fn is_testing(&self) -> bool { true } } diff --git a/src/interaction_library/broadcast.rs b/src/interaction_library/broadcast.rs index 3c712bf..b35e3a9 100644 --- a/src/interaction_library/broadcast.rs +++ b/src/interaction_library/broadcast.rs @@ -23,7 +23,7 @@ impl Broadcast { } } }, - Err(e) => error!("Error receiving: {}", e) + Err(e) => trace!("Error receiving: {}", e) } } } diff --git a/src/interaction_library/gateway.rs b/src/interaction_library/gateway.rs index 3ecdaf6..ce58886 100644 --- a/src/interaction_library/gateway.rs +++ b/src/interaction_library/gateway.rs @@ -22,20 +22,16 @@ pub trait Gateway: Sized + Send + Sync + 'static thread::spawn(move || { loop { - gateway.next() - .map(|i| { - tx.send(i) - .map_err(|err| error!("Error sending command: {:?}", err)) - }); + gateway.next().map(|i| { + tx.send(i).map_err(|err| error!("Error sending command: {:?}", err)) + }); } }); thread::spawn(move || { loop { - match rx.recv() { - Ok(e) => global.pulse(e), - Err(err) => error!("Error receiving event: {:?}", err), - } + let _ = rx.recv().map(|e| global.pulse(e)) + .map_err(|err| trace!("Error receiving event: {:?}", err)); } }); } diff --git a/src/interaction_library/http.rs b/src/interaction_library/http.rs index 6575710..f506fa0 100644 --- a/src/interaction_library/http.rs +++ b/src/interaction_library/http.rs @@ -221,8 +221,8 @@ mod tests { loop { let w = wrx.recv().unwrap(); match w.cmd { - Command::AcceptUpdate(id) => { - let ev = Event::Error(id); + Command::AcceptUpdates(ids) => { + let ev = Event::Error(ids.first().unwrap().to_owned()); match w.etx { Some(etx) => etx.lock().unwrap().send(ev).unwrap(), None => panic!("expected transmitter"), @@ -238,7 +238,7 @@ mod tests { for id in 0..10 { scope.spawn(move || { let client = AuthClient::new(Auth::None); - let cmd = Command::AcceptUpdate(format!("{}", id)); + let cmd = Command::AcceptUpdates(vec!(format!("{}", id))); let req_body = json::encode(&cmd).unwrap(); let req = HttpRequest { diff --git a/src/interpreter.rs b/src/interpreter.rs index 9a76912..00e5adc 100644 --- a/src/interpreter.rs +++ b/src/interpreter.rs @@ -1,12 +1,11 @@ use std::borrow::Cow; use std::process::exit; -use std::sync::{Arc, Mutex}; use std::sync::mpsc::{Sender, Receiver, channel}; use datatype::{AccessToken, Auth, ClientId, ClientSecret, Command, Config, - Error, Event, UpdateState}; + Error, Event, UpdateState, UpdateRequestId}; use datatype::Command::*; -use http_client::AuthClient; +use http_client::{AuthClient, HttpClient}; use interaction_library::gateway::Interpret; use oauth2::authenticate; use ota_plus::OTA; @@ -36,10 +35,6 @@ impl Interpreter for EventInterpreter { ctx.send(Command::Authenticate(None)) } - Event::NewUpdateAvailable(ref id) => { - ctx.send(Command::AcceptUpdate(id.clone())) - } - /* TODO: Handle PackageManger events Event::DownloadComplete => { env.config.ota.package_manager.install_package(p); @@ -58,89 +53,64 @@ impl Interpreter for EventInterpreter { } -pub type Wrapped = Interpret; +pub struct CommandInterpreter; -pub struct WrappedInterpreter<'t> { - pub config: Config, - pub access_token: Option>, - pub wtx: Sender, +impl Interpreter for CommandInterpreter { + fn interpret(&mut self, cmd: Command, wtx: &Sender) { + info!("Command interpreter: {:?}", cmd); + let _ = wtx.send(Wrapped { cmd: cmd, etx: None }) + .map_err(|err| panic!("couldn't forward command: {}", err)); + } } -impl<'t> Interpreter for WrappedInterpreter<'t> { - fn interpret(&mut self, w: Wrapped, global_tx: &Sender) { - fn send_global(ev: Event, global_tx: &Sender) { - let _ = global_tx.send(ev).map_err(|err| panic!("couldn't send global response: {}", err)); - } - - fn send_local(ev: Event, local_tx: Option>>>) { - if let Some(local) = local_tx { - let _ = local.lock().unwrap().send(ev) - .map_err(|err| panic!("couldn't send local response: {}", err)); - } - } - - info!("Interpreting wrapped command: {:?}", w.cmd); - let (multi_tx, multi_rx): (Sender, Receiver) = channel(); - match match self.access_token.to_owned() { - Some(token) => self.authenticated(w.cmd.clone(), token.into_owned(), multi_tx), - None => self.unauthenticated(w.cmd.clone(), multi_tx) - }{ - Ok(_) => { - let mut last_ev = None; - for ev in multi_rx { - send_global(ev.clone(), &global_tx); - last_ev = Some(ev); - } - match last_ev { - Some(ev) => send_local(ev, w.etx), - None => panic!("no local event to send back") - }; - } - Err(Error::AuthorizationError(_)) => { - debug!("retry authorization and request"); - let a = Wrapped { cmd: Command::Authenticate(None), etx: None }; - let _ = self.wtx.send(a).map_err(|err| panic!("couldn't retry authentication: {}", err)); - let _ = self.wtx.send(w).map_err(|err| panic!("couldn't retry request: {}", err)); - } +pub type Wrapped = Interpret; - Err(err) => { - let ev = Event::Error(format!("{}", err)); - send_global(ev.clone(), &global_tx); - send_local(ev, w.etx); - } +impl Wrapped { + fn publish(&self, ev: Event) { + if let Some(ref etx) = self.etx { + let _ = etx.lock().unwrap().send(ev).map_err(|err| panic!("couldn't publish event: {}", err)); } } } +pub struct WrappedInterpreter<'t> { + pub config: Config, + pub token: Option>, + pub client: Box, + pub loopback: Sender, +} + impl<'t> WrappedInterpreter<'t> { - fn authenticated(&self, cmd: Command, token: AccessToken, etx: Sender) -> Result<(), Error> { - let client = AuthClient::new(Auth::Token(token)); - let mut ota = OTA::new(&self.config, &client); + fn authenticated(&self, cmd: Command, etx: Sender) -> Result<(), Error> { + let mut ota = OTA::new(&self.config, self.client.as_ref()); // always send at least one Event response match cmd { - Authenticate(_) => try!(etx.send(Event::Ok)), - - AcceptUpdate(ref id) => { - try!(etx.send(Event::UpdateStateChanged(id.clone(), UpdateState::Downloading))); - let report = try!(ota.install_package_update(&id, &etx)); - try!(ota.send_install_report(&report)); - info!("Update finished. Report sent: {:?}", report); - try!(ota.update_installed_packages()) + AcceptUpdates(ids) => { + for id in ids { + info!("Accepting id {}", id); + try!(etx.send(Event::UpdateStateChanged(id.clone(), UpdateState::Downloading))); + let report = try!(ota.install_package_update(&id, &etx)); + try!(ota.send_install_report(&report)); + info!("Install Report for {}: {:?}", id, report); + try!(ota.update_installed_packages()) + } } + Authenticate(_) => try!(etx.send(Event::Ok)), + GetPendingUpdates => { let mut updates = try!(ota.get_package_updates()); - if updates.len() == 0 { - return Ok(try!(etx.send(Event::Ok))); - } - updates.sort_by_key(|update| update.installPos); - info!("New package updates available: {:?}", updates); - for update in updates.iter() { - try!(etx.send(Event::NewUpdateAvailable(update.requestId.clone()))) + if updates.len() > 0 { + updates.sort_by_key(|u| u.installPos); + info!("New package updates available: {:?}", updates); + let ids: Vec = updates.iter().map(|u| u.requestId.clone()).collect(); + let w = Wrapped { cmd: Command::AcceptUpdates(ids), etx: None }; + try!(self.loopback.send(w)) } + try!(etx.send(Event::Ok)); } ListInstalledPackages => { @@ -148,13 +118,13 @@ impl<'t> WrappedInterpreter<'t> { try!(etx.send(Event::FoundInstalledPackages(pkgs))) } + Shutdown => exit(0), + UpdateInstalledPackages => { try!(ota.update_installed_packages()); try!(etx.send(Event::Ok)); info!("Posted installed packages to the server.") } - - Shutdown => exit(0), } Ok(()) @@ -163,15 +133,12 @@ impl<'t> WrappedInterpreter<'t> { fn unauthenticated(&mut self, cmd: Command, etx: Sender) -> Result<(), Error> { match cmd { Authenticate(_) => { - let client = AuthClient::new(Auth::Credentials( - ClientId(self.config.auth.client_id.clone()), - ClientSecret(self.config.auth.secret.clone()))); - let token = try!(authenticate(&self.config.auth, &client)); - self.access_token = Some(token.into()); - try!(etx.send(Event::Ok)); + let token = try!(authenticate(&self.config.auth, self.client.as_ref())); + self.token = Some(token.into()); + try!(etx.send(Event::Authenticated)); } - AcceptUpdate(_) | + AcceptUpdates(_) | GetPendingUpdates | ListInstalledPackages | UpdateInstalledPackages => try!(etx.send(Event::NotAuthenticated)), @@ -182,3 +149,136 @@ impl<'t> WrappedInterpreter<'t> { Ok(()) } } + +impl<'t> Interpreter for WrappedInterpreter<'t> { + fn interpret(&mut self, w: Wrapped, global_tx: &Sender) { + info!("Wrapped interpreter: {:?}", w.cmd); + let broadcast = |ev: Event| { + let _ = global_tx.send(ev).map_err(|err| panic!("couldn't broadcast event: {}", err)); + }; + + let (etx, erx): (Sender, Receiver) = channel(); + let outcome = match self.token.to_owned() { + Some(token) => { + if !self.client.is_testing() { + self.client = Box::new(AuthClient::new(Auth::Token(token.into_owned()))); + } + self.authenticated(w.cmd.clone(), etx) + } + + None => { + if !self.client.is_testing() { + self.client = Box::new(AuthClient::new(Auth::Credentials( + ClientId(self.config.auth.client_id.clone()), + ClientSecret(self.config.auth.secret.clone())))); + } + self.unauthenticated(w.cmd.clone(), etx) + } + }; + + match outcome { + Ok(_) => { + let mut last_ev = None; + for ev in erx { + broadcast(ev.clone()); + last_ev = Some(ev); + } + match last_ev { + Some(ev) => w.publish(ev), + None => panic!("no local event to send back") + }; + } + + Err(Error::AuthorizationError(_)) => { + debug!("retry authorization and request"); + let a = Wrapped { cmd: Command::Authenticate(None), etx: None }; + let _ = self.loopback.send(a).map_err(|err| panic!("couldn't retry authentication: {}", err)); + let _ = self.loopback.send(w).map_err(|err| panic!("couldn't retry request: {}", err)); + } + + Err(err) => { + let ev = Event::Error(format!("{}", err)); + broadcast(ev.clone()); + w.publish(ev); + } + } + } +} + + +#[cfg(test)] +mod tests { + use std::thread; + use std::sync::mpsc::{channel, Sender, Receiver}; + + use super::*; + use datatype::{AccessToken, Command, Config, Event, UpdateState}; + use http_client::test_client::TestHttpClient; + use package_manager::PackageManager; + use package_manager::tpm::assert_rx; + + + fn new_interpreter(replies: Vec, pkg_mgr: PackageManager) -> (Sender, Receiver) { + let (ctx, crx): (Sender, Receiver) = channel(); + let (etx, erx): (Sender, Receiver) = channel(); + let (wtx, _): (Sender, Receiver) = channel(); + + thread::spawn(move || { + let mut wi = WrappedInterpreter { + config: Config::default(), + token: Some(AccessToken::default().into()), + client: Box::new(TestHttpClient::from(replies)), + loopback: wtx, + }; + wi.config.ota.package_manager = pkg_mgr; + loop { + match crx.recv().expect("couldn't receive cmd") { + Command::Shutdown => break, + cmd @ _ => wi.interpret(Wrapped { cmd: cmd, etx: None }, &etx) + } + } + }); + + (ctx, erx) + } + + #[test] + fn already_authenticated() { + let (ctx, erx) = new_interpreter(Vec::new(), PackageManager::new_file(true)); + ctx.send(Command::Authenticate(None)).unwrap(); + for ev in erx.recv() { + assert_eq!(ev, Event::Ok); + } + ctx.send(Command::Shutdown).unwrap(); + } + + #[test] + fn accept_updates() { + let replies = vec!["[]".to_string(); 10]; + let (ctx, erx) = new_interpreter(replies, PackageManager::new_file(true)); + + ctx.send(Command::AcceptUpdates(vec!["1".to_string(), "2".to_string()])).unwrap(); + assert_rx(erx, &[ + Event::UpdateStateChanged("1".to_string(), UpdateState::Downloading), + Event::UpdateStateChanged("1".to_string(), UpdateState::Installing), + Event::UpdateStateChanged("1".to_string(), UpdateState::Installed), + Event::UpdateStateChanged("2".to_string(), UpdateState::Downloading), + Event::UpdateStateChanged("2".to_string(), UpdateState::Installing), + Event::UpdateStateChanged("2".to_string(), UpdateState::Installed), + ]); + ctx.send(Command::Shutdown).unwrap(); + } + + #[test] + fn failed_updates() { + let replies = vec!["[]".to_string(); 10]; + let pkg_mgr = PackageManager::new_file(false); + let (ctx, erx) = new_interpreter(replies, pkg_mgr); + + ctx.send(Command::AcceptUpdates(vec!["1".to_string()])).unwrap(); + assert_rx(erx, &[ + Event::Error("IO error: No such file or directory (os error 2)".to_owned()), + ]); + ctx.send(Command::Shutdown).unwrap(); + } +} diff --git a/src/main.rs b/src/main.rs index 039fc6a..bba5132 100644 --- a/src/main.rs +++ b/src/main.rs @@ -4,11 +4,11 @@ extern crate crossbeam; extern crate env_logger; extern crate getopts; extern crate hyper; +#[macro_use] extern crate libotaplus; #[macro_use] extern crate log; extern crate rustc_serialize; extern crate time; extern crate ws; -#[macro_use] extern crate libotaplus; use chan::Receiver as ChanReceiver; use chan_signal::Signal; @@ -20,11 +20,12 @@ use std::sync::mpsc::{Sender, Receiver, channel}; use std::thread; use std::time::Duration; -use libotaplus::datatype::{config, Command, Config, Event, Url}; +use libotaplus::datatype::{config, Auth, Command, Config, Event, Url}; +use libotaplus::http_client::AuthClient; use libotaplus::interaction_library::{Console, Gateway, Http, Websocket}; use libotaplus::interaction_library::broadcast::Broadcast; -use libotaplus::interaction_library::gateway::Interpret; -use libotaplus::interpreter::{EventInterpreter, Interpreter, Wrapped, WrappedInterpreter}; +use libotaplus::interpreter::{EventInterpreter, CommandInterpreter, Interpreter, + Wrapped, WrappedInterpreter}; use libotaplus::package_manager::PackageManager; @@ -41,16 +42,8 @@ fn spawn_signal_handler(signals: ChanReceiver, ctx: Sender) { fn spawn_update_poller(ctx: Sender, interval: u64) { loop { - let _ = ctx.send(Command::GetPendingUpdates); thread::sleep(Duration::from_secs(interval)); - } -} - -fn spawn_command_forwarder(crx: Receiver, wtx: Sender) { - loop { - let _ = crx.recv() - .map(|cmd| wtx.send(Interpret { cmd: cmd, etx: None }).unwrap()) - .map_err(|err| panic!("couldn't receive command to forward: {:?}", err)); + let _ = ctx.send(Command::GetPendingUpdates); } } @@ -80,21 +73,6 @@ fn main() { let poll_interval = config.ota.polling_interval; scope.spawn(move || spawn_update_poller(poll_ctx, poll_interval)); - let cmd_wtx = wtx.clone(); - scope.spawn(move || spawn_command_forwarder(crx, cmd_wtx)); - - let event_sub = broadcast.subscribe(); - let event_ctx = ctx.clone(); - let mut event_int = EventInterpreter; - scope.spawn(move || event_int.run(event_sub, event_ctx)); - - let mut wrapped_int = WrappedInterpreter { - config: config.clone(), - access_token: None, - wtx: wtx.clone(), - }; - scope.spawn(move || wrapped_int.run(wrx, etx)); - let ws_wtx = wtx.clone(); let ws_sub = broadcast.subscribe(); scope.spawn(move || Websocket::run(ws_wtx, ws_sub)); @@ -112,6 +90,20 @@ fn main() { scope.spawn(move || Console::run(cons_wtx, cons_sub)); } + let event_sub = broadcast.subscribe(); + let event_ctx = ctx.clone(); + scope.spawn(move || EventInterpreter.run(event_sub, event_ctx)); + + let cmd_wtx = wtx.clone(); + scope.spawn(move || CommandInterpreter.run(crx, cmd_wtx)); + + scope.spawn(move || WrappedInterpreter { + config: config, + token: None, + client: Box::new(AuthClient::new(Auth::None)), + loopback: wtx, + }.run(wrx, etx)); + scope.spawn(move || broadcast.start()); }); } diff --git a/src/ota_plus.rs b/src/ota_plus.rs index 6d38da1..c139248 100644 --- a/src/ota_plus.rs +++ b/src/ota_plus.rs @@ -142,14 +142,14 @@ impl<'c, 'h> OTA<'c, 'h> { #[cfg(test)] mod tests { - use std::fmt::Debug; - use std::sync::mpsc::{channel, Receiver}; + use std::sync::mpsc::channel; use rustc_serialize::json; use super::*; use datatype::{Config, Event, Package, PendingUpdateRequest, UpdateResultCode, UpdateState}; use http_client::TestHttpClient; use package_manager::PackageManager; + use package_manager::tpm::assert_rx; #[test] @@ -185,20 +185,6 @@ mod tests { assert_eq!(expect, format!("{}", ota.download_package_update(&"0".to_string()).unwrap_err())); } - fn assert_receiver_eq(rx: Receiver, xs: &[X]) { - let mut xs = xs.iter(); - while let Ok(x) = rx.try_recv() { - if let Some(y) = xs.next() { - assert_eq!(x, *y) - } else { - panic!("assert_receiver_eq: never nexted `{:?}`", x) - } - } - if let Some(x) = xs.next() { - panic!("assert_receiver_eq: never received `{:?}`", x) - } - } - #[test] fn test_install_package_update_0() { let mut ota = OTA { @@ -211,19 +197,16 @@ mod tests { UpdateResultCode::GENERAL_ERROR); let expect = r#"ClientError("http://127.0.0.1:8080/api/v1/vehicle_updates/V1234567890123456/0/download")"#; - assert_receiver_eq(rx, &[ + assert_rx(rx, &[ Event::UpdateErrored("0".to_string(), String::from(expect)) ]); } #[test] fn test_install_package_update_1() { - let mut config = Config::default(); + let mut config = Config::default(); config.ota.packages_dir = "/tmp/".to_string(); - config.ota.package_manager = PackageManager::File { - filename: "test_install_package_update_1".to_string(), - succeeds: false - }; + config.ota.package_manager = PackageManager::new_file(false); let mut ota = OTA { config: &config, @@ -234,7 +217,7 @@ mod tests { assert_eq!(report.unwrap().operation_results.pop().unwrap().result_code, UpdateResultCode::INSTALL_FAILED); - assert_receiver_eq(rx, &[ + assert_rx(rx, &[ Event::UpdateStateChanged("0".to_string(), UpdateState::Installing), // XXX: Not very helpful message? Event::UpdateErrored("0".to_string(), r#"INSTALL_FAILED: "failed""#.to_string()) @@ -245,10 +228,7 @@ mod tests { fn test_install_package_update_2() { let mut config = Config::default(); config.ota.packages_dir = "/tmp/".to_string(); - config.ota.package_manager = PackageManager::File { - filename: "test_install_package_update_2".to_string(), - succeeds: true - }; + config.ota.package_manager = PackageManager::new_file(true); let replies = vec![ "[]".to_string(), @@ -263,7 +243,7 @@ mod tests { assert_eq!(report.unwrap().operation_results.pop().unwrap().result_code, UpdateResultCode::OK); - assert_receiver_eq(rx, &[ + assert_rx(rx, &[ Event::UpdateStateChanged("0".to_string(), UpdateState::Installing), Event::UpdateStateChanged("0".to_string(), UpdateState::Installed) ]); diff --git a/src/package_manager/mod.rs b/src/package_manager/mod.rs index b3db086..2ba31be 100644 --- a/src/package_manager/mod.rs +++ b/src/package_manager/mod.rs @@ -1,4 +1,5 @@ pub use self::package_manager::PackageManager; +pub use self::tpm::assert_rx; pub mod dpkg; pub mod package_manager; diff --git a/src/package_manager/package_manager.rs b/src/package_manager/package_manager.rs index 104f18b..a31d303 100644 --- a/src/package_manager/package_manager.rs +++ b/src/package_manager/package_manager.rs @@ -1,8 +1,14 @@ +extern crate tempfile; + use rustc_serialize::{Decoder, Decodable}; +use std::env::temp_dir; use datatype::{Error, Package, UpdateResultCode}; use package_manager::{dpkg, rpm, tpm}; +use tempfile::NamedTempFile; + +pub type InstallOutcome = (UpdateResultCode, String); #[derive(Debug, PartialEq, Eq, Clone)] pub enum PackageManager { @@ -11,9 +17,20 @@ pub enum PackageManager { File { filename: String, succeeds: bool } } -pub type InstallOutcome = (UpdateResultCode, String); - impl PackageManager { + pub fn new_file(succeeds: bool) -> Self { + PackageManager::File { + filename: NamedTempFile::new_in(temp_dir()).expect("couldn't create temporary file") + .path().file_name().expect("couldn't get file name") + .to_str().expect("couldn't parse file name").to_string(), + succeeds: succeeds + } + } + + pub fn from_file(filename: String, succeeds: bool) -> Self { + PackageManager::File { filename: filename, succeeds: succeeds } + } + pub fn installed_packages(&self) -> Result, Error> { match *self { PackageManager::Dpkg => dpkg::installed_packages(), diff --git a/src/package_manager/tpm.rs b/src/package_manager/tpm.rs index d9a67e0..aa17800 100644 --- a/src/package_manager/tpm.rs +++ b/src/package_manager/tpm.rs @@ -1,20 +1,21 @@ +use std::fmt::Debug; use std::fs::File; use std::fs::OpenOptions; use std::io::BufReader; use std::io::BufWriter; use std::io::prelude::*; +use std::sync::mpsc::Receiver; use datatype::{Error, Package, UpdateResultCode}; +use package_manager::package_manager::InstallOutcome; pub fn installed_packages(path: &str) -> Result, Error> { - let f = try!(File::open(path)); let reader = BufReader::new(f); let mut pkgs = Vec::new(); for line in reader.lines() { - let line = try!(line); let parts = line.split(' '); @@ -28,39 +29,39 @@ pub fn installed_packages(path: &str) -> Result, Error> { } } } - } - return Ok(pkgs) - + Ok(pkgs) } -pub fn install_package(path: &str, pkg: &str, succeeds: bool) -> Result<(UpdateResultCode, String), (UpdateResultCode, String)> { - - fn install(path: &str, pkg: &str) -> Result<(), Error> { - let f = try!(OpenOptions::new() - .create(true) - .write(true) - .append(true) - .open(path)); - +pub fn install_package(path: &str, pkg: &str, succeeds: bool) -> Result { + let install = || -> Result<(), Error> { + let f = OpenOptions::new().create(true).write(true).append(true).open(path) + .expect("couldn't open file for writing"); let mut writer = BufWriter::new(f); - try!(writer.write(pkg.as_bytes())); try!(writer.write(b"\n")); - - return Ok(()) - } + Ok(()) + }; if succeeds { - match install(path, pkg) { - Ok(_) => Ok((UpdateResultCode::OK, "".to_string())), + match install() { + Ok(_) => Ok((UpdateResultCode::OK, "".to_string())), Err(e) => Err((UpdateResultCode::INSTALL_FAILED, format!("{:?}", e))) } } else { Err((UpdateResultCode::INSTALL_FAILED, "failed".to_string())) } +} +pub fn assert_rx(rx: Receiver, xs: &[X]) { + let n = xs.len(); + let mut xs = xs.iter(); + for _ in 0..n { + let val = rx.recv().expect("assert_rx expected another val"); + let x = xs.next().expect(&format!("assert_rx: no match for val: {:?}", val)); + assert_eq!(val, *x); + } } -- cgit v1.2.1 From 13a552483b79150d7980823b749fc1d14d3eb1a8 Mon Sep 17 00:00:00 2001 From: Shaun Taheri Date: Sat, 18 Jun 2016 15:36:22 +0200 Subject: Propagate shutdown signal to all threads --- Cargo.lock | 54 +++---- Cargo.toml | 14 +- pkg/provision/start-up.sh | 2 +- src/datatype/error.rs | 12 +- src/http_client/auth_client.rs | 40 +++--- src/http_client/http_client.rs | 5 +- src/http_client/test_client.rs | 6 +- src/interaction_library/broadcast.rs | 47 +++---- src/interaction_library/console.rs | 62 ++++---- src/interaction_library/gateway.rs | 47 +++---- src/interaction_library/http.rs | 129 ++++++++--------- src/interaction_library/websocket.rs | 170 ++++++++++------------ src/interpreter.rs | 249 +++++++++++++++++---------------- src/lib.rs | 1 + src/main.rs | 113 ++++++++------- src/oauth2.rs | 14 +- src/ota_plus.rs | 53 ++++--- src/package_manager/package_manager.rs | 20 +-- src/package_manager/tpm.rs | 2 +- 19 files changed, 515 insertions(+), 525 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 85c924a..84f2e68 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -11,11 +11,11 @@ dependencies = [ "log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", "nom 1.2.3 (registry+https://github.com/rust-lang/crates.io-index)", "rustc-serialize 0.3.19 (registry+https://github.com/rust-lang/crates.io-index)", - "tempfile 2.1.3 (registry+https://github.com/rust-lang/crates.io-index)", + "tempfile 2.1.4 (registry+https://github.com/rust-lang/crates.io-index)", "time 0.1.35 (registry+https://github.com/rust-lang/crates.io-index)", "toml 0.1.30 (registry+https://github.com/rust-lang/crates.io-index)", "url 1.1.1 (registry+https://github.com/rust-lang/crates.io-index)", - "ws 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", + "ws 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -80,12 +80,12 @@ dependencies = [ "bit-set 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", "chan 0.1.18 (registry+https://github.com/rust-lang/crates.io-index)", "lazy_static 0.1.16 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.12 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "cookie" -version = "0.2.4" +version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "openssl 0.7.13 (registry+https://github.com/rust-lang/crates.io-index)", @@ -135,13 +135,13 @@ source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "hyper" version = "0.9.4" -source = "git+https://github.com/hyperium/hyper#0c847f7898d757a4ce9f2b60a04155ed35c538bb" +source = "git+https://github.com/hyperium/hyper#45eb4f93125e08d4ebe4b8d39dfd5fa3d5173438" dependencies = [ - "cookie 0.2.4 (registry+https://github.com/rust-lang/crates.io-index)", + "cookie 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)", "httparse 1.1.2 (registry+https://github.com/rust-lang/crates.io-index)", "language-tags 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", - "mime 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", + "mime 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", "openssl 0.7.13 (registry+https://github.com/rust-lang/crates.io-index)", "openssl-verify 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", "rotor 0.6.3 (registry+https://github.com/rust-lang/crates.io-index)", @@ -191,7 +191,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "libc" -version = "0.2.11" +version = "0.2.12" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] @@ -217,12 +217,12 @@ name = "memchr" version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "libc 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.12 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "mime" -version = "0.2.0" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", @@ -234,11 +234,11 @@ version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "bytes 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.12 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", "miow 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", - "net2 0.2.23 (registry+https://github.com/rust-lang/crates.io-index)", - "nix 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", + "net2 0.2.24 (registry+https://github.com/rust-lang/crates.io-index)", + "nix 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", "slab 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", "time 0.1.35 (registry+https://github.com/rust-lang/crates.io-index)", "winapi 0.2.7 (registry+https://github.com/rust-lang/crates.io-index)", @@ -250,30 +250,30 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", - "net2 0.2.23 (registry+https://github.com/rust-lang/crates.io-index)", + "net2 0.2.24 (registry+https://github.com/rust-lang/crates.io-index)", "winapi 0.2.7 (registry+https://github.com/rust-lang/crates.io-index)", "ws2_32-sys 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "net2" -version = "0.2.23" +version = "0.2.24" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "cfg-if 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", "kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.12 (registry+https://github.com/rust-lang/crates.io-index)", "winapi 0.2.7 (registry+https://github.com/rust-lang/crates.io-index)", "ws2_32-sys 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "nix" -version = "0.5.0" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "bitflags 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.12 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -289,7 +289,7 @@ dependencies = [ "bitflags 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", "gcc 0.3.28 (registry+https://github.com/rust-lang/crates.io-index)", "lazy_static 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.12 (registry+https://github.com/rust-lang/crates.io-index)", "openssl-sys 0.7.13 (registry+https://github.com/rust-lang/crates.io-index)", "openssl-sys-extras 0.7.13 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -300,7 +300,7 @@ version = "0.7.13" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "gdi32-sys 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.12 (registry+https://github.com/rust-lang/crates.io-index)", "libressl-pnacl-sys 2.1.6 (registry+https://github.com/rust-lang/crates.io-index)", "pkg-config 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", "user32-sys 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", @@ -312,7 +312,7 @@ version = "0.7.13" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "gcc 0.3.28 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.12 (registry+https://github.com/rust-lang/crates.io-index)", "openssl-sys 0.7.13 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -347,7 +347,7 @@ name = "rand" version = "0.3.14" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "libc 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.12 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -425,11 +425,11 @@ dependencies = [ [[package]] name = "tempfile" -version = "2.1.3" +version = "2.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.12 (registry+https://github.com/rust-lang/crates.io-index)", "rand 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)", "rustc_version 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)", "winapi 0.2.7 (registry+https://github.com/rust-lang/crates.io-index)", @@ -441,7 +441,7 @@ version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.12 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -458,7 +458,7 @@ version = "0.1.35" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.12 (registry+https://github.com/rust-lang/crates.io-index)", "winapi 0.2.7 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -550,7 +550,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "ws" -version = "0.4.8" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "httparse 1.1.2 (registry+https://github.com/rust-lang/crates.io-index)", diff --git a/Cargo.toml b/Cargo.toml index 80a000e..59e5c89 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,16 +14,16 @@ doc = false [dependencies] chan = "0.1.18" -chan-signal = "0.1.5" +chan-signal = "0.1.6" crossbeam = "0.2.9" env_logger = "0.3.3" getopts = "0.2.14" hyper = { git = "https://github.com/hyperium/hyper" } -log = "0.3.5" +log = "0.3.6" nom = "1.2.3" -rustc-serialize = "0.3.18" -tempfile = "2.1.2" +rustc-serialize = "0.3.19" +tempfile = "2.1.3" time = "0.1.35" -toml = "0.1.28" -url = "1.1.0" -ws = "0.4.6" +toml = "0.1.30" +url = "1.1.1" +ws = "0.5.0" diff --git a/pkg/provision/start-up.sh b/pkg/provision/start-up.sh index 35c710f..59465f3 100755 --- a/pkg/provision/start-up.sh +++ b/pkg/provision/start-up.sh @@ -46,5 +46,5 @@ then else OTA_TOML=$(cat $TEMPLATE_PATH | envsubst > $OUTPUT_PATH) cat $OUTPUT_PATH - RUST_LOG=debug ota_plus_client --config=/etc/ota.toml + RUST_LOG=${RUST_LOG-debug} ota_plus_client --config=/etc/ota.toml fi diff --git a/src/datatype/error.rs b/src/datatype/error.rs index 4ad3307..661ac16 100644 --- a/src/datatype/error.rs +++ b/src/datatype/error.rs @@ -13,7 +13,7 @@ use datatype::Event; use rustc_serialize::json::{EncoderError as JsonEncoderError, DecoderError as JsonDecoderError}; use ws::Error as WebsocketError; use super::super::http_client::auth_client::AuthHandler; -use super::super::interpreter::Wrapped; +use super::super::interpreter::Global; #[derive(Debug)] @@ -32,7 +32,7 @@ pub enum Error { ParseError(String), RecvError(RecvError), SendErrorEvent(SendError), - SendErrorWrapped(SendError), + SendErrorGlobal(SendError), TomlParserErrors(Vec), TomlDecodeError(TomlDecodeError), UrlParseError(UrlParseError), @@ -45,9 +45,9 @@ impl From> for Error { } } -impl From> for Error { - fn from(e: SendError) -> Error { - Error::SendErrorWrapped(e) +impl From> for Error { + fn from(e: SendError) -> Error { + Error::SendErrorGlobal(e) } } @@ -119,7 +119,7 @@ impl Display for Error { Error::ParseError(ref s) => s.clone(), Error::RecvError(ref s) => format!("Recv error: {}", s.clone()), Error::SendErrorEvent(ref s) => format!("Send error for Event: {}", s.clone()), - Error::SendErrorWrapped(ref s) => format!("Send error for Wrapped: {}", s.clone()), + Error::SendErrorGlobal(ref s) => format!("Send error for Global: {}", s.clone()), Error::TomlDecodeError(ref e) => format!("Toml decode error: {}", e.clone()), Error::TomlParserErrors(ref e) => format!("Toml parser errors: {:?}", e.clone()), Error::UrlParseError(ref s) => format!("Url parse error: {}", s.clone()), diff --git a/src/http_client/auth_client.rs b/src/http_client/auth_client.rs index f19f014..62850c3 100644 --- a/src/http_client/auth_client.rs +++ b/src/http_client/auth_client.rs @@ -1,12 +1,13 @@ +use chan::Sender; use hyper; use hyper::{Encoder, Decoder, Next}; use hyper::client::{Client, Handler, HttpsConnector, Request, Response}; use hyper::header::{Authorization, Basic, Bearer, ContentLength, ContentType, Location}; use hyper::mime::{Attr, Mime, TopLevel, SubLevel, Value}; use hyper::net::{HttpStream, HttpsStream, OpensslStream, Openssl}; +use hyper::status::StatusCode; use std::{io, mem}; use std::io::{ErrorKind, Write}; -use std::sync::mpsc::Sender; use std::time::Duration; use time; @@ -47,10 +48,7 @@ impl HttpClient for AuthClient { written: 0, response: Vec::new(), resp_tx: resp_tx.clone(), - }).map_err(|err| { - resp_tx.send(Err(Error::from(err))) - .map_err(|err| error!("couldn't send chan_request: {}", err)) - }); + }).map_err(|err| resp_tx.send(Err(Error::from(err)))); } } @@ -76,7 +74,7 @@ impl ::std::fmt::Debug for AuthHandler { impl AuthHandler { fn redirect_request(&self, resp: Response) { info!("redirect_request"); - let _ = match resp.headers().get::() { + match resp.headers().get::() { Some(&Location(ref loc)) => match self.req.url.join(loc) { Ok(url) => { debug!("redirecting to {:?}", url); @@ -92,11 +90,11 @@ impl AuthHandler { body: body, }); match resp_rx.recv() { - Ok(resp) => match resp { + Some(resp) => match resp { Ok(data) => self.resp_tx.send(Ok(data)), Err(err) => self.resp_tx.send(Err(Error::from(err))) }, - Err(err) => self.resp_tx.send(Err(Error::from(err))) + None => panic!("no redirect_request response") } } @@ -108,7 +106,7 @@ impl AuthHandler { error!("{}", msg); self.resp_tx.send(Err(Error::ClientError(msg))) } - }.map_err(|err| error!("couldn't send redirect response: {}", err)); + } } } @@ -179,9 +177,7 @@ impl Handler for AuthHandler { Err(err) => { error!("unable to write request body: {}", err); - let _ = self.resp_tx - .send(Err(Error::from(err))) - .map_err(|err| error!("couldn't send write error: {}", err)); + self.resp_tx.send(Err(Error::from(err))); Next::remove() } } @@ -199,12 +195,14 @@ impl Handler for AuthHandler { } else if resp.status().is_redirection() { self.redirect_request(resp); Next::end() + } else if resp.status() == &StatusCode::Forbidden { + error!("on_response: 403 Forbidden"); + self.resp_tx.send(Err(Error::AuthorizationError("403".to_string()))); + Next::end() } else { let msg = format!("failed response status: {}", resp.status()); error!("{}", msg); - let _ = self.resp_tx - .send(Err(Error::ClientError(msg))) - .map_err(|err| error!("couldn't send failed response: {}", err)); + self.resp_tx.send(Err(Error::ClientError(msg))); Next::end() } } @@ -213,9 +211,7 @@ impl Handler for AuthHandler { match io::copy(decoder, &mut self.response) { Ok(0) => { debug!("on_response_readable bytes read: {:?}", self.response.len()); - let _ = self.resp_tx - .send(Ok(mem::replace(&mut self.response, Vec::new()))) - .map_err(|err| error!("couldn't send response: {}", err)); + self.resp_tx.send(Ok(mem::replace(&mut self.response, Vec::new()))); Next::end() } @@ -231,9 +227,7 @@ impl Handler for AuthHandler { Err(err) => { error!("unable to read response body: {}", err); - let _ = self.resp_tx - .send(Err(Error::from(err))) - .map_err(|err| error!("couldn't send read error: {}", err)); + self.resp_tx.send(Err(Error::from(err))); Next::end() } } @@ -241,9 +235,7 @@ impl Handler for AuthHandler { fn on_error(&mut self, err: hyper::Error) -> Next { error!("on_error: {}", err); - let _ = self.resp_tx - .send(Err(Error::from(err))) - .map_err(|err| error!("couldn't send on_error response: {}", err)); + self.resp_tx.send(Err(Error::from(err))); Next::remove() } } diff --git a/src/http_client/http_client.rs b/src/http_client/http_client.rs index a2039bd..4e0725c 100644 --- a/src/http_client/http_client.rs +++ b/src/http_client/http_client.rs @@ -1,11 +1,12 @@ -use std::sync::mpsc::{Sender, Receiver, channel}; +use chan; +use chan::{Sender, Receiver}; use datatype::{Error, Method, Url}; pub trait HttpClient { fn send_request(&self, req: HttpRequest) -> Receiver { - let (resp_tx, resp_rx): (Sender, Receiver) = channel(); + let (resp_tx, resp_rx) = chan::async::(); self.chan_request(req, resp_tx); resp_rx } diff --git a/src/http_client/test_client.rs b/src/http_client/test_client.rs index a2a3017..ae52fe3 100644 --- a/src/http_client/test_client.rs +++ b/src/http_client/test_client.rs @@ -1,6 +1,6 @@ +use chan::Sender; use http_client::{HttpClient, HttpRequest, HttpResponse}; use std::cell::RefCell; -use std::sync::mpsc::Sender; use datatype::Error; @@ -21,10 +21,10 @@ impl TestHttpClient { impl HttpClient for TestHttpClient { fn chan_request(&self, req: HttpRequest, resp_tx: Sender) { - let _ = match self.replies.borrow_mut().pop() { + match self.replies.borrow_mut().pop() { Some(body) => resp_tx.send(Ok(body.as_bytes().to_vec())), None => resp_tx.send(Err(Error::ClientError(req.url.to_string()))) - }.map_err(|err| error!("couldn't send test chan_request response: {}", err)); + } } fn is_testing(&self) -> bool { true } diff --git a/src/interaction_library/broadcast.rs b/src/interaction_library/broadcast.rs index b35e3a9..981887b 100644 --- a/src/interaction_library/broadcast.rs +++ b/src/interaction_library/broadcast.rs @@ -1,65 +1,54 @@ -use std::sync::mpsc::{Sender, Receiver, channel}; +use chan; +use chan::{Sender, Receiver}; pub struct Broadcast { peers: Vec>, - rx: Receiver + rx: Receiver } impl Broadcast { - pub fn new(rx: Receiver) -> Broadcast { Broadcast { peers: vec![], rx: rx } } pub fn start(&self) { loop { - match self.rx.recv() { - Ok(payload) => { - for subscriber in &self.peers { - match subscriber.send(payload.clone()) { - Err(e) => error!("Error broadcasting: {}", e), - _ => {} - } - } - }, - Err(e) => trace!("Error receiving: {}", e) - } + self.rx.recv().map(|a| { + for subscriber in &self.peers { + subscriber.send(a.clone()); + } + }); } } pub fn subscribe(&mut self) -> Receiver { - let (tx, rx) = channel(); + let (tx, rx) = chan::sync::(0); self.peers.push(tx); - return rx + rx } - } #[cfg(test)] mod tests { + use chan; + use std::thread; + use super::*; - use std::sync::mpsc::channel; - use std::thread::spawn; #[test] fn test_broadcasts_events() { - let (tx, rx) = channel(); + let (tx, rx) = chan::sync(0); let mut broadcast = Broadcast::new(rx); let a = broadcast.subscribe(); let b = broadcast.subscribe(); + thread::spawn(move || broadcast.start()); - let _ = tx.send(123); - - spawn(move || broadcast.start()); - - let a_got = a.recv().unwrap(); - let b_got = b.recv().unwrap(); - - assert_eq!(a_got, 123); - assert_eq!(b_got, 123); + tx.send(123); + assert_eq!(123, a.recv().unwrap()); + assert_eq!(123, b.recv().unwrap()); } } diff --git a/src/interaction_library/console.rs b/src/interaction_library/console.rs index 6867a0a..4b7faa2 100644 --- a/src/interaction_library/console.rs +++ b/src/interaction_library/console.rs @@ -1,54 +1,62 @@ +use chan; +use chan::{Sender, Receiver}; use std::{io, thread}; use std::fmt::Debug; +use std::io::Write; use std::str::FromStr; use std::string::ToString; -use std::sync::{Arc, Mutex, mpsc}; -use std::sync::mpsc::{Sender, Receiver}; +use std::sync::{Arc, Mutex}; +use super::broadcast::Broadcast; use super::gateway::{Gateway, Interpret}; -pub struct Console { - etx: Arc>> -} +pub struct Console; -impl Gateway for Console - where C: Send + FromStr + 'static, - E: Send + ToString + 'static, +impl Gateway for Console + where C: FromStr + Send + Clone + Debug + 'static, + E: ToString + Send + Clone + Debug + 'static, ::Err: Debug, { - fn new() -> Console { - let (etx, erx): (Sender, Receiver) = mpsc::channel(); + fn new(itx: Sender>, shutdown_rx: Receiver<()>) -> Self { + let mut shutdown = Broadcast::new(shutdown_rx); + let (etx, erx) = chan::sync::(0); + let etx = Arc::new(Mutex::new(etx)); + let stop_tx = shutdown.subscribe(); thread::spawn(move || { loop { - match erx.recv() { - Ok(event) => println!("{}", event.to_string()), - Err(err) => error!("Error receiving event: {:?}", err), + chan_select! { + default => match parse_input(get_input()) { + Ok(cmd) => itx.send(Interpret{ command: cmd, response_tx: Some(etx.clone()) }), + Err(err) => println!("(error): {:?}", err) + }, + stop_tx.recv() => break } } }); - Console { etx: Arc::new(Mutex::new(etx)) } - } - - fn next(&self) -> Option> { - match parse_input(get_input()) { - Ok(cmd) => Some(Interpret{ - cmd: cmd, - etx: Some(self.etx.clone()), - }), - Err(err) => { - println!("{:?}", err); - None + let stop_rx = shutdown.subscribe(); + thread::spawn(move || { + loop { + chan_select! { + erx.recv() -> e => match e { + Some(e) => println!("(event): {}", e.to_string()), + None => panic!("all console event transmitters are closed") + }, + stop_rx.recv() => break + } } - } + }); + + println!("OTA Plus Client REPL started."); + Console } } fn get_input() -> String { - print!("> "); let mut input = String::new(); + let _ = io::stdout().write("> ".as_bytes()); let _ = io::stdin().read_line(&mut input); input } diff --git a/src/interaction_library/gateway.rs b/src/interaction_library/gateway.rs index ce58886..a698fc8 100644 --- a/src/interaction_library/gateway.rs +++ b/src/interaction_library/gateway.rs @@ -1,43 +1,40 @@ +use chan::{Sender, Receiver}; use std::thread; +use std::fmt::Debug; use std::sync::{Arc, Mutex}; -use std::sync::mpsc::{Sender, Receiver}; +use super::broadcast::Broadcast; -#[derive(Clone)] + +#[derive(Clone, Debug)] pub struct Interpret { - pub cmd: C, - pub etx: Option>>>, + pub command: C, + pub response_tx: Option>>>, } pub trait Gateway: Sized + Send + Sync + 'static - where C: Send + Clone + 'static, - E: Send + Clone + 'static, + where C: Send + Clone + Debug + 'static, + E: Send + Clone + Debug + 'static, { - fn new() -> Self; - fn next(&self) -> Option>; + fn new(itx: Sender>, shutdown_rx: Receiver<()>) -> Self; - fn run(tx: Sender>, rx: Receiver) { - let gateway = Arc::new(Self::new()); - let global = gateway.clone(); + fn run(itx: Sender>, erx: Receiver, shutdown_rx: Receiver<()>) { + let mut shutdown = Broadcast::new(shutdown_rx); + let gateway = Self::new(itx, shutdown.subscribe()); + let stop_pulse = shutdown.subscribe(); thread::spawn(move || { loop { - gateway.next().map(|i| { - tx.send(i).map_err(|err| error!("Error sending command: {:?}", err)) - }); - } - }); - - thread::spawn(move || { - loop { - let _ = rx.recv().map(|e| global.pulse(e)) - .map_err(|err| trace!("Error receiving event: {:?}", err)); + chan_select! { + stop_pulse.recv() => break, + erx.recv() -> e => match e { + Some(e) => gateway.pulse(e), + None => panic!("all gateway event transmitters are closed") + } + } } }); } - #[allow(unused_variables)] - fn pulse(&self, e: E) { - // ignore global events by default - } + fn pulse(&self, _: E) {} // ignore global events by default } diff --git a/src/interaction_library/http.rs b/src/interaction_library/http.rs index f506fa0..5f06620 100644 --- a/src/interaction_library/http.rs +++ b/src/interaction_library/http.rs @@ -1,3 +1,5 @@ +use chan; +use chan::{Sender, Receiver}; use hyper::{Decoder, Encoder, Next, StatusCode}; use hyper::header::ContentType; use hyper::mime::{Attr, Mime, TopLevel, SubLevel, Value}; @@ -7,70 +9,54 @@ use rustc_serialize::{json, Decodable, Encodable}; use std::{env, io, mem, thread}; use std::fmt::Debug; use std::io::{ErrorKind, Write}; -use std::sync::{Arc, Mutex, mpsc}; -use std::sync::mpsc::{Sender, Receiver}; +use std::sync::{Arc, Mutex}; use std::time::Duration; use super::gateway::{Gateway, Interpret}; -pub struct Http { - irx: Arc>>>, -} +pub struct Http; -impl Gateway for Http +impl Gateway for Http where C: Decodable + Send + Clone + Debug + 'static, - E: Encodable + Send + Clone + 'static + E: Encodable + Send + Clone + Debug + 'static { - fn new() -> Http { - let (itx, irx): (Sender>, Receiver>) = mpsc::channel(); - let itx = Arc::new(Mutex::new(itx)); - let irx = Arc::new(Mutex::new(irx)); - - let addr = env::var("OTA_PLUS_CLIENT_HTTP_ADDR").unwrap_or("127.0.0.1:8888".to_string()); + fn new(itx: Sender>, _: Receiver<()>) -> Self { + let itx = Arc::new(Mutex::new(itx)); + let addr = env::var("OTA_PLUS_CLIENT_HTTP_ADDR").unwrap_or("127.0.0.1:8888".to_string()); let server = Server::http(&addr.parse().unwrap()).unwrap(); + let (addr, server) = server.handle(move |_| HttpHandler::new(itx.clone())).unwrap(); + thread::spawn(move || server.run()); - thread::spawn(move || { server.run() }); info!("Listening on http://{}", addr); - - Http { irx: irx } - } - - fn next(&self) -> Option> { - match self.irx.lock().unwrap().recv() { - Ok(i) => Some(i), - Err(err) => { - error!("Error forwarding request: {}", err); - None - } - } + Http } } pub struct HttpHandler where C: Decodable + Send + Clone + Debug, - E: Encodable + Send + Clone + E: Encodable + Send + Clone + Debug { - itx: Arc>>>, - erx: Option>, - req_body: Vec, - resp_body: Option>, - written: usize, + forward_tx: Arc>>>, + response_rx: Option>, + req_body: Vec, + resp_body: Option>, + written: usize, } impl HttpHandler where C: Decodable + Send + Clone + Debug, - E: Encodable + Send + Clone + E: Encodable + Send + Clone + Debug { - fn new(itx: Arc>>>) -> HttpHandler { + fn new(forward_tx: Arc>>>) -> HttpHandler { HttpHandler { - itx: itx, - erx: None, - req_body: Vec::new(), - resp_body: None, - written: 0 + forward_tx: forward_tx, + response_rx: None, + req_body: Vec::new(), + resp_body: None, + written: 0 } } @@ -79,16 +65,14 @@ impl HttpHandler match String::from_utf8(body) { Ok(body) => match json::decode::(&body) { - Ok(c) => { - info!("on_request_readable: decoded command: {:?}", c); - - let (etx, erx): (Sender, Receiver) = mpsc::channel(); - self.erx = Some(erx); - self.itx.lock().unwrap().send(Interpret { - cmd: c, - etx: Some(Arc::new(Mutex::new(etx))), - }).unwrap(); - + Ok(cmd) => { + info!("on_request_readable: decoded command: {:?}", cmd); + let (etx, erx) = chan::async::(); + self.response_rx = Some(erx); + self.forward_tx.lock().unwrap().send(Interpret { + command: cmd, + response_tx: Some(Arc::new(Mutex::new(etx))), + }); Next::write().timeout(Duration::from_secs(20)) } @@ -107,8 +91,8 @@ impl HttpHandler } impl Handler for HttpHandler - where C: Send + Decodable + Clone + Debug, - E: Send + Encodable + Clone + where C: Decodable + Send + Clone + Debug, + E: Encodable + Send + Clone + Debug { fn on_request(&mut self, req: Request) -> Next { info!("on_request: {} {}", req.method(), req.uri()); @@ -141,10 +125,10 @@ impl Handler for HttpHandler fn on_response(&mut self, resp: &mut Response) -> Next { info!("on_response: status {}", resp.status()); - let rx = self.erx.as_ref().expect("Some receiver expected"); + let response_rx = self.response_rx.as_ref().expect("Some receiver expected"); - match rx.recv() { - Ok(e) => match json::encode(&e) { + match response_rx.recv() { + Some(e) => match json::encode(&e) { Ok(body) => { resp.set_status(StatusCode::Ok); resp.headers_mut().set(ContentType(Mime(TopLevel::Application, SubLevel::Json, @@ -160,8 +144,8 @@ impl Handler for HttpHandler } }, - Err(err) => { - error!("on_response receiver: {}", err); + None => { + error!("on_response receiver error"); resp.set_status(StatusCode::InternalServerError); Next::end() } @@ -199,36 +183,39 @@ impl Handler for HttpHandler #[cfg(test)] mod tests { + use chan; use crossbeam; use rustc_serialize::json; use std::thread; - use std::sync::mpsc::{channel, Sender, Receiver}; use super::*; use super::super::gateway::Gateway; use super::super::super::datatype::{Auth, Command, Event, Method, Url}; use super::super::super::http_client::{AuthClient, HttpClient, HttpRequest}; - use super::super::super::interpreter::Wrapped; - + use super::super::super::interpreter::Global; #[test] + #[allow(unused_variables)] fn multiple_connections() { - let (_, erx): (Sender, Receiver) = channel(); - let (wtx, wrx): (Sender, Receiver) = channel(); - Http::run(wtx, erx); + let (etx, erx) = chan::sync::(0); + let (gtx, grx) = chan::sync::(0); + let (_, shutdown_rx) = chan::sync::<()>(0); + Http::run(gtx, erx, shutdown_rx); thread::spawn(move || { loop { - let w = wrx.recv().unwrap(); - match w.cmd { - Command::AcceptUpdates(ids) => { - let ev = Event::Error(ids.first().unwrap().to_owned()); - match w.etx { - Some(etx) => etx.lock().unwrap().send(ev).unwrap(), - None => panic!("expected transmitter"), + match grx.recv() { + Some(g) => match g.command { + Command::AcceptUpdates(ids) => { + let ev = Event::Error(ids.first().unwrap().to_owned()); + match g.response_tx { + Some(etx) => etx.lock().unwrap().send(ev), + None => panic!("expected transmitter"), + } } - } - _ => panic!("expected AcceptUpdate"), + _ => panic!("expected AcceptUpdates"), + }, + None => panic!("gtx closed") } } }); diff --git a/src/interaction_library/websocket.rs b/src/interaction_library/websocket.rs index 8454665..ca67bb9 100644 --- a/src/interaction_library/websocket.rs +++ b/src/interaction_library/websocket.rs @@ -1,13 +1,13 @@ +use chan; +use chan::{Sender, Receiver}; use rustc_serialize::{json, Decodable, Encodable}; -use std::env; +use std::{env, thread}; use std::collections::HashMap; -use std::sync::mpsc::{Sender, Receiver}; -use std::sync::mpsc; +use std::fmt::Debug; use std::sync::{Arc, Mutex}; -use std::thread; -use ws::util::Token; -use ws::{listen, Sender as WsSender, Handler, Message, Handshake, CloseCode}; use ws; +use ws::{listen, Sender as WsSender, Handler, Message, Handshake, CloseCode}; +use ws::util::Token; use super::gateway::{Gateway, Interpret}; use datatype::Error; @@ -15,112 +15,88 @@ use datatype::Error; type Clients = Arc>>; -pub struct WebsocketHandler { - out: WsSender, - sender: Sender, - clients: Clients, -} -impl Handler for WebsocketHandler { - fn on_message(&mut self, msg: Message) -> ws::Result<()> { - Ok(match self.sender.send(format!("{}", msg)) { - Ok(_) => {} - Err(e) => error!("Error forwarding message from WS: {}", e), - }) - } - - fn on_open(&mut self, _: Handshake) -> ws::Result<()> { - let mut map = self.clients.lock().expect("Poisoned map lock -- can't continue"); - let _ = map.insert(self.out.token(), self.out.clone()); - Ok(()) - - } - - fn on_close(&mut self, _: CloseCode, _: &str) { - let mut map = self.clients.lock().expect("Poisoned map lock -- can't continue"); - let _ = map.remove(&self.out.token().clone()); - } -} +pub struct Websocket; +impl Gateway for Websocket + where C: Decodable + Send + Clone + Debug + 'static, + E: Encodable + Send + Clone + Debug + 'static, +{ + fn new(itx: Sender>, shutdown_rx: Receiver<()>) -> Self { + let addr = env::var("OTA_PLUS_CLIENT_WEBSOCKET_ADDR").unwrap_or("127.0.0.1:3012".to_string()); + let clients = Arc::new(Mutex::new(HashMap::new())); + let (etx, erx) = chan::sync::(0); -#[derive(Clone)] -pub struct Websocket { - clients: Clients, - receiver: Arc>>, -} + let ws_clients = clients.clone(); + thread::spawn(move || { + info!("Opening websocket listener on {}", addr); + let etx = Arc::new(Mutex::new(etx.clone())); + listen(&addr as &str, |sender| { + WebsocketHandler { + clients: ws_clients.clone(), + sender: sender, + itx: itx.clone(), + etx: etx.clone(), + } + }).unwrap(); + }); -impl Websocket { - fn get_line(&self) -> String { - let rx = self.receiver.lock().expect("Poisoned rx lock -- can't continue"); - match rx.recv() { - Ok(line) => line, - Err(err) => { - error!("Couldn't fetch from WS receiver: {:?}", err); - "".to_string() + thread::spawn(move || { + loop { + chan_select! { + shutdown_rx.recv() => break, + erx.recv() -> e => match e { + Some(e) => send_response(&clients, e), + None => panic!("all websocket response transmitters are closed") + } + } } - } - } + }); - fn put_line(&self, s: String) { - let map = self.clients.lock().expect("Poisoned map lock -- can't continue"); - for (_, out) in map.iter() { - let _ = out.send(Message::Text(s.clone())); - } + Websocket } } -impl Gateway for Websocket - where C: Decodable + Send + Clone + 'static, - E: Encodable + Send + Clone + 'static, +pub struct WebsocketHandler + where C: Decodable + Send + Clone + Debug + 'static, + E: Encodable + Send + Clone + Debug + 'static, { - fn new() -> Websocket { - let (tx, rx) = mpsc::channel(); - let clients = Arc::new(Mutex::new(HashMap::new())); - let moved = clients.clone(); - let addr = env::var("OTA_PLUS_CLIENT_WEBSOCKET_ADDR") - .unwrap_or("127.0.0.1:3012".to_string()); + clients: Clients, + sender: WsSender, + itx: Sender>, + etx: Arc>>, +} - thread::spawn(move || { - listen(&addr as &str, |out| { - WebsocketHandler { - out: out, - sender: tx.clone(), - clients: moved.clone(), - } - }) - }); +impl Handler for WebsocketHandler + where C: Decodable + Send + Clone + Debug + 'static, + E: Encodable + Send + Clone + Debug + 'static, +{ + fn on_message(&mut self, msg: Message) -> ws::Result<()> { + match decode(&format!("{}", msg)) { + Ok(cmd) => Ok(self.itx.send(Interpret { command: cmd, response_tx: Some(self.etx.clone()) })), + + Err(Error::WebsocketError(err)) => { + error!("websocket decode error: {}", err); + Err(err) + } - Websocket { - clients: clients, - receiver: Arc::new(Mutex::new(rx)), + Err(_) => unreachable!() } } - fn next(&self) -> Option> { - match decode(&self.get_line()) { - Ok(cmd) => { - let (etx, erx): (Sender, Receiver) = mpsc::channel(); - let clone = self.clone(); - thread::spawn(move || { - match erx.recv() { - Ok(e) => clone.put_line(encode(e)), - Err(err) => error!("Error receiving event: {:?}", err), - } - }); - Some(Interpret { - cmd: cmd, - etx: Some(Arc::new(Mutex::new(etx))), - }) - } + fn on_open(&mut self, _: Handshake) -> ws::Result<()> { + let mut map = self.clients.lock().expect("Poisoned map lock -- can't continue"); + let _ = map.insert(self.sender.token(), self.sender.clone()); + Ok(()) + } - Err(err) => { - error!("Error decoding JSON: {}", err); - None - } - } + fn on_close(&mut self, _: CloseCode, _: &str) { + let mut map = self.clients.lock().expect("Poisoned map lock -- can't continue"); + let _ = map.remove(&self.sender.token().clone()); } } + fn encode(e: E) -> String { json::encode(&e).expect("Error encoding event into JSON") } @@ -128,3 +104,11 @@ fn encode(e: E) -> String { fn decode(s: &str) -> Result { Ok(try!(json::decode::(s))) } + +fn send_response(clients: &Clients, e: E) { + let txt = encode(e); + let map = clients.lock().expect("Poisoned map lock -- can't continue"); + for (_, sender) in map.iter() { + let _ = sender.send(Message::Text(txt.clone())); + } +} diff --git a/src/interpreter.rs b/src/interpreter.rs index 00e5adc..9fa1a24 100644 --- a/src/interpreter.rs +++ b/src/interpreter.rs @@ -1,6 +1,6 @@ +use chan; +use chan::{Sender, Receiver}; use std::borrow::Cow; -use std::process::exit; -use std::sync::mpsc::{Sender, Receiver, channel}; use datatype::{AccessToken, Auth, ClientId, ClientSecret, Command, Config, Error, Event, UpdateState, UpdateRequestId}; @@ -11,14 +11,18 @@ use oauth2::authenticate; use ota_plus::OTA; -pub trait Interpreter { +pub trait Interpreter { fn interpret(&mut self, msg: I, otx: &Sender); - fn run(&mut self, irx: Receiver, otx: Sender) { + fn run(&mut self, irx: Receiver, otx: Sender, shutdown_rx: Receiver<()>) { loop { - let _ = irx.recv() - .map(|msg| self.interpret(msg, &otx)) - .map_err(|err| panic!("couldn't read interpreter input: {:?}", err)); + chan_select! { + shutdown_rx.recv() => break, + irx.recv() -> i => match i { + Some(i) => self.interpret(i, &otx), + None => panic!("interpreter sender closed") + } + } } } } @@ -29,10 +33,14 @@ pub struct EventInterpreter; impl Interpreter for EventInterpreter { fn interpret(&mut self, event: Event, ctx: &Sender) { info!("Event interpreter: {:?}", event); - let _ = match event { + match event { + Event::FoundInstalledPackages(pkgs) => { + info!("Installed packages: {:?}", pkgs); + } + Event::NotAuthenticated => { - debug!("trying to authenticate again..."); - ctx.send(Command::Authenticate(None)) + debug!("Trying to authenticate again..."); + ctx.send(Command::Authenticate(None)); } /* TODO: Handle PackageManger events @@ -47,51 +55,89 @@ impl Interpreter for EventInterpreter { } */ - _ => Ok(()) - }.map_err(|err| panic!("couldn't interpret event: {}", err)); + _ => () + } } } pub struct CommandInterpreter; -impl Interpreter for CommandInterpreter { - fn interpret(&mut self, cmd: Command, wtx: &Sender) { +impl Interpreter for CommandInterpreter { + fn interpret(&mut self, cmd: Command, gtx: &Sender) { info!("Command interpreter: {:?}", cmd); - let _ = wtx.send(Wrapped { cmd: cmd, etx: None }) - .map_err(|err| panic!("couldn't forward command: {}", err)); + gtx.send(Global { command: cmd, response_tx: None }); } } -pub type Wrapped = Interpret; +pub type Global = Interpret; -impl Wrapped { - fn publish(&self, ev: Event) { - if let Some(ref etx) = self.etx { - let _ = etx.lock().unwrap().send(ev).map_err(|err| panic!("couldn't publish event: {}", err)); - } - } +pub struct GlobalInterpreter<'t> { + pub config: Config, + pub token: Option>, + pub http_client: Box, + pub loopback_tx: Sender, + pub shutdown_tx: Sender<()>, } +impl<'t> Interpreter for GlobalInterpreter<'t> { + fn interpret(&mut self, global: Global, etx: &Sender) { + info!("Global interpreter started: {:?}", global.command); + let response = |ev: Event| { + if let Some(ref response_tx) = global.response_tx { + response_tx.lock().unwrap().send(ev) + } + }; + + let (multi_tx, multi_rx) = chan::async::(); + let outcome = match self.token { + Some(_) => self.authenticated(global.command.clone(), multi_tx), + None => self.unauthenticated(global.command.clone(), multi_tx) + }; + + let mut response_ev: Option = None; + match outcome { + Ok(_) => { + for ev in multi_rx { + etx.send(ev.clone()); + response_ev = Some(ev); + } + info!("Global interpreter success: {:?}", global.command); + } + + Err(Error::AuthorizationError(_)) => { + let ev = Event::NotAuthenticated; + etx.send(ev.clone()); + response_ev = Some(ev); + error!("Global interpreter authentication failed: {:?}", global.command); + } + + Err(err) => { + let ev = Event::Error(format!("{}", err)); + etx.send(ev.clone()); + response_ev = Some(ev); + error!("Global interpreter failed: {:?}: {}", global.command, err); + } + } -pub struct WrappedInterpreter<'t> { - pub config: Config, - pub token: Option>, - pub client: Box, - pub loopback: Sender, + match response_ev { + Some(ev) => response(ev), + None => panic!("no response event to send back") + }; + } } -impl<'t> WrappedInterpreter<'t> { +impl<'t> GlobalInterpreter<'t> { fn authenticated(&self, cmd: Command, etx: Sender) -> Result<(), Error> { - let mut ota = OTA::new(&self.config, self.client.as_ref()); + let mut ota = OTA::new(&self.config, self.http_client.as_ref()); // always send at least one Event response match cmd { AcceptUpdates(ids) => { for id in ids { - info!("Accepting id {}", id); - try!(etx.send(Event::UpdateStateChanged(id.clone(), UpdateState::Downloading))); + info!("Accepting ID: {}", id); + etx.send(Event::UpdateStateChanged(id.clone(), UpdateState::Downloading)); let report = try!(ota.install_package_update(&id, &etx)); try!(ota.send_install_report(&report)); info!("Install Report for {}: {:?}", id, report); @@ -99,7 +145,7 @@ impl<'t> WrappedInterpreter<'t> { } } - Authenticate(_) => try!(etx.send(Event::Ok)), + Authenticate(_) => etx.send(Event::Ok), GetPendingUpdates => { let mut updates = try!(ota.get_package_updates()); @@ -107,22 +153,24 @@ impl<'t> WrappedInterpreter<'t> { updates.sort_by_key(|u| u.installPos); info!("New package updates available: {:?}", updates); let ids: Vec = updates.iter().map(|u| u.requestId.clone()).collect(); - let w = Wrapped { cmd: Command::AcceptUpdates(ids), etx: None }; - try!(self.loopback.send(w)) + self.loopback_tx.send(Global { command: Command::AcceptUpdates(ids), response_tx: None }); } - try!(etx.send(Event::Ok)); + etx.send(Event::Ok); } ListInstalledPackages => { let pkgs = try!(self.config.ota.package_manager.installed_packages()); - try!(etx.send(Event::FoundInstalledPackages(pkgs))) + etx.send(Event::FoundInstalledPackages(pkgs)); } - Shutdown => exit(0), + Shutdown => { + self.shutdown_tx.send(()); + etx.send(Event::Ok); + } UpdateInstalledPackages => { try!(ota.update_installed_packages()); - try!(etx.send(Event::Ok)); + etx.send(Event::Ok); info!("Posted installed packages to the server.") } } @@ -133,74 +181,32 @@ impl<'t> WrappedInterpreter<'t> { fn unauthenticated(&mut self, cmd: Command, etx: Sender) -> Result<(), Error> { match cmd { Authenticate(_) => { - let token = try!(authenticate(&self.config.auth, self.client.as_ref())); + let auth = Auth::Credentials(ClientId(self.config.auth.client_id.clone()), + ClientSecret(self.config.auth.secret.clone())); + self.set_client(auth); + let token = try!(authenticate(&self.config.auth, self.http_client.as_ref())); + self.set_client(Auth::Token(token.clone())); self.token = Some(token.into()); - try!(etx.send(Event::Authenticated)); + etx.send(Event::Authenticated); } AcceptUpdates(_) | GetPendingUpdates | ListInstalledPackages | - UpdateInstalledPackages => try!(etx.send(Event::NotAuthenticated)), + UpdateInstalledPackages => etx.send(Event::NotAuthenticated), - Shutdown => exit(0), + Shutdown => { + self.shutdown_tx.send(()); + etx.send(Event::Ok); + } } Ok(()) } -} - -impl<'t> Interpreter for WrappedInterpreter<'t> { - fn interpret(&mut self, w: Wrapped, global_tx: &Sender) { - info!("Wrapped interpreter: {:?}", w.cmd); - let broadcast = |ev: Event| { - let _ = global_tx.send(ev).map_err(|err| panic!("couldn't broadcast event: {}", err)); - }; - - let (etx, erx): (Sender, Receiver) = channel(); - let outcome = match self.token.to_owned() { - Some(token) => { - if !self.client.is_testing() { - self.client = Box::new(AuthClient::new(Auth::Token(token.into_owned()))); - } - self.authenticated(w.cmd.clone(), etx) - } - - None => { - if !self.client.is_testing() { - self.client = Box::new(AuthClient::new(Auth::Credentials( - ClientId(self.config.auth.client_id.clone()), - ClientSecret(self.config.auth.secret.clone())))); - } - self.unauthenticated(w.cmd.clone(), etx) - } - }; - - match outcome { - Ok(_) => { - let mut last_ev = None; - for ev in erx { - broadcast(ev.clone()); - last_ev = Some(ev); - } - match last_ev { - Some(ev) => w.publish(ev), - None => panic!("no local event to send back") - }; - } - - Err(Error::AuthorizationError(_)) => { - debug!("retry authorization and request"); - let a = Wrapped { cmd: Command::Authenticate(None), etx: None }; - let _ = self.loopback.send(a).map_err(|err| panic!("couldn't retry authentication: {}", err)); - let _ = self.loopback.send(w).map_err(|err| panic!("couldn't retry request: {}", err)); - } - Err(err) => { - let ev = Event::Error(format!("{}", err)); - broadcast(ev.clone()); - w.publish(ev); - } + fn set_client(&mut self, auth: Auth) { + if !self.http_client.is_testing() { + self.http_client = Box::new(AuthClient::new(auth)); } } } @@ -208,8 +214,9 @@ impl<'t> Interpreter for WrappedInterpreter<'t> { #[cfg(test)] mod tests { + use chan; + use chan::{Sender, Receiver}; use std::thread; - use std::sync::mpsc::{channel, Sender, Receiver}; use super::*; use datatype::{AccessToken, Command, Config, Event, UpdateState}; @@ -219,22 +226,25 @@ mod tests { fn new_interpreter(replies: Vec, pkg_mgr: PackageManager) -> (Sender, Receiver) { - let (ctx, crx): (Sender, Receiver) = channel(); - let (etx, erx): (Sender, Receiver) = channel(); - let (wtx, _): (Sender, Receiver) = channel(); + let (etx, erx) = chan::sync::(0); + let (ctx, crx) = chan::sync::(0); + let (gtx, _) = chan::sync::(0); + let (shutdown_tx, _) = chan::sync::<()>(0); thread::spawn(move || { - let mut wi = WrappedInterpreter { - config: Config::default(), - token: Some(AccessToken::default().into()), - client: Box::new(TestHttpClient::from(replies)), - loopback: wtx, + let mut wi = GlobalInterpreter { + config: Config::default(), + token: Some(AccessToken::default().into()), + http_client: Box::new(TestHttpClient::from(replies)), + loopback_tx: gtx, + shutdown_tx: shutdown_tx }; wi.config.ota.package_manager = pkg_mgr; + loop { - match crx.recv().expect("couldn't receive cmd") { - Command::Shutdown => break, - cmd @ _ => wi.interpret(Wrapped { cmd: cmd, etx: None }, &etx) + match crx.recv() { + Some(cmd) => wi.interpret(Global { command: cmd, response_tx: None }, &etx), + None => break } } }); @@ -244,20 +254,21 @@ mod tests { #[test] fn already_authenticated() { - let (ctx, erx) = new_interpreter(Vec::new(), PackageManager::new_file(true)); - ctx.send(Command::Authenticate(None)).unwrap(); - for ev in erx.recv() { - assert_eq!(ev, Event::Ok); - } - ctx.send(Command::Shutdown).unwrap(); + let replies = Vec::new(); + let pkg_mgr = PackageManager::new_file(true); + let (ctx, erx) = new_interpreter(replies, pkg_mgr); + + ctx.send(Command::Authenticate(None)); + assert_rx(erx, &[Event::Ok]); } #[test] fn accept_updates() { let replies = vec!["[]".to_string(); 10]; - let (ctx, erx) = new_interpreter(replies, PackageManager::new_file(true)); + let pkg_mgr = PackageManager::new_file(true); + let (ctx, erx) = new_interpreter(replies, pkg_mgr); - ctx.send(Command::AcceptUpdates(vec!["1".to_string(), "2".to_string()])).unwrap(); + ctx.send(Command::AcceptUpdates(vec!["1".to_string(), "2".to_string()])); assert_rx(erx, &[ Event::UpdateStateChanged("1".to_string(), UpdateState::Downloading), Event::UpdateStateChanged("1".to_string(), UpdateState::Installing), @@ -266,7 +277,6 @@ mod tests { Event::UpdateStateChanged("2".to_string(), UpdateState::Installing), Event::UpdateStateChanged("2".to_string(), UpdateState::Installed), ]); - ctx.send(Command::Shutdown).unwrap(); } #[test] @@ -275,10 +285,7 @@ mod tests { let pkg_mgr = PackageManager::new_file(false); let (ctx, erx) = new_interpreter(replies, pkg_mgr); - ctx.send(Command::AcceptUpdates(vec!["1".to_string()])).unwrap(); - assert_rx(erx, &[ - Event::Error("IO error: No such file or directory (os error 2)".to_owned()), - ]); - ctx.send(Command::Shutdown).unwrap(); + ctx.send(Command::AcceptUpdates(vec!["1".to_string()])); + assert_rx(erx, &[Event::Error("IO error: No such file or directory (os error 2)".to_owned())]); } } diff --git a/src/lib.rs b/src/lib.rs index d141170..89e7b80 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,3 +1,4 @@ +#[macro_use] extern crate chan; extern crate crossbeam; extern crate hyper; #[macro_use] extern crate nom; diff --git a/src/main.rs b/src/main.rs index bba5132..6233001 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,4 +1,4 @@ -extern crate chan; +#[macro_use] extern crate chan; extern crate chan_signal; extern crate crossbeam; extern crate env_logger; @@ -10,14 +10,12 @@ extern crate rustc_serialize; extern crate time; extern crate ws; -use chan::Receiver as ChanReceiver; +use chan::{Sender, Receiver}; use chan_signal::Signal; use env_logger::LogBuilder; use getopts::Options; use log::LogRecord; use std::env; -use std::sync::mpsc::{Sender, Receiver, channel}; -use std::thread; use std::time::Duration; use libotaplus::datatype::{config, Auth, Command, Config, Event, Url}; @@ -25,85 +23,102 @@ use libotaplus::http_client::AuthClient; use libotaplus::interaction_library::{Console, Gateway, Http, Websocket}; use libotaplus::interaction_library::broadcast::Broadcast; use libotaplus::interpreter::{EventInterpreter, CommandInterpreter, Interpreter, - Wrapped, WrappedInterpreter}; + Global, GlobalInterpreter}; use libotaplus::package_manager::PackageManager; -fn spawn_signal_handler(signals: ChanReceiver, ctx: Sender) { +fn spawn_signal_handler(signals: Receiver, ctx: Sender, shutdown_rx: Receiver<()>) { loop { - match signals.recv() { - Some(Signal::TERM) | Some(Signal::INT) => { - ctx.send(Command::Shutdown).expect("send failed.") - } - _ => {} + chan_select! { + shutdown_rx.recv() => std::process::exit(0), + signals.recv() -> sig => match sig { + Some(Signal::INT) => ctx.send(Command::Shutdown), + Some(Signal::TERM) => ctx.send(Command::Shutdown), + _ => () + }, } } } -fn spawn_update_poller(ctx: Sender, interval: u64) { +fn spawn_update_poller(interval: u64, ctx: Sender, shutdown_rx: Receiver<()>) { + let tick = chan::tick(Duration::from_secs(interval)); loop { - thread::sleep(Duration::from_secs(interval)); - let _ = ctx.send(Command::GetPendingUpdates); + chan_select! { + shutdown_rx.recv() => break, + tick.recv() => ctx.send(Command::GetPendingUpdates), + } } } fn perform_initial_sync(ctx: &Sender) { - let _ = ctx.send(Command::Authenticate(None)); - let _ = ctx.send(Command::UpdateInstalledPackages); + ctx.send(Command::Authenticate(None)); + ctx.send(Command::UpdateInstalledPackages); } fn main() { setup_logging(); let config = build_config(); - let (ctx, crx): (Sender, Receiver) = channel(); - let (etx, erx): (Sender, Receiver) = channel(); - let (wtx, wrx): (Sender, Receiver) = channel(); + let (etx, erx) = chan::async::(); + let (ctx, crx) = chan::async::(); + let (gtx, grx) = chan::async::(); + let (shutdown_tx, shutdown_rx) = chan::sync::<()>(0); + + let mut broadcast = Broadcast::new(erx); + let mut shutdown = Broadcast::new(shutdown_rx); - let mut broadcast: Broadcast = Broadcast::new(erx); perform_initial_sync(&ctx); crossbeam::scope(|scope| { // Must subscribe to the signal before spawning ANY other threads - let signals = chan_signal::notify(&[Signal::INT, Signal::TERM]); - let sig_ctx = ctx.clone(); - scope.spawn(move || spawn_signal_handler(signals, sig_ctx)); - - let poll_ctx = ctx.clone(); - let poll_interval = config.ota.polling_interval; - scope.spawn(move || spawn_update_poller(poll_ctx, poll_interval)); + let signals = chan_signal::notify(&[Signal::INT, Signal::TERM]); + let signal_ctx = ctx.clone(); + let signal_shutdown = shutdown.subscribe(); + scope.spawn(move || spawn_signal_handler(signals, signal_ctx, signal_shutdown)); - let ws_wtx = wtx.clone(); - let ws_sub = broadcast.subscribe(); - scope.spawn(move || Websocket::run(ws_wtx, ws_sub)); + let ws_gtx = gtx.clone(); + let ws_event = broadcast.subscribe(); + let ws_shutdown = shutdown.subscribe(); + scope.spawn(move || Websocket::run(ws_gtx, ws_event, ws_shutdown)); if config.test.http { - let http_wtx = wtx.clone(); - let http_sub = broadcast.subscribe(); - scope.spawn(move || Http::run(http_wtx, http_sub)); + let http_gtx = gtx.clone(); + let http_event = broadcast.subscribe(); + let http_shutdown = shutdown.subscribe(); + scope.spawn(move || Http::run(http_gtx, http_event, http_shutdown)); } if config.test.looping { - println!("OTA Plus Client REPL started."); - let cons_wtx = wtx.clone(); - let cons_sub = broadcast.subscribe(); - scope.spawn(move || Console::run(cons_wtx, cons_sub)); + let repl_gtx = gtx.clone(); + let repl_event = broadcast.subscribe(); + let repl_shutdown = shutdown.subscribe(); + scope.spawn(move || Console::run(repl_gtx, repl_event, repl_shutdown)); } - let event_sub = broadcast.subscribe(); - let event_ctx = ctx.clone(); - scope.spawn(move || EventInterpreter.run(event_sub, event_ctx)); - - let cmd_wtx = wtx.clone(); - scope.spawn(move || CommandInterpreter.run(crx, cmd_wtx)); + let event_subscribe = broadcast.subscribe(); + let event_ctx = ctx.clone(); + let event_shutdown = shutdown.subscribe(); + scope.spawn(move || EventInterpreter.run(event_subscribe, event_ctx, event_shutdown)); + + let cmd_gtx = gtx.clone(); + let cmd_shutdown = shutdown.subscribe(); + scope.spawn(move || CommandInterpreter.run(crx, cmd_gtx, cmd_shutdown)); + + let poll_interval = config.ota.polling_interval; + let global_shutdown = shutdown.subscribe(); + scope.spawn(move || GlobalInterpreter { + config: config, + token: None, + http_client: Box::new(AuthClient::new(Auth::None)), + loopback_tx: gtx, + shutdown_tx: shutdown_tx, + }.run(grx, etx, global_shutdown)); - scope.spawn(move || WrappedInterpreter { - config: config, - token: None, - client: Box::new(AuthClient::new(Auth::None)), - loopback: wtx, - }.run(wrx, etx)); + let poll_ctx = ctx.clone(); + let poll_shutdown = shutdown.subscribe(); + scope.spawn(move || spawn_update_poller(poll_interval, poll_ctx, poll_shutdown)); + scope.spawn(move || shutdown.start()); scope.spawn(move || broadcast.start()); }); } diff --git a/src/oauth2.rs b/src/oauth2.rs index c9e3c34..2c9ad2a 100644 --- a/src/oauth2.rs +++ b/src/oauth2.rs @@ -13,12 +13,16 @@ pub fn authenticate(config: &AuthConfig, client: &HttpClient) -> Result { + let data = try!(resp); + let body = try!(String::from_utf8(data)); + debug!("authenticate, body: `{}`", body); + Ok(try!(json::decode(&body))) + }, - debug!("authenticate, body: `{}`", body); - Ok(try!(json::decode(&body))) + None => panic!("no authenticate response received") + } } diff --git a/src/ota_plus.rs b/src/ota_plus.rs index c139248..21560ce 100644 --- a/src/ota_plus.rs +++ b/src/ota_plus.rs @@ -1,8 +1,8 @@ +use chan::Sender; use rustc_serialize::json; use std::fs::File; use std::io; use std::path::PathBuf; -use std::sync::mpsc::Sender; use datatype::{Config, Error, Event, Method, PendingUpdateRequest, UpdateRequestId, UpdateReport, UpdateReportWithVin, @@ -36,12 +36,15 @@ impl<'c, 'h> OTA<'c, 'h> { url: self.update_endpoint(""), body: None, }); + match resp_rx.recv() { + Some(resp) => { + let data = try!(resp); + let text = try!(String::from_utf8(data)); + Ok(try!(json::decode::>(&text))) + } - let resp = try!(resp_rx.recv()); - let data = try!(resp); - let text = try!(String::from_utf8(data)); - - Ok(try!(json::decode::>(&text))) + None => panic!("no get_package_updates response received") + } } pub fn download_package_update(&mut self, id: &UpdateRequestId) -> Result { @@ -59,12 +62,16 @@ impl<'c, 'h> OTA<'c, 'h> { // TODO: Do not invoke package_manager path.set_extension(self.config.ota.package_manager.extension()); - let resp = try!(resp_rx.recv()); - let data = try!(resp); - let mut file = try!(File::create(path.as_path())); - let _ = io::copy(&mut &*data, &mut file); + match resp_rx.recv() { + Some(resp) => { + let data = try!(resp); + let mut file = try!(File::create(path.as_path())); + let _ = io::copy(&mut &*data, &mut file); + Ok(path) + } - Ok(path) + None => panic!("no download_package_update response received") + } } pub fn install_package_update(&mut self, id: &UpdateRequestId, etx: &Sender) @@ -95,7 +102,7 @@ impl<'c, 'h> OTA<'c, 'h> { } Err(err) => { - try!(etx.send(Event::UpdateErrored(id.clone(), format!("{:?}", err)))); + etx.send(Event::UpdateErrored(id.clone(), format!("{:?}", err))); let failed = format!("Download failed: {:?}", err); Ok(UpdateReport::new(id.clone(), UpdateResultCode::GENERAL_ERROR, failed)) } @@ -116,12 +123,16 @@ impl<'c, 'h> OTA<'c, 'h> { body: Some(body.into_bytes()), }); - let resp = try!(resp_rx.recv()); - let data = try!(resp); - let text = try!(String::from_utf8(data)); - let _ = try!(json::decode::>(&text)); + match resp_rx.recv() { + Some(resp) => { + let data = try!(resp); + let text = try!(String::from_utf8(data)); + let _ = try!(json::decode::>(&text)); + Ok(()) + } - Ok(()) + None => panic!("no update_installed_packages response received") + } } pub fn send_install_report(&mut self, report: &UpdateReport) -> Result<(), Error> { @@ -142,7 +153,7 @@ impl<'c, 'h> OTA<'c, 'h> { #[cfg(test)] mod tests { - use std::sync::mpsc::channel; + use chan; use rustc_serialize::json; use super::*; @@ -191,7 +202,7 @@ mod tests { config: &Config::default(), client: &mut TestHttpClient::new(), }; - let (tx, rx) = channel(); + let (tx, rx) = chan::async(); let report = ota.install_package_update(&"0".to_string(), &tx); assert_eq!(report.unwrap().operation_results.pop().unwrap().result_code, UpdateResultCode::GENERAL_ERROR); @@ -212,7 +223,7 @@ mod tests { config: &config, client: &mut TestHttpClient::from(vec!["".to_string()]), }; - let (tx, rx) = channel(); + let (tx, rx) = chan::async(); let report = ota.install_package_update(&"0".to_string(), &tx); assert_eq!(report.unwrap().operation_results.pop().unwrap().result_code, UpdateResultCode::INSTALL_FAILED); @@ -238,7 +249,7 @@ mod tests { config: &config, client: &mut TestHttpClient::from(replies), }; - let (tx, rx) = channel(); + let (tx, rx) = chan::async(); let report = ota.install_package_update(&"0".to_string(), &tx); assert_eq!(report.unwrap().operation_results.pop().unwrap().result_code, UpdateResultCode::OK); diff --git a/src/package_manager/package_manager.rs b/src/package_manager/package_manager.rs index a31d303..1833eab 100644 --- a/src/package_manager/package_manager.rs +++ b/src/package_manager/package_manager.rs @@ -27,10 +27,6 @@ impl PackageManager { } } - pub fn from_file(filename: String, succeeds: bool) -> Self { - PackageManager::File { filename: filename, succeeds: succeeds } - } - pub fn installed_packages(&self) -> Result, Error> { match *self { PackageManager::Dpkg => dpkg::installed_packages(), @@ -58,15 +54,13 @@ impl PackageManager { impl Decodable for PackageManager { fn decode(d: &mut D) -> Result { - d.read_str().and_then(|s| parse_package_manager(s).map_err(|e| d.error(&e))) - } -} - -fn parse_package_manager(s: String) -> Result { - match s.to_lowercase().as_str() { - "dpkg" => Ok(PackageManager::Dpkg), - "rpm" => Ok(PackageManager::Rpm), - _ => Ok(PackageManager::File { filename: s.to_string(), succeeds: true }), + d.read_str().and_then(|s| { + match s.to_lowercase().as_str() { + "dpkg" => Ok(PackageManager::Dpkg), + "rpm" => Ok(PackageManager::Rpm), + _ => Ok(PackageManager::File { filename: s.to_string(), succeeds: true }), + } + }) } } diff --git a/src/package_manager/tpm.rs b/src/package_manager/tpm.rs index aa17800..4c7da4c 100644 --- a/src/package_manager/tpm.rs +++ b/src/package_manager/tpm.rs @@ -1,10 +1,10 @@ +use chan::Receiver; use std::fmt::Debug; use std::fs::File; use std::fs::OpenOptions; use std::io::BufReader; use std::io::BufWriter; use std::io::prelude::*; -use std::sync::mpsc::Receiver; use datatype::{Error, Package, UpdateResultCode}; use package_manager::package_manager::InstallOutcome; -- cgit v1.2.1 From 0b892dd971f0abc51de4f2327ffe21b3794e2f35 Mon Sep 17 00:00:00 2001 From: Shaun Taheri Date: Tue, 21 Jun 2016 15:21:36 +0200 Subject: Read and set ContentLength Header --- src/http_client/auth_client.rs | 9 +++++++-- src/interaction_library/http.rs | 8 +++++--- 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/src/http_client/auth_client.rs b/src/http_client/auth_client.rs index 62850c3..98b2cbc 100644 --- a/src/http_client/auth_client.rs +++ b/src/http_client/auth_client.rs @@ -190,8 +190,13 @@ impl Handler for AuthHandler { } if resp.status().is_success() { - self.written = 0; - Next::read() + if let Some(len) = resp.headers().get::() { + if **len > 0 { + return Next::read(); + } + } + self.resp_tx.send(Ok(Vec::new())); + Next::end() } else if resp.status().is_redirection() { self.redirect_request(resp); Next::end() diff --git a/src/interaction_library/http.rs b/src/interaction_library/http.rs index 5f06620..ac0b02a 100644 --- a/src/interaction_library/http.rs +++ b/src/interaction_library/http.rs @@ -1,7 +1,7 @@ use chan; use chan::{Sender, Receiver}; use hyper::{Decoder, Encoder, Next, StatusCode}; -use hyper::header::ContentType; +use hyper::header::{ContentLength, ContentType}; use hyper::mime::{Attr, Mime, TopLevel, SubLevel, Value}; use hyper::net::HttpStream; use hyper::server::{Handler, Server, Request, Response}; @@ -131,8 +131,10 @@ impl Handler for HttpHandler Some(e) => match json::encode(&e) { Ok(body) => { resp.set_status(StatusCode::Ok); - resp.headers_mut().set(ContentType(Mime(TopLevel::Application, SubLevel::Json, - vec![(Attr::Charset, Value::Utf8)]))); + let mut headers = resp.headers_mut(); + headers.set(ContentType(Mime(TopLevel::Application, SubLevel::Json, + vec![(Attr::Charset, Value::Utf8)]))); + headers.set(ContentLength(body.len() as u64)); self.resp_body = Some(body.into_bytes()); Next::write() } -- cgit v1.2.1 From e024058e23478638ad5899c6a10e9016521fd022 Mon Sep 17 00:00:00 2001 From: Shaun Taheri Date: Tue, 21 Jun 2016 15:28:52 +0200 Subject: Don't wait for installed packages response --- src/ota_plus.rs | 15 ++------------- 1 file changed, 2 insertions(+), 13 deletions(-) diff --git a/src/ota_plus.rs b/src/ota_plus.rs index 21560ce..10751ec 100644 --- a/src/ota_plus.rs +++ b/src/ota_plus.rs @@ -116,23 +116,12 @@ impl<'c, 'h> OTA<'c, 'h> { let pkgs = try!(self.config.ota.package_manager.installed_packages()); let body = try!(json::encode(&pkgs)); debug!("installed packages: {}", body); - - let resp_rx = self.client.send_request(HttpRequest { + let _ = self.client.send_request(HttpRequest { method: Method::Put, url: self.update_endpoint("installed"), body: Some(body.into_bytes()), }); - - match resp_rx.recv() { - Some(resp) => { - let data = try!(resp); - let text = try!(String::from_utf8(data)); - let _ = try!(json::decode::>(&text)); - Ok(()) - } - - None => panic!("no update_installed_packages response received") - } + Ok(()) } pub fn send_install_report(&mut self, report: &UpdateReport) -> Result<(), Error> { -- cgit v1.2.1 From d7bc3cb0c17f5359a59f1787073941c5fa4e9f1b Mon Sep 17 00:00:00 2001 From: Shaun Taheri Date: Tue, 21 Jun 2016 15:54:27 +0200 Subject: Fix build on Yocto 1.7.0 --- Cargo.lock | 6 +++--- src/interaction_library/gateway.rs | 5 ++++- src/interaction_library/http.rs | 12 ++++++------ 3 files changed, 13 insertions(+), 10 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 84f2e68..147c297 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -237,7 +237,7 @@ dependencies = [ "libc 0.2.12 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", "miow 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", - "net2 0.2.24 (registry+https://github.com/rust-lang/crates.io-index)", + "net2 0.2.23 (registry+https://github.com/rust-lang/crates.io-index)", "nix 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", "slab 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", "time 0.1.35 (registry+https://github.com/rust-lang/crates.io-index)", @@ -250,14 +250,14 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", - "net2 0.2.24 (registry+https://github.com/rust-lang/crates.io-index)", + "net2 0.2.23 (registry+https://github.com/rust-lang/crates.io-index)", "winapi 0.2.7 (registry+https://github.com/rust-lang/crates.io-index)", "ws2_32-sys 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "net2" -version = "0.2.24" +version = "0.2.23" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "cfg-if 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", diff --git a/src/interaction_library/gateway.rs b/src/interaction_library/gateway.rs index a698fc8..55fd527 100644 --- a/src/interaction_library/gateway.rs +++ b/src/interaction_library/gateway.rs @@ -7,7 +7,10 @@ use super::broadcast::Broadcast; #[derive(Clone, Debug)] -pub struct Interpret { +pub struct Interpret + where C: Send + Clone + Debug + 'static, + E: Send + Clone + Debug + 'static, +{ pub command: C, pub response_tx: Option>>>, } diff --git a/src/interaction_library/http.rs b/src/interaction_library/http.rs index ac0b02a..5d32b17 100644 --- a/src/interaction_library/http.rs +++ b/src/interaction_library/http.rs @@ -36,8 +36,8 @@ impl Gateway for Http pub struct HttpHandler - where C: Decodable + Send + Clone + Debug, - E: Encodable + Send + Clone + Debug + where C: Decodable + Send + Clone + Debug + 'static, + E: Encodable + Send + Clone + Debug + 'static { forward_tx: Arc>>>, response_rx: Option>, @@ -47,8 +47,8 @@ pub struct HttpHandler } impl HttpHandler - where C: Decodable + Send + Clone + Debug, - E: Encodable + Send + Clone + Debug + where C: Decodable + Send + Clone + Debug + 'static, + E: Encodable + Send + Clone + Debug + 'static { fn new(forward_tx: Arc>>>) -> HttpHandler { HttpHandler { @@ -91,8 +91,8 @@ impl HttpHandler } impl Handler for HttpHandler - where C: Decodable + Send + Clone + Debug, - E: Encodable + Send + Clone + Debug + where C: Decodable + Send + Clone + Debug + 'static, + E: Encodable + Send + Clone + Debug + 'static { fn on_request(&mut self, req: Request) -> Next { info!("on_request: {} {}", req.method(), req.uri()); -- cgit v1.2.1 From ac9a95ace9f99c0026434f7906160a8190168d4c Mon Sep 17 00:00:00 2001 From: Shaun Taheri Date: Tue, 21 Jun 2016 18:21:27 +0200 Subject: Kill client on Gateway start failure --- pkg/ota.toml.template | 2 +- pkg/provision/start-up.sh | 9 ++++- src/interaction_library/console.rs | 33 ++++++---------- src/interaction_library/gateway.rs | 23 +++++------- src/interaction_library/http.rs | 24 ++++++------ src/interaction_library/websocket.rs | 52 ++++++++++++++----------- src/interpreter.rs | 31 +++++---------- src/main.rs | 73 +++++++++++++----------------------- src/ota_plus.rs | 35 ++++++----------- 9 files changed, 121 insertions(+), 161 deletions(-) diff --git a/pkg/ota.toml.template b/pkg/ota.toml.template index 67aac01..156bf50 100644 --- a/pkg/ota.toml.template +++ b/pkg/ota.toml.template @@ -2,7 +2,7 @@ server = "${OTA_AUTH_URL}" client_id = "${OTA_AUTH_CLIENT_ID}" secret = "${OTA_AUTH_SECRET}" -credentials_file = "/opt/ats/credentials.toml" +credentials_file = "${OTA_CREDENTIALS_FILE}" vin = "${OTA_CLIENT_VIN}" [ota] diff --git a/pkg/provision/start-up.sh b/pkg/provision/start-up.sh index 59465f3..3d36086 100755 --- a/pkg/provision/start-up.sh +++ b/pkg/provision/start-up.sh @@ -10,6 +10,12 @@ export OTA_WEB_USER="${OTA_WEB_USER-demo@advancedtelematic.com}" export OTA_WEB_PASSWORD="${OTA_WEB_PASSWORD-demo}" export OTA_HTTP=${OTA_HTTP-false} +if [[ -n $PROVISION ]]; then + export OTA_CREDENTIALS_FILE=${OTA_CREDENTIALS_FILE-credentials.toml} +else + export OTA_CREDENTIALS_FILE=${OTA_CREDENTIALS_FILE-/opt/ats/credentials.toml} +fi + TEMPLATE_PATH=${TEMPLATE_PATH-'/etc/ota.toml.template'} AUTH_JSON_PATH=${AUTH_JSON_PATH-'/etc/auth.json'} OUTPUT_PATH=${OUTPUT_PATH-/etc/ota.toml} @@ -39,8 +45,7 @@ SECRET=$(echo $AUTH_DATA | jq -r .client_secret) export OTA_AUTH_CLIENT_ID=${OTA_AUTH_CLIENT_ID-$CLIENT_ID} export OTA_AUTH_SECRET=${OTA_AUTH_SECRET-$SECRET} -if [[ -n $PROVISION ]] -then +if [[ -n $PROVISION ]]; then OTA_TOML=$(cat $TEMPLATE_PATH | envsubst ) echo "$OTA_TOML" else diff --git a/src/interaction_library/console.rs b/src/interaction_library/console.rs index 4b7faa2..419eb45 100644 --- a/src/interaction_library/console.rs +++ b/src/interaction_library/console.rs @@ -1,5 +1,5 @@ use chan; -use chan::{Sender, Receiver}; +use chan::Sender; use std::{io, thread}; use std::fmt::Debug; use std::io::Write; @@ -7,7 +7,6 @@ use std::str::FromStr; use std::string::ToString; use std::sync::{Arc, Mutex}; -use super::broadcast::Broadcast; use super::gateway::{Gateway, Interpret}; @@ -18,45 +17,37 @@ impl Gateway for Console E: ToString + Send + Clone + Debug + 'static, ::Err: Debug, { - fn new(itx: Sender>, shutdown_rx: Receiver<()>) -> Self { - let mut shutdown = Broadcast::new(shutdown_rx); - let (etx, erx) = chan::sync::(0); - let etx = Arc::new(Mutex::new(etx)); + fn new(itx: Sender>) -> Result { + let (etx, erx) = chan::sync::(0); + let etx = Arc::new(Mutex::new(etx)); - let stop_tx = shutdown.subscribe(); thread::spawn(move || { loop { - chan_select! { - default => match parse_input(get_input()) { - Ok(cmd) => itx.send(Interpret{ command: cmd, response_tx: Some(etx.clone()) }), - Err(err) => println!("(error): {:?}", err) - }, - stop_tx.recv() => break + match parse_input(get_input()) { + Ok(cmd) => itx.send(Interpret{ command: cmd, response_tx: Some(etx.clone()) }), + Err(err) => error!("Console Error: {:?}", err) } } }); - let stop_rx = shutdown.subscribe(); thread::spawn(move || { loop { - chan_select! { - erx.recv() -> e => match e { - Some(e) => println!("(event): {}", e.to_string()), - None => panic!("all console event transmitters are closed") - }, - stop_rx.recv() => break + match erx.recv() { + Some(e) => info!("Console Response: {}", e.to_string()), + None => panic!("all console event transmitters are closed") } } }); println!("OTA Plus Client REPL started."); - Console + Ok(Console) } } fn get_input() -> String { let mut input = String::new(); let _ = io::stdout().write("> ".as_bytes()); + io::stdout().flush().expect("couldn't flush console stdout buffer"); let _ = io::stdin().read_line(&mut input); input } diff --git a/src/interaction_library/gateway.rs b/src/interaction_library/gateway.rs index 55fd527..a4fb96c 100644 --- a/src/interaction_library/gateway.rs +++ b/src/interaction_library/gateway.rs @@ -1,10 +1,9 @@ use chan::{Sender, Receiver}; +use std; use std::thread; use std::fmt::Debug; use std::sync::{Arc, Mutex}; -use super::broadcast::Broadcast; - #[derive(Clone, Debug)] pub struct Interpret @@ -19,21 +18,19 @@ pub trait Gateway: Sized + Send + Sync + 'static where C: Send + Clone + Debug + 'static, E: Send + Clone + Debug + 'static, { - fn new(itx: Sender>, shutdown_rx: Receiver<()>) -> Self; + fn new(itx: Sender>) -> Result; - fn run(itx: Sender>, erx: Receiver, shutdown_rx: Receiver<()>) { - let mut shutdown = Broadcast::new(shutdown_rx); - let gateway = Self::new(itx, shutdown.subscribe()); + fn run(itx: Sender>, erx: Receiver) { + let gateway = Self::new(itx).unwrap_or_else(|err| { + error!("couldn't start gateway: {}", err); + std::process::exit(1); + }); - let stop_pulse = shutdown.subscribe(); thread::spawn(move || { loop { - chan_select! { - stop_pulse.recv() => break, - erx.recv() -> e => match e { - Some(e) => gateway.pulse(e), - None => panic!("all gateway event transmitters are closed") - } + match erx.recv() { + Some(e) => gateway.pulse(e), + None => panic!("all gateway event transmitters are closed") } } }); diff --git a/src/interaction_library/http.rs b/src/interaction_library/http.rs index 5d32b17..8cf7370 100644 --- a/src/interaction_library/http.rs +++ b/src/interaction_library/http.rs @@ -21,16 +21,19 @@ impl Gateway for Http where C: Decodable + Send + Clone + Debug + 'static, E: Encodable + Send + Clone + Debug + 'static { - fn new(itx: Sender>, _: Receiver<()>) -> Self { + fn new(itx: Sender>) -> Result { let itx = Arc::new(Mutex::new(itx)); let addr = env::var("OTA_PLUS_CLIENT_HTTP_ADDR").unwrap_or("127.0.0.1:8888".to_string()); - let server = Server::http(&addr.parse().unwrap()).unwrap(); + let server = match Server::http(&addr.parse().unwrap()) { + Ok(server) => server, + Err(err) => return Err(format!("couldn't start http server: {}", err)) + }; let (addr, server) = server.handle(move |_| HttpHandler::new(itx.clone())).unwrap(); thread::spawn(move || server.run()); info!("Listening on http://{}", addr); - Http + Ok(Http) } } @@ -197,27 +200,26 @@ mod tests { use super::super::super::interpreter::Global; #[test] - #[allow(unused_variables)] fn multiple_connections() { - let (etx, erx) = chan::sync::(0); - let (gtx, grx) = chan::sync::(0); - let (_, shutdown_rx) = chan::sync::<()>(0); - Http::run(gtx, erx, shutdown_rx); + let (etx, erx) = chan::sync::(0); + let (gtx, grx) = chan::sync::(0); + Http::run(gtx, erx); thread::spawn(move || { + let _ = etx; // move into this scope loop { match grx.recv() { + None => panic!("gtx is closed"), Some(g) => match g.command { Command::AcceptUpdates(ids) => { let ev = Event::Error(ids.first().unwrap().to_owned()); match g.response_tx { - Some(etx) => etx.lock().unwrap().send(ev), - None => panic!("expected transmitter"), + Some(rtx) => rtx.lock().unwrap().send(ev), + None => panic!("expected response_tx"), } } _ => panic!("expected AcceptUpdates"), }, - None => panic!("gtx closed") } } }); diff --git a/src/interaction_library/websocket.rs b/src/interaction_library/websocket.rs index ca67bb9..35caad7 100644 --- a/src/interaction_library/websocket.rs +++ b/src/interaction_library/websocket.rs @@ -1,5 +1,5 @@ use chan; -use chan::{Sender, Receiver}; +use chan::Sender; use rustc_serialize::{json, Decodable, Encodable}; use std::{env, thread}; use std::collections::HashMap; @@ -22,38 +22,46 @@ impl Gateway for Websocket where C: Decodable + Send + Clone + Debug + 'static, E: Encodable + Send + Clone + Debug + 'static, { - fn new(itx: Sender>, shutdown_rx: Receiver<()>) -> Self { - let addr = env::var("OTA_PLUS_CLIENT_WEBSOCKET_ADDR").unwrap_or("127.0.0.1:3012".to_string()); - let clients = Arc::new(Mutex::new(HashMap::new())); + fn new(itx: Sender>) -> Result { let (etx, erx) = chan::sync::(0); + let etx = Arc::new(Mutex::new(etx.clone())); + let clients = Arc::new(Mutex::new(HashMap::new())); + let addr = env::var("OTA_PLUS_CLIENT_WEBSOCKET_ADDR").unwrap_or("127.0.0.1:3012".to_string()); + + let rx_clients = clients.clone(); + thread::spawn(move || { + loop { + match erx.recv() { + Some(e) => send_response(rx_clients.clone(), e), + None => panic!("all websocket response transmitters are closed") + } + } + }); - let ws_clients = clients.clone(); + let (start_tx, start_rx) = chan::sync::>(0); thread::spawn(move || { info!("Opening websocket listener on {}", addr); - let etx = Arc::new(Mutex::new(etx.clone())); - listen(&addr as &str, |sender| { + start_tx.send(listen(&addr as &str, |sender| { WebsocketHandler { - clients: ws_clients.clone(), + clients: clients.clone(), sender: sender, itx: itx.clone(), etx: etx.clone(), } - }).unwrap(); + })); }); - thread::spawn(move || { - loop { - chan_select! { - shutdown_rx.recv() => break, - erx.recv() -> e => match e { - Some(e) => send_response(&clients, e), - None => panic!("all websocket response transmitters are closed") - } - } + let tick = chan::tick_ms(1000); // FIXME: ugly hack for blocking call + chan_select! { + tick.recv() => return Ok(Websocket), + start_rx.recv() -> outcome => match outcome { + Some(outcome) => match outcome { + Ok(_) => return Ok(Websocket), + Err(err) => return Err(format!("couldn't open websocket listener: {}", err)) + }, + None => panic!("expected websocket start outcome") } - }); - - Websocket + } } } @@ -105,7 +113,7 @@ fn decode(s: &str) -> Result { Ok(try!(json::decode::(s))) } -fn send_response(clients: &Clients, e: E) { +fn send_response(clients: Clients, e: E) { let txt = encode(e); let map = clients.lock().expect("Poisoned map lock -- can't continue"); for (_, sender) in map.iter() { diff --git a/src/interpreter.rs b/src/interpreter.rs index 9fa1a24..8e84eed 100644 --- a/src/interpreter.rs +++ b/src/interpreter.rs @@ -1,5 +1,6 @@ use chan; use chan::{Sender, Receiver}; +use std; use std::borrow::Cow; use datatype::{AccessToken, Auth, ClientId, ClientSecret, Command, Config, @@ -14,14 +15,11 @@ use ota_plus::OTA; pub trait Interpreter { fn interpret(&mut self, msg: I, otx: &Sender); - fn run(&mut self, irx: Receiver, otx: Sender, shutdown_rx: Receiver<()>) { + fn run(&mut self, irx: Receiver, otx: Sender) { loop { - chan_select! { - shutdown_rx.recv() => break, - irx.recv() -> i => match i { - Some(i) => self.interpret(i, &otx), - None => panic!("interpreter sender closed") - } + match irx.recv() { + Some(i) => self.interpret(i, &otx), + None => panic!("interpreter sender closed") } } } @@ -78,7 +76,6 @@ pub struct GlobalInterpreter<'t> { pub token: Option>, pub http_client: Box, pub loopback_tx: Sender, - pub shutdown_tx: Sender<()>, } impl<'t> Interpreter for GlobalInterpreter<'t> { @@ -163,10 +160,7 @@ impl<'t> GlobalInterpreter<'t> { etx.send(Event::FoundInstalledPackages(pkgs)); } - Shutdown => { - self.shutdown_tx.send(()); - etx.send(Event::Ok); - } + Shutdown => std::process::exit(0), UpdateInstalledPackages => { try!(ota.update_installed_packages()); @@ -195,10 +189,7 @@ impl<'t> GlobalInterpreter<'t> { ListInstalledPackages | UpdateInstalledPackages => etx.send(Event::NotAuthenticated), - Shutdown => { - self.shutdown_tx.send(()); - etx.send(Event::Ok); - } + Shutdown => std::process::exit(0), } Ok(()) @@ -226,10 +217,9 @@ mod tests { fn new_interpreter(replies: Vec, pkg_mgr: PackageManager) -> (Sender, Receiver) { - let (etx, erx) = chan::sync::(0); - let (ctx, crx) = chan::sync::(0); - let (gtx, _) = chan::sync::(0); - let (shutdown_tx, _) = chan::sync::<()>(0); + let (etx, erx) = chan::sync::(0); + let (ctx, crx) = chan::sync::(0); + let (gtx, _) = chan::sync::(0); thread::spawn(move || { let mut wi = GlobalInterpreter { @@ -237,7 +227,6 @@ mod tests { token: Some(AccessToken::default().into()), http_client: Box::new(TestHttpClient::from(replies)), loopback_tx: gtx, - shutdown_tx: shutdown_tx }; wi.config.ota.package_manager = pkg_mgr; diff --git a/src/main.rs b/src/main.rs index 6233001..fe2575d 100644 --- a/src/main.rs +++ b/src/main.rs @@ -27,26 +27,21 @@ use libotaplus::interpreter::{EventInterpreter, CommandInterpreter, Interpreter, use libotaplus::package_manager::PackageManager; -fn spawn_signal_handler(signals: Receiver, ctx: Sender, shutdown_rx: Receiver<()>) { +fn spawn_signal_handler(signals: Receiver) { loop { - chan_select! { - shutdown_rx.recv() => std::process::exit(0), - signals.recv() -> sig => match sig { - Some(Signal::INT) => ctx.send(Command::Shutdown), - Some(Signal::TERM) => ctx.send(Command::Shutdown), - _ => () - }, + match signals.recv() { + Some(Signal::INT) => std::process::exit(0), + Some(Signal::TERM) => std::process::exit(0), + _ => () } } } -fn spawn_update_poller(interval: u64, ctx: Sender, shutdown_rx: Receiver<()>) { +fn spawn_update_poller(interval: u64, ctx: Sender) { let tick = chan::tick(Duration::from_secs(interval)); loop { - chan_select! { - shutdown_rx.recv() => break, - tick.recv() => ctx.send(Command::GetPendingUpdates), - } + let _ = tick.recv(); + ctx.send(Command::GetPendingUpdates); } } @@ -62,63 +57,49 @@ fn main() { let (etx, erx) = chan::async::(); let (ctx, crx) = chan::async::(); let (gtx, grx) = chan::async::(); - let (shutdown_tx, shutdown_rx) = chan::sync::<()>(0); let mut broadcast = Broadcast::new(erx); - let mut shutdown = Broadcast::new(shutdown_rx); - perform_initial_sync(&ctx); crossbeam::scope(|scope| { // Must subscribe to the signal before spawning ANY other threads - let signals = chan_signal::notify(&[Signal::INT, Signal::TERM]); - let signal_ctx = ctx.clone(); - let signal_shutdown = shutdown.subscribe(); - scope.spawn(move || spawn_signal_handler(signals, signal_ctx, signal_shutdown)); + let signals = chan_signal::notify(&[Signal::INT, Signal::TERM]); + scope.spawn(move || spawn_signal_handler(signals)); - let ws_gtx = gtx.clone(); - let ws_event = broadcast.subscribe(); - let ws_shutdown = shutdown.subscribe(); - scope.spawn(move || Websocket::run(ws_gtx, ws_event, ws_shutdown)); + let poll_tick = config.ota.polling_interval; + let poll_ctx = ctx.clone(); + scope.spawn(move || spawn_update_poller(poll_tick, poll_ctx)); + + let ws_gtx = gtx.clone(); + let ws_event = broadcast.subscribe(); + scope.spawn(move || Websocket::run(ws_gtx, ws_event)); if config.test.http { - let http_gtx = gtx.clone(); - let http_event = broadcast.subscribe(); - let http_shutdown = shutdown.subscribe(); - scope.spawn(move || Http::run(http_gtx, http_event, http_shutdown)); + let http_gtx = gtx.clone(); + let http_event = broadcast.subscribe(); + scope.spawn(move || Http::run(http_gtx, http_event)); } if config.test.looping { - let repl_gtx = gtx.clone(); - let repl_event = broadcast.subscribe(); - let repl_shutdown = shutdown.subscribe(); - scope.spawn(move || Console::run(repl_gtx, repl_event, repl_shutdown)); + let repl_gtx = gtx.clone(); + let repl_event = broadcast.subscribe(); + scope.spawn(move || Console::run(repl_gtx, repl_event)); } let event_subscribe = broadcast.subscribe(); let event_ctx = ctx.clone(); - let event_shutdown = shutdown.subscribe(); - scope.spawn(move || EventInterpreter.run(event_subscribe, event_ctx, event_shutdown)); + scope.spawn(move || EventInterpreter.run(event_subscribe, event_ctx)); - let cmd_gtx = gtx.clone(); - let cmd_shutdown = shutdown.subscribe(); - scope.spawn(move || CommandInterpreter.run(crx, cmd_gtx, cmd_shutdown)); + let cmd_gtx = gtx.clone(); + scope.spawn(move || CommandInterpreter.run(crx, cmd_gtx)); - let poll_interval = config.ota.polling_interval; - let global_shutdown = shutdown.subscribe(); scope.spawn(move || GlobalInterpreter { config: config, token: None, http_client: Box::new(AuthClient::new(Auth::None)), loopback_tx: gtx, - shutdown_tx: shutdown_tx, - }.run(grx, etx, global_shutdown)); - - let poll_ctx = ctx.clone(); - let poll_shutdown = shutdown.subscribe(); - scope.spawn(move || spawn_update_poller(poll_interval, poll_ctx, poll_shutdown)); + }.run(grx, etx)); - scope.spawn(move || shutdown.start()); scope.spawn(move || broadcast.start()); }); } diff --git a/src/ota_plus.rs b/src/ota_plus.rs index 10751ec..0236ea7 100644 --- a/src/ota_plus.rs +++ b/src/ota_plus.rs @@ -36,15 +36,10 @@ impl<'c, 'h> OTA<'c, 'h> { url: self.update_endpoint(""), body: None, }); - match resp_rx.recv() { - Some(resp) => { - let data = try!(resp); - let text = try!(String::from_utf8(data)); - Ok(try!(json::decode::>(&text))) - } - - None => panic!("no get_package_updates response received") - } + let resp = resp_rx.recv().expect("no get_package_updates response received"); + let data = try!(resp); + let text = try!(String::from_utf8(data)); + Ok(try!(json::decode::>(&text))) } pub fn download_package_update(&mut self, id: &UpdateRequestId) -> Result { @@ -62,22 +57,16 @@ impl<'c, 'h> OTA<'c, 'h> { // TODO: Do not invoke package_manager path.set_extension(self.config.ota.package_manager.extension()); - match resp_rx.recv() { - Some(resp) => { - let data = try!(resp); - let mut file = try!(File::create(path.as_path())); - let _ = io::copy(&mut &*data, &mut file); - Ok(path) - } - - None => panic!("no download_package_update response received") - } + let resp = resp_rx.recv().expect("no download_package_update response received"); + let data = try!(resp); + let mut file = try!(File::create(path.as_path())); + let _ = io::copy(&mut &*data, &mut file); + Ok(path) } pub fn install_package_update(&mut self, id: &UpdateRequestId, etx: &Sender) -> Result { debug!("installing package update"); - match self.download_package_update(id) { Ok(path) => { let err_str = format!("Path is not valid UTF-8: {:?}", path); @@ -116,7 +105,7 @@ impl<'c, 'h> OTA<'c, 'h> { let pkgs = try!(self.config.ota.package_manager.installed_packages()); let body = try!(json::encode(&pkgs)); debug!("installed packages: {}", body); - let _ = self.client.send_request(HttpRequest { + let _ = self.client.send_request(HttpRequest { method: Method::Put, url: self.update_endpoint("installed"), body: Some(body.into_bytes()), @@ -128,13 +117,11 @@ impl<'c, 'h> OTA<'c, 'h> { debug!("sending installation report"); let vin_report = UpdateReportWithVin::new(&self.config.auth.vin, &report); let body = try!(json::encode(&vin_report)); - - let _ = self.client.send_request(HttpRequest { + let _ = self.client.send_request(HttpRequest { method: Method::Post, url: self.update_endpoint(&format!("{}", report.update_id)), body: Some(body.into_bytes()), }); - Ok(()) } } -- cgit v1.2.1 From 38f061b73a67b98d07be2507ea07e761d5f27d62 Mon Sep 17 00:00:00 2001 From: Jerry Trieu Date: Wed, 22 Jun 2016 11:22:15 +0200 Subject: Fix default OTA_CREDENTIALS_FILE env in pkg.sh --- pkg/pkg.sh | 1 + 1 file changed, 1 insertion(+) diff --git a/pkg/pkg.sh b/pkg/pkg.sh index 36cbd32..fb7c4db 100755 --- a/pkg/pkg.sh +++ b/pkg/pkg.sh @@ -7,6 +7,7 @@ PKG_VER=$VERSION PKG_DIR="${PKG_NAME}-${PKG_VER}" PKG_TARBALL="${PKG_NAME}_${PKG_VER}" PREFIX=/opt/ats +export OTA_CREDENTIALS_FILE=${OTA_CREDENTIALS_FILE-${PREFIX}/credentials.toml} export OTA_HTTP=false cd $(dirname $0) -- cgit v1.2.1 From fb50761da141c6905e70cf681936fd000eaecd1f Mon Sep 17 00:00:00 2001 From: Shaun Taheri Date: Wed, 22 Jun 2016 11:48:54 +0200 Subject: Fix websocket broadcast --- src/interaction_library/http.rs | 10 +-- src/interaction_library/websocket.rs | 161 +++++++++++++++++++++++++---------- src/interpreter.rs | 14 ++- 3 files changed, 127 insertions(+), 58 deletions(-) diff --git a/src/interaction_library/http.rs b/src/interaction_library/http.rs index 8cf7370..89881c2 100644 --- a/src/interaction_library/http.rs +++ b/src/interaction_library/http.rs @@ -42,7 +42,7 @@ pub struct HttpHandler where C: Decodable + Send + Clone + Debug + 'static, E: Encodable + Send + Clone + Debug + 'static { - forward_tx: Arc>>>, + itx: Arc>>>, response_rx: Option>, req_body: Vec, resp_body: Option>, @@ -53,9 +53,9 @@ impl HttpHandler where C: Decodable + Send + Clone + Debug + 'static, E: Encodable + Send + Clone + Debug + 'static { - fn new(forward_tx: Arc>>>) -> HttpHandler { + fn new(itx: Arc>>>) -> HttpHandler { HttpHandler { - forward_tx: forward_tx, + itx: itx, response_rx: None, req_body: Vec::new(), resp_body: None, @@ -72,7 +72,7 @@ impl HttpHandler info!("on_request_readable: decoded command: {:?}", cmd); let (etx, erx) = chan::async::(); self.response_rx = Some(erx); - self.forward_tx.lock().unwrap().send(Interpret { + self.itx.lock().unwrap().send(Interpret { command: cmd, response_tx: Some(Arc::new(Mutex::new(etx))), }); @@ -200,7 +200,7 @@ mod tests { use super::super::super::interpreter::Global; #[test] - fn multiple_connections() { + fn http_connections() { let (etx, erx) = chan::sync::(0); let (gtx, grx) = chan::sync::(0); Http::run(gtx, erx); diff --git a/src/interaction_library/websocket.rs b/src/interaction_library/websocket.rs index 35caad7..8977c01 100644 --- a/src/interaction_library/websocket.rs +++ b/src/interaction_library/websocket.rs @@ -6,73 +6,67 @@ use std::collections::HashMap; use std::fmt::Debug; use std::sync::{Arc, Mutex}; use ws; -use ws::{listen, Sender as WsSender, Handler, Message, Handshake, CloseCode}; +use ws::{listen, CloseCode, Handler, Handshake, Message, Sender as WsSender}; use ws::util::Token; use super::gateway::{Gateway, Interpret}; use datatype::Error; -type Clients = Arc>>; - - -pub struct Websocket; +pub struct Websocket { + clients: Arc>> +} impl Gateway for Websocket where C: Decodable + Send + Clone + Debug + 'static, E: Encodable + Send + Clone + Debug + 'static, { fn new(itx: Sender>) -> Result { - let (etx, erx) = chan::sync::(0); - let etx = Arc::new(Mutex::new(etx.clone())); - let clients = Arc::new(Mutex::new(HashMap::new())); - let addr = env::var("OTA_PLUS_CLIENT_WEBSOCKET_ADDR").unwrap_or("127.0.0.1:3012".to_string()); - - let rx_clients = clients.clone(); - thread::spawn(move || { - loop { - match erx.recv() { - Some(e) => send_response(rx_clients.clone(), e), - None => panic!("all websocket response transmitters are closed") - } - } - }); + let clients = Arc::new(Mutex::new(HashMap::new())); + let addr = env::var("OTA_PLUS_CLIENT_WEBSOCKET_ADDR").unwrap_or("127.0.0.1:3012".to_string()); + let handler_clients = clients.clone(); let (start_tx, start_rx) = chan::sync::>(0); thread::spawn(move || { info!("Opening websocket listener on {}", addr); - start_tx.send(listen(&addr as &str, |sender| { + start_tx.send(listen(&addr as &str, |out| { WebsocketHandler { - clients: clients.clone(), - sender: sender, + out: out, itx: itx.clone(), - etx: etx.clone(), + clients: handler_clients.clone() } })); }); let tick = chan::tick_ms(1000); // FIXME: ugly hack for blocking call chan_select! { - tick.recv() => return Ok(Websocket), + tick.recv() => return Ok(Websocket{ clients: clients }), start_rx.recv() -> outcome => match outcome { Some(outcome) => match outcome { - Ok(_) => return Ok(Websocket), + Ok(_) => return Ok(Websocket{ clients: clients }), Err(err) => return Err(format!("couldn't open websocket listener: {}", err)) }, None => panic!("expected websocket start outcome") } } } + + fn pulse(&self, event: E) { + let json = encode(event); + for (_, out) in self.clients.lock().unwrap().iter() { + let _ = out.send(Message::Text(json.clone())); + } + } } + pub struct WebsocketHandler where C: Decodable + Send + Clone + Debug + 'static, E: Encodable + Send + Clone + Debug + 'static, { - clients: Clients, - sender: WsSender, + out: WsSender, itx: Sender>, - etx: Arc>>, + clients: Arc>> } impl Handler for WebsocketHandler @@ -80,30 +74,56 @@ impl Handler for WebsocketHandler E: Encodable + Send + Clone + Debug + 'static, { fn on_message(&mut self, msg: Message) -> ws::Result<()> { - match decode(&format!("{}", msg)) { - Ok(cmd) => Ok(self.itx.send(Interpret { command: cmd, response_tx: Some(self.etx.clone()) })), + debug!("received websocket message: {:?}", msg); + match msg.as_text() { + Ok(msg) => match decode(msg) { + Ok(cmd) => Ok(self.forward_command(cmd)), + + Err(Error::WebsocketError(err)) => { + error!("websocket on_message error: {}", err); + Err(err) + } + + Err(_) => unreachable!(), + }, - Err(Error::WebsocketError(err)) => { - error!("websocket decode error: {}", err); + Err(err) => { + error!("websocket on_message text error: {}", err); Err(err) } - - Err(_) => unreachable!() } } fn on_open(&mut self, _: Handshake) -> ws::Result<()> { - let mut map = self.clients.lock().expect("Poisoned map lock -- can't continue"); - let _ = map.insert(self.sender.token(), self.sender.clone()); - Ok(()) + let _ = self.clients.lock().unwrap().insert(self.out.token(), self.out.clone()); + Ok(debug!("new websocket client: {:?}", self.out.token())) + } + + fn on_close(&mut self, code: CloseCode, _: &str) { + let _ = self.clients.lock().unwrap().remove(&self.out.token().clone()); + debug!("closing websocket client {:?}: {:?}", self.out.token(), code); } - fn on_close(&mut self, _: CloseCode, _: &str) { - let mut map = self.clients.lock().expect("Poisoned map lock -- can't continue"); - let _ = map.remove(&self.sender.token().clone()); + fn on_error(&mut self, err: ws::Error) { + error!("websocket error: {:?}", err); } } +impl WebsocketHandler + where C: Decodable + Send + Clone + Debug + 'static, + E: Encodable + Send + Clone + Debug + 'static, +{ + fn forward_command(&self, cmd: C) { + let (etx, erx) = chan::sync::(0); + let etx = Arc::new(Mutex::new(etx.clone())); + self.itx.send(Interpret { command: cmd, response_tx: Some(etx) }); + + let _ = match erx.recv() { + Some(e) => self.out.send(Message::Text(encode(e))), + None => panic!("websocket response_tx is closed") + }; + } +} fn encode(e: E) -> String { json::encode(&e).expect("Error encoding event into JSON") @@ -113,10 +133,61 @@ fn decode(s: &str) -> Result { Ok(try!(json::decode::(s))) } -fn send_response(clients: Clients, e: E) { - let txt = encode(e); - let map = clients.lock().expect("Poisoned map lock -- can't continue"); - for (_, sender) in map.iter() { - let _ = sender.send(Message::Text(txt.clone())); + +#[cfg(test)] +mod tests { + use chan; + use crossbeam; + use rustc_serialize::json; + use std::thread; + use ws; + use ws::{connect, CloseCode}; + + use super::*; + use super::super::gateway::Gateway; + use super::super::super::datatype::{Command, Event}; + use super::super::super::interpreter::Global; + + #[test] + fn websocket_connections() { + let (etx, erx) = chan::sync::(0); + let (gtx, grx) = chan::sync::(0); + Websocket::run(gtx, erx); + + thread::spawn(move || { + let _ = etx; // move into this scope + loop { + match grx.recv() { + None => panic!("gtx is closed"), + Some(g) => match g.command { + Command::AcceptUpdates(ids) => { + let ev = Event::Error(ids.first().unwrap().to_owned()); + match g.response_tx { + Some(rtx) => rtx.lock().unwrap().send(ev), + None => panic!("expected response_tx"), + } + } + _ => panic!("expected AcceptUpdates"), + }, + } + } + }); + + crossbeam::scope(|scope| { + for id in 0..10 { + scope.spawn(move || { + connect("ws://localhost:3012", |out| { + let text = format!(r#"{{ "variant": "AcceptUpdates", "fields": [["{}"]] }}"#, id); + out.send(text).unwrap(); + + move |msg: ws::Message| { + let ev: Event = json::decode(&format!("{}", msg)).unwrap(); + assert_eq!(ev, Event::Error(format!("{}", id))); + out.close(CloseCode::Normal) + } + }).unwrap(); + }); + } + }); } } diff --git a/src/interpreter.rs b/src/interpreter.rs index 8e84eed..75363e9 100644 --- a/src/interpreter.rs +++ b/src/interpreter.rs @@ -81,12 +81,6 @@ pub struct GlobalInterpreter<'t> { impl<'t> Interpreter for GlobalInterpreter<'t> { fn interpret(&mut self, global: Global, etx: &Sender) { info!("Global interpreter started: {:?}", global.command); - let response = |ev: Event| { - if let Some(ref response_tx) = global.response_tx { - response_tx.lock().unwrap().send(ev) - } - }; - let (multi_tx, multi_rx) = chan::async::(); let outcome = match self.token { Some(_) => self.authenticated(global.command.clone(), multi_tx), @@ -119,8 +113,12 @@ impl<'t> Interpreter for GlobalInterpreter<'t> { } match response_ev { - Some(ev) => response(ev), - None => panic!("no response event to send back") + None => panic!("no response event to send back"), + Some(ev) => { + if let Some(ref tx) = global.response_tx { + tx.lock().unwrap().send(ev) + } + } }; } } -- cgit v1.2.1 From 97632a3949700e28e61b787a34db8d913a58722a Mon Sep 17 00:00:00 2001 From: Shaun Taheri Date: Wed, 22 Jun 2016 18:23:08 +0200 Subject: Simplify receiving responses --- src/http_client/auth_client.rs | 9 +++------ src/interaction_library/console.rs | 6 ++---- src/interaction_library/gateway.rs | 5 +---- src/interaction_library/http.rs | 19 +++++++------------ src/interaction_library/websocket.rs | 25 +++++++++---------------- src/interpreter.rs | 5 +---- src/oauth2.rs | 16 +++++----------- 7 files changed, 28 insertions(+), 57 deletions(-) diff --git a/src/http_client/auth_client.rs b/src/http_client/auth_client.rs index 98b2cbc..335c4bd 100644 --- a/src/http_client/auth_client.rs +++ b/src/http_client/auth_client.rs @@ -89,12 +89,9 @@ impl AuthHandler { method: self.req.method.clone(), body: body, }); - match resp_rx.recv() { - Some(resp) => match resp { - Ok(data) => self.resp_tx.send(Ok(data)), - Err(err) => self.resp_tx.send(Err(Error::from(err))) - }, - None => panic!("no redirect_request response") + match resp_rx.recv().expect("no redirect_request response") { + Ok(data) => self.resp_tx.send(Ok(data)), + Err(err) => self.resp_tx.send(Err(Error::from(err))) } } diff --git a/src/interaction_library/console.rs b/src/interaction_library/console.rs index 419eb45..ed97faf 100644 --- a/src/interaction_library/console.rs +++ b/src/interaction_library/console.rs @@ -32,10 +32,8 @@ impl Gateway for Console thread::spawn(move || { loop { - match erx.recv() { - Some(e) => info!("Console Response: {}", e.to_string()), - None => panic!("all console event transmitters are closed") - } + let e = erx.recv().expect("all console event transmitters are closed"); + info!("Console Response: {}", e.to_string()); } }); diff --git a/src/interaction_library/gateway.rs b/src/interaction_library/gateway.rs index a4fb96c..46121ec 100644 --- a/src/interaction_library/gateway.rs +++ b/src/interaction_library/gateway.rs @@ -28,10 +28,7 @@ pub trait Gateway: Sized + Send + Sync + 'static thread::spawn(move || { loop { - match erx.recv() { - Some(e) => gateway.pulse(e), - None => panic!("all gateway event transmitters are closed") - } + gateway.pulse(erx.recv().expect("all gateway event transmitters are closed")); } }); } diff --git a/src/interaction_library/http.rs b/src/interaction_library/http.rs index 89881c2..1eef062 100644 --- a/src/interaction_library/http.rs +++ b/src/interaction_library/http.rs @@ -208,18 +208,13 @@ mod tests { thread::spawn(move || { let _ = etx; // move into this scope loop { - match grx.recv() { - None => panic!("gtx is closed"), - Some(g) => match g.command { - Command::AcceptUpdates(ids) => { - let ev = Event::Error(ids.first().unwrap().to_owned()); - match g.response_tx { - Some(rtx) => rtx.lock().unwrap().send(ev), - None => panic!("expected response_tx"), - } - } - _ => panic!("expected AcceptUpdates"), - }, + let global = grx.recv().expect("gtx is closed"); + match global.command { + Command::AcceptUpdates(ids) => { + let tx = global.response_tx.unwrap(); + tx.lock().unwrap().send(Event::Error(ids.first().unwrap().to_owned())); + } + _ => panic!("expected AcceptUpdates"), } } }); diff --git a/src/interaction_library/websocket.rs b/src/interaction_library/websocket.rs index 8977c01..b9477b3 100644 --- a/src/interaction_library/websocket.rs +++ b/src/interaction_library/websocket.rs @@ -118,10 +118,8 @@ impl WebsocketHandler let etx = Arc::new(Mutex::new(etx.clone())); self.itx.send(Interpret { command: cmd, response_tx: Some(etx) }); - let _ = match erx.recv() { - Some(e) => self.out.send(Message::Text(encode(e))), - None => panic!("websocket response_tx is closed") - }; + let e = erx.recv().expect("websocket response_tx is closed"); + let _ = self.out.send(Message::Text(encode(e))); } } @@ -157,18 +155,13 @@ mod tests { thread::spawn(move || { let _ = etx; // move into this scope loop { - match grx.recv() { - None => panic!("gtx is closed"), - Some(g) => match g.command { - Command::AcceptUpdates(ids) => { - let ev = Event::Error(ids.first().unwrap().to_owned()); - match g.response_tx { - Some(rtx) => rtx.lock().unwrap().send(ev), - None => panic!("expected response_tx"), - } - } - _ => panic!("expected AcceptUpdates"), - }, + let global = grx.recv().expect("gtx is closed"); + match global.command { + Command::AcceptUpdates(ids) => { + let tx = global.response_tx.unwrap(); + tx.lock().unwrap().send(Event::Error(ids.first().unwrap().to_owned())); + } + _ => panic!("expected AcceptUpdates"), } } }); diff --git a/src/interpreter.rs b/src/interpreter.rs index 75363e9..dc63ce3 100644 --- a/src/interpreter.rs +++ b/src/interpreter.rs @@ -17,10 +17,7 @@ pub trait Interpreter { fn run(&mut self, irx: Receiver, otx: Sender) { loop { - match irx.recv() { - Some(i) => self.interpret(i, &otx), - None => panic!("interpreter sender closed") - } + self.interpret(irx.recv().expect("interpreter sender closed"), &otx); } } } diff --git a/src/oauth2.rs b/src/oauth2.rs index 2c9ad2a..a727629 100644 --- a/src/oauth2.rs +++ b/src/oauth2.rs @@ -6,23 +6,17 @@ use http_client::{HttpClient, HttpRequest}; pub fn authenticate(config: &AuthConfig, client: &HttpClient) -> Result { debug!("authenticate()"); - let resp_rx = client.send_request(HttpRequest{ method: Method::Post, url: config.server.join("/token").unwrap(), body: None }); - match resp_rx.recv() { - Some(resp) => { - let data = try!(resp); - let body = try!(String::from_utf8(data)); - debug!("authenticate, body: `{}`", body); - Ok(try!(json::decode(&body))) - }, - - None => panic!("no authenticate response received") - } + let resp = resp_rx.recv().expect("no authenticate response received"); + let data = try!(resp); + let body = try!(String::from_utf8(data)); + debug!("authenticate, body: `{}`", body); + Ok(try!(json::decode(&body))) } -- cgit v1.2.1 From fb2fa832c103133e41a5f6f829565e980ac47554 Mon Sep 17 00:00:00 2001 From: Shaun Taheri Date: Thu, 23 Jun 2016 15:12:31 +0200 Subject: Update Makefile targets and add version to logs --- .gitignore | 1 + Makefile | 64 +++++++++--- README.md | 93 ++++++++--------- ota.toml | 3 +- pkg/ota.toml.template | 3 +- src/datatype/config.rs | 11 ++- src/datatype/error.rs | 2 +- src/http_client/auth_client.rs | 19 ++-- src/interpreter.rs | 8 +- src/lib.rs | 4 +- src/main.rs | 176 ++++++++++++--------------------- src/package_manager/mod.rs | 2 + src/package_manager/package_manager.rs | 28 ++++-- tests/ota_plus_client_tests.rs | 31 +++--- 14 files changed, 216 insertions(+), 229 deletions(-) diff --git a/.gitignore b/.gitignore index 58dd422..d73296e 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ target pkg/ota_plus_client +src/.version .tmp* diff --git a/Makefile b/Makefile index bb305ca..1e0dc9c 100644 --- a/Makefile +++ b/Makefile @@ -1,18 +1,58 @@ -MUSL=x86_64-unknown-linux-musl +.DEFAULT_GOAL := help +GIT_VERSION := $(shell git describe --abbrev=10 --dirty --always --tags) +MUSL_TARGET := x86_64-unknown-linux-musl -.PHONY: all clean ota_plus_client deb rpm +.PHONY: help all run clean version test client-release client-musl image deb rpm -all: deb rpm +help: + @awk 'BEGIN {FS = ":.*?## "} /^[a-zA-Z_-]+:.*?## / {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}' $(MAKEFILE_LIST) -clean: - cargo clean +all: clean test deb rpm ## Clean, test and make new DEB and RPM packages. -ota_plus_client: src/ - cargo build --release --target=$(MUSL) - cp target/$(MUSL)/release/ota_plus_client pkg/ +run: image ## Run the client inside a Docker container. + @docker run --rm -it --net=host \ + advancedtelematic/ota-plus-client:latest -deb: ota_plus_client - pkg/pkg.sh deb $(CURDIR) +clean: ## Remove all compiled libraries, builds and temporary files. + @cargo clean + @rm -f .tmp* src/.version -rpm: ota_plus_client - pkg/pkg.sh rpm $(CURDIR) +version: + @printf $(GIT_VERSION) > src/.version + +test: ## Run all Cargo tests. + @cargo test + +client-release: src/ version ## Make a release build of the client. + @cargo build --release + +client-musl: src/ version ## Make a statically linked release build of the client. + @docker run --rm -it \ + --env CARGO_HOME=/cargo \ + --volume ~/.cargo:/cargo \ + --volume $(CURDIR):/build \ + --workdir /build \ + clux/muslrust:latest \ + cargo build --release --target=$(MUSL_TARGET) + @cp target/$(MUSL_TARGET)/release/ota_plus_client pkg/ + +image: client-musl ## Build a Docker image from a statically linked binary. + @docker build -t advancedtelematic/ota-plus-client pkg + +deb: image ## Make a new DEB package inside a Docker container. + @docker run --rm -it \ + --env CARGO_HOME=/cargo \ + --volume ~/.cargo:/cargo \ + --volume $(CURDIR):/build \ + --workdir /build \ + advancedtelematic/ota-plus-client:latest \ + pkg/pkg.sh deb $(CURDIR) + +rpm: image ## Make a new RPM package inside a Docker container. + @docker run --rm -it \ + --env CARGO_HOME=/cargo \ + --volume ~/.cargo:/cargo \ + --volume $(CURDIR):/build \ + --workdir /build \ + advancedtelematic/ota-plus-client:latest \ + pkg/pkg.sh deb $(CURDIR) diff --git a/README.md b/README.md index 16d8fdd..23cb981 100644 --- a/README.md +++ b/README.md @@ -4,70 +4,57 @@ The OTA+ client source repository. ## Prerequisites -* Rust stable -* Cargo +At a minimum, a stable installation of Rust with Cargo is required. To compile a statically linked binary, a MUSL build target is also required. The easiest way to get both is via [Rustup](https://www.rustup.rs): -## Build instructions +1. `curl https://sh.rustup.rs -sSf | sh` (feel free to inspect the script first) +2. `rustup target add x86_64-unknown-linux-musl` -To build and test the project simply issue: +## Makefile targets - cargo build - cargo test +Run `make help` (or simply `make`) to see a list of Makefile targets. The following targets are available: -## Packaging instructions +Target | Description +-------------: | :---------- +all | Clean, test and make new DEB and RPM packages. +run | Run the client inside a Docker container. +clean | Remove all compiled libraries, builds and temporary files. +test | Run all Cargo tests. +client-release | Make a release build of the client. +client-musl | Make a statically linked release build of the client. +image | Build a Docker image from a statically linked binary. +deb | Make a new DEB package inside a Docker container. +rpm | Make a new RPM package inside a Docker container. -A Dockerfile has been set up with the correct libraries for building a statically linked binary. This can be built from the project root with `docker build -t advancedtelematic/client-packager pkg`. +## Customization -### DEB +Assuming an up-to-date Docker image (built with `make image`), you can configure how the client starts using the following environment variables: -A `.deb` package can be built with `docker run -e VERSION=0.0.0 -v $PWD:/build advancedtelematic/client-packager make deb` (or simply `make deb` with the correct build packages installed). Remember to set the `VERSION` environment variable to the correct version. +Variable | Default value +-------------------: | :-------------------- +`OTA_AUTH_URL` | http://localhost:9001 +`OTA_WEB_URL` | http://localhost:9000 +`OTA_CORE_URL` | http://localhost:8080 +`OTA_WEB_USER` | `demo@advancedtelematic.com` +`OTA_WEB_PASSWORD` | `demo` +`OTA_CLIENT_VIN` | (generated) +`OTA_AUTH_CLIENT_ID` | (generated) +`OTA_AUTH_SECRET` | (generated) +`PACKAGE_MANAGER` | `dpkg` +`OTA_HTTP` | `false` +`PROVISION` | `false` -### RPM +### Provisionin -[FPM](https://github.com/jordansissel/fpm) is used to create RPM packages. +Setting `PROVISION=true` will output a newly generated `ota.toml` to STDOUT then quit, rather than starting the client. -A `.rpm` package can be built with `docker run -e VERSION=0.0.0 -v $PWD:/build advancedtelematic/client-packager make rpm` (or simply `make rpm` with FPM and the build packages installed). Remember to set the `VERSION` environment variable to the correct version. - -## Dockerfile - -There is a Dockerfile in `/pkg` to create and image with ota-plus-client that automatically configures itself with a random VIN. To build this image run: - -``` -make -docker build -t advancedtelematic/ota-plus-client pkg/ -``` - -To use it, run: - -``` -docker run advancedtelematic/ota-plus-client -``` - -You can configure it using the following environment variables: - -- `OTA_AUTH_URL`, default value: http://localhost:9001 -- `OTA_WEB_URL`, default value: http://localhost:9000 -- `OTA_CORE_URL`, default value: http://localhost:8080 -- `OTA_WEB_USER`, default value: `demo@advancedtelematic.com` -- `OTA_WEB_PASSWORD`, default value: `demo` -- `OTA_CLIENT_VIN`, default value: Randomly generated -- `OTA_AUTH_CLIENT_ID`, default value: Generated for VIN -- `OTA_AUTH_SECRET`, default value: Generated for VIN -- `PACKAGE_MANAGER`, `dpkg` or `rpm`, default value: `dpkg` -- `OTA_HTTP`, default value: `false` -- `PROVISION`, default value: `false`. Set to `true` to output a configured `ota.toml` file to STDOUT then exit. - -Eg: +### Example ``` -docker run \ - -it \ - --rm \ - --net=host \ - -e OTA_AUTH_URL="http://auth-plus-staging.gw.prod01.advancedtelematic.com" \ - -e OTA_WEB_URL="http://ota-plus-web-staging.gw.prod01.advancedtelematic.com" \ - -e OTA_CORE_URL="http://ota-plus-core-staging.gw.prod01.advancedtelematic.com" \ - advancedtelematic/ota-plus-client:latest +docker run --rm -it --net=host \ + --env OTA_AUTH_URL="http://auth-plus-staging.gw.prod01.advancedtelematic.com" \ + --env OTA_WEB_URL="http://ota-plus-web-staging.gw.prod01.advancedtelematic.com" \ + --env OTA_CORE_URL="http://ota-plus-core-staging.gw.prod01.advancedtelematic.com" \ + advancedtelematic/ota-plus-client:latest ``` -If running against local urls, be sure to pass `--net=host` to the `docker run` command. +The `--net=host` flag is only required if the Docker container needs to communicate with other containers running on the same host. diff --git a/ota.toml b/ota.toml index 8367dbd..297eef1 100644 --- a/ota.toml +++ b/ota.toml @@ -12,5 +12,6 @@ packages_dir = "/tmp/" package_manager = "dpkg" [test] -looping = false http = false +repl = false +websocket = true diff --git a/pkg/ota.toml.template b/pkg/ota.toml.template index 156bf50..6630b7e 100644 --- a/pkg/ota.toml.template +++ b/pkg/ota.toml.template @@ -12,5 +12,6 @@ packages_dir = "/tmp/" package_manager = "${PACKAGE_MANAGER}" [test] -looping = false http = ${OTA_HTTP} +repl = false +websocket = true diff --git a/src/datatype/config.rs b/src/datatype/config.rs index b1fa894..06c8f29 100644 --- a/src/datatype/config.rs +++ b/src/datatype/config.rs @@ -62,8 +62,9 @@ pub struct OtaConfig { #[derive(RustcDecodable, PartialEq, Eq, Debug, Clone)] pub struct TestConfig { - pub looping: bool, pub http: bool, + pub repl: bool, + pub websocket: bool, } impl Default for AuthConfig { @@ -103,8 +104,9 @@ impl Default for OtaConfig { impl Default for TestConfig { fn default() -> TestConfig { TestConfig { - looping: false, - http: false, + http: false, + repl: false, + websocket: true, } } } @@ -219,8 +221,9 @@ mod tests { package_manager = "dpkg" [test] - looping = false http = false + repl = false + websocket = true "#; #[test] diff --git a/src/datatype/error.rs b/src/datatype/error.rs index 661ac16..8c9aea6 100644 --- a/src/datatype/error.rs +++ b/src/datatype/error.rs @@ -18,8 +18,8 @@ use super::super::interpreter::Global; #[derive(Debug)] pub enum Error { - ClientError(String), AuthorizationError(String), + ClientError(String), Command(String), FromUtf8Error(FromUtf8Error), HyperError(HyperError), diff --git a/src/http_client/auth_client.rs b/src/http_client/auth_client.rs index 335c4bd..b65c478 100644 --- a/src/http_client/auth_client.rs +++ b/src/http_client/auth_client.rs @@ -8,8 +8,8 @@ use hyper::net::{HttpStream, HttpsStream, OpensslStream, Openssl}; use hyper::status::StatusCode; use std::{io, mem}; use std::io::{ErrorKind, Write}; -use std::time::Duration; -use time; +use std::time::{Duration, SystemTime}; + use datatype::{Auth, Error}; use http_client::{HttpClient, HttpRequest, HttpResponse}; @@ -58,7 +58,7 @@ pub struct AuthHandler { auth: Auth, req: HttpRequest, timeout: Duration, - started: Option, + started: Option, written: usize, response: Vec, resp_tx: Sender, @@ -73,7 +73,6 @@ impl ::std::fmt::Debug for AuthHandler { impl AuthHandler { fn redirect_request(&self, resp: Response) { - info!("redirect_request"); match resp.headers().get::() { Some(&Location(ref loc)) => match self.req.url.join(loc) { Ok(url) => { @@ -112,7 +111,7 @@ pub type Stream = HttpsStream>; impl Handler for AuthHandler { fn on_request(&mut self, req: &mut Request) -> Next { info!("on_request: {} {}", req.method(), req.uri()); - self.started = Some(time::precise_time_ns()); + self.started = Some(SystemTime::now()); req.set_method(self.req.method.clone().into()); let mut headers = req.headers_mut(); @@ -181,10 +180,12 @@ impl Handler for AuthHandler { } fn on_response(&mut self, resp: Response) -> Next { - info!("on_response: status: {}, headers:\n{}", resp.status(), resp.headers()); - if let Some(started) = self.started { - debug!("latency: {}", time::precise_time_ns() - started); - } + info!("on_response status: {}", resp.status()); + debug!("on_response headers:\n{}", resp.headers()); + let _ = self.started.expect("expected start time").elapsed().map(|t| { + let ms = 1000 as f64 * (t.as_secs() as f64 + t.subsec_nanos() as f64 / 1e9); + debug!("on_response latency: {}ms", ms as u64) + }); if resp.status().is_success() { if let Some(len) = resp.headers().get::() { diff --git a/src/interpreter.rs b/src/interpreter.rs index dc63ce3..3ed2939 100644 --- a/src/interpreter.rs +++ b/src/interpreter.rs @@ -111,12 +111,10 @@ impl<'t> Interpreter for GlobalInterpreter<'t> { match response_ev { None => panic!("no response event to send back"), - Some(ev) => { - if let Some(ref tx) = global.response_tx { - tx.lock().unwrap().send(ev) - } + Some(ev) => if let Some(ref tx) = global.response_tx { + tx.lock().unwrap().send(ev); } - }; + } } } diff --git a/src/lib.rs b/src/lib.rs index 89e7b80..8755f35 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,16 +1,14 @@ #[macro_use] extern crate chan; extern crate crossbeam; extern crate hyper; -#[macro_use] extern crate nom; +#[macro_use] extern crate nom; // use before log to avoid error!() macro conflict #[macro_use] extern crate log; extern crate rustc_serialize; extern crate tempfile; -extern crate time; extern crate toml; extern crate url; extern crate ws; - pub mod oauth2; pub mod datatype; pub mod http_client; diff --git a/src/main.rs b/src/main.rs index fe2575d..4515d76 100644 --- a/src/main.rs +++ b/src/main.rs @@ -8,7 +8,6 @@ extern crate hyper; #[macro_use] extern crate log; extern crate rustc_serialize; extern crate time; -extern crate ws; use chan::{Sender, Receiver}; use chan_signal::Signal; @@ -70,25 +69,27 @@ fn main() { let poll_ctx = ctx.clone(); scope.spawn(move || spawn_update_poller(poll_tick, poll_ctx)); - let ws_gtx = gtx.clone(); - let ws_event = broadcast.subscribe(); - scope.spawn(move || Websocket::run(ws_gtx, ws_event)); - if config.test.http { - let http_gtx = gtx.clone(); - let http_event = broadcast.subscribe(); - scope.spawn(move || Http::run(http_gtx, http_event)); + let http_gtx = gtx.clone(); + let http_sub = broadcast.subscribe(); + scope.spawn(move || Http::run(http_gtx, http_sub)); + } + + if config.test.repl { + let repl_gtx = gtx.clone(); + let repl_sub = broadcast.subscribe(); + scope.spawn(move || Console::run(repl_gtx, repl_sub)); } - if config.test.looping { - let repl_gtx = gtx.clone(); - let repl_event = broadcast.subscribe(); - scope.spawn(move || Console::run(repl_gtx, repl_event)); + if config.test.websocket { + let ws_gtx = gtx.clone(); + let ws_sub = broadcast.subscribe(); + scope.spawn(move || Websocket::run(ws_gtx, ws_sub)); } - let event_subscribe = broadcast.subscribe(); - let event_ctx = ctx.clone(); - scope.spawn(move || EventInterpreter.run(event_subscribe, event_ctx)); + let event_sub = broadcast.subscribe(); + let event_ctx = ctx.clone(); + scope.spawn(move || EventInterpreter.run(event_sub, event_ctx)); let cmd_gtx = gtx.clone(); scope.spawn(move || CommandInterpreter.run(crx, cmd_gtx)); @@ -105,121 +106,72 @@ fn main() { } fn setup_logging() { - let format = |record: &LogRecord| { - let service_name = env::var("SERVICE_NAME") - .unwrap_or("ota-plus-client".to_string()); - - let service_version = env::var("SERVICE_VERSION") - .unwrap_or("?".to_string()); - - let timestamp = format!("{}", time::now().ctime()); - - format!("{} ({}), {}: {} - {}", - service_name, service_version, timestamp, record.level(), record.args()) + let format = |record: &LogRecord| { + let name = env::var("SERVICE_NAME").unwrap_or("ota-plus-client".to_string()); + let version = include_str!(".version"); + let timestamp = format!("{}", time::now_utc().rfc3339()); + format!("{}:{} @ {}: {} - {}", name, version, timestamp, record.level(), record.args()) }; let mut builder = LogBuilder::new(); builder.format(format); - - if let Ok(level) = env::var("RUST_LOG") { - builder.parse(&level); - } - + let _ = env::var("RUST_LOG").map(|level| builder.parse(&level)); builder.init().expect("env_logger::init() called twice, blame the programmers."); } fn build_config() -> Config { - let args: Vec = env::args().collect(); - let program = args[0].clone(); - + let args = env::args().collect::>(); + let program = args[0].clone(); let mut opts = Options::new(); - opts.optflag("h", "help", - "print this help menu"); - opts.optopt("", "config", - "change config path", "PATH"); - opts.optopt("", "auth-server", - "change the auth server URL", "URL"); - opts.optopt("", "auth-client-id", - "change auth client id", "ID"); - opts.optopt("", "auth-secret", - "change auth secret", "SECRET"); - opts.optopt("", "auth-vin", - "change auth vin", "VIN"); - opts.optopt("", "ota-server", - "change ota server URL", "URL"); - opts.optopt("", "ota-packages-dir", - "change downloaded directory for packages", "PATH"); - opts.optopt("", "ota-package-manager", - "change package manager", "MANAGER"); - opts.optflag("", "repl", - "enable repl"); - opts.optflag("", "http", - "enable interaction via http requests"); - - let matches = opts.parse(&args[1..]) - .unwrap_or_else(|err| panic!(err.to_string())); - if matches.opt_present("h") { - let brief = format!("Usage: {} [options]", program); - exit!("{}", opts.usage(&brief)); - } - - let mut config_file = env::var("OTA_PLUS_CLIENT_CFG") - .unwrap_or("/opt/ats/ota/etc/ota.toml".to_string()); - - if let Some(path) = matches.opt_str("config") { - config_file = path; - } - - let mut config = config::load_config(&config_file) - .unwrap_or_else(|err| exit!("{}", err)); - - if let Some(s) = matches.opt_str("auth-server") { - match Url::parse(&s) { - Ok(url) => config.auth.server = url, - Err(err) => exit!("Invalid auth-server URL: {}", err) - } - } - - if let Some(client_id) = matches.opt_str("auth-client-id") { - config.auth.client_id = client_id; - } - - if let Some(secret) = matches.opt_str("auth-secret") { - config.auth.secret = secret; - } - - if let Some(vin) = matches.opt_str("auth-vin") { - config.auth.vin = vin; - } - - if let Some(s) = matches.opt_str("ota-server") { - match Url::parse(&s) { - Ok(url) => config.ota.server = url, - Err(err) => exit!("Invalid ota-server URL: {}", err) - } - } - - if let Some(path) = matches.opt_str("ota-packages-dir") { - config.ota.packages_dir = path; - } + opts.optflag("h", "help", "print this help menu"); + opts.optflag("", "repl", "enable repl"); + opts.optflag("", "http", "enable interaction via http requests"); + opts.optflag("", "no-websocket", "disable websocket interaction"); + + opts.optopt("", "auth-server", "change the auth server URL", "URL"); + opts.optopt("", "auth-client-id", "change auth client id", "ID"); + opts.optopt("", "auth-secret", "change auth secret", "SECRET"); + opts.optopt("", "auth-vin", "change auth vin", "VIN"); + opts.optopt("", "config", "change config path", "PATH"); + opts.optopt("", "ota-server", "change ota server URL", "URL"); + opts.optopt("", "ota-packages-dir", "change downloaded directory for packages", "PATH"); + opts.optopt("", "ota-package-manager", "change package manager", "MANAGER"); + + let matches = opts.parse(&args[1..]).unwrap_or_else(|err| panic!(err.to_string())); + let config_file = matches.opt_str("config").unwrap_or_else(|| { + env::var("OTA_PLUS_CLIENT_CFG").unwrap_or("/opt/ats/ota/etc/ota.toml".to_string()) + }); + let mut config = config::load_config(&config_file).unwrap_or_else(|err| exit!("{}", err)); - if let Some(s) = matches.opt_str("ota-package-manager") { - config.ota.package_manager = match s.to_lowercase().as_str() { - "dpkg" => PackageManager::Dpkg, - "rpm" => PackageManager::Rpm, - path => PackageManager::File { filename: path.to_string(), succeeds: true }, - } + if matches.opt_present("h") { + exit!("{}", opts.usage(&format!("Usage: {} [options]", program))); } - if matches.opt_present("repl") { - config.test.looping = true; + config.test.repl = true; } - if matches.opt_present("http") { config.test.http = true; } + if matches.opt_present("no-websocket") { + config.test.websocket = false; + } + + matches.opt_str("auth-client-id").map(|id| config.auth.client_id = id); + matches.opt_str("auth-secret").map(|secret| config.auth.secret = secret); + matches.opt_str("auth-vin").map(|vin| config.auth.vin = vin); + matches.opt_str("ota-packages-dir").map(|path| config.ota.packages_dir = path); + + matches.opt_str("auth-server").map(|text| { + config.auth.server = Url::parse(&text).unwrap_or_else(|err| exit!("Invalid auth-server URL: {}", err)); + }); + matches.opt_str("ota-server").map(|text| { + config.ota.server = Url::parse(&text).unwrap_or_else(|err| exit!("Invalid ota-server URL: {}", err)); + }); + matches.opt_str("ota-package-manager").map(|text| { + config.ota.package_manager = text.parse::().unwrap_or_else(|err| exit!("Invalid package manager: {}", err)); + }); config } diff --git a/src/package_manager/mod.rs b/src/package_manager/mod.rs index 2ba31be..39c1420 100644 --- a/src/package_manager/mod.rs +++ b/src/package_manager/mod.rs @@ -1,3 +1,5 @@ +extern crate tempfile; + pub use self::package_manager::PackageManager; pub use self::tpm::assert_rx; diff --git a/src/package_manager/package_manager.rs b/src/package_manager/package_manager.rs index 1833eab..91b1b4f 100644 --- a/src/package_manager/package_manager.rs +++ b/src/package_manager/package_manager.rs @@ -1,7 +1,6 @@ -extern crate tempfile; - use rustc_serialize::{Decoder, Decodable}; use std::env::temp_dir; +use std::str::FromStr; use datatype::{Error, Package, UpdateResultCode}; use package_manager::{dpkg, rpm, tpm}; @@ -52,15 +51,26 @@ impl PackageManager { } } +impl FromStr for PackageManager { + type Err = Error; + + fn from_str(s: &str) -> Result { + match s.to_lowercase().as_str() { + "dpkg" => Ok(PackageManager::Dpkg), + "rpm" => Ok(PackageManager::Rpm), + + file if file.len() > 5 && file[..5].as_bytes() == b"file:" => { + Ok(PackageManager::File { filename: file[5..].to_string(), succeeds: true }) + } + + _ => Err(Error::ParseError(format!("unknown package manager: {}", s))) + } + } +} + impl Decodable for PackageManager { fn decode(d: &mut D) -> Result { - d.read_str().and_then(|s| { - match s.to_lowercase().as_str() { - "dpkg" => Ok(PackageManager::Dpkg), - "rpm" => Ok(PackageManager::Rpm), - _ => Ok(PackageManager::File { filename: s.to_string(), succeeds: true }), - } - }) + d.read_str().and_then(|s| Ok(s.parse::().expect("couldn't parse PackageManager"))) } } diff --git a/tests/ota_plus_client_tests.rs b/tests/ota_plus_client_tests.rs index 3f92284..b2d3500 100644 --- a/tests/ota_plus_client_tests.rs +++ b/tests/ota_plus_client_tests.rs @@ -1,12 +1,12 @@ extern crate tempfile; -use std::io::Write; -use std::process::Command; -use std::vec::Vec; use std::env; +use std::io::Write; use std::path::Path; +use std::process::Command; use tempfile::NamedTempFile; + fn bin_dir() -> String { let out_dir = env::var("OUT_DIR").unwrap(); let bin_dir = Path::new(&out_dir) @@ -21,20 +21,19 @@ fn client(args: &[&str]) -> String { .args(args) .output() .unwrap_or_else(|e| panic!("failed to execute child: {}", e)); - return String::from_utf8(output.stdout).unwrap() + String::from_utf8(output.stdout).unwrap() } fn client_with_config(args: &[&str], cfg: &str) -> String { let mut file = NamedTempFile::new().unwrap(); - let _ = file.write_all(cfg.as_bytes()).unwrap(); - - let arg: String = "--config=".to_string() + file.path().to_str().unwrap(); - let mut args: Vec<&str> = args.to_vec(); - + let _ = file.write_all(cfg.as_bytes()).unwrap(); + let arg = "--config=".to_string() + file.path().to_str().unwrap(); + let mut args = args.to_vec(); args.push(&arg); client(&args) } + #[test] fn help() { assert_eq!(client(&["-h"]), @@ -42,7 +41,9 @@ fn help() { Options: -h, --help print this help menu - --config PATH change config path + --repl enable repl + --http enable interaction via http requests + --no-websocket disable websocket interaction --auth-server URL change the auth server URL --auth-client-id ID @@ -50,14 +51,13 @@ Options: --auth-secret SECRET change auth secret --auth-vin VIN change auth vin + --config PATH change config path --ota-server URL change ota server URL --ota-packages-dir PATH change downloaded directory for packages --ota-package-manager MANAGER change package manager - --repl enable repl - --http enable interaction via http requests "#, bin_dir())); } @@ -74,13 +74,6 @@ fn bad_ota_server_url() { "Invalid ota-server URL: Url parse error: relative URL without a base\n") } -#[ignore] -#[test] -fn no_auth_server_to_connect_to() { - assert_eq!(client(&[""]), - "Authentication error, didn't receive access token: connection refused\n") -} - #[test] fn bad_section() { assert_eq!(client_with_config(&[""], "[uth]\n"), -- cgit v1.2.1 From ea6838187bb352861b34c34d8237470dca028fff Mon Sep 17 00:00:00 2001 From: Jerry Trieu Date: Fri, 24 Jun 2016 15:28:38 +0200 Subject: Move all files into rvi_sota_client/ --- .gitignore | 4 - Cargo.toml | 26 - LICENSE | 363 ------------- Makefile | 25 - README.md | 44 -- client.toml | 27 - docker/Dockerfile | 12 - docker/README.md | 29 - docker/client.toml | 14 - docker/run.sh | 16 - rvi_sota_client/.gitignore | 4 + rvi_sota_client/Cargo.toml | 26 + rvi_sota_client/LICENSE | 363 +++++++++++++ rvi_sota_client/Makefile | 25 + rvi_sota_client/README.md | 44 ++ rvi_sota_client/client.toml | 27 + rvi_sota_client/docker/Dockerfile | 12 + rvi_sota_client/docker/README.md | 29 + rvi_sota_client/docker/client.toml | 14 + rvi_sota_client/docker/run.sh | 16 + rvi_sota_client/src/configuration/client.rs | 179 +++++++ rvi_sota_client/src/configuration/common.rs | 148 ++++++ rvi_sota_client/src/configuration/configuration.rs | 181 +++++++ rvi_sota_client/src/configuration/dbus.rs | 155 ++++++ rvi_sota_client/src/configuration/mod.rs | 14 + rvi_sota_client/src/configuration/server.rs | 73 +++ rvi_sota_client/src/event/inbound.rs | 28 + rvi_sota_client/src/event/mod.rs | 9 + rvi_sota_client/src/event/outbound.rs | 74 +++ rvi_sota_client/src/genivi/dbus.rs | 121 +++++ rvi_sota_client/src/genivi/mod.rs | 4 + rvi_sota_client/src/genivi/sc.rs | 128 +++++ rvi_sota_client/src/genivi/start.rs | 101 ++++ rvi_sota_client/src/genivi/swm.rs | 63 +++ rvi_sota_client/src/lib.rs | 66 +++ rvi_sota_client/src/main.rs | 95 ++++ rvi_sota_client/src/remote/dw.rs | 581 +++++++++++++++++++++ rvi_sota_client/src/remote/http/api_client.rs | 137 +++++ rvi_sota_client/src/remote/http/auth.rs | 28 + rvi_sota_client/src/remote/http/datatype.rs | 242 +++++++++ rvi_sota_client/src/remote/http/http_client.rs | 94 ++++ rvi_sota_client/src/remote/http/hyper.rs | 135 +++++ rvi_sota_client/src/remote/http/mod.rs | 11 + rvi_sota_client/src/remote/http/remote.rs | 52 ++ rvi_sota_client/src/remote/http/update_poller.rs | 34 ++ rvi_sota_client/src/remote/jsonrpc.rs | 150 ++++++ rvi_sota_client/src/remote/mod.rs | 7 + rvi_sota_client/src/remote/parm.rs | 189 +++++++ rvi_sota_client/src/remote/rvi/edge.rs | 164 ++++++ rvi_sota_client/src/remote/rvi/message.rs | 61 +++ rvi_sota_client/src/remote/rvi/mod.rs | 18 + rvi_sota_client/src/remote/rvi/send.rs | 71 +++ rvi_sota_client/src/remote/svc.rs | 270 ++++++++++ rvi_sota_client/src/remote/upstream.rs | 8 + rvi_sota_client/src/test_library.rs | 72 +++ src/configuration/client.rs | 179 ------- src/configuration/common.rs | 148 ------ src/configuration/configuration.rs | 181 ------- src/configuration/dbus.rs | 155 ------ src/configuration/mod.rs | 14 - src/configuration/server.rs | 73 --- src/event/inbound.rs | 28 - src/event/mod.rs | 9 - src/event/outbound.rs | 74 --- src/genivi/dbus.rs | 121 ----- src/genivi/mod.rs | 4 - src/genivi/sc.rs | 128 ----- src/genivi/start.rs | 101 ---- src/genivi/swm.rs | 63 --- src/lib.rs | 66 --- src/main.rs | 95 ---- src/remote/dw.rs | 581 --------------------- src/remote/http/api_client.rs | 137 ----- src/remote/http/auth.rs | 28 - src/remote/http/datatype.rs | 242 --------- src/remote/http/http_client.rs | 94 ---- src/remote/http/hyper.rs | 135 ----- src/remote/http/mod.rs | 11 - src/remote/http/remote.rs | 52 -- src/remote/http/update_poller.rs | 34 -- src/remote/jsonrpc.rs | 150 ------ src/remote/mod.rs | 7 - src/remote/parm.rs | 189 ------- src/remote/rvi/edge.rs | 164 ------ src/remote/rvi/message.rs | 61 --- src/remote/rvi/mod.rs | 18 - src/remote/rvi/send.rs | 71 --- src/remote/svc.rs | 270 ---------- src/remote/upstream.rs | 8 - src/test_library.rs | 72 --- 90 files changed, 4323 insertions(+), 4323 deletions(-) delete mode 100644 .gitignore delete mode 100644 Cargo.toml delete mode 100644 LICENSE delete mode 100644 Makefile delete mode 100644 README.md delete mode 100644 client.toml delete mode 100644 docker/Dockerfile delete mode 100644 docker/README.md delete mode 100644 docker/client.toml delete mode 100755 docker/run.sh create mode 100644 rvi_sota_client/.gitignore create mode 100644 rvi_sota_client/Cargo.toml create mode 100644 rvi_sota_client/LICENSE create mode 100644 rvi_sota_client/Makefile create mode 100644 rvi_sota_client/README.md create mode 100644 rvi_sota_client/client.toml create mode 100644 rvi_sota_client/docker/Dockerfile create mode 100644 rvi_sota_client/docker/README.md create mode 100644 rvi_sota_client/docker/client.toml create mode 100755 rvi_sota_client/docker/run.sh create mode 100644 rvi_sota_client/src/configuration/client.rs create mode 100644 rvi_sota_client/src/configuration/common.rs create mode 100644 rvi_sota_client/src/configuration/configuration.rs create mode 100644 rvi_sota_client/src/configuration/dbus.rs create mode 100644 rvi_sota_client/src/configuration/mod.rs create mode 100644 rvi_sota_client/src/configuration/server.rs create mode 100644 rvi_sota_client/src/event/inbound.rs create mode 100644 rvi_sota_client/src/event/mod.rs create mode 100644 rvi_sota_client/src/event/outbound.rs create mode 100644 rvi_sota_client/src/genivi/dbus.rs create mode 100644 rvi_sota_client/src/genivi/mod.rs create mode 100644 rvi_sota_client/src/genivi/sc.rs create mode 100644 rvi_sota_client/src/genivi/start.rs create mode 100644 rvi_sota_client/src/genivi/swm.rs create mode 100644 rvi_sota_client/src/lib.rs create mode 100644 rvi_sota_client/src/main.rs create mode 100644 rvi_sota_client/src/remote/dw.rs create mode 100644 rvi_sota_client/src/remote/http/api_client.rs create mode 100644 rvi_sota_client/src/remote/http/auth.rs create mode 100644 rvi_sota_client/src/remote/http/datatype.rs create mode 100644 rvi_sota_client/src/remote/http/http_client.rs create mode 100644 rvi_sota_client/src/remote/http/hyper.rs create mode 100644 rvi_sota_client/src/remote/http/mod.rs create mode 100644 rvi_sota_client/src/remote/http/remote.rs create mode 100644 rvi_sota_client/src/remote/http/update_poller.rs create mode 100644 rvi_sota_client/src/remote/jsonrpc.rs create mode 100644 rvi_sota_client/src/remote/mod.rs create mode 100644 rvi_sota_client/src/remote/parm.rs create mode 100644 rvi_sota_client/src/remote/rvi/edge.rs create mode 100644 rvi_sota_client/src/remote/rvi/message.rs create mode 100644 rvi_sota_client/src/remote/rvi/mod.rs create mode 100644 rvi_sota_client/src/remote/rvi/send.rs create mode 100644 rvi_sota_client/src/remote/svc.rs create mode 100644 rvi_sota_client/src/remote/upstream.rs create mode 100644 rvi_sota_client/src/test_library.rs delete mode 100644 src/configuration/client.rs delete mode 100644 src/configuration/common.rs delete mode 100644 src/configuration/configuration.rs delete mode 100644 src/configuration/dbus.rs delete mode 100644 src/configuration/mod.rs delete mode 100644 src/configuration/server.rs delete mode 100644 src/event/inbound.rs delete mode 100644 src/event/mod.rs delete mode 100644 src/event/outbound.rs delete mode 100644 src/genivi/dbus.rs delete mode 100644 src/genivi/mod.rs delete mode 100644 src/genivi/sc.rs delete mode 100644 src/genivi/start.rs delete mode 100644 src/genivi/swm.rs delete mode 100644 src/lib.rs delete mode 100644 src/main.rs delete mode 100644 src/remote/dw.rs delete mode 100644 src/remote/http/api_client.rs delete mode 100644 src/remote/http/auth.rs delete mode 100644 src/remote/http/datatype.rs delete mode 100644 src/remote/http/http_client.rs delete mode 100644 src/remote/http/hyper.rs delete mode 100644 src/remote/http/mod.rs delete mode 100644 src/remote/http/remote.rs delete mode 100644 src/remote/http/update_poller.rs delete mode 100644 src/remote/jsonrpc.rs delete mode 100644 src/remote/mod.rs delete mode 100644 src/remote/parm.rs delete mode 100644 src/remote/rvi/edge.rs delete mode 100644 src/remote/rvi/message.rs delete mode 100644 src/remote/rvi/mod.rs delete mode 100644 src/remote/rvi/send.rs delete mode 100644 src/remote/svc.rs delete mode 100644 src/remote/upstream.rs delete mode 100644 src/test_library.rs diff --git a/.gitignore b/.gitignore deleted file mode 100644 index 1a73257..0000000 --- a/.gitignore +++ /dev/null @@ -1,4 +0,0 @@ -target -Cargo.lock -docker/sota_client -docker/sota-installer diff --git a/Cargo.toml b/Cargo.toml deleted file mode 100644 index b68c143..0000000 --- a/Cargo.toml +++ /dev/null @@ -1,26 +0,0 @@ -[package] -name = "sota_client" -version = "0.2.0" -authors = ["Jerry Trieu ", - "Philipp Millar "] - -[[bin]] -doc = false -name = "sota_client" -path = "src/main.rs" - -[dependencies] -hyper = "*" -rustc-serialize = "*" -time = "*" -url = "*" -log = "*" -env_logger = "*" -rust-crypto = "*" -toml = "*" -dbus = "*" -getopts = "*" -tempfile = "*" - -[dev-dependencies] -rand = "*" diff --git a/LICENSE b/LICENSE deleted file mode 100644 index e87a115..0000000 --- a/LICENSE +++ /dev/null @@ -1,363 +0,0 @@ -Mozilla Public License, version 2.0 - -1. Definitions - -1.1. "Contributor" - - means each individual or legal entity that creates, contributes to the - creation of, or owns Covered Software. - -1.2. "Contributor Version" - - means the combination of the Contributions of others (if any) used by a - Contributor and that particular Contributor's Contribution. - -1.3. "Contribution" - - means Covered Software of a particular Contributor. - -1.4. "Covered Software" - - means Source Code Form to which the initial Contributor has attached the - notice in Exhibit A, the Executable Form of such Source Code Form, and - Modifications of such Source Code Form, in each case including portions - thereof. - -1.5. "Incompatible With Secondary Licenses" - means - - a. that the initial Contributor has attached the notice described in - Exhibit B to the Covered Software; or - - b. that the Covered Software was made available under the terms of - version 1.1 or earlier of the License, but not also under the terms of - a Secondary License. - -1.6. "Executable Form" - - means any form of the work other than Source Code Form. - -1.7. "Larger Work" - - means a work that combines Covered Software with other material, in a - separate file or files, that is not Covered Software. - -1.8. "License" - - means this document. - -1.9. "Licensable" - - means having the right to grant, to the maximum extent possible, whether - at the time of the initial grant or subsequently, any and all of the - rights conveyed by this License. - -1.10. "Modifications" - - means any of the following: - - a. any file in Source Code Form that results from an addition to, - deletion from, or modification of the contents of Covered Software; or - - b. any new file in Source Code Form that contains any Covered Software. - -1.11. "Patent Claims" of a Contributor - - means any patent claim(s), including without limitation, method, - process, and apparatus claims, in any patent Licensable by such - Contributor that would be infringed, but for the grant of the License, - by the making, using, selling, offering for sale, having made, import, - or transfer of either its Contributions or its Contributor Version. - -1.12. "Secondary License" - - means either the GNU General Public License, Version 2.0, the GNU Lesser - General Public License, Version 2.1, the GNU Affero General Public - License, Version 3.0, or any later versions of those licenses. - -1.13. "Source Code Form" - - means the form of the work preferred for making modifications. - -1.14. "You" (or "Your") - - means an individual or a legal entity exercising rights under this - License. For legal entities, "You" includes any entity that controls, is - controlled by, or is under common control with You. For purposes of this - definition, "control" means (a) the power, direct or indirect, to cause - the direction or management of such entity, whether by contract or - otherwise, or (b) ownership of more than fifty percent (50%) of the - outstanding shares or beneficial ownership of such entity. - - -2. License Grants and Conditions - -2.1. Grants - - Each Contributor hereby grants You a world-wide, royalty-free, - non-exclusive license: - - a. under intellectual property rights (other than patent or trademark) - Licensable by such Contributor to use, reproduce, make available, - modify, display, perform, distribute, and otherwise exploit its - Contributions, either on an unmodified basis, with Modifications, or - as part of a Larger Work; and - - b. under Patent Claims of such Contributor to make, use, sell, offer for - sale, have made, import, and otherwise transfer either its - Contributions or its Contributor Version. - -2.2. Effective Date - - The licenses granted in Section 2.1 with respect to any Contribution - become effective for each Contribution on the date the Contributor first - distributes such Contribution. - -2.3. Limitations on Grant Scope - - The licenses granted in this Section 2 are the only rights granted under - this License. No additional rights or licenses will be implied from the - distribution or licensing of Covered Software under this License. - Notwithstanding Section 2.1(b) above, no patent license is granted by a - Contributor: - - a. for any code that a Contributor has removed from Covered Software; or - - b. for infringements caused by: (i) Your and any other third party's - modifications of Covered Software, or (ii) the combination of its - Contributions with other software (except as part of its Contributor - Version); or - - c. under Patent Claims infringed by Covered Software in the absence of - its Contributions. - - This License does not grant any rights in the trademarks, service marks, - or logos of any Contributor (except as may be necessary to comply with - the notice requirements in Section 3.4). - -2.4. Subsequent Licenses - - No Contributor makes additional grants as a result of Your choice to - distribute the Covered Software under a subsequent version of this - License (see Section 10.2) or under the terms of a Secondary License (if - permitted under the terms of Section 3.3). - -2.5. Representation - - Each Contributor represents that the Contributor believes its - Contributions are its original creation(s) or it has sufficient rights to - grant the rights to its Contributions conveyed by this License. - -2.6. Fair Use - - This License is not intended to limit any rights You have under - applicable copyright doctrines of fair use, fair dealing, or other - equivalents. - -2.7. Conditions - - Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted in - Section 2.1. - - -3. Responsibilities - -3.1. Distribution of Source Form - - All distribution of Covered Software in Source Code Form, including any - Modifications that You create or to which You contribute, must be under - the terms of this License. You must inform recipients that the Source - Code Form of the Covered Software is governed by the terms of this - License, and how they can obtain a copy of this License. You may not - attempt to alter or restrict the recipients' rights in the Source Code - Form. - -3.2. Distribution of Executable Form - - If You distribute Covered Software in Executable Form then: - - a. such Covered Software must also be made available in Source Code Form, - as described in Section 3.1, and You must inform recipients of the - Executable Form how they can obtain a copy of such Source Code Form by - reasonable means in a timely manner, at a charge no more than the cost - of distribution to the recipient; and - - b. You may distribute such Executable Form under the terms of this - License, or sublicense it under different terms, provided that the - license for the Executable Form does not attempt to limit or alter the - recipients' rights in the Source Code Form under this License. - -3.3. Distribution of a Larger Work - - You may create and distribute a Larger Work under terms of Your choice, - provided that You also comply with the requirements of this License for - the Covered Software. If the Larger Work is a combination of Covered - Software with a work governed by one or more Secondary Licenses, and the - Covered Software is not Incompatible With Secondary Licenses, this - License permits You to additionally distribute such Covered Software - under the terms of such Secondary License(s), so that the recipient of - the Larger Work may, at their option, further distribute the Covered - Software under the terms of either this License or such Secondary - License(s). - -3.4. Notices - - You may not remove or alter the substance of any license notices - (including copyright notices, patent notices, disclaimers of warranty, or - limitations of liability) contained within the Source Code Form of the - Covered Software, except that You may alter any license notices to the - extent required to remedy known factual inaccuracies. - -3.5. Application of Additional Terms - - You may choose to offer, and to charge a fee for, warranty, support, - indemnity or liability obligations to one or more recipients of Covered - Software. However, You may do so only on Your own behalf, and not on - behalf of any Contributor. You must make it absolutely clear that any - such warranty, support, indemnity, or liability obligation is offered by - You alone, and You hereby agree to indemnify every Contributor for any - liability incurred by such Contributor as a result of warranty, support, - indemnity or liability terms You offer. You may include additional - disclaimers of warranty and limitations of liability specific to any - jurisdiction. - -4. Inability to Comply Due to Statute or Regulation - - If it is impossible for You to comply with any of the terms of this License - with respect to some or all of the Covered Software due to statute, - judicial order, or regulation then You must: (a) comply with the terms of - this License to the maximum extent possible; and (b) describe the - limitations and the code they affect. Such description must be placed in a - text file included with all distributions of the Covered Software under - this License. Except to the extent prohibited by statute or regulation, - such description must be sufficiently detailed for a recipient of ordinary - skill to be able to understand it. - -5. Termination - -5.1. The rights granted under this License will terminate automatically if You - fail to comply with any of its terms. However, if You become compliant, - then the rights granted under this License from a particular Contributor - are reinstated (a) provisionally, unless and until such Contributor - explicitly and finally terminates Your grants, and (b) on an ongoing - basis, if such Contributor fails to notify You of the non-compliance by - some reasonable means prior to 60 days after You have come back into - compliance. Moreover, Your grants from a particular Contributor are - reinstated on an ongoing basis if such Contributor notifies You of the - non-compliance by some reasonable means, this is the first time You have - received notice of non-compliance with this License from such - Contributor, and You become compliant prior to 30 days after Your receipt - of the notice. - -5.2. If You initiate litigation against any entity by asserting a patent - infringement claim (excluding declaratory judgment actions, - counter-claims, and cross-claims) alleging that a Contributor Version - directly or indirectly infringes any patent, then the rights granted to - You by any and all Contributors for the Covered Software under Section - 2.1 of this License shall terminate. - -5.3. In the event of termination under Sections 5.1 or 5.2 above, all end user - license agreements (excluding distributors and resellers) which have been - validly granted by You or Your distributors under this License prior to - termination shall survive termination. - -6. Disclaimer of Warranty - - Covered Software is provided under this License on an "as is" basis, - without warranty of any kind, either expressed, implied, or statutory, - including, without limitation, warranties that the Covered Software is free - of defects, merchantable, fit for a particular purpose or non-infringing. - The entire risk as to the quality and performance of the Covered Software - is with You. Should any Covered Software prove defective in any respect, - You (not any Contributor) assume the cost of any necessary servicing, - repair, or correction. This disclaimer of warranty constitutes an essential - part of this License. No use of any Covered Software is authorized under - this License except under this disclaimer. - -7. Limitation of Liability - - Under no circumstances and under no legal theory, whether tort (including - negligence), contract, or otherwise, shall any Contributor, or anyone who - distributes Covered Software as permitted above, be liable to You for any - direct, indirect, special, incidental, or consequential damages of any - character including, without limitation, damages for lost profits, loss of - goodwill, work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses, even if such party shall have been - informed of the possibility of such damages. This limitation of liability - shall not apply to liability for death or personal injury resulting from - such party's negligence to the extent applicable law prohibits such - limitation. Some jurisdictions do not allow the exclusion or limitation of - incidental or consequential damages, so this exclusion and limitation may - not apply to You. - -8. Litigation - - Any litigation relating to this License may be brought only in the courts - of a jurisdiction where the defendant maintains its principal place of - business and such litigation shall be governed by laws of that - jurisdiction, without reference to its conflict-of-law provisions. Nothing - in this Section shall prevent a party's ability to bring cross-claims or - counter-claims. - -9. Miscellaneous - - This License represents the complete agreement concerning the subject - matter hereof. If any provision of this License is held to be - unenforceable, such provision shall be reformed only to the extent - necessary to make it enforceable. Any law or regulation which provides that - the language of a contract shall be construed against the drafter shall not - be used to construe this License against a Contributor. - - -10. Versions of the License - -10.1. New Versions - - Mozilla Foundation is the license steward. Except as provided in Section - 10.3, no one other than the license steward has the right to modify or - publish new versions of this License. Each version will be given a - distinguishing version number. - -10.2. Effect of New Versions - - You may distribute the Covered Software under the terms of the version - of the License under which You originally received the Covered Software, - or under the terms of any subsequent version published by the license - steward. - -10.3. Modified Versions - - If you create software not governed by this License, and you want to - create a new license for such software, you may create and use a - modified version of this License if you rename the license and remove - any references to the name of the license steward (except to note that - such modified license differs from this License). - -10.4. Distributing Source Code Form that is Incompatible With Secondary - Licenses If You choose to distribute Source Code Form that is - Incompatible With Secondary Licenses under the terms of this version of - the License, the notice described in Exhibit B of this License must be - attached. - -Exhibit A - Source Code Form License Notice - - This Source Code Form is subject to the - terms of the Mozilla Public License, v. - 2.0. If a copy of the MPL was not - distributed with this file, You can - obtain one at - http://mozilla.org/MPL/2.0/. - -If it is not possible or desirable to put the notice in a particular file, -then You may include the notice in a location (such as a LICENSE file in a -relevant directory) where a recipient would be likely to look for such a -notice. - -You may add additional accurate notices of copyright ownership. - -Exhibit B - "Incompatible With Secondary Licenses" Notice - - This Source Code Form is "Incompatible - With Secondary Licenses", as defined by - the Mozilla Public License, v. 2.0. - diff --git a/Makefile b/Makefile deleted file mode 100644 index 95be4d1..0000000 --- a/Makefile +++ /dev/null @@ -1,25 +0,0 @@ -.PHONY: release debug docker all clean - -SRCS := $(wildcard src/*.rs) -SRCS += Cargo.toml - -target/release/sota_client: $(SRCS) - cargo build --release - -target/debug/sota_client: $(SRCS) - cargo build - -docker/sota_client: target/release/sota_client - cp target/release/sota_client docker - -docker: docker/sota_client docker/client.toml - docker build -t advancedtelematic/sota-client docker - -clean: - rm -f docker/sota_client - cargo clean - -# aliases -debug: target/debug/sota_client -release: target/release/sota_client -all: docker diff --git a/README.md b/README.md deleted file mode 100644 index ba2d7ef..0000000 --- a/README.md +++ /dev/null @@ -1,44 +0,0 @@ -# sota-client - -This is the client (in-vehicle) portion of the SOTA project. See the [main SOTA Server project](https://github.com/advancedtelematic/rvi_sota_server) and [associated architecture document](http://advancedtelematic.github.io/rvi_sota_server/dev/architecture.html) for more information. - -## Building and running - -To see the SOTA client in action, you will need to first build and run the [SOTA Core Server](https://github.com/advancedtelematic/rvi_sota_server). - -### Building SOTA Client - -As long as you have `rust 1.8.0` and `cargo` installed, `cargo build` should build the `sota_client` executable in `target/debug`. - -### Running SOTA Client - -You can run the client with `target/debug/sota_client -c client.toml`. It will try to connect to the `core` service of `rvi_sota_server` specified in the `[server]` section of `client.toml`. If the `[server.auth]` section contains `client_id`, `client_secret` and `url`, it will first try to obtain an OAuth access token from `url` and then authenticate all the requests to the server with it. - -#### Running with HTTP communication - -HTTP tends to be much easier to get working than RVI. Enable HTTP by setting `http = “true”` in the [client] section of `client.toml`, and setting the url value in the [server] section to the url of your sota-core deployment. - -#### Running with RVI nodes - -To connect to the SOTA Server over RVI, run the `rvi_sota_server` project with RVI Nodes. - -You can build RVI directly from [its GitHub repo](https://github.com/GENIVI/rvi_core), or simply run our docker image. These instructions assume you are running the docker image. - -1. Pull the image: `docker pull advancedtelematic/rvi`. -2. In two terminal windows, run the rvi client and server nodes - * Client: `docker run -it --name rvi-client --expose 8901 --expose 8905-8908 -p 8901:8901 -p 8905:8905 -p 8906:8906 -p 8907:8907 -p 8908:8908 advancedtelematic/rvi client` - * Server: `docker run -it --name rvi-server --expose 8801 --expose 8805-8808 -p 8801:8801 -p 8805:8805 -p 8806:8806 -p 8807:8807 -p 8808:8808 advancedtelematic/rvi server` - -Now you can remove the `[server]` section from `client.toml` and disable http. - -### Running with GENIVI Software Loading Manager - -You can run the (GENIVI SWLM)[https://github.com/GENIVI/genivi_swm] to process the incoming update. You will need to run both the SWLM and SOTA Client as root to communicate over the DBus session. - -### Documentation - -To create a static HTML version of the module documentation run `cargo doc`. -Unfortunately this will only create documentation for the public interface. If -you want the full documentation you need to run `cargo doc -v` extract the -`rustdoc` command from the output and append `--no-defaults --passes -"collapse-docs" --passes "unindent-comments" --passes strip-hidden` to it. diff --git a/client.toml b/client.toml deleted file mode 100644 index 8f8f8df..0000000 --- a/client.toml +++ /dev/null @@ -1,27 +0,0 @@ -[client] -storage_dir = "/var/sota" -rvi_url = "http://127.0.0.1:8901" -edge_url = "http://127.0.0.1:9080" -timeout = 20 -vin_match = 2 -http = "true" - -[server] -url = "http://127.0.0.1:8080" -polling_interval = 10 -vin = "V1234567890123456" -packages_dir = "/tmp/" -packages_extension = "deb" - -[server.auth] -client_id = "client-id" -client_secret = "client-secret" -url = "http://127.0.0.1:9001/token" - -[dbus] -name = "org.genivi.SotaClient" -path = "/org/genivi/SotaClient" -interface = "org.genivi.SotaClient" -software_manager = "org.genivi.SoftwareLoadingManager" -software_manager_path = "/org/genivi/SoftwareLoadingManager" -timeout = 60 diff --git a/docker/Dockerfile b/docker/Dockerfile deleted file mode 100644 index 1d93a14..0000000 --- a/docker/Dockerfile +++ /dev/null @@ -1,12 +0,0 @@ -FROM debian:8 - -RUN apt-get update \ - && apt-get install -y openssl dbus libdbus-1-3 dbus-x11 libdbus-glib-1-2 \ - && mkdir /var/sota - -COPY sota_client /usr/bin/sota_client -COPY run.sh /usr/bin/run.sh -COPY client.toml /var/sota/client.toml - -EXPOSE 9080 -CMD ["/usr/bin/run.sh"] diff --git a/docker/README.md b/docker/README.md deleted file mode 100644 index e6ec854..0000000 --- a/docker/README.md +++ /dev/null @@ -1,29 +0,0 @@ -# Sota Client in Docker - -First you need to spawn two working and connected rvi nodes. One server and one -client. See the [rvi documentation](https://github.com/PDXostc/rvi_core) for -instructions. We assume you've named them `rvi-server` and `rvi-client` -respectively. - -Then you can run the Sota client with - -``` -docker run -it --name sota-client -p 9000:9000 --link rvi-client:rvi-client advancedtelematic/sota-client -``` - -The generated image accepts several environment variables for configuration. - -* `RVI_ADDR`: the address under which the RVI client node can be reached, - defaults to `rvi_client` -* `RVI_PORT`: the port under which the Service Edge of the RVI client node can - be reached, defaults to `8901` -* `SOTA_CLIENT_ADDR`: The address the client should listen on and advertise, - defaults to the address of `eth0` in the running container -* `SOTA_CLIENT_PORT`: The port the client should advertise and listen on, - defaults to `9000` - -## Known Issues - -* The address is both the listening and advertised address. That means you - currently can't connect the client to a RVI node, thats running on a different - docker host machine. diff --git a/docker/client.toml b/docker/client.toml deleted file mode 100644 index 0882ce4..0000000 --- a/docker/client.toml +++ /dev/null @@ -1,14 +0,0 @@ -[client] -storage_dir = "/var/sota" -rvi_url = "http://127.0.0.1:8901" -edge_url = "127.0.0.1:9080" -timeout = 20 -vin_match = 2 - -[dbus] -name = "org.genivi.sota_client" -path = "/org/genivi/sota_client" -interface = "org.genivi.sota_client" -software_manager = "org.genivi.software_loading_manager" -software_manager_path = "/org/genivi/software_loading_manager" -timeout = 60 diff --git a/docker/run.sh b/docker/run.sh deleted file mode 100755 index 250ca82..0000000 --- a/docker/run.sh +++ /dev/null @@ -1,16 +0,0 @@ -#!/bin/bash -# bash "strict mode", see -# http://redsymbol.net/articles/unofficial-bash-strict-mode/ -set -euo pipefail -IFS=$'\n\t' - -eval $(dbus-launch) -export DBUS_SESSION_BUS_ADDRESS -export DBUS_SESSION_BUS_PID - -LOGLEVEL=${LOGLEVEL:-"info"} -SOTA_CLIENT="${SOTA_CLIENT_ADDR:-0.0.0.0}:${SOTA_CLIENT_PORT:-9080}" -RVI="${RVI:-http://rvi-client:8901}" - -export RUST_LOG=${RUST_LOG:-"sota_client=$LOGLEVEL"} -/usr/bin/sota_client -c /var/sota/client.toml -r "$RVI" -e "$SOTA_CLIENT" diff --git a/rvi_sota_client/.gitignore b/rvi_sota_client/.gitignore new file mode 100644 index 0000000..1a73257 --- /dev/null +++ b/rvi_sota_client/.gitignore @@ -0,0 +1,4 @@ +target +Cargo.lock +docker/sota_client +docker/sota-installer diff --git a/rvi_sota_client/Cargo.toml b/rvi_sota_client/Cargo.toml new file mode 100644 index 0000000..b68c143 --- /dev/null +++ b/rvi_sota_client/Cargo.toml @@ -0,0 +1,26 @@ +[package] +name = "sota_client" +version = "0.2.0" +authors = ["Jerry Trieu ", + "Philipp Millar "] + +[[bin]] +doc = false +name = "sota_client" +path = "src/main.rs" + +[dependencies] +hyper = "*" +rustc-serialize = "*" +time = "*" +url = "*" +log = "*" +env_logger = "*" +rust-crypto = "*" +toml = "*" +dbus = "*" +getopts = "*" +tempfile = "*" + +[dev-dependencies] +rand = "*" diff --git a/rvi_sota_client/LICENSE b/rvi_sota_client/LICENSE new file mode 100644 index 0000000..e87a115 --- /dev/null +++ b/rvi_sota_client/LICENSE @@ -0,0 +1,363 @@ +Mozilla Public License, version 2.0 + +1. Definitions + +1.1. "Contributor" + + means each individual or legal entity that creates, contributes to the + creation of, or owns Covered Software. + +1.2. "Contributor Version" + + means the combination of the Contributions of others (if any) used by a + Contributor and that particular Contributor's Contribution. + +1.3. "Contribution" + + means Covered Software of a particular Contributor. + +1.4. "Covered Software" + + means Source Code Form to which the initial Contributor has attached the + notice in Exhibit A, the Executable Form of such Source Code Form, and + Modifications of such Source Code Form, in each case including portions + thereof. + +1.5. "Incompatible With Secondary Licenses" + means + + a. that the initial Contributor has attached the notice described in + Exhibit B to the Covered Software; or + + b. that the Covered Software was made available under the terms of + version 1.1 or earlier of the License, but not also under the terms of + a Secondary License. + +1.6. "Executable Form" + + means any form of the work other than Source Code Form. + +1.7. "Larger Work" + + means a work that combines Covered Software with other material, in a + separate file or files, that is not Covered Software. + +1.8. "License" + + means this document. + +1.9. "Licensable" + + means having the right to grant, to the maximum extent possible, whether + at the time of the initial grant or subsequently, any and all of the + rights conveyed by this License. + +1.10. "Modifications" + + means any of the following: + + a. any file in Source Code Form that results from an addition to, + deletion from, or modification of the contents of Covered Software; or + + b. any new file in Source Code Form that contains any Covered Software. + +1.11. "Patent Claims" of a Contributor + + means any patent claim(s), including without limitation, method, + process, and apparatus claims, in any patent Licensable by such + Contributor that would be infringed, but for the grant of the License, + by the making, using, selling, offering for sale, having made, import, + or transfer of either its Contributions or its Contributor Version. + +1.12. "Secondary License" + + means either the GNU General Public License, Version 2.0, the GNU Lesser + General Public License, Version 2.1, the GNU Affero General Public + License, Version 3.0, or any later versions of those licenses. + +1.13. "Source Code Form" + + means the form of the work preferred for making modifications. + +1.14. "You" (or "Your") + + means an individual or a legal entity exercising rights under this + License. For legal entities, "You" includes any entity that controls, is + controlled by, or is under common control with You. For purposes of this + definition, "control" means (a) the power, direct or indirect, to cause + the direction or management of such entity, whether by contract or + otherwise, or (b) ownership of more than fifty percent (50%) of the + outstanding shares or beneficial ownership of such entity. + + +2. License Grants and Conditions + +2.1. Grants + + Each Contributor hereby grants You a world-wide, royalty-free, + non-exclusive license: + + a. under intellectual property rights (other than patent or trademark) + Licensable by such Contributor to use, reproduce, make available, + modify, display, perform, distribute, and otherwise exploit its + Contributions, either on an unmodified basis, with Modifications, or + as part of a Larger Work; and + + b. under Patent Claims of such Contributor to make, use, sell, offer for + sale, have made, import, and otherwise transfer either its + Contributions or its Contributor Version. + +2.2. Effective Date + + The licenses granted in Section 2.1 with respect to any Contribution + become effective for each Contribution on the date the Contributor first + distributes such Contribution. + +2.3. Limitations on Grant Scope + + The licenses granted in this Section 2 are the only rights granted under + this License. No additional rights or licenses will be implied from the + distribution or licensing of Covered Software under this License. + Notwithstanding Section 2.1(b) above, no patent license is granted by a + Contributor: + + a. for any code that a Contributor has removed from Covered Software; or + + b. for infringements caused by: (i) Your and any other third party's + modifications of Covered Software, or (ii) the combination of its + Contributions with other software (except as part of its Contributor + Version); or + + c. under Patent Claims infringed by Covered Software in the absence of + its Contributions. + + This License does not grant any rights in the trademarks, service marks, + or logos of any Contributor (except as may be necessary to comply with + the notice requirements in Section 3.4). + +2.4. Subsequent Licenses + + No Contributor makes additional grants as a result of Your choice to + distribute the Covered Software under a subsequent version of this + License (see Section 10.2) or under the terms of a Secondary License (if + permitted under the terms of Section 3.3). + +2.5. Representation + + Each Contributor represents that the Contributor believes its + Contributions are its original creation(s) or it has sufficient rights to + grant the rights to its Contributions conveyed by this License. + +2.6. Fair Use + + This License is not intended to limit any rights You have under + applicable copyright doctrines of fair use, fair dealing, or other + equivalents. + +2.7. Conditions + + Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted in + Section 2.1. + + +3. Responsibilities + +3.1. Distribution of Source Form + + All distribution of Covered Software in Source Code Form, including any + Modifications that You create or to which You contribute, must be under + the terms of this License. You must inform recipients that the Source + Code Form of the Covered Software is governed by the terms of this + License, and how they can obtain a copy of this License. You may not + attempt to alter or restrict the recipients' rights in the Source Code + Form. + +3.2. Distribution of Executable Form + + If You distribute Covered Software in Executable Form then: + + a. such Covered Software must also be made available in Source Code Form, + as described in Section 3.1, and You must inform recipients of the + Executable Form how they can obtain a copy of such Source Code Form by + reasonable means in a timely manner, at a charge no more than the cost + of distribution to the recipient; and + + b. You may distribute such Executable Form under the terms of this + License, or sublicense it under different terms, provided that the + license for the Executable Form does not attempt to limit or alter the + recipients' rights in the Source Code Form under this License. + +3.3. Distribution of a Larger Work + + You may create and distribute a Larger Work under terms of Your choice, + provided that You also comply with the requirements of this License for + the Covered Software. If the Larger Work is a combination of Covered + Software with a work governed by one or more Secondary Licenses, and the + Covered Software is not Incompatible With Secondary Licenses, this + License permits You to additionally distribute such Covered Software + under the terms of such Secondary License(s), so that the recipient of + the Larger Work may, at their option, further distribute the Covered + Software under the terms of either this License or such Secondary + License(s). + +3.4. Notices + + You may not remove or alter the substance of any license notices + (including copyright notices, patent notices, disclaimers of warranty, or + limitations of liability) contained within the Source Code Form of the + Covered Software, except that You may alter any license notices to the + extent required to remedy known factual inaccuracies. + +3.5. Application of Additional Terms + + You may choose to offer, and to charge a fee for, warranty, support, + indemnity or liability obligations to one or more recipients of Covered + Software. However, You may do so only on Your own behalf, and not on + behalf of any Contributor. You must make it absolutely clear that any + such warranty, support, indemnity, or liability obligation is offered by + You alone, and You hereby agree to indemnify every Contributor for any + liability incurred by such Contributor as a result of warranty, support, + indemnity or liability terms You offer. You may include additional + disclaimers of warranty and limitations of liability specific to any + jurisdiction. + +4. Inability to Comply Due to Statute or Regulation + + If it is impossible for You to comply with any of the terms of this License + with respect to some or all of the Covered Software due to statute, + judicial order, or regulation then You must: (a) comply with the terms of + this License to the maximum extent possible; and (b) describe the + limitations and the code they affect. Such description must be placed in a + text file included with all distributions of the Covered Software under + this License. Except to the extent prohibited by statute or regulation, + such description must be sufficiently detailed for a recipient of ordinary + skill to be able to understand it. + +5. Termination + +5.1. The rights granted under this License will terminate automatically if You + fail to comply with any of its terms. However, if You become compliant, + then the rights granted under this License from a particular Contributor + are reinstated (a) provisionally, unless and until such Contributor + explicitly and finally terminates Your grants, and (b) on an ongoing + basis, if such Contributor fails to notify You of the non-compliance by + some reasonable means prior to 60 days after You have come back into + compliance. Moreover, Your grants from a particular Contributor are + reinstated on an ongoing basis if such Contributor notifies You of the + non-compliance by some reasonable means, this is the first time You have + received notice of non-compliance with this License from such + Contributor, and You become compliant prior to 30 days after Your receipt + of the notice. + +5.2. If You initiate litigation against any entity by asserting a patent + infringement claim (excluding declaratory judgment actions, + counter-claims, and cross-claims) alleging that a Contributor Version + directly or indirectly infringes any patent, then the rights granted to + You by any and all Contributors for the Covered Software under Section + 2.1 of this License shall terminate. + +5.3. In the event of termination under Sections 5.1 or 5.2 above, all end user + license agreements (excluding distributors and resellers) which have been + validly granted by You or Your distributors under this License prior to + termination shall survive termination. + +6. Disclaimer of Warranty + + Covered Software is provided under this License on an "as is" basis, + without warranty of any kind, either expressed, implied, or statutory, + including, without limitation, warranties that the Covered Software is free + of defects, merchantable, fit for a particular purpose or non-infringing. + The entire risk as to the quality and performance of the Covered Software + is with You. Should any Covered Software prove defective in any respect, + You (not any Contributor) assume the cost of any necessary servicing, + repair, or correction. This disclaimer of warranty constitutes an essential + part of this License. No use of any Covered Software is authorized under + this License except under this disclaimer. + +7. Limitation of Liability + + Under no circumstances and under no legal theory, whether tort (including + negligence), contract, or otherwise, shall any Contributor, or anyone who + distributes Covered Software as permitted above, be liable to You for any + direct, indirect, special, incidental, or consequential damages of any + character including, without limitation, damages for lost profits, loss of + goodwill, work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses, even if such party shall have been + informed of the possibility of such damages. This limitation of liability + shall not apply to liability for death or personal injury resulting from + such party's negligence to the extent applicable law prohibits such + limitation. Some jurisdictions do not allow the exclusion or limitation of + incidental or consequential damages, so this exclusion and limitation may + not apply to You. + +8. Litigation + + Any litigation relating to this License may be brought only in the courts + of a jurisdiction where the defendant maintains its principal place of + business and such litigation shall be governed by laws of that + jurisdiction, without reference to its conflict-of-law provisions. Nothing + in this Section shall prevent a party's ability to bring cross-claims or + counter-claims. + +9. Miscellaneous + + This License represents the complete agreement concerning the subject + matter hereof. If any provision of this License is held to be + unenforceable, such provision shall be reformed only to the extent + necessary to make it enforceable. Any law or regulation which provides that + the language of a contract shall be construed against the drafter shall not + be used to construe this License against a Contributor. + + +10. Versions of the License + +10.1. New Versions + + Mozilla Foundation is the license steward. Except as provided in Section + 10.3, no one other than the license steward has the right to modify or + publish new versions of this License. Each version will be given a + distinguishing version number. + +10.2. Effect of New Versions + + You may distribute the Covered Software under the terms of the version + of the License under which You originally received the Covered Software, + or under the terms of any subsequent version published by the license + steward. + +10.3. Modified Versions + + If you create software not governed by this License, and you want to + create a new license for such software, you may create and use a + modified version of this License if you rename the license and remove + any references to the name of the license steward (except to note that + such modified license differs from this License). + +10.4. Distributing Source Code Form that is Incompatible With Secondary + Licenses If You choose to distribute Source Code Form that is + Incompatible With Secondary Licenses under the terms of this version of + the License, the notice described in Exhibit B of this License must be + attached. + +Exhibit A - Source Code Form License Notice + + This Source Code Form is subject to the + terms of the Mozilla Public License, v. + 2.0. If a copy of the MPL was not + distributed with this file, You can + obtain one at + http://mozilla.org/MPL/2.0/. + +If it is not possible or desirable to put the notice in a particular file, +then You may include the notice in a location (such as a LICENSE file in a +relevant directory) where a recipient would be likely to look for such a +notice. + +You may add additional accurate notices of copyright ownership. + +Exhibit B - "Incompatible With Secondary Licenses" Notice + + This Source Code Form is "Incompatible + With Secondary Licenses", as defined by + the Mozilla Public License, v. 2.0. + diff --git a/rvi_sota_client/Makefile b/rvi_sota_client/Makefile new file mode 100644 index 0000000..95be4d1 --- /dev/null +++ b/rvi_sota_client/Makefile @@ -0,0 +1,25 @@ +.PHONY: release debug docker all clean + +SRCS := $(wildcard src/*.rs) +SRCS += Cargo.toml + +target/release/sota_client: $(SRCS) + cargo build --release + +target/debug/sota_client: $(SRCS) + cargo build + +docker/sota_client: target/release/sota_client + cp target/release/sota_client docker + +docker: docker/sota_client docker/client.toml + docker build -t advancedtelematic/sota-client docker + +clean: + rm -f docker/sota_client + cargo clean + +# aliases +debug: target/debug/sota_client +release: target/release/sota_client +all: docker diff --git a/rvi_sota_client/README.md b/rvi_sota_client/README.md new file mode 100644 index 0000000..ba2d7ef --- /dev/null +++ b/rvi_sota_client/README.md @@ -0,0 +1,44 @@ +# sota-client + +This is the client (in-vehicle) portion of the SOTA project. See the [main SOTA Server project](https://github.com/advancedtelematic/rvi_sota_server) and [associated architecture document](http://advancedtelematic.github.io/rvi_sota_server/dev/architecture.html) for more information. + +## Building and running + +To see the SOTA client in action, you will need to first build and run the [SOTA Core Server](https://github.com/advancedtelematic/rvi_sota_server). + +### Building SOTA Client + +As long as you have `rust 1.8.0` and `cargo` installed, `cargo build` should build the `sota_client` executable in `target/debug`. + +### Running SOTA Client + +You can run the client with `target/debug/sota_client -c client.toml`. It will try to connect to the `core` service of `rvi_sota_server` specified in the `[server]` section of `client.toml`. If the `[server.auth]` section contains `client_id`, `client_secret` and `url`, it will first try to obtain an OAuth access token from `url` and then authenticate all the requests to the server with it. + +#### Running with HTTP communication + +HTTP tends to be much easier to get working than RVI. Enable HTTP by setting `http = “true”` in the [client] section of `client.toml`, and setting the url value in the [server] section to the url of your sota-core deployment. + +#### Running with RVI nodes + +To connect to the SOTA Server over RVI, run the `rvi_sota_server` project with RVI Nodes. + +You can build RVI directly from [its GitHub repo](https://github.com/GENIVI/rvi_core), or simply run our docker image. These instructions assume you are running the docker image. + +1. Pull the image: `docker pull advancedtelematic/rvi`. +2. In two terminal windows, run the rvi client and server nodes + * Client: `docker run -it --name rvi-client --expose 8901 --expose 8905-8908 -p 8901:8901 -p 8905:8905 -p 8906:8906 -p 8907:8907 -p 8908:8908 advancedtelematic/rvi client` + * Server: `docker run -it --name rvi-server --expose 8801 --expose 8805-8808 -p 8801:8801 -p 8805:8805 -p 8806:8806 -p 8807:8807 -p 8808:8808 advancedtelematic/rvi server` + +Now you can remove the `[server]` section from `client.toml` and disable http. + +### Running with GENIVI Software Loading Manager + +You can run the (GENIVI SWLM)[https://github.com/GENIVI/genivi_swm] to process the incoming update. You will need to run both the SWLM and SOTA Client as root to communicate over the DBus session. + +### Documentation + +To create a static HTML version of the module documentation run `cargo doc`. +Unfortunately this will only create documentation for the public interface. If +you want the full documentation you need to run `cargo doc -v` extract the +`rustdoc` command from the output and append `--no-defaults --passes +"collapse-docs" --passes "unindent-comments" --passes strip-hidden` to it. diff --git a/rvi_sota_client/client.toml b/rvi_sota_client/client.toml new file mode 100644 index 0000000..8f8f8df --- /dev/null +++ b/rvi_sota_client/client.toml @@ -0,0 +1,27 @@ +[client] +storage_dir = "/var/sota" +rvi_url = "http://127.0.0.1:8901" +edge_url = "http://127.0.0.1:9080" +timeout = 20 +vin_match = 2 +http = "true" + +[server] +url = "http://127.0.0.1:8080" +polling_interval = 10 +vin = "V1234567890123456" +packages_dir = "/tmp/" +packages_extension = "deb" + +[server.auth] +client_id = "client-id" +client_secret = "client-secret" +url = "http://127.0.0.1:9001/token" + +[dbus] +name = "org.genivi.SotaClient" +path = "/org/genivi/SotaClient" +interface = "org.genivi.SotaClient" +software_manager = "org.genivi.SoftwareLoadingManager" +software_manager_path = "/org/genivi/SoftwareLoadingManager" +timeout = 60 diff --git a/rvi_sota_client/docker/Dockerfile b/rvi_sota_client/docker/Dockerfile new file mode 100644 index 0000000..1d93a14 --- /dev/null +++ b/rvi_sota_client/docker/Dockerfile @@ -0,0 +1,12 @@ +FROM debian:8 + +RUN apt-get update \ + && apt-get install -y openssl dbus libdbus-1-3 dbus-x11 libdbus-glib-1-2 \ + && mkdir /var/sota + +COPY sota_client /usr/bin/sota_client +COPY run.sh /usr/bin/run.sh +COPY client.toml /var/sota/client.toml + +EXPOSE 9080 +CMD ["/usr/bin/run.sh"] diff --git a/rvi_sota_client/docker/README.md b/rvi_sota_client/docker/README.md new file mode 100644 index 0000000..e6ec854 --- /dev/null +++ b/rvi_sota_client/docker/README.md @@ -0,0 +1,29 @@ +# Sota Client in Docker + +First you need to spawn two working and connected rvi nodes. One server and one +client. See the [rvi documentation](https://github.com/PDXostc/rvi_core) for +instructions. We assume you've named them `rvi-server` and `rvi-client` +respectively. + +Then you can run the Sota client with + +``` +docker run -it --name sota-client -p 9000:9000 --link rvi-client:rvi-client advancedtelematic/sota-client +``` + +The generated image accepts several environment variables for configuration. + +* `RVI_ADDR`: the address under which the RVI client node can be reached, + defaults to `rvi_client` +* `RVI_PORT`: the port under which the Service Edge of the RVI client node can + be reached, defaults to `8901` +* `SOTA_CLIENT_ADDR`: The address the client should listen on and advertise, + defaults to the address of `eth0` in the running container +* `SOTA_CLIENT_PORT`: The port the client should advertise and listen on, + defaults to `9000` + +## Known Issues + +* The address is both the listening and advertised address. That means you + currently can't connect the client to a RVI node, thats running on a different + docker host machine. diff --git a/rvi_sota_client/docker/client.toml b/rvi_sota_client/docker/client.toml new file mode 100644 index 0000000..0882ce4 --- /dev/null +++ b/rvi_sota_client/docker/client.toml @@ -0,0 +1,14 @@ +[client] +storage_dir = "/var/sota" +rvi_url = "http://127.0.0.1:8901" +edge_url = "127.0.0.1:9080" +timeout = 20 +vin_match = 2 + +[dbus] +name = "org.genivi.sota_client" +path = "/org/genivi/sota_client" +interface = "org.genivi.sota_client" +software_manager = "org.genivi.software_loading_manager" +software_manager_path = "/org/genivi/software_loading_manager" +timeout = 60 diff --git a/rvi_sota_client/docker/run.sh b/rvi_sota_client/docker/run.sh new file mode 100755 index 0000000..250ca82 --- /dev/null +++ b/rvi_sota_client/docker/run.sh @@ -0,0 +1,16 @@ +#!/bin/bash +# bash "strict mode", see +# http://redsymbol.net/articles/unofficial-bash-strict-mode/ +set -euo pipefail +IFS=$'\n\t' + +eval $(dbus-launch) +export DBUS_SESSION_BUS_ADDRESS +export DBUS_SESSION_BUS_PID + +LOGLEVEL=${LOGLEVEL:-"info"} +SOTA_CLIENT="${SOTA_CLIENT_ADDR:-0.0.0.0}:${SOTA_CLIENT_PORT:-9080}" +RVI="${RVI:-http://rvi-client:8901}" + +export RUST_LOG=${RUST_LOG:-"sota_client=$LOGLEVEL"} +/usr/bin/sota_client -c /var/sota/client.toml -r "$RVI" -e "$SOTA_CLIENT" diff --git a/rvi_sota_client/src/configuration/client.rs b/rvi_sota_client/src/configuration/client.rs new file mode 100644 index 0000000..cf54621 --- /dev/null +++ b/rvi_sota_client/src/configuration/client.rs @@ -0,0 +1,179 @@ +//! Handles the `client` section of the configuration file. + +use toml; + +use super::common::{get_required_key, get_optional_key, ConfTreeParser, Result}; + +/// Type to encode allowed keys for the `client` section of the configuration. +#[derive(Clone)] +pub struct ClientConfiguration { + /// Directory where chunks and packages will be stored. + pub storage_dir: String, + /// The full URL where RVI can be reached. + pub rvi_url: Option, + /// The `host:port` combination where the client should bind and listen for incoming RVI calls. + pub edge_url: Option, + /// How long to wait for further server messages before the `Transfer` will be dropped. + pub timeout: Option, + /// Index of the RVI service URL, that holds the VIN for this device. + pub vin_match: i32 +} + +impl ConfTreeParser for ClientConfiguration { + fn parse(tree: &toml::Table) -> Result { + let client_tree = try!(tree.get("client") + .ok_or("Missing required subgroup \"client\"")); + + let storage_dir = try!(get_required_key(client_tree, "storage_dir", "client")); + let rvi_url = try!(get_optional_key(client_tree, "rvi_url", "client")); + let edge_url = try!(get_optional_key(client_tree, "edge_url", "client")); + let timeout = try!(get_optional_key(client_tree, "timeout", "client")); + let vin_match = try!(get_optional_key(client_tree, "vin_match", "client")); + + Ok(ClientConfiguration { + storage_dir: storage_dir, + rvi_url: rvi_url, + edge_url: edge_url, + timeout: timeout, + vin_match: vin_match.unwrap_or(2) + }) + } +} + +#[cfg(test)] static STORAGE: &'static str = "/var/sota"; +#[cfg(test)] static RVI: &'static str = "/http://localhost:8901"; +#[cfg(test)] static EDGE: &'static str = "localhost:9080"; +#[cfg(test)] static TIMEOUT: i64 = 10; +#[cfg(test)] static VIN: i32 = 3; +#[cfg(test)] static HTTP: bool = false; + +#[cfg(test)] +pub fn gen_valid_conf() -> String { + format!(r#" + [client] + storage_dir = "{}" + rvi_url = "{}" + edge_url = "{}" + timeout = {} + vin_match = {} + http = {} + "#, STORAGE, RVI, EDGE, TIMEOUT, VIN, HTTP) +} + +#[cfg(test)] +pub fn assert_conf(configuration: &ClientConfiguration) -> bool { + assert_eq!(&configuration.storage_dir, STORAGE); + assert_eq!(&configuration.rvi_url.clone().unwrap(), RVI); + assert_eq!(&configuration.edge_url.clone().unwrap(), EDGE); + assert_eq!(configuration.timeout.unwrap(), TIMEOUT); + assert_eq!(configuration.vin_match, VIN); + true +} + +#[cfg(test)] +pub mod test { + use super::*; + use super::{STORAGE, RVI, EDGE, TIMEOUT, VIN}; + use configuration::common::{ConfTreeParser, read_tree}; + + #[test] + fn it_requires_the_storage_dir_key() { + test_init!(); + let data = format!(r#" + [client] + rvi_url = "{}" + edge_url = "{}" + timeout = {} + vin_match = {} + "#, RVI, EDGE, TIMEOUT, VIN); + + let tree = read_tree(&data).unwrap(); + match ClientConfiguration::parse(&tree) { + Ok(..) => panic!("Accepted invalid configuration!"), + Err(e) => { + assert_eq!(e, + "Missing required key \"storage_dir\" in \"client\"" + .to_string()); + } + }; + } + + #[test] + fn it_doesnt_require_the_rvi_url_key() { + test_init!(); + let data = format!(r#" + [client] + storage_dir = "{}" + edge_url = "{}" + timeout = {} + vin_match = {} + "#, STORAGE, EDGE, TIMEOUT, VIN); + + let tree = read_tree(&data).unwrap(); + let configuration = ClientConfiguration::parse(&tree).unwrap(); + assert_eq!(&configuration.storage_dir, STORAGE); + assert_eq!(configuration.rvi_url, None); + assert_eq!(&configuration.edge_url.unwrap(), EDGE); + assert_eq!(configuration.timeout.unwrap(), TIMEOUT); + assert_eq!(configuration.vin_match, VIN); + } + + #[test] + fn it_doesnt_require_the_edge_url_key() { + test_init!(); + let data = format!(r#" + [client] + storage_dir = "{}" + rvi_url = "{}" + timeout = {} + vin_match = {} + "#, STORAGE, RVI, TIMEOUT, VIN); + + let tree = read_tree(&data).unwrap(); + let configuration = ClientConfiguration::parse(&tree).unwrap(); + assert_eq!(&configuration.storage_dir, STORAGE); + assert_eq!(&configuration.rvi_url.unwrap(), RVI); + assert_eq!(configuration.edge_url, None); + assert_eq!(configuration.vin_match, VIN); + } + + #[test] + fn it_doesnt_require_the_timeout_key() { + test_init!(); + let data = format!(r#" + [client] + storage_dir = "{}" + rvi_url = "{}" + edge_url = "{}" + vin_match = {} + "#, STORAGE, RVI, EDGE, VIN); + + let tree = read_tree(&data).unwrap(); + let configuration = ClientConfiguration::parse(&tree).unwrap(); + assert_eq!(&configuration.storage_dir, STORAGE); + assert_eq!(&configuration.rvi_url.unwrap(), RVI); + assert_eq!(&configuration.edge_url.unwrap(), EDGE); + assert_eq!(configuration.timeout, None); + assert_eq!(configuration.vin_match, VIN); + } + + #[test] + fn it_doesnt_require_the_vin_match_key_and_uses_a_default() { + test_init!(); + let data = format!(r#" + [client] + storage_dir = "{}" + rvi_url = "{}" + edge_url = "{}" + timeout = {} + "#, STORAGE, RVI, EDGE, TIMEOUT); + + let tree = read_tree(&data).unwrap(); + let configuration = ClientConfiguration::parse(&tree).unwrap(); + assert_eq!(&configuration.storage_dir, STORAGE); + assert_eq!(&configuration.rvi_url.unwrap(), RVI); + assert_eq!(&configuration.edge_url.unwrap(), EDGE); + assert_eq!(configuration.timeout.unwrap(), TIMEOUT); + assert_eq!(configuration.vin_match, 2); + } +} diff --git a/rvi_sota_client/src/configuration/common.rs b/rvi_sota_client/src/configuration/common.rs new file mode 100644 index 0000000..aac2430 --- /dev/null +++ b/rvi_sota_client/src/configuration/common.rs @@ -0,0 +1,148 @@ +//! Helper functions and traits for all configuration sections. + +use toml; +use std::result; +use std::fmt; +use remote::http::Url; + +/// `Result` type used throughout the configuration parser. +pub type Result = result::Result; + +/// Trait that provides a interface for parsing a (sub-) tree of the configuration. +pub trait ConfTreeParser { + /// Try to parse the given `tree` into the type this trait is implemented for. + /// Returns the parsed object or a error message, with the first error encountered while + /// parsing the `tree`. + /// + /// # Arguments + /// * `tree`: The `toml` tree to parse + fn parse(tree: &toml::Table) -> Result; +} + +/// Parse a required key, returning a appropriate error message, if the key can't be found in the +/// configuration. +/// +/// # Arguments +/// * `subtree`: The `toml` tree to parse. +/// * `key`: The key to look for. +/// * `group`: The group, this (sub-) tree is associated with. +pub fn get_required_key(subtree: &toml::Value, key: &str, group: &str) + -> Result where D: ParseTomlValue { + let value = try!(subtree.lookup(key) + .ok_or(format!("Missing required key \"{}\" in \"{}\"", + key, group))); + ParseTomlValue::parse(value, key, group) +} + +/// Parse a optional key, returning None if it can't be found. +/// +/// This basically does a `Option` to `Result