/**************************************************************************
 *
 * 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 "config.h"


#include "sysdep.h"
#include "uuid.h"


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


#ifdef WIN32
	#include <winsock2.h>
#else
	#include <netinet/in.h>
#endif


/*
   various forward declarations 
 */
static int read_state( unsigned16 * clockseq,
                       uuid_time_t * timestamp,
                       uuid_node_t * node );
static void write_state( unsigned16 clockseq,
                         uuid_time_t timestamp,
                         uuid_node_t node );
static void format_uuid_v1( uuid_upnp * uid,
                            unsigned16 clockseq,
                            uuid_time_t timestamp,
                            uuid_node_t node );
static void format_uuid_v3( uuid_upnp * uid,
                            unsigned char hash[16] );
static void get_current_time( uuid_time_t * timestamp );
static unsigned16 true_random( void );

/*-----------------------------------------------------------------------------*/
/*
   uuid_create -- generator a UUID 
 */
int
uuid_create(uuid_upnp *uid)
{
    uuid_time_t timestamp;
    uuid_time_t last_time;
    unsigned16 clockseq;
    uuid_node_t node;
    uuid_node_t last_node;
    int f;

    /*
       acquire system wide lock so we're alone 
     */
    UUIDLock();

    /*
       get current time 
     */
    get_current_time(&timestamp);

    /*
       get node ID 
     */
    get_ieee_node_identifier(&node);

    /*
       get saved state from NV storage 
     */
    f = read_state(&clockseq, &last_time, &last_node);

    /*
       if no NV state, or if clock went backwards, or node ID changed
       (e.g., net card swap) change clockseq 
     */
    if (!f || memcmp(&node, &last_node, sizeof(uuid_node_t))) {
        clockseq = true_random();
    } else if (timestamp < last_time) {
        clockseq++;
    }

    /*
       stuff fields into the UUID 
     */
    format_uuid_v1(uid, clockseq, timestamp, node);

    /*
       save the state for next time 
     */
    write_state(clockseq, timestamp, node);

    UUIDUnlock();
    return 1;
};

/*-----------------------------------------------------------------------------*/
void
uuid_unpack(uuid_upnp *u, char *out)
{
	sprintf(out,
		"%8.8x-%4.4x-%4.4x-%2.2x%2.2x-%2.2x%2.2x%2.2x%2.2x%2.2x%2.2x",
		(unsigned int)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]);
};

/*-----------------------------------------------------------------------------*/
/*
   format_uuid_v1 -- make a UUID from the timestamp, clockseq,
   and node ID 
 */
void
format_uuid_v1( uuid_upnp * uid,
                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. 
     */
    uid->time_low = (unsigned long)(timestamp & 0xFFFFFFFF);
    uid->time_mid = (unsigned short)((timestamp >> 32) & 0xFFFF);
    uid->time_hi_and_version = (unsigned short)((timestamp >> 48) & 0x0FFF);
    uid->time_hi_and_version |= (1 << 12);
    uid->clock_seq_low = clock_seq & 0xFF;
    uid->clock_seq_hi_and_reserved = ( clock_seq & 0x3F00 ) >> 8;
    uid->clock_seq_hi_and_reserved |= 0x80;
    memcpy( &uid->node, &node, sizeof uid->node );
};

/*-----------------------------------------------------------------------------*/
/*
   data type for UUID generator persistent state 
 */
typedef struct {
    uuid_time_t ts;             /* saved timestamp */
    uuid_node_t node;           /* saved node ID */
    unsigned16 cs;              /* saved clock sequence */
} uuid_state;

static uuid_state st;
static int stateInited = 0;

/*-----------------------------------------------------------------------------*/
/*
   read_state -- read UUID generator state from non-volatile store 
 */
int
read_state( unsigned16 * clockseq,
            uuid_time_t * timestamp,
            uuid_node_t * node )
{

    if( !stateInited ) {
        return 0;
    };

    *clockseq = st.cs;
    *timestamp = st.ts;
    *node = st.node;
    return 1;
};

/*-----------------------------------------------------------------------------*/
/*
   write_state -- save UUID generator state back to non-volatile
   storage 
 */
void
write_state( unsigned16 clockseq,
             uuid_time_t timestamp,
             uuid_node_t node )
{
    static uuid_time_t next_save;

    if( !stateInited ) {
        next_save = timestamp;
        stateInited = 1;
    };

    /*
       always save state to volatile shared state 
     */
    st.cs = clockseq;
    st.ts = timestamp;
    st.node = node;
    if( timestamp >= next_save ) {
        /*
           schedule next save for 10 seconds from now 
         */
        next_save = timestamp + ( 10 * 10 * 1000 * 1000 );
    };
};

/*-----------------------------------------------------------------------------*/
/*
   get-current_time -- get time as 60 bit 100ns ticks since whenever.
   Compensate for the fact that real clock resolution is
   less than 100ns. 
 */
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 ) {
        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;
    time_last = *timestamp;
};

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

    if( !inited ) {
        get_system_time( &time_now );
        time_now = time_now / UUIDS_PER_TICK;
        srand( ( unsigned int )( ( ( time_now >> 32 ) ^ time_now ) &
                                 0xffffffff ) );
        inited = 1;
    };

    return ( rand() );
}

/*-----------------------------------------------------------------------------*/
/*
   uuid_create_from_name -- create a UUID using a "name" from a "name space" 
 */
void
uuid_create_from_name( uuid_upnp * uid, /* resulting UUID */

                       uuid_upnp nsid,  /* UUID to serve as context, so identical
                                           names from different name spaces generate
                                           different UUIDs */

                       void *name,  /* the name from which to generate a UUID */

                       int namelen  /* the length of the name */
     )
{
    MD5_CTX c;
    unsigned char hash[16];
    uuid_upnp net_nsid;         /* context UUID in network byte order */

    /*
       put name space ID in network byte order so it hashes the same
       no matter what endian machine we're on 
     */
    net_nsid = nsid;
    net_nsid.time_low=htonl( net_nsid.time_low );
    net_nsid.time_mid=htons( net_nsid.time_mid );
    net_nsid.time_hi_and_version=htons( net_nsid.time_hi_and_version );

    MD5Init( &c );
    MD5Update( &c, &net_nsid, sizeof( uuid_upnp ) );
    MD5Update( &c, name, namelen );
    MD5Final( hash, &c );

    /*
       the hash is in network byte order at this point 
     */
    format_uuid_v3( uid, hash );
};

/*-----------------------------------------------------------------------------*/
/*
   format_uuid_v3 -- make a UUID from a (pseudo)random 128 bit number
 */
void
format_uuid_v3( uuid_upnp * uid,
                unsigned char hash[16] )
{
    /*
       Construct a version 3 uuid with the (pseudo-)random number
       * plus a few constants. 
     */

    memcpy( uid, hash, sizeof( uuid_upnp ) );

    /*
       convert UUID to local byte order 
     */
    uid->time_low=ntohl( uid->time_low );
    uid->time_mid=ntohs( uid->time_mid );
    uid->time_hi_and_version=ntohs( uid->time_hi_and_version );

    /*
       put in the variant and version bits 
     */
    uid->time_hi_and_version &= 0x0FFF;
    uid->time_hi_and_version |= ( 3 << 12 );
    uid->clock_seq_hi_and_reserved &= 0x3F;
    uid->clock_seq_hi_and_reserved |= 0x80;
};

/*-----------------------------------------------------------------------------*/
/*
   uuid_compare --  Compare two UUID's "lexically" and return
   -1   u1 is lexically before u2
   0   u1 is equal to u2
   1   u1 is lexically after u2

   Note:   lexical ordering is not temporal ordering!
 */
int
uuid_compare( uuid_upnp * u1,
              uuid_upnp * u2 )
{
    int i;

#define CHECK(f1, f2) if (f1 != f2) return f1 < f2 ? -1 : 1;
    CHECK( u1->time_low, u2->time_low );
    CHECK( u1->time_mid, u2->time_mid );
    CHECK( u1->time_hi_and_version, u2->time_hi_and_version );
    CHECK( u1->clock_seq_hi_and_reserved, u2->clock_seq_hi_and_reserved );
    CHECK( u1->clock_seq_low, u2->clock_seq_low )
        for( i = 0; i < 6; i++ ) {
        if( u1->node[i] < u2->node[i] )
            return -1;
        if( u1->node[i] > u2->node[i] )
            return 1;
    }

    return 0;
};

