Skip to main content

rupg_types/
mac.rs

1//! `macaddr` and `macaddr8`, in the text formats of `src/backend/utils/adt/mac.c` and `mac8.c`. A value is the bytes of the address in a `u64`, with the first byte as the most significant byte, so that the order of the numbers is the order of the addresses.
2
3use rupg_common::SqlState;
4
5use crate::binary::Recv;
6use crate::error::TypeError;
7use crate::number::is_space;
8
9/// A part of a format of `sscanf` in `macaddr_in`.
10#[derive(Clone, Copy)]
11enum Part {
12    /// `%x` with a width, or with no width for 0.
13    Hex(usize),
14    /// A character that must be next in the text.
15    Char(u8),
16}
17
18use Part::{Char, Hex};
19
20/// The formats that `macaddr_in` tries, in its order.
21const FORMATS: [[Part; 11]; 7] = [
22    [
23        Hex(0),
24        Char(b':'),
25        Hex(0),
26        Char(b':'),
27        Hex(0),
28        Char(b':'),
29        Hex(0),
30        Char(b':'),
31        Hex(0),
32        Char(b':'),
33        Hex(0),
34    ],
35    [
36        Hex(0),
37        Char(b'-'),
38        Hex(0),
39        Char(b'-'),
40        Hex(0),
41        Char(b'-'),
42        Hex(0),
43        Char(b'-'),
44        Hex(0),
45        Char(b'-'),
46        Hex(0),
47    ],
48    [
49        Hex(2),
50        Hex(2),
51        Hex(2),
52        Char(b':'),
53        Hex(2),
54        Hex(2),
55        Hex(2),
56        Char(0),
57        Char(0),
58        Char(0),
59        Char(0),
60    ],
61    [
62        Hex(2),
63        Hex(2),
64        Hex(2),
65        Char(b'-'),
66        Hex(2),
67        Hex(2),
68        Hex(2),
69        Char(0),
70        Char(0),
71        Char(0),
72        Char(0),
73    ],
74    [
75        Hex(2),
76        Hex(2),
77        Char(b'.'),
78        Hex(2),
79        Hex(2),
80        Char(b'.'),
81        Hex(2),
82        Hex(2),
83        Char(0),
84        Char(0),
85        Char(0),
86    ],
87    [
88        Hex(2),
89        Hex(2),
90        Char(b'-'),
91        Hex(2),
92        Hex(2),
93        Char(b'-'),
94        Hex(2),
95        Hex(2),
96        Char(0),
97        Char(0),
98        Char(0),
99    ],
100    [Hex(2), Hex(2), Hex(2), Hex(2), Hex(2), Hex(2), Char(0), Char(0), Char(0), Char(0), Char(0)],
101];
102
103/// `%x` of glibc `sscanf` at `b[*i..]`: white space, a sign, a `0x` prefix and hex digits, with no more than `width` characters after the white space when `width` is not 0. The result is the bits of the `unsigned int` that `%x` stores, as the `int` of `macaddr_in`, or `None` when there is no digit.
104fn scan_hex(b: &[u8], i: &mut usize, width: usize) -> Option<i32> {
105    let at = |i: usize| b.get(i).copied().unwrap_or(0);
106    while is_space(at(*i)) {
107        *i += 1;
108    }
109    let mut left = if width == 0 { usize::MAX } else { width };
110    let negative = at(*i) == b'-';
111    if negative || at(*i) == b'+' {
112        *i += 1;
113        left -= 1;
114    }
115    let mut digits = 0;
116    let mut value = 0u64;
117    let mut overflow = false;
118    if left != 0 && at(*i) == b'0' {
119        *i += 1;
120        left -= 1;
121        digits += 1;
122        if left != 0 && matches!(at(*i), b'x' | b'X') {
123            *i += 1;
124            left -= 1;
125        }
126    }
127    while left != 0 && at(*i).is_ascii_hexdigit() {
128        let digit = u64::from(char::from(at(*i)).to_digit(16).unwrap_or(0));
129        match value.checked_mul(16).and_then(|v| v.checked_add(digit)) {
130            Some(v) => value = v,
131            None => overflow = true,
132        }
133        *i += 1;
134        left -= 1;
135        digits += 1;
136    }
137    if digits == 0 {
138        return None;
139    }
140    // `strtoul` gives `ULONG_MAX` for an overflow and negates the value for a minus sign. `%x` keeps the low 32 bits.
141    let value = if overflow {
142        u64::MAX
143    } else if negative {
144        value.wrapping_neg()
145    } else {
146        value
147    };
148    u32::try_from(value & u64::from(u32::MAX)).ok().map(u32::cast_signed)
149}
150
151/// The six numbers of `s` in `format`, or `None` when `s` does not match it. White space can follow the last number.
152fn scan(s: &[u8], format: &[Part]) -> Option<[i32; 6]> {
153    let mut out = [0; 6];
154    let mut count = 0;
155    let mut i = 0;
156    for part in format {
157        match *part {
158            Hex(width) => {
159                out[count] = scan_hex(s, &mut i, width)?;
160                count += 1;
161            }
162            Char(0) => break,
163            Char(c) => {
164                if s.get(i) != Some(&c) {
165                    return None;
166                }
167                i += 1;
168            }
169        }
170    }
171    while i < s.len() && is_space(s[i]) {
172        i += 1;
173    }
174    (i == s.len()).then_some(out)
175}
176
177/// `macaddr_in`: six numbers in hex with one of the formats of `sscanf` in `mac.c`, such as `08:00:2b:01:02:03`, `08002b-010203` or `0800.2b01.0203`.
178///
179/// # Errors
180///
181/// `22P02` for text in no format, and `22003` for a number that is not in the range 0 to 255.
182pub fn macaddr_in(s: &str) -> Result<u64, TypeError> {
183    let numbers = FORMATS
184        .iter()
185        .find_map(|format| scan(s.as_bytes(), format))
186        .ok_or_else(|| TypeError::syntax("macaddr", s))?;
187    let mut value = 0u64;
188    for n in numbers {
189        let byte = u8::try_from(n).map_err(|_| {
190            TypeError::new(
191                SqlState::NUMERIC_VALUE_OUT_OF_RANGE,
192                format!("invalid octet value in \"macaddr\" value: \"{s}\""),
193            )
194        })?;
195        value = value << 8 | u64::from(byte);
196    }
197    Ok(value)
198}
199
200/// The bytes of an address, from the first.
201fn bytes(value: u64, count: usize) -> impl Iterator<Item = u8> {
202    value.to_be_bytes().into_iter().skip(8 - count)
203}
204
205/// The bytes in hex with a `:` between them.
206fn hex_out(value: u64, count: usize, out: &mut Vec<u8>) {
207    const HEX: &[u8; 16] = b"0123456789abcdef";
208    for (i, byte) in bytes(value, count).enumerate() {
209        if i > 0 {
210            out.push(b':');
211        }
212        out.push(HEX[usize::from(byte >> 4)]);
213        out.push(HEX[usize::from(byte & 15)]);
214    }
215}
216
217/// `macaddr_out`: `08:00:2b:01:02:03`.
218pub fn macaddr_out(value: u64, out: &mut Vec<u8>) {
219    hex_out(value, 6, out);
220}
221
222/// `macaddr8_out`: `08:00:2b:01:02:03:04:05`.
223pub fn macaddr8_out(value: u64, out: &mut Vec<u8>) {
224    hex_out(value, 8, out);
225}
226
227/// `macaddr8_in`: 6 or 8 bytes of 2 hex digits, with the same `:`, `-` or `.` between them or with nothing between them. White space can come before and after the address. A 6-byte address gets `ff:fe` as the 4th and 5th bytes.
228///
229/// # Errors
230///
231/// `22P02` for text that is not an address.
232pub fn macaddr8_in(s: &str) -> Result<u64, TypeError> {
233    let fail = || TypeError::syntax("macaddr8", s);
234    let b = s.as_bytes();
235    let at = |i: usize| b.get(i).copied().unwrap_or(0);
236    let mut i = 0;
237    while is_space(at(i)) {
238        i += 1;
239    }
240    let mut bytes = [0u8; 8];
241    let mut count = 0;
242    let mut spacer = 0;
243    while at(i) != 0 && at(i + 1) != 0 {
244        if count == 8 {
245            return Err(fail());
246        }
247        let digit = |c: u8| char::from(c).to_digit(16).and_then(|d| u8::try_from(d).ok());
248        let (Some(high), Some(low)) = (digit(at(i)), digit(at(i + 1))) else { return Err(fail()) };
249        bytes[count] = high << 4 | low;
250        count += 1;
251        i += 2;
252        if matches!(at(i), b':' | b'-' | b'.') {
253            if spacer == 0 {
254                spacer = at(i);
255            } else if spacer != at(i) {
256                return Err(fail());
257            }
258            i += 1;
259        }
260        if (count == 6 || count == 8) && is_space(at(i)) {
261            while is_space(at(i)) {
262                i += 1;
263            }
264            if at(i) != 0 {
265                return Err(fail());
266            }
267        }
268    }
269    let bytes = match count {
270        6 => [bytes[0], bytes[1], bytes[2], 0xff, 0xfe, bytes[3], bytes[4], bytes[5]],
271        8 => bytes,
272        _ => return Err(fail()),
273    };
274    Ok(u64::from_be_bytes(bytes))
275}
276
277/// `macaddr_recv`: the 6 bytes.
278///
279/// # Errors
280///
281/// `08P01` when the value has less than 6 bytes.
282pub fn macaddr_recv(recv: &mut Recv<'_>) -> Result<u64, TypeError> {
283    let mut value = 0;
284    for _ in 0..6 {
285        value = value << 8 | u64::from(recv.byte()?);
286    }
287    Ok(value)
288}
289
290/// `macaddr8_recv`: the 8 bytes, or 6 bytes that get `ff:fe` as the 4th and 5th bytes.
291///
292/// # Errors
293///
294/// `08P01` when the value has less than 6 bytes, or 7 bytes.
295pub fn macaddr8_recv(recv: &mut Recv<'_>) -> Result<u64, TypeError> {
296    if recv.remaining() == 6 {
297        return macaddr_recv(recv).map(macaddr_to_macaddr8);
298    }
299    let mut value = 0;
300    for _ in 0..8 {
301        value = value << 8 | u64::from(recv.byte()?);
302    }
303    Ok(value)
304}
305
306/// `macaddrtomacaddr8`: `ff:fe` as the 4th and 5th bytes.
307#[must_use]
308pub fn macaddr_to_macaddr8(value: u64) -> u64 {
309    (value >> 24) << 40 | 0xfffe << 24 | value & 0xff_ffff
310}
311
312/// `macaddr8tomacaddr`: the address without the 4th and 5th bytes, or `None` when they are not `ff:fe`.
313#[must_use]
314pub fn macaddr8_to_macaddr(value: u64) -> Option<u64> {
315    (value >> 24 & 0xffff == 0xfffe).then_some((value >> 40) << 24 | value & 0xff_ffff)
316}
317
318#[cfg(test)]
319mod tests {
320    use super::*;
321
322    fn text(f: fn(u64, &mut Vec<u8>), value: u64) -> String {
323        let mut out = Vec::new();
324        f(value, &mut out);
325        String::from_utf8(out).unwrap()
326    }
327
328    /// The values and the errors of PostgreSQL 19 for the same text.
329    #[test]
330    fn text_forms() {
331        for input in [
332            "08:00:2b:01:02:03",
333            "08-00-2b-01-02-03",
334            "08002b:010203",
335            "08002b-010203",
336            "0800.2b01.0203",
337            "0800-2b01-0203",
338            "08002b010203",
339            " 08:00:2b:01:02:03 ",
340            "8:0:2b:1:2:3",
341        ] {
342            assert_eq!(
343                text(macaddr_out, macaddr_in(input).unwrap()),
344                "08:00:2b:01:02:03",
345                "{input}"
346            );
347        }
348        assert_eq!(
349            text(macaddr_out, macaddr_in("AB:CD:EF:01:02:03").unwrap()),
350            "ab:cd:ef:01:02:03"
351        );
352        for input in
353            ["08:00:2b:01:02", "08:00:2b:01:02:03:04", "08:00:2b:01:02:0g", "08:00-2b:01:02:03", ""]
354        {
355            let error = macaddr_in(input).unwrap_err();
356            assert_eq!(error.sqlstate.as_str(), "22P02", "{input}");
357        }
358        let error = macaddr_in("08:00:2b:01:02:100").unwrap_err();
359        assert_eq!(error.sqlstate.as_str(), "22003");
360        assert_eq!(
361            error.message,
362            "invalid octet value in \"macaddr\" value: \"08:00:2b:01:02:100\""
363        );
364
365        let mac8 = |s: &str| text(macaddr8_out, macaddr8_in(s).unwrap());
366        assert_eq!(mac8("08:00:2b:01:02:03"), "08:00:2b:ff:fe:01:02:03");
367        for input in [
368            "08:00:2b:01:02:03:04:05",
369            "08-00-2b-01-02-03-04-05",
370            "08002b0102030405",
371            "0800.2b01.0203.0405",
372            "08002b:0102030405",
373            "08:00:2b:01:02:03:04:05 ",
374            "0800:2b01:0203:0405",
375        ] {
376            assert_eq!(mac8(input), "08:00:2b:01:02:03:04:05", "{input}");
377        }
378        for input in ["08:00:2b:01:02:03:04", "08:00:2b:01:02:03:04:05:06", "x", "08:00:2b:01:02:3"]
379        {
380            assert_eq!(
381                macaddr8_in(input).unwrap_err().message,
382                format!("invalid input syntax for type macaddr8: \"{input}\"")
383            );
384        }
385    }
386
387    #[test]
388    fn binary_forms() {
389        let six = [8, 0, 0x2b, 1, 2, 3];
390        assert_eq!(macaddr_recv(&mut Recv::new(&six)).unwrap(), 0x0800_2b01_0203);
391        assert_eq!(macaddr8_recv(&mut Recv::new(&six)).unwrap(), 0x0800_2bff_fe01_0203);
392        let eight = [8, 0, 0x2b, 1, 2, 3, 4, 5];
393        assert_eq!(macaddr8_recv(&mut Recv::new(&eight)).unwrap(), 0x0800_2b01_0203_0405);
394        assert!(macaddr_recv(&mut Recv::new(&six[..5])).is_err());
395    }
396
397    #[test]
398    fn casts() {
399        let six = macaddr_in("08:00:2b:01:02:03").unwrap();
400        assert_eq!(text(macaddr8_out, macaddr_to_macaddr8(six)), "08:00:2b:ff:fe:01:02:03");
401        assert_eq!(macaddr8_to_macaddr(macaddr_to_macaddr8(six)), Some(six));
402        assert_eq!(macaddr8_to_macaddr(macaddr8_in("08:00:2b:01:02:03:04:05").unwrap()), None);
403    }
404}