How android application works.
- First, connect to socket (com/nice/hki/protocol/ConnectionsManager.java):
private Socket _openSocket(String host, int port) throws Exception {
Socket socket = null;
for (Network nw : this.cm.getAllNetworks()) {
int type = this.cm.getNetworkInfo(nw).getType();
if (type == 1 || type == 0 || type == 4 || type == 5) {
socket = nw.getSocketFactory().createSocket();
break;
}
}
if (socket == null) {
throw new Exception("No network available!");
}
socket.setReuseAddress(true);
socket.connect(new InetSocketAddress(host, port), 5000);
TrustManager[] trustAllCerts = new TrustManager[]{new TrustAllManager()};
SSLContext sslContext = SSLContext.getInstance("SSL");
sslContext.init(null, trustAllCerts, new SecureRandom());
socket = sslContext.getSocketFactory().createSocket(socket, host, port, true);
((SSLSocket) socket).startHandshake();
return socket;
}
- Now we can send requests. Most important function is buildCommand (com/nice/hki/protocol/commands/CommandHandler.java):
private synchronized Command buildCommand(CommandType commandType, String target, String protocol, String body) throws Exception {
String commandID;
String startRequest;
commandID = generateCommandID(target);
startRequest = buildStartRequest(commandID, commandType, Plugin.APP_UDID, target, protocol);
return new Command(commandID, target, commandType, protocol, startRequest + body + (isSignNeeded(commandType, protocol) ? buildSign(target, commandType, startRequest + body) : "") + buildEndRequest());
}
Generating CommandID, it depends on session ID:
private static int cmdSequence = 1;
...
private String generateCommandID(String target) {
Session session = ConnectionsManager.getInstance().getActiveConnection(target).getSession(target);
Integer sessionID = Integer.valueOf(0);
if (!(session == null || session.getSessionID() == null)) {
sessionID = session.getSessionID();
}
int i = cmdSequence;
cmdSequence = i + 1;
return String.valueOf((i << 8) | (sessionID.intValue() & 255));
}
Function build header and footer:
private String buildStartRequest(String commandID, CommandType commandType, String source, String target, String protocol) {
return String.format("<Request id=\"%s\" source=\"%s\" target=\"%s\" gw=\"gwID\" protocolType=\"%s\" protocolVersion=\"%s\" type=\"%s\">\r\n", new Object[]{commandID, source, target, protocol, "1.0", commandType});
}
private String buildEndRequest() {
return "</Request>\r\n";
}
Checks if a signature is needed:
private boolean isSignNeeded(CommandType commandType, String protocol) {
return !protocol.equals("CLOUD") && commandType.needsSign();
}
And signature building:
private String buildSign(String target, CommandType commandType, String xmlCommand) throws Exception {
Session session = ConnectionsManager.getInstance().getActiveSession(target);
if (session == null || session.getSessionPassword() == null) {
throw new Exception(Constants.ERROR_NO_SESSION);
}
return String.format("<Sign>%s</Sign>", new Object[]{session.signRequest(xmlCommand)});
}
signRequest we can find in Session.java (com/nice/hki/authentication/Session.java):
public String signRequest(String xmlRequest) throws Exception {
try {
byte[] msgHash = Utils.sha256(xmlRequest.getBytes());
ByteArrayOutputStream baos = new ByteArrayOutputStream();
baos.write(msgHash);
baos.write(this.sessionPassword);
byte[] sign = Utils.sha256(msgHash, this.sessionPassword);
return Utils.base64Encode(sign);
} catch (Exception e) {
Log.e(Constants.LOG_TAG, "Error signing request. " + e.getMessage());
return "";
}
}
Utils.sha256 we can find in com/nice/hki/Utils.java:
public static byte[] sha256(byte[]... values) throws Exception {
MessageDigest digest = MessageDigest.getInstance("SHA-256");
for (byte[] data : values) {
digest.update(data);
}
return digest.digest();
}
this.sessionPassword generated in setServerChallenge function in Session.java:
byte[] serverChallengeArr = Utils.hexStringToByteArray(serverChallenge);
byte[] clientChallengeArr = Utils.hexStringToByteArray(this.clientChallenge);
this.sessionPassword = Utils.sha256(this.pairingPassword, Utils.invertArray(serverChallengeArr), Utils.invertArray(clientChallengeArr));
this.pairingPassword is base64 decoded pwd from pair request. serverChallenge is SC from my previous message. this.clientChallenge is CC and randomly generated:
private String clientChallenge = Utils.intToHexString(new Random().nextInt());
Utils.invertArray, Utils.hexStringToByteArray and Utils.intToHexString:
public static byte[] hexStringToByteArray(String s) {
int len = s.length();
byte[] data = new byte[(len / 2)];
for (int i = 0; i < len; i += 2) {
data[i / 2] = (byte) ((Character.digit(s.charAt(i), 16) << 4) + Character.digit(s.charAt(i + 1), 16));
}
return data;
}
public static byte[] invertArray(byte[] data) {
byte[] result = new byte[data.length];
int i = data.length - 1;
int c = 0;
while (i >= 0) {
int c2 = c + 1;
result[c] = data[i];
i--;
c = c2;
}
return result;
}
public static String intToHexString(int value) {
return String.format("%08x", new Object[]{Integer.valueOf(value)}).toUpperCase();
}
Before sending result from buildCommand function to socket we need to do that:
result = ('\u0002' + buildCommand.result + '\u0003').getBytes("UTF-8"));
Thats all what we need to connect and sending request.