ElggOver the past few weeks I have been heavily involved in writing the upcoming 1.0 release of the Elgg social networking platform. Elgg 1 is the logical extension of Elgg classic but has been totally rewritten from the ground up, and I admit that I am rather excited about some of the cool features I’ve been working on…

For a start, Elgg 1 uses an entirely new and flexible object modal under the hood. I won’t say much on this except that we have focused on simplicity and flexibility, and I think you’ll like what we’ve come up with.

Next is a new thing for Elgg. I have been developing a flexible and highly extensible API framework that will let Elgg plug-in writers expose functionality to third party applications. You’ve had a taster of this with the Elgg Voices API, but Elgg takes this a lot further.

The new API even lets plugin authors extend the framework by providing new output formats above and beyond the JSON, XML and PHP serialisation that Elgg already has native support for. Plugin authors can even add new client authentication methods to API.

I think this will leave a lot of scope open for some very interesting plug-ins!

Building on the @ commands I talked about in a previous posting, I would like to introduce elggVoices #commands!

These commands offer a plugable framework for third parties to add channel specific information oriented commands.

To see this in action, text or post one of the following to a channel (assumes you have validated either a phone number or an email address previously).

  • #weather <location> – Receive the weather forecast for the location.
  • #stock <stock code> – Receive a stock quote.

I have found #weather very useful. Contact Curverider directly if you want to talk about adding your own commands!

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