mirror of
https://github.com/moonlight-stream/moonlight-android.git
synced 2026-02-16 02:20:55 +00:00
@@ -22,6 +22,7 @@ import com.limelight.nvstream.NvConnectionListener;
|
||||
import com.limelight.nvstream.StreamConfiguration;
|
||||
import com.limelight.nvstream.http.ComputerDetails;
|
||||
import com.limelight.nvstream.http.NvApp;
|
||||
import com.limelight.nvstream.http.NvHTTP;
|
||||
import com.limelight.nvstream.input.KeyboardPacket;
|
||||
import com.limelight.nvstream.input.MouseButtonPacket;
|
||||
import com.limelight.nvstream.jni.MoonBridge;
|
||||
@@ -166,6 +167,7 @@ public class Game extends Activity implements SurfaceHolder.Callback,
|
||||
};
|
||||
|
||||
public static final String EXTRA_HOST = "Host";
|
||||
public static final String EXTRA_PORT = "Port";
|
||||
public static final String EXTRA_APP_NAME = "AppName";
|
||||
public static final String EXTRA_APP_ID = "AppId";
|
||||
public static final String EXTRA_UNIQUEID = "UniqueId";
|
||||
@@ -311,6 +313,7 @@ public class Game extends Activity implements SurfaceHolder.Callback,
|
||||
pcName = Game.this.getIntent().getStringExtra(EXTRA_PC_NAME);
|
||||
|
||||
String host = Game.this.getIntent().getStringExtra(EXTRA_HOST);
|
||||
int port = Game.this.getIntent().getIntExtra(EXTRA_PORT, NvHTTP.DEFAULT_HTTP_PORT);
|
||||
int appId = Game.this.getIntent().getIntExtra(EXTRA_APP_ID, StreamConfiguration.INVALID_APP_ID);
|
||||
String uniqueId = Game.this.getIntent().getStringExtra(EXTRA_UNIQUEID);
|
||||
String uuid = Game.this.getIntent().getStringExtra(EXTRA_PC_UUID);
|
||||
@@ -472,7 +475,11 @@ public class Game extends Activity implements SurfaceHolder.Callback,
|
||||
.build();
|
||||
|
||||
// Initialize the connection
|
||||
conn = new NvConnection(getApplicationContext(), host, uniqueId, config, PlatformBinding.getCryptoProvider(this), serverCert, needsInputBatching);
|
||||
conn = new NvConnection(getApplicationContext(),
|
||||
new ComputerDetails.AddressTuple(host, port),
|
||||
uniqueId, config,
|
||||
PlatformBinding.getCryptoProvider(this), serverCert,
|
||||
needsInputBatching);
|
||||
controllerHandler = new ControllerHandler(this, conn, this, prefConfig);
|
||||
keyboardTranslator = new KeyboardTranslator();
|
||||
|
||||
|
||||
@@ -400,8 +400,7 @@ public class PcView extends Activity implements AdapterFragmentCallbacks {
|
||||
stopComputerUpdates(true);
|
||||
|
||||
httpConn = new NvHTTP(ServerHelper.getCurrentAddressFromComputer(computer),
|
||||
managerBinder.getUniqueId(),
|
||||
computer.serverCert,
|
||||
computer.httpsPort, managerBinder.getUniqueId(), computer.serverCert,
|
||||
PlatformBinding.getCryptoProvider(PcView.this));
|
||||
if (httpConn.getPairState() == PairState.PAIRED) {
|
||||
// Don't display any toast, but open the app list
|
||||
@@ -534,8 +533,7 @@ public class PcView extends Activity implements AdapterFragmentCallbacks {
|
||||
String message;
|
||||
try {
|
||||
httpConn = new NvHTTP(ServerHelper.getCurrentAddressFromComputer(computer),
|
||||
managerBinder.getUniqueId(),
|
||||
computer.serverCert,
|
||||
computer.httpsPort, managerBinder.getUniqueId(), computer.serverCert,
|
||||
PlatformBinding.getCryptoProvider(PcView.this));
|
||||
if (httpConn.getPairState() == PairingManager.PairState.PAIRED) {
|
||||
httpConn.unpair();
|
||||
|
||||
@@ -10,6 +10,7 @@ import java.util.List;
|
||||
import java.util.Locale;
|
||||
|
||||
import com.limelight.nvstream.http.ComputerDetails;
|
||||
import com.limelight.nvstream.http.NvHTTP;
|
||||
|
||||
import android.content.ContentValues;
|
||||
import android.content.Context;
|
||||
@@ -27,6 +28,7 @@ public class ComputerDatabaseManager {
|
||||
private static final String SERVER_CERT_COLUMN_NAME = "ServerCert";
|
||||
|
||||
private static final char ADDRESS_DELIMITER = ';';
|
||||
private static final char PORT_DELIMITER = '_';
|
||||
|
||||
private SQLiteDatabase computerDb;
|
||||
|
||||
@@ -74,10 +76,10 @@ public class ComputerDatabaseManager {
|
||||
values.put(COMPUTER_NAME_COLUMN_NAME, details.name);
|
||||
|
||||
StringBuilder addresses = new StringBuilder();
|
||||
addresses.append(details.localAddress != null ? details.localAddress : "");
|
||||
addresses.append(ADDRESS_DELIMITER).append(details.remoteAddress != null ? details.remoteAddress : "");
|
||||
addresses.append(ADDRESS_DELIMITER).append(details.manualAddress != null ? details.manualAddress : "");
|
||||
addresses.append(ADDRESS_DELIMITER).append(details.ipv6Address != null ? details.ipv6Address : "");
|
||||
addresses.append(details.localAddress != null ? splitTupleToAddress(details.localAddress) : "");
|
||||
addresses.append(ADDRESS_DELIMITER).append(details.remoteAddress != null ? splitTupleToAddress(details.remoteAddress) : "");
|
||||
addresses.append(ADDRESS_DELIMITER).append(details.manualAddress != null ? splitTupleToAddress(details.manualAddress) : "");
|
||||
addresses.append(ADDRESS_DELIMITER).append(details.ipv6Address != null ? splitTupleToAddress(details.ipv6Address) : "");
|
||||
|
||||
values.put(ADDRESSES_COLUMN_NAME, addresses.toString());
|
||||
values.put(MAC_ADDRESS_COLUMN_NAME, details.macAddress);
|
||||
@@ -103,6 +105,24 @@ public class ComputerDatabaseManager {
|
||||
return input;
|
||||
}
|
||||
|
||||
private static ComputerDetails.AddressTuple splitAddressToTuple(String input) {
|
||||
if (input == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
String[] parts = input.split(""+PORT_DELIMITER, -1);
|
||||
if (parts.length == 1) {
|
||||
return new ComputerDetails.AddressTuple(parts[0], NvHTTP.DEFAULT_HTTP_PORT);
|
||||
}
|
||||
else {
|
||||
return new ComputerDetails.AddressTuple(parts[0], Integer.parseInt(parts[1]));
|
||||
}
|
||||
}
|
||||
|
||||
private static String splitTupleToAddress(ComputerDetails.AddressTuple tuple) {
|
||||
return tuple.address+PORT_DELIMITER+tuple.port;
|
||||
}
|
||||
|
||||
private ComputerDetails getComputerFromCursor(Cursor c) {
|
||||
ComputerDetails details = new ComputerDetails();
|
||||
|
||||
@@ -111,10 +131,18 @@ public class ComputerDatabaseManager {
|
||||
|
||||
String[] addresses = c.getString(2).split(""+ADDRESS_DELIMITER, -1);
|
||||
|
||||
details.localAddress = readNonEmptyString(addresses[0]);
|
||||
details.remoteAddress = readNonEmptyString(addresses[1]);
|
||||
details.manualAddress = readNonEmptyString(addresses[2]);
|
||||
details.ipv6Address = readNonEmptyString(addresses[3]);
|
||||
details.localAddress = splitAddressToTuple(readNonEmptyString(addresses[0]));
|
||||
details.remoteAddress = splitAddressToTuple(readNonEmptyString(addresses[1]));
|
||||
details.manualAddress = splitAddressToTuple(readNonEmptyString(addresses[2]));
|
||||
details.ipv6Address = splitAddressToTuple(readNonEmptyString(addresses[3]));
|
||||
|
||||
// External port is persisted in the remote address field
|
||||
if (details.remoteAddress != null) {
|
||||
details.externalPort = details.remoteAddress.port;
|
||||
}
|
||||
else {
|
||||
details.externalPort = NvHTTP.DEFAULT_HTTP_PORT;
|
||||
}
|
||||
|
||||
details.macAddress = c.getString(3);
|
||||
|
||||
|
||||
@@ -139,7 +139,7 @@ public class ComputerManagerService extends Service {
|
||||
// then use STUN to populate the external address field if
|
||||
// it's not set already.
|
||||
if (details.remoteAddress == null) {
|
||||
InetAddress addr = InetAddress.getByName(details.activeAddress);
|
||||
InetAddress addr = InetAddress.getByName(details.activeAddress.address);
|
||||
if (addr.isSiteLocalAddress()) {
|
||||
populateExternalAddress(details);
|
||||
}
|
||||
@@ -369,7 +369,12 @@ public class ComputerManagerService extends Service {
|
||||
|
||||
// Perform the STUN request if we're not on a VPN or if we bound to a network
|
||||
if (!activeNetworkIsVpn || boundToNetwork) {
|
||||
details.remoteAddress = NvConnection.findExternalAddressForMdns("stun.moonlight-stream.org", 3478);
|
||||
String stunResolvedAddress = NvConnection.findExternalAddressForMdns("stun.moonlight-stream.org", 3478);
|
||||
if (stunResolvedAddress != null) {
|
||||
// We don't know for sure what the external port is, so we will have to guess.
|
||||
// When we contact the PC (if we haven't already), it will update the port.
|
||||
details.remoteAddress = new ComputerDetails.AddressTuple(stunResolvedAddress, details.guessExternalPort());
|
||||
}
|
||||
}
|
||||
|
||||
// Unbind from the network
|
||||
@@ -396,7 +401,7 @@ public class ComputerManagerService extends Service {
|
||||
|
||||
// Populate the computer template with mDNS info
|
||||
if (computer.getLocalAddress() != null) {
|
||||
details.localAddress = computer.getLocalAddress().getHostAddress();
|
||||
details.localAddress = new ComputerDetails.AddressTuple(computer.getLocalAddress().getHostAddress(), computer.getPort());
|
||||
|
||||
// Since we're on the same network, we can use STUN to find
|
||||
// our WAN address, which is also very likely the WAN address
|
||||
@@ -406,7 +411,7 @@ public class ComputerManagerService extends Service {
|
||||
}
|
||||
}
|
||||
if (computer.getIpv6Address() != null) {
|
||||
details.ipv6Address = computer.getIpv6Address().getHostAddress();
|
||||
details.ipv6Address = new ComputerDetails.AddressTuple(computer.getIpv6Address().getHostAddress(), computer.getPort());
|
||||
}
|
||||
|
||||
try {
|
||||
@@ -543,9 +548,9 @@ public class ComputerManagerService extends Service {
|
||||
}
|
||||
}
|
||||
|
||||
private ComputerDetails tryPollIp(ComputerDetails details, String address) {
|
||||
private ComputerDetails tryPollIp(ComputerDetails details, ComputerDetails.AddressTuple address) {
|
||||
try {
|
||||
NvHTTP http = new NvHTTP(address, idManager.getUniqueId(), details.serverCert,
|
||||
NvHTTP http = new NvHTTP(address, 0, idManager.getUniqueId(), details.serverCert,
|
||||
PlatformBinding.getCryptoProvider(ComputerManagerService.this));
|
||||
|
||||
ComputerDetails newDetails = http.getComputerDetails();
|
||||
@@ -572,14 +577,14 @@ public class ComputerManagerService extends Service {
|
||||
}
|
||||
|
||||
private static class ParallelPollTuple {
|
||||
public String address;
|
||||
public ComputerDetails.AddressTuple address;
|
||||
public ComputerDetails existingDetails;
|
||||
|
||||
public boolean complete;
|
||||
public Thread pollingThread;
|
||||
public ComputerDetails returnedDetails;
|
||||
|
||||
public ParallelPollTuple(String address, ComputerDetails existingDetails) {
|
||||
public ParallelPollTuple(ComputerDetails.AddressTuple address, ComputerDetails existingDetails) {
|
||||
this.address = address;
|
||||
this.existingDetails = existingDetails;
|
||||
}
|
||||
@@ -591,7 +596,7 @@ public class ComputerManagerService extends Service {
|
||||
}
|
||||
}
|
||||
|
||||
private void startParallelPollThread(ParallelPollTuple tuple, HashSet<String> uniqueAddresses) {
|
||||
private void startParallelPollThread(ParallelPollTuple tuple, HashSet<ComputerDetails.AddressTuple> uniqueAddresses) {
|
||||
// Don't bother starting a polling thread for an address that doesn't exist
|
||||
// or if the address has already been polled with an earlier tuple
|
||||
if (tuple.address == null || !uniqueAddresses.add(tuple.address)) {
|
||||
@@ -625,7 +630,7 @@ public class ComputerManagerService extends Service {
|
||||
|
||||
// These must be started in order of precedence for the deduplication algorithm
|
||||
// to result in the correct behavior.
|
||||
HashSet<String> uniqueAddresses = new HashSet<>();
|
||||
HashSet<ComputerDetails.AddressTuple> uniqueAddresses = new HashSet<>();
|
||||
startParallelPollThread(localInfo, uniqueAddresses);
|
||||
startParallelPollThread(manualInfo, uniqueAddresses);
|
||||
startParallelPollThread(remoteInfo, uniqueAddresses);
|
||||
@@ -821,7 +826,7 @@ public class ComputerManagerService extends Service {
|
||||
PollingTuple tuple = getPollingTuple(computer);
|
||||
|
||||
try {
|
||||
NvHTTP http = new NvHTTP(ServerHelper.getCurrentAddressFromComputer(computer), idManager.getUniqueId(),
|
||||
NvHTTP http = new NvHTTP(ServerHelper.getCurrentAddressFromComputer(computer), computer.httpsPort, idManager.getUniqueId(),
|
||||
computer.serverCert, PlatformBinding.getCryptoProvider(ComputerManagerService.this));
|
||||
|
||||
String appList;
|
||||
|
||||
@@ -7,6 +7,7 @@ import android.database.sqlite.SQLiteException;
|
||||
|
||||
import com.limelight.LimeLog;
|
||||
import com.limelight.nvstream.http.ComputerDetails;
|
||||
import com.limelight.nvstream.http.NvHTTP;
|
||||
|
||||
import java.net.InetAddress;
|
||||
import java.net.UnknownHostException;
|
||||
@@ -30,26 +31,26 @@ public class LegacyDatabaseReader {
|
||||
// too. To disambiguate, we'll need to prefix them with a string
|
||||
// greater than the allowable IP address length.
|
||||
try {
|
||||
details.localAddress = InetAddress.getByAddress(c.getBlob(2)).getHostAddress();
|
||||
details.localAddress = new ComputerDetails.AddressTuple(InetAddress.getByAddress(c.getBlob(2)).getHostAddress(), NvHTTP.DEFAULT_HTTP_PORT);
|
||||
LimeLog.warning("DB: Legacy local address for " + details.name);
|
||||
} catch (UnknownHostException e) {
|
||||
// This is probably a hostname/address with the prefix string
|
||||
String stringData = c.getString(2);
|
||||
if (stringData.startsWith(ADDRESS_PREFIX)) {
|
||||
details.localAddress = c.getString(2).substring(ADDRESS_PREFIX.length());
|
||||
details.localAddress = new ComputerDetails.AddressTuple(c.getString(2).substring(ADDRESS_PREFIX.length()), NvHTTP.DEFAULT_HTTP_PORT);
|
||||
} else {
|
||||
LimeLog.severe("DB: Corrupted local address for " + details.name);
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
details.remoteAddress = InetAddress.getByAddress(c.getBlob(3)).getHostAddress();
|
||||
details.remoteAddress = new ComputerDetails.AddressTuple(InetAddress.getByAddress(c.getBlob(3)).getHostAddress(), NvHTTP.DEFAULT_HTTP_PORT);
|
||||
LimeLog.warning("DB: Legacy remote address for " + details.name);
|
||||
} catch (UnknownHostException e) {
|
||||
// This is probably a hostname/address with the prefix string
|
||||
String stringData = c.getString(3);
|
||||
if (stringData.startsWith(ADDRESS_PREFIX)) {
|
||||
details.remoteAddress = c.getString(3).substring(ADDRESS_PREFIX.length());
|
||||
details.remoteAddress = new ComputerDetails.AddressTuple(c.getString(3).substring(ADDRESS_PREFIX.length()), NvHTTP.DEFAULT_HTTP_PORT);
|
||||
} else {
|
||||
LimeLog.severe("DB: Corrupted remote address for " + details.name);
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ import android.database.sqlite.SQLiteDatabase;
|
||||
import android.database.sqlite.SQLiteException;
|
||||
|
||||
import com.limelight.nvstream.http.ComputerDetails;
|
||||
import com.limelight.nvstream.http.NvHTTP;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.security.cert.CertificateException;
|
||||
@@ -23,9 +24,9 @@ public class LegacyDatabaseReader2 {
|
||||
|
||||
details.uuid = c.getString(0);
|
||||
details.name = c.getString(1);
|
||||
details.localAddress = c.getString(2);
|
||||
details.remoteAddress = c.getString(3);
|
||||
details.manualAddress = c.getString(4);
|
||||
details.localAddress = new ComputerDetails.AddressTuple(c.getString(2), NvHTTP.DEFAULT_HTTP_PORT);
|
||||
details.remoteAddress = new ComputerDetails.AddressTuple(c.getString(3), NvHTTP.DEFAULT_HTTP_PORT);
|
||||
details.manualAddress = new ComputerDetails.AddressTuple(c.getString(4), NvHTTP.DEFAULT_HTTP_PORT);
|
||||
details.macAddress = c.getString(5);
|
||||
|
||||
// This column wasn't always present in the old schema
|
||||
|
||||
@@ -22,8 +22,9 @@ public class NetworkAssetLoader {
|
||||
public InputStream getBitmapStream(CachedAppAssetLoader.LoaderTuple tuple) {
|
||||
InputStream in = null;
|
||||
try {
|
||||
NvHTTP http = new NvHTTP(ServerHelper.getCurrentAddressFromComputer(tuple.computer), uniqueId,
|
||||
tuple.computer.serverCert, PlatformBinding.getCryptoProvider(context));
|
||||
NvHTTP http = new NvHTTP(ServerHelper.getCurrentAddressFromComputer(tuple.computer),
|
||||
tuple.computer.httpsPort, uniqueId, tuple.computer.serverCert,
|
||||
PlatformBinding.getCryptoProvider(context));
|
||||
in = http.getBoxArt(tuple.app);
|
||||
} catch (IOException ignored) {}
|
||||
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
package com.limelight.nvstream;
|
||||
|
||||
import com.limelight.nvstream.http.ComputerDetails;
|
||||
|
||||
import java.security.cert.X509Certificate;
|
||||
|
||||
import javax.crypto.SecretKey;
|
||||
|
||||
public class ConnectionContext {
|
||||
public String serverAddress;
|
||||
public ComputerDetails.AddressTuple serverAddress;
|
||||
public X509Certificate serverCert;
|
||||
public StreamConfiguration streamConfig;
|
||||
public NvConnectionListener connListener;
|
||||
|
||||
@@ -32,6 +32,7 @@ import org.xmlpull.v1.XmlPullParserException;
|
||||
import com.limelight.LimeLog;
|
||||
import com.limelight.nvstream.av.audio.AudioRenderer;
|
||||
import com.limelight.nvstream.av.video.VideoDecoderRenderer;
|
||||
import com.limelight.nvstream.http.ComputerDetails;
|
||||
import com.limelight.nvstream.http.GfeHttpResponseException;
|
||||
import com.limelight.nvstream.http.LimelightCryptoProvider;
|
||||
import com.limelight.nvstream.http.NvApp;
|
||||
@@ -42,7 +43,7 @@ import com.limelight.nvstream.jni.MoonBridge;
|
||||
|
||||
public class NvConnection {
|
||||
// Context parameters
|
||||
private String host;
|
||||
private ComputerDetails.AddressTuple host;
|
||||
private LimelightCryptoProvider cryptoProvider;
|
||||
private String uniqueId;
|
||||
private ConnectionContext context;
|
||||
@@ -57,7 +58,7 @@ public class NvConnection {
|
||||
private short relMouseX, relMouseY, relMouseWidth, relMouseHeight;
|
||||
private short absMouseX, absMouseY, absMouseWidth, absMouseHeight;
|
||||
|
||||
public NvConnection(Context appContext, String host, String uniqueId, StreamConfiguration config, LimelightCryptoProvider cryptoProvider, X509Certificate serverCert, boolean batchMouseInput)
|
||||
public NvConnection(Context appContext, ComputerDetails.AddressTuple host, String uniqueId, StreamConfiguration config, LimelightCryptoProvider cryptoProvider, X509Certificate serverCert, boolean batchMouseInput)
|
||||
{
|
||||
this.appContext = appContext;
|
||||
this.host = host;
|
||||
@@ -134,11 +135,11 @@ public class NvConnection {
|
||||
|
||||
private InetAddress resolveServerAddress() throws IOException {
|
||||
// Try to find an address that works for this host
|
||||
InetAddress[] addrs = InetAddress.getAllByName(context.serverAddress);
|
||||
InetAddress[] addrs = InetAddress.getAllByName(context.serverAddress.address);
|
||||
for (InetAddress addr : addrs) {
|
||||
try (Socket s = new Socket()) {
|
||||
s.setSoLinger(true, 0);
|
||||
s.connect(new InetSocketAddress(addr, 47989), 1000);
|
||||
s.connect(new InetSocketAddress(addr, context.serverAddress.port), 1000);
|
||||
return addr;
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
@@ -252,7 +253,7 @@ public class NvConnection {
|
||||
|
||||
private boolean startApp() throws XmlPullParserException, IOException
|
||||
{
|
||||
NvHTTP h = new NvHTTP(context.serverAddress, uniqueId, context.serverCert, cryptoProvider);
|
||||
NvHTTP h = new NvHTTP(context.serverAddress, 0, uniqueId, context.serverCert, cryptoProvider);
|
||||
|
||||
String serverInfo = h.getServerInfo();
|
||||
|
||||
@@ -452,7 +453,7 @@ public class NvConnection {
|
||||
// we must not invoke that functionality in parallel.
|
||||
synchronized (MoonBridge.class) {
|
||||
MoonBridge.setupBridge(videoDecoderRenderer, audioRenderer, connectionListener);
|
||||
int ret = MoonBridge.startConnection(context.serverAddress,
|
||||
int ret = MoonBridge.startConnection(context.serverAddress.address,
|
||||
context.serverAppVersion, context.serverGfeVersion, context.rtspSessionUrl,
|
||||
context.negotiatedWidth, context.negotiatedHeight,
|
||||
context.streamConfig.getRefreshRate(), context.streamConfig.getBitrate(),
|
||||
|
||||
@@ -8,19 +8,57 @@ public class ComputerDetails {
|
||||
ONLINE, OFFLINE, UNKNOWN
|
||||
}
|
||||
|
||||
public static class AddressTuple {
|
||||
public String address;
|
||||
public int port;
|
||||
|
||||
public AddressTuple(String address, int port) {
|
||||
if (address == null) {
|
||||
throw new IllegalArgumentException("Address cannot be null");
|
||||
}
|
||||
if (port <= 0) {
|
||||
throw new IllegalArgumentException("Invalid port");
|
||||
}
|
||||
|
||||
this.address = address;
|
||||
this.port = port;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return address.hashCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (!(obj instanceof AddressTuple)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
AddressTuple that = (AddressTuple) obj;
|
||||
return address.equals(that.address) && port == that.port;
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
return address + ":" + port;
|
||||
}
|
||||
}
|
||||
|
||||
// Persistent attributes
|
||||
public String uuid;
|
||||
public String name;
|
||||
public String localAddress;
|
||||
public String remoteAddress;
|
||||
public String manualAddress;
|
||||
public String ipv6Address;
|
||||
public AddressTuple localAddress;
|
||||
public AddressTuple remoteAddress;
|
||||
public AddressTuple manualAddress;
|
||||
public AddressTuple ipv6Address;
|
||||
public String macAddress;
|
||||
public X509Certificate serverCert;
|
||||
|
||||
// Transient attributes
|
||||
public State state;
|
||||
public String activeAddress;
|
||||
public AddressTuple activeAddress;
|
||||
public int httpsPort;
|
||||
public int externalPort;
|
||||
public PairingManager.PairState pairState;
|
||||
public int runningGameId;
|
||||
public String rawAppList;
|
||||
@@ -35,6 +73,27 @@ public class ComputerDetails {
|
||||
update(details);
|
||||
}
|
||||
|
||||
public int guessExternalPort() {
|
||||
if (externalPort != 0) {
|
||||
return externalPort;
|
||||
}
|
||||
else if (remoteAddress != null) {
|
||||
return remoteAddress.port;
|
||||
}
|
||||
else if (activeAddress != null) {
|
||||
return activeAddress.port;
|
||||
}
|
||||
else if (ipv6Address != null) {
|
||||
return ipv6Address.port;
|
||||
}
|
||||
else if (localAddress != null) {
|
||||
return localAddress.port;
|
||||
}
|
||||
else {
|
||||
return NvHTTP.DEFAULT_HTTP_PORT;
|
||||
}
|
||||
}
|
||||
|
||||
public void update(ComputerDetails details) {
|
||||
this.state = details.state;
|
||||
this.name = details.name;
|
||||
@@ -43,11 +102,16 @@ public class ComputerDetails {
|
||||
this.activeAddress = details.activeAddress;
|
||||
}
|
||||
// We can get IPv4 loopback addresses with GS IPv6 Forwarder
|
||||
if (details.localAddress != null && !details.localAddress.startsWith("127.")) {
|
||||
if (details.localAddress != null && !details.localAddress.address.startsWith("127.")) {
|
||||
this.localAddress = details.localAddress;
|
||||
}
|
||||
if (details.remoteAddress != null) {
|
||||
this.remoteAddress = details.remoteAddress;
|
||||
|
||||
// If the port is unknown, populate it from the external port field
|
||||
if (this.remoteAddress.port == 0) {
|
||||
this.remoteAddress.port = externalPort;
|
||||
}
|
||||
}
|
||||
if (details.manualAddress != null) {
|
||||
this.manualAddress = details.manualAddress;
|
||||
@@ -61,6 +125,8 @@ public class ComputerDetails {
|
||||
if (details.serverCert != null) {
|
||||
this.serverCert = details.serverCert;
|
||||
}
|
||||
this.externalPort = details.externalPort;
|
||||
this.httpsPort = details.httpsPort;
|
||||
this.pairState = details.pairState;
|
||||
this.runningGameId = details.runningGameId;
|
||||
this.rawAppList = details.rawAppList;
|
||||
@@ -80,6 +146,7 @@ public class ComputerDetails {
|
||||
str.append("MAC Address: ").append(macAddress).append("\n");
|
||||
str.append("Pair State: ").append(pairState).append("\n");
|
||||
str.append("Running Game ID: ").append(runningGameId).append("\n");
|
||||
str.append("HTTPS Port: ").append(httpsPort).append("\n");
|
||||
return str.toString();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -63,7 +63,7 @@ public class NvHTTP {
|
||||
private PairingManager pm;
|
||||
|
||||
private static final int DEFAULT_HTTPS_PORT = 47984;
|
||||
public static final int HTTP_PORT = 47989;
|
||||
public static final int DEFAULT_HTTP_PORT = 47989;
|
||||
public static final int CONNECTION_TIMEOUT = 3000;
|
||||
public static final int READ_TIMEOUT = 5000;
|
||||
|
||||
@@ -190,7 +190,7 @@ public class NvHTTP {
|
||||
return new HttpUrl.Builder().scheme("https").host(baseUrlHttp.host()).port(httpsPort).build();
|
||||
}
|
||||
|
||||
public NvHTTP(String address, String uniqueId, X509Certificate serverCert, LimelightCryptoProvider cryptoProvider) throws IOException {
|
||||
public NvHTTP(ComputerDetails.AddressTuple address, int httpsPort, String uniqueId, X509Certificate serverCert, LimelightCryptoProvider cryptoProvider) throws IOException {
|
||||
// Use the same UID for all Moonlight clients so we can quit games
|
||||
// started by other Moonlight clients.
|
||||
this.uniqueId = "0123456789ABCDEF";
|
||||
@@ -199,11 +199,13 @@ public class NvHTTP {
|
||||
|
||||
initializeHttpState(cryptoProvider);
|
||||
|
||||
this.httpsPort = httpsPort;
|
||||
|
||||
try {
|
||||
this.baseUrlHttp = new HttpUrl.Builder()
|
||||
.scheme("http")
|
||||
.host(address)
|
||||
.port(HTTP_PORT)
|
||||
.host(address.address)
|
||||
.port(address.port)
|
||||
.build();
|
||||
} catch (IllegalArgumentException e) {
|
||||
// Encapsulate IllegalArgumentException into IOException for callers to handle more easily
|
||||
@@ -322,6 +324,14 @@ public class NvHTTP {
|
||||
return openHttpConnectionToString(baseUrlHttp, "serverinfo", true);
|
||||
}
|
||||
}
|
||||
|
||||
private static ComputerDetails.AddressTuple makeTuple(String address, int port) {
|
||||
if (address == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return new ComputerDetails.AddressTuple(address, port);
|
||||
}
|
||||
|
||||
public ComputerDetails getComputerDetails() throws IOException, XmlPullParserException {
|
||||
ComputerDetails details = new ComputerDetails();
|
||||
@@ -335,11 +345,16 @@ public class NvHTTP {
|
||||
// UUID is mandatory to determine which machine is responding
|
||||
details.uuid = getXmlString(serverInfo, "uniqueid", true);
|
||||
|
||||
details.macAddress = getXmlString(serverInfo, "mac", false);
|
||||
details.localAddress = getXmlString(serverInfo, "LocalIP", false);
|
||||
details.httpsPort = getHttpsPort(serverInfo);
|
||||
|
||||
// This is missing on on recent GFE versions
|
||||
details.remoteAddress = getXmlString(serverInfo, "ExternalIP", false);
|
||||
details.macAddress = getXmlString(serverInfo, "mac", false);
|
||||
|
||||
// FIXME: Do we want to use the current port?
|
||||
details.localAddress = makeTuple(getXmlString(serverInfo, "LocalIP", false), baseUrlHttp.port());
|
||||
|
||||
// This is missing on on recent GFE versions, but it's present on Sunshine
|
||||
details.externalPort = getExternalPort(serverInfo);
|
||||
details.remoteAddress = makeTuple(getXmlString(serverInfo, "ExternalIP", false), details.externalPort);
|
||||
|
||||
details.pairState = getPairState(serverInfo);
|
||||
details.runningGameId = getCurrentGame(serverInfo);
|
||||
@@ -543,6 +558,20 @@ public class NvHTTP {
|
||||
}
|
||||
}
|
||||
|
||||
public int getExternalPort(String serverInfo) {
|
||||
// This is an extension which is not present in GFE. It is present for Sunshine to be able
|
||||
// to support dynamic HTTP WAN ports without requiring the user to manually enter the port.
|
||||
try {
|
||||
return Integer.parseInt(getXmlString(serverInfo, "ExternalPort", true));
|
||||
} catch (XmlPullParserException e) {
|
||||
// Expected on non-Sunshine servers
|
||||
return DEFAULT_HTTP_PORT;
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
return DEFAULT_HTTP_PORT;
|
||||
}
|
||||
}
|
||||
|
||||
public NvApp getAppById(int appId) throws IOException, XmlPullParserException {
|
||||
LinkedList<NvApp> appList = getAppList();
|
||||
for (NvApp appFromList : appList) {
|
||||
|
||||
@@ -10,11 +10,53 @@ import com.limelight.LimeLog;
|
||||
import com.limelight.nvstream.http.ComputerDetails;
|
||||
|
||||
public class WakeOnLanSender {
|
||||
private static final int[] PORTS_TO_TRY = new int[] {
|
||||
// These ports will always be tried as-is.
|
||||
private static final int[] STATIC_PORTS_TO_TRY = new int[] {
|
||||
9, // Standard WOL port (privileged port)
|
||||
47998, 47999, 48000, 48002, 48010, // Ports opened by GFE
|
||||
47009, // Port opened by Moonlight Internet Hosting Tool for WoL (non-privileged port)
|
||||
};
|
||||
|
||||
// These ports will be offset by the base port number (47989) to support alternate ports.
|
||||
private static final int[] DYNAMIC_PORTS_TO_TRY = new int[] {
|
||||
47998, 47999, 48000, 48002, 48010, // Ports opened by GFE
|
||||
};
|
||||
|
||||
private static void sendPacketsForAddress(InetAddress address, int httpPort, DatagramSocket sock, byte[] payload) throws IOException {
|
||||
IOException lastException = null;
|
||||
boolean sentWolPacket = false;
|
||||
|
||||
// Try the static ports
|
||||
for (int port : STATIC_PORTS_TO_TRY) {
|
||||
try {
|
||||
DatagramPacket dp = new DatagramPacket(payload, payload.length);
|
||||
dp.setAddress(address);
|
||||
dp.setPort(port);
|
||||
sock.send(dp);
|
||||
sentWolPacket = true;
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
lastException = e;
|
||||
}
|
||||
}
|
||||
|
||||
// Try the dynamic ports
|
||||
for (int port : DYNAMIC_PORTS_TO_TRY) {
|
||||
try {
|
||||
DatagramPacket dp = new DatagramPacket(payload, payload.length);
|
||||
dp.setAddress(address);
|
||||
dp.setPort((port - 47989) + httpPort);
|
||||
sock.send(dp);
|
||||
sentWolPacket = true;
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
lastException = e;
|
||||
}
|
||||
}
|
||||
|
||||
if (!sentWolPacket) {
|
||||
throw lastException;
|
||||
}
|
||||
}
|
||||
|
||||
public static void sendWolPacket(ComputerDetails computer) throws IOException {
|
||||
byte[] payload = createWolPayload(computer);
|
||||
@@ -22,26 +64,21 @@ public class WakeOnLanSender {
|
||||
boolean sentWolPacket = false;
|
||||
|
||||
try (final DatagramSocket sock = new DatagramSocket(0)) {
|
||||
// Try all resolved remote and local addresses and IPv4 broadcast address.
|
||||
// Try all resolved remote and local addresses and broadcast addresses.
|
||||
// The broadcast address is required to avoid stale ARP cache entries
|
||||
// making the sleeping machine unreachable.
|
||||
for (String unresolvedAddress : new String[] {
|
||||
computer.localAddress, computer.remoteAddress, computer.manualAddress, computer.ipv6Address, "255.255.255.255"
|
||||
for (ComputerDetails.AddressTuple address : new ComputerDetails.AddressTuple[] {
|
||||
computer.localAddress, computer.remoteAddress,
|
||||
computer.manualAddress, computer.ipv6Address,
|
||||
}) {
|
||||
if (unresolvedAddress == null) {
|
||||
if (address == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
try {
|
||||
for (InetAddress resolvedAddress : InetAddress.getAllByName(unresolvedAddress)) {
|
||||
// Try all the ports for each resolved address
|
||||
for (int port : PORTS_TO_TRY) {
|
||||
DatagramPacket dp = new DatagramPacket(payload, payload.length);
|
||||
dp.setAddress(resolvedAddress);
|
||||
dp.setPort(port);
|
||||
sock.send(dp);
|
||||
sentWolPacket = true;
|
||||
}
|
||||
sendPacketsForAddress(InetAddress.getByName("255.255.255.255"), address.port, sock, payload);
|
||||
for (InetAddress resolvedAddress : InetAddress.getAllByName(address.address)) {
|
||||
sendPacketsForAddress(resolvedAddress, address.port, sock, payload);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
// We may have addresses that don't resolve on this subnet,
|
||||
|
||||
@@ -6,6 +6,8 @@ import java.net.InetAddress;
|
||||
import java.net.InterfaceAddress;
|
||||
import java.net.NetworkInterface;
|
||||
import java.net.SocketException;
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import java.net.UnknownHostException;
|
||||
import java.util.Collections;
|
||||
import java.util.concurrent.LinkedBlockingQueue;
|
||||
@@ -96,7 +98,7 @@ public class AddComputerManually extends Activity {
|
||||
}
|
||||
}
|
||||
|
||||
private void doAddPc(String host) throws InterruptedException {
|
||||
private void doAddPc(String rawUserInput) throws InterruptedException {
|
||||
boolean wrongSiteLocal = false;
|
||||
boolean success;
|
||||
int portTestResult;
|
||||
@@ -106,8 +108,28 @@ public class AddComputerManually extends Activity {
|
||||
|
||||
try {
|
||||
ComputerDetails details = new ComputerDetails();
|
||||
details.manualAddress = host;
|
||||
|
||||
// Use URI-style parsing for the host address input
|
||||
URI uri = new URI("moonlight://" + rawUserInput);
|
||||
|
||||
String host = uri.getHost();
|
||||
int port = uri.getPort();
|
||||
|
||||
// URI allows empty hosts, but we don't want that
|
||||
if (host == null || host.isEmpty()) {
|
||||
throw new URISyntaxException(rawUserInput, "Host failed to parse");
|
||||
}
|
||||
|
||||
// If a port was not specified, use the default
|
||||
if (port == -1) {
|
||||
port = NvHTTP.DEFAULT_HTTP_PORT;
|
||||
}
|
||||
|
||||
details.manualAddress = new ComputerDetails.AddressTuple(host, port);
|
||||
success = managerBinder.addComputerBlocking(details);
|
||||
if (!success){
|
||||
wrongSiteLocal = isWrongSubnetSiteLocalAddress(host);
|
||||
}
|
||||
} catch (InterruptedException e) {
|
||||
// Propagate the InterruptedException to the caller for proper handling
|
||||
dialog.dismiss();
|
||||
@@ -117,12 +139,12 @@ public class AddComputerManually extends Activity {
|
||||
// https://github.com/square/okhttp/blob/okhttp_27/okhttp/src/main/java/com/squareup/okhttp/HttpUrl.java#L705
|
||||
e.printStackTrace();
|
||||
success = false;
|
||||
} catch (URISyntaxException e) {
|
||||
e.printStackTrace();
|
||||
success = false;
|
||||
}
|
||||
|
||||
// Keep the SpinnerDialog open while testing connectivity
|
||||
if (!success){
|
||||
wrongSiteLocal = isWrongSubnetSiteLocalAddress(host);
|
||||
}
|
||||
if (!success && !wrongSiteLocal) {
|
||||
// Run the test before dismissing the spinner because it can take a few seconds.
|
||||
portTestResult = MoonBridge.testClientConnectivity(ServerHelper.CONNECTION_TEST_SERVER, 443,
|
||||
|
||||
@@ -27,7 +27,7 @@ import java.security.cert.CertificateEncodingException;
|
||||
public class ServerHelper {
|
||||
public static final String CONNECTION_TEST_SERVER = "android.conntest.moonlight-stream.org";
|
||||
|
||||
public static String getCurrentAddressFromComputer(ComputerDetails computer) throws IOException {
|
||||
public static ComputerDetails.AddressTuple getCurrentAddressFromComputer(ComputerDetails computer) throws IOException {
|
||||
if (computer.activeAddress == null) {
|
||||
throw new IOException("No active address for "+computer.name);
|
||||
}
|
||||
@@ -56,7 +56,8 @@ public class ServerHelper {
|
||||
public static Intent createStartIntent(Activity parent, NvApp app, ComputerDetails computer,
|
||||
ComputerManagerService.ComputerManagerBinder managerBinder) {
|
||||
Intent intent = new Intent(parent, Game.class);
|
||||
intent.putExtra(Game.EXTRA_HOST, computer.activeAddress);
|
||||
intent.putExtra(Game.EXTRA_HOST, computer.activeAddress.address);
|
||||
intent.putExtra(Game.EXTRA_PORT, computer.activeAddress.port);
|
||||
intent.putExtra(Game.EXTRA_APP_NAME, app.getAppName());
|
||||
intent.putExtra(Game.EXTRA_APP_ID, app.getAppId());
|
||||
intent.putExtra(Game.EXTRA_APP_HDR, app.isHdrSupported());
|
||||
@@ -126,7 +127,7 @@ public class ServerHelper {
|
||||
NvHTTP httpConn;
|
||||
String message;
|
||||
try {
|
||||
httpConn = new NvHTTP(ServerHelper.getCurrentAddressFromComputer(computer),
|
||||
httpConn = new NvHTTP(ServerHelper.getCurrentAddressFromComputer(computer), computer.httpsPort,
|
||||
managerBinder.getUniqueId(), computer.serverCert, PlatformBinding.getCryptoProvider(parent));
|
||||
if (httpConn.quitApp()) {
|
||||
message = parent.getResources().getString(R.string.applist_quit_success) + " " + app.getAppName();
|
||||
|
||||
Reference in New Issue
Block a user