Coverage Report

Created: 2025-08-12 06:43

/src/postgres/src/backend/utils/adt/mac.c
Line
Count
Source (jump to first uncovered line)
1
/*-------------------------------------------------------------------------
2
 *
3
 * mac.c
4
 *    PostgreSQL type definitions for 6 byte, EUI-48, MAC addresses.
5
 *
6
 * Portions Copyright (c) 1998-2025, PostgreSQL Global Development Group
7
 *
8
 * IDENTIFICATION
9
 *      src/backend/utils/adt/mac.c
10
 *
11
 *-------------------------------------------------------------------------
12
 */
13
14
#include "postgres.h"
15
16
#include "common/hashfn.h"
17
#include "lib/hyperloglog.h"
18
#include "libpq/pqformat.h"
19
#include "port/pg_bswap.h"
20
#include "utils/fmgrprotos.h"
21
#include "utils/guc.h"
22
#include "utils/inet.h"
23
#include "utils/sortsupport.h"
24
25
26
/*
27
 *  Utility macros used for sorting and comparing:
28
 */
29
30
#define hibits(addr) \
31
0
  ((unsigned long)(((addr)->a<<16)|((addr)->b<<8)|((addr)->c)))
32
33
#define lobits(addr) \
34
0
  ((unsigned long)(((addr)->d<<16)|((addr)->e<<8)|((addr)->f)))
35
36
/* sortsupport for macaddr */
37
typedef struct
38
{
39
  int64   input_count;  /* number of non-null values seen */
40
  bool    estimating;   /* true if estimating cardinality */
41
42
  hyperLogLogState abbr_card; /* cardinality estimator */
43
} macaddr_sortsupport_state;
44
45
static int  macaddr_cmp_internal(macaddr *a1, macaddr *a2);
46
static int  macaddr_fast_cmp(Datum x, Datum y, SortSupport ssup);
47
static bool macaddr_abbrev_abort(int memtupcount, SortSupport ssup);
48
static Datum macaddr_abbrev_convert(Datum original, SortSupport ssup);
49
50
/*
51
 *  MAC address reader.  Accepts several common notations.
52
 */
53
54
Datum
55
macaddr_in(PG_FUNCTION_ARGS)
56
0
{
57
0
  char     *str = PG_GETARG_CSTRING(0);
58
0
  Node     *escontext = fcinfo->context;
59
0
  macaddr    *result;
60
0
  int     a,
61
0
        b,
62
0
        c,
63
0
        d,
64
0
        e,
65
0
        f;
66
0
  char    junk[2];
67
0
  int     count;
68
69
  /* %1s matches iff there is trailing non-whitespace garbage */
70
71
0
  count = sscanf(str, "%x:%x:%x:%x:%x:%x%1s",
72
0
           &a, &b, &c, &d, &e, &f, junk);
73
0
  if (count != 6)
74
0
    count = sscanf(str, "%x-%x-%x-%x-%x-%x%1s",
75
0
             &a, &b, &c, &d, &e, &f, junk);
76
0
  if (count != 6)
77
0
    count = sscanf(str, "%2x%2x%2x:%2x%2x%2x%1s",
78
0
             &a, &b, &c, &d, &e, &f, junk);
79
0
  if (count != 6)
80
0
    count = sscanf(str, "%2x%2x%2x-%2x%2x%2x%1s",
81
0
             &a, &b, &c, &d, &e, &f, junk);
82
0
  if (count != 6)
83
0
    count = sscanf(str, "%2x%2x.%2x%2x.%2x%2x%1s",
84
0
             &a, &b, &c, &d, &e, &f, junk);
85
0
  if (count != 6)
86
0
    count = sscanf(str, "%2x%2x-%2x%2x-%2x%2x%1s",
87
0
             &a, &b, &c, &d, &e, &f, junk);
88
0
  if (count != 6)
89
0
    count = sscanf(str, "%2x%2x%2x%2x%2x%2x%1s",
90
0
             &a, &b, &c, &d, &e, &f, junk);
91
0
  if (count != 6)
92
0
    ereturn(escontext, (Datum) 0,
93
0
        (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
94
0
         errmsg("invalid input syntax for type %s: \"%s\"", "macaddr",
95
0
            str)));
96
97
0
  if ((a < 0) || (a > 255) || (b < 0) || (b > 255) ||
98
0
    (c < 0) || (c > 255) || (d < 0) || (d > 255) ||
99
0
    (e < 0) || (e > 255) || (f < 0) || (f > 255))
100
0
    ereturn(escontext, (Datum) 0,
101
0
        (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
102
0
         errmsg("invalid octet value in \"macaddr\" value: \"%s\"", str)));
103
104
0
  result = (macaddr *) palloc(sizeof(macaddr));
105
106
0
  result->a = a;
107
0
  result->b = b;
108
0
  result->c = c;
109
0
  result->d = d;
110
0
  result->e = e;
111
0
  result->f = f;
112
113
0
  PG_RETURN_MACADDR_P(result);
114
0
}
115
116
/*
117
 *  MAC address output function.  Fixed format.
118
 */
119
120
Datum
121
macaddr_out(PG_FUNCTION_ARGS)
122
0
{
123
0
  macaddr    *addr = PG_GETARG_MACADDR_P(0);
124
0
  char     *result;
125
126
0
  result = (char *) palloc(32);
127
128
0
  snprintf(result, 32, "%02x:%02x:%02x:%02x:%02x:%02x",
129
0
       addr->a, addr->b, addr->c, addr->d, addr->e, addr->f);
130
131
0
  PG_RETURN_CSTRING(result);
132
0
}
133
134
/*
135
 *    macaddr_recv      - converts external binary format to macaddr
136
 *
137
 * The external representation is just the six bytes, MSB first.
138
 */
139
Datum
140
macaddr_recv(PG_FUNCTION_ARGS)
141
0
{
142
0
  StringInfo  buf = (StringInfo) PG_GETARG_POINTER(0);
143
0
  macaddr    *addr;
144
145
0
  addr = (macaddr *) palloc(sizeof(macaddr));
146
147
0
  addr->a = pq_getmsgbyte(buf);
148
0
  addr->b = pq_getmsgbyte(buf);
149
0
  addr->c = pq_getmsgbyte(buf);
150
0
  addr->d = pq_getmsgbyte(buf);
151
0
  addr->e = pq_getmsgbyte(buf);
152
0
  addr->f = pq_getmsgbyte(buf);
153
154
0
  PG_RETURN_MACADDR_P(addr);
155
0
}
156
157
/*
158
 *    macaddr_send      - converts macaddr to binary format
159
 */
160
Datum
161
macaddr_send(PG_FUNCTION_ARGS)
162
0
{
163
0
  macaddr    *addr = PG_GETARG_MACADDR_P(0);
164
0
  StringInfoData buf;
165
166
0
  pq_begintypsend(&buf);
167
0
  pq_sendbyte(&buf, addr->a);
168
0
  pq_sendbyte(&buf, addr->b);
169
0
  pq_sendbyte(&buf, addr->c);
170
0
  pq_sendbyte(&buf, addr->d);
171
0
  pq_sendbyte(&buf, addr->e);
172
0
  pq_sendbyte(&buf, addr->f);
173
0
  PG_RETURN_BYTEA_P(pq_endtypsend(&buf));
174
0
}
175
176
177
/*
178
 *  Comparison function for sorting:
179
 */
180
181
static int
182
macaddr_cmp_internal(macaddr *a1, macaddr *a2)
183
0
{
184
0
  if (hibits(a1) < hibits(a2))
185
0
    return -1;
186
0
  else if (hibits(a1) > hibits(a2))
187
0
    return 1;
188
0
  else if (lobits(a1) < lobits(a2))
189
0
    return -1;
190
0
  else if (lobits(a1) > lobits(a2))
191
0
    return 1;
192
0
  else
193
0
    return 0;
194
0
}
195
196
Datum
197
macaddr_cmp(PG_FUNCTION_ARGS)
198
0
{
199
0
  macaddr    *a1 = PG_GETARG_MACADDR_P(0);
200
0
  macaddr    *a2 = PG_GETARG_MACADDR_P(1);
201
202
0
  PG_RETURN_INT32(macaddr_cmp_internal(a1, a2));
203
0
}
204
205
/*
206
 *  Boolean comparisons.
207
 */
208
209
Datum
210
macaddr_lt(PG_FUNCTION_ARGS)
211
0
{
212
0
  macaddr    *a1 = PG_GETARG_MACADDR_P(0);
213
0
  macaddr    *a2 = PG_GETARG_MACADDR_P(1);
214
215
0
  PG_RETURN_BOOL(macaddr_cmp_internal(a1, a2) < 0);
216
0
}
217
218
Datum
219
macaddr_le(PG_FUNCTION_ARGS)
220
0
{
221
0
  macaddr    *a1 = PG_GETARG_MACADDR_P(0);
222
0
  macaddr    *a2 = PG_GETARG_MACADDR_P(1);
223
224
0
  PG_RETURN_BOOL(macaddr_cmp_internal(a1, a2) <= 0);
225
0
}
226
227
Datum
228
macaddr_eq(PG_FUNCTION_ARGS)
229
0
{
230
0
  macaddr    *a1 = PG_GETARG_MACADDR_P(0);
231
0
  macaddr    *a2 = PG_GETARG_MACADDR_P(1);
232
233
0
  PG_RETURN_BOOL(macaddr_cmp_internal(a1, a2) == 0);
234
0
}
235
236
Datum
237
macaddr_ge(PG_FUNCTION_ARGS)
238
0
{
239
0
  macaddr    *a1 = PG_GETARG_MACADDR_P(0);
240
0
  macaddr    *a2 = PG_GETARG_MACADDR_P(1);
241
242
0
  PG_RETURN_BOOL(macaddr_cmp_internal(a1, a2) >= 0);
243
0
}
244
245
Datum
246
macaddr_gt(PG_FUNCTION_ARGS)
247
0
{
248
0
  macaddr    *a1 = PG_GETARG_MACADDR_P(0);
249
0
  macaddr    *a2 = PG_GETARG_MACADDR_P(1);
250
251
0
  PG_RETURN_BOOL(macaddr_cmp_internal(a1, a2) > 0);
252
0
}
253
254
Datum
255
macaddr_ne(PG_FUNCTION_ARGS)
256
0
{
257
0
  macaddr    *a1 = PG_GETARG_MACADDR_P(0);
258
0
  macaddr    *a2 = PG_GETARG_MACADDR_P(1);
259
260
0
  PG_RETURN_BOOL(macaddr_cmp_internal(a1, a2) != 0);
261
0
}
262
263
/*
264
 * Support function for hash indexes on macaddr.
265
 */
266
Datum
267
hashmacaddr(PG_FUNCTION_ARGS)
268
0
{
269
0
  macaddr    *key = PG_GETARG_MACADDR_P(0);
270
271
0
  return hash_any((unsigned char *) key, sizeof(macaddr));
272
0
}
273
274
Datum
275
hashmacaddrextended(PG_FUNCTION_ARGS)
276
0
{
277
0
  macaddr    *key = PG_GETARG_MACADDR_P(0);
278
279
0
  return hash_any_extended((unsigned char *) key, sizeof(macaddr),
280
0
               PG_GETARG_INT64(1));
281
0
}
282
283
/*
284
 * Arithmetic functions: bitwise NOT, AND, OR.
285
 */
286
Datum
287
macaddr_not(PG_FUNCTION_ARGS)
288
0
{
289
0
  macaddr    *addr = PG_GETARG_MACADDR_P(0);
290
0
  macaddr    *result;
291
292
0
  result = (macaddr *) palloc(sizeof(macaddr));
293
0
  result->a = ~addr->a;
294
0
  result->b = ~addr->b;
295
0
  result->c = ~addr->c;
296
0
  result->d = ~addr->d;
297
0
  result->e = ~addr->e;
298
0
  result->f = ~addr->f;
299
0
  PG_RETURN_MACADDR_P(result);
300
0
}
301
302
Datum
303
macaddr_and(PG_FUNCTION_ARGS)
304
0
{
305
0
  macaddr    *addr1 = PG_GETARG_MACADDR_P(0);
306
0
  macaddr    *addr2 = PG_GETARG_MACADDR_P(1);
307
0
  macaddr    *result;
308
309
0
  result = (macaddr *) palloc(sizeof(macaddr));
310
0
  result->a = addr1->a & addr2->a;
311
0
  result->b = addr1->b & addr2->b;
312
0
  result->c = addr1->c & addr2->c;
313
0
  result->d = addr1->d & addr2->d;
314
0
  result->e = addr1->e & addr2->e;
315
0
  result->f = addr1->f & addr2->f;
316
0
  PG_RETURN_MACADDR_P(result);
317
0
}
318
319
Datum
320
macaddr_or(PG_FUNCTION_ARGS)
321
0
{
322
0
  macaddr    *addr1 = PG_GETARG_MACADDR_P(0);
323
0
  macaddr    *addr2 = PG_GETARG_MACADDR_P(1);
324
0
  macaddr    *result;
325
326
0
  result = (macaddr *) palloc(sizeof(macaddr));
327
0
  result->a = addr1->a | addr2->a;
328
0
  result->b = addr1->b | addr2->b;
329
0
  result->c = addr1->c | addr2->c;
330
0
  result->d = addr1->d | addr2->d;
331
0
  result->e = addr1->e | addr2->e;
332
0
  result->f = addr1->f | addr2->f;
333
0
  PG_RETURN_MACADDR_P(result);
334
0
}
335
336
/*
337
 *  Truncation function to allow comparing mac manufacturers.
338
 *  From suggestion by Alex Pilosov <alex@pilosoft.com>
339
 */
340
Datum
341
macaddr_trunc(PG_FUNCTION_ARGS)
342
0
{
343
0
  macaddr    *addr = PG_GETARG_MACADDR_P(0);
344
0
  macaddr    *result;
345
346
0
  result = (macaddr *) palloc(sizeof(macaddr));
347
348
0
  result->a = addr->a;
349
0
  result->b = addr->b;
350
0
  result->c = addr->c;
351
0
  result->d = 0;
352
0
  result->e = 0;
353
0
  result->f = 0;
354
355
0
  PG_RETURN_MACADDR_P(result);
356
0
}
357
358
/*
359
 * SortSupport strategy function. Populates a SortSupport struct with the
360
 * information necessary to use comparison by abbreviated keys.
361
 */
362
Datum
363
macaddr_sortsupport(PG_FUNCTION_ARGS)
364
0
{
365
0
  SortSupport ssup = (SortSupport) PG_GETARG_POINTER(0);
366
367
0
  ssup->comparator = macaddr_fast_cmp;
368
0
  ssup->ssup_extra = NULL;
369
370
0
  if (ssup->abbreviate)
371
0
  {
372
0
    macaddr_sortsupport_state *uss;
373
0
    MemoryContext oldcontext;
374
375
0
    oldcontext = MemoryContextSwitchTo(ssup->ssup_cxt);
376
377
0
    uss = palloc(sizeof(macaddr_sortsupport_state));
378
0
    uss->input_count = 0;
379
0
    uss->estimating = true;
380
0
    initHyperLogLog(&uss->abbr_card, 10);
381
382
0
    ssup->ssup_extra = uss;
383
384
0
    ssup->comparator = ssup_datum_unsigned_cmp;
385
0
    ssup->abbrev_converter = macaddr_abbrev_convert;
386
0
    ssup->abbrev_abort = macaddr_abbrev_abort;
387
0
    ssup->abbrev_full_comparator = macaddr_fast_cmp;
388
389
0
    MemoryContextSwitchTo(oldcontext);
390
0
  }
391
392
0
  PG_RETURN_VOID();
393
0
}
394
395
/*
396
 * SortSupport "traditional" comparison function. Pulls two MAC addresses from
397
 * the heap and runs a standard comparison on them.
398
 */
399
static int
400
macaddr_fast_cmp(Datum x, Datum y, SortSupport ssup)
401
0
{
402
0
  macaddr    *arg1 = DatumGetMacaddrP(x);
403
0
  macaddr    *arg2 = DatumGetMacaddrP(y);
404
405
0
  return macaddr_cmp_internal(arg1, arg2);
406
0
}
407
408
/*
409
 * Callback for estimating effectiveness of abbreviated key optimization.
410
 *
411
 * We pay no attention to the cardinality of the non-abbreviated data, because
412
 * there is no equality fast-path within authoritative macaddr comparator.
413
 */
414
static bool
415
macaddr_abbrev_abort(int memtupcount, SortSupport ssup)
416
0
{
417
0
  macaddr_sortsupport_state *uss = ssup->ssup_extra;
418
0
  double    abbr_card;
419
420
0
  if (memtupcount < 10000 || uss->input_count < 10000 || !uss->estimating)
421
0
    return false;
422
423
0
  abbr_card = estimateHyperLogLog(&uss->abbr_card);
424
425
  /*
426
   * If we have >100k distinct values, then even if we were sorting many
427
   * billion rows we'd likely still break even, and the penalty of undoing
428
   * that many rows of abbrevs would probably not be worth it. At this point
429
   * we stop counting because we know that we're now fully committed.
430
   */
431
0
  if (abbr_card > 100000.0)
432
0
  {
433
0
    if (trace_sort)
434
0
      elog(LOG,
435
0
         "macaddr_abbrev: estimation ends at cardinality %f"
436
0
         " after " INT64_FORMAT " values (%d rows)",
437
0
         abbr_card, uss->input_count, memtupcount);
438
0
    uss->estimating = false;
439
0
    return false;
440
0
  }
441
442
  /*
443
   * Target minimum cardinality is 1 per ~2k of non-null inputs. 0.5 row
444
   * fudge factor allows us to abort earlier on genuinely pathological data
445
   * where we've had exactly one abbreviated value in the first 2k
446
   * (non-null) rows.
447
   */
448
0
  if (abbr_card < uss->input_count / 2000.0 + 0.5)
449
0
  {
450
0
    if (trace_sort)
451
0
      elog(LOG,
452
0
         "macaddr_abbrev: aborting abbreviation at cardinality %f"
453
0
         " below threshold %f after " INT64_FORMAT " values (%d rows)",
454
0
         abbr_card, uss->input_count / 2000.0 + 0.5, uss->input_count,
455
0
         memtupcount);
456
0
    return true;
457
0
  }
458
459
0
  if (trace_sort)
460
0
    elog(LOG,
461
0
       "macaddr_abbrev: cardinality %f after " INT64_FORMAT
462
0
       " values (%d rows)", abbr_card, uss->input_count, memtupcount);
463
464
0
  return false;
465
0
}
466
467
/*
468
 * SortSupport conversion routine. Converts original macaddr representation
469
 * to abbreviated key representation.
470
 *
471
 * Packs the bytes of a 6-byte MAC address into a Datum and treats it as an
472
 * unsigned integer for purposes of comparison. On a 64-bit machine, there
473
 * will be two zeroed bytes of padding. The integer is converted to native
474
 * endianness to facilitate easy comparison.
475
 */
476
static Datum
477
macaddr_abbrev_convert(Datum original, SortSupport ssup)
478
0
{
479
0
  macaddr_sortsupport_state *uss = ssup->ssup_extra;
480
0
  macaddr    *authoritative = DatumGetMacaddrP(original);
481
0
  Datum   res;
482
483
  /*
484
   * On a 64-bit machine, zero out the 8-byte datum and copy the 6 bytes of
485
   * the MAC address in. There will be two bytes of zero padding on the end
486
   * of the least significant bits.
487
   */
488
0
#if SIZEOF_DATUM == 8
489
0
  memset(&res, 0, SIZEOF_DATUM);
490
0
  memcpy(&res, authoritative, sizeof(macaddr));
491
#else             /* SIZEOF_DATUM != 8 */
492
  memcpy(&res, authoritative, SIZEOF_DATUM);
493
#endif
494
0
  uss->input_count += 1;
495
496
  /*
497
   * Cardinality estimation. The estimate uses uint32, so on a 64-bit
498
   * architecture, XOR the two 32-bit halves together to produce slightly
499
   * more entropy. The two zeroed bytes won't have any practical impact on
500
   * this operation.
501
   */
502
0
  if (uss->estimating)
503
0
  {
504
0
    uint32    tmp;
505
506
0
#if SIZEOF_DATUM == 8
507
0
    tmp = (uint32) res ^ (uint32) ((uint64) res >> 32);
508
#else             /* SIZEOF_DATUM != 8 */
509
    tmp = (uint32) res;
510
#endif
511
512
0
    addHyperLogLog(&uss->abbr_card, DatumGetUInt32(hash_uint32(tmp)));
513
0
  }
514
515
  /*
516
   * Byteswap on little-endian machines.
517
   *
518
   * This is needed so that ssup_datum_unsigned_cmp() (an unsigned integer
519
   * 3-way comparator) works correctly on all platforms. Without this, the
520
   * comparator would have to call memcmp() with a pair of pointers to the
521
   * first byte of each abbreviated key, which is slower.
522
   */
523
0
  res = DatumBigEndianToNative(res);
524
525
0
  return res;
526
0
}