Add Route Java API

This commit is contained in:
Andrew Bettison 2018-04-04 12:38:37 +09:30
parent cf9e0b4730
commit 189485a513
10 changed files with 537 additions and 7 deletions

View File

@ -24,6 +24,7 @@ import org.servalproject.codec.Base64;
import org.servalproject.servaldna.keyring.KeyringCommon;
import org.servalproject.servaldna.keyring.KeyringIdentity;
import org.servalproject.servaldna.keyring.KeyringIdentityList;
import org.servalproject.servaldna.route.RouteIdentityList;
import org.servalproject.servaldna.meshmb.MeshMBActivityList;
import org.servalproject.servaldna.meshmb.MeshMBCommon;
import org.servalproject.servaldna.meshmb.MeshMBSubscriptionList;
@ -114,6 +115,12 @@ public class ServalDClient implements ServalDHttpConnectionFactory {
return KeyringCommon.removeIdentity(this, sid, pin);
}
public RouteIdentityList routeListIdentities() throws ServalDInterfaceException, IOException {
RouteIdentityList list = new RouteIdentityList(this);
list.connect();
return list;
}
public RhizomeBundleList rhizomeListBundles() throws ServalDInterfaceException, IOException
{
RhizomeBundleList list = new RhizomeBundleList(this);

View File

@ -0,0 +1,146 @@
/**
* Copyright (C) 2016-2018 Flinders University
* Copyright (C) 2015 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.route;
import org.servalproject.json.JSONInputException;
import org.servalproject.json.JSONTokeniser;
import org.servalproject.servaldna.ContentType;
import org.servalproject.servaldna.ServalDInterfaceException;
import org.servalproject.servaldna.ServalDNotImplementedException;
import org.servalproject.servaldna.SubscriberId;
import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
public class RouteCommon
{
public static class Status {
ContentType contentType;
InputStream input_stream;
JSONTokeniser json;
public int http_status_code;
public String http_status_message;
}
protected static Status 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 Status receiveResponse(HttpURLConnection conn, int[] expected_response_codes) throws IOException, ServalDInterfaceException
{
Status status = new Status();
status.http_status_code = conn.getResponseCode();
status.http_status_message = conn.getResponseMessage();
try {
status.contentType = new ContentType(conn.getContentType());
} catch (ContentType.ContentTypeException e) {
throw new ServalDInterfaceException("malformed HTTP Content-Type: " + conn.getContentType(),e);
}
for (int code: expected_response_codes) {
if (status.http_status_code == code) {
status.input_stream = conn.getInputStream();
return status;
}
}
if (!ContentType.applicationJson.matches(status.contentType))
throw new ServalDInterfaceException("unexpected HTTP Content-Type: " + status.contentType);
if (status.http_status_code >= 300) {
status.json = new JSONTokeniser(conn.getErrorStream());
decodeRestfulStatus(status);
}
if (status.http_status_code == HttpURLConnection.HTTP_FORBIDDEN)
return status;
if (status.http_status_code == HttpURLConnection.HTTP_NOT_IMPLEMENTED)
throw new ServalDNotImplementedException(status.http_status_message);
throw new ServalDInterfaceException("unexpected HTTP response: " + status.http_status_code + " " + status.http_status_message);
}
protected static ServalDInterfaceException unexpectedResponse(HttpURLConnection conn, Status status)
{
return new ServalDInterfaceException(
"unexpected Route failure, " + quoteString(status.http_status_message)
+ " from " + conn.getURL()
);
}
protected static Status receiveRestfulResponse(HttpURLConnection conn, int expected_response_code) throws IOException, ServalDInterfaceException
{
int[] expected_response_codes = { expected_response_code };
return receiveRestfulResponse(conn, expected_response_codes);
}
protected static Status receiveRestfulResponse(HttpURLConnection conn, int[] expected_response_codes) throws IOException, ServalDInterfaceException
{
Status status = receiveResponse(conn, expected_response_codes);
status.json = new JSONTokeniser(status.input_stream);
return status;
}
protected static void decodeRestfulStatus(Status status) throws IOException, ServalDInterfaceException
{
JSONTokeniser json = status.json;
try {
json.consume(JSONTokeniser.Token.START_OBJECT);
json.consume("http_status_code");
json.consume(JSONTokeniser.Token.COLON);
int hs = json.consume(Integer.class);
json.consume(JSONTokeniser.Token.COMMA);
if (status.http_status_code == 0)
status.http_status_code = json.consume(Integer.class);
else if (hs != status.http_status_code)
throw new ServalDInterfaceException("JSON/header conflict"
+ ", http_status_code=" + hs
+ " but HTTP response code is " + status.http_status_code);
json.consume("http_status_message");
json.consume(JSONTokeniser.Token.COLON);
status.http_status_message = json.consume(String.class);
json.consume(JSONTokeniser.Token.END_OBJECT);
json.consume(JSONTokeniser.Token.EOF);
}
catch (JSONInputException e) {
throw new ServalDInterfaceException("malformed JSON status response", e);
}
}
private static String quoteString(String unquoted)
{
if (unquoted == null)
return "null";
StringBuilder b = new StringBuilder(unquoted.length() + 2);
b.append('"');
for (int i = 0; i < unquoted.length(); ++i) {
char c = unquoted.charAt(i);
if (c == '"' || c == '\\')
b.append('\\');
b.append(c);
}
b.append('"');
return b.toString();
}
}

View File

@ -0,0 +1,62 @@
/**
* Copyright (C) 2016-2018 Flinders University
* Copyright (C) 2012-2015 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.route;
import org.servalproject.servaldna.SubscriberId;
public class RouteIdentity {
public final int rowNumber;
public final SubscriberId sid;
public final String did;
public final String name;
public final boolean isSelf;
public final int hopCount;
public final boolean reachableBroadcast;
public final boolean reachableUnicast;
public final boolean reachableIndirect;
protected RouteIdentity(int rowNumber,
SubscriberId sid,
String did,
String name,
boolean isSelf,
int hopCount,
boolean reachableBroadcast,
boolean reachableUnicast,
boolean reachableIndirect)
{
assert(rowNumber >= 0);
assert(sid != null);
assert(hopCount >= 0);
this.rowNumber = rowNumber;
this.sid = sid;
this.did = did;
this.name = name;
this.isSelf = isSelf;
this.hopCount = hopCount;
this.reachableBroadcast = reachableBroadcast;
this.reachableUnicast = reachableUnicast;
this.reachableIndirect = reachableIndirect;
}
}

View File

@ -0,0 +1,143 @@
/**
* Copyright (C) 2016-2018 Flinders University
* Copyright (C) 2014-2015 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.route;
import org.servalproject.json.JSONInputException;
import org.servalproject.json.JSONTableScanner;
import org.servalproject.json.JSONTokeniser;
import org.servalproject.servaldna.ServalDHttpConnectionFactory;
import org.servalproject.servaldna.ServalDInterfaceException;
import org.servalproject.servaldna.SubscriberId;
import java.io.IOException;
import java.net.HttpURLConnection;
import java.util.ArrayList;
import java.util.List;
import java.util.Vector;
import java.util.Map;
public class RouteIdentityList {
private ServalDHttpConnectionFactory httpConnector;
private HttpURLConnection httpConnection;
private JSONTokeniser json;
private JSONTableScanner table;
int rowCount;
public RouteIdentityList(ServalDHttpConnectionFactory connector)
{
this.httpConnector = connector;
this.table = new JSONTableScanner()
.addColumn("sid", SubscriberId.class)
.addColumn("did", String.class, JSONTokeniser.Narrow.ALLOW_NULL)
.addColumn("name", String.class, JSONTokeniser.Narrow.ALLOW_NULL)
.addColumn("is_self", Boolean.class)
.addColumn("hop_count", Integer.class)
.addColumn("reachable_broadcast", Boolean.class)
.addColumn("reachable_unicast", Boolean.class)
.addColumn("reachable_indirect", Boolean.class)
;
}
public boolean isConnected()
{
return this.json != null;
}
public void connect() throws IOException, ServalDInterfaceException
{
try {
rowCount = 0;
httpConnection = httpConnector.newServalDHttpConnection("GET", "/restful/route/all.json");
httpConnection.connect();
RouteCommon.Status status = RouteCommon.receiveRestfulResponse(httpConnection, HttpURLConnection.HTTP_OK);
json = status.json;
json.consume(JSONTokeniser.Token.START_OBJECT);
json.consume("header");
json.consume(JSONTokeniser.Token.COLON);
table.consumeHeaderArray(json);
json.consume(JSONTokeniser.Token.COMMA);
json.consume("rows");
json.consume(JSONTokeniser.Token.COLON);
json.consume(JSONTokeniser.Token.START_ARRAY);
}
catch (JSONInputException e) {
throw new ServalDInterfaceException(e);
}
}
public static List<RouteIdentity> getTestIdentities() {
try {
List<RouteIdentity> ret = new ArrayList<RouteIdentity>();
byte[] sid = new byte[SubscriberId.BINARY_SIZE];
for (int i = 0; i < 10; i++) {
sid[0]=(byte)i;
ret.add(new RouteIdentity(i, new SubscriberId(sid), "555000" + i, "Agent " + i, i < 5, i, i >= 5, i >= 6, i >= 7));
}
return ret;
}catch (Exception e){
throw new IllegalStateException(e);
}
}
public RouteIdentity nextIdentity() throws ServalDInterfaceException, IOException
{
try {
Object tok = json.nextToken();
if (tok == JSONTokeniser.Token.END_ARRAY) {
json.consume(JSONTokeniser.Token.END_OBJECT);
json.consume(JSONTokeniser.Token.EOF);
return null;
}
if (rowCount != 0)
JSONTokeniser.match(tok, JSONTokeniser.Token.COMMA);
else
json.pushToken(tok);
Map<String,Object> row = table.consumeRowArray(json);
return new RouteIdentity(
rowCount++,
(SubscriberId)row.get("sid"),
(String)row.get("did"),
(String)row.get("name"),
(Boolean)row.get("is_self"),
(Integer)row.get("hop_count"),
(Boolean)row.get("reachable_broadcast"),
(Boolean)row.get("reachable_unicast"),
(Boolean)row.get("reachable_indirect")
);
}
catch (JSONInputException e) {
throw new ServalDInterfaceException(e);
}
}
public void close() throws IOException
{
httpConnection = null;
if (json != null) {
json.close();
json = null;
}
}
}

View File

@ -1,5 +1,6 @@
/**
* Copyright (C) 2015 Serval Project Inc.
* Copyright (C) 2016 Flinders University
*
* This file is part of Serval Software (http://www.servalproject.org)
*

View File

@ -0,0 +1,74 @@
/**
* Copyright (C) 2015 Serval Project Inc.
* Copyright (C) 2018 Flinders University
*
* 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.io.IOException;
import org.servalproject.servaldna.ServalDClient;
import org.servalproject.servaldna.ServalDInterfaceException;
import org.servalproject.servaldna.ServerControl;
import org.servalproject.servaldna.route.RouteIdentityList;
import org.servalproject.servaldna.route.RouteIdentity;
public class Route {
static void route_list() throws ServalDInterfaceException, IOException, InterruptedException
{
ServalDClient client = new ServerControl().getRestfulClient();
RouteIdentityList list = null;
try {
list = client.routeListIdentities();
RouteIdentity id;
while ((id = list.nextIdentity()) != null) {
System.out.println("sid=" + id.sid +
", did=" + id.did +
", name=" + id.name +
", isSelf=" + id.isSelf +
", hopCount=" + id.hopCount +
", reachableBroadcast=" + id.reachableBroadcast +
", reachableUnicast=" + id.reachableUnicast +
", reachableIndirect=" + id.reachableIndirect
);
}
}
finally {
if (list != null)
list.close();
}
System.exit(0);
}
public static void main(String... args)
{
if (args.length < 1)
return;
String methodName = args[0];
try {
if (methodName.equals("list-all"))
route_list();
} catch (Exception e) {
e.printStackTrace();
System.exit(1);
}
System.err.println("No such command: " + methodName);
System.exit(1);
}
}

View File

@ -146,13 +146,13 @@ static int restful_route_list_json_content_chunk(struct http_request *hr, strbuf
strbuf_json_integer(b, subscriber->hop_count);
// first_hop
strbuf_puts(b, ",");
if (subscriber->hop_count > 1)
if (subscriber->next_hop)
strbuf_json_string(b, alloca_tohex_sid_t(subscriber->next_hop->sid));
else
strbuf_json_null(b);
// penultimate_hop
strbuf_puts(b, ",");
if (subscriber->hop_count > 2)
if (subscriber->prior_hop)
strbuf_json_string(b, alloca_tohex_sid_t(subscriber->prior_hop->sid));
else
strbuf_json_null(b);

View File

@ -3,7 +3,7 @@
# Aggregation of all Java tests.
#
# Copyright 2012-2014 Serval Project Inc.
# Copyright 2017 Flinders University
# Copyright 2017-2018 Flinders University
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
@ -24,6 +24,7 @@ source "${0%/*}/../testdefs.sh"
includeTests jni
includeTests keyringjava
includeTests routejava
includeTests rhizomejava
includeTests meshmsjava

88
tests/routejava Executable file
View File

@ -0,0 +1,88 @@
#!/bin/bash
# Tests for Serval DNA Route Java API
#
# Copyright 2018 Flinders Univerity
#
# 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_routing.sh"
source "${0%/*}/../testdefs_rest.sh"
source "${0%/*}/../testdefs_java.sh"
setup() {
setup_rest_utilities #XXX
setup_servald
setup_servald_so
assert_java_classes_exist
assert_no_servald_processes
}
teardown() {
stop_all_servald_servers
kill_all_servald_processes
assert_no_servald_processes
report_all_servald_servers
}
configure_servald_server() {
executeOk_servald config \
set debug.mdprequests yes \
set debug.linkstate yes \
set debug.subscriber yes \
set debug.verbose yes \
set debug.overlayrouting yes \
set debug.overlayinterfaces yes \
set log.console.level debug \
set log.console.show_pid on \
set log.console.show_time on \
set rhizome.enable no
}
doc_RouteListAll="Java API list entire routing table"
setup_RouteListAll() {
setup
setup_rest_config +A #XXX
DIDA1=565656
NAMEA1="Neddy Seagoon"
DIDA2=3020304
NAMEA2="Spike Milligan"
foreach_instance +A +B create_identities 2
foreach_instance +A +B add_servald_interface 1
foreach_instance +A +B start_servald_server
wait_until_rest_server_ready +A
get_servald_primary_sid +B PRIMARY_SIDB
wait_until --timeout=20 path_exists +A +B
wait_until --timeout=10 path_exists +B +A
set_instance +A
rest_request GET "/restful/route/all.json" #XXX
}
test_RouteListAll() {
executeJavaOk org.servalproject.test.Route list-all
tfw_cat --stdout --stderr
assertStdoutLineCount == 4
assertStdoutGrep --matches=1 "sid=$SIDA1, did=$DIDA1, name=$NAMEA1, isSelf=true, hopCount=0"
assertStdoutGrep --matches=1 "sid=$SIDA2, did=$DIDA2, name=$NAMEA2, isSelf=true, hopCount=0"
assertStdoutGrep --matches=1 "sid=$PRIMARY_SIDB,.*isSelf=false, hopCount=1,.*reachableUnicast=true,.*reachableIndirect=false"
for SID in "${SIDB[@]}"; do
if [ "$SID" != "$PRIMARY_SIDB" ]; then
assertStdoutGrep --matches=1 "sid=$SID,.*isSelf=false, hopCount=2,.*reachableUnicast=false,.*reachableIndirect=true"
fi
done
}
runTests "$@"

View File

@ -87,6 +87,10 @@ test_AuthBasicWrong() {
doc_RouteListAll="REST API list entire routing table"
setup_RouteListAll() {
setup
DIDA1=565656
NAMEA1="Neddy Seagoon"
DIDA2=3020304
NAMEA2="Spike Milligan"
foreach_instance +A +B create_identities 2
foreach_instance +A +B add_servald_interface 1
foreach_instance +A +B start_servald_server
@ -100,20 +104,24 @@ test_RouteListAll() {
rest_request GET "/restful/route/all.json"
transform_list_json response.json routes.json
assert [ "$(jq 'length' routes.json)" = 4 ]
assertJq routes.json 'contains([{"sid": "'$SIDA1'",
assertJq routes.json 'contains([{"sid": "'"$SIDA1"'",
"did": "'"$DIDA1"'",
"name": "'"$NAMEA1"'",
"is_self": true,
"hop_count": 0}])'
assertJq routes.json 'contains([{"sid": "'$SIDA2'",
assertJq routes.json 'contains([{"sid": "'"$SIDA2"'",
"did": "'"$DIDA2"'",
"name": "'"$NAMEA2"'",
"is_self": true,
"hop_count": 0}])'
assertJq routes.json 'contains([{"sid": "'$PRIMARY_SIDB'",
assertJq routes.json 'contains([{"sid": "'"$PRIMARY_SIDB"'",
"is_self": false,
"reachable_unicast": true,
"reachable_indirect": false,
"hop_count": 1}])'
for SID in "${SIDB[@]}"; do
if [ "$SID" != "$PRIMARY_SIDB" ]; then
assertJq routes.json 'contains([{"sid": "'$SID'",
assertJq routes.json 'contains([{"sid": "'"$SID"'",
"is_self": false,
"reachable_unicast": false,
"reachable_indirect": true,