I got bored one evening, so I hacked together the beginnings of an API library for latakoo Flight. Currently it’s available in three four tasty flavours – PHP, Python, Ruby and C# .NET / Mono.

The libraries are minimal but functional; they let you perform both anonymous and authenticated queries against the latakoo API endpoint, but I’ve only had time to add method wrappers for a few of api calls. Feel free to fork the project to help flesh these out!

Hopefully these libraries will make it easier to get the power of latakoo behind your project, and be sure to check out Latakoo to find out how to share video on the Internet.

Happy hacking!

» API Documentation
» Github Project Page

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