dcsimg

Extend Your Scripting Language with SWIG

SWIG makes it easy to write an extension for your favorite scripting language (or languages). Learn how it works.

A More Complex Example for Ruby

I recently had occassion to decrypt and encrypt data to authenticate communication between a client and a server. Here’s the scheme:

  1. When contact is initiated by a client, the server replies with a message signed with a shared, secret key. Part of the message is a signature assigned to the client.
  2. Upon receipt of the reply, the client decrypts the message and extracts the signature assigned to it by the server.
  3. To make additional requests, the client must include the signature and sign the request with the shared, secret key.
  4. If the server cannot decrypt the message or does not see the assigned signature, the request is ignored.

In this case, each missive was also encoded in Base64 to transmit it as plain text. The encryption technique was DES ECB.

To make this interchange work with Rails, I created a small extension for Ruby based on mcrypt. Here is the code, secret.c, with the secret eight-character key changed to something innocuous.

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <mcrypt.h>

char *encode( char *string, int length );
char *decode( char *string, int length );

MCRYPT start() {
  MCRYPT td = mcrypt_module_open( "des", NULL, "ecb", NULL );
  if ( td == MCRYPT_FAILED ) {
    return( MCRYPT_FAILED );
  }

  if ( mcrypt_enc_self_test( td ) != 0 ) {
    return( MCRYPT_FAILED );
  }

  int i;
  char *IV;
  int iv_size = mcrypt_enc_get_iv_size( td );
  if ( iv_size != 0 ) {
    IV = calloc( 1, iv_size );
    for ( i = 0; i < iv_size; i++ ) {
      IV[ i ] = rand();
    }
  }

  int keysize = mcrypt_enc_get_key_size( td );
  char *key = calloc( 1, keysize );
  memcpy(key, "SECRET!!", keysize);

  i = mcrypt_generic_init ( td, key, keysize, IV );
  if ( i < 0 ) {
    mcrypt_perror( i );
    exit(1);
  }

  return( td );
}

void end( MCRYPT td ) {
  mcrypt_generic_deinit( td );
  mcrypt_module_close( td );
}

#define B64_DEF_LINE_SIZE   72
#define B64_MIN_LINE_SIZE    4

/*
** encode 3 8-bit binary bytes as 4 '6-bit' characters
*/
void encodeblock( unsigned char in[3], unsigned char out[4], int len ) {
    static const char cb64[]
           ="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz+/";
    out[0] = cb64[ in[0] >> 2 ];
    out[1] = cb64[ ((in[0] & 0x03) << 4) | ((in[1] & 0xf0) >> 4) ];
    out[2] = (unsigned char) (len > 1 ? cb64[ ((in[1] & 0x0f) << 2) |
      ((in[2] & 0xc0) >> 6) ] : '=');
    out[3] = (unsigned char) (len > 2 ? cb64[ in[2] & 0x3f ] : '=');
}

char *base64encode( char *input, int size ) {
    int i, x, len;
    unsigned char in[3], out[4];
    char *target = calloc( 1, ( ( size + 2 ) / 3 ) * 4 + 1 );
    char *t = target;

    for ( x = 0; x < size; ) {
        len = 0;

        for( i = 0; i < 3; i++ ) {
            if ( x < size ) {
                len++;
              in[i] = input[x++];
            }
            else {
                in[i] = 0;
            }
        }

        if( len ) {
            encodeblock( in, out, len );
            for( i = 0; i < 4; i++ ) {
                *t++ = out[i];
            }
        }
    }

    return( target );
}

char *encode( char *string, int length ) {
  MCRYPT td = start();
  int blocksize = mcrypt_enc_get_block_size( td );
  int cryptsize = ( ( length  + blocksize - 1 ) / blocksize ) * blocksize;
  char *target = calloc( 1,  cryptsize );

  memcpy( target, string, length );

  if ( mcrypt_generic( td, target, cryptsize ) != 0 ) {
      fprintf( stderr, "Code failing" );
  }

  end( td );

  char* result = base64encode( target, cryptsize );

  free( target );
  return result;
}

char *decode( char *string, int length ) {
  MCRYPT td = start();
  int blocksize = mcrypt_enc_get_block_size( td );
  char *block_buffer = calloc( 1, blocksize );
  int decryptlength = (length + blocksize - 1) / blocksize * blocksize;
  char *target = calloc( 1, decryptlength );

  memcpy(target, string, length);

  mdecrypt_generic( td, target, decryptlength ); 

  end( td );

  free(block_buffer);
  return( target );
}

The Rails code uses encode() and decode() to encrypt and decrypt strings, respectively. The rationale for formal arguments char *string, int length is explained below.

The rest of the code shown merely supports those two functions. For example, encode() calls base64encode() as a convenience, since whatever is encrypted has to be encoded too before it is sent.

Here’s the rather spartan secret.i, the SWIG interface for the extension.

%module secret

%include "typemaps.i"

%{
  #include "ruby.h"
%}

%newobject encode;
%newobject decode;
%apply (char *STRING, int LENGTH) { ( char *string, int length ) };

char *encode( char *string, int length );
char *decode( char *string, int length );

Let’s walk though the SWIG code.

  • The first line names the extension secret. The Rails code includes the extension with require 'secret'.
  • The line %include "typemaps.i" imports a SWIG library that helps simplify argument conversion between the host language and the extension. Here, one of those special conversions bridges the call to encode() and decode() from Ruby to the underlying C code.

    Specifically, calls to encode() and decode() in Ruby provide a single argument, the binary data of the outgoing or incoming encrypted message, respectively. (In other words, calls look like encode(request) and decode(reply).) However, to exchange the data with the extension, the single argument must be converted to a string and a count. The special typemap …

    %apply (char *STRING, int LENGTH) { ( char *string, int length ) }
    

    … treats the phrase ( char *string, int length ) anywhere it appears as a single argument and promotes it to two arguments. Hence, the definitions…

    char *encode( char *string, int length );
    char *decode( char *string, int length );
    

    specify that these two functions are called with a single actual argument and SWIG converts it to two actual arguments when passed on to the extension and vice versa from extension to language.

  • Much like Perl required its header files to build the extension, Ruby requires ruby.h. This line of code is added to secret_wrap.c, the output of SWIG.
  • Finally, the two %newobject operands specify that each of encode() and decode() return new objects that SWIG must subsequently free to prevent memory leaks.

Building the extension is just three steps. An additional file unique to Ruby creates a Makefile to automate the process.

$ swig -ruby secret.i
$ cat extconf.rb
# Loads mkmf which is used to make makefiles for Ruby extensions
require 'mkmf'

# Give it a name
extension_name = 'secret'

# Add a library
$libs = append_library($libs, "mcrypt")

# The destination
dir_config(extension_name)

# Do the work
create_makefile(extension_name)

$ ruby extconf.rb
$ make
cc -I. -I/usr/lib/ruby/1.8/i486-linux -I/usr/lib/ruby/1.8/i486-linux -I.
  -D_FILE_OFFSET_BITS=64  -fPIC -fno-strict-aliasing -g -g -O2  -fPIC   -c secret.c
cc -I. -I/usr/lib/ruby/1.8/i486-linux -I/usr/lib/ruby/1.8/i486-linux -I.
  -D_FILE_OFFSET_BITS=64  -fPIC -fno-strict-aliasing -g -g -O2  -fPIC   -c secret_wrap.c
cc -shared -o secret.so secret.o secret_wrap.o -L. -L/usr/lib -L.  -rdynamic
  -Wl,-export-dynamic    -lruby1.8 -lmcrypt  -lpthread -ldl -lcrypt -lm   -lc

The extension is now ready to use.

dcauto% irb
irb(main):001:0> require 'secret'
=> true
irb(main):002:0> message = "hello"
=> "hello"
irb(main):003:0> puts Secret.encode(message)
nSSB68WDFwk=
irb(main):004:0> require 'base64'
=> true
irb(main):005:0> puts Secret.decode(Base64.decode64(Secret.encode(message)))
hello
=> nil

Take a Swig!

SWIG is very rich and a thorough reading of the documentation is worthwhile and warranted. If you have C or C++ code you want to repurpose, SWIG can make that old code cool once again.

I’d like to thank Unix developer Barry Stone for his assistance squashing the (originally) errant pointers in my C code.

Fatal error: Call to undefined function aa_author_bios() in /opt/apache/dms/b2b/linuxdls.com/site/www/htdocs/wp-content/themes/linuxmag/single.php on line 62