Rhizome Java API: get manifest

Fixes assertion violation in GET /restful/rhizome/<BID>.rhm when <BID>
not found
This commit is contained in:
Andrew Bettison 2014-07-01 21:52:12 +09:30
parent db8ee79a4e
commit 93e67ede63
13 changed files with 788 additions and 141 deletions

View File

@ -0,0 +1,44 @@
/**
* Copyright (C) 2014 Serval Project Inc.
*
* This file is part of Serval Software (http://www.servalproject.org)
*
* Serval Software is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* This source code is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this source code; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
package org.servalproject.servaldna;
import java.nio.ByteBuffer;
public class BundleSecret extends AbstractId {
@Override
public int getBinarySize() {
return 32;
}
public BundleSecret(String hex) throws InvalidHexException {
super(hex);
}
public BundleSecret(ByteBuffer b) throws InvalidBinaryException {
super(b);
}
public BundleSecret(byte[] binary) throws InvalidBinaryException {
super(binary);
}
}

View File

@ -32,10 +32,12 @@ import java.net.URLConnection;
import java.net.HttpURLConnection;
import org.servalproject.codec.Base64;
import org.servalproject.servaldna.SubscriberId;
import org.servalproject.servaldna.BundleId;
import org.servalproject.servaldna.ServalDCommand;
import org.servalproject.servaldna.ServalDInterfaceException;
import org.servalproject.servaldna.rhizome.RhizomeCommon;
import org.servalproject.servaldna.rhizome.RhizomeBundleList;
import org.servalproject.servaldna.rhizome.RhizomeManifestBundle;
import org.servalproject.servaldna.meshms.MeshMSCommon;
import org.servalproject.servaldna.meshms.MeshMSConversationList;
import org.servalproject.servaldna.meshms.MeshMSMessageList;
@ -67,6 +69,11 @@ public class ServalDClient implements ServalDHttpConnectionFactory
return list;
}
public RhizomeManifestBundle rhizomeManifest(BundleId bid) throws ServalDInterfaceException, IOException
{
return RhizomeCommon.rhizomeManifest(this, bid);
}
public MeshMSConversationList meshmsListConversations(SubscriberId sid) throws ServalDInterfaceException, IOException, MeshMSException
{
MeshMSConversationList list = new MeshMSConversationList(this, sid);

View File

@ -1,78 +0,0 @@
/**
* Copyright (C) 2014 Serval Project Inc.
*
* This file is part of Serval Software (http://www.servalproject.org)
*
* Serval Software is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* This source code is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this source code; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
package org.servalproject.servaldna.rhizome;
import org.servalproject.servaldna.BundleId;
import org.servalproject.servaldna.SubscriberId;
import org.servalproject.servaldna.FileHash;
public class RhizomeBundle {
public final int _rowNumber;
public final int _id;
public final String _token;
public final String service;
public final BundleId id;
public final long version;
public final long date;
public final long _inserttime;
public final SubscriberId _author;
public final int _fromhere;
public final long filesize;
public final FileHash filehash;
public final SubscriberId sender;
public final SubscriberId recipient;
public final String name;
protected RhizomeBundle(int rowNumber,
int _id,
String _token,
String service,
BundleId id,
long version,
long date,
long _inserttime,
SubscriberId _author,
int _fromhere,
long filesize,
FileHash filehash,
SubscriberId sender,
SubscriberId recipient,
String name)
{
this._rowNumber = rowNumber;
this._id = _id;
this._token = _token;
this.service = service;
this.id = id;
this.version = version;
this.date = date;
this._inserttime = _inserttime;
this._author = _author;
this._fromhere = _fromhere;
this.filesize = filesize;
this.filehash = filehash;
this.sender = sender;
this.recipient = recipient;
this.name = name;
}
}

View File

@ -89,7 +89,7 @@ public class RhizomeBundleList {
}
}
public RhizomeBundle nextBundle() throws ServalDInterfaceException, IOException
public RhizomeListBundle nextBundle() throws ServalDInterfaceException, IOException
{
try {
Object tok = json.nextToken();
@ -103,22 +103,26 @@ public class RhizomeBundleList {
else
json.pushToken(tok);
Map<String,Object> row = table.consumeRowArray(json);
return new RhizomeBundle(
rowCount++,
(int)row.get("_id"),
(String)row.get(".token"),
(String)row.get("service"),
(BundleId)row.get("id"),
(long)row.get("version"),
(long)row.get("date"),
(long)row.get(".inserttime"),
(SubscriberId)row.get(".author"),
(int)row.get(".fromhere"),
(long)row.get("filesize"),
(FileHash)row.get("filehash"),
(SubscriberId)row.get("sender"),
(SubscriberId)row.get("recipient"),
(String)row.get("name"));
return new RhizomeListBundle(
new RhizomeManifest((BundleId)row.get("id"),
(long)row.get("version"),
(long)row.get("filesize"),
(FileHash)row.get("filehash"),
(SubscriberId)row.get("sender"),
(SubscriberId)row.get("recipient"),
null, // BK
null, // crypt
null, // tail
(long)row.get("date"),
(String)row.get("service"),
(String)row.get("name")),
rowCount++,
(int)row.get("_id"),
(String)row.get(".token"),
(long)row.get(".inserttime"),
(SubscriberId)row.get(".author"),
(int)row.get(".fromhere")
);
}
catch (JSONInputException e) {
throw new ServalDInterfaceException(e);

View File

@ -20,15 +20,47 @@
package org.servalproject.servaldna.rhizome;
import java.lang.reflect.InvocationTargetException;
import java.util.Map;
import java.util.List;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import org.servalproject.servaldna.ServalDInterfaceException;
import org.servalproject.json.JSONTokeniser;
import org.servalproject.json.JSONInputException;
import org.servalproject.servaldna.BundleId;
import org.servalproject.servaldna.SubscriberId;
import org.servalproject.servaldna.BundleSecret;
import org.servalproject.servaldna.ServalDHttpConnectionFactory;
import org.servalproject.servaldna.ServalDInterfaceException;
import org.servalproject.servaldna.ServalDFailureException;
public class RhizomeCommon
{
protected static InputStream receiveResponse(HttpURLConnection conn, int expected_response_code) throws IOException, ServalDInterfaceException
{
int[] expected_response_codes = { expected_response_code };
return receiveResponse(conn, expected_response_codes);
}
protected static InputStream receiveResponse(HttpURLConnection conn, int[] expected_response_codes) throws IOException, ServalDInterfaceException
{
for (int code: expected_response_codes) {
if (conn.getResponseCode() == code)
return conn.getInputStream();
}
if (!conn.getContentType().equals("application/json"))
throw new ServalDInterfaceException("unexpected HTTP Content-Type: " + conn.getContentType());
if (conn.getResponseCode() == HttpURLConnection.HTTP_FORBIDDEN) {
JSONTokeniser json = new JSONTokeniser(new InputStreamReader(conn.getErrorStream(), "US-ASCII"));
Status status = decodeRestfulStatus(json);
throw new ServalDInterfaceException("unexpected Rhizome failure, \"" + status.message + "\"");
}
throw new ServalDInterfaceException("unexpected HTTP response code: " + conn.getResponseCode());
}
protected static JSONTokeniser receiveRestfulResponse(HttpURLConnection conn, int expected_response_code) throws IOException, ServalDInterfaceException
{
int[] expected_response_codes = { expected_response_code };
@ -37,20 +69,10 @@ public class RhizomeCommon
protected static JSONTokeniser receiveRestfulResponse(HttpURLConnection conn, int[] expected_response_codes) throws IOException, ServalDInterfaceException
{
InputStream in = receiveResponse(conn, expected_response_codes);
if (!conn.getContentType().equals("application/json"))
throw new ServalDInterfaceException("unexpected HTTP Content-Type: " + conn.getContentType());
if (conn.getResponseCode() == HttpURLConnection.HTTP_FORBIDDEN) {
JSONTokeniser json = new JSONTokeniser(new InputStreamReader(conn.getErrorStream(), "US-ASCII"));
Status status = decodeRestfulStatus(json);
throw new ServalDInterfaceException("unexpected Rhizome failure, \"" + status.message + "\"");
}
for (int code: expected_response_codes) {
if (conn.getResponseCode() == code) {
JSONTokeniser json = new JSONTokeniser(new InputStreamReader(conn.getInputStream(), "US-ASCII"));
return json;
}
}
throw new ServalDInterfaceException("unexpected HTTP response code: " + conn.getResponseCode());
return new JSONTokeniser(new InputStreamReader(in, "US-ASCII"));
}
private static class Status {
@ -78,4 +100,79 @@ public class RhizomeCommon
}
}
public static RhizomeManifestBundle rhizomeManifest(ServalDHttpConnectionFactory connector, BundleId bid) throws IOException, ServalDInterfaceException
{
HttpURLConnection conn = connector.newServalDHttpConnection("/restful/rhizome/" + bid.toHex() + ".rhm");
conn.connect();
InputStream in = RhizomeCommon.receiveResponse(conn, HttpURLConnection.HTTP_OK);
if (!conn.getContentType().equals("rhizome-manifest/text"))
throw new ServalDInterfaceException("unexpected HTTP Content-Type: " + conn.getContentType());
RhizomeManifest manifest;
try {
manifest = RhizomeManifest.fromTextFormat(in);
}
catch (RhizomeManifestParseException e) {
throw new ServalDInterfaceException("malformed manifest from daemon", e);
}
finally {
in.close();
}
Map<String,List<String>> headers = conn.getHeaderFields();
for (Map.Entry<String,List<String>> e: headers.entrySet()) {
for (String v: e.getValue()) {
System.err.println("received header " + e.getKey() + ": " + v);
}
}
long insertTime = headerUnsignedLong(conn, "Serval-Rhizome-Bundle-Inserttime");
SubscriberId author = header(conn, "Serval-Rhizome-Bundle-Author", SubscriberId.class);
BundleSecret secret = header(conn, "Serval-Rhizome-Bundle-Secret", BundleSecret.class);
return new RhizomeManifestBundle(manifest, insertTime, author, secret);
}
private static String headerString(HttpURLConnection conn, String header) throws ServalDInterfaceException
{
String str = conn.getHeaderField(header);
if (str == null)
throw new ServalDInterfaceException("missing header field: " + header);
return str;
}
private static int headerInteger(HttpURLConnection conn, String header) throws ServalDInterfaceException
{
String str = headerString(conn, header);
try {
return Integer.parseInt(str);
}
catch (NumberFormatException e) {
}
throw new ServalDInterfaceException("invalid header field: " + header + ": " + str);
}
private static long headerUnsignedLong(HttpURLConnection conn, String header) throws ServalDInterfaceException
{
String str = headerString(conn, header);
try {
long value = Long.parseLong(str);
if (value >= 0)
return value;
}
catch (NumberFormatException e) {
}
throw new ServalDInterfaceException("invalid header field: " + header + ": " + str);
}
private static <T> T header(HttpURLConnection conn, String header, Class<T> cls) throws ServalDInterfaceException
{
String str = headerString(conn, header);
try {
return (T) cls.getConstructor(String.class).newInstance(str);
}
catch (InvocationTargetException e) {
throw new ServalDInterfaceException("invalid header field: " + header + ": " + str, e.getTargetException());
}
catch (Exception e) {
throw new ServalDInterfaceException("invalid header field: " + header + ": " + str, e);
}
}
}

View File

@ -0,0 +1,53 @@
/**
* Copyright (C) 2014 Serval Project Inc.
*
* This file is part of Serval Software (http://www.servalproject.org)
*
* Serval Software is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* This source code is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this source code; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
package org.servalproject.servaldna.rhizome;
import org.servalproject.servaldna.SubscriberId;
public class RhizomeListBundle {
public final int rowNumber;
public final int rowId;
public final String token;
public final long insertTime;
public final SubscriberId author;
public final int fromHere;
public final RhizomeManifest manifest;
protected RhizomeListBundle(RhizomeManifest manifest,
int rowNumber,
int rowId,
String token,
long insertTime,
SubscriberId author,
int fromHere)
{
this.manifest = manifest;
this.rowNumber = rowNumber;
this.rowId = rowId;
this.token = token;
this.insertTime = insertTime;
this.author = author;
this.fromHere = fromHere;
}
}

View File

@ -0,0 +1,311 @@
/**
* Copyright (C) 2014 Serval Project Inc.
*
* This file is part of Serval Software (http://www.servalproject.org)
*
* Serval Software is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* This source code is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this source code; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
package org.servalproject.servaldna.rhizome;
import java.util.Map;
import java.util.HashMap;
import java.util.HashSet;
import java.io.UnsupportedEncodingException;
import java.io.IOException;
import java.io.InputStream;
import java.io.ByteArrayOutputStream;
import java.io.OutputStreamWriter;
import org.servalproject.servaldna.AbstractId;
import org.servalproject.servaldna.SubscriberId;
import org.servalproject.servaldna.BundleId;
import org.servalproject.servaldna.FileHash;
import org.servalproject.servaldna.BundleKey;
public class RhizomeManifest {
public final static int TEXT_FORMAT_MAX_SIZE = 8192;
// Core fields used for routing and expiry (cannot be null)
public final BundleId id;
public final long version;
public final long filesize;
// Principal fields (can be null)
public final FileHash filehash; // null iff filesize == 0
public final SubscriberId sender;
public final SubscriberId recipient;
public final BundleKey BK;
public final Long tail; // null iff not a journal
public final Integer crypt;
// Optional fields (all can be null)
public final Long date; // can be null
public final String service;
public final String name;
private HashMap<String,String> extraFields;
private byte[] signatureBlock;
private byte[] textFormat;
protected RhizomeManifest( BundleId id,
long version,
long filesize,
FileHash filehash,
SubscriberId sender,
SubscriberId recipient,
BundleKey BK,
Integer crypt,
Long tail,
Long date,
String service,
String name
)
{
assert id != null;
if (filesize == 0)
assert filehash == null;
else
assert filehash != null;
this.id = id;
this.version = version;
this.filesize = filesize;
this.filehash = filehash;
this.sender = sender;
this.recipient = recipient;
this.BK = BK;
this.crypt = crypt;
this.tail = tail;
this.date = date;
this.service = service;
this.name = name;
this.extraFields = null;
this.signatureBlock = null;
this.textFormat = null;
}
/** Return the Rhizome manifest in its text format representation.
*
* @author Andrew Bettison <andrew@servalproject.com>
*/
public byte[] toTextFormat() throws RhizomeManifestSizeException
{
if (textFormat == null) {
try {
ByteArrayOutputStream os = new ByteArrayOutputStream();
OutputStreamWriter osw = new OutputStreamWriter(os, "US-ASCII");
osw.write("id=" + id.toHex() + "\n");
osw.write("version=" + version + "\n");
osw.write("filesize=" + filesize + "\n");
if (filehash != null)
osw.write("filehash=" + filehash.toHex() + "\n");
if (sender != null)
osw.write("sender=" + sender.toHex() + "\n");
if (recipient != null)
osw.write("recipient=" + recipient.toHex() + "\n");
if (BK != null)
osw.write("BK=" + BK.toHex() + "\n");
if (crypt != null)
osw.write("crypt=" + crypt + "\n");
if (tail != null)
osw.write("tail=" + tail + "\n");
if (date != null)
osw.write("date=" + date + "\n");
if (service != null)
osw.write("service=" + service + "\n");
if (name != null)
osw.write("name=" + name + "\n");
for (Map.Entry<String,String> e: extraFields.entrySet())
osw.write(e.getKey() + "=" + e.getValue() + "\n");
osw.flush();
if (signatureBlock != null) {
os.write(0);
os.write(signatureBlock);
}
osw.close();
if (os.size() > TEXT_FORMAT_MAX_SIZE)
throw new RhizomeManifestSizeException("manifest text format overflow", os.size(), TEXT_FORMAT_MAX_SIZE);
textFormat = os.toByteArray();
}
catch (IOException e) {
// should not happen with ByteArrayOutputStream
return new byte[0];
}
}
byte[] ret = new byte[textFormat.length];
System.arraycopy(textFormat, 0, ret, 0, ret.length);
return ret;
}
/** Construct a Rhizome manifest from its text format representation.
*
* @author Andrew Bettison <andrew@servalproject.com>
*/
static public RhizomeManifest fromTextFormat(byte[] bytes) throws RhizomeManifestParseException
{
return fromTextFormat(bytes, 0, bytes.length);
}
/** Construct a Rhizome manifest from its text format representation.
*
* @author Andrew Bettison <andrew@servalproject.com>
*/
static public RhizomeManifest fromTextFormat(byte[] bytes, int off, int len) throws RhizomeManifestParseException
{
// The signature block follows the first nul character at the start of a line.
byte[] sigblock = null;
int proplen = len;
for (int i = 0; i < len; ++i) {
if (bytes[off + i] == 0 && (i == 0 || bytes[off + i - 1] == '\n')) {
sigblock = new byte[len - i - 1];
System.arraycopy(bytes, off + i + 1, sigblock, 0, sigblock.length);
proplen = i;
break;
}
}
String text;
try {
text = new String(bytes, off, proplen, "US-ASCII");
}
catch (UnsupportedEncodingException e) {
throw new RhizomeManifestParseException(e);
}
BundleId id = null;
Long version = null;
Long filesize = null;
FileHash filehash = null;
SubscriberId sender = null;
SubscriberId recipient = null;
BundleKey BK = null;
Integer crypt = null;
Long tail = null;
Long date = null;
String service = null;
String name = null;
HashMap<String,String> extras = new HashMap<String,String>();
int pos = 0;
int lnum = 1;
while (pos < text.length()) {
int nl = text.indexOf('\n', pos);
if (nl == -1)
nl = text.length();
int field = pos;
if (!isFieldNameFirstChar(text.charAt(field)))
throw new RhizomeManifestParseException("invalid field name at line " + lnum + ": " + text.substring(pos, nl - pos));
++field;
while (isFieldNameChar(text.charAt(field)))
++field;
assert field < nl;
if (text.charAt(field) != '=')
throw new RhizomeManifestParseException("invalid field name at line " + lnum + ": " + text.substring(pos, nl - pos));
String fieldName = text.substring(pos, field);
String fieldValue = text.substring(field + 1, nl);
HashSet<String> fieldNames = new HashSet<String>(50);
try {
if (fieldNames.contains(fieldName))
throw new RhizomeManifestParseException("duplicate field at line " + lnum + ": " + text.substring(pos, nl - pos));
fieldNames.add(fieldName);
if (fieldName.equals("id"))
id = new BundleId(fieldValue);
else if (fieldName.equals("version"))
version = parseUnsignedLong(fieldValue);
else if (fieldName.equals("filesize"))
filesize = parseUnsignedLong(fieldValue);
else if (fieldName.equals("filehash"))
filehash = new FileHash(fieldValue);
else if (fieldName.equals("sender"))
sender = new SubscriberId(fieldValue);
else if (fieldName.equals("recipient"))
recipient = new SubscriberId(fieldValue);
else if (fieldName.equals("BK"))
BK = new BundleKey(fieldValue);
else if (fieldName.equals("crypt"))
crypt = Integer.parseInt(fieldValue);
else if (fieldName.equals("tail"))
tail = parseUnsignedLong(fieldValue);
else if (fieldName.equals("date"))
date = parseUnsignedLong(fieldValue);
else if (fieldName.equals("service"))
service = fieldValue;
else if (fieldName.equals("name"))
name = fieldValue;
else
extras.put(fieldName, fieldValue);
}
catch (AbstractId.InvalidHexException e) {
throw new RhizomeManifestParseException("invalid value at line " + lnum + ": " + text.substring(pos, nl - pos), e);
}
catch (NumberFormatException e) {
throw new RhizomeManifestParseException("invalid value at line " + lnum + ": " + text.substring(pos, nl - pos), e);
}
pos = nl + 1;
}
if (id == null)
throw new RhizomeManifestParseException("missing 'id' field");
if (version == null)
throw new RhizomeManifestParseException("missing 'version' field");
if (filesize == null)
throw new RhizomeManifestParseException("missing 'filesize' field");
if (filesize != 0 && filehash == null)
throw new RhizomeManifestParseException("missing 'filehash' field");
else if (filesize == 0 && filehash != null)
throw new RhizomeManifestParseException("spurious 'filehash' field");
RhizomeManifest m = new RhizomeManifest(id, version, filesize, filehash, sender, recipient, BK, crypt, tail, date, service, name);
m.extraFields = extras;
m.signatureBlock = sigblock;
m.textFormat = new byte[len];
System.arraycopy(bytes, off, m.textFormat, 0, m.textFormat.length);
return m;
}
/** Convenience method: construct a Rhizome manifest from all the bytes read from a given
* InputStream.
*
* @author Andrew Bettison <andrew@servalproject.com>
*/
static public RhizomeManifest fromTextFormat(InputStream in) throws IOException, RhizomeManifestParseException
{
byte[] bytes = new byte[TEXT_FORMAT_MAX_SIZE];
int n = 0;
int offset = 0;
while (offset < bytes.length && (n = in.read(bytes, offset, bytes.length - offset)) != -1)
offset += n;
assert offset <= bytes.length;
if (offset == bytes.length)
n = in.read();
if (n != -1)
throw new RhizomeManifestParseException("input stream too long");
return fromTextFormat(bytes, 0, offset);
}
private static boolean isFieldNameFirstChar(char c)
{
return (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z');
}
private static boolean isFieldNameChar(char c)
{
return (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || (c >= '0' && c <= '9');
}
private static Long parseUnsignedLong(String text) throws NumberFormatException
{
Long value = Long.valueOf(text);
if (value < 0)
throw new NumberFormatException("negative value not allowed");
return value;
}
}

View File

@ -0,0 +1,55 @@
/**
* Copyright (C) 2014 Serval Project Inc.
*
* This file is part of Serval Software (http://www.servalproject.org)
*
* Serval Software is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* This source code is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this source code; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
package org.servalproject.servaldna.rhizome;
import org.servalproject.servaldna.SubscriberId;
import org.servalproject.servaldna.BundleSecret;
import org.servalproject.servaldna.ServalDInterfaceException;
public class RhizomeManifestBundle {
public final long insertTime;
public final SubscriberId author;
public final BundleSecret secret;
public final RhizomeManifest manifest;
protected RhizomeManifestBundle(RhizomeManifest manifest,
long insertTime,
SubscriberId author,
BundleSecret secret)
{
this.manifest = manifest;
this.insertTime = insertTime;
this.author = author;
this.secret = secret;
}
public byte[] manifestText() throws ServalDInterfaceException
{
try {
return manifest.toTextFormat();
}
catch (RhizomeManifestSizeException e) {
throw new ServalDInterfaceException("manifest text overflow", e);
}
}
}

View File

@ -0,0 +1,42 @@
/**
* Copyright (C) 2014 Serval Project Inc.
*
* This file is part of Serval Software (http://www.servalproject.org)
*
* Serval Software is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* This source code is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this source code; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
package org.servalproject.servaldna.rhizome;
/**
* Thrown when a Rhizome manifest cannot be parsed from its text format.
*
* @author Andrew Bettison <andrew@servalproject.com>
*/
public class RhizomeManifestParseException extends Exception
{
public RhizomeManifestParseException(String message) {
super(message);
}
public RhizomeManifestParseException(Throwable cause) {
super(cause);
}
public RhizomeManifestParseException(String message, Throwable cause) {
super(message, cause);
}
}

View File

@ -0,0 +1,48 @@
/**
* Copyright (C) 2014 Serval Project Inc.
*
* This file is part of Serval Software (http://www.servalproject.org)
*
* Serval Software is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* This source code is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this source code; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
package org.servalproject.servaldna.rhizome;
import java.io.File;
/**
* Thrown when the text or binary format of a Rhizome manifest is too long to fit in a limited-size
* byte stream.
*
* @author Andrew Bettison <andrew@servalproject.com>
*/
public class RhizomeManifestSizeException extends Exception
{
private long mSize;
private long mMaxSize;
public RhizomeManifestSizeException(String message, long size, long maxSize)
{
super(message + " (" + size + " bytes exceeds " + maxSize + ")");
mSize = size;
mMaxSize = maxSize;
}
public RhizomeManifestSizeException(File manifestFile, long maxSize)
{
this(manifestFile.toString(), manifestFile.length(), maxSize);
}
}

View File

@ -21,39 +21,50 @@
package org.servalproject.test;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.FileOutputStream;
import org.servalproject.servaldna.ServalDClient;
import org.servalproject.servaldna.ServalDInterfaceException;
import org.servalproject.servaldna.ServerControl;
import org.servalproject.servaldna.SubscriberId;
import org.servalproject.servaldna.rhizome.RhizomeBundle;
import org.servalproject.servaldna.BundleId;
import org.servalproject.servaldna.rhizome.RhizomeManifest;
import org.servalproject.servaldna.rhizome.RhizomeListBundle;
import org.servalproject.servaldna.rhizome.RhizomeBundleList;
import org.servalproject.servaldna.rhizome.RhizomeManifestBundle;
public class Rhizome {
static String manifestFields(RhizomeManifest manifest, String sep)
{
return "id=" + manifest.id + sep +
"version=" + manifest.version + sep +
"filesize=" + manifest.filesize + sep +
"filehash=" + manifest.filehash + sep +
"sender=" + manifest.sender + sep +
"recipient=" + manifest.recipient + sep +
"date=" + manifest.date + sep +
"service=" + manifest.service + sep +
"name=" + manifest.name + sep +
"BK=" + manifest.BK;
}
static void rhizome_list() throws ServalDInterfaceException, IOException, InterruptedException
{
ServalDClient client = new ServerControl().getRestfulClient();
RhizomeBundleList list = null;
try {
list = client.rhizomeListBundles();
RhizomeBundle bundle;
RhizomeListBundle bundle;
while ((bundle = list.nextBundle()) != null) {
System.out.println(
"_id=" + bundle._id +
", .token=" + bundle._token +
", service=" + bundle.service +
", id=" + bundle.id +
", version=" + bundle.version +
", date=" + bundle.date +
", .inserttime=" + bundle._inserttime +
", .author=" + bundle._author +
", .fromhere=" + bundle._fromhere +
", filesize=" + bundle.filesize +
", filehash=" + bundle.filehash +
", sender=" + bundle.sender +
", recipient=" + bundle.recipient +
", name=" + bundle.name
);
"_rowId=" + bundle.rowId +
", _token=" + bundle.token +
", _insertTime=" + bundle.insertTime +
", _author=" + bundle.author +
", _fromHere=" + bundle.fromHere +
", " + manifestFields(bundle.manifest, ", ")
);
}
}
finally {
@ -63,6 +74,22 @@ public class Rhizome {
System.exit(0);
}
static void rhizome_manifest(BundleId bid, String dstpath) throws ServalDInterfaceException, IOException, InterruptedException
{
ServalDClient client = new ServerControl().getRestfulClient();
RhizomeManifestBundle bundle = client.rhizomeManifest(bid);
System.out.println(
"_insertTime=" + bundle.insertTime + "\n" +
"_author=" + bundle.author + "\n" +
"_secret=" + bundle.secret + "\n" +
manifestFields(bundle.manifest, "\n") + "\n"
);
FileOutputStream out = new FileOutputStream(dstpath);
out.write(bundle.manifestText());
out.close();
System.exit(0);
}
public static void main(String... args)
{
if (args.length < 1)
@ -71,6 +98,8 @@ public class Rhizome {
try {
if (methodName.equals("rhizome-list"))
rhizome_list();
else if (methodName.equals("rhizome-manifest"))
rhizome_manifest(new BundleId(args[1]), args[2]);
} catch (Exception e) {
e.printStackTrace();
System.exit(1);

View File

@ -595,13 +595,17 @@ int restful_rhizome_(httpd_request *r, const char *remainder)
if ((r->manifest = rhizome_new_manifest()) == NULL)
return 500;
ret = rhizome_retrieve_manifest(&bid, r->manifest);
if (ret == -1)
if (ret == -1) {
rhizome_manifest_free(r->manifest);
r->manifest = NULL;
return 500;
}
if (ret == 0) {
rhizome_authenticate_author(r->manifest);
r->http.render_extra_headers = render_manifest_headers;
} else {
assert(r->manifest == NULL);
rhizome_manifest_free(r->manifest);
r->manifest = NULL;
assert(r->http.render_extra_headers == NULL);
}
ret = handler(r, remainder);

View File

@ -117,25 +117,56 @@ test_RhizomeList() {
let lnum=NBUNDLES
for ((n = 0; n != NBUNDLES; ++n)); do
line="$(sed -n -e ${lnum}p "$TFWSTDOUT")"
unset_vars_with_prefix X__
unpack_vars X__ "$line"
unset_vars_with_prefix XX_
unpack_vars XX_ "$line"
if [ "${ROWID[$n]}" -eq "$ROWID_MAX" ]; then
# The first row must contain a non-null token string.
assert [ -n "$X____token" ]
assert [ -n "$XX__token" ]
assert [ "$lnum" -eq 1 ]
fi
assert [ "$X__name" = "file$n" ]
assert [ "$X__service" = "file" ]
assert [ "$X__id" = "${BID[$n]}" ]
assert [ "$X__version" = "${VERSION[$n]}" ]
assert [ "$X__filesize" = "${SIZE[$n]}" ]
assert [ "$X__filehash" = "${HASH[$n]}" ]
assert [ "$X__date" = "${DATE[$n]}" ]
assert [ "$X___id" = "${ROWID[$n]}" ]
assert [ "$X____fromhere" = "1" ]
assert [ "$X____author" = "$SIDA1" ]
assert [ "$XX_id" = "${BID[$n]}" ]
assert [ "$XX_version" = "${VERSION[$n]}" ]
assert [ "$XX_filesize" = "${SIZE[$n]}" ]
assert [ "$XX_filehash" = "${HASH[$n]}" ]
assert [ "$XX_date" = "${DATE[$n]}" ]
assert [ "$XX_service" = "file" ]
assert [ "$XX_name" = "file$n" ]
assert [ "$XX__rowId" = "${ROWID[$n]}" ]
assert [ "$XX__fromHere" = "1" ]
assert [ "$XX__author" = "$SIDA1" ]
assert [ "$XX__insertTime" = "${INSERTTIME[$n]}" ]
let --lnum
done
}
assert_metadata() {
local n=$1
assertStdoutGrep --matches=1 "^id=${BID[$n]}$CR\$"
assertStdoutGrep --matches=1 "^version=${VERSION[$n]}$CR\$"
assertStdoutGrep --matches=1 "^filesize=${SIZE[$n]}$CR\$"
assertStdoutGrep --matches=1 "^filehash=${HASH[$n]}$CR\$"
assertStdoutGrep --matches=1 "^BK=${BK[$n]}$CR\$"
assertStdoutGrep --matches=1 "^date=${DATE[$n]}$CR\$"
assertStdoutGrep --matches=1 "^name=${NAME[$n]}$CR\$"
assertStdoutGrep --matches=1 "^service=file$CR\$"
assertStdoutGrep --matches=1 "^_insertTime=${INSERTTIME[$n]}$CR\$"
assertStdoutGrep --matches=1 "^_author=${AUTHOR[$n]}$CR\$"
assertStdoutGrep --matches=1 "^_secret=${SECRET[$n]}$CR\$"
}
doc_RhizomeManifest="Java API fetch Rhizome manifest"
setup_RhizomeManifest() {
setup
rhizome_add_bundles $SIDA1 0 2
}
test_RhizomeManifest() {
for n in 0 1 2; do
executeJavaOk org.servalproject.test.Rhizome rhizome-manifest ${BID[$n]} bundle$n.rhm
tfw_cat --stdout --stderr
assert_metadata $n
tfw_cat -v file$n.manifest -v bundle$n.rhm
assert diff file$n.manifest bundle$n.rhm
done
}
runTests "$@"