summaryrefslogtreecommitdiffstats
path: root/contrib/uuid-ossp/uuid-ossp.c
blob: 456a43884cd75853892ac934e6416f16da2692ed (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
/*-------------------------------------------------------------------------
 *
 * UUID generation functions using the BSD, E2FS or OSSP UUID library
 *
 * Copyright (c) 2007-2022, PostgreSQL Global Development Group
 *
 * Portions Copyright (c) 2009 Andrew Gierth
 *
 * contrib/uuid-ossp/uuid-ossp.c
 *
 *-------------------------------------------------------------------------
 */

#include "postgres.h"

#include "fmgr.h"
#include "common/cryptohash.h"
#include "common/sha1.h"
#include "port/pg_bswap.h"
#include "utils/builtins.h"
#include "utils/uuid.h"

/*
 * It's possible that there's more than one uuid.h header file present.
 * We expect configure to set the HAVE_ symbol for only the one we want.
 *
 * BSD includes a uuid_hash() function that conflicts with the one in
 * builtins.h; we #define it out of the way.
 */
#define uuid_hash bsd_uuid_hash

#if defined(HAVE_UUID_H)
#include <uuid.h>
#elif defined(HAVE_OSSP_UUID_H)
#include <ossp/uuid.h>
#elif defined(HAVE_UUID_UUID_H)
#include <uuid/uuid.h>
#else
#error "please use configure's --with-uuid switch to select a UUID library"
#endif

#undef uuid_hash

/* Check our UUID length against OSSP's; better both be 16 */
#if defined(HAVE_UUID_OSSP) && (UUID_LEN != UUID_LEN_BIN)
#error UUID length mismatch
#endif

/* Define some constants like OSSP's, to make the code more readable */
#ifndef HAVE_UUID_OSSP
#define UUID_MAKE_MC 0
#define UUID_MAKE_V1 1
#define UUID_MAKE_V2 2
#define UUID_MAKE_V3 3
#define UUID_MAKE_V4 4
#define UUID_MAKE_V5 5
#endif

/*
 * A DCE 1.1 compatible source representation of UUIDs, derived from
 * the BSD implementation.  BSD already has this; OSSP doesn't need it.
 */
#ifdef HAVE_UUID_E2FS
typedef struct
{
	uint32_t	time_low;
	uint16_t	time_mid;
	uint16_t	time_hi_and_version;
	uint8_t		clock_seq_hi_and_reserved;
	uint8_t		clock_seq_low;
	uint8_t		node[6];
} dce_uuid_t;
#else
#define dce_uuid_t uuid_t
#endif

/* If not OSSP, we need some endianness-manipulation macros */
#ifndef HAVE_UUID_OSSP

#define UUID_TO_NETWORK(uu) \
do { \
	uu.time_low = pg_hton32(uu.time_low); \
	uu.time_mid = pg_hton16(uu.time_mid); \
	uu.time_hi_and_version = pg_hton16(uu.time_hi_and_version); \
} while (0)

#define UUID_TO_LOCAL(uu) \
do { \
	uu.time_low = pg_ntoh32(uu.time_low); \
	uu.time_mid = pg_ntoh16(uu.time_mid); \
	uu.time_hi_and_version = pg_ntoh16(uu.time_hi_and_version); \
} while (0)

#define UUID_V3_OR_V5(uu, v) \
do { \
	uu.time_hi_and_version &= 0x0FFF; \
	uu.time_hi_and_version |= (v << 12); \
	uu.clock_seq_hi_and_reserved &= 0x3F; \
	uu.clock_seq_hi_and_reserved |= 0x80; \
} while(0)

#endif							/* !HAVE_UUID_OSSP */

PG_MODULE_MAGIC;

PG_FUNCTION_INFO_V1(uuid_nil);
PG_FUNCTION_INFO_V1(uuid_ns_dns);
PG_FUNCTION_INFO_V1(uuid_ns_url);
PG_FUNCTION_INFO_V1(uuid_ns_oid);
PG_FUNCTION_INFO_V1(uuid_ns_x500);

PG_FUNCTION_INFO_V1(uuid_generate_v1);
PG_FUNCTION_INFO_V1(uuid_generate_v1mc);
PG_FUNCTION_INFO_V1(uuid_generate_v3);
PG_FUNCTION_INFO_V1(uuid_generate_v4);
PG_FUNCTION_INFO_V1(uuid_generate_v5);

#ifdef HAVE_UUID_OSSP

static void
pguuid_complain(uuid_rc_t rc)
{
	char	   *err = uuid_error(rc);

	if (err != NULL)
		ereport(ERROR,
				(errcode(ERRCODE_EXTERNAL_ROUTINE_EXCEPTION),
				 errmsg("OSSP uuid library failure: %s", err)));
	else
		ereport(ERROR,
				(errcode(ERRCODE_EXTERNAL_ROUTINE_EXCEPTION),
				 errmsg("OSSP uuid library failure: error code %d", rc)));
}

/*
 * We create a uuid_t object just once per session and re-use it for all
 * operations in this module.  OSSP UUID caches the system MAC address and
 * other state in this object.  Reusing the object has a number of benefits:
 * saving the cycles needed to fetch the system MAC address over and over,
 * reducing the amount of entropy we draw from /dev/urandom, and providing a
 * positive guarantee that successive generated V1-style UUIDs don't collide.
 * (On a machine fast enough to generate multiple UUIDs per microsecond,
 * or whatever the system's wall-clock resolution is, we'd otherwise risk
 * collisions whenever random initialization of the uuid_t's clock sequence
 * value chanced to produce duplicates.)
 *
 * However: when we're doing V3 or V5 UUID creation, uuid_make needs two
 * uuid_t objects, one holding the namespace UUID and one for the result.
 * It's unspecified whether it's safe to use the same uuid_t for both cases,
 * so let's cache a second uuid_t for use as the namespace holder object.
 */
static uuid_t *
get_cached_uuid_t(int which)
{
	static uuid_t *cached_uuid[2] = {NULL, NULL};

	if (cached_uuid[which] == NULL)
	{
		uuid_rc_t	rc;

		rc = uuid_create(&cached_uuid[which]);
		if (rc != UUID_RC_OK)
		{
			cached_uuid[which] = NULL;
			pguuid_complain(rc);
		}
	}
	return cached_uuid[which];
}

static char *
uuid_to_string(const uuid_t *uuid)
{
	char	   *buf = palloc(UUID_LEN_STR + 1);
	void	   *ptr = buf;
	size_t		len = UUID_LEN_STR + 1;
	uuid_rc_t	rc;

	rc = uuid_export(uuid, UUID_FMT_STR, &ptr, &len);
	if (rc != UUID_RC_OK)
		pguuid_complain(rc);

	return buf;
}


static void
string_to_uuid(const char *str, uuid_t *uuid)
{
	uuid_rc_t	rc;

	rc = uuid_import(uuid, UUID_FMT_STR, str, UUID_LEN_STR + 1);
	if (rc != UUID_RC_OK)
		pguuid_complain(rc);
}


static Datum
special_uuid_value(const char *name)
{
	uuid_t	   *uuid = get_cached_uuid_t(0);
	char	   *str;
	uuid_rc_t	rc;

	rc = uuid_load(uuid, name);
	if (rc != UUID_RC_OK)
		pguuid_complain(rc);
	str = uuid_to_string(uuid);

	return DirectFunctionCall1(uuid_in, CStringGetDatum(str));
}

/* len is unused with OSSP, but we want to have the same number of args */
static Datum
uuid_generate_internal(int mode, const uuid_t *ns, const char *name, int len)
{
	uuid_t	   *uuid = get_cached_uuid_t(0);
	char	   *str;
	uuid_rc_t	rc;

	rc = uuid_make(uuid, mode, ns, name);
	if (rc != UUID_RC_OK)
		pguuid_complain(rc);
	str = uuid_to_string(uuid);

	return DirectFunctionCall1(uuid_in, CStringGetDatum(str));
}


static Datum
uuid_generate_v35_internal(int mode, pg_uuid_t *ns, text *name)
{
	uuid_t	   *ns_uuid = get_cached_uuid_t(1);

	string_to_uuid(DatumGetCString(DirectFunctionCall1(uuid_out,
													   UUIDPGetDatum(ns))),
				   ns_uuid);

	return uuid_generate_internal(mode,
								  ns_uuid,
								  text_to_cstring(name),
								  0);
}

#else							/* !HAVE_UUID_OSSP */

static Datum
uuid_generate_internal(int v, unsigned char *ns, const char *ptr, int len)
{
	char		strbuf[40];

	switch (v)
	{
		case 0:					/* constant-value uuids */
			strlcpy(strbuf, ptr, 37);
			break;

		case 1:					/* time/node-based uuids */
			{
#ifdef HAVE_UUID_E2FS
				uuid_t		uu;

				uuid_generate_time(uu);
				uuid_unparse(uu, strbuf);

				/*
				 * PTR, if set, replaces the trailing characters of the uuid;
				 * this is to support v1mc, where a random multicast MAC is
				 * used instead of the physical one
				 */
				if (ptr && len <= 36)
					strcpy(strbuf + (36 - len), ptr);
#else							/* BSD */
				uuid_t		uu;
				uint32_t	status = uuid_s_ok;
				char	   *str = NULL;

				uuid_create(&uu, &status);

				if (status == uuid_s_ok)
				{
					uuid_to_string(&uu, &str, &status);
					if (status == uuid_s_ok)
					{
						strlcpy(strbuf, str, 37);

						/*
						 * In recent NetBSD, uuid_create() has started
						 * producing v4 instead of v1 UUIDs.  Check the
						 * version field and complain if it's not v1.
						 */
						if (strbuf[14] != '1')
							ereport(ERROR,
									(errcode(ERRCODE_EXTERNAL_ROUTINE_EXCEPTION),
							/* translator: %c will be a hex digit */
									 errmsg("uuid_create() produced a version %c UUID instead of the expected version 1",
											strbuf[14])));

						/*
						 * PTR, if set, replaces the trailing characters of
						 * the uuid; this is to support v1mc, where a random
						 * multicast MAC is used instead of the physical one
						 */
						if (ptr && len <= 36)
							strcpy(strbuf + (36 - len), ptr);
					}
					if (str)
						free(str);
				}

				if (status != uuid_s_ok)
					ereport(ERROR,
							(errcode(ERRCODE_EXTERNAL_ROUTINE_EXCEPTION),
							 errmsg("uuid library failure: %d",
									(int) status)));
#endif
				break;
			}

		case 3:					/* namespace-based MD5 uuids */
		case 5:					/* namespace-based SHA1 uuids */
			{
				dce_uuid_t	uu;
#ifdef HAVE_UUID_BSD
				uint32_t	status = uuid_s_ok;
				char	   *str = NULL;
#endif

				if (v == 3)
				{
					pg_cryptohash_ctx *ctx = pg_cryptohash_create(PG_MD5);

					if (pg_cryptohash_init(ctx) < 0)
						elog(ERROR, "could not initialize %s context: %s", "MD5",
							 pg_cryptohash_error(ctx));
					if (pg_cryptohash_update(ctx, ns, sizeof(uu)) < 0 ||
						pg_cryptohash_update(ctx, (unsigned char *) ptr, len) < 0)
						elog(ERROR, "could not update %s context: %s", "MD5",
							 pg_cryptohash_error(ctx));
					/* we assume sizeof MD5 result is 16, same as UUID size */
					if (pg_cryptohash_final(ctx, (unsigned char *) &uu,
											sizeof(uu)) < 0)
						elog(ERROR, "could not finalize %s context: %s", "MD5",
							 pg_cryptohash_error(ctx));
					pg_cryptohash_free(ctx);
				}
				else
				{
					pg_cryptohash_ctx *ctx = pg_cryptohash_create(PG_SHA1);
					unsigned char sha1result[SHA1_DIGEST_LENGTH];

					if (pg_cryptohash_init(ctx) < 0)
						elog(ERROR, "could not initialize %s context: %s", "SHA1",
							 pg_cryptohash_error(ctx));
					if (pg_cryptohash_update(ctx, ns, sizeof(uu)) < 0 ||
						pg_cryptohash_update(ctx, (unsigned char *) ptr, len) < 0)
						elog(ERROR, "could not update %s context: %s", "SHA1",
							 pg_cryptohash_error(ctx));
					if (pg_cryptohash_final(ctx, sha1result, sizeof(sha1result)) < 0)
						elog(ERROR, "could not finalize %s context: %s", "SHA1",
							 pg_cryptohash_error(ctx));
					pg_cryptohash_free(ctx);

					memcpy(&uu, sha1result, sizeof(uu));
				}

				/* the calculated hash is using local order */
				UUID_TO_NETWORK(uu);
				UUID_V3_OR_V5(uu, v);

#ifdef HAVE_UUID_E2FS
				/* uuid_unparse expects local order */
				UUID_TO_LOCAL(uu);
				uuid_unparse((unsigned char *) &uu, strbuf);
#else							/* BSD */
				uuid_to_string(&uu, &str, &status);

				if (status == uuid_s_ok)
					strlcpy(strbuf, str, 37);

				if (str)
					free(str);

				if (status != uuid_s_ok)
					ereport(ERROR,
							(errcode(ERRCODE_EXTERNAL_ROUTINE_EXCEPTION),
							 errmsg("uuid library failure: %d",
									(int) status)));
#endif
				break;
			}

		case 4:					/* random uuid */
		default:
			{
#ifdef HAVE_UUID_E2FS
				uuid_t		uu;

				uuid_generate_random(uu);
				uuid_unparse(uu, strbuf);
#else							/* BSD */
				snprintf(strbuf, sizeof(strbuf),
						 "%08lx-%04x-%04x-%04x-%04x%08lx",
						 (unsigned long) arc4random(),
						 (unsigned) (arc4random() & 0xffff),
						 (unsigned) ((arc4random() & 0xfff) | 0x4000),
						 (unsigned) ((arc4random() & 0x3fff) | 0x8000),
						 (unsigned) (arc4random() & 0xffff),
						 (unsigned long) arc4random());
#endif
				break;
			}
	}

	return DirectFunctionCall1(uuid_in, CStringGetDatum(strbuf));
}

#endif							/* HAVE_UUID_OSSP */


Datum
uuid_nil(PG_FUNCTION_ARGS)
{
#ifdef HAVE_UUID_OSSP
	return special_uuid_value("nil");
#else
	return uuid_generate_internal(0, NULL,
								  "00000000-0000-0000-0000-000000000000", 36);
#endif
}


Datum
uuid_ns_dns(PG_FUNCTION_ARGS)
{
#ifdef HAVE_UUID_OSSP
	return special_uuid_value("ns:DNS");
#else
	return uuid_generate_internal(0, NULL,
								  "6ba7b810-9dad-11d1-80b4-00c04fd430c8", 36);
#endif
}


Datum
uuid_ns_url(PG_FUNCTION_ARGS)
{
#ifdef HAVE_UUID_OSSP
	return special_uuid_value("ns:URL");
#else
	return uuid_generate_internal(0, NULL,
								  "6ba7b811-9dad-11d1-80b4-00c04fd430c8", 36);
#endif
}


Datum
uuid_ns_oid(PG_FUNCTION_ARGS)
{
#ifdef HAVE_UUID_OSSP
	return special_uuid_value("ns:OID");
#else
	return uuid_generate_internal(0, NULL,
								  "6ba7b812-9dad-11d1-80b4-00c04fd430c8", 36);
#endif
}


Datum
uuid_ns_x500(PG_FUNCTION_ARGS)
{
#ifdef HAVE_UUID_OSSP
	return special_uuid_value("ns:X500");
#else
	return uuid_generate_internal(0, NULL,
								  "6ba7b814-9dad-11d1-80b4-00c04fd430c8", 36);
#endif
}


Datum
uuid_generate_v1(PG_FUNCTION_ARGS)
{
	return uuid_generate_internal(UUID_MAKE_V1, NULL, NULL, 0);
}


Datum
uuid_generate_v1mc(PG_FUNCTION_ARGS)
{
#ifdef HAVE_UUID_OSSP
	char	   *buf = NULL;
#elif defined(HAVE_UUID_E2FS)
	char		strbuf[40];
	char	   *buf;
	uuid_t		uu;

	uuid_generate_random(uu);

	/* set IEEE802 multicast and local-admin bits */
	((dce_uuid_t *) &uu)->node[0] |= 0x03;

	uuid_unparse(uu, strbuf);
	buf = strbuf + 24;
#else							/* BSD */
	char		buf[16];

	/* set IEEE802 multicast and local-admin bits */
	snprintf(buf, sizeof(buf), "-%04x%08lx",
			 (unsigned) ((arc4random() & 0xffff) | 0x0300),
			 (unsigned long) arc4random());
#endif

	return uuid_generate_internal(UUID_MAKE_V1 | UUID_MAKE_MC, NULL,
								  buf, 13);
}


Datum
uuid_generate_v3(PG_FUNCTION_ARGS)
{
	pg_uuid_t  *ns = PG_GETARG_UUID_P(0);
	text	   *name = PG_GETARG_TEXT_PP(1);

#ifdef HAVE_UUID_OSSP
	return uuid_generate_v35_internal(UUID_MAKE_V3, ns, name);
#else
	return uuid_generate_internal(UUID_MAKE_V3, (unsigned char *) ns,
								  VARDATA_ANY(name), VARSIZE_ANY_EXHDR(name));
#endif
}


Datum
uuid_generate_v4(PG_FUNCTION_ARGS)
{
	return uuid_generate_internal(UUID_MAKE_V4, NULL, NULL, 0);
}


Datum
uuid_generate_v5(PG_FUNCTION_ARGS)
{
	pg_uuid_t  *ns = PG_GETARG_UUID_P(0);
	text	   *name = PG_GETARG_TEXT_PP(1);

#ifdef HAVE_UUID_OSSP
	return uuid_generate_v35_internal(UUID_MAKE_V5, ns, name);
#else
	return uuid_generate_internal(UUID_MAKE_V5, (unsigned char *) ns,
								  VARDATA_ANY(name), VARSIZE_ANY_EXHDR(name));
#endif
}