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