Hive VS Paho, Android Client. SSL issue with mosquitto broker - android

I am having issue connecting through SSL to my mosquitto broker. I have configured the broker correctly as it connects fine to my embedded device using lwIP mqtt client service. Sadly, i cannot use the same code on my android device.
I started down the rabbit hole...
Investigating potential clients for the android app; Paho client seemed the reasonable application software as it is part of the eclipse suite; as is the mosquitto broker. I spent many hours on the paho application, but i hit a wall that couldnt be overcome; the repo is just not up to date with the new androidX platform and the client would disconnect itself, i would get a proper certification and connection; the client would connect to the broker (YES!) but then Android would disconnect it and barf out the following error;
com.example.pahoclient: Targeting S+ (version 31 and above) requires that one of FLAG_IMMUTABLE or FLAG_MUTABLE be specified when creating a PendingIntent.
i investigated this; turns out this is not easily solved as the source code for the library is not easily modified. (At least not by someone with as little java experience as myself.) I tried to work through many "solutions" before giving up.
I then decided to try the HiveMQ client for android. While this seems a lot more current and active, I am unable to get the SSL cert to be received by my broker. its the same cert file but uses slightly modified functions. I will outline both codes here, starting with the successful client + broker connection.
my mosquitto broker is set up the following way;
listener 8883
#listener 1883
cafile certs/ca.crt
certfile certs/server.crt
keyfile certs/server.key
protocol mqtt
tls_version tlsv1.2
require_certificate false
allow_anonymous false
password_file certs/password
max_keepalive 5000
I load only the CA cert on the client. Hardcoded as follows;
package com.example.pahoclient
//import info.mqtt.android.service.MqttAndroidClient;
import android.app.Notification
import android.content.Context
import android.os.Bundle
import android.util.Log
import androidx.appcompat.app.AppCompatActivity
import org.eclipse.paho.android.service.MqttAndroidClient
import org.eclipse.paho.android.service.MqttService
import org.eclipse.paho.client.mqttv3.*
import java.security.KeyStore
import java.security.cert.CertificateFactory
import java.security.cert.X509Certificate
import javax.net.ssl.SSLContext
import javax.net.ssl.SSLSocketFactory
import javax.net.ssl.TrustManagerFactory
class MQTTClient(context: Context,
serverURI: String = "ssl://example.com:8883",
clientID: String = "nonNull") {
companion object {
private const val TAG = "MQTTClient"
private val certString: String = "-----BEGIN CERTIFICATE-----\n" +
...
"rtVtZNE+luuMaDyGQYkNt3d1S3TWFVgd\n" +
"-----END CERTIFICATE-----\n"
private fun createSSLSocketFactory(): SSLSocketFactory? {
val cf = CertificateFactory.getInstance("X.509")
val cert = cf.generateCertificate(certString.byteInputStream()) as X509Certificate
val ks = KeyStore.getInstance(KeyStore.getDefaultType())
ks.load(null, null)
ks.setCertificateEntry("ca", cert)
val tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm())
tmf.init(ks)
val sslContext = SSLContext.getInstance("TLS")
sslContext.init(null, tmf.trustManagers, null)
return sslContext.socketFactory
}
}
private var mqttClient = MqttAndroidClient(context, serverURI, clientID)
fun connect(username: String = "...",
password: String = "...",
cbConnect: IMqttActionListener = defaultCbConnect,
cbClient: MqttCallback = defaultCbClient
) {
mqttClient.setCallback(cbClient)
val options = MqttConnectOptions()
options.userName = username
options.password = password.toCharArray()
options.isCleanSession = false
options.socketFactory = createSSLSocketFactory()
options.keepAliveInterval = 20
try {
mqttClient.connect(options, null, cbConnect)
} catch (e: MqttException) {
e.printStackTrace()
}
}
no client cert is used.
This is sufficient for my embedded device+lwIP; however, I would get the following output from the mosquitto broker when trying with the android device;
1676858808: mosquitto version 2.0.15 running
1676858841: New connection from 207.216.33.43:54808 on port 8883.
<Client disconnected>...
This was accompanied by the FLAG_MUTABLE... error in my android console..
com.example.pahoclient: Targeting S+ (version 31 and above) requires that one of FLAG_IMMUTABLE or FLAG_MUTABLE be specified when creating a PendingIntent.
This tells me the cert is correctly parsed and forwarded , however when i try to use a similiar setup with the HiveMQ client, the cert does not get through and i get the following output in the console;
1676863126: New connection from 207.216.33.43:54922 on port 8883.
1676863126: OpenSSL Error[0]: error:14094416:SSL routines:ssl3_read_bytes:sslv3 alert certificate unknown
1676863126: Client <unknown> disconnected: Protocol error.
Here is the HiveMQ code;
package com.example.pahoclient
import android.R.attr.password
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import com.hivemq.client.mqtt.MqttClient
import com.hivemq.client.mqtt.mqtt3.Mqtt3AsyncClient
import java.security.KeyStore
import java.security.cert.CertificateFactory
import java.security.cert.X509Certificate
import javax.net.ssl.KeyManagerFactory
import javax.net.ssl.SSLContext
import javax.net.ssl.SSLSocketFactory
import javax.net.ssl.TrustManagerFactory
private val certString: String = "-----BEGIN CERTIFICATE-----\n"+
...
"-----END CERTIFICATE-----"
class MainActivity : AppCompatActivity() {
private var client: Mqtt3AsyncClient? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
/**Call the socket factory**/
//CA stuff
val cf = CertificateFactory.getInstance("X.509")
val cert = cf.generateCertificate(certString.byteInputStream()) as X509Certificate
val ks = KeyStore.getInstance(KeyStore.getDefaultType())
ks.load(null, null)
ks.setCertificateEntry("ca", cert)
val tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm())
tmf.init(ks)
client = MqttClient.builder()
.useMqttVersion3()
.identifier("my-mqtt-client-id")
.serverHost("******.com")
.serverPort(8883)
.sslConfig()
.keyManagerFactory(null)
.trustManagerFactory(tmf)
.applySslConfig()
.buildAsync()
client?.connectWith()
?.simpleAuth()
?.username("******")
?.password("*****".toByteArray())
?.applySimpleAuth()
?.send()
?.whenComplete { connAck, throwable ->
if (throwable != null) {
// handle failure
} else {
// setup subscribes or start publishing
}
}
}
}
Why would my cert fail with one implementation and not the other?
Is a client cert necessary with HiveMQ?
Both paho and hiveMQ use SSlSocketFactory, but HiveMQ has a requirement for KeyManagerFactory, which i have set to null.
Do I need to supply a client cert as well? I do have them created, but am unsure how i would go about implementing this.
Any suggestions?
I tried 2 different client, HiveMQ and Paho.
I tried to connect HiveMQ with CA cert loaded as well as a client cert loaded.

Related

Send text using Android Studio and MQTT broker

I'm using Kotlin. You can also answer in Java.
Currently, I want to send a message from Android Studio to the MQTT broker.
However, if the activity is not connected, the message is delivered, but when the activity is connected, an error occurs that cannot connect to the server.
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val button2 = findViewById<Button>(R.id.button2)
button2.setOnClickListener {
connect()
}
}
private fun connect() {
val topic = "/example"
val content = "Send Message"
val qos = 2
val broker = "tcp://localhost:1883"
val persistence = MemoryPersistence()
try {
val Client = MqttClient(broker, MqttClient.generateClientId(), persistence)
val con = MqttConnectOptions()
con.isCleanSession = true
println("Connecting to broker: $broker")
Client.connect(con)
println("Connected")
println("Send Message: $content")
val message = MqttMessage(content.toByteArray())
message.qos = qos
Client.publish(topic, message)
Client.disconnect()
exitProcess(0)
Toast.makeText(this, "success", Toast.LENGTH_SHORT ).show()
} catch (ex: MqttException) {
ex.printStackTrace()
Toast.makeText(this, "Fail", Toast.LENGTH_SHORT ).show()
}
}
}
The above code was created by referring to several examples.
W/System.err: Unable to connect to server (32103) - java.net.ConnectException: Connection refused
at org.eclipse.paho.client.mqttv3.internal.TCPNetworkModule.start(TCPNetworkModule.java:80)
at org.eclipse.paho.client.mqttv3.internal.ClientComms$ConnectBG.run(ClientComms.java:724)
at java.lang.Thread.run(Thread.java:762)
W/System.err: Caused by: java.net.ConnectException: Connection refused
at java.net.PlainSocketImpl.socketConnect(Native Method)
at java.net.AbstractPlainSocketImpl.doConnect(AbstractPlainSocketImpl.java:334)
at java.net.AbstractPlainSocketImpl.connectToAddress(AbstractPlainSocketImpl.java:196)
at java.net.AbstractPlainSocketImpl.connect(AbstractPlainSocketImpl.java:178)
at java.net.SocksSocketImpl.connect(SocksSocketImpl.java:356)
at java.net.Socket.connect(Socket.java:586)
at org.eclipse.paho.client.mqttv3.internal.TCPNetworkModule.start(TCPNetworkModule.java:74)
... 2 more
The above information is in error.
Here, if you execute the "fun connect()" function alone, a message arrives.
Can you tell me where I was wrong when I tried to send a message when I click the button on the function to be executed?
When the code runs on the phone (or in the emulator) the hostname localhost will resolve to the phone (or emulator) not the machine the MQTT broker is running on (I assume your development machine).
If running in the emulator you can use the IP address 10.0.2.2 but this should only be used for local testing.
You need to use the IP address of your development machine on the network to test from a phone attached to the same network as the development machine.
For further testing/deployment you will most likely need a broker that is publicly accessible from the internet.

How to send and receive strings through TCP connection using kotlin

I have a TCP Server on Windows, and I want to send and receive text strings between the server and my Android device.
I spent alot of time searching for an example using Kotlin but I didn't find any useful code, so I'm now only able to create the socket and connect.
fun connect() {
try{
val soc = Socket("192.168.1.5", 1419)
val dout = DataOutputStream(soc.getOutputStream())
dout.writeUTF("1")
dout.flush()
dout.close()
soc.close()
}
catch (e:Exception){
e.printStackTrace()
}
}
You can check this simple example. Hope it'll help you!
Server:
fun main() {
val server = ServerSocket(9999)
println("Server running on port ${server.localPort}")
val client = server.accept()
println("Client connected : ${client.inetAddress.hostAddress}")
val scanner = Scanner(client.inputStream)
while (scanner.hasNextLine()) {
println(scanner.nextLine())
break
}
server.close()
}
Client:
fun main() {
val client = Socket("127.0.0.1", 9999)
client.outputStream.write("Hello from the client!".toByteArray())
client.close()
}
You can also do it with ktor, it's a kotlin based asynchronous framework. It uses coroutines natively which allow concurrency.
Use Kotlin 1.4 and ktor 1.6.0, add it to your build.gradle.kts:
plugins {
kotlin("jvm") version "1.4.32"
}
dependencies {
implementation("io.ktor:ktor-server-netty:1.6.0")
implementation("io.ktor:ktor-network:1.6.0")
}
Then you can use the sockets, it's still a bit experimental but it's getting there, with newer version ktor-network is now necessary.
Here is the code:
Server:
suspend fun server() {
val server = aSocket(ActorSelectorManager(Executors.newCachedThreadPool().asCoroutineDispatcher())).tcp()
.bind(InetSocketAddress("127.0.0.1", 2323))
println("Server running: ${server.localAddress}")
val socket = server.accept()
println("Socket accepted: ${socket.remoteAddress}")
val input = socket.openReadChannel()
val output = socket.openWriteChannel(autoFlush = true)
val line = input.readUTF8Line()
println("received '$line' from ${socket.remoteAddress}")
output.writeFully("$line back\r\n".toByteArray())
}
Client:
suspend fun client() {
val socket = aSocket(ActorSelectorManager(Dispatchers.IO)).tcp()
.connect(InetSocketAddress("127.0.0.1", 2323))
val input = socket.openReadChannel()
val output = socket.openWriteChannel(autoFlush = true)
output.writeFully("hello\r\n".toByteArray())
println("Server said: '${input.readUTF8Line()}'")
}
Run them both:
fun main() {
CoroutineScope(Dispatchers.Default).launch { server() }
runBlocking { client() }
}
When you run them, the client will send a message, the server will respond and you should see something like this:
Server running: /127.0.0.1:2323
Socket accepted: /127.0.0.1:56215
received 'hello' from /127.0.0.1:56215
Server said: 'hello back'
Find more example on their documentation simple echo server
There are 2 important things based on my experiment:
get permission in AndroidManifest.xml
<uses-permission android:name="android.permission.INTERNET" />
create the socket from a background thread, the following works for me:
Executors.newSingleThreadExecutor().execute {
val socket = Socket("192.168.0.15", 50000)
val scanner = Scanner(socket.getInputStream())
val printWriter = PrintWriter(socket.getOutputStream())
while (scanner.hasNextLine()) {
Log.d(TAG, "${ scanner.nextLine() }")
}
}
This is the source code in GitHub.
There is a video of my experiment.

Testing RxWorker for WorkManager

I want to try out work manager for the first time. I am used to rxJava so I decided to implement my work manager using RxWorker. But the testing aspect is giving me headache.Basically, the work manager checks firebase to get latest changes to latest changes to particular document (This is not the best use case I know).But the problem is in the test returns without waiting for success or failure.It returns when the work manager is still running.
This is my work manager implementation
class MidiSyncWorker(context: Context, params: WorkerParameters) : RxWorker(context, params) {
override fun createWork(): Single<Result> {
return Injection.provideSharePrefsRepo.midiArchiveVersion()
.flatMapObservable { currentVersion ->
Injection.provideOnlineRepo.latestMidiArchive()
.filter { onlineMidi -> onlineMidi.version > currentVersion }
}.firstOrError()
.map { onlineMidi ->
val outputData = Data.Builder()
.putString(KEY_FIREBASE_ARCHIVE_PATH, onlineMidi.url)
Result.success(outputData.build()) }
}
.onErrorReturn { Result.failure() }
}
This is my test
fun midiSyncVersionCheck_success_onlineVersionDiffersFromLocalVersion() {
// create request
val request = OneTimeWorkRequestBuilder<MidiSyncWorker>()
.build()
wmRule.workManager.enqueue(request).result.get()
val workInfo = wmRule.workManager.getWorkInfoById(request.id).get(10, TimeUnit.SECONDS)
assertThat(workInfo.state, `is`(WorkInfo.State.SUCCEEDED))
}
I expected the test to wait until workmanager returns success or failure. But it returns while work manager is still running
java.lang.AssertionError:
Expected: is <SUCCEEDED>
but: was <RUNNING>
WorkManager makes available a couple of ways to test your Worker classes. You can find all the details on WorkManager Testing documentation page.
The original WorkManagerTestInitHelper only supports Worker classes, meanwhile, the newly introduce in (WorkManager v2.1.0-alpha01) TestListenableWorkerBuilder can be used to test both ListenableWorker classes and the other classes that derives from it (like CoroutineWorker and RxWorker.
In your particular case, you should be able to do:
import android.content.Context
import androidx.test.core.app.ApplicationProvider
import androidx.work.ListenableWorker.Result
import androidx.work.testing.TestListenableWorkerBuilder
import org.hamcrest.CoreMatchers.`is`
import org.junit.Assert.assertThat
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.JUnit4
#RunWith(JUnit4::class)
class MyWorkTest {
private lateinit var context: Context
#Before
fun setup() {
context = ApplicationProvider.getApplicationContext()
}
#Test
fun testMidiSyncWorker() {
// Get the ListenableWorker
val worker = TestListenableWorkerBuilder<MidiSyncWorker>(context).build()
// Start the work synchronously
val result = worker.startWork().get()
assertThat(result, `is`(Result.success()))
}
}
In this way you're calling synchrously your worker class.
In this case you need to use the as a test dependency in your build.gradle file:
def work_version = "2.1.0-alpha02"
androidTestImplementation "androidx.work:work-testing:$work_version"
You can find a similar, complete, sample (for a CoroutineWorker), on the Kotlin's coroutine Codelab.

kotlin not supporting Socket IO in android

I have two questions
1)My Project contain dagger2,retofit2,kotlin v1.0.21,rxJava2,OkHttp3 i want to implement SocketIO on my project how should i implement?
2) I try to several way but unable to connect socketIO so i try to sample code below given code but still unable to connect socket.. please help thanx in advance
package com.easymakers.myapplication
import android.os.Bundle
import android.support.design.widget.Snackbar
import android.support.v7.app.AppCompatActivity
import android.view.View
import io.socket.client.IO
import io.socket.client.Socket
import io.socket.emitter.Emitter
import kotlinx.android.synthetic.main.activity_main.*
import javax.net.ssl.SSLContext
class MainActivity : AppCompatActivity() {
private var socket : Socket? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
setSupportActionBar(toolbar)
fab.setOnClickListener { view ->
connect(view)
// connect1()
}
}
private fun connect(view : View) {
val opts = IO.Options()
opts.port= 5000
opts.reconnection = false
// opts.query =
socket = IO.socket("https://192.170.1.21",opts)
socket?.connect()
?.on(Socket.EVENT_CONNECT, {
Snackbar.make(view, "Socket connected", Snackbar.LENGTH_LONG)
.setAction("Action", null).show()
})
?.on(Socket.EVENT_DISCONNECT, { println("disconnected") })
}
private fun connect1(){
val sc = SSLContext.getInstance("SSL")
sc.init(null, null, null)
val opts = IO.Options()
opts.port = 3000
opts.secure = true
opts.forceNew = true
opts.reconnection = true
val socket = IO.socket("https://103.69.190.10",opts)
socket.on("connection", Emitter.Listener {
println("Connected")
socket.emit("foo", "hi")
socket.disconnect()
}).on("event", Emitter.Listener { }).on(Socket.EVENT_DISCONNECT, Emitter.Listener { })
socket.connect()
}
}
I used this library Socket.IO
With this code it was enough on method OnCreate. Even with an authentication token, but it's optional.
val opts = IO.Options()
opts.query = "token=XXXXXXXXX"
var socket = IO.socket("https://MYSERVERSOCKET.com", opts)
socket?.on(Socket.EVENT_CONNECT, {
Log.d("","==============================CONNECTED")
})?.on(Socket.EVENT_DISCONNECT, {
Log.d("","==============================OFF")
})
Start the connection
btnFindNext.setOnClickListener{
socket.connect()
}
and an example of emit
btnOtro.setOnClickListener{
val obj = JSONObject()
obj.put("var1", "5ab0a8931522f51fac37f5a1")
obj.put("var2", "91.8392894")
obj.put("var3", "-93.8392894")
socket.emit("SendData", obj)
}
1.1) https://github.com/socketio/socket.io-client-java
Gradle
Add it as a gradle dependency for Android Studio, in build.gradle:
compile ('io.socket:socket.io-client:1.0.0') {
// excluding org.json which is provided by Android
exclude group: 'org.json', module: 'json'
}
Socket.IO Server 1.x suppport
The current version of socket.io-client-java doesn't support socket.io
server 1.x. Please use socket.io-client-java 0.9.x for that instead.
1.2) in github you may see:
this link follow you to the updated version which support 1.x and upper server versions.
link: http://socketio.github.io/socket.io-client-java/project-summary.html
it have next dependency:
compile 'io.socket:socket.io-client:1.0.0-SNAPSHOT'
2) https://socket.io/get-started/chat/
Next step you must start node.js server
Integrating Socket.IO is composed of two parts:
1) A server that integrates with (or mounts on) the Node.JS HTTP Server:
socket.io
2) A client library that loads on the browser side:
socket.io-client During development, socket.io serves the client
automatically for us, as we’ll see, so for now we only have to install
one module:
npm install --save socket.io

How to test Evernote's Android Jobs?

How do I test Jobs created with Android-Job library? Any ideas on unit testing, instrumented testing or even manual testing are appreciated, I just want to check if it works as expected.
To be specific, I have a job that performs an HTTP request every N hours:
package com.kondenko.yamblzweather.job;
import android.support.annotation.NonNull;
import com.evernote.android.job.Job;
import com.evernote.android.job.JobRequest;
import com.kondenko.yamblzweather.model.entity.WeatherData;
import com.kondenko.yamblzweather.ui.weather.WeatherInteractor;
import com.kondenko.yamblzweather.utils.SettingsManager;
import java.util.concurrent.TimeUnit;
import javax.inject.Inject;
public class UpdateWeatherJob extends Job {
private WeatherInteractor interactor;
private String cityId;
private String units;
private long refreshRateHr;
// Do not delete, needed for job creation
public static final String TAG = "UpdateWeaterJob";
#Inject
public UpdateWeatherJob(WeatherInteractor interactor, SettingsManager settingsManager) {
this.interactor = interactor;
this.cityId = settingsManager.getSelectedCity();
this.units = settingsManager.getSelectedUnitValue();
this.refreshRateHr = settingsManager.getRefreshRate();
}
#NonNull
#Override
protected Result onRunJob(Params params) {
WeatherData data = interactor.getWeather(cityId, units).blockingGet();
return data != null ? Result.SUCCESS : Result.FAILURE;
}
public void buildJobRequest(String name) {
new JobRequest.Builder(UpdateWeatherJob.TAG)
.setPeriodic(TimeUnit.HOURS.toMillis(refreshRateHr))
.setRequiredNetworkType(JobRequest.NetworkType.CONNECTED)
.setRequirementsEnforced(true)
.setPersisted(true)
.build()
.schedule();
}
}
We at Evernote test jobs the following way:
Unit tests -> We tend to extract the logic into actions, similar to presenters in a MVP setup. This removes Android dependencies and the actions are unit testable.
QA -> We have QA options to trigger jobs manually. This way our QA team can verify that the job produces the correct output
Verifying timing -> There we rely on the logs.
You also should take a look at these slides. ADB can be really helpful to verify curtain assumptions.

Categories

Resources