diff --git a/src/org/kde/kdeconnect/Backends/DeviceLink.java b/src/org/kde/kdeconnect/Backends/BaseLink.java similarity index 86% rename from src/org/kde/kdeconnect/Backends/DeviceLink.java rename to src/org/kde/kdeconnect/Backends/BaseLink.java index c039fbed..bf9507d5 100644 --- a/src/org/kde/kdeconnect/Backends/DeviceLink.java +++ b/src/org/kde/kdeconnect/Backends/BaseLink.java @@ -31,7 +31,7 @@ import java.util.ArrayList; import androidx.annotation.WorkerThread; -public abstract class DeviceLink { +public abstract class BaseLink { protected final Context context; @@ -44,7 +44,7 @@ public abstract class DeviceLink { private final ArrayList receivers = new ArrayList<>(); protected PrivateKey privateKey; - protected DeviceLink(Context context, String deviceId, BaseLinkProvider linkProvider) { + protected BaseLink(Context context, String deviceId, BaseLinkProvider linkProvider) { this.context = context; this.linkProvider = linkProvider; this.deviceId = deviceId; @@ -62,6 +62,15 @@ public abstract class DeviceLink { privateKey = key; } + public BaseLinkProvider getLinkProvider() { + return linkProvider; + } + + //The daemon will periodically destroy unpaired links if this returns false + public boolean linkShouldBeKeptAlive() { + return false; + } + public void addPacketReceiver(PacketReceiver pr) { receivers.add(pr); } @@ -77,7 +86,7 @@ public abstract class DeviceLink { } public void disconnect() { - linkProvider.onLinkDisconnected(this); + linkProvider.connectionLost(this); } //TO OVERRIDE, should be sync diff --git a/src/org/kde/kdeconnect/Backends/BaseLinkProvider.java b/src/org/kde/kdeconnect/Backends/BaseLinkProvider.java index e3a24bf0..2ba72be2 100644 --- a/src/org/kde/kdeconnect/Backends/BaseLinkProvider.java +++ b/src/org/kde/kdeconnect/Backends/BaseLinkProvider.java @@ -20,6 +20,8 @@ package org.kde.kdeconnect.Backends; +import org.kde.kdeconnect.NetworkPacket; + import java.util.concurrent.CopyOnWriteArrayList; public abstract class BaseLinkProvider { @@ -27,47 +29,38 @@ public abstract class BaseLinkProvider { private final CopyOnWriteArrayList connectionReceivers = new CopyOnWriteArrayList<>(); public interface ConnectionReceiver { - void onOfferAdded(DeviceOffer offer); - void onOfferRemoved(String id); - void onLinkConnected(DeviceOffer offer, DeviceLink link); - void onConnectionFailed(DeviceOffer offer, String reason); - void onLinkDisconnected(DeviceLink link); + void onConnectionReceived(NetworkPacket identityPacket, BaseLink link); + void onConnectionLost(BaseLink link); } public void addConnectionReceiver(ConnectionReceiver cr) { connectionReceivers.add(cr); } - public boolean removeConnectionReceiver(ConnectionReceiver cr) { return connectionReceivers.remove(cr); } - protected void onOfferAdded(DeviceOffer offer) { - for(ConnectionReceiver cr : connectionReceivers) { - cr.onOfferAdded(offer); - } - } - protected void onOfferRemoved(String id) { - for(ConnectionReceiver cr : connectionReceivers) { - cr.onOfferRemoved(id); - } + public boolean removeConnectionReceiver(ConnectionReceiver cr) { + return connectionReceivers.remove(cr); } - protected void onLinkConnected(DeviceOffer offer, DeviceLink link) { + //These two should be called when the provider links to a new computer + protected void connectionAccepted(NetworkPacket identityPacket, BaseLink link) { + //Log.i("KDE/LinkProvider", "connectionAccepted"); for(ConnectionReceiver cr : connectionReceivers) { - cr.onLinkConnected(offer, link); + cr.onConnectionReceived(identityPacket, link); } } - protected void onConnectionFailed(DeviceOffer offer, String reason) { + protected void connectionLost(BaseLink link) { + //Log.i("KDE/LinkProvider", "connectionLost"); for(ConnectionReceiver cr : connectionReceivers) { - cr.onConnectionFailed(offer, reason); - } - } - protected void onLinkDisconnected(DeviceLink link) { - for(ConnectionReceiver cr : connectionReceivers) { - cr.onLinkDisconnected(link); + cr.onConnectionLost(link); } } //To override - public abstract void refresh(); + public abstract void onStart(); + public abstract void onStop(); + public abstract void onNetworkChange(); + + //public abstract int getPriority(); public abstract String getName(); - public abstract void connect(DeviceOffer id); + } diff --git a/src/org/kde/kdeconnect/Backends/BluetoothBackend/BluetoothLink.java b/src/org/kde/kdeconnect/Backends/BluetoothBackend/BluetoothLink.java index a8e2a18c..27e9f3f5 100644 --- a/src/org/kde/kdeconnect/Backends/BluetoothBackend/BluetoothLink.java +++ b/src/org/kde/kdeconnect/Backends/BluetoothBackend/BluetoothLink.java @@ -20,13 +20,17 @@ package org.kde.kdeconnect.Backends.BluetoothBackend; +import android.bluetooth.BluetoothAdapter; import android.bluetooth.BluetoothDevice; +import android.bluetooth.BluetoothServerSocket; +import android.bluetooth.BluetoothSocket; import android.content.Context; +import android.os.Build; import android.util.Log; import org.json.JSONException; import org.json.JSONObject; -import org.kde.kdeconnect.Backends.DeviceLink; +import org.kde.kdeconnect.Backends.BaseLink; import org.kde.kdeconnect.Backends.BasePairingHandler; import org.kde.kdeconnect.Device; import org.kde.kdeconnect.NetworkPacket; @@ -41,7 +45,7 @@ import java.util.UUID; import androidx.annotation.WorkerThread; -public class BluetoothLink extends DeviceLink { +public class BluetoothLink extends BaseLink { private final ConnectionMultiplexer connection; private final InputStream input; private final OutputStream output; @@ -199,6 +203,10 @@ public class BluetoothLink extends DeviceLink { } } + @Override + public boolean linkShouldBeKeptAlive() { + return receivingThread.isAlive(); + } /* public boolean isConnected() { diff --git a/src/org/kde/kdeconnect/Backends/BluetoothBackend/BluetoothLinkProvider.java b/src/org/kde/kdeconnect/Backends/BluetoothBackend/BluetoothLinkProvider.java index e81fe544..9c1076af 100644 --- a/src/org/kde/kdeconnect/Backends/BluetoothBackend/BluetoothLinkProvider.java +++ b/src/org/kde/kdeconnect/Backends/BluetoothBackend/BluetoothLinkProvider.java @@ -35,7 +35,6 @@ import android.util.Log; import org.apache.commons.io.IOUtils; import org.kde.kdeconnect.Backends.BaseLinkProvider; -import org.kde.kdeconnect.Backends.DeviceOffer; import org.kde.kdeconnect.Device; import org.kde.kdeconnect.NetworkPacket; @@ -73,9 +72,7 @@ public class BluetoothLinkProvider extends BaseLinkProvider { return; } visibleComputers.put(deviceId, link); - - onLinkConnected(DeviceOffer.FromLegacyIdentityPacket(identityPacket), link); - + connectionAccepted(identityPacket, link); link.startListening(); if (oldLink != null) { Log.i("BluetoothLinkProvider", "Removing old connection to same device"); @@ -92,6 +89,7 @@ public class BluetoothLinkProvider extends BaseLinkProvider { } } + @Override public void onStart() { if (bluetoothAdapter == null) { return; @@ -115,11 +113,13 @@ public class BluetoothLinkProvider extends BaseLinkProvider { new Thread(serverRunnable).start(); } - public void refresh() { + @Override + public void onNetworkChange() { onStop(); onStart(); } + @Override public void onStop() { if (bluetoothAdapter == null || clientRunnable == null || serverRunnable == null) { return; @@ -129,19 +129,15 @@ public class BluetoothLinkProvider extends BaseLinkProvider { serverRunnable.stopProcessing(); } + @Override public String getName() { return "BluetoothLinkProvider"; } - @Override - public void connect(DeviceOffer id) { - // TODO - } - public void disconnectedLink(BluetoothLink link, String deviceId, BluetoothDevice remoteAddress) { sockets.remove(remoteAddress); visibleComputers.remove(deviceId); - onLinkDisconnected(link); + connectionLost(link); } private class ServerRunnable implements Runnable { diff --git a/src/org/kde/kdeconnect/Backends/DeviceOffer.java b/src/org/kde/kdeconnect/Backends/DeviceOffer.java deleted file mode 100644 index c724592d..00000000 --- a/src/org/kde/kdeconnect/Backends/DeviceOffer.java +++ /dev/null @@ -1,59 +0,0 @@ -/* - * Copyright 2014 Albert Vaca Cintora - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation; either version 2 of - * the License or (at your option) version 3 or any later version - * accepted by the membership of KDE e.V. (or its successor approved - * by the membership of KDE e.V.), which shall act as a proxy - * defined in Section 14 of version 3 of the license. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . -*/ - -package org.kde.kdeconnect.Backends; - -import android.util.Log; - -import org.kde.kdeconnect.Device; -import org.kde.kdeconnect.NetworkPacket; - -import java.net.InetAddress; - - -public class DeviceOffer { - - public String id; - public String name; - public Device.DeviceType type; - public int protocolVersion; - - public InetAddress host; - public int port; - - public static DeviceOffer FromLegacyIdentityPacket(NetworkPacket identityPacket) { - DeviceOffer ret = new DeviceOffer(); - ret.id = identityPacket.getString("deviceId"); - ret.protocolVersion = identityPacket.getInt("protocolVersion"); - ret.name = identityPacket.getString("deviceName"); - ret.type = Device.DeviceType.FromString(identityPacket.getString("deviceType", "desktop")); - return ret; - } - - public BaseLinkProvider provider = null; - public void connect() { - if (provider == null) { - Log.e("AAA", "ERROR: Can't connect, provider unknown"); - return; - } - provider.connect(this); - } - -} diff --git a/src/org/kde/kdeconnect/Backends/LanBackend/LanLink.java b/src/org/kde/kdeconnect/Backends/LanBackend/LanLink.java index 88c3e9d2..5c264766 100644 --- a/src/org/kde/kdeconnect/Backends/LanBackend/LanLink.java +++ b/src/org/kde/kdeconnect/Backends/LanBackend/LanLink.java @@ -24,7 +24,7 @@ import android.content.Context; import android.util.Log; import org.json.JSONObject; -import org.kde.kdeconnect.Backends.DeviceLink; +import org.kde.kdeconnect.Backends.BaseLink; import org.kde.kdeconnect.Backends.BasePairingHandler; import org.kde.kdeconnect.Device; import org.kde.kdeconnect.Helpers.SecurityHelpers.SslHelper; @@ -47,7 +47,7 @@ import javax.net.ssl.SSLSocket; import androidx.annotation.WorkerThread; -public class LanLink extends DeviceLink { +public class LanLink extends BaseLink { public interface LinkDisconnectedCallback { void linkDisconnected(LanLink brokenLink); @@ -260,4 +260,15 @@ public class LanLink extends DeviceLink { packageReceived(np); } + + @Override + public boolean linkShouldBeKeptAlive() { + + return true; //FIXME: Current implementation is broken, so for now we will keep links always established + + //We keep the remotely initiated connections, since the remotes require them if they want to request + //pairing to us, or connections that are already paired. + //return (connectionSource == ConnectionStarted.Remotely); + + } } diff --git a/src/org/kde/kdeconnect/Backends/LanBackend/LanLinkProvider.java b/src/org/kde/kdeconnect/Backends/LanBackend/LanLinkProvider.java index 192f1984..ebf45f48 100644 --- a/src/org/kde/kdeconnect/Backends/LanBackend/LanLinkProvider.java +++ b/src/org/kde/kdeconnect/Backends/LanBackend/LanLinkProvider.java @@ -26,9 +26,8 @@ import android.preference.PreferenceManager; import android.util.Base64; import android.util.Log; -import org.kde.kdeconnect.Backends.DeviceLink; +import org.kde.kdeconnect.Backends.BaseLink; import org.kde.kdeconnect.Backends.BaseLinkProvider; -import org.kde.kdeconnect.Backends.DeviceOffer; import org.kde.kdeconnect.BackgroundService; import org.kde.kdeconnect.Device; import org.kde.kdeconnect.Helpers.DeviceHelper; @@ -90,7 +89,7 @@ public class LanLinkProvider extends BaseLinkProvider implements LanLink.LinkDis public void linkDisconnected(LanLink brokenLink) { String deviceId = brokenLink.getDeviceId(); visibleComputers.remove(deviceId); - onLinkDisconnected(brokenLink); + connectionLost(brokenLink); } //They received my UDP broadcast and are connecting to me. The first thing they sned should be their identity. @@ -165,7 +164,7 @@ public class LanLinkProvider extends BaseLinkProvider implements LanLink.LinkDis }, 5 * 1000); // Try to cause a reverse connection - refresh(); + onNetworkChange(); } } } @@ -263,7 +262,7 @@ public class LanLinkProvider extends BaseLinkProvider implements LanLink.LinkDis * link is operated on at a time. *

* Without synchronization, the call to {@link SslHelper#parseCertificate(byte[])} in - * {@link Device#addLink(NetworkPacket, DeviceLink)} crashes on some devices running Oreo 8.1 (SDK level 27). + * {@link Device#addLink(NetworkPacket, BaseLink)} crashes on some devices running Oreo 8.1 (SDK level 27). *

* * @param identityPacket representation of remote device @@ -285,14 +284,12 @@ public class LanLinkProvider extends BaseLinkProvider implements LanLink.LinkDis //Let's create the link LanLink link = new LanLink(context, deviceId, this, socket, connectionOrigin); visibleComputers.put(deviceId, link); - onLinkConnected(DeviceOffer.FromLegacyIdentityPacket(identityPacket), link); + connectionAccepted(identityPacket, link); } } - public LanLinkProvider(Context context) - { + public LanLinkProvider(Context context) { this.context = context; - onStart(); } private void setupUdpListener() { @@ -416,6 +413,7 @@ public class LanLinkProvider extends BaseLinkProvider implements LanLink.LinkDis }).start(); } + @Override public void onStart() { //Log.i("KDE/LanLinkProvider", "onStart"); if (!listening) { @@ -430,10 +428,11 @@ public class LanLinkProvider extends BaseLinkProvider implements LanLink.LinkDis } @Override - public void refresh() { + public void onNetworkChange() { broadcastUdpPacket(); } + @Override public void onStop() { //Log.i("KDE/LanLinkProvider", "onStop"); listening = false; @@ -454,9 +453,4 @@ public class LanLinkProvider extends BaseLinkProvider implements LanLink.LinkDis return "LanLinkProvider"; } - @Override - public void connect(DeviceOffer offer) { - - } - } diff --git a/src/org/kde/kdeconnect/Backends/LoopbackBackend/LoopbackLink.java b/src/org/kde/kdeconnect/Backends/LoopbackBackend/LoopbackLink.java index 4ef6ccac..9ee0a753 100644 --- a/src/org/kde/kdeconnect/Backends/LoopbackBackend/LoopbackLink.java +++ b/src/org/kde/kdeconnect/Backends/LoopbackBackend/LoopbackLink.java @@ -22,7 +22,7 @@ package org.kde.kdeconnect.Backends.LoopbackBackend; import android.content.Context; -import org.kde.kdeconnect.Backends.DeviceLink; +import org.kde.kdeconnect.Backends.BaseLink; import org.kde.kdeconnect.Backends.BaseLinkProvider; import org.kde.kdeconnect.Backends.BasePairingHandler; import org.kde.kdeconnect.Device; @@ -30,7 +30,7 @@ import org.kde.kdeconnect.NetworkPacket; import androidx.annotation.WorkerThread; -public class LoopbackLink extends DeviceLink { +public class LoopbackLink extends BaseLink { public LoopbackLink(Context context, BaseLinkProvider linkProvider) { super(context, "loopback", linkProvider); diff --git a/src/org/kde/kdeconnect/Backends/LoopbackBackend/LoopbackLinkProvider.java b/src/org/kde/kdeconnect/Backends/LoopbackBackend/LoopbackLinkProvider.java index 82d9f2ea..09559466 100644 --- a/src/org/kde/kdeconnect/Backends/LoopbackBackend/LoopbackLinkProvider.java +++ b/src/org/kde/kdeconnect/Backends/LoopbackBackend/LoopbackLinkProvider.java @@ -23,9 +23,6 @@ package org.kde.kdeconnect.Backends.LoopbackBackend; import android.content.Context; import org.kde.kdeconnect.Backends.BaseLinkProvider; -import org.kde.kdeconnect.Backends.DeviceOffer; -import org.kde.kdeconnect.Device; -import org.kde.kdeconnect.Helpers.DeviceHelper; import org.kde.kdeconnect.NetworkPacket; public class LoopbackLinkProvider extends BaseLinkProvider { @@ -37,20 +34,25 @@ public class LoopbackLinkProvider extends BaseLinkProvider { } @Override - public void connect(DeviceOffer offer) { - onLinkConnected(offer, new LoopbackLink(context, this)); + public void onStart() { + onNetworkChange(); } @Override - public void refresh() { - DeviceOffer offer = new DeviceOffer(); - offer.id = DeviceHelper.getDeviceId(context); - offer.name = DeviceHelper.getDeviceName(context); - offer.type = DeviceHelper.getDeviceType(context); - offer.protocolVersion = DeviceHelper.ProtocolVersion; - onOfferAdded(offer); + public void onStop() { } + @Override + public void onNetworkChange() { + NetworkPacket np = NetworkPacket.createIdentityPacket(context); + connectionAccepted(np, new LoopbackLink(context, this)); + } +/* + @Override + public int getPriority() { + return 0; + } +*/ @Override public String getName() { return "LoopbackLinkProvider"; diff --git a/src/org/kde/kdeconnect/Backends/MulticastBackend/MulticastLink.java b/src/org/kde/kdeconnect/Backends/MulticastBackend/MulticastLink.java index 7195e07a..0d3048de 100644 --- a/src/org/kde/kdeconnect/Backends/MulticastBackend/MulticastLink.java +++ b/src/org/kde/kdeconnect/Backends/MulticastBackend/MulticastLink.java @@ -25,7 +25,7 @@ import android.content.Context; import android.util.Log; import org.json.JSONObject; -import org.kde.kdeconnect.Backends.DeviceLink; +import org.kde.kdeconnect.Backends.BaseLink; import org.kde.kdeconnect.Backends.BasePairingHandler; import org.kde.kdeconnect.Device; import org.kde.kdeconnect.Helpers.SecurityHelpers.SslHelper; @@ -45,7 +45,7 @@ import java.nio.channels.NotYetConnectedException; import javax.net.ssl.SSLSocket; -public class MulticastLink extends DeviceLink { +public class MulticastLink extends BaseLink { static final String LOG_TAG = "MulticastLink"; @@ -57,6 +57,11 @@ public class MulticastLink extends DeviceLink { Locally, Remotely } + private ConnectionStarted connectionSource; // If the other device sent me a broadcast, + // I should not close the connection with it + // because it's probably trying to find me and + // potentially ask for pairing. + private volatile SSLSocket socket = null; private final LinkDisconnectedCallback callback; @@ -72,11 +77,13 @@ public class MulticastLink extends DeviceLink { } //Returns the old socket - public SSLSocket reset(final SSLSocket newSocket) throws IOException { + public SSLSocket reset(final SSLSocket newSocket, ConnectionStarted connectionSource) throws IOException { SSLSocket oldSocket = socket; socket = newSocket; + this.connectionSource = connectionSource; + if (oldSocket != null) { oldSocket.close(); //This should cancel the readThread } @@ -115,10 +122,10 @@ public class MulticastLink extends DeviceLink { return oldSocket; } - public MulticastLink(Context context, String deviceId, MulticastLinkProvider linkProvider, SSLSocket socket) throws IOException { + public MulticastLink(Context context, String deviceId, MulticastLinkProvider linkProvider, SSLSocket socket, ConnectionStarted connectionSource) throws IOException { super(context, deviceId, linkProvider); callback = linkProvider; - reset(socket); + reset(socket, connectionSource); } @@ -248,4 +255,14 @@ public class MulticastLink extends DeviceLink { packageReceived(np); } + @Override + public boolean linkShouldBeKeptAlive() { + + return true; //FIXME: Current implementation is broken, so for now we will keep links always established + + //We keep the remotely initiated connections, since the remotes require them if they want to request + //pairing to us, or connections that are already paired. + //return (connectionSource == ConnectionStarted.Remotely); + + } } diff --git a/src/org/kde/kdeconnect/Backends/MulticastBackend/MulticastLinkProvider.java b/src/org/kde/kdeconnect/Backends/MulticastBackend/MulticastLinkProvider.java index e2bed163..4f8e7149 100644 --- a/src/org/kde/kdeconnect/Backends/MulticastBackend/MulticastLinkProvider.java +++ b/src/org/kde/kdeconnect/Backends/MulticastBackend/MulticastLinkProvider.java @@ -24,36 +24,40 @@ package org.kde.kdeconnect.Backends.MulticastBackend; import android.content.Context; import android.content.SharedPreferences; +import android.net.Network; import android.net.nsd.NsdManager; +import android.net.nsd.NsdManager.RegistrationListener; import android.net.nsd.NsdManager.ResolveListener; import android.net.nsd.NsdServiceInfo; -import android.net.wifi.WifiInfo; -import android.net.wifi.WifiManager; import android.os.Build; +import android.util.Base64; import android.util.Log; import androidx.annotation.RequiresApi; -import org.kde.kdeconnect.Backends.DeviceLink; +import org.json.JSONException; +import org.kde.kdeconnect.Backends.BaseLink; import org.kde.kdeconnect.Backends.BaseLinkProvider; -import org.kde.kdeconnect.Backends.DeviceOffer; import org.kde.kdeconnect.BackgroundService; import org.kde.kdeconnect.Device; import org.kde.kdeconnect.Helpers.DeviceHelper; import org.kde.kdeconnect.Helpers.SecurityHelpers.SslHelper; import org.kde.kdeconnect.NetworkPacket; +import java.io.BufferedReader; import java.io.IOException; +import java.io.InputStreamReader; +import java.io.OutputStream; import java.net.InetAddress; import java.net.InetSocketAddress; import java.net.ServerSocket; import java.net.Socket; import java.net.SocketException; -import java.net.UnknownHostException; -import java.nio.charset.StandardCharsets; import java.security.cert.Certificate; +import java.util.ArrayList; import java.util.HashMap; +import javax.net.SocketFactory; import javax.net.ssl.SSLSocket; /** @@ -65,13 +69,13 @@ import javax.net.ssl.SSLSocket; */ public class MulticastLinkProvider extends BaseLinkProvider implements MulticastLink.LinkDisconnectedCallback { - HashMap offers = new HashMap<>(); - static final String LOG_TAG = "MulticastLink"; static final String SERVICE_TYPE = "_kdeconnect._tcp"; private NsdManager mNsdManager; + private RegistrationListener mRegistrationListener; + private boolean mServiceRegistered = false; private final static int MIN_PORT = 1716; private final static int MAX_PORT = 1764; @@ -85,31 +89,31 @@ public class MulticastLinkProvider extends BaseLinkProvider implements Multicast private boolean listening = false; + // To prevent infinte loop between Android < IceCream because both device can only broadcast identity package but cannot connect via TCP + private final ArrayList reverseConnectionBlackList = new ArrayList<>(); + @Override // SocketClosedCallback public void linkDisconnected(MulticastLink brokenLink) { String deviceId = brokenLink.getDeviceId(); visibleComputers.remove(deviceId); - onLinkDisconnected(brokenLink); + connectionLost(brokenLink); } - private InetAddress getDeviceIpAddress() { - WifiManager wifi = (WifiManager) context.getSystemService(Context.WIFI_SERVICE); - WifiManager.MulticastLock lock = wifi.createMulticastLock("jmdns-multicast-lock"); - lock.setReferenceCounted(true); - lock.acquire(); - InetAddress result = null; - try { - // figure out our wifi address, otherwise bail - WifiInfo wifiinfo = wifi.getConnectionInfo(); - int intaddr = wifiinfo.getIpAddress(); - byte[] byteaddr = new byte[] { (byte) (intaddr & 0xff), (byte) (intaddr >> 8 & 0xff), - (byte) (intaddr >> 16 & 0xff), (byte) (intaddr >> 24 & 0xff) }; - return InetAddress.getByAddress(byteaddr); - } catch (UnknownHostException e) { - e.printStackTrace(); - return null; + // They received my mDNS broadcast and are connecting to me. The first thing they send should be their identity. + private void tcpPacketReceived(Socket socket) { + + writeIdentity(socket); + NetworkPacket otherIdentity = readIdentity(socket); + + if (!otherIdentity.getType().equals(NetworkPacket.PACKET_TYPE_IDENTITY)) { + Log.e(LOG_TAG, "Expecting an identity package instead of " + otherIdentity.getType()); + return; } + + Log.i(LOG_TAG, "Identity package received from a TCP connection from " + otherIdentity.getString("deviceName")); + identityPacketReceived(otherIdentity, socket, MulticastLink.ConnectionStarted.Locally); } + private void configureSocket(Socket socket) { try { socket.setKeepAlive(true); @@ -118,34 +122,58 @@ public class MulticastLinkProvider extends BaseLinkProvider implements Multicast } } - /** - * Called when a socket is connected. + * Called when a new 'identity' packet is received. Those are passed here by + * {@link #tcpPacketReceived(Socket)} + *

+ * If the remote device should be connected, this calls {@link #addLink}. + * Otherwise, if there was an Exception, we unpair from that device. + *

* + * @param identityPacket identity of a remote device * @param socket a new Socket, which should be used to receive packets from the remote device * @param connectionStarted which side started this connection */ - private void doTheSslDance(DeviceOffer deviceOffer, final Socket socket, final MulticastLink.ConnectionStarted connectionStarted) { + private void identityPacketReceived(final NetworkPacket identityPacket, final Socket socket, final MulticastLink.ConnectionStarted connectionStarted) { - String deviceId = deviceOffer.id; + String myId = DeviceHelper.getDeviceId(context); + final String deviceId = identityPacket.getString("deviceId"); + if (deviceId.equals(myId)) { + Log.e(LOG_TAG, "Somehow I'm connected to myself, ignoring. This should not happen."); + return; + } // If I'm the TCP server I will be the SSL client and viceversa. final boolean clientMode = (connectionStarted == MulticastLink.ConnectionStarted.Locally); + // Do the SSL handshake try { SharedPreferences preferences = context.getSharedPreferences("trusted_devices", Context.MODE_PRIVATE); boolean isDeviceTrusted = preferences.getBoolean(deviceId, false); - Log.i(LOG_TAG, "Starting SSL handshake with " + deviceOffer.name + " trusted:" + isDeviceTrusted); + if (isDeviceTrusted && !SslHelper.isCertificateStored(context, deviceId)) { + //Device paired with and old version, we can't use it as we lack the certificate + BackgroundService.RunCommand(context, service -> { + Device device = service.getDevice(deviceId); + if (device == null) return; + device.unpair(); + //Retry as unpaired + identityPacketReceived(identityPacket, socket, connectionStarted); + }); + } + + Log.i(LOG_TAG, "Starting SSL handshake with " + identityPacket.getString("deviceName") + " trusted:" + isDeviceTrusted); final SSLSocket sslsocket = SslHelper.convertToSslSocket(context, socket, deviceId, isDeviceTrusted, clientMode); sslsocket.addHandshakeCompletedListener(event -> { + String mode = clientMode ? "client" : "server"; try { Certificate certificate = event.getPeerCertificates()[0]; - createLink(deviceOffer, sslsocket); + identityPacket.set("certificate", Base64.encodeToString(certificate.getEncoded(), 0)); + Log.i(LOG_TAG, "Handshake as " + mode + " successful with " + identityPacket.getString("deviceName") + " secured with " + event.getCipherSuite()); + addLink(identityPacket, sslsocket, connectionStarted); } catch (Exception e) { - String mode = clientMode ? "client" : "server"; - Log.e(LOG_TAG, "Handshake as " + mode + " failed with " + deviceOffer.name, e); + Log.e(LOG_TAG, "Handshake as " + mode + " failed with " + identityPacket.getString("deviceName"), e); BackgroundService.RunCommand(context, service -> { Device device = service.getDevice(deviceId); if (device == null) return; @@ -160,7 +188,7 @@ public class MulticastLinkProvider extends BaseLinkProvider implements Multicast sslsocket.startHandshake(); } } catch (Exception e) { - Log.e(LOG_TAG, "Handshake failed with " + deviceOffer.name, e); + Log.e(LOG_TAG, "Handshake failed with " + identityPacket.getString("deviceName"), e); //String[] ciphers = sslsocket.getSupportedCipherSuites(); //for (String cipher : ciphers) { @@ -179,7 +207,7 @@ public class MulticastLinkProvider extends BaseLinkProvider implements Multicast * link is operated on at a time. *

* Without synchronization, the call to {@link SslHelper#parseCertificate(byte[])} in - * {@link Device#addLink(NetworkPacket, DeviceLink)} crashes on some devices running Oreo 8.1 (SDK level 27). + * {@link Device#addLink(NetworkPacket, BaseLink)} crashes on some devices running Oreo 8.1 (SDK level 27). *

* * @param identityPacket representation of remote device @@ -187,50 +215,53 @@ public class MulticastLinkProvider extends BaseLinkProvider implements Multicast * @param connectionOrigin which side started this connection * @throws IOException if an exception is thrown by {@link MulticastLink#reset(SSLSocket, MulticastLink.ConnectionStarted)} */ - private void createLink(DeviceOffer offer, SSLSocket socket) throws IOException { - String deviceId = offer.id; + private void addLink(final NetworkPacket identityPacket, SSLSocket socket, MulticastLink.ConnectionStarted connectionOrigin) throws IOException { + + String deviceId = identityPacket.getString("deviceId"); MulticastLink currentLink = visibleComputers.get(deviceId); if (currentLink != null) { //Update old link Log.i(LOG_TAG, "Reusing same link for device " + deviceId); - final Socket oldSocket = currentLink.reset(socket); + final Socket oldSocket = currentLink.reset(socket, connectionOrigin); //Log.e(LOG_TAG, "Replacing socket. old: "+ oldSocket.hashCode() + " - new: "+ socket.hashCode()); } else { Log.i(LOG_TAG, "Creating a new link for device " + deviceId); //Let's create the link - MulticastLink link = new MulticastLink(context, deviceId, this, socket); + MulticastLink link = new MulticastLink(context, deviceId, this, socket, connectionOrigin); visibleComputers.put(deviceId, link); - onLinkConnected(offer, link); + connectionAccepted(identityPacket, link); } } - private NsdServiceInfo createServiceInfoForTcpServer(InetAddress address, int port) { - NsdServiceInfo serviceInfo = new NsdServiceInfo(); - - // The name is subject to change based on conflicts - // with other services advertised on the same network. - String name = DeviceHelper.getDeviceName(context); - - serviceInfo.setAttribute("name", name); - serviceInfo.setAttribute("id", DeviceHelper.getDeviceId(context)); - serviceInfo.setAttribute("type", DeviceHelper.getDeviceType(context).toString()); - serviceInfo.setAttribute("version", Integer.toString(DeviceHelper.ProtocolVersion)); - serviceInfo.setAttribute("ip", address.toString()); - - - // It might be nice to add the capabilities in the mDNS advertisement too, but without - // some kind of encoding that is too large for the TXT record - - serviceInfo.setServiceName("KDE Connect on " + name); - serviceInfo.setServiceType(SERVICE_TYPE); - serviceInfo.setPort(port); - serviceInfo.setHost(address); - - return serviceInfo; - } public MulticastLinkProvider(Context context) { this.context = context; - mNsdManager = (NsdManager) context.getSystemService(Context.NSD_SERVICE); + } + + private void writeIdentity(Socket socket) { + try { + OutputStream out = socket.getOutputStream(); + NetworkPacket myIdentity = NetworkPacket.createIdentityPacket(context); + out.write(myIdentity.serialize().getBytes()); + out.flush(); + } catch (IOException e) { + Log.e(LOG_TAG, "Unable to get stream from socket", e); + return; + } catch (JSONException e) { + Log.e(LOG_TAG, "Unable to deserialize JSON", e); + return; + } + } + + private NetworkPacket readIdentity(Socket socket) { + try { + BufferedReader reader = new BufferedReader(new InputStreamReader(socket.getInputStream())); + String message = reader.readLine(); + return NetworkPacket.unserialize(message); + //Log.e("TcpListener","Received TCP package: "+networkPacket.serialize()); + } catch (Exception e) { + Log.e(LOG_TAG, "Exception while receiving TCP packet", e); + return null; + } } private void setupTcpListener() { @@ -245,21 +276,14 @@ public class MulticastLinkProvider extends BaseLinkProvider implements Multicast try { Socket socket = tcpServer.accept(); configureSocket(socket); - InetAddress remoteAddress = socket.getInetAddress(); - DeviceOffer offer = offers.get(remoteAddress); - //if (offer == null) { - // Log.e(LOG_TAG, "Received a connection from an unknown device "+ remoteAddress.toString() + ", ignoring!"); - // socket.close(); - // return; - // //offer = (DeviceOffer)(offers.values().toArray()[0]); - //} - doTheSslDance(offer, socket, MulticastLink.ConnectionStarted.Remotely); + tcpPacketReceived(socket); } catch (Exception e) { Log.e(LOG_TAG, "TcpReceive exception", e); } } Log.w("TcpListener", "Stopping TCP listener"); }).start(); + } static ServerSocket openServerSocketOnFreePort(int minPort) throws IOException { @@ -281,37 +305,78 @@ public class MulticastLinkProvider extends BaseLinkProvider implements Multicast throw new RuntimeException("This should not be reachable"); } - NsdManager.RegistrationListener mRegistrationListener = new NsdManager.RegistrationListener() { + @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP) + public void initializeRegistrationListener() { + mRegistrationListener = new NsdManager.RegistrationListener() { - @Override - public void onServiceRegistered(NsdServiceInfo NsdServiceInfo) { - // Save the service name. Android may have changed it in order to - // resolve a conflict, so update the name you initially requested - // with the name Android actually used. - Log.i(LOG_TAG, "Registered " + NsdServiceInfo.getServiceName()); + @Override + public void onServiceRegistered(NsdServiceInfo NsdServiceInfo) { + // Save the service name. Android may have changed it in order to + // resolve a conflict, so update the name you initially requested + // with the name Android actually used. + Log.i(LOG_TAG, "Registered " + NsdServiceInfo.getServiceName()); + } + + @Override + public void onRegistrationFailed(NsdServiceInfo serviceInfo, int errorCode) { + // Registration failed! Put debugging code here to determine why. + Log.e(LOG_TAG, "Registration failed"); + } + + @Override + public void onServiceUnregistered(NsdServiceInfo arg0) { + // Service has been unregistered. This only happens when you call + // NsdManager.unregisterService() and pass in this listener. + Log.w(LOG_TAG, "Service unregistered: " + arg0); + } + + @Override + public void onUnregistrationFailed(NsdServiceInfo serviceInfo, int errorCode) { + // Unregistration failed. Put debugging code here to determine why. + Log.e(LOG_TAG, "Unregister of " + serviceInfo + " failed with: " + errorCode); + } + }; + } + + @RequiresApi(Build.VERSION_CODES.LOLLIPOP) + public void initializeNsdManager(RegistrationListener registrationListener) { + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) { + return; } - - @Override - public void onRegistrationFailed(NsdServiceInfo serviceInfo, int errorCode) { - // Registration failed! Put debugging code here to determine why. - Log.e(LOG_TAG, "Registration failed"); + mNsdManager = (NsdManager) context.getSystemService(Context.NSD_SERVICE); + try { + mNsdManager.unregisterService(registrationListener); + } catch (java.lang.IllegalArgumentException e) { + // not yet registered, but it's fine. } + NsdServiceInfo serviceInfo = new NsdServiceInfo(); - @Override - public void onServiceUnregistered(NsdServiceInfo serviceInfo) { - // Service has been unregistered. This only happens when you call - // NsdManager.unregisterService() and pass in this listener. + // The name is subject to change based on conflicts + // with other services advertised on the same network. + NetworkPacket myIdentity = NetworkPacket.createIdentityPacket(context); + String did = myIdentity.getString("deviceID"); + String name = myIdentity.getString("deviceName"); + InetAddress addr = this.tcpServer.getInetAddress(); + int port = this.tcpServer.getLocalPort(); - Log.e(LOG_TAG, "Service unregistered: " + serviceInfo); - offers.remove(serviceInfo.getHost()); - } + // These cause the requirement for api level 21. + serviceInfo.setAttribute("name", myIdentity.getString("deviceName")); + serviceInfo.setAttribute("id", myIdentity.getString("deviceId")); + serviceInfo.setAttribute("type", myIdentity.getString("deviceType")); + serviceInfo.setAttribute("version", myIdentity.getString("protocolVersion")); - @Override - public void onUnregistrationFailed(NsdServiceInfo serviceInfo, int errorCode) { - // Unregistration failed. Put debugging code here to determine why. - Log.e(LOG_TAG, "Unregister of " + serviceInfo + " failed with: " + errorCode); - } - }; + // It might be nice to add the capabilities in the mDNS advertisement too, but without + // some kind of encoding that is too large for the TXT record + + serviceInfo.setServiceName("KDE Connect on " + myIdentity.getString("deviceName")); + serviceInfo.setServiceType(SERVICE_TYPE); + serviceInfo.setHost(addr); + serviceInfo.setPort(port); + + //Log.d("KDE/Lan", "service: " + serviceInfo.toString()); + + mNsdManager.registerService(serviceInfo, NsdManager.PROTOCOL_DNS_SD, registrationListener); + } @RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN) public ResolveListener createResolveListener() { @@ -322,134 +387,120 @@ public class MulticastLinkProvider extends BaseLinkProvider implements Multicast Log.e(LOG_TAG, "Could not resolve service: " + serviceInfo); } - String getString(NsdServiceInfo serviceInfo, String key) { - byte[] raw = serviceInfo.getAttributes().get(key); - if (raw == null) { - return null; - } - return new String(raw, StandardCharsets.UTF_8); - } + @Override public void onServiceResolved(NsdServiceInfo serviceInfo) { - Log.e(LOG_TAG, "Successfully resolved " + serviceInfo); - - String id = getString(serviceInfo, "id"); - if (id == null) { - Log.e(LOG_TAG, "Id not found"); - return; - } - if (id.equals(DeviceHelper.getDeviceId(context))) { - Log.e(LOG_TAG, "Ignoring myself " + serviceInfo); - return; - } - - String name = getString(serviceInfo, "name"); - if (name == null) { - Log.e(LOG_TAG, "Name not found"); - return; - } - - String type_s = getString(serviceInfo, "type"); - if (type_s == null) { - Log.e(LOG_TAG, "Device type not found"); - return; - } - Device.DeviceType type = Device.DeviceType.FromString(type_s); - - String version_s = getString(serviceInfo, "version"); - if (version_s == null) { - Log.e(LOG_TAG, "Protocol version not found"); - return; - } - int protocolVersion = Integer.parseInt(version_s); + Log.i(LOG_TAG, "Successfully resolved " + serviceInfo); InetAddress hostname = serviceInfo.getHost(); int remotePort = serviceInfo.getPort(); - DeviceOffer offer = new DeviceOffer(); - offer.id = id; - offer.name = name; - offer.type = type; - offer.protocolVersion = protocolVersion; - offer.host = hostname; - offer.port = remotePort; - offer.provider = MulticastLinkProvider.this; + SocketFactory socketFactory = SocketFactory.getDefault(); + Socket socket; + try { + socket = socketFactory.createSocket(hostname, remotePort); + } catch (IOException e) { + Log.e(LOG_TAG, "Unable to open socket to mDNS remote: " + serviceInfo, e); + return; + } - offers.put(hostname, offer); - onOfferAdded(offer); + writeIdentity(socket); + NetworkPacket otherIdentity = readIdentity(socket); + + try { + Log.i(LOG_TAG, "Got identity: " + otherIdentity.serialize()); + } catch (JSONException e) { + Log.e(LOG_TAG, "Got identity, but unable to serialize!", e); + return; + } } }; } + @RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN) + public void initializeDiscoveryListener() { + // Instantiate a new DiscoveryListener + NsdManager.DiscoveryListener discoveryListener = new NsdManager.DiscoveryListener() { - NsdManager.DiscoveryListener discoveryListener = new NsdManager.DiscoveryListener() { + // Called as soon as service discovery begins. + @Override + public void onDiscoveryStarted(String regType) { + Log.d(LOG_TAG, "Service discovery started"); + } - // Called as soon as service discovery begins. - @Override - public void onDiscoveryStarted(String regType) { - Log.e(LOG_TAG, "Service discovery started"); - } + @Override + public void onServiceFound(NsdServiceInfo service) { + // A service was found! Do something with it. + Log.d(LOG_TAG, "Service discovery success" + service); + mNsdManager.resolveService(service, createResolveListener()); + } - @Override - public void onServiceFound(NsdServiceInfo service) { - // A service was found! Do something with it. - Log.e(LOG_TAG, "Service discovery success " + service); - mNsdManager.resolveService(service, createResolveListener()); - } + @Override + public void onServiceLost(NsdServiceInfo service) { + // When the network service is no longer available. + // Internal bookkeeping code goes here. + Log.e(LOG_TAG, "service lost: " + service); + } - @Override - public void onServiceLost(NsdServiceInfo service) { - // When the network service is no longer available. - // Internal bookkeeping code goes here. - Log.e(LOG_TAG, "service lost: " + service); - } + @Override + public void onDiscoveryStopped(String serviceType) { + Log.i(LOG_TAG, "Discovery stopped: " + serviceType); + listening = false; + } - @Override - public void onDiscoveryStopped(String serviceType) { - Log.e(LOG_TAG, "Discovery stopped: " + serviceType); - listening = false; - } + @Override + public void onStartDiscoveryFailed(String serviceType, int errorCode) { + Log.e(LOG_TAG, "Discovery failed: Error code:" + errorCode); + mNsdManager.stopServiceDiscovery(this); + } - @Override - public void onStartDiscoveryFailed(String serviceType, int errorCode) { - Log.e(LOG_TAG, "Discovery failed: Error code:" + errorCode); - mNsdManager.stopServiceDiscovery(this); - } + @Override + public void onStopDiscoveryFailed(String serviceType, int errorCode) { + Log.e(LOG_TAG, "Discovery failed: Error code:" + errorCode); + mNsdManager.stopServiceDiscovery(this); + } + }; - @Override - public void onStopDiscoveryFailed(String serviceType, int errorCode) { - Log.e(LOG_TAG, "Discovery failed: Error code:" + errorCode); - mNsdManager.stopServiceDiscovery(this); - } - }; + mNsdManager.discoverServices(SERVICE_TYPE, NsdManager.PROTOCOL_DNS_SD, discoveryListener); + } @Override - public void refresh() { + public void onStart() { + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) { + Log.i(LOG_TAG, "MulticastBackend not supported on devices older thab Lollipop"); + return; + } + + if (!listening) { + listening = true; + + // Need to set up TCP before setting up mDNS because we need to know the TCP listening + // address and port + setupTcpListener(); + + initializeRegistrationListener(); + + initializeNsdManager(mRegistrationListener); + + initializeDiscoveryListener(); + } + } + + @Override + public void onNetworkChange() { onStop(); onStart(); } - public synchronized void onStart() { - Log.e(LOG_TAG,"ON_START"); - if (!listening) { - Log.e(LOG_TAG,"ON_START doing things"); - listening = true; - // We set the tcp server port on the service info, so we need to create it beforehand - setupTcpListener(); - NsdServiceInfo serviceInfo = createServiceInfoForTcpServer( getDeviceIpAddress(), this.tcpServer.getLocalPort()); - mNsdManager.registerService(serviceInfo, NsdManager.PROTOCOL_DNS_SD, mRegistrationListener); - mNsdManager.discoverServices(SERVICE_TYPE, NsdManager.PROTOCOL_DNS_SD, discoveryListener); + @Override + public void onStop() { + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) { + // This backend does not work on older Android versions + return; } - } - public synchronized void onStop() { - /*Log.e(LOG_TAG,"ON_STOP"); - if (listening) { - Log.e(LOG_TAG,"ON_STOP doing things"); - mNsdManager.stopServiceDiscovery(discoveryListener); - mNsdManager.unregisterService(mRegistrationListener); - try { tcpServer.close(); } catch (IOException ignore) { } - listening = false; - }*/ + mNsdManager.unregisterService(mRegistrationListener); + tcpServer.close(); + listening = false; } @Override @@ -457,29 +508,4 @@ public class MulticastLinkProvider extends BaseLinkProvider implements Multicast return "MulticastLinkProvider"; } - @Override - public void connect(DeviceOffer offer) { - try { - Socket socket = new Socket(offer.host,offer.port); - doTheSslDance(offer, socket, MulticastLink.ConnectionStarted.Locally); - } catch (IOException e) { - onConnectionFailed(offer, e.getLocalizedMessage()); - e.printStackTrace(); - } - } - - /* - void trustDevice() { - if (identityPacket.has("certificate")) { - String certificateString = identityPacket.getString("certificate"); - - try { - byte[] certificateBytes = Base64.decode(certificateString, 0); - certificate = SslHelper.parseCertificate(certificateBytes); - Log.i("KDE/Device", "Got certificate "); - } catch (Exception e) { - Log.e("KDE/Device", "Error getting certificate", e); - } - } - */ } diff --git a/src/org/kde/kdeconnect/BackgroundService.java b/src/org/kde/kdeconnect/BackgroundService.java index 359e081b..effdb06a 100644 --- a/src/org/kde/kdeconnect/BackgroundService.java +++ b/src/org/kde/kdeconnect/BackgroundService.java @@ -39,9 +39,8 @@ import android.util.Log; import androidx.core.app.NotificationCompat; import androidx.core.content.ContextCompat; -import org.kde.kdeconnect.Backends.DeviceLink; +import org.kde.kdeconnect.Backends.BaseLink; import org.kde.kdeconnect.Backends.BaseLinkProvider; -import org.kde.kdeconnect.Backends.DeviceOffer; import org.kde.kdeconnect.Backends.LanBackend.LanLinkProvider; import org.kde.kdeconnect.Backends.MulticastBackend.MulticastLinkProvider; import org.kde.kdeconnect.Helpers.NotificationHelper; @@ -84,7 +83,6 @@ public class BackgroundService extends Service { private final ArrayList linkProviders = new ArrayList<>(); private final ConcurrentHashMap devices = new ConcurrentHashMap<>(); - private final ConcurrentHashMap deviceOffers = new ConcurrentHashMap<>(); private final HashSet discoveryModeAcquisitions = new HashSet<>(); @@ -102,6 +100,19 @@ public class BackgroundService extends Service { return wasEmpty; } + private void releaseDiscoveryMode(Object key) { + boolean removed = discoveryModeAcquisitions.remove(key); + //Log.e("releaseDiscoveryMode",key.getClass().getName() +" ["+discoveryModeAcquisitions.size()+"]"); + if (removed && discoveryModeAcquisitions.isEmpty()) { + cleanDevices(); + } + } + + private boolean isInDiscoveryMode() { + //return !discoveryModeAcquisitions.isEmpty(); + return true; // Keep it always on for now + } + private final Device.PairingCallback devicePairingCallback = new Device.PairingCallback() { @Override public void incomingRequest() { @@ -152,7 +163,7 @@ public class BackgroundService extends Service { } private void registerLinkProviders() { - //linkProviders.add(new LanLinkProvider(this)); + linkProviders.add(new LanLinkProvider(this)); linkProviders.add(new MulticastLinkProvider(this)); // linkProviders.add(new LoopbackLinkProvider(this)); // linkProviders.add(new BluetoothLinkProvider(this)); @@ -166,55 +177,46 @@ public class BackgroundService extends Service { return devices.get(id); } + private void cleanDevices() { + new Thread(() -> { + for (Device d : devices.values()) { + if (!d.isPaired() && !d.isPairRequested() && !d.isPairRequestedByPeer() && !d.deviceShouldBeKeptAlive()) { + d.disconnect(); + } + } + }).start(); + } + private final BaseLinkProvider.ConnectionReceiver deviceListener = new BaseLinkProvider.ConnectionReceiver() { - @Override - public void onOfferAdded(DeviceOffer offer) { - deviceOffers.put(offer.id, offer); + public void onConnectionReceived(final NetworkPacket identityPacket, final BaseLink link) { + String deviceId = identityPacket.getString("deviceId"); - // TEST - Log.e("KDE/BackgroundService", "offer added: " + offer.id); - Device device = devices.get(offer.id); - if (device == null) { - device = new Device(BackgroundService.this, offer, Device.PairStatus.NotPaired); - devices.put(offer.id, device); - Log.e("KDE/BackgroundService", "device stored: " + offer.id); - } - // TEST - - offer.connect(); - - - onDeviceListChanged(); - } - - @Override - public void onOfferRemoved(String id) { - Log.e("KDE/BackgroundService", "offer removed: " + id); - deviceOffers.remove(id); - if (devices.get(id) != null && !devices.get(id).isReachable() && !devices.get(id).isPaired()) { - devices.remove(id); - } - onDeviceListChanged(); - } - - @Override - public void onLinkConnected(DeviceOffer offer, DeviceLink link) { - String deviceId = link.getDeviceId(); - Log.e("KDE/BackgroundService", "device connected: " + deviceId); Device device = devices.get(deviceId); - if (device == null) { - device = new Device(BackgroundService.this, offer, Device.PairStatus.Paired); - devices.put(link.getDeviceId(), device); + + if (device != null) { + Log.i("KDE/BackgroundService", "addLink, known device: " + deviceId); + device.addLink(identityPacket, link); + } else { + Log.i("KDE/BackgroundService", "addLink,unknown device: " + deviceId); + device = new Device(BackgroundService.this, identityPacket, link); + if (device.isPaired() || device.isPairRequested() || device.isPairRequestedByPeer() + || link.linkShouldBeKeptAlive() + || isInDiscoveryMode()) { + devices.put(deviceId, device); + device.addPairingCallback(devicePairingCallback); + } else { + device.disconnect(); + } } - device.addLink(link); + onDeviceListChanged(); } @Override - public void onLinkDisconnected(DeviceLink link) { - Device d = devices.get(link); + public void onConnectionLost(BaseLink link) { + Device d = devices.get(link.getDeviceId()); Log.i("KDE/onConnectionLost", "removeLink, deviceId: " + link.getDeviceId()); if (d != null) { d.removeLink(link); @@ -228,12 +230,6 @@ public class BackgroundService extends Service { } onDeviceListChanged(); } - - @Override - public void onConnectionFailed(DeviceOffer offer, String reason) { - Log.e("KDE/BackgroundService", "device connect failed: " + reason); - } - }; public ConcurrentHashMap getDevices() { @@ -241,15 +237,8 @@ public class BackgroundService extends Service { } public void onNetworkChange() { - for (DeviceOffer d : deviceOffers.values()) { - Log.e("YESOFFER" , "OFFER " + d.name); - } - for (Device d : devices.values()) { - Log.e("NOTOFFER" , "DEVICE " + d.getName()); - } - for (BaseLinkProvider a : linkProviders) { - a.refresh(); + a.onNetworkChange(); } } @@ -301,7 +290,7 @@ public class BackgroundService extends Service { addConnectionListener(deviceListener); for (BaseLinkProvider a : linkProviders) { - a.refresh(); + a.onStart(); } } @@ -413,10 +402,9 @@ public class BackgroundService extends Service { @Override public void onDestroy() { stopForeground(true); - /* for (BaseLinkProvider a : linkProviders) { a.onStop(); - }*/ + } super.onDestroy(); } diff --git a/src/org/kde/kdeconnect/Device.java b/src/org/kde/kdeconnect/Device.java index bbbd7d9a..c810d177 100644 --- a/src/org/kde/kdeconnect/Device.java +++ b/src/org/kde/kdeconnect/Device.java @@ -41,11 +41,11 @@ import androidx.core.content.ContextCompat; import org.apache.commons.collections4.MultiValuedMap; import org.apache.commons.collections4.multimap.ArrayListValuedHashMap; -import org.kde.kdeconnect.Backends.DeviceLink; +import org.kde.kdeconnect.Backends.BaseLink; import org.kde.kdeconnect.Backends.BasePairingHandler; -import org.kde.kdeconnect.Backends.DeviceOffer; import org.kde.kdeconnect.Helpers.DeviceHelper; import org.kde.kdeconnect.Helpers.NotificationHelper; +import org.kde.kdeconnect.Helpers.SecurityHelpers.SslHelper; import org.kde.kdeconnect.Plugins.Plugin; import org.kde.kdeconnect.Plugins.PluginFactory; import org.kde.kdeconnect.UserInterface.MainActivity; @@ -66,7 +66,7 @@ import java.util.Vector; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.CopyOnWriteArrayList; -public class Device implements DeviceLink.PacketReceiver { +public class Device implements BaseLink.PacketReceiver { private final Context context; @@ -82,7 +82,7 @@ public class Device implements DeviceLink.PacketReceiver { private final CopyOnWriteArrayList pairingCallback = new CopyOnWriteArrayList<>(); private final Map pairingHandlers = new HashMap<>(); - private final CopyOnWriteArrayList links = new CopyOnWriteArrayList<>(); + private final CopyOnWriteArrayList links = new CopyOnWriteArrayList<>(); private DevicePacketQueue packetQueue; private List supportedPlugins = new ArrayList<>(); @@ -170,28 +170,21 @@ public class Device implements DeviceLink.PacketReceiver { //reloadPluginsFromSettings(); } - //Remembered trusted device, we need to wait for a incoming devicelink to communicate - Device(Context context, DeviceOffer deviceOffer, PairStatus paired) { - settings = context.getSharedPreferences(deviceOffer.id, Context.MODE_PRIVATE); + //Device known via an incoming connection sent to us via a devicelink, we know everything but we don't trust it yet + Device(Context context, NetworkPacket np, BaseLink dl) { + + //Log.e("Device","Constructor B"); - Log.e("AAAAA", "Adding device "+deviceOffer.name); this.context = context; - this.deviceId = deviceOffer.id; - this.name = deviceOffer.name; - this.pairStatus = paired; - this.protocolVersion = deviceOffer.protocolVersion; - this.deviceType = deviceOffer.type; + this.deviceId = np.getString("deviceId"); + this.name = context.getString(R.string.unknown_device); //We read it in addLink + this.pairStatus = PairStatus.NotPaired; + this.protocolVersion = 0; + this.deviceType = DeviceType.Computer; - SharedPreferences.Editor editor = settings.edit(); - editor.putString("deviceName", this.name); - editor.putString("deviceType", this.deviceType.toString()); - editor.apply(); + settings = context.getSharedPreferences(deviceId, Context.MODE_PRIVATE); - //Assume every plugin is supported until addLink is called and we can get the actual list - supportedPlugins = new Vector<>(PluginFactory.getAvailablePlugins()); - - //Do not load plugins yet, the device is not present - //reloadPluginsFromSettings(); + addLink(np, dl); } public String getName() { @@ -441,7 +434,7 @@ public class Device implements DeviceLink.PacketReceiver { return !links.isEmpty(); } - public void addLink(DeviceLink link) { + public void addLink(NetworkPacket identityPacket, BaseLink link) { if (links.isEmpty()) { packetQueue = new DevicePacketQueue(this); } @@ -449,6 +442,31 @@ public class Device implements DeviceLink.PacketReceiver { links.add(link); link.addPacketReceiver(this); + this.protocolVersion = identityPacket.getInt("protocolVersion"); + + if (identityPacket.has("deviceName")) { + this.name = identityPacket.getString("deviceName", this.name); + SharedPreferences.Editor editor = settings.edit(); + editor.putString("deviceName", this.name); + editor.apply(); + } + + if (identityPacket.has("deviceType")) { + this.deviceType = DeviceType.FromString(identityPacket.getString("deviceType", "desktop")); + } + + if (identityPacket.has("certificate")) { + String certificateString = identityPacket.getString("certificate"); + + try { + byte[] certificateBytes = Base64.decode(certificateString, 0); + certificate = SslHelper.parseCertificate(certificateBytes); + Log.i("KDE/Device", "Got certificate "); + } catch (Exception e) { + Log.e("KDE/Device", "Error getting certificate", e); + + } + } try { SharedPreferences globalSettings = PreferenceManager.getDefaultSharedPreferences(context); @@ -459,7 +477,7 @@ public class Device implements DeviceLink.PacketReceiver { Log.e("KDE/Device", "Exception reading our own private key", e); //Should not happen } - Log.i("KDE/Device", "addLink " + link + " -> " + getName() + " active links: " + links.size()); + Log.i("KDE/Device", "addLink " + link.getLinkProvider().getName() + " -> " + getName() + " active links: " + links.size()); if (!pairingHandlers.containsKey(link.getName())) { BasePairingHandler.PairingHandlerCallback callback = new BasePairingHandler.PairingHandlerCallback() { @@ -490,10 +508,11 @@ public class Device implements DeviceLink.PacketReceiver { pairingHandlers.put(link.getName(), link.getPairingHandler(this, callback)); } - Set outgoingCapabilities = new HashSet<>(); - Set incomingCapabilities = new HashSet<>(); + Set outgoingCapabilities = identityPacket.getStringSet("outgoingCapabilities", null); + Set incomingCapabilities = identityPacket.getStringSet("incomingCapabilities", null); - if (!incomingCapabilities.isEmpty() && !outgoingCapabilities.isEmpty()) { + + if (incomingCapabilities != null && outgoingCapabilities != null) { supportedPlugins = new Vector<>(PluginFactory.pluginsForCapabilities(incomingCapabilities, outgoingCapabilities)); } else { supportedPlugins = new Vector<>(PluginFactory.getAvailablePlugins()); @@ -504,12 +523,12 @@ public class Device implements DeviceLink.PacketReceiver { } - public void removeLink(DeviceLink link) { + public void removeLink(BaseLink link) { //FilesHelper.LogOpenFileCount(); /* Remove pairing handler corresponding to that link too if it was the only link*/ boolean linkPresent = false; - for (DeviceLink bl : links) { + for (BaseLink bl : links) { if (bl.getName().equals(link.getName())) { linkPresent = true; break; @@ -521,7 +540,7 @@ public class Device implements DeviceLink.PacketReceiver { link.removePacketReceiver(this); links.remove(link); - Log.i("KDE/Device", "removeLink: " + link + " -> " + getName() + " active links: " + links.size()); + Log.i("KDE/Device", "removeLink: " + link.getLinkProvider().getName() + " -> " + getName() + " active links: " + links.size()); if (links.isEmpty()) { reloadPluginsFromSettings(); if (packetQueue != null) { @@ -670,7 +689,7 @@ public class Device implements DeviceLink.PacketReceiver { * @param np the packet to send * @param callback a callback that can receive realtime updates * @return true if the packet was sent ok, false otherwise - * @see DeviceLink#sendPacket(NetworkPacket, SendPacketStatusCallback) + * @see BaseLink#sendPacket(NetworkPacket, SendPacketStatusCallback) */ @WorkerThread public boolean sendPacketBlocking(final NetworkPacket np, final SendPacketStatusCallback callback) { @@ -684,7 +703,7 @@ public class Device implements DeviceLink.PacketReceiver { boolean success = false; //Make a copy to avoid concurrent modification exception if the original list changes - for (final DeviceLink link : links) { + for (final BaseLink link : links) { if (link == null) continue; //Since we made a copy, maybe somebody destroyed the link in the meanwhile success = link.sendPacket(np, callback); @@ -861,10 +880,27 @@ public class Device implements DeviceLink.PacketReceiver { } public void disconnect() { - for (DeviceLink link : links) { + for (BaseLink link : links) { link.disconnect(); } } + + public boolean deviceShouldBeKeptAlive() { + + SharedPreferences preferences = context.getSharedPreferences("trusted_devices", Context.MODE_PRIVATE); + if (preferences.contains(getDeviceId())) { + //Log.e("DeviceShouldBeKeptAlive", "because it's a paired device"); + return true; //Already paired + } + + for (BaseLink l : links) { + if (l.linkShouldBeKeptAlive()) { + return true; + } + } + return false; + } + public List getSupportedPlugins() { return supportedPlugins; } diff --git a/src/org/kde/kdeconnect/Plugins/MprisPlugin/MprisActivity.java b/src/org/kde/kdeconnect/Plugins/MprisPlugin/MprisActivity.java index 03632104..d5ed3b6d 100644 --- a/src/org/kde/kdeconnect/Plugins/MprisPlugin/MprisActivity.java +++ b/src/org/kde/kdeconnect/Plugins/MprisPlugin/MprisActivity.java @@ -51,11 +51,11 @@ import androidx.core.content.ContextCompat; import androidx.core.graphics.drawable.DrawableCompat; import androidx.fragment.app.FragmentManager; -import org.kde.kdeconnect.Backends.DeviceLink; +import org.kde.kdeconnect.Backends.BaseLink; import org.kde.kdeconnect.Backends.BaseLinkProvider; -import org.kde.kdeconnect.Backends.DeviceOffer; import org.kde.kdeconnect.BackgroundService; import org.kde.kdeconnect.Helpers.VideoUrlsHelper; +import org.kde.kdeconnect.NetworkPacket; import org.kde.kdeconnect.Plugins.SystemvolumePlugin.SystemvolumeFragment; import org.kde.kdeconnect.UserInterface.ThemeUtil; import org.kde.kdeconnect_tp.R; @@ -237,32 +237,15 @@ public class MprisActivity extends AppCompatActivity { } private final BaseLinkProvider.ConnectionReceiver connectionReceiver = new BaseLinkProvider.ConnectionReceiver() { - @Override - public void onOfferAdded(DeviceOffer offer) { - - } - - @Override - public void onOfferRemoved(String id) { - - } - - @Override - public void onLinkConnected(DeviceOffer offer, DeviceLink link) { + public void onConnectionReceived(NetworkPacket identityPacket, BaseLink link) { connectToPlugin(null); } @Override - public void onConnectionFailed(DeviceOffer offer, String reason) { + public void onConnectionLost(BaseLink link) { } - - @Override - public void onLinkDisconnected(DeviceLink link) { - - } - }; @Override