This commit is contained in:
Aiden McClelland
2021-07-01 10:51:22 -06:00
parent dcd02c03a0
commit 7bbbb5463e
19 changed files with 258 additions and 49 deletions

2
appmgr/Cargo.lock generated
View File

@@ -696,7 +696,7 @@ dependencies = [
"patch-db", "patch-db",
"pin-project", "pin-project",
"prettytable-rs", "prettytable-rs",
"rand 0.8.4", "rand 0.7.3",
"regex", "regex",
"reqwest", "reqwest",
"rpassword", "rpassword",

View File

@@ -68,7 +68,7 @@ openssl = "0.10.30"
patch-db = { version="*", path="../../patch-db/patch-db" } patch-db = { version="*", path="../../patch-db/patch-db" }
pin-project = "1.0.6" pin-project = "1.0.6"
prettytable-rs = "0.8.0" prettytable-rs = "0.8.0"
rand = "0.8.3" rand = "0.7.3"
regex = "1.4.2" regex = "1.4.2"
reqwest = { version="0.11.2", features=["stream", "json"] } reqwest = { version="0.11.2", features=["stream", "json"] }
rpassword = "5.0.0" rpassword = "5.0.0"

View File

@@ -109,7 +109,7 @@ impl Action {
} }
#[derive(Clone, Debug, Deserialize, Serialize, HasModel)] #[derive(Clone, Debug, Deserialize, Serialize, HasModel)]
#[serde(rename = "kebab-case")] #[serde(rename_all = "kebab-case")]
#[serde(tag = "type")] #[serde(tag = "type")]
pub enum ActionImplementation { pub enum ActionImplementation {
Docker(DockerAction), Docker(DockerAction),

View File

@@ -4,13 +4,35 @@ use embassy::Error;
use rpc_toolkit::run_cli; use rpc_toolkit::run_cli;
fn inner_main() -> Result<(), Error> { fn inner_main() -> Result<(), Error> {
simple_logging::log_to_stderr(log::LevelFilter::Info);
run_cli!( run_cli!(
embassy::main_api, embassy::main_api,
app => app.name("Embassy CLI") app => app
.name("Embassy CLI")
.arg(
clap::Arg::with_name("config")
.short("c")
.long("config")
.takes_value(true),
)
.arg(
clap::Arg::with_name("verbosity")
.short("v")
.multiple(true)
.takes_value(false),
)
.arg(Arg::with_name("host").long("host").short("h").takes_value(true)) .arg(Arg::with_name("host").long("host").short("h").takes_value(true))
.arg(Arg::with_name("port").long("port").short("p").takes_value(true)), .arg(Arg::with_name("port").long("port").short("p").takes_value(true)),
matches => EitherContext::Cli(CliContext::init(matches)?), matches => {
simple_logging::log_to_stderr(match matches.occurrences_of("verbosity") {
0 => log::LevelFilter::Off,
1 => log::LevelFilter::Error,
2 => log::LevelFilter::Warn,
3 => log::LevelFilter::Info,
4 => log::LevelFilter::Debug,
_ => log::LevelFilter::Trace,
});
EitherContext::Cli(CliContext::init(matches)?)
},
|code| if code < 0 { 1 } else { code } |code| if code < 0 { 1 } else { code }
) )
} }

View File

@@ -4,11 +4,33 @@ use embassy::Error;
use rpc_toolkit::run_cli; use rpc_toolkit::run_cli;
fn inner_main() -> Result<(), Error> { fn inner_main() -> Result<(), Error> {
simple_logging::log_to_stderr(log::LevelFilter::Info);
run_cli!( run_cli!(
embassy::portable_api, embassy::portable_api,
app => app.name("Embassy SDK"), app => app
matches => EitherContext::Cli(CliContext::init(matches)?), .name("Embassy SDK")
.arg(
clap::Arg::with_name("config")
.short("c")
.long("config")
.takes_value(true),
)
.arg(
clap::Arg::with_name("verbosity")
.short("v")
.multiple(true)
.takes_value(false),
),
matches => {
simple_logging::log_to_stderr(match matches.occurrences_of("verbosity") {
0 => log::LevelFilter::Off,
1 => log::LevelFilter::Error,
2 => log::LevelFilter::Warn,
3 => log::LevelFilter::Info,
4 => log::LevelFilter::Debug,
_ => log::LevelFilter::Trace,
});
EitherContext::Cli(CliContext::init(matches)?)
},
|code| if code < 0 { 1 } else { code } |code| if code < 0 { 1 } else { code }
) )
} }

View File

@@ -16,7 +16,7 @@ impl CharSet {
self.0.iter().any(|r| r.0.contains(c)) self.0.iter().any(|r| r.0.contains(c))
} }
pub fn gen<R: Rng>(&self, rng: &mut R) -> char { pub fn gen<R: Rng>(&self, rng: &mut R) -> char {
let mut idx = rng.gen_range(0..self.1); let mut idx = rng.gen_range(0, self.1);
for r in &self.0 { for r in &self.0 {
if idx < r.1 { if idx < r.1 {
return std::convert::TryFrom::try_from( return std::convert::TryFrom::try_from(

View File

@@ -1,8 +1,10 @@
use std::fs::File; use std::fs::File;
use std::io::Read;
use std::net::IpAddr; use std::net::IpAddr;
use std::path::Path; use std::path::{Path, PathBuf};
use std::sync::Arc; use std::sync::Arc;
use anyhow::anyhow;
use clap::ArgMatches; use clap::ArgMatches;
use reqwest::Proxy; use reqwest::Proxy;
use rpc_toolkit::reqwest::{Client, Url}; use rpc_toolkit::reqwest::{Client, Url};
@@ -11,15 +13,17 @@ use rpc_toolkit::Context;
use serde::Deserialize; use serde::Deserialize;
use super::rpc::RpcContextConfig; use super::rpc::RpcContextConfig;
use crate::ResultExt; use crate::{Error, ResultExt};
#[derive(Debug, Default, Deserialize)] #[derive(Debug, Default, Deserialize)]
#[serde(rename_all = "kebab-case")]
pub struct CliContextConfig { pub struct CliContextConfig {
#[serde(deserialize_with = "deserialize_host")] #[serde(deserialize_with = "deserialize_host")]
pub host: Option<Host>, pub host: Option<Host>,
pub port: Option<u16>, pub port: Option<u16>,
#[serde(deserialize_with = "crate::util::deserialize_from_str_opt")] #[serde(deserialize_with = "crate::util::deserialize_from_str_opt")]
pub proxy: Option<Url>, pub proxy: Option<Url>,
pub developer_key_path: Option<PathBuf>,
#[serde(flatten)] #[serde(flatten)]
pub server_config: RpcContextConfig, pub server_config: RpcContextConfig,
} }
@@ -29,13 +33,15 @@ pub struct CliContextSeed {
pub host: Host, pub host: Host,
pub port: u16, pub port: u16,
pub client: Client, pub client: Client,
pub developer_key_path: PathBuf,
} }
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct CliContext(Arc<CliContextSeed>); pub struct CliContext(Arc<CliContextSeed>);
impl CliContext { impl CliContext {
/// BLOCKING
pub fn init(matches: &ArgMatches) -> Result<Self, crate::Error> { pub fn init(matches: &ArgMatches) -> Result<Self, crate::Error> {
let cfg_path = Path::new(crate::CONFIG_PATH); let cfg_path = Path::new(matches.value_of("config").unwrap_or(crate::CONFIG_PATH));
let mut base = if cfg_path.exists() { let mut base = if cfg_path.exists() {
serde_yaml::from_reader( serde_yaml::from_reader(
File::open(cfg_path) File::open(cfg_path)
@@ -76,8 +82,29 @@ impl CliContext {
} else { } else {
Client::new() Client::new()
}, },
developer_key_path: base.developer_key_path.unwrap_or_else(|| {
cfg_path
.parent()
.unwrap_or(Path::new("/"))
.join(".developer_key")
}),
}))) })))
} }
/// BLOCKING
pub fn developer_key(&self) -> Result<ed25519_dalek::Keypair, Error> {
if !self.developer_key_path.exists() {
return Err(Error::new(anyhow!("Developer Key does not exist! Please run `embassy-sdk init` before running this command."), crate::ErrorKind::Uninitialized));
}
let mut keypair_buf = [0; ed25519_dalek::KEYPAIR_LENGTH];
File::open(&self.developer_key_path)?.read_exact(&mut keypair_buf)?;
Ok(ed25519_dalek::Keypair::from_bytes(&keypair_buf)?)
}
}
impl std::ops::Deref for CliContext {
type Target = CliContextSeed;
fn deref(&self) -> &Self::Target {
&*self.0
}
} }
impl Context for CliContext { impl Context for CliContext {
fn host(&self) -> Host<&str> { fn host(&self) -> Host<&str> {

View File

@@ -80,7 +80,7 @@ impl std::fmt::Display for DependencyError {
if !comma { if !comma {
comma = true; comma = true;
} else { } else {
write!(f, ", "); write!(f, ", ")?;
} }
write!(f, "{} @ {} {}", check, res.time, res.result)?; write!(f, "{} @ {} {}", check, res.time, res.result)?;
} }

View File

@@ -0,0 +1,29 @@
use std::fs::File;
use std::io::Write;
use std::path::Path;
use ed25519_dalek::Keypair;
use rpc_toolkit::command;
use crate::context::EitherContext;
use crate::util::display_none;
use crate::{Error, ResultExt};
#[command(cli_only, blocking, display(display_none))]
pub fn init(#[context] ctx: EitherContext) -> Result<(), Error> {
let ctx = ctx.as_cli().unwrap();
if !ctx.developer_key_path.exists() {
let parent = ctx.developer_key_path.parent().unwrap_or(Path::new("/"));
if !parent.exists() {
std::fs::create_dir_all(parent)
.with_ctx(|_| (crate::ErrorKind::Filesystem, parent.display().to_string()))?;
}
log::info!("Generating new developer key...");
let keypair = Keypair::generate(&mut rand::thread_rng());
log::info!("Writing key to {}", ctx.developer_key_path.display());
let mut dev_key_file = File::create(&ctx.developer_key_path)?;
dev_key_file.write_all(&keypair.to_bytes())?;
dev_key_file.sync_all()?;
}
Ok(())
}

View File

@@ -45,6 +45,7 @@ pub enum ErrorKind {
RateLimited = 37, RateLimited = 37,
InvalidRequest = 38, InvalidRequest = 38,
MigrationFailed = 39, MigrationFailed = 39,
Uninitialized = 40,
} }
impl ErrorKind { impl ErrorKind {
pub fn as_str(&self) -> &'static str { pub fn as_str(&self) -> &'static str {
@@ -89,6 +90,7 @@ impl ErrorKind {
RateLimited => "Rate Limited", RateLimited => "Rate Limited",
InvalidRequest => "Invalid Request", InvalidRequest => "Invalid Request",
MigrationFailed => "Migration Failed", MigrationFailed => "Migration Failed",
Uninitialized => "Uninitialized",
} }
} }
} }

View File

@@ -22,6 +22,7 @@ pub mod config;
pub mod context; pub mod context;
pub mod db; pub mod db;
pub mod dependencies; pub mod dependencies;
pub mod developer;
pub mod error; pub mod error;
pub mod id; pub mod id;
pub mod install; pub mod install;
@@ -46,12 +47,19 @@ pub fn echo(#[context] _ctx: EitherContext, #[arg] message: String) -> Result<St
Ok(message) Ok(message)
} }
#[command(subcommands(config::config, version::git_info, echo, s9pk::pack, s9pk::verify))] #[command(subcommands(
config::config,
version::git_info,
echo,
s9pk::pack,
s9pk::verify,
developer::init
))]
pub fn main_api(#[context] ctx: EitherContext) -> Result<EitherContext, RpcError> { pub fn main_api(#[context] ctx: EitherContext) -> Result<EitherContext, RpcError> {
Ok(ctx) Ok(ctx)
} }
#[command(subcommands(version::git_info, s9pk::pack, s9pk::verify))] #[command(subcommands(version::git_info, s9pk::pack, s9pk::verify, developer::init))]
pub fn portable_api(#[context] ctx: EitherContext) -> Result<EitherContext, RpcError> { pub fn portable_api(#[context] ctx: EitherContext) -> Result<EitherContext, RpcError> {
Ok(ctx) Ok(ctx)
} }

View File

@@ -9,6 +9,7 @@ use torut::onion::TorSecretKeyV3;
use crate::db::model::{InterfaceAddressMap, InterfaceAddresses, InterfaceInfo}; use crate::db::model::{InterfaceAddressMap, InterfaceAddresses, InterfaceInfo};
use crate::id::Id; use crate::id::Id;
use crate::s9pk::manifest::PackageId; use crate::s9pk::manifest::PackageId;
use crate::util::Port;
use crate::Error; use crate::Error;
#[derive(Clone, Debug, Deserialize, Serialize)] #[derive(Clone, Debug, Deserialize, Serialize)]
@@ -99,7 +100,7 @@ impl<S: AsRef<str>> AsRef<Path> for InterfaceId<S> {
#[serde(rename_all = "kebab-case")] #[serde(rename_all = "kebab-case")]
pub struct Interface { pub struct Interface {
pub tor_config: Option<TorConfig>, pub tor_config: Option<TorConfig>,
pub lan_config: Option<IndexMap<u16, LanPortConfig>>, pub lan_config: Option<IndexMap<Port, LanPortConfig>>,
pub ui: bool, pub ui: bool,
pub protocols: Vec<String>, pub protocols: Vec<String>,
} }
@@ -107,7 +108,7 @@ pub struct Interface {
#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)] #[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)]
#[serde(rename_all = "kebab-case")] #[serde(rename_all = "kebab-case")]
pub struct TorConfig { pub struct TorConfig {
pub port_mapping: IndexMap<u16, u16>, pub port_mapping: IndexMap<Port, Port>,
} }
#[derive(Clone, Debug, Deserialize, Serialize)] #[derive(Clone, Debug, Deserialize, Serialize)]

View File

@@ -147,7 +147,7 @@ impl TorControllerInner {
.port_mapping .port_mapping
.iter() .iter()
.map(|(external, internal)| { .map(|(external, internal)| {
(*external, SocketAddr::from((config.ip, *internal))) (external.0, SocketAddr::from((config.ip, internal.0)))
}) })
.collect::<Vec<_>>() .collect::<Vec<_>>()
.iter(), .iter(),

View File

@@ -1,9 +1,13 @@
use std::io::{Read, Seek, SeekFrom, Write}; use std::io::{Read, Seek, SeekFrom, Write};
use digest::Digest;
use sha2::Sha512;
use typed_builder::TypedBuilder; use typed_builder::TypedBuilder;
use super::header::{FileSection, Header}; use super::header::{FileSection, Header};
use super::manifest::Manifest; use super::manifest::Manifest;
use super::SIG_CONTEXT;
use crate::util::HashWriter;
use crate::{Error, ResultExt}; use crate::{Error, ResultExt};
#[derive(TypedBuilder)] #[derive(TypedBuilder)]
@@ -32,7 +36,7 @@ impl<
> S9pkPacker<'a, W, RLicense, RInstructions, RIcon, RDockerImages> > S9pkPacker<'a, W, RLicense, RInstructions, RIcon, RDockerImages>
{ {
/// BLOCKING /// BLOCKING
pub fn pack(mut self) -> Result<(), Error> { pub fn pack(mut self, key: &ed25519_dalek::Keypair) -> Result<(), Error> {
let header_pos = self.writer.stream_position()?; let header_pos = self.writer.stream_position()?;
if header_pos != 0 { if header_pos != 0 {
log::warn!("Appending to non-empty file."); log::warn!("Appending to non-empty file.");
@@ -45,57 +49,63 @@ impl<
) )
})?; })?;
let mut position = self.writer.stream_position()?; let mut position = self.writer.stream_position()?;
let mut writer = HashWriter::new(Sha512::new(), &mut self.writer);
// manifest // manifest
serde_cbor::to_writer(&mut self.writer, self.manifest).with_ctx(|_| { serde_cbor::to_writer(&mut writer, self.manifest).with_ctx(|_| {
( (
crate::ErrorKind::Serialization, crate::ErrorKind::Serialization,
"Serializing Manifest (CBOR)", "Serializing Manifest (CBOR)",
) )
})?; })?;
let new_pos = self.writer.stream_position()?; let new_pos = writer.stream_position()?;
header.table_of_contents.manifest = FileSection { header.table_of_contents.manifest = FileSection {
position, position,
length: new_pos - position, length: new_pos - position,
}; };
position = new_pos; position = new_pos;
// license // license
std::io::copy(&mut self.license, &mut self.writer) std::io::copy(&mut self.license, &mut writer)
.with_ctx(|_| (crate::ErrorKind::Filesystem, "Copying License"))?; .with_ctx(|_| (crate::ErrorKind::Filesystem, "Copying License"))?;
let new_pos = self.writer.stream_position()?; let new_pos = writer.stream_position()?;
header.table_of_contents.license = FileSection { header.table_of_contents.license = FileSection {
position, position,
length: new_pos - position, length: new_pos - position,
}; };
position = new_pos; position = new_pos;
// instructions // instructions
std::io::copy(&mut self.instructions, &mut self.writer) std::io::copy(&mut self.instructions, &mut writer)
.with_ctx(|_| (crate::ErrorKind::Filesystem, "Copying Instructions"))?; .with_ctx(|_| (crate::ErrorKind::Filesystem, "Copying Instructions"))?;
let new_pos = self.writer.stream_position()?; let new_pos = writer.stream_position()?;
header.table_of_contents.instructions = FileSection { header.table_of_contents.instructions = FileSection {
position, position,
length: new_pos - position, length: new_pos - position,
}; };
position = new_pos; position = new_pos;
// icon // icon
std::io::copy(&mut self.icon, &mut self.writer) std::io::copy(&mut self.icon, &mut writer)
.with_ctx(|_| (crate::ErrorKind::Filesystem, "Copying Icon"))?; .with_ctx(|_| (crate::ErrorKind::Filesystem, "Copying Icon"))?;
let new_pos = self.writer.stream_position()?; let new_pos = writer.stream_position()?;
header.table_of_contents.icon = FileSection { header.table_of_contents.icon = FileSection {
position, position,
length: new_pos - position, length: new_pos - position,
}; };
position = new_pos; position = new_pos;
// docker_images // docker_images
std::io::copy(&mut self.docker_images, &mut self.writer) std::io::copy(&mut self.docker_images, &mut writer)
.with_ctx(|_| (crate::ErrorKind::Filesystem, "Copying App Image"))?; .with_ctx(|_| (crate::ErrorKind::Filesystem, "Copying App Image"))?;
let new_pos = self.writer.stream_position()?; let new_pos = writer.stream_position()?;
header.table_of_contents.docker_images = FileSection { header.table_of_contents.docker_images = FileSection {
position, position,
length: new_pos - position, length: new_pos - position,
}; };
position = new_pos; position = new_pos;
// header // header
let (hash, _) = writer.finish();
self.writer.seek(SeekFrom::Start(header_pos))?; self.writer.seek(SeekFrom::Start(header_pos))?;
header.pubkey = key.public.clone();
header.signature = key.sign_prehashed(hash, Some(SIG_CONTEXT))?;
header header
.serialize(&mut self.writer) .serialize(&mut self.writer)
.with_ctx(|_| (crate::ErrorKind::Serialization, "Writing Header"))?; .with_ctx(|_| (crate::ErrorKind::Serialization, "Writing Header"))?;

View File

@@ -10,6 +10,7 @@ use crate::Error;
pub const MAGIC: [u8; 2] = [59, 59]; pub const MAGIC: [u8; 2] = [59, 59];
pub const VERSION: u8 = 1; pub const VERSION: u8 = 1;
#[derive(Debug)]
pub struct Header { pub struct Header {
pub pubkey: PublicKey, pub pubkey: PublicKey,
pub signature: Signature, pub signature: Signature,
@@ -76,14 +77,11 @@ pub struct TableOfContents {
} }
impl TableOfContents { impl TableOfContents {
pub fn serialize<W: Write>(&self, mut writer: W) -> std::io::Result<()> { pub fn serialize<W: Write>(&self, mut writer: W) -> std::io::Result<()> {
let len: u32 = 16 // size of FileSection let len: u32 = ((1 + "manifest".len() + 16)
* ( + (1 + "license".len() + 16)
1 + // manifest + (1 + "instructions".len() + 16)
1 + // license + (1 + "icon".len() + 16)
1 + // instructions + (1 + "docker_images".len() + 16)) as u32;
1 + // icon
1 // docker_images
);
writer.write_all(&u32::to_be_bytes(len))?; writer.write_all(&u32::to_be_bytes(len))?;
self.manifest.serialize_entry("manifest", &mut writer)?; self.manifest.serialize_entry("manifest", &mut writer)?;
self.license.serialize_entry("license", &mut writer)?; self.license.serialize_entry("license", &mut writer)?;
@@ -153,7 +151,8 @@ impl FileSection {
if read == 0 { if read == 0 {
return Ok(None); return Ok(None);
} }
let label = vec![0; label_len[0] as usize]; let mut label = vec![0; label_len[0] as usize];
reader.read_exact(&mut label).await?;
let mut pos = [0; 8]; let mut pos = [0; 8];
reader.read_exact(&mut pos).await?; reader.read_exact(&mut pos).await?;
let mut len = [0; 8]; let mut len = [0; 8];

View File

@@ -134,6 +134,7 @@ pub struct Manifest {
} }
#[derive(Clone, Debug, Default, Deserialize, Serialize)] #[derive(Clone, Debug, Default, Deserialize, Serialize)]
#[serde(rename_all = "kebab-case")]
pub struct Assets { pub struct Assets {
#[serde(default)] #[serde(default)]
pub license: Option<PathBuf>, pub license: Option<PathBuf>,
@@ -168,7 +169,7 @@ impl Assets {
self.docker_images self.docker_images
.as_ref() .as_ref()
.map(|a| a.as_path()) .map(|a| a.as_path())
.unwrap_or(Path::new("images.tar")) .unwrap_or(Path::new("image.tar"))
} }
pub fn instructions_path(&self) -> &Path { pub fn instructions_path(&self) -> &Path {
self.instructions self.instructions

View File

@@ -20,8 +20,11 @@ pub mod reader;
pub const SIG_CONTEXT: &'static [u8] = b"s9pk"; pub const SIG_CONTEXT: &'static [u8] = b"s9pk";
#[command(cli_only, display(display_none), blocking)] #[command(cli_only, display(display_none), blocking)]
pub fn pack(#[context] _ctx: EitherContext, #[arg] path: Option<PathBuf>) -> Result<(), Error> { pub fn pack(#[context] ctx: EitherContext, #[arg] path: Option<PathBuf>) -> Result<(), Error> {
use std::fs::File; use std::fs::File;
use std::io::Read;
let ctx = ctx.as_cli().unwrap();
let path = if let Some(path) = path { let path = if let Some(path) = path {
path path
@@ -44,12 +47,40 @@ pub fn pack(#[context] _ctx: EitherContext, #[arg] path: Option<PathBuf>) -> Res
S9pkPacker::builder() S9pkPacker::builder()
.manifest(&manifest) .manifest(&manifest)
.writer(&mut outfile) .writer(&mut outfile)
.license(File::open(path.join(manifest.assets.license_path()))?) .license(
.icon(File::open(path.join(manifest.assets.icon_path()))?) File::open(path.join(manifest.assets.license_path())).with_ctx(|_| {
.instructions(File::open(path.join(manifest.assets.instructions_path()))?) (
.docker_images(File::open(path.join(manifest.assets.docker_images_path()))?) crate::ErrorKind::Filesystem,
manifest.assets.license_path().display().to_string(),
)
})?,
)
.icon(
File::open(path.join(manifest.assets.icon_path())).with_ctx(|_| {
(
crate::ErrorKind::Filesystem,
manifest.assets.icon_path().display().to_string(),
)
})?,
)
.instructions(
File::open(path.join(manifest.assets.instructions_path())).with_ctx(|_| {
(
crate::ErrorKind::Filesystem,
manifest.assets.instructions_path().display().to_string(),
)
})?,
)
.docker_images(
File::open(path.join(manifest.assets.docker_images_path())).with_ctx(|_| {
(
crate::ErrorKind::Filesystem,
manifest.assets.docker_images_path().display().to_string(),
)
})?,
)
.build() .build()
.pack()?; .pack(&ctx.developer_key()?)?;
outfile.sync_all()?; outfile.sync_all()?;
Ok(()) Ok(())

View File

@@ -67,7 +67,7 @@ impl<R: AsyncRead + AsyncSeek + Unpin> S9pkReader<InstallProgressTracker<R>> {
} }
impl<R: AsyncRead + AsyncSeek + Unpin> S9pkReader<R> { impl<R: AsyncRead + AsyncSeek + Unpin> S9pkReader<R> {
pub async fn validate(&mut self) -> Result<(), Error> { pub async fn validate(&mut self) -> Result<(), Error> {
todo!() Ok(())
} }
pub async fn from_reader(mut rdr: R) -> Result<Self, Error> { pub async fn from_reader(mut rdr: R) -> Result<Self, Error> {
let header = Header::deserialize(&mut rdr).await?; let header = Header::deserialize(&mut rdr).await?;

View File

@@ -10,6 +10,7 @@ use std::time::Duration;
use anyhow::anyhow; use anyhow::anyhow;
use async_trait::async_trait; use async_trait::async_trait;
use clap::ArgMatches; use clap::ArgMatches;
use digest::Digest;
use serde::{Deserialize, Deserializer, Serialize, Serializer}; use serde::{Deserialize, Deserializer, Serialize, Serializer};
use serde_json::Value; use serde_json::Value;
use sqlx::{Executor, Sqlite}; use sqlx::{Executor, Sqlite};
@@ -620,7 +621,7 @@ impl<W: std::fmt::Write> std::io::Write for FmtWriter<W> {
} }
#[derive(Clone, Copy, Debug, Deserialize, Serialize)] #[derive(Clone, Copy, Debug, Deserialize, Serialize)]
#[serde(rename = "kebab-case")] #[serde(rename_all = "kebab-case")]
pub enum IoFormat { pub enum IoFormat {
Json, Json,
JsonPretty, JsonPretty,
@@ -698,6 +699,7 @@ impl IoFormat {
.with_kind(crate::ErrorKind::Serialization), .with_kind(crate::ErrorKind::Serialization),
} }
} }
/// BLOCKING
pub fn from_reader<R: std::io::Read, T: for<'de> Deserialize<'de>>( pub fn from_reader<R: std::io::Read, T: for<'de> Deserialize<'de>>(
&self, &self,
mut reader: R, mut reader: R,
@@ -714,7 +716,9 @@ impl IoFormat {
} }
IoFormat::Toml | IoFormat::TomlPretty => { IoFormat::Toml | IoFormat::TomlPretty => {
let mut s = String::new(); let mut s = String::new();
reader.read_to_string(&mut s); reader
.read_to_string(&mut s)
.with_kind(crate::ErrorKind::Deserialization)?;
serde_toml::from_str(&s).with_kind(crate::ErrorKind::Deserialization) serde_toml::from_str(&s).with_kind(crate::ErrorKind::Deserialization)
} }
} }
@@ -809,3 +813,56 @@ impl<T> Container<T> {
*self.0.write().await = None; *self.0.write().await = None;
} }
} }
pub struct HashWriter<H: Digest, W: std::io::Write> {
hasher: H,
writer: W,
}
impl<H: Digest, W: std::io::Write> HashWriter<H, W> {
pub fn new(hasher: H, writer: W) -> Self {
HashWriter { hasher, writer }
}
pub fn finish(self) -> (H, W) {
(self.hasher, self.writer)
}
}
impl<H: Digest, W: std::io::Write> std::io::Write for HashWriter<H, W> {
fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
let written = self.writer.write(buf)?;
self.hasher.update(&buf[..written]);
Ok(written)
}
fn flush(&mut self) -> std::io::Result<()> {
self.writer.flush()
}
}
impl<H: Digest, W: std::io::Write> std::ops::Deref for HashWriter<H, W> {
type Target = W;
fn deref(&self) -> &Self::Target {
&self.writer
}
}
impl<H: Digest, W: std::io::Write> std::ops::DerefMut for HashWriter<H, W> {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.writer
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub struct Port(pub u16);
impl<'de> Deserialize<'de> for Port {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
deserialize_from_str(deserializer).map(Port)
}
}
impl Serialize for Port {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
serialize_display(&self.0, serializer)
}
}