(quick side question: if you were going to write an android aim client(just for yourself, not necessarily for mass consumption), is the server API the way to go? It's what I've been focusing on.)
I've been having trouble getting a session started using the aim server API for some reason. I've gone so far as to copy code off the official aim developer forums and still got the same error(statuscode: 401, statusdetailcode:1014). It's an undocumented error code but I believe I read on the official forums that it's got something to do with the URL signing process.
All of the code here is pretty much slapped together just to get a test up and running, but hopefully the intent is clear enough. I'm not ruling any code out, especially since I've never written in java with any level of seriousness, so here's the code I use to send an API call to the server(passed in are the strings urlstring, command, and method):
buffer = new byte[8192];
url = new URL(urlstring);
connection = (HttpURLConnection)url.openConnection();
connection.setRequestMethod(method);
connection.setDoOutput(true);
connection.setUseCaches (false);
connection.setDoInput(true);
DataOutputStream output = new DataOutputStream(connection.getOutputStream());
output.writeBytes(command);
output.flush();
output.close();
connection.connect();
responseBodyStream = connection.getInputStream();
responseBody = new StringBuffer();
while ((read = responseBodyStream.read(buffer)) != -1)
{
responseBody.append(new String(buffer, 0, read));
}
connection.disconnect();
return responseBody.toString();
I start with a clientLogin command that looks something like this:
response = APICall("https://api.screenname.aol.com/auth/clientLogin","devId=DEVKEY&f=JSON&s=EXAMPLESCREENNAME&pwd=LAMEPASS","POST");
I then tokenize the response and grab the authorization token, the session secret, and the timestamp like so:
StringTokenizer st = new StringTokenizer(response, "=&",false);
String temp = st.nextToken();
while(st.hasMoreTokens())
{
if(temp.equals("token_a"))
authcode = st.nextToken();
else if(temp.equals("sessionSecret"))
sessionsecret = st.nextToken().getBytes();
else if(temp.equals("hostTime"))
{
aimtime = st.nextToken();
}
temp = st.nextToken();
}
Everything runs perfectly up until here(as far as I can tell anyhow). I've toyed with calculating the time difference between aim's timestamp response and local time, but for simplicity's sake, I'm just going to use their timestamp from here on out. In order to create the session key, we need to do some crypto stuff. I use the javax.crypto.Mac stuff for the sha256 part of it, and org.apache.commons for the Base64 conversion. I've tested it against C#'s built-int crypto and it works as intended, but just for completeness(I left in the try/catches this time out of laziness, but believe me, this isn't throwing any exceptions):
public byte[] HMAC_SHA256_BASE64(byte[] key, byte[] data)
{
byte[] finalbytes=null;
try {
Mac hmac = Mac.getInstance("HMAC/SHA256");
hmac.init(new SecretKeySpec(key,hmac.getAlgorithm()));
finalbytes = hmac.doFinal(data);
} catch (NoSuchAlgorithmException e) {
// TODO Auto-generated catch block
e.printStackTrace();
return null;
} catch (InvalidKeyException e) {
// TODO Auto-generated catch block
e.printStackTrace();
return null;
}
Base64 b = new Base64();
return b.encode(finalbytes);
}
This gets called like this to create the session key:
byte[] sessionkey;
sessionkey = HMAC_SHA256_BASE64("LAMEPASS".getBytes(),sessionsecret);
In the aim server API docs, there is an example password and session secret, along with the resulting session key. Everything here checks out. Next up, I build the next request/command.. Most of this stuff is pretty much pasted straight out of the aim development forums. The claim was that it was working code. I wrote my own version first, but out of frustration went to a direct copy and paste, and yet here I am. Here's the code:
String baseurl = "http://api.oscar.aol.com/aim/startSession";
String signedParams = null;
try {
StringBuffer paramString = new StringBuffer();
paramString.append("a=" + authcode);
paramString.append("&clientName=" +
java.net.URLEncoder.encode("HostileIM", "UTF-8"));
paramString.append("&clientVersion=" +
java.net.URLEncoder.encode("1", "UTF-8"));
paramString.append("&events=" +
java.net.URLEncoder.encode("buddylist,clientError,im,offlineIM,presence,sentIM", "UTF-8"));
paramString.append("&f=xml");
paramString.append("&k=" + "DEVKEY");
paramString.append("&ts=" + aimtime);
signedParams = signRequest("GET", baseurl, paramString.toString());
} catch (UnsupportedEncodingException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
Here's signRequest:
private String signRequest(String postType, String baseURL, String queryString)
{
try
{
String fullRequest = new String(postType + "&" +
java.net.URLEncoder.encode(baseURL, "UTF-8") + "&" +
java.net.URLEncoder.encode(queryString, "UTF-8"));
String signature = new String(HMAC_SHA256_BASE64(sessionkey, fullRequest.getBytes()));
return queryString + "&sig_sha256=" + java.net.URLEncoder.encode(signature, "UTF-8");
}
catch(Exception ex)
{
System.out.println("Exception " + ex.toString());
return null;
}
}
Finally:
response = APICall("http://api.oscar.aol.com/aim/startSession",signedParams,"GET");
And this when I get the statusCode 401, statusDetailCode 1014.
I definitely should have done this all first outside of android(and in a language I'm more comfortable with), but I've read the docs many times, and I'm really just not sure what I'm missing. Hopefully someone can help me out. Thanks!