Improve handling of required XML tags

This commit is contained in:
Cameron Gutman
2022-05-20 17:15:26 -05:00
parent 077cb2103d
commit ac7c5c1064
2 changed files with 54 additions and 44 deletions

View File

@@ -205,7 +205,7 @@ public class NvHTTP {
return "uniqueid="+uniqueId+"&uuid="+UUID.randomUUID(); return "uniqueid="+uniqueId+"&uuid="+UUID.randomUUID();
} }
static String getXmlString(Reader r, String tagname) throws XmlPullParserException, IOException { static String getXmlString(Reader r, String tagname, boolean throwIfMissing) throws XmlPullParserException, IOException {
XmlPullParserFactory factory = XmlPullParserFactory.newInstance(); XmlPullParserFactory factory = XmlPullParserFactory.newInstance();
factory.setNamespaceAware(true); factory.setNamespaceAware(true);
XmlPullParser xpp = factory.newPullParser(); XmlPullParser xpp = factory.newPullParser();
@@ -234,11 +234,19 @@ public class NvHTTP {
eventType = xpp.next(); eventType = xpp.next();
} }
if (throwIfMissing) {
// We throw an XmlPullParserException here for ease of handling in all the various callers.
// We could also throw an IOException, but some callers expect those in cases where the
// host may not be reachable. We want to distinguish unreachable hosts vs. hosts that
// are returning garbage XML to us, so we use XmlPullParserException instead.
throw new XmlPullParserException("Missing mandatory field in host response: "+tagname);
}
return null; return null;
} }
static String getXmlString(String str, String tagname) throws XmlPullParserException, IOException { static String getXmlString(String str, String tagname, boolean throwIfMissing) throws XmlPullParserException, IOException {
return getXmlString(new StringReader(str), tagname); return getXmlString(new StringReader(str), tagname, throwIfMissing);
} }
private static void verifyResponseStatus(XmlPullParser xpp) throws GfeHttpResponseException { private static void verifyResponseStatus(XmlPullParser xpp) throws GfeHttpResponseException {
@@ -311,21 +319,21 @@ public class NvHTTP {
ComputerDetails details = new ComputerDetails(); ComputerDetails details = new ComputerDetails();
String serverInfo = getServerInfo(); String serverInfo = getServerInfo();
details.name = getXmlString(serverInfo, "hostname"); details.name = getXmlString(serverInfo, "hostname", false);
if (details.name == null || details.name.isEmpty()) { if (details.name == null || details.name.isEmpty()) {
details.name = "UNKNOWN"; details.name = "UNKNOWN";
} }
details.uuid = getXmlString(serverInfo, "uniqueid"); // UUID is mandatory to determine which machine is responding
details.macAddress = getXmlString(serverInfo, "mac"); details.uuid = getXmlString(serverInfo, "uniqueid", true);
details.localAddress = getXmlString(serverInfo, "LocalIP");
// This may be null, but that's okay details.macAddress = getXmlString(serverInfo, "mac", false);
details.remoteAddress = getXmlString(serverInfo, "ExternalIP"); details.localAddress = getXmlString(serverInfo, "LocalIP", false);
// This is missing on on recent GFE versions
details.remoteAddress = getXmlString(serverInfo, "ExternalIP", false);
// This has some extra logic to always report unpaired if the pinned cert isn't there
details.pairState = getPairState(serverInfo); details.pairState = getPairState(serverInfo);
details.runningGameId = getCurrentGame(serverInfo); details.runningGameId = getCurrentGame(serverInfo);
// We could reach it so it's online // We could reach it so it's online
@@ -416,7 +424,8 @@ public class NvHTTP {
} }
public String getServerVersion(String serverInfo) throws XmlPullParserException, IOException { public String getServerVersion(String serverInfo) throws XmlPullParserException, IOException {
return getXmlString(serverInfo, "appversion"); // appversion is present in all supported GFE versions
return getXmlString(serverInfo, "appversion", true);
} }
public PairingManager.PairState getPairState() throws IOException, XmlPullParserException { public PairingManager.PairState getPairState() throws IOException, XmlPullParserException {
@@ -424,15 +433,14 @@ public class NvHTTP {
} }
public PairingManager.PairState getPairState(String serverInfo) throws IOException, XmlPullParserException { public PairingManager.PairState getPairState(String serverInfo) throws IOException, XmlPullParserException {
if (!NvHTTP.getXmlString(serverInfo, "PairStatus").equals("1")) { // appversion is present in all supported GFE versions
return PairState.NOT_PAIRED; return NvHTTP.getXmlString(serverInfo, "PairStatus", true).equals("1") ?
} PairState.PAIRED : PairState.NOT_PAIRED;
return PairState.PAIRED;
} }
public long getMaxLumaPixelsH264(String serverInfo) throws XmlPullParserException, IOException { public long getMaxLumaPixelsH264(String serverInfo) throws XmlPullParserException, IOException {
String str = getXmlString(serverInfo, "MaxLumaPixelsH264"); // MaxLumaPixelsH264 wasn't present on old GFE versions
String str = getXmlString(serverInfo, "MaxLumaPixelsH264", false);
if (str != null) { if (str != null) {
return Long.parseLong(str); return Long.parseLong(str);
} else { } else {
@@ -441,7 +449,8 @@ public class NvHTTP {
} }
public long getMaxLumaPixelsHEVC(String serverInfo) throws XmlPullParserException, IOException { public long getMaxLumaPixelsHEVC(String serverInfo) throws XmlPullParserException, IOException {
String str = getXmlString(serverInfo, "MaxLumaPixelsHEVC"); // MaxLumaPixelsHEVC wasn't present on old GFE versions
String str = getXmlString(serverInfo, "MaxLumaPixelsHEVC", false);
if (str != null) { if (str != null) {
return Long.parseLong(str); return Long.parseLong(str);
} else { } else {
@@ -458,7 +467,8 @@ public class NvHTTP {
// Bit 10: HEVC Main10 4:4:4 // Bit 10: HEVC Main10 4:4:4
// Bit 11: ??? // Bit 11: ???
public long getServerCodecModeSupport(String serverInfo) throws XmlPullParserException, IOException { public long getServerCodecModeSupport(String serverInfo) throws XmlPullParserException, IOException {
String str = getXmlString(serverInfo, "ServerCodecModeSupport"); // ServerCodecModeSupport wasn't present on old GFE versions
String str = getXmlString(serverInfo, "ServerCodecModeSupport", false);
if (str != null) { if (str != null) {
return Long.parseLong(str); return Long.parseLong(str);
} else { } else {
@@ -467,16 +477,18 @@ public class NvHTTP {
} }
public String getGpuType(String serverInfo) throws XmlPullParserException, IOException { public String getGpuType(String serverInfo) throws XmlPullParserException, IOException {
return getXmlString(serverInfo, "gputype"); // ServerCodecModeSupport wasn't present on old GFE versions
return getXmlString(serverInfo, "gputype", false);
} }
public String getGfeVersion(String serverInfo) throws XmlPullParserException, IOException { public String getGfeVersion(String serverInfo) throws XmlPullParserException, IOException {
return getXmlString(serverInfo, "GfeVersion"); // ServerCodecModeSupport wasn't present on old GFE versions
return getXmlString(serverInfo, "GfeVersion", false);
} }
public boolean supports4K(String serverInfo) throws XmlPullParserException, IOException { public boolean supports4K(String serverInfo) throws XmlPullParserException, IOException {
// Only allow 4K on GFE 3.x // Only allow 4K on GFE 3.x. GfeVersion wasn't present on very old versions of GFE.
String gfeVersionStr = getXmlString(serverInfo, "GfeVersion"); String gfeVersionStr = getXmlString(serverInfo, "GfeVersion", false);
if (gfeVersionStr == null || gfeVersionStr.startsWith("2.")) { if (gfeVersionStr == null || gfeVersionStr.startsWith("2.")) {
return false; return false;
} }
@@ -488,10 +500,8 @@ public class NvHTTP {
// GFE 2.8 started keeping currentgame set to the last game played. As a result, it no longer // GFE 2.8 started keeping currentgame set to the last game played. As a result, it no longer
// has the semantics that its name would indicate. To contain the effects of this change as much // has the semantics that its name would indicate. To contain the effects of this change as much
// as possible, we'll force the current game to zero if the server isn't in a streaming session. // as possible, we'll force the current game to zero if the server isn't in a streaming session.
String serverState = getXmlString(serverInfo, "state"); if (getXmlString(serverInfo, "state", true).endsWith("_SERVER_BUSY")) {
if (serverState != null && serverState.endsWith("_SERVER_BUSY")) { return Integer.parseInt(getXmlString(serverInfo, "currentgame", true));
String game = getXmlString(serverInfo, "currentgame");
return Integer.parseInt(game);
} }
else { else {
return 0; return 0;
@@ -679,9 +689,9 @@ public class NvHTTP {
(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); false);
String gameSession = getXmlString(xmlStr, "gamesession"); if (!getXmlString(xmlStr, "gamesession", true).equals("0")) {
if (gameSession != null && !gameSession.equals("0")) { // sessionUrl0 will be missing for older GFE versions
context.rtspSessionUrl = getXmlString(xmlStr, "sessionUrl0"); context.rtspSessionUrl = getXmlString(xmlStr, "sessionUrl0", false);
return true; return true;
} }
else { else {
@@ -695,9 +705,9 @@ public class NvHTTP {
"&rikeyid="+context.riKeyId + "&rikeyid="+context.riKeyId +
"&surroundAudioInfo=" + context.streamConfig.getAudioConfiguration().getSurroundAudioInfo(), "&surroundAudioInfo=" + context.streamConfig.getAudioConfiguration().getSurroundAudioInfo(),
false); false);
String resume = getXmlString(xmlStr, "resume"); if (!getXmlString(xmlStr, "resume", true).equals("0")) {
if (Integer.parseInt(resume) != 0) { // sessionUrl0 will be missing for older GFE versions
context.rtspSessionUrl = getXmlString(xmlStr, "sessionUrl0"); context.rtspSessionUrl = getXmlString(xmlStr, "sessionUrl0", false);
return true; return true;
} }
else { else {
@@ -707,8 +717,7 @@ public class NvHTTP {
public boolean quitApp() throws IOException, XmlPullParserException { public boolean quitApp() throws IOException, XmlPullParserException {
String xmlStr = openHttpConnectionToString(baseUrlHttps + "/cancel?" + buildUniqueIdUuidString(), false); String xmlStr = openHttpConnectionToString(baseUrlHttps + "/cancel?" + buildUniqueIdUuidString(), false);
String cancel = getXmlString(xmlStr, "cancel"); if (getXmlString(xmlStr, "cancel", true).equals("0")) {
if (Integer.parseInt(cancel) == 0) {
return false; return false;
} }

View File

@@ -68,7 +68,8 @@ public class PairingManager {
private X509Certificate extractPlainCert(String text) throws XmlPullParserException, IOException private X509Certificate extractPlainCert(String text) throws XmlPullParserException, IOException
{ {
String certText = NvHTTP.getXmlString(text, "plaincert"); // Plaincert may be null if another client is already trying to pair
String certText = NvHTTP.getXmlString(text, "plaincert", false);
if (certText != null) { if (certText != null) {
byte[] certBytes = hexToBytes(certText); byte[] certBytes = hexToBytes(certText);
@@ -208,7 +209,7 @@ public class PairingManager {
"/pair?"+http.buildUniqueIdUuidString()+"&devicename=roth&updateState=1&phrase=getservercert&salt="+ "/pair?"+http.buildUniqueIdUuidString()+"&devicename=roth&updateState=1&phrase=getservercert&salt="+
bytesToHex(salt)+"&clientcert="+bytesToHex(pemCertBytes), bytesToHex(salt)+"&clientcert="+bytesToHex(pemCertBytes),
false); false);
if (!NvHTTP.getXmlString(getCert, "paired").equals("1")) { if (!NvHTTP.getXmlString(getCert, "paired", true).equals("1")) {
return PairState.FAILED; return PairState.FAILED;
} }
@@ -232,13 +233,13 @@ public class PairingManager {
String challengeResp = http.openHttpConnectionToString(http.baseUrlHttp + String challengeResp = http.openHttpConnectionToString(http.baseUrlHttp +
"/pair?"+http.buildUniqueIdUuidString()+"&devicename=roth&updateState=1&clientchallenge="+bytesToHex(encryptedChallenge), "/pair?"+http.buildUniqueIdUuidString()+"&devicename=roth&updateState=1&clientchallenge="+bytesToHex(encryptedChallenge),
true); true);
if (!NvHTTP.getXmlString(challengeResp, "paired").equals("1")) { if (!NvHTTP.getXmlString(challengeResp, "paired", true).equals("1")) {
http.openHttpConnectionToString(http.baseUrlHttp + "/unpair?"+http.buildUniqueIdUuidString(), true); http.openHttpConnectionToString(http.baseUrlHttp + "/unpair?"+http.buildUniqueIdUuidString(), true);
return PairState.FAILED; return PairState.FAILED;
} }
// Decode the server's response and subsequent challenge // Decode the server's response and subsequent challenge
byte[] encServerChallengeResponse = hexToBytes(NvHTTP.getXmlString(challengeResp, "challengeresponse")); byte[] encServerChallengeResponse = hexToBytes(NvHTTP.getXmlString(challengeResp, "challengeresponse", true));
byte[] decServerChallengeResponse = decryptAes(encServerChallengeResponse, aesKey); byte[] decServerChallengeResponse = decryptAes(encServerChallengeResponse, aesKey);
byte[] serverResponse = Arrays.copyOfRange(decServerChallengeResponse, 0, hashAlgo.getHashLength()); byte[] serverResponse = Arrays.copyOfRange(decServerChallengeResponse, 0, hashAlgo.getHashLength());
@@ -251,13 +252,13 @@ public class PairingManager {
String secretResp = http.openHttpConnectionToString(http.baseUrlHttp + String secretResp = http.openHttpConnectionToString(http.baseUrlHttp +
"/pair?"+http.buildUniqueIdUuidString()+"&devicename=roth&updateState=1&serverchallengeresp="+bytesToHex(challengeRespEncrypted), "/pair?"+http.buildUniqueIdUuidString()+"&devicename=roth&updateState=1&serverchallengeresp="+bytesToHex(challengeRespEncrypted),
true); true);
if (!NvHTTP.getXmlString(secretResp, "paired").equals("1")) { if (!NvHTTP.getXmlString(secretResp, "paired", true).equals("1")) {
http.openHttpConnectionToString(http.baseUrlHttp + "/unpair?"+http.buildUniqueIdUuidString(), true); http.openHttpConnectionToString(http.baseUrlHttp + "/unpair?"+http.buildUniqueIdUuidString(), true);
return PairState.FAILED; return PairState.FAILED;
} }
// Get the server's signed secret // Get the server's signed secret
byte[] serverSecretResp = hexToBytes(NvHTTP.getXmlString(secretResp, "pairingsecret")); byte[] serverSecretResp = hexToBytes(NvHTTP.getXmlString(secretResp, "pairingsecret", true));
byte[] serverSecret = Arrays.copyOfRange(serverSecretResp, 0, 16); byte[] serverSecret = Arrays.copyOfRange(serverSecretResp, 0, 16);
byte[] serverSignature = Arrays.copyOfRange(serverSecretResp, 16, 272); byte[] serverSignature = Arrays.copyOfRange(serverSecretResp, 16, 272);
@@ -285,7 +286,7 @@ public class PairingManager {
String clientSecretResp = http.openHttpConnectionToString(http.baseUrlHttp + String clientSecretResp = http.openHttpConnectionToString(http.baseUrlHttp +
"/pair?"+http.buildUniqueIdUuidString()+"&devicename=roth&updateState=1&clientpairingsecret="+bytesToHex(clientPairingSecret), "/pair?"+http.buildUniqueIdUuidString()+"&devicename=roth&updateState=1&clientpairingsecret="+bytesToHex(clientPairingSecret),
true); true);
if (!NvHTTP.getXmlString(clientSecretResp, "paired").equals("1")) { if (!NvHTTP.getXmlString(clientSecretResp, "paired", true).equals("1")) {
http.openHttpConnectionToString(http.baseUrlHttp + "/unpair?"+http.buildUniqueIdUuidString(), true); http.openHttpConnectionToString(http.baseUrlHttp + "/unpair?"+http.buildUniqueIdUuidString(), true);
return PairState.FAILED; return PairState.FAILED;
} }
@@ -293,7 +294,7 @@ public class PairingManager {
// 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.baseUrlHttps + String pairChallenge = http.openHttpConnectionToString(http.baseUrlHttps +
"/pair?"+http.buildUniqueIdUuidString()+"&devicename=roth&updateState=1&phrase=pairchallenge", true); "/pair?"+http.buildUniqueIdUuidString()+"&devicename=roth&updateState=1&phrase=pairchallenge", true);
if (!NvHTTP.getXmlString(pairChallenge, "paired").equals("1")) { if (!NvHTTP.getXmlString(pairChallenge, "paired", true).equals("1")) {
http.openHttpConnectionToString(http.baseUrlHttp + "/unpair?"+http.buildUniqueIdUuidString(), true); http.openHttpConnectionToString(http.baseUrlHttp + "/unpair?"+http.buildUniqueIdUuidString(), true);
return PairState.FAILED; return PairState.FAILED;
} }