mirror of
https://github.com/moonlight-stream/moonlight-android.git
synced 2025-07-20 11:33:06 +00:00
Add read timeouts to HTTP requests that should come back immediately if GFE is working properly
This commit is contained in:
parent
c9d003ca6d
commit
5f42ca66fe
@ -31,6 +31,7 @@ public class NvHTTP {
|
|||||||
|
|
||||||
public static final int PORT = 47984;
|
public static final int PORT = 47984;
|
||||||
public static final int CONNECTION_TIMEOUT = 2000;
|
public static final int CONNECTION_TIMEOUT = 2000;
|
||||||
|
public static final int READ_TIMEOUT = 2000;
|
||||||
|
|
||||||
private final boolean verbose = false;
|
private final boolean verbose = false;
|
||||||
|
|
||||||
@ -101,7 +102,7 @@ public class NvHTTP {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public String getServerInfo(String uniqueId) throws MalformedURLException, IOException {
|
public String getServerInfo(String uniqueId) throws MalformedURLException, IOException {
|
||||||
return openHttpConnectionToString(baseUrl + "/serverinfo?uniqueid=" + uniqueId);
|
return openHttpConnectionToString(baseUrl + "/serverinfo?uniqueid=" + uniqueId, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
public ComputerDetails getComputerDetails() throws MalformedURLException, IOException, XmlPullParserException {
|
public ComputerDetails getComputerDetails() throws MalformedURLException, IOException, XmlPullParserException {
|
||||||
@ -146,19 +147,27 @@ public class NvHTTP {
|
|||||||
return details;
|
return details;
|
||||||
}
|
}
|
||||||
|
|
||||||
private InputStream openHttpConnection(String url) throws IOException {
|
// 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 InputStream openHttpConnection(String url, boolean enableReadTimeout) throws IOException {
|
||||||
URLConnection conn = new URL(url).openConnection();
|
URLConnection conn = new URL(url).openConnection();
|
||||||
if (verbose) {
|
if (verbose) {
|
||||||
System.out.println(url);
|
System.out.println(url);
|
||||||
}
|
}
|
||||||
conn.setConnectTimeout(CONNECTION_TIMEOUT);
|
conn.setConnectTimeout(CONNECTION_TIMEOUT);
|
||||||
|
if (enableReadTimeout) {
|
||||||
|
conn.setReadTimeout(READ_TIMEOUT);
|
||||||
|
}
|
||||||
|
|
||||||
conn.setUseCaches(false);
|
conn.setUseCaches(false);
|
||||||
conn.connect();
|
conn.connect();
|
||||||
return conn.getInputStream();
|
return conn.getInputStream();
|
||||||
}
|
}
|
||||||
|
|
||||||
String openHttpConnectionToString(String url) throws MalformedURLException, IOException {
|
String openHttpConnectionToString(String url, boolean enableReadTimeout) throws MalformedURLException, IOException {
|
||||||
Scanner s = new Scanner(openHttpConnection(url));
|
Scanner s = new Scanner(openHttpConnection(url, enableReadTimeout));
|
||||||
|
|
||||||
String str = "";
|
String str = "";
|
||||||
while (s.hasNext()) {
|
while (s.hasNext()) {
|
||||||
@ -207,12 +216,14 @@ public class NvHTTP {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public InputStream getBoxArtPng(NvApp app) throws IOException {
|
public InputStream getBoxArtPng(NvApp app) throws IOException {
|
||||||
|
// FIXME: Investigate whether this should be subject to the 2 second read timeout
|
||||||
|
// or not.
|
||||||
return openHttpConnection(baseUrl + "/applist?uniqueid="+uniqueId+"&appid="+
|
return openHttpConnection(baseUrl + "/applist?uniqueid="+uniqueId+"&appid="+
|
||||||
app.getAppId()+"&AssetType=2&AssetIdx=0");
|
app.getAppId()+"&AssetType=2&AssetIdx=0", false);
|
||||||
}
|
}
|
||||||
|
|
||||||
public LinkedList<NvApp> getAppList() throws GfeHttpResponseException, IOException, XmlPullParserException {
|
public LinkedList<NvApp> getAppList() throws GfeHttpResponseException, IOException, XmlPullParserException {
|
||||||
InputStream in = openHttpConnection(baseUrl + "/applist?uniqueid=" + uniqueId);
|
InputStream in = openHttpConnection(baseUrl + "/applist?uniqueid=" + uniqueId, true);
|
||||||
XmlPullParserFactory factory = XmlPullParserFactory.newInstance();
|
XmlPullParserFactory factory = XmlPullParserFactory.newInstance();
|
||||||
factory.setNamespaceAware(true);
|
factory.setNamespaceAware(true);
|
||||||
XmlPullParser xpp = factory.newPullParser();
|
XmlPullParser xpp = factory.newPullParser();
|
||||||
@ -253,7 +264,7 @@ public class NvHTTP {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void unpair() throws IOException {
|
public void unpair() throws IOException {
|
||||||
openHttpConnection(baseUrl + "/unpair?uniqueid=" + uniqueId);
|
openHttpConnection(baseUrl + "/unpair?uniqueid=" + uniqueId, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
final private static char[] hexArray = "0123456789ABCDEF".toCharArray();
|
final private static char[] hexArray = "0123456789ABCDEF".toCharArray();
|
||||||
@ -274,7 +285,7 @@ public class NvHTTP {
|
|||||||
"&mode=" + width + "x" + height + "x" + refreshRate +
|
"&mode=" + width + "x" + height + "x" + refreshRate +
|
||||||
"&additionalStates=1&sops=" + (sops ? 1 : 0) +
|
"&additionalStates=1&sops=" + (sops ? 1 : 0) +
|
||||||
"&rikey="+bytesToHex(inputKey.getEncoded()) +
|
"&rikey="+bytesToHex(inputKey.getEncoded()) +
|
||||||
"&rikeyid="+riKeyId);
|
"&rikeyid="+riKeyId, false);
|
||||||
String gameSession = getXmlString(in, "gamesession");
|
String gameSession = getXmlString(in, "gamesession");
|
||||||
return Integer.parseInt(gameSession);
|
return Integer.parseInt(gameSession);
|
||||||
}
|
}
|
||||||
@ -282,13 +293,13 @@ public class NvHTTP {
|
|||||||
public boolean resumeApp(SecretKey inputKey, int riKeyId) throws IOException, XmlPullParserException {
|
public boolean resumeApp(SecretKey inputKey, int riKeyId) throws IOException, XmlPullParserException {
|
||||||
InputStream in = openHttpConnection(baseUrl + "/resume?uniqueid=" + uniqueId +
|
InputStream in = openHttpConnection(baseUrl + "/resume?uniqueid=" + uniqueId +
|
||||||
"&rikey="+bytesToHex(inputKey.getEncoded()) +
|
"&rikey="+bytesToHex(inputKey.getEncoded()) +
|
||||||
"&rikeyid="+riKeyId);
|
"&rikeyid="+riKeyId, false);
|
||||||
String resume = getXmlString(in, "resume");
|
String resume = getXmlString(in, "resume");
|
||||||
return Integer.parseInt(resume) != 0;
|
return Integer.parseInt(resume) != 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean quitApp() throws IOException, XmlPullParserException {
|
public boolean quitApp() throws IOException, XmlPullParserException {
|
||||||
InputStream in = openHttpConnection(baseUrl + "/cancel?uniqueid=" + uniqueId);
|
InputStream in = openHttpConnection(baseUrl + "/cancel?uniqueid=" + uniqueId, false);
|
||||||
String cancel = getXmlString(in, "cancel");
|
String cancel = getXmlString(in, "cancel");
|
||||||
return Integer.parseInt(cancel) != 0;
|
return Integer.parseInt(cancel) != 0;
|
||||||
}
|
}
|
||||||
|
@ -235,11 +235,14 @@ public class PairingManager {
|
|||||||
byte[] saltAndPin = saltPin(salt, pin);
|
byte[] saltAndPin = saltPin(salt, pin);
|
||||||
aesKey = generateAesKey(saltAndPin);
|
aesKey = generateAesKey(saltAndPin);
|
||||||
|
|
||||||
// Send the salt and get the server cert
|
// Send the salt and get the server cert. This doesn't have a read timeout
|
||||||
|
// because the user must enter the PIN before the server responds
|
||||||
String getCert = http.openHttpConnectionToString(http.baseUrl +
|
String getCert = http.openHttpConnectionToString(http.baseUrl +
|
||||||
"/pair?uniqueid="+uniqueId+"&devicename=roth&updateState=1&phrase=getservercert&salt="+bytesToHex(salt)+"&clientcert="+bytesToHex(pemCertBytes));
|
"/pair?uniqueid="+uniqueId+"&devicename=roth&updateState=1&phrase=getservercert&salt="+
|
||||||
|
bytesToHex(salt)+"&clientcert="+bytesToHex(pemCertBytes),
|
||||||
|
false);
|
||||||
if (!NvHTTP.getXmlString(getCert, "paired").equals("1")) {
|
if (!NvHTTP.getXmlString(getCert, "paired").equals("1")) {
|
||||||
http.openHttpConnectionToString(http.baseUrl + "/unpair?uniqueid="+uniqueId);
|
http.openHttpConnectionToString(http.baseUrl + "/unpair?uniqueid="+uniqueId, true);
|
||||||
return PairState.FAILED;
|
return PairState.FAILED;
|
||||||
}
|
}
|
||||||
X509Certificate serverCert = extractPlainCert(getCert);
|
X509Certificate serverCert = extractPlainCert(getCert);
|
||||||
@ -250,9 +253,10 @@ public class PairingManager {
|
|||||||
|
|
||||||
// Send the encrypted challenge to the server
|
// Send the encrypted challenge to the server
|
||||||
String challengeResp = http.openHttpConnectionToString(http.baseUrl +
|
String challengeResp = http.openHttpConnectionToString(http.baseUrl +
|
||||||
"/pair?uniqueid="+uniqueId+"&devicename=roth&updateState=1&clientchallenge="+bytesToHex(encryptedChallenge));
|
"/pair?uniqueid="+uniqueId+"&devicename=roth&updateState=1&clientchallenge="+bytesToHex(encryptedChallenge),
|
||||||
|
true);
|
||||||
if (!NvHTTP.getXmlString(challengeResp, "paired").equals("1")) {
|
if (!NvHTTP.getXmlString(challengeResp, "paired").equals("1")) {
|
||||||
http.openHttpConnectionToString(http.baseUrl + "/unpair?uniqueid="+uniqueId);
|
http.openHttpConnectionToString(http.baseUrl + "/unpair?uniqueid="+uniqueId, true);
|
||||||
return PairState.FAILED;
|
return PairState.FAILED;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -268,9 +272,10 @@ public class PairingManager {
|
|||||||
byte[] challengeRespHash = toSHA1Bytes(concatBytes(concatBytes(serverChallenge, cert.getSignature()), clientSecret));
|
byte[] challengeRespHash = toSHA1Bytes(concatBytes(concatBytes(serverChallenge, cert.getSignature()), clientSecret));
|
||||||
byte[] challengeRespEncrypted = encryptAes(challengeRespHash, aesKey);
|
byte[] challengeRespEncrypted = encryptAes(challengeRespHash, aesKey);
|
||||||
String secretResp = http.openHttpConnectionToString(http.baseUrl +
|
String secretResp = http.openHttpConnectionToString(http.baseUrl +
|
||||||
"/pair?uniqueid="+uniqueId+"&devicename=roth&updateState=1&serverchallengeresp="+bytesToHex(challengeRespEncrypted));
|
"/pair?uniqueid="+uniqueId+"&devicename=roth&updateState=1&serverchallengeresp="+bytesToHex(challengeRespEncrypted),
|
||||||
|
true);
|
||||||
if (!NvHTTP.getXmlString(secretResp, "paired").equals("1")) {
|
if (!NvHTTP.getXmlString(secretResp, "paired").equals("1")) {
|
||||||
http.openHttpConnectionToString(http.baseUrl + "/unpair?uniqueid="+uniqueId);
|
http.openHttpConnectionToString(http.baseUrl + "/unpair?uniqueid="+uniqueId, true);
|
||||||
return PairState.FAILED;
|
return PairState.FAILED;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -282,7 +287,7 @@ public class PairingManager {
|
|||||||
// Ensure the authenticity of the data
|
// Ensure the authenticity of the data
|
||||||
if (!verifySignature(serverSecret, serverSignature, serverCert)) {
|
if (!verifySignature(serverSecret, serverSignature, serverCert)) {
|
||||||
// Cancel the pairing process
|
// Cancel the pairing process
|
||||||
http.openHttpConnectionToString(http.baseUrl + "/unpair?uniqueid="+uniqueId);
|
http.openHttpConnectionToString(http.baseUrl + "/unpair?uniqueid="+uniqueId, true);
|
||||||
|
|
||||||
// Looks like a MITM
|
// Looks like a MITM
|
||||||
return PairState.FAILED;
|
return PairState.FAILED;
|
||||||
@ -292,7 +297,7 @@ public class PairingManager {
|
|||||||
byte[] serverChallengeRespHash = toSHA1Bytes(concatBytes(concatBytes(randomChallenge, serverCert.getSignature()), serverSecret));
|
byte[] serverChallengeRespHash = toSHA1Bytes(concatBytes(concatBytes(randomChallenge, serverCert.getSignature()), serverSecret));
|
||||||
if (!Arrays.equals(serverChallengeRespHash, serverResponse)) {
|
if (!Arrays.equals(serverChallengeRespHash, serverResponse)) {
|
||||||
// Cancel the pairing process
|
// Cancel the pairing process
|
||||||
http.openHttpConnectionToString(http.baseUrl + "/unpair?uniqueid="+uniqueId);
|
http.openHttpConnectionToString(http.baseUrl + "/unpair?uniqueid="+uniqueId, true);
|
||||||
|
|
||||||
// Probably got the wrong PIN
|
// Probably got the wrong PIN
|
||||||
return PairState.PIN_WRONG;
|
return PairState.PIN_WRONG;
|
||||||
@ -301,16 +306,18 @@ public class PairingManager {
|
|||||||
// Send the server our signed secret
|
// Send the server our signed secret
|
||||||
byte[] clientPairingSecret = concatBytes(clientSecret, signData(clientSecret, pk));
|
byte[] clientPairingSecret = concatBytes(clientSecret, signData(clientSecret, pk));
|
||||||
String clientSecretResp = http.openHttpConnectionToString(http.baseUrl +
|
String clientSecretResp = http.openHttpConnectionToString(http.baseUrl +
|
||||||
"/pair?uniqueid="+uniqueId+"&devicename=roth&updateState=1&clientpairingsecret="+bytesToHex(clientPairingSecret));
|
"/pair?uniqueid="+uniqueId+"&devicename=roth&updateState=1&clientpairingsecret="+bytesToHex(clientPairingSecret),
|
||||||
|
true);
|
||||||
if (!NvHTTP.getXmlString(clientSecretResp, "paired").equals("1")) {
|
if (!NvHTTP.getXmlString(clientSecretResp, "paired").equals("1")) {
|
||||||
http.openHttpConnectionToString(http.baseUrl + "/unpair?uniqueid="+uniqueId);
|
http.openHttpConnectionToString(http.baseUrl + "/unpair?uniqueid="+uniqueId, true);
|
||||||
return PairState.FAILED;
|
return PairState.FAILED;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Do the initial challenge (seems neccessary for us to show as paired)
|
// Do the initial challenge (seems neccessary for us to show as paired)
|
||||||
String pairChallenge = http.openHttpConnectionToString(http.baseUrl + "/pair?uniqueid="+uniqueId+"&devicename=roth&updateState=1&phrase=pairchallenge");
|
String pairChallenge = http.openHttpConnectionToString(http.baseUrl +
|
||||||
|
"/pair?uniqueid="+uniqueId+"&devicename=roth&updateState=1&phrase=pairchallenge", true);
|
||||||
if (!NvHTTP.getXmlString(pairChallenge, "paired").equals("1")) {
|
if (!NvHTTP.getXmlString(pairChallenge, "paired").equals("1")) {
|
||||||
http.openHttpConnectionToString(http.baseUrl + "/unpair?uniqueid="+uniqueId);
|
http.openHttpConnectionToString(http.baseUrl + "/unpair?uniqueid="+uniqueId, true);
|
||||||
return PairState.FAILED;
|
return PairState.FAILED;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user