Increase connection timeouts when the PC is presumed to be online

This commit is contained in:
Cameron Gutman 2022-11-09 20:22:07 -06:00
parent 57f55e6856
commit 392e3c7fe3
4 changed files with 54 additions and 55 deletions

View File

@ -416,7 +416,7 @@ public class PcView extends Activity implements AdapterFragmentCallbacks {
PairingManager pm = httpConn.getPairingManager(); PairingManager pm = httpConn.getPairingManager();
PairState pairState = pm.pair(httpConn.getServerInfo(), pinStr); PairState pairState = pm.pair(httpConn.getServerInfo(true), pinStr);
if (pairState == PairState.PIN_WRONG) { if (pairState == PairState.PIN_WRONG) {
message = getResources().getString(R.string.pair_incorrect_pin); message = getResources().getString(R.string.pair_incorrect_pin);
} }

View File

@ -553,7 +553,7 @@ public class ComputerManagerService extends Service {
NvHTTP http = new NvHTTP(address, 0, idManager.getUniqueId(), details.serverCert, NvHTTP http = new NvHTTP(address, 0, idManager.getUniqueId(), details.serverCert,
PlatformBinding.getCryptoProvider(ComputerManagerService.this)); PlatformBinding.getCryptoProvider(ComputerManagerService.this));
ComputerDetails newDetails = http.getComputerDetails(); ComputerDetails newDetails = http.getComputerDetails(false);
// Check if this is the PC we expected // Check if this is the PC we expected
if (newDetails.uuid == null) { if (newDetails.uuid == null) {

View File

@ -255,7 +255,7 @@ public class NvConnection {
{ {
NvHTTP h = new NvHTTP(context.serverAddress, context.httpsPort, uniqueId, context.serverCert, cryptoProvider); 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); context.serverAppVersion = h.getServerVersion(serverInfo);
if (context.serverAppVersion == null) { if (context.serverAppVersion == null) {

View File

@ -64,8 +64,9 @@ public class NvHTTP {
private static final int DEFAULT_HTTPS_PORT = 47984; private static final int DEFAULT_HTTPS_PORT = 47984;
public static final int DEFAULT_HTTP_PORT = 47989; public static final int DEFAULT_HTTP_PORT = 47989;
public static final int CONNECTION_TIMEOUT = 3000; public static final int SHORT_CONNECTION_TIMEOUT = 3000;
public static final int READ_TIMEOUT = 5000; public static final int LONG_CONNECTION_TIMEOUT = 5000;
public static final int READ_TIMEOUT = 7000;
// Print URL and content to logcat on debug builds // Print URL and content to logcat on debug builds
private static boolean verbose = BuildConfig.DEBUG; private static boolean verbose = BuildConfig.DEBUG;
@ -74,8 +75,9 @@ public class NvHTTP {
private int httpsPort; private int httpsPort;
private OkHttpClient httpClient; private OkHttpClient httpClientLongConnectTimeout;
private OkHttpClient httpClientWithReadTimeout; private OkHttpClient httpClientLongConnectNoReadTimeout;
private OkHttpClient httpClientShortConnectTimeout;
private X509TrustManager defaultTrustManager; private X509TrustManager defaultTrustManager;
private X509TrustManager trustManager; 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)) .connectionPool(new ConnectionPool(0, 1, TimeUnit.MILLISECONDS))
.hostnameVerifier(hv) .hostnameVerifier(hv)
.readTimeout(0, TimeUnit.MILLISECONDS) .readTimeout(READ_TIMEOUT, TimeUnit.MILLISECONDS)
.connectTimeout(CONNECTION_TIMEOUT, TimeUnit.MILLISECONDS) .connectTimeout(LONG_CONNECTION_TIMEOUT, TimeUnit.MILLISECONDS)
.proxy(Proxy.NO_PROXY) .proxy(Proxy.NO_PROXY)
.build(); .build();
httpClientWithReadTimeout = httpClient.newBuilder() httpClientShortConnectTimeout = httpClientLongConnectTimeout.newBuilder()
.readTimeout(READ_TIMEOUT, TimeUnit.MILLISECONDS) .connectTimeout(SHORT_CONNECTION_TIMEOUT, TimeUnit.MILLISECONDS)
.build();
httpClientLongConnectNoReadTimeout = httpClientLongConnectTimeout.newBuilder()
.readTimeout(0, TimeUnit.MILLISECONDS)
.build(); .build();
} }
public HttpUrl getHttpsUrl() throws IOException { public HttpUrl getHttpsUrl(boolean likelyOnline) throws IOException {
if (httpsPort == 0) { if (httpsPort == 0) {
// Fetch the HTTPS port if we don't have it already // 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(); 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; 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. // 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) { if (serverCert != null) {
try { try {
try { try {
resp = openHttpConnectionToString(getHttpsUrl(), "serverinfo", true); resp = openHttpConnectionToString(client, getHttpsUrl(likelyOnline), "serverinfo");
} catch (SSLHandshakeException e) { } catch (SSLHandshakeException e) {
// Detect if we failed due to a server cert mismatch // Detect if we failed due to a server cert mismatch
if (e.getCause() instanceof CertificateException) { if (e.getCause() instanceof CertificateException) {
@ -310,7 +320,7 @@ public class NvHTTP {
catch (GfeHttpResponseException e) { catch (GfeHttpResponseException e) {
if (e.getErrorCode() == 401) { if (e.getErrorCode() == 401) {
// Cert validation error - fall back to HTTP // 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 // If it's not a cert validation error, throw it
@ -321,7 +331,7 @@ public class NvHTTP {
} }
else { else {
// No pinned cert, so use HTTP // 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); return new ComputerDetails.AddressTuple(address, port);
} }
public ComputerDetails getComputerDetails() throws IOException, XmlPullParserException { public ComputerDetails getComputerDetails(boolean likelyOnline) throws IOException, XmlPullParserException {
ComputerDetails details = new ComputerDetails(); ComputerDetails details = new ComputerDetails();
String serverInfo = getServerInfo(); String serverInfo = getServerInfo(likelyOnline);
details.name = getXmlString(serverInfo, "hostname", false); details.name = getXmlString(serverInfo, "hostname", false);
if (details.name == null || details.name.isEmpty()) { if (details.name == null || details.name.isEmpty()) {
@ -397,25 +407,18 @@ public class NvHTTP {
.build(); .build();
} }
private ResponseBody openHttpConnection(HttpUrl baseUrl, String path, boolean enableReadTimeout) throws IOException { private ResponseBody openHttpConnection(OkHttpClient client, HttpUrl baseUrl, String path) throws IOException {
return openHttpConnection(baseUrl, path, null, enableReadTimeout); return openHttpConnection(client, baseUrl, path, null);
} }
// Read timeout should be enabled for any HTTP query that requires no outside action // 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. // 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 // The initial pair query does require outside action (user entering a PIN) but subsequent pairing
// queries do not. // 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); HttpUrl completeUrl = getCompleteUrl(baseUrl, path, query);
Request request = new Request.Builder().url(completeUrl).get().build(); Request request = new Request.Builder().url(completeUrl).get().build();
Response response; Response response = performAndroidTlsHack(client).newCall(request).execute();
if (enableReadTimeout) {
response = performAndroidTlsHack(httpClientWithReadTimeout).newCall(request).execute();
}
else {
response = performAndroidTlsHack(httpClient).newCall(request).execute();
}
ResponseBody body = response.body(); ResponseBody body = response.body();
@ -436,13 +439,13 @@ public class NvHTTP {
} }
} }
private String openHttpConnectionToString(HttpUrl baseUrl, String path, boolean enableReadTimeout) throws IOException { private String openHttpConnectionToString(OkHttpClient client, HttpUrl baseUrl, String path) throws IOException {
return openHttpConnectionToString(baseUrl, path, null, enableReadTimeout); 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 { try {
ResponseBody resp = openHttpConnection(baseUrl, path, query, enableReadTimeout); ResponseBody resp = openHttpConnection(client, baseUrl, path, query);
String respString = resp.string(); String respString = resp.string();
resp.close(); resp.close();
@ -467,7 +470,7 @@ public class NvHTTP {
} }
public PairingManager.PairState getPairState() throws IOException, XmlPullParserException { public PairingManager.PairState getPairState() throws IOException, XmlPullParserException {
return getPairState(getServerInfo()); return getPairState(getServerInfo(true));
} }
public PairingManager.PairState getPairState(String serverInfo) throws IOException, XmlPullParserException { public PairingManager.PairState getPairState(String serverInfo) throws IOException, XmlPullParserException {
@ -663,7 +666,7 @@ public class NvHTTP {
} }
public String getAppListRaw() throws IOException { public String getAppListRaw() throws IOException {
return openHttpConnectionToString(getHttpsUrl(), "applist", true); return openHttpConnectionToString(httpClientLongConnectTimeout, getHttpsUrl(true), "applist");
} }
public LinkedList<NvApp> getAppList() throws GfeHttpResponseException, IOException, XmlPullParserException { public LinkedList<NvApp> getAppList() throws GfeHttpResponseException, IOException, XmlPullParserException {
@ -672,30 +675,28 @@ public class NvHTTP {
return getAppListByReader(new StringReader(getAppListRaw())); return getAppListByReader(new StringReader(getAppListRaw()));
} }
else { else {
try (final ResponseBody resp = openHttpConnection(getHttpsUrl(), "applist", true)) { try (final ResponseBody resp = openHttpConnection(httpClientLongConnectTimeout, getHttpsUrl(true), "applist")) {
return getAppListByReader(new InputStreamReader(resp.byteStream())); return getAppListByReader(new InputStreamReader(resp.byteStream()));
} }
} }
} }
String executePairingCommand(String additionalArguments, boolean enableReadTimeout) throws GfeHttpResponseException, IOException { String executePairingCommand(String additionalArguments, boolean enableReadTimeout) throws GfeHttpResponseException, IOException {
return openHttpConnectionToString(baseUrlHttp, "pair", return openHttpConnectionToString(enableReadTimeout ? httpClientLongConnectTimeout : httpClientLongConnectNoReadTimeout,
"devicename=roth&updateState=1&" + additionalArguments, baseUrlHttp, "pair", "devicename=roth&updateState=1&" + additionalArguments);
enableReadTimeout);
} }
String executePairingChallenge() throws GfeHttpResponseException, IOException { String executePairingChallenge() throws GfeHttpResponseException, IOException {
return openHttpConnectionToString(getHttpsUrl(), "pair", return openHttpConnectionToString(httpClientLongConnectTimeout, getHttpsUrl(true),
"devicename=roth&updateState=1&phrase=pairchallenge", "pair", "devicename=roth&updateState=1&phrase=pairchallenge");
true);
} }
public void unpair() throws IOException { public void unpair() throws IOException {
openHttpConnectionToString(baseUrlHttp, "unpair", true); openHttpConnectionToString(httpClientLongConnectTimeout, baseUrlHttp, "unpair");
} }
public InputStream getBoxArt(NvApp app) throws IOException { 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(); return resp.byteStream();
} }
@ -750,7 +751,7 @@ public class NvHTTP {
enableSops = false; enableSops = false;
} }
String xmlStr = openHttpConnectionToString(getHttpsUrl(), "launch", String xmlStr = openHttpConnectionToString(httpClientLongConnectNoReadTimeout, getHttpsUrl(true), "launch",
"appid=" + appId + "appid=" + appId +
"&mode=" + context.negotiatedWidth + "x" + context.negotiatedHeight + "x" + fps + "&mode=" + context.negotiatedWidth + "x" + context.negotiatedHeight + "x" + fps +
"&additionalStates=1&sops=" + (enableSops ? 1 : 0) + "&additionalStates=1&sops=" + (enableSops ? 1 : 0) +
@ -760,8 +761,7 @@ public class NvHTTP {
"&localAudioPlayMode=" + (context.streamConfig.getPlayLocalAudio() ? 1 : 0) + "&localAudioPlayMode=" + (context.streamConfig.getPlayLocalAudio() ? 1 : 0) +
"&surroundAudioInfo=" + context.streamConfig.getAudioConfiguration().getSurroundAudioInfo() + "&surroundAudioInfo=" + context.streamConfig.getAudioConfiguration().getSurroundAudioInfo() +
(context.streamConfig.getAttachedGamepadMask() != 0 ? "&remoteControllersBitmap=" + context.streamConfig.getAttachedGamepadMask() : "") + (context.streamConfig.getAttachedGamepadMask() != 0 ? "&remoteControllersBitmap=" + context.streamConfig.getAttachedGamepadMask() : "") +
(context.streamConfig.getAttachedGamepadMask() != 0 ? "&gcmap=" + context.streamConfig.getAttachedGamepadMask() : ""), (context.streamConfig.getAttachedGamepadMask() != 0 ? "&gcmap=" + context.streamConfig.getAttachedGamepadMask() : ""));
false);
if (!getXmlString(xmlStr, "gamesession", true).equals("0")) { if (!getXmlString(xmlStr, "gamesession", true).equals("0")) {
// sessionUrl0 will be missing for older GFE versions // sessionUrl0 will be missing for older GFE versions
context.rtspSessionUrl = getXmlString(xmlStr, "sessionUrl0", false); context.rtspSessionUrl = getXmlString(xmlStr, "sessionUrl0", false);
@ -773,11 +773,10 @@ public class NvHTTP {
} }
public boolean resumeApp(ConnectionContext context) throws IOException, XmlPullParserException { 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()) + "rikey="+bytesToHex(context.riKey.getEncoded()) +
"&rikeyid="+context.riKeyId + "&rikeyid="+context.riKeyId +
"&surroundAudioInfo=" + context.streamConfig.getAudioConfiguration().getSurroundAudioInfo(), "&surroundAudioInfo=" + context.streamConfig.getAudioConfiguration().getSurroundAudioInfo());
false);
if (!getXmlString(xmlStr, "resume", true).equals("0")) { if (!getXmlString(xmlStr, "resume", true).equals("0")) {
// sessionUrl0 will be missing for older GFE versions // sessionUrl0 will be missing for older GFE versions
context.rtspSessionUrl = getXmlString(xmlStr, "sessionUrl0", false); context.rtspSessionUrl = getXmlString(xmlStr, "sessionUrl0", false);
@ -789,14 +788,14 @@ public class NvHTTP {
} }
public boolean quitApp() throws IOException, XmlPullParserException { 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")) { if (getXmlString(xmlStr, "cancel", true).equals("0")) {
return false; return false;
} }
// Newer GFE versions will just return success even if quitting fails // Newer GFE versions will just return success even if quitting fails
// if we're not the original requestor. // if we're not the original requestor.
if (getCurrentGame(getServerInfo()) != 0) { if (getCurrentGame(getServerInfo(true)) != 0) {
// Generate a synthetic GfeResponseException letting the caller know // Generate a synthetic GfeResponseException letting the caller know
// that they can't kill someone else's stream. // that they can't kill someone else's stream.
throw new GfeHttpResponseException(599, ""); throw new GfeHttpResponseException(599, "");