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