Change "client key" to "client id" (#3130)

In #3118 @ryneeverett mentioned that "key" suggests that this is a
secret, when in truth it's just a user identifier. So "ID" is a better
word for it than "key".
This commit is contained in:
Dustin J. Mitchell 2023-07-11 22:13:53 -04:00 committed by GitHub
parent 8097e28318
commit 7f68441916
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
32 changed files with 387 additions and 388 deletions

View file

@ -29,9 +29,9 @@ information from the server administrator:
.br
- The server's URL ("origin", such as "https://tw.example.com")
.br
- A client key ("client_key") identifying your tasks
- A client ID ("client_id") identifying your tasks
.br
- An encryption secret ("encryption_secret") used to encrypt and decrypt your tasks. This can be any secret string, and must match for all replicas using the same client key.
- An encryption secret ("encryption_secret") used to encrypt and decrypt your tasks. This can be any secret string, and must match for all replicas using the same client ID.
Tools such as
.BR pwgen (1)
@ -40,7 +40,7 @@ can generate suitable secret values.
Configure Taskwarrior with these details:
$ task config sync.server.origin <origin>
$ task config sync.server.client_key <client_key>
$ task config sync.server.client_id <client_id>
$ task config sync.server.encryption_secret <encryption_secret>
.SS Adding a Replica
@ -74,7 +74,7 @@ The default configuration is to sync to a database in the task directory
.SH RUNNING TASKCHAMPION-SYNC-SERVER
The Taskchampion sync server is an HTTP server supporting multiple users.
Users are identified by a client key, and users with different client keys are
Users are identified by a client ID, and users with different client IDs are
entirely independent. Task data is encrypted by Taskwarrior, and the sync
server never sees un-encrypted data.
@ -91,10 +91,10 @@ For example:
.SS Adding a New User
To add a new user to the server, invent a new client key with a tool like
To add a new user to the server, invent a new client ID with a tool like
`uuidgen` or an online UUID generator. There is no need to configure the server
for this new client key: the sync server will automatically create a new user
whenever presented with a new client key. Supply the key, along with the
for this new client ID: the sync server will automatically create a new user
whenever presented with a new client ID. Supply the ID, along with the
origin, to the user for inclusion in their Taskwarrior config. The user should
invent their own "encryption_secret".

View file

@ -163,7 +163,7 @@ syn match taskrcGoodKey '^\s*\Vrule.precedence.color='he=e-1
syn match taskrcGoodKey '^\s*\Vsearch.case.sensitive='he=e-1
syn match taskrcGoodKey '^\s*\Vsummary.all.projects='he=e-1
syn match taskrcGoodKey '^\s*\Vsugar='he=e-1
syn match taskrcGoodKey '^\s*\Vsync.\(server.\(origin\|client_key\|encryption_secret\)\|local.server_dir\)='he=e-1
syn match taskrcGoodKey '^\s*\Vsync.\(server.\(origin\|client_id\|encryption_secret\)\|local.server_dir\)='he=e-1
syn match taskrcGoodKey '^\s*\Vtag.indicator='he=e-1
syn match taskrcGoodKey '^\s*\Vuda.\S\{-}.\(default\|type\|label\|values\|indicator\)='he=e-1
syn match taskrcGoodKey '^\s*\Vundo.style='he=e-1

View file

@ -279,7 +279,7 @@ std::string configurationDefaults =
"# WARNING: Please read the documentation (man task-sync) before setting up\n"
"# Taskwarrior for Taskserver synchronization.\n"
"\n"
"#sync.server.client_key # Client key for sync to a server\n"
"#sync.server.client_id # Client ID for sync to a server\n"
"#sync.server.encryption_secret # Encryption secret for sync to a server\n"
"#sync.server.origin # Origin of the sync server\n"
"#sync.local.server_dir # Directory for local sync\n"

View file

@ -193,7 +193,7 @@ int CmdShow::execute (std::string& output)
" sugar"
" summary.all.projects"
" sync.local.server_dir"
" sync.server.client_key"
" sync.server.client_id"
" sync.server.encryption_secret"
" sync.server.origin"
" tag.indicator"

View file

@ -63,14 +63,14 @@ int CmdSync::execute (std::string& output)
// If no server is set up, quit.
std::string origin = Context::getContext ().config.get ("sync.server.origin");
std::string client_key = Context::getContext ().config.get ("sync.server.client_key");
std::string client_id = Context::getContext ().config.get ("sync.server.client_id");
std::string encryption_secret = Context::getContext ().config.get ("sync.server.encryption_secret");
std::string server_dir = Context::getContext ().config.get ("sync.local.server_dir");
if (server_dir != "") {
server = tc::Server (server_dir);
server_ident = server_dir;
} else if (origin != "" && client_key != "" && encryption_secret != "") {
server = tc::Server (origin, client_key, encryption_secret);
} else if (origin != "" && client_id != "" && encryption_secret != "") {
server = tc::Server (origin, client_id, encryption_secret);
server_ident = origin;
} else {
throw std::string ("Neither sync.server nor sync.local are configured.");

View file

@ -49,23 +49,23 @@ tc::Server::Server (const std::string &server_dir)
}
////////////////////////////////////////////////////////////////////////////////
tc::Server::Server (const std::string &origin, const std::string &client_key, const std::string &encryption_secret)
tc::Server::Server (const std::string &origin, const std::string &client_id, const std::string &encryption_secret)
{
TCString tc_origin = tc_string_borrow (origin.c_str ());
TCString tc_client_key_str = tc_string_borrow (client_key.c_str ());
TCString tc_client_id = tc_string_borrow (client_id.c_str ());
TCString tc_encryption_secret = tc_string_borrow (encryption_secret.c_str ());
TCUuid tc_client_key;
if (tc_uuid_from_str(tc_client_key_str, &tc_client_key) != TC_RESULT_OK) {
TCUuid tc_client_uuid;
if (tc_uuid_from_str(tc_client_id, &tc_client_uuid) != TC_RESULT_OK) {
tc_string_free(&tc_origin);
tc_string_free(&tc_encryption_secret);
throw "client_key must be a valid UUID";
throw "client_id must be a valid UUID";
}
TCString error;
auto tcserver = tc_server_new_remote (tc_origin, tc_client_key, tc_encryption_secret, &error);
auto tcserver = tc_server_new_remote (tc_origin, tc_client_uuid, tc_encryption_secret, &error);
if (!tcserver) {
auto errmsg = format ("Could not configure connection to server at {1}: {2}",
origin, tc_string_content (&error));

View file

@ -54,7 +54,7 @@ namespace tc {
Server (const std::string& server_dir);
// Construct a remote server (tc_server_new_remote).
Server (const std::string &origin, const std::string &client_key, const std::string &encryption_secret);
Server (const std::string &origin, const std::string &client_id, const std::string &encryption_secret);
// This object "owns" inner, so copy is not allowed.
Server (const Server &) = delete;

View file

@ -9,7 +9,7 @@ The protocol builds on the model presented in the previous chapter, and in parti
From the server's perspective, replicas accessing the same task history 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.
Each client is identified and authenticated with a "client_id key", known only to the server and to the replicas replicating the task history.
## Server
@ -48,7 +48,7 @@ This section describes how that information is used to encrypt and decrypt data
#### Key Derivation
The client derives the 32-byte encryption key from the configured encryption secret using PBKDF2 with HMAC-SHA256 and 100,000 iterations.
The salt is the SHA256 hash of the 16-byte form of the client key.
The salt is the SHA256 hash of the 16-byte form of the client ID.
#### Encryption
@ -172,7 +172,7 @@ The response contains the snapshot version ID and the snapshot data, if those ex
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 replica identifies itself to the server using a `clientKey` in the form of a UUID.
The replica identifies itself to the server using a `client_id` in the form of a UUID.
This value is passed with every request in the `X-Client-Id` header, in its dashed-hex format.
### AddVersion

View file

@ -26,12 +26,12 @@ async fn cross_sync() -> anyhow::Result<()> {
let mut rep1 = Replica::new(StorageConfig::InMemory.into_storage()?);
let mut rep2 = Replica::new(StorageConfig::InMemory.into_storage()?);
let client_key = Uuid::new_v4();
let client_id = Uuid::new_v4();
let encryption_secret = b"abc123".to_vec();
let make_server = || {
ServerConfig::Remote {
origin: format!("http://127.0.0.1:{}", port),
client_key,
client_id,
encryption_secret: encryption_secret.clone(),
}
.into_server()

View file

@ -30,12 +30,12 @@ async fn sync_with_snapshots() -> anyhow::Result<()> {
}
fn client(port: u16) -> anyhow::Result<()> {
let client_key = Uuid::new_v4();
let client_id = Uuid::new_v4();
let encryption_secret = b"abc123".to_vec();
let make_server = || {
ServerConfig::Remote {
origin: format!("http://127.0.0.1:{}", port),
client_key,
client_id,
encryption_secret: encryption_secret.clone(),
}
.into_server()

View file

@ -109,14 +109,14 @@ pub unsafe extern "C" fn tc_server_new_local(
///
/// ```c
/// EXTERN_C struct TCServer *tc_server_new_remote(struct TCString origin,
/// struct TCUuid client_key,
/// struct TCUuid client_id,
/// struct TCString encryption_secret,
/// struct TCString *error_out);
/// ```
#[no_mangle]
pub unsafe extern "C" fn tc_server_new_remote(
origin: TCString,
client_key: TCUuid,
client_id: TCUuid,
encryption_secret: TCString,
error_out: *mut TCString,
) -> *mut TCServer {
@ -128,9 +128,9 @@ pub unsafe extern "C" fn tc_server_new_remote(
let origin = unsafe { TCString::val_from_arg(origin) }.into_string()?;
// SAFETY:
// - client_key is a valid Uuid (any 8-byte sequence counts)
// - client_id is a valid Uuid (any 8-byte sequence counts)
let client_key = unsafe { TCUuid::val_from_arg(client_key) };
let client_id = unsafe { TCUuid::val_from_arg(client_id) };
// SAFETY:
// - encryption_secret is valid (promised by caller)
// - encryption_secret ownership is transferred to this function
@ -140,7 +140,7 @@ pub unsafe extern "C" fn tc_server_new_remote(
let server_config = ServerConfig::Remote {
origin,
client_key,
client_id,
encryption_secret,
};
let server = server_config.into_server()?;

View file

@ -433,7 +433,7 @@ EXTERN_C struct TCServer *tc_server_new_local(struct TCString server_dir, struct
//
// The server must be freed after it is used - tc_replica_sync does not automatically free it.
EXTERN_C struct TCServer *tc_server_new_remote(struct TCString origin,
struct TCUuid client_key,
struct TCUuid client_id,
struct TCString encryption_secret,
struct TCString *error_out);

View file

@ -1,4 +1,4 @@
use crate::api::{client_key_header, failure_to_ise, ServerState, SNAPSHOT_CONTENT_TYPE};
use crate::api::{client_id_header, failure_to_ise, ServerState, SNAPSHOT_CONTENT_TYPE};
use crate::server::{add_snapshot, VersionId, NIL_VERSION_ID};
use actix_web::{error, post, web, HttpMessage, HttpRequest, HttpResponse, Result};
use futures::StreamExt;
@ -29,7 +29,7 @@ pub(crate) async fn service(
return Err(error::ErrorBadRequest("Bad content-type"));
}
let client_key = client_key_header(&req)?;
let client_id = client_id_header(&req)?;
// read the body in its entirety
let mut body = web::BytesMut::new();
@ -52,19 +52,19 @@ pub(crate) async fn service(
let mut txn = server_state.storage.txn().map_err(failure_to_ise)?;
// get, or create, the client
let client = match txn.get_client(client_key).map_err(failure_to_ise)? {
let client = match txn.get_client(client_id).map_err(failure_to_ise)? {
Some(client) => client,
None => {
txn.new_client(client_key, NIL_VERSION_ID)
txn.new_client(client_id, NIL_VERSION_ID)
.map_err(failure_to_ise)?;
txn.get_client(client_key).map_err(failure_to_ise)?.unwrap()
txn.get_client(client_id).map_err(failure_to_ise)?.unwrap()
}
};
add_snapshot(
txn,
&server_state.config,
client_key,
client_id,
client,
version_id,
body.to_vec(),
@ -76,6 +76,7 @@ pub(crate) async fn service(
#[cfg(test)]
mod test {
use super::*;
use crate::api::CLIENT_ID_HEADER;
use crate::storage::{InMemoryStorage, Storage};
use crate::Server;
use actix_web::{http::StatusCode, test, App};
@ -84,15 +85,15 @@ mod test {
#[actix_rt::test]
async fn test_success() -> anyhow::Result<()> {
let client_key = Uuid::new_v4();
let client_id = Uuid::new_v4();
let version_id = Uuid::new_v4();
let storage: Box<dyn Storage> = Box::new(InMemoryStorage::new());
// set up the storage contents..
{
let mut txn = storage.txn().unwrap();
txn.new_client(client_key, version_id).unwrap();
txn.add_version(client_key, version_id, NIL_VERSION_ID, vec![])?;
txn.new_client(client_id, version_id).unwrap();
txn.add_version(client_id, version_id, NIL_VERSION_ID, vec![])?;
}
let server = Server::new(Default::default(), storage);
@ -103,7 +104,7 @@ mod test {
let req = test::TestRequest::post()
.uri(&uri)
.insert_header(("Content-Type", "application/vnd.taskchampion.snapshot"))
.insert_header(("X-Client-Key", client_key.to_string()))
.insert_header((CLIENT_ID_HEADER, client_id.to_string()))
.set_payload(b"abcd".to_vec())
.to_request();
let resp = test::call_service(&mut app, req).await;
@ -113,7 +114,7 @@ mod test {
let uri = "/v1/client/snapshot";
let req = test::TestRequest::get()
.uri(uri)
.append_header(("X-Client-Key", client_key.to_string()))
.append_header((CLIENT_ID_HEADER, client_id.to_string()))
.to_request();
let resp = test::call_service(&mut app, req).await;
assert_eq!(resp.status(), StatusCode::OK);
@ -127,14 +128,14 @@ mod test {
#[actix_rt::test]
async fn test_not_added_200() {
let client_key = Uuid::new_v4();
let client_id = Uuid::new_v4();
let version_id = Uuid::new_v4();
let storage: Box<dyn Storage> = Box::new(InMemoryStorage::new());
// set up the storage contents..
{
let mut txn = storage.txn().unwrap();
txn.new_client(client_key, NIL_VERSION_ID).unwrap();
txn.new_client(client_id, NIL_VERSION_ID).unwrap();
}
let server = Server::new(Default::default(), storage);
@ -146,7 +147,7 @@ mod test {
let req = test::TestRequest::post()
.uri(&uri)
.append_header(("Content-Type", "application/vnd.taskchampion.snapshot"))
.append_header(("X-Client-Key", client_key.to_string()))
.append_header((CLIENT_ID_HEADER, client_id.to_string()))
.set_payload(b"abcd".to_vec())
.to_request();
let resp = test::call_service(&mut app, req).await;
@ -156,7 +157,7 @@ mod test {
let uri = "/v1/client/snapshot";
let req = test::TestRequest::get()
.uri(uri)
.append_header(("X-Client-Key", client_key.to_string()))
.append_header((CLIENT_ID_HEADER, client_id.to_string()))
.to_request();
let resp = test::call_service(&mut app, req).await;
assert_eq!(resp.status(), StatusCode::NOT_FOUND);
@ -164,7 +165,7 @@ mod test {
#[actix_rt::test]
async fn test_bad_content_type() {
let client_key = Uuid::new_v4();
let client_id = Uuid::new_v4();
let version_id = Uuid::new_v4();
let storage: Box<dyn Storage> = Box::new(InMemoryStorage::new());
let server = Server::new(Default::default(), storage);
@ -175,7 +176,7 @@ mod test {
let req = test::TestRequest::post()
.uri(&uri)
.append_header(("Content-Type", "not/correct"))
.append_header(("X-Client-Key", client_key.to_string()))
.append_header((CLIENT_ID_HEADER, client_id.to_string()))
.set_payload(b"abcd".to_vec())
.to_request();
let resp = test::call_service(&mut app, req).await;
@ -184,7 +185,7 @@ mod test {
#[actix_rt::test]
async fn test_empty_body() {
let client_key = Uuid::new_v4();
let client_id = Uuid::new_v4();
let version_id = Uuid::new_v4();
let storage: Box<dyn Storage> = Box::new(InMemoryStorage::new());
let server = Server::new(Default::default(), storage);
@ -198,7 +199,7 @@ mod test {
"Content-Type",
"application/vnd.taskchampion.history-segment",
))
.append_header(("X-Client-Key", client_key.to_string()))
.append_header((CLIENT_ID_HEADER, client_id.to_string()))
.to_request();
let resp = test::call_service(&mut app, req).await;
assert_eq!(resp.status(), StatusCode::BAD_REQUEST);

View file

@ -1,5 +1,5 @@
use crate::api::{
client_key_header, failure_to_ise, ServerState, HISTORY_SEGMENT_CONTENT_TYPE,
client_id_header, failure_to_ise, ServerState, HISTORY_SEGMENT_CONTENT_TYPE,
PARENT_VERSION_ID_HEADER, SNAPSHOT_REQUEST_HEADER, VERSION_ID_HEADER,
};
use crate::server::{add_version, AddVersionResult, SnapshotUrgency, VersionId, NIL_VERSION_ID};
@ -37,7 +37,7 @@ pub(crate) async fn service(
return Err(error::ErrorBadRequest("Bad content-type"));
}
let client_key = client_key_header(&req)?;
let client_id = client_id_header(&req)?;
// read the body in its entirety
let mut body = web::BytesMut::new();
@ -60,19 +60,19 @@ pub(crate) async fn service(
let mut txn = server_state.storage.txn().map_err(failure_to_ise)?;
// get, or create, the client
let client = match txn.get_client(client_key).map_err(failure_to_ise)? {
let client = match txn.get_client(client_id).map_err(failure_to_ise)? {
Some(client) => client,
None => {
txn.new_client(client_key, NIL_VERSION_ID)
txn.new_client(client_id, NIL_VERSION_ID)
.map_err(failure_to_ise)?;
txn.get_client(client_key).map_err(failure_to_ise)?.unwrap()
txn.get_client(client_id).map_err(failure_to_ise)?.unwrap()
}
};
let (result, snap_urgency) = add_version(
txn,
&server_state.config,
client_key,
client_id,
client,
parent_version_id,
body.to_vec(),
@ -104,6 +104,7 @@ pub(crate) async fn service(
#[cfg(test)]
mod test {
use crate::api::CLIENT_ID_HEADER;
use crate::storage::{InMemoryStorage, Storage};
use crate::Server;
use actix_web::{http::StatusCode, test, App};
@ -112,7 +113,7 @@ mod test {
#[actix_rt::test]
async fn test_success() {
let client_key = Uuid::new_v4();
let client_id = Uuid::new_v4();
let version_id = Uuid::new_v4();
let parent_version_id = Uuid::new_v4();
let storage: Box<dyn Storage> = Box::new(InMemoryStorage::new());
@ -120,7 +121,7 @@ mod test {
// set up the storage contents..
{
let mut txn = storage.txn().unwrap();
txn.new_client(client_key, Uuid::nil()).unwrap();
txn.new_client(client_id, Uuid::nil()).unwrap();
}
let server = Server::new(Default::default(), storage);
@ -134,7 +135,7 @@ mod test {
"Content-Type",
"application/vnd.taskchampion.history-segment",
))
.append_header(("X-Client-Key", client_key.to_string()))
.append_header((CLIENT_ID_HEADER, client_id.to_string()))
.set_payload(b"abcd".to_vec())
.to_request();
let resp = test::call_service(&mut app, req).await;
@ -154,7 +155,7 @@ mod test {
#[actix_rt::test]
async fn test_conflict() {
let client_key = Uuid::new_v4();
let client_id = Uuid::new_v4();
let version_id = Uuid::new_v4();
let parent_version_id = Uuid::new_v4();
let storage: Box<dyn Storage> = Box::new(InMemoryStorage::new());
@ -162,7 +163,7 @@ mod test {
// set up the storage contents..
{
let mut txn = storage.txn().unwrap();
txn.new_client(client_key, version_id).unwrap();
txn.new_client(client_id, version_id).unwrap();
}
let server = Server::new(Default::default(), storage);
@ -176,7 +177,7 @@ mod test {
"Content-Type",
"application/vnd.taskchampion.history-segment",
))
.append_header(("X-Client-Key", client_key.to_string()))
.append_header((CLIENT_ID_HEADER, client_id.to_string()))
.set_payload(b"abcd".to_vec())
.to_request();
let resp = test::call_service(&mut app, req).await;
@ -190,7 +191,7 @@ mod test {
#[actix_rt::test]
async fn test_bad_content_type() {
let client_key = Uuid::new_v4();
let client_id = Uuid::new_v4();
let parent_version_id = Uuid::new_v4();
let storage: Box<dyn Storage> = Box::new(InMemoryStorage::new());
let server = Server::new(Default::default(), storage);
@ -201,7 +202,7 @@ mod test {
let req = test::TestRequest::post()
.uri(&uri)
.append_header(("Content-Type", "not/correct"))
.append_header(("X-Client-Key", client_key.to_string()))
.append_header((CLIENT_ID_HEADER, client_id.to_string()))
.set_payload(b"abcd".to_vec())
.to_request();
let resp = test::call_service(&mut app, req).await;
@ -210,7 +211,7 @@ mod test {
#[actix_rt::test]
async fn test_empty_body() {
let client_key = Uuid::new_v4();
let client_id = Uuid::new_v4();
let parent_version_id = Uuid::new_v4();
let storage: Box<dyn Storage> = Box::new(InMemoryStorage::new());
let server = Server::new(Default::default(), storage);
@ -224,7 +225,7 @@ mod test {
"Content-Type",
"application/vnd.taskchampion.history-segment",
))
.append_header(("X-Client-Key", client_key.to_string()))
.append_header((CLIENT_ID_HEADER, client_id.to_string()))
.to_request();
let resp = test::call_service(&mut app, req).await;
assert_eq!(resp.status(), StatusCode::BAD_REQUEST);

View file

@ -1,5 +1,5 @@
use crate::api::{
client_key_header, failure_to_ise, ServerState, HISTORY_SEGMENT_CONTENT_TYPE,
client_id_header, failure_to_ise, ServerState, HISTORY_SEGMENT_CONTENT_TYPE,
PARENT_VERSION_ID_HEADER, VERSION_ID_HEADER,
};
use crate::server::{get_child_version, GetVersionResult, VersionId};
@ -24,17 +24,17 @@ pub(crate) async fn service(
let mut txn = server_state.storage.txn().map_err(failure_to_ise)?;
let client_key = client_key_header(&req)?;
let client_id = client_id_header(&req)?;
let client = txn
.get_client(client_key)
.get_client(client_id)
.map_err(failure_to_ise)?
.ok_or_else(|| error::ErrorNotFound("no such client"))?;
return match get_child_version(
txn,
&server_state.config,
client_key,
client_id,
client,
parent_version_id,
)
@ -56,6 +56,7 @@ pub(crate) async fn service(
#[cfg(test)]
mod test {
use crate::api::CLIENT_ID_HEADER;
use crate::server::NIL_VERSION_ID;
use crate::storage::{InMemoryStorage, Storage};
use crate::Server;
@ -65,7 +66,7 @@ mod test {
#[actix_rt::test]
async fn test_success() {
let client_key = Uuid::new_v4();
let client_id = Uuid::new_v4();
let version_id = Uuid::new_v4();
let parent_version_id = Uuid::new_v4();
let storage: Box<dyn Storage> = Box::new(InMemoryStorage::new());
@ -73,8 +74,8 @@ mod test {
// set up the storage contents..
{
let mut txn = storage.txn().unwrap();
txn.new_client(client_key, Uuid::new_v4()).unwrap();
txn.add_version(client_key, version_id, parent_version_id, b"abcd".to_vec())
txn.new_client(client_id, Uuid::new_v4()).unwrap();
txn.add_version(client_id, version_id, parent_version_id, b"abcd".to_vec())
.unwrap();
}
@ -85,7 +86,7 @@ mod test {
let uri = format!("/v1/client/get-child-version/{}", parent_version_id);
let req = test::TestRequest::get()
.uri(&uri)
.append_header(("X-Client-Key", client_key.to_string()))
.append_header((CLIENT_ID_HEADER, client_id.to_string()))
.to_request();
let resp = test::call_service(&mut app, req).await;
assert_eq!(resp.status(), StatusCode::OK);
@ -109,7 +110,7 @@ mod test {
#[actix_rt::test]
async fn test_client_not_found() {
let client_key = Uuid::new_v4();
let client_id = Uuid::new_v4();
let parent_version_id = Uuid::new_v4();
let storage: Box<dyn Storage> = Box::new(InMemoryStorage::new());
let server = Server::new(Default::default(), storage);
@ -119,7 +120,7 @@ mod test {
let uri = format!("/v1/client/get-child-version/{}", parent_version_id);
let req = test::TestRequest::get()
.uri(&uri)
.append_header(("X-Client-Key", client_key.to_string()))
.append_header((CLIENT_ID_HEADER, client_id.to_string()))
.to_request();
let resp = test::call_service(&mut app, req).await;
assert_eq!(resp.status(), StatusCode::NOT_FOUND);
@ -129,14 +130,14 @@ mod test {
#[actix_rt::test]
async fn test_version_not_found_and_gone() {
let client_key = Uuid::new_v4();
let client_id = Uuid::new_v4();
let parent_version_id = Uuid::new_v4();
let storage: Box<dyn Storage> = Box::new(InMemoryStorage::new());
// create the client, but not the version
{
let mut txn = storage.txn().unwrap();
txn.new_client(client_key, Uuid::new_v4()).unwrap();
txn.new_client(client_id, Uuid::new_v4()).unwrap();
}
let server = Server::new(Default::default(), storage);
let app = App::new().configure(|sc| server.config(sc));
@ -146,7 +147,7 @@ mod test {
let uri = format!("/v1/client/get-child-version/{}", parent_version_id);
let req = test::TestRequest::get()
.uri(&uri)
.append_header(("X-Client-Key", client_key.to_string()))
.append_header((CLIENT_ID_HEADER, client_id.to_string()))
.to_request();
let resp = test::call_service(&mut app, req).await;
assert_eq!(resp.status(), StatusCode::GONE);
@ -159,7 +160,7 @@ mod test {
let uri = format!("/v1/client/get-child-version/{}", NIL_VERSION_ID);
let req = test::TestRequest::get()
.uri(&uri)
.append_header(("X-Client-Key", client_key.to_string()))
.append_header((CLIENT_ID_HEADER, client_id.to_string()))
.to_request();
let resp = test::call_service(&mut app, req).await;
assert_eq!(resp.status(), StatusCode::NOT_FOUND);

View file

@ -1,5 +1,5 @@
use crate::api::{
client_key_header, failure_to_ise, ServerState, SNAPSHOT_CONTENT_TYPE, VERSION_ID_HEADER,
client_id_header, failure_to_ise, ServerState, SNAPSHOT_CONTENT_TYPE, VERSION_ID_HEADER,
};
use crate::server::get_snapshot;
use actix_web::{error, get, web, HttpRequest, HttpResponse, Result};
@ -20,15 +20,15 @@ pub(crate) async fn service(
) -> Result<HttpResponse> {
let mut txn = server_state.storage.txn().map_err(failure_to_ise)?;
let client_key = client_key_header(&req)?;
let client_id = client_id_header(&req)?;
let client = txn
.get_client(client_key)
.get_client(client_id)
.map_err(failure_to_ise)?
.ok_or_else(|| error::ErrorNotFound("no such client"))?;
if let Some((version_id, data)) =
get_snapshot(txn, &server_state.config, client_key, client).map_err(failure_to_ise)?
get_snapshot(txn, &server_state.config, client_id, client).map_err(failure_to_ise)?
{
Ok(HttpResponse::Ok()
.content_type(SNAPSHOT_CONTENT_TYPE)
@ -41,6 +41,7 @@ pub(crate) async fn service(
#[cfg(test)]
mod test {
use crate::api::CLIENT_ID_HEADER;
use crate::storage::{InMemoryStorage, Snapshot, Storage};
use crate::Server;
use actix_web::{http::StatusCode, test, App};
@ -50,13 +51,13 @@ mod test {
#[actix_rt::test]
async fn test_not_found() {
let client_key = Uuid::new_v4();
let client_id = Uuid::new_v4();
let storage: Box<dyn Storage> = Box::new(InMemoryStorage::new());
// set up the storage contents..
{
let mut txn = storage.txn().unwrap();
txn.new_client(client_key, Uuid::new_v4()).unwrap();
txn.new_client(client_id, Uuid::new_v4()).unwrap();
}
let server = Server::new(Default::default(), storage);
@ -66,7 +67,7 @@ mod test {
let uri = "/v1/client/snapshot";
let req = test::TestRequest::get()
.uri(uri)
.append_header(("X-Client-Key", client_key.to_string()))
.append_header((CLIENT_ID_HEADER, client_id.to_string()))
.to_request();
let resp = test::call_service(&mut app, req).await;
assert_eq!(resp.status(), StatusCode::NOT_FOUND);
@ -74,7 +75,7 @@ mod test {
#[actix_rt::test]
async fn test_success() {
let client_key = Uuid::new_v4();
let client_id = Uuid::new_v4();
let version_id = Uuid::new_v4();
let snapshot_data = vec![1, 2, 3, 4];
let storage: Box<dyn Storage> = Box::new(InMemoryStorage::new());
@ -82,9 +83,9 @@ mod test {
// set up the storage contents..
{
let mut txn = storage.txn().unwrap();
txn.new_client(client_key, Uuid::new_v4()).unwrap();
txn.new_client(client_id, Uuid::new_v4()).unwrap();
txn.set_snapshot(
client_key,
client_id,
Snapshot {
version_id,
versions_since: 3,
@ -102,7 +103,7 @@ mod test {
let uri = "/v1/client/snapshot";
let req = test::TestRequest::get()
.uri(uri)
.append_header(("X-Client-Key", client_key.to_string()))
.append_header((CLIENT_ID_HEADER, client_id.to_string()))
.to_request();
let resp = test::call_service(&mut app, req).await;
assert_eq!(resp.status(), StatusCode::OK);

View file

@ -1,4 +1,4 @@
use crate::server::ClientKey;
use crate::server::ClientId;
use crate::storage::Storage;
use crate::ServerConfig;
use actix_web::{error, http::StatusCode, web, HttpRequest, Result, Scope};
@ -18,8 +18,8 @@ pub(crate) const SNAPSHOT_CONTENT_TYPE: &str = "application/vnd.taskchampion.sna
/// The header name for version ID
pub(crate) const VERSION_ID_HEADER: &str = "X-Version-Id";
/// The header name for client key
pub(crate) const CLIENT_KEY_HEADER: &str = "X-Client-Key";
/// The header name for client id
pub(crate) const CLIENT_ID_HEADER: &str = "X-Client-Id";
/// The header name for parent version ID
pub(crate) const PARENT_VERSION_ID_HEADER: &str = "X-Parent-Version-Id";
@ -46,15 +46,15 @@ fn failure_to_ise(err: anyhow::Error) -> impl actix_web::ResponseError {
error::InternalError::new(err, StatusCode::INTERNAL_SERVER_ERROR)
}
/// Get the client key
fn client_key_header(req: &HttpRequest) -> Result<ClientKey> {
/// Get the client id
fn client_id_header(req: &HttpRequest) -> Result<ClientId> {
fn badrequest() -> error::Error {
error::ErrorBadRequest("bad x-client-id")
}
if let Some(client_key_hdr) = req.headers().get(CLIENT_KEY_HEADER) {
let client_key = client_key_hdr.to_str().map_err(|_| badrequest())?;
let client_key = ClientKey::parse_str(client_key).map_err(|_| badrequest())?;
Ok(client_key)
if let Some(client_id_hdr) = req.headers().get(CLIENT_ID_HEADER) {
let client_id = client_id_hdr.to_str().map_err(|_| badrequest())?;
let client_id = ClientId::parse_str(client_id).map_err(|_| badrequest())?;
Ok(client_id)
} else {
Err(badrequest())
}

View file

@ -14,7 +14,7 @@ pub const NIL_VERSION_ID: VersionId = Uuid::nil();
const SNAPSHOT_SEARCH_LEN: i32 = 5;
pub(crate) type HistorySegment = Vec<u8>;
pub(crate) type ClientKey = Uuid;
pub(crate) type ClientId = Uuid;
pub(crate) type VersionId = Uuid;
/// ServerConfig contains configuration parameters for the server.
@ -60,12 +60,12 @@ pub(crate) enum GetVersionResult {
pub(crate) fn get_child_version<'a>(
mut txn: Box<dyn StorageTxn + 'a>,
_config: &ServerConfig,
client_key: ClientKey,
client_id: ClientId,
client: Client,
parent_version_id: VersionId,
) -> anyhow::Result<GetVersionResult> {
// If a version with parentVersionId equal to the requested parentVersionId exists, it is returned.
if let Some(version) = txn.get_version_by_parent(client_key, parent_version_id)? {
if let Some(version) = txn.get_version_by_parent(client_id, parent_version_id)? {
return Ok(GetVersionResult::Success {
version_id: version.version_id,
parent_version_id: version.parent_version_id,
@ -86,7 +86,7 @@ pub(crate) fn get_child_version<'a>(
}
// If a version with versionId equal to the requested parentVersionId exists, the response is _not-found_ (the client is up-to-date)
if txn.get_version(client_key, parent_version_id)?.is_some() {
if txn.get_version(client_id, parent_version_id)?.is_some() {
return Ok(GetVersionResult::NotFound);
}
@ -144,14 +144,14 @@ impl SnapshotUrgency {
pub(crate) fn add_version<'a>(
mut txn: Box<dyn StorageTxn + 'a>,
config: &ServerConfig,
client_key: ClientKey,
client_id: ClientId,
client: Client,
parent_version_id: VersionId,
history_segment: HistorySegment,
) -> anyhow::Result<(AddVersionResult, SnapshotUrgency)> {
log::debug!(
"add_version(client_key: {}, parent_version_id: {})",
client_key,
"add_version(client_id: {}, parent_version_id: {})",
client_id,
parent_version_id,
);
@ -172,7 +172,7 @@ pub(crate) fn add_version<'a>(
);
// update the DB
txn.add_version(client_key, version_id, parent_version_id, history_segment)?;
txn.add_version(client_id, version_id, parent_version_id, history_segment)?;
txn.commit()?;
// calculate the urgency
@ -200,14 +200,14 @@ pub(crate) fn add_version<'a>(
pub(crate) fn add_snapshot<'a>(
mut txn: Box<dyn StorageTxn + 'a>,
_config: &ServerConfig,
client_key: ClientKey,
client_id: ClientId,
client: Client,
version_id: VersionId,
data: Vec<u8>,
) -> anyhow::Result<()> {
log::debug!(
"add_snapshot(client_key: {}, version_id: {})",
client_key,
"add_snapshot(client_id: {}, version_id: {})",
client_id,
version_id,
);
@ -254,7 +254,7 @@ pub(crate) fn add_snapshot<'a>(
}
// get the parent version ID
if let Some(parent) = txn.get_version(client_key, vid)? {
if let Some(parent) = txn.get_version(client_id, vid)? {
vid = parent.parent_version_id;
} else {
// this version does not exist; "this should not happen" but if it does,
@ -269,7 +269,7 @@ pub(crate) fn add_snapshot<'a>(
log::warn!("accepting snapshot for version {}", version_id);
txn.set_snapshot(
client_key,
client_id,
Snapshot {
version_id,
timestamp: Utc::now(),
@ -285,11 +285,11 @@ pub(crate) fn add_snapshot<'a>(
pub(crate) fn get_snapshot<'a>(
mut txn: Box<dyn StorageTxn + 'a>,
_config: &ServerConfig,
client_key: ClientKey,
client_id: ClientId,
client: Client,
) -> anyhow::Result<Option<(Uuid, Vec<u8>)>> {
Ok(if let Some(snap) = client.snapshot {
txn.get_snapshot_data(client_key, snap.version_id)?
txn.get_snapshot_data(client_id, snap.version_id)?
.map(|data| (snap.version_id, data))
} else {
None
@ -354,16 +354,16 @@ mod test {
let storage = InMemoryStorage::new();
let mut txn = storage.txn()?;
let client_key = Uuid::new_v4();
txn.new_client(client_key, NIL_VERSION_ID)?;
let client_id = Uuid::new_v4();
txn.new_client(client_id, NIL_VERSION_ID)?;
// when no snapshot exists, the first version is NotFound
let client = txn.get_client(client_key)?.unwrap();
let client = txn.get_client(client_id)?.unwrap();
assert_eq!(
get_child_version(
txn,
&ServerConfig::default(),
client_key,
client_id,
client,
NIL_VERSION_ID
)?,
@ -378,11 +378,11 @@ mod test {
let storage = InMemoryStorage::new();
let mut txn = storage.txn()?;
let client_key = Uuid::new_v4();
let client_id = Uuid::new_v4();
txn.new_client(client_key, Uuid::new_v4())?;
txn.new_client(client_id, Uuid::new_v4())?;
txn.set_snapshot(
client_key,
client_id,
Snapshot {
version_id: Uuid::new_v4(),
versions_since: 0,
@ -392,12 +392,12 @@ mod test {
)?;
// when a snapshot exists, the first version is GONE
let client = txn.get_client(client_key)?.unwrap();
let client = txn.get_client(client_id)?.unwrap();
assert_eq!(
get_child_version(
txn,
&ServerConfig::default(),
client_key,
client_id,
client,
NIL_VERSION_ID
)?,
@ -412,19 +412,19 @@ mod test {
let storage = InMemoryStorage::new();
let mut txn = storage.txn()?;
let client_key = Uuid::new_v4();
let client_id = Uuid::new_v4();
// add a parent version, but not the requested child version
let parent_version_id = Uuid::new_v4();
txn.new_client(client_key, parent_version_id)?;
txn.add_version(client_key, parent_version_id, NIL_VERSION_ID, vec![])?;
txn.new_client(client_id, parent_version_id)?;
txn.add_version(client_id, parent_version_id, NIL_VERSION_ID, vec![])?;
let client = txn.get_client(client_key)?.unwrap();
let client = txn.get_client(client_id)?.unwrap();
assert_eq!(
get_child_version(
txn,
&ServerConfig::default(),
client_key,
client_id,
client,
parent_version_id
)?,
@ -439,19 +439,19 @@ mod test {
let storage = InMemoryStorage::new();
let mut txn = storage.txn()?;
let client_key = Uuid::new_v4();
let client_id = Uuid::new_v4();
// make up a parent version id, but neither that version
// nor its child exists (presumed to have been deleted)
let parent_version_id = Uuid::new_v4();
txn.new_client(client_key, Uuid::new_v4())?;
txn.new_client(client_id, Uuid::new_v4())?;
let client = txn.get_client(client_key)?.unwrap();
let client = txn.get_client(client_id)?.unwrap();
assert_eq!(
get_child_version(
txn,
&ServerConfig::default(),
client_key,
client_id,
client,
parent_version_id
)?,
@ -466,25 +466,25 @@ mod test {
let storage = InMemoryStorage::new();
let mut txn = storage.txn()?;
let client_key = Uuid::new_v4();
let client_id = Uuid::new_v4();
let version_id = Uuid::new_v4();
let parent_version_id = Uuid::new_v4();
let history_segment = b"abcd".to_vec();
txn.new_client(client_key, version_id)?;
txn.new_client(client_id, version_id)?;
txn.add_version(
client_key,
client_id,
version_id,
parent_version_id,
history_segment.clone(),
)?;
let client = txn.get_client(client_key)?.unwrap();
let client = txn.get_client(client_id)?.unwrap();
assert_eq!(
get_child_version(
txn,
&ServerConfig::default(),
client_key,
client_id,
client,
parent_version_id
)?,
@ -506,24 +506,24 @@ mod test {
) -> anyhow::Result<(Uuid, Vec<Uuid>)> {
init_logging();
let mut txn = storage.txn()?;
let client_key = Uuid::new_v4();
let client_id = Uuid::new_v4();
let mut versions = vec![];
let mut version_id = Uuid::nil();
txn.new_client(client_key, Uuid::nil())?;
txn.new_client(client_id, Uuid::nil())?;
for vnum in 0..num_versions {
let parent_version_id = version_id;
version_id = Uuid::new_v4();
versions.push(version_id);
txn.add_version(
client_key,
client_id,
version_id,
parent_version_id,
vec![0, 0, vnum as u8],
)?;
if Some(vnum) == snapshot_version {
txn.set_snapshot(
client_key,
client_id,
Snapshot {
version_id,
versions_since: 0,
@ -534,13 +534,13 @@ mod test {
}
}
Ok((client_key, versions))
Ok((client_id, versions))
}
/// Utility function to check the results of an add_version call
fn av_success_check(
storage: &InMemoryStorage,
client_key: Uuid,
client_id: Uuid,
existing_versions: &[Uuid],
result: (AddVersionResult, SnapshotUrgency),
expected_history: Vec<u8>,
@ -554,11 +554,11 @@ mod test {
// verify that the storage was updated
let mut txn = storage.txn()?;
let client = txn.get_client(client_key)?.unwrap();
let client = txn.get_client(client_id)?.unwrap();
assert_eq!(client.latest_version_id, new_version_id);
let parent_version_id = existing_versions.last().cloned().unwrap_or_else(Uuid::nil);
let version = txn.get_version(client_key, new_version_id)?.unwrap();
let version = txn.get_version(client_id, new_version_id)?.unwrap();
assert_eq!(version.version_id, new_version_id);
assert_eq!(version.parent_version_id, parent_version_id);
assert_eq!(version.history_segment, expected_history);
@ -574,17 +574,17 @@ mod test {
#[test]
fn add_version_conflict() -> anyhow::Result<()> {
let storage = InMemoryStorage::new();
let (client_key, versions) = av_setup(&storage, 3, None, None)?;
let (client_id, versions) = av_setup(&storage, 3, None, None)?;
let mut txn = storage.txn()?;
let client = txn.get_client(client_key)?.unwrap();
let client = txn.get_client(client_id)?.unwrap();
// try to add a child of a version other than the latest
assert_eq!(
add_version(
txn,
&ServerConfig::default(),
client_key,
client_id,
client,
versions[1],
vec![3, 6, 9]
@ -596,10 +596,10 @@ mod test {
// verify that the storage wasn't updated
txn = storage.txn()?;
assert_eq!(
txn.get_client(client_key)?.unwrap().latest_version_id,
txn.get_client(client_id)?.unwrap().latest_version_id,
versions[2]
);
assert_eq!(txn.get_version_by_parent(client_key, versions[2])?, None);
assert_eq!(txn.get_version_by_parent(client_id, versions[2])?, None);
Ok(())
}
@ -607,15 +607,15 @@ mod test {
#[test]
fn add_version_with_existing_history() -> anyhow::Result<()> {
let storage = InMemoryStorage::new();
let (client_key, versions) = av_setup(&storage, 1, None, None)?;
let (client_id, versions) = av_setup(&storage, 1, None, None)?;
let mut txn = storage.txn()?;
let client = txn.get_client(client_key)?.unwrap();
let client = txn.get_client(client_id)?.unwrap();
let result = add_version(
txn,
&ServerConfig::default(),
client_key,
client_id,
client,
versions[0],
vec![3, 6, 9],
@ -623,7 +623,7 @@ mod test {
av_success_check(
&storage,
client_key,
client_id,
&versions,
result,
vec![3, 6, 9],
@ -637,16 +637,16 @@ mod test {
#[test]
fn add_version_with_no_history() -> anyhow::Result<()> {
let storage = InMemoryStorage::new();
let (client_key, versions) = av_setup(&storage, 0, None, None)?;
let (client_id, versions) = av_setup(&storage, 0, None, None)?;
let mut txn = storage.txn()?;
let client = txn.get_client(client_key)?.unwrap();
let client = txn.get_client(client_id)?.unwrap();
let parent_version_id = Uuid::nil();
let result = add_version(
txn,
&ServerConfig::default(),
client_key,
client_id,
client,
parent_version_id,
vec![3, 6, 9],
@ -654,7 +654,7 @@ mod test {
av_success_check(
&storage,
client_key,
client_id,
&versions,
result,
vec![3, 6, 9],
@ -668,15 +668,15 @@ mod test {
#[test]
fn add_version_success_recent_snapshot() -> anyhow::Result<()> {
let storage = InMemoryStorage::new();
let (client_key, versions) = av_setup(&storage, 1, Some(0), None)?;
let (client_id, versions) = av_setup(&storage, 1, Some(0), None)?;
let mut txn = storage.txn()?;
let client = txn.get_client(client_key)?.unwrap();
let client = txn.get_client(client_id)?.unwrap();
let result = add_version(
txn,
&ServerConfig::default(),
client_key,
client_id,
client,
versions[0],
vec![1, 2, 3],
@ -684,7 +684,7 @@ mod test {
av_success_check(
&storage,
client_key,
client_id,
&versions,
result,
vec![1, 2, 3],
@ -699,15 +699,15 @@ mod test {
fn add_version_success_aged_snapshot() -> anyhow::Result<()> {
let storage = InMemoryStorage::new();
// one snapshot, but it was 50 days ago
let (client_key, versions) = av_setup(&storage, 1, Some(0), Some(50))?;
let (client_id, versions) = av_setup(&storage, 1, Some(0), Some(50))?;
let mut txn = storage.txn()?;
let client = txn.get_client(client_key)?.unwrap();
let client = txn.get_client(client_id)?.unwrap();
let result = add_version(
txn,
&ServerConfig::default(),
client_key,
client_id,
client,
versions[0],
vec![1, 2, 3],
@ -715,7 +715,7 @@ mod test {
av_success_check(
&storage,
client_key,
client_id,
&versions,
result,
vec![1, 2, 3],
@ -730,10 +730,10 @@ mod test {
fn add_version_success_snapshot_many_versions_ago() -> anyhow::Result<()> {
let storage = InMemoryStorage::new();
// one snapshot, but it was 50 versions ago
let (client_key, versions) = av_setup(&storage, 50, Some(0), None)?;
let (client_id, versions) = av_setup(&storage, 50, Some(0), None)?;
let mut txn = storage.txn()?;
let client = txn.get_client(client_key)?.unwrap();
let client = txn.get_client(client_id)?.unwrap();
let result = add_version(
txn,
@ -741,7 +741,7 @@ mod test {
snapshot_versions: 30,
..ServerConfig::default()
},
client_key,
client_id,
client,
versions[49],
vec![1, 2, 3],
@ -749,7 +749,7 @@ mod test {
av_success_check(
&storage,
client_key,
client_id,
&versions,
result,
vec![1, 2, 3],
@ -766,19 +766,19 @@ mod test {
let storage = InMemoryStorage::new();
let mut txn = storage.txn()?;
let client_key = Uuid::new_v4();
let client_id = Uuid::new_v4();
let version_id = Uuid::new_v4();
// set up a task DB with one version in it
txn.new_client(client_key, version_id)?;
txn.add_version(client_key, version_id, NIL_VERSION_ID, vec![])?;
txn.new_client(client_id, version_id)?;
txn.add_version(client_id, version_id, NIL_VERSION_ID, vec![])?;
// add a snapshot for that version
let client = txn.get_client(client_key)?.unwrap();
let client = txn.get_client(client_id)?.unwrap();
add_snapshot(
txn,
&ServerConfig::default(),
client_key,
client_id,
client,
version_id,
vec![1, 2, 3],
@ -786,12 +786,12 @@ mod test {
// verify the snapshot
let mut txn = storage.txn()?;
let client = txn.get_client(client_key)?.unwrap();
let client = txn.get_client(client_id)?.unwrap();
let snapshot = client.snapshot.unwrap();
assert_eq!(snapshot.version_id, version_id);
assert_eq!(snapshot.versions_since, 0);
assert_eq!(
txn.get_snapshot_data(client_key, version_id).unwrap(),
txn.get_snapshot_data(client_id, version_id).unwrap(),
Some(vec![1, 2, 3])
);
@ -804,21 +804,21 @@ mod test {
let storage = InMemoryStorage::new();
let mut txn = storage.txn()?;
let client_key = Uuid::new_v4();
let client_id = Uuid::new_v4();
let version_id_1 = Uuid::new_v4();
let version_id_2 = Uuid::new_v4();
// set up a task DB with two versions in it
txn.new_client(client_key, version_id_2)?;
txn.add_version(client_key, version_id_1, NIL_VERSION_ID, vec![])?;
txn.add_version(client_key, version_id_2, version_id_1, vec![])?;
txn.new_client(client_id, version_id_2)?;
txn.add_version(client_id, version_id_1, NIL_VERSION_ID, vec![])?;
txn.add_version(client_id, version_id_2, version_id_1, vec![])?;
// add a snapshot for version 1
let client = txn.get_client(client_key)?.unwrap();
let client = txn.get_client(client_id)?.unwrap();
add_snapshot(
txn,
&ServerConfig::default(),
client_key,
client_id,
client,
version_id_1,
vec![1, 2, 3],
@ -826,12 +826,12 @@ mod test {
// verify the snapshot
let mut txn = storage.txn()?;
let client = txn.get_client(client_key)?.unwrap();
let client = txn.get_client(client_id)?.unwrap();
let snapshot = client.snapshot.unwrap();
assert_eq!(snapshot.version_id, version_id_1);
assert_eq!(snapshot.versions_since, 0);
assert_eq!(
txn.get_snapshot_data(client_key, version_id_1).unwrap(),
txn.get_snapshot_data(client_id, version_id_1).unwrap(),
Some(vec![1, 2, 3])
);
@ -844,22 +844,22 @@ mod test {
let storage = InMemoryStorage::new();
let mut txn = storage.txn()?;
let client_key = Uuid::new_v4();
let client_id = Uuid::new_v4();
let version_id_1 = Uuid::new_v4();
let version_id_2 = Uuid::new_v4();
// set up a task DB with two versions in it
txn.new_client(client_key, version_id_2)?;
txn.add_version(client_key, version_id_1, NIL_VERSION_ID, vec![])?;
txn.add_version(client_key, version_id_2, version_id_1, vec![])?;
txn.new_client(client_id, version_id_2)?;
txn.add_version(client_id, version_id_1, NIL_VERSION_ID, vec![])?;
txn.add_version(client_id, version_id_2, version_id_1, vec![])?;
// add a snapshot for unknown version
let client = txn.get_client(client_key)?.unwrap();
let client = txn.get_client(client_id)?.unwrap();
let version_id_unk = Uuid::new_v4();
add_snapshot(
txn,
&ServerConfig::default(),
client_key,
client_id,
client,
version_id_unk,
vec![1, 2, 3],
@ -867,7 +867,7 @@ mod test {
// verify the snapshot does not exist
let mut txn = storage.txn()?;
let client = txn.get_client(client_key)?.unwrap();
let client = txn.get_client(client_id)?.unwrap();
assert!(client.snapshot.is_none());
Ok(())
@ -879,26 +879,26 @@ mod test {
let storage = InMemoryStorage::new();
let mut txn = storage.txn()?;
let client_key = Uuid::new_v4();
let client_id = Uuid::new_v4();
let mut version_id = Uuid::new_v4();
let mut parent_version_id = Uuid::nil();
let mut version_ids = vec![];
// set up a task DB with 10 versions in it (oldest to newest)
txn.new_client(client_key, Uuid::nil())?;
txn.new_client(client_id, Uuid::nil())?;
for _ in 0..10 {
txn.add_version(client_key, version_id, parent_version_id, vec![])?;
txn.add_version(client_id, version_id, parent_version_id, vec![])?;
version_ids.push(version_id);
parent_version_id = version_id;
version_id = Uuid::new_v4();
}
// add a snapshot for the earliest of those
let client = txn.get_client(client_key)?.unwrap();
let client = txn.get_client(client_id)?.unwrap();
add_snapshot(
txn,
&ServerConfig::default(),
client_key,
client_id,
client,
version_ids[0],
vec![1, 2, 3],
@ -906,7 +906,7 @@ mod test {
// verify the snapshot does not exist
let mut txn = storage.txn()?;
let client = txn.get_client(client_key)?.unwrap();
let client = txn.get_client(client_id)?.unwrap();
assert!(client.snapshot.is_none());
Ok(())
@ -918,22 +918,22 @@ mod test {
let storage = InMemoryStorage::new();
let mut txn = storage.txn()?;
let client_key = Uuid::new_v4();
let client_id = Uuid::new_v4();
let mut version_id = Uuid::new_v4();
let mut parent_version_id = Uuid::nil();
let mut version_ids = vec![];
// set up a task DB with 5 versions in it (oldest to newest) and a snapshot of the middle
// one
txn.new_client(client_key, Uuid::nil())?;
txn.new_client(client_id, Uuid::nil())?;
for _ in 0..5 {
txn.add_version(client_key, version_id, parent_version_id, vec![])?;
txn.add_version(client_id, version_id, parent_version_id, vec![])?;
version_ids.push(version_id);
parent_version_id = version_id;
version_id = Uuid::new_v4();
}
txn.set_snapshot(
client_key,
client_id,
Snapshot {
version_id: version_ids[2],
versions_since: 2,
@ -943,11 +943,11 @@ mod test {
)?;
// add a snapshot for the earliest of those
let client = txn.get_client(client_key)?.unwrap();
let client = txn.get_client(client_id)?.unwrap();
add_snapshot(
txn,
&ServerConfig::default(),
client_key,
client_id,
client,
version_ids[0],
vec![9, 9, 9],
@ -955,12 +955,12 @@ mod test {
// verify the snapshot was not replaced
let mut txn = storage.txn()?;
let client = txn.get_client(client_key)?.unwrap();
let client = txn.get_client(client_id)?.unwrap();
let snapshot = client.snapshot.unwrap();
assert_eq!(snapshot.version_id, version_ids[2]);
assert_eq!(snapshot.versions_since, 2);
assert_eq!(
txn.get_snapshot_data(client_key, version_ids[2]).unwrap(),
txn.get_snapshot_data(client_id, version_ids[2]).unwrap(),
Some(vec![1, 2, 3])
);
@ -973,17 +973,17 @@ mod test {
let storage = InMemoryStorage::new();
let mut txn = storage.txn()?;
let client_key = Uuid::new_v4();
let client_id = Uuid::new_v4();
// just set up the client
txn.new_client(client_key, NIL_VERSION_ID)?;
txn.new_client(client_id, NIL_VERSION_ID)?;
// add a snapshot for the nil version
let client = txn.get_client(client_key)?.unwrap();
let client = txn.get_client(client_id)?.unwrap();
add_snapshot(
txn,
&ServerConfig::default(),
client_key,
client_id,
client,
NIL_VERSION_ID,
vec![9, 9, 9],
@ -991,7 +991,7 @@ mod test {
// verify the snapshot does not exist
let mut txn = storage.txn()?;
let client = txn.get_client(client_key)?.unwrap();
let client = txn.get_client(client_id)?.unwrap();
assert!(client.snapshot.is_none());
Ok(())
@ -1003,13 +1003,13 @@ mod test {
let storage = InMemoryStorage::new();
let mut txn = storage.txn()?;
let client_key = Uuid::new_v4();
let client_id = Uuid::new_v4();
let data = vec![1, 2, 3];
let snapshot_version_id = Uuid::new_v4();
txn.new_client(client_key, snapshot_version_id)?;
txn.new_client(client_id, snapshot_version_id)?;
txn.set_snapshot(
client_key,
client_id,
Snapshot {
version_id: snapshot_version_id,
versions_since: 3,
@ -1018,9 +1018,9 @@ mod test {
data.clone(),
)?;
let client = txn.get_client(client_key)?.unwrap();
let client = txn.get_client(client_id)?.unwrap();
assert_eq!(
get_snapshot(txn, &ServerConfig::default(), client_key, client)?,
get_snapshot(txn, &ServerConfig::default(), client_id, client)?,
Some((snapshot_version_id, data))
);
@ -1033,13 +1033,13 @@ mod test {
let storage = InMemoryStorage::new();
let mut txn = storage.txn()?;
let client_key = Uuid::new_v4();
let client_id = Uuid::new_v4();
txn.new_client(client_key, NIL_VERSION_ID)?;
let client = txn.get_client(client_key)?.unwrap();
txn.new_client(client_id, NIL_VERSION_ID)?;
let client = txn.get_client(client_id)?.unwrap();
assert_eq!(
get_snapshot(txn, &ServerConfig::default(), client_key, client)?,
get_snapshot(txn, &ServerConfig::default(), client_id, client)?,
None
);

View file

@ -3,16 +3,16 @@ use std::collections::HashMap;
use std::sync::{Mutex, MutexGuard};
struct Inner {
/// Clients, indexed by client_key
/// Clients, indexed by client_id
clients: HashMap<Uuid, Client>,
/// Snapshot data, indexed by client key
/// Snapshot data, indexed by client id
snapshots: HashMap<Uuid, Vec<u8>>,
/// Versions, indexed by (client_key, version_id)
/// Versions, indexed by (client_id, version_id)
versions: HashMap<(Uuid, Uuid), Version>,
/// Child versions, indexed by (client_key, parent_version_id)
/// Child versions, indexed by (client_id, parent_version_id)
children: HashMap<(Uuid, Uuid), Uuid>,
}
@ -42,16 +42,16 @@ impl Storage for InMemoryStorage {
}
impl<'a> StorageTxn for InnerTxn<'a> {
fn get_client(&mut self, client_key: Uuid) -> anyhow::Result<Option<Client>> {
Ok(self.0.clients.get(&client_key).cloned())
fn get_client(&mut self, client_id: Uuid) -> anyhow::Result<Option<Client>> {
Ok(self.0.clients.get(&client_id).cloned())
}
fn new_client(&mut self, client_key: Uuid, latest_version_id: Uuid) -> anyhow::Result<()> {
if self.0.clients.get(&client_key).is_some() {
return Err(anyhow::anyhow!("Client {} already exists", client_key));
fn new_client(&mut self, client_id: Uuid, latest_version_id: Uuid) -> anyhow::Result<()> {
if self.0.clients.get(&client_id).is_some() {
return Err(anyhow::anyhow!("Client {} already exists", client_id));
}
self.0.clients.insert(
client_key,
client_id,
Client {
latest_version_id,
snapshot: None,
@ -62,44 +62,44 @@ impl<'a> StorageTxn for InnerTxn<'a> {
fn set_snapshot(
&mut self,
client_key: Uuid,
client_id: Uuid,
snapshot: Snapshot,
data: Vec<u8>,
) -> anyhow::Result<()> {
let mut client = self
.0
.clients
.get_mut(&client_key)
.get_mut(&client_id)
.ok_or_else(|| anyhow::anyhow!("no such client"))?;
client.snapshot = Some(snapshot);
self.0.snapshots.insert(client_key, data);
self.0.snapshots.insert(client_id, data);
Ok(())
}
fn get_snapshot_data(
&mut self,
client_key: Uuid,
client_id: Uuid,
version_id: Uuid,
) -> anyhow::Result<Option<Vec<u8>>> {
// sanity check
let client = self.0.clients.get(&client_key);
let client = self.0.clients.get(&client_id);
let client = client.ok_or_else(|| anyhow::anyhow!("no such client"))?;
if Some(&version_id) != client.snapshot.as_ref().map(|snap| &snap.version_id) {
return Err(anyhow::anyhow!("unexpected snapshot_version_id"));
}
Ok(self.0.snapshots.get(&client_key).cloned())
Ok(self.0.snapshots.get(&client_id).cloned())
}
fn get_version_by_parent(
&mut self,
client_key: Uuid,
client_id: Uuid,
parent_version_id: Uuid,
) -> anyhow::Result<Option<Version>> {
if let Some(parent_version_id) = self.0.children.get(&(client_key, parent_version_id)) {
if let Some(parent_version_id) = self.0.children.get(&(client_id, parent_version_id)) {
Ok(self
.0
.versions
.get(&(client_key, *parent_version_id))
.get(&(client_id, *parent_version_id))
.cloned())
} else {
Ok(None)
@ -108,15 +108,15 @@ impl<'a> StorageTxn for InnerTxn<'a> {
fn get_version(
&mut self,
client_key: Uuid,
client_id: Uuid,
version_id: Uuid,
) -> anyhow::Result<Option<Version>> {
Ok(self.0.versions.get(&(client_key, version_id)).cloned())
Ok(self.0.versions.get(&(client_id, version_id)).cloned())
}
fn add_version(
&mut self,
client_key: Uuid,
client_id: Uuid,
version_id: Uuid,
parent_version_id: Uuid,
history_segment: Vec<u8>,
@ -128,19 +128,19 @@ impl<'a> StorageTxn for InnerTxn<'a> {
history_segment,
};
if let Some(client) = self.0.clients.get_mut(&client_key) {
if let Some(client) = self.0.clients.get_mut(&client_id) {
client.latest_version_id = version_id;
if let Some(ref mut snap) = client.snapshot {
snap.versions_since += 1;
}
} else {
return Err(anyhow::anyhow!("Client {} does not exist", client_key));
return Err(anyhow::anyhow!("Client {} does not exist", client_id));
}
self.0
.children
.insert((client_key, parent_version_id), version_id);
self.0.versions.insert((client_key, version_id), version);
.insert((client_id, parent_version_id), version_id);
self.0.versions.insert((client_id, version_id), version);
Ok(())
}
@ -169,18 +169,18 @@ mod test {
let storage = InMemoryStorage::new();
let mut txn = storage.txn()?;
let client_key = Uuid::new_v4();
let client_id = Uuid::new_v4();
let latest_version_id = Uuid::new_v4();
txn.new_client(client_key, latest_version_id)?;
txn.new_client(client_id, latest_version_id)?;
let client = txn.get_client(client_key)?.unwrap();
let client = txn.get_client(client_id)?.unwrap();
assert_eq!(client.latest_version_id, latest_version_id);
assert!(client.snapshot.is_none());
let latest_version_id = Uuid::new_v4();
txn.add_version(client_key, latest_version_id, Uuid::new_v4(), vec![1, 1])?;
txn.add_version(client_id, latest_version_id, Uuid::new_v4(), vec![1, 1])?;
let client = txn.get_client(client_key)?.unwrap();
let client = txn.get_client(client_id)?.unwrap();
assert_eq!(client.latest_version_id, latest_version_id);
assert!(client.snapshot.is_none());
@ -189,9 +189,9 @@ mod test {
timestamp: Utc::now(),
versions_since: 4,
};
txn.set_snapshot(client_key, snap.clone(), vec![1, 2, 3])?;
txn.set_snapshot(client_id, snap.clone(), vec![1, 2, 3])?;
let client = txn.get_client(client_key)?.unwrap();
let client = txn.get_client(client_id)?.unwrap();
assert_eq!(client.latest_version_id, latest_version_id);
assert_eq!(client.snapshot.unwrap(), snap);
@ -212,14 +212,14 @@ mod test {
let storage = InMemoryStorage::new();
let mut txn = storage.txn()?;
let client_key = Uuid::new_v4();
let client_id = Uuid::new_v4();
let version_id = Uuid::new_v4();
let parent_version_id = Uuid::new_v4();
let history_segment = b"abc".to_vec();
txn.new_client(client_key, parent_version_id)?;
txn.new_client(client_id, parent_version_id)?;
txn.add_version(
client_key,
client_id,
version_id,
parent_version_id,
history_segment.clone(),
@ -232,11 +232,11 @@ mod test {
};
let version = txn
.get_version_by_parent(client_key, parent_version_id)?
.get_version_by_parent(client_id, parent_version_id)?
.unwrap();
assert_eq!(version, expected);
let version = txn.get_version(client_key, version_id)?.unwrap();
let version = txn.get_version(client_id, version_id)?.unwrap();
assert_eq!(version, expected);
Ok(())
@ -247,40 +247,39 @@ mod test {
let storage = InMemoryStorage::new();
let mut txn = storage.txn()?;
let client_key = Uuid::new_v4();
let client_id = Uuid::new_v4();
txn.new_client(client_key, Uuid::new_v4())?;
assert!(txn.get_client(client_key)?.unwrap().snapshot.is_none());
txn.new_client(client_id, Uuid::new_v4())?;
assert!(txn.get_client(client_id)?.unwrap().snapshot.is_none());
let snap = Snapshot {
version_id: Uuid::new_v4(),
timestamp: Utc::now(),
versions_since: 3,
};
txn.set_snapshot(client_key, snap.clone(), vec![9, 8, 9])?;
txn.set_snapshot(client_id, snap.clone(), vec![9, 8, 9])?;
assert_eq!(
txn.get_snapshot_data(client_key, snap.version_id)?.unwrap(),
txn.get_snapshot_data(client_id, snap.version_id)?.unwrap(),
vec![9, 8, 9]
);
assert_eq!(txn.get_client(client_key)?.unwrap().snapshot, Some(snap));
assert_eq!(txn.get_client(client_id)?.unwrap().snapshot, Some(snap));
let snap2 = Snapshot {
version_id: Uuid::new_v4(),
timestamp: Utc::now(),
versions_since: 10,
};
txn.set_snapshot(client_key, snap2.clone(), vec![0, 2, 4, 6])?;
txn.set_snapshot(client_id, snap2.clone(), vec![0, 2, 4, 6])?;
assert_eq!(
txn.get_snapshot_data(client_key, snap2.version_id)?
.unwrap(),
txn.get_snapshot_data(client_id, snap2.version_id)?.unwrap(),
vec![0, 2, 4, 6]
);
assert_eq!(txn.get_client(client_key)?.unwrap().snapshot, Some(snap2));
assert_eq!(txn.get_client(client_id)?.unwrap().snapshot, Some(snap2));
// check that mismatched version is detected
assert!(txn.get_snapshot_data(client_key, Uuid::new_v4()).is_err());
assert!(txn.get_snapshot_data(client_id, Uuid::new_v4()).is_err());
Ok(())
}

View file

@ -39,15 +39,15 @@ pub struct Version {
pub trait StorageTxn {
/// Get information about the given client
fn get_client(&mut self, client_key: Uuid) -> anyhow::Result<Option<Client>>;
fn get_client(&mut self, client_id: Uuid) -> anyhow::Result<Option<Client>>;
/// Create a new client with the given latest_version_id
fn new_client(&mut self, client_key: Uuid, latest_version_id: Uuid) -> anyhow::Result<()>;
fn new_client(&mut self, client_id: Uuid, latest_version_id: Uuid) -> anyhow::Result<()>;
/// Set the client's most recent snapshot.
fn set_snapshot(
&mut self,
client_key: Uuid,
client_id: Uuid,
snapshot: Snapshot,
data: Vec<u8>,
) -> anyhow::Result<()>;
@ -56,30 +56,27 @@ pub trait StorageTxn {
/// is used to verify that the snapshot is for the correct version.
fn get_snapshot_data(
&mut self,
client_key: Uuid,
client_id: Uuid,
version_id: Uuid,
) -> anyhow::Result<Option<Vec<u8>>>;
/// Get a version, indexed by parent version id
fn get_version_by_parent(
&mut self,
client_key: Uuid,
client_id: Uuid,
parent_version_id: Uuid,
) -> anyhow::Result<Option<Version>>;
/// Get a version, indexed by its own version id
fn get_version(
&mut self,
client_key: Uuid,
version_id: Uuid,
) -> anyhow::Result<Option<Version>>;
fn get_version(&mut self, client_id: Uuid, version_id: Uuid)
-> anyhow::Result<Option<Version>>;
/// Add a version (that must not already exist), and
/// - update latest_version_id
/// - increment snapshot.versions_since
fn add_version(
&mut self,
client_key: Uuid,
client_id: Uuid,
version_id: Uuid,
parent_version_id: Uuid,
history_segment: Vec<u8>,

View file

@ -53,13 +53,13 @@ impl SqliteStorage {
let queries = vec![
"CREATE TABLE IF NOT EXISTS clients (
client_key STRING PRIMARY KEY,
client_id STRING PRIMARY KEY,
latest_version_id STRING,
snapshot_version_id STRING,
versions_since_snapshot INTEGER,
snapshot_timestamp INTEGER,
snapshot BLOB);",
"CREATE TABLE IF NOT EXISTS versions (version_id STRING PRIMARY KEY, client_key STRING, parent_version_id STRING, history_segment BLOB);",
"CREATE TABLE IF NOT EXISTS versions (version_id STRING PRIMARY KEY, client_id STRING, parent_version_id STRING, history_segment BLOB);",
"CREATE INDEX IF NOT EXISTS versions_by_parent ON versions (parent_version_id);",
];
for q in queries {
@ -96,14 +96,14 @@ impl Txn {
fn get_version_impl(
&mut self,
query: &'static str,
client_key: Uuid,
client_id: Uuid,
version_id_arg: Uuid,
) -> anyhow::Result<Option<Version>> {
let t = self.get_txn()?;
let r = t
.query_row(
query,
params![&StoredUuid(version_id_arg), &StoredUuid(client_key)],
params![&StoredUuid(version_id_arg), &StoredUuid(client_id)],
|r| {
let version_id: StoredUuid = r.get("version_id")?;
let parent_version_id: StoredUuid = r.get("parent_version_id")?;
@ -122,7 +122,7 @@ impl Txn {
}
impl StorageTxn for Txn {
fn get_client(&mut self, client_key: Uuid) -> anyhow::Result<Option<Client>> {
fn get_client(&mut self, client_id: Uuid) -> anyhow::Result<Option<Client>> {
let t = self.get_txn()?;
let result: Option<Client> = t
.query_row(
@ -132,9 +132,9 @@ impl StorageTxn for Txn {
versions_since_snapshot,
snapshot_version_id
FROM clients
WHERE client_key = ?
WHERE client_id = ?
LIMIT 1",
[&StoredUuid(client_key)],
[&StoredUuid(client_id)],
|r| {
let latest_version_id: StoredUuid = r.get(0)?;
let snapshot_timestamp: Option<i64> = r.get(1)?;
@ -166,12 +166,12 @@ impl StorageTxn for Txn {
Ok(result)
}
fn new_client(&mut self, client_key: Uuid, latest_version_id: Uuid) -> anyhow::Result<()> {
fn new_client(&mut self, client_id: Uuid, latest_version_id: Uuid) -> anyhow::Result<()> {
let t = self.get_txn()?;
t.execute(
"INSERT OR REPLACE INTO clients (client_key, latest_version_id) VALUES (?, ?)",
params![&StoredUuid(client_key), &StoredUuid(latest_version_id)],
"INSERT OR REPLACE INTO clients (client_id, latest_version_id) VALUES (?, ?)",
params![&StoredUuid(client_id), &StoredUuid(latest_version_id)],
)
.context("Error creating/updating client")?;
t.commit()?;
@ -180,7 +180,7 @@ impl StorageTxn for Txn {
fn set_snapshot(
&mut self,
client_key: Uuid,
client_id: Uuid,
snapshot: Snapshot,
data: Vec<u8>,
) -> anyhow::Result<()> {
@ -193,13 +193,13 @@ impl StorageTxn for Txn {
snapshot_timestamp = ?,
versions_since_snapshot = ?,
snapshot = ?
WHERE client_key = ?",
WHERE client_id = ?",
params![
&StoredUuid(snapshot.version_id),
snapshot.timestamp.timestamp(),
snapshot.versions_since,
data,
&StoredUuid(client_key),
&StoredUuid(client_id),
],
)
.context("Error creating/updating snapshot")?;
@ -209,14 +209,14 @@ impl StorageTxn for Txn {
fn get_snapshot_data(
&mut self,
client_key: Uuid,
client_id: Uuid,
version_id: Uuid,
) -> anyhow::Result<Option<Vec<u8>>> {
let t = self.get_txn()?;
let r = t
.query_row(
"SELECT snapshot, snapshot_version_id FROM clients WHERE client_key = ?",
params![&StoredUuid(client_key)],
"SELECT snapshot, snapshot_version_id FROM clients WHERE client_id = ?",
params![&StoredUuid(client_id)],
|r| {
let v: StoredUuid = r.get("snapshot_version_id")?;
let d: Vec<u8> = r.get("snapshot")?;
@ -237,29 +237,29 @@ impl StorageTxn for Txn {
fn get_version_by_parent(
&mut self,
client_key: Uuid,
client_id: Uuid,
parent_version_id: Uuid,
) -> anyhow::Result<Option<Version>> {
self.get_version_impl(
"SELECT version_id, parent_version_id, history_segment FROM versions WHERE parent_version_id = ? AND client_key = ?",
client_key,
"SELECT version_id, parent_version_id, history_segment FROM versions WHERE parent_version_id = ? AND client_id = ?",
client_id,
parent_version_id)
}
fn get_version(
&mut self,
client_key: Uuid,
client_id: Uuid,
version_id: Uuid,
) -> anyhow::Result<Option<Version>> {
self.get_version_impl(
"SELECT version_id, parent_version_id, history_segment FROM versions WHERE version_id = ? AND client_key = ?",
client_key,
"SELECT version_id, parent_version_id, history_segment FROM versions WHERE version_id = ? AND client_id = ?",
client_id,
version_id)
}
fn add_version(
&mut self,
client_key: Uuid,
client_id: Uuid,
version_id: Uuid,
parent_version_id: Uuid,
history_segment: Vec<u8>,
@ -267,10 +267,10 @@ impl StorageTxn for Txn {
let t = self.get_txn()?;
t.execute(
"INSERT INTO versions (version_id, client_key, parent_version_id, history_segment) VALUES(?, ?, ?, ?)",
"INSERT INTO versions (version_id, client_id, parent_version_id, history_segment) VALUES(?, ?, ?, ?)",
params![
StoredUuid(version_id),
StoredUuid(client_key),
StoredUuid(client_id),
StoredUuid(parent_version_id),
history_segment
]
@ -281,8 +281,8 @@ impl StorageTxn for Txn {
SET
latest_version_id = ?,
versions_since_snapshot = versions_since_snapshot + 1
WHERE client_key = ?",
params![StoredUuid(version_id), StoredUuid(client_key),],
WHERE client_id = ?",
params![StoredUuid(version_id), StoredUuid(client_id),],
)
.context("Error updating client for new version")?;
@ -333,18 +333,18 @@ mod test {
let storage = SqliteStorage::new(tmp_dir.path())?;
let mut txn = storage.txn()?;
let client_key = Uuid::new_v4();
let client_id = Uuid::new_v4();
let latest_version_id = Uuid::new_v4();
txn.new_client(client_key, latest_version_id)?;
txn.new_client(client_id, latest_version_id)?;
let client = txn.get_client(client_key)?.unwrap();
let client = txn.get_client(client_id)?.unwrap();
assert_eq!(client.latest_version_id, latest_version_id);
assert!(client.snapshot.is_none());
let latest_version_id = Uuid::new_v4();
txn.add_version(client_key, latest_version_id, Uuid::new_v4(), vec![1, 1])?;
txn.add_version(client_id, latest_version_id, Uuid::new_v4(), vec![1, 1])?;
let client = txn.get_client(client_key)?.unwrap();
let client = txn.get_client(client_id)?.unwrap();
assert_eq!(client.latest_version_id, latest_version_id);
assert!(client.snapshot.is_none());
@ -353,9 +353,9 @@ mod test {
timestamp: "2014-11-28T12:00:09Z".parse::<DateTime<Utc>>().unwrap(),
versions_since: 4,
};
txn.set_snapshot(client_key, snap.clone(), vec![1, 2, 3])?;
txn.set_snapshot(client_id, snap.clone(), vec![1, 2, 3])?;
let client = txn.get_client(client_key)?.unwrap();
let client = txn.get_client(client_id)?.unwrap();
assert_eq!(client.latest_version_id, latest_version_id);
assert_eq!(client.snapshot.unwrap(), snap);
@ -378,12 +378,12 @@ mod test {
let storage = SqliteStorage::new(tmp_dir.path())?;
let mut txn = storage.txn()?;
let client_key = Uuid::new_v4();
let client_id = Uuid::new_v4();
let version_id = Uuid::new_v4();
let parent_version_id = Uuid::new_v4();
let history_segment = b"abc".to_vec();
txn.add_version(
client_key,
client_id,
version_id,
parent_version_id,
history_segment.clone(),
@ -396,11 +396,11 @@ mod test {
};
let version = txn
.get_version_by_parent(client_key, parent_version_id)?
.get_version_by_parent(client_id, parent_version_id)?
.unwrap();
assert_eq!(version, expected);
let version = txn.get_version(client_key, version_id)?.unwrap();
let version = txn.get_version(client_id, version_id)?.unwrap();
assert_eq!(version, expected);
Ok(())
@ -412,40 +412,39 @@ mod test {
let storage = SqliteStorage::new(tmp_dir.path())?;
let mut txn = storage.txn()?;
let client_key = Uuid::new_v4();
let client_id = Uuid::new_v4();
txn.new_client(client_key, Uuid::new_v4())?;
assert!(txn.get_client(client_key)?.unwrap().snapshot.is_none());
txn.new_client(client_id, Uuid::new_v4())?;
assert!(txn.get_client(client_id)?.unwrap().snapshot.is_none());
let snap = Snapshot {
version_id: Uuid::new_v4(),
timestamp: "2013-10-08T12:00:09Z".parse::<DateTime<Utc>>().unwrap(),
versions_since: 3,
};
txn.set_snapshot(client_key, snap.clone(), vec![9, 8, 9])?;
txn.set_snapshot(client_id, snap.clone(), vec![9, 8, 9])?;
assert_eq!(
txn.get_snapshot_data(client_key, snap.version_id)?.unwrap(),
txn.get_snapshot_data(client_id, snap.version_id)?.unwrap(),
vec![9, 8, 9]
);
assert_eq!(txn.get_client(client_key)?.unwrap().snapshot, Some(snap));
assert_eq!(txn.get_client(client_id)?.unwrap().snapshot, Some(snap));
let snap2 = Snapshot {
version_id: Uuid::new_v4(),
timestamp: "2014-11-28T12:00:09Z".parse::<DateTime<Utc>>().unwrap(),
versions_since: 10,
};
txn.set_snapshot(client_key, snap2.clone(), vec![0, 2, 4, 6])?;
txn.set_snapshot(client_id, snap2.clone(), vec![0, 2, 4, 6])?;
assert_eq!(
txn.get_snapshot_data(client_key, snap2.version_id)?
.unwrap(),
txn.get_snapshot_data(client_id, snap2.version_id)?.unwrap(),
vec![0, 2, 4, 6]
);
assert_eq!(txn.get_client(client_key)?.unwrap().snapshot, Some(snap2));
assert_eq!(txn.get_client(client_id)?.unwrap().snapshot, Some(snap2));
// check that mismatched version is detected
assert!(txn.get_snapshot_data(client_key, Uuid::new_v4()).is_err());
assert!(txn.get_snapshot_data(client_id, Uuid::new_v4()).is_err());
Ok(())
}

View file

@ -16,8 +16,8 @@ pub enum ServerConfig {
/// Sync server "origin"; a URL with schema and hostname but no path or trailing `/`
origin: String,
/// Client Key to identify and authenticate this replica to the server
client_key: Uuid,
/// Client ID to identify and authenticate this replica to the server
client_id: Uuid,
/// Private encryption secret used to encrypt all data sent to the server. This can
/// be any suitably un-guessable string of bytes.
@ -32,9 +32,9 @@ impl ServerConfig {
ServerConfig::Local { server_dir } => Box::new(LocalServer::new(server_dir)?),
ServerConfig::Remote {
origin,
client_key,
client_id,
encryption_secret,
} => Box::new(RemoteServer::new(origin, client_key, encryption_secret)?),
} => Box::new(RemoteServer::new(origin, client_id, encryption_secret)?),
})
}
}

View file

@ -12,23 +12,23 @@ const TASK_APP_ID: u8 = 1;
/// An Cryptor stores a secret and allows sealing and unsealing. It derives a key from the secret,
/// which takes a nontrivial amount of time, so it should be created once and re-used for the given
/// client_key.
/// client_id.
pub(super) struct Cryptor {
key: aead::LessSafeKey,
rng: rand::SystemRandom,
}
impl Cryptor {
pub(super) fn new(client_key: Uuid, secret: &Secret) -> Result<Self> {
pub(super) fn new(client_id: Uuid, secret: &Secret) -> Result<Self> {
Ok(Cryptor {
key: Self::derive_key(client_key, secret)?,
key: Self::derive_key(client_id, secret)?,
rng: rand::SystemRandom::new(),
})
}
/// Derive a key as specified for version 1. Note that this may take 10s of ms.
fn derive_key(client_key: Uuid, secret: &Secret) -> Result<aead::LessSafeKey> {
let salt = digest::digest(&digest::SHA256, client_key.as_bytes());
fn derive_key(client_id: Uuid, secret: &Secret) -> Result<aead::LessSafeKey> {
let salt = digest::digest(&digest::SHA256, client_id.as_bytes());
let mut key_bytes = vec![0u8; aead::CHACHA20_POLY1305.key_len()];
pbkdf2::derive(
@ -268,10 +268,10 @@ mod test {
fn round_trip_bad_key() {
let version_id = Uuid::new_v4();
let payload = b"HISTORY REPEATS ITSELF".to_vec();
let client_key = Uuid::new_v4();
let client_id = Uuid::new_v4();
let secret = Secret(b"SEKRIT".to_vec());
let cryptor = Cryptor::new(client_key, &secret).unwrap();
let cryptor = Cryptor::new(client_id, &secret).unwrap();
let unsealed = Unsealed {
version_id,
@ -280,7 +280,7 @@ mod test {
let sealed = cryptor.seal(unsealed).unwrap();
let secret = Secret(b"DIFFERENT_SECRET".to_vec());
let cryptor = Cryptor::new(client_key, &secret).unwrap();
let cryptor = Cryptor::new(client_id, &secret).unwrap();
assert!(cryptor.unseal(sealed).is_err());
}
@ -288,10 +288,10 @@ mod test {
fn round_trip_bad_version() {
let version_id = Uuid::new_v4();
let payload = b"HISTORY REPEATS ITSELF".to_vec();
let client_key = Uuid::new_v4();
let client_id = Uuid::new_v4();
let secret = Secret(b"SEKRIT".to_vec());
let cryptor = Cryptor::new(client_key, &secret).unwrap();
let cryptor = Cryptor::new(client_id, &secret).unwrap();
let unsealed = Unsealed {
version_id,
@ -303,13 +303,13 @@ mod test {
}
#[test]
fn round_trip_bad_client_key() {
fn round_trip_bad_client_id() {
let version_id = Uuid::new_v4();
let payload = b"HISTORY REPEATS ITSELF".to_vec();
let client_key = Uuid::new_v4();
let client_id = Uuid::new_v4();
let secret = Secret(b"SEKRIT".to_vec());
let cryptor = Cryptor::new(client_key, &secret).unwrap();
let cryptor = Cryptor::new(client_id, &secret).unwrap();
let unsealed = Unsealed {
version_id,
@ -317,8 +317,8 @@ mod test {
};
let sealed = cryptor.seal(unsealed).unwrap();
let client_key = Uuid::new_v4();
let cryptor = Cryptor::new(client_key, &secret).unwrap();
let client_id = Uuid::new_v4();
let cryptor = Cryptor::new(client_id, &secret).unwrap();
assert!(cryptor.unseal(sealed).is_err());
}
@ -340,13 +340,13 @@ mod test {
#[test]
fn good() {
let (version_id, client_key, encryption_secret) = defaults();
let (version_id, client_id, encryption_secret) = defaults();
let sealed = Sealed {
version_id,
payload: include_bytes!("test-good.data").to_vec(),
};
let cryptor = Cryptor::new(client_key, &Secret(encryption_secret)).unwrap();
let cryptor = Cryptor::new(client_id, &Secret(encryption_secret)).unwrap();
let unsealed = cryptor.unseal(sealed).unwrap();
assert_eq!(unsealed.payload, b"SUCCESS");
@ -355,61 +355,61 @@ mod test {
#[test]
fn bad_version_id() {
let (version_id, client_key, encryption_secret) = defaults();
let (version_id, client_id, encryption_secret) = defaults();
let sealed = Sealed {
version_id,
payload: include_bytes!("test-bad-version-id.data").to_vec(),
};
let cryptor = Cryptor::new(client_key, &Secret(encryption_secret)).unwrap();
let cryptor = Cryptor::new(client_id, &Secret(encryption_secret)).unwrap();
assert!(cryptor.unseal(sealed).is_err());
}
#[test]
fn bad_client_key() {
let (version_id, client_key, encryption_secret) = defaults();
fn bad_client_id() {
let (version_id, client_id, encryption_secret) = defaults();
let sealed = Sealed {
version_id,
payload: include_bytes!("test-bad-client-key.data").to_vec(),
payload: include_bytes!("test-bad-client-id.data").to_vec(),
};
let cryptor = Cryptor::new(client_key, &Secret(encryption_secret)).unwrap();
let cryptor = Cryptor::new(client_id, &Secret(encryption_secret)).unwrap();
assert!(cryptor.unseal(sealed).is_err());
}
#[test]
fn bad_secret() {
let (version_id, client_key, encryption_secret) = defaults();
let (version_id, client_id, encryption_secret) = defaults();
let sealed = Sealed {
version_id,
payload: include_bytes!("test-bad-secret.data").to_vec(),
};
let cryptor = Cryptor::new(client_key, &Secret(encryption_secret)).unwrap();
let cryptor = Cryptor::new(client_id, &Secret(encryption_secret)).unwrap();
assert!(cryptor.unseal(sealed).is_err());
}
#[test]
fn bad_version() {
let (version_id, client_key, encryption_secret) = defaults();
let (version_id, client_id, encryption_secret) = defaults();
let sealed = Sealed {
version_id,
payload: include_bytes!("test-bad-version.data").to_vec(),
};
let cryptor = Cryptor::new(client_key, &Secret(encryption_secret)).unwrap();
let cryptor = Cryptor::new(client_id, &Secret(encryption_secret)).unwrap();
assert!(cryptor.unseal(sealed).is_err());
}
#[test]
fn bad_app_id() {
let (version_id, client_key, encryption_secret) = defaults();
let (version_id, client_id, encryption_secret) = defaults();
let sealed = Sealed {
version_id,
payload: include_bytes!("test-bad-app-id.data").to_vec(),
};
let cryptor = Cryptor::new(client_key, &Secret(encryption_secret)).unwrap();
let cryptor = Cryptor::new(client_id, &Secret(encryption_secret)).unwrap();
assert!(cryptor.unseal(sealed).is_err());
}
}

View file

@ -1,6 +1,6 @@
# This file generates test-encrypted.data. To run it:
# - pip install cryptography pbkdf2
# - python taskchampion/src/server/generate-test-data.py taskchampion/src/server/
# - python taskchampion/taskchampion/src/server/generate-test-data.py taskchampion/taskchampion/src/server/
import os
import hashlib
@ -12,15 +12,15 @@ import uuid
from cryptography.hazmat.primitives.ciphers.aead import ChaCha20Poly1305
# these values match values used in the rust tests
client_key = "0666d464-418a-4a08-ad53-6f15c78270cd"
client_id = "0666d464-418a-4a08-ad53-6f15c78270cd"
encryption_secret = b"b4a4e6b7b811eda1dc1a2693ded"
version_id = "b0517957-f912-4d49-8330-f612e73030c4"
def gen(
version_id=version_id, client_key=client_key, encryption_secret=encryption_secret,
version_id=version_id, client_id=client_id, encryption_secret=encryption_secret,
app_id=1, version=1):
# first, generate the encryption key
salt = hashlib.sha256(uuid.UUID(client_key).bytes).digest()
salt = hashlib.sha256(uuid.UUID(client_id).bytes).digest()
key = pbkdf2.PBKDF2(
encryption_secret,
salt,
@ -61,8 +61,8 @@ def main():
with open(os.path.join(dir, 'test-bad-version-id.data'), "wb") as f:
f.write(gen(version_id=uuid.uuid4().hex))
with open(os.path.join(dir, 'test-bad-client-key.data'), "wb") as f:
f.write(gen(client_key=uuid.uuid4().hex))
with open(os.path.join(dir, 'test-bad-client-id.data'), "wb") as f:
f.write(gen(client_id=uuid.uuid4().hex))
with open(os.path.join(dir, 'test-bad-secret.data'), "wb") as f:
f.write(gen(encryption_secret=b"xxxxxxxxxxxxxxxxxxxxx"))

View file

@ -10,7 +10,7 @@ use super::crypto::{Cryptor, Sealed, Secret, Unsealed};
pub struct RemoteServer {
origin: String,
client_key: Uuid,
client_id: Uuid,
cryptor: Cryptor,
agent: ureq::Agent,
}
@ -25,18 +25,18 @@ const SNAPSHOT_CONTENT_TYPE: &str = "application/vnd.taskchampion.snapshot";
/// taskchampion-sync-server).
impl RemoteServer {
/// 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_key to
/// without a trailing slash, such as `https://tcsync.example.com`. Pass a client_id to
/// identify this client to the server. Multiple replicas synchronizing the same task history
/// should use the same client_key.
/// should use the same client_id.
pub fn new(
origin: String,
client_key: Uuid,
client_id: Uuid,
encryption_secret: Vec<u8>,
) -> Result<RemoteServer> {
Ok(RemoteServer {
origin,
client_key,
cryptor: Cryptor::new(client_key, &Secret(encryption_secret.to_vec()))?,
client_id,
cryptor: Cryptor::new(client_id, &Secret(encryption_secret.to_vec()))?,
agent: ureq::AgentBuilder::new()
.timeout_connect(Duration::from_secs(10))
.timeout_read(Duration::from_secs(60))
@ -86,7 +86,7 @@ impl Server for RemoteServer {
.agent
.post(&url)
.set("Content-Type", HISTORY_SEGMENT_CONTENT_TYPE)
.set("X-Client-Key", &self.client_key.to_string())
.set("X-Client-Id", &self.client_id.to_string())
.send_bytes(sealed.as_ref())
{
Ok(resp) => {
@ -115,7 +115,7 @@ impl Server for RemoteServer {
match self
.agent
.get(&url)
.set("X-Client-Key", &self.client_key.to_string())
.set("X-Client-Id", &self.client_id.to_string())
.call()
{
Ok(resp) => {
@ -148,7 +148,7 @@ impl Server for RemoteServer {
.agent
.post(&url)
.set("Content-Type", SNAPSHOT_CONTENT_TYPE)
.set("X-Client-Key", &self.client_key.to_string())
.set("X-Client-Id", &self.client_id.to_string())
.send_bytes(sealed.as_ref())
.map(|_| ())?)
}
@ -158,7 +158,7 @@ impl Server for RemoteServer {
match self
.agent
.get(&url)
.set("X-Client-Key", &self.client_key.to_string())
.set("X-Client-Id", &self.client_id.to_string())
.call()
{
Ok(resp) => {

View file

@ -1,2 +1,2 @@
$á­
†—Õ^~B>n)ji†¯1—î9™|µœÓ~
®
<EFBFBD>KŸo]æâʶ£McØ\ï©QL)cHÂ;ÚÎ-:

View file

@ -0,0 +1 @@
%<25>ۇ <09><07><>ko¹<6F>{hكb<D983><62><EFBFBD> <0A><><02>ˁLqU

View file

@ -1 +0,0 @@
<01>ΝA4φ―Γθ t;Δτ υηp¦Ο¦x^Αύreό…<CF8C>JΤ¤<CEA4>

View file

@ -1 +1 @@
/}åd E°‡dIcÁXéè-‡!V°Û%è4îáòd]³ÃÇ}
κsq<0F><>nΟΐk,Βν\ ‹ΕΘ;W¶< ΄' ®Ξ;{Π<>

View file

@ -1 +1 @@
lΰζδa|ο@Ο<>S_¬…γzέV9£q¦Ρ…‘)+¦
 ñ±ŸâajJšÕ·ïq\IF4¤ó<C2A4>Y Ó£ €$‹ë9W

View file

@ -1 +1 @@
c╤TH╗Гp>╔╚Ф╨╕m4О╧к~в1╣0P░IЖ╢W╒
cR<06>ùË|¯|iKoÌÀ˜³+t•U|Ñ™ï˜&  —BIŸ·3

View file

@ -1 +1 @@
pÑ¿µÒŸ½V²ûÝäToë"}cT·äY7Æ ˆÀ@ÙdLTý`Ò
PRÇojÇ—Þgs²&vMØYÔn<>œ?ƒÛcå¼~:œ