From b556b758ea7f9f18fd3805e0f71bd44143c19b42 Mon Sep 17 00:00:00 2001 From: akafredperry Date: Wed, 28 Jan 2026 11:01:17 +0000 Subject: [PATCH] feat:added spotless and checkstyle configuration and added to maven build lifecycle reformatted and adjusted to make code pass checkstyle and spotless rules --- .gitattributes | 1 + SECURITY.md | 80 +- at_client/pom.xml | 458 +++-- .../java/org/atsign/client/api/AtClient.java | 292 +-- .../org/atsign/client/api/AtConnection.java | 32 +- .../client/api/AtConnectionFactory.java | 29 +- .../java/org/atsign/client/api/AtEvents.java | 76 +- .../java/org/atsign/client/api/Secondary.java | 279 +-- .../client/api/impl/clients/AtClientImpl.java | 1498 +++++++-------- .../impl/connections/AtConnectionBase.java | 267 +-- .../impl/connections/AtMonitorConnection.java | 431 ++--- .../impl/connections/AtRootConnection.java | 108 +- .../connections/AtSecondaryConnection.java | 84 +- .../DefaultAtConnectionFactory.java | 51 +- .../api/impl/events/SimpleAtEventBus.java | 73 +- .../api/impl/secondaries/RemoteSecondary.java | 285 +-- .../java/org/atsign/client/cli/Delete.java | 63 +- .../java/org/atsign/client/cli/DumpKeys.java | 10 +- .../main/java/org/atsign/client/cli/Get.java | 65 +- .../java/org/atsign/client/cli/Onboard.java | 175 +- .../main/java/org/atsign/client/cli/REPL.java | 443 ++--- .../java/org/atsign/client/cli/Register.java | 444 ++--- .../main/java/org/atsign/client/cli/Scan.java | 222 +-- .../java/org/atsign/client/cli/Share.java | 95 +- .../java/org/atsign/client/util/ArgsUtil.java | 30 +- .../client/util/AtClientValidation.java | 190 +- .../java/org/atsign/client/util/AuthUtil.java | 132 +- .../java/org/atsign/client/util/ByteUtil.java | 41 +- .../org/atsign/client/util/CameraUtil.java | 189 +- .../org/atsign/client/util/Constants.java | 12 +- .../java/org/atsign/client/util/DateUtil.java | 24 +- .../atsign/client/util/EncryptionUtil.java | 214 ++- .../java/org/atsign/client/util/FileUtil.java | 48 +- .../org/atsign/client/util/ImageUtil.java | 166 +- .../org/atsign/client/util/KeyStringUtil.java | 379 ++-- .../java/org/atsign/client/util/KeysUtil.java | 186 +- .../atsign/client/util/OnboardingUtil.java | 95 +- .../org/atsign/client/util/Preconditions.java | 10 +- .../org/atsign/client/util/RegisterUtil.java | 509 +++--- .../java/org/atsign/common/ApiCallStatus.java | 4 +- .../java/org/atsign/common/AtException.java | 18 +- .../main/java/org/atsign/common/AtSign.java | 88 +- .../java/org/atsign/common/KeyBuilders.java | 476 ++--- .../src/main/java/org/atsign/common/Keys.java | 364 ++-- .../main/java/org/atsign/common/Metadata.java | 335 ++-- .../org/atsign/common/NotificationStatus.java | 2 +- .../org/atsign/common/RegisterApiResult.java | 6 +- .../org/atsign/common/RegisterApiTask.java | 85 +- .../atsign/common/ResponseTransformers.java | 81 +- .../java/org/atsign/common/VerbBuilders.java | 1470 +++++++-------- .../AtBlockedConnectionException.java | 6 +- .../exceptions/AtBufferOverFlowException.java | 6 +- .../exceptions/AtClientConfigException.java | 12 +- .../exceptions/AtDecryptionException.java | 8 +- .../exceptions/AtEncryptionException.java | 12 +- .../exceptions/AtHandShakeException.java | 6 +- .../AtIllegalArgumentException.java | 6 +- .../AtInboundConnectionLimitException.java | 6 +- .../exceptions/AtInternalServerError.java | 6 +- .../exceptions/AtInternalServerException.java | 6 +- .../exceptions/AtInvalidAtKeyException.java | 6 +- .../exceptions/AtInvalidSyntaxException.java | 6 +- .../exceptions/AtKeyNotFoundException.java | 6 +- .../AtNewErrorCodeWhoDisException.java | 8 +- .../AtNotYetImplementedException.java | 6 +- .../AtOutboundConnectionLimitException.java | 6 +- .../exceptions/AtRegistrarException.java | 12 +- .../AtResponseHandlingException.java | 12 +- .../AtSecondaryConnectException.java | 12 +- .../AtSecondaryNotFoundException.java | 12 +- .../exceptions/AtServerIsPausedException.java | 6 +- .../exceptions/AtServerRuntimeException.java | 6 +- .../common/exceptions/AtTimeoutException.java | 6 +- .../AtUnauthenticatedException.java | 6 +- .../exceptions/AtUnauthorizedException.java | 6 +- .../AtUnknownResponseException.java | 6 +- .../exceptions/MalformedKeyException.java | 6 +- .../exceptions/RemoteLookupException.java | 6 +- .../common/options/GetRequestOptions.java | 28 +- .../atsign/common/options/RequestOptions.java | 2 +- .../response_models/LookupResponse.java | 14 +- .../java/org/atsign/config/ConfigReader.java | 50 +- .../atsign/common/AtClientValidationTest.java | 464 +++-- .../java/org/atsign/common/ByteUtilTest.java | 142 +- .../org/atsign/common/ConfigReaderTest.java | 52 +- .../java/org/atsign/common/DateUtilTest.java | 26 +- .../org/atsign/common/FromStringTest.java | 169 +- .../org/atsign/common/KeyStringUtilTest.java | 990 +++++----- .../java/org/atsign/common/KeysUtilTest.java | 162 +- .../common/ResponseTransformerTest.java | 131 +- .../org/atsign/common/VerbBuildersTest.java | 1617 ++++++++--------- .../org/atsign/cucumber/CucumberTests.java | 3 +- .../org/atsign/cucumber/helpers/Helpers.java | 8 +- .../cucumber/steps/AtClientContext.java | 7 +- .../atsign/cucumber/steps/GetAtKeysSteps.java | 81 +- .../atsign/cucumber/steps/MonitorSteps.java | 2 +- .../cucumber/steps/PublicAtKeySteps.java | 12 +- .../atsign/cucumber/steps/SelfAtKeySteps.java | 2 +- .../cucumber/steps/SharedAtKeySteps.java | 50 +- config/checkstyle.xml | 52 + config/java-format.xml | 337 ++++ 101 files changed, 8132 insertions(+), 7192 deletions(-) create mode 100644 .gitattributes create mode 100644 config/checkstyle.xml create mode 100644 config/java-format.xml diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 00000000..6313b56c --- /dev/null +++ b/.gitattributes @@ -0,0 +1 @@ +* text=auto eol=lf diff --git a/SECURITY.md b/SECURITY.md index f4d0ce33..d737d0f7 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -1,41 +1,41 @@ -The Atsign FoundationThe Atsign Foundation - -# Atsign Foundation Open Source Security Policies and Procedures - -This document outlines security procedures and general policies for the -Atsign Foundation Open Source projects as found on -https://github.com/atsign-foundation. - - * [Reporting a Vulnerability](#reporting-a-vulnerability) - * [Disclosure Policy](#disclosure-policy) - -## Reporting a Vulnerability - -The Atsign Foundation team and community take all security vulnerabilities -seriously. Thank you for improving the security of our open source -software. We appreciate your efforts and responsible disclosure and will -make every effort to acknowledge your contributions. - -Report security vulnerabilities by emailing the Atsign security team at: - - security@atsign.com - -The lead maintainer will acknowledge your email within 24 hours, and will -send a more detailed response within 48 hours indicating the next steps in -handling your report. After the initial reply to your report, the security -team will endeavor to keep you informed of the progress towards a fix and -full announcement, and may ask for additional information or guidance. - -Please report security vulnerabilities in third-party modules to the person -or team maintaining the module. - -## Disclosure Policy - -When the security team receives a security bug report, they will assign it -to a primary handler. This person will coordinate the fix and release -process, involving the following steps: - - * Confirm the problem and determine the affected versions. - * Audit code to find any potential similar problems. - * Prepare fixes for all releases still under maintenance. These fixes +The Atsign FoundationThe Atsign Foundation + +# Atsign Foundation Open Source Security Policies and Procedures + +This document outlines security procedures and general policies for the +Atsign Foundation Open Source projects as found on +https://github.com/atsign-foundation. + + * [Reporting a Vulnerability](#reporting-a-vulnerability) + * [Disclosure Policy](#disclosure-policy) + +## Reporting a Vulnerability + +The Atsign Foundation team and community take all security vulnerabilities +seriously. Thank you for improving the security of our open source +software. We appreciate your efforts and responsible disclosure and will +make every effort to acknowledge your contributions. + +Report security vulnerabilities by emailing the Atsign security team at: + + security@atsign.com + +The lead maintainer will acknowledge your email within 24 hours, and will +send a more detailed response within 48 hours indicating the next steps in +handling your report. After the initial reply to your report, the security +team will endeavor to keep you informed of the progress towards a fix and +full announcement, and may ask for additional information or guidance. + +Please report security vulnerabilities in third-party modules to the person +or team maintaining the module. + +## Disclosure Policy + +When the security team receives a security bug report, they will assign it +to a primary handler. This person will coordinate the fix and release +process, involving the following steps: + + * Confirm the problem and determine the affected versions. + * Audit code to find any potential similar problems. + * Prepare fixes for all releases still under maintenance. These fixes will be released as fast as possible to pub.dev where applicable. \ No newline at end of file diff --git a/at_client/pom.xml b/at_client/pom.xml index 818dc167..056f1f0a 100644 --- a/at_client/pom.xml +++ b/at_client/pom.xml @@ -1,21 +1,21 @@ - 4.0.0 + 4.0.0 org.atsign at_client 0.0.1-SNAPSHOT at_client - atsign Java client SDK - https://github.com/atsign-foundation/at_java - - - 8 - 8 - true - UTF-8 + atsign Java client SDK + https://github.com/atsign-foundation/at_java + + + 8 + 8 + true + UTF-8 false ${skipTests} ${skipTests} @@ -24,79 +24,78 @@ 1.2.0 - - - BSD 3-Clause License - https://github.com/atsign-foundation/at_java/blob/trunk/LICENSE - - + + + BSD 3-Clause License + https://github.com/atsign-foundation/at_java/blob/trunk/LICENSE + + - + atsign maven@atsign.com atsign https://atsign.com - + - - scm:git:git://github.com/atsign-foundation/at_java.git - scm:git:ssh://git@github.com:atsign-foundation/at_java.git - https://github.com/atsign-foundation/at_java/tree/trunk - + + scm:git:git://github.com/atsign-foundation/at_java.git + scm:git:ssh://git@github.com:atsign-foundation/at_java.git + https://github.com/atsign-foundation/at_java/tree/trunk + - - + + - + - com.googlecode.maven-download-plugin - download-maven-plugin - 1.6.8 - - - download-at_demo_data - generate-test-resources - - wget - - - https://pub.dev/packages/at_demo_data/versions/${version.at_demo_data}.tar.gz - ${project.build.directory}/at_demo_data - at_demo_data.tar.gz - true - - - - - - - org.codehaus.mojo - build-helper-maven-plugin - 3.0.0 - - - add-integration-test-source - generate-test-sources - - add-test-source - - - - src/integration-test/java - - - - - - - maven-surefire-plugin - 3.0.0-M7 - - ${skipUnitTests} - - + com.googlecode.maven-download-plugin + download-maven-plugin + 1.6.8 + + + download-at_demo_data + generate-test-resources + + wget + + + https://pub.dev/packages/at_demo_data/versions/${version.at_demo_data}.tar.gz + ${project.build.directory}/at_demo_data + true + + + + + + + org.codehaus.mojo + build-helper-maven-plugin + 3.0.0 + + + add-integration-test-source + generate-test-sources + + add-test-source + + + + src/integration-test/java + + + + + + + maven-surefire-plugin + 3.0.0-M7 + + ${skipUnitTests} + + org.apache.maven.plugins maven-failsafe-plugin @@ -115,18 +114,18 @@ - org.apache.maven.plugins - maven-source-plugin + org.apache.maven.plugins + maven-source-plugin 3.3.1 - - - attach-sources + + + attach-sources - jar - - - - + jar + + + + org.apache.maven.plugins @@ -172,128 +171,185 @@ + + com.diffplug.spotless + spotless-maven-plugin + 2.30.0 + + + spotless-check + validate + + check + + + + + + + ${project.basedir}/../config/java-format.xml + + + + + + + **/pom.xml + + + + + true + 2 + + + + + + + + org.apache.maven.plugins + maven-checkstyle-plugin + 3.3.1 + + ../config/checkstyle.xml + true + true + false + + + + checkstyle + validate + + check + + + + + - + - - com.fasterxml.jackson.core - jackson-core - 2.15.0 - - - - com.fasterxml.jackson.core - jackson-annotations - 2.14.0 - - - - com.fasterxml.jackson.core - jackson-databind - 2.14.0 - - - - - com.fasterxml.jackson.dataformat - jackson-dataformat-yaml - 2.14.0 - - - - org.bouncycastle - bcprov-jdk15to18 - 1.78 - - - - junit - junit - 4.13.2 - test - - - org.apache.commons - commons-lang3 - 3.18.0 - - - info.picocli - picocli - 4.7.0 - - - org.mockito - mockito-inline - 4.8.0 - - - org.mockito - mockito-junit-jupiter - 4.8.0 - test - - - com.github.sarxos - webcam-capture - 0.3.12 - - - org.fusesource.jansi - jansi - 2.4.0 - - - - io.cucumber - cucumber-java - ${version.cucumber} - test - - - - io.cucumber - cucumber-junit - ${version.cucumber} - test - - - - io.cucumber - cucumber-picocontainer - ${version.cucumber} - test - - - - org.testcontainers - testcontainers - 1.21.3 - test - - - - org.slf4j - slf4j-api - 2.0.7 - test - - - - org.slf4j - slf4j-simple - 2.0.7 - test - - - - org.hamcrest - hamcrest-all - 1.3 - test - - - + + com.fasterxml.jackson.core + jackson-core + 2.15.0 + + + + com.fasterxml.jackson.core + jackson-annotations + 2.14.0 + + + + com.fasterxml.jackson.core + jackson-databind + 2.14.0 + + + + + com.fasterxml.jackson.dataformat + jackson-dataformat-yaml + 2.14.0 + + + + org.bouncycastle + bcprov-jdk15to18 + 1.78 + + + + junit + junit + 4.13.2 + test + + + org.apache.commons + commons-lang3 + 3.18.0 + + + info.picocli + picocli + 4.7.0 + + + org.mockito + mockito-inline + 4.8.0 + + + org.mockito + mockito-junit-jupiter + 4.8.0 + test + + + com.github.sarxos + webcam-capture + 0.3.12 + + + org.fusesource.jansi + jansi + 2.4.0 + + + + io.cucumber + cucumber-java + ${version.cucumber} + test + + + + io.cucumber + cucumber-junit + ${version.cucumber} + test + + + + io.cucumber + cucumber-picocontainer + ${version.cucumber} + test + + + + org.testcontainers + testcontainers + 1.21.3 + test + + + + org.slf4j + slf4j-api + 2.0.7 + test + + + + org.slf4j + slf4j-simple + 2.0.7 + test + + + + org.hamcrest + hamcrest-all + 1.3 + test + + + diff --git a/at_client/src/main/java/org/atsign/client/api/AtClient.java b/at_client/src/main/java/org/atsign/client/api/AtClient.java index 205b32cf..efef6f6a 100644 --- a/at_client/src/main/java/org/atsign/client/api/AtClient.java +++ b/at_client/src/main/java/org/atsign/client/api/AtClient.java @@ -26,151 +26,185 @@ @SuppressWarnings("unused") public interface AtClient extends Secondary, AtEvents.AtEventBus { - /** - * Standard AtClient factory - uses production @ root to look up the cloud secondary address for this atSign - * @param atSign the atsign of this client - * @return An {@link AtClient} - * @throws AtException if something goes wrong with looking up or connecting to the remote secondary - */ - static AtClient withRemoteSecondary(AtSign atSign) throws AtException { - return withRemoteSecondary("root.atsign.org:64", atSign); + /** + * Standard AtClient factory - uses production @ root to look up the cloud secondary address for + * this atSign + * + * @param atSign the atsign of this client + * @return An {@link AtClient} + * @throws AtException if something goes wrong with looking up or connecting to the remote secondary + */ + static AtClient withRemoteSecondary(AtSign atSign) throws AtException { + return withRemoteSecondary("root.atsign.org:64", atSign); + } + + /** + * Standard AtClient factory - uses production @ root to look up the cloud secondary address for + * this atSign + * + * @param atSign the atsign of this client + * @param verbose set to true for chatty logs + * @return An {@link AtClient} + * @throws AtException if something goes wrong with looking up or connecting to the remote secondary + */ + static AtClient withRemoteSecondary(AtSign atSign, boolean verbose) throws AtException { + return withRemoteSecondary("root.atsign.org:64", atSign, verbose); + } + + /** + * Factory to use when you wish to use a custom Secondary.AddressFinder + * + * @param atSign the atSign of this client + * @param secondaryAddressFinder will be used to find the Secondary.Address of the atSign + * @return An {@link AtClient} + * @throws AtException if any other exception occurs while connecting to the remote (cloud) + * secondary + */ + static AtClient withRemoteSecondary(AtSign atSign, Secondary.AddressFinder secondaryAddressFinder) + throws AtException { + Secondary.Address remoteSecondaryAddress; + try { + remoteSecondaryAddress = secondaryAddressFinder.findSecondary(atSign); + } catch (IOException e) { + throw new AtSecondaryConnectException("Failed to find secondary, with IOException", e); } - /** - * Standard AtClient factory - uses production @ root to look up the cloud secondary address for this atSign - * @param atSign the atsign of this client - * @param verbose set to true for chatty logs - * @return An {@link AtClient} - * @throws AtException if something goes wrong with looking up or connecting to the remote secondary - */ - static AtClient withRemoteSecondary(AtSign atSign, boolean verbose) throws AtException { - return withRemoteSecondary("root.atsign.org:64", atSign, verbose); + return withRemoteSecondary(atSign, remoteSecondaryAddress, false); + } + + /** + * Factory - returns default AtClientImpl with a RemoteSecondary and a DefaultConnectionFactory + * + * @param rootUrl the address of the root server to use - e.g. root.atsign.org:64 for production + * at-signs + * @param atSign the atSign of the client - e.g. @alice + * @return An {@link AtClient} + * @throws AtException if anything goes wrong during construction + */ + static AtClient withRemoteSecondary(String rootUrl, AtSign atSign) throws AtException { + return withRemoteSecondary(rootUrl, atSign, false); + } + + static AtClient withRemoteSecondary(String rootUrl, AtSign atSign, boolean verbose) throws AtException { + DefaultAtConnectionFactory connectionFactory = new DefaultAtConnectionFactory(); + + Secondary.Address secondaryAddress; + try { + AtRootConnection rootConnection = connectionFactory.getRootConnection(new SimpleAtEventBus(), rootUrl, verbose); + rootConnection.connect(); + secondaryAddress = rootConnection.findSecondary(atSign); + } catch (AtSecondaryNotFoundException e) { + throw e; + } catch (Exception e) { + throw new AtSecondaryNotFoundException("Failed to lookup remote secondary", e); } - /** - * Factory to use when you wish to use a custom Secondary.AddressFinder - * @param atSign the atSign of this client - * @param secondaryAddressFinder will be used to find the Secondary.Address of the atSign - * @return An {@link AtClient} - * @throws AtException if any other exception occurs while connecting to the remote (cloud) secondary - */ - static AtClient withRemoteSecondary(AtSign atSign, Secondary.AddressFinder secondaryAddressFinder) throws AtException { - Secondary.Address remoteSecondaryAddress; - try { - remoteSecondaryAddress = secondaryAddressFinder.findSecondary(atSign); - } catch (IOException e) { - throw new AtSecondaryConnectException("Failed to find secondary, with IOException", e); - } - return withRemoteSecondary(atSign, remoteSecondaryAddress, false); + return withRemoteSecondary(atSign, secondaryAddress, verbose); + } + + /** + * Factory to use when you wish to use a custom Secondary.AddressFinder + * + * @param atSign the atSign of this client + * @param verbose set to true for chatty logs + * @return An {@link AtClient} + * @throws IOException if thrown by the address finder + * @throws AtException if any other exception occurs while connecting to the remote (cloud) + * secondary + */ + static AtClient withRemoteSecondary(AtSign atSign, Secondary.AddressFinder secondaryAddressFinder, boolean verbose) + throws IOException, AtException { + Secondary.Address remoteSecondaryAddress = secondaryAddressFinder.findSecondary(atSign); + return withRemoteSecondary(atSign, remoteSecondaryAddress, verbose); + } + + /** + * Factory to use when you already know the address of the remote (cloud) secondary + * + * @param atSign the atSign of this client + * @param remoteSecondaryAddress the address of the remote secondary server + * @param verbose set to true for chatty logs + * @return An {@link AtClient} + * @throws AtException if any other exception occurs while connecting to the remote (cloud) + * secondary + */ + static AtClient withRemoteSecondary(AtSign atSign, Secondary.Address remoteSecondaryAddress, boolean verbose) + throws AtException { + DefaultAtConnectionFactory connectionFactory = new DefaultAtConnectionFactory(); + AtEvents.AtEventBus eventBus = new SimpleAtEventBus(); + + Map keys; + try { + keys = KeysUtil.loadKeys(atSign); + } catch (Exception e) { + throw new AtClientConfigException("Failed to load keys", e); } - /** - * Factory - returns default AtClientImpl with a RemoteSecondary and a DefaultConnectionFactory - * @param rootUrl the address of the root server to use - e.g. root.atsign.org:64 for production at-signs - * @param atSign the atSign of the client - e.g. @alice - * @return An {@link AtClient} - * @throws AtException if anything goes wrong during construction - */ - static AtClient withRemoteSecondary(String rootUrl, AtSign atSign) throws AtException { - return withRemoteSecondary(rootUrl, atSign, false); + RemoteSecondary secondary; + try { + secondary = new RemoteSecondary(eventBus, atSign, remoteSecondaryAddress, keys, connectionFactory, verbose); + } catch (IOException e) { + throw new AtSecondaryConnectException("Failed to create RemoteSecondary", e); } - static AtClient withRemoteSecondary(String rootUrl, AtSign atSign, boolean verbose) throws AtException { - DefaultAtConnectionFactory connectionFactory = new DefaultAtConnectionFactory(); - - Secondary.Address secondaryAddress; - try { - AtRootConnection rootConnection = connectionFactory.getRootConnection(new SimpleAtEventBus(), rootUrl, verbose); - rootConnection.connect(); - secondaryAddress = rootConnection.findSecondary(atSign); - } catch (AtSecondaryNotFoundException e) { - throw e; - } catch (Exception e) { - throw new AtSecondaryNotFoundException("Failed to lookup remote secondary", e); - } - - return withRemoteSecondary(atSign, secondaryAddress, verbose); - } + return new AtClientImpl(eventBus, atSign, keys, secondary); + } - /** - * Factory to use when you wish to use a custom Secondary.AddressFinder - * @param atSign the atSign of this client - * @param verbose set to true for chatty logs - * @return An {@link AtClient} - * @throws IOException if thrown by the address finder - * @throws AtException if any other exception occurs while connecting to the remote (cloud) secondary - */ - static AtClient withRemoteSecondary(AtSign atSign, Secondary.AddressFinder secondaryAddressFinder, boolean verbose) throws IOException, AtException { - Secondary.Address remoteSecondaryAddress = secondaryAddressFinder.findSecondary(atSign); - return withRemoteSecondary(atSign, remoteSecondaryAddress, verbose); - } + /** + * Factory to use when you already know the address of the remote (cloud) secondary + * + * @param atSign the atSign of this client + * @param remoteSecondaryAddress the address of the remote secondary server + * @return An {@link AtClient} + * @throws AtException if any other exception occurs while connecting to the remote (cloud) + * secondary + */ + static AtClient withRemoteSecondary(Secondary.Address remoteSecondaryAddress, AtSign atSign) throws AtException { + return withRemoteSecondary(atSign, remoteSecondaryAddress, false); + } - /** - * Factory to use when you already know the address of the remote (cloud) secondary - * @param atSign the atSign of this client - * @param remoteSecondaryAddress the address of the remote secondary server - * @param verbose set to true for chatty logs - * @return An {@link AtClient} - * @throws AtException if any other exception occurs while connecting to the remote (cloud) secondary - */ - static AtClient withRemoteSecondary(AtSign atSign, Secondary.Address remoteSecondaryAddress, boolean verbose) throws AtException { - DefaultAtConnectionFactory connectionFactory = new DefaultAtConnectionFactory(); - AtEvents.AtEventBus eventBus = new SimpleAtEventBus(); - - Map keys; - try { - keys = KeysUtil.loadKeys(atSign); - } catch (Exception e) { - throw new AtClientConfigException("Failed to load keys", e); - } - - RemoteSecondary secondary; - try { - secondary = new RemoteSecondary(eventBus, atSign, remoteSecondaryAddress, keys, connectionFactory, verbose); - } catch (IOException e) { - throw new AtSecondaryConnectException("Failed to create RemoteSecondary", e); - } - - return new AtClientImpl(eventBus, atSign, keys, secondary); - } - /** - * Factory to use when you already know the address of the remote (cloud) secondary - * @param atSign the atSign of this client - * @param remoteSecondaryAddress the address of the remote secondary server - * @return An {@link AtClient} - * @throws AtException if any other exception occurs while connecting to the remote (cloud) secondary - */ - static AtClient withRemoteSecondary(Secondary.Address remoteSecondaryAddress, AtSign atSign) throws AtException { - return withRemoteSecondary(atSign, remoteSecondaryAddress, false); - } + AtSign getAtSign(); + + Secondary getSecondary(); + + Map getEncryptionKeys(); + + CompletableFuture get(SharedKey sharedKey); + + CompletableFuture getBinary(SharedKey sharedKey); + + CompletableFuture put(SharedKey sharedKey, String value); + + CompletableFuture delete(SharedKey sharedKey); + + CompletableFuture get(SelfKey selfKey); + + CompletableFuture getBinary(SelfKey selfKey); + + CompletableFuture put(SelfKey selfKey, String value); + + CompletableFuture delete(SelfKey selfKey); + + CompletableFuture get(PublicKey publicKey); + + CompletableFuture get(PublicKey publicKey, GetRequestOptions getRequestOptions); + + CompletableFuture getBinary(PublicKey publicKey); + + CompletableFuture getBinary(PublicKey publicKey, GetRequestOptions getRequestOptions); + CompletableFuture put(PublicKey publicKey, String value); - AtSign getAtSign(); - Secondary getSecondary(); - Map getEncryptionKeys(); + CompletableFuture delete(PublicKey publicKey); - CompletableFuture get(SharedKey sharedKey); - CompletableFuture getBinary(SharedKey sharedKey); - CompletableFuture put(SharedKey sharedKey, String value); - CompletableFuture delete(SharedKey sharedKey); + CompletableFuture put(SharedKey sharedKey, byte[] value); - CompletableFuture get(SelfKey selfKey); - CompletableFuture getBinary(SelfKey selfKey); - CompletableFuture put(SelfKey selfKey, String value); - CompletableFuture delete(SelfKey selfKey); + CompletableFuture put(SelfKey selfKey, byte[] value); - CompletableFuture get(PublicKey publicKey); - CompletableFuture get(PublicKey publicKey, GetRequestOptions getRequestOptions); - CompletableFuture getBinary(PublicKey publicKey); - CompletableFuture getBinary(PublicKey publicKey, GetRequestOptions getRequestOptions); - CompletableFuture put(PublicKey publicKey, String value); - CompletableFuture delete(PublicKey publicKey); + CompletableFuture put(PublicKey publicKey, byte[] value); - CompletableFuture put(SharedKey sharedKey, byte[] value); - CompletableFuture put(SelfKey selfKey, byte[] value); - CompletableFuture put(PublicKey publicKey, byte[] value); + CompletableFuture> getAtKeys(String regex); - CompletableFuture> getAtKeys(String regex); - CompletableFuture> getAtKeys(String regex, boolean fetchMetadata); + CompletableFuture> getAtKeys(String regex, boolean fetchMetadata); } diff --git a/at_client/src/main/java/org/atsign/client/api/AtConnection.java b/at_client/src/main/java/org/atsign/client/api/AtConnection.java index a649066f..e34e51b7 100644 --- a/at_client/src/main/java/org/atsign/client/api/AtConnection.java +++ b/at_client/src/main/java/org/atsign/client/api/AtConnection.java @@ -6,33 +6,35 @@ import java.net.Socket; /** - * A simple abstraction around connections to @ platform services - e.g. the root server and secondary servers + * A simple abstraction around connections to @ platform services - e.g. the root server and + * secondary servers */ @SuppressWarnings("unused") public interface AtConnection { - String getUrl(); + String getUrl(); - String getHost(); + String getHost(); - int getPort(); + int getPort(); - Socket getSocket(); + Socket getSocket(); - boolean isConnected(); + boolean isConnected(); - boolean isAutoReconnect(); + boolean isAutoReconnect(); - boolean isVerbose(); + boolean isVerbose(); - void setVerbose(boolean verbose); + void setVerbose(boolean verbose); - void connect() throws IOException, AtException; - void disconnect(); + void connect() throws IOException, AtException; - String executeCommand(String command) throws IOException; + void disconnect(); - interface Authenticator { - void authenticate(AtConnection connection) throws AtException, IOException; - } + String executeCommand(String command) throws IOException; + + interface Authenticator { + void authenticate(AtConnection connection) throws AtException, IOException; + } } diff --git a/at_client/src/main/java/org/atsign/client/api/AtConnectionFactory.java b/at_client/src/main/java/org/atsign/client/api/AtConnectionFactory.java index fb8f88f5..8936769f 100644 --- a/at_client/src/main/java/org/atsign/client/api/AtConnectionFactory.java +++ b/at_client/src/main/java/org/atsign/client/api/AtConnectionFactory.java @@ -5,13 +5,28 @@ import org.atsign.common.AtSign; /** - * For getting a hold of AtConnections to things. - * We inject an AtConnectionFactory into AtClientImpl, primarily for testability + * For getting a hold of AtConnections to things. We inject an AtConnectionFactory into + * AtClientImpl, primarily for testability */ public interface AtConnectionFactory { - AtSecondaryConnection getSecondaryConnection(AtEvents.AtEventBus eventBus, AtSign atSign, Secondary.Address secondaryAddress, AtConnection.Authenticator authenticator); - AtSecondaryConnection getSecondaryConnection(AtEvents.AtEventBus eventBus, AtSign atSign, String secondaryUrl, AtConnection.Authenticator authenticator, boolean verbose); - AtSecondaryConnection getSecondaryConnection(AtEvents.AtEventBus eventBus, AtSign atSign, Secondary.Address secondaryAddress, AtConnection.Authenticator authenticator, boolean verbose); - AtRootConnection getRootConnection(AtEvents.AtEventBus eventBus, String rootUrl); - AtRootConnection getRootConnection(AtEvents.AtEventBus eventBus, String rootUrl, boolean verbose); + AtSecondaryConnection getSecondaryConnection(AtEvents.AtEventBus eventBus, + AtSign atSign, + Secondary.Address secondaryAddress, + AtConnection.Authenticator authenticator); + + AtSecondaryConnection getSecondaryConnection(AtEvents.AtEventBus eventBus, + AtSign atSign, + String secondaryUrl, + AtConnection.Authenticator authenticator, + boolean verbose); + + AtSecondaryConnection getSecondaryConnection(AtEvents.AtEventBus eventBus, + AtSign atSign, + Secondary.Address secondaryAddress, + AtConnection.Authenticator authenticator, + boolean verbose); + + AtRootConnection getRootConnection(AtEvents.AtEventBus eventBus, String rootUrl); + + AtRootConnection getRootConnection(AtEvents.AtEventBus eventBus, String rootUrl, boolean verbose); } diff --git a/at_client/src/main/java/org/atsign/client/api/AtEvents.java b/at_client/src/main/java/org/atsign/client/api/AtEvents.java index 530dd24d..0acdd018 100644 --- a/at_client/src/main/java/org/atsign/client/api/AtEvents.java +++ b/at_client/src/main/java/org/atsign/client/api/AtEvents.java @@ -7,42 +7,42 @@ * */ public interface AtEvents { - interface AtEventListener { - void handleEvent(AtEventType eventType, Map eventData); - } - - interface AtEventBus { - /** - * @param listener to handle various events which originate from Secondaries - * @param eventTypes the set of EventTypes that the listener is interested in - */ - void addEventListener(AtEventListener listener, Set eventTypes); - - /** - * @param listener the listener to remove - */ - void removeEventListener(AtEventListener listener); - - int publishEvent(AtEventType eventType, Map eventData); - - } - enum AtEventType { - // events which originate from the client's "Monitor" connection, which asynchronously - // receives notification events from its secondary server - // Monitor-originating events most interesting to developers: - sharedKeyNotification, // a shared key has been updated by some other atSign - updateNotification, // a value has been updated for some key - deleteNotification, // some key has been deleted - updateNotificationText, // an in-the-clear notification, no encryption - // Monitor-originating events usually only consumed within the AtClient library - statsNotification, - monitorHeartbeatAck, - monitorException, - - // Event which originates from AtClient - AtClient listens for updateNotification events, - // does decryption on the fly, then publishes a decryptedUpdateNotification event - decryptedUpdateNotification, - - userDefined - } + interface AtEventListener { + void handleEvent(AtEventType eventType, Map eventData); + } + + interface AtEventBus { + /** + * @param listener to handle various events which originate from Secondaries + * @param eventTypes the set of EventTypes that the listener is interested in + */ + void addEventListener(AtEventListener listener, Set eventTypes); + + /** + * @param listener the listener to remove + */ + void removeEventListener(AtEventListener listener); + + int publishEvent(AtEventType eventType, Map eventData); + + } + enum AtEventType { + // events which originate from the client's "Monitor" connection, which asynchronously + // receives notification events from its secondary server + // Monitor-originating events most interesting to developers: + sharedKeyNotification, // a shared key has been updated by some other atSign + updateNotification, // a value has been updated for some key + deleteNotification, // some key has been deleted + updateNotificationText, // an in-the-clear notification, no encryption + // Monitor-originating events usually only consumed within the AtClient library + statsNotification, // + monitorHeartbeatAck, // + monitorException, // + + // Event which originates from AtClient - AtClient listens for updateNotification events, + // does decryption on the fly, then publishes a decryptedUpdateNotification event + decryptedUpdateNotification, + + userDefined + } } diff --git a/at_client/src/main/java/org/atsign/client/api/Secondary.java b/at_client/src/main/java/org/atsign/client/api/Secondary.java index 29198a79..be57bc9a 100644 --- a/at_client/src/main/java/org/atsign/client/api/Secondary.java +++ b/at_client/src/main/java/org/atsign/client/api/Secondary.java @@ -8,137 +8,166 @@ /** * Clients ultimately talk to a Secondary server - usually this is a microservice which implements - * the @ protocol server spec, running somewhere in the cloud. - *
- * In the initial implementation we just have AtClientImpl talking to a RemoteSecondary which in turn - * talks, via TLS over a secure socket, to the cloud Secondary server. - *
- * As we implement client-side offline storage, performance caching etc., we can expect e.g. - *
AtClient -> FastCacheSecondary -> OfflineStorageSecondary -> RemoteSecondary
+ * the @ protocol server spec, running somewhere in the cloud.
+ * In the initial implementation we just have AtClientImpl talking to a RemoteSecondary which in + * turn talks, via TLS over a secure socket, to the cloud Secondary server.
+ * As we implement client-side offline storage, performance caching etc., we can expect e.g.
+ * AtClient -> FastCacheSecondary -> OfflineStorageSecondary -> RemoteSecondary
* where FastCacheSecondary might be an in-memory LRU cache, and OfflineStorageSecondary is a - * persistent cache of some or all of the information in the RemoteSecondary. To make this - * possible, each Secondary will need to be able to fully handle the @ protocol, thus the - * interface is effectively the same as when interacting with a cloud secondary via openssl - * from command line. + * persistent cache of some or all of the information in the RemoteSecondary. To make this possible, + * each Secondary will need to be able to fully handle the @ protocol, thus the interface is + * effectively the same as when interacting with a cloud secondary via openssl from command line. */ public interface Secondary extends AtEvents.AtEventListener { - /** - * @param command in @ protocol format - * @param throwExceptionOnErrorResponse sometimes we want to inspect an error response, - * sometimes we want to just throw an exception - * @return response in @ protocol format - * @throws AtException if there was an error response and throwExceptionOnErrorResponse is true - * @throws IOException if one is encountered - */ - Response executeCommand(String command, boolean throwExceptionOnErrorResponse) throws IOException, AtException; - - void startMonitor(); - void stopMonitor(); - boolean isMonitorRunning(); - - class Response { - private String rawDataResponse = null; - private String rawErrorResponse; - private String errorCode; - private String errorText; - - public String getRawDataResponse() { - return rawDataResponse; - } - public void setRawDataResponse(String s) { - rawDataResponse = s; - rawErrorResponse = null; - errorCode = null; - errorText = null; - } - - public String getRawErrorResponse() { - return rawErrorResponse; - } - public void setRawErrorResponse(String s) { - // In format "AT1234-meaning of error code : " - rawErrorResponse = s; - rawDataResponse = null; - - String errorCodeSegment = rawErrorResponse.substring(0, rawErrorResponse.indexOf(":")).trim(); - String[] separatedByHyphen = errorCodeSegment.split("-"); - errorCode = separatedByHyphen[0].trim(); - - errorText = rawErrorResponse.replaceFirst(errorCodeSegment + ":", "").trim(); - } - public boolean isError() {return rawErrorResponse != null;} - - public String getErrorCode() {return errorCode;} - - public String getErrorText() {return errorText;} - - @Override - public String toString() { - if (isError()) { - return "error:" + rawErrorResponse; - } else { - return "data:" + rawDataResponse; - } - } - - public AtException getException() { - if (! isError()) { - return null; - } - if ("AT0001".equals(errorCode)) return new AtServerRuntimeException(errorText); - if ("AT0003".equals(errorCode)) return new AtInvalidSyntaxException(errorText); - if ("AT0005".equals(errorCode)) return new AtBufferOverFlowException(errorText); - if ("AT0006".equals(errorCode)) return new AtOutboundConnectionLimitException(errorText); - if ("AT0007".equals(errorCode)) return new AtSecondaryNotFoundException(errorText); - if ("AT0008".equals(errorCode)) return new AtHandShakeException(errorText); - if ("AT0009".equals(errorCode)) return new AtUnauthorizedException(errorText); - if ("AT0010".equals(errorCode)) return new AtInternalServerError(errorText); - if ("AT0011".equals(errorCode)) return new AtInternalServerException(errorText); - if ("AT0012".equals(errorCode)) return new AtInboundConnectionLimitException(errorText); - if ("AT0013".equals(errorCode)) return new AtBlockedConnectionException(errorText); - if ("AT0015".equals(errorCode)) return new AtKeyNotFoundException(errorText); - if ("AT0016".equals(errorCode)) return new AtInvalidAtKeyException(errorText); - if ("AT0021".equals(errorCode)) return new AtSecondaryConnectException(errorText); - if ("AT0022".equals(errorCode)) return new AtIllegalArgumentException(errorText); - if ("AT0023".equals(errorCode)) return new AtTimeoutException(errorText); - if ("AT0024".equals(errorCode)) return new AtServerIsPausedException(errorText); - if ("AT0401".equals(errorCode)) return new AtUnauthenticatedException(errorText); - - return new AtNewErrorCodeWhoDisException(errorCode, errorText); - } + /** + * @param command in @ protocol format + * @param throwExceptionOnErrorResponse sometimes we want to inspect an error response, sometimes we + * want to just throw an exception + * @return response in @ protocol format + * @throws AtException if there was an error response and throwExceptionOnErrorResponse is true + * @throws IOException if one is encountered + */ + Response executeCommand(String command, boolean throwExceptionOnErrorResponse) throws IOException, AtException; + + void startMonitor(); + + void stopMonitor(); + + boolean isMonitorRunning(); + + class Response { + private String rawDataResponse = null; + private String rawErrorResponse; + private String errorCode; + private String errorText; + + public String getRawDataResponse() { + return rawDataResponse; } - class Address { - public final String host; - public final int port; - - public Address(String host, int port) { - this.host = host; - this.port = port; - } - - public static Address fromString(String hostAndPort) throws IllegalArgumentException { - String[] split = hostAndPort.split(":"); - if (split.length != 2) { - throw new IllegalArgumentException("Cannot construct Secondary.Address from malformed host:port string '" + hostAndPort + "'"); - } - String host = split[0]; - int port; - try { - port = Integer.parseInt(split[1]); - } catch (NumberFormatException e) { - throw new IllegalArgumentException("Cannot construct Secondary.Address from malformed host:port string '" + hostAndPort + "'"); - } - return new Address(host, port); - } - - @Override - public String toString() { - return host + ":" + port; - } + public void setRawDataResponse(String s) { + rawDataResponse = s; + rawErrorResponse = null; + errorCode = null; + errorText = null; } - interface AddressFinder { - Address findSecondary(AtSign atSign) throws IOException, AtSecondaryNotFoundException; + public String getRawErrorResponse() { + return rawErrorResponse; } + + public void setRawErrorResponse(String s) { + // In format "AT1234-meaning of error code : " + rawErrorResponse = s; + rawDataResponse = null; + + String errorCodeSegment = rawErrorResponse.substring(0, rawErrorResponse.indexOf(":")).trim(); + String[] separatedByHyphen = errorCodeSegment.split("-"); + errorCode = separatedByHyphen[0].trim(); + + errorText = rawErrorResponse.replaceFirst(errorCodeSegment + ":", "").trim(); + } + + public boolean isError() { + return rawErrorResponse != null; + } + + public String getErrorCode() { + return errorCode; + } + + public String getErrorText() { + return errorText; + } + + @Override + public String toString() { + if (isError()) { + return "error:" + rawErrorResponse; + } else { + return "data:" + rawDataResponse; + } + } + + public AtException getException() { + if (!isError()) { + return null; + } + if ("AT0001".equals(errorCode)) { + return new AtServerRuntimeException(errorText); + } else if ("AT0003".equals(errorCode)) { + return new AtInvalidSyntaxException(errorText); + } else if ("AT0005".equals(errorCode)) { + return new AtBufferOverFlowException(errorText); + } else if ("AT0006".equals(errorCode)) { + return new AtOutboundConnectionLimitException(errorText); + } else if ("AT0007".equals(errorCode)) { + return new AtSecondaryNotFoundException(errorText); + } else if ("AT0008".equals(errorCode)) { + return new AtHandShakeException(errorText); + } else if ("AT0009".equals(errorCode)) { + return new AtUnauthorizedException(errorText); + } else if ("AT0010".equals(errorCode)) { + return new AtInternalServerError(errorText); + } else if ("AT0011".equals(errorCode)) { + return new AtInternalServerException(errorText); + } else if ("AT0012".equals(errorCode)) { + return new AtInboundConnectionLimitException(errorText); + } else if ("AT0013".equals(errorCode)) { + return new AtBlockedConnectionException(errorText); + } else if ("AT0015".equals(errorCode)) { + return new AtKeyNotFoundException(errorText); + } else if ("AT0016".equals(errorCode)) { + return new AtInvalidAtKeyException(errorText); + } else if ("AT0021".equals(errorCode)) { + return new AtSecondaryConnectException(errorText); + } else if ("AT0022".equals(errorCode)) { + return new AtIllegalArgumentException(errorText); + } else if ("AT0023".equals(errorCode)) { + return new AtTimeoutException(errorText); + } else if ("AT0024".equals(errorCode)) { + return new AtServerIsPausedException(errorText); + } else if ("AT0401".equals(errorCode)) { + return new AtUnauthenticatedException(errorText); + } + + return new AtNewErrorCodeWhoDisException(errorCode, errorText); + } + } + + class Address { + public final String host; + public final int port; + + public Address(String host, int port) { + this.host = host; + this.port = port; + } + + public static Address fromString(String hostAndPort) throws IllegalArgumentException { + String[] split = hostAndPort.split(":"); + if (split.length != 2) { + throw new IllegalArgumentException( + "Cannot construct Secondary.Address from malformed host:port string '" + hostAndPort + "'"); + } + String host = split[0]; + int port; + try { + port = Integer.parseInt(split[1]); + } catch (NumberFormatException e) { + throw new IllegalArgumentException( + "Cannot construct Secondary.Address from malformed host:port string '" + hostAndPort + "'"); + } + return new Address(host, port); + } + + @Override + public String toString() { + return host + ":" + port; + } + } + + interface AddressFinder { + Address findSecondary(AtSign atSign) throws IOException, AtSecondaryNotFoundException; + } } diff --git a/at_client/src/main/java/org/atsign/client/api/impl/clients/AtClientImpl.java b/at_client/src/main/java/org/atsign/client/api/impl/clients/AtClientImpl.java index f30b643c..6ed4871f 100644 --- a/at_client/src/main/java/org/atsign/client/api/impl/clients/AtClientImpl.java +++ b/at_client/src/main/java/org/atsign/client/api/impl/clients/AtClientImpl.java @@ -1,7 +1,18 @@ package org.atsign.client.api.impl.clients; -import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.databind.ObjectMapper; +import static org.atsign.client.api.AtEvents.AtEventType.decryptedUpdateNotification; + +import java.io.IOException; +import java.security.*; +import java.time.OffsetDateTime; +import java.util.*; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionException; + +import javax.crypto.BadPaddingException; +import javax.crypto.IllegalBlockSizeException; +import javax.crypto.NoSuchPaddingException; + import org.atsign.client.api.AtClient; import org.atsign.client.api.AtEvents.AtEventBus; import org.atsign.client.api.AtEvents.AtEventListener; @@ -19,748 +30,797 @@ import org.atsign.common.options.GetRequestOptions; import org.atsign.common.response_models.LookupResponse; -import javax.crypto.BadPaddingException; -import javax.crypto.IllegalBlockSizeException; -import javax.crypto.NoSuchPaddingException; -import java.io.IOException; -import java.security.InvalidAlgorithmParameterException; -import java.security.InvalidKeyException; -import java.security.NoSuchAlgorithmException; -import java.security.NoSuchProviderException; -import java.time.OffsetDateTime; -import java.util.*; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.CompletionException; - -import static org.atsign.client.api.AtEvents.AtEventType.decryptedUpdateNotification; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; /** * @see org.atsign.client.api.AtClient */ @SuppressWarnings({"RedundantThrows", "unused"}) public class AtClientImpl implements AtClient { - static final ObjectMapper json = new ObjectMapper(); - - // Factory method - creates an AtClientImpl with a RemoteSecondary - - private final AtSign atSign; - @Override public AtSign getAtSign() {return atSign;} - - private final Map keys; - @Override public Map getEncryptionKeys() {return keys;} - private final Secondary secondary; - @Override public Secondary getSecondary() {return secondary;} - - private final AtEventBus eventBus; - public AtClientImpl(AtEventBus eventBus, AtSign atSign, Map keys, Secondary secondary) { - this.eventBus = eventBus; - this.atSign = atSign; - this.keys = keys; - this.secondary = secondary; - - eventBus.addEventListener(this, EnumSet.allOf(AtEventType.class)); - } - - @Override public void startMonitor() {secondary.startMonitor();} - @Override public void stopMonitor() {secondary.stopMonitor();} - @Override public boolean isMonitorRunning() {return secondary.isMonitorRunning();} - - @Override - public synchronized void addEventListener(AtEventListener listener, Set eventTypes) { - eventBus.addEventListener(listener, eventTypes); - } - - @Override - public synchronized void removeEventListener(AtEventListener listener) { - eventBus.removeEventListener(listener); - } - - @Override - public int publishEvent(AtEventType eventType, Map eventData) { - return eventBus.publishEvent(eventType, eventData); - } - - @Override - public synchronized void handleEvent(AtEventType eventType, Map eventData) { - switch (eventType) { - case sharedKeyNotification: { - // We've got notification that someone has shared an encryption key with us - // If we also got a value, we can decrypt it and add it to our keys map - // Note: a value isn't supplied when the ttr on the shared key was set to 0 - if (eventData.get("value") != null) { - String sharedSharedKeyName = (String) eventData.get("key"); - String sharedSharedKeyEncryptedValue = (String) eventData.get("value"); - // decrypt it with our encryption private key - try { - String sharedKeyDecryptedValue = EncryptionUtil.rsaDecryptFromBase64(sharedSharedKeyEncryptedValue, keys.get(KeysUtil.encryptionPrivateKeyName)); - keys.put(sharedSharedKeyName, sharedKeyDecryptedValue); - } catch (Exception e) { - System.err.println(OffsetDateTime.now() + ": caught exception " + e + " while decrypting received shared key " + sharedSharedKeyName); - } - } - } - break; - case updateNotification: { - // Let's see if we can decrypt it on the fly - if (eventData.get("value") != null) { - String key = (String) eventData.get("key"); - String encryptedValue = (String) eventData.get("value"); - @SuppressWarnings("unchecked") Map metadata = (Map) eventData.get("metadata"); - String ivNonce = (String) metadata.get("ivNonce"); - - try { - // decrypt it with the symmetric key that the other atSign shared with me - String encryptionKeySharedByOther = getEncryptionKeySharedByOther(SharedKey.fromString(key)); - - String decryptedValue = EncryptionUtil.aesDecryptFromBase64(encryptedValue, encryptionKeySharedByOther, ivNonce); - HashMap newEventData = new HashMap<>(eventData); - newEventData.put("decryptedValue", decryptedValue); - eventBus.publishEvent(decryptedUpdateNotification, newEventData); - } catch (Exception e) { - System.err.println(OffsetDateTime.now() + ": caught exception " + e + " while decrypting received data with key name [" + key + "]"); - } - } - } - break; - default: - break; - } - } - - @Override - public CompletableFuture get(SharedKey sharedKey) { - return CompletableFuture.supplyAsync(() -> { - try { - return _get(sharedKey); - } catch (Exception e) { - throw new CompletionException(e); - } - }); - } - - @Override - public CompletableFuture getBinary(SharedKey sharedKey) { - return CompletableFuture.supplyAsync(() -> { - try { - return _getBinary(sharedKey); - } catch (Exception e) { - throw new CompletionException(e); - } - }); - } - - @Override - public CompletableFuture put(SharedKey sharedKey, String value) { - return CompletableFuture.supplyAsync(() -> { - try { - return _put(sharedKey, value); - } catch (Exception e) { - throw new CompletionException(e); - } - }); - } - - @Override - public CompletableFuture delete(SharedKey sharedKey) { - return CompletableFuture.supplyAsync(() -> { - try { - return _delete(sharedKey); - } catch (Exception e) { - throw new CompletionException(e); - } - }); - } - - @Override - public CompletableFuture get(SelfKey selfKey) { - return CompletableFuture.supplyAsync(() -> { - try { - return _get(selfKey); - } catch (Exception e) { - throw new CompletionException(e); - } - }); - } - - @Override - public CompletableFuture getBinary(SelfKey selfKey) { - return CompletableFuture.supplyAsync(() -> { - try { - return _getBinary(selfKey); - } catch (Exception e) { - throw new CompletionException(e); - } - }); - } - - @Override - public CompletableFuture put(SelfKey selfKey, String value) { - return CompletableFuture.supplyAsync(() -> { - try { - return _put(selfKey, value); - } catch (Exception e) { - throw new CompletionException(e); - } - }); - } - - @Override - public CompletableFuture delete(SelfKey selfKey) { - return CompletableFuture.supplyAsync(() -> { - try { - return _delete(selfKey); - } catch (Exception e) { - throw new CompletionException(e); - } - }); - } - - @Override - public CompletableFuture get(PublicKey publicKey) { - return CompletableFuture.supplyAsync(() -> { - try { - return _get(publicKey); - } catch (Exception e) { - throw new CompletionException(e); - } - }); - } - - @Override - public CompletableFuture get(PublicKey publicKey, GetRequestOptions getRequestOptions) { - return CompletableFuture.supplyAsync(() -> { - try { - return _get(publicKey, getRequestOptions); - } catch (Exception e) { - throw new CompletionException(e); - } - }); - } - - @Override - public CompletableFuture getBinary(PublicKey publicKey) { - return CompletableFuture.supplyAsync(() -> { - try { - return _getBinary(publicKey); - } catch (Exception e) { - throw new CompletionException(e); - } - }); - } - - @Override - public CompletableFuture getBinary(PublicKey publicKey, GetRequestOptions getRequestOptions) { - return CompletableFuture.supplyAsync(() -> { - try { - return _getBinary(publicKey, getRequestOptions); - } catch (Exception e) { - throw new CompletionException(e); - } - }); - } - - @Override - public CompletableFuture put(PublicKey publicKey, String value) { - return CompletableFuture.supplyAsync(() -> { - try { - return _put(publicKey, value); - } catch (Exception e) { - throw new CompletionException(e); - } - }); - } - - @Override - public CompletableFuture delete(PublicKey publicKey) { - return CompletableFuture.supplyAsync(() -> { - try { - return _delete(publicKey); - } catch (Exception e) { - throw new CompletionException(e); - } - }); - } - - @Override - public CompletableFuture put(SharedKey sharedKey, byte[] value) { - return CompletableFuture.supplyAsync(() -> { - try { - return _put(sharedKey, value); - } catch (Exception e) { - throw new CompletionException(e); - } - }); - } - - @Override - public CompletableFuture put(SelfKey selfKey, byte[] value) { - return CompletableFuture.supplyAsync(() -> { - try { - return _put(selfKey, value); - } catch (Exception e) { - throw new CompletionException(e); - } - }); - } - - @Override - public CompletableFuture put(PublicKey publicKey, byte[] value) { - return CompletableFuture.supplyAsync(() -> { - try { - return _put(publicKey, value); - } catch (Exception e) { - throw new CompletionException(e); - } - }); - } - - @Override - public CompletableFuture> getAtKeys(String regex) { - return getAtKeys(regex, true); - } - - @Override - public CompletableFuture> getAtKeys(String regex, boolean fetchMetadata) { - return CompletableFuture.supplyAsync(() -> { - try { - return _getAtKeys(regex, fetchMetadata); - } catch (Exception e) { - throw new CompletionException(e); - } - }); - } - - /** - * Synchronous, talks @-protocol directly to the client's Secondary server - * @param command in @ protocol format - * @param throwExceptionOnErrorResponse sometimes we want to inspect an error response, - * sometimes we want to just throw an exception - * @return a Secondary Response - * @throws AtException if the response from the Secondary starts with 'error:', or - * if there is any other exception - */ - @Override - public Response executeCommand(String command, boolean throwExceptionOnErrorResponse) throws AtException, IOException { - return secondary.executeCommand(command, throwExceptionOnErrorResponse); - } - -// ============================================================================================================================================ - // ============================================================================================================================================ - // ============================================================================================================================================ - - // - // Synchronous methods which do the actual work - // - private String _get(SharedKey sharedKey) throws AtException { - if (sharedKey.sharedBy.toString().equals(atSign.toString())) { - return _getSharedByMeWithOther(sharedKey); - } else { - return _getSharedByOtherWithMe(sharedKey); - } - } - - private String _getSharedByMeWithOther(SharedKey sharedKey) throws AtException { - String shareEncryptionKey = getEncryptionKeySharedByMe(sharedKey); - - // fetch local - e.g. if I'm @bob, I would first "llookup:@alice:some.key.name@bob" - Response rawResponse; - String command = "llookup:" + sharedKey; - try { - rawResponse = secondary.executeCommand(command, true); - } catch (IOException e) { - throw new AtSecondaryConnectException("Failed to execute " + command, e); - } - - try { - return EncryptionUtil.aesDecryptFromBase64(rawResponse.getRawDataResponse(), shareEncryptionKey); - } catch (Exception e) { - throw new AtDecryptionException("Failed to decrypt value with shared encryption key", e); - } - } - - private String _getSharedByOtherWithMe(SharedKey sharedKey) throws AtException { - String what; - String shareEncryptionKey = getEncryptionKeySharedByOther(sharedKey); - - Response rawResponse; - String command = "lookup:" + sharedKey.name; - if(sharedKey.getNamespace() != null && !sharedKey.getNamespace().isEmpty()) { - command += "." + sharedKey.getNamespace(); - } - command += sharedKey.sharedBy.toString(); - try { - rawResponse = secondary.executeCommand(command, true); - } catch (IOException e) { - throw new AtSecondaryConnectException("Failed to execute " + command, e); - } - - what = "decrypt value with shared encryption key"; - try { - return EncryptionUtil.aesDecryptFromBase64(rawResponse.getRawDataResponse(), shareEncryptionKey); - } catch (Exception e) { - throw new AtDecryptionException("Failed to " + what, e); - } - } - - private String _put(SharedKey sharedKey, String value) throws AtException { - if (! this.atSign.equals(sharedKey.sharedBy)) { - throw new AtIllegalArgumentException("sharedBy is [" + sharedKey.sharedBy + "] but should be this client's atSign [" + atSign + "]"); - } - String what = ""; - String cipherText; - try { - what = "fetch/create shared encryption key"; - String shareToEncryptionKey = getEncryptionKeySharedByMe(sharedKey); - - what = "encrypt value with shared encryption key"; - cipherText = EncryptionUtil.aesEncryptToBase64(value, shareToEncryptionKey); - } catch (Exception e) { - throw new AtEncryptionException("Failed to " + what, e); - } - - String command = "update" + sharedKey.metadata.toString() + ":" + sharedKey + " " + cipherText; - + static final ObjectMapper json = new ObjectMapper(); + + // Factory method - creates an AtClientImpl with a RemoteSecondary + + private final AtSign atSign; + + @Override + public AtSign getAtSign() { + return atSign; + } + + private final Map keys; + + @Override + public Map getEncryptionKeys() { + return keys; + } + + private final Secondary secondary; + + @Override + public Secondary getSecondary() { + return secondary; + } + + private final AtEventBus eventBus; + + public AtClientImpl(AtEventBus eventBus, AtSign atSign, Map keys, Secondary secondary) { + this.eventBus = eventBus; + this.atSign = atSign; + this.keys = keys; + this.secondary = secondary; + + eventBus.addEventListener(this, EnumSet.allOf(AtEventType.class)); + } + + @Override + public void startMonitor() { + secondary.startMonitor(); + } + + @Override + public void stopMonitor() { + secondary.stopMonitor(); + } + + @Override + public boolean isMonitorRunning() { + return secondary.isMonitorRunning(); + } + + @Override + public synchronized void addEventListener(AtEventListener listener, Set eventTypes) { + eventBus.addEventListener(listener, eventTypes); + } + + @Override + public synchronized void removeEventListener(AtEventListener listener) { + eventBus.removeEventListener(listener); + } + + @Override + public int publishEvent(AtEventType eventType, Map eventData) { + return eventBus.publishEvent(eventType, eventData); + } + + @Override + public synchronized void handleEvent(AtEventType eventType, Map eventData) { + switch (eventType) { + case sharedKeyNotification: + // We've got notification that someone has shared an encryption key with us + // If we also got a value, we can decrypt it and add it to our keys map + // Note: a value isn't supplied when the ttr on the shared key was set to 0 + if (eventData.get("value") != null) { + String sharedSharedKeyName = (String) eventData.get("key"); + String sharedSharedKeyEncryptedValue = (String) eventData.get("value"); + // decrypt it with our encryption private key + try { + String sharedKeyDecryptedValue = EncryptionUtil.rsaDecryptFromBase64(sharedSharedKeyEncryptedValue, + keys.get(KeysUtil.encryptionPrivateKeyName)); + keys.put(sharedSharedKeyName, sharedKeyDecryptedValue); + } catch (Exception e) { + System.err + .println(OffsetDateTime.now() + ": caught exception " + e + " while decrypting received shared key " + + sharedSharedKeyName); + } + } + break; + case updateNotification: + // Let's see if we can decrypt it on the fly + if (eventData.get("value") != null) { + String key = (String) eventData.get("key"); + String encryptedValue = (String) eventData.get("value"); + @SuppressWarnings("unchecked") + Map metadata = (Map) eventData.get("metadata"); + String ivNonce = (String) metadata.get("ivNonce"); + + try { + // decrypt it with the symmetric key that the other atSign shared with me + String encryptionKeySharedByOther = getEncryptionKeySharedByOther(SharedKey.fromString(key)); + + String decryptedValue = + EncryptionUtil.aesDecryptFromBase64(encryptedValue, encryptionKeySharedByOther, ivNonce); + HashMap newEventData = new HashMap<>(eventData); + newEventData.put("decryptedValue", decryptedValue); + eventBus.publishEvent(decryptedUpdateNotification, newEventData); + } catch (Exception e) { + System.err.println(OffsetDateTime.now() + ": caught exception " + e + + " while decrypting received data with key name [" + key + "]"); + } + } + break; + default: + break; + } + } + + + @Override + public CompletableFuture get(SharedKey sharedKey) { + return CompletableFuture.supplyAsync(() -> { + try { + return _get(sharedKey); + } catch (Exception e) { + throw new CompletionException(e); + } + }); + } + + @Override + public CompletableFuture getBinary(SharedKey sharedKey) { + return CompletableFuture.supplyAsync(() -> { + try { + return _getBinary(sharedKey); + } catch (Exception e) { + throw new CompletionException(e); + } + }); + } + + @Override + public CompletableFuture put(SharedKey sharedKey, String value) { + return CompletableFuture.supplyAsync(() -> { + try { + return _put(sharedKey, value); + } catch (Exception e) { + throw new CompletionException(e); + } + }); + } + + @Override + public CompletableFuture delete(SharedKey sharedKey) { + return CompletableFuture.supplyAsync(() -> { + try { + return _delete(sharedKey); + } catch (Exception e) { + throw new CompletionException(e); + } + }); + } + + @Override + public CompletableFuture get(SelfKey selfKey) { + return CompletableFuture.supplyAsync(() -> { + try { + return _get(selfKey); + } catch (Exception e) { + throw new CompletionException(e); + } + }); + } + + @Override + public CompletableFuture getBinary(SelfKey selfKey) { + return CompletableFuture.supplyAsync(() -> { + try { + return _getBinary(selfKey); + } catch (Exception e) { + throw new CompletionException(e); + } + }); + } + + @Override + public CompletableFuture put(SelfKey selfKey, String value) { + return CompletableFuture.supplyAsync(() -> { + try { + return _put(selfKey, value); + } catch (Exception e) { + throw new CompletionException(e); + } + }); + } + + @Override + public CompletableFuture delete(SelfKey selfKey) { + return CompletableFuture.supplyAsync(() -> { + try { + return _delete(selfKey); + } catch (Exception e) { + throw new CompletionException(e); + } + }); + } + + @Override + public CompletableFuture get(PublicKey publicKey) { + return CompletableFuture.supplyAsync(() -> { + try { + return _get(publicKey); + } catch (Exception e) { + throw new CompletionException(e); + } + }); + } + + @Override + public CompletableFuture get(PublicKey publicKey, GetRequestOptions getRequestOptions) { + return CompletableFuture.supplyAsync(() -> { + try { + return _get(publicKey, getRequestOptions); + } catch (Exception e) { + throw new CompletionException(e); + } + }); + } + + @Override + public CompletableFuture getBinary(PublicKey publicKey) { + return CompletableFuture.supplyAsync(() -> { + try { + return _getBinary(publicKey); + } catch (Exception e) { + throw new CompletionException(e); + } + }); + } + + @Override + public CompletableFuture getBinary(PublicKey publicKey, GetRequestOptions getRequestOptions) { + return CompletableFuture.supplyAsync(() -> { + try { + return _getBinary(publicKey, getRequestOptions); + } catch (Exception e) { + throw new CompletionException(e); + } + }); + } + + @Override + public CompletableFuture put(PublicKey publicKey, String value) { + return CompletableFuture.supplyAsync(() -> { + try { + return _put(publicKey, value); + } catch (Exception e) { + throw new CompletionException(e); + } + }); + } + + @Override + public CompletableFuture delete(PublicKey publicKey) { + return CompletableFuture.supplyAsync(() -> { + try { + return _delete(publicKey); + } catch (Exception e) { + throw new CompletionException(e); + } + }); + } + + @Override + public CompletableFuture put(SharedKey sharedKey, byte[] value) { + return CompletableFuture.supplyAsync(() -> { + try { + return _put(sharedKey, value); + } catch (Exception e) { + throw new CompletionException(e); + } + }); + } + + @Override + public CompletableFuture put(SelfKey selfKey, byte[] value) { + return CompletableFuture.supplyAsync(() -> { + try { + return _put(selfKey, value); + } catch (Exception e) { + throw new CompletionException(e); + } + }); + } + + @Override + public CompletableFuture put(PublicKey publicKey, byte[] value) { + return CompletableFuture.supplyAsync(() -> { + try { + return _put(publicKey, value); + } catch (Exception e) { + throw new CompletionException(e); + } + }); + } + + @Override + public CompletableFuture> getAtKeys(String regex) { + return getAtKeys(regex, true); + } + + @Override + public CompletableFuture> getAtKeys(String regex, boolean fetchMetadata) { + return CompletableFuture.supplyAsync(() -> { + try { + return _getAtKeys(regex, fetchMetadata); + } catch (Exception e) { + throw new CompletionException(e); + } + }); + } + + /** + * Synchronous, talks @-protocol directly to the client's Secondary server + * + * @param command in @ protocol format + * @param throwExceptionOnErrorResponse sometimes we want to inspect an error response, sometimes we + * want to just throw an exception + * @return a Secondary Response + * @throws AtException if the response from the Secondary starts with 'error:', or if there is any + * other exception + */ + @Override + public Response executeCommand(String command, boolean throwExceptionOnErrorResponse) + throws AtException, IOException { + return secondary.executeCommand(command, throwExceptionOnErrorResponse); + } + + // ============================================================================================================================================ + // ============================================================================================================================================ + // ============================================================================================================================================ + + // + // Synchronous methods which do the actual work + // + private String _get(SharedKey sharedKey) throws AtException { + if (sharedKey.sharedBy.toString().equals(atSign.toString())) { + return _getSharedByMeWithOther(sharedKey); + } else { + return _getSharedByOtherWithMe(sharedKey); + } + } + + private String _getSharedByMeWithOther(SharedKey sharedKey) throws AtException { + String shareEncryptionKey = getEncryptionKeySharedByMe(sharedKey); + + // fetch local - e.g. if I'm @bob, I would first "llookup:@alice:some.key.name@bob" + Response rawResponse; + String command = "llookup:" + sharedKey; + try { + rawResponse = secondary.executeCommand(command, true); + } catch (IOException e) { + throw new AtSecondaryConnectException("Failed to execute " + command, e); + } + + try { + return EncryptionUtil.aesDecryptFromBase64(rawResponse.getRawDataResponse(), shareEncryptionKey); + } catch (Exception e) { + throw new AtDecryptionException("Failed to decrypt value with shared encryption key", e); + } + } + + private String _getSharedByOtherWithMe(SharedKey sharedKey) throws AtException { + String what; + String shareEncryptionKey = getEncryptionKeySharedByOther(sharedKey); + + Response rawResponse; + String command = "lookup:" + sharedKey.name; + if (sharedKey.getNamespace() != null && !sharedKey.getNamespace().isEmpty()) { + command += "." + sharedKey.getNamespace(); + } + command += sharedKey.sharedBy.toString(); + try { + rawResponse = secondary.executeCommand(command, true); + } catch (IOException e) { + throw new AtSecondaryConnectException("Failed to execute " + command, e); + } + + what = "decrypt value with shared encryption key"; + try { + return EncryptionUtil.aesDecryptFromBase64(rawResponse.getRawDataResponse(), shareEncryptionKey); + } catch (Exception e) { + throw new AtDecryptionException("Failed to " + what, e); + } + } + + private String _put(SharedKey sharedKey, String value) throws AtException { + if (!this.atSign.equals(sharedKey.sharedBy)) { + throw new AtIllegalArgumentException( + "sharedBy is [" + sharedKey.sharedBy + "] but should be this client's atSign [" + atSign + "]"); + } + String what = ""; + String cipherText; + try { + what = "fetch/create shared encryption key"; + String shareToEncryptionKey = getEncryptionKeySharedByMe(sharedKey); + + what = "encrypt value with shared encryption key"; + cipherText = EncryptionUtil.aesEncryptToBase64(value, shareToEncryptionKey); + } catch (Exception e) { + throw new AtEncryptionException("Failed to " + what, e); + } + + String command = "update" + sharedKey.metadata.toString() + ":" + sharedKey + " " + cipherText; + + try { + return secondary.executeCommand(command, true).toString(); + } catch (IOException e) { + throw new AtSecondaryConnectException("Failed to execute " + command, e); + } + } + + private String _delete(SharedKey sharedKey) throws AtException { + String command = "delete:" + sharedKey; + try { + return secondary.executeCommand(command, true).toString(); + } catch (IOException e) { + throw new AtSecondaryConnectException("Failed to execute " + command, e); + } + } + + private String _get(SelfKey key) throws AtException { + // 1. build command + String command; + LlookupVerbBuilder builder = new LlookupVerbBuilder(); + builder.with(key, LlookupVerbBuilder.Type.ALL); + command = builder.build(); + + // 2. execute command + LookupResponse fetched = getLookupResponse(command); + + // 3. decrypt the value + String decryptedValue; + String encryptedValue = fetched.data; + String selfEncryptionKey = keys.get(KeysUtil.selfEncryptionKeyName); + try { + decryptedValue = EncryptionUtil.aesDecryptFromBase64(encryptedValue, selfEncryptionKey); + } catch (NoSuchPaddingException | NoSuchAlgorithmException | InvalidAlgorithmParameterException + | InvalidKeyException | IllegalBlockSizeException | BadPaddingException | NoSuchProviderException e) { + throw new AtDecryptionException("Failed to " + command, e); + } + + // 4. update metadata. squash the fetchedMetadata with current key.metadata (fetchedMetadata has higher priority) + key.metadata = Metadata.squash(fetched.metaData, key.metadata); + + return decryptedValue; + } + + private String _put(SelfKey selfKey, String value) throws AtException { + // 1. generate dataSignature + selfKey.metadata.dataSignature = generateSignature(value); + + // 2. encrypt data with self encryption key + String cipherText; + try { + cipherText = EncryptionUtil.aesEncryptToBase64(value, keys.get(KeysUtil.selfEncryptionKeyName)); + } catch (NoSuchPaddingException | NoSuchAlgorithmException | InvalidAlgorithmParameterException + | InvalidKeyException | IllegalBlockSizeException | BadPaddingException | NoSuchProviderException e) { + throw new AtEncryptionException("Failed to encrypt value with self encryption key", e); + } + + // 3. update secondary + UpdateVerbBuilder builder = new UpdateVerbBuilder(); + builder.with(selfKey, cipherText); + String command = builder.build(); + try { + return secondary.executeCommand(command, true).toString(); + } catch (IOException e) { + throw new AtSecondaryConnectException("Failed to execute " + command, e); + } + } + + private String _delete(SelfKey key) throws AtException { + // 1. build delete command + DeleteVerbBuilder builder = new DeleteVerbBuilder(); + builder.with(key); + String command = builder.build(); + + // 2. run command + try { + return secondary.executeCommand(command, true).toString(); + } catch (IOException e) { + throw new AtSecondaryConnectException("Failed to execute " + command, e); + } + } + + private String _get(PublicKey key) throws AtException { + return _get(key, null); + } + + private String _get(PublicKey key, GetRequestOptions getRequestOptions) throws AtException { + // 1. build command + String command; + if (atSign.toString().equals(key.sharedBy.toString())) { + // it's a public key created by this client => llookup + LlookupVerbBuilder builder = new LlookupVerbBuilder(); + builder.with(key, LlookupVerbBuilder.Type.ALL); + command = builder.build(); + } else { + // it's a public key created by another => plookup + PlookupVerbBuilder builder = new PlookupVerbBuilder(); + builder.with(key, PlookupVerbBuilder.Type.ALL); + builder.setBypassCache(getRequestOptions != null && getRequestOptions.getBypassCache()); + command = builder.build(); + } + + // 2. run the command + LookupResponse fetched = getLookupResponse(command); + + // 4. update key object metadata + key.metadata = Metadata.squash(fetched.metaData, key.metadata); + key.metadata.isCached = fetched.key.contains("cached:"); + + // 5. return the AtValue + return fetched.data; + } + + private String _put(PublicKey publicKey, String value) throws AtException { + // 1. generate dataSignature + publicKey.metadata.dataSignature = generateSignature(value); + + // 2. build command + String command; + UpdateVerbBuilder builder = new UpdateVerbBuilder(); + builder.with(publicKey, value); + command = builder.build(); + + // 3. run command + try { + return secondary.executeCommand(command, true).toString(); + } catch (IOException e) { + throw new AtSecondaryConnectException("Failed to execute " + command, e); + } + } + + private String _delete(PublicKey key) throws AtException { + // 1. build command + String command; + DeleteVerbBuilder builder = new DeleteVerbBuilder(); + builder.with(key); + command = builder.build(); + + // 2. run command + try { + return secondary.executeCommand(command, true).toString(); + } catch (IOException e) { + throw new AtSecondaryConnectException("Failed to execute " + command, e); + } + } + + private byte[] _getBinary(SharedKey sharedKey) throws AtException { + throw new RuntimeException("Not Implemented"); + } + + private byte[] _getBinary(SelfKey selfKey) throws AtException { + throw new RuntimeException("Not Implemented"); + } + + private byte[] _getBinary(PublicKey publicKey) throws AtException { + throw new RuntimeException("Not Implemented"); + } + + private byte[] _getBinary(PublicKey publicKey, GetRequestOptions getRequestOptions) throws AtException { + throw new RuntimeException("Not Implemented"); + } + + private String _put(SharedKey sharedKey, byte[] value) throws AtException { + throw new RuntimeException("Not Implemented"); + } + + private String _put(SelfKey selfKey, byte[] value) throws AtException { + throw new RuntimeException("Not Implemented"); + } + + private String _put(PublicKey publicKey, byte[] value) throws AtException { + throw new RuntimeException("Not Implemented"); + } + + private List _getAtKeys(String regex, boolean fetchMetadata) throws AtException { + ScanVerbBuilder scanVerbBuilder = new ScanVerbBuilder(); + scanVerbBuilder.setRegex(regex); + scanVerbBuilder.setShowHidden(true); + String scanCommand = scanVerbBuilder.build(); + Response scanRawResponse; + try { + scanRawResponse = executeCommand(scanCommand, true); + } catch (IOException e) { + throw new AtSecondaryConnectException("Failed to execute " + scanCommand, e); + } + ResponseTransformers.ScanResponseTransformer scanResponseTransformer = + new ResponseTransformers.ScanResponseTransformer(); + List rawArray = scanResponseTransformer.transform(scanRawResponse); + List atKeys = new ArrayList<>(); + for (String atKeyRaw : rawArray) { // eg atKeyRaw == @bob:phone@alice + AtKey atKey = Keys.fromString(atKeyRaw); + if (fetchMetadata) { + String llookupCommand = "llookup:meta:" + atKeyRaw; + Response llookupMetaResponse; try { - return secondary.executeCommand(command, true).toString(); + llookupMetaResponse = secondary.executeCommand(llookupCommand, true); } catch (IOException e) { - throw new AtSecondaryConnectException("Failed to execute " + command, e); + throw new AtSecondaryConnectException("Failed to execute " + llookupCommand, e); } - } - - private String _delete(SharedKey sharedKey) throws AtException { - String command = "delete:" + sharedKey; - try { - return secondary.executeCommand(command, true).toString(); - } catch (IOException e) { - throw new AtSecondaryConnectException("Failed to execute " + command, e); - } - } - - private String _get(SelfKey key) throws AtException { - // 1. build command - String command; - LlookupVerbBuilder builder = new LlookupVerbBuilder(); - builder.with(key, LlookupVerbBuilder.Type.ALL); - command = builder.build(); - - // 2. execute command - LookupResponse fetched = getLookupResponse(command); - - // 3. decrypt the value - String decryptedValue; - String encryptedValue = fetched.data; - String selfEncryptionKey = keys.get(KeysUtil.selfEncryptionKeyName); - try { - decryptedValue = EncryptionUtil.aesDecryptFromBase64(encryptedValue, selfEncryptionKey); - } catch (NoSuchPaddingException | NoSuchAlgorithmException | InvalidAlgorithmParameterException | InvalidKeyException | IllegalBlockSizeException | BadPaddingException | NoSuchProviderException e) { - throw new AtDecryptionException("Failed to " + command, e); - } - - // 4. update metadata. squash the fetchedMetadata with current key.metadata (fetchedMetadata has higher priority) - key.metadata = Metadata.squash(fetched.metaData, key.metadata); - - return decryptedValue; - } - - private String _put(SelfKey selfKey, String value) throws AtException { - // 1. generate dataSignature - selfKey.metadata.dataSignature = generateSignature(value); - - // 2. encrypt data with self encryption key - String cipherText; - try { - cipherText = EncryptionUtil.aesEncryptToBase64(value, keys.get(KeysUtil.selfEncryptionKeyName)); - } catch (NoSuchPaddingException | NoSuchAlgorithmException | InvalidAlgorithmParameterException | InvalidKeyException | IllegalBlockSizeException | BadPaddingException | NoSuchProviderException e) { - throw new AtEncryptionException("Failed to encrypt value with self encryption key", e); - } - - // 3. update secondary - UpdateVerbBuilder builder = new UpdateVerbBuilder(); - builder.with(selfKey, cipherText); - String command = builder.build(); try { - return secondary.executeCommand(command, true).toString(); - } catch (IOException e) { - throw new AtSecondaryConnectException("Failed to execute " + command, e); - } - } - - private String _delete(SelfKey key) throws AtException { - // 1. build delete command - DeleteVerbBuilder builder = new DeleteVerbBuilder(); - builder.with(key); - String command = builder.build(); - - // 2. run command - try { - return secondary.executeCommand(command, true).toString(); - } catch (IOException e) { - throw new AtSecondaryConnectException("Failed to execute " + command, e); - } - } - - private String _get(PublicKey key) throws AtException { - return _get(key, null); - } - - private String _get(PublicKey key, GetRequestOptions getRequestOptions) throws AtException { - // 1. build command - String command; - if(atSign.toString().equals(key.sharedBy.toString())) { - // it's a public key created by this client => llookup - LlookupVerbBuilder builder = new LlookupVerbBuilder(); - builder.with(key, LlookupVerbBuilder.Type.ALL); - command = builder.build(); - } else { - // it's a public key created by another => plookup - PlookupVerbBuilder builder = new PlookupVerbBuilder(); - builder.with(key, PlookupVerbBuilder.Type.ALL); - builder.setBypassCache(getRequestOptions != null && getRequestOptions.getBypassCache()); - command = builder.build(); - } - - // 2. run the command - LookupResponse fetched = getLookupResponse(command); - - // 4. update key object metadata - key.metadata = Metadata.squash(fetched.metaData, key.metadata); - key.metadata.isCached = fetched.key.contains("cached:"); - - // 5. return the AtValue - return fetched.data; - } - - private String _put(PublicKey publicKey, String value) throws AtException { - // 1. generate dataSignature - publicKey.metadata.dataSignature = generateSignature(value); - - // 2. build command - String command; - UpdateVerbBuilder builder = new UpdateVerbBuilder(); - builder.with(publicKey, value); - command = builder.build(); - - // 3. run command - try { - return secondary.executeCommand(command, true).toString(); - } catch (IOException e) { - throw new AtSecondaryConnectException("Failed to execute " + command, e); - } - } - - private String _delete(PublicKey key) throws AtException { - // 1. build command - String command; - DeleteVerbBuilder builder = new DeleteVerbBuilder(); - builder.with(key); - command = builder.build(); - - // 2. run command - try { - return secondary.executeCommand(command, true).toString(); - } catch (IOException e) { - throw new AtSecondaryConnectException("Failed to execute " + command, e); - } - } - - private byte[] _getBinary(SharedKey sharedKey) throws AtException {throw new RuntimeException("Not Implemented");} - private byte[] _getBinary(SelfKey selfKey) throws AtException {throw new RuntimeException("Not Implemented");} - private byte[] _getBinary(PublicKey publicKey) throws AtException {throw new RuntimeException("Not Implemented");} - private byte[] _getBinary(PublicKey publicKey, GetRequestOptions getRequestOptions) throws AtException {throw new RuntimeException("Not Implemented");} - - private String _put(SharedKey sharedKey, byte[] value) throws AtException {throw new RuntimeException("Not Implemented");} - private String _put(SelfKey selfKey, byte[] value) throws AtException {throw new RuntimeException("Not Implemented");} - private String _put(PublicKey publicKey, byte[] value) throws AtException {throw new RuntimeException("Not Implemented");} - - private List _getAtKeys(String regex, boolean fetchMetadata) throws AtException { - ScanVerbBuilder scanVerbBuilder = new ScanVerbBuilder(); - scanVerbBuilder.setRegex(regex); - scanVerbBuilder.setShowHidden(true); - String scanCommand = scanVerbBuilder.build(); - Response scanRawResponse; - try { - scanRawResponse = executeCommand(scanCommand, true); - } catch (IOException e) { - throw new AtSecondaryConnectException("Failed to execute " + scanCommand, e); - } - ResponseTransformers.ScanResponseTransformer scanResponseTransformer = new ResponseTransformers.ScanResponseTransformer(); - List rawArray = scanResponseTransformer.transform(scanRawResponse); - List atKeys = new ArrayList<>(); - for(String atKeyRaw : rawArray) { // eg atKeyRaw == @bob:phone@alice - AtKey atKey = Keys.fromString(atKeyRaw); - if (fetchMetadata) { - String llookupCommand = "llookup:meta:" + atKeyRaw; - Response llookupMetaResponse; - try { - llookupMetaResponse = secondary.executeCommand(llookupCommand, true); - } catch (IOException e) { - throw new AtSecondaryConnectException("Failed to execute " + llookupCommand, e); - } - try { - atKey.metadata = Metadata.squash(atKey.metadata, Metadata.fromJson(llookupMetaResponse.getRawDataResponse())); // atKey.metadata has priority over llookupMetaRaw.data - } catch (JsonProcessingException e) { - throw new AtResponseHandlingException("Failed to parse JSON " + llookupMetaResponse.getRawDataResponse(), e); - } - } - atKeys.add(atKey); - } - return atKeys; - } - - // ============================================================================================================================================ - // ============================================================================================================================================ - // ============================================================================================================================================ - - // - // Internal utility methods. Will move these to another class later, so that other AtClient implementations can easily use them. - // - - private LookupResponse getLookupResponse(String command) throws AtException { - Response response; - try { - response = secondary.executeCommand(command, true); - } catch (IOException e) { - throw new AtSecondaryConnectException("Failed to execute " + command, e); - } - - // 3. transform the data to a LlookupAllResponse object - LookupResponse fetched; - try { - fetched = json.readValue(response.getRawDataResponse(), LookupResponse.class); + // atKey.metadata has priority over llookupMetaRaw.data + atKey.metadata = Metadata.squash(atKey.metadata, Metadata.fromJson(llookupMetaResponse.getRawDataResponse())); } catch (JsonProcessingException e) { - throw new AtResponseHandlingException("Failed to parse JSON " + response.getRawDataResponse(), e); + throw new AtResponseHandlingException("Failed to parse JSON " + llookupMetaResponse.getRawDataResponse(), e); } - return fetched; + } + atKeys.add(atKey); } + return atKeys; + } - private String getEncryptionKeySharedByMe(SharedKey key) throws AtException { - // llookup:shared_key.bob@alice - Secondary.Response rawResponse; - String toLookup = "shared_key." + key.sharedWith.withoutPrefix() + atSign; + // ============================================================================================================================================ + // ============================================================================================================================================ + // ============================================================================================================================================ - String command = "llookup:" + toLookup; - try { - rawResponse = secondary.executeCommand(command, false); - } catch (IOException e) { - throw new AtSecondaryConnectException("Failed to execute " + command, e); - } - - if (rawResponse.isError()) { - if (rawResponse.getException() instanceof AtKeyNotFoundException) { - // No key found - so we should create one - return createSharedEncryptionKey(key); - } else { - throw rawResponse.getException(); - } - } + // + // Internal utility methods. Will move these to another class later, so that other AtClient + // implementations can easily use them. + // - // When we stored it, we encrypted it with our encryption public key; so we need to decrypt it now with our encryption private key - try { - return EncryptionUtil.rsaDecryptFromBase64(rawResponse.getRawDataResponse(), keys.get(KeysUtil.encryptionPrivateKeyName)); - } catch (Exception e) { - throw new AtDecryptionException("Failed to decrypt " + toLookup, e); - } + private LookupResponse getLookupResponse(String command) throws AtException { + Response response; + try { + response = secondary.executeCommand(command, true); + } catch (IOException e) { + throw new AtSecondaryConnectException("Failed to execute " + command, e); } - private String getEncryptionKeySharedByOther(SharedKey sharedKey) throws AtException { - // Let's see if it's in our in-memory cache - String sharedSharedKeyName = sharedKey.getSharedSharedKeyName(); - String sharedKeyValue = keys.get(sharedSharedKeyName); - if (sharedKeyValue != null) { - return sharedKeyValue; - } + // 3. transform the data to a LlookupAllResponse object + LookupResponse fetched; + try { + fetched = json.readValue(response.getRawDataResponse(), LookupResponse.class); + } catch (JsonProcessingException e) { + throw new AtResponseHandlingException("Failed to parse JSON " + response.getRawDataResponse(), e); + } + return fetched; + } - String what = ""; + private String getEncryptionKeySharedByMe(SharedKey key) throws AtException { + // llookup:shared_key.bob@alice + Secondary.Response rawResponse; + String toLookup = "shared_key." + key.sharedWith.withoutPrefix() + atSign; - // Not in memory so now let's try to fetch from remote - e.g. if I'm @bob, lookup:shared_key@alice - String lookupCommand = "lookup:" + "shared_key" + sharedKey.sharedBy; - Response rawResponse; - try { - rawResponse = secondary.executeCommand(lookupCommand, true); - } catch (IOException e) { - throw new AtSecondaryConnectException("Failed to execute " + lookupCommand, e); - } - - String sharedSharedKeyDecryptedValue; - try { - sharedSharedKeyDecryptedValue = EncryptionUtil.rsaDecryptFromBase64(rawResponse.getRawDataResponse(), keys.get(KeysUtil.encryptionPrivateKeyName)); - } catch (Exception e) { - throw new AtDecryptionException("Failed to decrypt the shared_key with our encryption private key", e); - } - keys.put(sharedSharedKeyName, sharedSharedKeyDecryptedValue); - - return sharedSharedKeyDecryptedValue; + String command = "llookup:" + toLookup; + try { + rawResponse = secondary.executeCommand(command, false); + } catch (IOException e) { + throw new AtSecondaryConnectException("Failed to execute " + command, e); } - private String createSharedEncryptionKey(SharedKey sharedKey) throws AtException { - // We need their public key - String theirPublicEncryptionKey = getPublicEncryptionKey(sharedKey.sharedWith); - if (theirPublicEncryptionKey == null) { - throw new AtKeyNotFoundException(" public key " + sharedKey.sharedWith + " not found but service is running - maybe that AtSign has not yet been onboarded"); - } - - // Cut an AES key - String aesKey; - try { - aesKey = EncryptionUtil.generateAESKeyBase64(); - } catch (Exception e) { - throw new AtEncryptionException("Failed to generate AES key for sharing with " + sharedKey.sharedWith, e); - } - - String what = ""; - try { - // Encrypt key with the other at-sign's publickey and save it @bob:shared_key@alice - what = "encrypt new shared key with their public key"; - String encryptedForOther = EncryptionUtil.rsaEncryptToBase64(aesKey, theirPublicEncryptionKey); - - what = "encrypt new shared key with our public key"; - // Encrypt key with our publickey and save it shared_key.bob@alice - String encryptedForUs = EncryptionUtil.rsaEncryptToBase64(aesKey, keys.get(KeysUtil.encryptionPublicKeyName)); - - what = "save encrypted shared key for us"; - secondary.executeCommand("update:" + "shared_key." + sharedKey.sharedWith.withoutPrefix() + sharedKey.sharedBy - + " " + encryptedForUs, true); - - what = "save encrypted shared key for them"; - int ttr = 24 * 60 * 60 * 1000; - secondary.executeCommand("update:ttr:" + ttr + ":" + sharedKey.sharedWith + ":shared_key" + sharedKey.sharedBy - + " " + encryptedForOther, true); - } catch (Exception e) { - throw new AtEncryptionException("Failed to " + what, e); - } - - return aesKey; + if (rawResponse.isError()) { + if (rawResponse.getException() instanceof AtKeyNotFoundException) { + // No key found - so we should create one + return createSharedEncryptionKey(key); + } else { + throw rawResponse.getException(); + } } - private String getPublicEncryptionKey(AtSign sharedWith) throws AtException { - // plookup:publickey@alice - Secondary.Response rawResponse; + // When we stored it, we encrypted it with our encryption public key; so we need to decrypt it now with our encryption private key + try { + return EncryptionUtil.rsaDecryptFromBase64(rawResponse.getRawDataResponse(), + keys.get(KeysUtil.encryptionPrivateKeyName)); + } catch (Exception e) { + throw new AtDecryptionException("Failed to decrypt " + toLookup, e); + } + } + + private String getEncryptionKeySharedByOther(SharedKey sharedKey) throws AtException { + // Let's see if it's in our in-memory cache + String sharedSharedKeyName = sharedKey.getSharedSharedKeyName(); + + String sharedKeyValue = keys.get(sharedSharedKeyName); + if (sharedKeyValue != null) { + return sharedKeyValue; + } + + String what = ""; + + // Not in memory so now let's try to fetch from remote - e.g. if I'm @bob, lookup:shared_key@alice + String lookupCommand = "lookup:" + "shared_key" + sharedKey.sharedBy; + Response rawResponse; + try { + rawResponse = secondary.executeCommand(lookupCommand, true); + } catch (IOException e) { + throw new AtSecondaryConnectException("Failed to execute " + lookupCommand, e); + } + + String sharedSharedKeyDecryptedValue; + try { + sharedSharedKeyDecryptedValue = EncryptionUtil.rsaDecryptFromBase64(rawResponse.getRawDataResponse(), + keys.get(KeysUtil.encryptionPrivateKeyName)); + } catch (Exception e) { + throw new AtDecryptionException("Failed to decrypt the shared_key with our encryption private key", e); + } + keys.put(sharedSharedKeyName, sharedSharedKeyDecryptedValue); + + return sharedSharedKeyDecryptedValue; + } + + private String createSharedEncryptionKey(SharedKey sharedKey) throws AtException { + // We need their public key + String theirPublicEncryptionKey = getPublicEncryptionKey(sharedKey.sharedWith); + if (theirPublicEncryptionKey == null) { + throw new AtKeyNotFoundException(" public key " + sharedKey.sharedWith + + " not found but service is running - maybe that AtSign has not yet been onboarded"); + } + + // Cut an AES key + String aesKey; + try { + aesKey = EncryptionUtil.generateAESKeyBase64(); + } catch (Exception e) { + throw new AtEncryptionException("Failed to generate AES key for sharing with " + sharedKey.sharedWith, e); + } + + String what = ""; + try { + // Encrypt key with the other at-sign's publickey and save it @bob:shared_key@alice + what = "encrypt new shared key with their public key"; + String encryptedForOther = EncryptionUtil.rsaEncryptToBase64(aesKey, theirPublicEncryptionKey); + + what = "encrypt new shared key with our public key"; + // Encrypt key with our publickey and save it shared_key.bob@alice + String encryptedForUs = EncryptionUtil.rsaEncryptToBase64(aesKey, keys.get(KeysUtil.encryptionPublicKeyName)); + + what = "save encrypted shared key for us"; + secondary.executeCommand("update:" + "shared_key." + sharedKey.sharedWith.withoutPrefix() + sharedKey.sharedBy + + " " + encryptedForUs, true); + + what = "save encrypted shared key for them"; + int ttr = 24 * 60 * 60 * 1000; + secondary.executeCommand("update:ttr:" + ttr + ":" + sharedKey.sharedWith + ":shared_key" + sharedKey.sharedBy + + " " + encryptedForOther, true); + } catch (Exception e) { + throw new AtEncryptionException("Failed to " + what, e); + } - String command = "plookup:publickey" + sharedWith; - try { - rawResponse = secondary.executeCommand(command, false); - } catch (IOException e) { - throw new AtSecondaryConnectException("Failed to execute " + command, e); - } + return aesKey; + } + + private String getPublicEncryptionKey(AtSign sharedWith) throws AtException { + // plookup:publickey@alice + Secondary.Response rawResponse; + + String command = "plookup:publickey" + sharedWith; + try { + rawResponse = secondary.executeCommand(command, false); + } catch (IOException e) { + throw new AtSecondaryConnectException("Failed to execute " + command, e); + } + + if (rawResponse.isError()) { + if (rawResponse.getException() instanceof AtKeyNotFoundException) { + return null; + } else { + throw rawResponse.getException(); + } + } else { + return rawResponse.getRawDataResponse(); + } + } - if (rawResponse.isError()) { - if (rawResponse.getException() instanceof AtKeyNotFoundException) { - return null; - } else { - throw rawResponse.getException(); - } - } else { - return rawResponse.getRawDataResponse(); - } - } - - private String generateSignature(String value) throws AtException { - String signature; - try { - signature = EncryptionUtil.signSHA256RSA(value, keys.get(KeysUtil.encryptionPrivateKeyName)); - } catch (Exception e) { - throw new AtEncryptionException("Failed to sign value: " + value, e); - } - return signature; + private String generateSignature(String value) throws AtException { + String signature; + try { + signature = EncryptionUtil.signSHA256RSA(value, keys.get(KeysUtil.encryptionPrivateKeyName)); + } catch (Exception e) { + throw new AtEncryptionException("Failed to sign value: " + value, e); } + return signature; + } } diff --git a/at_client/src/main/java/org/atsign/client/api/impl/connections/AtConnectionBase.java b/at_client/src/main/java/org/atsign/client/api/impl/connections/AtConnectionBase.java index ddd6cadf..3bc302bd 100644 --- a/at_client/src/main/java/org/atsign/client/api/impl/connections/AtConnectionBase.java +++ b/at_client/src/main/java/org/atsign/client/api/impl/connections/AtConnectionBase.java @@ -15,130 +15,169 @@ * @see org.atsign.client.api.AtConnection */ public abstract class AtConnectionBase implements AtConnection { - private final String url; - @Override - public String getUrl() { return url; } - - private final String host; - @Override - public String getHost() { return host; } - - private final int port; - @Override - public int getPort() { return port; } - - private Socket socket; - @Override - public Socket getSocket() { return socket; } - - private boolean connected = false; - @Override - public boolean isConnected() { return connected; } - - private final boolean autoReconnect; - @Override - public boolean isAutoReconnect() {return autoReconnect;} - - protected boolean verbose; - @Override - public boolean isVerbose() {return verbose;} - @Override - public void setVerbose(boolean verbose) {this.verbose = verbose;} - - protected final Authenticator authenticator; - public Authenticator getAuthenticator() {return authenticator;} - - protected PrintWriter socketWriter; - protected Scanner socketScanner; - - protected final AtEvents.AtEventBus eventBus; - public AtConnectionBase(AtEvents.AtEventBus eventBus, String url, AtConnection.Authenticator authenticator, boolean autoReconnect, boolean verbose) { - this.eventBus = eventBus; - this.url = url; - this.host = url.split(":")[0]; - this.port = Integer.parseInt(url.split(":")[1]); - this.autoReconnect = autoReconnect; - this.verbose = verbose; - this.authenticator = authenticator; + private final String url; + + @Override + public String getUrl() { + return url; + } + + private final String host; + + @Override + public String getHost() { + return host; + } + + private final int port; + + @Override + public int getPort() { + return port; + } + + private Socket socket; + + @Override + public Socket getSocket() { + return socket; + } + + private boolean connected = false; + + @Override + public boolean isConnected() { + return connected; + } + + private final boolean autoReconnect; + + @Override + public boolean isAutoReconnect() { + return autoReconnect; + } + + protected boolean verbose; + + @Override + public boolean isVerbose() { + return verbose; + } + + @Override + public void setVerbose(boolean verbose) { + this.verbose = verbose; + } + + protected final Authenticator authenticator; + + public Authenticator getAuthenticator() { + return authenticator; + } + + protected PrintWriter socketWriter; + protected Scanner socketScanner; + + protected final AtEvents.AtEventBus eventBus; + + public AtConnectionBase(AtEvents.AtEventBus eventBus, + String url, + AtConnection.Authenticator authenticator, + boolean autoReconnect, + boolean verbose) { + this.eventBus = eventBus; + this.url = url; + this.host = url.split(":")[0]; + this.port = Integer.parseInt(url.split(":")[1]); + this.autoReconnect = autoReconnect; + this.verbose = verbose; + this.authenticator = authenticator; + } + + @Override + public synchronized void disconnect() { + if (!isConnected()) { + return; } + connected = false; + try { + System.err.println(this.getClass().getSimpleName() + " disconnecting"); + socket.close(); + socketScanner.close(); + socketWriter.close(); + socket.shutdownInput(); + socket.shutdownOutput(); + } catch (Exception ignore) { + } + } - @Override - public synchronized void disconnect() { - if (! isConnected()) { - return; - } - connected = false; - try { - System.err.println(this.getClass().getSimpleName() + " disconnecting"); - socket.close(); - socketScanner.close(); - socketWriter.close(); - socket.shutdownInput(); - socket.shutdownOutput(); - } catch (Exception ignore) { - } + @Override + public synchronized void connect() throws IOException, AtException { + if (isConnected()) { + return; } - @Override - public synchronized void connect() throws IOException, AtException { - if (isConnected()) { - return; - } - SocketFactory sf = SSLSocketFactory.getDefault(); - this.socket = sf.createSocket(host, port); - this.socketWriter = new PrintWriter(socket.getOutputStream()); - this.socketScanner = new Scanner(socket.getInputStream()); + SocketFactory sf = SSLSocketFactory.getDefault(); + this.socket = sf.createSocket(host, port); + this.socketWriter = new PrintWriter(socket.getOutputStream()); + this.socketScanner = new Scanner(socket.getInputStream()); - if (authenticator != null) { - authenticator.authenticate(this); - } - connected = true; + if (authenticator != null) { + authenticator.authenticate(this); } + connected = true; + } + + protected abstract String parseRawResponse(String rawResponse) throws IOException; - protected abstract String parseRawResponse(String rawResponse) throws IOException; + @Override + public final synchronized String executeCommand(String command) throws IOException { + return executeCommand(command, autoReconnect, true); + } - @Override - public final synchronized String executeCommand(String command) throws IOException { - return executeCommand(command, autoReconnect, true); + protected synchronized String executeCommand(String command, boolean retryOnException, boolean readTheResponse) + throws IOException { + if (socket.isClosed()) { + throw new IOException("executeCommand failed: socket is closed"); } - protected synchronized String executeCommand(String command, boolean retryOnException, boolean readTheResponse) throws IOException { - if (socket.isClosed()) { - throw new IOException("executeCommand failed: socket is closed"); + try { + if (!command.endsWith("\n")) { + command = command + "\n"; + } + socketWriter.write(command); + socketWriter.flush(); + + if (verbose) { + System.out.println("\tSENT: " + command.trim()); + } + + if (readTheResponse) { + // Responses are always terminated by newline + String rawResponse = socketScanner.nextLine(); + if (verbose) { + System.out.println("\tRCVD: " + rawResponse); } + + return parseRawResponse(rawResponse); + } else { + return ""; + } + } catch (Exception first) { + disconnect(); + + if (retryOnException) { + System.err.println("\tCaught exception " + first + " : reconnecting"); try { - if (! command.endsWith("\n")) { - command = command + "\n"; - } - socketWriter.write(command); - socketWriter.flush(); - - if (verbose) System.out.println("\tSENT: " + command.trim()); - - if (readTheResponse) { - // Responses are always terminated by newline - String rawResponse = socketScanner.nextLine(); - if (verbose) System.out.println("\tRCVD: " + rawResponse); - - return parseRawResponse(rawResponse); - } else { - return ""; - } - } catch (Exception first) { - disconnect(); - - if (retryOnException) { - System.err.println("\tCaught exception " + first + " : reconnecting"); - try { - connect(); - return executeCommand(command, false, true); - } catch (Exception second) { - second.printStackTrace(System.err); - throw new IOException("Failed to reconnect after original exception " + first + " : ", second); - } - } else { - connected = false; - - throw new IOException(first); - } + connect(); + return executeCommand(command, false, true); + } catch (Exception second) { + second.printStackTrace(System.err); + throw new IOException("Failed to reconnect after original exception " + first + " : ", second); } + } else { + connected = false; + + throw new IOException(first); + } } + } } diff --git a/at_client/src/main/java/org/atsign/client/api/impl/connections/AtMonitorConnection.java b/at_client/src/main/java/org/atsign/client/api/impl/connections/AtMonitorConnection.java index 5ee0bd05..855c3630 100644 --- a/at_client/src/main/java/org/atsign/client/api/impl/connections/AtMonitorConnection.java +++ b/at_client/src/main/java/org/atsign/client/api/impl/connections/AtMonitorConnection.java @@ -12,222 +12,237 @@ * */ public class AtMonitorConnection extends AtSecondaryConnection implements Runnable { - private static final ObjectMapper mapper = new ObjectMapper(); - - private long _lastReceivedTime = 0; - public long getLastReceivedTime() {return _lastReceivedTime;} - public void setLastReceivedTime(long lastReceivedTime) {this._lastReceivedTime = lastReceivedTime;} - - private boolean running = false; - public boolean isRunning() { return running; } - - private boolean _shouldBeRunning = false; - private void setShouldBeRunning(boolean b) { - _shouldBeRunning = b; - } - - public boolean isShouldBeRunning() { - return _shouldBeRunning; - } - - public AtMonitorConnection( - AtEventBus eventBus, - AtSign atSign, - String secondaryUrl, - Authenticator authenticator, - boolean verbose) { - // Note that the Monitor doesn't make use of the auto-reconnect functionality, it does its own thing - super(eventBus, atSign, secondaryUrl, authenticator, false, verbose); - startHeartbeat(); - } - - private long lastHeartbeatSentTime = System.currentTimeMillis(); - private long lastHeartbeatAckTime = System.currentTimeMillis(); - private final int heartbeatIntervalMillis = 30000; - private void startHeartbeat() { - new Thread(() -> { - while (true) { - if (isShouldBeRunning()) { - if (!isRunning() || lastHeartbeatSentTime - lastHeartbeatAckTime >= heartbeatIntervalMillis) { - try { - // heartbeats have stopped being acked - System.err.println("Monitor heartbeats not being received"); - stopMonitor(); - long waitStartTime = System.currentTimeMillis(); - while (isRunning() && System.currentTimeMillis() - waitStartTime < 5000) { - // wait for monitor to stop - try { - //noinspection BusyWait - Thread.sleep(1000); - } catch (Exception ignore) {} - } - if (isRunning()) { - System.err.println("Monitor thread has not stopped, but going to start another one anyway"); - } - startMonitor(); - } catch (Exception e) { - System.err.println("Monitor restart failed " + e); - e.printStackTrace(System.err); - } - } else { - if (System.currentTimeMillis() - lastHeartbeatSentTime > heartbeatIntervalMillis) { - try { - executeCommand("noop:0", false, false); - lastHeartbeatSentTime = System.currentTimeMillis(); - } catch (Exception ignore) { - // Can't do anything, the heartbeat loop will take care of restarting the monitor connection - } - } - } - } - try { - //noinspection BusyWait - Thread.sleep(heartbeatIntervalMillis / 5); - } catch (Exception ignore) {} - } - }).start(); - } - - /** - * @return true if the monitor start request has succeeded, or if the monitor is already running. - */ - @SuppressWarnings("UnusedReturnValue") - public synchronized boolean startMonitor() { - lastHeartbeatSentTime = lastHeartbeatAckTime = System.currentTimeMillis(); - - setShouldBeRunning(true); - if (! running) { - running = true; - if (!isConnected()) { + private static final ObjectMapper mapper = new ObjectMapper(); + + private long _lastReceivedTime = 0; + + public long getLastReceivedTime() { + return _lastReceivedTime; + } + + public void setLastReceivedTime(long lastReceivedTime) { + this._lastReceivedTime = lastReceivedTime; + } + + private boolean running = false; + + public boolean isRunning() { + return running; + } + + private boolean _shouldBeRunning = false; + + private void setShouldBeRunning(boolean b) { + _shouldBeRunning = b; + } + + public boolean isShouldBeRunning() { + return _shouldBeRunning; + } + + public AtMonitorConnection(AtEventBus eventBus, + AtSign atSign, + String secondaryUrl, + Authenticator authenticator, + boolean verbose) { + // Note that the Monitor doesn't make use of the auto-reconnect functionality, it does its own thing + super(eventBus, atSign, secondaryUrl, authenticator, false, verbose); + startHeartbeat(); + } + + private long lastHeartbeatSentTime = System.currentTimeMillis(); + private long lastHeartbeatAckTime = System.currentTimeMillis(); + private final int heartbeatIntervalMillis = 30000; + + private void startHeartbeat() { + new Thread(() -> { + while (true) { + if (isShouldBeRunning()) { + if (!isRunning() || lastHeartbeatSentTime - lastHeartbeatAckTime >= heartbeatIntervalMillis) { + try { + // heartbeats have stopped being acked + System.err.println("Monitor heartbeats not being received"); + stopMonitor(); + long waitStartTime = System.currentTimeMillis(); + while (isRunning() && System.currentTimeMillis() - waitStartTime < 5000) { + // wait for monitor to stop try { - connect(); - } catch (Exception e) { - System.err.println("startMonitor failed to connect to secondary : " + e.getMessage()); - running = false; - return false; + // noinspection BusyWait + Thread.sleep(1000); + } catch (Exception ignore) { } + } + if (isRunning()) { + System.err.println("Monitor thread has not stopped, but going to start another one anyway"); + } + startMonitor(); + } catch (Exception e) { + System.err.println("Monitor restart failed " + e); + e.printStackTrace(System.err); + } + } else { + if (System.currentTimeMillis() - lastHeartbeatSentTime > heartbeatIntervalMillis) { + try { + executeCommand("noop:0", false, false); + lastHeartbeatSentTime = System.currentTimeMillis(); + } catch (Exception ignore) { + // Can't do anything, the heartbeat loop will take care of restarting the monitor connection + } } - new Thread(this).start(); + } } - return true; - } - - public synchronized void stopMonitor() { - setShouldBeRunning(false); - lastHeartbeatSentTime = lastHeartbeatAckTime = System.currentTimeMillis(); - disconnect(); - } - - /** - * Please don't call this directly. Call startMonitor() instead, which starts the monitor in its own thread - */ - @SuppressWarnings("unchecked") - @Override - public void run() { - String what = ""; - // call executeCommand("monitor: