diesel/pg/types/
ipnet_address.rs

1extern crate libc;
2
3use ipnet::{IpNet, Ipv4Net, Ipv6Net};
4use std::io::prelude::*;
5use std::net::{Ipv4Addr, Ipv6Addr};
6
7use crate::deserialize::{self, FromSql, FromSqlRow};
8use crate::pg::{Pg, PgValue};
9#[cfg(test)]
10use crate::query_builder::bind_collector::ByteWrapper;
11use crate::serialize::{self, IsNull, Output, ToSql};
12use crate::sql_types::{Cidr, Inet};
13
14#[cfg(windows)]
15const AF_INET: u8 = 2;
16// Maybe not used, but defining to follow Rust's libstd/net/sys
17#[cfg(target_os = "redox")]
18const AF_INET: u8 = 1;
19
20#[allow(clippy::cast_possible_truncation)] // it's 2
21#[cfg(not(any(windows, target_os = "redox")))]
22const AF_INET: u8 = libc::AF_INET as u8;
23
24const PGSQL_AF_INET: u8 = AF_INET;
25const PGSQL_AF_INET6: u8 = AF_INET + 1;
26
27#[allow(dead_code)]
28mod foreign_derives {
29    use super::*;
30    use crate::expression::AsExpression;
31
32    #[derive(AsExpression, FromSqlRow)]
33    #[diesel(foreign_derive)]
34    #[diesel(sql_type = Inet)]
35    #[diesel(sql_type = Cidr)]
36    struct IpNetworkProxy(IpNet);
37}
38
39macro_rules! err {
40    () => {
41        Err("invalid network address format".into())
42    };
43    ($msg:expr) => {
44        Err(format!("invalid network address format. {}", $msg).into())
45    };
46}
47
48macro_rules! assert_or_error {
49    ($cond:expr) => {
50        if !$cond {
51            return err!();
52        }
53    };
54
55    ($cond:expr, $msg:expr) => {
56        if !$cond {
57            return err!($msg);
58        }
59    };
60}
61
62macro_rules! impl_Sql {
63    ($ty: ty, $net_type: expr) => {
64        #[cfg(all(feature = "postgres_backend", feature = "ipnet-address"))]
65        impl FromSql<$ty, Pg> for IpNet {
66            fn from_sql(value: PgValue<'_>) -> deserialize::Result<Self> {
67                // https://github.com/postgres/postgres/blob/55c3391d1e6a201b5b891781d21fe682a8c64fe6/src/include/utils/inet.h#L23-L28
68                let bytes = value.as_bytes();
69                assert_or_error!(4 <= bytes.len(), "input is too short.");
70                let af = bytes[0];
71                let prefix = bytes[1];
72                let net_type = bytes[2];
73                let len = bytes[3];
74                assert_or_error!(
75                    net_type == $net_type,
76                    format!("returned type isn't a {}", stringify!($ty))
77                );
78                if af == PGSQL_AF_INET {
79                    assert_or_error!(bytes.len() == 8);
80                    assert_or_error!(len == 4, "the data isn't the size of ipv4");
81                    let b = &bytes[4..];
82                    let addr = Ipv4Addr::new(b[0], b[1], b[2], b[3]);
83                    let inet = Ipv4Net::new(addr, prefix)?;
84                    Ok(IpNet::V4(inet))
85                } else if af == PGSQL_AF_INET6 {
86                    assert_or_error!(bytes.len() == 20);
87                    assert_or_error!(len == 16, "the data isn't the size of ipv6");
88                    let b = &bytes[4..];
89                    let addr = Ipv6Addr::from([
90                        b[0], b[1], b[2], b[3], b[4], b[5], b[6], b[7], b[8], b[9], b[10], b[11],
91                        b[12], b[13], b[14], b[15],
92                    ]);
93                    let inet = Ipv6Net::new(addr, prefix)?;
94                    Ok(IpNet::V6(inet))
95                } else {
96                    err!()
97                }
98            }
99        }
100
101        #[cfg(all(feature = "postgres_backend", feature = "ipnet-address"))]
102        impl ToSql<$ty, Pg> for IpNet {
103            fn to_sql<'b>(&'b self, out: &mut Output<'b, '_, Pg>) -> serialize::Result {
104                let net_type = $net_type;
105                match *self {
106                    IpNet::V4(ref net) => {
107                        let mut data = [0u8; 8];
108                        let af = PGSQL_AF_INET;
109                        let prefix = net.prefix_len();
110                        let len: u8 = 4;
111                        let addr = if (net_type == 0) {
112                            net.addr().octets()
113                        } else {
114                            net.network().octets()
115                        };
116                        data[0] = af;
117                        data[1] = prefix;
118                        data[2] = net_type;
119                        data[3] = len;
120                        data[4..].copy_from_slice(&addr);
121                        out.write_all(&data).map(|_| IsNull::No).map_err(Into::into)
122                    }
123                    IpNet::V6(ref net) => {
124                        let mut data = [0u8; 20];
125                        let af = PGSQL_AF_INET6;
126                        let prefix = net.prefix_len();
127                        let len: u8 = 16;
128                        let addr = if (net_type == 0) {
129                            net.addr().octets()
130                        } else {
131                            net.network().octets()
132                        };
133                        data[0] = af;
134                        data[1] = prefix;
135                        data[2] = net_type;
136                        data[3] = len;
137                        data[4..].copy_from_slice(&addr);
138                        out.write_all(&data).map(|_| IsNull::No).map_err(Into::into)
139                    }
140                }
141            }
142        }
143    };
144}
145
146impl_Sql!(Inet, 0);
147impl_Sql!(Cidr, 1);
148
149#[cfg(test)]
150mod tests {
151    use super::*;
152    #[diesel_test_helper::test]
153    fn v4address_to_sql() {
154        macro_rules! test_to_sql {
155            ($ty:ty, $net_type:expr) => {
156                let mut buffer = Vec::new();
157                {
158                    let mut bytes = Output::test(ByteWrapper(&mut buffer));
159                    let test_address =
160                        IpNet::V4(Ipv4Net::new(Ipv4Addr::new(127, 0, 0, 1), 32).unwrap());
161                    ToSql::<$ty, Pg>::to_sql(&test_address, &mut bytes).unwrap();
162                }
163                assert_eq!(buffer, vec![PGSQL_AF_INET, 32, $net_type, 4, 127, 0, 0, 1]);
164            };
165        }
166
167        test_to_sql!(Inet, 0);
168        test_to_sql!(Cidr, 1);
169    }
170
171    #[diesel_test_helper::test]
172    fn v4_masked_address_to_sql() {
173        macro_rules! test_to_sql {
174            ($ty:ty, $net_type:expr, $last_byte:expr) => {
175                let mut buffer = Vec::new();
176                {
177                    let mut bytes = Output::test(ByteWrapper(&mut buffer));
178                    let test_address =
179                        IpNet::V4(Ipv4Net::new(Ipv4Addr::new(192, 168, 0, 1), 24).unwrap());
180                    ToSql::<$ty, Pg>::to_sql(&test_address, &mut bytes).unwrap();
181                }
182                assert_eq!(
183                    buffer,
184                    vec![PGSQL_AF_INET, 24, $net_type, 4, 192, 168, 0, $last_byte]
185                );
186            };
187        }
188
189        test_to_sql!(Inet, 0, 1);
190        test_to_sql!(Cidr, 1, 0);
191    }
192
193    #[diesel_test_helper::test]
194    fn some_v4address_from_sql() {
195        macro_rules! test_some_address_from_sql {
196            ($ty:tt) => {
197                let input_address =
198                    IpNet::V4(Ipv4Net::new(Ipv4Addr::new(127, 0, 0, 1), 32).unwrap());
199                let mut buffer = Vec::new();
200                {
201                    let mut bytes = Output::test(ByteWrapper(&mut buffer));
202                    ToSql::<$ty, Pg>::to_sql(&input_address, &mut bytes).unwrap();
203                }
204                let output_address =
205                    FromSql::<$ty, Pg>::from_sql(PgValue::for_test(&buffer)).unwrap();
206                assert_eq!(input_address, output_address);
207            };
208        }
209
210        test_some_address_from_sql!(Cidr);
211        test_some_address_from_sql!(Inet);
212    }
213
214    #[diesel_test_helper::test]
215    fn v6address_to_sql() {
216        macro_rules! test_to_sql {
217            ($ty:ty, $net_type:expr) => {
218                let mut buffer = Vec::new();
219                {
220                    let mut bytes = Output::test(ByteWrapper(&mut buffer));
221                    let test_address = IpNet::V6(
222                        Ipv6Net::new(Ipv6Addr::new(0xfd, 0, 0, 0, 0, 0, 0, 0), 64).unwrap(),
223                    );
224                    ToSql::<$ty, Pg>::to_sql(&test_address, &mut bytes).unwrap();
225                }
226                assert_eq!(
227                    buffer,
228                    vec![
229                        PGSQL_AF_INET6,
230                        64,
231                        $net_type,
232                        16,
233                        0,
234                        0xfd,
235                        0,
236                        0,
237                        0,
238                        0,
239                        0,
240                        0,
241                        0,
242                        0,
243                        0,
244                        0,
245                        0,
246                        0,
247                        0,
248                        0,
249                    ]
250                );
251            };
252        }
253
254        test_to_sql!(Inet, 0);
255        test_to_sql!(Cidr, 1);
256    }
257
258    #[diesel_test_helper::test]
259    fn v6_masked_address_from_sql() {
260        macro_rules! test_to_sql {
261            ($ty:ty, $net_type:expr, $last_byte:expr) => {
262                let mut buffer = Vec::new();
263                {
264                    let mut bytes = Output::test(ByteWrapper(&mut buffer));
265                    let test_address = IpNet::V6(
266                        Ipv6Net::new(Ipv6Addr::new(0xfd, 0, 0, 0, 0, 0, 0, 1), 64).unwrap(),
267                    );
268                    ToSql::<$ty, Pg>::to_sql(&test_address, &mut bytes).unwrap();
269                }
270                assert_eq!(
271                    buffer,
272                    vec![
273                        PGSQL_AF_INET6,
274                        64,
275                        $net_type,
276                        16,
277                        0,
278                        0xfd,
279                        0,
280                        0,
281                        0,
282                        0,
283                        0,
284                        0,
285                        0,
286                        0,
287                        0,
288                        0,
289                        0,
290                        0,
291                        0,
292                        $last_byte,
293                    ]
294                );
295            };
296        }
297
298        test_to_sql!(Inet, 0, 1);
299        test_to_sql!(Cidr, 1, 0);
300    }
301
302    #[diesel_test_helper::test]
303    fn some_v6address_from_sql() {
304        macro_rules! test_some_address_from_sql {
305            ($ty:tt) => {
306                let input_address =
307                    IpNet::V6(Ipv6Net::new(Ipv6Addr::new(0xfd, 0, 0, 0, 0, 0, 0, 0), 64).unwrap());
308                let mut buffer = Vec::new();
309                {
310                    let mut bytes = Output::test(ByteWrapper(&mut buffer));
311                    ToSql::<$ty, Pg>::to_sql(&input_address, &mut bytes).unwrap();
312                }
313                let output_address =
314                    FromSql::<$ty, Pg>::from_sql(PgValue::for_test(&buffer)).unwrap();
315                assert_eq!(input_address, output_address);
316            };
317        }
318
319        test_some_address_from_sql!(Inet);
320        test_some_address_from_sql!(Cidr);
321    }
322
323    #[diesel_test_helper::test]
324    fn bad_address_from_sql() {
325        macro_rules! bad_address_from_sql {
326            ($ty:tt) => {
327                let address: Result<IpNet, _> =
328                    FromSql::<$ty, Pg>::from_sql(PgValue::for_test(&[7, PGSQL_AF_INET, 0]));
329                assert_eq!(
330                    address.unwrap_err().to_string(),
331                    "invalid network address format. input is too short."
332                );
333            };
334        }
335
336        bad_address_from_sql!(Inet);
337        bad_address_from_sql!(Cidr);
338    }
339
340    #[diesel_test_helper::test]
341    fn no_address_from_sql() {
342        macro_rules! test_no_address_from_sql {
343            ($ty:ty) => {
344                let address: Result<IpNet, _> = FromSql::<$ty, Pg>::from_nullable_sql(None);
345                assert_eq!(
346                    address.unwrap_err().to_string(),
347                    "Unexpected null for non-null column"
348                );
349            };
350        }
351
352        test_no_address_from_sql!(Inet);
353        test_no_address_from_sql!(Cidr);
354    }
355}