123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351 |
- {
- This file is part of the Free Pascal packages.
- Copyright (c) 1999-2006 by the Free Pascal development team
- Implements a UUID generation algorithm (RFC 4122)
- See the file COPYING.FPC, included in this distribution,
- for details about the copyright.
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
- **********************************************************************}
- unit uuid;
- interface
- {$mode objfpc}
- {$h+}
- uses
- SysUtils, DateUtils, md5;
- (******************************************************************************
- * types and constants
- ******************************************************************************)
- type
- uuid_t = TGuid;
- uuid_time_t = qword;
- uuid_node_t = array[0..5] of byte;
- unsigned16 = word;
- uuid_state = record
- ts : uuid_time_t; // saved timestamp
- node : uuid_node_t; // saved node ID
- cs : unsigned16; // saved clock sequence
- end;
- const
- UUID_VERSION_1 = $1; // The time-based version specified in this document.
- UUID_VERSION_2 = $2; // DCE Security version, with embedded POSIX UIDs.
- UUID_VERSION_3 = $3; // The name-based version specified in this document that uses MD5 hashing.
- UUID_VERSION_4 = $4; // The randomly or pseudo-randomly generated version specified in this document.
- UUID_VERSION_5 = $5; // The name-based version specified in this document that uses SHA-1 hashing.
- { set the following to the number of 100ns ticks of the actual resolution of your system's clock }
- UUIDS_PER_TICK = 1024;
- (******************************************************************************
- * core uuid functions
- ******************************************************************************)
- { uuid_initialize -- used to initialize the uuid_create function }
- procedure uuid_initialize(const state: uuid_state);
- { uuid_create -- generator a UUID }
- function uuid_create(var uuid: uuid_t): boolean;
- { uuid_finalize -- returns the current state }
- procedure uuid_finalize(var state: uuid_state);
- { uuid_create_md5_from_name -- create a version 3 (MD5) UUID using a "name" from a "name space" }
- procedure uuid_create_md5_from_name(var uuid: uuid_t; const nsid: uuid_t; const name: string);
- { uuid_create_sha1_from_name -- create a version 5 (SHA-1) UUID using a "name" from a "name space" }
- procedure uuid_create_sha1_from_name(var uuid: uuid_t; const nsid: uuid_t; const name: string);
- { uuid_compare -- Compare two UUID's "lexically" }
- function uuid_compare(const u1, u2: uuid_t): integer;
- (******************************************************************************
- * auxilary functions
- ******************************************************************************)
- { read_state -- read UUID generator state from non-volatile store }
- function read_state(var clockseq: unsigned16; var timestamp: uuid_time_t; var node: uuid_node_t): boolean;
- { write_state -- save UUID generator state back to non-volatile storage }
- procedure write_state(var clockseq: unsigned16; const timestamp: uuid_time_t; const node: uuid_node_t);
- { format_uuid_v1 -- make a UUID from the timestamp, clockseq, and node ID }
- procedure format_uuid_v1(var uuid: uuid_t; const clockseq: unsigned16; const timestamp: uuid_time_t; const node: uuid_node_t);
- { format_uuid_v3or5 -- make a UUID from a (pseudo)random 128-bit number }
- procedure format_uuid_v3or5(var uuid: uuid_t; const hash: pointer; const v: integer);
- { get_current_time -- get time as 60-bit 100ns ticks since UUID epoch. Compensate for the fact that real clock resolution is less than 100ns. }
- procedure get_current_time(var timestamp: uuid_time_t);
- (******************************************************************************
- * system functions
- ******************************************************************************)
- { get_system_time -- system dependent call to get the current system time. Returned as 100ns ticks since UUID epoch, but resolution may be less than 100ns. }
- procedure get_system_time(var timestamp: uuid_time_t);
- { true_random -- generate a crypto-quality random number. }
- function true_random: unsigned16;
- implementation
- { uuid_initialize }
- var
- current_state : uuid_state;
- current_node : uuid_node_t;
- procedure uuid_initialize(const state: uuid_state);
- begin
- Randomize;
- current_node[0] := Random($100);
- current_node[1] := Random($100);
- current_node[2] := Random($100);
- current_node[3] := Random($100);
- current_node[4] := Random($100);
- current_node[5] := Random($100);
- current_state := state;
- end;
- { uuid_finalize }
- procedure uuid_finalize(var state: uuid_state);
- begin
- state := current_state;
- end;
- { uuid_create }
- function uuid_create(var uuid: TGuid): boolean;
- var
- timestamp: uuid_time_t;
- last_time: uuid_time_t;
- clockseq: unsigned16;
- last_node: uuid_node_t;
- f: boolean;
- begin
- (* acquire system-wide lock so we're alone *)
- // LOCK;
- (* get time, node ID, saved state from non-volatile storage *)
- get_current_time(timestamp);
- f := read_state(clockseq, last_time, last_node);
- (* if no NV state, or if clock went backwards, or node ID
- changed (e.g., new network card) change clockseq *)
- if not f or not CompareMem(@current_node, @last_node, sizeof(uuid_node_t)) then
- clockseq := true_random() else
- if timestamp < last_time then
- clockseq := clockseq + 1;
- (* save the state for next time *)
- write_state(clockseq, timestamp, current_node);
- // UNLOCK;
- (* stuff fields into the UUID *)
- format_uuid_v1(uuid, clockseq, timestamp, current_node);
- Result := true;
- end;
- { uuid_create_md5_from_name }
- procedure uuid_create_md5_from_name(var uuid: uuid_t; const nsid: uuid_t; const name: string);
- var
- net_nsid: uuid_t;
- c: TMDContext;
- hash: TMDDigest;
- begin
- (* 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 := ntobe(net_nsid.time_low);
- net_nsid.time_mid := ntobe(net_nsid.time_mid);
- net_nsid.time_hi_and_version := ntobe(net_nsid.time_hi_and_version);
- MDInit(c, MD_VERSION_5);
- MDUpdate(c, net_nsid, sizeof(net_nsid));
- MDUpdate(c, pchar(name)^, Length(name));
- MDFinal(c, hash);
- (* the hash is in network byte order at this point *)
- format_uuid_v3or5(uuid, @hash, UUID_VERSION_3);
- end;
- { uuid_create_sha1_from_name }
- procedure uuid_create_sha1_from_name(var uuid: uuid_t; const nsid: uuid_t; const name: string);
- var
- net_nsid: uuid_t;
- { c: TMDContext;
- hash: TMDDigest;}
- begin
- (* 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 := ntobe(net_nsid.time_low);
- net_nsid.time_mid := ntobe(net_nsid.time_mid);
- net_nsid.time_hi_and_version := ntobe(net_nsid.time_hi_and_version);
- {SHAInit(c, SHA_VERSION_1);
- SHAUpdate(c, net_nsid, sizeof(net_nsid));
- SHAUpdate(c, pchar(name)^, Length(name));
- SHAFinal(c, hash);}
- (* the hash is in network byte order at this point *)
- format_uuid_v3or5(uuid, @hash, UUID_VERSION_5);
- end;
- { uuid_compare }
- function uuid_compare(const u1, u2: uuid_t): integer;
- begin
- Result := pinteger(@u1)[0] - pinteger(@u2)[0];
- if Result <> 0 then Exit;
- Result := pinteger(@u1)[1] - pinteger(@u2)[1];
- if Result <> 0 then Exit;
- Result := pinteger(@u1)[2] - pinteger(@u2)[2];
- if Result <> 0 then Exit;
- Result := pinteger(@u1)[3] - pinteger(@u2)[3];
- end;
- { read_state }
- function read_state(var clockseq: unsigned16; var timestamp: uuid_time_t; var node: uuid_node_t): boolean;
- begin
- clockseq := current_state.cs;
- timestamp := current_state.ts;
- node := current_state.node;
- Result := true;
- end;
- { write_state }
- procedure write_state(var clockseq: unsigned16; const timestamp: uuid_time_t; const node: uuid_node_t);
- begin
- (* always save state to volatile shared state *)
- current_state.cs := clockseq;
- current_state.ts := timestamp;
- current_state.node := node;
- end;
- { format_uuid_v1 }
- procedure format_uuid_v1(var uuid: uuid_t; const clockseq: unsigned16; const timestamp: uuid_time_t; const node: uuid_node_t);
- begin
- uuid.time_low := timestamp and $FFFFFFFF;
- uuid.time_mid := (timestamp shr 32) and $FFFF;
- uuid.time_hi_and_version := (timestamp shr 48) and $0FFF;
- uuid.time_hi_and_version := uuid.time_hi_and_version or (UUID_VERSION_1 shl 12);
- uuid.clock_seq_low := clockseq and $FF;
- uuid.clock_seq_hi_and_reserved := (clockseq shr 8) and $3F;
- uuid.clock_seq_hi_and_reserved := uuid.clock_seq_hi_and_reserved or $80;
- uuid.node := node;
- end;
- { format_uuid_v3or5 }
- procedure format_uuid_v3or5(var uuid: uuid_t; const hash: pointer; const v: integer);
- begin
- (* convert UUID to local byte order *)
- move(hash^, uuid, sizeof(uuid));
- uuid.time_low := beton(uuid.time_low);
- uuid.time_mid := beton(uuid.time_mid);
- uuid.time_hi_and_version := beton(uuid.time_hi_and_version);
- (* put in the variant and version bits *)
- uuid.time_hi_and_version := uuid.time_hi_and_version and $0FFF;
- uuid.time_hi_and_version := uuid.time_hi_and_version or (v shl 12);
- uuid.clock_seq_hi_and_reserved := $3F;
- uuid.clock_seq_hi_and_reserved := uuid.clock_seq_hi_and_reserved or $80;
- end;
- { get_current_time }
- var
- time_last: uuid_time_t;
- uuids_this_tick: unsigned16 = UUIDS_PER_TICK;
- procedure get_current_time(var timestamp: uuid_time_t);
- var
- time_now: uuid_time_t;
- begin
- while true do
- begin
- get_system_time(time_now);
- (* if clock reading changed since last UUID generated, *)
- if time_last <> time_now then
- begin
- (* reset count of uuids gen'd with this clock reading *)
- uuids_this_tick := 0;
- time_last := time_now;
- Break;
- end;
- if uuids_this_tick < UUIDS_PER_TICK then
- begin
- uuids_this_tick := uuids_this_tick + 1;
- Break;
- end;
- (* going too fast for our clock; spin *)
- end;
- (* add the count of uuids to low order bits of the clock reading *)
- timestamp := time_now + uuids_this_tick;
- end;
- { get_system_time }
- procedure get_system_time(var timestamp: uuid_time_t);
- var
- Epoch:TDateTime;
- begin
- Epoch := EncodeDateTime(1582, 10, 15, 0, 0, 0, 0);
- timestamp := 10000*MilliSecondsBetween(Epoch, Now);
- end;
- { true_random }
- function true_random: unsigned16;
- begin
- Result := Random($10000);
- end;
- end.
|