Skip to content

Commit

Permalink
Merge branch 'reopen-repo'
Browse files Browse the repository at this point in the history
  • Loading branch information
madadam committed Mar 1, 2023
2 parents 78cd3b5 + 5939be4 commit 55a40c1
Show file tree
Hide file tree
Showing 13 changed files with 217 additions and 133 deletions.
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ async-trait = "0.1.64"
btdht = { git = "https://github.com/equalitie/btdht.git", rev = "5287b4a2bc507cde87903bc30ca2f1c548a31e18" }
futures-util = { version = "0.3.21", default-features = false }
serde = { version = "1.0", features = ["derive", "rc"] }
serde_bytes = "0.11.8"
thiserror = "1.0.31"
tokio = { version = "1.24.1", default-features = false }
tokio-stream = "0.1.9"
Expand Down
2 changes: 1 addition & 1 deletion bridge/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ ouisync-lib = { package = "ouisync", path = "../lib" }
rmp-serde = "1.1.0"
scoped_task = { path = "../scoped_task" }
serde = { workspace = true }
serde_bytes = "0.11.8"
serde_bytes = { workspace = true }
thiserror = { workspace = true }
tokio = { workspace = true }
tokio-stream = { workspace = true }
Expand Down
18 changes: 12 additions & 6 deletions bridge/src/protocol/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,12 @@ pub enum Request {
password: Option<String>,
},
RepositoryClose(Handle<RepositoryHolder>),
RepositoryCreateReopenToken(Handle<RepositoryHolder>),
RepositoryReopen {
path: Utf8PathBuf,
#[serde(with = "serde_bytes")]
token: Vec<u8>,
},
RepositorySubscribe(Handle<RepositoryHolder>),
RepositorySetReadAccess {
repository: Handle<RepositoryHolder>,
Expand Down Expand Up @@ -82,8 +88,6 @@ pub enum Request {
ShareTokenInfoHash(#[serde(with = "as_str")] ShareToken),
ShareTokenSuggestedName(#[serde(with = "as_str")] ShareToken),
ShareTokenNormalize(#[serde(with = "as_str")] ShareToken),
ShareTokenEncode(#[serde(with = "as_str")] ShareToken),
ShareTokenDecode(#[serde(with = "serde_bytes")] Vec<u8>),
DirectoryCreate {
repository: Handle<RepositoryHolder>,
path: Utf8PathBuf,
Expand Down Expand Up @@ -184,6 +188,12 @@ pub async fn dispatch(
repository::open(server_state, path, password).await?.into()
}
Request::RepositoryClose(handle) => repository::close(server_state, handle).await?.into(),
Request::RepositoryCreateReopenToken(handle) => {
repository::create_reopen_token(server_state, handle)?.into()
}
Request::RepositoryReopen { path, token } => {
repository::reopen(server_state, path, token).await?.into()
}
Request::RepositorySubscribe(handle) => {
repository::subscribe(server_state, client_state, handle).into()
}
Expand Down Expand Up @@ -276,10 +286,6 @@ pub async fn dispatch(
Request::ShareTokenInfoHash(token) => share_token::info_hash(token).into(),
Request::ShareTokenSuggestedName(token) => share_token::suggested_name(token).into(),
Request::ShareTokenNormalize(token) => token.to_string().into(),
Request::ShareTokenEncode(token) => share_token::encode(token).into(),
Request::ShareTokenDecode(bytes) => share_token::decode(bytes)
.map(|token| token.to_string())
.into(),
Request::RepositoryAccessMode(repository) => {
repository::access_mode(server_state, repository).into()
}
Expand Down
53 changes: 44 additions & 9 deletions bridge/src/repository.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ use ouisync_lib::{
device_id,
network::{self, Registration},
path, Access, AccessMode, AccessSecrets, EntryType, Event, LocalSecret, Payload, Progress,
Repository, RepositoryDb, ShareToken,
ReopenToken, Repository, RepositoryDb, ShareToken,
};
use std::borrow::Cow;
use tokio::sync::broadcast::error::RecvError;
Expand Down Expand Up @@ -75,10 +75,7 @@ pub(crate) async fn create(
let repository = Repository::create(db, device_id, access).await?;

let registration = state.network.handle().register(repository.store().clone());

// TODO: consider leaving the decision to enable DHT, PEX to the app.
registration.enable_dht();
registration.enable_pex();
init(&registration);

let holder = RepositoryHolder {
repository,
Expand Down Expand Up @@ -114,10 +111,7 @@ pub(crate) async fn open(
.await?;

let registration = state.network.handle().register(repository.store().clone());

// TODO: consider leaving the decision to enable DHT, PEX to the app.
registration.enable_dht();
registration.enable_pex();
init(&registration);

let holder = RepositoryHolder {
repository,
Expand All @@ -143,6 +137,41 @@ pub(crate) async fn close(state: &ServerState, handle: Handle<RepositoryHolder>)
Ok(())
}

pub(crate) fn create_reopen_token(
state: &ServerState,
handle: Handle<RepositoryHolder>,
) -> Result<Vec<u8>> {
let holder = state.repositories.get(handle);
let token = holder.repository.reopen_token().encode();

Ok(token)
}

pub(crate) async fn reopen(
state: &ServerState,
store: Utf8PathBuf,
token: Vec<u8>,
) -> Result<Handle<RepositoryHolder>> {
let token = ReopenToken::decode(&token)?;
let span = state.repo_span(&store);

async {
let repository = Repository::reopen(store.into_std_path_buf(), token).await?;

let registration = state.network.handle().register(repository.store().clone());
init(&registration);

let holder = RepositoryHolder {
repository,
registration,
};

Ok(state.repositories.insert(holder))
}
.instrument(span)
.await
}

/// If `share_token` is null, the function will try with the currently active access secrets in the
/// repository. Note that passing `share_token` explicitly (as opposed to implicitly using the
/// currently active secrets) may be used to increase access permissions.
Expand Down Expand Up @@ -493,6 +522,12 @@ pub(crate) fn access_mode_to_num(mode: AccessMode) -> u8 {
}
}

fn init(registration: &Registration) {
// TODO: consider leaving the decision to enable DHT, PEX to the app.
registration.enable_dht();
registration.enable_pex();
}

#[cfg(test)]
mod tests {
use super::*;
Expand Down
10 changes: 0 additions & 10 deletions bridge/src/share_token.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,3 @@ pub(crate) fn info_hash(token: ShareToken) -> String {
pub(crate) fn suggested_name(token: ShareToken) -> String {
token.suggested_name().into_owned()
}

pub(crate) fn encode(token: ShareToken) -> Vec<u8> {
let mut buffer = Vec::new();
token.encode(&mut buffer);
buffer
}

pub(crate) fn decode(bytes: Vec<u8>) -> Option<ShareToken> {
ShareToken::decode(&bytes).ok()
}
1 change: 1 addition & 0 deletions lib/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ ref-cast = "1.0.14"
rupnp = { version = "1.1.0", default-features = false, features = [] }
scoped_task = { path = "../scoped_task" }
serde = { workspace = true }
serde_bytes = { workspace = true }
slab = "0.4.6"
sqlx = { version = "0.6.2", default-features = false, features = ["runtime-tokio-rustls", "sqlite"] }
ssdp-client = "1.0"
Expand Down
95 changes: 32 additions & 63 deletions lib/src/access_control/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,12 @@ use crate::{
Result,
};
use rand::{rngs::OsRng, CryptoRng, Rng};
use serde::{Deserialize, Deserializer, Serialize, Serializer};
use std::{fmt, str::Utf8Error, string::FromUtf8Error, sync::Arc};
use thiserror::Error;

/// Secrets for access to a repository.
#[derive(Clone, Eq, PartialEq)]
#[derive(Clone, Serialize, Deserialize)]
pub enum AccessSecrets {
Blind {
id: RepositoryId,
Expand Down Expand Up @@ -69,57 +70,6 @@ impl AccessSecrets {
}
}

pub(crate) fn encode(&self, out: &mut Vec<u8>) {
match self {
Self::Blind { id } => {
out.push(AccessMode::Blind as u8);
out.extend_from_slice(id.as_ref());
}
Self::Read { id, read_key } => {
out.push(AccessMode::Read as u8);
out.extend_from_slice(id.as_ref());
out.extend_from_slice(read_key.as_ref());
}
Self::Write(secrets) => {
out.push(AccessMode::Write as u8);
out.extend_from_slice(secrets.write_keys.secret.as_ref());
}
}
}

// Returns the decoded secrets and the remaining input.
pub(crate) fn decode(input: &[u8]) -> Result<(Self, &[u8]), DecodeError> {
let (mode, input) = input.split_first().ok_or(DecodeError)?;
let mode = AccessMode::try_from(*mode)?;

match mode {
AccessMode::Blind => {
let (id, input) = try_split_at(input, RepositoryId::SIZE).ok_or(DecodeError)?;
let id = RepositoryId::try_from(id)?;

Ok((Self::Blind { id }, input))
}
AccessMode::Read => {
let (id, input) = try_split_at(input, RepositoryId::SIZE).ok_or(DecodeError)?;
let id = RepositoryId::try_from(id)?;

let (read_key, input) =
try_split_at(input, cipher::SecretKey::SIZE).ok_or(DecodeError)?;
let read_key = cipher::SecretKey::try_from(read_key)?;

Ok((Self::Read { id, read_key }, input))
}
AccessMode::Write => {
let (write_key, input) =
try_split_at(input, sign::SecretKey::SIZE).ok_or(DecodeError)?;
let write_key = sign::SecretKey::try_from(write_key)?;
let write_keys = sign::Keypair::from(write_key);

Ok((Self::Write(write_keys.into()), input))
}
}
}

pub(crate) fn can_write(&self) -> bool {
matches!(self, Self::Write(_))
}
Expand Down Expand Up @@ -169,6 +119,14 @@ impl fmt::Debug for AccessSecrets {
}
}

impl PartialEq for AccessSecrets {
fn eq(&self, other: &Self) -> bool {
self.access_mode() == other.access_mode() && self.id() == other.id()
}
}

impl Eq for AccessSecrets {}

/// Secrets for write access.
#[derive(Clone)]
pub struct WriteSecrets {
Expand All @@ -192,8 +150,6 @@ impl WriteSecrets {
impl PartialEq for WriteSecrets {
fn eq(&self, other: &Self) -> bool {
self.id == other.id
&& self.read_key == other.read_key
&& self.write_keys.public == other.write_keys.public
}
}

Expand All @@ -212,6 +168,28 @@ impl From<sign::Keypair> for WriteSecrets {
}
}

impl Serialize for WriteSecrets {
fn serialize<S>(&self, s: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
// Serialize only the write secret key because all the other keys can be derived from it
self.write_keys.secret.serialize(s)
}
}

impl<'de> Deserialize<'de> for WriteSecrets {
fn deserialize<D>(d: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
let write_key = sign::SecretKey::deserialize(d)?;
let write_keys = sign::Keypair::from(write_key);

Ok(Self::from(write_keys))
}
}

/// Secret keys for read and optionaly write access.
#[derive(Clone)]
pub(crate) struct AccessKeys {
Expand Down Expand Up @@ -249,15 +227,6 @@ fn derive_read_key_from_write_key(write_key: &sign::SecretKey) -> cipher::Secret
cipher::SecretKey::derive_from_key(write_key.as_ref(), b"ouisync repository read key")
}

// Similar to `split_at` but returns `None` instead of panic when `index` is out of range.
fn try_split_at(slice: &[u8], index: usize) -> Option<(&[u8], &[u8])> {
if index <= slice.len() {
Some(slice.split_at(index))
} else {
None
}
}

#[derive(Debug, Error)]
#[error("decode error")]
pub struct DecodeError;
Expand Down
45 changes: 6 additions & 39 deletions lib/src/access_control/share_token.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use super::{AccessMode, AccessSecrets, DecodeError};
use crate::repository::RepositoryId;
use bincode::Options;
use std::{
borrow::Cow,
fmt,
Expand All @@ -8,7 +9,7 @@ use std::{
use zeroize::Zeroizing;

pub const PREFIX: &str = "https://ouisync.net/r";
pub const VERSION: u64 = 0;
pub const VERSION: u64 = 1;

/// Token to share a repository which can be encoded as a URL-formatted string and transmitted to
/// other replicas.
Expand Down Expand Up @@ -55,20 +56,6 @@ impl ShareToken {
pub fn access_mode(&self) -> AccessMode {
self.secrets.access_mode()
}

pub fn encode(&self, out: &mut Vec<u8>) {
encode_version(out, VERSION);
self.secrets.encode(out);
out.extend_from_slice(self.name.as_bytes());
}

pub fn decode(input: &[u8]) -> Result<Self, DecodeError> {
let input = decode_version(input)?;
let (secrets, input) = AccessSecrets::decode(input)?;
let name = str::from_utf8(input)?.to_owned();

Ok(Self { secrets, name })
}
}

impl From<AccessSecrets> for ShareToken {
Expand Down Expand Up @@ -102,7 +89,7 @@ impl FromStr for ShareToken {
let input = Zeroizing::new(base64::decode_config(input, base64::URL_SAFE_NO_PAD)?);
let input = decode_version(&input)?;

let (secrets, _) = AccessSecrets::decode(input)?;
let secrets: AccessSecrets = bincode::options().deserialize(input)?;
let name = parse_name(params)?;

Ok(Self::from(secrets).with_name(name))
Expand Down Expand Up @@ -138,7 +125,9 @@ impl fmt::Display for ShareToken {

let mut buffer = Vec::new();
encode_version(&mut buffer, VERSION);
self.secrets.encode(&mut buffer);
bincode::options()
.serialize_into(&mut buffer, &self.secrets)
.map_err(|_| fmt::Error)?;

write!(
f,
Expand Down Expand Up @@ -222,26 +211,4 @@ mod tests {
assert_eq!(access.id, token_id);
});
}

#[test]
fn encode_and_decode() {
let token_id = RepositoryId::random();
let token_read_key = cipher::SecretKey::random();
let token = ShareToken::from(AccessSecrets::Read {
id: token_id,
read_key: token_read_key.clone(),
})
.with_name("foo");

let mut buffer = vec![];
token.encode(&mut buffer);

let decoded = ShareToken::decode(&buffer).unwrap();

assert_eq!(decoded.name, token.name);
assert_matches!(decoded.secrets, AccessSecrets::Read { id, read_key } => {
assert_eq!(id, token_id);
assert_eq!(read_key.as_ref(), token_read_key.as_ref());
});
}
}

0 comments on commit 55a40c1

Please sign in to comment.