• Home
  • Consultancy
  • Contact
  • Connecting to elggVoices with C

    February 28th, 2008 by Marcus Povey

    The other week I posted a PHP example for connecting to the elggVoices API. For those of you interested in developing desktop applications, here is some example code for doing the same thing in C/C++.

    This code is designed to be built into a library (either a static or dynamic) but could probably be directly included without too much trouble. It is tested and works fine on Linux, but should also build as a Windows DLL without too much trouble. Windows doesn’t support gettimeofday, so this has been rewritten.

    You will also need to install libcurl and cryptopp, on Debian/Ubuntu these are apt-gettable.

    searunner.h

    /**
    * @file searunner.h Searunner C++ Client library.
    * This is the main include file for the Linux/Win32 client api for the
    * searunner social networking platform.
    * @author Marcus Povey <marcus@dushka.co.uk>
    */
    #ifndef SEARUNNER_H_
    #define SEARUNNER_H_

    /** Some definitions */
    #define VERSION "0.1.0"

    #ifdef WIN32
    // Windows Only

    #ifdef LIBSEARUNNERW32_EXPORTS
    #define LIBSEARUNNERW32_API __declspec(dllexport)
    #else
    #define LIBSEARUNNERW32_API __declspec(dllimport)
    #endif

    #include <winsock2.h>

    // Map snprintf to _snprintf
    #define snprintf _snprintf

    #endif

    #ifdef LINUX
    // Linux

    #define LIBSEARUNNERW32_API

    #include <sys/types.h>
    #include <sys/socket.h>
    #include <sys/time.h>
    #include <netinet/in.h>
    #include <unistd.h>
    #include <netdb.h>

    #endif

    /* Include crypto++ lib from www.cryptopp.com */
    #include <crypto++/cryptlib.h>

    /**
    * Create the configuration structure.
    */
    typedef struct {
    char apikey[33];
    char secret[33];
    char endpoint[255];
    char hmac_algo[8];
    char hash_algo[8];
    } SearunnerConfig;


    extern LIBSEARUNNERW32_API SearunnerConfig hSearunnerConfig;

    /** Get call */
    #define SEARUNNER_CALLMODE_GET 0
    /** Post call */
    #define SEARUNNER_CALLMODE_POST 1

    /** CURL Write function callback type */
    typedef size_t (*CURLOPT_WRITEFUNCTION_CALLBACK)( void *ptr, size_t size, size_t nmemb, void *stream);

    /**
    * Initialisation the API client.
    * This function initialises the api client configuration structure ready for use.
    * @param api_key The API key you have been provided with.
    * @param secret The secret key you have been provided with.
    * @param endpoint_url The API Endpoint.
    */
    LIBSEARUNNERW32_API int searunner_initialise_api(const char * api_key, const char * secret, const char * endpoint_url);

    /**
    * Raw POST request.
    * @param parameters List of parameters.
    * @param postData Optional post data in post call or null if this is a GET call.
    * @param postdata_length The length of the data being sent.
    * @param content_type Optional content type or null if this is a GET call.
    * @param buffer The buffer to write the result into [OUT]
    * @param buffer_size The size of the buffer
    * @return The amount actually written.
    */
    LIBSEARUNNERW32_API int __searunner_post_call(const char * parameters, unsigned char * postData, int postdata_length, char * content_type, CURLOPT_WRITEFUNCTION_CALLBACK curl_read_func);

    /**
    * Raw GET request.
    * @param parameters List of parameters.
    * @param buffer The buffer to write the result into [OUT]
    * @param buffer_size The size of the buffer
    * @return The amount actually written.
    */
    LIBSEARUNNERW32_API int __searunner_get_call(const char * parameters, CURLOPT_WRITEFUNCTION_CALLBACK curl_read_func);

    /**
    * Make a searunner call.
    * @param method Method call.
    * @param parameters List of parameters.
    * @param postData Optional post data in post call or null if this is a GET call.
    * @param postdata_length The length of the data being sent.
    * @param content_type Optional content type or null if this is a GET call.
    * @param buffer The buffer to write the result into [OUT]
    * @param buffer_size The size of the buffer
    * @return The amount actually written.
    */
    LIBSEARUNNERW32_API int __searunner_call(int method, const char * parameters, unsigned char * postData, int postdata_length, char * content_type, CURLOPT_WRITEFUNCTION_CALLBACK curl_read_func);

    #endif /*SEARUNNER_H_*/

    searunner.cpp

    /**
    * @file searunner.cpp
    * Main functions and Win32 DLL entrypoint.
    * @author Marcus Povey <marcus@dushka.co.uk>
    */

    #include "searunner.h"
    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    #include <crypto++/sha.h>
    #include <crypto++/hmac.h>
    #include <crypto++/hex.h>
    #include <crypto++/filters.h>
    #include <curl/curl.h>
    #include <time.h>

    /* Configuration structure */
    LIBSEARUNNERW32_API SearunnerConfig hSearunnerConfig;

    /* Utility functions used internally */
    #ifdef WIN32

    #if defined(_MSC_VER) || defined(_MSC_EXTENSIONS)
    #define DELTA_EPOCH_IN_MICROSECS 11644473600000000Ui64
    #else
    #define DELTA_EPOCH_IN_MICROSECS 11644473600000000ULL
    #endif

    struct timezone
    {
    int tz_minuteswest; /* minutes W of Greenwich */
    int tz_dsttime; /* type of dst correction */
    };

    /**
    * Replacement gettimeofday function for windows.
    * Obtained from http://www.openasthra.com/c-tidbits/gettimeofday-function-for-windows/
    */
    int gettimeofday(struct timeval *tv, struct timezone *tz)
    {
    FILETIME ft;
    unsigned __int64 tmpres = 0;
    static int tzflag;

    if (NULL != tv)
    {
    GetSystemTimeAsFileTime(&ft);

    tmpres |= ft.dwHighDateTime;
    tmpres <<= 32;
    tmpres |= ft.dwLowDateTime;

    /*converting file time to unix epoch*/
    tmpres /= 10; /*convert into microseconds*/
    tmpres -= DELTA_EPOCH_IN_MICROSECS;
    tv->tv_sec = (long)(tmpres / 1000000UL);
    tv->tv_usec = (long)(tmpres % 1000000UL);
    }

    if (NULL != tz)
    {
    if (!tzflag)
    {
    _tzset();
    tzflag++;
    }
    tz->tz_minuteswest = _timezone / 60;
    tz->tz_dsttime = _daylight;
    }

    return 0;
    }

    #endif

    #define MICRO_IN_SEC 1000000.00

    /**
    * Micro time function.
    *
    * Example usage:
    *
    * char oo[100];
    * microtime(oo);
    * printf(oo);
    *
    * @param buffer Where to output variables.
    */
    int microtime(char * buffer)
    {
    struct timeval tp;
    long sec = 0L;
    double msec = 0.0;
    char ret[100];

    if (gettimeofday((struct timeval *) &tp, (NULL)) == 0)
    {
    msec = (double) (tp.tv_usec / MICRO_IN_SEC);
    sec = tp.tv_sec;

    if (msec >= 1.0) msec -= (long) msec;

    int msec2 = (int)(msec*100);

    snprintf(ret, 100, "%ld.%d", sec, msec2);
    strcpy(buffer, ret);
    return 0;
    }

    return -1;
    }

    /**
    * Calculate a hmac for the given data.
    *
    * Example usage:
    *
    * char oo [2*CryptoPP::SHA1::DIGESTSIZE+1];
    * calculate_hmac(&hSearunnerConfig, "1", "2", "3", oo, 2*CryptoPP::SHA1::DIGESTSIZE+1);
    * printf(oo);
    *
    * @param time String representation of time
    * @param get_variables String of parameters
    * @param post_hash The hash
    * @param buffer The buffer
    * @param bugger_size Size of the buffer
    */
    int calculate_hmac(
    const char * time,
    const char * get_variables,
    const char * post_hash,

    char * buffer, int buffer_size)
    {
    memset(buffer,0,buffer_size);

    if (strcmp(hSearunnerConfig.hmac_algo, "sha1")==0)
    {
    // Sha1

    // Check the buffer size before we do anything
    if (buffer_size<2*CryptoPP::SHA1::DIGESTSIZE + 1) return -1;

    // Initialise memory
    unsigned char tmp[ 2*CryptoPP::SHA1::DIGESTSIZE + 1 ];
    memset(tmp,0,2*CryptoPP::SHA1::DIGESTSIZE + 1);

    // Compute HMAC
    CryptoPP::HMAC<CryptoPP::SHA1> hmac((const byte*)hSearunnerConfig.secret, strlen(hSearunnerConfig.secret));
    hmac.Update((const byte*)time, strlen(time));
    hmac.Update((const byte*)hSearunnerConfig.apikey, strlen(hSearunnerConfig.apikey));
    hmac.Update((const byte*)get_variables, strlen(get_variables));
    if (post_hash) hmac.Update((const byte*)post_hash, strlen(post_hash));

    hmac.Final((byte*)tmp);

    // hex encode
    CryptoPP::HexEncoder encoder;
    encoder.Attach(new CryptoPP::ArraySink((byte*)buffer, 2*CryptoPP::SHA1::DIGESTSIZE));
    encoder.Put(tmp, sizeof(tmp));
    encoder.MessageEnd();

    // Convert to lower case
    for (int n = 0; n<buffer_size; n++)
    if (buffer[n]!=0) buffer[n] = tolower(buffer[n]);

    return 0;
    }

    return -1;
    }

    /*
    * Calculate the hash of some POST data.
    * TODO: Tidy this up
    * TODO: Support more algorithms
    *
    * Example usage:
    *
    * char oo [2*CryptoPP::MD5::DIGESTSIZE+1];
    * calculate_POST_hash(&hSearunnerConfig, (unsigned char*)"test", 4, oo, 2*CryptoPP::MD5::DIGESTSIZE+1);
    * printf(oo);
    *
    * @param postdata The data
    * @param postlength The length
    * @param outputbuffer The buffer
    * @param outputbuffer_size The size of the output buffer
    */
    int calculate_POST_hash(const unsigned char * postdata, int postlength, char * outputbuffer, int outputbuffer_size)
    {
    memset(outputbuffer,0,outputbuffer_size);

    if (strcmp(hSearunnerConfig.hash_algo, "sha1")==0)
    {
    // Check the buffer size before we do anything
    if (outputbuffer_size<2*CryptoPP::SHA1::DIGESTSIZE + 1) return -1;


    // Initialise memory
    unsigned char tmp[ 2*CryptoPP::SHA1::DIGESTSIZE + 1 ];
    memset(tmp,0,2*CryptoPP::SHA1::DIGESTSIZE + 1);

    // Calculate the digest
    CryptoPP::SHA1 hash;
    hash.CalculateDigest((byte*)tmp, (const byte*)postdata, postlength);

    // hex encode
    CryptoPP::HexEncoder encoder;
    encoder.Attach(new CryptoPP::ArraySink((byte*)outputbuffer, 2*CryptoPP::SHA1::DIGESTSIZE));
    encoder.Put(tmp, sizeof(tmp));
    encoder.MessageEnd();

    // Convert to lower case
    for (int n = 0; n<outputbuffer_size; n++)
    if (outputbuffer[n]!=0) outputbuffer[n] = tolower(outputbuffer[n]);

    return 0;
    }

    return -1;
    }

    /*************************************/

    LIBSEARUNNERW32_API int searunner_initialise_api(const char * api_key, const char * secret, const char * endpoint_url)
    {
    strcpy(hSearunnerConfig.apikey, api_key);
    strcpy(hSearunnerConfig.secret, secret);
    strcpy(hSearunnerConfig.endpoint, endpoint_url);
    strcpy(hSearunnerConfig.hmac_algo, "sha1");
    strcpy(hSearunnerConfig.hash_algo, "sha1");
    return 0;
    }

    LIBSEARUNNERW32_API int __searunner_call(int method,
    const char * parameters,
    unsigned char * postData,
    int postdata_length,
    char * content_type,
    CURLOPT_WRITEFUNCTION_CALLBACK curl_read_func)
    {
    char url[2048] = {""};
    char posthash[256] = {""};
    char hmac[256] = {""};
    char tmp[100] = {""};

    struct curl_slist *headerlist = NULL;

    // Get the microtime for now
    char time[100];
    microtime(time);

    // Construct URL
    strcpy(url, hSearunnerConfig.endpoint);
    strcat(url, "?");
    strcat(url, parameters);

    // If post data, hash it
    if (postdata_length > 0)
    calculate_POST_hash(postData, postdata_length, posthash, 256);

    // Constract the hmac
    calculate_hmac(time, parameters, posthash, hmac, 256);

    // Initialise CURL
    CURL * curl = curl_easy_init();
    if(curl) {

    // Set headers
    sprintf(tmp, "X-Searunner-apikey: %s", hSearunnerConfig.apikey);
    headerlist = curl_slist_append(headerlist, tmp);

    sprintf(tmp, "X-Searunner-time: %s", time);
    headerlist = curl_slist_append(headerlist, tmp);

    sprintf(tmp, "X-Searunner-hmac-algo: %s", hSearunnerConfig.hmac_algo);
    headerlist = curl_slist_append(headerlist, tmp);

    sprintf(tmp, "X-Searunner-hmac: %s", hmac);
    headerlist = curl_slist_append(headerlist, tmp);

    // Set options
    if (method == SEARUNNER_CALLMODE_POST)
    {
    curl_easy_setopt(curl, CURLOPT_POST, 1); // Use POST

    sprintf(tmp, "X-Searunner-posthash: %s", posthash);
    headerlist = curl_slist_append(headerlist, tmp);

    sprintf(tmp, "X-Searunner-posthash-algo: %s", hSearunnerConfig.hash_algo);
    headerlist = curl_slist_append(headerlist, tmp);

    sprintf(tmp, "Content-type: %s", content_type);
    headerlist = curl_slist_append(headerlist, tmp);

    sprintf(tmp, "Content-Length: %d", postdata_length);
    headerlist = curl_slist_append(headerlist, tmp);

    curl_easy_setopt(curl, CURLOPT_POSTFIELDS, postData); // Set the post data
    curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE, postdata_length);
    }

    curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headerlist); // Set the headers
    curl_easy_setopt(curl,CURLOPT_URL, url); // Set the url
    curl_easy_setopt(curl, CURLOPT_READFUNCTION, curl_read_func); // Use our own read function

    // Make call
    CURLcode res = curl_easy_perform(curl);

    // Clean up
    curl_easy_cleanup(curl);

    return res;
    }

    return 0;
    }

    LIBSEARUNNERW32_API int __searunner_post_call(const char * parameters, unsigned char * postData, int postdata_length, char * content_type, CURLOPT_WRITEFUNCTION_CALLBACK curl_read_func)
    {
    return __searunner_call(SEARUNNER_CALLMODE_POST, parameters, postData, postdata_length, content_type, curl_read_func);
    }

    LIBSEARUNNERW32_API int __searunner_get_call(const char * parameters, CURLOPT_WRITEFUNCTION_CALLBACK curl_read_func)
    {
    return __searunner_call(SEARUNNER_CALLMODE_GET, parameters, NULL, 0, NULL, curl_read_func);
    }

    #ifdef WIN32

    /*
    Windows Only DLL entrypoint.
    */

    BOOL APIENTRY DllMain( HANDLE hModule,
    DWORD ul_reason_for_call,
    LPVOID lpReserved
    )
    {
    switch (ul_reason_for_call)
    {
    case DLL_PROCESS_ATTACH:
    case DLL_THREAD_ATTACH:
    case DLL_THREAD_DETACH:
    case DLL_PROCESS_DETACH:
    break;
    }
    return TRUE;
    }


    #endif

    @commands on elggVoices

    February 26th, 2008 by Marcus Povey

    Some of you who have been using elggVoices recently, may have noticed some rather odd looking shouts appearing in the channels.

    Shouts beginning with “@” can be used to execute commands in a channel – especially useful when used remotely via SMS.

    I thought it might be useful to give you a quick run down of the commands currently available. I say “currently”, since we are adding to this all the time.

    SMS and Website

    • @status message: Updates your status on a channel.
    • @sleep on|off: Set your sleep status on and off, when @sleep is on you won’t receive SMS messages.
    • @location city: Updates your location.
    • @country country: Updates your country.

    SMS only

    • @signup desired_username your_email: Send this to a channel’s SMS number and you will create a elggVoices account and automatically be signed up to the channel. Your password will be emailed to your email address.
    • @join: Send this to a channel’s SMS number will join the channel.

    Just a few commands for now, but there will be more to come.

    Getting started with the elggVoices API (part 4 – Putting it all together)

    February 25th, 2008 by Marcus Povey

    In yesterday’s article, we took a look at the server’s reply format. You now have all the information you need to construct the core of a library and start making server requests.

    Here is some sample PHP code for making POST and GET style queries. This library will let you make a query, and return a stdClass object containing the serialised fields as described yesterday, from this you can build wrapper functions for the methods.

    $API_CLIENT = new stdClass;

    // Configure your client
    $API_CLIENT->api_key = "your api key";
    $API_CLIENT->secret = "your secret key";
    $API_CLIENT->api_endpoint = "http://elggvoices.com/api/v1/";
    $API_CLIENT->hmac_algo = 'sha256';
    $API_CLIENT->postdata_hash_algo = 'md5';

    // Status variables we can query later
    $LAST_CALL = null;
    $LAST_CALL_RAW = "";
    $LAST_ERROR = null;

    /**
    * Generate our HMAC.
    */
    function calculate_hmac($algo, $time, $api_key, $secret_key, $get_variables, $post_hash = "")
    {
    $ctx = hash_init($algo, HASH_HMAC, $secret_key);
    hash_update($ctx, trim($time));
    hash_update($ctx, trim($api_key));
    hash_update($ctx, trim($get_variables));
    if (trim($post_hash)!="") hash_update($ctx, trim($post_hash));
    return hash_final($ctx);
    }

    /**
    * Generate our POST hash.
    */
    function calculate_posthash($postdata, $algo)
    {
    $ctx = hash_init($algo);
    hash_update($ctx, $postdata);
    return hash_final($ctx);
    }

    /**
    * Serialise HTTP headers.
    */
    function serialise_headers(array $headers)
    {
    $headers_str = "";
    foreach ($headers as $k => $v)
    $headers_str .= trim($k) . ": " . trim($v) . "\r\n";
    return trim($headers_str);
    }

    /**
    * Make a raw call.
    * @param array $method Method call parameters.
    * @param string $postdata Optional POST data.
    * @param string $content_type The content type.
    * @return stdClass
    */
    function call(array $method, $postdata = "", $content_type = 'application/octet-stream')
    {

    // Get the config
    global $API_CLIENT, $LAST_CALL, $LAST_CALL_RAW, $LAST_ERROR;

    $headers = array();
    $encoded_params = array();

    $time = microtime(true); // Get the current time in microseconds
    $request = ($postdata!="" ? "POST" : "GET"); // Get the request method, either post or get

    // Hard code the format - we're using PHP, so lets use PHP serialisation.
    $method['format'] = "php";

    // URL encode all the parameters
    foreach ($method as $k => $v){
    $encoded_params[] = urlencode($k).'='.urlencode($v);
    }

    $params = implode('&', $encoded_params);

    // Put together the query string
    $url = $API_CLIENT->api_endpoint."?". $params;

    // Construct headers
    $posthash = "";
    if ($request=='POST')
    {
    $posthash = calculate_posthash($postdata, $API_CLIENT->postdata_hash_algo);
    $headers['X-Searunner-posthash'] = $posthash;
    $headers['X-Searunner-posthash-algo'] = $API_CLIENT->postdata_hash_algo;
    $headers['Content-type'] = $content_type;
    $headers['Content-Length'] = strlen($postdata);
    }

    $headers['X-Searunner-apikey'] = $API_CLIENT->api_key;
    $headers['X-Searunner-time'] = $time;
    $headers['X-Searunner-hmac-algo'] = $API_CLIENT->hmac_algo;
    $headers['X-Searunner-hmac'] = calculate_hmac($API_CLIENT->hmac_algo,
    $time,
    $API_CLIENT->api_key,
    $API_CLIENT->secret,
    $params,
    $posthash
    );

    // Configure stream options
    $opts = array(
    'http'=>array(
    'method'=> $request,
    'header'=> serialise_headers($headers)
    )
    );

    // If this is a post request then set the content
    if ($request=='POST')
    $opts['http']['content'] = $postdata;

    // Set stream options
    $context = stream_context_create($opts);

    // Send the query and get the result and decode.
    $LAST_CALL_RAW = file_get_contents($url, false, $context);
    $LAST_CALL = unserialize($LAST_CALL_RAW);

    if (($LAST_CALL) && ($LAST_CALL->status!=0)) // Check to see if this was an error
    $LAST_ERROR = $LAST_CALL;

    return $LAST_CALL; // Return a stdClass containing the API result
    }

    // Example GET call
    $get = call(
    array (
    'method' => 'test.test',
    'variable1' => 1,
    'variable2' => "test string"
    )
    );

    // Example POST call
    $post = call(
    array (
    'method' => 'test.test',
    'variable1' => 1,
    'variable2' => "test string"
    ),
    "Some post data"
    );

    // Output
    echo "Example GET result: \n";
    print_r($get);

    echo "Example POST result: \n";
    print_r($post);

    Getting started with the elggVoices API (part 3 – The reply)

    February 24th, 2008 by Marcus Povey

    On Friday we discussed how to construct an API request, today we will discuss how to handle the response that the server returns.

    Server replies

    The elggVoices server will respond with a result packet containing a number of fields. The format of this reply depends on the format requested. From the manual:

    • ’status’: The status code, 0 for success or a non-zero error code.
    • ‘message’: If the status code is non-zero this contains a human readable
      explanation.
    • ‘result’: The mixed result of the execution, this may be a simple type
      (int, string etc), an array or even an object, depending on the API being
      called (see also result objects).
    • ‘runtime_errors’: If there have been any runtime errors picked up by
      the internal error handler during the execution of the command, these
      are given here in an array.

    The server result will be returned, serialised in the requested format (xml,php or json).

    Errors will often give details pointing to what went wrong, these can be helpful when debugging your application or when looking for support on the forums.

    Have a look at this XML formatted error message, XML success messages will be of a similar format:

    <SearunnerResult>
    <status type="integer">-1</status>
    <message type="string">Missing X-Searunner-apikey HTTP header</message>
    <result type="string">exception 'APIException' with message 'Missing X-Searunner-apikey HTTP header' in /home/liveshouts/html/lib/api/validation.php:109
    Stack trace:
    #0 /home/liveshouts/html/api/endpoints/rest.php(19): get_and_validate_api_headers()
    #1 {main}</result>
    <runtime_errors type="array">
    <array_item name="0" type="string" >DEBUG: 2008-02-24 18:38:19 (UTC): & quot;Undefined index: method& quot;
    in file /home/liveshouts/html/lib/engine/input.php (line 30)</array_item>
    <array_item name="1" type="string">DEBUG: 2008-02-24 18:38:19 (UTC): &quot;Undefined index: HTTP_X_SEARUNNER_APIKEY&quot; in file /home/liveshouts/html/lib/api/validation.php (line 107)</array_item>
    </runtime_errors>
    </SearunnerResult>

    Getting started with the elggVoices API (part 2 – The request)

    February 22nd, 2008 by Marcus Povey

    HTTP Headers

    In addition to the method call, format and variables sent on the URL line (discussed in yesterday’s article), you are required to send some extra information in the headers of the outgoing HTTP request.

    From the API Documentation:

    • X-Searunner-apikey Your public API key you were given when signing
      up for a developer account.
    • X-Searunner-time A float representation of “now” including
      milliseconds (like that produced by the php function microtime(true))
    • X-Searunner-hmac-algo The algorithm used for generating the HMAC,
      like sha1, sha256, MD5 etc.
    • X-Searunner-hmac The HEX representation of the HMAC signing the
      request.

    The following headers are only required when sending POST data:

    • X-Searunner-posthash A hash of the POST data packet.
    • X-Searunner-posthash-algo The algorithm used to create the POST
      hash, eg md5, sha1 etc.
    • Content-type The content type of the data (default:
      application/octet-stream)
    • Content-Length The length of the POST data in bytes.

    Initially, this may look like rather a lot to take in, but it is actually quite simple.

    The two algorithm selection fields X-Searunner-hmac-algo and X-Searunner-posthash-algo give the name of the algorithm used – md5, sha1, sha256 etc. Letting the client specify the algorithm used lets us get around issues with buggy/missing algorithm implementations. For example, we use MD5 for the Java library as this removes the need to install a third party JCE.

    Where possible you should probably try and use “sha256” for HMAC and “sha1” for hashing your post data, since over time we will likely “retire” the weaker algorithms.

    X-Searunner-time timestamps the request, while Content-type and Content-Length (for POST requests) are pretty much self explanatory.

    The field X-Searunner-posthash ensures that the POST body can not be modified, and it is a lower case hexadecimal representation of the desired HASH algorithm, taken over all the data in the POST body, take a look at this pseudo code:

    hash = algorithmFactory( //X-Searunner-posthash-algo// );
    hash.update( //POST DATA// );
    return hex_encode(hash.final());

    Finally, the field X-Searunner-hmac contains a signature which lets the elggVoices server guarantee that it was you that sent the request (rather than someone pretending to be you) and that the contents of the request have not been modified. This is where your secret key comes in, and is the reason why it is important that you keep it safe!

    From the manual:

    A HMAC is a simple cryptographic signature, in this case is formed over the
    following information:

    • The time header eg, 12345678.90
    • Your API key
    • The GET query string, eg “format=xml&method.example&variable=foo“. It is
      important to note two things, firstly that the preceding “?” character has
      been dropped, and second that the order is identical to the order they
      appear on the URL line.
    • If this is a POST request, the hash of the POST data.

    Take a look at this pseudo code:

    hmac_cipher = algorithmFactory( //X-Searunner-hmac-algo// );
    hmac_cipher.init(//SECRET KEY//);
    hmac_cipher.update( //X-Searunner-time// );
    hmac_cipher.update( //X-Searunner-apikey// );
    hmac_cipher.update( //GET VARIABLES IN CALL ORDER// );
    if (POST CALL) hmac_cipher.update( //X-Searunner-posthash// );

    return hex_encode(hmac_cipher.final());

    To form the HMAC, each variable’s string representation is turned into a string of bytes. This byte string is fed in turn into the HMAC algorithm which spits out a HMAC signature which is then converted into a lower case hexadecimal string. These are Octet strings, ensure that when adding the strings to the cipher that your language doesn’t convert it into unicode!

    Note that the order in which you add each field is VERY IMPORTANT.

    Next comes the server response…

    Next Page »
    All content is © Copyright Marcus Povey 2008-2009 unless otherwise stated.