/*
** Copyright (C) 1998-2000 Greg Stein. All Rights Reserved.
**
** By using this file, you agree to the terms and conditions set forth in
** the LICENSE.html file which can be found at the top level of the mod_dav
** distribution or at http://www.webdav.org/mod_dav/license-1.html.
**
** Contact information:
**   Greg Stein, PO Box 760, Palo Alto, CA, 94302
**   gstein@lyra.org, http://www.webdav.org/mod_dav/
*/

/*
** DAV opaquelocktoken scheme implementation
**
** Written 5/99 by Keith Wannamaker, wannamak@us.ibm.com
** Adapted from ISO/DCE RPC spec and a former Internet Draft
** by Leach and Salz:
** http://www.ics.uci.edu/pub/ietf/webdav/uuid-guid/draft-leach-uuids-guids-01
**
** Portions of the code are covered by the following license:
**
** Copyright (c) 1990- 1993, 1996 Open Software Foundation, Inc.
** Copyright (c) 1989 by Hewlett-Packard Company, Palo Alto, Ca. &
** Digital Equipment Corporation, Maynard, Mass.
** Copyright (c) 1998 Microsoft.
** To anyone who acknowledges that this file is provided "AS IS"
** without any express or implied warranty: permission to use, copy,
** modify, and distribute this file for any purpose is hereby
** granted without fee, provided that the above copyright notices and
** this notice appears in all source code copies, and that none of
** the names of Open Software Foundation, Inc., Hewlett-Packard
** Company, or Digital Equipment Corporation be used in advertising
** or publicity pertaining to distribution of the software without
** specific, written prior permission.  Neither Open Software
** Foundation, Inc., Hewlett-Packard Company, Microsoft, nor Digital Equipment
** Corporation makes any representations about the suitability of
** this software for any purpose.
*/

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

#include "httpd.h"
#include "ap_md5.h"
#include "ap_ctype.h"
#include "http_log.h"

#include "mod_dav.h"
#include "dav_opaquelock.h"

#ifdef WIN32
#include <windows.h>
#else
#include <sys/types.h>
#include <sys/time.h>
#endif

/* set the following to the number of 100ns ticks of the actual resolution of
   your system's clock */
#define UUIDS_PER_TICK 1024

/* Set this to what your compiler uses for 64 bit data type */
#ifdef WIN32
#define unsigned64_t unsigned __int64
#else
#define unsigned64_t unsigned long long
#endif

typedef unsigned64_t uuid_time_t;

static void format_uuid_v1(uuid_t * uuid, unsigned16 clockseq, uuid_time_t timestamp, uuid_node_t node);
static void get_current_time(uuid_time_t * timestamp);
static unsigned16 true_random(void);
static void get_pseudo_node_identifier(uuid_node_t *node);
static void get_system_time(uuid_time_t *uuid_time);
static void get_random_info(unsigned char seed[16]);


/* dav_create_opaquelocktoken - generates a UUID version 1 token.
 *   Clock_sequence and node_address set to pseudo-random
 *   numbers during init.  
 *
 *   Should postpend pid to account for non-seralized creation?
 */
void dav_create_opaquelocktoken(uuid_state *st, uuid_t *u)
{
    uuid_time_t timestamp;
    
    get_current_time(&timestamp);
    format_uuid_v1(u, st->cs, timestamp, st->node);
}

/*
 * dav_create_uuid_state - seed UUID state with pseudorandom data
 */
void dav_create_uuid_state(uuid_state *st)
{
    st->cs = true_random();
    get_pseudo_node_identifier(&st->node);
}

/*
 * dav_format_opaquelocktoken - generates a text representation
 *    of an opaquelocktoken
 */
const char *dav_format_opaquelocktoken(pool *p, const uuid_t *u)
{
    return ap_psprintf(p, "%08lx-%04x-%04x-%02x%02x-%02x%02x%02x%02x%02x%02x",
		       u->time_low, u->time_mid, u->time_hi_and_version,
		       u->clock_seq_hi_and_reserved, u->clock_seq_low,
		       u->node[0], u->node[1], u->node[2],
		       u->node[3], u->node[4], u->node[5]);
}

/* convert a pair of hex digits to an integer value [0,255] */
static int dav_parse_hexpair(const char *s)
{
    int result;
    int temp;

#ifdef CHARSET_EBCDIC
    ebcdic2ascii(s, s, 2);
    result = s[0] - (char)0x30;
#else
    result = s[0] - '0';
#endif

    if (result > 48)
	result = (result - 39) << 4;
    else if (result > 16)
	result = (result - 7) << 4;
    else
	result = result << 4;

#ifdef CHARSET_EBCDIC
    temp = s[1] - (char)0x30;
#else
    temp = s[1] - '0';
#endif
    if (temp > 48)
	result |= temp - 39;
    else if (temp > 16)
	result |= temp - 7;
    else
	result |= temp;

#ifdef CHARSET_EBCDIC
    ascii2ebcdic(s,s,2);
#endif
    return result;
}

/* dav_parse_opaquelocktoken:  Parses string produced from
 *    dav_format_opaquelocktoken back into a uuid_t
 *    structure.
 *
 * Returns 0 on success, 1 on failure.
 */
int dav_parse_opaquelocktoken(const char *char_token, uuid_t *bin_token)
{
    int i;

    for (i = 0; i < 36; ++i) {
	char c = char_token[i];
	if (!ap_isxdigit(c) &&
	    !(c == '-' && (i == 8 || i == 13 || i == 18 || i == 23)))
	    return 1; /* failure */
    }
    if (char_token[36] != '\0')
	return 1; /* failure */

    bin_token->time_low =
	(dav_parse_hexpair(&char_token[0]) << 24) |
	(dav_parse_hexpair(&char_token[2]) << 16) |
	(dav_parse_hexpair(&char_token[4]) << 8) |
	dav_parse_hexpair(&char_token[6]);

    bin_token->time_mid =
	(dav_parse_hexpair(&char_token[9]) << 8) |
	dav_parse_hexpair(&char_token[11]);

    bin_token->time_hi_and_version =
	(dav_parse_hexpair(&char_token[14]) << 8) |
	dav_parse_hexpair(&char_token[16]);

    bin_token->clock_seq_hi_and_reserved = dav_parse_hexpair(&char_token[19]);
    bin_token->clock_seq_low = dav_parse_hexpair(&char_token[21]);

    for (i = 6; i--;)
	bin_token->node[i] = dav_parse_hexpair(&char_token[i*2+24]);

    return 0;
}

/* dav_compare_opaquelocktoken:
 *    < 0 : a < b
 *   == 0 : a = b
 *    > 0 : a > b
 */
int dav_compare_opaquelocktoken(const uuid_t a, const uuid_t b)
{
    return memcmp(&a, &b, sizeof(uuid_t));
}

/* format_uuid_v1 -- make a UUID from the timestamp, clockseq, and node ID */
static void format_uuid_v1(uuid_t * uuid, unsigned16 clock_seq,
			   uuid_time_t timestamp, uuid_node_t node)
{
    /* Construct a version 1 uuid with the information we've gathered
     * plus a few constants. */
    uuid->time_low = (unsigned long)(timestamp & 0xFFFFFFFF);
    uuid->time_mid = (unsigned short)((timestamp >> 32) & 0xFFFF);
    uuid->time_hi_and_version = (unsigned short)((timestamp >> 48) & 0x0FFF);
    uuid->time_hi_and_version |= (1 << 12);
    uuid->clock_seq_low = clock_seq & 0xFF;
    uuid->clock_seq_hi_and_reserved = (clock_seq & 0x3F00) >> 8;
    uuid->clock_seq_hi_and_reserved |= 0x80;
    memcpy(&uuid->node, &node, sizeof uuid->node);
}

/* get-current_time -- get time as 60 bit 100ns ticks since whenever.
   Compensate for the fact that real clock resolution is less than 100ns. */
static void get_current_time(uuid_time_t * timestamp)
{
    uuid_time_t         time_now;
    static uuid_time_t  time_last;
    static unsigned16   uuids_this_tick;
    static int          inited = 0;
    
    if (!inited) {
        get_system_time(&time_now);
        uuids_this_tick = UUIDS_PER_TICK;
        inited = 1;
    };
	
    while (1) {
	get_system_time(&time_now);
        
	/* if clock reading changed since last UUID generated... */
	if (time_last != time_now) {
	    /* reset count of uuids gen'd with this clock reading */
	    uuids_this_tick = 0;
	    break;
	};
	if (uuids_this_tick < UUIDS_PER_TICK) {
	    uuids_this_tick++;
	    break;
	};        /* going too fast for our clock; spin */
    };    /* add the count of uuids to low order bits of the clock reading */

    *timestamp = time_now + uuids_this_tick;
}

/* true_random -- generate a crypto-quality random number.
   This sample doesn't do that. */
static unsigned16 true_random(void)
{
    uuid_time_t time_now;

    get_system_time(&time_now);
    time_now = time_now/UUIDS_PER_TICK;
    srand((unsigned int)(((time_now >> 32) ^ time_now)&0xffffffff));

    return rand();
}

/* This sample implementation generates a random node ID   *
 * in lieu of a system dependent call to get IEEE node ID. */
static void get_pseudo_node_identifier(uuid_node_t *node)
{
    unsigned char seed[16];

    get_random_info(seed);
    seed[0] |= 0x80;
    memcpy(node, seed, sizeof(*node));
}

/* system dependent call to get the current system time.
   Returned as 100ns ticks since Oct 15, 1582, but resolution may be
   less than 100ns.  */

#ifdef WIN32

static void get_system_time(uuid_time_t *uuid_time)
{
    ULARGE_INTEGER time;

    GetSystemTimeAsFileTime((FILETIME *)&time);

    /* NT keeps time in FILETIME format which is 100ns ticks since
       Jan 1, 1601.  UUIDs use time in 100ns ticks since Oct 15, 1582.
       The difference is 17 Days in Oct + 30 (Nov) + 31 (Dec)
       + 18 years and 5 leap days.    */
	
    time.QuadPart +=
	(unsigned __int64) (1000*1000*10)		/* seconds */
	* (unsigned __int64) (60 * 60 * 24)		/* days */
	* (unsigned __int64) (17+30+31+365*18+5);	/* # of days */
    *uuid_time = time.QuadPart;
}

static void get_random_info(unsigned char seed[16])
{
    AP_MD5_CTX c;
    struct {
        MEMORYSTATUS m;
        SYSTEM_INFO s;
        FILETIME t;
        LARGE_INTEGER pc;
        DWORD tc;
        DWORD l;
        char hostname[MAX_COMPUTERNAME_LENGTH + 1];

    } r;
    
    ap_MD5Init(&c);    /* memory usage stats */
    GlobalMemoryStatus(&r.m);    /* random system stats */
    GetSystemInfo(&r.s);    /* 100ns resolution (nominally) time of day */
    GetSystemTimeAsFileTime(&r.t);    /* high resolution performance counter */
    QueryPerformanceCounter(&r.pc);    /* milliseconds since last boot */
    r.tc = GetTickCount();
    r.l = MAX_COMPUTERNAME_LENGTH + 1;

    GetComputerName(r.hostname, &r.l );
    ap_MD5Update(&c, (const unsigned char *) &r, sizeof(r));
    ap_MD5Final(seed, &c);
}

#else /* WIN32 */

static void get_system_time(uuid_time_t *uuid_time)
{
    struct timeval tp;

    gettimeofday(&tp, (struct timezone *)0);

    /* Offset between UUID formatted times and Unix formatted times.
       UUID UTC base time is October 15, 1582.
       Unix base time is January 1, 1970.      */
    *uuid_time = (tp.tv_sec * 10000000) + (tp.tv_usec * 10) +
        0x01B21DD213814000LL;
}
 
static void get_random_info(unsigned char seed[16])
{
    AP_MD5_CTX c;
    /* Leech & Salz use Linux-specific struct sysinfo;
     * replace with pid/tid for portability (in the spirit of mod_unique_id) */
    struct {
	/* Add thread id here, if applicable, when we get to pthread or apr */
	pid_t pid;
        struct timeval t;
        char hostname[257];

    } r;

    ap_MD5Init(&c);
    r.pid = getpid();
    gettimeofday(&r.t, (struct timezone *)0);
    gethostname(r.hostname, 256);
    ap_MD5Update(&c, (const unsigned char *)&r, sizeof(r));
    ap_MD5Final(seed, &c);
}

#endif /* WIN32 */
