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