Coverage Report

Created: 2025-10-09 06:07

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