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-2022, 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, const unsigned char *str);
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
61 16178 : hex2_to_uchar(const unsigned char *ptr, const unsigned char *str)
62 : {
63 16178 : unsigned char ret = 0;
64 : signed char lookup;
65 :
66 : /* Handle the first character */
67 16178 : if (*ptr > 127)
68 0 : goto invalid_input;
69 :
70 16178 : lookup = hexlookup[*ptr];
71 16178 : if (lookup < 0)
72 12 : goto invalid_input;
73 :
74 16166 : ret = lookup << 4;
75 :
76 : /* Move to the second character */
77 16166 : ptr++;
78 :
79 16166 : if (*ptr > 127)
80 0 : goto invalid_input;
81 :
82 16166 : lookup = hexlookup[*ptr];
83 16166 : if (lookup < 0)
84 18 : goto invalid_input;
85 :
86 16148 : ret += lookup;
87 :
88 16148 : return ret;
89 :
90 30 : invalid_input:
91 30 : 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
104 2398 : macaddr8_in(PG_FUNCTION_ARGS)
105 : {
106 2398 : const unsigned char *str = (unsigned char *) PG_GETARG_CSTRING(0);
107 2398 : const unsigned char *ptr = str;
108 : macaddr8 *result;
109 2398 : unsigned char a = 0,
110 2398 : b = 0,
111 2398 : c = 0,
112 2398 : d = 0,
113 2398 : e = 0,
114 2398 : f = 0,
115 2398 : g = 0,
116 2398 : h = 0;
117 2398 : int count = 0;
118 2398 : unsigned char spacer = '\0';
119 :
120 : /* skip leading spaces */
121 2494 : while (*ptr && isspace(*ptr))
122 96 : ptr++;
123 :
124 : /* digits must always come in pairs */
125 18510 : 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 16202 : count++;
135 :
136 16202 : switch (count)
137 : {
138 2398 : case 1:
139 2398 : a = hex2_to_uchar(ptr, str);
140 2386 : break;
141 2386 : case 2:
142 2386 : b = hex2_to_uchar(ptr, str);
143 2374 : break;
144 2362 : case 3:
145 2362 : c = hex2_to_uchar(ptr, str);
146 2362 : break;
147 2362 : case 4:
148 2362 : d = hex2_to_uchar(ptr, str);
149 2362 : break;
150 2350 : case 5:
151 2350 : e = hex2_to_uchar(ptr, str);
152 2350 : break;
153 2350 : case 6:
154 2350 : f = hex2_to_uchar(ptr, str);
155 2350 : break;
156 988 : case 7:
157 988 : g = hex2_to_uchar(ptr, str);
158 982 : break;
159 982 : case 8:
160 982 : h = hex2_to_uchar(ptr, str);
161 982 : break;
162 24 : default:
163 : /* must be trailing garbage... */
164 24 : 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 16148 : ptr += 2;
172 :
173 : /* Check for a spacer, these are valid, anything else is not */
174 16148 : if (*ptr == ':' || *ptr == '-' || *ptr == '.')
175 : {
176 : /* remember the spacer used, if it changes then it isn't valid */
177 8764 : if (spacer == '\0')
178 1690 : spacer = *ptr;
179 :
180 : /* Have to use the same spacer throughout */
181 7074 : else if (spacer != *ptr)
182 24 : 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 8740 : ptr++;
189 : }
190 :
191 : /* allow trailing whitespace after if we have 6 or 8 bytes */
192 16124 : if (count == 6 || count == 8)
193 : {
194 3332 : if (isspace(*ptr))
195 : {
196 144 : while (*++ptr && isspace(*ptr));
197 :
198 : /* If we found a space and then non-space, it's invalid */
199 36 : if (*ptr)
200 12 : 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 2308 : if (count == 6)
210 : {
211 1356 : h = f;
212 1356 : g = e;
213 1356 : f = d;
214 :
215 1356 : d = 0xFF;
216 1356 : e = 0xFE;
217 : }
218 952 : else if (count != 8)
219 0 : ereport(ERROR,
220 : (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
221 : errmsg("invalid input syntax for type %s: \"%s\"", "macaddr8",
222 : str)));
223 :
224 2308 : result = (macaddr8 *) palloc0(sizeof(macaddr8));
225 :
226 2308 : result->a = a;
227 2308 : result->b = b;
228 2308 : result->c = c;
229 2308 : result->d = d;
230 2308 : result->e = e;
231 2308 : result->f = f;
232 2308 : result->g = g;
233 2308 : result->h = h;
234 :
235 2308 : PG_RETURN_MACADDR8_P(result);
236 : }
237 :
238 : /*
239 : * MAC8 address (EUI-64) output function. Fixed format.
240 : */
241 : Datum
242 1744 : macaddr8_out(PG_FUNCTION_ARGS)
243 : {
244 1744 : macaddr8 *addr = PG_GETARG_MACADDR8_P(0);
245 : char *result;
246 :
247 1744 : result = (char *) palloc(32);
248 :
249 1744 : snprintf(result, 32, "%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x",
250 1744 : addr->a, addr->b, addr->c, addr->d,
251 1744 : addr->e, addr->f, addr->g, addr->h);
252 :
253 1744 : 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
262 0 : macaddr8_recv(PG_FUNCTION_ARGS)
263 : {
264 0 : StringInfo buf = (StringInfo) PG_GETARG_POINTER(0);
265 : macaddr8 *addr;
266 :
267 0 : addr = (macaddr8 *) palloc0(sizeof(macaddr8));
268 :
269 0 : addr->a = pq_getmsgbyte(buf);
270 0 : addr->b = pq_getmsgbyte(buf);
271 0 : addr->c = pq_getmsgbyte(buf);
272 :
273 0 : if (buf->len == 6)
274 : {
275 0 : addr->d = 0xFF;
276 0 : addr->e = 0xFE;
277 : }
278 : else
279 : {
280 0 : addr->d = pq_getmsgbyte(buf);
281 0 : addr->e = pq_getmsgbyte(buf);
282 : }
283 :
284 0 : addr->f = pq_getmsgbyte(buf);
285 0 : addr->g = pq_getmsgbyte(buf);
286 0 : addr->h = pq_getmsgbyte(buf);
287 :
288 0 : PG_RETURN_MACADDR8_P(addr);
289 : }
290 :
291 : /*
292 : * macaddr8_send - converts macaddr8(EUI-64) to binary format
293 : */
294 : Datum
295 0 : macaddr8_send(PG_FUNCTION_ARGS)
296 : {
297 0 : macaddr8 *addr = PG_GETARG_MACADDR8_P(0);
298 : StringInfoData buf;
299 :
300 0 : pq_begintypsend(&buf);
301 0 : pq_sendbyte(&buf, addr->a);
302 0 : pq_sendbyte(&buf, addr->b);
303 0 : pq_sendbyte(&buf, addr->c);
304 0 : pq_sendbyte(&buf, addr->d);
305 0 : pq_sendbyte(&buf, addr->e);
306 0 : pq_sendbyte(&buf, addr->f);
307 0 : pq_sendbyte(&buf, addr->g);
308 0 : pq_sendbyte(&buf, addr->h);
309 :
310 0 : PG_RETURN_BYTEA_P(pq_endtypsend(&buf));
311 : }
312 :
313 :
314 : /*
315 : * macaddr8_cmp_internal - comparison function for sorting:
316 : */
317 : static int32
318 50038 : macaddr8_cmp_internal(macaddr8 *a1, macaddr8 *a2)
319 : {
320 50038 : if (hibits(a1) < hibits(a2))
321 22982 : return -1;
322 27056 : else if (hibits(a1) > hibits(a2))
323 20654 : return 1;
324 6402 : else if (lobits(a1) < lobits(a2))
325 222 : return -1;
326 6180 : else if (lobits(a1) > lobits(a2))
327 24 : return 1;
328 : else
329 6156 : return 0;
330 : }
331 :
332 : Datum
333 15942 : macaddr8_cmp(PG_FUNCTION_ARGS)
334 : {
335 15942 : macaddr8 *a1 = PG_GETARG_MACADDR8_P(0);
336 15942 : macaddr8 *a2 = PG_GETARG_MACADDR8_P(1);
337 :
338 15942 : PG_RETURN_INT32(macaddr8_cmp_internal(a1, a2));
339 : }
340 :
341 : /*
342 : * Boolean comparison functions.
343 : */
344 :
345 : Datum
346 16104 : macaddr8_lt(PG_FUNCTION_ARGS)
347 : {
348 16104 : macaddr8 *a1 = PG_GETARG_MACADDR8_P(0);
349 16104 : macaddr8 *a2 = PG_GETARG_MACADDR8_P(1);
350 :
351 16104 : PG_RETURN_BOOL(macaddr8_cmp_internal(a1, a2) < 0);
352 : }
353 :
354 : Datum
355 3704 : macaddr8_le(PG_FUNCTION_ARGS)
356 : {
357 3704 : macaddr8 *a1 = PG_GETARG_MACADDR8_P(0);
358 3704 : macaddr8 *a2 = PG_GETARG_MACADDR8_P(1);
359 :
360 3704 : PG_RETURN_BOOL(macaddr8_cmp_internal(a1, a2) <= 0);
361 : }
362 :
363 : Datum
364 5158 : macaddr8_eq(PG_FUNCTION_ARGS)
365 : {
366 5158 : macaddr8 *a1 = PG_GETARG_MACADDR8_P(0);
367 5158 : macaddr8 *a2 = PG_GETARG_MACADDR8_P(1);
368 :
369 5158 : PG_RETURN_BOOL(macaddr8_cmp_internal(a1, a2) == 0);
370 : }
371 :
372 : Datum
373 2928 : macaddr8_ge(PG_FUNCTION_ARGS)
374 : {
375 2928 : macaddr8 *a1 = PG_GETARG_MACADDR8_P(0);
376 2928 : macaddr8 *a2 = PG_GETARG_MACADDR8_P(1);
377 :
378 2928 : PG_RETURN_BOOL(macaddr8_cmp_internal(a1, a2) >= 0);
379 : }
380 :
381 : Datum
382 6190 : macaddr8_gt(PG_FUNCTION_ARGS)
383 : {
384 6190 : macaddr8 *a1 = PG_GETARG_MACADDR8_P(0);
385 6190 : macaddr8 *a2 = PG_GETARG_MACADDR8_P(1);
386 :
387 6190 : PG_RETURN_BOOL(macaddr8_cmp_internal(a1, a2) > 0);
388 : }
389 :
390 : Datum
391 12 : macaddr8_ne(PG_FUNCTION_ARGS)
392 : {
393 12 : macaddr8 *a1 = PG_GETARG_MACADDR8_P(0);
394 12 : macaddr8 *a2 = PG_GETARG_MACADDR8_P(1);
395 :
396 12 : PG_RETURN_BOOL(macaddr8_cmp_internal(a1, a2) != 0);
397 : }
398 :
399 : /*
400 : * Support function for hash indexes on macaddr8.
401 : */
402 : Datum
403 180 : hashmacaddr8(PG_FUNCTION_ARGS)
404 : {
405 180 : macaddr8 *key = PG_GETARG_MACADDR8_P(0);
406 :
407 180 : return hash_any((unsigned char *) key, sizeof(macaddr8));
408 : }
409 :
410 : Datum
411 60 : hashmacaddr8extended(PG_FUNCTION_ARGS)
412 : {
413 60 : macaddr8 *key = PG_GETARG_MACADDR8_P(0);
414 :
415 120 : return hash_any_extended((unsigned char *) key, sizeof(macaddr8),
416 60 : PG_GETARG_INT64(1));
417 : }
418 :
419 : /*
420 : * Arithmetic functions: bitwise NOT, AND, OR.
421 : */
422 : Datum
423 120 : macaddr8_not(PG_FUNCTION_ARGS)
424 : {
425 120 : macaddr8 *addr = PG_GETARG_MACADDR8_P(0);
426 : macaddr8 *result;
427 :
428 120 : result = (macaddr8 *) palloc0(sizeof(macaddr8));
429 120 : result->a = ~addr->a;
430 120 : result->b = ~addr->b;
431 120 : result->c = ~addr->c;
432 120 : result->d = ~addr->d;
433 120 : result->e = ~addr->e;
434 120 : result->f = ~addr->f;
435 120 : result->g = ~addr->g;
436 120 : result->h = ~addr->h;
437 :
438 120 : PG_RETURN_MACADDR8_P(result);
439 : }
440 :
441 : Datum
442 120 : macaddr8_and(PG_FUNCTION_ARGS)
443 : {
444 120 : macaddr8 *addr1 = PG_GETARG_MACADDR8_P(0);
445 120 : macaddr8 *addr2 = PG_GETARG_MACADDR8_P(1);
446 : macaddr8 *result;
447 :
448 120 : result = (macaddr8 *) palloc0(sizeof(macaddr8));
449 120 : result->a = addr1->a & addr2->a;
450 120 : result->b = addr1->b & addr2->b;
451 120 : result->c = addr1->c & addr2->c;
452 120 : result->d = addr1->d & addr2->d;
453 120 : result->e = addr1->e & addr2->e;
454 120 : result->f = addr1->f & addr2->f;
455 120 : result->g = addr1->g & addr2->g;
456 120 : result->h = addr1->h & addr2->h;
457 :
458 120 : PG_RETURN_MACADDR8_P(result);
459 : }
460 :
461 : Datum
462 120 : macaddr8_or(PG_FUNCTION_ARGS)
463 : {
464 120 : macaddr8 *addr1 = PG_GETARG_MACADDR8_P(0);
465 120 : macaddr8 *addr2 = PG_GETARG_MACADDR8_P(1);
466 : macaddr8 *result;
467 :
468 120 : result = (macaddr8 *) palloc0(sizeof(macaddr8));
469 120 : result->a = addr1->a | addr2->a;
470 120 : result->b = addr1->b | addr2->b;
471 120 : result->c = addr1->c | addr2->c;
472 120 : result->d = addr1->d | addr2->d;
473 120 : result->e = addr1->e | addr2->e;
474 120 : result->f = addr1->f | addr2->f;
475 120 : result->g = addr1->g | addr2->g;
476 120 : result->h = addr1->h | addr2->h;
477 :
478 120 : PG_RETURN_MACADDR8_P(result);
479 : }
480 :
481 : /*
482 : * Truncation function to allow comparing macaddr8 manufacturers.
483 : */
484 : Datum
485 120 : macaddr8_trunc(PG_FUNCTION_ARGS)
486 : {
487 120 : macaddr8 *addr = PG_GETARG_MACADDR8_P(0);
488 : macaddr8 *result;
489 :
490 120 : result = (macaddr8 *) palloc0(sizeof(macaddr8));
491 :
492 120 : result->a = addr->a;
493 120 : result->b = addr->b;
494 120 : result->c = addr->c;
495 120 : result->d = 0;
496 120 : result->e = 0;
497 120 : result->f = 0;
498 120 : result->g = 0;
499 120 : result->h = 0;
500 :
501 120 : PG_RETURN_MACADDR8_P(result);
502 : }
503 :
504 : /*
505 : * Set 7th bit for modified EUI-64 as used in IPv6.
506 : */
507 : Datum
508 6 : macaddr8_set7bit(PG_FUNCTION_ARGS)
509 : {
510 6 : macaddr8 *addr = PG_GETARG_MACADDR8_P(0);
511 : macaddr8 *result;
512 :
513 6 : result = (macaddr8 *) palloc0(sizeof(macaddr8));
514 :
515 6 : result->a = addr->a | 0x02;
516 6 : result->b = addr->b;
517 6 : result->c = addr->c;
518 6 : result->d = addr->d;
519 6 : result->e = addr->e;
520 6 : result->f = addr->f;
521 6 : result->g = addr->g;
522 6 : result->h = addr->h;
523 :
524 6 : PG_RETURN_MACADDR8_P(result);
525 : }
526 :
527 : /*----------------------------------------------------------
528 : * Conversion operators.
529 : *---------------------------------------------------------*/
530 :
531 : Datum
532 0 : macaddrtomacaddr8(PG_FUNCTION_ARGS)
533 : {
534 0 : macaddr *addr6 = PG_GETARG_MACADDR_P(0);
535 : macaddr8 *result;
536 :
537 0 : result = (macaddr8 *) palloc0(sizeof(macaddr8));
538 :
539 0 : result->a = addr6->a;
540 0 : result->b = addr6->b;
541 0 : result->c = addr6->c;
542 0 : result->d = 0xFF;
543 0 : result->e = 0xFE;
544 0 : result->f = addr6->d;
545 0 : result->g = addr6->e;
546 0 : result->h = addr6->f;
547 :
548 :
549 0 : PG_RETURN_MACADDR8_P(result);
550 : }
551 :
552 : Datum
553 24 : macaddr8tomacaddr(PG_FUNCTION_ARGS)
554 : {
555 24 : macaddr8 *addr = PG_GETARG_MACADDR8_P(0);
556 : macaddr *result;
557 :
558 24 : result = (macaddr *) palloc0(sizeof(macaddr));
559 :
560 24 : if ((addr->d != 0xFF) || (addr->e != 0xFE))
561 0 : 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 24 : result->a = addr->a;
570 24 : result->b = addr->b;
571 24 : result->c = addr->c;
572 24 : result->d = addr->f;
573 24 : result->e = addr->g;
574 24 : result->f = addr->h;
575 :
576 24 : PG_RETURN_MACADDR_P(result);
577 : }
|