diff --git a/java/org/servalproject/codec/Base64.java b/java/org/servalproject/codec/Base64.java new file mode 100644 index 00000000..56ff6ac4 --- /dev/null +++ b/java/org/servalproject/codec/Base64.java @@ -0,0 +1,70 @@ +/** + * 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.codec; + +import java.lang.StringBuilder; + +public class Base64 { + + public static final char[] SYMBOLS = { + 'A','B','C','D','E','F','G','H','I','J','K','L','M','N','O','P', + 'Q','R','S','T','U','V','W','X','Y','Z','a','b','c','d','e','f', + 'g','h','i','j','k','l','m','n','o','p','q','r','s','t','u','v', + 'w','x','y','z','0','1','2','3','4','5','6','7','8','9','+','/', + '=' + }; + + public static String encode(byte[] binary) + { + StringBuilder sb = new StringBuilder(); + int place = 0; + byte buf = 0; + for (byte b: binary) { + switch (place) { + case 0: + sb.append(SYMBOLS[b >>> 2]); + buf = (byte)((b << 4) & 0x3f); + place = 1; + break; + case 1: + sb.append(SYMBOLS[(b >>> 4) | buf]); + buf = (byte)((b << 2) & 0x3f); + place = 2; + break; + case 2: + sb.append(SYMBOLS[(b >>> 6) | buf]); + sb.append(SYMBOLS[b & 0x3f]); + place = 0; + break; + } + } + if (place != 0) + sb.append(SYMBOLS[buf]); + switch (place) { + case 1: + sb.append(SYMBOLS[64]); + case 2: + sb.append(SYMBOLS[64]); + } + return sb.toString(); + } + +} diff --git a/java/org/servalproject/servaldna/ServalDClient.java b/java/org/servalproject/servaldna/ServalDClient.java new file mode 100644 index 00000000..21c71dba --- /dev/null +++ b/java/org/servalproject/servaldna/ServalDClient.java @@ -0,0 +1,122 @@ +/** + * 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.io.IOException; +import java.io.UnsupportedEncodingException; +import java.net.URL; +import java.net.URLConnection; +import java.net.HttpURLConnection; +import org.servalproject.codec.Base64; +import org.servalproject.servaldna.SubscriberId; +import org.servalproject.servaldna.ServalDCommand; +import org.servalproject.servaldna.ServalDInterfaceException; +import org.servalproject.servaldna.meshms.MeshMSConversationList; + +public class ServalDClient implements ServalDHttpConnectionFactory +{ + + private static final String restfulUsername = "ServalDClient"; + private static final String restfulPasswordDefault = "u6ng^ues%@@SabLEEEE8"; + private static String restfulPassword; + protected boolean connected; + int httpPort; + + public static ServalDClient newServalDClient() + { + return new ServalDClient(); + } + + protected ServalDClient() + { + restfulPassword = null; + connected = false; + httpPort = 0; + } + + private void connect() throws ServalDInterfaceException + { + ensureServerRunning(); + if (!connected) { + if (!fetchRestfulAuthorization()) + createRestfulAuthorization(); + connected = true; + } + } + + private void ensureServerRunning() throws ServalDInterfaceException + { + ServalDCommand.Status s = ServalDCommand.serverStatus(); + if (!s.status.equals("running")) + throw new ServalDInterfaceException("server is not running"); + if (s.httpPort < 1 || s.httpPort > 65535) + throw new ServalDInterfaceException("invalid HTTP port number: " + s.httpPort); + httpPort = s.httpPort; + } + + private boolean fetchRestfulAuthorization() throws ServalDInterfaceException + { + restfulPassword = ServalDCommand.getConfigItem("rhizome.api.restful.users." + restfulUsername + ".password"); + return restfulPassword != null; + } + + private void createRestfulAuthorization() throws ServalDInterfaceException + { + ServalDCommand.setConfigItem("rhizome.api.restful.users." + restfulUsername + ".password", restfulPasswordDefault); + ServalDCommand.configSync(); + if (!fetchRestfulAuthorization()) + throw new ServalDInterfaceException("restful password not set"); + } + + public MeshMSConversationList meshmsListConversations(SubscriberId sid) throws ServalDInterfaceException, IOException + { + MeshMSConversationList list = new MeshMSConversationList(this, sid); + list.connect(); + return list; + } + + // interface ServalDHttpConnectionFactory + public HttpURLConnection newServalDHttpConnection(String path) throws ServalDInterfaceException, IOException + { + connect(); + assert restfulPassword != null; + assert httpPort != 0; + URL url = new URL("http", "localhost", httpPort, path); + URLConnection uconn = url.openConnection(); + HttpURLConnection conn; + try { + conn = (HttpURLConnection) uconn; + } + catch (ClassCastException e) { + throw new ServalDInterfaceException("URL.openConnection() returned a " + uconn.getClass().getName() + ", expecting a HttpURLConnection", e); + } + int status = 0; + conn.setAllowUserInteraction(false); + try { + conn.addRequestProperty("Authorization", "Basic " + Base64.encode((restfulUsername + ":" + restfulPassword).getBytes("US-ASCII"))); + } + catch (UnsupportedEncodingException e) { + throw new ServalDInterfaceException("invalid RESTful password", e); + } + return conn; + } + +} diff --git a/java/org/servalproject/servaldna/ServalDFailureException.java b/java/org/servalproject/servaldna/ServalDFailureException.java index b2211e7d..90296a86 100644 --- a/java/org/servalproject/servaldna/ServalDFailureException.java +++ b/java/org/servalproject/servaldna/ServalDFailureException.java @@ -26,7 +26,7 @@ package org.servalproject.servaldna; * * @author Andrew Bettison */ -public class ServalDFailureException extends Exception +public class ServalDFailureException extends ServalDInterfaceException { private static final long serialVersionUID = 1L; diff --git a/java/org/servalproject/servaldna/ServalDHttpConnectionFactory.java b/java/org/servalproject/servaldna/ServalDHttpConnectionFactory.java new file mode 100644 index 00000000..2ac1def5 --- /dev/null +++ b/java/org/servalproject/servaldna/ServalDHttpConnectionFactory.java @@ -0,0 +1,30 @@ +/** + * 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.io.IOException; +import java.net.HttpURLConnection; + +public interface ServalDHttpConnectionFactory { + + public HttpURLConnection newServalDHttpConnection(String path) throws ServalDInterfaceException, IOException; + +} diff --git a/java/org/servalproject/servaldna/ServalDInterfaceException.java b/java/org/servalproject/servaldna/ServalDInterfaceException.java new file mode 100644 index 00000000..ec2ff597 --- /dev/null +++ b/java/org/servalproject/servaldna/ServalDInterfaceException.java @@ -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; + +/** + * Thrown when the Serval DNA interface has not behaved as expected. This is a general class of + * errors, and is specialised by subclasses that represent an error returned by a server command, + * MDP protocol non-compliance, etc. + * + * @author Andrew Bettison + */ +public class ServalDInterfaceException extends Exception +{ + public ServalDInterfaceException(String message) { + super(message); + } + + public ServalDInterfaceException(Throwable cause) { + super(cause); + } + + public ServalDInterfaceException(String message, Throwable cause) { + super(message, cause); + } + +} diff --git a/java/org/servalproject/servaldna/meshms/MeshMSConversation.java b/java/org/servalproject/servaldna/meshms/MeshMSConversation.java new file mode 100644 index 00000000..c944c1f0 --- /dev/null +++ b/java/org/servalproject/servaldna/meshms/MeshMSConversation.java @@ -0,0 +1,46 @@ +/** + * 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.meshms; + +import org.servalproject.servaldna.SubscriberId; + +public class MeshMSConversation { + + public final int _rowNumber; + public final int _id; + public final SubscriberId mySid; + public final SubscriberId theirSid; + public final boolean isRead; + public final int lastMessageOffset; + public final int readOffset; + + protected MeshMSConversation(int rowNumber, int _id, SubscriberId my_sid, SubscriberId their_sid, boolean read, int last_message_offset, int read_offset) + { + this._rowNumber = rowNumber; + this._id = _id; + this.mySid = my_sid; + this.theirSid = their_sid; + this.isRead = read; + this.lastMessageOffset = last_message_offset; + this.readOffset = read_offset; + } + +} diff --git a/java/org/servalproject/servaldna/meshms/MeshMSConversationList.java b/java/org/servalproject/servaldna/meshms/MeshMSConversationList.java new file mode 100644 index 00000000..9bd8ff27 --- /dev/null +++ b/java/org/servalproject/servaldna/meshms/MeshMSConversationList.java @@ -0,0 +1,438 @@ +/** + * 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.meshms; + +import java.io.IOException; +import java.lang.StringBuilder; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.Reader; +import java.io.PushbackReader; +import java.util.Collection; +import java.util.Vector; +import java.net.HttpURLConnection; +import org.servalproject.servaldna.ServalDHttpConnectionFactory; +import org.servalproject.servaldna.ServalDInterfaceException; +import org.servalproject.servaldna.SubscriberId; + +public class MeshMSConversationList { + + private ServalDHttpConnectionFactory httpConnector; + private SubscriberId sid; + private HttpURLConnection httpConnection; + private PushbackReader reader; + private Vector headers; + int columnIndex__id; + int columnIndex_my_sid; + int columnIndex_their_sid; + int columnIndex_read; + int columnIndex_last_message; + int columnIndex_read_offset; + int rowCount; + + public MeshMSConversationList(ServalDHttpConnectionFactory connector, SubscriberId sid) + { + this.httpConnector = connector; + this.sid = sid; + } + + public void connect() throws ServalDInterfaceException, IOException + { + columnIndex__id = -1; + columnIndex_my_sid = -1; + columnIndex_their_sid = -1; + columnIndex_read = -1; + columnIndex_last_message = -1; + columnIndex_read_offset = -1; + rowCount = 0; + httpConnection = httpConnector.newServalDHttpConnection("/restful/meshms/" + sid.toHex() + "/conversationlist.json"); + httpConnection.connect(); + reader = new PushbackReader(new InputStreamReader(httpConnection.getInputStream(), "US-ASCII")); + consume(reader, JsonToken.START_OBJECT); + consume(reader, "header"); + consume(reader, JsonToken.COLON); + headers = new Vector(); + consumeArray(reader, headers, String.class); + if (headers.size() < 1) + throw new ServalDInterfaceException("empty JSON headers array"); + for (int i = 0; i < headers.size(); ++i) { + String header = headers.get(i); + if (header.equals("_id")) + columnIndex__id = i; + else if (header.equals("my_sid")) + columnIndex_my_sid = i; + else if (header.equals("their_sid")) + columnIndex_their_sid = i; + else if (header.equals("read")) + columnIndex_read = i; + else if (header.equals("last_message")) + columnIndex_last_message = i; + else if (header.equals("read_offset")) + columnIndex_read_offset = i; + } + if (columnIndex__id == -1) + throw new ServalDInterfaceException("missing JSON column: _id"); + if (columnIndex_my_sid == -1) + throw new ServalDInterfaceException("missing JSON column: my_sid"); + if (columnIndex_their_sid == -1) + throw new ServalDInterfaceException("missing JSON column: their_sid"); + if (columnIndex_read == -1) + throw new ServalDInterfaceException("missing JSON column: read"); + if (columnIndex_last_message == -1) + throw new ServalDInterfaceException("missing JSON column: last_message"); + if (columnIndex_read_offset == -1) + throw new ServalDInterfaceException("missing JSON column: read_offset"); + consume(reader, JsonToken.COMMA); + consume(reader, "rows"); + consume(reader, JsonToken.COLON); + consume(reader, JsonToken.START_ARRAY); + } + + public MeshMSConversation nextConversation() throws ServalDInterfaceException, IOException + { + Object tok = nextJsonToken(reader); + if (tok == JsonToken.END_ARRAY) { + consume(reader, JsonToken.END_OBJECT); + consume(reader, JsonToken.EOF); + return null; + } + if (rowCount != 0) { + match(tok, JsonToken.COMMA); + tok = nextJsonToken(reader); + } + match(tok, JsonToken.START_ARRAY); + Object[] row = new Object[headers.size()]; + for (int i = 0; i < headers.size(); ++i) { + if (i != 0) + consume(reader, JsonToken.COMMA); + row[i] = consume(reader); + } + consume(reader, JsonToken.END_ARRAY); + int _id = narrow(row[columnIndex__id], Integer.class); + SubscriberId my_sid; + try { + my_sid = new SubscriberId(narrow(row[columnIndex_my_sid], String.class)); + } + catch (SubscriberId.InvalidHexException e) { + throw new ServalDInterfaceException("invalid JSON column value: my_sid", e); + } + SubscriberId their_sid; + try { + their_sid = new SubscriberId(narrow(row[columnIndex_their_sid], String.class)); + } + catch (SubscriberId.InvalidHexException e) { + throw new ServalDInterfaceException("invalid JSON column value: their_sid", e); + } + boolean is_read = narrow(row[columnIndex_read], Boolean.class); + int last_message = narrow(row[columnIndex_last_message], Integer.class); + int read_offset = narrow(row[columnIndex_read_offset], Integer.class); + return new MeshMSConversation(rowCount++, _id, my_sid, their_sid, is_read, last_message, read_offset); + } + + public void close() throws IOException + { + if (reader != null) { + reader.close(); + reader = null; + } + } + + static void match(Object tok, JsonToken exactly) throws ServalDInterfaceException + { + if (tok != exactly) + throw new ServalDInterfaceException("unexpected JSON token " + exactly + ", got: " + jsonTokenDescription(tok)); + } + + static void consume(PushbackReader rd, JsonToken exactly) throws ServalDInterfaceException, IOException + { + match(nextJsonToken(rd), exactly); + } + + @SuppressWarnings("unchecked") + static T narrow(Object tok, Class cls) throws ServalDInterfaceException + { + assert !cls.isAssignableFrom(JsonToken.class); // can only narrow to values + if (tok == JsonToken.EOF) + throw new ServalDInterfaceException("unexpected EOF"); + if (tok instanceof JsonToken) + throw new ServalDInterfaceException("expecting JSON " + cls.getName() + ", got: " + tok); + // Convert: + // Integer --> Float or Double + // Float --> Double + // Double --> Float + if (cls == Double.class && (tok instanceof Float || tok instanceof Integer)) + tok = new Double(((Number)tok).doubleValue()); + else if (cls == Float.class && (tok instanceof Double || tok instanceof Integer)) + tok = new Float(((Number)tok).floatValue()); + if (cls.isInstance(tok)) + return (T)tok; + throw new ServalDInterfaceException("expecting JSON " + cls.getName() + ", got: " + jsonTokenDescription(tok)); + } + + static T consume(PushbackReader rd, Class cls) throws ServalDInterfaceException, IOException + { + return narrow(nextJsonToken(rd), cls); + } + + static Object consume(PushbackReader rd) throws ServalDInterfaceException, IOException + { + return consume(rd, Object.class); + } + + static String consume(PushbackReader rd, String exactly) throws ServalDInterfaceException, IOException + { + String tok = consume(rd, String.class); + if (tok.equals(exactly)) + return tok; + throw new ServalDInterfaceException("unexpected JSON String \"" + exactly + "\", got: " + jsonTokenDescription(tok)); + } + + static int consumeArray(PushbackReader rd, Collection collection, Class cls) throws ServalDInterfaceException, IOException + { + int added = 0; + consume(rd, JsonToken.START_ARRAY); + Object tok = nextJsonToken(rd); + if (tok != JsonToken.END_ARRAY) { + while (true) { + try { + collection.add(narrow(tok, cls)); + ++added; + } + catch (ClassCastException e) { + throw new ServalDInterfaceException("unexpected JSON token: " + jsonTokenDescription(tok)); + } + tok = nextJsonToken(rd); + if (tok == JsonToken.END_ARRAY) + break; + match(tok, JsonToken.COMMA); + tok = nextJsonToken(rd); + } + } + return added; + } + + enum JsonToken { + START_OBJECT, + END_OBJECT, + START_ARRAY, + END_ARRAY, + COMMA, + COLON, + NULL, + EOF + }; + + static boolean jsonIsToken(Object tok) + { + return tok instanceof JsonToken || tok instanceof String || tok instanceof Double || tok instanceof Integer || tok instanceof Boolean; + } + + static String jsonTokenDescription(Object tok) + { + if (tok instanceof String) + return "\"" + tok + "\""; + if (tok instanceof Number) + return "" + tok; + if (tok instanceof Boolean) + return "" + tok; + assert tok instanceof JsonToken; + return tok.toString(); + } + + static void readAll(Reader rd, char[] word) throws ServalDInterfaceException, IOException + { + int len = 0; + while (len < word.length) { + int n = rd.read(word, len, word.length - len); + if (n == -1) + throw new ServalDInterfaceException("unexpected EOF"); + len += n; + } + } + + static Object nextJsonToken(PushbackReader rd) throws ServalDInterfaceException, IOException + { + while (true) { + int c = rd.read(); + switch (c) { + case -1: + return JsonToken.EOF; + case '\t': + case '\r': + case '\n': + case ' ': + break; + case '{': + return JsonToken.START_OBJECT; + case '}': + return JsonToken.END_OBJECT; + case '[': + return JsonToken.START_ARRAY; + case ']': + return JsonToken.END_ARRAY; + case ',': + return JsonToken.COMMA; + case ':': + return JsonToken.COLON; + case 't': { + char[] word = new char[3]; + readAll(rd, word); + if (word[0] == 'r' && word[1] == 'u' && word[2] == 'e') + return Boolean.TRUE; + } + throw new ServalDInterfaceException("malformed JSON"); + case 'f': { + char[] word = new char[4]; + readAll(rd, word); + if (word[0] == 'a' && word[1] == 'l' && word[2] == 's' && word[3] == 'e') + return Boolean.FALSE; + } + throw new ServalDInterfaceException("malformed JSON"); + case 'n': { + char[] word = new char[3]; + readAll(rd, word); + if (word[0] == 'u' && word[1] == 'l' && word[2] == 'l') + return JsonToken.NULL; + } + throw new ServalDInterfaceException("malformed JSON"); + case '"': { + StringBuilder sb = new StringBuilder(); + boolean slosh = false; + while (true) { + c = rd.read(); + if (c == -1) + throw new ServalDInterfaceException("unexpected EOF in JSON string"); + if (slosh) { + switch (c) { + case '"': case '/': case '\\': sb.append('"'); break; + case 'b': sb.append('\b'); break; + case 'f': sb.append('\f'); break; + case 'n': sb.append('\n'); break; + case 'r': sb.append('\r'); break; + case 't': sb.append('\t'); break; + case 'u': + char[] hex = new char[4]; + readAll(rd, hex); + int code = Integer.valueOf(new String(hex), 16); + if (code >= 0 && code <= 0xffff) { + sb.append((char)code); + break; + } + // fall through + default: + throw new ServalDInterfaceException("malformed JSON string"); + } + } + else { + switch (c) { + case '"': + return sb.toString(); + case '\\': + slosh = true; + break; + default: + sb.append((char)c); + break; + } + } + } + } + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + case '-': { + StringBuilder sb = new StringBuilder(); + if (c == '-') { + sb.append((char)c); + c = rd.read(); + } + if (c == '0') { + sb.append((char)c); + c = rd.read(); + } + else if (Character.isDigit(c)) { + do { + sb.append((char)c); + c = rd.read(); + } + while (Character.isDigit(c)); + } + else + throw new ServalDInterfaceException("malformed JSON number"); + boolean isfloat = false; + if (c == '.') { + isfloat = true; + sb.append((char)c); + c = rd.read(); + if (c == -1) + throw new ServalDInterfaceException("unexpected EOF in JSON number"); + if (!Character.isDigit(c)) + throw new ServalDInterfaceException("malformed JSON number"); + do { + sb.append((char)c); + c = rd.read(); + } + while (Character.isDigit(c)); + } + if (c == 'e' || c == 'E') { + isfloat = true; + sb.append((char)c); + c = rd.read(); + if (c == '+' || c == '-') { + sb.append((char)c); + c = rd.read(); + } + if (c == -1) + throw new ServalDInterfaceException("unexpected EOF in JSON number"); + if (!Character.isDigit(c)) + throw new ServalDInterfaceException("malformed JSON number"); + do { + sb.append((char)c); + c = rd.read(); + } + while (Character.isDigit(c)); + } + rd.unread(c); + String number = sb.toString(); + try { + if (isfloat) + return Double.parseDouble(number); + else + return Integer.parseInt(number); + } + catch (NumberFormatException e) { + throw new ServalDInterfaceException("malformed JSON number: " + number); + } + } + default: + throw new ServalDInterfaceException("malformed JSON: '" + (char)c + "'"); + } + } + } + +} diff --git a/java/org/servalproject/test/Meshms.java b/java/org/servalproject/test/Meshms.java new file mode 100644 index 00000000..ee786328 --- /dev/null +++ b/java/org/servalproject/test/Meshms.java @@ -0,0 +1,72 @@ +/** + * 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.test; + +import java.lang.System; +import java.io.IOException; +import org.servalproject.servaldna.SubscriberId; + +import org.servalproject.servaldna.ServalDClient; +import org.servalproject.servaldna.ServalDInterfaceException; +import org.servalproject.servaldna.meshms.MeshMSConversationList; +import org.servalproject.servaldna.meshms.MeshMSConversation; + +public class Meshms { + + static void meshms_list_conversations(SubscriberId sid, String offset, String count) throws ServalDInterfaceException, IOException, InterruptedException + { + ServalDClient client = ServalDClient.newServalDClient(); + MeshMSConversationList list = client.meshmsListConversations(sid); + try { + MeshMSConversation conv; + while ((conv = list.nextConversation()) != null) { + System.out.println( + "_id=" + conv._id + + ", my_sid=" + conv.mySid + + ", their_sid=" + conv.theirSid + + ", read=" + conv.isRead + + ", last_message=" + conv.lastMessageOffset + + ", read_offset=" + conv.readOffset + ); + } + } + finally { + list.close(); + } + System.exit(0); + } + + public static void main(String... args) + { + if (args.length < 1) + return; + String methodName = args[0]; + try { + if (methodName.equals("meshms-list-conversations")) + meshms_list_conversations(new SubscriberId(args[1]), args.length > 2 ? args[2] : null, args.length > 3 ? args[3] : null); + } catch (Exception e) { + e.printStackTrace(); + System.exit(1); + } + System.err.println("No such command: " + methodName); + System.exit(1); + } +} diff --git a/tests/meshmsjava b/tests/meshmsjava new file mode 100755 index 00000000..57ffda50 --- /dev/null +++ b/tests/meshmsjava @@ -0,0 +1,72 @@ +#!/bin/bash + +# Tests for MeshMS Java API. +# +# Copyright 2014 Serval Project Inc. +# +# This program 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 2 +# of the License, or (at your option) any later version. +# +# This program 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 program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +source "${0%/*}/../testframework.sh" +source "${0%/*}/../testdefs.sh" +source "${0%/*}/../testdefs_java.sh" + +setup() { + setup_servald + setup_servald_so + compile_java_classes + set_instance +A + executeOk_servald config \ + set log.console.level debug \ + set debug.httpd on + create_identities 5 + configure_servald_server() { + add_servald_interface + executeOk_servald config \ + set rhizome.api.restful.users.joe.password bloggs + } + start_servald_server +} + +teardown() { + stop_all_servald_servers + kill_all_servald_processes + assert_no_servald_processes + report_all_servald_servers +} + +doc_MeshmsListConversations="Java API list MeshMS conversations" +setup_MeshmsListConversations() { + setup + # create 3 threads, with all permutations of incoming and outgoing messages + executeOk_servald meshms send message $SIDA1 $SIDA2 "Message One" + executeOk_servald meshms send message $SIDA3 $SIDA1 "Message Two" + executeOk_servald meshms send message $SIDA1 $SIDA4 "Message Three" + executeOk_servald meshms send message $SIDA4 $SIDA1 "Message Four" +} +test_MeshmsListConversations() { + executeJavaOk org.servalproject.test.Meshms meshms-list-conversations $SIDA1 + assertStdoutLineCount '==' 3 + assertStdoutGrep "my_sid=$SIDA1, their_sid=$SIDA2, read=true, last_message=0, read_offset=0" + assertStdoutGrep "my_sid=$SIDA1, their_sid=$SIDA3, read=false, last_message=14, read_offset=0" + assertStdoutGrep "my_sid=$SIDA1, their_sid=$SIDA4, read=false, last_message=18, read_offset=0" + executeOk_servald meshms read messages $SIDA1 + executeJavaOk org.servalproject.test.Meshms meshms-list-conversations $SIDA1 + assertStdoutLineCount '==' 3 + assertStdoutGrep "my_sid=$SIDA1, their_sid=$SIDA2, read=true, last_message=0, read_offset=0" + assertStdoutGrep "my_sid=$SIDA1, their_sid=$SIDA3, read=true, last_message=14, read_offset=14" + assertStdoutGrep "my_sid=$SIDA1, their_sid=$SIDA4, read=true, last_message=18, read_offset=18" +} + +runTests "$@"