1 /*-------------------------------------------------------------------------
2  *
3  * mac8.c
4  *	  PostgreSQL type definitions for 8 byte (EUI-64) MAC addresses.
5  *
6  * EUI-48 (6 byte) MAC addresses are accepted as input and are stored in
7  * EUI-64 format, with the 4th and 5th bytes set to FF and FE, respectively.
8  *
9  * Output is always in 8 byte (EUI-64) format.
10  *
11  * The following code is written with the assumption that the OUI field
12  * size is 24 bits.
13  *
14  * Portions Copyright (c) 1998-2018, PostgreSQL Global Development Group
15  *
16  * IDENTIFICATION
17  *		  src/backend/utils/adt/mac8.c
18  *
19  *-------------------------------------------------------------------------
20  */
21 
22 #include "postgres.h"
23 
24 #include "access/hash.h"
25 #include "libpq/pqformat.h"
26 #include "utils/builtins.h"
27 #include "utils/inet.h"
28 
29 /*
30  *	Utility macros used for sorting and comparing:
31  */
32 #define hibits(addr) \
33   ((unsigned long)(((addr)->a<<24) | ((addr)->b<<16) | ((addr)->c<<8) | ((addr)->d)))
34 
35 #define lobits(addr) \
36   ((unsigned long)(((addr)->e<<24) | ((addr)->f<<16) | ((addr)->g<<8) | ((addr)->h)))
37 
38 static unsigned char hex2_to_uchar(const unsigned char *str, const unsigned char *ptr);
39 
40 static const signed char hexlookup[128] = {
41 	-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
42 	-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
43 	-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
44 	0, 1, 2, 3, 4, 5, 6, 7, 8, 9, -1, -1, -1, -1, -1, -1,
45 	-1, 10, 11, 12, 13, 14, 15, -1, -1, -1, -1, -1, -1, -1, -1, -1,
46 	-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
47 	-1, 10, 11, 12, 13, 14, 15, -1, -1, -1, -1, -1, -1, -1, -1, -1,
48 	-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
49 };
50 
51 /*
52  * hex2_to_uchar - convert 2 hex digits to a byte (unsigned char)
53  *
54  * This will ereport() if the end of the string is reached ('\0' found), or if
55  * either character is not a valid hex digit.
56  *
57  * ptr is the pointer to where the digits to convert are in the string, str is
58  * the entire string, which is used only for error reporting.
59  */
60 static inline unsigned char
hex2_to_uchar(const unsigned char * ptr,const unsigned char * str)61 hex2_to_uchar(const unsigned char *ptr, const unsigned char *str)
62 {
63 	unsigned char ret = 0;
64 	signed char lookup;
65 
66 	/* Handle the first character */
67 	if (*ptr > 127)
68 		goto invalid_input;
69 
70 	lookup = hexlookup[*ptr];
71 	if (lookup < 0)
72 		goto invalid_input;
73 
74 	ret = lookup << 4;
75 
76 	/* Move to the second character */
77 	ptr++;
78 
79 	if (*ptr > 127)
80 		goto invalid_input;
81 
82 	lookup = hexlookup[*ptr];
83 	if (lookup < 0)
84 		goto invalid_input;
85 
86 	ret += lookup;
87 
88 	return ret;
89 
90 invalid_input:
91 	ereport(ERROR,
92 			(errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
93 			 errmsg("invalid input syntax for type %s: \"%s\"", "macaddr8",
94 					str)));
95 
96 	/* We do not actually reach here */
97 	return 0;
98 }
99 
100 /*
101  * MAC address (EUI-48 and EUI-64) reader. Accepts several common notations.
102  */
103 Datum
macaddr8_in(PG_FUNCTION_ARGS)104 macaddr8_in(PG_FUNCTION_ARGS)
105 {
106 	const unsigned char *str = (unsigned char *) PG_GETARG_CSTRING(0);
107 	const unsigned char *ptr = str;
108 	macaddr8   *result;
109 	unsigned char a = 0,
110 				b = 0,
111 				c = 0,
112 				d = 0,
113 				e = 0,
114 				f = 0,
115 				g = 0,
116 				h = 0;
117 	int			count = 0;
118 	unsigned char spacer = '\0';
119 
120 	/* skip leading spaces */
121 	while (*ptr && isspace(*ptr))
122 		ptr++;
123 
124 	/* digits must always come in pairs */
125 	while (*ptr && *(ptr + 1))
126 	{
127 		/*
128 		 * Attempt to decode each byte, which must be 2 hex digits in a row.
129 		 * If either digit is not hex, hex2_to_uchar will throw ereport() for
130 		 * us.  Either 6 or 8 byte MAC addresses are supported.
131 		 */
132 
133 		/* Attempt to collect a byte */
134 		count++;
135 
136 		switch (count)
137 		{
138 			case 1:
139 				a = hex2_to_uchar(ptr, str);
140 				break;
141 			case 2:
142 				b = hex2_to_uchar(ptr, str);
143 				break;
144 			case 3:
145 				c = hex2_to_uchar(ptr, str);
146 				break;
147 			case 4:
148 				d = hex2_to_uchar(ptr, str);
149 				break;
150 			case 5:
151 				e = hex2_to_uchar(ptr, str);
152 				break;
153 			case 6:
154 				f = hex2_to_uchar(ptr, str);
155 				break;
156 			case 7:
157 				g = hex2_to_uchar(ptr, str);
158 				break;
159 			case 8:
160 				h = hex2_to_uchar(ptr, str);
161 				break;
162 			default:
163 				/* must be trailing garbage... */
164 				ereport(ERROR,
165 						(errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
166 						 errmsg("invalid input syntax for type %s: \"%s\"", "macaddr8",
167 								str)));
168 		}
169 
170 		/* Move forward to where the next byte should be */
171 		ptr += 2;
172 
173 		/* Check for a spacer, these are valid, anything else is not */
174 		if (*ptr == ':' || *ptr == '-' || *ptr == '.')
175 		{
176 			/* remember the spacer used, if it changes then it isn't valid */
177 			if (spacer == '\0')
178 				spacer = *ptr;
179 
180 			/* Have to use the same spacer throughout */
181 			else if (spacer != *ptr)
182 				ereport(ERROR,
183 						(errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
184 						 errmsg("invalid input syntax for type %s: \"%s\"", "macaddr8",
185 								str)));
186 
187 			/* move past the spacer */
188 			ptr++;
189 		}
190 
191 		/* allow trailing whitespace after if we have 6 or 8 bytes */
192 		if (count == 6 || count == 8)
193 		{
194 			if (isspace(*ptr))
195 			{
196 				while (*++ptr && isspace(*ptr));
197 
198 				/* If we found a space and then non-space, it's invalid */
199 				if (*ptr)
200 					ereport(ERROR,
201 							(errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
202 							 errmsg("invalid input syntax for type %s: \"%s\"", "macaddr8",
203 									str)));
204 			}
205 		}
206 	}
207 
208 	/* Convert a 6 byte MAC address to macaddr8 */
209 	if (count == 6)
210 	{
211 		h = f;
212 		g = e;
213 		f = d;
214 
215 		d = 0xFF;
216 		e = 0xFE;
217 	}
218 	else if (count != 8)
219 		ereport(ERROR,
220 				(errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
221 				 errmsg("invalid input syntax for type %s: \"%s\"", "macaddr8",
222 						str)));
223 
224 	result = (macaddr8 *) palloc0(sizeof(macaddr8));
225 
226 	result->a = a;
227 	result->b = b;
228 	result->c = c;
229 	result->d = d;
230 	result->e = e;
231 	result->f = f;
232 	result->g = g;
233 	result->h = h;
234 
235 	PG_RETURN_MACADDR8_P(result);
236 }
237 
238 /*
239  * MAC8 address (EUI-64) output function. Fixed format.
240  */
241 Datum
macaddr8_out(PG_FUNCTION_ARGS)242 macaddr8_out(PG_FUNCTION_ARGS)
243 {
244 	macaddr8   *addr = PG_GETARG_MACADDR8_P(0);
245 	char	   *result;
246 
247 	result = (char *) palloc(32);
248 
249 	snprintf(result, 32, "%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x",
250 			 addr->a, addr->b, addr->c, addr->d,
251 			 addr->e, addr->f, addr->g, addr->h);
252 
253 	PG_RETURN_CSTRING(result);
254 }
255 
256 /*
257  * macaddr8_recv - converts external binary format(EUI-48 and EUI-64) to macaddr8
258  *
259  * The external representation is just the eight bytes, MSB first.
260  */
261 Datum
macaddr8_recv(PG_FUNCTION_ARGS)262 macaddr8_recv(PG_FUNCTION_ARGS)
263 {
264 	StringInfo	buf = (StringInfo) PG_GETARG_POINTER(0);
265 	macaddr8   *addr;
266 
267 	addr = (macaddr8 *) palloc0(sizeof(macaddr8));
268 
269 	addr->a = pq_getmsgbyte(buf);
270 	addr->b = pq_getmsgbyte(buf);
271 	addr->c = pq_getmsgbyte(buf);
272 
273 	if (buf->len == 6)
274 	{
275 		addr->d = 0xFF;
276 		addr->e = 0xFE;
277 	}
278 	else
279 	{
280 		addr->d = pq_getmsgbyte(buf);
281 		addr->e = pq_getmsgbyte(buf);
282 	}
283 
284 	addr->f = pq_getmsgbyte(buf);
285 	addr->g = pq_getmsgbyte(buf);
286 	addr->h = pq_getmsgbyte(buf);
287 
288 	PG_RETURN_MACADDR8_P(addr);
289 }
290 
291 /*
292  * macaddr8_send - converts macaddr8(EUI-64) to binary format
293  */
294 Datum
macaddr8_send(PG_FUNCTION_ARGS)295 macaddr8_send(PG_FUNCTION_ARGS)
296 {
297 	macaddr8   *addr = PG_GETARG_MACADDR8_P(0);
298 	StringInfoData buf;
299 
300 	pq_begintypsend(&buf);
301 	pq_sendbyte(&buf, addr->a);
302 	pq_sendbyte(&buf, addr->b);
303 	pq_sendbyte(&buf, addr->c);
304 	pq_sendbyte(&buf, addr->d);
305 	pq_sendbyte(&buf, addr->e);
306 	pq_sendbyte(&buf, addr->f);
307 	pq_sendbyte(&buf, addr->g);
308 	pq_sendbyte(&buf, addr->h);
309 
310 	PG_RETURN_BYTEA_P(pq_endtypsend(&buf));
311 }
312 
313 
314 /*
315  * macaddr8_cmp_internal - comparison function for sorting:
316  */
317 static int32
macaddr8_cmp_internal(macaddr8 * a1,macaddr8 * a2)318 macaddr8_cmp_internal(macaddr8 *a1, macaddr8 *a2)
319 {
320 	if (hibits(a1) < hibits(a2))
321 		return -1;
322 	else if (hibits(a1) > hibits(a2))
323 		return 1;
324 	else if (lobits(a1) < lobits(a2))
325 		return -1;
326 	else if (lobits(a1) > lobits(a2))
327 		return 1;
328 	else
329 		return 0;
330 }
331 
332 Datum
macaddr8_cmp(PG_FUNCTION_ARGS)333 macaddr8_cmp(PG_FUNCTION_ARGS)
334 {
335 	macaddr8   *a1 = PG_GETARG_MACADDR8_P(0);
336 	macaddr8   *a2 = PG_GETARG_MACADDR8_P(1);
337 
338 	PG_RETURN_INT32(macaddr8_cmp_internal(a1, a2));
339 }
340 
341 /*
342  * Boolean comparison functions.
343  */
344 
345 Datum
macaddr8_lt(PG_FUNCTION_ARGS)346 macaddr8_lt(PG_FUNCTION_ARGS)
347 {
348 	macaddr8   *a1 = PG_GETARG_MACADDR8_P(0);
349 	macaddr8   *a2 = PG_GETARG_MACADDR8_P(1);
350 
351 	PG_RETURN_BOOL(macaddr8_cmp_internal(a1, a2) < 0);
352 }
353 
354 Datum
macaddr8_le(PG_FUNCTION_ARGS)355 macaddr8_le(PG_FUNCTION_ARGS)
356 {
357 	macaddr8   *a1 = PG_GETARG_MACADDR8_P(0);
358 	macaddr8   *a2 = PG_GETARG_MACADDR8_P(1);
359 
360 	PG_RETURN_BOOL(macaddr8_cmp_internal(a1, a2) <= 0);
361 }
362 
363 Datum
macaddr8_eq(PG_FUNCTION_ARGS)364 macaddr8_eq(PG_FUNCTION_ARGS)
365 {
366 	macaddr8   *a1 = PG_GETARG_MACADDR8_P(0);
367 	macaddr8   *a2 = PG_GETARG_MACADDR8_P(1);
368 
369 	PG_RETURN_BOOL(macaddr8_cmp_internal(a1, a2) == 0);
370 }
371 
372 Datum
macaddr8_ge(PG_FUNCTION_ARGS)373 macaddr8_ge(PG_FUNCTION_ARGS)
374 {
375 	macaddr8   *a1 = PG_GETARG_MACADDR8_P(0);
376 	macaddr8   *a2 = PG_GETARG_MACADDR8_P(1);
377 
378 	PG_RETURN_BOOL(macaddr8_cmp_internal(a1, a2) >= 0);
379 }
380 
381 Datum
macaddr8_gt(PG_FUNCTION_ARGS)382 macaddr8_gt(PG_FUNCTION_ARGS)
383 {
384 	macaddr8   *a1 = PG_GETARG_MACADDR8_P(0);
385 	macaddr8   *a2 = PG_GETARG_MACADDR8_P(1);
386 
387 	PG_RETURN_BOOL(macaddr8_cmp_internal(a1, a2) > 0);
388 }
389 
390 Datum
macaddr8_ne(PG_FUNCTION_ARGS)391 macaddr8_ne(PG_FUNCTION_ARGS)
392 {
393 	macaddr8   *a1 = PG_GETARG_MACADDR8_P(0);
394 	macaddr8   *a2 = PG_GETARG_MACADDR8_P(1);
395 
396 	PG_RETURN_BOOL(macaddr8_cmp_internal(a1, a2) != 0);
397 }
398 
399 /*
400  * Support function for hash indexes on macaddr8.
401  */
402 Datum
hashmacaddr8(PG_FUNCTION_ARGS)403 hashmacaddr8(PG_FUNCTION_ARGS)
404 {
405 	macaddr8   *key = PG_GETARG_MACADDR8_P(0);
406 
407 	return hash_any((unsigned char *) key, sizeof(macaddr8));
408 }
409 
410 Datum
hashmacaddr8extended(PG_FUNCTION_ARGS)411 hashmacaddr8extended(PG_FUNCTION_ARGS)
412 {
413 	macaddr8   *key = PG_GETARG_MACADDR8_P(0);
414 
415 	return hash_any_extended((unsigned char *) key, sizeof(macaddr8),
416 							 PG_GETARG_INT64(1));
417 }
418 
419 /*
420  * Arithmetic functions: bitwise NOT, AND, OR.
421  */
422 Datum
macaddr8_not(PG_FUNCTION_ARGS)423 macaddr8_not(PG_FUNCTION_ARGS)
424 {
425 	macaddr8   *addr = PG_GETARG_MACADDR8_P(0);
426 	macaddr8   *result;
427 
428 	result = (macaddr8 *) palloc0(sizeof(macaddr8));
429 	result->a = ~addr->a;
430 	result->b = ~addr->b;
431 	result->c = ~addr->c;
432 	result->d = ~addr->d;
433 	result->e = ~addr->e;
434 	result->f = ~addr->f;
435 	result->g = ~addr->g;
436 	result->h = ~addr->h;
437 
438 	PG_RETURN_MACADDR8_P(result);
439 }
440 
441 Datum
macaddr8_and(PG_FUNCTION_ARGS)442 macaddr8_and(PG_FUNCTION_ARGS)
443 {
444 	macaddr8   *addr1 = PG_GETARG_MACADDR8_P(0);
445 	macaddr8   *addr2 = PG_GETARG_MACADDR8_P(1);
446 	macaddr8   *result;
447 
448 	result = (macaddr8 *) palloc0(sizeof(macaddr8));
449 	result->a = addr1->a & addr2->a;
450 	result->b = addr1->b & addr2->b;
451 	result->c = addr1->c & addr2->c;
452 	result->d = addr1->d & addr2->d;
453 	result->e = addr1->e & addr2->e;
454 	result->f = addr1->f & addr2->f;
455 	result->g = addr1->g & addr2->g;
456 	result->h = addr1->h & addr2->h;
457 
458 	PG_RETURN_MACADDR8_P(result);
459 }
460 
461 Datum
macaddr8_or(PG_FUNCTION_ARGS)462 macaddr8_or(PG_FUNCTION_ARGS)
463 {
464 	macaddr8   *addr1 = PG_GETARG_MACADDR8_P(0);
465 	macaddr8   *addr2 = PG_GETARG_MACADDR8_P(1);
466 	macaddr8   *result;
467 
468 	result = (macaddr8 *) palloc0(sizeof(macaddr8));
469 	result->a = addr1->a | addr2->a;
470 	result->b = addr1->b | addr2->b;
471 	result->c = addr1->c | addr2->c;
472 	result->d = addr1->d | addr2->d;
473 	result->e = addr1->e | addr2->e;
474 	result->f = addr1->f | addr2->f;
475 	result->g = addr1->g | addr2->g;
476 	result->h = addr1->h | addr2->h;
477 
478 	PG_RETURN_MACADDR8_P(result);
479 }
480 
481 /*
482  * Truncation function to allow comparing macaddr8 manufacturers.
483  */
484 Datum
macaddr8_trunc(PG_FUNCTION_ARGS)485 macaddr8_trunc(PG_FUNCTION_ARGS)
486 {
487 	macaddr8   *addr = PG_GETARG_MACADDR8_P(0);
488 	macaddr8   *result;
489 
490 	result = (macaddr8 *) palloc0(sizeof(macaddr8));
491 
492 	result->a = addr->a;
493 	result->b = addr->b;
494 	result->c = addr->c;
495 	result->d = 0;
496 	result->e = 0;
497 	result->f = 0;
498 	result->g = 0;
499 	result->h = 0;
500 
501 	PG_RETURN_MACADDR8_P(result);
502 }
503 
504 /*
505  * Set 7th bit for modified EUI-64 as used in IPv6.
506  */
507 Datum
macaddr8_set7bit(PG_FUNCTION_ARGS)508 macaddr8_set7bit(PG_FUNCTION_ARGS)
509 {
510 	macaddr8   *addr = PG_GETARG_MACADDR8_P(0);
511 	macaddr8   *result;
512 
513 	result = (macaddr8 *) palloc0(sizeof(macaddr8));
514 
515 	result->a = addr->a | 0x02;
516 	result->b = addr->b;
517 	result->c = addr->c;
518 	result->d = addr->d;
519 	result->e = addr->e;
520 	result->f = addr->f;
521 	result->g = addr->g;
522 	result->h = addr->h;
523 
524 	PG_RETURN_MACADDR8_P(result);
525 }
526 
527 /*----------------------------------------------------------
528  *	Conversion operators.
529  *---------------------------------------------------------*/
530 
531 Datum
macaddrtomacaddr8(PG_FUNCTION_ARGS)532 macaddrtomacaddr8(PG_FUNCTION_ARGS)
533 {
534 	macaddr    *addr6 = PG_GETARG_MACADDR_P(0);
535 	macaddr8   *result;
536 
537 	result = (macaddr8 *) palloc0(sizeof(macaddr8));
538 
539 	result->a = addr6->a;
540 	result->b = addr6->b;
541 	result->c = addr6->c;
542 	result->d = 0xFF;
543 	result->e = 0xFE;
544 	result->f = addr6->d;
545 	result->g = addr6->e;
546 	result->h = addr6->f;
547 
548 
549 	PG_RETURN_MACADDR8_P(result);
550 }
551 
552 Datum
macaddr8tomacaddr(PG_FUNCTION_ARGS)553 macaddr8tomacaddr(PG_FUNCTION_ARGS)
554 {
555 	macaddr8   *addr = PG_GETARG_MACADDR8_P(0);
556 	macaddr    *result;
557 
558 	result = (macaddr *) palloc0(sizeof(macaddr));
559 
560 	if ((addr->d != 0xFF) || (addr->e != 0xFE))
561 		ereport(ERROR,
562 				(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
563 				 errmsg("macaddr8 data out of range to convert to macaddr"),
564 				 errhint("Only addresses that have FF and FE as values in the "
565 						 "4th and 5th bytes from the left, for example "
566 						 "xx:xx:xx:ff:fe:xx:xx:xx, are eligible to be converted "
567 						 "from macaddr8 to macaddr.")));
568 
569 	result->a = addr->a;
570 	result->b = addr->b;
571 	result->c = addr->c;
572 	result->d = addr->f;
573 	result->e = addr->g;
574 	result->f = addr->h;
575 
576 	PG_RETURN_MACADDR_P(result);
577 }
578