• 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

    Share and Enjoy: These icons link to social bookmarking sites where readers can share and discover new web pages.
    • Digg
    • del.icio.us
    • StumbleUpon
    • Reddit
    • Facebook
    • TwitThis
    • LinkedIn
    • NewsVine
    • Slashdot
    • Technorati
    • Google Bookmarks

    Related posts

    Leave a Reply

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