This project is a fork from codeestX
TCP socket client Android library. Written in Kotlin, powered by RxJava2
- Compression
- Encryption
- CheckSum (with Ok - Wrong Responses)
- File send
- Add the JitPack repository to your build.gradle file
allprojects {
repositories {
maven { url "https://jitpack.io" }
}
}
- Add the dependency
dependencies {
implementation 'com.github.panos-stavrianos:RxSocketClient:0.0.5'
}
val mClient = RxSocketClient
.create(SocketConfig.Builder()
.setIp("192.168.1.2")
.setPort(5000)
.setCharset(Charsets.UTF_8)
.setThreadStrategy(ThreadStrategy.SYNC)
.setTimeout(5, TimeUnit.SECONDS)
.build())
.option(SocketOption.Builder()
.setHeartBeat("beep".toByteArray(), 15, TimeUnit.SECONDS)
.setHead(HEAD)
.setTail(TAIL)
.setCheckSum("ACK".toByteArray(), "NAK".toByteArray())
.setEncryption("pre shared password", EncryptionPadding.PKCS5Padding, "ENC:")
.setFirstContact(first)//FirstContact is neither compressed nor encrypted
.useCompression(true)
.build())
value | default | description |
---|---|---|
Ip | required | host address |
Port | required | port number |
Charset | UTF_8 | the charset when encode a String to byte[] |
ThreadStrategy | Async | sending data asynchronously or synchronously |
Timeout | 0 | the timeout of a connection with TimeUnit as second parameter (millisecond default) |
HeartBeat | Optional | value and interval of heartbeat, millisecond |
CheckSum | Optional | ok and wrong responses as ByteArray |
Encryption | Optional | password, padding and prefix |
Compression | Optional | boolean, false default |
Head | Optional | appending bytes at head when sending data |
Tail | Optional | appending bytes at last when sending data |
val disposables = CompositeDisposable() // Just good practice
mClient.connect()
.doOnSubscribe { disposables.add(it) }
.subscribe(
object : SocketSubscriber() {
override fun onConnected() {
}
override fun onDisconnected(timePassed: Long) {
Log.e(TAG, "onDisconnected in ${TimeUnit.MILLISECONDS.toSeconds(timePassed)} sec")
}
override fun onDisconnectedWithError(throwable: Throwable, timePassed: Long) {
Log.e(TAG, "onDisconnectedWithError in ${TimeUnit.MILLISECONDS.toSeconds(timePassed)} sec, cause: ${throwable.message}")
}
override fun onResponse(data: String, timePassed: Long) {
Log.e(TAG, "onResponse in ${TimeUnit.MILLISECONDS.toSeconds(timePassed)} sec: $data")
}
},
Consumer {
it.printStackTrace()
})
mClient.disconnect()
//or you can force the error disconnect
mClient.disconnectWithError(Throwable("Something bad happend"))
//In case you have multiple disposables, you can use CompositeDisposable to add and dispose them all together
disposables.clear()
There are three send methods, for string, bytes and file. There also two optional boolean parameters for compress and encrypt. If you pass a boolean value it will ignore the current configuration (init)
mClient.send(string)
mClient.send(bytes)
mClient.sendFile(path)
//or
mClient.send(string, encrypt = false, compress = true)
mClient.send(bytes, encrypt = true, compress = false)
mClient.sendFile(path, encrypt = true, compress = true)
Init a socket with encryption, compression and head-tail
val mClient = RxSocketClient
.create(SocketConfig.Builder()
.setIp(host)
.setPort(port)
.setCharset(Charsets.UTF_8)
.setThreadStrategy(ThreadStrategy.SYNC)
.setTimeout(5, TimeUnit.SECONDS)
.build())
.option(SocketOption.Builder()
.setEncryption("supersecretpassword", EncryptionPadding.PKCS5Padding, "ENC:")
.useCompression(true)
.setHead(HEAD)
.setTail(TAIL)
.build())
Now we can imagine the following scenario
override fun onResponse(data: String, timePassed: Long) {
Log.e(TAG, "onResponse in ${TimeUnit.MILLISECONDS.toSeconds(timePassed)} sec: $data")
when (data) {
"Hey you" -> mClient.send("Who? Me?")
"Send me some bytes" -> {
mClient.send("Well ok...")
mClient.send(ByteArray(4) { i -> (i * i).toByte() })
}
"Send me a selfie" -> {
val path = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES).absolutePath + "/me.jpg"
mClient.sendFile(path)
}
"Send me first the size of the (encrypted) file and then the actual file" -> {
val path = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES).absolutePath + "/me.jpg"
val encryptedPath = mClient.encryptFile(path)
val encryptedFileSize = File(encryptedPath).length()
mClient.send("File size: $encryptedFileSize")
// In this case we already have encrypted our file and jpeg images are already compressed
// so we pass false in both parameters
mClient.sendFile(encryptedPath, encrypt = false, compress = false)
}
"I am done with you..." -> mClient.disconnect() // or disposables.clear()
}
}
There are some static parameters here... In time i will add more customization.
But for now we use:
"PBKDF2WithHmacSHA1" for the creation of the 'big key', with 100 iterations and 128 key length. We also have a random 'salt' and 'iv' of 16 bytes each
For the actual encryption we are going to use AES/CBC with two padding choices, either PKCS5Padding or PKCS7Padding.(For some reason PKCS7 breaks in unit tests.)
After we encrypt our data, we need to send the iv and salt along with the encrypted data so the decryption will be possible.
So we add them at the beginning of the message
-
iv + salt + encrypted_data
-
16 Bytes + 16 Bytes + n Bytes
Before the decrypt part, we need to split the received data.
This can be done like this: (not real code)
iv = encrypted.getRange(0,15)
salt = encrypted.getRange(16,31)
clear_encrypted = encrypted.getRange(32,encrypted.size-1)
Then we use the salt and the pre shared key to create the 'big key'
And Last we use the iv to decrypt the message.
Obviously you need a server! Unless you already have on you can try this for testing.
At some point i will make some scripts (most likely python) to test all the features properly.
Copyright (c) 2017 codeestX (original)
Copyright (c) 2018 panos-stavrianos
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.