File indexing completed on 2025-02-02 04:47:48
0001 /* 0002 * SPDX-FileCopyrightText: 2014 Albert Vaca Cintora <albertvaka@gmail.com> 0003 * 0004 * SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL 0005 */ 0006 0007 package org.kde.kdeconnect.Backends.LanBackend; 0008 0009 import android.content.Context; 0010 import android.util.Log; 0011 0012 import org.apache.commons.io.IOUtils; 0013 import androidx.annotation.NonNull; 0014 import androidx.annotation.WorkerThread; 0015 0016 import org.json.JSONObject; 0017 import org.kde.kdeconnect.Backends.BaseLink; 0018 import org.kde.kdeconnect.Backends.BaseLinkProvider; 0019 import org.kde.kdeconnect.Device; 0020 import org.kde.kdeconnect.DeviceInfo; 0021 import org.kde.kdeconnect.Helpers.SecurityHelpers.SslHelper; 0022 import org.kde.kdeconnect.Helpers.ThreadHelper; 0023 import org.kde.kdeconnect.NetworkPacket; 0024 0025 import java.io.BufferedReader; 0026 import java.io.IOException; 0027 import java.io.InputStream; 0028 import java.io.InputStreamReader; 0029 import java.io.OutputStream; 0030 import java.net.InetSocketAddress; 0031 import java.net.ServerSocket; 0032 import java.net.Socket; 0033 import java.net.SocketTimeoutException; 0034 import java.nio.channels.NotYetConnectedException; 0035 0036 import javax.net.ssl.SSLHandshakeException; 0037 import javax.net.ssl.SSLSocket; 0038 0039 import kotlin.text.Charsets; 0040 0041 public class LanLink extends BaseLink { 0042 0043 public enum ConnectionStarted { 0044 Locally, Remotely 0045 } 0046 0047 private final DeviceInfo deviceInfo; 0048 0049 private volatile SSLSocket socket = null; 0050 0051 @Override 0052 public void disconnect() { 0053 Log.i("LanLink/Disconnect","socket:"+ socket.hashCode()); 0054 try { 0055 socket.close(); 0056 } catch (IOException e) { 0057 Log.e("LanLink", "Error", e); 0058 } 0059 } 0060 0061 //Returns the old socket 0062 public SSLSocket reset(final SSLSocket newSocket) throws IOException { 0063 0064 SSLSocket oldSocket = socket; 0065 socket = newSocket; 0066 0067 IOUtils.close(oldSocket); //This should cancel the readThread 0068 0069 //Log.e("LanLink", "Start listening"); 0070 //Create a thread to take care of incoming data for the new socket 0071 ThreadHelper.execute(() -> { 0072 try { 0073 BufferedReader reader = new BufferedReader(new InputStreamReader(newSocket.getInputStream(), Charsets.UTF_8)); 0074 while (true) { 0075 String packet; 0076 try { 0077 packet = reader.readLine(); 0078 } catch (SocketTimeoutException e) { 0079 continue; 0080 } 0081 if (packet == null) { 0082 throw new IOException("End of stream"); 0083 } 0084 if (packet.isEmpty()) { 0085 continue; 0086 } 0087 NetworkPacket np = NetworkPacket.unserialize(packet); 0088 receivedNetworkPacket(np); 0089 } 0090 } catch (Exception e) { 0091 Log.i("LanLink", "Socket closed: " + newSocket.hashCode() + ". Reason: " + e.getMessage()); 0092 try { Thread.sleep(300); } catch (InterruptedException ignored) {} // Wait a bit because we might receive a new socket meanwhile 0093 boolean thereIsaANewSocket = (newSocket != socket); 0094 if (!thereIsaANewSocket) { 0095 Log.i("LanLink", "Socket closed and there's no new socket, disconnecting device"); 0096 getLinkProvider().onConnectionLost(LanLink.this); 0097 } 0098 } 0099 }); 0100 0101 return oldSocket; 0102 } 0103 0104 public LanLink(@NonNull Context context, @NonNull DeviceInfo deviceInfo, @NonNull BaseLinkProvider linkProvider, @NonNull SSLSocket socket) throws IOException { 0105 super(context, linkProvider); 0106 this.deviceInfo = deviceInfo; 0107 reset(socket); 0108 } 0109 0110 @Override 0111 public String getName() { 0112 return "LanLink"; 0113 } 0114 0115 @Override 0116 public DeviceInfo getDeviceInfo() { 0117 return deviceInfo; 0118 } 0119 0120 @WorkerThread 0121 @Override 0122 public boolean sendPacket(@NonNull NetworkPacket np, @NonNull final Device.SendPacketStatusCallback callback, boolean sendPayloadFromSameThread) { 0123 if (socket == null) { 0124 Log.e("KDE/sendPacket", "Not yet connected"); 0125 callback.onFailure(new NotYetConnectedException()); 0126 return false; 0127 } 0128 0129 try { 0130 0131 //Prepare socket for the payload 0132 final ServerSocket server; 0133 if (np.hasPayload()) { 0134 server = LanLinkProvider.openServerSocketOnFreePort(LanLinkProvider.PAYLOAD_TRANSFER_MIN_PORT); 0135 JSONObject payloadTransferInfo = new JSONObject(); 0136 payloadTransferInfo.put("port", server.getLocalPort()); 0137 np.setPayloadTransferInfo(payloadTransferInfo); 0138 } else { 0139 server = null; 0140 } 0141 0142 //Log.e("LanLink/sendPacket", np.getType()); 0143 0144 //Send body of the network packet 0145 try { 0146 OutputStream writer = socket.getOutputStream(); 0147 writer.write(np.serialize().getBytes(Charsets.UTF_8)); 0148 writer.flush(); 0149 } catch (Exception e) { 0150 disconnect(); //main socket is broken, disconnect 0151 throw e; 0152 } 0153 0154 //Send payload 0155 if (server != null) { 0156 if (sendPayloadFromSameThread) { 0157 sendPayload(np, callback, server); 0158 } else { 0159 ThreadHelper.execute(() -> { 0160 try { 0161 sendPayload(np, callback, server); 0162 } catch (IOException e) { 0163 e.printStackTrace(); 0164 Log.e("LanLink/sendPacket", "Async sendPayload failed for packet of type " + np.getType() + ". The Plugin was NOT notified."); 0165 } 0166 }); 0167 } 0168 } 0169 0170 if (!np.isCanceled()) { 0171 callback.onSuccess(); 0172 } 0173 return true; 0174 } catch (Exception e) { 0175 callback.onFailure(e); 0176 return false; 0177 } finally { 0178 //Make sure we close the payload stream, if any 0179 if (np.hasPayload()) { 0180 np.getPayload().close(); 0181 } 0182 } 0183 } 0184 0185 private void sendPayload(NetworkPacket np, Device.SendPacketStatusCallback callback, ServerSocket server) throws IOException { 0186 Socket payloadSocket = null; 0187 OutputStream outputStream = null; 0188 InputStream inputStream; 0189 try { 0190 if (!np.isCanceled()) { 0191 //Wait a maximum of 10 seconds for the other end to establish a connection with our socket, close it afterwards 0192 server.setSoTimeout(10 * 1000); 0193 0194 payloadSocket = server.accept(); 0195 0196 //Convert to SSL if needed 0197 payloadSocket = SslHelper.convertToSslSocket(context, payloadSocket, getDeviceId(), true, false); 0198 0199 outputStream = payloadSocket.getOutputStream(); 0200 inputStream = np.getPayload().getInputStream(); 0201 0202 Log.i("KDE/LanLink", "Beginning to send payload for " + np.getType()); 0203 byte[] buffer = new byte[4096]; 0204 int bytesRead; 0205 long size = np.getPayloadSize(); 0206 long progress = 0; 0207 long timeSinceLastUpdate = -1; 0208 while (!np.isCanceled() && (bytesRead = inputStream.read(buffer)) != -1) { 0209 //Log.e("ok",""+bytesRead); 0210 progress += bytesRead; 0211 outputStream.write(buffer, 0, bytesRead); 0212 if (size > 0) { 0213 if (timeSinceLastUpdate + 500 < System.currentTimeMillis()) { //Report progress every half a second 0214 long percent = ((100 * progress) / size); 0215 callback.onPayloadProgressChanged((int) percent); 0216 timeSinceLastUpdate = System.currentTimeMillis(); 0217 } 0218 } 0219 } 0220 outputStream.flush(); 0221 Log.i("KDE/LanLink", "Finished sending payload (" + progress + " bytes written)"); 0222 } 0223 } catch(SocketTimeoutException e) { 0224 Log.e("LanLink", "Socket for payload in packet " + np.getType() + " timed out. The other end didn't fetch the payload."); 0225 } catch(SSLHandshakeException e) { 0226 // The exception can be due to several causes. "Connection closed by peer" seems to be a common one. 0227 // If we could distinguish different cases we could react differently for some of them, but I haven't found how. 0228 Log.e("sendPacket","Payload SSLSocket failed"); 0229 e.printStackTrace(); 0230 } finally { 0231 try { server.close(); } catch (Exception ignored) { } 0232 try { IOUtils.close(payloadSocket); } catch (Exception ignored) { } 0233 np.getPayload().close(); 0234 try { IOUtils.close(outputStream); } catch (Exception ignored) { } 0235 } 0236 } 0237 0238 private void receivedNetworkPacket(NetworkPacket np) { 0239 0240 if (np.hasPayloadTransferInfo()) { 0241 Socket payloadSocket = new Socket(); 0242 try { 0243 int tcpPort = np.getPayloadTransferInfo().getInt("port"); 0244 InetSocketAddress deviceAddress = (InetSocketAddress) socket.getRemoteSocketAddress(); 0245 payloadSocket.connect(new InetSocketAddress(deviceAddress.getAddress(), tcpPort)); 0246 payloadSocket = SslHelper.convertToSslSocket(context, payloadSocket, getDeviceId(), true, true); 0247 np.setPayload(new NetworkPacket.Payload(payloadSocket, np.getPayloadSize())); 0248 } catch (Exception e) { 0249 try { payloadSocket.close(); } catch(Exception ignored) { } 0250 Log.e("KDE/LanLink", "Exception connecting to payload remote socket", e); 0251 } 0252 0253 } 0254 0255 packetReceived(np); 0256 } 0257 0258 }