rsef_rs/
lib.rs

1#![deny(missing_docs)]
2
3//!
4//! The `rsef-rs` crate provides functionality to download and parse RSEF listings.
5//!
6
7use std::convert::TryFrom;
8use std::error::Error;
9use std::io::BufRead;
10use std::io::BufReader;
11use std::io::Read;
12
13#[cfg(feature = "download")]
14pub mod download;
15
16#[cfg(feature = "download")]
17pub use crate::download::*;
18
19/// Represents either a Version, Summary or Record line.
20#[derive(Debug, Clone)]
21pub enum Line {
22    /// Represents a version line in an RSEF listing.
23    Version(Version),
24
25    /// Represents a summary line in an RSEF listing. States statistics on an Internet Resource.
26    Summary(Summary),
27
28    /// Represents an individual record on a specific Internet Resource.
29    Record(Record),
30}
31
32/// Represents the different number of Internet resource types.
33#[derive(Debug, Clone)]
34pub enum Type {
35    /// Autonomous System Number
36    ASN,
37
38    /// Internet Protocol version 4
39    IPv4,
40
41    /// Internet Protocol version 6
42    IPv6,
43
44    /// Unknown Internet Resource
45    Unknown,
46}
47
48/// Converts a string to a Type.
49impl TryFrom<&str> for Type {
50    type Error = &'static str;
51
52    fn try_from(value: &str) -> Result<Self, Self::Error> {
53        let string = value.to_lowercase();
54
55        if string.eq("asn") {
56            Ok(Type::ASN)
57        } else if string.eq("ipv4") {
58            Ok(Type::IPv4)
59        } else if string.eq("ipv6") {
60            Ok(Type::IPv6)
61        } else {
62            Err("Unknown type not matching 'asn', 'ipv4' or 'ipv6' found while attempting to parse resource type.")
63        }
64    }
65}
66
67/// Represents an RSEF summary line.
68#[derive(Debug, Clone)]
69pub struct Summary {
70    /// The registry that this record belongs to.
71    pub registry: String,
72
73    /// Type of Internet number resource represented in this record.
74    pub res_type: Type,
75
76    /// Sum of the number of record lines of this type in the file.
77    pub count: u32,
78}
79
80/// Represents an RSEF version line.
81#[derive(Debug, Clone)]
82pub struct Version {
83    /// The version of the RIR Statistics Exchange Format.
84    pub version: f64,
85
86    ///  The registry that this record belongs to.
87    pub registry: String,
88
89    /// Serial number of this file (within the creating RIR series).
90    pub serial: String,
91
92    ///  Number of records in file, excluding blank lines, summary lines, the version line and comments.
93    pub records: u32,
94
95    ///  Start date of time period, in yyyymmdd format.
96    pub start_date: String,
97
98    /// End date of period, in yyyymmdd format.
99    pub end_date: String,
100
101    ///  Offset from UTC (+/- hours) of local RIR producing file.
102    pub utc_offset: String,
103}
104
105/// Represents an record about either an ASN, IPv4 prefix or IPv6 prefix.
106#[derive(Debug, Clone)]
107pub struct Record {
108    /// The registry that this record belongs to.
109    pub registry: String,
110
111    ///  ISO 3166 2-letter code of the organization to which the allocation or assignment was made.
112    pub organization: String,
113
114    ///  Type of Internet number resource represented in this record.
115    pub res_type: Type,
116
117    /// For IPv4 or IPv6, the base of the IP prefix. For asn the ASN number.
118    pub start: String,
119
120    /// For IPv4 the amount of hosts in this prefix. For ipv6 the CIDR prefix. For asn the amount of ASN numbers.
121    pub value: u32,
122
123    /// The date on which this allocation was made in YYYYMMDD format.
124    pub date: String,
125
126    /// Type of allocation from the set.
127    pub status: String,
128
129    /// The ID handle of this object. Often a reference to an organisation (which is also related to an AS)
130    pub id: String,
131}
132
133///
134/// Reads all the RSEF entries found in a stream and returns a Vec of RSEF entries.
135///
136pub fn read_all(read: impl Read) -> Result<impl Iterator<Item = Line>, Box<dyn Error>> {
137    let mut stream = BufReader::new(read);
138    let mut lines: Vec<Line> = Vec::new();
139
140    loop {
141        let mut line = String::new();
142        let len = stream.read_line(&mut line)?;
143
144        if len == 0 {
145            break;
146        }
147
148        // Remove the trailing whitespaces and newline characters
149        line.pop();
150
151        // Skip the comments.
152        if line.starts_with('#') {
153            continue;
154        }
155
156        // Divide the line into fields.
157        let fields = line.split('|').collect::<Vec<_>>();
158
159        // Check if line is a version.
160        if fields[0].chars().all(|x| x.is_digit(10) || x.eq(&'.')) {
161            lines.push(Line::Version(Version {
162                version: fields[0].parse::<f64>().unwrap(),
163                registry: fields[1].to_string(),
164                serial: fields[2].to_string(),
165                records: fields[3].parse::<u32>().unwrap(),
166                start_date: fields[4].to_string(),
167                end_date: fields[5].to_string(),
168                utc_offset: fields[6].to_string(),
169            }));
170            continue;
171        }
172
173        // Check if line is a summary.
174        if fields[5].to_string().eq("summary") {
175            lines.push(Line::Summary(Summary {
176                registry: fields[0].to_string(),
177                res_type: Type::try_from(fields[2])?,
178                count: fields[4].parse::<u32>().unwrap(),
179            }));
180            continue;
181        }
182
183        lines.push(Line::Record(Record {
184            registry: fields[0].to_string(),
185            organization: fields[1].to_string(),
186            res_type: Type::try_from(fields[2])?,
187            start: fields[3].to_string(),
188            value: fields[4].parse::<u32>().unwrap(),
189            date: fields[5].to_string(),
190            status: fields[6].to_string(),
191            id: if fields.len() > 7 {
192                fields[7].to_string()
193            } else {
194                "".to_string()
195            },
196        }));
197    }
198
199    Ok(lines.into_iter())
200}