From 2aec8f31a4ea71196d6041a3a59421884caaa0f0 Mon Sep 17 00:00:00 2001 From: Andrew Bettison Date: Thu, 3 Jul 2014 17:23:11 +0930 Subject: [PATCH] Rhizome Java API: get decrypted payload --- .../org/servalproject/json/JSONTokeniser.java | 13 ++ .../servaldna/ServalDClient.java | 13 +- .../servaldna/rhizome/RhizomeBundleList.java | 2 +- .../rhizome/RhizomeBundleStatus.java | 71 +++++++++++ .../servaldna/rhizome/RhizomeCommon.java | 113 +++++++++++++++--- .../rhizome/RhizomeDecryptionException.java | 37 ++++++ .../servaldna/rhizome/RhizomeException.java | 40 +++++++ .../rhizome/RhizomeFakeManifestException.java | 36 ++++++ .../RhizomeInconsistencyException.java | 37 ++++++ .../RhizomeInvalidManifestException.java | 38 ++++++ .../rhizome/RhizomePayloadBundle.java | 50 ++++++++ .../rhizome/RhizomePayloadStatus.java | 71 +++++++++++ java/org/servalproject/test/Rhizome.java | 68 +++++++++-- rhizome_restful.c | 4 +- tests/rhizomejava | 36 ++++++ 15 files changed, 597 insertions(+), 32 deletions(-) create mode 100644 java/org/servalproject/servaldna/rhizome/RhizomeBundleStatus.java create mode 100644 java/org/servalproject/servaldna/rhizome/RhizomeDecryptionException.java create mode 100644 java/org/servalproject/servaldna/rhizome/RhizomeException.java create mode 100644 java/org/servalproject/servaldna/rhizome/RhizomeFakeManifestException.java create mode 100644 java/org/servalproject/servaldna/rhizome/RhizomeInconsistencyException.java create mode 100644 java/org/servalproject/servaldna/rhizome/RhizomeInvalidManifestException.java create mode 100644 java/org/servalproject/servaldna/rhizome/RhizomePayloadBundle.java create mode 100644 java/org/servalproject/servaldna/rhizome/RhizomePayloadStatus.java diff --git a/java/org/servalproject/json/JSONTokeniser.java b/java/org/servalproject/json/JSONTokeniser.java index 9ea14c7a..5c5da1b5 100644 --- a/java/org/servalproject/json/JSONTokeniser.java +++ b/java/org/servalproject/json/JSONTokeniser.java @@ -54,6 +54,10 @@ public class JSONTokeniser { public static class UnexpectedException extends JSONInputException { + public UnexpectedException(String got) { + super("unexpected " + got); + } + public UnexpectedException(String got, Class expecting) { super("unexpected " + got + ", expecting " + expecting.getName()); } @@ -78,6 +82,10 @@ public class JSONTokeniser { public static class UnexpectedTokenException extends UnexpectedException { + public UnexpectedTokenException(Object got) { + super(jsonTokenDescription(got)); + } + public UnexpectedTokenException(Object got, Class expecting) { super(jsonTokenDescription(got), expecting); } @@ -115,6 +123,11 @@ public class JSONTokeniser { return n; } + public static void unexpected(Object tok) throws UnexpectedTokenException + { + throw new UnexpectedTokenException(tok); + } + public static void match(Object tok, Token exactly) throws SyntaxException { if (tok != exactly) diff --git a/java/org/servalproject/servaldna/ServalDClient.java b/java/org/servalproject/servaldna/ServalDClient.java index 780e1f68..afec9e39 100644 --- a/java/org/servalproject/servaldna/ServalDClient.java +++ b/java/org/servalproject/servaldna/ServalDClient.java @@ -39,6 +39,8 @@ import org.servalproject.servaldna.rhizome.RhizomeCommon; import org.servalproject.servaldna.rhizome.RhizomeBundleList; import org.servalproject.servaldna.rhizome.RhizomeManifestBundle; import org.servalproject.servaldna.rhizome.RhizomePayloadRawBundle; +import org.servalproject.servaldna.rhizome.RhizomePayloadBundle; +import org.servalproject.servaldna.rhizome.RhizomeException; import org.servalproject.servaldna.meshms.MeshMSCommon; import org.servalproject.servaldna.meshms.MeshMSConversationList; import org.servalproject.servaldna.meshms.MeshMSMessageList; @@ -63,23 +65,28 @@ public class ServalDClient implements ServalDHttpConnectionFactory this.restfulPassword = restfulPassword; } - public RhizomeBundleList rhizomeListBundles() throws ServalDInterfaceException, IOException + public RhizomeBundleList rhizomeListBundles() throws ServalDInterfaceException, IOException, RhizomeException { RhizomeBundleList list = new RhizomeBundleList(this); list.connect(); return list; } - public RhizomeManifestBundle rhizomeManifest(BundleId bid) throws ServalDInterfaceException, IOException + public RhizomeManifestBundle rhizomeManifest(BundleId bid) throws ServalDInterfaceException, IOException, RhizomeException { return RhizomeCommon.rhizomeManifest(this, bid); } - public RhizomePayloadRawBundle rhizomePayloadRaw(BundleId bid) throws ServalDInterfaceException, IOException + public RhizomePayloadRawBundle rhizomePayloadRaw(BundleId bid) throws ServalDInterfaceException, IOException, RhizomeException { return RhizomeCommon.rhizomePayloadRaw(this, bid); } + public RhizomePayloadBundle rhizomePayload(BundleId bid) throws ServalDInterfaceException, IOException, RhizomeException + { + return RhizomeCommon.rhizomePayload(this, bid); + } + public MeshMSConversationList meshmsListConversations(SubscriberId sid) throws ServalDInterfaceException, IOException, MeshMSException { MeshMSConversationList list = new MeshMSConversationList(this, sid); diff --git a/java/org/servalproject/servaldna/rhizome/RhizomeBundleList.java b/java/org/servalproject/servaldna/rhizome/RhizomeBundleList.java index 47845bca..d1e1d6ff 100644 --- a/java/org/servalproject/servaldna/rhizome/RhizomeBundleList.java +++ b/java/org/servalproject/servaldna/rhizome/RhizomeBundleList.java @@ -68,7 +68,7 @@ public class RhizomeBundleList { return this.json != null; } - public void connect() throws IOException, ServalDInterfaceException + public void connect() throws IOException, ServalDInterfaceException, RhizomeException { try { rowCount = 0; diff --git a/java/org/servalproject/servaldna/rhizome/RhizomeBundleStatus.java b/java/org/servalproject/servaldna/rhizome/RhizomeBundleStatus.java new file mode 100644 index 00000000..92d817a4 --- /dev/null +++ b/java/org/servalproject/servaldna/rhizome/RhizomeBundleStatus.java @@ -0,0 +1,71 @@ +/** + * 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.ServalDInterfaceException; + +/* This enum is a direct isomorphism from the C "enum rhizome_bundle_status" defined in rhizome.h. + */ +public enum RhizomeBundleStatus { + ERROR(-1), // internal error + NEW(0), // bundle is newer than store + SAME(1), // same version already in store + DUPLICATE(2), // equivalent bundle already in store + OLD(3), // newer version already in store + INVALID(4), // manifest is invalid + FAKE(5), // manifest signature not valid + INCONSISTENT(6), // manifest filesize/filehash does not match supplied payload + NO_ROOM(7) // doesn't fit; store may contain more important bundles + ; + + final public int code; + + private RhizomeBundleStatus(int code) { + this.code = code; + } + + public static RhizomeBundleStatus fromCode(int code) throws InvalidException + { + RhizomeBundleStatus status = null; + switch (code) { + case -1: status = ERROR; break; + case 0: status = NEW; break; + case 1: status = SAME; break; + case 2: status = DUPLICATE; break; + case 3: status = OLD; break; + case 4: status = INVALID; break; + case 5: status = FAKE; break; + case 6: status = INCONSISTENT; break; + case 7: status = NO_ROOM; break; + default: throw new InvalidException(code); + } + assert status.code == code; + return status; + } + + public static class InvalidException extends ServalDInterfaceException + { + public InvalidException(int code) { + super("invalid Rhizome bundle status code = " + code); + } + } + +} diff --git a/java/org/servalproject/servaldna/rhizome/RhizomeCommon.java b/java/org/servalproject/servaldna/rhizome/RhizomeCommon.java index 30eb9279..6271434b 100644 --- a/java/org/servalproject/servaldna/rhizome/RhizomeCommon.java +++ b/java/org/servalproject/servaldna/rhizome/RhizomeCommon.java @@ -28,6 +28,7 @@ import java.io.IOException; import java.io.PrintStream; import java.io.InputStream; import java.io.InputStreamReader; +import java.net.URL; import java.net.HttpURLConnection; import org.servalproject.json.JSONTokeniser; import org.servalproject.json.JSONInputException; @@ -43,13 +44,22 @@ import org.servalproject.servaldna.ServalDFailureException; public class RhizomeCommon { - protected static InputStream receiveResponse(HttpURLConnection conn, int expected_response_code) throws IOException, ServalDInterfaceException + private static class Status { + public int http_status_code; + public String http_status_message; + RhizomeBundleStatus bundle_status_code; + String bundle_status_message; + RhizomePayloadStatus payload_status_code; + String payload_status_message; + } + + protected static InputStream receiveResponse(HttpURLConnection conn, int expected_response_code) throws IOException, ServalDInterfaceException, RhizomeException { 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 + protected static InputStream receiveResponse(HttpURLConnection conn, int[] expected_response_codes) throws IOException, ServalDInterfaceException, RhizomeException { for (int code: expected_response_codes) { if (conn.getResponseCode() == code) @@ -60,18 +70,64 @@ public class RhizomeCommon 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 + "\""); + throwRestfulResponseExceptions(status, conn.getURL()); + throw new ServalDInterfaceException( + "unexpected Rhizome failure, \"" + status.http_status_message + "\"" + + (status.bundle_status_code == null ? "" : ", " + status.bundle_status_code) + + (status.bundle_status_message == null ? "" : " \"" + status.bundle_status_message + "\"") + + (status.payload_status_code == null ? "" : ", " + status.payload_status_code) + + (status.payload_status_message == null ? "" : ", " + status.payload_status_message + "\"") + ); } throw new ServalDInterfaceException("unexpected HTTP response code: " + conn.getResponseCode()); } - protected static JSONTokeniser receiveRestfulResponse(HttpURLConnection conn, int expected_response_code) throws IOException, ServalDInterfaceException + protected static void throwRestfulResponseExceptions(Status status, URL url) throws RhizomeException, ServalDFailureException + { + if (status.bundle_status_code != null) { + switch (status.bundle_status_code) { + case ERROR: + throw new ServalDFailureException("received rhizome_bundle_status_code=ERROR(-1) from " + url); + case NEW: + case SAME: + case DUPLICATE: + case OLD: + case NO_ROOM: + break; + case INVALID: + throw new RhizomeInvalidManifestException(url); + case FAKE: + throw new RhizomeFakeManifestException(url); + case INCONSISTENT: + throw new RhizomeInconsistencyException(url); + } + } + if (status.payload_status_code != null) { + switch (status.payload_status_code) { + case ERROR: + throw new ServalDFailureException("received rhizome_payload_status_code=ERROR(-1) from " + url); + case EMPTY: + case NEW: + case STORED: + case TOO_BIG: + case EVICTED: + break; + case WRONG_SIZE: + case WRONG_HASH: + throw new RhizomeInconsistencyException(url); + case CRYPTO_FAIL: + throw new RhizomeDecryptionException(url); + } + } + } + + protected static JSONTokeniser receiveRestfulResponse(HttpURLConnection conn, int expected_response_code) throws IOException, ServalDInterfaceException, RhizomeException { int[] expected_response_codes = { expected_response_code }; return receiveRestfulResponse(conn, expected_response_codes); } - protected static JSONTokeniser receiveRestfulResponse(HttpURLConnection conn, int[] expected_response_codes) throws IOException, ServalDInterfaceException + protected static JSONTokeniser receiveRestfulResponse(HttpURLConnection conn, int[] expected_response_codes) throws IOException, ServalDInterfaceException, RhizomeException { InputStream in = receiveResponse(conn, expected_response_codes); if (!conn.getContentType().equals("application/json")) @@ -79,10 +135,6 @@ public class RhizomeCommon return new JSONTokeniser(new InputStreamReader(in, "US-ASCII")); } - private static class Status { - public String message; - } - protected static Status decodeRestfulStatus(JSONTokeniser json) throws IOException, ServalDInterfaceException { try { @@ -90,12 +142,28 @@ public class RhizomeCommon json.consume(JSONTokeniser.Token.START_OBJECT); json.consume("http_status_code"); json.consume(JSONTokeniser.Token.COLON); - json.consume(Integer.class); + status.http_status_code = json.consume(Integer.class); json.consume(JSONTokeniser.Token.COMMA); - status.message = json.consume("http_status_message"); + json.consume("http_status_message"); json.consume(JSONTokeniser.Token.COLON); - String message = json.consume(String.class); - json.consume(JSONTokeniser.Token.END_OBJECT); + status.http_status_message = json.consume(String.class); + Object tok = json.nextToken(); + while (tok == JSONTokeniser.Token.COMMA) { + String label = json.consume(String.class); + json.consume(JSONTokeniser.Token.COLON); + if (label.equals("rhizome_bundle_status_code")) + status.bundle_status_code = RhizomeBundleStatus.fromCode(json.consume(Integer.class)); + else if (label.equals("rhizome_bundle_status_message")) + status.bundle_status_message = json.consume(String.class); + else if (label.equals("rhizome_payload_status_code")) + status.payload_status_code = RhizomePayloadStatus.fromCode(json.consume(Integer.class)); + else if (label.equals("rhizome_payload_status_message")) + status.payload_status_message = json.consume(String.class); + else + json.unexpected(label); + tok = json.nextToken(); + } + json.match(tok, JSONTokeniser.Token.END_OBJECT); json.consume(JSONTokeniser.Token.EOF); return status; } @@ -104,7 +172,7 @@ public class RhizomeCommon } } - public static RhizomeManifestBundle rhizomeManifest(ServalDHttpConnectionFactory connector, BundleId bid) throws IOException, ServalDInterfaceException + public static RhizomeManifestBundle rhizomeManifest(ServalDHttpConnectionFactory connector, BundleId bid) throws IOException, ServalDInterfaceException, RhizomeException { HttpURLConnection conn = connector.newServalDHttpConnection("/restful/rhizome/" + bid.toHex() + ".rhm"); conn.connect(); @@ -128,7 +196,7 @@ public class RhizomeCommon return new RhizomeManifestBundle(manifest, insertTime, author, secret); } - public static RhizomePayloadRawBundle rhizomePayloadRaw(ServalDHttpConnectionFactory connector, BundleId bid) throws IOException, ServalDInterfaceException + public static RhizomePayloadRawBundle rhizomePayloadRaw(ServalDHttpConnectionFactory connector, BundleId bid) throws IOException, ServalDInterfaceException, RhizomeException { HttpURLConnection conn = connector.newServalDHttpConnection("/restful/rhizome/" + bid.toHex() + "/raw.bin"); conn.connect(); @@ -143,6 +211,21 @@ public class RhizomeCommon return new RhizomePayloadRawBundle(manifest, in, insertTime, author, secret); } + public static RhizomePayloadBundle rhizomePayload(ServalDHttpConnectionFactory connector, BundleId bid) throws IOException, ServalDInterfaceException, RhizomeException + { + HttpURLConnection conn = connector.newServalDHttpConnection("/restful/rhizome/" + bid.toHex() + "/decrypted.bin"); + conn.connect(); + InputStream in = RhizomeCommon.receiveResponse(conn, HttpURLConnection.HTTP_OK); + if (!conn.getContentType().equals("application/octet-stream")) + throw new ServalDInterfaceException("unexpected HTTP Content-Type: " + conn.getContentType()); + dumpHeaders(conn, System.err); + RhizomeManifest manifest = manifestFromHeaders(conn); + 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 RhizomePayloadBundle(manifest, in, insertTime, author, secret); + } + private static void dumpHeaders(HttpURLConnection conn, PrintStream out) { for (Map.Entry> e: conn.getHeaderFields().entrySet()) diff --git a/java/org/servalproject/servaldna/rhizome/RhizomeDecryptionException.java b/java/org/servalproject/servaldna/rhizome/RhizomeDecryptionException.java new file mode 100644 index 00000000..2d44d719 --- /dev/null +++ b/java/org/servalproject/servaldna/rhizome/RhizomeDecryptionException.java @@ -0,0 +1,37 @@ +/** + * 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.net.URL; + +/** + * Thrown when a Rhizome API method is asked to decrypt a payload without possessing the necessary + * recipient identity (ie, is locked or not in the keyring). + * + * @author Andrew Bettison + */ +public class RhizomeDecryptionException extends RhizomeException +{ + public RhizomeDecryptionException(URL url) { + super("cannot decrypt payload", url); + } + +} diff --git a/java/org/servalproject/servaldna/rhizome/RhizomeException.java b/java/org/servalproject/servaldna/rhizome/RhizomeException.java new file mode 100644 index 00000000..2b895791 --- /dev/null +++ b/java/org/servalproject/servaldna/rhizome/RhizomeException.java @@ -0,0 +1,40 @@ +/** + * 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.net.URL; + +/** + * Thrown when a Rhizome API encounters an exceptional condition. This exception is subclassed for + * specific causes. + * + * @author Andrew Bettison + */ +public abstract class RhizomeException extends Exception +{ + public final URL url; + + public RhizomeException(String message, URL url) { + super(message + "; " + url); + this.url = url; + } + +} diff --git a/java/org/servalproject/servaldna/rhizome/RhizomeFakeManifestException.java b/java/org/servalproject/servaldna/rhizome/RhizomeFakeManifestException.java new file mode 100644 index 00000000..7bf44c67 --- /dev/null +++ b/java/org/servalproject/servaldna/rhizome/RhizomeFakeManifestException.java @@ -0,0 +1,36 @@ +/** + * 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.net.URL; + +/** + * Thrown when a Rhizome API method is passed a manifest with an invalid or missing signature. + * + * @author Andrew Bettison + */ +public class RhizomeFakeManifestException extends RhizomeException +{ + public RhizomeFakeManifestException(URL url) { + super("unsigned manifest", url); + } + +} diff --git a/java/org/servalproject/servaldna/rhizome/RhizomeInconsistencyException.java b/java/org/servalproject/servaldna/rhizome/RhizomeInconsistencyException.java new file mode 100644 index 00000000..cdab7985 --- /dev/null +++ b/java/org/servalproject/servaldna/rhizome/RhizomeInconsistencyException.java @@ -0,0 +1,37 @@ +/** + * 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.net.URL; + +/** + * Thrown when a Rhizome API method is passed a manifest which is inconsistent with a supplied + * payload. I.e., filesize or filehash does not match. + * + * @author Andrew Bettison + */ +public class RhizomeInconsistencyException extends RhizomeException +{ + public RhizomeInconsistencyException(URL url) { + super("manifest inconsistent with payload", url); + } + +} diff --git a/java/org/servalproject/servaldna/rhizome/RhizomeInvalidManifestException.java b/java/org/servalproject/servaldna/rhizome/RhizomeInvalidManifestException.java new file mode 100644 index 00000000..f76bc01b --- /dev/null +++ b/java/org/servalproject/servaldna/rhizome/RhizomeInvalidManifestException.java @@ -0,0 +1,38 @@ +/** + * 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.net.URL; + +/** + * Thrown when a Rhizome API method is passed an invalid manifest. This is not an error within the + * Serval DNA interface, so it is not a subclass of ServalDInterfaceException. The programmer must + * explicitly deal with it instead of just absorbing it as an interface malfunction. + * + * @author Andrew Bettison + */ +public class RhizomeInvalidManifestException extends RhizomeException +{ + public RhizomeInvalidManifestException(URL url) { + super("invalid manifest", url); + } + +} diff --git a/java/org/servalproject/servaldna/rhizome/RhizomePayloadBundle.java b/java/org/servalproject/servaldna/rhizome/RhizomePayloadBundle.java new file mode 100644 index 00000000..590eea0c --- /dev/null +++ b/java/org/servalproject/servaldna/rhizome/RhizomePayloadBundle.java @@ -0,0 +1,50 @@ +/** + * 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.InputStream; +import org.servalproject.servaldna.SubscriberId; +import org.servalproject.servaldna.BundleSecret; +import org.servalproject.servaldna.ServalDInterfaceException; + +public class RhizomePayloadBundle { + + public final RhizomeManifest manifest; + public final InputStream payloadInputStream; + public final long insertTime; + public final SubscriberId author; + public final BundleSecret secret; + + protected RhizomePayloadBundle(RhizomeManifest manifest, + InputStream payloadInputStream, + long insertTime, + SubscriberId author, + BundleSecret secret) + + { + this.payloadInputStream = payloadInputStream; + this.manifest = manifest; + this.insertTime = insertTime; + this.author = author; + this.secret = secret; + } + +} diff --git a/java/org/servalproject/servaldna/rhizome/RhizomePayloadStatus.java b/java/org/servalproject/servaldna/rhizome/RhizomePayloadStatus.java new file mode 100644 index 00000000..9828aa73 --- /dev/null +++ b/java/org/servalproject/servaldna/rhizome/RhizomePayloadStatus.java @@ -0,0 +1,71 @@ +/** + * 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.ServalDInterfaceException; + +/* This enum is a direct isomorphism from the C "enum rhizome_payload_status" defined in rhizome.h. + */ +public enum RhizomePayloadStatus { + ERROR(-1), // unexpected error (underlying failure) + EMPTY(0), // payload is empty (zero length) + NEW(1), // payload is not yet in store (added) + STORED(2), // payload is already in store + WRONG_SIZE(3), // payload's size does not match manifest + WRONG_HASH(4), // payload's hash does not match manifest + CRYPTO_FAIL(5), // cannot encrypt/decrypt (payload key unknown) + TOO_BIG(6), // payload will never fit in our store + EVICTED(7) // other payloads in our store are more important + ; + + final public int code; + + private RhizomePayloadStatus(int code) { + this.code = code; + } + + public static RhizomePayloadStatus fromCode(int code) throws InvalidException + { + RhizomePayloadStatus status = null; + switch (code) { + case -1: status = ERROR; break; + case 0: status = EMPTY; break; + case 1: status = NEW; break; + case 2: status = STORED; break; + case 3: status = WRONG_SIZE; break; + case 4: status = WRONG_HASH; break; + case 5: status = CRYPTO_FAIL; break; + case 6: status = TOO_BIG; break; + case 7: status = EVICTED; break; + default: throw new InvalidException(code); + } + assert status.code == code; + return status; + } + + public static class InvalidException extends ServalDInterfaceException + { + public InvalidException(int code) { + super("invalid Rhizome payload status code = " + code); + } + } + +} diff --git a/java/org/servalproject/test/Rhizome.java b/java/org/servalproject/test/Rhizome.java index 0e572191..d66c62c4 100644 --- a/java/org/servalproject/test/Rhizome.java +++ b/java/org/servalproject/test/Rhizome.java @@ -33,6 +33,8 @@ import org.servalproject.servaldna.rhizome.RhizomeListBundle; import org.servalproject.servaldna.rhizome.RhizomeBundleList; import org.servalproject.servaldna.rhizome.RhizomeManifestBundle; import org.servalproject.servaldna.rhizome.RhizomePayloadRawBundle; +import org.servalproject.servaldna.rhizome.RhizomePayloadBundle; +import org.servalproject.servaldna.rhizome.RhizomeException; public class Rhizome { @@ -68,6 +70,9 @@ public class Rhizome { ); } } + catch (RhizomeException e) { + System.out.println(e.toString()); + } finally { if (list != null) list.close(); @@ -77,17 +82,22 @@ public class Rhizome { 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(); + try { + 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(); + } + catch (RhizomeException e) { + System.out.println(e.toString()); + } System.exit(0); } @@ -112,6 +122,40 @@ public class Rhizome { manifestFields(bundle.manifest, "\n") + "\n" ); } + catch (RhizomeException e) { + System.out.println(e.toString()); + } + finally { + if (out != null) + out.close(); + } + System.exit(0); + } + + static void rhizome_payload_decrypted(BundleId bid, String dstpath) throws ServalDInterfaceException, IOException, InterruptedException + { + ServalDClient client = new ServerControl().getRestfulClient(); + FileOutputStream out = new FileOutputStream(dstpath); + try { + RhizomePayloadBundle bundle = client.rhizomePayload(bid); + InputStream in = bundle.payloadInputStream; + byte[] buf = new byte[4096]; + int n; + while ((n = in.read(buf)) > 0) + out.write(buf, 0, n); + in.close(); + out.close(); + out = null; + System.out.println( + "_insertTime=" + bundle.insertTime + "\n" + + "_author=" + bundle.author + "\n" + + "_secret=" + bundle.secret + "\n" + + manifestFields(bundle.manifest, "\n") + "\n" + ); + } + catch (RhizomeException e) { + System.out.println(e.toString()); + } finally { if (out != null) out.close(); @@ -131,6 +175,8 @@ public class Rhizome { rhizome_manifest(new BundleId(args[1]), args[2]); else if (methodName.equals("rhizome-payload-raw")) rhizome_payload_raw(new BundleId(args[1]), args[2]); + else if (methodName.equals("rhizome-payload-decrypted")) + rhizome_payload_decrypted(new BundleId(args[1]), args[2]); } catch (Exception e) { e.printStackTrace(); System.exit(1); diff --git a/rhizome_restful.c b/rhizome_restful.c index 47a96ba7..c05fb285 100644 --- a/rhizome_restful.c +++ b/rhizome_restful.c @@ -727,7 +727,7 @@ static int restful_rhizome_bid_raw_bin(httpd_request *r, const char *remainder) } int ret = rhizome_response_content_init_filehash(r, &r->manifest->filehash); if (ret) - return ret; + return http_request_rhizome_response(r, ret, NULL, NULL); http_request_response_generated(&r->http, 200, CONTENT_TYPE_BLOB, rhizome_payload_content); return 1; } @@ -745,7 +745,7 @@ static int restful_rhizome_bid_decrypted_bin(httpd_request *r, const char *remai } int ret = rhizome_response_content_init_payload(r, r->manifest); if (ret) - return ret; + return http_request_rhizome_response(r, ret, NULL, NULL); // TODO use Content Type from manifest (once it is implemented) http_request_response_generated(&r->http, 200, CONTENT_TYPE_BLOB, rhizome_payload_content); return 1; diff --git a/tests/rhizomejava b/tests/rhizomejava index 0b199221..d1f698b8 100755 --- a/tests/rhizomejava +++ b/tests/rhizomejava @@ -180,8 +180,44 @@ test_RhizomePayloadRaw() { executeJavaOk org.servalproject.test.Rhizome rhizome-payload-raw "${BID[$n]}" raw.bin$n tfw_cat --stdout --stderr assert_metadata $n + done + for n in 0 1 2 3; do assert cmp raw$n raw.bin$n done } +doc_RhizomePayloadDecrypted="Java API fetch Rhizome decrypted payload" +setup_RhizomePayloadDecrypted() { + setup + rhizome_add_bundles $SIDA1 0 1 + rhizome_add_bundles --encrypted $SIDA1 2 3 +} +test_RhizomePayloadDecrypted() { + for n in 0 1 2 3; do + executeJavaOk org.servalproject.test.Rhizome rhizome-payload-decrypted "${BID[$n]}" decrypted.bin$n + tfw_cat --stdout --stderr + assert_metadata $n + done + for n in 0 1 2 3; do + assert cmp file$n decrypted.bin$n + done +} + +doc_RhizomePayloadDecryptedForeign="Java API cannot fetch foreign Rhizome decrypted payload" +setup_RhizomePayloadDecryptedForeign() { + setup + rhizome_add_bundles --encrypted $SIDA1 0 0 + set_instance +B + create_single_identity + rhizome_add_bundles --encrypted $SIDB 1 1 + executeOk_servald rhizome export manifest "${BID[1]}" file1.manifest + set_instance +A + executeOk_servald rhizome import bundle raw1 file1.manifest +} +test_RhizomePayloadDecryptedForeign() { + executeJavaOk org.servalproject.test.Rhizome rhizome-payload-decrypted "${BID[1]}" decrypted.bin$n + tfw_cat --stdout --stderr + assertStdoutGrep RhizomeDecryptionException +} + runTests "$@"