diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml
index 7c7e766e..2e813338 100644
--- a/.github/workflows/publish.yml
+++ b/.github/workflows/publish.yml
@@ -34,6 +34,7 @@ jobs:
:timer-pingsender:publishReleasePublicationToSonatypeRepository
:adaptive-keep-alive:publishReleasePublicationToSonatypeRepository
:network-tracker:publishReleasePublicationToSonatypeRepository
+ :courier-message-adapter-text:publishReleasePublicationToSonatypeRepository
:courier-message-adapter-gson:publishReleasePublicationToSonatypeRepository
:courier-message-adapter-moshi:publishReleasePublicationToSonatypeRepository
:courier-message-adapter-protobuf:publishReleasePublicationToSonatypeRepository
diff --git a/adaptive-keep-alive/build.gradle.kts b/adaptive-keep-alive/build.gradle.kts
index 4468e680..98860448 100644
--- a/adaptive-keep-alive/build.gradle.kts
+++ b/adaptive-keep-alive/build.gradle.kts
@@ -46,6 +46,7 @@ dependencies {
implementation(project(":mqtt-pingsender"))
implementation(project(":network-tracker"))
+ testImplementation(deps.android.test.mockitoCore)
testImplementation(deps.android.test.kotlinTestJunit)
}
diff --git a/adaptive-keep-alive/src/main/java/com/gojek/keepalive/AdaptiveKeepAliveStateHandler.kt b/adaptive-keep-alive/src/main/java/com/gojek/keepalive/AdaptiveKeepAliveStateHandler.kt
index 20f5467c..60377261 100644
--- a/adaptive-keep-alive/src/main/java/com/gojek/keepalive/AdaptiveKeepAliveStateHandler.kt
+++ b/adaptive-keep-alive/src/main/java/com/gojek/keepalive/AdaptiveKeepAliveStateHandler.kt
@@ -98,7 +98,7 @@ internal class AdaptiveKeepAliveStateHandler(
)
} else {
val currentUpperBound = keepAlive.keepAliveMinutes - 1
- if (state.lastSuccessfulKA == currentUpperBound) {
+ if (state.lastSuccessfulKA >= currentUpperBound) {
state = state.copy(
currentUpperBound = currentUpperBound,
isOptimalKeepAlive = true,
@@ -191,8 +191,7 @@ internal class AdaptiveKeepAliveStateHandler(
keepAlive.keepAliveMinutes == state.currentKA
}
- @VisibleForTesting
- internal fun resetState() {
+ fun resetState() {
state = state.copy(
lastSuccessfulKA = state.lowerBound - state.step,
isOptimalKeepAlive = false,
@@ -201,7 +200,8 @@ internal class AdaptiveKeepAliveStateHandler(
currentKA = -1,
currentKAFailureCount = 0,
probeCount = 0,
- convergenceTime = 0
+ convergenceTime = 0,
+ optimalKAFailureCount = 0
)
}
diff --git a/adaptive-keep-alive/src/main/java/com/gojek/keepalive/OptimalKeepAliveCalculator.kt b/adaptive-keep-alive/src/main/java/com/gojek/keepalive/OptimalKeepAliveCalculator.kt
index f61b6022..8f879658 100644
--- a/adaptive-keep-alive/src/main/java/com/gojek/keepalive/OptimalKeepAliveCalculator.kt
+++ b/adaptive-keep-alive/src/main/java/com/gojek/keepalive/OptimalKeepAliveCalculator.kt
@@ -20,11 +20,9 @@ internal class OptimalKeepAliveCalculator(
object : NetworkStateListener {
override fun onStateChanged(activeNetworkState: NetworkState) {
synchronized(this) {
- if (activeNetworkState.isConnected) {
- val networkType = networkUtils.getNetworkType(activeNetworkState.netInfo)
- val networkName = networkUtils.getNetworkName(activeNetworkState.netInfo)
- onNetworkStateChanged(networkType, networkName)
- }
+ val networkType = networkUtils.getNetworkType(activeNetworkState.netInfo)
+ val networkName = networkUtils.getNetworkName(activeNetworkState.netInfo)
+ onNetworkStateChanged(networkType, networkName)
}
}
}
@@ -108,6 +106,7 @@ internal class OptimalKeepAliveCalculator(
stateHandler.updateOptimalKeepAliveFailureState()
if (stateHandler.isOptimalKeepAliveFailureLimitExceeded()) {
stateHandler.removeStateFromPersistence()
+ stateHandler.resetState()
}
}
}
diff --git a/adaptive-keep-alive/src/test/java/com/gojek/keepalive/OptimalKeepAliveCalculatorTest.kt b/adaptive-keep-alive/src/test/java/com/gojek/keepalive/OptimalKeepAliveCalculatorTest.kt
index f5ca76d0..9b729073 100644
--- a/adaptive-keep-alive/src/test/java/com/gojek/keepalive/OptimalKeepAliveCalculatorTest.kt
+++ b/adaptive-keep-alive/src/test/java/com/gojek/keepalive/OptimalKeepAliveCalculatorTest.kt
@@ -39,10 +39,9 @@ class OptimalKeepAliveCalculatorTest {
}
@Test
- fun `test onStateChanged should notify state handler when network is connected`() {
+ fun `test onStateChanged should notify state handler`() {
val networkState = mock()
val netInfo = mock()
- whenever(networkState.isConnected).thenReturn(true)
whenever(networkState.netInfo).thenReturn(netInfo)
val networkType = 1
val networkName = "test-network"
@@ -52,23 +51,12 @@ class OptimalKeepAliveCalculatorTest {
optimalKeepAliveCalculator.networkStateListener.onStateChanged(networkState)
verify(stateHandler).onNetworkChanged(networkType, networkName)
- verify(networkState).isConnected
verify(networkState, times(2)).netInfo
verify(networkUtils).getNetworkType(netInfo)
verify(networkUtils).getNetworkName(netInfo)
verifyNoMoreInteractions(networkState)
}
- @Test
- fun `test onStateChanged should not notify state handler when network is not connected`() {
- val networkState = mock()
- whenever(networkState.isConnected).thenReturn(false)
-
- optimalKeepAliveCalculator.networkStateListener.onStateChanged(networkState)
-
- verify(networkState).isConnected
- }
-
@Test
fun `test getUnderTrialKeepAlive when optimal keep alive is already found`() {
val optimalKeepAlive = mock()
@@ -271,6 +259,7 @@ class OptimalKeepAliveCalculatorTest {
verify(stateHandler).updateOptimalKeepAliveFailureState()
verify(stateHandler).isOptimalKeepAliveFailureLimitExceeded()
verify(stateHandler).removeStateFromPersistence()
+ verify(stateHandler).resetState()
}
@Test
diff --git a/app/src/main/java/com/gojek/courier/app/ui/MainActivity.kt b/app/src/main/java/com/gojek/courier/app/ui/MainActivity.kt
index a5c89f6e..83a821c5 100644
--- a/app/src/main/java/com/gojek/courier/app/ui/MainActivity.kt
+++ b/app/src/main/java/com/gojek/courier/app/ui/MainActivity.kt
@@ -92,23 +92,20 @@ class MainActivity : AppCompatActivity() {
}
private fun connectMqtt(clientId: String, username: String, password: String, ip: String, port: Int) {
- val connectOptions = MqttConnectOptions(
- serverUris = listOf(ServerUri(ip, port, if (port == 443) "ssl" else "tcp")),
- clientId = clientId,
- username = username,
- keepAlive = KeepAlive(
- timeSeconds = 30
- ),
- isCleanSession = false,
- password = password
- )
+ val connectOptions = MqttConnectOptions.Builder()
+ .serverUris(listOf(ServerUri(ip, port, if (port == 443) "ssl" else "tcp")))
+ .clientId(clientId)
+ .userName(username)
+ .password(password)
+ .cleanSession(false)
+ .keepAlive(KeepAlive(timeSeconds = 30))
+ .build()
mqttClient.connect(connectOptions)
}
private fun initialiseCourier() {
val mqttConfig = MqttV3Configuration(
- socketFactory = null,
logger = getLogger(),
eventHandler = eventHandler,
authenticator = object : Authenticator {
@@ -116,7 +113,9 @@ class MainActivity : AppCompatActivity() {
connectOptions: MqttConnectOptions,
forceRefresh: Boolean
): MqttConnectOptions {
- return connectOptions.copy(password = password.text.toString())
+ return connectOptions.newBuilder()
+ .password(password.text.toString())
+ .build()
}
},
mqttInterceptorList = listOf(MqttChuckInterceptor(this, MqttChuckConfig(retentionPeriod = Period.ONE_HOUR))),
diff --git a/build.gradle.kts b/build.gradle.kts
index fc9b35ca..72a29c32 100644
--- a/build.gradle.kts
+++ b/build.gradle.kts
@@ -56,6 +56,7 @@ val clean by tasks.creating(Delete::class) {
delete("${rootDir}/courier/build")
delete("${rootDir}/courier-core/build")
delete("${rootDir}/courier-core-android/build")
+ delete("${rootDir}/courier-message-adapter-text/build")
delete("${rootDir}/courier-message-adapter-gson/build")
delete("${rootDir}/courier-message-adapter-moshi/build")
delete("${rootDir}/courier-message-adapter-protobuf/build")
diff --git a/buildSrc/src/main/kotlin/deps.kt b/buildSrc/src/main/kotlin/deps.kt
index 388f820e..fa26f2b9 100644
--- a/buildSrc/src/main/kotlin/deps.kt
+++ b/buildSrc/src/main/kotlin/deps.kt
@@ -59,6 +59,7 @@ object deps {
const val runner = "androidx.test:runner:1.2.0"
const val roboelectric = "org.robolectric:robolectric:4.2"
const val mockito = "com.nhaarman.mockitokotlin2:mockito-kotlin:2.2.0"
+ const val mockitoCore = "org.mockito:mockito-core:4.4.0"
const val junitExt = "androidx.test.ext:junit:1.1.1"
const val kotlinTestJunit = "org.jetbrains.kotlin:kotlin-test-junit:${versions.kotlin}"
}
diff --git a/courier-auth-http/build.gradle.kts b/courier-auth-http/build.gradle.kts
index b9dcefab..11e058eb 100644
--- a/courier-auth-http/build.gradle.kts
+++ b/courier-auth-http/build.gradle.kts
@@ -30,6 +30,7 @@ dependencies {
implementation(deps.square.retrofit)
+ testImplementation(deps.android.test.mockitoCore)
testImplementation(deps.android.test.kotlinTestJunit)
}
diff --git a/courier-core/api/courier-core.api b/courier-core/api/courier-core.api
index 055044f4..e4ca55c0 100644
--- a/courier-core/api/courier-core.api
+++ b/courier-core/api/courier-core.api
@@ -8,8 +8,9 @@ public final class com/gojek/courier/Message$Bytes : com/gojek/courier/Message {
}
public abstract interface class com/gojek/courier/MessageAdapter {
- public abstract fun fromMessage (Lcom/gojek/courier/Message;)Ljava/lang/Object;
- public abstract fun toMessage (Ljava/lang/Object;)Lcom/gojek/courier/Message;
+ public abstract fun contentType ()Ljava/lang/String;
+ public abstract fun fromMessage (Ljava/lang/String;Lcom/gojek/courier/Message;)Ljava/lang/Object;
+ public abstract fun toMessage (Ljava/lang/String;Ljava/lang/Object;)Lcom/gojek/courier/Message;
}
public abstract interface class com/gojek/courier/MessageAdapter$Factory {
diff --git a/courier-core/src/main/java/com/gojek/courier/MessageAdapter.kt b/courier-core/src/main/java/com/gojek/courier/MessageAdapter.kt
index 665d4d8a..3e4085a7 100644
--- a/courier-core/src/main/java/com/gojek/courier/MessageAdapter.kt
+++ b/courier-core/src/main/java/com/gojek/courier/MessageAdapter.kt
@@ -5,10 +5,13 @@ import java.lang.reflect.Type
interface MessageAdapter {
/** Returns an object of type `T` that represents a [Message]. */
- fun fromMessage(message: Message): T
+ fun fromMessage(topic: String, message: Message): T
/** Returns a [Message] that represents [data]. */
- fun toMessage(data: T): Message
+ fun toMessage(topic: String, data: T): Message
+
+ /** Returns the content type supported by this adapter. */
+ fun contentType(): String
/** Creates [MessageAdapter] instances based on a type and target usage. */
interface Factory {
diff --git a/courier-message-adapter-gson/src/main/java/com/gojek/courier/messageadapter/gson/GsonMessageAdapter.kt b/courier-message-adapter-gson/src/main/java/com/gojek/courier/messageadapter/gson/GsonMessageAdapter.kt
index 0239c442..03e27a74 100644
--- a/courier-message-adapter-gson/src/main/java/com/gojek/courier/messageadapter/gson/GsonMessageAdapter.kt
+++ b/courier-message-adapter-gson/src/main/java/com/gojek/courier/messageadapter/gson/GsonMessageAdapter.kt
@@ -19,7 +19,7 @@ private class GsonMessageAdapter constructor(
private val typeAdapter: TypeAdapter
) : MessageAdapter {
- override fun fromMessage(message: Message): T {
+ override fun fromMessage(topic: String, message: Message): T {
val stringValue = when (message) {
is Message.Bytes -> String(message.value)
}
@@ -27,7 +27,7 @@ private class GsonMessageAdapter constructor(
return typeAdapter.read(jsonReader)!!
}
- override fun toMessage(data: T): Message {
+ override fun toMessage(topic: String, data: T): Message {
val buffer = Buffer()
val writer = OutputStreamWriter(buffer.outputStream(), UTF_8)
val jsonWriter = gson.newJsonWriter(writer)
@@ -36,6 +36,8 @@ private class GsonMessageAdapter constructor(
val stringValue = buffer.readByteString().utf8()
return Message.Bytes(stringValue.toByteArray())
}
+
+ override fun contentType() = "application/json"
}
class GsonMessageAdapterFactory(
diff --git a/courier-message-adapter-moshi/src/main/java/com/gojek/courier/messageadapter/moshi/MoshiMessageAdapter.kt b/courier-message-adapter-moshi/src/main/java/com/gojek/courier/messageadapter/moshi/MoshiMessageAdapter.kt
index c6cd0945..1ba6152a 100644
--- a/courier-message-adapter-moshi/src/main/java/com/gojek/courier/messageadapter/moshi/MoshiMessageAdapter.kt
+++ b/courier-message-adapter-moshi/src/main/java/com/gojek/courier/messageadapter/moshi/MoshiMessageAdapter.kt
@@ -16,7 +16,7 @@ private class MoshiMessageAdapter constructor(
private val jsonAdapter: JsonAdapter
) : MessageAdapter {
- override fun fromMessage(message: Message): T {
+ override fun fromMessage(topic: String, message: Message): T {
val stringValue = when (message) {
is Message.Bytes -> {
val byteString = ByteString.of(message.value, 0, message.value.size)
@@ -32,11 +32,13 @@ private class MoshiMessageAdapter constructor(
return jsonAdapter.fromJson(stringValue)!!
}
- override fun toMessage(data: T): Message {
+ override fun toMessage(topic: String, data: T): Message {
val stringValue = jsonAdapter.toJson(data)
return Message.Bytes(stringValue.toByteArray())
}
+ override fun contentType() = "application/json"
+
private companion object {
private val UTF8_BOM = ByteString.decodeHex("EFBBBF")
}
diff --git a/courier-message-adapter-protobuf/src/main/java/com/gojek/courier/messageadapter/protobuf/ProtobufMessageAdapter.kt b/courier-message-adapter-protobuf/src/main/java/com/gojek/courier/messageadapter/protobuf/ProtobufMessageAdapter.kt
index 3e583b19..3bf1044d 100644
--- a/courier-message-adapter-protobuf/src/main/java/com/gojek/courier/messageadapter/protobuf/ProtobufMessageAdapter.kt
+++ b/courier-message-adapter-protobuf/src/main/java/com/gojek/courier/messageadapter/protobuf/ProtobufMessageAdapter.kt
@@ -17,7 +17,7 @@ private class ProtobufMessageAdapter constructor(
private val registry: ExtensionRegistryLite?
) : MessageAdapter {
- override fun fromMessage(message: Message): T {
+ override fun fromMessage(topic: String, message: Message): T {
val bytesValue = when (message) {
is Message.Bytes -> message.value
}
@@ -31,7 +31,9 @@ private class ProtobufMessageAdapter constructor(
}
}
- override fun toMessage(data: T): Message = Message.Bytes(data.toByteArray())
+ override fun toMessage(topic: String, data: T): Message = Message.Bytes(data.toByteArray())
+
+ override fun contentType() = "application/x-protobuf"
}
class ProtobufMessageAdapterFactory(
diff --git a/courier-message-adapter-text/.gitignore b/courier-message-adapter-text/.gitignore
new file mode 100644
index 00000000..796b96d1
--- /dev/null
+++ b/courier-message-adapter-text/.gitignore
@@ -0,0 +1 @@
+/build
diff --git a/courier-message-adapter-text/api/courier-message-adapter-text.api b/courier-message-adapter-text/api/courier-message-adapter-text.api
new file mode 100644
index 00000000..c902388d
--- /dev/null
+++ b/courier-message-adapter-text/api/courier-message-adapter-text.api
@@ -0,0 +1,5 @@
+public final class com/gojek/courier/messageadapter/text/TextMessageAdapterFactory : com/gojek/courier/MessageAdapter$Factory {
+ public fun ()V
+ public fun create (Ljava/lang/reflect/Type;[Ljava/lang/annotation/Annotation;)Lcom/gojek/courier/MessageAdapter;
+}
+
diff --git a/courier-message-adapter-text/build.gradle.kts b/courier-message-adapter-text/build.gradle.kts
new file mode 100644
index 00000000..16bc8532
--- /dev/null
+++ b/courier-message-adapter-text/build.gradle.kts
@@ -0,0 +1,28 @@
+import plugin.KotlinLibraryConfigurationPlugin
+
+apply()
+apply("$rootDir/gradle/script-ext.gradle")
+
+val version = ext.get("gitVersionName")
+
+ext {
+ set("PUBLISH_GROUP_ID", "com.gojek.courier")
+ set("PUBLISH_ARTIFACT_ID", "courier-message-adapter-text")
+ set("PUBLISH_VERSION", ext.get("gitVersionName"))
+ set("minimumCoverage", "0.0")
+}
+
+plugins {
+ id("java-library")
+ kotlin("jvm")
+ id(ScriptPlugins.apiValidator) version versions.apiValidator
+}
+
+dependencies {
+ api(project(":courier-core"))
+ implementation(deps.kotlin.stdlib.core)
+ implementation(deps.square.okio)
+ testImplementation(deps.android.test.kotlinTestJunit)
+}
+
+apply(from = "${rootProject.projectDir}/gradle/publish-module.gradle")
diff --git a/courier-message-adapter-text/src/main/java/com/gojek/courier/messageadapter/text/TextMessageAdapter.kt b/courier-message-adapter-text/src/main/java/com/gojek/courier/messageadapter/text/TextMessageAdapter.kt
new file mode 100644
index 00000000..eb20768a
--- /dev/null
+++ b/courier-message-adapter-text/src/main/java/com/gojek/courier/messageadapter/text/TextMessageAdapter.kt
@@ -0,0 +1,23 @@
+package com.gojek.courier.messageadapter.text
+
+import com.gojek.courier.Message
+import com.gojek.courier.MessageAdapter
+import com.gojek.courier.utils.getRawType
+import java.lang.reflect.Type
+
+class TextMessageAdapterFactory : MessageAdapter.Factory {
+ override fun create(type: Type, annotations: Array): MessageAdapter<*> = when (type.getRawType()) {
+ String::class.java -> TextMessageAdapter()
+ else -> throw IllegalArgumentException("Type is not supported by this MessageAdapterFactory: $type")
+ }
+}
+
+internal class TextMessageAdapter : MessageAdapter {
+ override fun fromMessage(topic: String, message: Message): String = when (message) {
+ is Message.Bytes -> String(message.value)
+ }
+
+ override fun toMessage(topic: String, data: String): Message = Message.Bytes(data.toByteArray())
+
+ override fun contentType() = "text/plain"
+}
diff --git a/courier-message-adapter-text/src/test/java/com/gojek/courier/messageadapter/text/ExampleUnitTest.kt b/courier-message-adapter-text/src/test/java/com/gojek/courier/messageadapter/text/ExampleUnitTest.kt
new file mode 100644
index 00000000..0fd4d3b7
--- /dev/null
+++ b/courier-message-adapter-text/src/test/java/com/gojek/courier/messageadapter/text/ExampleUnitTest.kt
@@ -0,0 +1,16 @@
+package com.gojek.courier.messageadapter.gson
+
+import org.junit.Assert.assertEquals
+import org.junit.Test
+
+/**
+ * Example local unit test, which will execute on the development machine (host).
+ *
+ * See [testing documentation](http://d.android.com/tools/testing).
+ */
+class ExampleUnitTest {
+ @Test
+ fun addition_isCorrect() {
+ assertEquals(4, 2 + 2)
+ }
+}
diff --git a/courier/src/main/java/com/gojek/courier/Courier.kt b/courier/src/main/java/com/gojek/courier/Courier.kt
index bf11b215..5af24039 100644
--- a/courier/src/main/java/com/gojek/courier/Courier.kt
+++ b/courier/src/main/java/com/gojek/courier/Courier.kt
@@ -3,8 +3,6 @@ package com.gojek.courier
import com.gojek.courier.coordinator.Coordinator
import com.gojek.courier.logging.ILogger
import com.gojek.courier.logging.NoOpLogger
-import com.gojek.courier.messageadapter.builtin.BuiltInMessageAdapterFactory
-import com.gojek.courier.streamadapter.builtin.BuiltInStreamAdapterFactory
import com.gojek.courier.stub.ProxyFactory
import com.gojek.courier.stub.StubInterface
import com.gojek.courier.stub.StubMethod
@@ -52,10 +50,10 @@ class Courier(configuration: Configuration) {
)
private fun Configuration.createStreamAdapterResolver(): StreamAdapterResolver {
- return StreamAdapterResolver(listOf(BuiltInStreamAdapterFactory()) + streamAdapterFactories)
+ return StreamAdapterResolver(streamAdapterFactories)
}
private fun Configuration.createMessageAdapterResolver(): MessageAdapterResolver {
- return MessageAdapterResolver(listOf(BuiltInMessageAdapterFactory()) + messageAdapterFactories)
+ return MessageAdapterResolver(messageAdapterFactories)
}
}
diff --git a/courier/src/main/java/com/gojek/courier/coordinator/Coordinator.kt b/courier/src/main/java/com/gojek/courier/coordinator/Coordinator.kt
index c3a906d1..3d54e6d8 100644
--- a/courier/src/main/java/com/gojek/courier/coordinator/Coordinator.kt
+++ b/courier/src/main/java/com/gojek/courier/coordinator/Coordinator.kt
@@ -23,9 +23,9 @@ internal class Coordinator(
@Synchronized
override fun send(stubMethod: StubMethod.Send, args: Array): Any {
val data = stubMethod.argumentProcessor.getDataArgument(args)
- val message = stubMethod.messageAdapter.toMessage(data)
stubMethod.argumentProcessor.inject(args)
val topic = stubMethod.argumentProcessor.getTopic()
+ val message = stubMethod.messageAdapter.toMessage(topic, data)
return client.send(message, topic, stubMethod.qos)
}
@@ -50,9 +50,13 @@ internal class Coordinator(
)
val stream = flowable
- .map { it.message }
.observeOn(Schedulers.computation())
- .flatMap { message -> message.adapt(stubMethod.messageAdapter)?.let { Flowable.just(it) } ?: Flowable.empty() }
+ .flatMap { mqttMessage ->
+ mqttMessage.message.adapt(
+ mqttMessage.topic,
+ stubMethod.messageAdapter
+ )?.let { Flowable.just(it) } ?: Flowable.empty()
+ }
.toStream()
return stubMethod.streamAdapter.adapt(stream)
}
@@ -87,9 +91,13 @@ internal class Coordinator(
)
val stream = flowable
- .map { it.message }
.observeOn(Schedulers.computation())
- .flatMap { message -> message.adapt(stubMethod.messageAdapter)?.let { Flowable.just(it) } ?: Flowable.empty() }
+ .flatMap { mqttMessage ->
+ mqttMessage.message.adapt(
+ mqttMessage.topic,
+ stubMethod.messageAdapter
+ )?.let { Flowable.just(it) } ?: Flowable.empty()
+ }
.toStream()
return stubMethod.streamAdapter.adapt(stream)
}
@@ -113,9 +121,9 @@ internal class Coordinator(
}
}
- private fun Message.adapt(messageAdapter: MessageAdapter): T? {
+ private fun Message.adapt(topic: String, messageAdapter: MessageAdapter): T? {
return try {
- val message = messageAdapter.fromMessage(this)
+ val message = messageAdapter.fromMessage(topic, this)
logger.d("Coordinator", "Message after parsing: $message")
message
} catch (th: Throwable) {
diff --git a/courier/src/main/java/com/gojek/courier/messageadapter/builtin/BuiltInMessageAdapterFactory.kt b/courier/src/main/java/com/gojek/courier/messageadapter/builtin/BuiltInMessageAdapterFactory.kt
deleted file mode 100644
index 674abf30..00000000
--- a/courier/src/main/java/com/gojek/courier/messageadapter/builtin/BuiltInMessageAdapterFactory.kt
+++ /dev/null
@@ -1,14 +0,0 @@
-package com.gojek.courier.messageadapter.builtin
-
-import com.gojek.courier.MessageAdapter
-import com.gojek.courier.utils.getRawType
-import java.lang.reflect.Type
-
-internal class BuiltInMessageAdapterFactory : MessageAdapter.Factory {
-
- override fun create(type: Type, annotations: Array): MessageAdapter<*> = when (type.getRawType()) {
- String::class.java -> TextMessageAdapter()
- ByteArray::class.java -> ByteArrayMessageAdapter()
- else -> throw IllegalArgumentException("Type is not supported by this MessageAdapterFactory: $type")
- }
-}
diff --git a/courier/src/main/java/com/gojek/courier/messageadapter/builtin/ByteArrayMessageAdapter.kt b/courier/src/main/java/com/gojek/courier/messageadapter/builtin/ByteArrayMessageAdapter.kt
deleted file mode 100644
index c41ef7af..00000000
--- a/courier/src/main/java/com/gojek/courier/messageadapter/builtin/ByteArrayMessageAdapter.kt
+++ /dev/null
@@ -1,13 +0,0 @@
-package com.gojek.courier.messageadapter.builtin
-
-import com.gojek.courier.Message
-import com.gojek.courier.MessageAdapter
-
-internal class ByteArrayMessageAdapter : MessageAdapter {
-
- override fun fromMessage(message: Message): ByteArray = when (message) {
- is Message.Bytes -> message.value
- }
-
- override fun toMessage(data: ByteArray): Message = Message.Bytes(data)
-}
diff --git a/courier/src/main/java/com/gojek/courier/messageadapter/builtin/TextMessageAdapter.kt b/courier/src/main/java/com/gojek/courier/messageadapter/builtin/TextMessageAdapter.kt
deleted file mode 100644
index 9cb44d47..00000000
--- a/courier/src/main/java/com/gojek/courier/messageadapter/builtin/TextMessageAdapter.kt
+++ /dev/null
@@ -1,13 +0,0 @@
-package com.gojek.courier.messageadapter.builtin
-
-import com.gojek.courier.Message
-import com.gojek.courier.MessageAdapter
-
-internal class TextMessageAdapter : MessageAdapter {
-
- override fun fromMessage(message: Message): String = when (message) {
- is Message.Bytes -> String(message.value)
- }
-
- override fun toMessage(data: String): Message = Message.Bytes(data.toByteArray())
-}
diff --git a/courier/src/main/java/com/gojek/courier/streamadapter/builtin/BuiltInStreamAdapterFactory.kt b/courier/src/main/java/com/gojek/courier/streamadapter/builtin/BuiltInStreamAdapterFactory.kt
deleted file mode 100644
index 503bf8d9..00000000
--- a/courier/src/main/java/com/gojek/courier/streamadapter/builtin/BuiltInStreamAdapterFactory.kt
+++ /dev/null
@@ -1,14 +0,0 @@
-package com.gojek.courier.streamadapter.builtin
-
-import com.gojek.courier.Stream
-import com.gojek.courier.StreamAdapter
-import com.gojek.courier.utils.getRawType
-import java.lang.reflect.Type
-
-internal class BuiltInStreamAdapterFactory : StreamAdapter.Factory {
-
- override fun create(type: Type): StreamAdapter = when (type.getRawType()) {
- Stream::class.java -> IdentityStreamAdapter()
- else -> throw IllegalArgumentException("$type is not supported.")
- }
-}
diff --git a/courier/src/main/java/com/gojek/courier/streamadapter/builtin/IdentityStreamAdapter.kt b/courier/src/main/java/com/gojek/courier/streamadapter/builtin/IdentityStreamAdapter.kt
deleted file mode 100644
index bb2551ee..00000000
--- a/courier/src/main/java/com/gojek/courier/streamadapter/builtin/IdentityStreamAdapter.kt
+++ /dev/null
@@ -1,9 +0,0 @@
-package com.gojek.courier.streamadapter.builtin
-
-import com.gojek.courier.Stream
-import com.gojek.courier.StreamAdapter
-
-internal class IdentityStreamAdapter : StreamAdapter> {
-
- override fun adapt(stream: Stream): Stream = stream
-}
diff --git a/dependencies.gradle b/dependencies.gradle
index 080678c4..11eecb84 100644
--- a/dependencies.gradle
+++ b/dependencies.gradle
@@ -6,6 +6,6 @@ ext {
kotlinCoroutines = 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.2'
kotlinCoroutinesRxInterop = 'org.jetbrains.kotlinx:kotlinx-coroutines-reactive:1.3.2'
- gson = 'com.google.code.gson:gson:2.8.5'
+ gson = 'com.google.code.gson:gson:2.8.9'
okio = 'com.squareup.okio:okio:1.13.0'
}
\ No newline at end of file
diff --git a/docs/docs/ConnectionSetup.md b/docs/docs/ConnectionSetup.md
index 04bedde3..4c64e8ef 100644
--- a/docs/docs/ConnectionSetup.md
+++ b/docs/docs/ConnectionSetup.md
@@ -14,16 +14,16 @@ val mqttClient = MqttClientFactory.create(
### Connect using MqttClient
~~~ kotlin
-val connectOptions = MqttConnectOptions(
- serverUris = listOf(ServerUri(SERVER_URI, SERVER_PORT)),
- clientId = clientId,
- username = username,
- keepAlive = KeepAlive(
- timeSeconds = keepAliveSeconds
- ),
- isCleanSession = cleanSessionFlag,
- password = password
-)
+val alpnProtocol = "mqtt"
+val connectOptions = MqttConnectOptions.Builder()
+ .serverUris(listof(ServerUri(SERVER_URI, SERVER_PORT)))
+ .clientId(clientId)
+ .userName(username)
+ .password(password)
+ .keepAlive(KeepAlive(timeSeconds = keepAliveSeconds))
+ .cleanSession(cleanSessionFlag)
+ .alpnProtocols(listOf(Protocol(alpnProtocol)))
+ .build()
mqttClient.connect(connectOptions)
~~~
@@ -56,6 +56,14 @@ mqttClient.disconnect()
- **User properties** : Custom user properties appended to the CONNECT packet.
+- **Socket Factory** : Sets the socket factory used to create connections. If unset, the **SocketFactory.getDefault** socket factory will be used. Set [shouldUseNewSSLFlow](ExperimentConfigs) to true to enable this.
+
+- **SSL Socket Factory**: Sets the socket factory and trust manager used to secure MQTT connections. If unset, the system defaults will be used. Set [shouldUseNewSSLFlow](ExperimentConfigs) to true to enable this.
+
+- **Connection Spec**: Specifies configuration for the socket connection that MQTT traffic travels through. This includes the TLS version and cipher suites to use when negotiating a secure connection. Set [shouldUseNewSSLFlow](ExperimentConfigs) to true to enable this.
+
+- **ALPN Protocols**: Configure the alpn protocols used by this client to communicate with MQTT broker. Set [shouldUseNewSSLFlow](ExperimentConfigs) to true to enable this.
+
[1]: https://github.com/gojek/courier-android/blob/main/mqtt-client/src/main/java/com/gojek/mqtt/client/MqttClient.kt
[2]: https://github.com/gojek/courier-android/blob/main/mqtt-client/src/main/java/com/gojek/mqtt/model/MqttConnectOptions.kt
[3]: https://github.com/gojek/courier-android/blob/main/mqtt-client/src/main/java/com/gojek/mqtt/model/ServerUri.kt
diff --git a/docs/docs/ExperimentConfigs.md b/docs/docs/ExperimentConfigs.md
index 9fd5c0c8..a4f4f833 100644
--- a/docs/docs/ExperimentConfigs.md
+++ b/docs/docs/ExperimentConfigs.md
@@ -14,4 +14,6 @@ These are the experimentation configs used in Courier library. These are volatil
- **incomingMessagesTTLSecs** : When there is no listener attached for an incoming message, messages are persisted for this interval.
-- **incomingMessagesCleanupIntervalSecs** : Interval at which cleanup for incoming messages persistence is performed.
\ No newline at end of file
+- **incomingMessagesCleanupIntervalSecs** : Interval at which cleanup for incoming messages persistence is performed.
+
+- **shouldUseNewSSLFlow** : Set this true for enabling alpn protocol, custom ssl socket factory.
\ No newline at end of file
diff --git a/docs/docs/MessageStreamAdapters.md b/docs/docs/MessageStreamAdapters.md
index 3857e07a..0e9015c0 100644
--- a/docs/docs/MessageStreamAdapters.md
+++ b/docs/docs/MessageStreamAdapters.md
@@ -26,13 +26,17 @@ class MyCustomMessageAdapterFactory : MessageAdapter.Factory {
private class MyCustomMessageAdapter constructor() : MessageAdapter {
- override fun fromMessage(message: Message): T {
+ override fun fromMessage(topic: String, message: Message): T {
// convert message to custom type
}
- override fun toMessage(data: T): Message {
+ override fun toMessage(topic: String, data: T): Message {
// convert custom type to message
}
+
+ override fun contentType(): String {
+ // content-type supported by this adapter.
+ }
}
~~~
diff --git a/docs/docs/MqttConfiguration.md b/docs/docs/MqttConfiguration.md
index a93f3a56..55a438e4 100644
--- a/docs/docs/MqttConfiguration.md
+++ b/docs/docs/MqttConfiguration.md
@@ -22,8 +22,6 @@ As we have seen earlier, [MqttClient][1] requires an instance of [MqttV3Configur
- **Experimentation Configs** : These are the experiment configs used inside Courier library which are explained in detail [here](ExperimentConfigs).
-- **Socket Factory** : An instance of SocketFactory can be passed to control the socket creation for the underlying MQTT connection.
-
- **WakeLock Timeout** : When positive value of this timeout is passed, a wakelock is acquired while creating the MQTT connection. By default, it is 0.
[1]: https://github.com/gojek/courier-android/blob/main/mqtt-client/src/main/java/com/gojek/mqtt/client/MqttClient.kt
diff --git a/docs/docs/NonStandardOptions.md b/docs/docs/NonStandardOptions.md
index 241e7445..641f6197 100644
--- a/docs/docs/NonStandardOptions.md
+++ b/docs/docs/NonStandardOptions.md
@@ -5,15 +5,17 @@
This option allows you to send user-properties in CONNECT packet for MQTT v3.1.1.
~~~ kotlin
-val connectOptions = MqttConnectOptions(
- serverUris = listOf(ServerUri(SERVER_URI, SERVER_PORT)),
- clientId = clientId,
- ...
- userPropertiesMap = mapOf(
- "key1" to "value1",
- "key2" to "value2"
- )
-)
+val connectOptions = MqttConnectOptions.Builder()
+ .serverUris(listOf(ServerUri(SERVER_URI, SERVER_PORT)))
+ .clientId(clientId)
+ ...
+ .userProperties(
+ userProperties = mapOf(
+ "key1" to "value1",
+ "key2" to "value2"
+ )
+ )
+ .build()
mqttClient.connect(connectOptions)
~~~
diff --git a/mqtt-client/api/mqtt-client.api b/mqtt-client/api/mqtt-client.api
index f0b0e04f..fcbcf07a 100644
--- a/mqtt-client/api/mqtt-client.api
+++ b/mqtt-client/api/mqtt-client.api
@@ -26,8 +26,8 @@ public abstract interface class com/gojek/mqtt/client/MqttInterceptor {
public final class com/gojek/mqtt/client/config/ExperimentConfigs {
public fun ()V
- public fun (Lcom/gojek/mqtt/client/config/SubscriptionStore;Lcom/gojek/mqtt/model/AdaptiveKeepAliveConfig;IIIJJ)V
- public synthetic fun (Lcom/gojek/mqtt/client/config/SubscriptionStore;Lcom/gojek/mqtt/model/AdaptiveKeepAliveConfig;IIIJJILkotlin/jvm/internal/DefaultConstructorMarker;)V
+ public fun (Lcom/gojek/mqtt/client/config/SubscriptionStore;Lcom/gojek/mqtt/model/AdaptiveKeepAliveConfig;IIIJJZ)V
+ public synthetic fun (Lcom/gojek/mqtt/client/config/SubscriptionStore;Lcom/gojek/mqtt/model/AdaptiveKeepAliveConfig;IIIJJZILkotlin/jvm/internal/DefaultConstructorMarker;)V
public final fun component1 ()Lcom/gojek/mqtt/client/config/SubscriptionStore;
public final fun component2 ()Lcom/gojek/mqtt/model/AdaptiveKeepAliveConfig;
public final fun component3 ()I
@@ -35,8 +35,9 @@ public final class com/gojek/mqtt/client/config/ExperimentConfigs {
public final fun component5 ()I
public final fun component6 ()J
public final fun component7 ()J
- public final fun copy (Lcom/gojek/mqtt/client/config/SubscriptionStore;Lcom/gojek/mqtt/model/AdaptiveKeepAliveConfig;IIIJJ)Lcom/gojek/mqtt/client/config/ExperimentConfigs;
- public static synthetic fun copy$default (Lcom/gojek/mqtt/client/config/ExperimentConfigs;Lcom/gojek/mqtt/client/config/SubscriptionStore;Lcom/gojek/mqtt/model/AdaptiveKeepAliveConfig;IIIJJILjava/lang/Object;)Lcom/gojek/mqtt/client/config/ExperimentConfigs;
+ public final fun component8 ()Z
+ public final fun copy (Lcom/gojek/mqtt/client/config/SubscriptionStore;Lcom/gojek/mqtt/model/AdaptiveKeepAliveConfig;IIIJJZ)Lcom/gojek/mqtt/client/config/ExperimentConfigs;
+ public static synthetic fun copy$default (Lcom/gojek/mqtt/client/config/ExperimentConfigs;Lcom/gojek/mqtt/client/config/SubscriptionStore;Lcom/gojek/mqtt/model/AdaptiveKeepAliveConfig;IIIJJZILjava/lang/Object;)Lcom/gojek/mqtt/client/config/ExperimentConfigs;
public fun equals (Ljava/lang/Object;)Z
public final fun getActivityCheckIntervalSeconds ()I
public final fun getAdaptiveKeepAliveConfig ()Lcom/gojek/mqtt/model/AdaptiveKeepAliveConfig;
@@ -44,13 +45,14 @@ public final class com/gojek/mqtt/client/config/ExperimentConfigs {
public final fun getIncomingMessagesCleanupIntervalSecs ()J
public final fun getIncomingMessagesTTLSecs ()J
public final fun getPolicyResetTimeSeconds ()I
+ public final fun getShouldUseNewSSLFlow ()Z
public final fun getSubscriptionStore ()Lcom/gojek/mqtt/client/config/SubscriptionStore;
public fun hashCode ()I
public fun toString ()Ljava/lang/String;
}
public abstract class com/gojek/mqtt/client/config/MqttConfiguration {
- public fun (Lcom/gojek/mqtt/policies/connectretrytime/IConnectRetryTimePolicy;Lcom/gojek/mqtt/policies/connecttimeout/IConnectTimeoutPolicy;Lcom/gojek/mqtt/policies/subscriptionretry/ISubscriptionRetryPolicy;Lcom/gojek/mqtt/policies/subscriptionretry/ISubscriptionRetryPolicy;ILjavax/net/SocketFactory;Lcom/gojek/courier/logging/ILogger;Lcom/gojek/mqtt/auth/Authenticator;Lcom/gojek/mqtt/exception/handler/v3/AuthFailureHandler;Lcom/gojek/mqtt/event/EventHandler;Lcom/gojek/mqtt/pingsender/MqttPingSender;Ljava/util/List;Lcom/gojek/mqtt/client/config/PersistenceOptions;Lcom/gojek/mqtt/client/config/ExperimentConfigs;)V
+ public fun (Lcom/gojek/mqtt/policies/connectretrytime/IConnectRetryTimePolicy;Lcom/gojek/mqtt/policies/connecttimeout/IConnectTimeoutPolicy;Lcom/gojek/mqtt/policies/subscriptionretry/ISubscriptionRetryPolicy;Lcom/gojek/mqtt/policies/subscriptionretry/ISubscriptionRetryPolicy;ILcom/gojek/courier/logging/ILogger;Lcom/gojek/mqtt/auth/Authenticator;Lcom/gojek/mqtt/exception/handler/v3/AuthFailureHandler;Lcom/gojek/mqtt/event/EventHandler;Lcom/gojek/mqtt/pingsender/MqttPingSender;Ljava/util/List;Lcom/gojek/mqtt/client/config/PersistenceOptions;Lcom/gojek/mqtt/client/config/ExperimentConfigs;)V
public fun getAuthFailureHandler ()Lcom/gojek/mqtt/exception/handler/v3/AuthFailureHandler;
public fun getAuthenticator ()Lcom/gojek/mqtt/auth/Authenticator;
public fun getConnectRetryTimePolicy ()Lcom/gojek/mqtt/policies/connectretrytime/IConnectRetryTimePolicy;
@@ -61,7 +63,6 @@ public abstract class com/gojek/mqtt/client/config/MqttConfiguration {
public fun getMqttInterceptorList ()Ljava/util/List;
public fun getPersistenceOptions ()Lcom/gojek/mqtt/client/config/PersistenceOptions;
public fun getPingSender ()Lcom/gojek/mqtt/pingsender/MqttPingSender;
- public fun getSocketFactory ()Ljavax/net/SocketFactory;
public fun getSubscriptionRetryPolicy ()Lcom/gojek/mqtt/policies/subscriptionretry/ISubscriptionRetryPolicy;
public fun getUnsubscriptionRetryPolicy ()Lcom/gojek/mqtt/policies/subscriptionretry/ISubscriptionRetryPolicy;
public fun getWakeLockTimeout ()I
@@ -94,24 +95,23 @@ public final class com/gojek/mqtt/client/config/SubscriptionStore : java/lang/En
}
public final class com/gojek/mqtt/client/config/v3/MqttV3Configuration : com/gojek/mqtt/client/config/MqttConfiguration {
- public fun (Lcom/gojek/mqtt/policies/connectretrytime/IConnectRetryTimePolicy;Lcom/gojek/mqtt/policies/connecttimeout/IConnectTimeoutPolicy;Lcom/gojek/mqtt/policies/subscriptionretry/ISubscriptionRetryPolicy;Lcom/gojek/mqtt/policies/subscriptionretry/ISubscriptionRetryPolicy;ILjavax/net/SocketFactory;Lcom/gojek/courier/logging/ILogger;Lcom/gojek/mqtt/auth/Authenticator;Lcom/gojek/mqtt/exception/handler/v3/AuthFailureHandler;Lcom/gojek/mqtt/event/EventHandler;Lcom/gojek/mqtt/pingsender/MqttPingSender;Ljava/util/List;Lcom/gojek/mqtt/client/config/PersistenceOptions;Lcom/gojek/mqtt/client/config/ExperimentConfigs;)V
- public synthetic fun (Lcom/gojek/mqtt/policies/connectretrytime/IConnectRetryTimePolicy;Lcom/gojek/mqtt/policies/connecttimeout/IConnectTimeoutPolicy;Lcom/gojek/mqtt/policies/subscriptionretry/ISubscriptionRetryPolicy;Lcom/gojek/mqtt/policies/subscriptionretry/ISubscriptionRetryPolicy;ILjavax/net/SocketFactory;Lcom/gojek/courier/logging/ILogger;Lcom/gojek/mqtt/auth/Authenticator;Lcom/gojek/mqtt/exception/handler/v3/AuthFailureHandler;Lcom/gojek/mqtt/event/EventHandler;Lcom/gojek/mqtt/pingsender/MqttPingSender;Ljava/util/List;Lcom/gojek/mqtt/client/config/PersistenceOptions;Lcom/gojek/mqtt/client/config/ExperimentConfigs;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
+ public fun (Lcom/gojek/mqtt/policies/connectretrytime/IConnectRetryTimePolicy;Lcom/gojek/mqtt/policies/connecttimeout/IConnectTimeoutPolicy;Lcom/gojek/mqtt/policies/subscriptionretry/ISubscriptionRetryPolicy;Lcom/gojek/mqtt/policies/subscriptionretry/ISubscriptionRetryPolicy;ILcom/gojek/courier/logging/ILogger;Lcom/gojek/mqtt/auth/Authenticator;Lcom/gojek/mqtt/exception/handler/v3/AuthFailureHandler;Lcom/gojek/mqtt/event/EventHandler;Lcom/gojek/mqtt/pingsender/MqttPingSender;Ljava/util/List;Lcom/gojek/mqtt/client/config/PersistenceOptions;Lcom/gojek/mqtt/client/config/ExperimentConfigs;)V
+ public synthetic fun (Lcom/gojek/mqtt/policies/connectretrytime/IConnectRetryTimePolicy;Lcom/gojek/mqtt/policies/connecttimeout/IConnectTimeoutPolicy;Lcom/gojek/mqtt/policies/subscriptionretry/ISubscriptionRetryPolicy;Lcom/gojek/mqtt/policies/subscriptionretry/ISubscriptionRetryPolicy;ILcom/gojek/courier/logging/ILogger;Lcom/gojek/mqtt/auth/Authenticator;Lcom/gojek/mqtt/exception/handler/v3/AuthFailureHandler;Lcom/gojek/mqtt/event/EventHandler;Lcom/gojek/mqtt/pingsender/MqttPingSender;Ljava/util/List;Lcom/gojek/mqtt/client/config/PersistenceOptions;Lcom/gojek/mqtt/client/config/ExperimentConfigs;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
public final fun component1 ()Lcom/gojek/mqtt/policies/connectretrytime/IConnectRetryTimePolicy;
- public final fun component10 ()Lcom/gojek/mqtt/event/EventHandler;
- public final fun component11 ()Lcom/gojek/mqtt/pingsender/MqttPingSender;
- public final fun component12 ()Ljava/util/List;
- public final fun component13 ()Lcom/gojek/mqtt/client/config/PersistenceOptions;
- public final fun component14 ()Lcom/gojek/mqtt/client/config/ExperimentConfigs;
+ public final fun component10 ()Lcom/gojek/mqtt/pingsender/MqttPingSender;
+ public final fun component11 ()Ljava/util/List;
+ public final fun component12 ()Lcom/gojek/mqtt/client/config/PersistenceOptions;
+ public final fun component13 ()Lcom/gojek/mqtt/client/config/ExperimentConfigs;
public final fun component2 ()Lcom/gojek/mqtt/policies/connecttimeout/IConnectTimeoutPolicy;
public final fun component3 ()Lcom/gojek/mqtt/policies/subscriptionretry/ISubscriptionRetryPolicy;
public final fun component4 ()Lcom/gojek/mqtt/policies/subscriptionretry/ISubscriptionRetryPolicy;
public final fun component5 ()I
- public final fun component6 ()Ljavax/net/SocketFactory;
- public final fun component7 ()Lcom/gojek/courier/logging/ILogger;
- public final fun component8 ()Lcom/gojek/mqtt/auth/Authenticator;
- public final fun component9 ()Lcom/gojek/mqtt/exception/handler/v3/AuthFailureHandler;
- public final fun copy (Lcom/gojek/mqtt/policies/connectretrytime/IConnectRetryTimePolicy;Lcom/gojek/mqtt/policies/connecttimeout/IConnectTimeoutPolicy;Lcom/gojek/mqtt/policies/subscriptionretry/ISubscriptionRetryPolicy;Lcom/gojek/mqtt/policies/subscriptionretry/ISubscriptionRetryPolicy;ILjavax/net/SocketFactory;Lcom/gojek/courier/logging/ILogger;Lcom/gojek/mqtt/auth/Authenticator;Lcom/gojek/mqtt/exception/handler/v3/AuthFailureHandler;Lcom/gojek/mqtt/event/EventHandler;Lcom/gojek/mqtt/pingsender/MqttPingSender;Ljava/util/List;Lcom/gojek/mqtt/client/config/PersistenceOptions;Lcom/gojek/mqtt/client/config/ExperimentConfigs;)Lcom/gojek/mqtt/client/config/v3/MqttV3Configuration;
- public static synthetic fun copy$default (Lcom/gojek/mqtt/client/config/v3/MqttV3Configuration;Lcom/gojek/mqtt/policies/connectretrytime/IConnectRetryTimePolicy;Lcom/gojek/mqtt/policies/connecttimeout/IConnectTimeoutPolicy;Lcom/gojek/mqtt/policies/subscriptionretry/ISubscriptionRetryPolicy;Lcom/gojek/mqtt/policies/subscriptionretry/ISubscriptionRetryPolicy;ILjavax/net/SocketFactory;Lcom/gojek/courier/logging/ILogger;Lcom/gojek/mqtt/auth/Authenticator;Lcom/gojek/mqtt/exception/handler/v3/AuthFailureHandler;Lcom/gojek/mqtt/event/EventHandler;Lcom/gojek/mqtt/pingsender/MqttPingSender;Ljava/util/List;Lcom/gojek/mqtt/client/config/PersistenceOptions;Lcom/gojek/mqtt/client/config/ExperimentConfigs;ILjava/lang/Object;)Lcom/gojek/mqtt/client/config/v3/MqttV3Configuration;
+ public final fun component6 ()Lcom/gojek/courier/logging/ILogger;
+ public final fun component7 ()Lcom/gojek/mqtt/auth/Authenticator;
+ public final fun component8 ()Lcom/gojek/mqtt/exception/handler/v3/AuthFailureHandler;
+ public final fun component9 ()Lcom/gojek/mqtt/event/EventHandler;
+ public final fun copy (Lcom/gojek/mqtt/policies/connectretrytime/IConnectRetryTimePolicy;Lcom/gojek/mqtt/policies/connecttimeout/IConnectTimeoutPolicy;Lcom/gojek/mqtt/policies/subscriptionretry/ISubscriptionRetryPolicy;Lcom/gojek/mqtt/policies/subscriptionretry/ISubscriptionRetryPolicy;ILcom/gojek/courier/logging/ILogger;Lcom/gojek/mqtt/auth/Authenticator;Lcom/gojek/mqtt/exception/handler/v3/AuthFailureHandler;Lcom/gojek/mqtt/event/EventHandler;Lcom/gojek/mqtt/pingsender/MqttPingSender;Ljava/util/List;Lcom/gojek/mqtt/client/config/PersistenceOptions;Lcom/gojek/mqtt/client/config/ExperimentConfigs;)Lcom/gojek/mqtt/client/config/v3/MqttV3Configuration;
+ public static synthetic fun copy$default (Lcom/gojek/mqtt/client/config/v3/MqttV3Configuration;Lcom/gojek/mqtt/policies/connectretrytime/IConnectRetryTimePolicy;Lcom/gojek/mqtt/policies/connecttimeout/IConnectTimeoutPolicy;Lcom/gojek/mqtt/policies/subscriptionretry/ISubscriptionRetryPolicy;Lcom/gojek/mqtt/policies/subscriptionretry/ISubscriptionRetryPolicy;ILcom/gojek/courier/logging/ILogger;Lcom/gojek/mqtt/auth/Authenticator;Lcom/gojek/mqtt/exception/handler/v3/AuthFailureHandler;Lcom/gojek/mqtt/event/EventHandler;Lcom/gojek/mqtt/pingsender/MqttPingSender;Ljava/util/List;Lcom/gojek/mqtt/client/config/PersistenceOptions;Lcom/gojek/mqtt/client/config/ExperimentConfigs;ILjava/lang/Object;)Lcom/gojek/mqtt/client/config/v3/MqttV3Configuration;
public fun equals (Ljava/lang/Object;)Z
public fun getAuthFailureHandler ()Lcom/gojek/mqtt/exception/handler/v3/AuthFailureHandler;
public fun getAuthenticator ()Lcom/gojek/mqtt/auth/Authenticator;
@@ -123,7 +123,6 @@ public final class com/gojek/mqtt/client/config/v3/MqttV3Configuration : com/goj
public fun getMqttInterceptorList ()Ljava/util/List;
public fun getPersistenceOptions ()Lcom/gojek/mqtt/client/config/PersistenceOptions;
public fun getPingSender ()Lcom/gojek/mqtt/pingsender/MqttPingSender;
- public fun getSocketFactory ()Ljavax/net/SocketFactory;
public fun getSubscriptionRetryPolicy ()Lcom/gojek/mqtt/policies/subscriptionretry/ISubscriptionRetryPolicy;
public fun getUnsubscriptionRetryPolicy ()Lcom/gojek/mqtt/policies/subscriptionretry/ISubscriptionRetryPolicy;
public fun getWakeLockTimeout ()I
@@ -1012,6 +1011,7 @@ public final class com/gojek/mqtt/model/AdaptiveKeepAliveConfig {
}
public final class com/gojek/mqtt/model/KeepAlive {
+ public static final field Companion Lcom/gojek/mqtt/model/KeepAlive$Companion;
public fun (IZ)V
public synthetic fun (IZILkotlin/jvm/internal/DefaultConstructorMarker;)V
public final fun component1 ()I
@@ -1023,32 +1023,49 @@ public final class com/gojek/mqtt/model/KeepAlive {
public fun toString ()Ljava/lang/String;
}
+public final class com/gojek/mqtt/model/KeepAlive$Companion {
+ public final fun getNO_KEEP_ALIVE ()Lcom/gojek/mqtt/model/KeepAlive;
+}
+
public final class com/gojek/mqtt/model/MqttConnectOptions {
- public fun (Ljava/util/List;Lcom/gojek/mqtt/model/KeepAlive;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;ZILcom/gojek/mqtt/model/MqttVersion;Ljava/util/Map;)V
- public synthetic fun (Ljava/util/List;Lcom/gojek/mqtt/model/KeepAlive;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;ZILcom/gojek/mqtt/model/MqttVersion;Ljava/util/Map;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
- public final fun component1 ()Ljava/util/List;
- public final fun component2 ()Lcom/gojek/mqtt/model/KeepAlive;
- public final fun component3 ()Ljava/lang/String;
- public final fun component4 ()Ljava/lang/String;
- public final fun component5 ()Ljava/lang/String;
- public final fun component6 ()Z
- public final fun component7 ()I
- public final fun component8 ()Lcom/gojek/mqtt/model/MqttVersion;
- public final fun component9 ()Ljava/util/Map;
- public final fun copy (Ljava/util/List;Lcom/gojek/mqtt/model/KeepAlive;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;ZILcom/gojek/mqtt/model/MqttVersion;Ljava/util/Map;)Lcom/gojek/mqtt/model/MqttConnectOptions;
- public static synthetic fun copy$default (Lcom/gojek/mqtt/model/MqttConnectOptions;Ljava/util/List;Lcom/gojek/mqtt/model/KeepAlive;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;ZILcom/gojek/mqtt/model/MqttVersion;Ljava/util/Map;ILjava/lang/Object;)Lcom/gojek/mqtt/model/MqttConnectOptions;
- public fun equals (Ljava/lang/Object;)Z
+ public static final field Companion Lcom/gojek/mqtt/model/MqttConnectOptions$Companion;
+ public synthetic fun (Lcom/gojek/mqtt/model/MqttConnectOptions$Builder;Lkotlin/jvm/internal/DefaultConstructorMarker;)V
public final fun getClientId ()Ljava/lang/String;
+ public final fun getConnectionSpec ()Lorg/eclipse/paho/client/mqttv3/ConnectionSpec;
public final fun getKeepAlive ()Lcom/gojek/mqtt/model/KeepAlive;
public final fun getPassword ()Ljava/lang/String;
+ public final fun getProtocols ()Ljava/util/List;
public final fun getReadTimeoutSecs ()I
public final fun getServerUris ()Ljava/util/List;
+ public final fun getSocketFactory ()Ljavax/net/SocketFactory;
+ public final fun getSslSocketFactory ()Ljavax/net/ssl/SSLSocketFactory;
public final fun getUserPropertiesMap ()Ljava/util/Map;
public final fun getUsername ()Ljava/lang/String;
public final fun getVersion ()Lcom/gojek/mqtt/model/MqttVersion;
- public fun hashCode ()I
+ public final fun getX509TrustManager ()Ljavax/net/ssl/X509TrustManager;
public final fun isCleanSession ()Z
- public fun toString ()Ljava/lang/String;
+ public final fun newBuilder ()Lcom/gojek/mqtt/model/MqttConnectOptions$Builder;
+}
+
+public final class com/gojek/mqtt/model/MqttConnectOptions$Builder {
+ public fun ()V
+ public final fun alpnProtocols (Ljava/util/List;)Lcom/gojek/mqtt/model/MqttConnectOptions$Builder;
+ public final fun build ()Lcom/gojek/mqtt/model/MqttConnectOptions;
+ public final fun cleanSession (Z)Lcom/gojek/mqtt/model/MqttConnectOptions$Builder;
+ public final fun clientId (Ljava/lang/String;)Lcom/gojek/mqtt/model/MqttConnectOptions$Builder;
+ public final fun connectionSpec (Lorg/eclipse/paho/client/mqttv3/ConnectionSpec;)Lcom/gojek/mqtt/model/MqttConnectOptions$Builder;
+ public final fun keepAlive (Lcom/gojek/mqtt/model/KeepAlive;)Lcom/gojek/mqtt/model/MqttConnectOptions$Builder;
+ public final fun mqttVersion (Lcom/gojek/mqtt/model/MqttVersion;)Lcom/gojek/mqtt/model/MqttConnectOptions$Builder;
+ public final fun password (Ljava/lang/String;)Lcom/gojek/mqtt/model/MqttConnectOptions$Builder;
+ public final fun readTimeoutSecs (I)Lcom/gojek/mqtt/model/MqttConnectOptions$Builder;
+ public final fun serverUris (Ljava/util/List;)Lcom/gojek/mqtt/model/MqttConnectOptions$Builder;
+ public final fun socketFactory (Ljavax/net/SocketFactory;)Lcom/gojek/mqtt/model/MqttConnectOptions$Builder;
+ public final fun sslSocketFactory (Ljavax/net/ssl/SSLSocketFactory;Ljavax/net/ssl/X509TrustManager;)Lcom/gojek/mqtt/model/MqttConnectOptions$Builder;
+ public final fun userName (Ljava/lang/String;)Lcom/gojek/mqtt/model/MqttConnectOptions$Builder;
+ public final fun userProperties (Ljava/util/Map;)Lcom/gojek/mqtt/model/MqttConnectOptions$Builder;
+}
+
+public final class com/gojek/mqtt/model/MqttConnectOptions$Companion {
}
public final class com/gojek/mqtt/model/MqttVersion : java/lang/Enum {
diff --git a/mqtt-client/build.gradle.kts b/mqtt-client/build.gradle.kts
index 7d62209c..afa651bf 100644
--- a/mqtt-client/build.gradle.kts
+++ b/mqtt-client/build.gradle.kts
@@ -53,6 +53,7 @@ dependencies {
testImplementation(deps.android.test.junit)
testImplementation(deps.android.test.mockito)
+ testImplementation(deps.android.test.mockitoCore)
androidTestImplementation(deps.android.test.junitExt)
}
diff --git a/mqtt-client/src/main/java/com/gojek/mqtt/client/config/ExperimentConfigs.kt b/mqtt-client/src/main/java/com/gojek/mqtt/client/config/ExperimentConfigs.kt
index ed49af0c..2f1d863d 100644
--- a/mqtt-client/src/main/java/com/gojek/mqtt/client/config/ExperimentConfigs.kt
+++ b/mqtt-client/src/main/java/com/gojek/mqtt/client/config/ExperimentConfigs.kt
@@ -13,7 +13,8 @@ data class ExperimentConfigs(
val inactivityTimeoutSeconds: Int = DEFAULT_INACTIVITY_TIMEOUT_SECS,
val policyResetTimeSeconds: Int = DEFAULT_POLICY_RESET_TIME_SECS,
val incomingMessagesTTLSecs: Long = 360,
- val incomingMessagesCleanupIntervalSecs: Long = 60
+ val incomingMessagesCleanupIntervalSecs: Long = 60,
+ val shouldUseNewSSLFlow: Boolean = false
)
enum class SubscriptionStore {
diff --git a/mqtt-client/src/main/java/com/gojek/mqtt/client/config/MqttConfiguration.kt b/mqtt-client/src/main/java/com/gojek/mqtt/client/config/MqttConfiguration.kt
index abfbc30b..0a6c3f6b 100644
--- a/mqtt-client/src/main/java/com/gojek/mqtt/client/config/MqttConfiguration.kt
+++ b/mqtt-client/src/main/java/com/gojek/mqtt/client/config/MqttConfiguration.kt
@@ -9,7 +9,6 @@ import com.gojek.mqtt.pingsender.MqttPingSender
import com.gojek.mqtt.policies.connectretrytime.IConnectRetryTimePolicy
import com.gojek.mqtt.policies.connecttimeout.IConnectTimeoutPolicy
import com.gojek.mqtt.policies.subscriptionretry.ISubscriptionRetryPolicy
-import javax.net.SocketFactory
abstract class MqttConfiguration(
open val connectRetryTimePolicy: IConnectRetryTimePolicy,
@@ -17,7 +16,6 @@ abstract class MqttConfiguration(
open val subscriptionRetryPolicy: ISubscriptionRetryPolicy,
open val unsubscriptionRetryPolicy: ISubscriptionRetryPolicy,
open val wakeLockTimeout: Int,
- open val socketFactory: SocketFactory?,
open val logger: ILogger,
open val authenticator: Authenticator,
open val authFailureHandler: AuthFailureHandler?,
diff --git a/mqtt-client/src/main/java/com/gojek/mqtt/client/config/v3/MqttV3Configuration.kt b/mqtt-client/src/main/java/com/gojek/mqtt/client/config/v3/MqttV3Configuration.kt
index d7457a2e..39dd9c2a 100644
--- a/mqtt-client/src/main/java/com/gojek/mqtt/client/config/v3/MqttV3Configuration.kt
+++ b/mqtt-client/src/main/java/com/gojek/mqtt/client/config/v3/MqttV3Configuration.kt
@@ -22,7 +22,6 @@ import com.gojek.mqtt.policies.connecttimeout.IConnectTimeoutPolicy
import com.gojek.mqtt.policies.subscriptionretry.ISubscriptionRetryPolicy
import com.gojek.mqtt.policies.subscriptionretry.SubscriptionRetryConfig
import com.gojek.mqtt.policies.subscriptionretry.SubscriptionRetryPolicy
-import javax.net.SocketFactory
data class MqttV3Configuration(
override val connectRetryTimePolicy: IConnectRetryTimePolicy =
@@ -34,7 +33,6 @@ data class MqttV3Configuration(
override val unsubscriptionRetryPolicy: ISubscriptionRetryPolicy =
SubscriptionRetryPolicy(SubscriptionRetryConfig()),
override val wakeLockTimeout: Int = DEFAULT_WAKELOCK_TIMEOUT,
- override val socketFactory: SocketFactory? = null,
override val logger: ILogger = NoOpLogger(),
override val authenticator: Authenticator,
override val authFailureHandler: AuthFailureHandler? = null,
@@ -49,7 +47,6 @@ data class MqttV3Configuration(
subscriptionRetryPolicy = subscriptionRetryPolicy,
unsubscriptionRetryPolicy = unsubscriptionRetryPolicy,
wakeLockTimeout = wakeLockTimeout,
- socketFactory = socketFactory,
logger = logger,
authenticator = authenticator,
authFailureHandler = authFailureHandler,
diff --git a/mqtt-client/src/main/java/com/gojek/mqtt/client/v3/impl/AndroidMqttClient.kt b/mqtt-client/src/main/java/com/gojek/mqtt/client/v3/impl/AndroidMqttClient.kt
index 9d510ef0..dcd9f480 100644
--- a/mqtt-client/src/main/java/com/gojek/mqtt/client/v3/impl/AndroidMqttClient.kt
+++ b/mqtt-client/src/main/java/com/gojek/mqtt/client/v3/impl/AndroidMqttClient.kt
@@ -171,13 +171,13 @@ internal class AndroidMqttClient(
maxInflightMessages = MAX_INFLIGHT_MESSAGES_ALLOWED,
logger = mqttConfiguration.logger,
connectionEventHandler = mqttClientEventAdapter.adapt(),
- socketFactory = mqttConfiguration.socketFactory,
mqttInterceptorList = mqttConfiguration.mqttInterceptorList.map {
mapToPahoInterceptor(it)
},
persistenceOptions = mqttConfiguration.persistenceOptions,
inactivityTimeoutSeconds = experimentConfigs.inactivityTimeoutSeconds,
- policyResetTimeSeconds = experimentConfigs.policyResetTimeSeconds
+ policyResetTimeSeconds = experimentConfigs.policyResetTimeSeconds,
+ shouldUseNewSSLFlow = experimentConfigs.shouldUseNewSSLFlow
)
mqttConnection = MqttConnection(
@@ -513,15 +513,15 @@ internal class AndroidMqttClient(
forceRefresh = false
this.hostFallbackPolicy = HostFallbackPolicy(connectOptions.serverUris)
val mqttConnectOptions = if (isAdaptiveKAConnection) {
- connectOptions.copy(
- keepAlive = keepAliveProvider.getKeepAlive(connectOptions),
- clientId = connectOptions.clientId + ":adaptive",
- isCleanSession = true
- )
+ connectOptions.newBuilder()
+ .keepAlive(keepAliveProvider.getKeepAlive(connectOptions))
+ .clientId(connectOptions.clientId + ":adaptive")
+ .cleanSession(true)
+ .build()
} else {
- connectOptions.copy(
- keepAlive = keepAliveProvider.getKeepAlive(connectOptions)
- )
+ connectOptions.newBuilder()
+ .keepAlive(keepAliveProvider.getKeepAlive(connectOptions))
+ .build()
}
if (isAdaptiveKAConnection.not()) {
diff --git a/mqtt-client/src/main/java/com/gojek/mqtt/connection/MqttConnection.kt b/mqtt-client/src/main/java/com/gojek/mqtt/connection/MqttConnection.kt
index a3c872a6..543d4753 100644
--- a/mqtt-client/src/main/java/com/gojek/mqtt/connection/MqttConnection.kt
+++ b/mqtt-client/src/main/java/com/gojek/mqtt/connection/MqttConnection.kt
@@ -162,27 +162,26 @@ internal class MqttConnection(
if (options == null) {
options = MqttConnectOptions()
}
- // Setting some connection options which we need to reset on every connect
- if (isSSL()) {
- options!!.socketFactory = connectionConfig.socketFactory
- } else {
- options!!.socketFactory = null
- }
- options!!.userName = connectOptions.username
- options!!.password = connectOptions.password.toCharArray()
- options!!.isCleanSession = connectOptions.isCleanSession
- options!!.keepAliveInterval = connectOptions.keepAlive.timeSeconds
- options!!.keepAliveIntervalServer = connectOptions.keepAlive.timeSeconds
- options!!.readTimeout = if (connectOptions.readTimeoutSecs == -1) {
- connectOptions.keepAlive.timeSeconds + 60
- } else {
- connectOptions.readTimeoutSecs
+ options!!.apply {
+ userName = connectOptions.username
+ password = connectOptions.password.toCharArray()
+ isCleanSession = connectOptions.isCleanSession
+ keepAliveInterval = connectOptions.keepAlive.timeSeconds
+ keepAliveIntervalServer = connectOptions.keepAlive.timeSeconds
+ readTimeout = connectOptions.readTimeoutSecs
+ connectionTimeout = connectTimeoutPolicy.getConnectTimeOut()
+ handshakeTimeout = connectTimeoutPolicy.getHandshakeTimeOut()
+ protocolName = mqttConnectOptions.version.protocolName
+ protocolLevel = mqttConnectOptions.version.protocolLevel
+ userPropertyList = getUserPropertyList(connectOptions.userPropertiesMap)
+ socketFactory = mqttConnectOptions.socketFactory
+ sslSocketFactory = mqttConnectOptions.sslSocketFactory
+ x509TrustManager = mqttConnectOptions.x509TrustManager
+ connectionSpec = mqttConnectOptions.connectionSpec
+ alpnProtocolList = mqttConnectOptions.protocols
}
- options!!.connectionTimeout = connectTimeoutPolicy.getConnectTimeOut()
- options!!.handshakeTimeout = connectTimeoutPolicy.getHandshakeTimeOut()
- options!!.protocolName = mqttConnectOptions.version.protocolName
- options!!.protocolLevel = mqttConnectOptions.version.protocolLevel
- options!!.userPropertyList = getUserPropertyList(connectOptions.userPropertiesMap)
+ // Setting some connection options which we need to reset on every connect
+
logger.d(TAG, "MQTT connecting on : " + mqtt!!.serverURI)
updatePolicyParams = true
connectStartTime = clock.nanoTime()
@@ -663,6 +662,10 @@ internal class MqttConnection(
override fun inactivityTimeoutSecs(): Int {
return connectionConfig.inactivityTimeoutSeconds
}
+
+ override fun useNewSSLFlow(): Boolean {
+ return connectionConfig.shouldUseNewSSLFlow
+ }
}
}
diff --git a/mqtt-client/src/main/java/com/gojek/mqtt/connection/config/v3/ConnectionConfig.kt b/mqtt-client/src/main/java/com/gojek/mqtt/connection/config/v3/ConnectionConfig.kt
index 933374b2..82bd9eb5 100644
--- a/mqtt-client/src/main/java/com/gojek/mqtt/connection/config/v3/ConnectionConfig.kt
+++ b/mqtt-client/src/main/java/com/gojek/mqtt/connection/config/v3/ConnectionConfig.kt
@@ -8,7 +8,6 @@ import com.gojek.mqtt.constants.QUIESCE_TIME_MILLIS
import com.gojek.mqtt.policies.connectretrytime.IConnectRetryTimePolicy
import com.gojek.mqtt.policies.connecttimeout.IConnectTimeoutPolicy
import com.gojek.mqtt.policies.subscriptionretry.ISubscriptionRetryPolicy
-import javax.net.SocketFactory
import `in`.mohalla.paho.client.mqttv3.MqttInterceptor
internal data class ConnectionConfig(
@@ -19,12 +18,12 @@ internal data class ConnectionConfig(
val wakeLockTimeout: Int,
val maxInflightMessages: Int,
val logger: ILogger,
- val socketFactory: SocketFactory?,
val connectionEventHandler: ConnectionEventHandler,
val quiesceTimeout: Int = QUIESCE_TIME_MILLIS,
val disconnectTimeout: Int = DISCONNECT_TIMEOUT_MILLIS,
val mqttInterceptorList: List,
val persistenceOptions: PersistenceOptions,
val inactivityTimeoutSeconds: Int,
- val policyResetTimeSeconds: Int
+ val policyResetTimeSeconds: Int,
+ val shouldUseNewSSLFlow: Boolean
)
diff --git a/mqtt-client/src/main/java/com/gojek/mqtt/model/KeepAlive.kt b/mqtt-client/src/main/java/com/gojek/mqtt/model/KeepAlive.kt
index 6ea17cdf..0e59d48e 100644
--- a/mqtt-client/src/main/java/com/gojek/mqtt/model/KeepAlive.kt
+++ b/mqtt-client/src/main/java/com/gojek/mqtt/model/KeepAlive.kt
@@ -3,4 +3,8 @@ package com.gojek.mqtt.model
data class KeepAlive(
val timeSeconds: Int,
internal val isOptimal: Boolean = false
-)
+) {
+ companion object {
+ val NO_KEEP_ALIVE = KeepAlive(timeSeconds = 60, isOptimal = false)
+ }
+}
diff --git a/mqtt-client/src/main/java/com/gojek/mqtt/model/MqttConnectOptions.kt b/mqtt-client/src/main/java/com/gojek/mqtt/model/MqttConnectOptions.kt
index 5aa83e0e..043e84ab 100644
--- a/mqtt-client/src/main/java/com/gojek/mqtt/model/MqttConnectOptions.kt
+++ b/mqtt-client/src/main/java/com/gojek/mqtt/model/MqttConnectOptions.kt
@@ -1,18 +1,219 @@
package com.gojek.mqtt.model
+import com.gojek.mqtt.model.KeepAlive.Companion.NO_KEEP_ALIVE
import com.gojek.mqtt.model.MqttVersion.VERSION_3_1_1
+import javax.net.SocketFactory
+import javax.net.ssl.SSLSocketFactory
+import javax.net.ssl.X509TrustManager
+import `in`.mohalla.paho.client.mqttv3.ConnectionSpec
+import `in`.mohalla.paho.client.mqttv3.Protocol
+import `in`.mohalla.paho.client.mqttv3.internal.platform.Platform
-data class MqttConnectOptions(
- val serverUris: List,
- val keepAlive: KeepAlive,
- val clientId: String,
- val username: String,
- val password: String,
- val isCleanSession: Boolean,
- val readTimeoutSecs: Int = -1,
- val version: MqttVersion = VERSION_3_1_1,
- val userPropertiesMap: Map = emptyMap()
-)
+class MqttConnectOptions private constructor(
+ builder: Builder
+) {
+ val serverUris: List = builder.serverUris
+
+ val keepAlive: KeepAlive = builder.keepAlive
+
+ val clientId: String = builder.clientId
+
+ val username: String = builder.username
+
+ val password: String = builder.password
+
+ val isCleanSession: Boolean = builder.isCleanSession
+
+ val readTimeoutSecs: Int
+
+ val version: MqttVersion = builder.version
+
+ val userPropertiesMap: Map = builder.userPropertiesMap
+
+ val socketFactory: SocketFactory = builder.socketFactory
+
+ val sslSocketFactory: SSLSocketFactory?
+
+ val x509TrustManager: X509TrustManager?
+
+ val connectionSpec: ConnectionSpec =
+ builder.connectionSpec
+
+ val protocols: List = builder.protocols
+
+ init {
+ if (connectionSpec.isTls.not()) {
+ this.sslSocketFactory = null
+ this.x509TrustManager = null
+ } else if (builder.sslSocketFactoryOrNull != null) {
+ this.sslSocketFactory = builder.sslSocketFactoryOrNull
+ this.x509TrustManager = builder.x509TrustManagerOrNull!!
+ } else {
+ this.x509TrustManager = Platform.get().platformTrustManager()
+ this.sslSocketFactory = Platform.get().newSslSocketFactory(x509TrustManager!!)
+ }
+
+ this.readTimeoutSecs = if (builder.readTimeoutSecs < builder.keepAlive.timeSeconds) {
+ builder.keepAlive.timeSeconds + 60
+ } else {
+ builder.readTimeoutSecs
+ }
+ }
+
+ fun newBuilder(): Builder = Builder(this)
+
+ class Builder() {
+ internal var serverUris: List = emptyList()
+ internal var keepAlive: KeepAlive = NO_KEEP_ALIVE
+ internal var clientId: String = ""
+ internal var username: String = ""
+ internal var password: String = ""
+ internal var isCleanSession: Boolean = false
+ internal var readTimeoutSecs: Int = DEFAULT_READ_TIMEOUT
+ internal var version: MqttVersion = VERSION_3_1_1
+ internal var userPropertiesMap: Map = emptyMap()
+ internal var socketFactory: SocketFactory = SocketFactory.getDefault()
+ internal var sslSocketFactoryOrNull: SSLSocketFactory? = null
+ internal var x509TrustManagerOrNull: X509TrustManager? = null
+ internal var connectionSpec: ConnectionSpec = DEFAULT_CONNECTION_SPECS
+ internal var protocols: List = emptyList()
+
+ internal constructor(mqttConnectOptions: MqttConnectOptions) : this() {
+ this.serverUris = mqttConnectOptions.serverUris
+ this.keepAlive = mqttConnectOptions.keepAlive
+ this.clientId = mqttConnectOptions.clientId
+ this.username = mqttConnectOptions.username
+ this.password = mqttConnectOptions.password
+ this.isCleanSession = mqttConnectOptions.isCleanSession
+ this.readTimeoutSecs = mqttConnectOptions.readTimeoutSecs
+ this.version = mqttConnectOptions.version
+ this.userPropertiesMap = mqttConnectOptions.userPropertiesMap
+ this.socketFactory = mqttConnectOptions.socketFactory
+ this.sslSocketFactoryOrNull = mqttConnectOptions.sslSocketFactory
+ this.x509TrustManagerOrNull = mqttConnectOptions.x509TrustManager
+ this.connectionSpec = mqttConnectOptions.connectionSpec
+ this.protocols = mqttConnectOptions.protocols
+ }
+
+ fun serverUris(serverUris: List) = apply {
+ require(serverUris.isNotEmpty()) { "serverUris cannot be empty" }
+ this.serverUris = serverUris
+ }
+
+ fun keepAlive(keepAlive: KeepAlive) = apply {
+ require(keepAlive.timeSeconds > 0) { "keepAlive timeSeconds must be >0" }
+ this.keepAlive = keepAlive
+ }
+
+ fun clientId(clientId: String) = apply {
+ require(clientId.isNotEmpty()) { "clientId cannot be empty" }
+ this.clientId = clientId
+ }
+
+ fun userName(username: String) = apply {
+ this.username = username
+ }
+
+ fun password(password: String) = apply {
+ this.password = password
+ }
+
+ fun cleanSession(cleanSession: Boolean) = apply {
+ this.isCleanSession = cleanSession
+ }
+
+ fun readTimeoutSecs(readTimeoutSecs: Int) = apply {
+ require(readTimeoutSecs > 0) { "read timeout should be > 0" }
+ this.readTimeoutSecs = readTimeoutSecs
+ }
+
+ fun mqttVersion(mqttVersion: MqttVersion) = apply {
+ this.version = mqttVersion
+ }
+
+ fun userProperties(userProperties: Map) = apply {
+ this.userPropertiesMap = userProperties.toMap()
+ }
+
+ /**
+ * Sets the socket factory used to create connections.
+ *
+ * If unset, the [system-wide default][SocketFactory.getDefault] socket factory will be used.
+ */
+ fun socketFactory(socketFactory: SocketFactory) = apply {
+ require(socketFactory !is SSLSocketFactory) { "socketFactory instanceof SSLSocketFactory" }
+
+ this.socketFactory = socketFactory
+ }
+
+ /**
+ * Sets the socket factory and trust manager used to secure MQTT/Websocket connections. If unset, the
+ * system defaults will be used.
+ *
+ * Most applications should not call this method, and instead use the system defaults. Those
+ * classes include special optimizations that can be lost if the implementations are decorated.
+ *
+ * If necessary, you can create and configure the defaults yourself with the following code:
+ *
+ * ```java
+ * TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(
+ * TrustManagerFactory.getDefaultAlgorithm());
+ * trustManagerFactory.init((KeyStore) null);
+ * TrustManager[] trustManagers = trustManagerFactory.getTrustManagers();
+ * if (trustManagers.length != 1 || !(trustManagers[0] instanceof X509TrustManager)) {
+ * throw new IllegalStateException("Unexpected default trust managers:"
+ * + Arrays.toString(trustManagers));
+ * }
+ * X509TrustManager trustManager = (X509TrustManager) trustManagers[0];
+ *
+ * SSLContext sslContext = SSLContext.getInstance("TLS");
+ * sslContext.init(null, new TrustManager[] { trustManager }, null);
+ * SSLSocketFactory sslSocketFactory = sslContext.getSocketFactory();
+ *
+ * MQTTConnectOptions client = MQTTConnectOptions.Builder()
+ * .sslSocketFactory(sslSocketFactory, trustManager)
+ * .build();
+ * ```
+ *
+ * ## TrustManagers on Android are Weird!
+ *
+ * Trust managers targeting Android must also define a method that has this signature:
+ *
+ * ```java
+ * @SuppressWarnings("unused")
+ * public List checkServerTrusted(
+ * X509Certificate[] chain, String authType, String host) throws CertificateException {
+ * }
+ * ```
+ * See [android.net.http.X509TrustManagerExtensions] for more information.
+ */
+ fun sslSocketFactory(
+ sslSocketFactory: SSLSocketFactory,
+ trustManager: X509TrustManager
+ ) = apply {
+ this.sslSocketFactoryOrNull = sslSocketFactory
+ this.x509TrustManagerOrNull = trustManager
+ }
+
+ fun connectionSpec(connectionSpec: ConnectionSpec) = apply {
+ this.connectionSpec = connectionSpec
+ }
+
+ fun alpnProtocols(protocols: List) = apply {
+ require(protocols.isNotEmpty()) { "alpn protocol list cannot be empty" }
+ this.protocols = protocols
+ }
+
+ fun build(): MqttConnectOptions = MqttConnectOptions(this)
+ }
+
+ companion object {
+
+ internal const val DEFAULT_READ_TIMEOUT = -1
+
+ internal val DEFAULT_CONNECTION_SPECS = ConnectionSpec.MODERN_TLS
+ }
+}
enum class MqttVersion(internal val protocolName: String, internal val protocolLevel: Int) {
VERSION_3_1("MQIsdp", 3), VERSION_3_1_1("MQTT", 4)
diff --git a/mqtt-client/src/test/java/com/gojek/mqtt/exception/handler/v3/impl/MqttExceptionHandlerImplTest.kt b/mqtt-client/src/test/java/com/gojek/mqtt/exception/handler/v3/impl/MqttExceptionHandlerImplTest.kt
index 18f23875..e0599f5c 100644
--- a/mqtt-client/src/test/java/com/gojek/mqtt/exception/handler/v3/impl/MqttExceptionHandlerImplTest.kt
+++ b/mqtt-client/src/test/java/com/gojek/mqtt/exception/handler/v3/impl/MqttExceptionHandlerImplTest.kt
@@ -6,7 +6,6 @@ import com.gojek.mqtt.policies.connectretrytime.IConnectRetryTimePolicy
import com.gojek.mqtt.scheduler.IRunnableScheduler
import com.nhaarman.mockitokotlin2.mock
import com.nhaarman.mockitokotlin2.verify
-import com.nhaarman.mockitokotlin2.verifyZeroInteractions
import com.nhaarman.mockitokotlin2.whenever
import java.net.SocketException
import java.net.SocketTimeoutException
@@ -40,6 +39,7 @@ import `in`.mohalla.paho.client.mqttv3.MqttException.REASON_CODE_TOKEN_INUSE
import `in`.mohalla.paho.client.mqttv3.MqttException.REASON_CODE_UNEXPECTED_ERROR
import org.junit.Test
import org.junit.runner.RunWith
+import org.mockito.Mockito.verifyNoInteractions
import org.mockito.junit.MockitoJUnitRunner
@RunWith(MockitoJUnitRunner::class)
@@ -216,7 +216,7 @@ class MqttExceptionHandlerImplTest {
REASON_CODE_CLIENT_ALREADY_DISCONNECTED.toInt()
)
mqttExceptionHandlerImpl.handleException(exception, false)
- verifyZeroInteractions(runnableScheduler, connectRetryTimePolicy)
+ verifyNoInteractions(runnableScheduler, connectRetryTimePolicy)
}
@Test
@@ -234,7 +234,7 @@ class MqttExceptionHandlerImplTest {
REASON_CODE_CLIENT_DISCONNECTING.toInt()
)
mqttExceptionHandlerImpl.handleException(exception, false)
- verifyZeroInteractions(runnableScheduler, connectRetryTimePolicy)
+ verifyNoInteractions(runnableScheduler, connectRetryTimePolicy)
}
@Test
@@ -252,7 +252,7 @@ class MqttExceptionHandlerImplTest {
REASON_CODE_CLIENT_NOT_CONNECTED.toInt()
)
mqttExceptionHandlerImpl.handleException(exception, false)
- verifyZeroInteractions(runnableScheduler, connectRetryTimePolicy)
+ verifyNoInteractions(runnableScheduler, connectRetryTimePolicy)
}
@Test
@@ -270,7 +270,7 @@ class MqttExceptionHandlerImplTest {
REASON_CODE_CLIENT_TIMEOUT.toInt()
)
mqttExceptionHandlerImpl.handleException(exception, false)
- verifyZeroInteractions(runnableScheduler, connectRetryTimePolicy)
+ verifyNoInteractions(runnableScheduler, connectRetryTimePolicy)
}
@Test
@@ -279,7 +279,7 @@ class MqttExceptionHandlerImplTest {
REASON_CODE_CONNECT_IN_PROGRESS.toInt()
)
mqttExceptionHandlerImpl.handleException(exception, true)
- verifyZeroInteractions(runnableScheduler, connectRetryTimePolicy)
+ verifyNoInteractions(runnableScheduler, connectRetryTimePolicy)
}
@Test
@@ -308,7 +308,7 @@ class MqttExceptionHandlerImplTest {
REASON_CODE_CONNECTION_LOST.toInt()
)
mqttExceptionHandlerImpl.handleException(exception, false)
- verifyZeroInteractions(runnableScheduler, connectRetryTimePolicy)
+ verifyNoInteractions(runnableScheduler, connectRetryTimePolicy)
}
@Test
@@ -317,7 +317,7 @@ class MqttExceptionHandlerImplTest {
REASON_CODE_MAX_INFLIGHT.toInt()
)
mqttExceptionHandlerImpl.handleException(exception, true)
- verifyZeroInteractions(runnableScheduler, connectRetryTimePolicy)
+ verifyNoInteractions(runnableScheduler, connectRetryTimePolicy)
}
@Test
@@ -326,7 +326,7 @@ class MqttExceptionHandlerImplTest {
REASON_CODE_CLIENT_CLOSED.toInt()
)
mqttExceptionHandlerImpl.handleException(exception, true)
- verifyZeroInteractions(runnableScheduler, connectRetryTimePolicy)
+ verifyNoInteractions(runnableScheduler, connectRetryTimePolicy)
}
@Test
@@ -335,7 +335,7 @@ class MqttExceptionHandlerImplTest {
REASON_CODE_CLIENT_CONNECTED.toInt()
)
mqttExceptionHandlerImpl.handleException(exception, true)
- verifyZeroInteractions(runnableScheduler, connectRetryTimePolicy)
+ verifyNoInteractions(runnableScheduler, connectRetryTimePolicy)
}
@Test
@@ -344,7 +344,7 @@ class MqttExceptionHandlerImplTest {
REASON_CODE_CLIENT_DISCONNECT_PROHIBITED.toInt()
)
mqttExceptionHandlerImpl.handleException(exception, true)
- verifyZeroInteractions(runnableScheduler, connectRetryTimePolicy)
+ verifyNoInteractions(runnableScheduler, connectRetryTimePolicy)
}
@Test
@@ -353,7 +353,7 @@ class MqttExceptionHandlerImplTest {
REASON_CODE_INVALID_CLIENT_ID.toInt()
)
mqttExceptionHandlerImpl.handleException(exception, true)
- verifyZeroInteractions(runnableScheduler, connectRetryTimePolicy)
+ verifyNoInteractions(runnableScheduler, connectRetryTimePolicy)
}
@Test
@@ -362,7 +362,7 @@ class MqttExceptionHandlerImplTest {
REASON_CODE_INVALID_MESSAGE.toInt()
)
mqttExceptionHandlerImpl.handleException(exception, true)
- verifyZeroInteractions(runnableScheduler, connectRetryTimePolicy)
+ verifyNoInteractions(runnableScheduler, connectRetryTimePolicy)
}
@Test
@@ -371,7 +371,7 @@ class MqttExceptionHandlerImplTest {
REASON_CODE_INVALID_PROTOCOL_VERSION.toInt()
)
mqttExceptionHandlerImpl.handleException(exception, true)
- verifyZeroInteractions(runnableScheduler, connectRetryTimePolicy)
+ verifyNoInteractions(runnableScheduler, connectRetryTimePolicy)
}
@Test
@@ -380,7 +380,7 @@ class MqttExceptionHandlerImplTest {
REASON_CODE_NO_MESSAGE_IDS_AVAILABLE.toInt()
)
mqttExceptionHandlerImpl.handleException(exception, true)
- verifyZeroInteractions(runnableScheduler, connectRetryTimePolicy)
+ verifyNoInteractions(runnableScheduler, connectRetryTimePolicy)
}
@Test
@@ -389,7 +389,7 @@ class MqttExceptionHandlerImplTest {
REASON_CODE_SOCKET_FACTORY_MISMATCH.toInt()
)
mqttExceptionHandlerImpl.handleException(exception, true)
- verifyZeroInteractions(runnableScheduler, connectRetryTimePolicy)
+ verifyNoInteractions(runnableScheduler, connectRetryTimePolicy)
}
@Test
@@ -398,7 +398,7 @@ class MqttExceptionHandlerImplTest {
REASON_CODE_SSL_CONFIG_ERROR.toInt()
)
mqttExceptionHandlerImpl.handleException(exception, true)
- verifyZeroInteractions(runnableScheduler, connectRetryTimePolicy)
+ verifyNoInteractions(runnableScheduler, connectRetryTimePolicy)
}
@Test
@@ -406,6 +406,6 @@ class MqttExceptionHandlerImplTest {
val exception =
MqttException(REASON_CODE_TOKEN_INUSE.toInt())
mqttExceptionHandlerImpl.handleException(exception, true)
- verifyZeroInteractions(runnableScheduler, connectRetryTimePolicy)
+ verifyNoInteractions(runnableScheduler, connectRetryTimePolicy)
}
}
diff --git a/network-tracker/build.gradle.kts b/network-tracker/build.gradle.kts
index d95ac3a3..d71c31b8 100644
--- a/network-tracker/build.gradle.kts
+++ b/network-tracker/build.gradle.kts
@@ -28,6 +28,7 @@ dependencies {
api(project(":courier-core"))
implementation(deps.android.androidx.coreKtx)
+ testImplementation(deps.android.test.mockitoCore)
testImplementation(deps.android.test.kotlinTestJunit)
}
diff --git a/paho/build.gradle.kts b/paho/build.gradle.kts
index 04d505a7..4baaee4b 100644
--- a/paho/build.gradle.kts
+++ b/paho/build.gradle.kts
@@ -22,6 +22,14 @@ java {
dependencies {
implementation(deps.kotlin.stdlib.core)
+ implementation("com.squareup.okio:okio:3.2.0")
+
+ compileOnly("org.robolectric:android-all:13-robolectric-9030017")
+ compileOnly("org.bouncycastle:bcprov-jdk15to18:1.71")
+ compileOnly("org.bouncycastle:bctls-jdk15to18:1.71")
+ compileOnly("org.conscrypt:conscrypt-openjdk-uber:2.5.2")
+ compileOnly("org.openjsse:openjsse:1.1.10")
+
testImplementation(deps.android.test.kotlinTestJunit)
}
diff --git a/paho/src/main/AndroidManifest.xml b/paho/src/main/AndroidManifest.xml
new file mode 100644
index 00000000..17e0e3ec
--- /dev/null
+++ b/paho/src/main/AndroidManifest.xml
@@ -0,0 +1,3 @@
+
+
diff --git a/paho/src/main/java/in/mohalla/paho/client/mqttv3/ConnectionSpec.kt b/paho/src/main/java/in/mohalla/paho/client/mqttv3/ConnectionSpec.kt
new file mode 100644
index 00000000..63e4f2da
--- /dev/null
+++ b/paho/src/main/java/in/mohalla/paho/client/mqttv3/ConnectionSpec.kt
@@ -0,0 +1,375 @@
+/*
+ * Copyright (C) 2014 Square, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package `in`.mohalla.paho.client.mqttv3
+
+import java.util.Arrays
+import java.util.Objects
+import javax.net.ssl.SSLSocket
+import `in`.mohalla.paho.client.mqttv3.ConnectionSpec.Builder
+import `in`.mohalla.paho.client.mqttv3.internal.tls.CipherSuite
+import `in`.mohalla.paho.client.mqttv3.internal.tls.TlsVersion
+
+/**
+ * Specifies configuration for the socket connection that HTTP traffic travels through. For `https:`
+ * URLs, this includes the TLS version and cipher suites to use when negotiating a secure
+ * connection.
+ *
+ * The TLS versions configured in a connection spec are only be used if they are also enabled in the
+ * SSL socket. For example, if an SSL socket does not have TLS 1.3 enabled, it will not be used even
+ * if it is present on the connection spec. The same policy also applies to cipher suites.
+ *
+ * Use [Builder.allEnabledTlsVersions] and [Builder.allEnabledCipherSuites] to defer all feature
+ * selection to the underlying SSL socket.
+ *
+ * The configuration of each spec changes with each OkHttp release. This is annoying: upgrading
+ * your OkHttp library can break connectivity to certain web servers! But it’s a necessary annoyance
+ * because the TLS ecosystem is dynamic and staying up to date is necessary to stay secure. See
+ * [OkHttp's TLS Configuration History][tls_history] to track these changes.
+ *
+ * [tls_history]: https://square.github.io/okhttp/tls_configuration_history/
+ */
+class ConnectionSpec internal constructor(
+ @get:JvmName("isTls") val isTls: Boolean,
+ @get:JvmName("supportsTlsExtensions") val supportsTlsExtensions: Boolean,
+ internal val cipherSuitesAsString: Array?,
+ private val tlsVersionsAsString: Array?
+) {
+
+ /**
+ * Returns the cipher suites to use for a connection. Returns null if all of the SSL socket's
+ * enabled cipher suites should be used.
+ */
+ @get:JvmName("cipherSuites")
+ val cipherSuites: List?
+ get() {
+ return cipherSuitesAsString?.map { CipherSuite.forJavaName(it) }?.toList()
+ }
+
+ @JvmName("-deprecated_cipherSuites")
+ @Deprecated(
+ message = "moved to val",
+ replaceWith = ReplaceWith(expression = "cipherSuites"),
+ level = DeprecationLevel.ERROR
+ )
+ fun cipherSuites(): List? = cipherSuites
+
+ /**
+ * Returns the TLS versions to use when negotiating a connection. Returns null if all of the SSL
+ * socket's enabled TLS versions should be used.
+ */
+ @get:JvmName("tlsVersions")
+ val tlsVersions: List?
+ get() {
+ return tlsVersionsAsString?.map { TlsVersion.forJavaName(it) }?.toList()
+ }
+
+ @JvmName("-deprecated_tlsVersions")
+ @Deprecated(
+ message = "moved to val",
+ replaceWith = ReplaceWith(expression = "tlsVersions"),
+ level = DeprecationLevel.ERROR
+ )
+ fun tlsVersions(): List? = tlsVersions
+
+ @JvmName("-deprecated_supportsTlsExtensions")
+ @Deprecated(
+ message = "moved to val",
+ replaceWith = ReplaceWith(expression = "supportsTlsExtensions"),
+ level = DeprecationLevel.ERROR
+ )
+ fun supportsTlsExtensions(): Boolean = supportsTlsExtensions
+
+ /** Applies this spec to [sslSocket]. */
+ fun apply(sslSocket: SSLSocket, isFallback: Boolean) {
+ val specToApply = supportedSpec(sslSocket, isFallback)
+
+ if (specToApply.tlsVersions != null) {
+ sslSocket.enabledProtocols = specToApply.tlsVersionsAsString
+ }
+
+ if (specToApply.cipherSuites != null) {
+ sslSocket.enabledCipherSuites = specToApply.cipherSuitesAsString
+ }
+ }
+
+ /**
+ * Returns a copy of this that omits cipher suites and TLS versions not enabled by [sslSocket].
+ */
+ private fun supportedSpec(sslSocket: SSLSocket, isFallback: Boolean): ConnectionSpec {
+ val socketEnabledCipherSuites = sslSocket.enabledCipherSuites
+ var cipherSuitesIntersection: Array =
+ effectiveCipherSuites(socketEnabledCipherSuites)
+
+ val tlsVersionsIntersection = if (tlsVersionsAsString != null) {
+ sslSocket.enabledProtocols.intersect(tlsVersionsAsString, naturalOrder())
+ } else {
+ sslSocket.enabledProtocols
+ }
+
+ // In accordance with https://tools.ietf.org/html/draft-ietf-tls-downgrade-scsv-00 the SCSV
+ // cipher is added to signal that a protocol fallback has taken place.
+ val supportedCipherSuites = sslSocket.supportedCipherSuites
+ val indexOfFallbackScsv = supportedCipherSuites.indexOf(
+ "TLS_FALLBACK_SCSV",
+ CipherSuite.ORDER_BY_NAME
+ )
+ if (isFallback && indexOfFallbackScsv != -1) {
+ cipherSuitesIntersection = cipherSuitesIntersection.concat(
+ supportedCipherSuites[indexOfFallbackScsv]
+ )
+ }
+
+ return Builder(this)
+ .cipherSuites(*cipherSuitesIntersection)
+ .tlsVersions(*tlsVersionsIntersection)
+ .build()
+ }
+
+ /**
+ * Returns `true` if the socket, as currently configured, supports this connection spec. In
+ * order for a socket to be compatible the enabled cipher suites and protocols must intersect.
+ *
+ * For cipher suites, at least one of the [required cipher suites][cipherSuites] must match the
+ * socket's enabled cipher suites. If there are no required cipher suites the socket must have at
+ * least one cipher suite enabled.
+ *
+ * For protocols, at least one of the [required protocols][tlsVersions] must match the socket's
+ * enabled protocols.
+ */
+ fun isCompatible(socket: SSLSocket): Boolean {
+ if (!isTls) {
+ return false
+ }
+
+ if (tlsVersionsAsString != null &&
+ !tlsVersionsAsString.hasIntersection(socket.enabledProtocols, naturalOrder())
+ ) {
+ return false
+ }
+
+ if (cipherSuitesAsString != null && !cipherSuitesAsString.hasIntersection(
+ socket.enabledCipherSuites,
+ CipherSuite.ORDER_BY_NAME
+ )
+ ) {
+ return false
+ }
+
+ return true
+ }
+
+ override fun equals(other: Any?): Boolean {
+ if (other !is ConnectionSpec) return false
+ if (other === this) return true
+
+ if (this.isTls != other.isTls) return false
+
+ if (isTls) {
+ if (!Arrays.equals(this.cipherSuitesAsString, other.cipherSuitesAsString)) return false
+ if (!Arrays.equals(this.tlsVersionsAsString, other.tlsVersionsAsString)) return false
+ if (this.supportsTlsExtensions != other.supportsTlsExtensions) return false
+ }
+
+ return true
+ }
+
+ override fun hashCode(): Int {
+ var result = 17
+ if (isTls) {
+ result = 31 * result + (cipherSuitesAsString?.contentHashCode() ?: 0)
+ result = 31 * result + (tlsVersionsAsString?.contentHashCode() ?: 0)
+ result = 31 * result + if (supportsTlsExtensions) 0 else 1
+ }
+ return result
+ }
+
+ override fun toString(): String {
+ if (!isTls) return "ConnectionSpec()"
+
+ return (
+ "ConnectionSpec(" +
+ "cipherSuites=${Objects.toString(cipherSuites, "[all enabled]")}, " +
+ "tlsVersions=${Objects.toString(tlsVersions, "[all enabled]")}, " +
+ "supportsTlsExtensions=$supportsTlsExtensions)"
+ )
+ }
+
+ class Builder {
+ internal var tls: Boolean = false
+ internal var cipherSuites: Array? = null
+ internal var tlsVersions: Array? = null
+ internal var supportsTlsExtensions: Boolean = false
+
+ internal constructor(tls: Boolean) {
+ this.tls = tls
+ }
+
+ constructor(connectionSpec: ConnectionSpec) {
+ this.tls = connectionSpec.isTls
+ this.cipherSuites = connectionSpec.cipherSuitesAsString
+ this.tlsVersions = connectionSpec.tlsVersionsAsString
+ this.supportsTlsExtensions = connectionSpec.supportsTlsExtensions
+ }
+
+ fun allEnabledCipherSuites() = apply {
+ require(tls) { "no cipher suites for cleartext connections" }
+ this.cipherSuites = null
+ }
+
+ fun cipherSuites(vararg cipherSuites: CipherSuite): Builder = apply {
+ require(tls) { "no cipher suites for cleartext connections" }
+ val strings = cipherSuites.map { it.javaName }.toTypedArray()
+ return cipherSuites(*strings)
+ }
+
+ fun cipherSuites(vararg cipherSuites: String) = apply {
+ require(tls) { "no cipher suites for cleartext connections" }
+ require(cipherSuites.isNotEmpty()) { "At least one cipher suite is required" }
+
+ this.cipherSuites = cipherSuites.clone() as Array // Defensive copy.
+ }
+
+ fun allEnabledTlsVersions() = apply {
+ require(tls) { "no TLS versions for cleartext connections" }
+ this.tlsVersions = null
+ }
+
+ fun tlsVersions(vararg tlsVersions: TlsVersion): Builder = apply {
+ require(tls) { "no TLS versions for cleartext connections" }
+
+ val strings = tlsVersions.map { it.javaName }.toTypedArray()
+ return tlsVersions(*strings)
+ }
+
+ fun tlsVersions(vararg tlsVersions: String) = apply {
+ require(tls) { "no TLS versions for cleartext connections" }
+ require(tlsVersions.isNotEmpty()) { "At least one TLS version is required" }
+
+ this.tlsVersions = tlsVersions.clone() as Array // Defensive copy.
+ }
+
+ @Deprecated(
+ "since OkHttp 3.13 all TLS-connections are expected to support TLS extensions.\n" +
+ "In a future release setting this to true will be unnecessary and setting it to false\n" +
+ "will have no effect."
+ )
+ fun supportsTlsExtensions(supportsTlsExtensions: Boolean) = apply {
+ require(tls) { "no TLS extensions for cleartext connections" }
+ this.supportsTlsExtensions = supportsTlsExtensions
+ }
+
+ fun build(): ConnectionSpec = ConnectionSpec(
+ tls,
+ supportsTlsExtensions,
+ cipherSuites,
+ tlsVersions
+ )
+ }
+
+ @Suppress("DEPRECATION")
+ companion object {
+ // Most secure but generally supported list.
+ private val RESTRICTED_CIPHER_SUITES = arrayOf(
+ // TLSv1.3.
+ CipherSuite.TLS_AES_128_GCM_SHA256,
+ CipherSuite.TLS_AES_256_GCM_SHA384,
+ CipherSuite.TLS_CHACHA20_POLY1305_SHA256,
+
+ // TLSv1.0, TLSv1.1, TLSv1.2.
+ CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
+ CipherSuite.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
+ CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,
+ CipherSuite.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
+ CipherSuite.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256,
+ CipherSuite.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256
+ )
+
+ // This is nearly equal to the cipher suites supported in Chrome 72, current as of 2019-02-24.
+ // See https://tinyurl.com/okhttp-cipher-suites for availability.
+ private val APPROVED_CIPHER_SUITES = arrayOf(
+ // TLSv1.3.
+ CipherSuite.TLS_AES_128_GCM_SHA256,
+ CipherSuite.TLS_AES_256_GCM_SHA384,
+ CipherSuite.TLS_CHACHA20_POLY1305_SHA256,
+
+ // TLSv1.0, TLSv1.1, TLSv1.2.
+ CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
+ CipherSuite.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
+ CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,
+ CipherSuite.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
+ CipherSuite.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256,
+ CipherSuite.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256,
+
+ // Note that the following cipher suites are all on HTTP/2's bad cipher suites list. We'll
+ // continue to include them until better suites are commonly available.
+ CipherSuite.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA,
+ CipherSuite.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA,
+ CipherSuite.TLS_RSA_WITH_AES_128_GCM_SHA256,
+ CipherSuite.TLS_RSA_WITH_AES_256_GCM_SHA384,
+ CipherSuite.TLS_RSA_WITH_AES_128_CBC_SHA,
+ CipherSuite.TLS_RSA_WITH_AES_256_CBC_SHA,
+ CipherSuite.TLS_RSA_WITH_3DES_EDE_CBC_SHA
+ )
+
+ /** A secure TLS connection that requires a recent client platform and a recent server. */
+ @JvmField
+ val RESTRICTED_TLS = Builder(true)
+ .cipherSuites(*RESTRICTED_CIPHER_SUITES)
+ .tlsVersions(TlsVersion.TLS_1_3, TlsVersion.TLS_1_2)
+ .supportsTlsExtensions(true)
+ .build()
+
+ /**
+ * A modern TLS configuration that works on most client platforms and can connect to most servers.
+ * This is OkHttp's default configuration.
+ */
+ @JvmField
+ val MODERN_TLS = Builder(true)
+ .cipherSuites(*APPROVED_CIPHER_SUITES)
+ .tlsVersions(TlsVersion.TLS_1_3, TlsVersion.TLS_1_2)
+ .supportsTlsExtensions(true)
+ .build()
+
+ /**
+ * A backwards-compatible fallback configuration that works on obsolete client platforms and can
+ * connect to obsolete servers. When possible, prefer to upgrade your client platform or server
+ * rather than using this configuration.
+ */
+ @JvmField
+ val COMPATIBLE_TLS = Builder(true)
+ .cipherSuites(*APPROVED_CIPHER_SUITES)
+ .tlsVersions(
+ TlsVersion.TLS_1_3,
+ TlsVersion.TLS_1_2,
+ TlsVersion.TLS_1_1,
+ TlsVersion.TLS_1_0
+ )
+ .supportsTlsExtensions(true)
+ .build()
+
+ /** Unencrypted, unauthenticated connections for `http:` URLs. */
+ @JvmField
+ val CLEARTEXT = Builder(false).build()
+ }
+
+ private fun ConnectionSpec.effectiveCipherSuites(socketEnabledCipherSuites: Array): Array {
+ return if (cipherSuitesAsString != null) {
+ socketEnabledCipherSuites.intersect(cipherSuitesAsString, CipherSuite.ORDER_BY_NAME)
+ } else {
+ socketEnabledCipherSuites
+ }
+ }
+}
diff --git a/paho/src/main/java/in/mohalla/paho/client/mqttv3/IExperimentsConfig.java b/paho/src/main/java/in/mohalla/paho/client/mqttv3/IExperimentsConfig.java
index 3de78393..db55f4b4 100644
--- a/paho/src/main/java/in/mohalla/paho/client/mqttv3/IExperimentsConfig.java
+++ b/paho/src/main/java/in/mohalla/paho/client/mqttv3/IExperimentsConfig.java
@@ -2,4 +2,6 @@
public interface IExperimentsConfig {
int inactivityTimeoutSecs();
+
+ Boolean useNewSSLFlow();
}
diff --git a/paho/src/main/java/in/mohalla/paho/client/mqttv3/MqttAsyncClient.java b/paho/src/main/java/in/mohalla/paho/client/mqttv3/MqttAsyncClient.java
index f00532d2..32422525 100644
--- a/paho/src/main/java/in/mohalla/paho/client/mqttv3/MqttAsyncClient.java
+++ b/paho/src/main/java/in/mohalla/paho/client/mqttv3/MqttAsyncClient.java
@@ -23,10 +23,12 @@
import in.mohalla.paho.client.mqttv3.internal.LocalNetworkModule;
import in.mohalla.paho.client.mqttv3.internal.NetworkModule;
import in.mohalla.paho.client.mqttv3.internal.SSLNetworkModule;
+import in.mohalla.paho.client.mqttv3.internal.SSLNetworkModuleV2;
import in.mohalla.paho.client.mqttv3.internal.TCPNetworkModule;
import in.mohalla.paho.client.mqttv3.internal.security.SSLSocketFactoryFactory;
import in.mohalla.paho.client.mqttv3.internal.websocket.WebSocketNetworkModule;
import in.mohalla.paho.client.mqttv3.internal.websocket.WebSocketSecureNetworkModule;
+import in.mohalla.paho.client.mqttv3.internal.websocket.WebSocketSecureNetworkModuleV2;
import in.mohalla.paho.client.mqttv3.internal.wire.MqttDisconnect;
import in.mohalla.paho.client.mqttv3.internal.wire.MqttPingReq;
import in.mohalla.paho.client.mqttv3.internal.wire.MqttPublish;
@@ -35,17 +37,16 @@
import in.mohalla.paho.client.mqttv3.persist.MemoryPersistence;
import in.mohalla.paho.client.mqttv3.persist.MqttDefaultFilePersistence;
+import javax.net.SocketFactory;
+import javax.net.ssl.SSLSocketFactory;
import java.util.Hashtable;
import java.util.List;
import java.util.Properties;
-import javax.net.SocketFactory;
-import javax.net.ssl.SSLSocketFactory;
-
/**
* Lightweight client for talking to an MQTT server using non-blocking methods that allow an operation to run in the background.
- *
+ *
*
* This class implements the non-blocking {@link IMqttAsyncClient} client interface allowing applications to initiate MQTT actions and then carry on working while the MQTT action
* completes on a background thread. This implementation is compatible with all Java SE runtimes from 1.4.2 and up.
@@ -72,7 +73,7 @@
*
* The message store interface is pluggable. Different stores can be used by implementing the {@link MqttClientPersistence} interface and passing it to the clients constructor.
*
- *
+ *
* @see IMqttAsyncClient
*/
public class MqttAsyncClient implements IMqttAsyncClient
@@ -101,6 +102,8 @@ public class MqttAsyncClient implements IMqttAsyncClient
private IPahoEvents pahoEvents;
+ private IExperimentsConfig experimentsConfig;
+
final static String className = MqttAsyncClient.class.getName();
final private String TAG = "MqttAsyncClient";
@@ -110,13 +113,13 @@ public class MqttAsyncClient implements IMqttAsyncClient
*
* The address of a server can be specified on the constructor. Alternatively a list containing one or more servers can be specified using the
* {@link MqttConnectOptions#setServerURIs(String[]) setServerURIs} method on MqttConnectOptions.
- *
+ *
*
* The serverURI
parameter is typically used with the the clientId
parameter to form a key. The key is used to store and reference messages while they
* are being delivered. Hence the serverURI specified on the constructor must still be specified even if a list of servers is specified on an MqttConnectOptions object. The
* serverURI on the constructor must remain the same across restarts of the client for delivery of messages to be maintained from a given client to a given server or set of
* servers.
- *
+ *
*
* The address of the server to connect to is specified as a URI. Two types of connection are supported tcp://
for a TCP connection and ssl://
for a
* TCP connection secured by SSL/TLS. For example:
@@ -126,7 +129,7 @@ public class MqttAsyncClient implements IMqttAsyncClient
*
* If the port is not specified, it will default to 1883 for tcp://
" URIs, and 8883 for ssl://
URIs.
*
- *
+ *
*
* A client identifier clientId
must be specified and be less that 65535 characters. It must be unique across all clients connecting to the same server. The
* clientId is used by the server to store data related to the client, hence it is important that the clientId remain the same when connecting to a server if durable
@@ -145,15 +148,15 @@ public class MqttAsyncClient implements IMqttAsyncClient
*
SSL Properties - applications can supply SSL settings as a simple Java Properties using {@link MqttConnectOptions#setSSLProperties(Properties)}.
* Use JVM settings - There are a number of standard Java system properties that can be used to configure key and trust stores.
*
- *
+ *
*
* In Java ME, the platform settings are used for SSL connections.
*
- *
+ *
*
* An instance of the default persistence mechanism {@link MqttDefaultFilePersistence} is used by the client. To specify a different persistence mechanism or to turn off
* persistence, use the {@link #MqttAsyncClient(String, String, MqttClientPersistence)} constructor.
- *
+ *
* @param serverURI
* the address of the server to connect to, specified as a URI. Can be overridden using {@link MqttConnectOptions#setServerURIs(String[])}
* @param clientId
@@ -194,13 +197,13 @@ public MqttAsyncClient(
*
* The address of a server can be specified on the constructor. Alternatively a list containing one or more servers can be specified using the
* {@link MqttConnectOptions#setServerURIs(String[]) setServerURIs} method on MqttConnectOptions.
- *
+ *
*
* The serverURI
parameter is typically used with the the clientId
parameter to form a key. The key is used to store and reference messages while they
* are being delivered. Hence the serverURI specified on the constructor must still be specified even if a list of servers is specified on an MqttConnectOptions object. The
* serverURI on the constructor must remain the same across restarts of the client for delivery of messages to be maintained from a given client to a given server or set of
* servers.
- *
+ *
*
* The address of the server to connect to is specified as a URI. Two types of connection are supported tcp://
for a TCP connection and ssl://
for a
* TCP connection secured by SSL/TLS. For example:
@@ -210,7 +213,7 @@ public MqttAsyncClient(
*
* If the port is not specified, it will default to 1883 for tcp://
" URIs, and 8883 for ssl://
URIs.
*
- *
+ *
*
* A client identifier clientId
must be specified and be less that 65535 characters. It must be unique across all clients connecting to the same server. The
* clientId is used by the server to store data related to the client, hence it is important that the clientId remain the same when connecting to a server if durable
@@ -229,7 +232,7 @@ public MqttAsyncClient(
*
SSL Properties - applications can supply SSL settings as a simple Java Properties using {@link MqttConnectOptions#setSSLProperties(Properties)}.
* Use JVM settings - There are a number of standard Java system properties that can be used to configure key and trust stores.
*
- *
+ *
*
* In Java ME, the platform settings are used for SSL connections.
*
@@ -244,7 +247,7 @@ public MqttAsyncClient(
* An implementation of file-based persistence is provided in class {@link MqttDefaultFilePersistence} which will work in all Java SE based systems. If no persistence is
* needed, the persistence parameter can be explicitly set to null
.
*
- *
+ *
* @param serverURI
* the address of the server to connect to, specified as a URI. Can be overridden using {@link MqttConnectOptions#setServerURIs(String[])}
* @param clientId
@@ -298,6 +301,7 @@ public MqttAsyncClient(
this.mqttVersion = mqttVersion;
this.persistence = persistence;
+ this.experimentsConfig = experimentsConfig;
if (this.persistence == null)
{
this.persistence = new MemoryPersistence();
@@ -331,7 +335,7 @@ protected static boolean Character_isHighSurrogate(char ch)
/**
* Factory method to create an array of network modules, one for each of the supplied URIs
- *
+ *
* @param address
* the URI for the server.
* @return a network module appropriate to the specified address.
@@ -410,29 +414,22 @@ else if (factory instanceof SSLSocketFactory)
((TCPNetworkModule) netModule).setReadTimeout(options.getReadTimeout());
break;
case MqttConnectOptions.URI_TYPE_SSL:
+ if(experimentsConfig.useNewSSLFlow()) {
+ netModule = getSSLNetworkModuleV2(address, options);
+ break;
+ }
shortAddress = address.substring(6);
host = getHostName(shortAddress);
port = getPort(shortAddress, 8883);
SSLSocketFactoryFactory factoryFactory = null;
- if (factory == null)
- {
- // try {
- factoryFactory = new SSLSocketFactoryFactory();
- Properties sslClientProps = options.getSSLProperties();
- if (null != sslClientProps)
- factoryFactory.initialize(sslClientProps, null);
- factory = factoryFactory.createSocketFactory(null);
- // }
- // catch (MqttDirectException ex) {
- // throw ExceptionHelper.createMqttException(ex.getCause());
- // }
- }
- else if ((factory instanceof SSLSocketFactory) == false)
- {
- throw ExceptionHelper.createMqttException(MqttException.REASON_CODE_SOCKET_FACTORY_MISMATCH);
+
+ factoryFactory = new SSLSocketFactoryFactory();
+ Properties sslClientProps = options.getSSLProperties();
+ if (null != sslClientProps) {
+ factoryFactory.initialize(sslClientProps, null);
}
+ factory = factoryFactory.createSocketFactory(null);
- // Create the network module...
netModule = new SSLNetworkModule((SSLSocketFactory) factory, host, port, clientId, logger, pahoEvents);
((SSLNetworkModule) netModule).setConnectTimeout(options.getConnectionTimeout());
((SSLNetworkModule) netModule).setSSLhandshakeTimeout(options.getHandshakeTimeout());
@@ -462,23 +459,22 @@ else if (factory instanceof SSLSocketFactory) {
((WebSocketNetworkModule)netModule).setReadTimeout(options.getReadTimeout());
break;
case MqttConnectOptions.URI_TYPE_WSS:
+ if(experimentsConfig.useNewSSLFlow()) {
+ netModule = getWSSNetworkModuleV2(address, options);
+ break;
+ }
shortAddress = address.substring(6);
host = getHostName(shortAddress);
port = getPort(shortAddress, 443);
SSLSocketFactoryFactory wSSFactoryFactory = null;
- if (factory == null) {
- wSSFactoryFactory = new SSLSocketFactoryFactory();
- Properties sslClientProps = options.getSSLProperties();
- if (null != sslClientProps)
- wSSFactoryFactory.initialize(sslClientProps, null);
- factory = wSSFactoryFactory.createSocketFactory(null);
+ wSSFactoryFactory = new SSLSocketFactoryFactory();
+ sslClientProps = options.getSSLProperties();
+ if (null != sslClientProps) {
+ wSSFactoryFactory.initialize(sslClientProps, null);
}
- else if ((factory instanceof SSLSocketFactory) == false) {
- throw ExceptionHelper.createMqttException(MqttException.REASON_CODE_SOCKET_FACTORY_MISMATCH);
- }
+ factory = wSSFactoryFactory.createSocketFactory(null);
- // Create the network module...
netModule = new WebSocketSecureNetworkModule((SSLSocketFactory) factory, address, host, port, clientId, logger, pahoEvents);
((WebSocketSecureNetworkModule)netModule).setConnectTimeout(options.getConnectionTimeout());
((WebSocketSecureNetworkModule)netModule).setSSLhandshakeTimeout(options.getHandshakeTimeout());
@@ -501,6 +497,56 @@ else if ((factory instanceof SSLSocketFactory) == false) {
return netModule;
}
+ private NetworkModule getSSLNetworkModuleV2(String address, MqttConnectOptions options)
+ throws MqttException {
+ String shortAddress = address.substring(6);
+ String host = getHostName(shortAddress);
+ int port = getPort(shortAddress, 443);
+
+ // Create the network module...
+ NetworkModule netModule = new SSLNetworkModuleV2(
+ options.getSocketFactory(),
+ options.getSslSocketFactory(),
+ options.getX509TrustManager(),
+ options.getConnectionSpec(),
+ options.getAlpnProtocolList(),
+ host,
+ port,
+ clientId,
+ logger,
+ pahoEvents
+ );
+ ((SSLNetworkModuleV2) netModule).setConnectTimeout(options.getConnectionTimeout());
+ ((SSLNetworkModuleV2) netModule).setSSLhandshakeTimeout(options.getHandshakeTimeout());
+ ((SSLNetworkModuleV2) netModule).setReadTimeout(options.getReadTimeout());
+ return netModule;
+ }
+
+ private NetworkModule getWSSNetworkModuleV2(String address, MqttConnectOptions options)
+ throws MqttException {
+ String shortAddress = address.substring(6);
+ String host = getHostName(shortAddress);
+ int port = getPort(shortAddress, 443);
+
+ NetworkModule netModule = new WebSocketSecureNetworkModuleV2(
+ options.getSocketFactory(),
+ options.getSslSocketFactory(),
+ options.getX509TrustManager(),
+ options.getConnectionSpec(),
+ options.getAlpnProtocolList(),
+ address,
+ host,
+ port,
+ clientId,
+ logger,
+ pahoEvents
+ );
+ ((WebSocketSecureNetworkModuleV2) netModule).setConnectTimeout(options.getConnectionTimeout());
+ ((WebSocketSecureNetworkModuleV2) netModule).setSSLhandshakeTimeout(options.getHandshakeTimeout());
+ ((WebSocketSecureNetworkModuleV2) netModule).setReadTimeout(options.getReadTimeout());
+ return netModule;
+ }
+
private int getPort(String uri, int defaultPort)
{
int port;
@@ -529,7 +575,7 @@ private String getHostName(String uri)
/*
* (non-Javadoc)
- *
+ *
* @see org.eclipse.paho.client.mqttv3.IMqttAsyncClient#connect(java.lang.Object, org.eclipse.paho.client.mqttv3.IMqttActionListener)
*/
public IMqttToken connect(Object userContext, IMqttActionListener callback) throws MqttException, MqttSecurityException
@@ -539,7 +585,7 @@ public IMqttToken connect(Object userContext, IMqttActionListener callback) thro
/*
* (non-Javadoc)
- *
+ *
* @see org.eclipse.paho.client.mqttv3.IMqttAsyncClient#connect()
*/
public IMqttToken connect() throws MqttException, MqttSecurityException
@@ -549,7 +595,7 @@ public IMqttToken connect() throws MqttException, MqttSecurityException
/*
* (non-Javadoc)
- *
+ *
* @see org.eclipse.paho.client.mqttv3.IMqttAsyncClient#connect(org.eclipse.paho.client.mqttv3.MqttConnectOptions)
*/
public IMqttToken connect(MqttConnectOptions options) throws MqttException, MqttSecurityException
@@ -559,7 +605,7 @@ public IMqttToken connect(MqttConnectOptions options) throws MqttException, Mqtt
/*
* (non-Javadoc)
- *
+ *
* @see org.eclipse.paho.client.mqttv3.IMqttAsyncClient#connect(org.eclipse.paho.client.mqttv3.MqttConnectOptions, java.lang.Object,
* org.eclipse.paho.client.mqttv3.IMqttActionListener)
*/
@@ -601,7 +647,7 @@ public IMqttToken connect(MqttConnectOptions options, Object userContext, IMqttA
/*
* (non-Javadoc)
- *
+ *
* @see org.eclipse.paho.client.mqttv3.IMqttAsyncClient#disconnect(java.lang.Object, org.eclipse.paho.client.mqttv3.IMqttActionListener)
*/
public IMqttToken disconnect(Object userContext, IMqttActionListener callback) throws MqttException
@@ -611,7 +657,7 @@ public IMqttToken disconnect(Object userContext, IMqttActionListener callback) t
/*
* (non-Javadoc)
- *
+ *
* @see org.eclipse.paho.client.mqttv3.IMqttAsyncClient#disconnect()
*/
public IMqttToken disconnect() throws MqttException
@@ -621,7 +667,7 @@ public IMqttToken disconnect() throws MqttException
/*
* (non-Javadoc)
- *
+ *
* @see org.eclipse.paho.client.mqttv3.IMqttAsyncClient#disconnect(long)
*/
public IMqttToken disconnect(long quiesceTimeout) throws MqttException
@@ -631,7 +677,7 @@ public IMqttToken disconnect(long quiesceTimeout) throws MqttException
/*
* (non-Javadoc)
- *
+ *
* @see org.eclipse.paho.client.mqttv3.IMqttAsyncClient#disconnect(long, java.lang.Object, org.eclipse.paho.client.mqttv3.IMqttActionListener)
*/
public IMqttToken disconnect(long quiesceTimeout, Object userContext, IMqttActionListener callback) throws MqttException
@@ -660,7 +706,7 @@ public IMqttToken disconnect(long quiesceTimeout, Object userContext, IMqttActio
/*
* (non-Javadoc)
- *
+ *
* @see org.eclipse.paho.client.mqttv3.IMqttAsyncClient#disconnectForcibly()
*/
public void disconnectForcibly() throws MqttException
@@ -670,7 +716,7 @@ public void disconnectForcibly() throws MqttException
/*
* (non-Javadoc)
- *
+ *
* @see org.eclipse.paho.client.mqttv3.IMqttAsyncClient#disconnectForcibly(long)
*/
public void disconnectForcibly(long disconnectTimeout) throws MqttException
@@ -680,7 +726,7 @@ public void disconnectForcibly(long disconnectTimeout) throws MqttException
/*
* (non-Javadoc)
- *
+ *
* @see org.eclipse.paho.client.mqttv3.IMqttAsyncClient#disconnectForcibly(long, long)
*/
public void disconnectForcibly(long quiesceTimeout, long disconnectTimeout) throws MqttException
@@ -690,24 +736,24 @@ public void disconnectForcibly(long quiesceTimeout, long disconnectTimeout) thro
/*
* (non-Javadoc)
- *
+ *
* @see IMqttAsyncClient#isConnected()
*/
public boolean isConnected()
{
return comms.isConnected();
}
-
+
public boolean isConnecting()
{
return comms.isConnecting();
}
-
+
public boolean isDisconnecting()
{
return comms.isDisconnecting();
}
-
+
public boolean isDisconnected()
{
return comms.isDisconnected();
@@ -715,7 +761,7 @@ public boolean isDisconnected()
/*
* (non-Javadoc)
- *
+ *
* @see IMqttAsyncClient#getClientId()
*/
public String getClientId()
@@ -735,7 +781,7 @@ public void setClientId(String clientId)
/*
* (non-Javadoc)
- *
+ *
* @see IMqttAsyncClient#getServerURI()
*/
public String getServerURI()
@@ -769,7 +815,7 @@ public void setServerURI(String serverURI) throws MqttException
*
* When you build an application, the design of the topic tree should take into account the following principles of topic name syntax and semantics:
*
- *
+ *
*
* - A topic must be at least one character long.
* - Topic names are case sensitive. For example, ACCOUNTS and Accounts are two different topics.
@@ -778,17 +824,17 @@ public void setServerURI(String serverURI) throws MqttException
* - A leading "/" creates a distinct topic. For example, /finance is different from finance. /finance matches "+/+" and "/+", but not "+".
* - Do not include the null character (Unicode \x0000) in any topic.
*
- *
+ *
*
* The following principles apply to the construction and content of a topic tree:
*
- *
+ *
*
* - The length is limited to 64k but within that there are no limits to the number of levels in a topic tree.
* - There can be any number of root nodes; that is, there can be any number of topic trees.
*
*
- *
+ *
* @param topic
* the topic to use, for example "finance/stock/ibm".
* @return an MqttTopic object, which can be used to publish messages to the topic.
@@ -811,7 +857,7 @@ protected MqttTopic getTopic(String topic)
/*
* (non-Javadoc) Check and send a ping if needed. By default, client sends PingReq to server to keep the connection to server. For some platforms which cannot use this
* mechanism, such as Android, developer needs to handle the ping request manually with this method.
- *
+ *
* @throws MqttException for other errors encountered while publishing the message.
*/
public IMqttToken checkPing(Object userContext, IMqttActionListener callback) throws MqttException
@@ -831,10 +877,10 @@ public void checkActivity()
{
comms.checkActivity();
}
-
+
/*
* (non-Javadoc)
- *
+ *
* @see org.eclipse.paho.client.mqttv3.IMqttAsyncClient#subscribe(java.lang.String, int, java.lang.Object, org.eclipse.paho.client.mqttv3.IMqttActionListener)
*/
public IMqttToken subscribe(String topicFilter, int qos, Object userContext, IMqttActionListener callback) throws MqttException
@@ -844,7 +890,7 @@ public IMqttToken subscribe(String topicFilter, int qos, Object userContext, IMq
/*
* (non-Javadoc)
- *
+ *
* @see org.eclipse.paho.client.mqttv3.IMqttAsyncClient#subscribe(java.lang.String, int)
*/
public IMqttToken subscribe(String topicFilter, int qos) throws MqttException
@@ -854,7 +900,7 @@ public IMqttToken subscribe(String topicFilter, int qos) throws MqttException
/*
* (non-Javadoc)
- *
+ *
* @see org.eclipse.paho.client.mqttv3.IMqttAsyncClient#subscribe(java.lang.String[], int[])
*/
public IMqttToken subscribe(String[] topicFilters, int[] qos) throws MqttException
@@ -864,7 +910,7 @@ public IMqttToken subscribe(String[] topicFilters, int[] qos) throws MqttExcepti
/*
* (non-Javadoc)
- *
+ *
* @see org.eclipse.paho.client.mqttv3.IMqttAsyncClient#subscribe(java.lang.String[], int[], java.lang.Object, org.eclipse.paho.client.mqttv3.IMqttActionListener)
*/
public IMqttToken subscribe(String[] topicFilters, int[] qos, Object userContext, IMqttActionListener callback) throws MqttException
@@ -906,7 +952,7 @@ public IMqttToken subscribe(String[] topicFilters, int[] qos, Object userContext
/*
* (non-Javadoc)
- *
+ *
* @see org.eclipse.paho.client.mqttv3.IMqttAsyncClient#unsubscribe(java.lang.String, java.lang.Object, org.eclipse.paho.client.mqttv3.IMqttActionListener)
*/
public IMqttToken unsubscribe(String topicFilter, Object userContext, IMqttActionListener callback) throws MqttException
@@ -916,7 +962,7 @@ public IMqttToken unsubscribe(String topicFilter, Object userContext, IMqttActio
/*
* (non-Javadoc)
- *
+ *
* @see org.eclipse.paho.client.mqttv3.IMqttAsyncClient#unsubscribe(java.lang.String)
*/
public IMqttToken unsubscribe(String topicFilter) throws MqttException
@@ -926,7 +972,7 @@ public IMqttToken unsubscribe(String topicFilter) throws MqttException
/*
* (non-Javadoc)
- *
+ *
* @see org.eclipse.paho.client.mqttv3.IMqttAsyncClient#unsubscribe(java.lang.String[])
*/
public IMqttToken unsubscribe(String[] topicFilters) throws MqttException
@@ -936,7 +982,7 @@ public IMqttToken unsubscribe(String[] topicFilters) throws MqttException
/*
* (non-Javadoc)
- *
+ *
* @see org.eclipse.paho.client.mqttv3.IMqttAsyncClient#unsubscribe(java.lang.String[], java.lang.Object, org.eclipse.paho.client.mqttv3.IMqttActionListener)
*/
public IMqttToken unsubscribe(String[] topicFilters, Object userContext, IMqttActionListener callback) throws MqttException
@@ -975,7 +1021,7 @@ public IMqttToken unsubscribe(String[] topicFilters, Object userContext, IMqttAc
/*
* (non-Javadoc)
- *
+ *
* @see IMqttAsyncClient#setCallback(MqttCallback)
*/
public void setCallback(MqttCallback callback)
@@ -989,7 +1035,7 @@ public void setCallback(MqttCallback callback)
* When cleanSession is set to false, an application must ensure it uses the same client identifier when it reconnects to the server to resume state and maintain assured
* message delivery.
*
- *
+ *
* @return a generated client identifier
* @see MqttConnectOptions#setCleanSession(boolean)
*/
@@ -1001,7 +1047,7 @@ public static String generateClientId()
/*
* (non-Javadoc)
- *
+ *
* @see IMqttAsyncClient#getPendingDeliveryTokens()
*/
public IMqttDeliveryToken[] getPendingDeliveryTokens()
@@ -1011,7 +1057,7 @@ public IMqttDeliveryToken[] getPendingDeliveryTokens()
/*
* (non-Javadoc)
- *
+ *
* @see org.eclipse.paho.client.mqttv3.IMqttAsyncClient#publish(java.lang.String, byte[], int, boolean, java.lang.Object, org.eclipse.paho.client.mqttv3.IMqttActionListener)
*/
public IMqttDeliveryToken publish(String topic, byte[] payload, int qos, boolean retained, Object userContext, IMqttActionListener callback) throws MqttException,
@@ -1025,7 +1071,7 @@ public IMqttDeliveryToken publish(String topic, byte[] payload, int qos, boolean
/*
* (non-Javadoc)
- *
+ *
* @see org.eclipse.paho.client.mqttv3.IMqttAsyncClient#publish(java.lang.String, byte[], int, boolean)
*/
public IMqttDeliveryToken publish(String topic, byte[] payload, int qos, boolean retained) throws MqttException, MqttPersistenceException
@@ -1035,7 +1081,7 @@ public IMqttDeliveryToken publish(String topic, byte[] payload, int qos, boolean
/*
* (non-Javadoc)
- *
+ *
* @see org.eclipse.paho.client.mqttv3.IMqttAsyncClient#publish(java.lang.String, org.eclipse.paho.client.mqttv3.MqttMessage)
*/
public IMqttDeliveryToken publish(String topic, MqttMessage message) throws MqttException, MqttPersistenceException
@@ -1045,7 +1091,7 @@ public IMqttDeliveryToken publish(String topic, MqttMessage message) throws Mqtt
/*
* (non-Javadoc)
- *
+ *
* @see org.eclipse.paho.client.mqttv3.IMqttAsyncClient#publish(java.lang.String, org.eclipse.paho.client.mqttv3.MqttMessage, java.lang.Object,
* org.eclipse.paho.client.mqttv3.IMqttActionListener)
*/
@@ -1073,7 +1119,7 @@ public IMqttDeliveryToken publish(String topic, MqttMessage message, Object user
/*
* (non-Javadoc)
- *
+ *
* @see org.eclipse.paho.client.mqttv3.IMqttAsyncClient#close()
*/
public void close() throws MqttException
diff --git a/paho/src/main/java/in/mohalla/paho/client/mqttv3/MqttConnectOptions.java b/paho/src/main/java/in/mohalla/paho/client/mqttv3/MqttConnectOptions.java
index 221ee1a0..211ee070 100644
--- a/paho/src/main/java/in/mohalla/paho/client/mqttv3/MqttConnectOptions.java
+++ b/paho/src/main/java/in/mohalla/paho/client/mqttv3/MqttConnectOptions.java
@@ -15,6 +15,7 @@
*/
package in.mohalla.paho.client.mqttv3;
+import in.mohalla.paho.client.mqttv3.internal.tls.CertificateChainCleaner;
import in.mohalla.paho.client.mqttv3.internal.wire.UserProperty;
import java.net.URI;
@@ -23,6 +24,8 @@
import java.util.Properties;
import javax.net.SocketFactory;
+import javax.net.ssl.SSLSocketFactory;
+import javax.net.ssl.X509TrustManager;
/**
* Holds the set of options that control how the client connects to a server.
@@ -78,6 +81,14 @@ public class MqttConnectOptions
private SocketFactory socketFactory;
+ private SSLSocketFactory sslSocketFactory;
+
+ private X509TrustManager x509TrustManager;
+
+ private ConnectionSpec connectionSpec;
+
+ private List alpnProtocolList;
+
private Properties sslClientProps = null;
private boolean cleanSession = CLEAN_SESSION_DEFAULT;
@@ -151,7 +162,7 @@ public void setUserName(String userName)
{
if ((userName != null) && (userName.trim().equals("")))
{
- throw new IllegalArgumentException();
+ throw new IllegalArgumentException("Username is empty");
}
this.userName = userName;
}
@@ -371,6 +382,39 @@ public void setSocketFactory(SocketFactory socketFactory)
this.socketFactory = socketFactory;
}
+ public SSLSocketFactory getSslSocketFactory() {
+ return sslSocketFactory;
+ }
+
+ public void setSslSocketFactory(SSLSocketFactory sslSocketFactory) {
+ this.sslSocketFactory = sslSocketFactory;
+ }
+
+ public X509TrustManager getX509TrustManager() {
+ return x509TrustManager;
+ }
+
+ public void setX509TrustManager(X509TrustManager x509TrustManager) {
+ this.x509TrustManager = x509TrustManager;
+ }
+
+ public ConnectionSpec getConnectionSpec() {
+ return connectionSpec;
+ }
+
+ public void setConnectionSpec(
+ ConnectionSpec connectionSpec) {
+ this.connectionSpec = connectionSpec;
+ }
+
+ public List getAlpnProtocolList() {
+ return alpnProtocolList;
+ }
+
+ public void setAlpnProtocolList(List alpnProtocolList) {
+ this.alpnProtocolList = alpnProtocolList;
+ }
+
/**
* Returns the topic to be used for last will and testament (LWT).
*
diff --git a/paho/src/main/java/in/mohalla/paho/client/mqttv3/Protocol.kt b/paho/src/main/java/in/mohalla/paho/client/mqttv3/Protocol.kt
new file mode 100644
index 00000000..bf64dece
--- /dev/null
+++ b/paho/src/main/java/in/mohalla/paho/client/mqttv3/Protocol.kt
@@ -0,0 +1,5 @@
+package `in`.mohalla.paho.client.mqttv3
+
+data class Protocol(
+ val name: String
+)
diff --git a/paho/src/main/java/in/mohalla/paho/client/mqttv3/SuppressSignatureCheck.kt b/paho/src/main/java/in/mohalla/paho/client/mqttv3/SuppressSignatureCheck.kt
new file mode 100644
index 00000000..99d5f31c
--- /dev/null
+++ b/paho/src/main/java/in/mohalla/paho/client/mqttv3/SuppressSignatureCheck.kt
@@ -0,0 +1,12 @@
+package `in`.mohalla.paho.client.mqttv3
+
+import java.lang.annotation.Documented
+import kotlin.annotation.AnnotationRetention.BINARY
+import kotlin.annotation.AnnotationTarget.CLASS
+import kotlin.annotation.AnnotationTarget.CONSTRUCTOR
+import kotlin.annotation.AnnotationTarget.FUNCTION
+
+@Retention(BINARY)
+@Documented
+@Target(CONSTRUCTOR, CLASS, FUNCTION)
+annotation class SuppressSignatureCheck
diff --git a/paho/src/main/java/in/mohalla/paho/client/mqttv3/Util.kt b/paho/src/main/java/in/mohalla/paho/client/mqttv3/Util.kt
new file mode 100644
index 00000000..d202b0de
--- /dev/null
+++ b/paho/src/main/java/in/mohalla/paho/client/mqttv3/Util.kt
@@ -0,0 +1,68 @@
+package `in`.mohalla.paho.client.mqttv3
+
+internal fun readFieldOrNull(instance: Any, fieldType: Class, fieldName: String): T? {
+ var c: Class<*> = instance.javaClass
+ while (c != Any::class.java) {
+ try {
+ val field = c.getDeclaredField(fieldName)
+ field.isAccessible = true
+ val value = field.get(instance)
+ return if (!fieldType.isInstance(value)) null else fieldType.cast(value)
+ } catch (_: NoSuchFieldException) {
+ }
+
+ c = c.superclass
+ }
+
+ // Didn't find the field we wanted. As a last gasp attempt,
+ // try to find the value on a delegate.
+ if (fieldName != "delegate") {
+ val delegate = readFieldOrNull(instance, Any::class.java, "delegate")
+ if (delegate != null) return readFieldOrNull(delegate, fieldType, fieldName)
+ }
+
+ return null
+}
+
+internal fun Array.hasIntersection(
+ other: Array?,
+ comparator: Comparator
+): Boolean {
+ if (isEmpty() || other == null || other.isEmpty()) {
+ return false
+ }
+ for (a in this) {
+ for (b in other) {
+ if (comparator.compare(a, b) == 0) {
+ return true
+ }
+ }
+ }
+ return false
+}
+
+internal fun Array.intersect(
+ other: Array,
+ comparator: Comparator
+): Array {
+ val result = mutableListOf()
+ for (a in this) {
+ for (b in other) {
+ if (comparator.compare(a, b) == 0) {
+ result.add(a)
+ break
+ }
+ }
+ }
+ return result.toTypedArray()
+}
+
+internal fun Array.indexOf(value: String, comparator: Comparator): Int =
+ indexOfFirst { comparator.compare(it, value) == 0 }
+
+@Suppress("UNCHECKED_CAST")
+internal fun Array.concat(value: String): Array {
+ val result = copyOf(size + 1)
+ result[result.lastIndex] = value
+ return result as Array
+}
diff --git a/paho/src/main/java/in/mohalla/paho/client/mqttv3/internal/SSLNetworkModule.java b/paho/src/main/java/in/mohalla/paho/client/mqttv3/internal/SSLNetworkModule.java
index e4051492..42d91ede 100644
--- a/paho/src/main/java/in/mohalla/paho/client/mqttv3/internal/SSLNetworkModule.java
+++ b/paho/src/main/java/in/mohalla/paho/client/mqttv3/internal/SSLNetworkModule.java
@@ -3,11 +3,11 @@
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
- * and Eclipse Distribution License v1.0 which accompany this distribution.
+ * and Eclipse Distribution License v1.0 which accompany this distribution.
*
- * The Eclipse Public License is available at
+ * The Eclipse Public License is available at
* http://www.eclipse.org/legal/epl-v10.html
- * and the Eclipse Distribution License is available at
+ * and the Eclipse Distribution License is available at
* http://www.eclipse.org/org/documents/edl-v10.php.
*
* Contributors:
@@ -63,7 +63,6 @@ public void setEnabledCiphers(String[] enabledCiphers)
this.enabledCiphers = enabledCiphers;
if ((socket != null) && (enabledCiphers != null))
{
-
((SSLSocket) socket).setEnabledCipherSuites(enabledCiphers);
}
}
@@ -103,7 +102,7 @@ public void start() throws IOException, MqttException
throw ex;
}
}
-
+
public void stop() throws IOException
{
// In case of SSLSocket we should not try to shutdownOutput and shutdownInput it would result
diff --git a/paho/src/main/java/in/mohalla/paho/client/mqttv3/internal/SSLNetworkModuleV2.java b/paho/src/main/java/in/mohalla/paho/client/mqttv3/internal/SSLNetworkModuleV2.java
new file mode 100644
index 00000000..362d8440
--- /dev/null
+++ b/paho/src/main/java/in/mohalla/paho/client/mqttv3/internal/SSLNetworkModuleV2.java
@@ -0,0 +1,122 @@
+/*******************************************************************************
+ * Copyright (c) 2009, 2014 IBM Corp.
+ *
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * and Eclipse Distribution License v1.0 which accompany this distribution.
+ *
+ * The Eclipse Public License is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ * and the Eclipse Distribution License is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * Contributors:
+ * Dave Locke - initial API and implementation and/or initial documentation
+ */
+package in.mohalla.paho.client.mqttv3.internal;
+
+import in.mohalla.paho.client.mqttv3.ConnectionSpec;
+import in.mohalla.paho.client.mqttv3.ILogger;
+import in.mohalla.paho.client.mqttv3.IPahoEvents;
+import in.mohalla.paho.client.mqttv3.MqttException;
+import in.mohalla.paho.client.mqttv3.Protocol;
+import in.mohalla.paho.client.mqttv3.internal.platform.Platform;
+import in.mohalla.paho.client.mqttv3.internal.tls.CertificateChainCleaner;
+
+import javax.net.SocketFactory;
+import javax.net.ssl.SSLSocket;
+import javax.net.ssl.SSLSocketFactory;
+import javax.net.ssl.X509TrustManager;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * A network module for connecting over SSL.
+ */
+public class SSLNetworkModuleV2 extends TCPNetworkModule {
+ private SocketFactory socketFactory;
+ private SSLSocketFactory sslSocketFactory;
+ private X509TrustManager x509TrustManager;
+ private ConnectionSpec connectionSpec;
+ private List alpnProtocolList;
+
+ private int handshakeTimeoutSecs;
+
+ final static String className = SSLNetworkModuleV2.class.getName();
+
+ private final String TAG = "SSLNETWORKMODULE";
+
+ /**
+ * Constructs a new SSLNetworkModule using the specified host and port. The supplied
+ * SSLSocketFactory is used to supply the network socket.
+ */
+
+ public SSLNetworkModuleV2(
+ SocketFactory socketFactory,
+ SSLSocketFactory sslSocketFactory,
+ X509TrustManager x509TrustManager,
+ ConnectionSpec connectionSpec,
+ List alpnProtocolList,
+ String host,
+ int port,
+ String resourceContext,
+ ILogger logger,
+ IPahoEvents pahoEvents
+ ) {
+ super(socketFactory, host, port, resourceContext, logger, pahoEvents);
+ this.socketFactory = socketFactory;
+ this.sslSocketFactory = sslSocketFactory;
+ this.x509TrustManager = x509TrustManager;
+ this.connectionSpec = connectionSpec;
+ this.alpnProtocolList = alpnProtocolList;
+ }
+
+ public void setSSLhandshakeTimeout(int timeout) {
+ this.handshakeTimeoutSecs = timeout;
+ }
+
+ public void start() throws IOException, MqttException {
+ super.start();
+ long socketStartTime = System.nanoTime();
+ try {
+ pahoEvents.onSSLSocketAttempt(port, host, socket.getSoTimeout());
+
+ socket = sslSocketFactory.createSocket(socket, host, port, true);
+
+ connectionSpec.apply((SSLSocket) socket, false);
+ if (connectionSpec.supportsTlsExtensions()) {
+ Platform.get().configureTlsExtensions((SSLSocket) socket, host, alpnProtocolList);
+ }
+ long socketEndTime = System.nanoTime();
+ pahoEvents.onSSLSocketSuccess(port, host, socket.getSoTimeout(),
+ TimeUnit.NANOSECONDS.toMillis(socketEndTime - socketStartTime));
+
+ int soTimeout = socket.getSoTimeout();
+ // RTC 765: Set a timeout to avoid the SSL handshake being blocked indefinitely
+ socket.setSoTimeout(this.handshakeTimeoutSecs * 1000);
+ long handshakeStartTime = System.nanoTime();
+ ((SSLSocket) socket).startHandshake();
+ long handshakeEndTime = System.nanoTime();
+
+ pahoEvents.onSSLHandshakeSuccess(port, host, socket.getSoTimeout(),
+ TimeUnit.NANOSECONDS.toMillis(handshakeEndTime - handshakeStartTime));
+
+ // reset timeout to default value
+ socket.setSoTimeout(soTimeout);
+ } catch (IOException ex) {
+ long socketEndTime = System.nanoTime();
+ pahoEvents.onSSLSocketFailure(port, host, socket.getSoTimeout(), ex,
+ TimeUnit.NANOSECONDS.toMillis(socketEndTime - socketStartTime));
+ throw ex;
+ }
+ }
+
+ public void stop() throws IOException {
+ // In case of SSLSocket we should not try to shutdownOutput and shutdownInput it would result
+ // in java.lang.UnsupportedOperationException. only SSLSocket.close() is enough to close
+ // an SSLSocket.
+ socket.close();
+ }
+}
\ No newline at end of file
diff --git a/paho/src/main/java/in/mohalla/paho/client/mqttv3/internal/platform/Android10Platform.kt b/paho/src/main/java/in/mohalla/paho/client/mqttv3/internal/platform/Android10Platform.kt
new file mode 100644
index 00000000..9fad3e89
--- /dev/null
+++ b/paho/src/main/java/in/mohalla/paho/client/mqttv3/internal/platform/Android10Platform.kt
@@ -0,0 +1,89 @@
+/*
+ * Copyright (C) 2016 Square, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package `in`.mohalla.paho.client.mqttv3.internal.platform
+
+import android.annotation.SuppressLint
+import android.os.Build
+import android.security.NetworkSecurityPolicy
+import android.util.CloseGuard
+import javax.net.ssl.SSLSocket
+import javax.net.ssl.SSLSocketFactory
+import javax.net.ssl.X509TrustManager
+import `in`.mohalla.paho.client.mqttv3.Protocol
+import `in`.mohalla.paho.client.mqttv3.SuppressSignatureCheck
+import `in`.mohalla.paho.client.mqttv3.internal.platform.android.Android10SocketAdapter
+import `in`.mohalla.paho.client.mqttv3.internal.platform.android.AndroidCertificateChainCleaner
+import `in`.mohalla.paho.client.mqttv3.internal.platform.android.AndroidSocketAdapter
+import `in`.mohalla.paho.client.mqttv3.internal.platform.android.BouncyCastleSocketAdapter
+import `in`.mohalla.paho.client.mqttv3.internal.platform.android.ConscryptSocketAdapter
+import `in`.mohalla.paho.client.mqttv3.internal.platform.android.DeferredSocketAdapter
+import `in`.mohalla.paho.client.mqttv3.internal.tls.CertificateChainCleaner
+
+/** Android 29+. */
+@SuppressSignatureCheck
+class Android10Platform : Platform() {
+ private val socketAdapters = listOfNotNull(
+ Android10SocketAdapter.buildIfSupported(),
+ DeferredSocketAdapter(AndroidSocketAdapter.playProviderFactory),
+ // Delay and Defer any initialisation of Conscrypt and BouncyCastle
+ DeferredSocketAdapter(ConscryptSocketAdapter.factory),
+ DeferredSocketAdapter(BouncyCastleSocketAdapter.factory)
+ ).filter { it.isSupported() }
+
+ override fun trustManager(sslSocketFactory: SSLSocketFactory): X509TrustManager? =
+ socketAdapters.find { it.matchesSocketFactory(sslSocketFactory) }
+ ?.trustManager(sslSocketFactory)
+
+ override fun configureTlsExtensions(sslSocket: SSLSocket, hostname: String?, protocols: List) {
+ // No TLS extensions if the socket class is custom.
+ socketAdapters.find { it.matchesSocket(sslSocket) }
+ ?.configureTlsExtensions(sslSocket, hostname, protocols)
+ }
+
+ override fun getSelectedProtocol(sslSocket: SSLSocket): String? =
+ // No TLS extensions if the socket class is custom.
+ socketAdapters.find { it.matchesSocket(sslSocket) }?.getSelectedProtocol(sslSocket)
+
+ override fun getStackTraceForCloseable(closer: String): Any? {
+ return if (Build.VERSION.SDK_INT >= 30) {
+ CloseGuard().apply { open(closer) }
+ } else {
+ super.getStackTraceForCloseable(closer)
+ }
+ }
+
+ override fun logCloseableLeak(message: String, stackTrace: Any?) {
+ if (Build.VERSION.SDK_INT >= 30) {
+ (stackTrace as CloseGuard).warnIfOpen()
+ } else {
+ // Unable to report via CloseGuard. As a last-ditch effort, send it to the logger.
+ super.logCloseableLeak(message, stackTrace)
+ }
+ }
+
+ @SuppressLint("NewApi")
+ override fun isCleartextTrafficPermitted(hostname: String): Boolean =
+ NetworkSecurityPolicy.getInstance().isCleartextTrafficPermitted(hostname)
+
+ override fun buildCertificateChainCleaner(trustManager: X509TrustManager): CertificateChainCleaner =
+ AndroidCertificateChainCleaner.buildIfSupported(trustManager) ?: super.buildCertificateChainCleaner(trustManager)
+
+ companion object {
+ val isSupported: Boolean = isAndroid && Build.VERSION.SDK_INT >= 29
+
+ fun buildIfSupported(): Platform? = if (isSupported) Android10Platform() else null
+ }
+}
diff --git a/paho/src/main/java/in/mohalla/paho/client/mqttv3/internal/platform/AndroidPlatform.kt b/paho/src/main/java/in/mohalla/paho/client/mqttv3/internal/platform/AndroidPlatform.kt
new file mode 100644
index 00000000..08b71a47
--- /dev/null
+++ b/paho/src/main/java/in/mohalla/paho/client/mqttv3/internal/platform/AndroidPlatform.kt
@@ -0,0 +1,155 @@
+/*
+ * Copyright (C) 2016 Square, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package `in`.mohalla.paho.client.mqttv3.internal.platform
+
+import android.os.Build
+import android.security.NetworkSecurityPolicy
+import java.io.IOException
+import java.lang.reflect.InvocationTargetException
+import java.lang.reflect.Method
+import java.net.InetSocketAddress
+import java.net.Socket
+import java.security.cert.TrustAnchor
+import java.security.cert.X509Certificate
+import javax.net.ssl.SSLSocket
+import javax.net.ssl.SSLSocketFactory
+import javax.net.ssl.X509TrustManager
+import `in`.mohalla.paho.client.mqttv3.Protocol
+import `in`.mohalla.paho.client.mqttv3.SuppressSignatureCheck
+import `in`.mohalla.paho.client.mqttv3.internal.platform.android.AndroidCertificateChainCleaner
+import `in`.mohalla.paho.client.mqttv3.internal.platform.android.AndroidSocketAdapter
+import `in`.mohalla.paho.client.mqttv3.internal.platform.android.BouncyCastleSocketAdapter
+import `in`.mohalla.paho.client.mqttv3.internal.platform.android.ConscryptSocketAdapter
+import `in`.mohalla.paho.client.mqttv3.internal.platform.android.DeferredSocketAdapter
+import `in`.mohalla.paho.client.mqttv3.internal.platform.android.StandardAndroidSocketAdapter
+import `in`.mohalla.paho.client.mqttv3.internal.tls.CertificateChainCleaner
+import `in`.mohalla.paho.client.mqttv3.internal.tls.TrustRootIndex
+
+/** Android 5+. */
+@SuppressSignatureCheck
+class AndroidPlatform : Platform() {
+ private val socketAdapters = listOfNotNull(
+ StandardAndroidSocketAdapter.buildIfSupported(),
+ DeferredSocketAdapter(AndroidSocketAdapter.playProviderFactory),
+ // Delay and Defer any initialisation of Conscrypt and BouncyCastle
+ DeferredSocketAdapter(ConscryptSocketAdapter.factory),
+ DeferredSocketAdapter(BouncyCastleSocketAdapter.factory)
+ ).filter { it.isSupported() }
+
+ @Throws(IOException::class)
+ override fun connectSocket(
+ socket: Socket,
+ address: InetSocketAddress,
+ connectTimeout: Int
+ ) {
+ try {
+ socket.connect(address, connectTimeout)
+ } catch (e: ClassCastException) {
+ // On android 8.0, socket.connect throws a ClassCastException due to a bug
+ // see https://issuetracker.google.com/issues/63649622
+ if (Build.VERSION.SDK_INT == 26) {
+ throw IOException("Exception in connect", e)
+ } else {
+ throw e
+ }
+ }
+ }
+
+ override fun trustManager(sslSocketFactory: SSLSocketFactory): X509TrustManager? =
+ socketAdapters.find { it.matchesSocketFactory(sslSocketFactory) }
+ ?.trustManager(sslSocketFactory)
+
+ override fun configureTlsExtensions(
+ sslSocket: SSLSocket,
+ hostname: String?,
+ protocols: List<@JvmSuppressWildcards Protocol>
+ ) {
+ // No TLS extensions if the socket class is custom.
+ socketAdapters.find { it.matchesSocket(sslSocket) }
+ ?.configureTlsExtensions(sslSocket, hostname, protocols)
+ }
+
+ override fun getSelectedProtocol(sslSocket: SSLSocket): String? =
+ // No TLS extensions if the socket class is custom.
+ socketAdapters.find { it.matchesSocket(sslSocket) }?.getSelectedProtocol(sslSocket)
+
+ override fun isCleartextTrafficPermitted(hostname: String): Boolean = when {
+ Build.VERSION.SDK_INT >= 24 -> NetworkSecurityPolicy.getInstance()
+ .isCleartextTrafficPermitted(hostname)
+ Build.VERSION.SDK_INT >= 23 -> NetworkSecurityPolicy.getInstance().isCleartextTrafficPermitted
+ else -> true
+ }
+
+ override fun buildCertificateChainCleaner(trustManager: X509TrustManager): CertificateChainCleaner =
+ AndroidCertificateChainCleaner.buildIfSupported(trustManager)
+ ?: super.buildCertificateChainCleaner(trustManager)
+
+ override fun buildTrustRootIndex(trustManager: X509TrustManager): TrustRootIndex = try {
+ // From org.conscrypt.TrustManagerImpl, we want the method with this signature:
+ // private TrustAnchor findTrustAnchorByIssuerAndSignature(X509Certificate lastCert);
+ val method = trustManager.javaClass.getDeclaredMethod(
+ "findTrustAnchorByIssuerAndSignature",
+ X509Certificate::class.java
+ )
+ method.isAccessible = true
+ CustomTrustRootIndex(trustManager, method)
+ } catch (e: NoSuchMethodException) {
+ super.buildTrustRootIndex(trustManager)
+ }
+
+ /**
+ * A trust manager for Android applications that customize the trust manager.
+ *
+ * This class exploits knowledge of Android implementation details. This class is potentially
+ * much faster to initialize than [BasicTrustRootIndex] because it doesn't need to load and
+ * index trusted CA certificates.
+ */
+ internal data class CustomTrustRootIndex(
+ private val trustManager: X509TrustManager,
+ private val findByIssuerAndSignatureMethod: Method
+ ) : TrustRootIndex {
+ override fun findByIssuerAndSignature(cert: X509Certificate): X509Certificate? {
+ return try {
+ val trustAnchor = findByIssuerAndSignatureMethod.invoke(
+ trustManager,
+ cert
+ ) as TrustAnchor
+ trustAnchor.trustedCert
+ } catch (e: IllegalAccessException) {
+ throw AssertionError("unable to get issues and signature", e)
+ } catch (_: InvocationTargetException) {
+ null
+ }
+ }
+ }
+
+ companion object {
+ val isSupported: Boolean = when {
+ !isAndroid -> false
+ Build.VERSION.SDK_INT >= 30 -> false // graylisted methods are banned
+ else -> {
+ // Fail Fast
+ check(
+ Build.VERSION.SDK_INT >= 21
+ ) { "Expected Android API level 21+ but was ${Build.VERSION.SDK_INT}" }
+
+ true
+ }
+ }
+
+ fun buildIfSupported(): Platform? = if (isSupported) AndroidPlatform() else null
+ }
+}
diff --git a/paho/src/main/java/in/mohalla/paho/client/mqttv3/internal/platform/BouncyCastlePlatform.kt b/paho/src/main/java/in/mohalla/paho/client/mqttv3/internal/platform/BouncyCastlePlatform.kt
new file mode 100644
index 00000000..f5b6d137
--- /dev/null
+++ b/paho/src/main/java/in/mohalla/paho/client/mqttv3/internal/platform/BouncyCastlePlatform.kt
@@ -0,0 +1,104 @@
+/*
+ * Copyright (C) 2019 Square, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package `in`.mohalla.paho.client.mqttv3.internal.platform
+
+import java.security.KeyStore
+import java.security.Provider
+import javax.net.ssl.SSLContext
+import javax.net.ssl.SSLSocket
+import javax.net.ssl.SSLSocketFactory
+import javax.net.ssl.TrustManagerFactory
+import javax.net.ssl.X509TrustManager
+import org.bouncycastle.jsse.BCSSLSocket
+import org.bouncycastle.jsse.provider.BouncyCastleJsseProvider
+import `in`.mohalla.paho.client.mqttv3.Protocol
+
+/**
+ * Platform using BouncyCastle if installed as the first Security Provider.
+ *
+ * Requires org.bouncycastle:bctls-jdk15on on the classpath.
+ */
+class BouncyCastlePlatform private constructor() : Platform() {
+ private val provider: Provider = BouncyCastleJsseProvider()
+
+ override fun newSSLContext(): SSLContext =
+ SSLContext.getInstance("TLS", provider)
+
+ override fun platformTrustManager(): X509TrustManager {
+ val factory = TrustManagerFactory.getInstance(
+ "PKIX",
+ BouncyCastleJsseProvider.PROVIDER_NAME
+ )
+ factory.init(null as KeyStore?)
+ val trustManagers = factory.trustManagers!!
+ check(trustManagers.size == 1 && trustManagers[0] is X509TrustManager) {
+ "Unexpected default trust managers: ${trustManagers.contentToString()}"
+ }
+ return trustManagers[0] as X509TrustManager
+ }
+
+ override fun trustManager(sslSocketFactory: SSLSocketFactory): X509TrustManager =
+ throw UnsupportedOperationException(
+ "clientBuilder.sslSocketFactory(SSLSocketFactory) not supported with BouncyCastle"
+ )
+
+ override fun configureTlsExtensions(
+ sslSocket: SSLSocket,
+ hostname: String?,
+ protocols: List<@JvmSuppressWildcards Protocol>
+ ) {
+ if (sslSocket is BCSSLSocket) {
+ val sslParameters = sslSocket.parameters
+
+ // Enable ALPN.
+ val names = alpnProtocolNames(protocols)
+ sslParameters.applicationProtocols = names.toTypedArray()
+
+ sslSocket.parameters = sslParameters
+ } else {
+ super.configureTlsExtensions(sslSocket, hostname, protocols)
+ }
+ }
+
+ override fun getSelectedProtocol(sslSocket: SSLSocket): String? =
+ if (sslSocket is BCSSLSocket) {
+ when (val protocol = (sslSocket as BCSSLSocket).applicationProtocol) {
+ // Handles both un-configured and none selected.
+ null, "" -> null
+ else -> protocol
+ }
+ } else {
+ super.getSelectedProtocol(sslSocket)
+ }
+
+ companion object {
+ val isSupported: Boolean = try {
+ // Trigger an early exception over a fatal error, prefer a RuntimeException over Error.
+ Class.forName(
+ "org.bouncycastle.jsse.provider.BouncyCastleJsseProvider",
+ false,
+ javaClass.classLoader
+ )
+
+ true
+ } catch (_: ClassNotFoundException) {
+ false
+ }
+
+ fun buildIfSupported(): BouncyCastlePlatform? =
+ if (isSupported) BouncyCastlePlatform() else null
+ }
+}
diff --git a/paho/src/main/java/in/mohalla/paho/client/mqttv3/internal/platform/ConscryptPlatform.kt b/paho/src/main/java/in/mohalla/paho/client/mqttv3/internal/platform/ConscryptPlatform.kt
new file mode 100644
index 00000000..eb792e12
--- /dev/null
+++ b/paho/src/main/java/in/mohalla/paho/client/mqttv3/internal/platform/ConscryptPlatform.kt
@@ -0,0 +1,140 @@
+/*
+ * Copyright (C) 2014 Square, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package `in`.mohalla.paho.client.mqttv3.internal.platform
+
+import java.security.KeyStore
+import java.security.Provider
+import java.security.cert.X509Certificate
+import javax.net.ssl.SSLContext
+import javax.net.ssl.SSLSession
+import javax.net.ssl.SSLSocket
+import javax.net.ssl.SSLSocketFactory
+import javax.net.ssl.TrustManager
+import javax.net.ssl.TrustManagerFactory
+import javax.net.ssl.X509TrustManager
+import org.conscrypt.Conscrypt
+import org.conscrypt.ConscryptHostnameVerifier
+import `in`.mohalla.paho.client.mqttv3.Protocol
+
+/**
+ * Platform using Conscrypt (conscrypt.org) if installed as the first Security Provider.
+ *
+ * Requires org.conscrypt:conscrypt-openjdk-uber >= 2.1.0 on the classpath.
+ */
+class ConscryptPlatform private constructor() : Platform() {
+ private val provider: Provider = Conscrypt.newProvider()
+
+ // See release notes https://groups.google.com/forum/#!forum/conscrypt
+ // for version differences
+ override fun newSSLContext(): SSLContext =
+ // supports TLSv1.3 by default (version api is >= 1.4.0)
+ SSLContext.getInstance("TLS", provider)
+
+ override fun platformTrustManager(): X509TrustManager {
+ val trustManagers = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()).apply {
+ init(null as KeyStore?)
+ }.trustManagers!!
+ check(trustManagers.size == 1 && trustManagers[0] is X509TrustManager) {
+ "Unexpected default trust managers: ${trustManagers.contentToString()}"
+ }
+ val x509TrustManager = trustManagers[0] as X509TrustManager
+ // Disabled because OkHttp will run anyway
+ Conscrypt.setHostnameVerifier(x509TrustManager, DisabledHostnameVerifier)
+ return x509TrustManager
+ }
+
+ internal object DisabledHostnameVerifier : ConscryptHostnameVerifier {
+ fun verify(
+ hostname: String?,
+ session: SSLSession?
+ ): Boolean {
+ return true
+ }
+
+ override fun verify(
+ certs: Array?,
+ hostname: String?,
+ session: SSLSession?
+ ): Boolean {
+ return true
+ }
+ }
+
+ override fun trustManager(sslSocketFactory: SSLSocketFactory): X509TrustManager? = null
+
+ override fun configureTlsExtensions(
+ sslSocket: SSLSocket,
+ hostname: String?,
+ protocols: List<@JvmSuppressWildcards Protocol>
+ ) {
+ if (Conscrypt.isConscrypt(sslSocket)) {
+ // Enable session tickets.
+ Conscrypt.setUseSessionTickets(sslSocket, true)
+
+ // Enable ALPN.
+ val names = alpnProtocolNames(protocols)
+ Conscrypt.setApplicationProtocols(sslSocket, names.toTypedArray())
+ } else {
+ super.configureTlsExtensions(sslSocket, hostname, protocols)
+ }
+ }
+
+ override fun getSelectedProtocol(sslSocket: SSLSocket): String? =
+ if (Conscrypt.isConscrypt(sslSocket)) {
+ Conscrypt.getApplicationProtocol(sslSocket)
+ } else {
+ super.getSelectedProtocol(sslSocket)
+ }
+
+ override fun newSslSocketFactory(trustManager: X509TrustManager): SSLSocketFactory {
+ return newSSLContext().apply {
+ init(null, arrayOf(trustManager), null)
+ }.socketFactory
+ }
+
+ companion object {
+ val isSupported: Boolean = try {
+ // Trigger an early exception over a fatal error, prefer a RuntimeException over Error.
+ Class.forName("org.conscrypt.Conscrypt\$Version", false, javaClass.classLoader)
+
+ when {
+ // Bump this version if we ever have a binary incompatibility
+ Conscrypt.isAvailable() && atLeastVersion(2, 1, 0) -> true
+ else -> false
+ }
+ } catch (e: NoClassDefFoundError) {
+ false
+ } catch (e: ClassNotFoundException) {
+ false
+ }
+
+ fun buildIfSupported(): ConscryptPlatform? = if (isSupported) ConscryptPlatform() else null
+
+ fun atLeastVersion(major: Int, minor: Int = 0, patch: Int = 0): Boolean {
+ val conscryptVersion = Conscrypt.version() ?: return false
+
+ if (conscryptVersion.major() != major) {
+ return conscryptVersion.major() > major
+ }
+
+ if (conscryptVersion.minor() != minor) {
+ return conscryptVersion.minor() > minor
+ }
+
+ return conscryptVersion.patch() >= patch
+ }
+ }
+}
diff --git a/paho/src/main/java/in/mohalla/paho/client/mqttv3/internal/platform/Jdk8WithJettyBootPlatform.kt b/paho/src/main/java/in/mohalla/paho/client/mqttv3/internal/platform/Jdk8WithJettyBootPlatform.kt
new file mode 100644
index 00000000..9bbf192f
--- /dev/null
+++ b/paho/src/main/java/in/mohalla/paho/client/mqttv3/internal/platform/Jdk8WithJettyBootPlatform.kt
@@ -0,0 +1,166 @@
+/*
+ * Copyright (C) 2016 Square, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package `in`.mohalla.paho.client.mqttv3.internal.platform
+
+import java.lang.reflect.InvocationHandler
+import java.lang.reflect.InvocationTargetException
+import java.lang.reflect.Method
+import java.lang.reflect.Proxy
+import javax.net.ssl.SSLSocket
+import `in`.mohalla.paho.client.mqttv3.Protocol
+
+/** OpenJDK 8 with `org.mortbay.jetty.alpn:alpn-boot` in the boot class path. */
+class Jdk8WithJettyBootPlatform(
+ private val putMethod: Method,
+ private val getMethod: Method,
+ private val removeMethod: Method,
+ private val clientProviderClass: Class<*>,
+ private val serverProviderClass: Class<*>
+) : Platform() {
+ override fun configureTlsExtensions(
+ sslSocket: SSLSocket,
+ hostname: String?,
+ protocols: List
+ ) {
+ val names = alpnProtocolNames(protocols)
+
+ try {
+ val alpnProvider = Proxy.newProxyInstance(
+ Platform::class.java.classLoader,
+ arrayOf(clientProviderClass, serverProviderClass),
+ AlpnProvider(names)
+ )
+ putMethod.invoke(null, sslSocket, alpnProvider)
+ } catch (e: InvocationTargetException) {
+ throw AssertionError("failed to set ALPN", e)
+ } catch (e: IllegalAccessException) {
+ throw AssertionError("failed to set ALPN", e)
+ }
+ }
+
+ override fun afterHandshake(sslSocket: SSLSocket) {
+ try {
+ removeMethod.invoke(null, sslSocket)
+ } catch (e: IllegalAccessException) {
+ throw AssertionError("failed to remove ALPN", e)
+ } catch (e: InvocationTargetException) {
+ throw AssertionError("failed to remove ALPN", e)
+ }
+ }
+
+ override fun getSelectedProtocol(sslSocket: SSLSocket): String? {
+ try {
+ val provider =
+ Proxy.getInvocationHandler(getMethod.invoke(null, sslSocket)) as AlpnProvider
+ if (!provider.unsupported && provider.selected == null) {
+ log("ALPN callback dropped: HTTP/2 is disabled. " + "Is alpn-boot on the boot class path?")
+ return null
+ }
+ return if (provider.unsupported) null else provider.selected
+ } catch (e: InvocationTargetException) {
+ throw AssertionError("failed to get ALPN selected protocol", e)
+ } catch (e: IllegalAccessException) {
+ throw AssertionError("failed to get ALPN selected protocol", e)
+ }
+ }
+
+ /**
+ * Handle the methods of ALPN's ClientProvider and ServerProvider without a compile-time
+ * dependency on those interfaces.
+ */
+ private class AlpnProvider(
+ /** This peer's supported protocols. */
+ private val protocols: List
+ ) : InvocationHandler {
+ /** Set when remote peer notifies ALPN is unsupported. */
+ var unsupported: Boolean = false
+
+ /** The protocol the server selected. */
+ var selected: String? = null
+
+ @Throws(Throwable::class)
+ override fun invoke(proxy: Any, method: Method, args: Array?): Any? {
+ val callArgs = args ?: arrayOf()
+ val methodName = method.name
+ val returnType = method.returnType
+ if (methodName == "supports" && Boolean::class.javaPrimitiveType == returnType) {
+ return true // ALPN is supported.
+ } else if (methodName == "unsupported" && Void.TYPE == returnType) {
+ this.unsupported = true // Peer doesn't support ALPN.
+ return null
+ } else if (methodName == "protocols" && callArgs.isEmpty()) {
+ return protocols // Client advertises these protocols.
+ } else if ((methodName == "selectProtocol" || methodName == "select") &&
+ String::class.java == returnType && callArgs.size == 1 && callArgs[0] is List<*>
+ ) {
+ val peerProtocols = callArgs[0] as List<*>
+ // Pick the first known protocol the peer advertises.
+ for (i in 0..peerProtocols.size) {
+ val protocol = peerProtocols[i] as String
+ if (protocol in protocols) {
+ selected = protocol
+ return selected
+ }
+ }
+ selected = protocols[0] // On no intersection, try peer's first protocol.
+ return selected
+ } else if ((methodName == "protocolSelected" || methodName == "selected") && callArgs.size == 1) {
+ this.selected = callArgs[0] as String // Server selected this protocol.
+ return null
+ } else {
+ return method.invoke(this, *callArgs)
+ }
+ }
+ }
+
+ companion object {
+ fun buildIfSupported(): Platform? {
+ val jvmVersion = System.getProperty("java.specification.version", "unknown")
+ try {
+ // 1.8, 9, 10, 11, 12 etc
+ val version = jvmVersion.toInt()
+ if (version >= 9) return null
+ } catch (_: NumberFormatException) {
+ // expected on >= JDK 9
+ }
+
+ // Find Jetty's ALPN extension for OpenJDK.
+ try {
+ val alpnClassName = "org.eclipse.jetty.alpn.ALPN"
+ val alpnClass = Class.forName(alpnClassName, true, null)
+ val providerClass = Class.forName("$alpnClassName\$Provider", true, null)
+ val clientProviderClass =
+ Class.forName("$alpnClassName\$ClientProvider", true, null)
+ val serverProviderClass =
+ Class.forName("$alpnClassName\$ServerProvider", true, null)
+ val putMethod = alpnClass.getMethod("put", SSLSocket::class.java, providerClass)
+ val getMethod = alpnClass.getMethod("get", SSLSocket::class.java)
+ val removeMethod = alpnClass.getMethod("remove", SSLSocket::class.java)
+ return Jdk8WithJettyBootPlatform(
+ putMethod,
+ getMethod,
+ removeMethod,
+ clientProviderClass,
+ serverProviderClass
+ )
+ } catch (_: ClassNotFoundException) {
+ } catch (_: NoSuchMethodException) {
+ }
+
+ return null
+ }
+ }
+}
diff --git a/paho/src/main/java/in/mohalla/paho/client/mqttv3/internal/platform/Jdk9Platform.kt b/paho/src/main/java/in/mohalla/paho/client/mqttv3/internal/platform/Jdk9Platform.kt
new file mode 100644
index 00000000..421726f4
--- /dev/null
+++ b/paho/src/main/java/in/mohalla/paho/client/mqttv3/internal/platform/Jdk9Platform.kt
@@ -0,0 +1,105 @@
+/*
+ * Copyright (C) 2016 Square, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package `in`.mohalla.paho.client.mqttv3.internal.platform
+
+import java.security.NoSuchAlgorithmException
+import javax.net.ssl.SSLContext
+import javax.net.ssl.SSLSocket
+import javax.net.ssl.SSLSocketFactory
+import javax.net.ssl.X509TrustManager
+import `in`.mohalla.paho.client.mqttv3.Protocol
+import `in`.mohalla.paho.client.mqttv3.SuppressSignatureCheck
+
+/** OpenJDK 9+. */
+open class Jdk9Platform : Platform() {
+ @SuppressSignatureCheck
+ override fun configureTlsExtensions(
+ sslSocket: SSLSocket,
+ hostname: String?,
+ protocols: List<@JvmSuppressWildcards Protocol>
+ ) {
+ val sslParameters = sslSocket.sslParameters
+
+ val names = alpnProtocolNames(protocols)
+
+ sslParameters.applicationProtocols = names.toTypedArray()
+
+ sslSocket.sslParameters = sslParameters
+ }
+
+ @SuppressSignatureCheck
+ override fun getSelectedProtocol(sslSocket: SSLSocket): String? {
+ return try {
+ // SSLSocket.getApplicationProtocol returns "" if application protocols values will not
+ // be used. Observed if you didn't specify SSLParameters.setApplicationProtocols
+ when (val protocol = sslSocket.applicationProtocol) {
+ null, "" -> null
+ else -> protocol
+ }
+ } catch (e: UnsupportedOperationException) {
+ // https://docs.oracle.com/javase/9/docs/api/javax/net/ssl/SSLSocket.html#getApplicationProtocol--
+ null
+ }
+ }
+
+ override fun trustManager(sslSocketFactory: SSLSocketFactory): X509TrustManager? {
+ // Not supported due to access checks on JDK 9+:
+ // java.lang.reflect.InaccessibleObjectException: Unable to make member of class
+ // sun.security.ssl.SSLSocketFactoryImpl accessible: module java.base does not export
+ // sun.security.ssl to unnamed module @xxx
+ throw UnsupportedOperationException(
+ "clientBuilder.sslSocketFactory(SSLSocketFactory) not supported on JDK 8 (>= 252) or JDK 9+"
+ )
+ }
+
+ override fun newSSLContext(): SSLContext {
+ return when {
+ majorVersion != null && majorVersion >= 9 ->
+ SSLContext.getInstance("TLS")
+ else ->
+ try {
+ // Based on SSLSocket.getApplicationProtocol check we should
+ // have TLSv1.3 if we request it.
+ // See https://www.oracle.com/java/technologies/javase/8u261-relnotes.html
+ SSLContext.getInstance("TLSv1.3")
+ } catch (nsae: NoSuchAlgorithmException) {
+ SSLContext.getInstance("TLS")
+ }
+ }
+ }
+
+ companion object {
+ val isAvailable: Boolean
+
+ val majorVersion = System.getProperty("java.specification.version")?.toIntOrNull()
+
+ init {
+ isAvailable = if (majorVersion != null) {
+ majorVersion >= 9
+ } else {
+ try {
+ // also present on JDK8 after build 252.
+ SSLSocket::class.java.getMethod("getApplicationProtocol")
+ true
+ } catch (nsme: NoSuchMethodException) {
+ false
+ }
+ }
+ }
+
+ fun buildIfSupported(): Jdk9Platform? = if (isAvailable) Jdk9Platform() else null
+ }
+}
diff --git a/paho/src/main/java/in/mohalla/paho/client/mqttv3/internal/platform/OpenJSSEPlatform.kt b/paho/src/main/java/in/mohalla/paho/client/mqttv3/internal/platform/OpenJSSEPlatform.kt
new file mode 100644
index 00000000..ee795a65
--- /dev/null
+++ b/paho/src/main/java/in/mohalla/paho/client/mqttv3/internal/platform/OpenJSSEPlatform.kt
@@ -0,0 +1,103 @@
+/*
+ * Copyright (C) 2019 Square, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package `in`.mohalla.paho.client.mqttv3.internal.platform
+
+import java.security.KeyStore
+import java.security.Provider
+import javax.net.ssl.SSLContext
+import javax.net.ssl.SSLSocket
+import javax.net.ssl.SSLSocketFactory
+import javax.net.ssl.TrustManagerFactory
+import javax.net.ssl.X509TrustManager
+import `in`.mohalla.paho.client.mqttv3.Protocol
+
+/**
+ * Platform using OpenJSSE (https://github.com/openjsse/openjsse) if installed as the first
+ * Security Provider.
+ *
+ * Requires org.openjsse:openjsse >= 1.1.0 on the classpath.
+ */
+class OpenJSSEPlatform private constructor() : Platform() {
+ private val provider: Provider = org.openjsse.net.ssl.OpenJSSE()
+
+ // Selects TLSv1.3 so we are specific about our intended version ranges (not just 1.3)
+ // and because it's a common pattern for VMs to have differences between supported and
+ // defaulted versions for TLS based on what is requested.
+ override fun newSSLContext(): SSLContext =
+ SSLContext.getInstance("TLSv1.3", provider)
+
+ override fun platformTrustManager(): X509TrustManager {
+ val factory = TrustManagerFactory.getInstance(
+ TrustManagerFactory.getDefaultAlgorithm(),
+ provider
+ )
+ factory.init(null as KeyStore?)
+ val trustManagers = factory.trustManagers!!
+ check(trustManagers.size == 1 && trustManagers[0] is X509TrustManager) {
+ "Unexpected default trust managers: ${trustManagers.contentToString()}"
+ }
+ return trustManagers[0] as X509TrustManager
+ }
+
+ override fun trustManager(sslSocketFactory: SSLSocketFactory): X509TrustManager =
+ throw UnsupportedOperationException(
+ "clientBuilder.sslSocketFactory(SSLSocketFactory) not supported with OpenJSSE"
+ )
+
+ override fun configureTlsExtensions(
+ sslSocket: SSLSocket,
+ hostname: String?,
+ protocols: List<@JvmSuppressWildcards Protocol>
+ ) {
+ if (sslSocket is org.openjsse.javax.net.ssl.SSLSocket) {
+ val sslParameters = sslSocket.sslParameters
+
+ if (sslParameters is org.openjsse.javax.net.ssl.SSLParameters) {
+ // Enable ALPN.
+ val names = alpnProtocolNames(protocols)
+ sslParameters.applicationProtocols = names.toTypedArray()
+
+ sslSocket.sslParameters = sslParameters
+ }
+ } else {
+ super.configureTlsExtensions(sslSocket, hostname, protocols)
+ }
+ }
+
+ override fun getSelectedProtocol(sslSocket: SSLSocket): String? =
+ if (sslSocket is org.openjsse.javax.net.ssl.SSLSocket) {
+ when (val protocol = sslSocket.applicationProtocol) {
+ // Handles both un-configured and none selected.
+ null, "" -> null
+ else -> protocol
+ }
+ } else {
+ super.getSelectedProtocol(sslSocket)
+ }
+
+ companion object {
+ val isSupported: Boolean = try {
+ // Trigger an early exception over a fatal error, prefer a RuntimeException over Error.
+ Class.forName("org.openjsse.net.ssl.OpenJSSE", false, javaClass.classLoader)
+
+ true
+ } catch (_: ClassNotFoundException) {
+ false
+ }
+
+ fun buildIfSupported(): OpenJSSEPlatform? = if (isSupported) OpenJSSEPlatform() else null
+ }
+}
diff --git a/paho/src/main/java/in/mohalla/paho/client/mqttv3/internal/platform/Platform.kt b/paho/src/main/java/in/mohalla/paho/client/mqttv3/internal/platform/Platform.kt
new file mode 100644
index 00000000..99af275f
--- /dev/null
+++ b/paho/src/main/java/in/mohalla/paho/client/mqttv3/internal/platform/Platform.kt
@@ -0,0 +1,284 @@
+/*
+ * Copyright (C) 2012 Square, Inc.
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package `in`.mohalla.paho.client.mqttv3.internal.platform
+
+import java.io.IOException
+import java.net.InetSocketAddress
+import java.net.Socket
+import java.security.GeneralSecurityException
+import java.security.KeyStore
+import java.security.Security
+import java.util.logging.Level
+import java.util.logging.Logger
+import javax.net.ssl.SSLContext
+import javax.net.ssl.SSLSocket
+import javax.net.ssl.SSLSocketFactory
+import javax.net.ssl.TrustManager
+import javax.net.ssl.TrustManagerFactory
+import javax.net.ssl.X509TrustManager
+import okio.Buffer
+import `in`.mohalla.paho.client.mqttv3.Protocol
+import `in`.mohalla.paho.client.mqttv3.internal.tls.BasicCertificateChainCleaner
+import `in`.mohalla.paho.client.mqttv3.internal.tls.BasicTrustRootIndex
+import `in`.mohalla.paho.client.mqttv3.internal.tls.CertificateChainCleaner
+import `in`.mohalla.paho.client.mqttv3.internal.tls.TrustRootIndex
+import `in`.mohalla.paho.client.mqttv3.readFieldOrNull
+
+/**
+ * Access to platform-specific features.
+ *
+ * ### Session Tickets
+ *
+ * Supported on Android 2.3+.
+ * Supported on JDK 8+ via Conscrypt.
+ *
+ * ### ALPN (Application Layer Protocol Negotiation)
+ *
+ * Supported on Android 5.0+.
+ *
+ * Supported on OpenJDK 8 via the JettyALPN-boot library or Conscrypt.
+ *
+ * Supported on OpenJDK 9+ via SSLParameters and SSLSocket features.
+ *
+ * ### Trust Manager Extraction
+ *
+ * Supported on Android 2.3+ and OpenJDK 7+. There are no public APIs to recover the trust
+ * manager that was used to create an [SSLSocketFactory].
+ *
+ * Not supported by choice on JDK9+ due to access checks.
+ *
+ * ### Android Cleartext Permit Detection
+ *
+ * Supported on Android 6.0+ via `NetworkSecurityPolicy`.
+ */
+open class Platform {
+
+ /** Prefix used on custom headers. */
+ fun getPrefix() = "OkHttp"
+
+ open fun newSSLContext(): SSLContext = SSLContext.getInstance("TLS")
+
+ open fun platformTrustManager(): X509TrustManager {
+ val factory = TrustManagerFactory.getInstance(
+ TrustManagerFactory.getDefaultAlgorithm()
+ )
+ factory.init(null as KeyStore?)
+ val trustManagers = factory.trustManagers!!
+ check(trustManagers.size == 1 && trustManagers[0] is X509TrustManager) {
+ "Unexpected default trust managers: ${trustManagers.contentToString()}"
+ }
+ return trustManagers[0] as X509TrustManager
+ }
+
+ open fun trustManager(sslSocketFactory: SSLSocketFactory): X509TrustManager? {
+ return try {
+ // Attempt to get the trust manager from an OpenJDK socket factory. We attempt this on all
+ // platforms in order to support Robolectric, which mixes classes from both Android and the
+ // Oracle JDK. Note that we don't support HTTP/2 or other nice features on Robolectric.
+ val sslContextClass = Class.forName("sun.security.ssl.SSLContextImpl")
+ val context =
+ readFieldOrNull(sslSocketFactory, sslContextClass, "context") ?: return null
+ readFieldOrNull(context, X509TrustManager::class.java, "trustManager")
+ } catch (e: ClassNotFoundException) {
+ null
+ } catch (e: RuntimeException) {
+ // Throws InaccessibleObjectException (added in JDK9) on JDK 17 due to
+ // JEP 403 Strongly Encapsulate JDK Internals.
+ if (e.javaClass.name != "java.lang.reflect.InaccessibleObjectException") {
+ throw e
+ }
+
+ null
+ }
+ }
+
+ /**
+ * Configure TLS extensions on `sslSocket` for `route`.
+ */
+ open fun configureTlsExtensions(
+ sslSocket: SSLSocket,
+ hostname: String?,
+ protocols: List<@JvmSuppressWildcards Protocol>
+ ) {
+ }
+
+ /** Called after the TLS handshake to release resources allocated by [configureTlsExtensions]. */
+ open fun afterHandshake(sslSocket: SSLSocket) {
+ }
+
+ /** Returns the negotiated protocol, or null if no protocol was negotiated. */
+ open fun getSelectedProtocol(sslSocket: SSLSocket): String? = null
+
+ @Throws(IOException::class)
+ open fun connectSocket(socket: Socket, address: InetSocketAddress, connectTimeout: Int) {
+ socket.connect(address, connectTimeout)
+ }
+
+ open fun log(message: String, level: Int = INFO, t: Throwable? = null) {
+ val logLevel = if (level == WARN) Level.WARNING else Level.INFO
+ logger.log(logLevel, message, t)
+ }
+
+ open fun isCleartextTrafficPermitted(hostname: String): Boolean = true
+
+ /**
+ * Returns an object that holds a stack trace created at the moment this method is executed. This
+ * should be used specifically for [java.io.Closeable] objects and in conjunction with
+ * [logCloseableLeak].
+ */
+ open fun getStackTraceForCloseable(closer: String): Any? {
+ return when {
+ logger.isLoggable(Level.FINE) -> Throwable(closer) // These are expensive to allocate.
+ else -> null
+ }
+ }
+
+ open fun logCloseableLeak(message: String, stackTrace: Any?) {
+ var logMessage = message
+ if (stackTrace == null) {
+ logMessage += " To see where this was allocated, set the OkHttpClient logger level to " +
+ "FINE: Logger.getLogger(OkHttpClient.class.getName()).setLevel(Level.FINE);"
+ }
+ log(logMessage, WARN, stackTrace as Throwable?)
+ }
+
+ open fun buildCertificateChainCleaner(trustManager: X509TrustManager): CertificateChainCleaner =
+ BasicCertificateChainCleaner(buildTrustRootIndex(trustManager))
+
+ open fun buildTrustRootIndex(trustManager: X509TrustManager): TrustRootIndex =
+ BasicTrustRootIndex(*trustManager.acceptedIssuers)
+
+ open fun newSslSocketFactory(trustManager: X509TrustManager): SSLSocketFactory {
+ try {
+ return newSSLContext().apply {
+ init(null, arrayOf(trustManager), null)
+ }.socketFactory
+ } catch (e: GeneralSecurityException) {
+ throw AssertionError("No System TLS: $e", e) // The system has no TLS. Just give up.
+ }
+ }
+
+ override fun toString(): String = javaClass.simpleName
+
+ companion object {
+ @Volatile private var platform = findPlatform()
+
+ const val INFO = 4
+ const val WARN = 5
+
+ private val logger = Logger.getLogger(Platform::class.java.name)
+
+ @JvmStatic
+ fun get(): Platform = platform
+
+ fun resetForTests(platform: Platform = findPlatform()) {
+ Companion.platform = platform
+ }
+
+ fun alpnProtocolNames(protocols: List) =
+ protocols.map { it.name }
+
+ // This explicit check avoids activating in Android Studio with Android specific classes
+ // available when running plugins inside the IDE.
+ val isAndroid: Boolean
+ get() = "Dalvik" == System.getProperty("java.vm.name")
+
+ private val isConscryptPreferred: Boolean
+ get() {
+ val preferredProvider = Security.getProviders()[0].name
+ return "Conscrypt" == preferredProvider
+ }
+
+ private val isOpenJSSEPreferred: Boolean
+ get() {
+ val preferredProvider = Security.getProviders()[0].name
+ return "OpenJSSE" == preferredProvider
+ }
+
+ private val isBouncyCastlePreferred: Boolean
+ get() {
+ val preferredProvider = Security.getProviders()[0].name
+ return "BC" == preferredProvider
+ }
+
+ /** Attempt to match the host runtime to a capable Platform implementation. */
+ private fun findPlatform(): Platform = if (isAndroid) {
+ findAndroidPlatform()
+ } else {
+ findJvmPlatform()
+ }
+
+ private fun findAndroidPlatform(): Platform {
+ return Android10Platform.buildIfSupported() ?: AndroidPlatform.buildIfSupported()!!
+ }
+
+ private fun findJvmPlatform(): Platform {
+ if (isConscryptPreferred) {
+ val conscrypt = ConscryptPlatform.buildIfSupported()
+
+ if (conscrypt != null) {
+ return conscrypt
+ }
+ }
+
+ if (isBouncyCastlePreferred) {
+ val bc = BouncyCastlePlatform.buildIfSupported()
+
+ if (bc != null) {
+ return bc
+ }
+ }
+
+ if (isOpenJSSEPreferred) {
+ val openJSSE = OpenJSSEPlatform.buildIfSupported()
+
+ if (openJSSE != null) {
+ return openJSSE
+ }
+ }
+
+ // An Oracle JDK 9 like OpenJDK, or JDK 8 251+.
+ val jdk9 = Jdk9Platform.buildIfSupported()
+
+ if (jdk9 != null) {
+ return jdk9
+ }
+
+ // An Oracle JDK 8 like OpenJDK, pre 251.
+ val jdkWithJettyBoot = Jdk8WithJettyBootPlatform.buildIfSupported()
+
+ if (jdkWithJettyBoot != null) {
+ return jdkWithJettyBoot
+ }
+
+ return Platform()
+ }
+
+ /**
+ * Returns the concatenation of 8-bit, length prefixed protocol names.
+ * http://tools.ietf.org/html/draft-agl-tls-nextprotoneg-04#page-4
+ */
+ fun concatLengthPrefixed(protocols: List): ByteArray {
+ val result = Buffer()
+ for (protocol in alpnProtocolNames(protocols)) {
+ result.writeByte(protocol.length)
+ result.writeUtf8(protocol)
+ }
+ return result.readByteArray()
+ }
+ }
+}
diff --git a/paho/src/main/java/in/mohalla/paho/client/mqttv3/internal/platform/android/Android10SocketAdapter.kt b/paho/src/main/java/in/mohalla/paho/client/mqttv3/internal/platform/android/Android10SocketAdapter.kt
new file mode 100644
index 00000000..2be18362
--- /dev/null
+++ b/paho/src/main/java/in/mohalla/paho/client/mqttv3/internal/platform/android/Android10SocketAdapter.kt
@@ -0,0 +1,83 @@
+/*
+ * Copyright (C) 2019 Square, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package `in`.mohalla.paho.client.mqttv3.internal.platform.android
+
+import android.annotation.SuppressLint
+import android.net.ssl.SSLSockets
+import android.os.Build
+import java.io.IOException
+import javax.net.ssl.SSLSocket
+import `in`.mohalla.paho.client.mqttv3.Protocol
+import `in`.mohalla.paho.client.mqttv3.SuppressSignatureCheck
+import `in`.mohalla.paho.client.mqttv3.internal.platform.Platform
+import `in`.mohalla.paho.client.mqttv3.internal.platform.Platform.Companion.isAndroid
+
+/**
+ * Simple non-reflection SocketAdapter for Android Q+.
+ *
+ * These API assumptions make it unsuitable for use on earlier Android versions.
+ */
+@SuppressLint("NewApi")
+@SuppressSignatureCheck
+class Android10SocketAdapter : SocketAdapter {
+ override fun matchesSocket(sslSocket: SSLSocket): Boolean =
+ SSLSockets.isSupportedSocket(sslSocket)
+
+ override fun isSupported(): Boolean = Companion.isSupported()
+
+ @SuppressLint("NewApi")
+ override fun getSelectedProtocol(sslSocket: SSLSocket): String? {
+ return try {
+ // SSLSocket.getApplicationProtocol returns "" if application protocols values will not
+ // be used. Observed if you didn't specify SSLParameters.setApplicationProtocols
+ when (val protocol = sslSocket.applicationProtocol) {
+ null, "" -> null
+ else -> protocol
+ }
+ } catch (e: UnsupportedOperationException) {
+ // https://docs.oracle.com/javase/9/docs/api/javax/net/ssl/SSLSocket.html#getApplicationProtocol--
+ null
+ }
+ }
+
+ @SuppressLint("NewApi")
+ override fun configureTlsExtensions(
+ sslSocket: SSLSocket,
+ hostname: String?,
+ protocols: List
+ ) {
+ try {
+ val sslParameters = sslSocket.sslParameters
+
+ // Enable ALPN.
+ sslParameters.applicationProtocols =
+ Platform.alpnProtocolNames(protocols).toTypedArray()
+
+ sslSocket.sslParameters = sslParameters
+ } catch (iae: IllegalArgumentException) {
+ // probably java.lang.IllegalArgumentException: Invalid input to toASCII from IDN.toASCII
+ throw IOException("Android internal error", iae)
+ }
+ }
+
+ @SuppressSignatureCheck
+ companion object {
+ fun buildIfSupported(): SocketAdapter? =
+ if (isSupported()) Android10SocketAdapter() else null
+
+ fun isSupported() = isAndroid && Build.VERSION.SDK_INT >= 29
+ }
+}
diff --git a/paho/src/main/java/in/mohalla/paho/client/mqttv3/internal/platform/android/AndroidCertificateChainCleaner.kt b/paho/src/main/java/in/mohalla/paho/client/mqttv3/internal/platform/android/AndroidCertificateChainCleaner.kt
new file mode 100644
index 00000000..14f94a7d
--- /dev/null
+++ b/paho/src/main/java/in/mohalla/paho/client/mqttv3/internal/platform/android/AndroidCertificateChainCleaner.kt
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2019 Square, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package `in`.mohalla.paho.client.mqttv3.internal.platform.android
+
+import android.net.http.X509TrustManagerExtensions
+import java.security.cert.Certificate
+import java.security.cert.CertificateException
+import java.security.cert.X509Certificate
+import javax.net.ssl.SSLPeerUnverifiedException
+import javax.net.ssl.X509TrustManager
+import `in`.mohalla.paho.client.mqttv3.SuppressSignatureCheck
+import `in`.mohalla.paho.client.mqttv3.internal.tls.CertificateChainCleaner
+
+/**
+ * Android implementation of CertificateChainCleaner using direct Android API calls.
+ * Not used if X509TrustManager doesn't implement [X509TrustManager.checkServerTrusted] with
+ * an additional host param.
+ */
+internal class AndroidCertificateChainCleaner(
+ private val trustManager: X509TrustManager,
+ private val x509TrustManagerExtensions: X509TrustManagerExtensions
+) : CertificateChainCleaner() {
+ @Suppress("UNCHECKED_CAST")
+ @Throws(SSLPeerUnverifiedException::class)
+ @SuppressSignatureCheck
+ override
+ fun clean(chain: List, hostname: String): List {
+ val certificates = (chain as List).toTypedArray()
+ try {
+ return x509TrustManagerExtensions.checkServerTrusted(certificates, "RSA", hostname)
+ } catch (ce: CertificateException) {
+ throw SSLPeerUnverifiedException(ce.message).apply { initCause(ce) }
+ }
+ }
+
+ override fun equals(other: Any?): Boolean =
+ other is AndroidCertificateChainCleaner &&
+ other.trustManager === this.trustManager
+
+ override fun hashCode(): Int = System.identityHashCode(trustManager)
+
+ companion object {
+ @SuppressSignatureCheck
+ fun buildIfSupported(trustManager: X509TrustManager): AndroidCertificateChainCleaner? {
+ val extensions = try {
+ X509TrustManagerExtensions(trustManager)
+ } catch (iae: IllegalArgumentException) {
+ // X509TrustManagerExtensions checks for checkServerTrusted(X509Certificate[], String, String)
+ null
+ }
+
+ return when {
+ extensions != null -> AndroidCertificateChainCleaner(trustManager, extensions)
+ else -> null
+ }
+ }
+ }
+}
diff --git a/paho/src/main/java/in/mohalla/paho/client/mqttv3/internal/platform/android/AndroidSocketAdapter.kt b/paho/src/main/java/in/mohalla/paho/client/mqttv3/internal/platform/android/AndroidSocketAdapter.kt
new file mode 100644
index 00000000..2b7c4bda
--- /dev/null
+++ b/paho/src/main/java/in/mohalla/paho/client/mqttv3/internal/platform/android/AndroidSocketAdapter.kt
@@ -0,0 +1,133 @@
+/*
+ * Copyright (C) 2019 Square, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package `in`.mohalla.paho.client.mqttv3.internal.platform.android
+
+import java.lang.reflect.InvocationTargetException
+import java.lang.reflect.Method
+import javax.net.ssl.SSLSocket
+import `in`.mohalla.paho.client.mqttv3.Protocol
+import `in`.mohalla.paho.client.mqttv3.internal.platform.AndroidPlatform
+import `in`.mohalla.paho.client.mqttv3.internal.platform.Platform
+import `in`.mohalla.paho.client.mqttv3.internal.platform.android.DeferredSocketAdapter.Factory
+
+/**
+ * Modern reflection based SocketAdapter for Conscrypt class SSLSockets.
+ *
+ * This is used directly for providers where class name is known e.g. the Google Play Provider
+ * but we can't compile directly against it, or in fact reliably know if it is registered and
+ * on classpath.
+ */
+open class AndroidSocketAdapter(private val sslSocketClass: Class) : SocketAdapter {
+ private val setUseSessionTickets: Method =
+ sslSocketClass.getDeclaredMethod("setUseSessionTickets", Boolean::class.javaPrimitiveType)
+ private val setHostname = sslSocketClass.getMethod("setHostname", String::class.java)
+ private val getAlpnSelectedProtocol = sslSocketClass.getMethod("getAlpnSelectedProtocol")
+ private val setAlpnProtocols =
+ sslSocketClass.getMethod("setAlpnProtocols", ByteArray::class.java)
+
+ override fun isSupported(): Boolean = AndroidPlatform.isSupported
+
+ override fun matchesSocket(sslSocket: SSLSocket): Boolean = sslSocketClass.isInstance(sslSocket)
+
+ override fun configureTlsExtensions(
+ sslSocket: SSLSocket,
+ hostname: String?,
+ protocols: List
+ ) {
+ // No TLS extensions if the socket class is custom.
+ if (matchesSocket(sslSocket)) {
+ try {
+ // Enable session tickets.
+ setUseSessionTickets.invoke(sslSocket, true)
+
+ if (hostname != null) {
+ // This is SSLParameters.setServerNames() in API 24+.
+ setHostname.invoke(sslSocket, hostname)
+ }
+
+ // Enable ALPN.
+ if (protocols.isNotEmpty()) {
+ setAlpnProtocols.invoke(
+ sslSocket,
+ Platform.concatLengthPrefixed(protocols)
+ )
+ }
+ } catch (e: IllegalAccessException) {
+ throw AssertionError(e)
+ } catch (e: InvocationTargetException) {
+ throw AssertionError(e)
+ }
+ }
+ }
+
+ override fun getSelectedProtocol(sslSocket: SSLSocket): String? {
+ // No TLS extensions if the socket class is custom.
+ if (!matchesSocket(sslSocket)) {
+ return null
+ }
+
+ return try {
+ val alpnResult = getAlpnSelectedProtocol.invoke(sslSocket) as ByteArray?
+ alpnResult?.toString(Charsets.UTF_8)
+ } catch (e: IllegalAccessException) {
+ throw AssertionError(e)
+ } catch (e: InvocationTargetException) {
+ // https://github.com/square/okhttp/issues/5587
+ val cause = e.cause
+ when {
+ cause is NullPointerException && cause.message == "ssl == null" -> null
+ else -> throw AssertionError(e)
+ }
+ }
+ }
+
+ companion object {
+ val playProviderFactory: Factory =
+ factory("com.google.android.gms.org.conscrypt")
+
+ /**
+ * Builds a SocketAdapter from an observed implementation class, by grabbing the Class
+ * reference to perform reflection on at runtime.
+ *
+ * @param actualSSLSocketClass the runtime class of Conscrypt class socket.
+ */
+ private fun build(actualSSLSocketClass: Class): AndroidSocketAdapter {
+ var possibleClass: Class? = actualSSLSocketClass
+ while (possibleClass != null && possibleClass.simpleName != "OpenSSLSocketImpl") {
+ possibleClass = possibleClass.superclass
+
+ if (possibleClass == null) {
+ throw AssertionError(
+ "No OpenSSLSocketImpl superclass of socket of type $actualSSLSocketClass"
+ )
+ }
+ }
+
+ return AndroidSocketAdapter(possibleClass!!)
+ }
+
+ fun factory(packageName: String): Factory {
+ return object : Factory {
+ override fun matchesSocket(sslSocket: SSLSocket): Boolean =
+ sslSocket.javaClass.name.startsWith("$packageName.")
+
+ override fun create(sslSocket: SSLSocket): SocketAdapter {
+ return build(sslSocket.javaClass)
+ }
+ }
+ }
+ }
+}
diff --git a/paho/src/main/java/in/mohalla/paho/client/mqttv3/internal/platform/android/BouncyCastleSocketAdapter.kt b/paho/src/main/java/in/mohalla/paho/client/mqttv3/internal/platform/android/BouncyCastleSocketAdapter.kt
new file mode 100644
index 00000000..0ea18009
--- /dev/null
+++ b/paho/src/main/java/in/mohalla/paho/client/mqttv3/internal/platform/android/BouncyCastleSocketAdapter.kt
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2019 Square, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package `in`.mohalla.paho.client.mqttv3.internal.platform.android
+
+import javax.net.ssl.SSLSocket
+import org.bouncycastle.jsse.BCSSLSocket
+import `in`.mohalla.paho.client.mqttv3.Protocol
+import `in`.mohalla.paho.client.mqttv3.internal.platform.BouncyCastlePlatform
+import `in`.mohalla.paho.client.mqttv3.internal.platform.Platform
+import `in`.mohalla.paho.client.mqttv3.internal.platform.android.DeferredSocketAdapter.Factory
+
+/**
+ * Simple non-reflection SocketAdapter for BouncyCastle.
+ */
+class BouncyCastleSocketAdapter : SocketAdapter {
+ override fun matchesSocket(sslSocket: SSLSocket): Boolean = sslSocket is BCSSLSocket
+
+ override fun isSupported(): Boolean = BouncyCastlePlatform.isSupported
+
+ override fun getSelectedProtocol(sslSocket: SSLSocket): String? {
+ val s = sslSocket as BCSSLSocket
+
+ return when (val protocol = s.applicationProtocol) {
+ null, "" -> null
+ else -> protocol
+ }
+ }
+
+ override fun configureTlsExtensions(
+ sslSocket: SSLSocket,
+ hostname: String?,
+ protocols: List
+ ) {
+ // No TLS extensions if the socket class is custom.
+ if (matchesSocket(sslSocket)) {
+ val bcSocket = sslSocket as BCSSLSocket
+
+ val sslParameters = bcSocket.parameters
+
+ // Enable ALPN.
+ sslParameters.applicationProtocols = Platform.alpnProtocolNames(protocols).toTypedArray()
+
+ bcSocket.parameters = sslParameters
+ }
+ }
+
+ companion object {
+ val factory = object : Factory {
+ override fun matchesSocket(sslSocket: SSLSocket): Boolean {
+ return BouncyCastlePlatform.isSupported && sslSocket is BCSSLSocket
+ }
+ override fun create(sslSocket: SSLSocket): SocketAdapter = BouncyCastleSocketAdapter()
+ }
+ }
+}
diff --git a/paho/src/main/java/in/mohalla/paho/client/mqttv3/internal/platform/android/ConscryptSocketAdapter.kt b/paho/src/main/java/in/mohalla/paho/client/mqttv3/internal/platform/android/ConscryptSocketAdapter.kt
new file mode 100644
index 00000000..80cac921
--- /dev/null
+++ b/paho/src/main/java/in/mohalla/paho/client/mqttv3/internal/platform/android/ConscryptSocketAdapter.kt
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2019 Square, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package `in`.mohalla.paho.client.mqttv3.internal.platform.android
+
+import javax.net.ssl.SSLSocket
+import org.conscrypt.Conscrypt
+import `in`.mohalla.paho.client.mqttv3.Protocol
+import `in`.mohalla.paho.client.mqttv3.internal.platform.ConscryptPlatform
+import `in`.mohalla.paho.client.mqttv3.internal.platform.Platform
+import `in`.mohalla.paho.client.mqttv3.internal.platform.android.DeferredSocketAdapter.Factory
+
+/**
+ * Simple non-reflection SocketAdapter for Conscrypt when included as an application dependency
+ * directly.
+ */
+class ConscryptSocketAdapter : SocketAdapter {
+ override fun matchesSocket(sslSocket: SSLSocket): Boolean = Conscrypt.isConscrypt(sslSocket)
+
+ override fun isSupported(): Boolean = ConscryptPlatform.isSupported
+
+ override fun getSelectedProtocol(sslSocket: SSLSocket): String? =
+ when {
+ matchesSocket(sslSocket) -> Conscrypt.getApplicationProtocol(sslSocket)
+ else -> null // No TLS extensions if the socket class is custom.
+ }
+
+ override fun configureTlsExtensions(
+ sslSocket: SSLSocket,
+ hostname: String?,
+ protocols: List
+ ) {
+ // No TLS extensions if the socket class is custom.
+ if (matchesSocket(sslSocket)) {
+ // Enable session tickets.
+ Conscrypt.setUseSessionTickets(sslSocket, true)
+
+ // Enable ALPN.
+ val names = Platform.alpnProtocolNames(protocols)
+ Conscrypt.setApplicationProtocols(sslSocket, names.toTypedArray())
+ }
+ }
+
+ companion object {
+ val factory = object : Factory {
+ override fun matchesSocket(sslSocket: SSLSocket): Boolean {
+ return ConscryptPlatform.isSupported && Conscrypt.isConscrypt(sslSocket)
+ }
+
+ override fun create(sslSocket: SSLSocket): SocketAdapter = ConscryptSocketAdapter()
+ }
+ }
+}
diff --git a/paho/src/main/java/in/mohalla/paho/client/mqttv3/internal/platform/android/DeferredSocketAdapter.kt b/paho/src/main/java/in/mohalla/paho/client/mqttv3/internal/platform/android/DeferredSocketAdapter.kt
new file mode 100644
index 00000000..b9518b88
--- /dev/null
+++ b/paho/src/main/java/in/mohalla/paho/client/mqttv3/internal/platform/android/DeferredSocketAdapter.kt
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2019 Square, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package `in`.mohalla.paho.client.mqttv3.internal.platform.android
+
+import javax.net.ssl.SSLSocket
+import `in`.mohalla.paho.client.mqttv3.Protocol
+
+/**
+ * Deferred implementation of SocketAdapter that works by observing the socket
+ * and initializing on first use.
+ *
+ * We use this because eager classpath checks cause confusion and excessive logging in Android,
+ * and we can't rely on classnames after proguard, so are probably best served by falling through
+ * to a situation of trying our least likely noisiest options.
+ */
+class DeferredSocketAdapter(private val socketAdapterFactory: Factory) : SocketAdapter {
+ private var delegate: SocketAdapter? = null
+
+ override fun isSupported(): Boolean {
+ return true
+ }
+
+ override fun matchesSocket(sslSocket: SSLSocket): Boolean =
+ socketAdapterFactory.matchesSocket(sslSocket)
+
+ override fun configureTlsExtensions(
+ sslSocket: SSLSocket,
+ hostname: String?,
+ protocols: List
+ ) {
+ getDelegate(sslSocket)?.configureTlsExtensions(sslSocket, hostname, protocols)
+ }
+
+ override fun getSelectedProtocol(sslSocket: SSLSocket): String? {
+ return getDelegate(sslSocket)?.getSelectedProtocol(sslSocket)
+ }
+
+ @Synchronized
+ private fun getDelegate(sslSocket: SSLSocket): SocketAdapter? {
+ if (this.delegate == null && socketAdapterFactory.matchesSocket(sslSocket)) {
+ this.delegate = socketAdapterFactory.create(sslSocket)
+ }
+
+ return delegate
+ }
+
+ interface Factory {
+ fun matchesSocket(sslSocket: SSLSocket): Boolean
+ fun create(sslSocket: SSLSocket): SocketAdapter
+ }
+}
diff --git a/paho/src/main/java/in/mohalla/paho/client/mqttv3/internal/platform/android/SocketAdapter.kt b/paho/src/main/java/in/mohalla/paho/client/mqttv3/internal/platform/android/SocketAdapter.kt
new file mode 100644
index 00000000..cd97fa12
--- /dev/null
+++ b/paho/src/main/java/in/mohalla/paho/client/mqttv3/internal/platform/android/SocketAdapter.kt
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2019 Square, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package `in`.mohalla.paho.client.mqttv3.internal.platform.android
+
+import javax.net.ssl.SSLSocket
+import javax.net.ssl.SSLSocketFactory
+import javax.net.ssl.X509TrustManager
+import `in`.mohalla.paho.client.mqttv3.Protocol
+
+interface SocketAdapter {
+ fun isSupported(): Boolean
+ fun trustManager(sslSocketFactory: SSLSocketFactory): X509TrustManager? = null
+ fun matchesSocket(sslSocket: SSLSocket): Boolean
+ fun matchesSocketFactory(sslSocketFactory: SSLSocketFactory): Boolean = false
+
+ fun configureTlsExtensions(
+ sslSocket: SSLSocket,
+ hostname: String?,
+ protocols: List
+ )
+
+ fun getSelectedProtocol(sslSocket: SSLSocket): String?
+}
diff --git a/paho/src/main/java/in/mohalla/paho/client/mqttv3/internal/platform/android/StandardAndroidSocketAdapter.kt b/paho/src/main/java/in/mohalla/paho/client/mqttv3/internal/platform/android/StandardAndroidSocketAdapter.kt
new file mode 100644
index 00000000..1b54af05
--- /dev/null
+++ b/paho/src/main/java/in/mohalla/paho/client/mqttv3/internal/platform/android/StandardAndroidSocketAdapter.kt
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2019 Square, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package `in`.mohalla.paho.client.mqttv3.internal.platform.android
+
+import javax.net.ssl.SSLSocket
+import javax.net.ssl.SSLSocketFactory
+import javax.net.ssl.X509TrustManager
+import `in`.mohalla.paho.client.mqttv3.internal.platform.Platform
+import `in`.mohalla.paho.client.mqttv3.readFieldOrNull
+
+/**
+ * Base Android reflection based SocketAdapter for the built in Android SSLSocket.
+ *
+ * It's assumed to always be present with known class names on Android devices, so we build
+ * optimistically via [buildIfSupported]. But it also doesn't assume a compile time API.
+ */
+class StandardAndroidSocketAdapter(
+ sslSocketClass: Class,
+ private val sslSocketFactoryClass: Class,
+ private val paramClass: Class<*>
+) : AndroidSocketAdapter(sslSocketClass) {
+
+ override fun matchesSocketFactory(sslSocketFactory: SSLSocketFactory): Boolean =
+ sslSocketFactoryClass.isInstance(sslSocketFactory)
+
+ override fun trustManager(sslSocketFactory: SSLSocketFactory): X509TrustManager? {
+ val context: Any? =
+ readFieldOrNull(
+ sslSocketFactory,
+ paramClass,
+ "sslParameters"
+ )
+ val x509TrustManager = readFieldOrNull(
+ context!!,
+ X509TrustManager::class.java,
+ "x509TrustManager"
+ )
+ return x509TrustManager ?: readFieldOrNull(
+ context,
+ X509TrustManager::class.java,
+ "trustManager"
+ )
+ }
+
+ companion object {
+ @Suppress("UNCHECKED_CAST")
+ fun buildIfSupported(packageName: String = "com.android.org.conscrypt"): SocketAdapter? {
+ return try {
+ val sslSocketClass = Class.forName("$packageName.OpenSSLSocketImpl") as Class
+ val sslSocketFactoryClass =
+ Class.forName("$packageName.OpenSSLSocketFactoryImpl") as Class
+ val paramsClass = Class.forName("$packageName.SSLParametersImpl")
+
+ StandardAndroidSocketAdapter(sslSocketClass, sslSocketFactoryClass, paramsClass)
+ } catch (e: Exception) {
+ Platform.get().log(level = Platform.WARN, message = "unable to load android socket classes", t = e)
+ null
+ }
+ }
+ }
+}
diff --git a/paho/src/main/java/in/mohalla/paho/client/mqttv3/internal/tls/BasicCertificateChainCleaner.kt b/paho/src/main/java/in/mohalla/paho/client/mqttv3/internal/tls/BasicCertificateChainCleaner.kt
new file mode 100644
index 00000000..31fcf7e0
--- /dev/null
+++ b/paho/src/main/java/in/mohalla/paho/client/mqttv3/internal/tls/BasicCertificateChainCleaner.kt
@@ -0,0 +1,139 @@
+/*
+ * Copyright (C) 2016 Square, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package `in`.mohalla.paho.client.mqttv3.internal.tls
+
+import java.security.GeneralSecurityException
+import java.security.cert.Certificate
+import java.security.cert.X509Certificate
+import java.util.ArrayDeque
+import java.util.Deque
+import javax.net.ssl.SSLPeerUnverifiedException
+
+/**
+ * A certificate chain cleaner that uses a set of trusted root certificates to build the trusted
+ * chain. This class duplicates the clean chain building performed during the TLS handshake. We
+ * prefer other mechanisms where they exist, such as with
+ * [okhttp3.internal.platform.AndroidPlatform.AndroidCertificateChainCleaner].
+ *
+ * This class includes code from [Conscrypt's][Conscrypt] [TrustManagerImpl] and
+ * [TrustedCertificateIndex].
+ *
+ * [Conscrypt]: https://conscrypt.org/
+ */
+class BasicCertificateChainCleaner(
+ private val trustRootIndex: TrustRootIndex
+) : CertificateChainCleaner() {
+
+ /**
+ * Returns a cleaned chain for [chain].
+ *
+ * This method throws if the complete chain to a trusted CA certificate cannot be constructed.
+ * This is unexpected unless the trust root index in this class has a different trust manager than
+ * what was used to establish [chain].
+ */
+ @Throws(SSLPeerUnverifiedException::class)
+ override fun clean(chain: List, hostname: String): List {
+ val queue: Deque = ArrayDeque(chain)
+ val result = mutableListOf()
+ result.add(queue.removeFirst())
+ var foundTrustedCertificate = false
+
+ followIssuerChain@
+ for (c in 0 until MAX_SIGNERS) {
+ val toVerify = result[result.size - 1] as X509Certificate
+
+ // If this cert has been signed by a trusted cert, use that. Add the trusted certificate to
+ // the end of the chain unless it's already present. (That would happen if the first
+ // certificate in the chain is itself a self-signed and trusted CA certificate.)
+ val trustedCert = trustRootIndex.findByIssuerAndSignature(toVerify)
+ if (trustedCert != null) {
+ if (result.size > 1 || toVerify != trustedCert) {
+ result.add(trustedCert)
+ }
+ if (verifySignature(trustedCert, trustedCert, result.size - 2)) {
+ return result // The self-signed cert is a root CA. We're done.
+ }
+ foundTrustedCertificate = true
+ continue
+ }
+
+ // Search for the certificate in the chain that signed this certificate. This is typically
+ // the next element in the chain, but it could be any element.
+ val i = queue.iterator()
+ while (i.hasNext()) {
+ val signingCert = i.next() as X509Certificate
+ if (verifySignature(toVerify, signingCert, result.size - 1)) {
+ i.remove()
+ result.add(signingCert)
+ continue@followIssuerChain
+ }
+ }
+
+ // We've reached the end of the chain. If any cert in the chain is trusted, we're done.
+ if (foundTrustedCertificate) {
+ return result
+ }
+
+ // The last link isn't trusted. Fail.
+ throw SSLPeerUnverifiedException(
+ "Failed to find a trusted cert that signed $toVerify"
+ )
+ }
+
+ throw SSLPeerUnverifiedException("Certificate chain too long: $result")
+ }
+
+ /**
+ * Returns true if [toVerify] was signed by [signingCert]'s public key.
+ *
+ * @param minIntermediates the minimum number of intermediate certificates in [signingCert]. This
+ * is -1 if signing cert is a lone self-signed certificate.
+ */
+ private fun verifySignature(
+ toVerify: X509Certificate,
+ signingCert: X509Certificate,
+ minIntermediates: Int
+ ): Boolean {
+ if (toVerify.issuerDN != signingCert.subjectDN) {
+ return false
+ }
+ if (signingCert.basicConstraints < minIntermediates) {
+ return false // The signer can't have this many intermediates beneath it.
+ }
+ return try {
+ toVerify.verify(signingCert.publicKey)
+ true
+ } catch (verifyFailed: GeneralSecurityException) {
+ false
+ }
+ }
+
+ override fun hashCode(): Int {
+ return trustRootIndex.hashCode()
+ }
+
+ override fun equals(other: Any?): Boolean {
+ return if (other === this) {
+ true
+ } else {
+ other is BasicCertificateChainCleaner && other.trustRootIndex == trustRootIndex
+ }
+ }
+
+ companion object {
+ private const val MAX_SIGNERS = 9
+ }
+}
diff --git a/paho/src/main/java/in/mohalla/paho/client/mqttv3/internal/tls/BasicTrustRootIndex.kt b/paho/src/main/java/in/mohalla/paho/client/mqttv3/internal/tls/BasicTrustRootIndex.kt
new file mode 100644
index 00000000..62eb8a6f
--- /dev/null
+++ b/paho/src/main/java/in/mohalla/paho/client/mqttv3/internal/tls/BasicTrustRootIndex.kt
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2016 Square, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package `in`.mohalla.paho.client.mqttv3.internal.tls
+
+import java.security.cert.X509Certificate
+import javax.security.auth.x500.X500Principal
+
+/** A simple index that of trusted root certificates that have been loaded into memory. */
+class BasicTrustRootIndex(vararg caCerts: X509Certificate) : TrustRootIndex {
+ private val subjectToCaCerts: Map>
+
+ init {
+ val map = mutableMapOf>()
+ for (caCert in caCerts) {
+ map.getOrPut(caCert.subjectX500Principal) { mutableSetOf() }.add(caCert)
+ }
+ this.subjectToCaCerts = map
+ }
+
+ override fun findByIssuerAndSignature(cert: X509Certificate): X509Certificate? {
+ val issuer = cert.issuerX500Principal
+ val subjectCaCerts = subjectToCaCerts[issuer] ?: return null
+
+ return subjectCaCerts.firstOrNull {
+ try {
+ cert.verify(it.publicKey)
+ return@firstOrNull true
+ } catch (_: Exception) {
+ return@firstOrNull false
+ }
+ }
+ }
+
+ override fun equals(other: Any?): Boolean {
+ return other === this ||
+ (other is BasicTrustRootIndex && other.subjectToCaCerts == subjectToCaCerts)
+ }
+
+ override fun hashCode(): Int {
+ return subjectToCaCerts.hashCode()
+ }
+}
diff --git a/paho/src/main/java/in/mohalla/paho/client/mqttv3/internal/tls/CertificateChainCleaner.kt b/paho/src/main/java/in/mohalla/paho/client/mqttv3/internal/tls/CertificateChainCleaner.kt
new file mode 100644
index 00000000..f70b3825
--- /dev/null
+++ b/paho/src/main/java/in/mohalla/paho/client/mqttv3/internal/tls/CertificateChainCleaner.kt
@@ -0,0 +1,49 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package `in`.mohalla.paho.client.mqttv3.internal.tls
+
+import java.security.cert.Certificate
+import java.security.cert.X509Certificate
+import javax.net.ssl.SSLPeerUnverifiedException
+import javax.net.ssl.X509TrustManager
+import `in`.mohalla.paho.client.mqttv3.internal.platform.Platform
+
+/**
+ * Computes the effective certificate chain from the raw array returned by Java's built in TLS APIs.
+ * Cleaning a chain returns a list of certificates where the first element is `chain[0]`, each
+ * certificate is signed by the certificate that follows, and the last certificate is a trusted CA
+ * certificate.
+ *
+ * Use of the chain cleaner is necessary to omit unexpected certificates that aren't relevant to
+ * the TLS handshake and to extract the trusted CA certificate for the benefit of certificate
+ * pinning.
+ */
+abstract class CertificateChainCleaner {
+
+ @Throws(SSLPeerUnverifiedException::class)
+ abstract fun clean(chain: List, hostname: String): List
+
+ companion object {
+ fun get(trustManager: X509TrustManager): CertificateChainCleaner {
+ return Platform.get().buildCertificateChainCleaner(trustManager)
+ }
+
+ fun get(vararg caCerts: X509Certificate): CertificateChainCleaner {
+ return BasicCertificateChainCleaner(BasicTrustRootIndex(*caCerts))
+ }
+ }
+}
diff --git a/paho/src/main/java/in/mohalla/paho/client/mqttv3/internal/tls/CipherSuite.kt b/paho/src/main/java/in/mohalla/paho/client/mqttv3/internal/tls/CipherSuite.kt
new file mode 100644
index 00000000..5b550924
--- /dev/null
+++ b/paho/src/main/java/in/mohalla/paho/client/mqttv3/internal/tls/CipherSuite.kt
@@ -0,0 +1,665 @@
+/*
+ * Copyright (C) 2014 Square, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package `in`.mohalla.paho.client.mqttv3.internal.tls
+
+/**
+ * [TLS cipher suites][iana_tls_parameters].
+ *
+ * **Not all cipher suites are supported on all platforms.** As newer cipher suites are created (for
+ * stronger privacy, better performance, etc.) they will be adopted by the platform and then exposed
+ * here. Cipher suites that are not available on either Android (through API level 24) or Java
+ * (through JDK 9) are omitted for brevity.
+ *
+ * See [Android SSLEngine][sslengine] which lists the cipher suites supported by Android.
+ *
+ * See [JDK Providers][oracle_providers] which lists the cipher suites supported by Oracle.
+ *
+ * See [NativeCrypto.java][conscrypt_providers] which lists the cipher suites supported by
+ * Conscrypt.
+ *
+ * [iana_tls_parameters]: https://www.iana.org/assignments/tls-parameters/tls-parameters.xhtml
+ * [sslengine]: https://developer.android.com/reference/javax/net/ssl/SSLEngine.html
+ * [oracle_providers]: https://docs.oracle.com/javase/10/security/oracle-providers.htm
+ * [conscrypt_providers]: https://github.com/google/conscrypt/blob/master/common/src/main/java/org/conscrypt/NativeCrypto.java
+ */
+class CipherSuite private constructor(
+ /**
+ * Returns the Java name of this cipher suite. For some older cipher suites the Java name has the
+ * prefix `SSL_`, causing the Java name to be different from the instance name which is always
+ * prefixed `TLS_`. For example, `TLS_RSA_EXPORT_WITH_RC4_40_MD5.javaName()` is
+ * `"SSL_RSA_EXPORT_WITH_RC4_40_MD5"`.
+ */
+ @get:JvmName("javaName") val javaName: String
+) {
+ @JvmName("-deprecated_javaName")
+ @Deprecated(
+ message = "moved to val",
+ replaceWith = ReplaceWith(expression = "javaName"),
+ level = DeprecationLevel.ERROR
+ )
+ fun javaName(): String = javaName
+
+ override fun toString(): String = javaName
+
+ companion object {
+ /**
+ * Compares cipher suites names like "TLS_RSA_WITH_NULL_MD5" and "SSL_RSA_WITH_NULL_MD5",
+ * ignoring the "TLS_" or "SSL_" prefix which is not consistent across platforms. In particular
+ * some IBM JVMs use the "SSL_" prefix everywhere whereas Oracle JVMs mix "TLS_" and "SSL_".
+ */
+ internal val ORDER_BY_NAME = object : Comparator {
+ override fun compare(a: String, b: String): Int {
+ var i = 4
+ val limit = minOf(a.length, b.length)
+ while (i < limit) {
+ val charA = a[i]
+ val charB = b[i]
+ if (charA != charB) return if (charA < charB) -1 else 1
+ i++
+ }
+ val lengthA = a.length
+ val lengthB = b.length
+ if (lengthA != lengthB) return if (lengthA < lengthB) -1 else 1
+ return 0
+ }
+ }
+
+ /**
+ * Holds interned instances. This needs to be above the init() calls below so that it's
+ * initialized by the time those parts of `()` run. Guarded by CipherSuite.class.
+ */
+ private val INSTANCES = mutableMapOf()
+
+ // Last updated 2016-07-03 using cipher suites from Android 24 and Java 9.
+
+ // @JvmField val TLS_NULL_WITH_NULL_NULL = init("TLS_NULL_WITH_NULL_NULL", 0x0000)
+ @JvmField val TLS_RSA_WITH_NULL_MD5 = init("SSL_RSA_WITH_NULL_MD5", 0x0001)
+
+ @JvmField val TLS_RSA_WITH_NULL_SHA = init("SSL_RSA_WITH_NULL_SHA", 0x0002)
+
+ @JvmField val TLS_RSA_EXPORT_WITH_RC4_40_MD5 =
+ init("SSL_RSA_EXPORT_WITH_RC4_40_MD5", 0x0003)
+
+ @JvmField val TLS_RSA_WITH_RC4_128_MD5 = init("SSL_RSA_WITH_RC4_128_MD5", 0x0004)
+
+ @JvmField val TLS_RSA_WITH_RC4_128_SHA = init("SSL_RSA_WITH_RC4_128_SHA", 0x0005)
+
+ // @JvmField val TLS_RSA_EXPORT_WITH_RC2_CBC_40_MD5 = init("SSL_RSA_EXPORT_WITH_RC2_CBC_40_MD5", 0x0006)
+ // @JvmField val TLS_RSA_WITH_IDEA_CBC_SHA = init("TLS_RSA_WITH_IDEA_CBC_SHA", 0x0007)
+ @JvmField val TLS_RSA_EXPORT_WITH_DES40_CBC_SHA =
+ init("SSL_RSA_EXPORT_WITH_DES40_CBC_SHA", 0x0008)
+
+ @JvmField val TLS_RSA_WITH_DES_CBC_SHA = init("SSL_RSA_WITH_DES_CBC_SHA", 0x0009)
+
+ @JvmField val TLS_RSA_WITH_3DES_EDE_CBC_SHA = init("SSL_RSA_WITH_3DES_EDE_CBC_SHA", 0x000a)
+
+ // @JvmField val TLS_DH_DSS_EXPORT_WITH_DES40_CBC_SHA = init("SSL_DH_DSS_EXPORT_WITH_DES40_CBC_SHA", 0x000b)
+ // @JvmField val TLS_DH_DSS_WITH_DES_CBC_SHA = init("TLS_DH_DSS_WITH_DES_CBC_SHA", 0x000c)
+ // @JvmField val TLS_DH_DSS_WITH_3DES_EDE_CBC_SHA = init("TLS_DH_DSS_WITH_3DES_EDE_CBC_SHA", 0x000d)
+ // @JvmField val TLS_DH_RSA_EXPORT_WITH_DES40_CBC_SHA = init("SSL_DH_RSA_EXPORT_WITH_DES40_CBC_SHA", 0x000e)
+ // @JvmField val TLS_DH_RSA_WITH_DES_CBC_SHA = init("TLS_DH_RSA_WITH_DES_CBC_SHA", 0x000f)
+ // @JvmField val TLS_DH_RSA_WITH_3DES_EDE_CBC_SHA = init("TLS_DH_RSA_WITH_3DES_EDE_CBC_SHA", 0x0010)
+ @JvmField val TLS_DHE_DSS_EXPORT_WITH_DES40_CBC_SHA =
+ init("SSL_DHE_DSS_EXPORT_WITH_DES40_CBC_SHA", 0x0011)
+
+ @JvmField val TLS_DHE_DSS_WITH_DES_CBC_SHA = init("SSL_DHE_DSS_WITH_DES_CBC_SHA", 0x0012)
+
+ @JvmField val TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA =
+ init("SSL_DHE_DSS_WITH_3DES_EDE_CBC_SHA", 0x0013)
+
+ @JvmField val TLS_DHE_RSA_EXPORT_WITH_DES40_CBC_SHA =
+ init("SSL_DHE_RSA_EXPORT_WITH_DES40_CBC_SHA", 0x0014)
+
+ @JvmField val TLS_DHE_RSA_WITH_DES_CBC_SHA = init("SSL_DHE_RSA_WITH_DES_CBC_SHA", 0x0015)
+
+ @JvmField val TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA =
+ init("SSL_DHE_RSA_WITH_3DES_EDE_CBC_SHA", 0x0016)
+
+ @JvmField val TLS_DH_anon_EXPORT_WITH_RC4_40_MD5 =
+ init("SSL_DH_anon_EXPORT_WITH_RC4_40_MD5", 0x0017)
+
+ @JvmField val TLS_DH_anon_WITH_RC4_128_MD5 = init("SSL_DH_anon_WITH_RC4_128_MD5", 0x0018)
+
+ @JvmField val TLS_DH_anon_EXPORT_WITH_DES40_CBC_SHA =
+ init("SSL_DH_anon_EXPORT_WITH_DES40_CBC_SHA", 0x0019)
+
+ @JvmField val TLS_DH_anon_WITH_DES_CBC_SHA = init("SSL_DH_anon_WITH_DES_CBC_SHA", 0x001a)
+
+ @JvmField val TLS_DH_anon_WITH_3DES_EDE_CBC_SHA =
+ init("SSL_DH_anon_WITH_3DES_EDE_CBC_SHA", 0x001b)
+
+ @JvmField val TLS_KRB5_WITH_DES_CBC_SHA = init("TLS_KRB5_WITH_DES_CBC_SHA", 0x001e)
+
+ @JvmField val TLS_KRB5_WITH_3DES_EDE_CBC_SHA =
+ init("TLS_KRB5_WITH_3DES_EDE_CBC_SHA", 0x001f)
+
+ @JvmField val TLS_KRB5_WITH_RC4_128_SHA = init("TLS_KRB5_WITH_RC4_128_SHA", 0x0020)
+
+ // @JvmField val TLS_KRB5_WITH_IDEA_CBC_SHA = init("TLS_KRB5_WITH_IDEA_CBC_SHA", 0x0021)
+ @JvmField val TLS_KRB5_WITH_DES_CBC_MD5 = init("TLS_KRB5_WITH_DES_CBC_MD5", 0x0022)
+
+ @JvmField val TLS_KRB5_WITH_3DES_EDE_CBC_MD5 =
+ init("TLS_KRB5_WITH_3DES_EDE_CBC_MD5", 0x0023)
+
+ @JvmField val TLS_KRB5_WITH_RC4_128_MD5 = init("TLS_KRB5_WITH_RC4_128_MD5", 0x0024)
+
+ // @JvmField val TLS_KRB5_WITH_IDEA_CBC_MD5 = init("TLS_KRB5_WITH_IDEA_CBC_MD5", 0x0025)
+ @JvmField val TLS_KRB5_EXPORT_WITH_DES_CBC_40_SHA =
+ init("TLS_KRB5_EXPORT_WITH_DES_CBC_40_SHA", 0x0026)
+
+ // @JvmField val TLS_KRB5_EXPORT_WITH_RC2_CBC_40_SHA = init("TLS_KRB5_EXPORT_WITH_RC2_CBC_40_SHA", 0x0027)
+ @JvmField val TLS_KRB5_EXPORT_WITH_RC4_40_SHA =
+ init("TLS_KRB5_EXPORT_WITH_RC4_40_SHA", 0x0028)
+
+ @JvmField val TLS_KRB5_EXPORT_WITH_DES_CBC_40_MD5 =
+ init("TLS_KRB5_EXPORT_WITH_DES_CBC_40_MD5", 0x0029)
+
+ // @JvmField val TLS_KRB5_EXPORT_WITH_RC2_CBC_40_MD5 = init("TLS_KRB5_EXPORT_WITH_RC2_CBC_40_MD5", 0x002a)
+ @JvmField val TLS_KRB5_EXPORT_WITH_RC4_40_MD5 =
+ init("TLS_KRB5_EXPORT_WITH_RC4_40_MD5", 0x002b)
+
+ // @JvmField val TLS_PSK_WITH_NULL_SHA = init("TLS_PSK_WITH_NULL_SHA", 0x002c)
+ // @JvmField val TLS_DHE_PSK_WITH_NULL_SHA = init("TLS_DHE_PSK_WITH_NULL_SHA", 0x002d)
+ // @JvmField val TLS_RSA_PSK_WITH_NULL_SHA = init("TLS_RSA_PSK_WITH_NULL_SHA", 0x002e)
+ @JvmField val TLS_RSA_WITH_AES_128_CBC_SHA = init("TLS_RSA_WITH_AES_128_CBC_SHA", 0x002f)
+
+ // @JvmField val TLS_DH_DSS_WITH_AES_128_CBC_SHA = init("TLS_DH_DSS_WITH_AES_128_CBC_SHA", 0x0030)
+ // @JvmField val TLS_DH_RSA_WITH_AES_128_CBC_SHA = init("TLS_DH_RSA_WITH_AES_128_CBC_SHA", 0x0031)
+ @JvmField val TLS_DHE_DSS_WITH_AES_128_CBC_SHA =
+ init("TLS_DHE_DSS_WITH_AES_128_CBC_SHA", 0x0032)
+
+ @JvmField val TLS_DHE_RSA_WITH_AES_128_CBC_SHA =
+ init("TLS_DHE_RSA_WITH_AES_128_CBC_SHA", 0x0033)
+
+ @JvmField val TLS_DH_anon_WITH_AES_128_CBC_SHA =
+ init("TLS_DH_anon_WITH_AES_128_CBC_SHA", 0x0034)
+
+ @JvmField val TLS_RSA_WITH_AES_256_CBC_SHA = init("TLS_RSA_WITH_AES_256_CBC_SHA", 0x0035)
+
+ // @JvmField val TLS_DH_DSS_WITH_AES_256_CBC_SHA = init("TLS_DH_DSS_WITH_AES_256_CBC_SHA", 0x0036)
+ // @JvmField val TLS_DH_RSA_WITH_AES_256_CBC_SHA = init("TLS_DH_RSA_WITH_AES_256_CBC_SHA", 0x0037)
+ @JvmField val TLS_DHE_DSS_WITH_AES_256_CBC_SHA =
+ init("TLS_DHE_DSS_WITH_AES_256_CBC_SHA", 0x0038)
+
+ @JvmField val TLS_DHE_RSA_WITH_AES_256_CBC_SHA =
+ init("TLS_DHE_RSA_WITH_AES_256_CBC_SHA", 0x0039)
+
+ @JvmField val TLS_DH_anon_WITH_AES_256_CBC_SHA =
+ init("TLS_DH_anon_WITH_AES_256_CBC_SHA", 0x003a)
+
+ @JvmField val TLS_RSA_WITH_NULL_SHA256 = init("TLS_RSA_WITH_NULL_SHA256", 0x003b)
+
+ @JvmField val TLS_RSA_WITH_AES_128_CBC_SHA256 =
+ init("TLS_RSA_WITH_AES_128_CBC_SHA256", 0x003c)
+
+ @JvmField val TLS_RSA_WITH_AES_256_CBC_SHA256 =
+ init("TLS_RSA_WITH_AES_256_CBC_SHA256", 0x003d)
+
+ // @JvmField val TLS_DH_DSS_WITH_AES_128_CBC_SHA256 = init("TLS_DH_DSS_WITH_AES_128_CBC_SHA256", 0x003e)
+ // @JvmField val TLS_DH_RSA_WITH_AES_128_CBC_SHA256 = init("TLS_DH_RSA_WITH_AES_128_CBC_SHA256", 0x003f)
+ @JvmField val TLS_DHE_DSS_WITH_AES_128_CBC_SHA256 =
+ init("TLS_DHE_DSS_WITH_AES_128_CBC_SHA256", 0x0040)
+
+ @JvmField val TLS_RSA_WITH_CAMELLIA_128_CBC_SHA =
+ init("TLS_RSA_WITH_CAMELLIA_128_CBC_SHA", 0x0041)
+
+ // @JvmField val TLS_DH_DSS_WITH_CAMELLIA_128_CBC_SHA = init("TLS_DH_DSS_WITH_CAMELLIA_128_CBC_SHA", 0x0042)
+ // @JvmField val TLS_DH_RSA_WITH_CAMELLIA_128_CBC_SHA = init("TLS_DH_RSA_WITH_CAMELLIA_128_CBC_SHA", 0x0043)
+ @JvmField val TLS_DHE_DSS_WITH_CAMELLIA_128_CBC_SHA =
+ init("TLS_DHE_DSS_WITH_CAMELLIA_128_CBC_SHA", 0x0044)
+
+ @JvmField val TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA =
+ init("TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA", 0x0045)
+
+ // @JvmField val TLS_DH_anon_WITH_CAMELLIA_128_CBC_SHA = init("TLS_DH_anon_WITH_CAMELLIA_128_CBC_SHA", 0x0046)
+ @JvmField val TLS_DHE_RSA_WITH_AES_128_CBC_SHA256 =
+ init("TLS_DHE_RSA_WITH_AES_128_CBC_SHA256", 0x0067)
+
+ // @JvmField val TLS_DH_DSS_WITH_AES_256_CBC_SHA256 = init("TLS_DH_DSS_WITH_AES_256_CBC_SHA256", 0x0068)
+ // @JvmField val TLS_DH_RSA_WITH_AES_256_CBC_SHA256 = init("TLS_DH_RSA_WITH_AES_256_CBC_SHA256", 0x0069)
+ @JvmField val TLS_DHE_DSS_WITH_AES_256_CBC_SHA256 =
+ init("TLS_DHE_DSS_WITH_AES_256_CBC_SHA256", 0x006a)
+
+ @JvmField val TLS_DHE_RSA_WITH_AES_256_CBC_SHA256 =
+ init("TLS_DHE_RSA_WITH_AES_256_CBC_SHA256", 0x006b)
+
+ @JvmField val TLS_DH_anon_WITH_AES_128_CBC_SHA256 =
+ init("TLS_DH_anon_WITH_AES_128_CBC_SHA256", 0x006c)
+
+ @JvmField val TLS_DH_anon_WITH_AES_256_CBC_SHA256 =
+ init("TLS_DH_anon_WITH_AES_256_CBC_SHA256", 0x006d)
+
+ @JvmField val TLS_RSA_WITH_CAMELLIA_256_CBC_SHA =
+ init("TLS_RSA_WITH_CAMELLIA_256_CBC_SHA", 0x0084)
+
+ // @JvmField val TLS_DH_DSS_WITH_CAMELLIA_256_CBC_SHA = init("TLS_DH_DSS_WITH_CAMELLIA_256_CBC_SHA", 0x0085)
+ // @JvmField val TLS_DH_RSA_WITH_CAMELLIA_256_CBC_SHA = init("TLS_DH_RSA_WITH_CAMELLIA_256_CBC_SHA", 0x0086)
+ @JvmField val TLS_DHE_DSS_WITH_CAMELLIA_256_CBC_SHA =
+ init("TLS_DHE_DSS_WITH_CAMELLIA_256_CBC_SHA", 0x0087)
+
+ @JvmField val TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA =
+ init("TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA", 0x0088)
+
+ // @JvmField val TLS_DH_anon_WITH_CAMELLIA_256_CBC_SHA = init("TLS_DH_anon_WITH_CAMELLIA_256_CBC_SHA", 0x0089)
+ @JvmField val TLS_PSK_WITH_RC4_128_SHA = init("TLS_PSK_WITH_RC4_128_SHA", 0x008a)
+
+ @JvmField val TLS_PSK_WITH_3DES_EDE_CBC_SHA = init("TLS_PSK_WITH_3DES_EDE_CBC_SHA", 0x008b)
+
+ @JvmField val TLS_PSK_WITH_AES_128_CBC_SHA = init("TLS_PSK_WITH_AES_128_CBC_SHA", 0x008c)
+
+ @JvmField val TLS_PSK_WITH_AES_256_CBC_SHA = init("TLS_PSK_WITH_AES_256_CBC_SHA", 0x008d)
+
+ // @JvmField val TLS_DHE_PSK_WITH_RC4_128_SHA = init("TLS_DHE_PSK_WITH_RC4_128_SHA", 0x008e)
+ // @JvmField val TLS_DHE_PSK_WITH_3DES_EDE_CBC_SHA = init("TLS_DHE_PSK_WITH_3DES_EDE_CBC_SHA", 0x008f)
+ // @JvmField val TLS_DHE_PSK_WITH_AES_128_CBC_SHA = init("TLS_DHE_PSK_WITH_AES_128_CBC_SHA", 0x0090)
+ // @JvmField val TLS_DHE_PSK_WITH_AES_256_CBC_SHA = init("TLS_DHE_PSK_WITH_AES_256_CBC_SHA", 0x0091)
+ // @JvmField val TLS_RSA_PSK_WITH_RC4_128_SHA = init("TLS_RSA_PSK_WITH_RC4_128_SHA", 0x0092)
+ // @JvmField val TLS_RSA_PSK_WITH_3DES_EDE_CBC_SHA = init("TLS_RSA_PSK_WITH_3DES_EDE_CBC_SHA", 0x0093)
+ // @JvmField val TLS_RSA_PSK_WITH_AES_128_CBC_SHA = init("TLS_RSA_PSK_WITH_AES_128_CBC_SHA", 0x0094)
+ // @JvmField val TLS_RSA_PSK_WITH_AES_256_CBC_SHA = init("TLS_RSA_PSK_WITH_AES_256_CBC_SHA", 0x0095)
+ @JvmField val TLS_RSA_WITH_SEED_CBC_SHA = init("TLS_RSA_WITH_SEED_CBC_SHA", 0x0096)
+
+ // @JvmField val TLS_DH_DSS_WITH_SEED_CBC_SHA = init("TLS_DH_DSS_WITH_SEED_CBC_SHA", 0x0097)
+ // @JvmField val TLS_DH_RSA_WITH_SEED_CBC_SHA = init("TLS_DH_RSA_WITH_SEED_CBC_SHA", 0x0098)
+ // @JvmField val TLS_DHE_DSS_WITH_SEED_CBC_SHA = init("TLS_DHE_DSS_WITH_SEED_CBC_SHA", 0x0099)
+ // @JvmField val TLS_DHE_RSA_WITH_SEED_CBC_SHA = init("TLS_DHE_RSA_WITH_SEED_CBC_SHA", 0x009a)
+ // @JvmField val TLS_DH_anon_WITH_SEED_CBC_SHA = init("TLS_DH_anon_WITH_SEED_CBC_SHA", 0x009b)
+ @JvmField val TLS_RSA_WITH_AES_128_GCM_SHA256 =
+ init("TLS_RSA_WITH_AES_128_GCM_SHA256", 0x009c)
+
+ @JvmField val TLS_RSA_WITH_AES_256_GCM_SHA384 =
+ init("TLS_RSA_WITH_AES_256_GCM_SHA384", 0x009d)
+
+ @JvmField val TLS_DHE_RSA_WITH_AES_128_GCM_SHA256 =
+ init("TLS_DHE_RSA_WITH_AES_128_GCM_SHA256", 0x009e)
+
+ @JvmField val TLS_DHE_RSA_WITH_AES_256_GCM_SHA384 =
+ init("TLS_DHE_RSA_WITH_AES_256_GCM_SHA384", 0x009f)
+
+ // @JvmField val TLS_DH_RSA_WITH_AES_128_GCM_SHA256 = init("TLS_DH_RSA_WITH_AES_128_GCM_SHA256", 0x00a0)
+ // @JvmField val TLS_DH_RSA_WITH_AES_256_GCM_SHA384 = init("TLS_DH_RSA_WITH_AES_256_GCM_SHA384", 0x00a1)
+ @JvmField val TLS_DHE_DSS_WITH_AES_128_GCM_SHA256 =
+ init("TLS_DHE_DSS_WITH_AES_128_GCM_SHA256", 0x00a2)
+
+ @JvmField val TLS_DHE_DSS_WITH_AES_256_GCM_SHA384 =
+ init("TLS_DHE_DSS_WITH_AES_256_GCM_SHA384", 0x00a3)
+
+ // @JvmField val TLS_DH_DSS_WITH_AES_128_GCM_SHA256 = init("TLS_DH_DSS_WITH_AES_128_GCM_SHA256", 0x00a4)
+ // @JvmField val TLS_DH_DSS_WITH_AES_256_GCM_SHA384 = init("TLS_DH_DSS_WITH_AES_256_GCM_SHA384", 0x00a5)
+ @JvmField val TLS_DH_anon_WITH_AES_128_GCM_SHA256 =
+ init("TLS_DH_anon_WITH_AES_128_GCM_SHA256", 0x00a6)
+
+ @JvmField val TLS_DH_anon_WITH_AES_256_GCM_SHA384 =
+ init("TLS_DH_anon_WITH_AES_256_GCM_SHA384", 0x00a7)
+
+ // @JvmField val TLS_PSK_WITH_AES_128_GCM_SHA256 = init("TLS_PSK_WITH_AES_128_GCM_SHA256", 0x00a8)
+ // @JvmField val TLS_PSK_WITH_AES_256_GCM_SHA384 = init("TLS_PSK_WITH_AES_256_GCM_SHA384", 0x00a9)
+ // @JvmField val TLS_DHE_PSK_WITH_AES_128_GCM_SHA256 = init("TLS_DHE_PSK_WITH_AES_128_GCM_SHA256", 0x00aa)
+ // @JvmField val TLS_DHE_PSK_WITH_AES_256_GCM_SHA384 = init("TLS_DHE_PSK_WITH_AES_256_GCM_SHA384", 0x00ab)
+ // @JvmField val TLS_RSA_PSK_WITH_AES_128_GCM_SHA256 = init("TLS_RSA_PSK_WITH_AES_128_GCM_SHA256", 0x00ac)
+ // @JvmField val TLS_RSA_PSK_WITH_AES_256_GCM_SHA384 = init("TLS_RSA_PSK_WITH_AES_256_GCM_SHA384", 0x00ad)
+ // @JvmField val TLS_PSK_WITH_AES_128_CBC_SHA256 = init("TLS_PSK_WITH_AES_128_CBC_SHA256", 0x00ae)
+ // @JvmField val TLS_PSK_WITH_AES_256_CBC_SHA384 = init("TLS_PSK_WITH_AES_256_CBC_SHA384", 0x00af)
+ // @JvmField val TLS_PSK_WITH_NULL_SHA256 = init("TLS_PSK_WITH_NULL_SHA256", 0x00b0)
+ // @JvmField val TLS_PSK_WITH_NULL_SHA384 = init("TLS_PSK_WITH_NULL_SHA384", 0x00b1)
+ // @JvmField val TLS_DHE_PSK_WITH_AES_128_CBC_SHA256 = init("TLS_DHE_PSK_WITH_AES_128_CBC_SHA256", 0x00b2)
+ // @JvmField val TLS_DHE_PSK_WITH_AES_256_CBC_SHA384 = init("TLS_DHE_PSK_WITH_AES_256_CBC_SHA384", 0x00b3)
+ // @JvmField val TLS_DHE_PSK_WITH_NULL_SHA256 = init("TLS_DHE_PSK_WITH_NULL_SHA256", 0x00b4)
+ // @JvmField val TLS_DHE_PSK_WITH_NULL_SHA384 = init("TLS_DHE_PSK_WITH_NULL_SHA384", 0x00b5)
+ // @JvmField val TLS_RSA_PSK_WITH_AES_128_CBC_SHA256 = init("TLS_RSA_PSK_WITH_AES_128_CBC_SHA256", 0x00b6)
+ // @JvmField val TLS_RSA_PSK_WITH_AES_256_CBC_SHA384 = init("TLS_RSA_PSK_WITH_AES_256_CBC_SHA384", 0x00b7)
+ // @JvmField val TLS_RSA_PSK_WITH_NULL_SHA256 = init("TLS_RSA_PSK_WITH_NULL_SHA256", 0x00b8)
+ // @JvmField val TLS_RSA_PSK_WITH_NULL_SHA384 = init("TLS_RSA_PSK_WITH_NULL_SHA384", 0x00b9)
+ // @JvmField val TLS_RSA_WITH_CAMELLIA_128_CBC_SHA256 = init("TLS_RSA_WITH_CAMELLIA_128_CBC_SHA256", 0x00ba)
+ // @JvmField val TLS_DH_DSS_WITH_CAMELLIA_128_CBC_SHA256 = init("TLS_DH_DSS_WITH_CAMELLIA_128_CBC_SHA256", 0x00bb)
+ // @JvmField val TLS_DH_RSA_WITH_CAMELLIA_128_CBC_SHA256 = init("TLS_DH_RSA_WITH_CAMELLIA_128_CBC_SHA256", 0x00bc)
+ // @JvmField val TLS_DHE_DSS_WITH_CAMELLIA_128_CBC_SHA256 = init("TLS_DHE_DSS_WITH_CAMELLIA_128_CBC_SHA256", 0x00bd)
+ // @JvmField val TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA256 = init("TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA256", 0x00be)
+ // @JvmField val TLS_DH_anon_WITH_CAMELLIA_128_CBC_SHA256 = init("TLS_DH_anon_WITH_CAMELLIA_128_CBC_SHA256", 0x00bf)
+ // @JvmField val TLS_RSA_WITH_CAMELLIA_256_CBC_SHA256 = init("TLS_RSA_WITH_CAMELLIA_256_CBC_SHA256", 0x00c0)
+ // @JvmField val TLS_DH_DSS_WITH_CAMELLIA_256_CBC_SHA256 = init("TLS_DH_DSS_WITH_CAMELLIA_256_CBC_SHA256", 0x00c1)
+ // @JvmField val TLS_DH_RSA_WITH_CAMELLIA_256_CBC_SHA256 = init("TLS_DH_RSA_WITH_CAMELLIA_256_CBC_SHA256", 0x00c2)
+ // @JvmField val TLS_DHE_DSS_WITH_CAMELLIA_256_CBC_SHA256 = init("TLS_DHE_DSS_WITH_CAMELLIA_256_CBC_SHA256", 0x00c3)
+ // @JvmField val TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA256 = init("TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA256", 0x00c4)
+ // @JvmField val TLS_DH_anon_WITH_CAMELLIA_256_CBC_SHA256 = init("TLS_DH_anon_WITH_CAMELLIA_256_CBC_SHA256", 0x00c5)
+ @JvmField val TLS_EMPTY_RENEGOTIATION_INFO_SCSV =
+ init("TLS_EMPTY_RENEGOTIATION_INFO_SCSV", 0x00ff)
+
+ @JvmField val TLS_FALLBACK_SCSV = init("TLS_FALLBACK_SCSV", 0x5600)
+
+ @JvmField val TLS_ECDH_ECDSA_WITH_NULL_SHA = init("TLS_ECDH_ECDSA_WITH_NULL_SHA", 0xc001)
+
+ @JvmField val TLS_ECDH_ECDSA_WITH_RC4_128_SHA =
+ init("TLS_ECDH_ECDSA_WITH_RC4_128_SHA", 0xc002)
+
+ @JvmField val TLS_ECDH_ECDSA_WITH_3DES_EDE_CBC_SHA =
+ init("TLS_ECDH_ECDSA_WITH_3DES_EDE_CBC_SHA", 0xc003)
+
+ @JvmField val TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA =
+ init("TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA", 0xc004)
+
+ @JvmField val TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA =
+ init("TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA", 0xc005)
+
+ @JvmField val TLS_ECDHE_ECDSA_WITH_NULL_SHA = init("TLS_ECDHE_ECDSA_WITH_NULL_SHA", 0xc006)
+
+ @JvmField val TLS_ECDHE_ECDSA_WITH_RC4_128_SHA =
+ init("TLS_ECDHE_ECDSA_WITH_RC4_128_SHA", 0xc007)
+
+ @JvmField val TLS_ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA =
+ init("TLS_ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA", 0xc008)
+
+ @JvmField val TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA =
+ init("TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA", 0xc009)
+
+ @JvmField val TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA =
+ init("TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA", 0xc00a)
+
+ @JvmField val TLS_ECDH_RSA_WITH_NULL_SHA = init("TLS_ECDH_RSA_WITH_NULL_SHA", 0xc00b)
+
+ @JvmField val TLS_ECDH_RSA_WITH_RC4_128_SHA = init("TLS_ECDH_RSA_WITH_RC4_128_SHA", 0xc00c)
+
+ @JvmField val TLS_ECDH_RSA_WITH_3DES_EDE_CBC_SHA =
+ init("TLS_ECDH_RSA_WITH_3DES_EDE_CBC_SHA", 0xc00d)
+
+ @JvmField val TLS_ECDH_RSA_WITH_AES_128_CBC_SHA =
+ init("TLS_ECDH_RSA_WITH_AES_128_CBC_SHA", 0xc00e)
+
+ @JvmField val TLS_ECDH_RSA_WITH_AES_256_CBC_SHA =
+ init("TLS_ECDH_RSA_WITH_AES_256_CBC_SHA", 0xc00f)
+
+ @JvmField val TLS_ECDHE_RSA_WITH_NULL_SHA = init("TLS_ECDHE_RSA_WITH_NULL_SHA", 0xc010)
+
+ @JvmField val TLS_ECDHE_RSA_WITH_RC4_128_SHA =
+ init("TLS_ECDHE_RSA_WITH_RC4_128_SHA", 0xc011)
+
+ @JvmField val TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA =
+ init("TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA", 0xc012)
+
+ @JvmField val TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA =
+ init("TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA", 0xc013)
+
+ @JvmField val TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA =
+ init("TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA", 0xc014)
+
+ @JvmField val TLS_ECDH_anon_WITH_NULL_SHA = init("TLS_ECDH_anon_WITH_NULL_SHA", 0xc015)
+
+ @JvmField val TLS_ECDH_anon_WITH_RC4_128_SHA =
+ init("TLS_ECDH_anon_WITH_RC4_128_SHA", 0xc016)
+
+ @JvmField val TLS_ECDH_anon_WITH_3DES_EDE_CBC_SHA =
+ init("TLS_ECDH_anon_WITH_3DES_EDE_CBC_SHA", 0xc017)
+
+ @JvmField val TLS_ECDH_anon_WITH_AES_128_CBC_SHA =
+ init("TLS_ECDH_anon_WITH_AES_128_CBC_SHA", 0xc018)
+
+ @JvmField val TLS_ECDH_anon_WITH_AES_256_CBC_SHA =
+ init("TLS_ECDH_anon_WITH_AES_256_CBC_SHA", 0xc019)
+
+ // @JvmField val TLS_SRP_SHA_WITH_3DES_EDE_CBC_SHA = init("TLS_SRP_SHA_WITH_3DES_EDE_CBC_SHA", 0xc01a)
+ // @JvmField val TLS_SRP_SHA_RSA_WITH_3DES_EDE_CBC_SHA = init("TLS_SRP_SHA_RSA_WITH_3DES_EDE_CBC_SHA", 0xc01b)
+ // @JvmField val TLS_SRP_SHA_DSS_WITH_3DES_EDE_CBC_SHA = init("TLS_SRP_SHA_DSS_WITH_3DES_EDE_CBC_SHA", 0xc01c)
+ // @JvmField val TLS_SRP_SHA_WITH_AES_128_CBC_SHA = init("TLS_SRP_SHA_WITH_AES_128_CBC_SHA", 0xc01d)
+ // @JvmField val TLS_SRP_SHA_RSA_WITH_AES_128_CBC_SHA = init("TLS_SRP_SHA_RSA_WITH_AES_128_CBC_SHA", 0xc01e)
+ // @JvmField val TLS_SRP_SHA_DSS_WITH_AES_128_CBC_SHA = init("TLS_SRP_SHA_DSS_WITH_AES_128_CBC_SHA", 0xc01f)
+ // @JvmField val TLS_SRP_SHA_WITH_AES_256_CBC_SHA = init("TLS_SRP_SHA_WITH_AES_256_CBC_SHA", 0xc020)
+ // @JvmField val TLS_SRP_SHA_RSA_WITH_AES_256_CBC_SHA = init("TLS_SRP_SHA_RSA_WITH_AES_256_CBC_SHA", 0xc021)
+ // @JvmField val TLS_SRP_SHA_DSS_WITH_AES_256_CBC_SHA = init("TLS_SRP_SHA_DSS_WITH_AES_256_CBC_SHA", 0xc022)
+ @JvmField val TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256 =
+ init("TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256", 0xc023)
+
+ @JvmField val TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384 =
+ init("TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384", 0xc024)
+
+ @JvmField val TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA256 =
+ init("TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA256", 0xc025)
+
+ @JvmField val TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA384 =
+ init("TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA384", 0xc026)
+
+ @JvmField val TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256 =
+ init("TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256", 0xc027)
+
+ @JvmField val TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384 =
+ init("TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384", 0xc028)
+
+ @JvmField val TLS_ECDH_RSA_WITH_AES_128_CBC_SHA256 =
+ init("TLS_ECDH_RSA_WITH_AES_128_CBC_SHA256", 0xc029)
+
+ @JvmField val TLS_ECDH_RSA_WITH_AES_256_CBC_SHA384 =
+ init("TLS_ECDH_RSA_WITH_AES_256_CBC_SHA384", 0xc02a)
+
+ @JvmField val TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256 =
+ init("TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256", 0xc02b)
+
+ @JvmField val TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384 =
+ init("TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384", 0xc02c)
+
+ @JvmField val TLS_ECDH_ECDSA_WITH_AES_128_GCM_SHA256 =
+ init("TLS_ECDH_ECDSA_WITH_AES_128_GCM_SHA256", 0xc02d)
+
+ @JvmField val TLS_ECDH_ECDSA_WITH_AES_256_GCM_SHA384 =
+ init("TLS_ECDH_ECDSA_WITH_AES_256_GCM_SHA384", 0xc02e)
+
+ @JvmField val TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 =
+ init("TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256", 0xc02f)
+
+ @JvmField val TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 =
+ init("TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384", 0xc030)
+
+ @JvmField val TLS_ECDH_RSA_WITH_AES_128_GCM_SHA256 =
+ init("TLS_ECDH_RSA_WITH_AES_128_GCM_SHA256", 0xc031)
+
+ @JvmField val TLS_ECDH_RSA_WITH_AES_256_GCM_SHA384 =
+ init("TLS_ECDH_RSA_WITH_AES_256_GCM_SHA384", 0xc032)
+
+ // @JvmField val TLS_ECDHE_PSK_WITH_RC4_128_SHA = init("TLS_ECDHE_PSK_WITH_RC4_128_SHA", 0xc033)
+ // @JvmField val TLS_ECDHE_PSK_WITH_3DES_EDE_CBC_SHA = init("TLS_ECDHE_PSK_WITH_3DES_EDE_CBC_SHA", 0xc034)
+ @JvmField val TLS_ECDHE_PSK_WITH_AES_128_CBC_SHA =
+ init("TLS_ECDHE_PSK_WITH_AES_128_CBC_SHA", 0xc035)
+
+ @JvmField val TLS_ECDHE_PSK_WITH_AES_256_CBC_SHA =
+ init("TLS_ECDHE_PSK_WITH_AES_256_CBC_SHA", 0xc036)
+
+ // @JvmField val TLS_ECDHE_PSK_WITH_AES_128_CBC_SHA256 = init("TLS_ECDHE_PSK_WITH_AES_128_CBC_SHA256", 0xc037)
+ // @JvmField val TLS_ECDHE_PSK_WITH_AES_256_CBC_SHA384 = init("TLS_ECDHE_PSK_WITH_AES_256_CBC_SHA384", 0xc038)
+ // @JvmField val TLS_ECDHE_PSK_WITH_NULL_SHA = init("TLS_ECDHE_PSK_WITH_NULL_SHA", 0xc039)
+ // @JvmField val TLS_ECDHE_PSK_WITH_NULL_SHA256 = init("TLS_ECDHE_PSK_WITH_NULL_SHA256", 0xc03a)
+ // @JvmField val TLS_ECDHE_PSK_WITH_NULL_SHA384 = init("TLS_ECDHE_PSK_WITH_NULL_SHA384", 0xc03b)
+ // @JvmField val TLS_RSA_WITH_ARIA_128_CBC_SHA256 = init("TLS_RSA_WITH_ARIA_128_CBC_SHA256", 0xc03c)
+ // @JvmField val TLS_RSA_WITH_ARIA_256_CBC_SHA384 = init("TLS_RSA_WITH_ARIA_256_CBC_SHA384", 0xc03d)
+ // @JvmField val TLS_DH_DSS_WITH_ARIA_128_CBC_SHA256 = init("TLS_DH_DSS_WITH_ARIA_128_CBC_SHA256", 0xc03e)
+ // @JvmField val TLS_DH_DSS_WITH_ARIA_256_CBC_SHA384 = init("TLS_DH_DSS_WITH_ARIA_256_CBC_SHA384", 0xc03f)
+ // @JvmField val TLS_DH_RSA_WITH_ARIA_128_CBC_SHA256 = init("TLS_DH_RSA_WITH_ARIA_128_CBC_SHA256", 0xc040)
+ // @JvmField val TLS_DH_RSA_WITH_ARIA_256_CBC_SHA384 = init("TLS_DH_RSA_WITH_ARIA_256_CBC_SHA384", 0xc041)
+ // @JvmField val TLS_DHE_DSS_WITH_ARIA_128_CBC_SHA256 = init("TLS_DHE_DSS_WITH_ARIA_128_CBC_SHA256", 0xc042)
+ // @JvmField val TLS_DHE_DSS_WITH_ARIA_256_CBC_SHA384 = init("TLS_DHE_DSS_WITH_ARIA_256_CBC_SHA384", 0xc043)
+ // @JvmField val TLS_DHE_RSA_WITH_ARIA_128_CBC_SHA256 = init("TLS_DHE_RSA_WITH_ARIA_128_CBC_SHA256", 0xc044)
+ // @JvmField val TLS_DHE_RSA_WITH_ARIA_256_CBC_SHA384 = init("TLS_DHE_RSA_WITH_ARIA_256_CBC_SHA384", 0xc045)
+ // @JvmField val TLS_DH_anon_WITH_ARIA_128_CBC_SHA256 = init("TLS_DH_anon_WITH_ARIA_128_CBC_SHA256", 0xc046)
+ // @JvmField val TLS_DH_anon_WITH_ARIA_256_CBC_SHA384 = init("TLS_DH_anon_WITH_ARIA_256_CBC_SHA384", 0xc047)
+ // @JvmField val TLS_ECDHE_ECDSA_WITH_ARIA_128_CBC_SHA256 = init("TLS_ECDHE_ECDSA_WITH_ARIA_128_CBC_SHA256", 0xc048)
+ // @JvmField val TLS_ECDHE_ECDSA_WITH_ARIA_256_CBC_SHA384 = init("TLS_ECDHE_ECDSA_WITH_ARIA_256_CBC_SHA384", 0xc049)
+ // @JvmField val TLS_ECDH_ECDSA_WITH_ARIA_128_CBC_SHA256 = init("TLS_ECDH_ECDSA_WITH_ARIA_128_CBC_SHA256", 0xc04a)
+ // @JvmField val TLS_ECDH_ECDSA_WITH_ARIA_256_CBC_SHA384 = init("TLS_ECDH_ECDSA_WITH_ARIA_256_CBC_SHA384", 0xc04b)
+ // @JvmField val TLS_ECDHE_RSA_WITH_ARIA_128_CBC_SHA256 = init("TLS_ECDHE_RSA_WITH_ARIA_128_CBC_SHA256", 0xc04c)
+ // @JvmField val TLS_ECDHE_RSA_WITH_ARIA_256_CBC_SHA384 = init("TLS_ECDHE_RSA_WITH_ARIA_256_CBC_SHA384", 0xc04d)
+ // @JvmField val TLS_ECDH_RSA_WITH_ARIA_128_CBC_SHA256 = init("TLS_ECDH_RSA_WITH_ARIA_128_CBC_SHA256", 0xc04e)
+ // @JvmField val TLS_ECDH_RSA_WITH_ARIA_256_CBC_SHA384 = init("TLS_ECDH_RSA_WITH_ARIA_256_CBC_SHA384", 0xc04f)
+ // @JvmField val TLS_RSA_WITH_ARIA_128_GCM_SHA256 = init("TLS_RSA_WITH_ARIA_128_GCM_SHA256", 0xc050)
+ // @JvmField val TLS_RSA_WITH_ARIA_256_GCM_SHA384 = init("TLS_RSA_WITH_ARIA_256_GCM_SHA384", 0xc051)
+ // @JvmField val TLS_DHE_RSA_WITH_ARIA_128_GCM_SHA256 = init("TLS_DHE_RSA_WITH_ARIA_128_GCM_SHA256", 0xc052)
+ // @JvmField val TLS_DHE_RSA_WITH_ARIA_256_GCM_SHA384 = init("TLS_DHE_RSA_WITH_ARIA_256_GCM_SHA384", 0xc053)
+ // @JvmField val TLS_DH_RSA_WITH_ARIA_128_GCM_SHA256 = init("TLS_DH_RSA_WITH_ARIA_128_GCM_SHA256", 0xc054)
+ // @JvmField val TLS_DH_RSA_WITH_ARIA_256_GCM_SHA384 = init("TLS_DH_RSA_WITH_ARIA_256_GCM_SHA384", 0xc055)
+ // @JvmField val TLS_DHE_DSS_WITH_ARIA_128_GCM_SHA256 = init("TLS_DHE_DSS_WITH_ARIA_128_GCM_SHA256", 0xc056)
+ // @JvmField val TLS_DHE_DSS_WITH_ARIA_256_GCM_SHA384 = init("TLS_DHE_DSS_WITH_ARIA_256_GCM_SHA384", 0xc057)
+ // @JvmField val TLS_DH_DSS_WITH_ARIA_128_GCM_SHA256 = init("TLS_DH_DSS_WITH_ARIA_128_GCM_SHA256", 0xc058)
+ // @JvmField val TLS_DH_DSS_WITH_ARIA_256_GCM_SHA384 = init("TLS_DH_DSS_WITH_ARIA_256_GCM_SHA384", 0xc059)
+ // @JvmField val TLS_DH_anon_WITH_ARIA_128_GCM_SHA256 = init("TLS_DH_anon_WITH_ARIA_128_GCM_SHA256", 0xc05a)
+ // @JvmField val TLS_DH_anon_WITH_ARIA_256_GCM_SHA384 = init("TLS_DH_anon_WITH_ARIA_256_GCM_SHA384", 0xc05b)
+ // @JvmField val TLS_ECDHE_ECDSA_WITH_ARIA_128_GCM_SHA256 = init("TLS_ECDHE_ECDSA_WITH_ARIA_128_GCM_SHA256", 0xc05c)
+ // @JvmField val TLS_ECDHE_ECDSA_WITH_ARIA_256_GCM_SHA384 = init("TLS_ECDHE_ECDSA_WITH_ARIA_256_GCM_SHA384", 0xc05d)
+ // @JvmField val TLS_ECDH_ECDSA_WITH_ARIA_128_GCM_SHA256 = init("TLS_ECDH_ECDSA_WITH_ARIA_128_GCM_SHA256", 0xc05e)
+ // @JvmField val TLS_ECDH_ECDSA_WITH_ARIA_256_GCM_SHA384 = init("TLS_ECDH_ECDSA_WITH_ARIA_256_GCM_SHA384", 0xc05f)
+ // @JvmField val TLS_ECDHE_RSA_WITH_ARIA_128_GCM_SHA256 = init("TLS_ECDHE_RSA_WITH_ARIA_128_GCM_SHA256", 0xc060)
+ // @JvmField val TLS_ECDHE_RSA_WITH_ARIA_256_GCM_SHA384 = init("TLS_ECDHE_RSA_WITH_ARIA_256_GCM_SHA384", 0xc061)
+ // @JvmField val TLS_ECDH_RSA_WITH_ARIA_128_GCM_SHA256 = init("TLS_ECDH_RSA_WITH_ARIA_128_GCM_SHA256", 0xc062)
+ // @JvmField val TLS_ECDH_RSA_WITH_ARIA_256_GCM_SHA384 = init("TLS_ECDH_RSA_WITH_ARIA_256_GCM_SHA384", 0xc063)
+ // @JvmField val TLS_PSK_WITH_ARIA_128_CBC_SHA256 = init("TLS_PSK_WITH_ARIA_128_CBC_SHA256", 0xc064)
+ // @JvmField val TLS_PSK_WITH_ARIA_256_CBC_SHA384 = init("TLS_PSK_WITH_ARIA_256_CBC_SHA384", 0xc065)
+ // @JvmField val TLS_DHE_PSK_WITH_ARIA_128_CBC_SHA256 = init("TLS_DHE_PSK_WITH_ARIA_128_CBC_SHA256", 0xc066)
+ // @JvmField val TLS_DHE_PSK_WITH_ARIA_256_CBC_SHA384 = init("TLS_DHE_PSK_WITH_ARIA_256_CBC_SHA384", 0xc067)
+ // @JvmField val TLS_RSA_PSK_WITH_ARIA_128_CBC_SHA256 = init("TLS_RSA_PSK_WITH_ARIA_128_CBC_SHA256", 0xc068)
+ // @JvmField val TLS_RSA_PSK_WITH_ARIA_256_CBC_SHA384 = init("TLS_RSA_PSK_WITH_ARIA_256_CBC_SHA384", 0xc069)
+ // @JvmField val TLS_PSK_WITH_ARIA_128_GCM_SHA256 = init("TLS_PSK_WITH_ARIA_128_GCM_SHA256", 0xc06a)
+ // @JvmField val TLS_PSK_WITH_ARIA_256_GCM_SHA384 = init("TLS_PSK_WITH_ARIA_256_GCM_SHA384", 0xc06b)
+ // @JvmField val TLS_DHE_PSK_WITH_ARIA_128_GCM_SHA256 = init("TLS_DHE_PSK_WITH_ARIA_128_GCM_SHA256", 0xc06c)
+ // @JvmField val TLS_DHE_PSK_WITH_ARIA_256_GCM_SHA384 = init("TLS_DHE_PSK_WITH_ARIA_256_GCM_SHA384", 0xc06d)
+ // @JvmField val TLS_RSA_PSK_WITH_ARIA_128_GCM_SHA256 = init("TLS_RSA_PSK_WITH_ARIA_128_GCM_SHA256", 0xc06e)
+ // @JvmField val TLS_RSA_PSK_WITH_ARIA_256_GCM_SHA384 = init("TLS_RSA_PSK_WITH_ARIA_256_GCM_SHA384", 0xc06f)
+ // @JvmField val TLS_ECDHE_PSK_WITH_ARIA_128_CBC_SHA256 = init("TLS_ECDHE_PSK_WITH_ARIA_128_CBC_SHA256", 0xc070)
+ // @JvmField val TLS_ECDHE_PSK_WITH_ARIA_256_CBC_SHA384 = init("TLS_ECDHE_PSK_WITH_ARIA_256_CBC_SHA384", 0xc071)
+ // @JvmField val TLS_ECDHE_ECDSA_WITH_CAMELLIA_128_CBC_SHA256 = init("TLS_ECDHE_ECDSA_WITH_CAMELLIA_128_CBC_SHA256", 0xc072)
+ // @JvmField val TLS_ECDHE_ECDSA_WITH_CAMELLIA_256_CBC_SHA384 = init("TLS_ECDHE_ECDSA_WITH_CAMELLIA_256_CBC_SHA384", 0xc073)
+ // @JvmField val TLS_ECDH_ECDSA_WITH_CAMELLIA_128_CBC_SHA256 = init("TLS_ECDH_ECDSA_WITH_CAMELLIA_128_CBC_SHA256", 0xc074)
+ // @JvmField val TLS_ECDH_ECDSA_WITH_CAMELLIA_256_CBC_SHA384 = init("TLS_ECDH_ECDSA_WITH_CAMELLIA_256_CBC_SHA384", 0xc075)
+ // @JvmField val TLS_ECDHE_RSA_WITH_CAMELLIA_128_CBC_SHA256 = init("TLS_ECDHE_RSA_WITH_CAMELLIA_128_CBC_SHA256", 0xc076)
+ // @JvmField val TLS_ECDHE_RSA_WITH_CAMELLIA_256_CBC_SHA384 = init("TLS_ECDHE_RSA_WITH_CAMELLIA_256_CBC_SHA384", 0xc077)
+ // @JvmField val TLS_ECDH_RSA_WITH_CAMELLIA_128_CBC_SHA256 = init("TLS_ECDH_RSA_WITH_CAMELLIA_128_CBC_SHA256", 0xc078)
+ // @JvmField val TLS_ECDH_RSA_WITH_CAMELLIA_256_CBC_SHA384 = init("TLS_ECDH_RSA_WITH_CAMELLIA_256_CBC_SHA384", 0xc079)
+ // @JvmField val TLS_RSA_WITH_CAMELLIA_128_GCM_SHA256 = init("TLS_RSA_WITH_CAMELLIA_128_GCM_SHA256", 0xc07a)
+ // @JvmField val TLS_RSA_WITH_CAMELLIA_256_GCM_SHA384 = init("TLS_RSA_WITH_CAMELLIA_256_GCM_SHA384", 0xc07b)
+ // @JvmField val TLS_DHE_RSA_WITH_CAMELLIA_128_GCM_SHA256 = init("TLS_DHE_RSA_WITH_CAMELLIA_128_GCM_SHA256", 0xc07c)
+ // @JvmField val TLS_DHE_RSA_WITH_CAMELLIA_256_GCM_SHA384 = init("TLS_DHE_RSA_WITH_CAMELLIA_256_GCM_SHA384", 0xc07d)
+ // @JvmField val TLS_DH_RSA_WITH_CAMELLIA_128_GCM_SHA256 = init("TLS_DH_RSA_WITH_CAMELLIA_128_GCM_SHA256", 0xc07e)
+ // @JvmField val TLS_DH_RSA_WITH_CAMELLIA_256_GCM_SHA384 = init("TLS_DH_RSA_WITH_CAMELLIA_256_GCM_SHA384", 0xc07f)
+ // @JvmField val TLS_DHE_DSS_WITH_CAMELLIA_128_GCM_SHA256 = init("TLS_DHE_DSS_WITH_CAMELLIA_128_GCM_SHA256", 0xc080)
+ // @JvmField val TLS_DHE_DSS_WITH_CAMELLIA_256_GCM_SHA384 = init("TLS_DHE_DSS_WITH_CAMELLIA_256_GCM_SHA384", 0xc081)
+ // @JvmField val TLS_DH_DSS_WITH_CAMELLIA_128_GCM_SHA256 = init("TLS_DH_DSS_WITH_CAMELLIA_128_GCM_SHA256", 0xc082)
+ // @JvmField val TLS_DH_DSS_WITH_CAMELLIA_256_GCM_SHA384 = init("TLS_DH_DSS_WITH_CAMELLIA_256_GCM_SHA384", 0xc083)
+ // @JvmField val TLS_DH_anon_WITH_CAMELLIA_128_GCM_SHA256 = init("TLS_DH_anon_WITH_CAMELLIA_128_GCM_SHA256", 0xc084)
+ // @JvmField val TLS_DH_anon_WITH_CAMELLIA_256_GCM_SHA384 = init("TLS_DH_anon_WITH_CAMELLIA_256_GCM_SHA384", 0xc085)
+ // @JvmField val TLS_ECDHE_ECDSA_WITH_CAMELLIA_128_GCM_SHA256 = init("TLS_ECDHE_ECDSA_WITH_CAMELLIA_128_GCM_SHA256", 0xc086)
+ // @JvmField val TLS_ECDHE_ECDSA_WITH_CAMELLIA_256_GCM_SHA384 = init("TLS_ECDHE_ECDSA_WITH_CAMELLIA_256_GCM_SHA384", 0xc087)
+ // @JvmField val TLS_ECDH_ECDSA_WITH_CAMELLIA_128_GCM_SHA256 = init("TLS_ECDH_ECDSA_WITH_CAMELLIA_128_GCM_SHA256", 0xc088)
+ // @JvmField val TLS_ECDH_ECDSA_WITH_CAMELLIA_256_GCM_SHA384 = init("TLS_ECDH_ECDSA_WITH_CAMELLIA_256_GCM_SHA384", 0xc089)
+ // @JvmField val TLS_ECDHE_RSA_WITH_CAMELLIA_128_GCM_SHA256 = init("TLS_ECDHE_RSA_WITH_CAMELLIA_128_GCM_SHA256", 0xc08a)
+ // @JvmField val TLS_ECDHE_RSA_WITH_CAMELLIA_256_GCM_SHA384 = init("TLS_ECDHE_RSA_WITH_CAMELLIA_256_GCM_SHA384", 0xc08b)
+ // @JvmField val TLS_ECDH_RSA_WITH_CAMELLIA_128_GCM_SHA256 = init("TLS_ECDH_RSA_WITH_CAMELLIA_128_GCM_SHA256", 0xc08c)
+ // @JvmField val TLS_ECDH_RSA_WITH_CAMELLIA_256_GCM_SHA384 = init("TLS_ECDH_RSA_WITH_CAMELLIA_256_GCM_SHA384", 0xc08d)
+ // @JvmField val TLS_PSK_WITH_CAMELLIA_128_GCM_SHA256 = init("TLS_PSK_WITH_CAMELLIA_128_GCM_SHA256", 0xc08e)
+ // @JvmField val TLS_PSK_WITH_CAMELLIA_256_GCM_SHA384 = init("TLS_PSK_WITH_CAMELLIA_256_GCM_SHA384", 0xc08f)
+ // @JvmField val TLS_DHE_PSK_WITH_CAMELLIA_128_GCM_SHA256 = init("TLS_DHE_PSK_WITH_CAMELLIA_128_GCM_SHA256", 0xc090)
+ // @JvmField val TLS_DHE_PSK_WITH_CAMELLIA_256_GCM_SHA384 = init("TLS_DHE_PSK_WITH_CAMELLIA_256_GCM_SHA384", 0xc091)
+ // @JvmField val TLS_RSA_PSK_WITH_CAMELLIA_128_GCM_SHA256 = init("TLS_RSA_PSK_WITH_CAMELLIA_128_GCM_SHA256", 0xc092)
+ // @JvmField val TLS_RSA_PSK_WITH_CAMELLIA_256_GCM_SHA384 = init("TLS_RSA_PSK_WITH_CAMELLIA_256_GCM_SHA384", 0xc093)
+ // @JvmField val TLS_PSK_WITH_CAMELLIA_128_CBC_SHA256 = init("TLS_PSK_WITH_CAMELLIA_128_CBC_SHA256", 0xc094)
+ // @JvmField val TLS_PSK_WITH_CAMELLIA_256_CBC_SHA384 = init("TLS_PSK_WITH_CAMELLIA_256_CBC_SHA384", 0xc095)
+ // @JvmField val TLS_DHE_PSK_WITH_CAMELLIA_128_CBC_SHA256 = init("TLS_DHE_PSK_WITH_CAMELLIA_128_CBC_SHA256", 0xc096)
+ // @JvmField val TLS_DHE_PSK_WITH_CAMELLIA_256_CBC_SHA384 = init("TLS_DHE_PSK_WITH_CAMELLIA_256_CBC_SHA384", 0xc097)
+ // @JvmField val TLS_RSA_PSK_WITH_CAMELLIA_128_CBC_SHA256 = init("TLS_RSA_PSK_WITH_CAMELLIA_128_CBC_SHA256", 0xc098)
+ // @JvmField val TLS_RSA_PSK_WITH_CAMELLIA_256_CBC_SHA384 = init("TLS_RSA_PSK_WITH_CAMELLIA_256_CBC_SHA384", 0xc099)
+ // @JvmField val TLS_ECDHE_PSK_WITH_CAMELLIA_128_CBC_SHA256 = init("TLS_ECDHE_PSK_WITH_CAMELLIA_128_CBC_SHA256", 0xc09a)
+ // @JvmField val TLS_ECDHE_PSK_WITH_CAMELLIA_256_CBC_SHA384 = init("TLS_ECDHE_PSK_WITH_CAMELLIA_256_CBC_SHA384", 0xc09b)
+ // @JvmField val TLS_RSA_WITH_AES_128_CCM = init("TLS_RSA_WITH_AES_128_CCM", 0xc09c)
+ // @JvmField val TLS_RSA_WITH_AES_256_CCM = init("TLS_RSA_WITH_AES_256_CCM", 0xc09d)
+ // @JvmField val TLS_DHE_RSA_WITH_AES_128_CCM = init("TLS_DHE_RSA_WITH_AES_128_CCM", 0xc09e)
+ // @JvmField val TLS_DHE_RSA_WITH_AES_256_CCM = init("TLS_DHE_RSA_WITH_AES_256_CCM", 0xc09f)
+ // @JvmField val TLS_RSA_WITH_AES_128_CCM_8 = init("TLS_RSA_WITH_AES_128_CCM_8", 0xc0a0)
+ // @JvmField val TLS_RSA_WITH_AES_256_CCM_8 = init("TLS_RSA_WITH_AES_256_CCM_8", 0xc0a1)
+ // @JvmField val TLS_DHE_RSA_WITH_AES_128_CCM_8 = init("TLS_DHE_RSA_WITH_AES_128_CCM_8", 0xc0a2)
+ // @JvmField val TLS_DHE_RSA_WITH_AES_256_CCM_8 = init("TLS_DHE_RSA_WITH_AES_256_CCM_8", 0xc0a3)
+ // @JvmField val TLS_PSK_WITH_AES_128_CCM = init("TLS_PSK_WITH_AES_128_CCM", 0xc0a4)
+ // @JvmField val TLS_PSK_WITH_AES_256_CCM = init("TLS_PSK_WITH_AES_256_CCM", 0xc0a5)
+ // @JvmField val TLS_DHE_PSK_WITH_AES_128_CCM = init("TLS_DHE_PSK_WITH_AES_128_CCM", 0xc0a6)
+ // @JvmField val TLS_DHE_PSK_WITH_AES_256_CCM = init("TLS_DHE_PSK_WITH_AES_256_CCM", 0xc0a7)
+ // @JvmField val TLS_PSK_WITH_AES_128_CCM_8 = init("TLS_PSK_WITH_AES_128_CCM_8", 0xc0a8)
+ // @JvmField val TLS_PSK_WITH_AES_256_CCM_8 = init("TLS_PSK_WITH_AES_256_CCM_8", 0xc0a9)
+ // @JvmField val TLS_PSK_DHE_WITH_AES_128_CCM_8 = init("TLS_PSK_DHE_WITH_AES_128_CCM_8", 0xc0aa)
+ // @JvmField val TLS_PSK_DHE_WITH_AES_256_CCM_8 = init("TLS_PSK_DHE_WITH_AES_256_CCM_8", 0xc0ab)
+ // @JvmField val TLS_ECDHE_ECDSA_WITH_AES_128_CCM = init("TLS_ECDHE_ECDSA_WITH_AES_128_CCM", 0xc0ac)
+ // @JvmField val TLS_ECDHE_ECDSA_WITH_AES_256_CCM = init("TLS_ECDHE_ECDSA_WITH_AES_256_CCM", 0xc0ad)
+ // @JvmField val TLS_ECDHE_ECDSA_WITH_AES_128_CCM_8 = init("TLS_ECDHE_ECDSA_WITH_AES_128_CCM_8", 0xc0ae)
+ // @JvmField val TLS_ECDHE_ECDSA_WITH_AES_256_CCM_8 = init("TLS_ECDHE_ECDSA_WITH_AES_256_CCM_8", 0xc0af)
+ @JvmField val TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256 =
+ init("TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256", 0xcca8)
+
+ @JvmField val TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256 =
+ init("TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256", 0xcca9)
+
+ @JvmField val TLS_DHE_RSA_WITH_CHACHA20_POLY1305_SHA256 =
+ init("TLS_DHE_RSA_WITH_CHACHA20_POLY1305_SHA256", 0xccaa)
+
+ // @JvmField val TLS_PSK_WITH_CHACHA20_POLY1305_SHA256 = init("TLS_PSK_WITH_CHACHA20_POLY1305_SHA256", 0xccab)
+ @JvmField val TLS_ECDHE_PSK_WITH_CHACHA20_POLY1305_SHA256 =
+ init("TLS_ECDHE_PSK_WITH_CHACHA20_POLY1305_SHA256", 0xccac)
+
+ // @JvmField val TLS_DHE_PSK_WITH_CHACHA20_POLY1305_SHA256 = init("TLS_DHE_PSK_WITH_CHACHA20_POLY1305_SHA256", 0xccad)
+ // @JvmField val TLS_RSA_PSK_WITH_CHACHA20_POLY1305_SHA256 = init("TLS_RSA_PSK_WITH_CHACHA20_POLY1305_SHA256", 0xccae)
+
+ // TLS 1.3 https://tools.ietf.org/html/rfc8446
+ @JvmField val TLS_AES_128_GCM_SHA256 = init("TLS_AES_128_GCM_SHA256", 0x1301)
+
+ @JvmField val TLS_AES_256_GCM_SHA384 = init("TLS_AES_256_GCM_SHA384", 0x1302)
+
+ @JvmField val TLS_CHACHA20_POLY1305_SHA256 = init("TLS_CHACHA20_POLY1305_SHA256", 0x1303)
+
+ @JvmField val TLS_AES_128_CCM_SHA256 = init("TLS_AES_128_CCM_SHA256", 0x1304)
+
+ @JvmField val TLS_AES_128_CCM_8_SHA256 = init("TLS_AES_128_CCM_8_SHA256", 0x1305)
+
+ /**
+ * @param javaName the name used by Java APIs for this cipher suite. Different than the IANA
+ * name for older cipher suites because the prefix is `SSL_` instead of `TLS_`.
+ */
+ @JvmStatic
+ @Synchronized
+ fun forJavaName(javaName: String): CipherSuite {
+ var result: CipherSuite? = INSTANCES[javaName]
+ if (result == null) {
+ result = INSTANCES[secondaryName(javaName)]
+
+ if (result == null) {
+ result = CipherSuite(javaName)
+ }
+
+ // Add the new cipher suite, or a confirmed alias.
+ INSTANCES[javaName] = result
+ }
+ return result
+ }
+
+ private fun secondaryName(javaName: String): String {
+ return when {
+ javaName.startsWith("TLS_") -> "SSL_" + javaName.substring(4)
+ javaName.startsWith("SSL_") -> "TLS_" + javaName.substring(4)
+ else -> javaName
+ }
+ }
+
+ /**
+ * @param javaName the name used by Java APIs for this cipher suite. Different than the IANA
+ * name for older cipher suites because the prefix is `SSL_` instead of `TLS_`.
+ * @param value the integer identifier for this cipher suite. (Documentation only.)
+ */
+ private fun init(javaName: String, value: Int): CipherSuite {
+ val suite = CipherSuite(javaName)
+ INSTANCES[javaName] = suite
+ return suite
+ }
+ }
+}
diff --git a/paho/src/main/java/in/mohalla/paho/client/mqttv3/internal/tls/TlsVersion.kt b/paho/src/main/java/in/mohalla/paho/client/mqttv3/internal/tls/TlsVersion.kt
new file mode 100644
index 00000000..510dce03
--- /dev/null
+++ b/paho/src/main/java/in/mohalla/paho/client/mqttv3/internal/tls/TlsVersion.kt
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2014 Square, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package `in`.mohalla.paho.client.mqttv3.internal.tls
+
+/**
+ * Versions of TLS that can be offered when negotiating a secure socket. See
+ * [javax.net.ssl.SSLSocket.setEnabledProtocols].
+ */
+enum class TlsVersion(
+ @get:JvmName("javaName") val javaName: String
+) {
+ TLS_1_3("TLSv1.3"), // 2016.
+ TLS_1_2("TLSv1.2"), // 2008.
+ TLS_1_1("TLSv1.1"), // 2006.
+ TLS_1_0("TLSv1"), // 1999.
+ SSL_3_0("SSLv3"); // 1996.
+
+ @JvmName("-deprecated_javaName")
+ @Deprecated(
+ message = "moved to val",
+ replaceWith = ReplaceWith(expression = "javaName"),
+ level = DeprecationLevel.ERROR
+ )
+ fun javaName(): String = javaName
+
+ companion object {
+ @JvmStatic
+ fun forJavaName(javaName: String): TlsVersion {
+ return when (javaName) {
+ "TLSv1.3" -> TLS_1_3
+ "TLSv1.2" -> TLS_1_2
+ "TLSv1.1" -> TLS_1_1
+ "TLSv1" -> TLS_1_0
+ "SSLv3" -> SSL_3_0
+ else -> throw IllegalArgumentException("Unexpected TLS version: $javaName")
+ }
+ }
+ }
+}
diff --git a/paho/src/main/java/in/mohalla/paho/client/mqttv3/internal/tls/TrustRootIndex.kt b/paho/src/main/java/in/mohalla/paho/client/mqttv3/internal/tls/TrustRootIndex.kt
new file mode 100644
index 00000000..59d56f55
--- /dev/null
+++ b/paho/src/main/java/in/mohalla/paho/client/mqttv3/internal/tls/TrustRootIndex.kt
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2016 Square, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package `in`.mohalla.paho.client.mqttv3.internal.tls
+
+import java.security.cert.X509Certificate
+
+fun interface TrustRootIndex {
+ /** Returns the trusted CA certificate that signed [cert]. */
+ fun findByIssuerAndSignature(cert: X509Certificate): X509Certificate?
+}
diff --git a/paho/src/main/java/in/mohalla/paho/client/mqttv3/internal/websocket/ExtendedByteArrayOutputStream.java b/paho/src/main/java/in/mohalla/paho/client/mqttv3/internal/websocket/ExtendedByteArrayOutputStream.java
index 7cc56493..f117f5a7 100755
--- a/paho/src/main/java/in/mohalla/paho/client/mqttv3/internal/websocket/ExtendedByteArrayOutputStream.java
+++ b/paho/src/main/java/in/mohalla/paho/client/mqttv3/internal/websocket/ExtendedByteArrayOutputStream.java
@@ -9,15 +9,24 @@ class ExtendedByteArrayOutputStream extends ByteArrayOutputStream {
final WebSocketNetworkModule webSocketNetworkModule;
final WebSocketSecureNetworkModule webSocketSecureNetworkModule;
+ final WebSocketSecureNetworkModuleV2 webSocketSecureNetworkModuleV2;
ExtendedByteArrayOutputStream(WebSocketNetworkModule module) {
this.webSocketNetworkModule = module;
this.webSocketSecureNetworkModule = null;
+ this.webSocketSecureNetworkModuleV2 = null;
}
ExtendedByteArrayOutputStream(WebSocketSecureNetworkModule module) {
this.webSocketNetworkModule = null;
this.webSocketSecureNetworkModule = module;
+ this.webSocketSecureNetworkModuleV2 = null;
+ }
+
+ ExtendedByteArrayOutputStream(WebSocketSecureNetworkModuleV2 module) {
+ this.webSocketNetworkModule = null;
+ this.webSocketSecureNetworkModule = null;
+ this.webSocketSecureNetworkModuleV2 = module;
}
public void flush() throws IOException {
@@ -41,6 +50,9 @@ OutputStream getSocketOutputStream() throws IOException {
if(webSocketSecureNetworkModule != null){
return webSocketSecureNetworkModule.getSocketOutputStream();
}
+ if(webSocketSecureNetworkModuleV2 != null) {
+ return webSocketSecureNetworkModuleV2.getSocketOutputStream();
+ }
return null;
}
diff --git a/paho/src/main/java/in/mohalla/paho/client/mqttv3/internal/websocket/WebSocketSecureNetworkModuleV2.java b/paho/src/main/java/in/mohalla/paho/client/mqttv3/internal/websocket/WebSocketSecureNetworkModuleV2.java
new file mode 100755
index 00000000..e3274923
--- /dev/null
+++ b/paho/src/main/java/in/mohalla/paho/client/mqttv3/internal/websocket/WebSocketSecureNetworkModuleV2.java
@@ -0,0 +1,132 @@
+/*******************************************************************************
+ * Copyright (c) 2009, 2014 IBM Corp.
+ *
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * and Eclipse Distribution License v1.0 which accompany this distribution.
+ *
+ * The Eclipse Public License is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ * and the Eclipse Distribution License is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * Contributors:
+ * James Sutton - Bug 459142 - WebSocket support for the Java client.
+ */
+package in.mohalla.paho.client.mqttv3.internal.websocket;
+
+import in.mohalla.paho.client.mqttv3.ConnectionSpec;
+import in.mohalla.paho.client.mqttv3.ILogger;
+import in.mohalla.paho.client.mqttv3.IPahoEvents;
+import in.mohalla.paho.client.mqttv3.MqttException;
+import in.mohalla.paho.client.mqttv3.Protocol;
+import in.mohalla.paho.client.mqttv3.internal.SSLNetworkModuleV2;
+import in.mohalla.paho.client.mqttv3.internal.tls.CertificateChainCleaner;
+
+import javax.net.SocketFactory;
+import javax.net.ssl.SSLSocketFactory;
+import javax.net.ssl.X509TrustManager;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.PipedInputStream;
+import java.nio.ByteBuffer;
+import java.util.List;
+
+public class WebSocketSecureNetworkModuleV2 extends SSLNetworkModuleV2 {
+
+ private static final String CLASS_NAME = WebSocketSecureNetworkModuleV2.class.getName();
+
+ private PipedInputStream pipedInputStream;
+ private WebSocketReceiver webSocketReceiver;
+ private String uri;
+ private String host;
+ private int port;
+ private ILogger logger;
+ ByteBuffer recievedPayload;
+
+ /**
+ * Overrides the flush method.
+ * This allows us to encode the MQTT payload into a WebSocket
+ * Frame before passing it through to the real socket.
+ */
+ private ByteArrayOutputStream outputStream = new ExtendedByteArrayOutputStream(this);
+
+ public WebSocketSecureNetworkModuleV2(
+ SocketFactory socketFactory,
+ SSLSocketFactory sslSocketFactory,
+ X509TrustManager x509TrustManager,
+ ConnectionSpec connectionSpec,
+ List alpnProtocolList,
+ String uri,
+ String host,
+ int port,
+ String clientId,
+ ILogger logger,
+ IPahoEvents pahoEvents
+ ) {
+ super(
+ socketFactory,
+ sslSocketFactory,
+ x509TrustManager,
+ connectionSpec,
+ alpnProtocolList,
+ host,
+ port,
+ clientId,
+ logger,
+ pahoEvents
+ );
+ this.uri = uri;
+ this.host = host;
+ this.port = port;
+ this.logger = logger;
+ this.pipedInputStream = new PipedInputStream();
+ }
+
+ public void start() throws IOException, MqttException {
+ super.start();
+ WebSocketHandshake handshake = new WebSocketHandshake(super.getInputStream(),
+ super.getOutputStream(), uri, host, port);
+ handshake.execute();
+ this.webSocketReceiver = new WebSocketReceiver(getSocketInputStream(), pipedInputStream);
+ webSocketReceiver.start("WssSocketReceiver");
+
+ }
+
+ OutputStream getSocketOutputStream() throws IOException {
+ return super.getOutputStream();
+ }
+
+ InputStream getSocketInputStream() throws IOException {
+ return super.getInputStream();
+ }
+
+ public InputStream getInputStream() throws IOException {
+ return pipedInputStream;
+ }
+
+ public OutputStream getOutputStream() throws IOException {
+ return outputStream;
+ }
+
+ public void stop() throws IOException {
+ // Creating Close Frame
+ WebSocketFrame frame = new WebSocketFrame((byte) 0x08, true, "1000".getBytes());
+ byte[] rawFrame = frame.encodeFrame();
+ getSocketOutputStream().write(rawFrame);
+ getSocketOutputStream().flush();
+
+ if (webSocketReceiver != null) {
+ webSocketReceiver.stop();
+ }
+ super.stop();
+ }
+
+ public String getServerURI() {
+ return "wss://" + host + ":" + port;
+ }
+
+
+}
diff --git a/paho/src/test/java/org/eclipse/paho/client/mqttv3/ExampleUnitTest.kt b/paho/src/test/java/org/eclipse/paho/client/mqttv3/ExampleUnitTest.kt
index aaf6cd2a..3fcbe2c2 100644
--- a/paho/src/test/java/org/eclipse/paho/client/mqttv3/ExampleUnitTest.kt
+++ b/paho/src/test/java/org/eclipse/paho/client/mqttv3/ExampleUnitTest.kt
@@ -1,4 +1,4 @@
-package org.eclipse.paho.client.mqttv3
+package `in`.mohalla.paho.client.mqttv3
import org.junit.Assert.assertEquals
import org.junit.Test
diff --git a/paho/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker b/paho/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker
new file mode 100644
index 00000000..ca6ee9ce
--- /dev/null
+++ b/paho/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker
@@ -0,0 +1 @@
+mock-maker-inline
\ No newline at end of file
diff --git a/pingsender/timer-pingsender/build.gradle.kts b/pingsender/timer-pingsender/build.gradle.kts
index 2fdb52e4..6b7daa6d 100644
--- a/pingsender/timer-pingsender/build.gradle.kts
+++ b/pingsender/timer-pingsender/build.gradle.kts
@@ -35,6 +35,7 @@ dependencies {
implementation(project(":courier-core-android"))
implementation(deps.android.androidx.annotation)
+ testImplementation(deps.android.test.mockitoCore)
testImplementation(deps.android.test.kotlinTestJunit)
}
diff --git a/pingsender/workmanager-2.6.0-pingsender/build.gradle.kts b/pingsender/workmanager-2.6.0-pingsender/build.gradle.kts
index ce605016..48de2630 100644
--- a/pingsender/workmanager-2.6.0-pingsender/build.gradle.kts
+++ b/pingsender/workmanager-2.6.0-pingsender/build.gradle.kts
@@ -32,7 +32,7 @@ dependencies {
api(project(":mqtt-pingsender"))
implementation(project(":courier-core-android"))
implementation(deps.workManager.runtime_2_6_0)
-
+ testImplementation(deps.android.test.mockitoCore)
testImplementation(deps.android.test.kotlinTestJunit)
}
diff --git a/pingsender/workmanager-pingsender/build.gradle.kts b/pingsender/workmanager-pingsender/build.gradle.kts
index 79e2162b..09a7d04a 100644
--- a/pingsender/workmanager-pingsender/build.gradle.kts
+++ b/pingsender/workmanager-pingsender/build.gradle.kts
@@ -35,7 +35,7 @@ dependencies {
api(project(":mqtt-pingsender"))
implementation(project(":courier-core-android"))
implementation(deps.workManager.runtime)
-
+ testImplementation(deps.android.test.mockitoCore)
testImplementation(deps.android.test.kotlinTestJunit)
}
diff --git a/scripts/publishMavenLocal.sh b/scripts/publishMavenLocal.sh
index 661d826b..0803a611 100755
--- a/scripts/publishMavenLocal.sh
+++ b/scripts/publishMavenLocal.sh
@@ -4,7 +4,7 @@ echo "Publishing Libraries to Maven Local..."
./gradlew :paho:assemble :courier-core:assemble :courier-core-android:assemble --parallel --daemon && ./gradlew :paho:publishToMavenLocal -PIS_LOCAL=true :courier-core:publishToMavenLocal -PIS_LOCAL=true :courier-core-android:publishToMavenLocal -PIS_LOCAL=true --parallel --daemon
./gradlew :mqtt-pingsender:assemble && ./gradlew :mqtt-pingsender:publishToMavenLocal -PIS_LOCAL=true
./gradlew :workmanager-pingsender:assemble :workmanager-2.6.0-pingsender:assemble :alarm-pingsender:assemble :timer-pingsender:assemble --parallel --daemon && ./gradlew :workmanager-pingsender:publishToMavenLocal -PIS_LOCAL=true :workmanager-2.6.0-pingsender:publishToMavenLocal -PIS_LOCAL=true :alarm-pingsender:publishToMavenLocal -PIS_LOCAL=true :timer-pingsender:publishToMavenLocal -PIS_LOCAL=true --parallel --daemon
-./gradlew :network-tracker:assemble :adaptive-keep-alive:assemble :courier-message-adapter-gson:assemble :courier-message-adapter-protobuf:assemble :courier-message-adapter-moshi:assemble :courier-stream-adapter-rxjava:assemble :courier-stream-adapter-rxjava2:assemble :courier-stream-adapter-coroutines:assemble :courier:assemble :app-state-manager:assemble --parallel --daemon && ./gradlew :network-tracker:publishToMavenLocal -PIS_LOCAL=true :adaptive-keep-alive:publishToMavenLocal -PIS_LOCAL=true :courier-message-adapter-gson:publishToMavenLocal -PIS_LOCAL=true :courier-message-adapter-moshi:publishToMavenLocal -PIS_LOCAL=true :courier-message-adapter-protobuf:publishToMavenLocal -PIS_LOCAL=true :courier-stream-adapter-rxjava:publishToMavenLocal -PIS_LOCAL=true :courier-stream-adapter-rxjava2:publishToMavenLocal -PIS_LOCAL=true :courier-stream-adapter-coroutines:publishToMavenLocal -PIS_LOCAL=true :courier:publishToMavenLocal -PIS_LOCAL=true :app-state-manager:publishToMavenLocal -PIS_LOCAL=true --parallel --daemon
+./gradlew :network-tracker:assemble :adaptive-keep-alive:assemble :courier-message-adapter-gson:assemble :courier-message-adapter-text:assemble :courier-message-adapter-protobuf:assemble :courier-message-adapter-moshi:assemble :courier-stream-adapter-rxjava:assemble :courier-stream-adapter-rxjava2:assemble :courier-stream-adapter-coroutines:assemble :courier:assemble :app-state-manager:assemble --parallel --daemon && ./gradlew :network-tracker:publishToMavenLocal -PIS_LOCAL=true :adaptive-keep-alive:publishToMavenLocal -PIS_LOCAL=true :courier-message-adapter-gson:publishToMavenLocal -PIS_LOCAL=true :courier-message-adapter-text:publishToMavenLocal -PIS_LOCAL=true :courier-message-adapter-moshi:publishToMavenLocal -PIS_LOCAL=true :courier-message-adapter-protobuf:publishToMavenLocal -PIS_LOCAL=true :courier-stream-adapter-rxjava:publishToMavenLocal -PIS_LOCAL=true :courier-stream-adapter-rxjava2:publishToMavenLocal -PIS_LOCAL=true :courier-stream-adapter-coroutines:publishToMavenLocal -PIS_LOCAL=true :courier:publishToMavenLocal -PIS_LOCAL=true :app-state-manager:publishToMavenLocal -PIS_LOCAL=true --parallel --daemon
./gradlew :mqtt-client:assemble --parallel --daemon && ./gradlew :mqtt-client:publishToMavenLocal -PIS_LOCAL=true --parallel --daemon
./gradlew :courier:assemble :courier-auth-http:assemble --parallel --daemon && ./gradlew :courier:publishToMavenLocal -PIS_LOCAL=true :courier-auth-http:publishToMavenLocal -PIS_LOCAL=true --parallel --daemon
./gradlew :chuck-mqtt:assemble :chuck-mqtt-no-ops:assembleRelease --parallel --daemon && ./gradlew :chuck-mqtt:publishToMavenLocal -PIS_LOCAL=true :chuck-mqtt-no-ops:publishToMavenLocal -PIS_LOCAL=true --parallel --daemon
diff --git a/settings.gradle b/settings.gradle
index bfc495c8..535bc28f 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -5,6 +5,7 @@ include ':mqtt-client'
include ':courier'
include ':courier-core'
include ':courier-core-android'
+include ':courier-message-adapter-text'
include ':courier-message-adapter-gson'
include ':courier-message-adapter-moshi'
include ':courier-message-adapter-protobuf'