Merge remote-tracking branch 'origin/master'

Conflicts:
	rhizome_fetch.c
	rhizome_http.c
	testframework.sh
This commit is contained in:
Jeremy Lakeman 2012-07-17 15:39:55 +09:30
commit 1d53726b7b
17 changed files with 849 additions and 487 deletions

@ -1108,23 +1108,43 @@ int app_rhizome_add_file(int argc, const char *const *argv, struct command_line_
ret = WHY("Could not overwrite manifest file.");
service = rhizome_manifest_get(mwritten, "service", NULL, 0);
if (service) {
cli_puts("service"); cli_delim(":"); cli_puts(service); cli_delim("\n");
cli_puts("service");
cli_delim(":");
cli_puts(service);
cli_delim("\n");
}
{
char bid[RHIZOME_MANIFEST_ID_STRLEN + 1];
rhizome_bytes_to_hex_upper(mwritten->cryptoSignPublic, bid, RHIZOME_MANIFEST_ID_BYTES);
cli_puts("manifestid"); cli_delim(":"); cli_puts(bid); cli_delim("\n");
cli_puts("manifestid");
cli_delim(":");
cli_puts(bid);
cli_delim("\n");
}
{
char secret[RHIZOME_BUNDLE_KEY_STRLEN + 1];
rhizome_bytes_to_hex_upper(mwritten->cryptoSignSecret, secret, RHIZOME_BUNDLE_KEY_BYTES);
cli_puts("secret"); cli_delim(":"); cli_puts(secret); cli_delim("\n");
cli_puts("secret");
cli_delim(":");
cli_puts(secret);
cli_delim("\n");
}
cli_puts("filesize");
cli_delim(":");
cli_printf("%lld", mwritten->fileLength);
cli_delim("\n");
if (mwritten->fileLength != 0) {
cli_puts("filehash");
cli_delim(":");
cli_puts(mwritten->fileHexHash);
cli_delim("\n");
}
cli_puts("filehash"); cli_delim(":"); cli_puts(mwritten->fileHexHash); cli_delim("\n");
cli_puts("filesize"); cli_delim(":"); cli_printf("%lld", mwritten->fileLength); cli_delim("\n");
const char *name = rhizome_manifest_get(mwritten, "name", NULL, 0);
if (name) {
cli_puts("name"); cli_delim(":"); cli_puts(name); cli_delim("\n");
cli_puts("name");
cli_delim(":");
cli_puts(name);
cli_delim("\n");
}
rhizome_manifest_free(m);
if (mout != m)
@ -1178,14 +1198,16 @@ int app_rhizome_import_bundle(int argc, const char *const *argv, struct command_
cli_puts(alloca_tohex(m->cryptoSignPublic, RHIZOME_MANIFEST_ID_BYTES));
cli_delim("\n");
}
cli_puts("filehash");
cli_delim(":");
cli_puts(m->fileHexHash);
cli_delim("\n");
cli_puts("filesize");
cli_delim(":");
cli_printf("%lld", m->fileLength);
cli_delim("\n");
if (m->fileLength != 0) {
cli_puts("filehash");
cli_delim(":");
cli_puts(m->fileHexHash);
cli_delim("\n");
}
const char *name = rhizome_manifest_get(m, "name", NULL, 0);
if (name) {
cli_puts("name");

@ -88,11 +88,20 @@ parseDnaReply(unsigned char *bytes, int count,
return 0;
}
void dna_helper_monitor(int fd)
{
return;
}
struct sched_ent dna_helper_sched;
static int
dna_helper_start(const char *command, const char *arg) {
int stdin_fds[2], stdout_fds[2];
pid_t pid;
DEBUG("Starting DNA helper");
if (pipe(stdin_fds))
return WHY_perror("pipe");
@ -102,7 +111,7 @@ dna_helper_start(const char *command, const char *arg) {
return WHY_perror("pipe");
}
if ((pid = fork()) != 0) {
if ((pid = fork()) == 0) {
/* Child, should exec() to become helper after installing file descriptors. */
if (dup2(stdin_fds[1], 0)) /* replace stdin */
exit(-1);
@ -111,6 +120,7 @@ dna_helper_start(const char *command, const char *arg) {
if (dup2(stdout_fds[0], 2)) /* replace stderr */
exit(-1);
execl(command, command, arg, NULL);
DEBUG("execl() failed");
abort(); /* Can't get here */
} else {
if (pid == -1) {
@ -125,6 +135,11 @@ dna_helper_start(const char *command, const char *arg) {
/* Parent, should put file descriptors into place for use */
dna_helper_stdin = stdin_fds[0];
dna_helper_stdout = stdout_fds[1];
/* Need to watch dna_helper_stdout */
// XXX need to initialise structure before calling watch
// XXX watch(&dna_helper_sched);
// XXX need to add unwatch() when we detect that the process has died.
return 0;
}
}
@ -145,6 +160,7 @@ dna_helper_enqueue(char *did, unsigned char *requestorSid) {
/* Check if we have a helper configured. If not, then set
dna_helper_stdin to magic value of -2 so that we don't waste time
in future looking up the dna helper configuration value. */
DEBUG("We have no DNA helper configured");
dna_helper_stdin = -2;
return -1;
}
@ -154,9 +170,11 @@ dna_helper_enqueue(char *did, unsigned char *requestorSid) {
/* Okay, so we have a helper configured.
Run it */
if (dna_helper_start(dna_helper, dna_helper_arg) < 0)
if (dna_helper_start(dna_helper, dna_helper_arg) < 0) {
/* Something broke, bail out */
DEBUG("Failed to start dna helper");
return -1;
}
}
/* Write request to dna helper.
@ -186,6 +204,7 @@ dna_helper_enqueue(char *did, unsigned char *requestorSid) {
close(dna_helper_stdout);
dna_helper_stdin = -1;
dna_helper_stdout = -1;
DEBUG("Sigpipe encountered");
return -1;
}

@ -30,23 +30,16 @@ struct sched_ent *next_alarm=NULL;
struct sched_ent *next_deadline=NULL;
struct profile_total poll_stats={NULL,0,"Idle (in poll)",0,0,0};
void list_alarms(){
long long now=overlay_gettime_ms();
struct sched_ent *alarm = next_alarm;
void list_alarms() {
DEBUG("Alarms;");
long long now = overlay_gettime_ms();
struct sched_ent *alarm;
for (alarm = next_alarm; alarm; alarm = alarm->_next)
DEBUGF("%s in %lldms", (alarm->stats ? alarm->stats->name : "Unnamed"), alarm->alarm - now);
DEBUG("File handles;");
int i;
INFO("Alarms;");
while(alarm){
INFOF("%s in %lldms",
(alarm->stats?alarm->stats->name:"Unnamed"),
alarm->alarm - now);
alarm = alarm->_next;
}
INFO("File handles;");
for (i=0;i<fdcount;i++)
INFOF("%s watching #%d",
(fd_callbacks[i]->stats?fd_callbacks[i]->stats->name:"Unnamed"),
fds[i].fd);
for (i = 0; i < fdcount; ++i)
DEBUGF("%s watching #%d", (fd_callbacks[i]->stats ? fd_callbacks[i]->stats->name : "Unnamed"), fds[i].fd);
}
int deadline(struct sched_ent *alarm){
@ -136,9 +129,11 @@ int watch(struct sched_ent *alarm){
if (alarm->_poll_index>=0 && fd_callbacks[alarm->_poll_index]==alarm){
// updating event flags
INFOF("Updating watch %s, #%d for %d", (alarm->stats?alarm->stats->name:"Unnamed"), alarm->poll.fd, alarm->poll.events);
if (debug & DEBUG_IO)
DEBUGF("Updating watch %s, #%d for %d", (alarm->stats?alarm->stats->name:"Unnamed"), alarm->poll.fd, alarm->poll.events);
}else{
INFOF("Adding watch %s, #%d for %d", (alarm->stats?alarm->stats->name:"Unnamed"), alarm->poll.fd, alarm->poll.events);
if (debug & DEBUG_IO)
DEBUGF("Adding watch %s, #%d for %d", (alarm->stats?alarm->stats->name:"Unnamed"), alarm->poll.fd, alarm->poll.events);
if (fdcount>=MAX_WATCHED_FDS)
return WHY("Too many file handles to watch");
fd_callbacks[fdcount]=alarm;
@ -165,7 +160,8 @@ int unwatch(struct sched_ent *alarm){
fds[fdcount].fd=-1;
fd_callbacks[fdcount]=NULL;
alarm->_poll_index=-1;
INFOF("%s stopped watching #%d for %d", (alarm->stats?alarm->stats->name:"Unnamed"), alarm->poll.fd, alarm->poll.events);
if (debug & DEBUG_IO)
DEBUGF("%s stopped watching #%d for %d", (alarm->stats?alarm->stats->name:"Unnamed"), alarm->poll.fd, alarm->poll.events);
return 0;
}

4
log.c

@ -266,7 +266,9 @@ char *toprint(char *dstStr, size_t dstChars, const unsigned char *srcBuf, size_t
strbuf_puts(b, "\\r");
else if (*srcBuf == '\t')
strbuf_puts(b, "\\t");
else if (isprint(*srcBuf))
else if (*srcBuf == '\\')
strbuf_puts(b, "\\\\");
else if (*srcBuf >= ' ' && *srcBuf <= '~')
strbuf_putc(b, *srcBuf);
else
strbuf_sprintf(b, "\\x%02x", *srcBuf);

@ -563,6 +563,7 @@ int overlay_saw_mdp_frame(overlay_mdp_frame *mdp,long long now)
when results become available, so this function will return
immediately, so as not to cause blockages and delays in servald.
*/
DEBUGF("Asking DNA helper to resolve '%s'",did);
dna_helper_enqueue(did,mdp->out.src.sid);
}
RETURN(0);

@ -83,7 +83,7 @@ int rhizome_bundle_import(rhizome_manifest *m_in, rhizome_manifest **m_out,
}
/* Add the manifest and its associated file to the Rhizome database. */
m->dataFileName=strdup(filename);
m->dataFileName = strdup(filename);
if (rhizome_manifest_check_file(m))
return WHY("File does not belong to manifest");
int ret=rhizome_manifest_check_duplicate(m,NULL);
@ -207,15 +207,19 @@ int rhizome_manifest_bind_file(rhizome_manifest *m_in,const char *filename,int e
DEBUGF("filename=%s, fileLength=%lld", filename, m_in->fileLength);
rhizome_manifest_set_ll(m_in,"filesize",m_in->fileLength);
/* Compute hash of payload */
char hexhashbuf[RHIZOME_FILEHASH_STRLEN + 1];
if (rhizome_hash_file(m_in,filename, hexhashbuf))
return WHY("Could not hash file.");
memcpy(&m_in->fileHexHash[0], &hexhashbuf[0], sizeof hexhashbuf);
/* Store hash of payload */
rhizome_manifest_set(m_in, "filehash", m_in->fileHexHash);
m_in->fileHashedP=1;
/* Compute hash of non-empty payload */
if (m_in->fileLength != 0) {
char hexhashbuf[RHIZOME_FILEHASH_STRLEN + 1];
if (rhizome_hash_file(m_in,filename, hexhashbuf))
return WHY("Could not hash file.");
memcpy(&m_in->fileHexHash[0], &hexhashbuf[0], sizeof hexhashbuf);
rhizome_manifest_set(m_in, "filehash", m_in->fileHexHash);
m_in->fileHashedP = 1;
} else {
m_in->fileHexHash[0] = '\0';
rhizome_manifest_del(m_in, "filehash");
m_in->fileHashedP = 0;
}
return 0;
}
@ -237,34 +241,43 @@ int rhizome_manifest_check_file(rhizome_manifest *m_in)
/* Check payload file is accessible and discover its length, then check that it matches
the file size stored in the manifest */
long long mfilesize = rhizome_manifest_get_ll(m_in, "filesize");
m_in->fileLength = 0;
if (m_in->dataFileName[0]) {
struct stat stat;
if (lstat(m_in->dataFileName,&stat))
return WHYF("Could not stat() payload file '%s'",m_in->dataFileName);
m_in->fileLength = stat.st_size;
} else
m_in->fileLength = 0;
if (lstat(m_in->dataFileName,&stat) == -1) {
if (errno != ENOENT || mfilesize != 0)
return WHYF_perror("stat(%s)", m_in->dataFileName);
} else {
m_in->fileLength = stat.st_size;
}
}
if (debug & DEBUG_RHIZOME)
DEBUGF("filename=%s, fileLength=%lld", m_in->dataFileName, m_in->fileLength);
long long mfilesize = rhizome_manifest_get_ll(m_in, "filesize");
if (mfilesize != -1 && mfilesize != m_in->fileLength) {
WHYF("Manifest.filesize (%lld) != actual file size (%lld)", mfilesize, m_in->fileLength);
return -1;
}
/* Compute hash of payload */
char hexhashbuf[RHIZOME_FILEHASH_STRLEN + 1];
if (rhizome_hash_file(m_in,m_in->dataFileName, hexhashbuf))
return WHY("Could not hash file.");
memcpy(&m_in->fileHexHash[0], &hexhashbuf[0], sizeof hexhashbuf);
m_in->fileHashedP = 1;
/* Check that payload hash matches manifest */
/* If payload is empty, ensure manifest has not file hash, otherwis compute the hash of the
payload and check that it matches manifest. */
const char *mhexhash = rhizome_manifest_get(m_in, "filehash", NULL, 0);
if (!mhexhash) return WHY("manifest contains no file hash");
if (mhexhash && strcmp(m_in->fileHexHash, mhexhash)) {
WHYF("Manifest.filehash (%s) does not match payload hash (%s)", mhexhash, m_in->fileHexHash);
return -1;
if (m_in->fileLength != 0) {
char hexhashbuf[RHIZOME_FILEHASH_STRLEN + 1];
if (rhizome_hash_file(m_in,m_in->dataFileName, hexhashbuf))
return WHY("Could not hash file.");
memcpy(&m_in->fileHexHash[0], &hexhashbuf[0], sizeof hexhashbuf);
m_in->fileHashedP = 1;
if (!mhexhash) return WHY("manifest contains no file hash");
if (mhexhash && strcmp(m_in->fileHexHash, mhexhash)) {
WHYF("Manifest.filehash (%s) does not match payload hash (%s)", mhexhash, m_in->fileHexHash);
return -1;
}
} else {
if (mhexhash != NULL) {
WHYF("Manifest.filehash (%s) should be absent for empty payload", mhexhash);
return -1;
}
}
return 0;
@ -324,8 +337,6 @@ int rhizome_add_manifest(rhizome_manifest *m_in,int ttl)
/* If the manifest already has an ID */
char id[SID_STRLEN + 1];
char ofilehash[RHIZOME_FILEHASH_STRLEN + 1];
ofilehash[0] = '\0';
if (rhizome_manifest_get(m_in, "id", id, SID_STRLEN + 1)) {
str_toupper_inplace(id);
/* Discard the new manifest unless it is newer than the most recent known version with the same ID */
@ -346,10 +357,6 @@ int rhizome_add_manifest(rhizome_manifest *m_in,int ttl)
default:
return WHY("Select found too many rows!");
}
strbuf b = strbuf_local(ofilehash, sizeof ofilehash);
sqlite_exec_strbuf(b, "SELECT filehash from manifests where id='%s';", id);
if (strbuf_overrun(b))
return WHYF("fileid too long: '%s'", strbuf_str(b));
} else {
/* no manifest ID */
return WHY("Manifest does not have an ID");

@ -163,6 +163,10 @@ int rhizome_str_is_file_hash(const char *text);
#define alloca_tohex_bid(bid) alloca_tohex((bid), RHIZOME_MANIFEST_ID_BYTES)
int http_header_complete(const char *buf, size_t len, size_t tail);
int str_startswith(char *str, const char *substring, char **afterp);
int strcase_startswith(char *str, const char *substring, char **afterp);
int rhizome_write_manifest_file(rhizome_manifest *m, const char *filename);
int rhizome_manifest_selfsign(rhizome_manifest *m);
int rhizome_drop_stored_file(const char *id,int maximum_priority);
@ -173,6 +177,7 @@ char *rhizome_manifest_get(const rhizome_manifest *m, const char *var, char *out
long long rhizome_manifest_get_ll(rhizome_manifest *m, const char *var);
int rhizome_manifest_set_ll(rhizome_manifest *m,char *var,long long value);
int rhizome_manifest_set(rhizome_manifest *m, const char *var, const char *value);
int rhizome_manifest_del(rhizome_manifest *m, const char *var);
long long rhizome_file_size(char *filename);
void _rhizome_manifest_free(const char *sourcefile,const char *funcname,int line,
rhizome_manifest *m);
@ -248,4 +253,3 @@ int rhizome_ignore_manifest_check(rhizome_manifest *m,
int rhizome_suggest_queue_manifest_import(rhizome_manifest *m,
struct sockaddr_in *peerip);

@ -281,6 +281,10 @@ int rhizome_read_manifest_file(rhizome_manifest *m, const char *filename, int bu
WARNF("Missing filehash field");
m->errors++;
}
if (have_filehash && m->fileLength == 0) {
WARNF("Spurious filehash field");
m->errors++;
}
// TODO Determine group membership here.
@ -370,13 +374,33 @@ double rhizome_manifest_get_double(rhizome_manifest *m,char *var,double default_
return default_value;
}
/* @author Andrew Bettison <andrew@servalproject.com>
*/
int rhizome_manifest_del(rhizome_manifest *m, const char *var)
{
int ret = 0;
int i;
for (i = 0; i < m->var_count; ++i)
if (strcmp(m->vars[i], var) == 0) {
free(m->vars[i]);
free(m->values[i]);
--m->var_count;
m->finalised = 0;
ret = 1;
break;
}
for (; i < m->var_count; ++i) {
m->vars[i] = m->vars[i + 1];
m->values[i] = m->values[i + 1];
}
return ret;
}
int rhizome_manifest_set(rhizome_manifest *m, const char *var, const char *value)
{
if (!m)
return WHY("m == NULL");
int i;
if (!m) return -1;
for(i=0;i<m->var_count;i++)
if (!strcmp(m->vars[i],var)) {
free(m->values[i]);
@ -384,14 +408,12 @@ int rhizome_manifest_set(rhizome_manifest *m, const char *var, const char *value
m->finalised=0;
return 0;
}
if (m->var_count>=MAX_MANIFEST_VARS) return -1;
if (m->var_count >= MAX_MANIFEST_VARS)
return WHY("no more manifest vars");
m->vars[m->var_count]=strdup(var);
m->values[m->var_count]=strdup(value);
m->var_count++;
m->finalised=0;
return 0;
}
@ -622,27 +644,31 @@ int rhizome_manifest_dump(rhizome_manifest *m, const char *msg)
int rhizome_manifest_finalise(rhizome_manifest *m)
{
/* set fileHexHash */
if (!m->fileHashedP) {
if (rhizome_hash_file(m,m->dataFileName,m->fileHexHash))
return WHY("rhizome_hash_file() failed during finalisation of manifest.");
m->fileHashedP=1;
/* set fileLength and "filesize" var */
if (m->dataFileName[0]) {
struct stat stat;
if (lstat(m->dataFileName, &stat)) {
WHY_perror("lstat");
return WHY("Could not stat() associated file");
}
m->fileLength = stat.st_size;
} else
m->fileLength = 0;
rhizome_manifest_set_ll(m, "filesize", m->fileLength);
/* set fileLength */
if (m->dataFileName[0]) {
struct stat stat;
if (lstat(m->dataFileName, &stat)) {
WHY_perror("lstat");
return WHY("Could not stat() associated file");
}
m->fileLength = stat.st_size;
} else
m->fileLength = 0;
/* set fileHexHash and "filehash" var */
if (m->fileLength != 0) {
if (!m->fileHashedP) {
if (rhizome_hash_file(m, m->dataFileName, m->fileHexHash))
return WHY("rhizome_hash_file() failed during finalisation of manifest.");
m->fileHashedP = 1;
}
rhizome_manifest_set(m, "filehash", m->fileHexHash);
} else {
m->fileHexHash[0] = '\0';
m->fileHashedP = 0;
rhizome_manifest_del(m, "filehash");
}
/* Set file hash and size information */
rhizome_manifest_set(m,"filehash",m->fileHexHash);
rhizome_manifest_set_ll(m,"filesize",m->fileLength);
/* set fileHighestPriority based on group associations.
XXX - Should probably be set as groups are added */

@ -165,7 +165,7 @@ int rhizome_opendb()
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, "DELETE FROM MANIFESTS WHERE filehash IS NULL;");
sqlite_exec_void_loglevel(LOG_LEVEL_WARN, "DELETE FROM FILES WHERE NOT EXISTS( SELECT 1 FROM MANIFESTS WHERE MANIFESTS.filehash = FILES.id);");
sqlite_exec_void_loglevel(LOG_LEVEL_WARN, "DELETE FROM MANIFESTS WHERE NOT EXISTS( SELECT 1 FROM FILES WHERE MANIFESTS.filehash = FILES.id);");
sqlite_exec_void_loglevel(LOG_LEVEL_WARN, "DELETE FROM MANIFESTS WHERE filehash != '' AND NOT EXISTS( SELECT 1 FROM FILES WHERE MANIFESTS.filehash = FILES.id);");
RETURN(0);
}
@ -527,21 +527,14 @@ int rhizome_store_bundle(rhizome_manifest *m)
} else {
/* We don't have the secret for this manifest, so only allow updates if
the self-signature is valid */
if (!m->selfSigned) {
WHY("*** Insert into manifests failed (-2).");
if (!m->selfSigned)
return WHY("Manifest is not signed, and I don't have the key. Manifest might be forged or corrupt.");
}
}
char manifestid[RHIZOME_MANIFEST_ID_STRLEN + 1];
rhizome_manifest_get(m, "id", manifestid, sizeof manifestid);
str_toupper_inplace(manifestid);
char filehash[RHIZOME_FILEHASH_STRLEN + 1];
strncpy(filehash, m->fileHexHash, sizeof filehash);
str_toupper_inplace(filehash);
char *err;
int sql_ret;
sqlite3_stmt *stmt;
@ -550,14 +543,21 @@ int rhizome_store_bundle(rhizome_manifest *m)
unsigned char bar[RHIZOME_BAR_BYTES];
rhizome_manifest_to_bar(m,bar);
// we should add the file in the same transaction, but closing the blob seems to cause some issues.
/* Store the file */
// TODO encrypted payloads - pass encryption key here
if (m->fileLength>0){
if (rhizome_store_file(m,NULL)){
char filehash[RHIZOME_FILEHASH_STRLEN + 1];
if (m->fileLength > 0) {
if (!m->fileHashedP)
return WHY("Manifest payload hash unknown");
strncpy(filehash, m->fileHexHash, sizeof filehash);
str_toupper_inplace(filehash);
// TODO encrypted payloads - pass encryption key here
// We should add the file in the same transaction, but closing the blob seems to cause some issues.
if (rhizome_store_file(m, NULL)) {
WHY("Could not store file");
sql_ret = 1;
}
} else {
filehash[0] = '\0';
}
if (!rhizome_db) rhizome_opendb();
@ -697,6 +697,7 @@ int rhizome_list_manifests(const char *service, const char *sender_sid, const ch
break;
}
const char *q_manifestid = (const char *) sqlite3_column_text(statement, 0);
DEBUGF("id = %s", q_manifestid);
const char *manifestblob = (char *) sqlite3_column_blob(statement, 1);
size_t manifestblobsize = sqlite3_column_bytes(statement, 1); // must call after sqlite3_column_blob()
long long q_version = sqlite3_column_int64(statement, 2);
@ -976,8 +977,6 @@ int rhizome_update_file_priority(const char *fileid)
int rhizome_find_duplicate(const rhizome_manifest *m, rhizome_manifest **found,
int checkVersionP)
{
if (!m->fileHashedP)
return WHY("Manifest payload is not hashed");
const char *service = rhizome_manifest_get(m, "service", NULL, 0);
const char *name = NULL;
const char *sender = NULL;
@ -996,29 +995,37 @@ int rhizome_find_duplicate(const rhizome_manifest *m, rhizome_manifest **found,
return WHYF("Unsupported service '%s'", service);
}
char sqlcmd[1024];
char *s = sqlcmd;
s += snprintf(s, &sqlcmd[sizeof sqlcmd] - s,
"SELECT id, manifest, version FROM manifests"
" WHERE filehash = ?"
);
if (checkVersionP && s < &sqlcmd[sizeof sqlcmd])
s += snprintf(s, sqlcmd + sizeof(sqlcmd) - s, " AND version = ?");
if (s >= &sqlcmd[sizeof sqlcmd])
return WHY("SQL command too long");
strbuf b = strbuf_local(sqlcmd, sizeof sqlcmd);
strbuf_puts(b, "SELECT id, manifest, version FROM manifests WHERE ");
if (m->fileLength != 0) {
if (!m->fileHashedP)
return WHY("Manifest payload is not hashed");
strbuf_puts(b, "filehash = ?");
} else
strbuf_puts(b, "filesize = 0");
if (checkVersionP)
strbuf_puts(b, " AND version = ?");
if (strbuf_overrun(b))
return WHYF("SQL command too long: %s", strbuf_str(b));
int ret = 0;
sqlite3_stmt *statement;
const char *cmdtail;
if (debug&DEBUG_RHIZOME) DEBUGF("sql query: %s",sqlcmd);
if (debug & DEBUG_RHIZOME)
DEBUGF("sql query: %s", sqlcmd);
if (sqlite3_prepare_v2(rhizome_db, sqlcmd, strlen(sqlcmd) + 1, &statement, &cmdtail) != SQLITE_OK) {
ret = WHY(sqlite3_errmsg(rhizome_db));
} else {
int field = 1;
char filehash[RHIZOME_FILEHASH_STRLEN + 1];
strncpy(filehash, m->fileHexHash, sizeof filehash);
str_toupper_inplace(filehash);
if (debug & DEBUG_RHIZOME) DEBUGF("filehash=\"%s\"", filehash);
sqlite3_bind_text(statement, 1, filehash, -1, SQLITE_STATIC);
if (m->fileLength != 0) {
strncpy(filehash, m->fileHexHash, sizeof filehash);
str_toupper_inplace(filehash);
if (debug & DEBUG_RHIZOME)
DEBUGF("filehash=\"%s\"", filehash);
sqlite3_bind_text(statement, field++, filehash, -1, SQLITE_STATIC);
}
if (checkVersionP)
sqlite3_bind_int64(statement, 2, m->version);
sqlite3_bind_int64(statement, field++, m->version);
size_t rows = 0;
while (sqlite3_step(statement) == SQLITE_ROW) {
++rows;
@ -1071,16 +1078,24 @@ int rhizome_find_duplicate(const rhizome_manifest *m, rhizome_manifest **found,
q_manifestid, q_version, blob_version);
++inconsistent;
}
if (!blob_filehash && strcasecmp(blob_filehash, m->fileHexHash)) {
WARNF("MANIFESTS row id=%s joined to FILES row id=%s has inconsistent blob: blob.filehash=%s -- skipped",
q_manifestid, m->fileHexHash, blob_filehash);
++inconsistent;
}
if (blob_filesize != -1 && blob_filesize != m->fileLength) {
WARNF("MANIFESTS row id=%s joined to FILES row id=%s has inconsistent blob: known file size %lld, blob.filesize=%lld -- skipped",
q_manifestid, m->fileHexHash, m->fileLength, blob_filesize);
++inconsistent;
}
if (m->fileLength != 0) {
if (!blob_filehash && strcasecmp(blob_filehash, m->fileHexHash)) {
WARNF("MANIFESTS row id=%s joined to FILES row id=%s has inconsistent blob: blob.filehash=%s -- skipped",
q_manifestid, m->fileHexHash, blob_filehash);
++inconsistent;
}
} else {
if (!blob_filehash) {
WARNF("MANIFESTS row id=%s joined to FILES row id=%s has inconsistent blob: blob.filehash should be absent -- skipped",
q_manifestid, m->fileHexHash);
++inconsistent;
}
}
if (checkVersionP && q_version != m->version) {
WARNF("SELECT query with version=%lld returned incorrect row: manifests.version=%lld -- skipped", m->version, q_version);
++inconsistent;
@ -1182,23 +1197,30 @@ int rhizome_retrieve_manifest(const char *manifestid, rhizome_manifest **mp)
const char *blob_service = rhizome_manifest_get(m, "service", NULL, 0);
if (blob_service == NULL)
ret = WHY("Manifest is missing 'service' field");
long long filesizeq = rhizome_manifest_get_ll(m, "filesize");
if (filesizeq == -1)
ret = WHY("Manifest is missing 'filesize' field");
else
m->fileLength = filesizeq;
const char *blob_filehash = rhizome_manifest_get(m, "filehash", NULL, 0);
if (blob_filehash == NULL)
ret = WHY("Manifest is missing 'filehash' field");
else {
memcpy(m->fileHexHash, blob_filehash, RHIZOME_FILEHASH_STRLEN + 1);
m->fileHashedP = 1;
if (m->fileLength != 0) {
if (blob_filehash == NULL)
ret = WHY("Manifest is missing 'filehash' field");
else {
memcpy(m->fileHexHash, blob_filehash, RHIZOME_FILEHASH_STRLEN + 1);
m->fileHashedP = 1;
}
} else {
if (blob_filehash != NULL)
WARN("Manifest contains spurious 'filehash' field");
m->fileHexHash[0] = '\0';
m->fileHashedP = 0;
}
long long blob_version = rhizome_manifest_get_ll(m, "version");
if (blob_version == -1)
ret = WHY("Manifest is missing 'version' field");
else
m->version = blob_version;
long long filesizeq = rhizome_manifest_get_ll(m, "filesize");
if (filesizeq == -1)
ret = WHY("Manifest is missing 'filesize' field");
else
m->fileLength = filesizeq;
if (ret == 1) {
cli_puts("service"); cli_delim(":");
cli_puts(blob_service); cli_delim("\n");
@ -1208,10 +1230,12 @@ int rhizome_retrieve_manifest(const char *manifestid, rhizome_manifest **mp)
cli_printf("%lld", (long long) sqlite3_column_int64(statement, 2)); cli_delim("\n");
cli_puts("inserttime"); cli_delim(":");
cli_printf("%lld", (long long) sqlite3_column_int64(statement, 3)); cli_delim("\n");
cli_puts("filehash"); cli_delim(":");
cli_puts(m->fileHexHash); cli_delim("\n");
cli_puts("filesize"); cli_delim(":");
cli_printf("%lld", (long long) m->fileLength); cli_delim("\n");
if (m->fileLength != 0) {
cli_puts("filehash"); cli_delim(":");
cli_puts(m->fileHexHash); cli_delim("\n");
}
// Could write the manifest blob to the CLI output here, but that would require the output to
// support byte[] fields as well as String fields.
}

@ -381,20 +381,41 @@ int rhizome_position_candidate(int position)
return 0;
}
void rhizome_import_received_bundle(struct rhizome_manifest *m)
{
// TODO: We already have the manifest struct in memory, should import the bundle
// directly from that, not by writing it to a file and re-reading it!
const char *id = rhizome_manifest_get(m, "id", NULL, 0);
if (id == NULL) {
WHY("Manifest missing ID");
return;
}
if (create_rhizome_import_dir() == -1)
return;
char filename[1024];
if (!FORM_RHIZOME_IMPORT_PATH(filename, "manifest.%s", id))
return;
/* Do really write the manifest unchanged */
m->finalised = 1;
m->manifest_bytes = m->manifest_all_bytes;
if (debug & DEBUG_RHIZOME_RX) {
DEBUGF("manifest bid=%s len=%d has %d signatories", id, m->manifest_bytes, m->sig_count);
dump("manifest", m->manifestdata, m->manifest_all_bytes);
}
if (rhizome_write_manifest_file(m, filename) != -1)
rhizome_bundle_import(m, NULL, id, m->ttl - 1 /* TTL */);
}
/* Verifies manifests as late as possible to avoid wasting time. */
int rhizome_suggest_queue_manifest_import(rhizome_manifest *m, struct sockaddr_in *peerip)
{
IN();
/* must free manifest when done with it */
char *id=rhizome_manifest_get(m,"id",NULL,0);
long long filesize=rhizome_manifest_get_ll(m,"filesize");
char *id = rhizome_manifest_get(m, "id", NULL, 0);
int priority=100; /* normal priority */
int i;
m->version = rhizome_manifest_get_ll(m,"version");
if (debug & DEBUG_RHIZOME_RX)
DEBUGF("Considering manifest import bid=%s version=%lld size=%lld priority=%d:", id, m->version, filesize, priority);
DEBUGF("Considering manifest import bid=%s version=%lld size=%lld priority=%d:", id, m->version, m->fileLength, priority);
if (rhizome_manifest_version_cache_lookup(m)) {
if (debug & DEBUG_RHIZOME_RX)
@ -409,7 +430,20 @@ int rhizome_suggest_queue_manifest_import(rhizome_manifest *m, struct sockaddr_i
DEBUGF(" is new (have version %lld)", stored_version);
}
if (m->fileLength == 0) {
if (rhizome_manifest_verify(m) != 0) {
WHY("Error verifying manifest when considering for import");
/* Don't waste time looking at this manifest again for a while */
rhizome_queue_ignore_manifest(m, peerip, 60000);
rhizome_manifest_free(m);
RETURN(-1);
}
rhizome_import_received_bundle(m);
RETURN(0);
}
/* work out where to put it in the list */
int i;
for(i=0;i<candidate_count;i++)
{
/* If this manifest is already in the list, stop.
@ -421,11 +455,9 @@ int rhizome_suggest_queue_manifest_import(rhizome_manifest *m, struct sockaddr_i
/* duplicate.
XXX - Check versions! We should replace older with newer,
and then update position in queue based on size */
long long list_version=rhizome_manifest_get_ll(candidates[i].manifest, "version");
long long this_version=rhizome_manifest_get_ll(m,"version");
if (list_version>=this_version) {
/* this version is older than the one in the list,
so don't list this one */
long long list_version = rhizome_manifest_get_ll(candidates[i].manifest, "version");
if (list_version >= m->version) {
/* this version is older than the one in the list, so don't list this one */
rhizome_manifest_free(m);
RETURN(0);
} else {
@ -451,7 +483,7 @@ int rhizome_suggest_queue_manifest_import(rhizome_manifest *m, struct sockaddr_i
the list down. */
if (candidates[i].priority>priority
||(candidates[i].priority==priority
&&candidates[i].size>filesize))
&&candidates[i].size>m->fileLength))
break;
}
if (i>=MAX_CANDIDATES) {
@ -482,7 +514,7 @@ int rhizome_suggest_queue_manifest_import(rhizome_manifest *m, struct sockaddr_i
bytes);
/* put new candidate in */
candidates[i].manifest=m;
candidates[i].size=filesize;
candidates[i].size=m->fileLength;
candidates[i].priority=priority;
candidates[i].peer=*peerip;
@ -770,37 +802,11 @@ void rhizome_write_content(rhizome_file_fetch_record *q, char *buffer, int bytes
/* got all of file */
if (debug & DEBUG_RHIZOME_RX)
DEBUGF("Received all of file via rhizome -- now to import it");
{
fclose(q->file);
q->file = NULL;
// TODO: We already have the manifest struct in memory, should import the bundle
// directly from that, not by writing it to a file and re-reading it!
const char *id = rhizome_manifest_get(q->manifest, "id", NULL, 0);
if (id == NULL)
{ WHY("Manifest missing ID"); return; }
if (create_rhizome_import_dir() == -1)
return;
char filename[1024];
if (!FORM_RHIZOME_IMPORT_PATH(filename,"manifest.%s", id))
return;
/* Do really write the manifest unchanged */
if (debug & DEBUG_RHIZOME_RX) {
DEBUGF("manifest has %d signatories",q->manifest->sig_count);
DEBUGF("manifest bid=%s len=%d",
rhizome_manifest_get(q->manifest, "id", NULL, 0),
q->manifest->manifest_bytes);
dump("manifest",&q->manifest->manifestdata[0],
q->manifest->manifest_all_bytes);
}
q->manifest->finalised=1;
q->manifest->manifest_bytes=q->manifest->manifest_all_bytes;
if (rhizome_write_manifest_file(q->manifest,filename) != -1) {
rhizome_bundle_import(q->manifest, NULL, id,
q->manifest->ttl - 1 /* TTL */);
}
rhizome_manifest_free(q->manifest);
q->manifest = NULL;
}
fclose(q->file);
q->file = NULL;
rhizome_import_received_bundle(q->manifest);
rhizome_manifest_free(q->manifest);
q->manifest = NULL;
rhizome_fetch_close(q);
return;
}
@ -822,128 +828,131 @@ void rhizome_fetch_poll(struct sched_ent *alarm)
return;
}
switch(q->state)
{
switch(q->state) {
case RHIZOME_FETCH_CONNECTING:
case RHIZOME_FETCH_SENDINGHTTPREQUEST:
rhizome_fetch_write(q);
break;
case RHIZOME_FETCH_RXFILE:
/* Keep reading until we have the promised amount of data */
sigPipeFlag=0;
errno=0;
char buffer[8192];
int bytes=read(q->alarm.poll.fd,buffer,8192);
/* If we got some data, see if we have found the end of the HTTP request */
if (bytes>0)
rhizome_write_content(q, buffer, bytes);
break;
case RHIZOME_FETCH_RXHTTPHEADERS:
/* Keep reading until we have two CR/LFs in a row */
sigPipeFlag=0;
errno=0;
bytes=read(q->alarm.poll.fd,&q->request[q->request_len],
1024-q->request_len-1);
if (sigPipeFlag||((bytes==0)&&(errno==0))) {
/* broken pipe, so close connection */
if (debug&DEBUG_RHIZOME)
DEBUG("Closing rhizome fetch connection due to sigpipe");
rhizome_fetch_close(q);
return;
}
/* If we got some data, see if we have found the end of the HTTP request */
if (bytes>0) {
int lfcount=0;
int i=q->request_len-160;
// reset timeout
unschedule(&q->alarm);
q->alarm.alarm=overlay_gettime_ms() + RHIZOME_IDLE_TIMEOUT;
q->alarm.deadline = q->alarm.alarm + RHIZOME_IDLE_TIMEOUT;
schedule(&q->alarm);
if (i<0) i=0;
q->request_len+=bytes;
if (q->request_len<1024)
q->request[q->request_len]=0;
for(;i<(q->request_len+bytes);i++)
{
switch(q->request[i]) {
case '\n': lfcount++; break;
case '\r': /* ignore CR */ break;
case 0: /* ignore NUL (telnet inserts them) */ break;
default: lfcount=0; break;
}
if (lfcount==2) break;
}
if (debug&DEBUG_RHIZOME)
dump("http reply headers",(unsigned char *)q->request,lfcount==2?i:q->request_len);
if (lfcount==2) {
/* We have the response headers, so parse.
(we may also have some bytes of content, so we need to be a little
careful) */
/* Terminate string at end of headers */
q->request[i]=0;
/* Get HTTP result code */
char *s=strstr(q->request,"HTTP/1.0 ");
if (!s) {
if (debug&DEBUG_RHIZOME) DEBUGF("HTTP response lacked HTTP/1.0 response code.");
rhizome_fetch_close(q);
return;
}
int http_response_code=strtoll(&s[9],NULL,10);
if (http_response_code!=200) {
if (debug&DEBUG_RHIZOME) DEBUGF("Rhizome web server returned %d != 200 OK",http_response_code);
rhizome_fetch_close(q);
return;
}
/* Get content length */
s=strstr(q->request,"Content-length: ");
if (!s) {
if (debug&DEBUG_RHIZOME)
DEBUGF("Missing Content-Length: header.");
rhizome_fetch_close(q);
return;
}
q->file_len=strtoll(&s[16],NULL,10);
if (q->file_len<0) {
if (debug&DEBUG_RHIZOME)
DEBUGF("Illegal file size (%d).",q->file_len);
rhizome_fetch_close(q);
return;
}
/* Okay, we have both, and are all set.
File is already open, so just write out any initial bytes of the
file we read, and update state flag.
*/
q->state=RHIZOME_FETCH_RXFILE;
int fileRxBytes=q->request_len-(i+1);
if (fileRxBytes>0)
rhizome_write_content(q, &q->request[i+1], fileRxBytes);
case RHIZOME_FETCH_RXFILE: {
/* Keep reading until we have the promised amount of data */
char buffer[8192];
sigPipeFlag = 0;
int bytes = read_nonblock(q->alarm.poll.fd, buffer, sizeof buffer);
/* If we got some data, see if we have found the end of the HTTP request */
if (bytes > 0) {
rhizome_write_content(q, buffer, bytes);
} else {
if (debug & DEBUG_RHIZOME_RX)
DEBUG("Empty read, closing connection");
rhizome_fetch_close(q);
return;
}
}
if (sigPipeFlag) {
if (debug & DEBUG_RHIZOME_RX)
DEBUG("Received SIGPIPE, closing connection");
rhizome_fetch_close(q);
return;
}
}
break;
case RHIZOME_FETCH_RXHTTPHEADERS: {
/* Keep reading until we have two CR/LFs in a row */
sigPipeFlag = 0;
int bytes = read_nonblock(q->alarm.poll.fd, &q->request[q->request_len], 1024 - q->request_len - 1);
/* If we got some data, see if we have found the end of the HTTP reply */
if (bytes > 0) {
// reset timeout
unschedule(&q->alarm);
q->alarm.alarm = overlay_gettime_ms() + RHIZOME_IDLE_TIMEOUT;
q->alarm.deadline = q->alarm.alarm + RHIZOME_IDLE_TIMEOUT;
schedule(&q->alarm);
q->request_len += bytes;
if (http_header_complete(q->request, q->request_len, bytes + 4)) {
if (debug & DEBUG_RHIZOME_RX)
DEBUGF("Got HTTP reply: %s", alloca_toprint(160, (unsigned char *)q->request, q->request_len));
/* We have all the reply headers, so parse them, taking care of any following bytes of
content. */
char *p = NULL;
if (!str_startswith(q->request, "HTTP/1.0 ", &p)) {
if (debug&DEBUG_RHIZOME_RX)
DEBUGF("Malformed HTTP reply: missing HTTP/1.0 preamble");
rhizome_fetch_close(q);
return;
}
int http_response_code = 0;
char *nump;
for (nump = p; isdigit(*p); ++p)
http_response_code = http_response_code * 10 + *p - '0';
if (p == nump || *p != ' ') {
if (debug&DEBUG_RHIZOME_RX)
DEBUGF("Malformed HTTP reply: missing decimal status code");
rhizome_fetch_close(q);
return;
}
if (http_response_code != 200) {
if (debug & DEBUG_RHIZOME_RX)
DEBUGF("Failed HTTP request: rhizome server returned %d != 200 OK", http_response_code);
rhizome_fetch_close(q);
return;
}
// This loop will terminate, because http_header_complete() above found at least
// "\n\n" at the end of the header, and probably "\r\n\r\n".
while (*p++ != '\n')
;
// Iterate over header lines until the last blank line.
long long content_length = -1;
while (*p != '\r' && *p != '\n') {
if (strcase_startswith(p, "Content-Length:", &p)) {
while (*p == ' ')
++p;
content_length = 0;
for (nump = p; isdigit(*p); ++p)
content_length = content_length * 10 + *p - '0';
if (p == nump || (*p != '\r' && *p != '\n')) {
if (debug & DEBUG_RHIZOME_RX) {
DEBUGF("Invalid HTTP reply: malformed Content-Length header");
rhizome_fetch_close(q);
return;
}
}
}
while (*p++ != '\n')
;
}
if (*p == '\r')
++p;
++p; // skip '\n' at end of blank line
if (content_length == -1) {
if (debug & DEBUG_RHIZOME_RX)
DEBUGF("Invalid HTTP reply: missing Content-Length header");
rhizome_fetch_close(q);
return;
}
q->file_len = content_length;
/* We have all we need. The file is already open, so just write out any initial bytes of
the body we read.
*/
q->state = RHIZOME_FETCH_RXFILE;
int content_bytes = q->request + q->request_len - p;
if (content_bytes > 0)
rhizome_write_content(q, p, content_bytes);
}
} else {
if (debug & DEBUG_RHIZOME_RX)
DEBUG("Empty read, closing connection");
rhizome_fetch_close(q);
return;
}
if (sigPipeFlag) {
if (debug & DEBUG_RHIZOME_RX)
DEBUG("Received SIGPIPE, closing connection");
rhizome_fetch_close(q);
return;
}
}
break;
default:
if (debug&DEBUG_RHIZOME)
if (debug & DEBUG_RHIZOME_RX)
DEBUG("Closing rhizome fetch connection due to illegal/unimplemented state.");
rhizome_fetch_close(q);
return;

@ -72,13 +72,12 @@ typedef struct rhizome_http_request {
} rhizome_http_request;
int rhizome_server_free_http_request(rhizome_http_request *r);
int rhizome_server_http_send_bytes(rhizome_http_request *r);
int rhizome_server_parse_http_request(rhizome_http_request *r);
int rhizome_server_simple_http_response(rhizome_http_request *r,int result, char *response);
int rhizome_server_http_response_header(rhizome_http_request *r,int result,
char *mime_type,unsigned long long bytes);
int rhizome_server_sql_query_fill_buffer(rhizome_http_request *r, char *table, char *column);
static int rhizome_server_free_http_request(rhizome_http_request *r);
static int rhizome_server_http_send_bytes(rhizome_http_request *r);
static int rhizome_server_parse_http_request(rhizome_http_request *r);
static int rhizome_server_simple_http_response(rhizome_http_request *r, int result, const char *response);
static int rhizome_server_http_response_header(rhizome_http_request *r, int result, const char *mime_type, unsigned long long bytes);
static int rhizome_server_sql_query_fill_buffer(rhizome_http_request *r, char *table, char *column);
#define RHIZOME_SERVER_MAX_LIVE_REQUESTS 32
@ -226,7 +225,7 @@ void rhizome_client_poll(struct sched_ent *alarm)
/* Keep reading until we have two CR/LFs in a row */
r->request[r->request_length] = '\0';
sigPipeFlag=0;
int bytes = read_nonblock(r->alarm.poll.fd, &r->request[r->request_length], RHIZOME_HTTP_REQUEST_MAXLEN - r->request_length - 1);
int bytes = read_nonblock(r->alarm.poll.fd, &r->request[r->request_length], RHIZOME_HTTP_REQUEST_MAXLEN - r->request_length);
/* If we got some data, see if we have found the end of the HTTP request */
if (bytes > 0) {
// reset inactivity timer
@ -234,23 +233,8 @@ void rhizome_client_poll(struct sched_ent *alarm)
r->alarm.deadline = r->alarm.alarm + RHIZOME_IDLE_TIMEOUT;
unschedule(&r->alarm);
schedule(&r->alarm);
int i = r->request_length - 160;
if (i<0) i=0;
r->request_length+=bytes;
if (r->request_length<RHIZOME_HTTP_REQUEST_MAXLEN)
r->request[r->request_length]=0;
if (0)
dump("request", (unsigned char *)r->request,r->request_length);
int lfcount;
for(lfcount = 0; lfcount < 2 && i < r->request_length + bytes; ++i) {
switch (r->request[i]) {
case '\n': ++lfcount; break;
case '\r': break;
case '\0': break; // ignore NUL (telnet inserts them)
default: lfcount = 0; break;
}
}
if (lfcount == 2) {
r->request_length += bytes;
if (http_header_complete(r->request, r->request_length, bytes + 4)) {
/* We have the request. Now parse it to see if we can respond to it */
rhizome_server_parse_http_request(r);
}
@ -275,7 +259,6 @@ void rhizome_client_poll(struct sched_ent *alarm)
return;
}
void rhizome_server_poll(struct sched_ent *alarm)
{
struct sockaddr addr;
@ -284,7 +267,8 @@ void rhizome_server_poll(struct sched_ent *alarm)
while ((sock = accept(rhizome_server_socket, &addr, &addr_len)) != -1) {
if (addr.sa_family == AF_INET) {
struct sockaddr_in *peerip = (struct sockaddr_in *)&addr;
INFOF("HTTP ACCEPT addrlen=%u family=%u port=%u addr=%u.%u.%u.%u",
if (debug & DEBUG_RHIZOME_TX)
DEBUGF("HTTP ACCEPT addrlen=%u family=%u port=%u addr=%u.%u.%u.%u",
addr_len, peerip->sin_family, peerip->sin_port,
((unsigned char*)&peerip->sin_addr.s_addr)[0],
((unsigned char*)&peerip->sin_addr.s_addr)[1],
@ -292,41 +276,49 @@ void rhizome_server_poll(struct sched_ent *alarm)
((unsigned char*)&peerip->sin_addr.s_addr)[3]
);
} else {
INFOF("HTTP ACCEPT addrlen=%u family=%u data=%s",
if (debug & DEBUG_RHIZOME_TX)
DEBUGF("HTTP ACCEPT addrlen=%u family=%u data=%s",
addr_len, addr.sa_family, alloca_tohex((unsigned char *)addr.sa_data, sizeof addr.sa_data));
}
rhizome_http_request *request = calloc(sizeof(rhizome_http_request),1);
/* We are now trying to read the HTTP request */
request->request_type=RHIZOME_HTTP_REQUEST_RECEIVING;
request->alarm.function = rhizome_client_poll;
connection_stats.name="rhizome_client_poll";
request->alarm.stats=&connection_stats;
request->alarm.poll.fd=sock;
request->alarm.poll.events=POLLIN;
request->alarm.alarm = overlay_gettime_ms()+RHIZOME_IDLE_TIMEOUT;
request->alarm.deadline = request->alarm.alarm+RHIZOME_IDLE_TIMEOUT;
// watch for the incoming http request
watch(&request->alarm);
// set an inactivity timeout to close the connection
schedule(&request->alarm);
rhizome_http_request *request = calloc(sizeof(rhizome_http_request), 1);
if (request == NULL) {
WHYF_perror("calloc(%u, 1)", sizeof(rhizome_http_request));
WHY("Cannot respond to request, out of memory");
} else {
/* We are now trying to read the HTTP request */
request->request_type=RHIZOME_HTTP_REQUEST_RECEIVING;
request->alarm.function = rhizome_client_poll;
connection_stats.name="rhizome_client_poll";
request->alarm.stats=&connection_stats;
request->alarm.poll.fd=sock;
request->alarm.poll.events=POLLIN;
request->alarm.alarm = overlay_gettime_ms()+RHIZOME_IDLE_TIMEOUT;
request->alarm.deadline = request->alarm.alarm+RHIZOME_IDLE_TIMEOUT;
// watch for the incoming http request
watch(&request->alarm);
// set an inactivity timeout to close the connection
schedule(&request->alarm);
}
}
if (errno != EAGAIN) {
WARN_perror("accept");
}
}
int rhizome_server_free_http_request(rhizome_http_request *r)
static int rhizome_server_free_http_request(rhizome_http_request *r)
{
unwatch(&r->alarm);
unschedule(&r->alarm);
close(r->alarm.poll.fd);
if (r->buffer&&r->buffer_size) free(r->buffer);
if (r->blob) sqlite3_blob_close(r->blob);
if (r->buffer)
free(r->buffer);
if (r->blob)
sqlite3_blob_close(r->blob);
free(r);
return 0;
}
int rhizome_server_sql_query_http_response(rhizome_http_request *r,
static int rhizome_server_sql_query_http_response(rhizome_http_request *r,
char *column,char *table,char *query_body,
int bytes_per_row,int dehexP)
{
@ -339,10 +331,17 @@ int rhizome_server_sql_query_http_response(rhizome_http_request *r,
the body, although encryption is not yet implemented here.
*/
if (r->buffer) { free(r->buffer); r->buffer=NULL; }
r->buffer_size=16384;
r->buffer=malloc(r->buffer_size);
if (!r->buffer) return WHY_perror("malloc");
if (r->buffer == NULL || r->buffer_size < 16384) {
if (r->buffer)
free(r->buffer);
r->buffer_size = 16384;
r->buffer = malloc(r->buffer_size);
if (r->buffer == NULL) {
r->buffer_size = 0;
WHY_perror("malloc");
return WHY("Cannot send response, out of memory");
}
}
r->buffer_length=0;
r->buffer_offset=0;
r->source_record_size=bytes_per_row;
@ -351,10 +350,9 @@ int rhizome_server_sql_query_http_response(rhizome_http_request *r,
/* Work out total response length */
long long response_bytes=256+r->source_count*r->source_record_size;
rhizome_server_http_response_header(r,200,"servalproject.org/rhizome-list",
response_bytes);
rhizome_server_http_response_header(r, 200, "servalproject.org/rhizome-list", response_bytes);
if (debug & DEBUG_RHIZOME_TX)
DEBUGF("headers consumed %d bytes",r->buffer_length);
DEBUGF("headers consumed %d bytes", r->buffer_length);
/* Clear and prepare response header */
bzero(&r->buffer[r->buffer_length],256);
@ -389,7 +387,7 @@ int rhizome_server_sql_query_http_response(rhizome_http_request *r,
return rhizome_server_sql_query_fill_buffer(r, table, column);
}
int rhizome_server_sql_query_fill_buffer(rhizome_http_request *r, char *table, char *column)
static int rhizome_server_sql_query_fill_buffer(rhizome_http_request *r, char *table, char *column)
{
unsigned char blob_value[r->source_record_size*2+1];
@ -486,37 +484,90 @@ int rhizome_server_sql_query_fill_buffer(rhizome_http_request *r, char *table, c
return 0;
}
static int strcmp_prefix(char *str, const char *prefix, char **afterp)
int http_header_complete(const char *buf, size_t len, size_t tail)
{
while (*prefix && *str && *prefix == *str)
++prefix, ++str;
if (*prefix)
return (unsigned char)*str - (unsigned char)*prefix;
if (afterp)
*afterp = str;
return 0;
const char *bufend = buf + len;
if (tail < len)
buf = bufend - tail;
int count = 0;
for (; count < 2 && buf != bufend; ++buf) {
switch (*buf) {
case '\n': ++count; break;
case '\r': break;
case '\0': break; // ignore NUL (telnet inserts them)
default: count = 0; break;
}
}
return count == 2;
}
int rhizome_server_parse_http_request(rhizome_http_request *r)
/* Check if a given string starts with a given sub-string. If so, return 1 and, if afterp is not
NULL, set *afterp to point to the character immediately following the substring. Otherwise
return 0.
This function is used to parse HTTP headers and responses, which are typically not
nul-terminated, but are held in a buffer which has an associated length. To avoid this function
running past the end of the buffer, the caller must ensure that the buffer contains a sub-string
that is not part of the sub-string being sought, eg, "\r\n\r\n" as detected by
http_header_complete(). This guarantees that this function will return nonzero before running
past the end of the buffer.
@author Andrew Bettison <andrew@servalproject.com>
*/
int str_startswith(char *str, const char *substring, char **afterp)
{
while (*substring && *substring == *str)
++substring, ++str;
if (*substring)
return 0;
if (afterp)
*afterp = str;
return 1;
}
/* Case-insensitive form of str_startswith().
*/
int strcase_startswith(char *str, const char *substring, char **afterp)
{
while (*substring && *str && toupper(*substring) == toupper(*str))
++substring, ++str;
if (*substring)
return 0;
if (afterp)
*afterp = str;
return 1;
}
static int rhizome_server_parse_http_request(rhizome_http_request *r)
{
/* Switching to writing, so update the call-back */
r->alarm.poll.events=POLLOUT;
watch(&r->alarm);
/* Clear request type flags */
r->request_type=0;
char path[1024];
if (sscanf(r->request, "GET %1024s HTTP/1.%*1[01]%*[\r\n]", path) != 1) {
if (debug & DEBUG_RHIZOME_TX)
DEBUGF("Received malformed HTTP request: %s", alloca_toprint(120, (unsigned char *)r->request, r->request_length));
rhizome_server_simple_http_response(r, 400, "<html><h1>Malformed request</h1></html>\r\n");
} else {
// Start building up a response.
r->request_type = 0;
// Parse the HTTP "GET" line.
char *path = NULL;
size_t pathlen = 0;
if (str_startswith(r->request, "GET ", &path)) {
char *p;
// This loop is guaranteed to terminate before the end of the buffer, because we know that the
// buffer contains at least "\n\n" and maybe "\r\n\r\n" at the end of the header block.
for (p = path; !isspace(*p); ++p)
;
pathlen = p - path;
if ( str_startswith(p, " HTTP/1.", &p)
&& (str_startswith(p, "0", &p) || str_startswith(p, "1", &p))
&& (str_startswith(p, "\r\n", &p) || str_startswith(p, "\n", &p))
)
path[pathlen] = '\0';
else
path = NULL;
}
if (path) {
char *id = NULL;
if (debug & DEBUG_RHIZOME_TX)
DEBUGF("GET %s", path);
DEBUGF("GET %s", alloca_toprint(1024, (unsigned char *)path, pathlen));
if (strcmp(path, "/favicon.ico") == 0) {
r->request_type = RHIZOME_HTTP_REQUEST_FAVICON;
rhizome_server_http_response_header(r, 200, "image/vnd.microsoft.icon", favicon_len);
rhizome_server_http_response_header(r, 200, "image/vnd.microsoft.icon", favicon_len);
} else if (strcmp(path, "/rhizome/groups") == 0) {
/* Return the list of known groups */
rhizome_server_sql_query_http_response(r, "id", "groups", "from groups", 32, 1);
@ -526,7 +577,7 @@ int rhizome_server_parse_http_request(rhizome_http_request *r)
} else if (strcmp(path, "/rhizome/bars") == 0) {
/* Return the list of known BARs */
rhizome_server_sql_query_http_response(r, "bar", "manifests", "from manifests", 32, 0);
} else if (strcmp_prefix(path, "/rhizome/file/", &id) == 0) {
} else if (str_startswith(path, "/rhizome/file/", &id)) {
/* Stream the specified payload */
if (!rhizome_str_is_file_hash(id)) {
rhizome_server_simple_http_response(r, 400, "<html><h1>Invalid payload ID</h1></html>\r\n");
@ -546,12 +597,16 @@ int rhizome_server_parse_http_request(rhizome_http_request *r)
r->request_type |= RHIZOME_HTTP_REQUEST_BLOB;
}
}
} else if (strcmp_prefix(path, "/rhizome/manifest/", &id) == 0) {
/* TODO: Stream the specified manifest */
} else if (str_startswith(path, "/rhizome/manifest/", &id)) {
// TODO: Stream the specified manifest
rhizome_server_simple_http_response(r, 500, "<html><h1>Not implemented</h1></html>\r\n");
} else {
rhizome_server_simple_http_response(r, 404, "<html><h1>Not found</h1></html>\r\n");
}
} else {
if (debug & DEBUG_RHIZOME_TX)
DEBUGF("Received malformed HTTP request: %s", alloca_toprint(120, (unsigned char *)r->request, r->request_length));
rhizome_server_simple_http_response(r, 400, "<html><h1>Malformed request</h1></html>\r\n");
}
/* Try sending data immediately. */
@ -562,43 +617,89 @@ int rhizome_server_parse_http_request(rhizome_http_request *r)
/* Return appropriate message for HTTP response codes, both known and unknown. */
#define A_VALUE_GREATER_THAN_FOUR (2+3)
char *httpResultString(int id) {
switch (id) {
case 200: return "OK"; break;
case 206: return "Partial Content"; break;
case 404: return "Not found"; break;
default:
case A_VALUE_GREATER_THAN_FOUR:
if (id>4) return "A suffusion of yellow";
/* The following MUST be the longest string returned by this function */
else return "THE JUDGEMENT OF KING WEN: Chun Signifies Difficulties At Outset, As Of Blade Of Grass Pushing Up Against Stone.";
static const char *httpResultString(int response_code) {
switch (response_code) {
case 200: return "OK";
case 206: return "Partial Content";
case 404: return "Not found";
case 500: return "Internal server error";
default: return "A suffusion of yellow";
}
}
int rhizome_server_simple_http_response(rhizome_http_request *r,int result, char *response)
struct http_response {
unsigned int result_code;
const char * content_type;
unsigned long long content_length;
const char * body;
};
static strbuf strbuf_build_http_response(strbuf sb, const struct http_response *h)
{
if (r->buffer) free(r->buffer);
r->buffer_size=strlen(response)+strlen("HTTP/1.0 000 \r\n\r\nContent-type: text/html\r\nContent-length: 0000\r\n\r\n")+strlen(httpResultString(result))+strlen(response)+100;
strbuf_sprintf(sb, "HTTP/1.0 %03u %s\r\n", h->result_code, httpResultString(h->result_code));
strbuf_sprintf(sb, "Content-type: %s\r\n", h->content_type);
strbuf_sprintf(sb, "Content-length: %llu\r\n", h->content_length);
strbuf_puts(sb, "\r\n");
if (h->body)
strbuf_puts(sb, h->body);
return sb;
}
r->buffer=(unsigned char *)malloc(r->buffer_size);
snprintf((char *)r->buffer,r->buffer_size,"HTTP/1.0 %03d %s\r\nContent-type: text/html\r\nContent-length: %d\r\n\r\n%s",result,httpResultString(result),(int)strlen(response),response);
r->buffer_size=strlen((char *)r->buffer)+1;
r->buffer_length=r->buffer_size-1;
r->buffer_offset=0;
r->request_type=RHIZOME_HTTP_REQUEST_FROMBUFFER;
static int rhizome_server_set_response(rhizome_http_request *r, const struct http_response *h)
{
strbuf b = strbuf_local((char *) r->buffer, r->buffer_size);
strbuf_build_http_response(b, h);
if (r->buffer == NULL || strbuf_overrun(b)) {
// Need a bigger buffer
if (r->buffer)
free(r->buffer);
r->buffer_size = strbuf_count(b) + 1;
r->buffer = malloc(r->buffer_size);
if (r->buffer == NULL) {
WHYF_perror("malloc(%u)", r->buffer_size);
r->buffer_size = 0;
return WHY("Cannot send response, out of memory");
}
strbuf_init(b, (char *) r->buffer, r->buffer_size);
strbuf_build_http_response(b, h);
if (strbuf_overrun(b))
return WHYF("Bug! Cannot send response, buffer not big enough");
}
r->buffer_length = strbuf_len(b);
r->buffer_offset = 0;
r->request_type |= RHIZOME_HTTP_REQUEST_FROMBUFFER;
if (debug & DEBUG_RHIZOME_TX)
DEBUGF("Sending HTTP response: %s", alloca_toprint(120, r->buffer, r->buffer_length));
return 0;
}
static int rhizome_server_simple_http_response(rhizome_http_request *r, int result, const char *response)
{
struct http_response hr;
hr.result_code = result;
hr.content_type = "text/html";
hr.content_length = strlen(response);
hr.body = response;
return rhizome_server_set_response(r, &hr);
}
static int rhizome_server_http_response_header(rhizome_http_request *r, int result, const char *mime_type, unsigned long long bytes)
{
struct http_response hr;
hr.result_code = result;
hr.content_type = mime_type;
hr.content_length = bytes;
hr.body = NULL;
return rhizome_server_set_response(r, &hr);
}
/*
return codes:
1: connection still open.
0: connection finished.
<0: an error occurred.
*/
int rhizome_server_http_send_bytes(rhizome_http_request *r)
static int rhizome_server_http_send_bytes(rhizome_http_request *r)
{
// keep writing until the write would block or we run out of data
while(r->request_type){
@ -703,33 +804,3 @@ int rhizome_server_http_send_bytes(rhizome_http_request *r)
if (!r->request_type) return rhizome_server_free_http_request(r);
return 1;
}
int rhizome_server_http_response_header(rhizome_http_request *r,int result,
char *mime_type,unsigned long long bytes)
{
int min_buff = strlen("HTTP/1.0 000 \r\nContent-type: \r\nContent-length: \r\n\r\n")
+strlen(httpResultString(result))
+strlen(mime_type)+20;
if (min_buff+bytes > 65536){
min_buff = 65536;
}else{
min_buff += bytes;
}
if (r->buffer_size < min_buff) {
if (r->buffer)
free(r->buffer);
r->buffer=(unsigned char *)malloc(min_buff);
r->buffer_size=min_buff;
}
snprintf((char *)r->buffer,r->buffer_size,"HTTP/1.0 %03d %s\r\nContent-type: %s\r\nContent-length: %lld\r\n\r\n",result,httpResultString(result),mime_type,bytes);
r->buffer_length=strlen((char *)r->buffer);
r->buffer_offset=0;
r->request_type|=RHIZOME_HTTP_REQUEST_FROMBUFFER;
return 0;
}

@ -795,7 +795,7 @@ char *catv(const char *data, char *buf, size_t len);
int dump(char *name, unsigned char *addr, size_t len);
char *toprint(char *dstStr, size_t dstChars, const unsigned char *srcBuf, size_t srcBytes);
#define alloca_toprint(dstlen,buf,len) toprint((char *)alloca((dstlen) + 1), (dstlen) + 1, (buf), (len) + 1)
#define alloca_toprint(dstlen,buf,len) toprint((char *)alloca((dstlen) + 1), (dstlen) + 1, (buf), (len))
#define alloca_tohex(buf,len) tohex((char *)alloca((len)*2+1), (buf), (len))
#define alloca_tohex_sid(sid) alloca_tohex((sid), SID_SIZE)

@ -14,6 +14,26 @@ char *indent(int n)
int senderSet=0;
unsigned char senderAddress[32];
#ifdef STANDALONE
int main(int argc,char **argv)
{
int i,n;
int len;
unsigned char buff[8192];
for(n=0;n<1024;n++)
{
int i;
len=random()%8192;
for(i=0;i<len;i++) buff[i]=random()&0xff;
serval_packetvisualise(stdout,"Fuzz Test",buff,len);
}
return 0;
}
#endif
int serval_packetvisualise_renderaddress(FILE *f,unsigned char *packet,int *ofs,int senderP)
{

@ -28,6 +28,8 @@ rexp_date='[0-9]\{1,\}'
# Utility function:
# - create N identities in the current instance (I)
# - set variable SID to SID of first identity
# - set variable SID{I} to SID of first identity, eg, SIDA
# - set variables SID{I}{1..N} to SIDs of identities, eg, SIDA1, SIDA2...
# - set variables DID{I}{1..N} to DIDs of identities, eg, DIDA1, DIDA2...
# - set variables NAME{I}{1..N} to names of identities, eg, NAMEA1, NAMEA2...
@ -47,6 +49,10 @@ create_rhizome_identities() {
local namevar=NAME$instance_name$i
extract_stdout_keyvalue $sidvar sid "$rexp_sid"
tfw_log "$sidvar=${!sidvar}"
if [ $i -eq 1 ]; then
SID="${!sidvar}"
eval SID$instance_name="$SID"
fi
extract_stdout_keyvalue_optional DID$instance_name$i did "$rexp_did" && tfw_log "$didvar=${!didvar}"
extract_stdout_keyvalue_optional NAME$instance_name$i name ".*" && tfw_log "$namevar=${!namevar}"
done
@ -74,15 +80,19 @@ assert_manifest_complete() {
assertGrep "$manifest" "^BK=$rexp_bundlekey\$"
assertGrep "$manifest" "^date=$rexp_date\$"
assertGrep "$manifest" "^version=$rexp_version\$"
assertGrep "$manifest" "^filehash=$rexp_filehash\$"
assertGrep "$manifest" "^filesize=$rexp_filesize\$"
if grep -q '^filesize=0$' "$manifest"; then
assertGrep --matches=0 "$manifest" "^filehash="
else
assertGrep "$manifest" "^filehash=$rexp_filehash\$"
fi
if grep -q '^service=file$' "$manifest"; then
assertGrep "$manifest" "^name="
fi
}
assert_rhizome_list() {
assertStdoutLineCount '==' $(($# + 2))
assertStdoutLineCount --stderr '==' $(($# + 2))
assertStdoutIs --stderr --line=1 -e '11\n'
assertStdoutIs --stderr --line=2 -e 'service:id:version:date:.inserttime:.selfsigned:filesize:filehash:sender:recipient:name\n'
local filename
@ -105,7 +115,7 @@ assert_stdout_add_file() {
if replayStdout | grep -q '^service:file$'; then
opt_name=true
fi
fieldnames='service|manifestid|secret|filehash|filesize|name'
fieldnames='service|manifestid|secret|filesize|filehash|name'
for arg; do
case "$arg" in
!+($fieldnames))
@ -125,8 +135,12 @@ assert_stdout_add_file() {
${opt_service:-true} && assertStdoutGrep --matches=1 "^service:$re_service\$"
${opt_manifestid:-true} && assertStdoutGrep --matches=1 "^manifestid:$re_manifestid\$"
${opt_secret:-true} && assertStdoutGrep --matches=1 "^secret:$re_secret\$"
${opt_filehash:-true} && assertStdoutGrep --matches=1 "^filehash:$re_filehash\$"
${opt_filesize:-true} && assertStdoutGrep --matches=1 "^filesize:$re_filesize\$"
if replayStdout | grep -q '^filesize:0$'; then
assertStdoutGrep --matches=0 "^filehash:"
else
${opt_filehash:-true} && assertStdoutGrep --matches=1 "^filehash:$re_filehash\$"
fi
}
assert_stdout_import_bundle() {
@ -137,16 +151,19 @@ assert_stdout_import_bundle() {
unpack_manifest_for_grep() {
local filename="$1"
re_service="$rexp_service"
if [ -n "$filename" ]; then
re_filesize=$(( $(cat "$filename" | wc -c) + 0 ))
else
re_filesize=0
fi
compute_filehash re_filehash "$filename"
re_manifestid="$rexp_manifestid"
re_version="$rexp_version"
re_secret="$rexp_bundlesecret"
re_name=$(escape_grep_basic "${filename##*/}")
local filesize=$(sed -n -e '/^filesize=/s///p' "$filename.manifest" 2>/dev/null)
if [ "$filesize" = 0 ]; then
re_filesize=0
re_filehash=
re_name="\($re_name\)\{0,1\}"
else
re_filesize=$(( $(cat "$filename" | wc -c) + 0 ))
compute_filehash re_filehash "$filename"
fi
# If there is a manifest file that looks like it matches this payload
# 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)
@ -220,6 +237,10 @@ extract_manifest_BK() {
extract_manifest "$1" "$2" BK "$rexp_bundlekey"
}
extract_manifest_filesize() {
extract_manifest "$1" "$2" filesize "$rexp_filesize"
}
extract_manifest_filehash() {
extract_manifest "$1" "$2" filehash "$rexp_filehash"
}

@ -65,6 +65,11 @@ Options:
-E, --stop-on-error Do not execute any tests after an ERROR occurs
-F, --stop-on-failure Do not execute any tests after a FAIL occurs
--filter=PREFIX Only execute tests whose names start with PREFIX
--filter=N Only execute test number N
--filter=M-N Only execute tests with numbers in range M-N inclusive
--filter=-N Only execute tests with numbers <= N
--filter=N- Only execute tests with numbers >= N
--filter=M,N,... Only execute tests with number M or N or ...
"
}
@ -155,6 +160,7 @@ runTests() {
_tfw_find_tests "${filters[@]}"
# Iterate through all test cases, starting a new test whenever the number of
# running tests is less than the job limit.
_tfw_testcount=0
_tfw_passcount=0
_tfw_failcount=0
_tfw_errorcount=0
@ -162,8 +168,12 @@ runTests() {
_tfw_running_pids=()
_tfw_test_number_watermark=0
local testNumber
local testPosition=0
for ((testNumber = 1; testNumber <= ${#_tfw_tests[*]}; ++testNumber)); do
testName="${_tfw_tests[$(($testNumber - 1))]}"
[ -z "$testName" ] && continue
let ++testPosition
let ++_tfw_testcount
# Wait for any existing child process to finish.
while [ $njobs -ne 0 -a ${#_tfw_running_pids[*]} -ge $njobs ]; do
_tfw_harvest_processes
@ -172,11 +182,11 @@ runTests() {
$_tfw_stop_on_error && [ $_tfw_errorcount -ne 0 ] && break
$_tfw_stop_on_failure && [ $_tfw_failcount -ne 0 ] && break
# Start the next test in a child process.
_tfw_echo_intro $testNumber $testName
_tfw_echo_intro $testPosition $testNumber $testName
[ $njobs -ne 1 ] && echo
(
_tfw_unique=$!
echo "$testNumber $testName" >"$_tfw_results_dir/$_tfw_unique"
echo "$testPosition $testNumber $testName" >"$_tfw_results_dir/$_tfw_unique"
_tfw_tmp=/tmp/_tfw-$_tfw_unique
trap '_tfw_status=$?; rm -rf "$_tfw_tmp"; exit $_tfw_status' EXIT SIGHUP SIGINT SIGTERM
local start_time=$(_tfw_timestamp)
@ -208,7 +218,7 @@ runTests() {
1) result=FAIL;;
0) result=PASS;;
esac
echo "$testNumber $testName $result" >"$_tfw_results_dir/$_tfw_unique"
echo "$testPosition $testNumber $testName $result" >"$_tfw_results_dir/$_tfw_unique"
{
echo "Name: $testName"
echo "Result: $result"
@ -238,14 +248,14 @@ runTests() {
rm -rf "$_tfw_tmpdir"
trap - EXIT SIGHUP SIGINT SIGTERM
# Echo result summary and exit with success if no failures or errors.
s=$([ ${#_tfw_tests[*]} -eq 1 ] || echo s)
echo "${#_tfw_tests[*]} test$s, $_tfw_passcount pass, $_tfw_failcount fail, $_tfw_errorcount error"
s=$([ $_tfw_testcount -eq 1 ] || echo s)
echo "$_tfw_testcount test$s, $_tfw_passcount pass, $_tfw_failcount fail, $_tfw_errorcount error"
[ $_tfw_fatalcount -eq 0 -a $_tfw_failcount -eq 0 -a $_tfw_errorcount -eq 0 ]
}
_tfw_echo_intro() {
local docvar="doc_$2"
echo -n "$1. ${!docvar:-$2}..."
local docvar="doc_$3"
echo -n "$2. ${!docvar:-$3}..."
[ $1 -gt $_tfw_test_number_watermark ] && _tfw_test_number_watermark=$1
}
@ -263,9 +273,10 @@ _tfw_harvest_processes() {
surviving_pids+=($pid)
elif [ -s "$_tfw_results_dir/$pid" ]; then
set -- $(<"$_tfw_results_dir/$pid")
local testNumber="$1"
local testName="$2"
local result="$3"
local testPosition="$1"
local testNumber="$2"
local testName="$3"
local result="$4"
case "$result" in
ERROR)
let _tfw_errorcount=_tfw_errorcount+1
@ -287,16 +298,16 @@ _tfw_harvest_processes() {
_tfw_echo_result "$result"
echo
elif lines=$($_tfw_tput lines); then
local travel=$(($_tfw_test_number_watermark - $testNumber + 1))
local travel=$(($_tfw_test_number_watermark - $testPosition + 1))
if [ $travel -gt 0 -a $travel -lt $lines ] && $_tfw_tput cuu $travel ; then
_tfw_echo_intro $testNumber $testName
_tfw_echo_intro $testPosition $testNumber $testName
echo -n " "
_tfw_echo_result "$result"
echo
$_tfw_tput cud $(($_tfw_test_number_watermark - $testNumber))
travel=$(($_tfw_test_number_watermark - $testPosition))
[ $travel -gt 0 ] && $_tfw_tput cud $travel
fi
else
_tfw_echo_intro $testNumber $testName
echo -n "$testNumber. ... "
_tfw_echo_result "$result"
echo
@ -1043,27 +1054,76 @@ _tfw_checkTerminfo() {
}
# Return a list of test names in the _tfw_tests array variable, in the order
# that the test_TestName functions were defined.
# that the test_TestName functions were defined. Test names must start with
# an alphabetic character (not numeric or '_').
_tfw_find_tests() {
_tfw_tests=()
_tfw_shopt -s extdebug
local name
for name in $(builtin declare -F |
sed -n -e '/^declare -f test_./s/^declare -f test_//p' |
sed -n -e '/^declare -f test_[A-Za-z]/s/^declare -f test_//p' |
while read name; do builtin declare -F "test_$name"; done |
sort --key 2,2n --key 3,3 |
sed -e 's/^test_//' -e 's/[ ].*//')
do
local number=$((${#_tfw_tests[*]} + 1))
local testName=
if [ $# -eq 0 ]; then
_tfw_tests+=("$name")
testName="$name"
else
local filter
for filter; do
case "$name" in
"$filter"*) _tfw_tests+=("$name"); break;;
case "$filter" in
+([0-9]))
if [ $number -eq $filter ]; then
testName="$name"
break
fi
;;
+([0-9])*(,+([0-9])))
local oIFS="$IFS"
IFS=,
local -a numbers=($filter)
IFS="$oIFS"
local n
for n in ${numbers[*]}; do
if [ $number -eq $n ]; then
testName="$name"
break 2
fi
done
;;
+([0-9])-)
local start=${filter%-}
if [ $number -ge $start ]; then
testName="$name"
break
fi
;;
-+([0-9]))
local end=${filter#-}
if [ $number -le $end ]; then
testName="$name"
break
fi
;;
+([0-9])-+([0-9]))
local start=${filter%-*}
local end=${filter#*-}
if [ $number -ge $start -a $number -le $end ]; then
testName="$name"
break
fi
;;
*)
case "$name" in
"$filter"*) testName="$name"; break;;
esac
;;
esac
done
fi
_tfw_tests+=("$testName")
done
_tfw_shopt_restore
}

@ -140,6 +140,8 @@ test_AddEmpty() {
assertGrep .manifest '^service=file$'
assertGrep .manifest '^name=$'
assertGrep .manifest '^filesize=0$'
executeOk_servald rhizome list ''
assert_rhizome_list ''
}
doc_AddThenList="List contains one file after one add"

@ -63,45 +63,123 @@ bundle_received_by() {
return 0
}
doc_FileTransferNew="One new bundle single transfer"
setup_FileTransferNew() {
setup_common() {
setup_servald
setup_rhizome
assert_no_servald_processes
start_servald_instances +A +B
echo 'File one' >file1
set_instance +A
executeOk_servald rhizome add file '' '' file1 file1.manifest
extract_manifest_id BID file1.manifest
extract_manifest_version VERSION file1.manifest
set_instance +B
}
test_FileTransferNew() {
wait_until bundle_received_by +B
executeOk_servald rhizome list ''
assert_rhizome_list file1!
}
doc_FileMultiTransferNew="One new bundle multi transfer"
setup_FileMultiTransferNew() {
setup_servald
setup_rhizome
assert_no_servald_processes
start_servald_instances +A +B +C +D +E
echo 'File one' >file1
set_instance +A
executeOk_servald rhizome add file '' '' file1 file1.manifest
extract_manifest_id BID file1.manifest
extract_manifest_version VERSION file1.manifest
extract_manifest_vars() {
local manifest="${1?}"
extract_manifest_id BID "$manifest"
extract_manifest_version VERSION "$manifest"
extract_manifest_filesize FILESIZE "$manifest"
FILEHASH=
if [ "$FILESIZE" != '0' ]; then
extract_manifest_filehash FILEHASH "$manifest"
fi
}
test_FileMultiTransferNew() {
add_file() {
local name="$1"
[ -e "$name" ] || echo 'File $name' >"$name"
local sidvar="SID$instance_name"
executeOk_servald rhizome add file "${!sidvar}" '' "$name" "$name.manifest"
executeOk_servald rhizome list ''
assert_rhizome_list "$name"
extract_manifest_vars "$name.manifest"
}
update_file() {
local orig_name="$1"
local new_name="$2"
[ -e "$new_name" ] || echo 'File $new_name' >"$new_name"
local sidvar="SID$instance_name"
[ "$new_name" != "$orig_name" ] && cp "$orig_name.manifest" "$new_name.manifest"
sed -i -e '/^date=/d;/^filehash=/d;/^filesize=/d;/^version=/d;/^name=/d' "$new_name.manifest"
executeOk_servald rhizome add file "${!sidvar}" '' "$new_name" "$new_name.manifest"
executeOk_servald rhizome list ''
assert_rhizome_list "$new_name"
extract_manifest_vars "$new_name.manifest"
}
doc_FileTransfer="New bundle and update transfer to one node"
setup_FileTransfer() {
setup_common
set_instance +A
add_file file1
start_servald_instances +A +B
}
test_FileTransfer() {
wait_until bundle_received_by +B
set_instance +B
assert_received file1
set_instance +A
update_file file1 file2
set_instance +B
wait_until bundle_received_by +B
assert_received file2
}
doc_FileTransferBig="Big new bundle transfers to one node"
setup_FileTransferBig() {
setup_common
set_instance +A
dd if=/dev/urandom of=file1 bs=1k count=1k 2>&1
echo x >>file1
ls -l file1
add_file file1
start_servald_instances +A +B
}
test_FileTransferBig() {
wait_until bundle_received_by +B
set_instance +B
assert_received file1
}
assert_received() {
local name="${1?}"
executeOk_servald rhizome list ''
assert_rhizome_list "$name!"
local _hash
if [ -s "$name" ]; then
extract_manifest_filehash _hash "$name.manifest"
executeOk_servald rhizome extract file "$_hash" extracted
assert cmp "$name" extracted
fi
}
doc_FileTransferMulti="New bundle transfers to four nodes"
setup_FileTransferMulti() {
setup_common
set_instance +A
add_file file1
start_servald_instances +A +B +C +D +E
}
test_FileTransferMulti() {
wait_until bundle_received_by +B +C +D +E
local I
for I in +B +C +D +E; do
set_instance $I
executeOk_servald rhizome list ''
assert_rhizome_list file1!
assert_received file1
done
}
doc_FileTransferDelete="Payload deletion transfers to one node"
setup_FileTransferDelete() {
setup_common
set_instance +A
add_file file1
start_servald_instances +A +B
wait_until bundle_received_by +B
set_instance +A
>file1_2
update_file file1 file1_2
}
test_FileTransferDelete() {
wait_until bundle_received_by +B
set_instance +B
assert_received file1_2
}
runTests "$@"