diesel/pg/types/
network_address.rs

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