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-2026, 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 8179 : 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 8179 : if (*ptr > 127)
66 0 : goto invalid_input;
67 :
68 8179 : lookup = hexlookup[*ptr];
69 8179 : if (lookup < 0)
70 12 : goto invalid_input;
71 :
72 8167 : ret = lookup << 4;
73 :
74 : /* Move to the second character */
75 8167 : ptr++;
76 :
77 8167 : if (*ptr > 127)
78 0 : goto invalid_input;
79 :
80 8167 : lookup = hexlookup[*ptr];
81 8167 : if (lookup < 0)
82 9 : goto invalid_input;
83 :
84 8158 : ret += lookup;
85 :
86 8158 : return ret;
87 :
88 21 : invalid_input:
89 21 : *badhex = true;
90 21 : return 0;
91 : }
92 :
93 : /*
94 : * MAC address (EUI-48 and EUI-64) reader. Accepts several common notations.
95 : */
96 : Datum
97 1211 : macaddr8_in(PG_FUNCTION_ARGS)
98 : {
99 1211 : const unsigned char *str = (unsigned char *) PG_GETARG_CSTRING(0);
100 1211 : Node *escontext = fcinfo->context;
101 1211 : const unsigned char *ptr = str;
102 1211 : bool badhex = false;
103 : macaddr8 *result;
104 1211 : unsigned char a = 0,
105 1211 : b = 0,
106 1211 : c = 0,
107 1211 : d = 0,
108 1211 : e = 0,
109 1211 : f = 0,
110 1211 : g = 0,
111 1211 : h = 0;
112 1211 : int count = 0;
113 1211 : unsigned char spacer = '\0';
114 :
115 : /* skip leading spaces */
116 1259 : while (*ptr && isspace(*ptr))
117 48 : ptr++;
118 :
119 : /* digits must always come in pairs */
120 9351 : 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 8191 : count++;
130 :
131 8191 : switch (count)
132 : {
133 1211 : case 1:
134 1211 : a = hex2_to_uchar(ptr, &badhex);
135 1211 : break;
136 1205 : case 2:
137 1205 : b = hex2_to_uchar(ptr, &badhex);
138 1205 : break;
139 1193 : case 3:
140 1193 : c = hex2_to_uchar(ptr, &badhex);
141 1193 : break;
142 1193 : case 4:
143 1193 : d = hex2_to_uchar(ptr, &badhex);
144 1193 : break;
145 1187 : case 5:
146 1187 : e = hex2_to_uchar(ptr, &badhex);
147 1187 : break;
148 1187 : case 6:
149 1187 : f = hex2_to_uchar(ptr, &badhex);
150 1187 : break;
151 506 : case 7:
152 506 : g = hex2_to_uchar(ptr, &badhex);
153 506 : break;
154 497 : case 8:
155 497 : h = hex2_to_uchar(ptr, &badhex);
156 497 : break;
157 12 : default:
158 : /* must be trailing garbage... */
159 12 : goto fail;
160 : }
161 :
162 8179 : if (badhex)
163 21 : goto fail;
164 :
165 : /* Move forward to where the next byte should be */
166 8158 : ptr += 2;
167 :
168 : /* Check for a spacer, these are valid, anything else is not */
169 8158 : if (*ptr == ':' || *ptr == '-' || *ptr == '.')
170 : {
171 : /* remember the spacer used, if it changes then it isn't valid */
172 4466 : if (spacer == '\0')
173 857 : spacer = *ptr;
174 :
175 : /* Have to use the same spacer throughout */
176 3609 : else if (spacer != *ptr)
177 12 : goto fail;
178 :
179 : /* move past the spacer */
180 4454 : ptr++;
181 : }
182 :
183 : /* allow trailing whitespace after if we have 6 or 8 bytes */
184 8146 : if (count == 6 || count == 8)
185 : {
186 1678 : if (isspace(*ptr))
187 : {
188 72 : while (*++ptr && isspace(*ptr));
189 :
190 : /* If we found a space and then non-space, it's invalid */
191 18 : if (*ptr)
192 6 : goto fail;
193 : }
194 : }
195 : }
196 :
197 : /* Convert a 6 byte MAC address to macaddr8 */
198 1160 : if (count == 6)
199 : {
200 678 : h = f;
201 678 : g = e;
202 678 : f = d;
203 :
204 678 : d = 0xFF;
205 678 : e = 0xFE;
206 : }
207 482 : else if (count != 8)
208 6 : goto fail;
209 :
210 1154 : result = palloc0_object(macaddr8);
211 :
212 1154 : result->a = a;
213 1154 : result->b = b;
214 1154 : result->c = c;
215 1154 : result->d = d;
216 1154 : result->e = e;
217 1154 : result->f = f;
218 1154 : result->g = g;
219 1154 : result->h = h;
220 :
221 1154 : PG_RETURN_MACADDR8_P(result);
222 :
223 57 : fail:
224 57 : 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 1072 : macaddr8_out(PG_FUNCTION_ARGS)
235 : {
236 1072 : macaddr8 *addr = PG_GETARG_MACADDR8_P(0);
237 : char *result;
238 :
239 1072 : result = (char *) palloc(32);
240 :
241 1072 : snprintf(result, 32, "%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x",
242 1072 : addr->a, addr->b, addr->c, addr->d,
243 1072 : addr->e, addr->f, addr->g, addr->h);
244 :
245 1072 : 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 = palloc0_object(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 27785 : macaddr8_cmp_internal(macaddr8 *a1, macaddr8 *a2)
311 : {
312 27785 : if (hibits(a1) < hibits(a2))
313 12526 : return -1;
314 15259 : else if (hibits(a1) > hibits(a2))
315 10400 : return 1;
316 4859 : else if (lobits(a1) < lobits(a2))
317 111 : return -1;
318 4748 : else if (lobits(a1) > lobits(a2))
319 12 : return 1;
320 : else
321 4736 : return 0;
322 : }
323 :
324 : Datum
325 10019 : macaddr8_cmp(PG_FUNCTION_ARGS)
326 : {
327 10019 : macaddr8 *a1 = PG_GETARG_MACADDR8_P(0);
328 10019 : macaddr8 *a2 = PG_GETARG_MACADDR8_P(1);
329 :
330 10019 : PG_RETURN_INT32(macaddr8_cmp_internal(a1, a2));
331 : }
332 :
333 : /*
334 : * Boolean comparison functions.
335 : */
336 :
337 : Datum
338 8432 : macaddr8_lt(PG_FUNCTION_ARGS)
339 : {
340 8432 : macaddr8 *a1 = PG_GETARG_MACADDR8_P(0);
341 8432 : macaddr8 *a2 = PG_GETARG_MACADDR8_P(1);
342 :
343 8432 : PG_RETURN_BOOL(macaddr8_cmp_internal(a1, a2) < 0);
344 : }
345 :
346 : Datum
347 1846 : macaddr8_le(PG_FUNCTION_ARGS)
348 : {
349 1846 : macaddr8 *a1 = PG_GETARG_MACADDR8_P(0);
350 1846 : macaddr8 *a2 = PG_GETARG_MACADDR8_P(1);
351 :
352 1846 : PG_RETURN_BOOL(macaddr8_cmp_internal(a1, a2) <= 0);
353 : }
354 :
355 : Datum
356 2506 : macaddr8_eq(PG_FUNCTION_ARGS)
357 : {
358 2506 : macaddr8 *a1 = PG_GETARG_MACADDR8_P(0);
359 2506 : macaddr8 *a2 = PG_GETARG_MACADDR8_P(1);
360 :
361 2506 : PG_RETURN_BOOL(macaddr8_cmp_internal(a1, a2) == 0);
362 : }
363 :
364 : Datum
365 1497 : macaddr8_ge(PG_FUNCTION_ARGS)
366 : {
367 1497 : macaddr8 *a1 = PG_GETARG_MACADDR8_P(0);
368 1497 : macaddr8 *a2 = PG_GETARG_MACADDR8_P(1);
369 :
370 1497 : PG_RETURN_BOOL(macaddr8_cmp_internal(a1, a2) >= 0);
371 : }
372 :
373 : Datum
374 3479 : macaddr8_gt(PG_FUNCTION_ARGS)
375 : {
376 3479 : macaddr8 *a1 = PG_GETARG_MACADDR8_P(0);
377 3479 : macaddr8 *a2 = PG_GETARG_MACADDR8_P(1);
378 :
379 3479 : PG_RETURN_BOOL(macaddr8_cmp_internal(a1, a2) > 0);
380 : }
381 :
382 : Datum
383 6 : macaddr8_ne(PG_FUNCTION_ARGS)
384 : {
385 6 : macaddr8 *a1 = PG_GETARG_MACADDR8_P(0);
386 6 : macaddr8 *a2 = PG_GETARG_MACADDR8_P(1);
387 :
388 6 : PG_RETURN_BOOL(macaddr8_cmp_internal(a1, a2) != 0);
389 : }
390 :
391 : /*
392 : * Support function for hash indexes on macaddr8.
393 : */
394 : Datum
395 90 : hashmacaddr8(PG_FUNCTION_ARGS)
396 : {
397 90 : macaddr8 *key = PG_GETARG_MACADDR8_P(0);
398 :
399 90 : return hash_any((unsigned char *) key, sizeof(macaddr8));
400 : }
401 :
402 : Datum
403 30 : hashmacaddr8extended(PG_FUNCTION_ARGS)
404 : {
405 30 : macaddr8 *key = PG_GETARG_MACADDR8_P(0);
406 :
407 30 : return hash_any_extended((unsigned char *) key, sizeof(macaddr8),
408 30 : PG_GETARG_INT64(1));
409 : }
410 :
411 : /*
412 : * Arithmetic functions: bitwise NOT, AND, OR.
413 : */
414 : Datum
415 60 : macaddr8_not(PG_FUNCTION_ARGS)
416 : {
417 60 : macaddr8 *addr = PG_GETARG_MACADDR8_P(0);
418 : macaddr8 *result;
419 :
420 60 : result = palloc0_object(macaddr8);
421 60 : result->a = ~addr->a;
422 60 : result->b = ~addr->b;
423 60 : result->c = ~addr->c;
424 60 : result->d = ~addr->d;
425 60 : result->e = ~addr->e;
426 60 : result->f = ~addr->f;
427 60 : result->g = ~addr->g;
428 60 : result->h = ~addr->h;
429 :
430 60 : PG_RETURN_MACADDR8_P(result);
431 : }
432 :
433 : Datum
434 60 : macaddr8_and(PG_FUNCTION_ARGS)
435 : {
436 60 : macaddr8 *addr1 = PG_GETARG_MACADDR8_P(0);
437 60 : macaddr8 *addr2 = PG_GETARG_MACADDR8_P(1);
438 : macaddr8 *result;
439 :
440 60 : result = palloc0_object(macaddr8);
441 60 : result->a = addr1->a & addr2->a;
442 60 : result->b = addr1->b & addr2->b;
443 60 : result->c = addr1->c & addr2->c;
444 60 : result->d = addr1->d & addr2->d;
445 60 : result->e = addr1->e & addr2->e;
446 60 : result->f = addr1->f & addr2->f;
447 60 : result->g = addr1->g & addr2->g;
448 60 : result->h = addr1->h & addr2->h;
449 :
450 60 : PG_RETURN_MACADDR8_P(result);
451 : }
452 :
453 : Datum
454 60 : macaddr8_or(PG_FUNCTION_ARGS)
455 : {
456 60 : macaddr8 *addr1 = PG_GETARG_MACADDR8_P(0);
457 60 : macaddr8 *addr2 = PG_GETARG_MACADDR8_P(1);
458 : macaddr8 *result;
459 :
460 60 : result = palloc0_object(macaddr8);
461 60 : result->a = addr1->a | addr2->a;
462 60 : result->b = addr1->b | addr2->b;
463 60 : result->c = addr1->c | addr2->c;
464 60 : result->d = addr1->d | addr2->d;
465 60 : result->e = addr1->e | addr2->e;
466 60 : result->f = addr1->f | addr2->f;
467 60 : result->g = addr1->g | addr2->g;
468 60 : result->h = addr1->h | addr2->h;
469 :
470 60 : PG_RETURN_MACADDR8_P(result);
471 : }
472 :
473 : /*
474 : * Truncation function to allow comparing macaddr8 manufacturers.
475 : */
476 : Datum
477 60 : macaddr8_trunc(PG_FUNCTION_ARGS)
478 : {
479 60 : macaddr8 *addr = PG_GETARG_MACADDR8_P(0);
480 : macaddr8 *result;
481 :
482 60 : result = palloc0_object(macaddr8);
483 :
484 60 : result->a = addr->a;
485 60 : result->b = addr->b;
486 60 : result->c = addr->c;
487 60 : result->d = 0;
488 60 : result->e = 0;
489 60 : result->f = 0;
490 60 : result->g = 0;
491 60 : result->h = 0;
492 :
493 60 : PG_RETURN_MACADDR8_P(result);
494 : }
495 :
496 : /*
497 : * Set 7th bit for modified EUI-64 as used in IPv6.
498 : */
499 : Datum
500 3 : macaddr8_set7bit(PG_FUNCTION_ARGS)
501 : {
502 3 : macaddr8 *addr = PG_GETARG_MACADDR8_P(0);
503 : macaddr8 *result;
504 :
505 3 : result = palloc0_object(macaddr8);
506 :
507 3 : result->a = addr->a | 0x02;
508 3 : result->b = addr->b;
509 3 : result->c = addr->c;
510 3 : result->d = addr->d;
511 3 : result->e = addr->e;
512 3 : result->f = addr->f;
513 3 : result->g = addr->g;
514 3 : result->h = addr->h;
515 :
516 3 : 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 = palloc0_object(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 12 : macaddr8tomacaddr(PG_FUNCTION_ARGS)
546 : {
547 12 : macaddr8 *addr = PG_GETARG_MACADDR8_P(0);
548 : macaddr *result;
549 :
550 12 : result = palloc0_object(macaddr);
551 :
552 12 : 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 12 : result->a = addr->a;
562 12 : result->b = addr->b;
563 12 : result->c = addr->c;
564 12 : result->d = addr->f;
565 12 : result->e = addr->g;
566 12 : result->f = addr->h;
567 :
568 12 : PG_RETURN_MACADDR_P(result);
569 : }
|