mirror of
https://code.briarproject.org/briar/briar.git
synced 2026-02-20 14:49:53 +01:00
Made the invitation protocol symmetrical.
Both devices try to make outgoing connections and accept incoming connections simultaneously. This should lead to faster connection establishment when there are asymmetrical connectivity problems, such as devices that are unable to receive LAN multicast packets or make themselves discoverable via Bluetooth.
This commit is contained in:
@@ -18,14 +18,10 @@ public interface DuplexPlugin extends Plugin {
|
|||||||
boolean supportsInvitations();
|
boolean supportsInvitations();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Starts the invitation process from the inviter's side. Returns null if
|
* Attempts to create and return an invitation connection to the remote
|
||||||
* no connection can be established within the given timeout.
|
* peer. Returns null if no connection can be established within the given
|
||||||
|
* time.
|
||||||
*/
|
*/
|
||||||
DuplexTransportConnection sendInvitation(PseudoRandom r, long timeout);
|
DuplexTransportConnection createInvitationConnection(PseudoRandom r,
|
||||||
|
long timeout);
|
||||||
/**
|
|
||||||
* Starts the invitation process from the invitee's side. Returns null if
|
|
||||||
* no connection can be established within the given timeout.
|
|
||||||
*/
|
|
||||||
DuplexTransportConnection acceptInvitation(PseudoRandom r, long timeout);
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ package net.sf.briar.invitation;
|
|||||||
|
|
||||||
import static java.util.logging.Level.INFO;
|
import static java.util.logging.Level.INFO;
|
||||||
import static java.util.logging.Level.WARNING;
|
import static java.util.logging.Level.WARNING;
|
||||||
import static net.sf.briar.api.invitation.InvitationConstants.CONNECTION_TIMEOUT;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
@@ -57,13 +56,8 @@ class AliceConnector extends Connector {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
// Try an outgoing connection first, then an incoming connection
|
// Create an incoming or outgoing connection
|
||||||
long halfTime = clock.currentTimeMillis() + CONNECTION_TIMEOUT;
|
DuplexTransportConnection conn = createInvitationConnection();
|
||||||
DuplexTransportConnection conn = makeOutgoingConnection();
|
|
||||||
if(conn == null) {
|
|
||||||
waitForHalfTime(halfTime);
|
|
||||||
conn = acceptIncomingConnection();
|
|
||||||
}
|
|
||||||
if(conn == null) return;
|
if(conn == null) return;
|
||||||
if(LOG.isLoggable(INFO)) LOG.info(pluginName + " connected");
|
if(LOG.isLoggable(INFO)) LOG.info(pluginName + " connected");
|
||||||
// Don't proceed with more than one connection
|
// Don't proceed with more than one connection
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ package net.sf.briar.invitation;
|
|||||||
|
|
||||||
import static java.util.logging.Level.INFO;
|
import static java.util.logging.Level.INFO;
|
||||||
import static java.util.logging.Level.WARNING;
|
import static java.util.logging.Level.WARNING;
|
||||||
import static net.sf.briar.api.invitation.InvitationConstants.CONNECTION_TIMEOUT;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
@@ -57,13 +56,8 @@ class BobConnector extends Connector {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
// Try an incoming connection first, then an outgoing connection
|
// Create an incoming or outgoing connection
|
||||||
long halfTime = clock.currentTimeMillis() + CONNECTION_TIMEOUT;
|
DuplexTransportConnection conn = createInvitationConnection();
|
||||||
DuplexTransportConnection conn = acceptIncomingConnection();
|
|
||||||
if(conn == null) {
|
|
||||||
waitForHalfTime(halfTime);
|
|
||||||
conn = makeOutgoingConnection();
|
|
||||||
}
|
|
||||||
if(conn == null) return;
|
if(conn == null) return;
|
||||||
if(LOG.isLoggable(INFO)) LOG.info(pluginName + " connected");
|
if(LOG.isLoggable(INFO)) LOG.info(pluginName + " connected");
|
||||||
// Carry out the key agreement protocol
|
// Carry out the key agreement protocol
|
||||||
|
|||||||
@@ -112,31 +112,10 @@ abstract class Connector extends Thread {
|
|||||||
messageDigest = crypto.getMessageDigest();
|
messageDigest = crypto.getMessageDigest();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected DuplexTransportConnection acceptIncomingConnection() {
|
protected DuplexTransportConnection createInvitationConnection() {
|
||||||
if(LOG.isLoggable(INFO))
|
if(LOG.isLoggable(INFO))
|
||||||
LOG.info(pluginName + " accepting incoming connection");
|
LOG.info(pluginName + " creating invitation connection");
|
||||||
return plugin.acceptInvitation(random, CONNECTION_TIMEOUT);
|
return plugin.createInvitationConnection(random, CONNECTION_TIMEOUT);
|
||||||
}
|
|
||||||
|
|
||||||
protected DuplexTransportConnection makeOutgoingConnection() {
|
|
||||||
if(LOG.isLoggable(INFO))
|
|
||||||
LOG.info(pluginName + " making outgoing connection");
|
|
||||||
return plugin.sendInvitation(random, CONNECTION_TIMEOUT);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void waitForHalfTime(long halfTime) {
|
|
||||||
long now = clock.currentTimeMillis();
|
|
||||||
if(now < halfTime) {
|
|
||||||
if(LOG.isLoggable(INFO))
|
|
||||||
LOG.info(pluginName + " sleeping until half-time");
|
|
||||||
try {
|
|
||||||
clock.sleep(halfTime - now);
|
|
||||||
} catch(InterruptedException e) {
|
|
||||||
if(LOG.isLoggable(INFO)) LOG.info("Interrupted while sleeping");
|
|
||||||
Thread.currentThread().interrupt();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void sendPublicKeyHash(Writer w) throws IOException {
|
protected void sendPublicKeyHash(Writer w) throws IOException {
|
||||||
|
|||||||
@@ -166,13 +166,15 @@ class BluetoothPlugin implements DuplexPlugin {
|
|||||||
tryToClose(ss);
|
tryToClose(ss);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
BluetoothTransportConnection conn =
|
callback.incomingConnectionCreated(wrapSocket(s));
|
||||||
new BluetoothTransportConnection(this, s);
|
|
||||||
callback.incomingConnectionCreated(conn);
|
|
||||||
if(!running) return;
|
if(!running) return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private DuplexTransportConnection wrapSocket(StreamConnection s) {
|
||||||
|
return new BluetoothTransportConnection(this, s);
|
||||||
|
}
|
||||||
|
|
||||||
public void stop() {
|
public void stop() {
|
||||||
running = false;
|
running = false;
|
||||||
tryToClose(socket);
|
tryToClose(socket);
|
||||||
@@ -202,11 +204,8 @@ class BluetoothPlugin implements DuplexPlugin {
|
|||||||
public void run() {
|
public void run() {
|
||||||
if(!running) return;
|
if(!running) return;
|
||||||
StreamConnection s = connect(makeUrl(address, uuid));
|
StreamConnection s = connect(makeUrl(address, uuid));
|
||||||
if(s != null) {
|
if(s != null)
|
||||||
callback.outgoingConnectionCreated(c,
|
callback.outgoingConnectionCreated(c, wrapSocket(s));
|
||||||
new BluetoothTransportConnection(
|
|
||||||
BluetoothPlugin.this, s));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -239,7 +238,7 @@ class BluetoothPlugin implements DuplexPlugin {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public DuplexTransportConnection sendInvitation(PseudoRandom r,
|
public DuplexTransportConnection createInvitationConnection(PseudoRandom r,
|
||||||
long timeout) {
|
long timeout) {
|
||||||
if(!running) return null;
|
if(!running) return null;
|
||||||
// Use the invitation codes to generate the UUID
|
// Use the invitation codes to generate the UUID
|
||||||
@@ -282,7 +281,6 @@ class BluetoothPlugin implements DuplexPlugin {
|
|||||||
|
|
||||||
private void makeDeviceDiscoverable() {
|
private void makeDeviceDiscoverable() {
|
||||||
// Try to make the device discoverable (requires root on Linux)
|
// Try to make the device discoverable (requires root on Linux)
|
||||||
if(!running) return;
|
|
||||||
try {
|
try {
|
||||||
localDevice.setDiscoverable(GIAC);
|
localDevice.setDiscoverable(GIAC);
|
||||||
} catch(BluetoothStateException e) {
|
} catch(BluetoothStateException e) {
|
||||||
@@ -290,12 +288,6 @@ class BluetoothPlugin implements DuplexPlugin {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public DuplexTransportConnection acceptInvitation(PseudoRandom r,
|
|
||||||
long timeout) {
|
|
||||||
// FIXME
|
|
||||||
return sendInvitation(r, timeout);
|
|
||||||
}
|
|
||||||
|
|
||||||
private class DiscoveryThread extends Thread {
|
private class DiscoveryThread extends Thread {
|
||||||
|
|
||||||
private final LatchedReference<StreamConnection> socketLatch;
|
private final LatchedReference<StreamConnection> socketLatch;
|
||||||
@@ -325,18 +317,16 @@ class BluetoothPlugin implements DuplexPlugin {
|
|||||||
new InvitationListener(discoveryAgent, uuid);
|
new InvitationListener(discoveryAgent, uuid);
|
||||||
discoveryAgent.startInquiry(GIAC, listener);
|
discoveryAgent.startInquiry(GIAC, listener);
|
||||||
String url = listener.waitForUrl();
|
String url = listener.waitForUrl();
|
||||||
if(url != null) {
|
if(url == null) continue;
|
||||||
StreamConnection s = connect(url);
|
StreamConnection s = connect(url);
|
||||||
if(s == null) return;
|
if(s == null) continue;
|
||||||
|
if(LOG.isLoggable(INFO)) LOG.info("Outgoing connection");
|
||||||
|
if(!socketLatch.set(s)) {
|
||||||
if(LOG.isLoggable(INFO))
|
if(LOG.isLoggable(INFO))
|
||||||
LOG.info("Outgoing connection");
|
LOG.info("Closing redundant connection");
|
||||||
if(!socketLatch.set(s)) {
|
tryToClose(s);
|
||||||
if(LOG.isLoggable(INFO))
|
|
||||||
LOG.info("Closing redundant connection");
|
|
||||||
tryToClose(s);
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
return;
|
||||||
} catch(BluetoothStateException e) {
|
} catch(BluetoothStateException e) {
|
||||||
if(LOG.isLoggable(WARNING))
|
if(LOG.isLoggable(WARNING))
|
||||||
LOG.log(WARNING, e.toString(), e);
|
LOG.log(WARNING, e.toString(), e);
|
||||||
|
|||||||
@@ -217,13 +217,15 @@ class DroidtoothPlugin implements DuplexPlugin {
|
|||||||
tryToClose(ss);
|
tryToClose(ss);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
DroidtoothTransportConnection conn =
|
callback.incomingConnectionCreated(wrapSocket(s));
|
||||||
new DroidtoothTransportConnection(this, s);
|
|
||||||
callback.incomingConnectionCreated(conn);
|
|
||||||
if(!running) return;
|
if(!running) return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private DuplexTransportConnection wrapSocket(BluetoothSocket s) {
|
||||||
|
return new DroidtoothTransportConnection(this, s);
|
||||||
|
}
|
||||||
|
|
||||||
public void stop() {
|
public void stop() {
|
||||||
running = false;
|
running = false;
|
||||||
// Disable Bluetooth if we enabled it at startup
|
// Disable Bluetooth if we enabled it at startup
|
||||||
@@ -277,11 +279,8 @@ class DroidtoothPlugin implements DuplexPlugin {
|
|||||||
public void run() {
|
public void run() {
|
||||||
if(!running) return;
|
if(!running) return;
|
||||||
BluetoothSocket s = connect(address, uuid);
|
BluetoothSocket s = connect(address, uuid);
|
||||||
if(s != null) {
|
if(s != null)
|
||||||
callback.outgoingConnectionCreated(c,
|
callback.outgoingConnectionCreated(c, wrapSocket(s));
|
||||||
new DroidtoothTransportConnection(
|
|
||||||
DroidtoothPlugin.this, s));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -343,7 +342,7 @@ class DroidtoothPlugin implements DuplexPlugin {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public DuplexTransportConnection sendInvitation(PseudoRandom r,
|
public DuplexTransportConnection createInvitationConnection(PseudoRandom r,
|
||||||
long timeout) {
|
long timeout) {
|
||||||
if(!running) return null;
|
if(!running) return null;
|
||||||
// Use the invitation codes to generate the UUID
|
// Use the invitation codes to generate the UUID
|
||||||
@@ -379,12 +378,6 @@ class DroidtoothPlugin implements DuplexPlugin {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public DuplexTransportConnection acceptInvitation(PseudoRandom r,
|
|
||||||
long timeout) {
|
|
||||||
// FIXME
|
|
||||||
return sendInvitation(r, timeout);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static class BluetoothStateReceiver extends BroadcastReceiver {
|
private static class BluetoothStateReceiver extends BroadcastReceiver {
|
||||||
|
|
||||||
private final CountDownLatch finished = new CountDownLatch(1);
|
private final CountDownLatch finished = new CountDownLatch(1);
|
||||||
|
|||||||
@@ -219,12 +219,7 @@ class ModemPlugin implements DuplexPlugin, Modem.Callback {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public DuplexTransportConnection sendInvitation(PseudoRandom r,
|
public DuplexTransportConnection createInvitationConnection(PseudoRandom r,
|
||||||
long timeout) {
|
|
||||||
throw new UnsupportedOperationException();
|
|
||||||
}
|
|
||||||
|
|
||||||
public DuplexTransportConnection acceptInvitation(PseudoRandom r,
|
|
||||||
long timeout) {
|
long timeout) {
|
||||||
throw new UnsupportedOperationException();
|
throw new UnsupportedOperationException();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ class DroidLanTcpPlugin extends LanTcpPlugin {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public DuplexTransportConnection sendInvitation(PseudoRandom r,
|
public DuplexTransportConnection createInvitationConnection(PseudoRandom r,
|
||||||
long timeout) {
|
long timeout) {
|
||||||
WifiManager wifi =
|
WifiManager wifi =
|
||||||
(WifiManager) appContext.getSystemService(WIFI_SERVICE);
|
(WifiManager) appContext.getSystemService(WIFI_SERVICE);
|
||||||
@@ -33,22 +33,7 @@ class DroidLanTcpPlugin extends LanTcpPlugin {
|
|||||||
MulticastLock lock = wifi.createMulticastLock("invitation");
|
MulticastLock lock = wifi.createMulticastLock("invitation");
|
||||||
lock.acquire();
|
lock.acquire();
|
||||||
try {
|
try {
|
||||||
return super.sendInvitation(r, timeout);
|
return super.createInvitationConnection(r, timeout);
|
||||||
} finally {
|
|
||||||
lock.release();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public DuplexTransportConnection acceptInvitation(PseudoRandom r,
|
|
||||||
long timeout) {
|
|
||||||
WifiManager wifi =
|
|
||||||
(WifiManager) appContext.getSystemService(WIFI_SERVICE);
|
|
||||||
if(wifi == null || !wifi.isWifiEnabled()) return null;
|
|
||||||
MulticastLock lock = wifi.createMulticastLock("invitation");
|
|
||||||
lock.acquire();
|
|
||||||
try {
|
|
||||||
return super.acceptInvitation(r, timeout);
|
|
||||||
} finally {
|
} finally {
|
||||||
lock.release();
|
lock.release();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -119,7 +119,7 @@ class LanTcpPlugin extends TcpPlugin {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public DuplexTransportConnection sendInvitation(PseudoRandom r,
|
public DuplexTransportConnection createInvitationConnection(PseudoRandom r,
|
||||||
long timeout) {
|
long timeout) {
|
||||||
if(!running) return null;
|
if(!running) return null;
|
||||||
// Use the invitation codes to generate the group address and port
|
// Use the invitation codes to generate the group address and port
|
||||||
@@ -244,12 +244,6 @@ class LanTcpPlugin extends TcpPlugin {
|
|||||||
ms.close();
|
ms.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
public DuplexTransportConnection acceptInvitation(PseudoRandom r,
|
|
||||||
long timeout) {
|
|
||||||
// FIXME
|
|
||||||
return sendInvitation(r, timeout);
|
|
||||||
}
|
|
||||||
|
|
||||||
private class MulticastListenerThread extends Thread {
|
private class MulticastListenerThread extends Thread {
|
||||||
|
|
||||||
private final LatchedReference<Socket> socketLatch;
|
private final LatchedReference<Socket> socketLatch;
|
||||||
|
|||||||
@@ -125,12 +125,7 @@ class WanTcpPlugin extends TcpPlugin {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public DuplexTransportConnection sendInvitation(PseudoRandom r,
|
public DuplexTransportConnection createInvitationConnection(PseudoRandom r,
|
||||||
long timeout) {
|
|
||||||
throw new UnsupportedOperationException();
|
|
||||||
}
|
|
||||||
|
|
||||||
public DuplexTransportConnection acceptInvitation(PseudoRandom r,
|
|
||||||
long timeout) {
|
long timeout) {
|
||||||
throw new UnsupportedOperationException();
|
throw new UnsupportedOperationException();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -513,12 +513,7 @@ class TorPlugin implements DuplexPlugin, EventHandler {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public DuplexTransportConnection sendInvitation(PseudoRandom r,
|
public DuplexTransportConnection createInvitationConnection(PseudoRandom r,
|
||||||
long timeout) {
|
|
||||||
throw new UnsupportedOperationException();
|
|
||||||
}
|
|
||||||
|
|
||||||
public DuplexTransportConnection acceptInvitation(PseudoRandom r,
|
|
||||||
long timeout) {
|
long timeout) {
|
||||||
throw new UnsupportedOperationException();
|
throw new UnsupportedOperationException();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import java.util.Map;
|
|||||||
import net.sf.briar.api.ContactId;
|
import net.sf.briar.api.ContactId;
|
||||||
import net.sf.briar.api.TransportConfig;
|
import net.sf.briar.api.TransportConfig;
|
||||||
import net.sf.briar.api.TransportProperties;
|
import net.sf.briar.api.TransportProperties;
|
||||||
|
import net.sf.briar.api.crypto.PseudoRandom;
|
||||||
import net.sf.briar.api.plugins.duplex.DuplexPluginCallback;
|
import net.sf.briar.api.plugins.duplex.DuplexPluginCallback;
|
||||||
import net.sf.briar.api.plugins.duplex.DuplexTransportConnection;
|
import net.sf.briar.api.plugins.duplex.DuplexTransportConnection;
|
||||||
|
|
||||||
@@ -35,23 +36,13 @@ public abstract class DuplexClientTest extends DuplexTest {
|
|||||||
receiveChallengeSendResponse(d);
|
receiveChallengeSendResponse(d);
|
||||||
}
|
}
|
||||||
if(!plugin.supportsInvitations()) {
|
if(!plugin.supportsInvitations()) {
|
||||||
System.out.println("Skipping invitation tests");
|
System.out.println("Skipping invitation test");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// Try to send an invitation
|
// Try to create an invitation connection
|
||||||
System.out.println("Sending invitation");
|
System.out.println("Creating invitation connection");
|
||||||
d = plugin.sendInvitation(getPseudoRandom(123), CONNECTION_TIMEOUT);
|
PseudoRandom r = getPseudoRandom(123);
|
||||||
if(d == null) {
|
d = plugin.createInvitationConnection(r, CONNECTION_TIMEOUT);
|
||||||
System.out.println("Connection failed");
|
|
||||||
return;
|
|
||||||
} else {
|
|
||||||
System.out.println("Connection created");
|
|
||||||
receiveChallengeSendResponse(d);
|
|
||||||
}
|
|
||||||
// Try to accept an invitation
|
|
||||||
System.out.println("Accepting invitation");
|
|
||||||
d = plugin.acceptInvitation(getPseudoRandom(456),
|
|
||||||
CONNECTION_TIMEOUT);
|
|
||||||
if(d == null) {
|
if(d == null) {
|
||||||
System.out.println("Connection failed");
|
System.out.println("Connection failed");
|
||||||
return;
|
return;
|
||||||
|
|||||||
@@ -33,23 +33,13 @@ public abstract class DuplexServerTest extends DuplexTest {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if(!plugin.supportsInvitations()) {
|
if(!plugin.supportsInvitations()) {
|
||||||
System.out.println("Skipping invitation tests");
|
System.out.println("Skipping invitation test");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// Try to accept an invitation
|
// Try to create an invitation connection
|
||||||
System.out.println("Accepting invitation");
|
System.out.println("Creating invitation connection");
|
||||||
DuplexTransportConnection d = plugin.acceptInvitation(
|
DuplexTransportConnection d = plugin.createInvitationConnection(
|
||||||
getPseudoRandom(123), CONNECTION_TIMEOUT);
|
getPseudoRandom(123), CONNECTION_TIMEOUT);
|
||||||
if(d == null) {
|
|
||||||
System.out.println("Connection failed");
|
|
||||||
return;
|
|
||||||
} else {
|
|
||||||
System.out.println("Connection created");
|
|
||||||
sendChallengeReceiveResponse(d);
|
|
||||||
}
|
|
||||||
// Try to send an invitation
|
|
||||||
System.out.println("Sending invitation");
|
|
||||||
d = plugin.sendInvitation(getPseudoRandom(456), CONNECTION_TIMEOUT);
|
|
||||||
if(d == null) {
|
if(d == null) {
|
||||||
System.out.println("Connection failed");
|
System.out.println("Connection failed");
|
||||||
return;
|
return;
|
||||||
|
|||||||
Reference in New Issue
Block a user