2
0
mirror of https://github.com/KDE/kdeconnect-android synced 2025-08-22 18:07:55 +00:00

refactor: migrate NetworkPacket to Kotlin

This commit is contained in:
ShellWen Chen 2024-04-06 11:20:17 +00:00 committed by Albert Vaca Cintora
parent c6cb1a54e5
commit 05cdbf98d7
6 changed files with 380 additions and 428 deletions

View File

@ -144,7 +144,7 @@ class BluetoothLink(context: Context?, connection: ConnectionMultiplexer, input:
val buffer = ByteArray(BUFFER_LENGTH) val buffer = ByteArray(BUFFER_LENGTH)
var bytesRead: Int var bytesRead: Int
var progress: Long = 0 var progress: Long = 0
val stream = np.payload.inputStream val stream = np.payload!!.inputStream!!
while (stream.read(buffer).also { bytesRead = it } != -1) { while (stream.read(buffer).also { bytesRead = it } != -1) {
progress += bytesRead.toLong() progress += bytesRead.toLong()
payloadStream.write(buffer, 0, bytesRead) payloadStream.write(buffer, 0, bytesRead)

View File

@ -60,8 +60,8 @@ class DeviceInfo(
np.set("deviceName", name) np.set("deviceName", name)
np.set("protocolVersion", protocolVersion) np.set("protocolVersion", protocolVersion)
np.set("deviceType", type.toString()) np.set("deviceType", type.toString())
np.set("incomingCapabilities", incomingCapabilities) np.set("incomingCapabilities", incomingCapabilities!!)
np.set("outgoingCapabilities", outgoingCapabilities) np.set("outgoingCapabilities", outgoingCapabilities!!)
} }
companion object { companion object {

View File

@ -1,332 +0,0 @@
/*
* SPDX-FileCopyrightText: 2014 Albert Vaca Cintora <albertvaka@gmail.com>
*
* SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
*/
package org.kde.kdeconnect;
import org.apache.commons.io.IOUtils;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.Socket;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
public class NetworkPacket {
public final static String PACKET_TYPE_IDENTITY = "kdeconnect.identity";
public final static String PACKET_TYPE_PAIR = "kdeconnect.pair";
public final static int PACKET_REPLACEID_MOUSEMOVE = 0;
public final static int PACKET_REPLACEID_PRESENTERPOINTER = 1;
public static Set<String> protocolPacketTypes = new HashSet<String>() {{
add(PACKET_TYPE_IDENTITY);
add(PACKET_TYPE_PAIR);
}};
private long mId;
String mType;
private JSONObject mBody;
private Payload mPayload;
private JSONObject mPayloadTransferInfo;
private volatile boolean canceled;
private NetworkPacket() {
}
public NetworkPacket(String type) {
mId = System.currentTimeMillis();
mType = type;
mBody = new JSONObject();
mPayload = null;
mPayloadTransferInfo = new JSONObject();
}
public boolean isCanceled() { return canceled; }
public void cancel() { canceled = true; }
public String getType() {
return mType;
}
public long getId() {
return mId;
}
//Most commons getters and setters defined for convenience
public String getString(String key) {
return mBody.optString(key, "");
}
public String getString(String key, String defaultValue) {
return mBody.optString(key, defaultValue);
}
public void set(String key, String value) {
if (value == null) return;
try {
mBody.put(key, value);
} catch (Exception ignored) {
}
}
public int getInt(String key) {
return mBody.optInt(key, -1);
}
public int getInt(String key, int defaultValue) {
return mBody.optInt(key, defaultValue);
}
public void set(String key, int value) {
try {
mBody.put(key, value);
} catch (Exception ignored) {
}
}
public long getLong(String key) {
return mBody.optLong(key, -1);
}
public long getLong(String key, long defaultValue) {
return mBody.optLong(key, defaultValue);
}
public void set(String key, long value) {
try {
mBody.put(key, value);
} catch (Exception ignored) {
}
}
public boolean getBoolean(String key) {
return mBody.optBoolean(key, false);
}
public boolean getBoolean(String key, boolean defaultValue) {
return mBody.optBoolean(key, defaultValue);
}
public void set(String key, boolean value) {
try {
mBody.put(key, value);
} catch (Exception ignored) {
}
}
public double getDouble(String key) {
return mBody.optDouble(key, Double.NaN);
}
public double getDouble(String key, double defaultValue) {
return mBody.optDouble(key, defaultValue);
}
public void set(String key, double value) {
try {
mBody.put(key, value);
} catch (Exception ignored) {
}
}
public JSONArray getJSONArray(String key) {
return mBody.optJSONArray(key);
}
public void set(String key, JSONArray value) {
try {
mBody.put(key, value);
} catch (Exception ignored) {
}
}
public JSONObject getJSONObject(String key) {
return mBody.optJSONObject(key);
}
public void set(String key, JSONObject value) {
try {
mBody.put(key, value);
} catch (JSONException ignored) {
}
}
public Set<String> getStringSet(String key) {
JSONArray jsonArray = mBody.optJSONArray(key);
if (jsonArray == null) return null;
Set<String> list = new HashSet<>();
int length = jsonArray.length();
for (int i = 0; i < length; i++) {
try {
String str = jsonArray.getString(i);
list.add(str);
} catch (Exception ignored) {
}
}
return list;
}
public Set<String> getStringSet(String key, Set<String> defaultValue) {
if (mBody.has(key)) return getStringSet(key);
else return defaultValue;
}
public void set(String key, Set<String> value) {
try {
JSONArray jsonArray = new JSONArray();
for (String str : value) {
jsonArray.put(str);
}
mBody.put(key, jsonArray);
} catch (Exception ignored) {
}
}
public List<String> getStringList(String key) {
JSONArray jsonArray = mBody.optJSONArray(key);
if (jsonArray == null) return null;
List<String> list = new ArrayList<>();
int length = jsonArray.length();
for (int i = 0; i < length; i++) {
try {
String str = jsonArray.getString(i);
list.add(str);
} catch (Exception ignored) {
}
}
return list;
}
public List<String> getStringList(String key, List<String> defaultValue) {
if (mBody.has(key)) return getStringList(key);
else return defaultValue;
}
public void set(String key, List<String> value) {
try {
JSONArray jsonArray = new JSONArray();
for (String str : value) {
jsonArray.put(str);
}
mBody.put(key, jsonArray);
} catch (Exception ignored) {
}
}
public boolean has(String key) {
return mBody.has(key);
}
public String serialize() throws JSONException {
JSONObject jo = new JSONObject();
jo.put("id", mId);
jo.put("type", mType);
jo.put("body", mBody);
if (hasPayload()) {
jo.put("payloadSize", mPayload.payloadSize);
jo.put("payloadTransferInfo", mPayloadTransferInfo);
}
//QJSon does not escape slashes, but Java JSONObject does. Converting to QJson format.
return jo.toString().replace("\\/", "/") + "\n";
}
static public NetworkPacket unserialize(String s) throws JSONException {
NetworkPacket np = new NetworkPacket();
JSONObject jo = new JSONObject(s);
np.mId = jo.getLong("id");
np.mType = jo.getString("type");
np.mBody = jo.getJSONObject("body");
if (jo.has("payloadSize")) {
np.mPayloadTransferInfo = jo.getJSONObject("payloadTransferInfo");
np.mPayload = new Payload(jo.getLong("payloadSize"));
} else {
np.mPayloadTransferInfo = new JSONObject();
np.mPayload = new Payload(0);
}
return np;
}
public void setPayload(Payload payload) { mPayload = payload; }
public Payload getPayload() {
return mPayload;
}
public long getPayloadSize() {
return mPayload == null ? 0 : mPayload.payloadSize;
}
public boolean hasPayload() {
return (mPayload != null && mPayload.payloadSize != 0);
}
public boolean hasPayloadTransferInfo() {
return (mPayloadTransferInfo.length() > 0);
}
public JSONObject getPayloadTransferInfo() {
return mPayloadTransferInfo;
}
public void setPayloadTransferInfo(JSONObject payloadTransferInfo) {
mPayloadTransferInfo = payloadTransferInfo;
}
public static class Payload {
private final InputStream inputStream;
private final Socket inputSocket;
private final long payloadSize;
public Payload(long payloadSize) {
this((InputStream)null, payloadSize);
}
public Payload(byte[] data) {
this(new ByteArrayInputStream(data), data.length);
}
/**
* <b>NOTE: Do not use this to set an SSLSockets InputStream as the payload, use Payload(Socket, long) instead because of this <a href="https://issuetracker.google.com/issues/37018094">bug</a></b>
*/
public Payload(InputStream inputStream, long payloadSize) {
this.inputSocket = null;
this.inputStream = inputStream;
this.payloadSize = payloadSize;
}
public Payload(Socket inputSocket, long payloadSize) throws IOException {
this.inputSocket = inputSocket;
this.inputStream = inputSocket.getInputStream();
this.payloadSize = payloadSize;
}
/**
* <b>NOTE: Do not close the InputStream directly call Payload.close() instead, this is because of this <a href="https://issuetracker.google.com/issues/37018094">bug</a></b>
*/
public InputStream getInputStream() { return inputStream; }
long getPayloadSize() { return payloadSize; }
public void close() {
//TODO: If socket only close socket if that also closes the streams that is
try {
IOUtils.close(inputStream);
} catch(IOException ignored) {}
try {
IOUtils.close(inputSocket);
} catch (IOException ignored) {}
}
}
}

View File

@ -0,0 +1,291 @@
/*
* SPDX-FileCopyrightText: 2014 Albert Vaca Cintora <albertvaka@gmail.com>
*
* SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
*/
package org.kde.kdeconnect
import org.json.JSONArray
import org.json.JSONException
import org.json.JSONObject
import java.io.ByteArrayInputStream
import java.io.IOException
import java.io.InputStream
import java.net.Socket
import kotlin.concurrent.Volatile
data class NetworkPacket(
val id: Long,
val type: String,
val mBody: JSONObject,
var payload: Payload?,
var payloadTransferInfo: JSONObject,
) {
constructor(type: String) : this(
id = System.currentTimeMillis(),
type = type,
mBody = JSONObject(),
payload = null,
payloadTransferInfo = JSONObject()
)
@Volatile
var isCanceled: Boolean = false
private set
fun cancel() {
isCanceled = true
}
// Most commons getters and setters defined for convenience
fun getString(key: String): String {
return mBody.optString(key, "")
}
fun getString(key: String, defaultValue: String): String {
return mBody.optString(key, defaultValue)
}
operator fun set(key: String, value: String?) {
if (value == null) return
try {
mBody.put(key, value)
} catch (ignored: Exception) {
}
}
fun getInt(key: String): Int {
return mBody.optInt(key, -1)
}
fun getInt(key: String, defaultValue: Int): Int {
return mBody.optInt(key, defaultValue)
}
operator fun set(key: String, value: Int) {
try {
mBody.put(key, value)
} catch (ignored: Exception) {
}
}
fun getLong(key: String): Long {
return mBody.optLong(key, -1)
}
fun getLong(key: String, defaultValue: Long): Long {
return mBody.optLong(key, defaultValue)
}
operator fun set(key: String, value: Long) {
try {
mBody.put(key, value)
} catch (ignored: Exception) {
}
}
fun getBoolean(key: String): Boolean {
return mBody.optBoolean(key, false)
}
fun getBoolean(key: String, defaultValue: Boolean): Boolean {
return mBody.optBoolean(key, defaultValue)
}
operator fun set(key: String, value: Boolean) {
try {
mBody.put(key, value)
} catch (ignored: Exception) {
}
}
fun getDouble(key: String): Double {
return mBody.optDouble(key, Double.NaN)
}
fun getDouble(key: String, defaultValue: Double): Double {
return mBody.optDouble(key, defaultValue)
}
operator fun set(key: String, value: Double) {
try {
mBody.put(key, value)
} catch (ignored: Exception) {
}
}
fun getJSONArray(key: String): JSONArray? {
return mBody.optJSONArray(key)
}
operator fun set(key: String, value: JSONArray?) {
try {
mBody.put(key, value)
} catch (ignored: Exception) {
}
}
fun getJSONObject(key: String): JSONObject? {
return mBody.optJSONObject(key)
}
operator fun set(key: String, value: JSONObject?) {
try {
mBody.put(key, value)
} catch (ignored: JSONException) {
}
}
fun getStringSet(key: String): Set<String>? {
val jsonArray = mBody.optJSONArray(key) ?: return null
val list: MutableSet<String> = HashSet()
val length = jsonArray.length()
for (i in 0 until length) {
try {
val str = jsonArray.getString(i)
list.add(str)
} catch (ignored: Exception) {
}
}
return list
}
fun getStringSet(key: String, defaultValue: Set<String>?): Set<String>? {
return if (mBody.has(key)) getStringSet(key)
else defaultValue
}
operator fun set(key: String, value: Set<String>) {
try {
val jsonArray = JSONArray()
for (str in value) {
jsonArray.put(str)
}
mBody.put(key, jsonArray)
} catch (ignored: Exception) {
}
}
fun getStringList(key: String): List<String>? {
val jsonArray = mBody.optJSONArray(key) ?: return null
val list: MutableList<String> = ArrayList()
val length = jsonArray.length()
for (i in 0 until length) {
try {
val str = jsonArray.getString(i)
list.add(str)
} catch (ignored: Exception) {
}
}
return list
}
fun getStringList(key: String, defaultValue: List<String>?): List<String>? {
return if (mBody.has(key)) getStringList(key)
else defaultValue
}
operator fun set(key: String, value: List<String>) {
try {
val jsonArray = JSONArray()
for (str in value) {
jsonArray.put(str)
}
mBody.put(key, jsonArray)
} catch (ignored: Exception) {
}
}
fun has(key: String): Boolean {
return mBody.has(key)
}
@Throws(JSONException::class)
fun serialize(): String {
val jo = JSONObject()
jo.put("id", id)
jo.put("type", type)
jo.put("body", mBody)
if (hasPayload()) {
jo.put("payloadSize", payload!!.payloadSize)
jo.put("payloadTransferInfo", payloadTransferInfo)
}
// QJSon does not escape slashes, but Java JSONObject does. Converting to QJson format.
return jo.toString().replace("\\/", "/") + "\n"
}
val payloadSize: Long
get() = payload?.payloadSize ?: 0
fun hasPayload(): Boolean {
val payload = payload
return payload != null && payload.payloadSize != 0L
}
fun hasPayloadTransferInfo(): Boolean {
return payloadTransferInfo.length() > 0
}
class Payload {
/**
* **NOTE: Do not close the InputStream directly call Payload.close() instead, this is because of this [bug](https://issuetracker.google.com/issues/37018094)**
*/
val inputStream: InputStream?
private val inputSocket: Socket?
val payloadSize: Long
constructor(payloadSize: Long) : this(null, payloadSize)
constructor(data: ByteArray) : this(ByteArrayInputStream(data), data.size.toLong())
/**
* **NOTE: Do not use this to set an SSLSockets InputStream as the payload, use Payload(Socket, long) instead because of this [bug](https://issuetracker.google.com/issues/37018094)**
*/
constructor(inputStream: InputStream?, payloadSize: Long) {
this.inputSocket = null
this.inputStream = inputStream
this.payloadSize = payloadSize
}
constructor(inputSocket: Socket, payloadSize: Long) {
this.inputSocket = inputSocket
this.inputStream = inputSocket.getInputStream()
this.payloadSize = payloadSize
}
fun close() {
// TODO: If socket only close socket if that also closes the streams that is
try {
inputStream?.close()
} catch (ignored: IOException) {
}
try {
inputSocket?.close()
} catch (ignored: IOException) {
}
}
}
companion object {
const val PACKET_TYPE_IDENTITY: String = "kdeconnect.identity"
const val PACKET_TYPE_PAIR: String = "kdeconnect.pair"
const val PACKET_REPLACEID_MOUSEMOVE: Int = 0
const val PACKET_REPLACEID_PRESENTERPOINTER: Int = 1
@JvmStatic
@Throws(JSONException::class)
fun unserialize(s: String): NetworkPacket {
val jo = JSONObject(s)
val id = jo.getLong("id")
val type = jo.getString("type")
val mBody = jo.getJSONObject("body")
val hasPayload = jo.has("payloadSize")
val payloadTransferInfo = if (hasPayload) jo.getJSONObject("payloadTransferInfo") else JSONObject()
val payload = if (hasPayload) Payload(jo.getLong("payloadSize")) else null
return NetworkPacket(id, type, mBody, payload, payloadTransferInfo)
}
}
}

View File

@ -1,93 +0,0 @@
/*
* SPDX-FileCopyrightText: 2015 Vineet Garg <grg.vineet@gmail.com>
*
* SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
*/
package org.kde.kdeconnect;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import android.util.Log;
import org.json.JSONException;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.kde.kdeconnect.Helpers.DeviceHelper;
import org.mockito.Mockito;
import org.mockito.internal.util.collections.Sets;
import org.powermock.api.mockito.PowerMockito;
import org.powermock.core.classloader.annotations.PrepareForTest;
import org.powermock.modules.junit4.PowerMockRunner;
import java.security.cert.Certificate;
@RunWith(PowerMockRunner.class)
@PrepareForTest({DeviceHelper.class, Log.class})
public class NetworkPacketTest {
@Before
public void setUp() {
PowerMockito.mockStatic(DeviceHelper.class);
PowerMockito.when(DeviceHelper.getDeviceId(any())).thenReturn("123");
PowerMockito.when(DeviceHelper.getDeviceType(any())).thenReturn(DeviceType.PHONE);
PowerMockito.mockStatic(Log.class);
}
@Test
public void testNetworkPacket() throws JSONException {
NetworkPacket np = new NetworkPacket("com.test");
np.set("hello", "hola");
assertEquals(np.getString("hello", "bye"), "hola");
np.set("hello", "");
assertEquals(np.getString("hello", "bye"), "");
assertEquals(np.getString("hi", "bye"), "bye");
np.set("foo", "bar");
String serialized = np.serialize();
NetworkPacket np2 = NetworkPacket.unserialize(serialized);
assertEquals(np.getLong("id"), np2.getLong("id"));
assertEquals(np.getString("type"), np2.getString("type"));
assertEquals(np.getJSONArray("body"), np2.getJSONArray("body"));
String json = "{\"id\":123,\"type\":\"test\",\"body\":{\"testing\":true}}";
np2 = NetworkPacket.unserialize(json);
assertEquals(np2.getId(), 123);
assertTrue(np2.getBoolean("testing"));
assertFalse(np2.getBoolean("not_testing"));
assertTrue(np2.getBoolean("not_testing", true));
}
@Test
public void testIdentity() {
Certificate cert = Mockito.mock(Certificate.class);
DeviceInfo deviceInfo = new DeviceInfo("myid", cert, "myname", DeviceType.TV, 12, Sets.newSet("ASDFG"), Sets.newSet("QWERTY"));
NetworkPacket np = deviceInfo.toIdentityPacket();
assertEquals(np.getInt("protocolVersion"), 12);
DeviceInfo parsed = DeviceInfo.fromIdentityPacketAndCert(np, cert);
assertEquals(parsed.name, deviceInfo.name);
assertEquals(parsed.id, deviceInfo.id);
assertEquals(parsed.type, deviceInfo.type);
assertEquals(parsed.protocolVersion, deviceInfo.protocolVersion);
assertEquals(parsed.incomingCapabilities, deviceInfo.incomingCapabilities);
assertEquals(parsed.outgoingCapabilities, deviceInfo.outgoingCapabilities);
}
}

View File

@ -0,0 +1,86 @@
/*
* SPDX-FileCopyrightText: 2015 Vineet Garg <grg.vineet@gmail.com>
*
* SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
*/
package org.kde.kdeconnect
import android.util.Log
import org.json.JSONException
import org.junit.Assert
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.kde.kdeconnect.DeviceInfo.Companion.fromIdentityPacketAndCert
import org.kde.kdeconnect.Helpers.DeviceHelper
import org.kde.kdeconnect.NetworkPacket.Companion.unserialize
import org.mockito.ArgumentMatchers
import org.mockito.Mockito
import org.mockito.internal.util.collections.Sets
import org.powermock.api.mockito.PowerMockito
import org.powermock.core.classloader.annotations.PrepareForTest
import org.powermock.modules.junit4.PowerMockRunner
import java.security.cert.Certificate
@RunWith(PowerMockRunner::class)
@PrepareForTest(DeviceHelper::class, Log::class)
class NetworkPacketTest {
@Before
fun setUp() {
PowerMockito.mockStatic(DeviceHelper::class.java)
PowerMockito.`when`(DeviceHelper.getDeviceId(ArgumentMatchers.any())).thenReturn("123")
PowerMockito.`when`(DeviceHelper.getDeviceType(ArgumentMatchers.any())).thenReturn(DeviceType.PHONE)
PowerMockito.mockStatic(Log::class.java)
}
@Test
@Throws(JSONException::class)
fun testNetworkPacket() {
val np = NetworkPacket("com.test")
np["hello"] = "hola"
Assert.assertEquals(np.getString("hello", "bye"), "hola")
np["hello"] = ""
Assert.assertEquals(np.getString("hello", "bye"), "")
Assert.assertEquals(np.getString("hi", "bye"), "bye")
np["foo"] = "bar"
val serialized = np.serialize()
var np2 = unserialize(serialized)
Assert.assertEquals(np.getLong("id"), np2.getLong("id"))
Assert.assertEquals(np.getString("type"), np2.getString("type"))
Assert.assertEquals(np.getJSONArray("body"), np2.getJSONArray("body"))
val json = "{\"id\":123,\"type\":\"test\",\"body\":{\"testing\":true}}"
np2 = unserialize(json)
Assert.assertEquals(np2.id, 123)
Assert.assertTrue(np2.getBoolean("testing"))
Assert.assertFalse(np2.getBoolean("not_testing"))
Assert.assertTrue(np2.getBoolean("not_testing", true))
}
@Test
fun testIdentity() {
val cert = Mockito.mock(Certificate::class.java)
val deviceInfo =
DeviceInfo("myid", cert, "myname", DeviceType.TV, 12, Sets.newSet("ASDFG"), Sets.newSet("QWERTY"))
val np = deviceInfo.toIdentityPacket()
Assert.assertEquals(np.getInt("protocolVersion").toLong(), 12)
val parsed = fromIdentityPacketAndCert(np, cert)
Assert.assertEquals(parsed.name, deviceInfo.name)
Assert.assertEquals(parsed.id, deviceInfo.id)
Assert.assertEquals(parsed.type, deviceInfo.type)
Assert.assertEquals(parsed.protocolVersion.toLong(), deviceInfo.protocolVersion.toLong())
Assert.assertEquals(parsed.incomingCapabilities, deviceInfo.incomingCapabilities)
Assert.assertEquals(parsed.outgoingCapabilities, deviceInfo.outgoingCapabilities)
}
}