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

Leave a Reply