changeset 14:f5d82424b0ed

Made song-length database support FINALLY work!
author Matti Hamalainen <ccr@tnsp.org>
date Wed, 04 Jun 2003 18:00:03 +0000
parents 7e664541ea36
children 17947c69fcc9
files src/xs_length.c src/xs_length.h src/xs_md5.h
diffstat 3 files changed, 486 insertions(+), 127 deletions(-) [+]
line wrap: on
line diff
--- a/src/xs_length.c	Wed Jun 04 14:04:02 2003 +0000
+++ b/src/xs_length.c	Wed Jun 04 18:00:03 2003 +0000
@@ -21,52 +21,174 @@
 */
 
 #include "xmms-sid.h"
+#include "xs_support.h"
 #include "xs_length.h"
+#include "xs_config.h"
+#include "xs_md5.h"
 #include <stdlib.h>
 #include <stdio.h>
 #include <ctype.h>
 #include <string.h>
 
+
 /*
  * Pointer to database in memory
  */
-static t_xs_dbentry	*xs_database = NULL;
+static t_xs_dbentry	*xs_database = NULL, *xs_dblast = NULL;
+static t_xs_dbentry	**xs_dbindex = NULL;
+static gint		xs_dbnodes   = 0;
+
+
+/*
+ * Hash-database handling functions
+ */
+t_xs_dbentry *xs_db_node_new(void)
+{
+ t_xs_dbentry *pResult;
+ 
+ /* Allocate memory for new node */
+ pResult = (t_xs_dbentry *) calloc(1, sizeof(t_xs_dbentry));
+ if (pResult == NULL) return NULL;
+ 
+ return pResult;
+}
+
+
+void xs_db_node_free(t_xs_dbentry *pNode)
+{
+ if (pNode) free(pNode);
+}
+
+
+/*
+ * Insert given node to db linked list
+ */
+void xs_db_node_insert(t_xs_dbentry *pNode)
+{
+ if (xs_dblast)
+ 	{
+ 	xs_dblast->pNext = pNode;
+ 	xs_dblast = pNode;
+ 	} else {
+ 	xs_dblast = xs_database = pNode;
+ 	pNode->pNext = NULL;
+ 	}
+}
+
+
+/*
+ * Compare two given MD5-hashes.
+ * Return: 0 if equal
+ *         negative if testHash1 < testHash2
+ *         positive if testHash1 > testHash2
+ */
+gint xs_db_cmphash(t_xs_md5hash testHash1, t_xs_md5hash testHash2)
+{
+ register gint i, res = 0;
+  
+ /* Compute difference of hashes */
+ for (i = 0; (i < XS_MD5HASH_LENGTH) && (!res); i++)
+ 	res = (testHash1[i] - testHash2[i]);
+
+ return res;
+}
+
+
+/*
+ * Get song length from database
+ */
+t_xs_dbentry * xs_db_get(t_xs_md5hash pHash)
+{
+ gint iStartNode, iEndNode, iQNode, iFound, r, i;
+ t_xs_dbentry *pResult;
+
+ /* Check the database pointers */
+ if ((xs_database == NULL) || (xs_dbindex == NULL))
+ 	return NULL;
+ 	
+ /* Look-up via index using binary search */
+ pResult = NULL;
+ iStartNode = 0;
+ iEndNode = (xs_dbnodes - 1);
+ iQNode = (iEndNode / 2);
+ iFound = 0;
+
+ while ((!iFound) && ((iEndNode - iStartNode) > 128))
+ 	{
+ 	fprintf(stderr, "[");
+ 	fprinth(stderr, xs_dbindex[iQNode]->md5Hash);
+ 	r = xs_db_cmphash(pHash, xs_dbindex[iQNode]->md5Hash);
+ 	fprintf(stderr, "] = %i\n", r);
+ 	if (r < 0)
+ 		{
+ 		/* Hash was in the <- LEFT side */
+ 		iEndNode = iQNode;
+ 		iQNode = iStartNode + ((iEndNode - iStartNode) / 2);
+ 		} else
+ 	if (r > 0)
+ 		{
+ 		/* Hash was in the RIGHT -> side */
+ 		iStartNode = iQNode;
+ 		iQNode = iStartNode + ((iEndNode - iStartNode) / 2);
+ 		} else
+ 		iFound = 1;
+ 	}
+
+ /* If not found already */
+ if (!iFound)
+ 	{
+	/* Search the are linearly */
+	iFound = 0;
+	i = iStartNode;
+	while ((i <= iEndNode) && (!iFound))
+		{
+		if (xs_db_cmphash(pHash, xs_dbindex[i]->md5Hash) == 0)
+			iFound = 1;
+			else
+			i++;
+		}
+	
+	/* Check the result */
+	if (iFound)
+		pResult = xs_dbindex[i];
+
+ 	} else {
+	/* Found via binary search */
+ 	pResult = xs_dbindex[iEndNode];
+	}
+
+
+ return pResult;
+}
 
 
 /*
  * Parses a time-entry in SLDB format
  */
-long int xs_gettime(char *pcStr, int *piPos)
+gint32 xs_gettime(gchar *pcStr, int *piPos)
 {
- long int iResult;
- int iTemp;
- char chTempBuf[16];
+ gint32 iResult, iTemp;
 
  /* Check if it starts with a digit */ 
  if (isdigit(pcStr[*piPos]))
 	{
 	/* Get minutes-field */
-	iTemp = *piPos;
-	xs_findnum(pcStr, piPos);
- 
-	strncpy(chTempBuf, &pcStr[iTemp], (*piPos - iTemp));
-	chTempBuf[*piPos - iTemp] = 0;
- 
-	iResult = (atol(chTempBuf) * 60);
+	iResult = 0;
+	while (isdigit(pcStr[*piPos]))
+		iResult = (iResult * 10) + (pcStr[(*piPos)++] - '0');
+		
+	iResult *= 60;
 
 	/* Check the field separator char */
 	if (pcStr[*piPos] == ':')
 		{
 		/* Get seconds-field */
 		(*piPos)++;
-		iTemp = *piPos;
-		xs_findnum(pcStr, piPos);
+		iTemp = 0;
+		while (isdigit(pcStr[*piPos]))
+			iTemp = (iTemp * 10) + (pcStr[(*piPos)++] - '0');
 
-		strncpy(chTempBuf, &pcStr[iTemp], (*piPos - iTemp));
-		chTempBuf[*piPos - iTemp] = 0;
- 
-		iResult += atol(chTempBuf);
-		
+		iResult += iTemp;
 		} else
 		iResult = -2;
 	} else
@@ -80,86 +202,105 @@
 
 
 /*
- * Initialize, read database to memory
+ * Read database to memory
  */
-gint xs_db_initialize(gchar *pcFilename)
+gint xs_db_read(gchar *dbFilename, t_xs_dbentry **dataBase)
 {
  FILE *inFile;
- char inLine[256];
- int lineNum, linePos, i, j, k;
- t_xs_hash tmpHash;
-
-   
+ gchar inLine[XMMS_SID_BUFSIZE];
+ gint lineNum, linePos, iOK;
+ t_xs_dbentry *tmpNode;
+ 
  /* Try to open the file */
- if ((inFile = fopen(pcFilename, "r")) == NULL)
+ if ((inFile = fopen(dbFilename, "ra")) == NULL)
  	{
- 	fprintf(stderr, "hv!\n");
+ 	XSERR("Could not open SongLengthDB '%s'\n", dbFilename);
  	return -1;
  	}
  
-
  /* Read and parse the data */
  lineNum = 0;
 
  while (!feof(inFile))
  {
  fgets(inLine, sizeof(inLine), inFile);
+ lineNum++;
+
+ /* Check if it is datafield */
+ if (isxdigit(inLine[0])) 
+ {
+ /* Check the length of the hash */
  linePos = 0;
-  
- if (strlen(inLine) > 1)
- {
- /* Find first character */
- xs_findnext(inLine, &linePos);
+ while (isxdigit(inLine[linePos])) linePos++;
 
- /* Check if it is a hash-line */ 
- if (isxdigit(inLine[linePos]))
- 	{
- 	i = linePos;
- 	while (isxdigit(inLine[linePos])) linePos++;
-
-	if ((linePos - i) != XS_HASH_LENGTH_CH)
+ if (linePos != XS_MD5HASH_LENGTH_CH)
+	{
+	XSERR("Invalid hash in SongLengthDB file '%s' line #%d!\n",
+	dbFilename, lineNum);
+	} else {
+	/* Allocate new node */
+	if ((tmpNode = xs_db_node_new()) == NULL)
 		{
-		XSERR("Invalid hash/syntax error in SongLengthDB file '%s' line #%d!\n",
-		pcFilename, lineNum);
+		XSERR("Error allocating new node. Fatal error.\n");
+		exit(5);
+		}
+			
+	/* Get hash value */
+#if (XS_MD5HASH_LENGTH != 16)
+#error Mismatch in hashcode length. Fix here.
+#endif
+	sscanf(&inLine[0], "%2x%2x%2x%2x%2x%2x%2x%2x%2x%2x%2x%2x%2x%2x%2x%2x",
+	(guint *) &(tmpNode->md5Hash[0]), (guint *) &(tmpNode->md5Hash[1]),
+	(guint *) &(tmpNode->md5Hash[2]), (guint *) &(tmpNode->md5Hash[3]),
+	(guint *) &(tmpNode->md5Hash[4]), (guint *) &(tmpNode->md5Hash[5]),
+	(guint *) &(tmpNode->md5Hash[6]), (guint *) &(tmpNode->md5Hash[7]),
+	(guint *) &(tmpNode->md5Hash[8]), (guint *) &(tmpNode->md5Hash[9]),
+	(guint *) &(tmpNode->md5Hash[10]), (guint *) &(tmpNode->md5Hash[11]),
+	(guint *) &(tmpNode->md5Hash[12]), (guint *) &(tmpNode->md5Hash[13]),
+	(guint *) &(tmpNode->md5Hash[14]), (guint *) &(tmpNode->md5Hash[15]));
+
+	/* Get playtimes */
+	if (inLine[linePos] != '=')
+		{
+		XSERR("'=' expected in SongLengthDB file '%s' line #%d, column #%d\n",
+		dbFilename, lineNum, linePos);
+
+		xs_db_node_free(tmpNode);
 		} else {
-		/* Get hashcode value */
-		for (j = 0; j < XS_HASH_LENGTH; j++)
+		/* First playtime is after '=' */
+		linePos++;
+		iOK = 1;
+
+		while ((linePos < strlen(inLine)) && (iOK))
 			{
-			
+			xs_findnext(inLine, &linePos);
+
+			if (tmpNode->nLengths < XS_STIL_MAXENTRY)
+				{
+				tmpNode->sLengths[tmpNode->nLengths] =
+				xs_gettime(inLine, &linePos);
+				tmpNode->nLengths++;
+				} else
+				iOK = 0;
 			}
 
-		/* Get playtimes */
-		xs_findnext(line, &linePos);
-
-		if (line[linePos] != '=')
-			{
-			XSERR("Warning: '=' expected in line #%d in SongLengthDB file '%s'\n",
-			lineNum, pcFilename);
-			} else {
-			linePos++;
+		/* Add an entry to db in memory */
+		if (iOK)
+			xs_db_node_insert(tmpNode);
+			else
+			xs_db_node_free(tmpNode);
+		}
+	}
+ } else
+ if ((inLine[0] != ';') && (inLine[0] != '['))
+ 	{
+	XSERR("Invalid line in SongLengthDB file '%s' line #%d\n", 
+	dbFilename, lineNum);
+	}
 
-			while (linePos < strlen(inLine))
-				{
-				xs_findnext(line, &linePos);
-				printf("[%lis]", xs_gettime(inLine, &linePos));
-				}
-		
+ } /* while */
 
-			/* Add an entry to db in memory */
-			}
-		}
- 	}
- 	else
- if ((line[linePos] != ';') && (line[linePos] != '['))
- 	{
-	XSERR("Invalid line #%d in SongLengthDB file '%s'\n", lineNum, pcFilename);
-	}
- 
- } /* strlen(line) > 1 */
- 
- lineNum++;
- }
-  
+ /* Close the file */  
  fclose(inFile);
  
  return 0;
@@ -167,72 +308,281 @@
 
 
 /*
- *
+ * Compare two nodes' hashes
+ */
+gint xs_db_cmp(const void *pNode1, const void *pNode2)
+{
+ /* We assume here that we never ever get NULL-pointers or similar */
+ return xs_db_cmphash((*(t_xs_dbentry **) pNode1)->md5Hash,
+                      (*(t_xs_dbentry **) pNode2)->md5Hash);
+}
+
+
+/*
+ * Initialize the song-length system
  */
-gint xs_db_delete(void)
+gint xs_songlen_init(void)
 {
+ t_xs_dbentry *pCurr;
+ gint i;
+ 
+ /* Read the database */
+ if (xs_cfg.playDBPath == NULL)
+ 	return -10;
+
+fprintf(stderr, "reading '%s'\n", xs_cfg.playDBPath);
+
+ if (xs_db_read(xs_cfg.playDBPath, &xs_database) < 0)
+ 	return -9;
+
+fprintf(stderr, "read_done, now size DB for index\n");
+
+ /* Get size of db */
+ pCurr = xs_database;
+ xs_dbnodes = 0;
+ while (pCurr)
+ 	{
+ 	xs_dbnodes++;
+ 	pCurr = pCurr->pNext;
+ 	}
+
+ /* Check number of nodes */
+ if (xs_dbnodes > 0)
+	{
+fprintf(stderr, "allocating %i nodes...\n", xs_dbnodes);
+	/* Allocate memory for index-table */
+	xs_dbindex = (t_xs_dbentry **) malloc(sizeof(t_xs_dbentry *) * xs_dbnodes);
+	if (xs_dbindex == NULL) return -6;
+
+	/* Get node-pointers to table */
+	i = 0;
+	pCurr = xs_database;
+	while (pCurr)
+		{
+		xs_dbindex[i++] = pCurr;
+		pCurr = pCurr->pNext;
+		}
+
+fprintf(stderr, "sorting!\n");
+	/* Sort the indexes */
+	qsort(xs_dbindex, xs_dbnodes, sizeof(t_xs_dbentry *), xs_db_cmp);
+	}
+
+ /* OK */
+ return 0;
+}
+
+
+/*
+ * Close song-length system
+ */
+gint xs_songlen_close(void)
+{
+ t_xs_dbentry *pCurr, *pNext;
+ 
  /* Free the memory allocated for database */
+ pCurr = xs_database;
+ while (pCurr)
+ 	{
+ 	pNext = pCurr->pNext;
+	xs_db_node_free(pCurr);
+ 	pCurr = pNext;
+ 	}
+
+ /* Free memory allocated for indexes */
+ if (xs_dbindex)
+ 	free(xs_dbindex);
 
  return 0;
 }
 
 
 /*
- *
+ * Compute md5hash of given SID-file
  */
-int xs_comparehashes(t_xs_hash *testHash1, t_xs_hash *testHash2)
-{
- int iIndex, iOK;
+typedef struct {
+	guint8	magicID[4];	/* "PSID" magic identifier */
+	guint16	version,	/* Version number */
+		dataOffset,	/* Start of actual c64 data in file */
+		loadAddress,	/* Loading address */
+		initAddress,	/* Initialization address */
+		playAddress,	/* Play one frame */
+		nSongs,		/* Number of subsongs */
+		startSong;	/* Default starting song */
+	guint32	speed;		/* Speed */
+	gchar	sidName[32];	/* Descriptive text-fields, ASCIIZ */
+	gchar	sidAuthor[32];
+	gchar	sidCopyright[32];
+} t_xs_psidv1_header;
+
 
- iOK = 1;
- iIndex = 0;
- while ((iIndex < 16) && (iOK))
- 	{
- 	if (testHash1[iIndex] != testHash2[iIndex])
- 		iOK = 0;
- 		else
- 		iIndex++;
-	}
+typedef struct {
+	guint16	flags;		/* Flags */
+	guint8	startPage,
+		pageLength;
+	guint16 reserved;
+} t_xs_psidv2_header;
+
+
+guint16 rd_be16(FILE *f)
+{
+ return (((guint16) fgetc(f)) * 256) +
+ 	 ((guint16) fgetc(f));
+}
 
- return iOK;
+
+guint32 rd_be32(FILE *f)
+{
+ return (((guint32) fgetc(f)) * 16777216) +
+ 	(((guint32) fgetc(f)) * 65536) +
+ 	(((guint32) fgetc(f)) * 256) +
+ 	 ((guint32) fgetc(f));
+}
+
+
+gint rd_str(FILE *f, gchar *s, gint l)
+{
+ return fread(s, sizeof(gchar), l, f);
 }
 
 
+gint xs_get_sid_hash(gchar *fileName, t_xs_md5hash hash)
+{
+ FILE *inFile;
+ t_xs_md5state inState;
+ t_xs_psidv1_header psidH;
+ t_xs_psidv2_header psidH2;
+ guint8 *songData, ib8[2], i8;
+ gint iIndex, iRes, songDataLen;
+
+ /* Try to open the file */
+ if ((inFile = fopen(fileName, "rb")) == NULL)
+ 	return -1;
+
+ /* Read PSID header in */
+ rd_str(inFile, psidH.magicID, sizeof(psidH.magicID));
+ if ((psidH.magicID[0] != 'P') || (psidH.magicID[1] != 'S') || 
+     (psidH.magicID[2] != 'I') || (psidH.magicID[3] != 'D'))
+     return -2;
+ 
+ psidH.version		= rd_be16(inFile);
+ psidH.dataOffset	= rd_be16(inFile);
+ psidH.loadAddress	= rd_be16(inFile);
+ psidH.initAddress	= rd_be16(inFile);
+ psidH.playAddress	= rd_be16(inFile);
+ psidH.nSongs		= rd_be16(inFile);
+ psidH.startSong	= rd_be16(inFile);
+ psidH.speed		= rd_be32(inFile);
+
+ rd_str(inFile, psidH.sidName, sizeof(psidH.sidName));
+ rd_str(inFile, psidH.sidAuthor, sizeof(psidH.sidAuthor));
+ rd_str(inFile, psidH.sidCopyright, sizeof(psidH.sidCopyright));
+ 
+ /* Check if we need to load PSIDv2NG header ... */
+ if (psidH.version == 2)
+ 	{
+ 	/* Yes, we need to */
+ 	psidH2.flags		= rd_be16(inFile);
+ 	psidH2.startPage	= fgetc(inFile);
+ 	psidH2.pageLength	= fgetc(inFile);
+ 	psidH2.reserved		= rd_be16(inFile);
+ 	}
+
+ /* Get data length and seek to data offset */
+ fseek(inFile, 0L, SEEK_END);
+ songDataLen = ftell(inFile) - psidH.dataOffset;
+ fseek(inFile, psidH.dataOffset, SEEK_SET);
+
+ /* Allocate memory */ 
+ songData = (guint8 *) malloc(sizeof(guint8) * songDataLen);
+ if (songData == NULL)
+ 	{
+ 	fclose(inFile);
+ 	return -7;
+ 	}
+
+ /* Read data to buffer */
+ iRes = fread(songData, sizeof(guint8), songDataLen, inFile);
+ fclose(inFile);
+
+ if (iRes != songDataLen) return -9;
+ 
+ /* Initialize and start MD5-hash calculation */
+ xs_md5_init(&inState);
+ if (psidH.loadAddress == 0)
+ 	/* COULD SOMEONE EXPLAIN WHY DO WE NEED THIS +2 STRIP???? */
+	xs_md5_append(&inState, songData+2, iRes-2);
+	else
+	xs_md5_append(&inState, songData, iRes);
+	
+ free(songData);
+ 
+ /* Append header data to hash */
+#define XSADDHASH(QDATAB) { ib8[0] = (QDATAB & 0xff); ib8[1] = (QDATAB >> 8); xs_md5_append(&inState, (guint8 *) &ib8, sizeof(ib8)); }
+ 
+ XSADDHASH(psidH.initAddress)
+ XSADDHASH(psidH.playAddress)
+ XSADDHASH(psidH.nSongs)
+
+#undef XSADDHASH
+
+ /* Append song speed data to hash */ 
+ i8 = 0;
+ for (iIndex = 0; (iIndex < psidH.nSongs) && (iIndex < 32); iIndex++)
+ 	{
+ 	i8 = (psidH.speed & (1 << iIndex)) ? 60 : 0;
+ 	xs_md5_append(&inState, &i8, sizeof(i8));
+ 	}
+ 
+ /* Rest of songs (more than 32) */
+ for (iIndex = 32; iIndex < psidH.nSongs; iIndex++)
+ 	{
+ 	xs_md5_append(&inState, &i8, sizeof(i8));
+ 	}
+
+
+ /* PSIDv2NG specific */ 
+ if (psidH.version == 2)
+ 	{
+ 	/* SEE SIDPLAY HEADERS FOR INFO */
+ 	i8 = (psidH2.flags >> 2) & 3;
+	if (i8 == 2)
+		xs_md5_append(&inState, &i8, sizeof(i8));
+	}
+
+ /* Calculate the hash */
+ xs_md5_finish(&inState, hash);
+ 
+ return 0; 
+}
 
 
 /*
- * Get song length from database
+ * Get song length
  */
-t_xs_dbentry * xs_db_get(gchar *pcFilename)
+gint32 xs_get_songlength(gchar *fileName, gint subTune)
 {
-
- return NULL;
-}
-
-
-gint32 xs_get_length(gchar *pcFilename, gint iSubTune)
-{
+ t_xs_dbentry *dbEntry;
+ t_xs_md5hash dbHash;
  gint32 iResult;
- t_xs_dbentry *dbEntry;
- t_xs_hash dbHash;
 
  iResult = -1;
-
+ 
  switch (xs_cfg.playMethod) {
 
   case XMMS_SID_PMETHOD_DATABASE:
-  		
-	iResult = xs_db_get(pcFilename, &dbHash);
-	if (iResult >= 0)
+	/* Get the hash and then look up from db */
+	if (xs_get_sid_hash(fileName, dbHash) == 0)
 		{
-		}
-		
-	if (dbEntry)
-		{
-		if ((iSubTune >= 0) && (iSubTune < dbEntry->nlengths))
-			iResult = dbEntry->lengths[iSubTune];
-			else
-			iResult = -1;
+		fprinth(stderr, dbHash);
+		fprintf(stderr, "\n");
+		dbEntry = xs_db_get(dbHash);
+
+		if (dbEntry)
+			{
+			if ((subTune >= 0) && (subTune < dbEntry->nLengths))
+				iResult = dbEntry->sLengths[subTune];
+  			}
   		}
   	break;
 
@@ -255,8 +605,9 @@
 		iResult = -1;
  	}
  
- XSDEBUG("fname='%s', sub=%i, res=%li\n", pcFilename, iSubTune, iResult);
+ XSDEBUG("fname='%s', sub=%i, res=%i\n", fileName, subTune, iResult);
   
  return iResult;
 }
 
+
--- a/src/xs_length.h	Wed Jun 04 14:04:02 2003 +0000
+++ b/src/xs_length.h	Wed Jun 04 18:00:03 2003 +0000
@@ -5,27 +5,30 @@
 extern "C" {
 #endif
 
+#include <glib.h>
+#include "xs_md5.h"
 
 /*
  * Defines and typedefs
  */
-#define XS_HASH_LENGTH		(16)
-#define XS_HASH_LENGTH_CH	(XS_HASH_LENGTH * 2)
-
-typedef guint8 t_xs_hash[XS_HASH_LENGTH];
+#define XS_STIL_MAXENTRY	(32)
+typedef struct dbnode {
+	t_xs_md5hash	md5Hash;	/* 128-bit MD5 hash-digest */
+	gint		nLengths;	/* Number of lengths */
+	gint32		sLengths[XS_STIL_MAXENTRY];	
+					/* Lengths in seconds */
 
-typedef struct {
-	t_xs_hash	hashcode;	/* 128-bit MD5 hash-digest */
-	gint		nlengths;	/* Number of lengths */
-	long int	lengths[XMMS_SID_STIL_MAXENTRY];	
-					/* Lengths in seconds */
+	struct dbnode *pNext;
 } t_xs_dbentry;
 
 
 /*
  * Functions
  */
-gint32	xs_get_length(gchar *, gint);
+gint	xs_songlen_init(void);	/* Initialize songlength subsystem */
+gint	xs_songlen_close(void); /* Close/shutdown */
+
+gint32	xs_songlen_get(gchar *, gint); /* Get length in seconds */
 
 
 #ifdef __cplusplus
--- a/src/xs_md5.h	Wed Jun 04 14:04:02 2003 +0000
+++ b/src/xs_md5.h	Wed Jun 04 18:00:03 2003 +0000
@@ -5,7 +5,7 @@
 extern "C" {
 #endif
 
-#include "xs_length.h"
+#include <glib.h>
 
 /*
  * Typedefs
@@ -16,6 +16,11 @@
     guint8 buf[64];	/* accumulate block */
 } t_xs_md5state;
 
+#define XS_MD5HASH_LENGTH	(16)
+#define XS_MD5HASH_LENGTH_CH	(XS_MD5HASH_LENGTH * 2)
+
+typedef guint8 t_xs_md5hash[XS_MD5HASH_LENGTH];
+
 
 /*
  * Functions