diff --git a/app/src/main/java/com/limelight/PcView.java b/app/src/main/java/com/limelight/PcView.java index a9ea5dc8..b6206c37 100644 --- a/app/src/main/java/com/limelight/PcView.java +++ b/app/src/main/java/com/limelight/PcView.java @@ -416,7 +416,7 @@ public class PcView extends Activity implements AdapterFragmentCallbacks { PairingManager pm = httpConn.getPairingManager(); - PairState pairState = pm.pair(httpConn.getServerInfo(), pinStr); + PairState pairState = pm.pair(httpConn.getServerInfo(true), pinStr); if (pairState == PairState.PIN_WRONG) { message = getResources().getString(R.string.pair_incorrect_pin); } diff --git a/app/src/main/java/com/limelight/computers/ComputerManagerService.java b/app/src/main/java/com/limelight/computers/ComputerManagerService.java index 18f1d383..6c25256e 100644 --- a/app/src/main/java/com/limelight/computers/ComputerManagerService.java +++ b/app/src/main/java/com/limelight/computers/ComputerManagerService.java @@ -553,7 +553,7 @@ public class ComputerManagerService extends Service { NvHTTP http = new NvHTTP(address, 0, idManager.getUniqueId(), details.serverCert, PlatformBinding.getCryptoProvider(ComputerManagerService.this)); - ComputerDetails newDetails = http.getComputerDetails(); + ComputerDetails newDetails = http.getComputerDetails(false); // Check if this is the PC we expected if (newDetails.uuid == null) { diff --git a/app/src/main/java/com/limelight/nvstream/NvConnection.java b/app/src/main/java/com/limelight/nvstream/NvConnection.java index 9e2e9f2f..87031d5f 100644 --- a/app/src/main/java/com/limelight/nvstream/NvConnection.java +++ b/app/src/main/java/com/limelight/nvstream/NvConnection.java @@ -255,7 +255,7 @@ public class NvConnection { { NvHTTP h = new NvHTTP(context.serverAddress, context.httpsPort, uniqueId, context.serverCert, cryptoProvider); - String serverInfo = h.getServerInfo(); + String serverInfo = h.getServerInfo(true); context.serverAppVersion = h.getServerVersion(serverInfo); if (context.serverAppVersion == null) { diff --git a/app/src/main/java/com/limelight/nvstream/http/NvHTTP.java b/app/src/main/java/com/limelight/nvstream/http/NvHTTP.java index d83c1879..b28a72e6 100644 --- a/app/src/main/java/com/limelight/nvstream/http/NvHTTP.java +++ b/app/src/main/java/com/limelight/nvstream/http/NvHTTP.java @@ -64,8 +64,9 @@ public class NvHTTP { private static final int DEFAULT_HTTPS_PORT = 47984; public static final int DEFAULT_HTTP_PORT = 47989; - public static final int CONNECTION_TIMEOUT = 3000; - public static final int READ_TIMEOUT = 5000; + public static final int SHORT_CONNECTION_TIMEOUT = 3000; + public static final int LONG_CONNECTION_TIMEOUT = 5000; + public static final int READ_TIMEOUT = 7000; // Print URL and content to logcat on debug builds private static boolean verbose = BuildConfig.DEBUG; @@ -74,8 +75,9 @@ public class NvHTTP { private int httpsPort; - private OkHttpClient httpClient; - private OkHttpClient httpClientWithReadTimeout; + private OkHttpClient httpClientLongConnectTimeout; + private OkHttpClient httpClientLongConnectNoReadTimeout; + private OkHttpClient httpClientShortConnectTimeout; private X509TrustManager defaultTrustManager; private X509TrustManager trustManager; @@ -168,23 +170,28 @@ public class NvHTTP { } }; - httpClient = new OkHttpClient.Builder() + httpClientLongConnectTimeout = new OkHttpClient.Builder() .connectionPool(new ConnectionPool(0, 1, TimeUnit.MILLISECONDS)) .hostnameVerifier(hv) - .readTimeout(0, TimeUnit.MILLISECONDS) - .connectTimeout(CONNECTION_TIMEOUT, TimeUnit.MILLISECONDS) + .readTimeout(READ_TIMEOUT, TimeUnit.MILLISECONDS) + .connectTimeout(LONG_CONNECTION_TIMEOUT, TimeUnit.MILLISECONDS) .proxy(Proxy.NO_PROXY) .build(); - - httpClientWithReadTimeout = httpClient.newBuilder() - .readTimeout(READ_TIMEOUT, TimeUnit.MILLISECONDS) + + httpClientShortConnectTimeout = httpClientLongConnectTimeout.newBuilder() + .connectTimeout(SHORT_CONNECTION_TIMEOUT, TimeUnit.MILLISECONDS) + .build(); + + httpClientLongConnectNoReadTimeout = httpClientLongConnectTimeout.newBuilder() + .readTimeout(0, TimeUnit.MILLISECONDS) .build(); } - public HttpUrl getHttpsUrl() throws IOException { + public HttpUrl getHttpsUrl(boolean likelyOnline) throws IOException { if (httpsPort == 0) { // Fetch the HTTPS port if we don't have it already - httpsPort = getHttpsPort(openHttpConnectionToString(baseUrlHttp, "serverinfo", true)); + httpsPort = getHttpsPort(openHttpConnectionToString(likelyOnline ? httpClientLongConnectTimeout : httpClientShortConnectTimeout, + baseUrlHttp, "serverinfo")); } return new HttpUrl.Builder().scheme("https").host(baseUrlHttp.host()).port(httpsPort).build(); @@ -277,8 +284,11 @@ public class NvHTTP { } } - public String getServerInfo() throws IOException, XmlPullParserException { + public String getServerInfo(boolean likelyOnline) throws IOException, XmlPullParserException { String resp; + + // If we believe the PC is online, give it a little extra time to respond + OkHttpClient client = likelyOnline ? httpClientLongConnectTimeout : httpClientShortConnectTimeout; // // TODO: Shield Hub uses HTTP for this and is able to get an accurate PairStatus with HTTP. @@ -290,7 +300,7 @@ public class NvHTTP { if (serverCert != null) { try { try { - resp = openHttpConnectionToString(getHttpsUrl(), "serverinfo", true); + resp = openHttpConnectionToString(client, getHttpsUrl(likelyOnline), "serverinfo"); } catch (SSLHandshakeException e) { // Detect if we failed due to a server cert mismatch if (e.getCause() instanceof CertificateException) { @@ -310,7 +320,7 @@ public class NvHTTP { catch (GfeHttpResponseException e) { if (e.getErrorCode() == 401) { // Cert validation error - fall back to HTTP - return openHttpConnectionToString(baseUrlHttp, "serverinfo", true); + return openHttpConnectionToString(client, baseUrlHttp, "serverinfo"); } // If it's not a cert validation error, throw it @@ -321,7 +331,7 @@ public class NvHTTP { } else { // No pinned cert, so use HTTP - return openHttpConnectionToString(baseUrlHttp, "serverinfo", true); + return openHttpConnectionToString(client, baseUrlHttp, "serverinfo"); } } @@ -333,9 +343,9 @@ public class NvHTTP { return new ComputerDetails.AddressTuple(address, port); } - public ComputerDetails getComputerDetails() throws IOException, XmlPullParserException { + public ComputerDetails getComputerDetails(boolean likelyOnline) throws IOException, XmlPullParserException { ComputerDetails details = new ComputerDetails(); - String serverInfo = getServerInfo(); + String serverInfo = getServerInfo(likelyOnline); details.name = getXmlString(serverInfo, "hostname", false); if (details.name == null || details.name.isEmpty()) { @@ -397,25 +407,18 @@ public class NvHTTP { .build(); } - private ResponseBody openHttpConnection(HttpUrl baseUrl, String path, boolean enableReadTimeout) throws IOException { - return openHttpConnection(baseUrl, path, null, enableReadTimeout); + private ResponseBody openHttpConnection(OkHttpClient client, HttpUrl baseUrl, String path) throws IOException { + return openHttpConnection(client, baseUrl, path, null); } // Read timeout should be enabled for any HTTP query that requires no outside action // on the GFE server. Examples of queries that DO require outside action are launch, resume, and quit. // The initial pair query does require outside action (user entering a PIN) but subsequent pairing // queries do not. - private ResponseBody openHttpConnection(HttpUrl baseUrl, String path, String query, boolean enableReadTimeout) throws IOException { + private ResponseBody openHttpConnection(OkHttpClient client, HttpUrl baseUrl, String path, String query) throws IOException { HttpUrl completeUrl = getCompleteUrl(baseUrl, path, query); Request request = new Request.Builder().url(completeUrl).get().build(); - Response response; - - if (enableReadTimeout) { - response = performAndroidTlsHack(httpClientWithReadTimeout).newCall(request).execute(); - } - else { - response = performAndroidTlsHack(httpClient).newCall(request).execute(); - } + Response response = performAndroidTlsHack(client).newCall(request).execute(); ResponseBody body = response.body(); @@ -436,13 +439,13 @@ public class NvHTTP { } } - private String openHttpConnectionToString(HttpUrl baseUrl, String path, boolean enableReadTimeout) throws IOException { - return openHttpConnectionToString(baseUrl, path, null, enableReadTimeout); + private String openHttpConnectionToString(OkHttpClient client, HttpUrl baseUrl, String path) throws IOException { + return openHttpConnectionToString(client, baseUrl, path, null); } - private String openHttpConnectionToString(HttpUrl baseUrl, String path, String query, boolean enableReadTimeout) throws IOException { + private String openHttpConnectionToString(OkHttpClient client, HttpUrl baseUrl, String path, String query) throws IOException { try { - ResponseBody resp = openHttpConnection(baseUrl, path, query, enableReadTimeout); + ResponseBody resp = openHttpConnection(client, baseUrl, path, query); String respString = resp.string(); resp.close(); @@ -467,7 +470,7 @@ public class NvHTTP { } public PairingManager.PairState getPairState() throws IOException, XmlPullParserException { - return getPairState(getServerInfo()); + return getPairState(getServerInfo(true)); } public PairingManager.PairState getPairState(String serverInfo) throws IOException, XmlPullParserException { @@ -663,7 +666,7 @@ public class NvHTTP { } public String getAppListRaw() throws IOException { - return openHttpConnectionToString(getHttpsUrl(), "applist", true); + return openHttpConnectionToString(httpClientLongConnectTimeout, getHttpsUrl(true), "applist"); } public LinkedList getAppList() throws GfeHttpResponseException, IOException, XmlPullParserException { @@ -672,30 +675,28 @@ public class NvHTTP { return getAppListByReader(new StringReader(getAppListRaw())); } else { - try (final ResponseBody resp = openHttpConnection(getHttpsUrl(), "applist", true)) { + try (final ResponseBody resp = openHttpConnection(httpClientLongConnectTimeout, getHttpsUrl(true), "applist")) { return getAppListByReader(new InputStreamReader(resp.byteStream())); } } } String executePairingCommand(String additionalArguments, boolean enableReadTimeout) throws GfeHttpResponseException, IOException { - return openHttpConnectionToString(baseUrlHttp, "pair", - "devicename=roth&updateState=1&" + additionalArguments, - enableReadTimeout); + return openHttpConnectionToString(enableReadTimeout ? httpClientLongConnectTimeout : httpClientLongConnectNoReadTimeout, + baseUrlHttp, "pair", "devicename=roth&updateState=1&" + additionalArguments); } String executePairingChallenge() throws GfeHttpResponseException, IOException { - return openHttpConnectionToString(getHttpsUrl(), "pair", - "devicename=roth&updateState=1&phrase=pairchallenge", - true); + return openHttpConnectionToString(httpClientLongConnectTimeout, getHttpsUrl(true), + "pair", "devicename=roth&updateState=1&phrase=pairchallenge"); } public void unpair() throws IOException { - openHttpConnectionToString(baseUrlHttp, "unpair", true); + openHttpConnectionToString(httpClientLongConnectTimeout, baseUrlHttp, "unpair"); } public InputStream getBoxArt(NvApp app) throws IOException { - ResponseBody resp = openHttpConnection(getHttpsUrl(), "appasset", "appid=" + app.getAppId() + "&AssetType=2&AssetIdx=0", true); + ResponseBody resp = openHttpConnection(httpClientLongConnectTimeout, getHttpsUrl(true), "appasset", "appid=" + app.getAppId() + "&AssetType=2&AssetIdx=0"); return resp.byteStream(); } @@ -750,7 +751,7 @@ public class NvHTTP { enableSops = false; } - String xmlStr = openHttpConnectionToString(getHttpsUrl(), "launch", + String xmlStr = openHttpConnectionToString(httpClientLongConnectNoReadTimeout, getHttpsUrl(true), "launch", "appid=" + appId + "&mode=" + context.negotiatedWidth + "x" + context.negotiatedHeight + "x" + fps + "&additionalStates=1&sops=" + (enableSops ? 1 : 0) + @@ -760,8 +761,7 @@ public class NvHTTP { "&localAudioPlayMode=" + (context.streamConfig.getPlayLocalAudio() ? 1 : 0) + "&surroundAudioInfo=" + context.streamConfig.getAudioConfiguration().getSurroundAudioInfo() + (context.streamConfig.getAttachedGamepadMask() != 0 ? "&remoteControllersBitmap=" + context.streamConfig.getAttachedGamepadMask() : "") + - (context.streamConfig.getAttachedGamepadMask() != 0 ? "&gcmap=" + context.streamConfig.getAttachedGamepadMask() : ""), - false); + (context.streamConfig.getAttachedGamepadMask() != 0 ? "&gcmap=" + context.streamConfig.getAttachedGamepadMask() : "")); if (!getXmlString(xmlStr, "gamesession", true).equals("0")) { // sessionUrl0 will be missing for older GFE versions context.rtspSessionUrl = getXmlString(xmlStr, "sessionUrl0", false); @@ -773,11 +773,10 @@ public class NvHTTP { } public boolean resumeApp(ConnectionContext context) throws IOException, XmlPullParserException { - String xmlStr = openHttpConnectionToString(getHttpsUrl(), "resume", + String xmlStr = openHttpConnectionToString(httpClientLongConnectNoReadTimeout, getHttpsUrl(true), "resume", "rikey="+bytesToHex(context.riKey.getEncoded()) + "&rikeyid="+context.riKeyId + - "&surroundAudioInfo=" + context.streamConfig.getAudioConfiguration().getSurroundAudioInfo(), - false); + "&surroundAudioInfo=" + context.streamConfig.getAudioConfiguration().getSurroundAudioInfo()); if (!getXmlString(xmlStr, "resume", true).equals("0")) { // sessionUrl0 will be missing for older GFE versions context.rtspSessionUrl = getXmlString(xmlStr, "sessionUrl0", false); @@ -789,14 +788,14 @@ public class NvHTTP { } public boolean quitApp() throws IOException, XmlPullParserException { - String xmlStr = openHttpConnectionToString(getHttpsUrl(), "cancel", false); + String xmlStr = openHttpConnectionToString(httpClientLongConnectNoReadTimeout, getHttpsUrl(true), "cancel"); if (getXmlString(xmlStr, "cancel", true).equals("0")) { return false; } // Newer GFE versions will just return success even if quitting fails // if we're not the original requestor. - if (getCurrentGame(getServerInfo()) != 0) { + if (getCurrentGame(getServerInfo(true)) != 0) { // Generate a synthetic GfeResponseException letting the caller know // that they can't kill someone else's stream. throw new GfeHttpResponseException(599, "");