commit b68c556e1743420c1f12a33d5f727f3972f9bb45 Author: Karsten Loesing karsten.loesing@gmx.net Date: Wed Aug 16 11:28:59 2017 +0200
Start using a query response object.
This is another prerequisite for changing queries towards making a single query per request (#16596) and towards splitting up ExoneraTor into front-end and back-end. --- .../torproject/exonerator/ExoneraTorServlet.java | 207 ++++++++++++--------- .../org/torproject/exonerator/QueryResponse.java | 63 +++++++ 2 files changed, 186 insertions(+), 84 deletions(-)
diff --git a/src/main/java/org/torproject/exonerator/ExoneraTorServlet.java b/src/main/java/org/torproject/exonerator/ExoneraTorServlet.java index ca5d34b..b7d0497 100644 --- a/src/main/java/org/torproject/exonerator/ExoneraTorServlet.java +++ b/src/main/java/org/torproject/exonerator/ExoneraTorServlet.java @@ -105,89 +105,41 @@ public class ExoneraTorServlet extends HttpServlet {
/* Step 2: Query the database. */
- /* Query the following data. */ boolean successfullyConnectedToDatabase = false; String firstDate = null; String lastDate = null; boolean noRelevantConsensuses = true; - List<String[]> statusEntries = null; + List<String[]> statusEntries = new ArrayList<>(); List<String> addressesInSameNetwork = null;
/* Only query the database if we received valid user input. */ if (null != relayIp && !relayIp.isEmpty() && null != timestampStr && !timestampStr.isEmpty()) { - - /* Open a database connection that we'll use to handle the whole - * request. */ - long requestedConnection = System.currentTimeMillis(); - Connection conn = this.connectToDatabase(); - if (null != conn) { + QueryResponse queryResponse = this.queryDatabase(relayIp, timestampStr); + if (null != queryResponse) { successfullyConnectedToDatabase = true; - - /* Look up first and last date in the database. */ - long[] firstAndLastDates = this.queryFirstAndLastDatesFromDatabase( - conn); - if (null != firstAndLastDates) { - SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd"); - dateFormat.setTimeZone(TimeZone.getTimeZone("UTC")); - firstDate = dateFormat.format(firstAndLastDates[0]); - lastDate = dateFormat.format(firstAndLastDates[1]); - - /* Consider all consensuses published on or within a day of the given - * date. */ - long timestamp = 0L; - if (timestampStr != null && timestampStr.length() > 0) { - try { - timestamp = dateFormat.parse(timestampParameter).getTime(); - } catch (ParseException e) { - /* Already checked in parseTimestamp(). */ - } - } - long timestampFrom = timestamp - 24L * 60L * 60L * 1000L; - long timestampTo = timestamp + 2 * 24L * 60L * 60L * 1000L - 1L; - SimpleDateFormat validAfterTimeFormat = new SimpleDateFormat( - "yyyy-MM-dd HH:mm:ss"); - validAfterTimeFormat.setTimeZone(TimeZone.getTimeZone("UTC")); - String fromValidAfter = validAfterTimeFormat.format(timestampFrom); - String toValidAfter = validAfterTimeFormat.format(timestampTo); - SortedSet<Long> relevantConsensuses - = this.queryKnownConsensusValidAfterTimes(conn, - fromValidAfter, toValidAfter); - if (null != relevantConsensuses && !relevantConsensuses.isEmpty()) { - noRelevantConsensuses = false; - - /* Search for status entries with the given IP address as onion - * routing address, plus status entries of relays having an exit - * list entry with the given IP address as exit address. */ - statusEntries = this.queryStatusEntries(conn, relayIp, - timestamp, validAfterTimeFormat); - - /* If we didn't find anything, run another query to find out if - * there are relays running on other IP addresses in the same /24 or - * /48 network and tell the user about it. */ - if (statusEntries.isEmpty()) { - addressesInSameNetwork = new ArrayList<>(); - if (!relayIp.contains(":")) { - String address24 = this.convertIpV4ToHex(relayIp) - .substring(0, 6); - if (address24 != null) { - addressesInSameNetwork = this.queryAddressesInSame24(conn, - address24, timestamp); - } - } else { - String address48 = this.convertIpV6ToHex(relayIp) - .substring(0, 12); - if (address48 != null) { - addressesInSameNetwork = this.queryAddressesInSame48(conn, - address48, timestamp); - } - } + firstDate = queryResponse.firstDateInDatabase; + lastDate = queryResponse.lastDateInDatabase; + if (null != queryResponse.relevantStatuses + && queryResponse.relevantStatuses) { + noRelevantConsensuses = false; + } + if (null != queryResponse.matches) { + for (QueryResponse.Match match : queryResponse.matches) { + StringBuilder sb = new StringBuilder(); + int writtenAddresses = 0; + for (String address : match.addresses) { + sb.append((writtenAddresses++ > 0 ? ", " : "") + address); } + String[] statusEntry = new String[]{match.timestamp, + sb.toString(), match.fingerprint, match.nickname, + null == match.exit ? "U" : (match.exit ? "Y" : "N")}; + statusEntries.add(statusEntry); } } - - /* Close the database connection. */ - this.closeDatabaseConnection(conn, requestedConnection); + if (null != queryResponse.nearbyAddresses) { + addressesInSameNetwork = Arrays.asList(queryResponse.nearbyAddresses); + } } }
@@ -411,6 +363,94 @@ public class ExoneraTorServlet extends HttpServlet {
/* Helper methods for querying the database. */
+ private QueryResponse queryDatabase(String relayIp, String timestampStr) { + + QueryResponse response = null; + SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd"); + dateFormat.setTimeZone(TimeZone.getTimeZone("UTC")); + SimpleDateFormat validAfterTimeFormat = new SimpleDateFormat( + "yyyy-MM-dd HH:mm:ss"); + validAfterTimeFormat.setTimeZone(TimeZone.getTimeZone("UTC")); + long timestamp = 0L; + if (timestampStr != null && timestampStr.length() > 0) { + try { + timestamp = dateFormat.parse(timestampStr).getTime(); + } catch (ParseException e) { + /* Already checked in parseTimestamp(). */ + } + } + + /* Only query the database if we received valid user input. */ + if (!"".equals(relayIp) && !"".equals(timestampStr)) { + + /* Open a database connection that we'll use to handle the whole + * request. */ + long requestedConnection = System.currentTimeMillis(); + Connection conn = this.connectToDatabase(); + if (null != conn) { + + response = new QueryResponse(); + response.queryAddress = relayIp; + response.queryDate = timestampStr; + + /* Look up first and last date in the database. */ + long[] firstAndLastDates = this.queryFirstAndLastDatesFromDatabase( + conn); + if (null != firstAndLastDates) { + response.firstDateInDatabase = dateFormat.format( + firstAndLastDates[0]); + response.lastDateInDatabase = dateFormat.format(firstAndLastDates[1]); + + /* Consider all consensuses published on or within a day of the given + * date. */ + long timestampFrom = timestamp - 24L * 60L * 60L * 1000L; + long timestampTo = timestamp + 2 * 24L * 60L * 60L * 1000L - 1L; + String fromValidAfter = validAfterTimeFormat.format(timestampFrom); + String toValidAfter = validAfterTimeFormat.format(timestampTo); + SortedSet<Long> relevantConsensuses = + this.queryKnownConsensusValidAfterTimes(conn, fromValidAfter, + toValidAfter); + if (null != relevantConsensuses && !relevantConsensuses.isEmpty()) { + response.relevantStatuses = true; + + /* Search for status entries with the given IP address as onion + * routing address, plus status entries of relays having an exit + * list entry with the given IP address as exit address. */ + List<QueryResponse.Match> matches = this.queryStatusEntries(conn, + relayIp, timestamp, validAfterTimeFormat); + if (!matches.isEmpty()) { + response.matches = matches.toArray(new QueryResponse.Match[0]); + + /* If we didn't find anything, run another query to find out if + * there are relays running on other IP addresses in the same /24 or + * /48 network and tell the user about it. */ + } else { + if (!relayIp.contains(":")) { + String address24 = this.convertIpV4ToHex(relayIp) + .substring(0, 6); + if (address24 != null) { + response.nearbyAddresses = this.queryAddressesInSame24(conn, + address24, timestamp).toArray(new String[0]); + } + } else { + String address48 = this.convertIpV6ToHex(relayIp) + .substring(0, 12); + if (address48 != null) { + response.nearbyAddresses = this.queryAddressesInSame48(conn, + address48, timestamp).toArray(new String[0]); + } + } + } + } + } + + /* Close the database connection. */ + this.closeDatabaseConnection(conn, requestedConnection); + } + } + return response; + } + private Connection connectToDatabase() { Connection conn = null; try { @@ -468,10 +508,10 @@ public class ExoneraTorServlet extends HttpServlet { return relevantConsensuses; }
- private List<String[]> queryStatusEntries(Connection conn, + private List<QueryResponse.Match> queryStatusEntries(Connection conn, String relayIp, long timestamp, SimpleDateFormat validAfterTimeFormat) { - List<String[]> statusEntries = new ArrayList<>(); + List<QueryResponse.Match> matches = new ArrayList<>(); String addressHex = !relayIp.contains(":") ? this.convertIpV4ToHex(relayIp) : this.convertIpV6ToHex(relayIp); if (addressHex == null) { @@ -496,7 +536,7 @@ public class ExoneraTorServlet extends HttpServlet { SortedSet<String> addresses = new TreeSet<>(); SortedSet<String> addressesHex = new TreeSet<>(); String nickname = null; - String exit = "U"; + Boolean exit = null; for (String line : new String(rawstatusentry).split("\n")) { if (line.startsWith("r ")) { String[] parts = line.split(" "); @@ -512,7 +552,7 @@ public class ExoneraTorServlet extends HttpServlet { : this.convertIpV6ToHex(address); addressesHex.add(orAddressHex); } else if (line.startsWith("p ")) { - exit = line.equals("p reject 1-65535") ? "N" : "Y"; + exit = !line.equals("p reject 1-65535"); } } String exitaddress = rs.getString(4); @@ -523,25 +563,24 @@ public class ExoneraTorServlet extends HttpServlet { if (!addressesHex.contains(addressHex)) { continue; } - StringBuilder sb = new StringBuilder(); - int writtenAddresses = 0; - for (String address : addresses) { - sb.append((writtenAddresses++ > 0 ? ", " : "") + address); - } long validafter = rs.getTimestamp(2, utcCalendar).getTime(); String validAfterString = validAfterTimeFormat.format(validafter); String fingerprint = rs.getString(3).toUpperCase(); - String[] statusEntry = new String[] { validAfterString, - sb.toString(), fingerprint, nickname, exit }; - statusEntries.add(statusEntry); + QueryResponse.Match match = new QueryResponse.Match(); + match.timestamp = validAfterString; + match.addresses = addresses.toArray(new String[0]); + match.fingerprint = fingerprint; + match.nickname = nickname; + match.exit = exit; + matches.add(match); } rs.close(); cs.close(); } catch (SQLException e) { /* Nothing found. */ - statusEntries = null; + matches.clear(); } - return statusEntries; + return matches; }
private List<String> queryAddressesInSame24(Connection conn, diff --git a/src/main/java/org/torproject/exonerator/QueryResponse.java b/src/main/java/org/torproject/exonerator/QueryResponse.java new file mode 100644 index 0000000..d86efb0 --- /dev/null +++ b/src/main/java/org/torproject/exonerator/QueryResponse.java @@ -0,0 +1,63 @@ +/* Copyright 2017 The Tor Project + * See LICENSE for licensing information */ + +package org.torproject.exonerator; + +import com.google.gson.annotations.SerializedName; + +/** Query response from the ExoneraTor database. */ +public class QueryResponse { + + /** Query IP address passed in the request; never <code>null</code>. */ + @SerializedName("query_address") + String queryAddress; + + /** Query date passed in the request; never <code>null</code>. */ + @SerializedName("query_date") + String queryDate; + + /** ISO-formatted valid-after time of the first status contained in the + * database; only <code>null</code> if the database is empty. */ + @SerializedName("first_date_in_database") + String firstDateInDatabase; + + /** ISO-formatted valid-after time of the last status contained in the + * database; only <code>null</code> if the database is empty. */ + @SerializedName("last_date_in_database") + String lastDateInDatabase; + + /** Whether there is at least one relevant status in the database on or within + * a day of the requested date; <code>null</code> if the database is empty. */ + @SerializedName("relevant_statuses") + Boolean relevantStatuses; + + /** All matches for the given IP address and date; <code>null</code> if there + * were no matches at all. */ + Match[] matches; + + /** Match details. */ + static class Match { + + /** ISO-formatted valid-after time of the status containing the match. */ + String timestamp; + + /** All known IP addresses of the relay at the time. */ + String[] addresses; + + /** Relay fingerprint. */ + String fingerprint; + + /** Relay nickname. */ + String nickname; + + /** Whether this relay permitted exiting or not; <code>null</code> if + * unknown. */ + Boolean exit; + } + + /** All known IP addresses in the same /24 or /48 network; <code>null</code> + * if there were direct matches for the given IP address. */ + @SerializedName("nearby_addresses") + String[] nearbyAddresses; +} +