Index: trunk/psLib/configure.ac
===================================================================
--- trunk/psLib/configure.ac	(revision 10308)
+++ trunk/psLib/configure.ac	(revision 10309)
@@ -421,7 +421,41 @@
 dnl ------- openssl ---------
 
-PKG_CHECK_MODULES([SSL], [openssl >= 0.9.6])
+dnl save LIBS/CFLAGS/LDFLAGS
+TMP_LIBS=${LIBS}
+TMP_CFLAGS=${CFLAGS}
+TMP_LDFLAGS=${LDFLAGS}
+TMP_CPPFLAGS=${CPPFLAGS}
+
+AC_ARG_WITH(ssl,
+[AS_HELP_STRING(--with-ssl=DIR,Specify location of SSL.)],
+[SSL_CFLAGS="-I$withval/include"
+ SSL_LDFLAGS="-L$withval/lib"])
+AC_ARG_WITH(ssl-include,
+[AS_HELP_STRING(--with-ssl-include=DIR,Specify SSL include directory.)],
+[SSL_CFLAGS="-I$withval"])
+AC_ARG_WITH(ssl-lib,
+[AS_HELP_STRING(--with-ssl-lib=DIR,Specify SSL library directory.)],
+[SSL_LDFLAGS="-L$withval"])
 PSLIB_CFLAGS="${PSLIB_CFLAGS=} ${SSL_CFLAGS}"
-PSLIB_LIBS="${PSLIB_LIBS=} ${SSL_LIBS}"
+PSLIB_LIBS="${PSLIB_LIBS=} $SSL_LDFLAGS -lssl"
+
+CFLAGS="${CFLAGS} ${SSL_CFLAGS}"
+CPPFLAGS=${CFLAGS}
+LDFLAGS="${LDFLAGS} ${SSL_LIBS}"
+
+AC_CHECK_HEADERS([openssl/md5.h],[],
+  [AC_MSG_ERROR([SSL headers not found.  Obtain it at http://www.openssl.org/ or use --with-ssl to specify location.])]
+)
+AC_CHECK_LIB(ssl,MD5,[],
+  [AC_MSG_ERROR([SSL library not found.  Obtain it at http://www.openssl.org/ or use --with-ssl to specify location.])]
+)
+
+AC_SUBST([SSL_CFLAGS])
+
+dnl restore the CFLAGS/LDFLAGS
+LIBS=${TMP_LIBS}
+CFLAGS=${TMP_CFLAGS}
+LDFLAGS=${TMP_LDFLAGS}
+CPPFLAGS=${TMP_CPPFLAGS}
 
 dnl ------- enable -Werror after all of the probes have run ---------
Index: trunk/psLib/src/math/Makefile.am
===================================================================
--- trunk/psLib/src/math/Makefile.am	(revision 10308)
+++ trunk/psLib/src/math/Makefile.am	(revision 10309)
@@ -10,5 +10,7 @@
 	psCompare.c \
 	psEllipse.c \
+	psMathUtils.c \
 	psMatrix.c \
+	psMD5.c \
 	psMinimizeLMM.c \
 	psMinimizePowell.c \
@@ -22,5 +24,4 @@
 	psSpline.c \
 	psStats.c \
-	psMathUtils.c \
 	psVectorSmooth.c
 
@@ -34,5 +35,7 @@
 	psConstants.h \
 	psEllipse.h \
+	psMathUtils.h \
 	psMatrix.h \
+	psMD5.h \
 	psMinimizeLMM.h \
 	psMinimizePowell.h \
@@ -46,5 +49,4 @@
 	psSpline.h \
 	psStats.h \
-	psMathUtils.h \
 	psVectorSmooth.h
 
Index: trunk/psLib/src/math/psMD5.c
===================================================================
--- trunk/psLib/src/math/psMD5.c	(revision 10309)
+++ trunk/psLib/src/math/psMD5.c	(revision 10309)
@@ -0,0 +1,71 @@
+#include <stdio.h>
+#include <openssl/md5.h>
+
+#include "psAssert.h"
+#include "psMemory.h"
+#include "psString.h"
+#include "psVector.h"
+#include "psImage.h"
+#include "psError.h"
+#include "psMD5.h"
+
+
+psVector *psStringMD5(const char *string)
+{
+    psVector *hash = psVectorAlloc(MD5_DIGEST_LENGTH, PS_TYPE_U8); // The resultant MD5 hash
+    if (!MD5(string, strlen(string), &hash->data.U8[0])) {
+        psError(PS_ERR_UNEXPECTED_NULL, true, "Unable to generate md5 hash.\n");
+        psFree(hash);
+        return NULL;
+    }
+    return hash;
+}
+
+psVector *psVectorMD5(const psVector *vector)
+{
+    PS_ASSERT_VECTOR_NON_NULL(vector, NULL);
+
+    psVector *hash = psVectorAlloc(MD5_DIGEST_LENGTH, PS_TYPE_U8); // The resultant MD5 hash
+    if (!MD5(vector->data.U8, vector->n * PSELEMTYPE_SIZEOF(vector->type.type), &hash->data.U8[0])) {
+        psError(PS_ERR_UNEXPECTED_NULL, true, "Unable to generate md5 hash.\n");
+        psFree(hash);
+        return NULL;
+    }
+    return hash;
+}
+
+
+psVector *psImageMD5(const psImage *image)
+{
+    PS_ASSERT_IMAGE_NON_NULL(image, NULL);
+
+    psVector *hash = psVectorAlloc(MD5_DIGEST_LENGTH, PS_TYPE_U8); // The resultant MD5 hash
+    if (!image->parent) {
+        // No parent means image is contiguous
+        if (!MD5(&image->data.U8[0][0], image->numCols * image->numRows * PSELEMTYPE_SIZEOF(image->type.type),
+                 &hash->data.U8[0])) {
+            psError(PS_ERR_UNEXPECTED_NULL, true, "Unable to generate md5 hash.\n");
+            psFree(hash);
+            return NULL;
+        }
+    } else {
+        MD5_CTX ctx;                    // MD5 calculator
+        MD5_Init(&ctx);
+        for (int row = 0; row < image->numRows; row++) {
+            MD5_Update(&ctx, image->data.U8[row], image->numCols * PSELEMTYPE_SIZEOF(image->type.type));
+        }
+        MD5_Final(&hash->data.U8[0], &ctx);
+    }
+
+    return hash;
+}
+
+
+psString psMD5toString(const psVector *hash)
+{
+    psString string = psStringAlloc(MD5_DIGEST_LENGTH * 2 + 1); // String to return
+    for (int i = 0; i < MD5_DIGEST_LENGTH; i++) {
+        sprintf(string + i * 2, "%02x", hash->data.U8[i]);
+    }
+    return string;
+}
Index: trunk/psLib/src/math/psMD5.h
===================================================================
--- trunk/psLib/src/math/psMD5.h	(revision 10309)
+++ trunk/psLib/src/math/psMD5.h	(revision 10309)
@@ -0,0 +1,31 @@
+#ifndef PS_MD5_H
+#define PS_MD5_h
+
+#include "psVector.h"
+#include "psString.h"
+#include "psImage.h"
+
+/// Return an MD5 hash of the supplied string.
+///
+/// The MD5 hash is returned in a U8 vector of size 16.
+psVector *psStringMD5(const char *string   ///< String to hash
+                     );
+
+/// Return an MD5 hash of the supplied vector.
+///
+/// The MD5 hash is returned in a U8 vector of size 16.
+psVector *psVectorMD5(const psVector *vector ///< Vector to hash
+                     );
+
+/// Return an MD5 hash of the supplied image.
+///
+/// The MD5 hash is returned in a U8 vector of size 16.
+psVector *psImageMD5(const psImage *image ///< Image to hash
+                    );
+
+/// Convert an MD5 hash into a string, for printing.
+psString psMD5toString(const psVector *hash ///< Hash to stringify
+                      );
+
+
+#endif
Index: trunk/psLib/src/pslib_strict.h
===================================================================
--- trunk/psLib/src/pslib_strict.h	(revision 10308)
+++ trunk/psLib/src/pslib_strict.h	(revision 10309)
@@ -9,6 +9,6 @@
 *  @author Eric Van Alst, MHPCC
 *
-*  @version $Revision: 1.23 $ $Name: not supported by cvs2svn $
-*  @date $Date: 2006-09-27 01:31:46 $
+*  @version $Revision: 1.24 $ $Name: not supported by cvs2svn $
+*  @date $Date: 2006-11-30 04:13:44 $
 *
 *  Copyright 2004-2005 Maui High Performance Computing Center, University of Hawaii
@@ -59,4 +59,5 @@
 #include "psConstants.h"
 #include "psMatrix.h"
+#include "psMD5.h"
 #include "psMinimizeLMM.h"
 #include "psMinimizePowell.h"
Index: trunk/psLib/test/math/.cvsignore
===================================================================
--- trunk/psLib/test/math/.cvsignore	(revision 10308)
+++ trunk/psLib/test/math/.cvsignore	(revision 10309)
@@ -51,2 +51,3 @@
 tap_psSparse
 tap_psStatsTiming
+tap_psMD5
Index: trunk/psLib/test/math/Makefile.am
===================================================================
--- trunk/psLib/test/math/Makefile.am	(revision 10308)
+++ trunk/psLib/test/math/Makefile.am	(revision 10309)
@@ -13,4 +13,5 @@
 
 TEST_PROGS = \
+	tap_psMD5 \
 	tap_psSparse \
 	tap_psStatsTiming
Index: trunk/psLib/test/math/tap_psMD5.c
===================================================================
--- trunk/psLib/test/math/tap_psMD5.c	(revision 10309)
+++ trunk/psLib/test/math/tap_psMD5.c	(revision 10309)
@@ -0,0 +1,142 @@
+#include <stdio.h>
+#include <string.h>
+#include <pslib.h>
+
+#include "tap.h"
+#include "pstap.h"
+
+// The official MD5 test cases
+#define NUM_TESTS 7
+static const struct
+{
+    const char *input;                  // The input string
+    const char result[16];              // The hash
+    const char string[34];              // The stringified hash
+}
+tests[] =
+    {
+        { "",
+          "\xd4\x1d\x8c\xd9\x8f\x00\xb2\x04\xe9\x80\x09\x98\xec\xf8\x42\x7e",
+          "d41d8cd98f00b204e9800998ecf8427e" },
+        { "a",
+          "\x0c\xc1\x75\xb9\xc0\xf1\xb6\xa8\x31\xc3\x99\xe2\x69\x77\x26\x61",
+          "0cc175b9c0f1b6a831c399e269772661" },
+        { "abc",
+          "\x90\x01\x50\x98\x3c\xd2\x4f\xb0\xd6\x96\x3f\x7d\x28\xe1\x7f\x72",
+          "900150983cd24fb0d6963f7d28e17f72" },
+        { "message digest",
+          "\xf9\x6b\x69\x7d\x7c\xb7\x93\x8d\x52\x5a\x2f\x31\xaa\xf1\x61\xd0",
+          "f96b697d7cb7938d525a2f31aaf161d0" },
+        { "abcdefghijklmnopqrstuvwxyz",
+          "\xc3\xfc\xd3\xd7\x61\x92\xe4\x00\x7d\xfb\x49\x6c\xca\x67\xe1\x3b",
+          "c3fcd3d76192e4007dfb496cca67e13b" },
+        { "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789",
+          "\xd1\x74\xab\x98\xd2\x77\xd9\xf5\xa5\x61\x1c\x2c\x9f\x41\x9d\x9f",
+          "d174ab98d277d9f5a5611c2c9f419d9f" },
+        { "12345678901234567890123456789012345678901234567890123456789012345678901234567890",
+          "\x57\xed\xf4\xa2\x2b\xe3\xc9\x55\xac\x49\xda\x2e\x21\x07\xb6\x7a",
+          "57edf4a22be3c955ac49da2e2107b67a" }
+    };
+
+
+int main(void)
+{
+    plan_tests(NUM_TESTS * 3 +          // Strings
+               NUM_TESTS * 2 +          // Vectors
+               (NUM_TESTS - 1) * 2 +    // Images (except the empty string test)
+               (NUM_TESTS - 2) * 3 +    // Sub-images (except the empty and "a" string test)
+               1                        // Memory leaks
+              );
+
+    diag("psMD5 tests");
+
+    // Strings
+    for (int i = 0; i < NUM_TESTS; i++) {
+        psString string = psStringCopy(tests[i].input); // String to test
+        psVector *hash = psStringMD5(string); // The MD5 hash
+        ok(hash, "hash generated");
+
+        skip_start(!hash, 2, "Skipping tests because hash calculation failed");
+
+        ok(memcmp(hash->data.U8, tests[i].result, 16) == 0, "hash matches");
+
+        psString hashString = psMD5toString(hash); // String of the hash
+        ok(strcmp(hashString, tests[i].string) == 0, "hash string matches");
+        psFree(hashString);
+
+        skip_end();
+
+        psFree(hash);
+        psFree(string);
+    }
+
+    // Vectors
+    for (int i = 0; i < NUM_TESTS; i++) {
+        psVector *vector = psVectorAlloc(strlen(tests[i].input), PS_TYPE_U8); // Vector to test
+        memcpy(vector->data.U8, tests[i].input, strlen(tests[i].input));
+        psVector *hash = psVectorMD5(vector); // The MD5 hash
+        ok(hash, "hash generated");
+
+        skip_start(!hash, 1, "Skipping test because hash calculation failed");
+        ok(memcmp(hash->data.U8, tests[i].result, 16) == 0, "hash matches");
+        skip_end();
+
+        psFree(hash);
+        psFree(vector);
+    }
+
+    // Images
+    for (int i = 0; i < NUM_TESTS; i++) {
+        if (strlen(tests[i].input) == 0) {
+            // We don't like images with size = 0,0
+            continue;
+        }
+
+        psImage *image = psImageAlloc(1, strlen(tests[i].input), PS_TYPE_U8); // Image to test
+        for (int j = 0; j < strlen(tests[i].input); j++) {
+            image->data.U8[j][0] = tests[i].input[j];
+        }
+
+        {
+            psVector *hash = psImageMD5(image);
+            ok(hash, "hash generated");
+
+            skip_start(!hash, 1, "Skipping test because hash calculation failed");
+            ok(memcmp(hash->data.U8, tests[i].result, 16) == 0, "hash matches");
+            skip_end();
+
+            psFree(hash);
+        }
+
+        if (strlen(tests[i].input) > 1) {
+            // Generate a subImage (so the source can't assume that everything's contiguous)
+            psRegion region = { 0, 1, 1, image->numRows }; // Region of interest --- all but the first char
+            psImage *subImage = psImageSubset(image, region);
+
+            // We're going to test against something that we calculate ourselves.  Not the most robust of
+            // tests, but we've already verified psStringMD5, so we cross our fingers and hope.
+            psVector *reference = psStringMD5(tests[i].input + 1);
+            ok(reference, "reference hash generated");
+            skip_start(!reference, 2, "Skipping tests because hash calculation failed");
+
+            psVector *hash = psImageMD5(subImage);
+            ok(hash, "hash generated");
+
+            skip_start(!hash, 1, "Skipping test because hash calculation failed");
+            ok(memcmp(reference->data.U8, hash->data.U8, 16) == 0, "hash matches");
+            skip_end();
+
+            psFree(hash);
+
+            skip_end();
+
+            psFree(reference);
+
+            psFree(subImage);
+        }
+
+        psFree(image);
+    }
+
+    done();
+}
