rename client id -> client key

This commit is contained in:
Dustin J. Mitchell 2020-12-28 21:31:02 +00:00
parent e555af8895
commit 92d629522b
11 changed files with 124 additions and 122 deletions

View file

@ -108,20 +108,20 @@ fn get_replica(settings: &Config) -> Fallible<Replica> {
fn get_server(settings: &Config) -> Fallible<Box<dyn server::Server>> { fn get_server(settings: &Config) -> Fallible<Box<dyn server::Server>> {
// if server_client_id and server_origin are both set, use // if server_client_id and server_origin are both set, use
// the remote server // the remote server
if let (Ok(client_id), Ok(origin)) = ( if let (Ok(client_key), Ok(origin)) = (
settings.get_str("server_client_id"), settings.get_str("server_client_id"),
settings.get_str("server_origin"), settings.get_str("server_origin"),
) { ) {
let client_id = Uuid::parse_str(&client_id)?; let client_key = Uuid::parse_str(&client_key)?;
let encryption_secret = settings let encryption_secret = settings
.get_str("encryption_secret") .get_str("encryption_secret")
.map_err(|_| format_err!("Could not read `encryption_secret` configuration"))?; .map_err(|_| format_err!("Could not read `encryption_secret` configuration"))?;
log::debug!("Using sync-server with origin {}", origin); log::debug!("Using sync-server with origin {}", origin);
log::debug!("Sync client ID: {}", client_id); log::debug!("Sync client ID: {}", client_key);
Ok(server::from_config(ServerConfig::Remote { Ok(server::from_config(ServerConfig::Remote {
origin, origin,
client_id, client_key,
encryption_secret: encryption_secret.as_bytes().to_vec(), encryption_secret: encryption_secret.as_bytes().to_vec(),
})?) })?)
} else { } else {

View file

@ -9,6 +9,8 @@ The protocol builds on the model presented in the previous chapter, and in parti
From the server's perspective, replicas are indistinguishable, so this protocol uses the term "client" to refer generically to all replicas replicating a single task history. From the server's perspective, replicas are indistinguishable, so this protocol uses the term "client" to refer generically to all replicas replicating a single task history.
Each client is identified and authenticated with a "client key", known only to the server and to the replicas replicating the task history.
## Server ## Server
For each client, the server is responsible for storing the task history, in the form of a branch-free sequence of versions. For each client, the server is responsible for storing the task history, in the form of a branch-free sequence of versions.
@ -66,7 +68,7 @@ If not found, the server returns a negative response.
The transactions above are realized for an HTTP server at `<origin>` using the HTTP requests and responses described here. The transactions above are realized for an HTTP server at `<origin>` using the HTTP requests and responses described here.
The `origin` *should* be an HTTPS endpoint on general principle, but nothing in the functonality or security of the protocol depends on connection encryption. The `origin` *should* be an HTTPS endpoint on general principle, but nothing in the functonality or security of the protocol depends on connection encryption.
The replica identifies itself to the server using a `clientId` in the form of a UUID. The replica identifies itself to the server using a `clientKey` in the form of a UUID.
### AddVersion ### AddVersion

View file

@ -2,7 +2,7 @@ use crate::api::{
failure_to_ise, ServerState, HISTORY_SEGMENT_CONTENT_TYPE, PARENT_VERSION_ID_HEADER, failure_to_ise, ServerState, HISTORY_SEGMENT_CONTENT_TYPE, PARENT_VERSION_ID_HEADER,
VERSION_ID_HEADER, VERSION_ID_HEADER,
}; };
use crate::server::{add_version, AddVersionResult, ClientId, VersionId, NO_VERSION_ID}; use crate::server::{add_version, AddVersionResult, ClientKey, VersionId, NO_VERSION_ID};
use actix_web::{error, post, web, HttpMessage, HttpRequest, HttpResponse, Result}; use actix_web::{error, post, web, HttpMessage, HttpRequest, HttpResponse, Result};
use futures::StreamExt; use futures::StreamExt;
@ -19,11 +19,11 @@ const MAX_SIZE: usize = 100 * 1024 * 1024;
/// parent version ID in the `X-Parent-Version-Id` header. /// parent version ID in the `X-Parent-Version-Id` header.
/// ///
/// Returns other 4xx or 5xx responses on other errors. /// Returns other 4xx or 5xx responses on other errors.
#[post("/client/{client_id}/add-version/{parent_version_id}")] #[post("/client/{client_key}/add-version/{parent_version_id}")]
pub(crate) async fn service( pub(crate) async fn service(
req: HttpRequest, req: HttpRequest,
server_state: web::Data<ServerState>, server_state: web::Data<ServerState>,
web::Path((client_id, parent_version_id)): web::Path<(ClientId, VersionId)>, web::Path((client_key, parent_version_id)): web::Path<(ClientKey, VersionId)>,
mut payload: web::Payload, mut payload: web::Payload,
) -> Result<HttpResponse> { ) -> Result<HttpResponse> {
// check content-type // check content-type
@ -52,16 +52,16 @@ pub(crate) async fn service(
let mut txn = server_state.txn().map_err(failure_to_ise)?; let mut txn = server_state.txn().map_err(failure_to_ise)?;
// get, or create, the client // get, or create, the client
let client = match txn.get_client(client_id).map_err(failure_to_ise)? { let client = match txn.get_client(client_key).map_err(failure_to_ise)? {
Some(client) => client, Some(client) => client,
None => { None => {
txn.new_client(client_id, NO_VERSION_ID) txn.new_client(client_key, NO_VERSION_ID)
.map_err(failure_to_ise)?; .map_err(failure_to_ise)?;
txn.get_client(client_id).map_err(failure_to_ise)?.unwrap() txn.get_client(client_key).map_err(failure_to_ise)?.unwrap()
} }
}; };
let result = add_version(txn, client_id, client, parent_version_id, body.to_vec()) let result = add_version(txn, client_key, client, parent_version_id, body.to_vec())
.map_err(failure_to_ise)?; .map_err(failure_to_ise)?;
Ok(match result { Ok(match result {
AddVersionResult::Ok(version_id) => HttpResponse::Ok() AddVersionResult::Ok(version_id) => HttpResponse::Ok()
@ -83,7 +83,7 @@ mod test {
#[actix_rt::test] #[actix_rt::test]
async fn test_success() { async fn test_success() {
let client_id = Uuid::new_v4(); let client_key = Uuid::new_v4();
let version_id = Uuid::new_v4(); let version_id = Uuid::new_v4();
let parent_version_id = Uuid::new_v4(); let parent_version_id = Uuid::new_v4();
let server_box: Box<dyn Storage> = Box::new(InMemoryStorage::new()); let server_box: Box<dyn Storage> = Box::new(InMemoryStorage::new());
@ -91,13 +91,13 @@ mod test {
// set up the storage contents.. // set up the storage contents..
{ {
let mut txn = server_box.txn().unwrap(); let mut txn = server_box.txn().unwrap();
txn.new_client(client_id, Uuid::nil()).unwrap(); txn.new_client(client_key, Uuid::nil()).unwrap();
} }
let server_state = ServerState::new(server_box); let server_state = ServerState::new(server_box);
let mut app = test::init_service(App::new().service(app_scope(server_state))).await; let mut app = test::init_service(App::new().service(app_scope(server_state))).await;
let uri = format!("/client/{}/add-version/{}", client_id, parent_version_id); let uri = format!("/client/{}/add-version/{}", client_key, parent_version_id);
let req = test::TestRequest::post() let req = test::TestRequest::post()
.uri(&uri) .uri(&uri)
.header( .header(
@ -119,7 +119,7 @@ mod test {
#[actix_rt::test] #[actix_rt::test]
async fn test_conflict() { async fn test_conflict() {
let client_id = Uuid::new_v4(); let client_key = Uuid::new_v4();
let version_id = Uuid::new_v4(); let version_id = Uuid::new_v4();
let parent_version_id = Uuid::new_v4(); let parent_version_id = Uuid::new_v4();
let server_box: Box<dyn Storage> = Box::new(InMemoryStorage::new()); let server_box: Box<dyn Storage> = Box::new(InMemoryStorage::new());
@ -127,13 +127,13 @@ mod test {
// set up the storage contents.. // set up the storage contents..
{ {
let mut txn = server_box.txn().unwrap(); let mut txn = server_box.txn().unwrap();
txn.new_client(client_id, version_id).unwrap(); txn.new_client(client_key, version_id).unwrap();
} }
let server_state = ServerState::new(server_box); let server_state = ServerState::new(server_box);
let mut app = test::init_service(App::new().service(app_scope(server_state))).await; let mut app = test::init_service(App::new().service(app_scope(server_state))).await;
let uri = format!("/client/{}/add-version/{}", client_id, parent_version_id); let uri = format!("/client/{}/add-version/{}", client_key, parent_version_id);
let req = test::TestRequest::post() let req = test::TestRequest::post()
.uri(&uri) .uri(&uri)
.header( .header(
@ -153,13 +153,13 @@ mod test {
#[actix_rt::test] #[actix_rt::test]
async fn test_bad_content_type() { async fn test_bad_content_type() {
let client_id = Uuid::new_v4(); let client_key = Uuid::new_v4();
let parent_version_id = Uuid::new_v4(); let parent_version_id = Uuid::new_v4();
let server_box: Box<dyn Storage> = Box::new(InMemoryStorage::new()); let server_box: Box<dyn Storage> = Box::new(InMemoryStorage::new());
let server_state = ServerState::new(server_box); let server_state = ServerState::new(server_box);
let mut app = test::init_service(App::new().service(app_scope(server_state))).await; let mut app = test::init_service(App::new().service(app_scope(server_state))).await;
let uri = format!("/client/{}/add-version/{}", client_id, parent_version_id); let uri = format!("/client/{}/add-version/{}", client_key, parent_version_id);
let req = test::TestRequest::post() let req = test::TestRequest::post()
.uri(&uri) .uri(&uri)
.header("Content-Type", "not/correct") .header("Content-Type", "not/correct")
@ -171,13 +171,13 @@ mod test {
#[actix_rt::test] #[actix_rt::test]
async fn test_empty_body() { async fn test_empty_body() {
let client_id = Uuid::new_v4(); let client_key = Uuid::new_v4();
let parent_version_id = Uuid::new_v4(); let parent_version_id = Uuid::new_v4();
let server_box: Box<dyn Storage> = Box::new(InMemoryStorage::new()); let server_box: Box<dyn Storage> = Box::new(InMemoryStorage::new());
let server_state = ServerState::new(server_box); let server_state = ServerState::new(server_box);
let mut app = test::init_service(App::new().service(app_scope(server_state))).await; let mut app = test::init_service(App::new().service(app_scope(server_state))).await;
let uri = format!("/client/{}/add-version/{}", client_id, parent_version_id); let uri = format!("/client/{}/add-version/{}", client_key, parent_version_id);
let req = test::TestRequest::post() let req = test::TestRequest::post()
.uri(&uri) .uri(&uri)
.header( .header(

View file

@ -2,7 +2,7 @@ use crate::api::{
failure_to_ise, ServerState, HISTORY_SEGMENT_CONTENT_TYPE, PARENT_VERSION_ID_HEADER, failure_to_ise, ServerState, HISTORY_SEGMENT_CONTENT_TYPE, PARENT_VERSION_ID_HEADER,
VERSION_ID_HEADER, VERSION_ID_HEADER,
}; };
use crate::server::{get_child_version, ClientId, VersionId}; use crate::server::{get_child_version, ClientKey, VersionId};
use actix_web::{error, get, web, HttpResponse, Result}; use actix_web::{error, get, web, HttpResponse, Result};
/// Get a child version. /// Get a child version.
@ -13,18 +13,18 @@ use actix_web::{error, get, web, HttpResponse, Result};
/// ///
/// If no such child exists, returns a 404 with no content. /// If no such child exists, returns a 404 with no content.
/// Returns other 4xx or 5xx responses on other errors. /// Returns other 4xx or 5xx responses on other errors.
#[get("/client/{client_id}/get-child-version/{parent_version_id}")] #[get("/client/{client_key}/get-child-version/{parent_version_id}")]
pub(crate) async fn service( pub(crate) async fn service(
server_state: web::Data<ServerState>, server_state: web::Data<ServerState>,
web::Path((client_id, parent_version_id)): web::Path<(ClientId, VersionId)>, web::Path((client_key, parent_version_id)): web::Path<(ClientKey, VersionId)>,
) -> Result<HttpResponse> { ) -> Result<HttpResponse> {
let mut txn = server_state.txn().map_err(failure_to_ise)?; let mut txn = server_state.txn().map_err(failure_to_ise)?;
txn.get_client(client_id) txn.get_client(client_key)
.map_err(failure_to_ise)? .map_err(failure_to_ise)?
.ok_or_else(|| error::ErrorNotFound("no such client"))?; .ok_or_else(|| error::ErrorNotFound("no such client"))?;
let result = get_child_version(txn, client_id, parent_version_id).map_err(failure_to_ise)?; let result = get_child_version(txn, client_key, parent_version_id).map_err(failure_to_ise)?;
if let Some(result) = result { if let Some(result) = result {
Ok(HttpResponse::Ok() Ok(HttpResponse::Ok()
.content_type(HISTORY_SEGMENT_CONTENT_TYPE) .content_type(HISTORY_SEGMENT_CONTENT_TYPE)
@ -49,7 +49,7 @@ mod test {
#[actix_rt::test] #[actix_rt::test]
async fn test_success() { async fn test_success() {
let client_id = Uuid::new_v4(); let client_key = Uuid::new_v4();
let version_id = Uuid::new_v4(); let version_id = Uuid::new_v4();
let parent_version_id = Uuid::new_v4(); let parent_version_id = Uuid::new_v4();
let server_box: Box<dyn Storage> = Box::new(InMemoryStorage::new()); let server_box: Box<dyn Storage> = Box::new(InMemoryStorage::new());
@ -57,8 +57,8 @@ mod test {
// set up the storage contents.. // set up the storage contents..
{ {
let mut txn = server_box.txn().unwrap(); let mut txn = server_box.txn().unwrap();
txn.new_client(client_id, Uuid::new_v4()).unwrap(); txn.new_client(client_key, Uuid::new_v4()).unwrap();
txn.add_version(client_id, version_id, parent_version_id, b"abcd".to_vec()) txn.add_version(client_key, version_id, parent_version_id, b"abcd".to_vec())
.unwrap(); .unwrap();
} }
@ -67,7 +67,7 @@ mod test {
let uri = format!( let uri = format!(
"/client/{}/get-child-version/{}", "/client/{}/get-child-version/{}",
client_id, parent_version_id client_key, parent_version_id
); );
let req = test::TestRequest::get().uri(&uri).to_request(); let req = test::TestRequest::get().uri(&uri).to_request();
let mut resp = test::call_service(&mut app, req).await; let mut resp = test::call_service(&mut app, req).await;
@ -92,7 +92,7 @@ mod test {
#[actix_rt::test] #[actix_rt::test]
async fn test_client_not_found() { async fn test_client_not_found() {
let client_id = Uuid::new_v4(); let client_key = Uuid::new_v4();
let parent_version_id = Uuid::new_v4(); let parent_version_id = Uuid::new_v4();
let server_box: Box<dyn Storage> = Box::new(InMemoryStorage::new()); let server_box: Box<dyn Storage> = Box::new(InMemoryStorage::new());
let server_state = ServerState::new(server_box); let server_state = ServerState::new(server_box);
@ -100,7 +100,7 @@ mod test {
let uri = format!( let uri = format!(
"/client/{}/get-child-version/{}", "/client/{}/get-child-version/{}",
client_id, parent_version_id client_key, parent_version_id
); );
let req = test::TestRequest::get().uri(&uri).to_request(); let req = test::TestRequest::get().uri(&uri).to_request();
let resp = test::call_service(&mut app, req).await; let resp = test::call_service(&mut app, req).await;
@ -111,21 +111,21 @@ mod test {
#[actix_rt::test] #[actix_rt::test]
async fn test_version_not_found() { async fn test_version_not_found() {
let client_id = Uuid::new_v4(); let client_key = Uuid::new_v4();
let parent_version_id = Uuid::new_v4(); let parent_version_id = Uuid::new_v4();
let server_box: Box<dyn Storage> = Box::new(InMemoryStorage::new()); let server_box: Box<dyn Storage> = Box::new(InMemoryStorage::new());
// create the client, but not the version // create the client, but not the version
{ {
let mut txn = server_box.txn().unwrap(); let mut txn = server_box.txn().unwrap();
txn.new_client(client_id, Uuid::new_v4()).unwrap(); txn.new_client(client_key, Uuid::new_v4()).unwrap();
} }
let server_state = ServerState::new(server_box); let server_state = ServerState::new(server_box);
let mut app = test::init_service(App::new().service(app_scope(server_state))).await; let mut app = test::init_service(App::new().service(app_scope(server_state))).await;
let uri = format!( let uri = format!(
"/client/{}/get-child-version/{}", "/client/{}/get-child-version/{}",
client_id, parent_version_id client_key, parent_version_id
); );
let req = test::TestRequest::get().uri(&uri).to_request(); let req = test::TestRequest::get().uri(&uri).to_request();
let resp = test::call_service(&mut app, req).await; let resp = test::call_service(&mut app, req).await;

View file

@ -8,7 +8,7 @@ use uuid::Uuid;
pub const NO_VERSION_ID: VersionId = Uuid::nil(); pub const NO_VERSION_ID: VersionId = Uuid::nil();
pub(crate) type HistorySegment = Vec<u8>; pub(crate) type HistorySegment = Vec<u8>;
pub(crate) type ClientId = Uuid; pub(crate) type ClientKey = Uuid;
pub(crate) type VersionId = Uuid; pub(crate) type VersionId = Uuid;
/// Response to get_child_version /// Response to get_child_version
@ -21,11 +21,11 @@ pub(crate) struct GetVersionResult {
pub(crate) fn get_child_version<'a>( pub(crate) fn get_child_version<'a>(
mut txn: Box<dyn StorageTxn + 'a>, mut txn: Box<dyn StorageTxn + 'a>,
client_id: ClientId, client_key: ClientKey,
parent_version_id: VersionId, parent_version_id: VersionId,
) -> Fallible<Option<GetVersionResult>> { ) -> Fallible<Option<GetVersionResult>> {
Ok(txn Ok(txn
.get_version_by_parent(client_id, parent_version_id)? .get_version_by_parent(client_key, parent_version_id)?
.map(|version| GetVersionResult { .map(|version| GetVersionResult {
version_id: version.version_id, version_id: version.version_id,
parent_version_id: version.parent_version_id, parent_version_id: version.parent_version_id,
@ -44,14 +44,14 @@ pub(crate) enum AddVersionResult {
pub(crate) fn add_version<'a>( pub(crate) fn add_version<'a>(
mut txn: Box<dyn StorageTxn + 'a>, mut txn: Box<dyn StorageTxn + 'a>,
client_id: ClientId, client_key: ClientKey,
client: Client, client: Client,
parent_version_id: VersionId, parent_version_id: VersionId,
history_segment: HistorySegment, history_segment: HistorySegment,
) -> Fallible<AddVersionResult> { ) -> Fallible<AddVersionResult> {
log::debug!( log::debug!(
"add_version(client_id: {}, parent_version_id: {})", "add_version(client_key: {}, parent_version_id: {})",
client_id, client_key,
parent_version_id, parent_version_id,
); );
@ -71,8 +71,8 @@ pub(crate) fn add_version<'a>(
); );
// update the DB // update the DB
txn.add_version(client_id, version_id, parent_version_id, history_segment)?; txn.add_version(client_key, version_id, parent_version_id, history_segment)?;
txn.set_client_latest_version_id(client_id, version_id)?; txn.set_client_latest_version_id(client_key, version_id)?;
txn.commit()?; txn.commit()?;
Ok(AddVersionResult::Ok(version_id)) Ok(AddVersionResult::Ok(version_id))
@ -87,9 +87,9 @@ mod test {
fn gcv_not_found() -> Fallible<()> { fn gcv_not_found() -> Fallible<()> {
let storage = InMemoryStorage::new(); let storage = InMemoryStorage::new();
let txn = storage.txn()?; let txn = storage.txn()?;
let client_id = Uuid::new_v4(); let client_key = Uuid::new_v4();
let parent_version_id = Uuid::new_v4(); let parent_version_id = Uuid::new_v4();
assert_eq!(get_child_version(txn, client_id, parent_version_id)?, None); assert_eq!(get_child_version(txn, client_key, parent_version_id)?, None);
Ok(()) Ok(())
} }
@ -97,20 +97,20 @@ mod test {
fn gcv_found() -> Fallible<()> { fn gcv_found() -> Fallible<()> {
let storage = InMemoryStorage::new(); let storage = InMemoryStorage::new();
let mut txn = storage.txn()?; let mut txn = storage.txn()?;
let client_id = Uuid::new_v4(); let client_key = Uuid::new_v4();
let version_id = Uuid::new_v4(); let version_id = Uuid::new_v4();
let parent_version_id = Uuid::new_v4(); let parent_version_id = Uuid::new_v4();
let history_segment = b"abcd".to_vec(); let history_segment = b"abcd".to_vec();
txn.add_version( txn.add_version(
client_id, client_key,
version_id, version_id,
parent_version_id, parent_version_id,
history_segment.clone(), history_segment.clone(),
)?; )?;
assert_eq!( assert_eq!(
get_child_version(txn, client_id, parent_version_id)?, get_child_version(txn, client_key, parent_version_id)?,
Some(GetVersionResult { Some(GetVersionResult {
version_id, version_id,
parent_version_id, parent_version_id,
@ -124,7 +124,7 @@ mod test {
fn av_conflict() -> Fallible<()> { fn av_conflict() -> Fallible<()> {
let storage = InMemoryStorage::new(); let storage = InMemoryStorage::new();
let mut txn = storage.txn()?; let mut txn = storage.txn()?;
let client_id = Uuid::new_v4(); let client_key = Uuid::new_v4();
let parent_version_id = Uuid::new_v4(); let parent_version_id = Uuid::new_v4();
let history_segment = b"abcd".to_vec(); let history_segment = b"abcd".to_vec();
let existing_parent_version_id = Uuid::new_v4(); let existing_parent_version_id = Uuid::new_v4();
@ -135,7 +135,7 @@ mod test {
assert_eq!( assert_eq!(
add_version( add_version(
txn, txn,
client_id, client_key,
client, client,
parent_version_id, parent_version_id,
history_segment.clone() history_segment.clone()
@ -145,9 +145,9 @@ mod test {
// verify that the storage wasn't updated // verify that the storage wasn't updated
txn = storage.txn()?; txn = storage.txn()?;
assert_eq!(txn.get_client(client_id)?, None); assert_eq!(txn.get_client(client_key)?, None);
assert_eq!( assert_eq!(
txn.get_version_by_parent(client_id, parent_version_id)?, txn.get_version_by_parent(client_key, parent_version_id)?,
None None
); );
@ -157,7 +157,7 @@ mod test {
fn test_av_success(latest_version_id_nil: bool) -> Fallible<()> { fn test_av_success(latest_version_id_nil: bool) -> Fallible<()> {
let storage = InMemoryStorage::new(); let storage = InMemoryStorage::new();
let mut txn = storage.txn()?; let mut txn = storage.txn()?;
let client_id = Uuid::new_v4(); let client_key = Uuid::new_v4();
let parent_version_id = Uuid::new_v4(); let parent_version_id = Uuid::new_v4();
let history_segment = b"abcd".to_vec(); let history_segment = b"abcd".to_vec();
let latest_version_id = if latest_version_id_nil { let latest_version_id = if latest_version_id_nil {
@ -166,12 +166,12 @@ mod test {
parent_version_id parent_version_id
}; };
txn.new_client(client_id, latest_version_id)?; txn.new_client(client_key, latest_version_id)?;
let client = txn.get_client(client_id)?.unwrap(); let client = txn.get_client(client_key)?.unwrap();
let result = add_version( let result = add_version(
txn, txn,
client_id, client_key,
client, client,
parent_version_id, parent_version_id,
history_segment.clone(), history_segment.clone(),
@ -182,10 +182,10 @@ mod test {
// verify that the storage was updated // verify that the storage was updated
txn = storage.txn()?; txn = storage.txn()?;
let client = txn.get_client(client_id)?.unwrap(); let client = txn.get_client(client_key)?.unwrap();
assert_eq!(client.latest_version_id, new_version_id); assert_eq!(client.latest_version_id, new_version_id);
let version = txn let version = txn
.get_version_by_parent(client_id, parent_version_id)? .get_version_by_parent(client_key, parent_version_id)?
.unwrap(); .unwrap();
assert_eq!(version.version_id, new_version_id); assert_eq!(version.version_id, new_version_id);
assert_eq!(version.parent_version_id, parent_version_id); assert_eq!(version.parent_version_id, parent_version_id);

View file

@ -4,10 +4,10 @@ use std::collections::HashMap;
use std::sync::{Mutex, MutexGuard}; use std::sync::{Mutex, MutexGuard};
struct Inner { struct Inner {
/// Clients, indexed by client_id /// Clients, indexed by client_key
clients: HashMap<Uuid, Client>, clients: HashMap<Uuid, Client>,
/// Versions, indexed by (client_id, parent_version_id) /// Versions, indexed by (client_key, parent_version_id)
versions: HashMap<(Uuid, Uuid), Version>, versions: HashMap<(Uuid, Uuid), Version>,
} }
@ -34,48 +34,48 @@ impl Storage for InMemoryStorage {
} }
impl<'a> StorageTxn for InnerTxn<'a> { impl<'a> StorageTxn for InnerTxn<'a> {
fn get_client(&mut self, client_id: Uuid) -> Fallible<Option<Client>> { fn get_client(&mut self, client_key: Uuid) -> Fallible<Option<Client>> {
Ok(self.0.clients.get(&client_id).cloned()) Ok(self.0.clients.get(&client_key).cloned())
} }
fn new_client(&mut self, client_id: Uuid, latest_version_id: Uuid) -> Fallible<()> { fn new_client(&mut self, client_key: Uuid, latest_version_id: Uuid) -> Fallible<()> {
if self.0.clients.get(&client_id).is_some() { if self.0.clients.get(&client_key).is_some() {
return Err(format_err!("Client {} already exists", client_id)); return Err(format_err!("Client {} already exists", client_key));
} }
self.0 self.0
.clients .clients
.insert(client_id, Client { latest_version_id }); .insert(client_key, Client { latest_version_id });
Ok(()) Ok(())
} }
fn set_client_latest_version_id( fn set_client_latest_version_id(
&mut self, &mut self,
client_id: Uuid, client_key: Uuid,
latest_version_id: Uuid, latest_version_id: Uuid,
) -> Fallible<()> { ) -> Fallible<()> {
if let Some(client) = self.0.clients.get_mut(&client_id) { if let Some(client) = self.0.clients.get_mut(&client_key) {
client.latest_version_id = latest_version_id; client.latest_version_id = latest_version_id;
Ok(()) Ok(())
} else { } else {
Err(format_err!("Client {} does not exist", client_id)) Err(format_err!("Client {} does not exist", client_key))
} }
} }
fn get_version_by_parent( fn get_version_by_parent(
&mut self, &mut self,
client_id: Uuid, client_key: Uuid,
parent_version_id: Uuid, parent_version_id: Uuid,
) -> Fallible<Option<Version>> { ) -> Fallible<Option<Version>> {
Ok(self Ok(self
.0 .0
.versions .versions
.get(&(client_id, parent_version_id)) .get(&(client_key, parent_version_id))
.cloned()) .cloned())
} }
fn add_version( fn add_version(
&mut self, &mut self,
client_id: Uuid, client_key: Uuid,
version_id: Uuid, version_id: Uuid,
parent_version_id: Uuid, parent_version_id: Uuid,
history_segment: Vec<u8>, history_segment: Vec<u8>,
@ -88,7 +88,7 @@ impl<'a> StorageTxn for InnerTxn<'a> {
}; };
self.0 self.0
.versions .versions
.insert((client_id, version.parent_version_id), version); .insert((client_key, version.parent_version_id), version);
Ok(()) Ok(())
} }

View file

@ -4,28 +4,28 @@ use kv::msgpack::Msgpack;
use kv::{Bucket, Config, Error, Serde, Store, ValueBuf}; use kv::{Bucket, Config, Error, Serde, Store, ValueBuf};
use std::path::Path; use std::path::Path;
/// Key for versions: concatenation of client_id and parent_version_id /// DB Key for versions: concatenation of client_key and parent_version_id
type VersionKey = [u8; 32]; type VersionDbKey = [u8; 32];
fn version_key(client_id: Uuid, parent_version_id: Uuid) -> VersionKey { fn version_db_key(client_key: Uuid, parent_version_id: Uuid) -> VersionDbKey {
let mut key = [0u8; 32]; let mut key = [0u8; 32];
key[..16].clone_from_slice(client_id.as_bytes()); key[..16].clone_from_slice(client_key.as_bytes());
key[16..].clone_from_slice(parent_version_id.as_bytes()); key[16..].clone_from_slice(parent_version_id.as_bytes());
key key
} }
/// Key for clients: just the client_id /// Key for clients: just the client_key
type ClientKey = [u8; 16]; type ClientDbKey = [u8; 16];
fn client_key(client_id: Uuid) -> ClientKey { fn client_db_key(client_key: Uuid) -> ClientDbKey {
*client_id.as_bytes() *client_key.as_bytes()
} }
/// KVStorage is an on-disk storage backend which uses LMDB via the `kv` crate. /// KVStorage is an on-disk storage backend which uses LMDB via the `kv` crate.
pub(crate) struct KVStorage<'t> { pub(crate) struct KVStorage<'t> {
store: Store, store: Store,
clients_bucket: Bucket<'t, ClientKey, ValueBuf<Msgpack<Client>>>, clients_bucket: Bucket<'t, ClientDbKey, ValueBuf<Msgpack<Client>>>,
versions_bucket: Bucket<'t, VersionKey, ValueBuf<Msgpack<Version>>>, versions_bucket: Bucket<'t, VersionDbKey, ValueBuf<Msgpack<Version>>>,
} }
impl<'t> KVStorage<'t> { impl<'t> KVStorage<'t> {
@ -37,9 +37,9 @@ impl<'t> KVStorage<'t> {
let store = Store::new(config)?; let store = Store::new(config)?;
let clients_bucket = let clients_bucket =
store.bucket::<ClientKey, ValueBuf<Msgpack<Client>>>(Some("clients"))?; store.bucket::<ClientDbKey, ValueBuf<Msgpack<Client>>>(Some("clients"))?;
let versions_bucket = let versions_bucket =
store.bucket::<VersionKey, ValueBuf<Msgpack<Version>>>(Some("versions"))?; store.bucket::<VersionDbKey, ValueBuf<Msgpack<Version>>>(Some("versions"))?;
Ok(KVStorage { Ok(KVStorage {
store, store,
@ -73,17 +73,17 @@ impl<'t> Txn<'t> {
} }
} }
fn clients_bucket(&self) -> &'t Bucket<'t, ClientKey, ValueBuf<Msgpack<Client>>> { fn clients_bucket(&self) -> &'t Bucket<'t, ClientDbKey, ValueBuf<Msgpack<Client>>> {
&self.storage.clients_bucket &self.storage.clients_bucket
} }
fn versions_bucket(&self) -> &'t Bucket<'t, VersionKey, ValueBuf<Msgpack<Version>>> { fn versions_bucket(&self) -> &'t Bucket<'t, VersionDbKey, ValueBuf<Msgpack<Version>>> {
&self.storage.versions_bucket &self.storage.versions_bucket
} }
} }
impl<'t> StorageTxn for Txn<'t> { impl<'t> StorageTxn for Txn<'t> {
fn get_client(&mut self, client_id: Uuid) -> Fallible<Option<Client>> { fn get_client(&mut self, client_key: Uuid) -> Fallible<Option<Client>> {
let key = client_key(client_id); let key = client_db_key(client_key);
let bucket = self.clients_bucket(); let bucket = self.clients_bucket();
let kvtxn = self.kvtxn(); let kvtxn = self.kvtxn();
@ -97,8 +97,8 @@ impl<'t> StorageTxn for Txn<'t> {
Ok(Some(client)) Ok(Some(client))
} }
fn new_client(&mut self, client_id: Uuid, latest_version_id: Uuid) -> Fallible<()> { fn new_client(&mut self, client_key: Uuid, latest_version_id: Uuid) -> Fallible<()> {
let key = client_key(client_id); let key = client_db_key(client_key);
let bucket = self.clients_bucket(); let bucket = self.clients_bucket();
let kvtxn = self.kvtxn(); let kvtxn = self.kvtxn();
let client = Client { latest_version_id }; let client = Client { latest_version_id };
@ -108,19 +108,19 @@ impl<'t> StorageTxn for Txn<'t> {
fn set_client_latest_version_id( fn set_client_latest_version_id(
&mut self, &mut self,
client_id: Uuid, client_key: Uuid,
latest_version_id: Uuid, latest_version_id: Uuid,
) -> Fallible<()> { ) -> Fallible<()> {
// implementation is the same as new_client.. // implementation is the same as new_client..
self.new_client(client_id, latest_version_id) self.new_client(client_key, latest_version_id)
} }
fn get_version_by_parent( fn get_version_by_parent(
&mut self, &mut self,
client_id: Uuid, client_key: Uuid,
parent_version_id: Uuid, parent_version_id: Uuid,
) -> Fallible<Option<Version>> { ) -> Fallible<Option<Version>> {
let key = version_key(client_id, parent_version_id); let key = version_db_key(client_key, parent_version_id);
let bucket = self.versions_bucket(); let bucket = self.versions_bucket();
let kvtxn = self.kvtxn(); let kvtxn = self.kvtxn();
let version = match kvtxn.get(&bucket, key) { let version = match kvtxn.get(&bucket, key) {
@ -135,12 +135,12 @@ impl<'t> StorageTxn for Txn<'t> {
fn add_version( fn add_version(
&mut self, &mut self,
client_id: Uuid, client_key: Uuid,
version_id: Uuid, version_id: Uuid,
parent_version_id: Uuid, parent_version_id: Uuid,
history_segment: Vec<u8>, history_segment: Vec<u8>,
) -> Fallible<()> { ) -> Fallible<()> {
let key = version_key(client_id, parent_version_id); let key = version_db_key(client_key, parent_version_id);
let bucket = self.versions_bucket(); let bucket = self.versions_bucket();
let kvtxn = self.kvtxn(); let kvtxn = self.kvtxn();
let version = Version { let version = Version {
@ -184,17 +184,17 @@ mod test {
let storage = KVStorage::new(&tmp_dir.path())?; let storage = KVStorage::new(&tmp_dir.path())?;
let mut txn = storage.txn()?; let mut txn = storage.txn()?;
let client_id = Uuid::new_v4(); let client_key = Uuid::new_v4();
let latest_version_id = Uuid::new_v4(); let latest_version_id = Uuid::new_v4();
txn.new_client(client_id, latest_version_id)?; txn.new_client(client_key, latest_version_id)?;
let client = txn.get_client(client_id)?.unwrap(); let client = txn.get_client(client_key)?.unwrap();
assert_eq!(client.latest_version_id, latest_version_id); assert_eq!(client.latest_version_id, latest_version_id);
let latest_version_id = Uuid::new_v4(); let latest_version_id = Uuid::new_v4();
txn.set_client_latest_version_id(client_id, latest_version_id)?; txn.set_client_latest_version_id(client_key, latest_version_id)?;
let client = txn.get_client(client_id)?.unwrap(); let client = txn.get_client(client_key)?.unwrap();
assert_eq!(client.latest_version_id, latest_version_id); assert_eq!(client.latest_version_id, latest_version_id);
Ok(()) Ok(())
@ -216,18 +216,18 @@ mod test {
let storage = KVStorage::new(&tmp_dir.path())?; let storage = KVStorage::new(&tmp_dir.path())?;
let mut txn = storage.txn()?; let mut txn = storage.txn()?;
let client_id = Uuid::new_v4(); let client_key = Uuid::new_v4();
let version_id = Uuid::new_v4(); let version_id = Uuid::new_v4();
let parent_version_id = Uuid::new_v4(); let parent_version_id = Uuid::new_v4();
let history_segment = b"abc".to_vec(); let history_segment = b"abc".to_vec();
txn.add_version( txn.add_version(
client_id, client_key,
version_id, version_id,
parent_version_id, parent_version_id,
history_segment.clone(), history_segment.clone(),
)?; )?;
let version = txn let version = txn
.get_version_by_parent(client_id, parent_version_id)? .get_version_by_parent(client_key, parent_version_id)?
.unwrap(); .unwrap();
assert_eq!( assert_eq!(

View file

@ -24,29 +24,29 @@ pub(crate) struct Version {
pub(crate) trait StorageTxn { pub(crate) trait StorageTxn {
/// Get information about the given client /// Get information about the given client
fn get_client(&mut self, client_id: Uuid) -> Fallible<Option<Client>>; fn get_client(&mut self, client_key: Uuid) -> Fallible<Option<Client>>;
/// Create a new client with the given latest_version_id /// Create a new client with the given latest_version_id
fn new_client(&mut self, client_id: Uuid, latest_version_id: Uuid) -> Fallible<()>; fn new_client(&mut self, client_key: Uuid, latest_version_id: Uuid) -> Fallible<()>;
/// Set the client's latest_version_id /// Set the client's latest_version_id
fn set_client_latest_version_id( fn set_client_latest_version_id(
&mut self, &mut self,
client_id: Uuid, client_key: Uuid,
latest_version_id: Uuid, latest_version_id: Uuid,
) -> Fallible<()>; ) -> Fallible<()>;
/// Get a version, indexed by parent version id /// Get a version, indexed by parent version id
fn get_version_by_parent( fn get_version_by_parent(
&mut self, &mut self,
client_id: Uuid, client_key: Uuid,
parent_version_id: Uuid, parent_version_id: Uuid,
) -> Fallible<Option<Version>>; ) -> Fallible<Option<Version>>;
/// Add a version (that must not already exist) /// Add a version (that must not already exist)
fn add_version( fn add_version(
&mut self, &mut self,
client_id: Uuid, client_key: Uuid,
version_id: Uuid, version_id: Uuid,
parent_version_id: Uuid, parent_version_id: Uuid,
history_segment: Vec<u8>, history_segment: Vec<u8>,

View file

@ -20,8 +20,8 @@ pub enum ServerConfig {
/// Sync server "origin"; a URL with schema and hostname but no path or trailing `/` /// Sync server "origin"; a URL with schema and hostname but no path or trailing `/`
origin: String, origin: String,
/// Client ID to identify this replica to the server /// Client Key to identify and authenticate this replica to the server
client_id: Uuid, client_key: Uuid,
/// Private encryption secret used to encrypt all data sent to the server. This can /// Private encryption secret used to encrypt all data sent to the server. This can
/// be any suitably un-guessable string of bytes. /// be any suitably un-guessable string of bytes.

View file

@ -18,8 +18,8 @@ pub fn from_config(config: ServerConfig) -> Fallible<Box<dyn Server>> {
ServerConfig::Local { server_dir } => Box::new(LocalServer::new(server_dir)?), ServerConfig::Local { server_dir } => Box::new(LocalServer::new(server_dir)?),
ServerConfig::Remote { ServerConfig::Remote {
origin, origin,
client_id, client_key,
encryption_secret, encryption_secret,
} => Box::new(RemoteServer::new(origin, client_id, encryption_secret)), } => Box::new(RemoteServer::new(origin, client_key, encryption_secret)),
}) })
} }

View file

@ -8,7 +8,7 @@ use crypto::{HistoryCiphertext, HistoryCleartext, Secret};
pub struct RemoteServer { pub struct RemoteServer {
origin: String, origin: String,
client_id: Uuid, client_key: Uuid,
encryption_secret: Secret, encryption_secret: Secret,
agent: ureq::Agent, agent: ureq::Agent,
} }
@ -17,13 +17,13 @@ pub struct RemoteServer {
/// taskchampion-sync-server). /// taskchampion-sync-server).
impl RemoteServer { impl RemoteServer {
/// Construct a new RemoteServer. The `origin` is the sync server's protocol and hostname /// Construct a new RemoteServer. The `origin` is the sync server's protocol and hostname
/// without a trailing slash, such as `https://tcsync.example.com`. Pass a client_id to /// without a trailing slash, such as `https://tcsync.example.com`. Pass a client_key to
/// identify this client to the server. Multiple replicas synchronizing the same task history /// identify this client to the server. Multiple replicas synchronizing the same task history
/// should use the same client_id. /// should use the same client_key.
pub fn new(origin: String, client_id: Uuid, encryption_secret: Vec<u8>) -> RemoteServer { pub fn new(origin: String, client_key: Uuid, encryption_secret: Vec<u8>) -> RemoteServer {
RemoteServer { RemoteServer {
origin, origin,
client_id, client_key,
encryption_secret: encryption_secret.into(), encryption_secret: encryption_secret.into(),
agent: ureq::agent(), agent: ureq::agent(),
} }
@ -58,7 +58,7 @@ impl Server for RemoteServer {
) -> Fallible<AddVersionResult> { ) -> Fallible<AddVersionResult> {
let url = format!( let url = format!(
"{}/client/{}/add-version/{}", "{}/client/{}/add-version/{}",
self.origin, self.client_id, parent_version_id self.origin, self.client_key, parent_version_id
); );
let history_cleartext = HistoryCleartext { let history_cleartext = HistoryCleartext {
parent_version_id, parent_version_id,
@ -89,7 +89,7 @@ impl Server for RemoteServer {
fn get_child_version(&mut self, parent_version_id: VersionId) -> Fallible<GetVersionResult> { fn get_child_version(&mut self, parent_version_id: VersionId) -> Fallible<GetVersionResult> {
let url = format!( let url = format!(
"{}/client/{}/get-child-version/{}", "{}/client/{}/get-child-version/{}",
self.origin, self.client_id, parent_version_id self.origin, self.client_key, parent_version_id
); );
let resp = self let resp = self
.agent .agent