Issue #17, add AUTHOR column to Rhizome MANIFESTS table

Replace ".selfsigned" column with ".author" and ".fromhere" columns in
output of "rhizome list" command.  (Note that a "sender" column is
already present.)

Add 'author' field to struct rhizome_manifest.

Log all fully rendered SQL statements on DEBUG_RHIZOME.

Update 'rhizomeops' test cases and improve the assert_rhizome_list()
test function to be able to assert authorship of files.
This commit is contained in:
Andrew Bettison 2012-10-09 17:43:34 +10:30
parent 1de2bc8f23
commit 3678522872
8 changed files with 138 additions and 73 deletions

View File

@ -1030,8 +1030,13 @@ int app_rhizome_add_file(int argc, const char *const *argv, struct command_line_
} }
/* Bind an ID to the manifest, and also bind the file. Then finalise the manifest. /* Bind an ID to the manifest, and also bind the file. Then finalise the manifest.
But if the manifest already contains an ID, don't override it. */ But if the manifest already contains an ID, don't override it. */
if (authorSidHex[0]) {
if (debug & DEBUG_RHIZOME)
DEBUGF("author=%s", authorSidHex);
memcpy(m->author, authorSid, SID_SIZE);
}
if (rhizome_manifest_get(m, "id", NULL, 0) == NULL) { if (rhizome_manifest_get(m, "id", NULL, 0) == NULL) {
if (rhizome_manifest_bind_id(m, authorSidHex[0] ? authorSid : NULL)) { if (rhizome_manifest_bind_id(m) == -1) {
rhizome_manifest_free(m); rhizome_manifest_free(m);
m = NULL; m = NULL;
return WHY("Could not bind manifest to an ID"); return WHY("Could not bind manifest to an ID");
@ -1067,12 +1072,9 @@ int app_rhizome_add_file(int argc, const char *const *argv, struct command_line_
PGS @20121003 - Hang on, didn't we create the ID above? Presumably the PGS @20121003 - Hang on, didn't we create the ID above? Presumably the
following does NOT in fact generate a bundle ID. following does NOT in fact generate a bundle ID.
*/ */
rhizome_manifest *mout = NULL;
if (debug & DEBUG_RHIZOME) DEBUGF("rhizome_add_manifest(author='%s')", authorSidHex);
int ret=0; int ret=0;
if (rhizome_manifest_check_duplicate(m,&mout)==2) rhizome_manifest *mout = NULL;
{ if (rhizome_manifest_check_duplicate(m, &mout) == 2) {
/* duplicate found -- verify it so that we can write it out later */ /* duplicate found -- verify it so that we can write it out later */
rhizome_manifest_verify(mout); rhizome_manifest_verify(mout);
ret=2; ret=2;
@ -1082,7 +1084,7 @@ int app_rhizome_add_file(int argc, const char *const *argv, struct command_line_
rhizome_manifest_free(m); rhizome_manifest_free(m);
return WHY("Could not finalise manifest"); return WHY("Could not finalise manifest");
} }
if (rhizome_add_manifest(m,255 /* TTL */)) { if (rhizome_add_manifest(m, 255 /* TTL */)) {
rhizome_manifest_free(m); rhizome_manifest_free(m);
return WHY("Manifest not added to Rhizome database"); return WHY("Manifest not added to Rhizome database");
} }
@ -1090,7 +1092,7 @@ int app_rhizome_add_file(int argc, const char *const *argv, struct command_line_
/* If successfully added, overwrite the manifest file so that the Java component that is /* If successfully added, overwrite the manifest file so that the Java component that is
invoking this command can read it to obtain feedback on the result. */ invoking this command can read it to obtain feedback on the result. */
rhizome_manifest *mwritten=mout?mout:m; rhizome_manifest *mwritten = mout ? mout : m;
if (manifestpath[0] if (manifestpath[0]
&& rhizome_write_manifest_file(mwritten, manifestpath) == -1) && rhizome_write_manifest_file(mwritten, manifestpath) == -1)
ret = WHY("Could not overwrite manifest file."); ret = WHY("Could not overwrite manifest file.");

View File

@ -149,9 +149,10 @@ int rhizome_manifest_check_sanity(rhizome_manifest *m_in)
by joining the parent group. by joining the parent group.
*/ */
int rhizome_manifest_bind_id(rhizome_manifest *m_in, const unsigned char *authorSid) int rhizome_manifest_bind_id(rhizome_manifest *m_in)
{ {
rhizome_manifest_createid(m_in); if (rhizome_manifest_createid(m_in) == -1)
return -1;
/* The ID is implicit in transit, but we need to store it in the file, so that reimporting /* The ID is implicit in transit, but we need to store it in the file, so that reimporting
manifests on receiver nodes works easily. We might implement something that strips the id manifests on receiver nodes works easily. We might implement something that strips the id
variable out of the manifest when sending it, or some other scheme to avoid sending all the variable out of the manifest when sending it, or some other scheme to avoid sending all the
@ -159,7 +160,7 @@ int rhizome_manifest_bind_id(rhizome_manifest *m_in, const unsigned char *author
char id[RHIZOME_MANIFEST_ID_STRLEN + 1]; char id[RHIZOME_MANIFEST_ID_STRLEN + 1];
rhizome_bytes_to_hex_upper(m_in->cryptoSignPublic, id, RHIZOME_MANIFEST_ID_BYTES); rhizome_bytes_to_hex_upper(m_in->cryptoSignPublic, id, RHIZOME_MANIFEST_ID_BYTES);
rhizome_manifest_set(m_in, "id", id); rhizome_manifest_set(m_in, "id", id);
if (authorSid) { if (!is_sid_any(m_in->author)) {
/* Set the BK using the provided authorship information. /* Set the BK using the provided authorship information.
Serval Security Framework defines BK as being: Serval Security Framework defines BK as being:
BK = privateKey XOR sha512(RS##BID), where BID = cryptoSignPublic, BK = privateKey XOR sha512(RS##BID), where BID = cryptoSignPublic,
@ -168,7 +169,7 @@ int rhizome_manifest_bind_id(rhizome_manifest *m_in, const unsigned char *author
privateKey = BK XOR sha512(RS##BID), so the same function can be used privateKey = BK XOR sha512(RS##BID), so the same function can be used
to encrypt and decrypt the BK field. */ to encrypt and decrypt the BK field. */
unsigned char bkbytes[RHIZOME_BUNDLE_KEY_BYTES]; unsigned char bkbytes[RHIZOME_BUNDLE_KEY_BYTES];
if (rhizome_bk_xor(authorSid, m_in->cryptoSignPublic, m_in->cryptoSignSecret, bkbytes) == 0) { if (rhizome_bk_xor(m_in->author, m_in->cryptoSignPublic, m_in->cryptoSignSecret, bkbytes) == 0) {
char bkhex[RHIZOME_BUNDLE_KEY_STRLEN + 1]; char bkhex[RHIZOME_BUNDLE_KEY_STRLEN + 1];
(void) tohex(bkhex, bkbytes, RHIZOME_BUNDLE_KEY_BYTES); (void) tohex(bkhex, bkbytes, RHIZOME_BUNDLE_KEY_BYTES);
if (debug&DEBUG_RHIZOME) DEBUGF("set BK=%s", bkhex); if (debug&DEBUG_RHIZOME) DEBUGF("set BK=%s", bkhex);
@ -281,7 +282,7 @@ int rhizome_manifest_check_file(rhizome_manifest *m_in)
/* Check if a manifest is already stored for the same payload with the same details. /* Check if a manifest is already stored for the same payload with the same details.
This catches the case of "dna rhizome add file <filename>" on the same file more than once. This catches the case of "dna rhizome add file <filename>" on the same file more than once.
(Debounce!) */ (Debounce!) */
int rhizome_manifest_check_duplicate(rhizome_manifest *m_in,rhizome_manifest **m_out) int rhizome_manifest_check_duplicate(rhizome_manifest *m_in, rhizome_manifest **m_out)
{ {
if (debug & DEBUG_RHIZOME) DEBUG("Checking for duplicate"); if (debug & DEBUG_RHIZOME) DEBUG("Checking for duplicate");
if (m_out) *m_out = NULL; if (m_out) *m_out = NULL;

View File

@ -132,6 +132,11 @@ typedef struct rhizome_manifest {
int group_count; int group_count;
char *groups[MAX_MANIFEST_VARS]; char *groups[MAX_MANIFEST_VARS];
/* Author of the manifest. A reference to a local keyring entry. Manifests
* not authored locally will have the ANY author (all zeros).
*/
unsigned char author[SID_SIZE];
} rhizome_manifest; } rhizome_manifest;
/* Supported service identifiers. These go in the 'service' field of every /* Supported service identifiers. These go in the 'service' field of every
@ -222,7 +227,7 @@ int rhizome_manifest_check_sanity(rhizome_manifest *m_in);
int rhizome_manifest_check_file(rhizome_manifest *m_in); int rhizome_manifest_check_file(rhizome_manifest *m_in);
int rhizome_manifest_check_duplicate(rhizome_manifest *m_in,rhizome_manifest **m_out); int rhizome_manifest_check_duplicate(rhizome_manifest *m_in,rhizome_manifest **m_out);
int rhizome_manifest_bind_id(rhizome_manifest *m_in, const unsigned char *authorSid); int rhizome_manifest_bind_id(rhizome_manifest *m_in);
int rhizome_manifest_bind_file(rhizome_manifest *m_in,const char *filename,int encryptP); int rhizome_manifest_bind_file(rhizome_manifest *m_in,const char *filename,int encryptP);
int rhizome_manifest_finalise(rhizome_manifest *m); int rhizome_manifest_finalise(rhizome_manifest *m);
int rhizome_add_manifest(rhizome_manifest *m_in,int ttl); int rhizome_add_manifest(rhizome_manifest *m_in,int ttl);

View File

@ -112,10 +112,14 @@ int rhizome_extract_privatekey(rhizome_manifest *m, const unsigned char *authorS
/* /*
Test to see if the given manifest was created (signed) by any unlocked identity currently in the Test to see if the given manifest was created (signed) by any unlocked identity currently in the
keyring. keyring.
Returns -1 if an error occurs, eg, the manifest contains an invalid BK field. - Returns -1 if an error occurs, eg, the manifest contains an invalid BK field.
Return 0 if the manifest's BK field was produced by any currently unlocked SID. - Return 0 if the manifest's BK field was produced by any currently unlocked SID.
Returns 1 if the manifest has no BK field. - Returns 1 if the manifest has no BK field.
Returns 2 otherwise. - Returns 2 otherwise.
Currently unused; was called from rhizome_list_manifests() to compute the now-defunct
".selfsigned" column, but that made the Rhizome List view too slow in the Serval Mesh app.
See issue servalproject/serval-dna#17.
@author Andrew Bettison <andrew@servalproject.com>
*/ */
int rhizome_is_self_signed(rhizome_manifest *m) int rhizome_is_self_signed(rhizome_manifest *m)
{ {

View File

@ -120,6 +120,12 @@ int rhizome_manifest_priority(sqlite_retry_state *retry, const char *id)
return (int) result; return (int) result;
} }
static void sqlite_trace_callback(void *context, const char *rendered_sql)
{
if (debug & DEBUG_RHIZOME)
DEBUG(rendered_sql);
}
int rhizome_opendb() int rhizome_opendb()
{ {
if (rhizome_db) return 0; if (rhizome_db) return 0;
@ -137,6 +143,7 @@ int rhizome_opendb()
if (sqlite3_open(dbpath,&rhizome_db)){ if (sqlite3_open(dbpath,&rhizome_db)){
RETURN(WHYF("SQLite could not open database %s: %s", dbpath, sqlite3_errmsg(rhizome_db))); RETURN(WHYF("SQLite could not open database %s: %s", dbpath, sqlite3_errmsg(rhizome_db)));
} }
sqlite3_trace(rhizome_db, sqlite_trace_callback, NULL);
int loglevel = (debug & DEBUG_RHIZOME) ? LOG_LEVEL_DEBUG : LOG_LEVEL_SILENT; int loglevel = (debug & DEBUG_RHIZOME) ? LOG_LEVEL_DEBUG : LOG_LEVEL_SILENT;
/* Read Rhizome configuration */ /* Read Rhizome configuration */
@ -149,7 +156,7 @@ int rhizome_opendb()
/* Create tables as required */ /* Create tables as required */
sqlite_exec_void_loglevel(loglevel, "PRAGMA auto_vacuum=2;"); sqlite_exec_void_loglevel(loglevel, "PRAGMA auto_vacuum=2;");
if ( sqlite_exec_void("CREATE TABLE IF NOT EXISTS GROUPLIST(id text not null primary key, closed integer,ciphered integer,priority integer);") == -1 if ( sqlite_exec_void("CREATE TABLE IF NOT EXISTS GROUPLIST(id text not null primary key, closed integer,ciphered integer,priority integer);") == -1
|| sqlite_exec_void("CREATE TABLE IF NOT EXISTS MANIFESTS(id text not null primary key, manifest blob, version integer,inserttime integer, bar blob, filesize integer, filehash text);") == -1 || sqlite_exec_void("CREATE TABLE IF NOT EXISTS MANIFESTS(id text not null primary key, manifest blob, version integer,inserttime integer, bar blob, filesize integer, filehash text, author text);") == -1
|| sqlite_exec_void("CREATE TABLE IF NOT EXISTS FILES(id text not null primary key, data blob, length integer, highestpriority integer, datavalid integer, inserttime integer);") == -1 || sqlite_exec_void("CREATE TABLE IF NOT EXISTS FILES(id text not null primary key, data blob, length integer, highestpriority integer, datavalid integer, inserttime integer);") == -1
|| sqlite_exec_void("DROP TABLE IF EXISTS FILEMANIFESTS;") == -1 || sqlite_exec_void("DROP TABLE IF EXISTS FILEMANIFESTS;") == -1
|| sqlite_exec_void("CREATE TABLE IF NOT EXISTS GROUPMEMBERSHIPS(manifestid text not null, groupid text not null);") == -1 || sqlite_exec_void("CREATE TABLE IF NOT EXISTS GROUPMEMBERSHIPS(manifestid text not null, groupid text not null);") == -1
@ -159,7 +166,7 @@ int rhizome_opendb()
} }
/* Create indexes if they don't already exist */ /* Create indexes if they don't already exist */
sqlite_exec_void_loglevel(LOG_LEVEL_WARN,"CREATE INDEX IF NOT EXISTS bundlesizeindex ON manifests (filesize);"); sqlite_exec_void_loglevel(LOG_LEVEL_WARN, "CREATE INDEX IF NOT EXISTS bundlesizeindex ON manifests (filesize);");
sqlite_exec_void_loglevel(LOG_LEVEL_WARN, "CREATE INDEX IF NOT EXISTS IDX_MANIFESTS_HASH ON MANIFESTS(filehash);"); sqlite_exec_void_loglevel(LOG_LEVEL_WARN, "CREATE INDEX IF NOT EXISTS IDX_MANIFESTS_HASH ON MANIFESTS(filehash);");
/* Clean out half-finished entries from the database */ /* Clean out half-finished entries from the database */
@ -669,12 +676,14 @@ int rhizome_store_bundle(rhizome_manifest *m)
filehash[0] = '\0'; filehash[0] = '\0';
} }
const char *author = is_sid_any(m->author) ? NULL : alloca_tohex_sid(m->author);
sqlite_retry_state retry = SQLITE_RETRY_STATE_DEFAULT; sqlite_retry_state retry = SQLITE_RETRY_STATE_DEFAULT;
if (sqlite_exec_void_retry(&retry, "BEGIN TRANSACTION;") == -1) if (sqlite_exec_void_retry(&retry, "BEGIN TRANSACTION;") == -1)
return -1; return -1;
sqlite3_stmt *stmt; sqlite3_stmt *stmt;
if ((stmt = sqlite_prepare(&retry, "INSERT OR REPLACE INTO MANIFESTS(id,manifest,version,inserttime,bar,filesize,filehash) VALUES(?,?,?,?,?,?,?);")) == NULL) if ((stmt = sqlite_prepare(&retry, "INSERT OR REPLACE INTO MANIFESTS(id,manifest,version,inserttime,bar,filesize,filehash,author) VALUES(?,?,?,?,?,?,?,?);")) == NULL)
goto rollback; goto rollback;
if (!( sqlite_code_ok(sqlite3_bind_text(stmt, 1, manifestid, -1, SQLITE_TRANSIENT)) if (!( sqlite_code_ok(sqlite3_bind_text(stmt, 1, manifestid, -1, SQLITE_TRANSIENT))
&& sqlite_code_ok(sqlite3_bind_blob(stmt, 2, m->manifestdata, m->manifest_bytes, SQLITE_TRANSIENT)) && sqlite_code_ok(sqlite3_bind_blob(stmt, 2, m->manifestdata, m->manifest_bytes, SQLITE_TRANSIENT))
@ -683,6 +692,7 @@ int rhizome_store_bundle(rhizome_manifest *m)
&& sqlite_code_ok(sqlite3_bind_blob(stmt, 5, bar, RHIZOME_BAR_BYTES, SQLITE_TRANSIENT)) && sqlite_code_ok(sqlite3_bind_blob(stmt, 5, bar, RHIZOME_BAR_BYTES, SQLITE_TRANSIENT))
&& sqlite_code_ok(sqlite3_bind_int64(stmt, 6, m->fileLength)) && sqlite_code_ok(sqlite3_bind_int64(stmt, 6, m->fileLength))
&& sqlite_code_ok(sqlite3_bind_text(stmt, 7, filehash, -1, SQLITE_TRANSIENT)) && sqlite_code_ok(sqlite3_bind_text(stmt, 7, filehash, -1, SQLITE_TRANSIENT))
&& sqlite_code_ok(sqlite3_bind_text(stmt, 8, author, -1, SQLITE_TRANSIENT))
)) { )) {
WHYF("query failed, %s: %s", sqlite3_errmsg(rhizome_db), sqlite3_sql(stmt)); WHYF("query failed, %s: %s", sqlite3_errmsg(rhizome_db), sqlite3_sql(stmt));
goto rollback; goto rollback;
@ -758,7 +768,7 @@ int rhizome_list_manifests(const char *service, const char *sender_sid, const ch
{ {
IN(); IN();
strbuf b = strbuf_alloca(1024); strbuf b = strbuf_alloca(1024);
strbuf_sprintf(b, "SELECT id, manifest, version, inserttime FROM manifests ORDER BY inserttime DESC"); strbuf_sprintf(b, "SELECT id, manifest, version, inserttime, author FROM manifests ORDER BY inserttime DESC");
if (limit) if (limit)
strbuf_sprintf(b, " LIMIT %u", limit); strbuf_sprintf(b, " LIMIT %u", limit);
if (offset) if (offset)
@ -771,13 +781,14 @@ int rhizome_list_manifests(const char *service, const char *sender_sid, const ch
return -1; return -1;
int ret = 0; int ret = 0;
size_t rows = 0; size_t rows = 0;
cli_puts("11"); cli_delim("\n"); // number of columns cli_puts("12"); cli_delim("\n"); // number of columns
cli_puts("service"); cli_delim(":"); cli_puts("service"); cli_delim(":");
cli_puts("id"); cli_delim(":"); cli_puts("id"); cli_delim(":");
cli_puts("version"); cli_delim(":"); cli_puts("version"); cli_delim(":");
cli_puts("date"); cli_delim(":"); cli_puts("date"); cli_delim(":");
cli_puts(".inserttime"); cli_delim(":"); cli_puts(".inserttime"); cli_delim(":");
cli_puts(".selfsigned"); cli_delim(":"); cli_puts(".author"); cli_delim(":");
cli_puts(".fromhere"); cli_delim(":");
cli_puts("filesize"); cli_delim(":"); cli_puts("filesize"); cli_delim(":");
cli_puts("filehash"); cli_delim(":"); cli_puts("filehash"); cli_delim(":");
cli_puts("sender"); cli_delim(":"); cli_puts("sender"); cli_delim(":");
@ -785,11 +796,14 @@ int rhizome_list_manifests(const char *service, const char *sender_sid, const ch
cli_puts("name"); cli_delim("\n"); // should be last, because name may contain ':' cli_puts("name"); cli_delim("\n"); // should be last, because name may contain ':'
while (sqlite_step_retry(&retry, statement) == SQLITE_ROW) { while (sqlite_step_retry(&retry, statement) == SQLITE_ROW) {
++rows; ++rows;
if (!( sqlite3_column_count(statement) == 4 if (!( sqlite3_column_count(statement) == 5
&& sqlite3_column_type(statement, 0) == SQLITE_TEXT && sqlite3_column_type(statement, 0) == SQLITE_TEXT
&& sqlite3_column_type(statement, 1) == SQLITE_BLOB && sqlite3_column_type(statement, 1) == SQLITE_BLOB
&& sqlite3_column_type(statement, 2) == SQLITE_INTEGER && sqlite3_column_type(statement, 2) == SQLITE_INTEGER
&& sqlite3_column_type(statement, 3) == SQLITE_INTEGER && sqlite3_column_type(statement, 3) == SQLITE_INTEGER
&& ( sqlite3_column_type(statement, 4) == SQLITE_TEXT
|| sqlite3_column_type(statement, 4) == SQLITE_NULL
)
)) { )) {
ret = WHY("Incorrect statement column"); ret = WHY("Incorrect statement column");
break; break;
@ -804,6 +818,7 @@ int rhizome_list_manifests(const char *service, const char *sender_sid, const ch
size_t manifestblobsize = sqlite3_column_bytes(statement, 1); // must call after sqlite3_column_blob() size_t manifestblobsize = sqlite3_column_bytes(statement, 1); // must call after sqlite3_column_blob()
long long q_version = sqlite3_column_int64(statement, 2); long long q_version = sqlite3_column_int64(statement, 2);
long long q_inserttime = sqlite3_column_int64(statement, 3); long long q_inserttime = sqlite3_column_int64(statement, 3);
const char *q_author = (const char *) sqlite3_column_text(statement, 4);
if (rhizome_read_manifest_file(m, manifestblob, manifestblobsize) == -1) { if (rhizome_read_manifest_file(m, manifestblob, manifestblobsize) == -1) {
WARNF("MANIFESTS row id=%s has invalid manifest blob -- skipped", q_manifestid); WARNF("MANIFESTS row id=%s has invalid manifest blob -- skipped", q_manifestid);
} else { } else {
@ -829,14 +844,29 @@ int rhizome_list_manifests(const char *service, const char *sender_sid, const ch
long long blob_date = rhizome_manifest_get_ll(m, "date"); long long blob_date = rhizome_manifest_get_ll(m, "date");
const char *blob_filehash = rhizome_manifest_get(m, "filehash", NULL, 0); const char *blob_filehash = rhizome_manifest_get(m, "filehash", NULL, 0);
long long blob_filesize = rhizome_manifest_get_ll(m, "filesize"); long long blob_filesize = rhizome_manifest_get_ll(m, "filesize");
int self_signed = rhizome_is_self_signed(m) ? 0 : 1; int from_here = 0;
if (q_author) {
DEBUGF("q_author=%s", alloca_str_toprint(q_author));
unsigned char authorSid[SID_SIZE];
stowSid(authorSid, 0, q_author);
int cn = 0, in = 0, kp = 0;
from_here = keyring_find_sid(keyring, &cn, &in, &kp, authorSid);
}
if (!from_here && blob_sender) {
DEBUGF("blob_sender=%s", alloca_str_toprint(blob_sender));
unsigned char senderSid[SID_SIZE];
stowSid(senderSid, 0, blob_sender);
int cn = 0, in = 0, kp = 0;
from_here = keyring_find_sid(keyring, &cn, &in, &kp, senderSid);
}
if (debug & DEBUG_RHIZOME) DEBUGF("manifest payload size = %lld", blob_filesize); if (debug & DEBUG_RHIZOME) DEBUGF("manifest payload size = %lld", blob_filesize);
cli_puts(blob_service ? blob_service : ""); cli_delim(":"); cli_puts(blob_service ? blob_service : ""); cli_delim(":");
cli_puts(q_manifestid); cli_delim(":"); cli_puts(q_manifestid); cli_delim(":");
cli_printf("%lld", blob_version); cli_delim(":"); cli_printf("%lld", blob_version); cli_delim(":");
cli_printf("%lld", blob_date); cli_delim(":"); cli_printf("%lld", blob_date); cli_delim(":");
cli_printf("%lld", q_inserttime); cli_delim(":"); cli_printf("%lld", q_inserttime); cli_delim(":");
cli_printf("%d", self_signed); cli_delim(":"); cli_puts(q_author ? q_author : ""); cli_delim(":");
cli_printf("%d", from_here); cli_delim(":");
cli_printf("%lld", blob_filesize); cli_delim(":"); cli_printf("%lld", blob_filesize); cli_delim(":");
cli_puts(blob_filehash ? blob_filehash : ""); cli_delim(":"); cli_puts(blob_filehash ? blob_filehash : ""); cli_delim(":");
cli_puts(blob_sender ? blob_sender : ""); cli_delim(":"); cli_puts(blob_sender ? blob_sender : ""); cli_delim(":");
@ -1104,7 +1134,7 @@ int rhizome_find_duplicate(const rhizome_manifest *m, rhizome_manifest **found,
} }
char sqlcmd[1024]; char sqlcmd[1024];
strbuf b = strbuf_local(sqlcmd, sizeof sqlcmd); strbuf b = strbuf_local(sqlcmd, sizeof sqlcmd);
strbuf_puts(b, "SELECT id, manifest, version FROM manifests WHERE "); strbuf_puts(b, "SELECT id, manifest, version, author FROM manifests WHERE ");
if (m->fileLength != 0) { if (m->fileLength != 0) {
if (!m->fileHashedP) if (!m->fileHashedP)
return WHY("Manifest payload is not hashed"); return WHY("Manifest payload is not hashed");
@ -1115,8 +1145,6 @@ int rhizome_find_duplicate(const rhizome_manifest *m, rhizome_manifest **found,
strbuf_puts(b, " AND version = ?"); strbuf_puts(b, " AND version = ?");
if (strbuf_overrun(b)) if (strbuf_overrun(b))
return WHYF("SQL command too long: %s", strbuf_str(b)); return WHYF("SQL command too long: %s", strbuf_str(b));
if (debug & DEBUG_RHIZOME)
DEBUGF("sql query: %s", sqlcmd);
int ret = 0; int ret = 0;
sqlite_retry_state retry = SQLITE_RETRY_STATE_DEFAULT; sqlite_retry_state retry = SQLITE_RETRY_STATE_DEFAULT;
sqlite3_stmt *statement = sqlite_prepare(&retry, "%s", strbuf_str(b)); sqlite3_stmt *statement = sqlite_prepare(&retry, "%s", strbuf_str(b));
@ -1137,10 +1165,13 @@ int rhizome_find_duplicate(const rhizome_manifest *m, rhizome_manifest **found,
while (sqlite_step_retry(&retry, statement) == SQLITE_ROW) { while (sqlite_step_retry(&retry, statement) == SQLITE_ROW) {
++rows; ++rows;
if (debug & DEBUG_RHIZOME) DEBUGF("Row %d", rows); if (debug & DEBUG_RHIZOME) DEBUGF("Row %d", rows);
if (!( sqlite3_column_count(statement) == 3 if (!( sqlite3_column_count(statement) == 4
&& sqlite3_column_type(statement, 0) == SQLITE_TEXT && sqlite3_column_type(statement, 0) == SQLITE_TEXT
&& sqlite3_column_type(statement, 1) == SQLITE_BLOB && sqlite3_column_type(statement, 1) == SQLITE_BLOB
&& sqlite3_column_type(statement, 2) == SQLITE_INTEGER && sqlite3_column_type(statement, 2) == SQLITE_INTEGER
&& ( sqlite3_column_type(statement, 3) == SQLITE_TEXT
|| sqlite3_column_type(statement, 3) == SQLITE_NULL
)
)) { )) {
ret = WHY("Incorrect statement columns"); ret = WHY("Incorrect statement columns");
break; break;
@ -1220,7 +1251,7 @@ int rhizome_find_duplicate(const rhizome_manifest *m, rhizome_manifest **found,
strbuf_sprintf(b, " name=\"%s\"", blob_name); strbuf_sprintf(b, " name=\"%s\"", blob_name);
ret = 1; ret = 1;
} }
} else if (strcasecmp(service, RHIZOME_SERVICE_FILE) == 0) { } else if (strcasecmp(service, RHIZOME_SERVICE_MESHMS) == 0) {
const char *blob_sender = rhizome_manifest_get(blob_m, "sender", NULL, 0); const char *blob_sender = rhizome_manifest_get(blob_m, "sender", NULL, 0);
const char *blob_recipient = rhizome_manifest_get(blob_m, "recipient", NULL, 0); const char *blob_recipient = rhizome_manifest_get(blob_m, "recipient", NULL, 0);
if (blob_sender && !strcasecmp(blob_sender, sender) && blob_recipient && !strcasecmp(blob_recipient, recipient)) { if (blob_sender && !strcasecmp(blob_sender, sender) && blob_recipient && !strcasecmp(blob_recipient, recipient)) {
@ -1230,6 +1261,12 @@ int rhizome_find_duplicate(const rhizome_manifest *m, rhizome_manifest **found,
} }
} }
if (ret == 1) { if (ret == 1) {
const char *q_author = (const char *) sqlite3_column_text(statement, 3);
if (q_author) {
if (debug & DEBUG_RHIZOME)
strbuf_sprintf(b, " .author=%s", q_author);
stowSid(blob_m->author, 0, q_author);
}
memcpy(blob_m->cryptoSignPublic, manifest_id, RHIZOME_MANIFEST_ID_BYTES); memcpy(blob_m->cryptoSignPublic, manifest_id, RHIZOME_MANIFEST_ID_BYTES);
memcpy(blob_m->fileHexHash, m->fileHexHash, RHIZOME_FILEHASH_STRLEN + 1); memcpy(blob_m->fileHexHash, m->fileHexHash, RHIZOME_FILEHASH_STRLEN + 1);
blob_m->fileHashedP = 1; blob_m->fileHashedP = 1;
@ -1237,7 +1274,7 @@ int rhizome_find_duplicate(const rhizome_manifest *m, rhizome_manifest **found,
blob_m->version = q_version; blob_m->version = q_version;
*found = blob_m; *found = blob_m;
DEBUGF("Found duplicate payload: service=%s%s version=%llu hexhash=%s", DEBUGF("Found duplicate payload: service=%s%s version=%llu hexhash=%s",
blob_service, strbuf_str(b), blob_m->version, blob_m->fileHexHash blob_service, strbuf_str(b), blob_m->version, blob_m->fileHexHash, q_author ? q_author : ""
); );
break; break;
} }
@ -1263,7 +1300,7 @@ int rhizome_retrieve_manifest(const char *manifestid, rhizome_manifest **mp)
if (fromhexstr(manifest_id, manifestid, RHIZOME_MANIFEST_ID_BYTES) == -1) if (fromhexstr(manifest_id, manifestid, RHIZOME_MANIFEST_ID_BYTES) == -1)
return WHY("Invalid manifest ID"); return WHY("Invalid manifest ID");
sqlite_retry_state retry = SQLITE_RETRY_STATE_DEFAULT; sqlite_retry_state retry = SQLITE_RETRY_STATE_DEFAULT;
sqlite3_stmt *statement = sqlite_prepare(&retry, "SELECT id, manifest, version, inserttime FROM manifests WHERE id = ?"); sqlite3_stmt *statement = sqlite_prepare(&retry, "SELECT id, manifest, version, inserttime, author FROM manifests WHERE id = ?");
if (!statement) if (!statement)
return -1; return -1;
char manifestIdUpper[RHIZOME_MANIFEST_ID_STRLEN + 1]; char manifestIdUpper[RHIZOME_MANIFEST_ID_STRLEN + 1];
@ -1272,11 +1309,12 @@ int rhizome_retrieve_manifest(const char *manifestid, rhizome_manifest **mp)
int ret = 0; int ret = 0;
rhizome_manifest *m = NULL; rhizome_manifest *m = NULL;
while (sqlite_step_retry(&retry, statement) == SQLITE_ROW) { while (sqlite_step_retry(&retry, statement) == SQLITE_ROW) {
if (!( sqlite3_column_count(statement) == 4 if (!( sqlite3_column_count(statement) == 5
&& sqlite3_column_type(statement, 0) == SQLITE_TEXT && sqlite3_column_type(statement, 0) == SQLITE_TEXT
&& sqlite3_column_type(statement, 1) == SQLITE_BLOB && sqlite3_column_type(statement, 1) == SQLITE_BLOB
&& sqlite3_column_type(statement, 2) == SQLITE_INTEGER && sqlite3_column_type(statement, 2) == SQLITE_INTEGER
&& sqlite3_column_type(statement, 3) == SQLITE_INTEGER && sqlite3_column_type(statement, 3) == SQLITE_INTEGER
&& sqlite3_column_type(statement, 4) == SQLITE_TEXT
)) { )) {
ret = WHY("Incorrect statement column"); ret = WHY("Incorrect statement column");
break; break;
@ -1323,6 +1361,7 @@ int rhizome_retrieve_manifest(const char *manifestid, rhizome_manifest **mp)
else else
m->version = blob_version; m->version = blob_version;
if (ret == 1) { if (ret == 1) {
const char *q_author = (const char *) sqlite3_column_text(statement, 4);
cli_puts("service"); cli_delim(":"); cli_puts("service"); cli_delim(":");
cli_puts(blob_service); cli_delim("\n"); cli_puts(blob_service); cli_delim("\n");
cli_puts("manifestid"); cli_delim(":"); cli_puts("manifestid"); cli_delim(":");
@ -1331,6 +1370,10 @@ int rhizome_retrieve_manifest(const char *manifestid, rhizome_manifest **mp)
cli_printf("%lld", (long long) sqlite3_column_int64(statement, 2)); cli_delim("\n"); cli_printf("%lld", (long long) sqlite3_column_int64(statement, 2)); cli_delim("\n");
cli_puts("inserttime"); cli_delim(":"); cli_puts("inserttime"); cli_delim(":");
cli_printf("%lld", (long long) sqlite3_column_int64(statement, 3)); cli_delim("\n"); cli_printf("%lld", (long long) sqlite3_column_int64(statement, 3)); cli_delim("\n");
if (q_author) {
cli_puts(".author"); cli_delim(":");
cli_puts(q_author); cli_delim("\n");
}
cli_puts("filesize"); cli_delim(":"); cli_puts("filesize"); cli_delim(":");
cli_printf("%lld", (long long) m->fileLength); cli_delim("\n"); cli_printf("%lld", (long long) m->fileLength); cli_delim("\n");
if (m->fileLength != 0) { if (m->fileLength != 0) {

View File

@ -251,18 +251,17 @@ int rhizome_direct_form_received(rhizome_http_request *r)
if (debug & DEBUG_RHIZOME) DEBUGF("manifest contains name=\"%s\"", name); if (debug & DEBUG_RHIZOME) DEBUGF("manifest contains name=\"%s\"", name);
} }
const char *senderhex const char *senderhex = rhizome_manifest_get(m, "sender", NULL, 0);
= rhizome_manifest_get(m, "sender", NULL, 0); if (!senderhex)
if (!senderhex) senderhex=confValueGet("rhizome.api.addfile.author",NULL); senderhex = confValueGet("rhizome.api.addfile.author", NULL);
unsigned char authorSid[SID_SIZE]; if (senderhex)
if (senderhex) fromhexstr(authorSid,senderhex,SID_SIZE); fromhexstr(m->author, senderhex, SID_SIZE);
const char *bskhex const char *bskhex = confValueGet("rhizome.api.addfile.bundlesecretkey", NULL);
=confValueGet("rhizome.api.addfile.bundlesecretkey", NULL);
/* Bind an ID to the manifest, and also bind the file. Then finalise the /* Bind an ID to the manifest, and also bind the file. Then finalise the
manifest. But if the manifest already contains an ID, don't override it. */ manifest. But if the manifest already contains an ID, don't override it. */
if (rhizome_manifest_get(m, "id", NULL, 0) == NULL) { if (rhizome_manifest_get(m, "id", NULL, 0) == NULL) {
if (rhizome_manifest_bind_id(m, senderhex ? authorSid : NULL)) { if (rhizome_manifest_bind_id(m)) {
rhizome_manifest_free(m); rhizome_manifest_free(m);
m = NULL; m = NULL;
rhizome_direct_clear_temporary_files(r); rhizome_direct_clear_temporary_files(r);

View File

@ -47,17 +47,22 @@ assert_manifest_complete() {
assert_rhizome_list() { assert_rhizome_list() {
assertStdoutLineCount --stderr '==' $(($# + 2)) assertStdoutLineCount --stderr '==' $(($# + 2))
assertStdoutIs --stderr --line=1 -e '11\n' assertStdoutIs --stderr --line=1 -e '12\n'
assertStdoutIs --stderr --line=2 -e 'service:id:version:date:.inserttime:.selfsigned:filesize:filehash:sender:recipient:name\n' assertStdoutIs --stderr --line=2 -e 'service:id:version:date:.inserttime:.author:.fromhere:filesize:filehash:sender:recipient:name\n'
local filename local filename
local re__author="\($rexp_sid\)\{0,1\}"
local re__fromhere
local re__inserttime="$rexp_date" local re__inserttime="$rexp_date"
for filename; do for filename; do
re__selfsigned=1 re__fromhere=1
case "$filename" in case "$filename" in
*!) filename="${filename%!}"; re__selfsigned=0;; *@*) re__author="${filename##*@}"; filename="${filename%@*}";;
esac
case "$filename" in
*!) re__fromhere=0; filename="${filename%!}";;
esac esac
unpack_manifest_for_grep "$filename" unpack_manifest_for_grep "$filename"
assertStdoutGrep --stderr --matches=1 "^$re_service:$re_manifestid:$re_version:$re_date:$re__inserttime:$re__selfsigned:$re_filesize:$re_filehash:$re_sender:$re_recipient:$re_name\$" assertStdoutGrep --stderr --matches=1 "^$re_service:$re_manifestid:$re_version:$re_date:$re__inserttime:$re__author:$re__fromhere:$re_filesize:$re_filehash:$re_sender:$re_recipient:$re_name\$"
done done
} }
@ -110,6 +115,8 @@ unpack_manifest_for_grep() {
re_version="$rexp_version" re_version="$rexp_version"
re_date="$rexp_date" re_date="$rexp_date"
re_secret="$rexp_bundlesecret" re_secret="$rexp_bundlesecret"
re_sender="\($rexp_sid\)\{0,1\}"
re_recipient="\($rexp_sid\)\{0,1\}"
re_name=$(escape_grep_basic "${filename##*/}") re_name=$(escape_grep_basic "${filename##*/}")
local filesize=$($SED -n -e '/^filesize=/s///p' "$filename.manifest" 2>/dev/null) local filesize=$($SED -n -e '/^filesize=/s///p' "$filename.manifest" 2>/dev/null)
if [ "$filesize" = 0 ]; then if [ "$filesize" = 0 ]; then
@ -124,11 +131,11 @@ unpack_manifest_for_grep() {
# file, then use its file hash to check the rhizome list '' output. # file, then use its file hash to check the rhizome list '' output.
local filehash=$($SED -n -e '/^filehash=/s///p' "$filename.manifest" 2>/dev/null) local filehash=$($SED -n -e '/^filehash=/s///p' "$filename.manifest" 2>/dev/null)
if [ "$filehash" = "$re_filehash" ]; then if [ "$filehash" = "$re_filehash" ]; then
re_service=$($SED -n -e '/^service=/s///p' "$filename.manifest")
re_service=$(escape_grep_basic "$re_service")
re_manifestid=$($SED -n -e '/^id=/s///p' "$filename.manifest") re_manifestid=$($SED -n -e '/^id=/s///p' "$filename.manifest")
re_version=$($SED -n -e '/^version=/s///p' "$filename.manifest") re_version=$($SED -n -e '/^version=/s///p' "$filename.manifest")
re_date=$($SED -n -e '/^date=/s///p' "$filename.manifest") re_date=$($SED -n -e '/^date=/s///p' "$filename.manifest")
re_service=$($SED -n -e '/^service=/s///p' "$filename.manifest")
re_service=$(escape_grep_basic "$re_service")
re_sender=$($SED -n -e '/^sender=/s///p' "$filename.manifest") re_sender=$($SED -n -e '/^sender=/s///p' "$filename.manifest")
re_recipient=$($SED -n -e '/^recipient=/s///p' "$filename.manifest") re_recipient=$($SED -n -e '/^recipient=/s///p' "$filename.manifest")
case "$re_service" in case "$re_service" in
@ -138,6 +145,8 @@ unpack_manifest_for_grep() {
;; ;;
*) *)
re_name= re_name=
re_sender="$rexp_sid"
re_recipient="$rexp_sid"
;; ;;
esac esac
fi fi

View File

@ -141,7 +141,7 @@ test_AddEmpty() {
assertGrep .manifest '^name=$' assertGrep .manifest '^name=$'
assertGrep .manifest '^filesize=0$' assertGrep .manifest '^filesize=0$'
executeOk_servald rhizome list '' executeOk_servald rhizome list ''
assert_rhizome_list '' assert_rhizome_list @$SIDB1
} }
doc_AddThenList="List contains one file after one add" doc_AddThenList="List contains one file after one add"
@ -157,11 +157,11 @@ test_AddThenList() {
# Add first file # Add first file
executeOk_servald rhizome add file $SIDB1 '' file1 file1.manifest executeOk_servald rhizome add file $SIDB1 '' file1 file1.manifest
executeOk_servald rhizome list '' executeOk_servald rhizome list ''
assert_rhizome_list file1 assert_rhizome_list file1@$SIDB1
# Add second file # Add second file
executeOk_servald rhizome add file $SIDB1 '' file2 file2.manifest executeOk_servald rhizome add file $SIDB1 '' file2 file2.manifest
executeOk_servald rhizome list '' executeOk_servald rhizome list ''
assert_rhizome_list file1 file2 assert_rhizome_list file1@$SIDB1 file2@$SIDB1
} }
doc_AddThenExtractManifest="Extract manifest after one add" doc_AddThenExtractManifest="Extract manifest after one add"
@ -171,7 +171,7 @@ setup_AddThenExtractManifest() {
echo "A test file" >file1 echo "A test file" >file1
executeOk_servald rhizome add file $SIDB1 '' file1 file1.manifest executeOk_servald rhizome add file $SIDB1 '' file1 file1.manifest
executeOk_servald rhizome list '' executeOk_servald rhizome list ''
assert_rhizome_list file1 assert_rhizome_list file1@$SIDB1
extract_manifest_id manifestid file1.manifest extract_manifest_id manifestid file1.manifest
extract_manifest_version version file1.manifest extract_manifest_version version file1.manifest
extract_manifest_filehash filehash file1.manifest extract_manifest_filehash filehash file1.manifest
@ -179,14 +179,15 @@ setup_AddThenExtractManifest() {
test_AddThenExtractManifest() { test_AddThenExtractManifest() {
executeOk_servald rhizome extract manifest $manifestid file1x.manifest executeOk_servald rhizome extract manifest $manifestid file1x.manifest
assert cmp file1.manifest file1x.manifest assert cmp file1.manifest file1x.manifest
assertStdoutLineCount '==' 6 assertStdoutLineCount '==' 7
local size=$(( $(cat file1 | wc -c) + 0 )) local size=$(( $(cat file1 | wc -c) + 0 ))
assertStdoutGrep --matches=1 "^service:file$" assertStdoutGrep --matches=1 "^service:file$"
assertStdoutGrep --matches=1 "^manifestid:$manifestid$" assertStdoutGrep --matches=1 "^manifestid:$manifestid\$"
assertStdoutGrep --matches=1 "^version:$version$" assertStdoutGrep --matches=1 "^version:$version\$"
assertStdoutGrep --matches=1 "^inserttime:[0-9]\+$" assertStdoutGrep --matches=1 "^inserttime:[0-9]\+\$"
assertStdoutGrep --matches=1 "^filehash:$filehash$" assertStdoutGrep --matches=1 "^\.author:$SIDB1\$"
assertStdoutGrep --matches=1 "^filesize:$size$" assertStdoutGrep --matches=1 "^filehash:$filehash\$"
assertStdoutGrep --matches=1 "^filesize:$size\$"
} }
doc_ExtractMissingManifest="Extract non-existent manifest" doc_ExtractMissingManifest="Extract non-existent manifest"
@ -226,7 +227,7 @@ setup_AddThenExtractFile() {
executeOk_servald rhizome add file $SIDB1 '' file1 file1.manifest executeOk_servald rhizome add file $SIDB1 '' file1 file1.manifest
tfw_cat --stderr tfw_cat --stderr
executeOk_servald rhizome list '' executeOk_servald rhizome list ''
assert_rhizome_list file1 assert_rhizome_list file1@$SIDB1
extract_manifest_filehash filehash file1.manifest extract_manifest_filehash filehash file1.manifest
} }
test_AddThenExtractFile() { test_AddThenExtractFile() {
@ -284,7 +285,7 @@ setup_AddDuplicate() {
extract_stdout_secret file2_secret extract_stdout_secret file2_secret
# Make sure they are both in the list. # Make sure they are both in the list.
executeOk_servald rhizome list '' executeOk_servald rhizome list ''
assert_rhizome_list file1 file2 assert_rhizome_list file1@$SIDB1 file2@$SIDB1
} }
test_AddDuplicate() { test_AddDuplicate() {
# Add first file again - nothing should change in its manifests, and it # Add first file again - nothing should change in its manifests, and it
@ -294,7 +295,7 @@ test_AddDuplicate() {
assert [ -s file1.manifestA ] assert [ -s file1.manifestA ]
assert_stdout_add_file file1 assert_stdout_add_file file1
executeOk_servald rhizome list '' executeOk_servald rhizome list ''
assert_rhizome_list file1 file2 assert_rhizome_list file1@$SIDB1 file2@$SIDB1
strip_signatures file1.manifest file1.manifestA strip_signatures file1.manifest file1.manifestA
assert diff file1.manifest file1.manifestA assert diff file1.manifest file1.manifestA
# Repeat for second file. # Repeat for second file.
@ -302,7 +303,7 @@ test_AddDuplicate() {
assert [ -s file2.manifestA ] assert [ -s file2.manifestA ]
assert_stdout_add_file file2 assert_stdout_add_file file2
executeOk_servald rhizome list '' executeOk_servald rhizome list ''
assert_rhizome_list file1 file2 assert_rhizome_list file1@$SIDB1 file2@$SIDB1
strip_signatures file2.manifest file2.manifestA strip_signatures file2.manifest file2.manifestA
assert diff file2.manifest file2.manifestA assert diff file2.manifest file2.manifestA
} }
@ -320,7 +321,7 @@ test_AddMismatched() {
assert cmp file1.manifest file1_2.manifest assert cmp file1.manifest file1_2.manifest
# And rhizome store should be unchanged. # And rhizome store should be unchanged.
executeOk_servald rhizome list '' executeOk_servald rhizome list ''
assert_rhizome_list file1 file2 assert_rhizome_list file1@$SIDB1 file2@$SIDB1
} }
doc_AddUpdateSameVersion="Add new payload to existing manifest with same version fails" doc_AddUpdateSameVersion="Add new payload to existing manifest with same version fails"
@ -343,7 +344,7 @@ test_AddUpdateSameVersion() {
assert cmp file1_2.manifest file1_2.manifest.orig assert cmp file1_2.manifest file1_2.manifest.orig
# And rhizome store should be unchanged. # And rhizome store should be unchanged.
executeOk_servald rhizome list '' executeOk_servald rhizome list ''
assert_rhizome_list file1 file2 assert_rhizome_list file1@$SIDB1 file2@$SIDB1
} }
doc_AddUpdateNewVersion="Add new payload to existing manifest with new version" doc_AddUpdateNewVersion="Add new payload to existing manifest with new version"
@ -357,11 +358,12 @@ setup_AddUpdateNewVersion() {
test_AddUpdateNewVersion() { test_AddUpdateNewVersion() {
tfw_cat -v file1_2.manifest tfw_cat -v file1_2.manifest
executeOk_servald rhizome add file $SIDB1 '' file1_2 file1_2.manifest executeOk_servald rhizome add file $SIDB1 '' file1_2 file1_2.manifest
tfw_cat --stderr
assert_stdout_add_file file1_2 name=file1 assert_stdout_add_file file1_2 name=file1
assert_manifest_newer file1.manifest file1_2.manifest assert_manifest_newer file1.manifest file1_2.manifest
# Rhizome store contents reflect new payload. # Rhizome store contents reflect new payload.
executeOk_servald rhizome list '' executeOk_servald rhizome list ''
assert_rhizome_list file1_2 file2 assert_rhizome_list file1_2@$SIDB1 file2@$SIDB1
} }
doc_AddUpdateNoAuthor="Cannot add new payload to authorless manifest" doc_AddUpdateNoAuthor="Cannot add new payload to authorless manifest"
@ -376,7 +378,7 @@ test_AddUpdateNoAuthor() {
assertExitStatus '!=' 0 assertExitStatus '!=' 0
# Rhizome store contents have old payload. # Rhizome store contents have old payload.
executeOk_servald rhizome list '' executeOk_servald rhizome list ''
assert_rhizome_list file1 file2 assert_rhizome_list file1@$SIDB1 file2@$SIDB1
} }
doc_AddUpdateNoAuthorWithSecret="Add new payload to authorless manifest with bundle secret" doc_AddUpdateNoAuthorWithSecret="Add new payload to authorless manifest with bundle secret"
@ -389,7 +391,7 @@ test_AddUpdateNoAuthorWithSecret() {
tfw_cat --stderr tfw_cat --stderr
# Rhizome store contents have new payload. # Rhizome store contents have new payload.
executeOk_servald rhizome list '' executeOk_servald rhizome list ''
assert_rhizome_list file1_2! file2 assert_rhizome_list file1_2@$SIDB1 file2@$SIDB1
} }
doc_AddUpdateAutoVersion="Add new payload to existing manifest with automatic version" doc_AddUpdateAutoVersion="Add new payload to existing manifest with automatic version"