kenyee / java-ddp-client

A DDP client written in java for the Meteor framework (https://github.com/meteor/meteor)
Other
67 stars 20 forks source link

SSL Stack Overflow Crash on Android 4.x #17

Closed stephen-workpop closed 7 years ago

stephen-workpop commented 7 years ago

If SSL is enabled when using your android-ddp-client library (which uses java-ddp-client), there is a stack overflow crash on Android 4.x devices. (See: https://github.com/kenyee/android-ddp-client/issues/28)

05-02 16:30:12.372 25945-26349/com.workpop.app.debug E/AndroidRuntime: FATAL EXCEPTION: Thread-1547 java.lang.StackOverflowError at org.apache.harmony.xnet.provider.jsse.OpenSSLCipher.getOutputSize(OpenSSLCipher.java:197) at org.apache.harmony.xnet.provider.jsse.OpenSSLCipher.engineGetOutputSize(OpenSSLCipher.java:209) at javax.crypto.Cipher.getOutputSize(Cipher.java:402) at org.apache.harmony.xnet.provider.jsse.ConnectionState.getMinFragmentSize(ConnectionState.java:69) at org.apache.harmony.xnet.provider.jsse.SSLRecordProtocol.getMinRecordSize(SSLRecordProtocol.java:134) at org.apache.harmony.xnet.provider.jsse.SSLEngineImpl.unwrap(SSLEngineImpl.java:435) at javax.net.ssl.SSLEngine.unwrap(SSLEngine.java:383) at org.java_websocket.SSLSocketChannel2.unwrap(SSLSocketChannel2.java:150) at org.java_websocket.SSLSocketChannel2.readRemaining(SSLSocketChannel2.java:254) at org.java_websocket.SSLSocketChannel2.read(SSLSocketChannel2.java:220) at org.java_websocket.SSLSocketChannel2.read(SSLSocketChannel2.java:241) at org.java_websocket.SSLSocketChannel2.read(SSLSocketChannel2.java:241) at org.java_websocket.SSLSocketChannel2.read(SSLSocketChannel2.java:241) at org.java_websocket.SSLSocketChannel2.read(SSLSocketChannel2.java:241) at org.java_websocket.SSLSocketChannel2.read(SSLSocketChannel2.java:241) at org.java_websocket.SSLSocketChannel2.read(SSLSocketChannel2.java:241) at org.java_websocket.SSLSocketChannel2.read(SSLSocketChannel2.java:241) at org.java_websocket.SSLSocketChannel2.read(SSLSocketChannel2.java:241) at org.java_websocket.SSLSocketChannel2.read(SSLSocketChannel2.java:241) at org.java_websocket.SSLSocketChannel2.read(SSLSocketChannel2.java:241) at org.java_websocket.SSLSocketChannel2.read(SSLSocketChannel2.java:241) at org.java_websocket.SSLSocketChannel2.read(SSLSocketChannel2.java:241) at org.java_websocket.SSLSocketChannel2.read(SSLSocketChannel2.java:241) at org.java_websocket.SSLSocketChannel2.read(SSLSocketChannel2.java:241) at org.java_websocket.SSLSocketChannel2.read(SSLSocketChannel2.java:241) at org.java_websocket.SSLSocketChannel2.read(SSLSocketChannel2.java:241) at org.java_websocket.SSLSocketChannel2.read(SSLSocketChannel2.java:241) at org.java_websocket.SSLSocketChannel2.read(SSLSocketChannel2.java:241) at org.java_websocket.SSLSocketChannel2.read(SSLSocketChannel2.java:241) at org.java_websocket.SSLSocketChannel2.read(SSLSocketChannel2.java:241) at org.java_websocket.SSLSocketChannel2.read(SSLSocketChannel2.java:241) at org.java_websocket.SSLSocketChannel2.read(SSLSocketChannel2.java:241) at org.java_websocket.SSLSocketChannel2.read(SSLSocketChannel2.java:241) at org.java_websocket.SSLSocketChannel2.read(SSLSocketChannel2.java:241) at org.java_websocket.SSLSocketChannel2.read(SSLSocketChannel2.java:241) at org.java_websocket.SSLSocketChannel2.read(SSLSocketChannel2.java:241) at org.java_websocket.SSLSocketChannel2.read(SSLSocketChannel2.java:241) at org.java_websocket.SSLSocketChannel2.read(SSLSocketChannel2.java:241) at org.java_websocket.SSLSocketChannel2.read(SSLSocketChannel2.java:241) at org.java_websocket.SSLSocketChannel2.read(SSLSocketChannel2.java:241) at org.java_websocket.SSLSocketChannel2.read(SSLSocketChannel2.java:241) at org.java_websocket.SSLSocketChannel2.read(SSLSocketChannel2.java:241) at org.java_websocket.SSLSocketChannel2.read(SSLSocketChannel2.java:241) at org.java_websocket.SSLSocketChannel2.read(SSLSocketChannel2.java:241) at org.java_websocket.SSLSocketChannel2.read(SSLSocketChannel2.java:241) at org.java_websocket.SSLSocketChannel2.read(SSLSocketChannel2.java:241) at org.java_websocket.SSLSocketChannel2.read(SSLSocketChannel2.java:241) at org.java_websocket.SSLSocketChannel2.read(SSLSocketChannel2.java:241) at org.java_websocket.SSLSocketChannel2.read(SSLSocketChannel2.java:241) at org.java_websocket.SSLSocketChannel2.read(SSLSocketChannel2.java:241) at org.java_websocket.SSLSocketChannel2.read(SSLSocketChannel2.java:241) at org.java_websocket.SSLSocketChannel2.read(SSLSocketChannel2.java:241) at org.java_websocket.SSLSocketChannel2.read(SSLSocketChannel

The crash happens in the java-websocket 1.3.0 dependency. It looks like this may be fixed in the latest version, 1.3.3 (see: https://github.com/TooTallNate/Java-WebSocket/issues/190 ). I tried forcing a dependency substitution in order to pick up any fixes as follows:

configurations.all {
    resolutionStrategy {
        resolutionStrategy.dependencySubstitution {
            substitute module("org.java-websocket:Java-WebSocket:1.3.0") with module("org.java-websocket:java-websocket:1.3.3")
       }
    }
}

Unfortunately, the newest version of the java-websocket library has a breaking API change and removes a class that DDPClient relies on. That means the dependency substitution will not work.

One solution would be to update java-ddp-client that uses the latest version of java-websocket. This would also require a one line change to DDPClient so that it no longer uses the removed class. It is an easy one line change to update DDPClient so it no longer relies on the class. See https://github.com/TooTallNate/Java-WebSocket/issues/425 and the example code the last post references to see how https://github.com/TooTallNate/Java-WebSocket/blob/master/src/main/example/SSLClientExample.java

stephen-workpop commented 7 years ago

Thanks for merging!

I would like to try out the fix so I can verify it and close the issue, but I am unable to pull in the latest version. Do you have a build-server that automatically builds the library and uploads it to Maven or is it done manually? Also, android-ddp-client should pick this update up automatically since it's dependency version is not hardcoded and is dynamic right (always pulls in 1.0.0.latest)?

kenyee commented 7 years ago

Sorry about the delay...no autobuilding..it's all manual :-( I just did the upload. The android-ddp-client is configured to automatically grab all 1.0.0.x releases, so it'll pull it in automatically. Let me know if you see any issues. Closing this for now because the artifact is up on maven central.

stephen-workpop commented 7 years ago

Unfortunately, I am not seeing it in any of the Maven repos.

Shouldn't it be on this page? https://search.maven.org/#search%7Cgav%7C1%7Cg%3A%22com.keysolutions%22%20AND%20a%3A%22java-ddp-client%22

It isn't on the list, but if I manually change the URL, it looks like there is an entry. Maybe I just need to wait for a cron job on their server to run or something to sync everything up and officially publish it. https://search.maven.org/#artifactdetails%7Ccom.keysolutions%7Cjava-ddp-client%7C1.0.0.6%7Cjar

PS: Thanks for the shout out in the README!

EDIT: Never mind. It looks like it can take a few hours: http://stackoverflow.com/questions/23235892/how-long-does-sonatype-staging-take-to-sync-my-artifacts-with-maven-central

stephen-workpop commented 7 years ago

I waited a bit and now it seems like the java-ddp-client is pulling in the new dependencies. I checked out the java-ddp-client repository on it's own and ran the dependencies task. Here is the output showing that the library now uses java-websocket version 1.3.3 as expected:

> ./gradlew dependencies

...

compile - Dependencies for source set 'main'.
+--- org.apache.commons:commons-collections4:[4.0,5.0) -> 4.1
+--- org.java-websocket:java-websocket:1.3.3
+--- com.google.code.gson:gson:[2.3,3.0) -> 2.8.0
+--- org.slf4j:slf4j-api:[1.7,1.8) -> 1.7.25
+--- org.slf4j:slf4j-simple:[1.7,1.8) -> 1.7.25
|    \--- org.slf4j:slf4j-api:1.7.25
\--- com.nimbusds:srp6a:[1.5,1.6) -> 1.5.4

...

Next, I ran into issues when I tried to run the same command on my clone of the android-ddp-client repo. When I ran the dependencies task, I received the following error:

`> ./gradlew dependencies

FAILURE: Build failed with an exception.

* What went wrong:
A problem occurred configuring root project 'android-ddp-client'.
> Could not resolve all dependencies for configuration ':_debugCompile'.
   > Could not find org.java-websocket:java-websocket:1.3.3.
     Searched in the following locations:
         https://repo1.maven.org/maven2/org/java-websocket/java-websocket/1.3.3/java-websocket-1.3.3.pom
         https://repo1.maven.org/maven2/org/java-websocket/java-websocket/1.3.3/java-websocket-1.3.3.jar
         file:/Users/stephen/Development/android-sdk-mac_x86/extras/android/m2repository/org/java-websocket/java-websocket/1.3.3/java-websocket-1.3.3.pom
         file:/Users/stephen/Development/android-sdk-mac_x86/extras/android/m2repository/org/java-websocket/java-websocket/1.3.3/java-websocket-1.3.3.jar
         file:/Users/stephen/Development/android-sdk-mac_x86/extras/google/m2repository/org/java-websocket/java-websocket/1.3.3/java-websocket-1.3.3.pom
         file:/Users/stephen/Development/android-sdk-mac_x86/extras/google/m2repository/org/java-websocket/java-websocket/1.3.3/java-websocket-1.3.3.jar
     Required by:
         com.keysolutions:android-ddp-client:1.0.2.0 > com.keysolutions:java-ddp-client:1.0.0.6

I then added the same Maven repo I added to the java-ddp-client repository in my last patch to the build.gradle file of this project:

repositories {
    mavenCentral()

    maven {
        url 'http://clojars.org/repo'
    }
}

After the Maven addition to the build file, the dependencies command actually worked without an error. I would have thought that the Maven repositories in a dependency's build.gradle would be "inherited" by the parent project, but I guess they aren't! Here is the output including the correct java-ddp-client version:

> ./gradlew dependencies

...

compile - Classpath for compiling the main sources.
+--- com.android.support:support-v4:23.3.0
|    \--- com.android.support:support-annotations:23.3.0
+--- com.keysolutions:java-ddp-client:[1.0.0,) -> 1.0.0.6
|    +--- org.apache.commons:commons-collections4:[4.0,5.0) -> 4.1
|    +--- org.java-websocket:java-websocket:1.3.3
|    +--- com.google.code.gson:gson:[2.3,3.0) -> 2.8.0
|    +--- org.slf4j:slf4j-api:[1.7,1.8) -> 1.7.25
|    +--- org.slf4j:slf4j-simple:[1.7,1.8) -> 1.7.25
|    |    \--- org.slf4j:slf4j-api:1.7.25
|    \--- com.nimbusds:srp6a:[1.5,1.6) -> 1.5.4
\--- com.mcxiaoke.volley:library:[1.0,) -> 1.0.19

...

Unfortunately, when I run the dependencies task on my own project, it is still pulling in com.keysolutions:java-ddp-client:[1.0.0,) -> 1.0.0.5 and thus Java-Websocket:1.3.0. I was expecting it to resolve the 1.0.0.6, but for some reason it is resolving to 1.0.0.5 still.

> ./gradlew app:dependencies

...

releaseCompile - Classpath for compiling the release sources.
\--- project :lib

....

     +--- com.keysolutions:android-ddp-client:1.0.2.0
     |    +--- com.android.support:support-v4:23.3.0 -> 25.2.0 (*)
     |    +--- com.mcxiaoke.volley:library:[1.0,) -> 1.0.19
     |    \--- com.keysolutions:java-ddp-client:[1.0.0,) -> 1.0.0.5
     |         +--- org.apache.commons:commons-collections4:[4.0,5.0) -> 4.1
     |         +--- org.java-websocket:Java-WebSocket:1.3.0
     |         +--- com.google.code.gson:gson:[2.3,3.0) -> 2.8.0
     |         +--- org.slf4j:slf4j-api:[1.7,1.8) -> 1.7.25
     |         +--- org.slf4j:slf4j-simple:[1.7,1.8) -> 1.7.25
     |         |    \--- org.slf4j:slf4j-api:1.7.25
     |         \--- com.nimbusds:srp6a:[1.5,1.6) -> 1.5.4

Do you have any idea how to get my project to pull in the latest version java-ddp-client which will have the 1.3.3 version of java-websocket? I have cleared my caches in Android Studio ("Invalidate caches and restart" menu action) and it didn't work. I also deleted my ~/.gradle/caches directory to no avail. I even called ./gradlew clean and './gradlew --refresh-dependencies_ and nothing seems to work!

I can add the following resolution strategy to get the job done, but I don't see why it is necessary. Shouldn't the correct version of the dependencies work without it?

configurations.all {
    resolutionStrategy {
        force "com.keysolutions:java-ddp-client:[1.0.0,)", 'com.keysolutions:java-ddp-client:1.0.0.6'
    }
}

Thanks in advance for any help! I really appreciate your quick responses :)

kenyee commented 7 years ago

That's pretty bizarre...something is definitely caching it. You did all the things I would have suggested. The resolution strategy force is the only thing I can think of and you're doing that. I'd say leave it in for a week and maybe things will sync up and your gradle cache will expire whatever it thinks is telling it to use 1.0.0.5.

I tried to build a test project and it's definitely pulling in the latest java-ddp-client, though it has problems finding version 1.3.3 of the java-websocket library without adding the clojars repo which is pretty annoying (the maintainer should push it to maven central because it's more standard)-:

stephen-workpop commented 7 years ago

I just tried again this morning and my project build is now pulling in the correct dependency version. I guess waiting was the solution. Thanks for the help!