1#include "EXTERN.h"
2#include "perl.h"
3#include "XSUB.h"
4#include "UUID.h"
5
6#if defined __BEOS__ || defined __HAIKU__
7#  undef bool
8#  include <OS.h>
9#endif
10
11#ifdef USE_ITHREADS
12# define DU_THREADSAFE 1
13#else
14# define DU_THREADSAFE 0
15#endif
16
17#if DU_THREADSAFE
18
19# define pPTBL   pTHX
20# define pPTBL_  pTHX_
21# define aPTBL   aTHX
22# define aPTBL_  aTHX_
23
24# define PTABLE_VAL_FREE(V) ((void) (V))
25
26# include "ptable.h"
27
28# define ptable_store(T, K, V)  ptable_store(aTHX_ (T), (K), (V))
29
30static ptable *instances;
31static perl_mutex instances_mutex;
32
33static void inc(pTHX_ ptable_ent *ent, void *ud) {
34    UV count = PTR2UV(ent->val);
35    PERL_UNUSED_VAR(ud);
36    ptable_store(instances, ent->key, (void *)++count);
37}
38
39#endif
40
41static  perl_uuid_t NameSpace_DNS = { /* 6ba7b810-9dad-11d1-80b4-00c04fd430c8 */
42   0x6ba7b810,
43   0x9dad,
44   0x11d1,
45   0x80, 0xb4, { 0x00, 0xc0, 0x4f, 0xd4, 0x30, 0xc8 }
46};
47
48static  perl_uuid_t NameSpace_URL = { /* 6ba7b811-9dad-11d1-80b4-00c04fd430c8 */
49   0x6ba7b811,
50   0x9dad,
51   0x11d1,
52   0x80, 0xb4, { 0x00, 0xc0, 0x4f, 0xd4, 0x30, 0xc8 }
53};
54
55static  perl_uuid_t NameSpace_OID = { /* 6ba7b812-9dad-11d1-80b4-00c04fd430c8 */
56   0x6ba7b812,
57   0x9dad,
58   0x11d1,
59   0x80, 0xb4, { 0x00, 0xc0, 0x4f, 0xd4, 0x30, 0xc8 }
60};
61
62static  perl_uuid_t NameSpace_X500 = { /* 6ba7b814-9dad-11d1-80b4-00c04fd430c8 */
63   0x6ba7b814,
64   0x9dad,
65   0x11d1,
66   0x80, 0xb4, { 0x00, 0xc0, 0x4f, 0xd4, 0x30, 0xc8 }
67};
68
69static void format_uuid_v1(
70   perl_uuid_t     *uuid,
71   unsigned16  clock_seq,
72   perl_uuid_time_t timestamp,
73   uuid_node_t node
74) {
75   uuid->time_low = (unsigned long)(timestamp & 0xFFFFFFFF);
76   uuid->time_mid = (unsigned short)((timestamp >> 32) & 0xFFFF);
77   uuid->time_hi_and_version = (unsigned short)((timestamp >> 48) &
78      0x0FFF);
79
80   uuid->time_hi_and_version |= (1 << 12);
81   uuid->clock_seq_low = clock_seq & 0xFF;
82   uuid->clock_seq_hi_and_reserved = (clock_seq & 0x3F00) >> 8;
83   uuid->clock_seq_hi_and_reserved |= 0x80;
84   memcpy(&uuid->node, &node, sizeof uuid->node);
85}
86
87static void get_current_time(perl_uuid_time_t * timestamp) {
88   perl_uuid_time_t        time_now;
89   static perl_uuid_time_t time_last;
90   static unsigned16  uuids_this_tick;
91   static int         inited = 0;
92
93   if (!inited) {
94      get_system_time(&time_last);
95      uuids_this_tick = UUIDS_PER_TICK;
96      inited = 1;
97   };
98   while (1) {
99      get_system_time(&time_now);
100
101      if (time_last != time_now) {
102         uuids_this_tick = 0;
103         time_last = time_now;
104         break;
105      };
106      if (uuids_this_tick < UUIDS_PER_TICK) {
107         uuids_this_tick++;
108         break;
109      };
110   };
111   *timestamp = time_now + uuids_this_tick;
112}
113
114static unsigned16 true_random(void) {
115   static int  inited = 0;
116   perl_uuid_time_t time_now;
117
118   if (!inited) {
119      get_system_time(&time_now);
120      time_now = time_now/UUIDS_PER_TICK;
121      srand((unsigned int)(((time_now >> 32) ^ time_now)&0xffffffff));
122      inited = 1;
123    };
124    return (rand());
125}
126
127static void format_uuid_v3(
128   perl_uuid_t        *uuid,
129   unsigned char  hash[16]
130) {
131   memcpy(uuid, hash, sizeof(perl_uuid_t));
132
133   uuid->time_low            = ntohl(uuid->time_low);
134   uuid->time_mid            = ntohs(uuid->time_mid);
135   uuid->time_hi_and_version = ntohs(uuid->time_hi_and_version);
136
137   uuid->time_hi_and_version &= 0x0FFF;
138   uuid->time_hi_and_version |= (3 << 12);
139   uuid->clock_seq_hi_and_reserved &= 0x3F;
140   uuid->clock_seq_hi_and_reserved |= 0x80;
141}
142
143static void get_system_time(perl_uuid_time_t *perl_uuid_time) {
144#if defined __cygwin__ || defined __MINGW32__ || defined WIN32
145   /* ULARGE_INTEGER time; */
146   LARGE_INTEGER time;
147
148   /* use QeryPerformanceCounter for +ms resolution - as per Paul Stodghill
149   GetSystemTimeAsFileTime((FILETIME *)&time); */
150   QueryPerformanceCounter(&time);
151   time.QuadPart +=
152      (unsigned __int64) (1000*1000*10) *
153      (unsigned __int64) (60 * 60 * 24) *
154      (unsigned __int64) (17+30+31+365*18+5);
155
156   *perl_uuid_time = time.QuadPart;
157#else
158   struct timeval tp;
159
160   gettimeofday(&tp, (struct timezone *)0);
161   *perl_uuid_time = (tp.tv_sec * I64(10000000)) + (tp.tv_usec * I64(10)) +
162      I64(0x01B21DD213814000);
163#endif
164}
165
166static void get_random_info(unsigned char seed[16]) {
167   SV* ctx;
168#if defined __cygwin__ || defined __MINGW32__ || defined __MSWin32__
169   typedef struct {
170      MEMORYSTATUS  m;
171      SYSTEM_INFO   s;
172      FILETIME      t;
173      LARGE_INTEGER pc;
174      DWORD         tc;
175      DWORD         l;
176      char          hostname[MAX_COMPUTERNAME_LENGTH + 1];
177   } randomness;
178#else
179   typedef struct {
180#if defined __BEOS__ || defined __HAIKU__
181      system_info    sys_info;
182#else
183      long           hostid;
184#endif
185      struct timeval t;
186      char           hostname[257];
187   } randomness;
188#endif
189   randomness r;
190
191#if defined __cygwin__ || defined __MINGW32__ || defined __MSWin32__
192   GlobalMemoryStatus(&r.m);
193   GetSystemInfo(&r.s);
194   GetSystemTimeAsFileTime(&r.t);
195   QueryPerformanceCounter(&r.pc);
196   r.tc = GetTickCount();
197   r.l = MAX_COMPUTERNAME_LENGTH + 1;
198   GetComputerName(r.hostname, &r.l );
199#else
200#  if defined __BEOS__ || defined __HAIKU__
201   get_system_info(&r.sys_info);
202#  else
203   r.hostid = gethostid();
204#  endif
205   gettimeofday(&r.t, (struct timezone *)0);
206   gethostname(r.hostname, 256);
207#endif
208
209   ctx = MD5Init();
210   MD5Update(ctx, sv_2mortal(newSVpv((char*)&r, sizeof(randomness))));
211   MD5Final(seed, ctx);
212}
213
214static SV* make_ret(const perl_uuid_t u, int type) {
215   char                 buf[BUFSIZ];
216   const unsigned char *from;
217   unsigned char       *to;
218   STRLEN               len;
219   int                  i;
220
221   memset(buf, 0x00, BUFSIZ);
222   switch(type) {
223   case F_BIN:
224      memcpy(buf, &u, sizeof(perl_uuid_t));
225      len = sizeof(perl_uuid_t);
226      break;
227   case F_STR:
228      sprintf(buf, "%8.8X-%4.4X-%4.4X-%2.2X%2.2X-", (unsigned int)u.time_low, u.time_mid,
229	 u.time_hi_and_version, u.clock_seq_hi_and_reserved, u.clock_seq_low);
230      for(i = 0; i < 6; i++ )
231	 sprintf(buf+strlen(buf), "%2.2X", u.node[i]);
232      len = strlen(buf);
233      break;
234   case F_HEX:
235      sprintf(buf, "0x%8.8X%4.4X%4.4X%2.2X%2.2X", (unsigned int)u.time_low, u.time_mid,
236	 u.time_hi_and_version, u.clock_seq_hi_and_reserved, u.clock_seq_low);
237      for(i = 0; i < 6; i++ )
238	 sprintf(buf+strlen(buf), "%2.2X", u.node[i]);
239      len = strlen(buf);
240      break;
241   case F_B64:
242      for(from = (const unsigned char*)&u, to = (unsigned char*)buf, i = sizeof(u); i > 0; i -= 3, from += 3) {
243         *to++ = base64[from[0]>>2];
244         switch(i) {
245	 case 1:
246	    *to++ = base64[(from[0]&0x03)<<4];
247	    *to++ = '=';
248	    *to++ = '=';
249	     break;
250         case 2:
251	    *to++ = base64[((from[0]&0x03)<<4) | ((from[1]&0xF0)>>4)];
252	    *to++ = base64[(from[1]&0x0F)<<2];
253	    *to++ = '=';
254	     break;
255         default:
256	    *to++ = base64[((from[0]&0x03)<<4) | ((from[1]&0xF0)>>4)];
257	    *to++ = base64[((from[1]&0x0F)<<2) | ((from[2]&0xC0)>>6)];
258	    *to++ = base64[(from[2]&0x3F)];
259         }
260      }
261      len = strlen(buf);
262      break;
263   default:
264      croak("invalid type: %d\n", type);
265      break;
266   }
267   return sv_2mortal(newSVpv(buf,len));
268}
269
270static SV* MD5Init() {
271   SV* res;
272   int rcount;
273
274   dSP;
275
276   ENTER; SAVETMPS;
277
278   PUSHMARK(SP);
279   XPUSHs(sv_2mortal(newSVpv("Digest::MD5", 0)));
280   PUTBACK;
281
282   rcount = call_method("new", G_SCALAR);
283   SPAGAIN;
284
285   if ( rcount != 1 )
286       croak("couldn't construct new Digest::MD5 object");
287
288   res = newSVsv(POPs);
289
290   PUTBACK;
291   FREETMPS;
292   LEAVE;
293
294   return res;
295};
296
297static void MD5Update( SV* ctx, SV* data ) {
298   dSP;
299   ENTER; SAVETMPS;
300
301   PUSHMARK(SP);
302   XPUSHs(ctx);
303   XPUSHs(data);
304   PUTBACK;
305
306   call_method("add", G_DISCARD);
307   SPAGAIN;
308
309   PUTBACK;
310   FREETMPS;
311   LEAVE;
312};
313
314static void MD5Final( unsigned char hash[16], SV* ctx ) {
315   int rcount;
316   char* tmp;
317   STRLEN len;
318   SV* retval;
319   dSP;
320
321   ENTER; SAVETMPS;
322
323   PUSHMARK(SP);
324   XPUSHs(sv_2mortal(ctx));
325   PUTBACK;
326
327   rcount = call_method("digest", G_SCALAR);
328   SPAGAIN;
329
330   if ( rcount != 1 )
331       croak("Digest::MD5->digest hasn't returned a scalar");
332
333   retval = POPs;
334   tmp = SvPV(retval, len);
335   if ( len != 16 )
336       croak("Digest::MD5->digest returned not 16 bytes");
337
338   memcpy(hash, tmp, len);
339
340   PUTBACK;
341   FREETMPS;
342   LEAVE;
343};
344
345MODULE = Data::UUID		PACKAGE = Data::UUID
346
347PROTOTYPES: DISABLE
348
349uuid_context_t*
350new(class)
351PREINIT:
352   FILE          *fd;
353   unsigned char  seed[16];
354   perl_uuid_time_t    timestamp;
355   mode_t         mask;
356   UV             one = 1;
357CODE:
358   RETVAL = (uuid_context_t *)PerlMemShared_malloc(sizeof(uuid_context_t));
359   if ((fd = fopen(UUID_STATE_NV_STORE, "rb"))) {
360      fread(&(RETVAL->state), sizeof(uuid_state_t), 1, fd);
361      fclose(fd);
362      get_current_time(&timestamp);
363      RETVAL->next_save = timestamp;
364   }
365   if ((fd = fopen(UUID_NODEID_NV_STORE, "rb"))) {
366      pid_t *hate = (pid_t *) &(RETVAL->nodeid);
367      fread(&(RETVAL->nodeid), sizeof(uuid_node_t), 1, fd );
368      fclose(fd);
369
370      *hate += getpid();
371   } else {
372      get_random_info(seed);
373      seed[0] |= 0x80;
374      memcpy(&(RETVAL->nodeid), seed, sizeof(uuid_node_t));
375      mask = umask(_DEFAULT_UMASK);
376      if ((fd = fopen(UUID_NODEID_NV_STORE, "wb"))) {
377         fwrite(&(RETVAL->nodeid), sizeof(uuid_node_t), 1, fd);
378         fclose(fd);
379      };
380      umask(mask);
381   }
382   errno = 0;
383#if DU_THREADSAFE
384   MUTEX_LOCK(&instances_mutex);
385   ptable_store(instances, RETVAL, INT2PTR(void *, one));
386   MUTEX_UNLOCK(&instances_mutex);
387#endif
388OUTPUT:
389   RETVAL
390
391void
392create(self)
393   uuid_context_t *self;
394ALIAS:
395   Data::UUID::create_bin = F_BIN
396   Data::UUID::create_str = F_STR
397   Data::UUID::create_hex = F_HEX
398   Data::UUID::create_b64 = F_B64
399PREINIT:
400   perl_uuid_time_t  timestamp;
401   unsigned16   clockseq;
402   perl_uuid_t       uuid;
403   FILE        *fd;
404   mode_t       mask;
405PPCODE:
406   clockseq = self->state.cs;
407   get_current_time(&timestamp);
408   if ( self->state.ts == I64(0) ||
409      memcmp(&(self->nodeid), &(self->state.node), sizeof(uuid_node_t)))
410      clockseq = true_random();
411   else if (timestamp <= self->state.ts)
412      clockseq++;
413
414   format_uuid_v1(&uuid, clockseq, timestamp, self->nodeid);
415   self->state.node = self->nodeid;
416   self->state.ts   = timestamp;
417   self->state.cs   = clockseq;
418   if (timestamp > self->next_save ) {
419      mask = umask(_DEFAULT_UMASK);
420      if((fd = fopen(UUID_STATE_NV_STORE, "wb"))) {
421	 LOCK(fd);
422         fwrite(&(self->state), sizeof(uuid_state_t), 1, fd);
423	 UNLOCK(fd);
424         fclose(fd);
425      }
426      umask(mask);
427      self->next_save = timestamp + (10 * 10 * 1000 * 1000);
428   }
429   ST(0) = make_ret(uuid, ix);
430   XSRETURN(1);
431
432void
433create_from_name(self,nsid,name)
434   uuid_context_t *self;
435   perl_uuid_t    *nsid;
436   SV             *name;
437ALIAS:
438   Data::UUID::create_from_name_bin = F_BIN
439   Data::UUID::create_from_name_str = F_STR
440   Data::UUID::create_from_name_hex = F_HEX
441   Data::UUID::create_from_name_b64 = F_B64
442PREINIT:
443   SV *ctx;
444   unsigned char hash[16];
445   perl_uuid_t        net_nsid;
446   perl_uuid_t        uuid;
447PPCODE:
448   net_nsid = *nsid;
449   net_nsid.time_low            = htonl(net_nsid.time_low);
450   net_nsid.time_mid            = htons(net_nsid.time_mid);
451   net_nsid.time_hi_and_version = htons(net_nsid.time_hi_and_version);
452
453   ctx = MD5Init();
454   MD5Update(ctx, newSVpv((char*)&net_nsid, sizeof(perl_uuid_t)));
455   MD5Update(ctx, name);
456   MD5Final(hash, ctx);
457
458   format_uuid_v3(&uuid, hash);
459   ST(0) = make_ret(uuid, ix);
460   XSRETURN(1);
461
462int
463compare(self,u1,u2)
464   uuid_context_t *self;
465   perl_uuid_t         *u1;
466   perl_uuid_t         *u2;
467PREINIT:
468   int i;
469CODE:
470   RETVAL = 0;
471   CHECK(u1->time_low, u2->time_low);
472   CHECK(u1->time_mid, u2->time_mid);
473   CHECK(u1->time_hi_and_version, u2->time_hi_and_version);
474   CHECK(u1->clock_seq_hi_and_reserved, u2->clock_seq_hi_and_reserved);
475   CHECK(u1->clock_seq_low, u2->clock_seq_low);
476   for (i = 0; i < 6; i++) {
477      if (u1->node[i] < u2->node[i])
478         RETVAL = -1;
479      if (u1->node[i] > u2->node[i])
480         RETVAL =  1;
481   }
482OUTPUT:
483   RETVAL
484
485void
486to_string(self,uuid)
487   uuid_context_t *self;
488   perl_uuid_t         *uuid;
489ALIAS:
490   Data::UUID::to_hexstring = F_HEX
491   Data::UUID::to_b64string = F_B64
492PPCODE:
493   ST(0) = make_ret(*uuid, ix ? ix : F_STR);
494   XSRETURN(1);
495
496void
497from_string(self,str)
498   uuid_context_t *self;
499   char           *str;
500ALIAS:
501   Data::UUID::from_hexstring = F_HEX
502   Data::UUID::from_b64string = F_B64
503PREINIT:
504   perl_uuid_t         uuid;
505   char          *from, *to;
506   int            c;
507   unsigned int   i;
508   unsigned char  buf[4];
509PPCODE:
510   switch(ix) {
511   case F_BIN:
512   case F_STR:
513   case F_HEX:
514      from = str;
515      memset(&uuid, 0x00, sizeof(perl_uuid_t));
516      if ( from[0] == '0' && from[1] == 'x' )
517         from += 2;
518      for (i = 0; i < sizeof(perl_uuid_t); i++) {
519         if (*from == '-')
520	    from++;
521         if (sscanf(from, "%2x", &c) != 1)
522	    croak("from_string(%s) failed...\n", str);
523         ((unsigned char*)&uuid)[i] = (unsigned char)c;
524         from += 2;
525      }
526      uuid.time_low            = ntohl(uuid.time_low);
527      uuid.time_mid            = ntohs(uuid.time_mid);
528      uuid.time_hi_and_version = ntohs(uuid.time_hi_and_version);
529      break;
530   case F_B64:
531      from = str; to = (char*)&uuid;
532      while(from < (str + strlen(str))) {
533	 i = 0; memset(buf, 254, 4);
534	 do {
535	    c = index64[(int)*from++];
536	    if (c != 255) buf[i++] = (unsigned char)c;
537	    if (from == (str + strlen(str)))
538	       break;
539         } while (i < 4);
540
541	 if (buf[0] == 254 || buf[1] == 254)
542	    break;
543         *to++ = (buf[0] << 2) | ((buf[1] & 0x30) >> 4);
544
545	 if (buf[2] == 254) break;
546	 *to++ = ((buf[1] & 0x0F) << 4) | ((buf[2] & 0x3C) >> 2);
547
548	 if (buf[3] == 254) break;
549	 *to++ = ((buf[2] & 0x03) << 6) | buf[3];
550      }
551      break;
552   default:
553      croak("invalid type %d\n", ix);
554      break;
555   }
556   ST(0) = make_ret(uuid, F_BIN);
557   XSRETURN(1);
558
559#if DU_THREADSAFE
560
561void
562CLONE(klass)
563CODE:
564   MUTEX_LOCK(&instances_mutex);
565   ptable_walk(instances, inc, instances);
566   MUTEX_UNLOCK(&instances_mutex);
567
568#endif
569
570void
571DESTROY(self)
572   uuid_context_t *self;
573PREINIT:
574#if DU_THREADSAFE
575   UV            count;
576#endif
577   FILE           *fd;
578CODE:
579#if DU_THREADSAFE
580   MUTEX_LOCK(&instances_mutex);
581   count = PTR2UV(ptable_fetch(instances, self));
582   count--;
583   ptable_store(instances, self, (void *)count);
584   MUTEX_UNLOCK(&instances_mutex);
585   if (count == 0) {
586#endif
587      if ((fd = fopen(UUID_STATE_NV_STORE, "wb"))) {
588         LOCK(fd);
589         fwrite(&(self->state), sizeof(uuid_state_t), 1, fd);
590         UNLOCK(fd);
591         fclose(fd);
592      };
593      PerlMemShared_free(self);
594#if DU_THREADSAFE
595   }
596#endif
597
598BOOT:
599{
600  HV *stash = gv_stashpv("Data::UUID", 0);
601  STRLEN len = sizeof(perl_uuid_t);
602#if DU_THREADSAFE
603  instances = ptable_new();
604  MUTEX_INIT(&instances_mutex);
605#endif
606  newCONSTSUB(stash, "NameSpace_DNS", newSVpv((char *)&NameSpace_DNS, len));
607  newCONSTSUB(stash, "NameSpace_URL", newSVpv((char *)&NameSpace_URL, len));
608  newCONSTSUB(stash, "NameSpace_OID", newSVpv((char *)&NameSpace_OID, len));
609  newCONSTSUB(stash, "NameSpace_X500", newSVpv((char *)&NameSpace_X500, len));
610}
611