nasoone_lib/
lib.rs

1//! Nasoone-lib is a library for the NASOONE project.
2//!
3//! It provides an easy way for analyzing network traffic using pcap.
4//!
5//! The output is a CSV file with the following columns separated by a semicolon:
6//! - Source IP
7//! - Source port
8//! - Destination IP
9//! - Destination port
10//! - List of observed protocols
11//! - Timestamp of the first packet
12//! - Timestamp of the last packet
13//! - Number of bytes
14//! - Number of packets
15//!
16//! Example usage:
17//! ```
18//! use std::thread::sleep;
19//! use std::time::Duration;
20//! use nasoone_lib::Nasoone;
21//!
22//! let mut naso = Nasoone::new();
23//! // set the capture device from a physical interface
24//! naso.set_capture_device("en0").unwrap();
25//! naso.set_output("./report.csv").unwrap();
26//! // set the timeout between report updates (in seconds)
27//! naso.set_timeout(1).unwrap();
28//! // start the capture (non-blocking)
29//! naso.start().unwrap();
30//! sleep(Duration::from_secs(10));
31//! // pause the capture
32//! naso.pause().unwrap();
33//! sleep(Duration::from_secs(2));
34//! // resume the capture
35//! naso.resume().unwrap();
36//! sleep(Duration::from_secs(10));
37//! // stop the capture and get the stats
38//! let stats = naso.stop().unwrap();
39//! println!("{:?}", stats);
40//! ```
41
42mod filter;
43mod parser;
44mod producer;
45mod writer;
46
47pub use crate::filter::Filter;
48use crate::parser::parser_task;
49use crate::producer::producer_task;
50use crate::writer::writer_task;
51use crossbeam_channel::Sender;
52use pcap::{Active, Capture, Device, Inactive, Offline, Stat};
53use std::collections::HashSet;
54use std::error::Error;
55use std::fmt::{Debug, Display, Formatter};
56use std::fs::File;
57use std::net::IpAddr;
58use std::path::Path;
59use std::sync::{Arc, Mutex};
60use std::thread;
61
62enum Command {
63    Stop,
64    Pause,
65    Resume,
66}
67
68#[derive(Hash, Eq, PartialEq, Debug)]
69struct ReportKey {
70    source_ip: IpAddr,
71    source_port: u16,
72    destination_ip: IpAddr,
73    destination_port: u16,
74}
75
76impl Display for ReportKey {
77    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
78        let source_ip = self.source_ip;
79        let source_port = self.source_port.clone().to_string();
80        let dest_ip = self.destination_ip;
81        let dest_port = self.destination_port;
82        write!(
83            f,
84            "{}; {}; {}; {}",
85            source_ip, source_port, dest_ip, dest_port
86        )
87    }
88}
89
90#[derive(Debug)]
91struct ReportValue {
92    first_timestamp_ms: u64,
93    last_timestamp_ms: u64,
94    bytes: u64,
95    packets_count: usize,
96    protocols: HashSet<u8>,
97}
98
99impl Display for ReportValue {
100    fn fmt(&self, f: &mut Formatter) -> std::fmt::Result {
101        let protocols = self
102            .protocols
103            .iter()
104            .map(|p| match p {
105                6 => "TCP",
106                17 => "UDP",
107                _ => "",
108            })
109            .collect::<Vec<_>>()
110            .join(", ");
111        write!(
112            f,
113            "[{}]; {}; {}; {}; {}",
114            protocols,
115            self.first_timestamp_ms,
116            self.last_timestamp_ms,
117            self.bytes,
118            self.packets_count
119        )
120    }
121}
122
123#[derive(PartialEq, Eq, Debug, Clone)]
124/// Represents in which state the capture is.
125pub enum NasooneState {
126    /// The capture is not started and can be configured
127    Initial,
128    /// The capture is started and can be paused or stopped but not configured.
129    Running,
130    /// The capture is paused and can be resumed or stopped.
131    Paused,
132    /// The capture has finished, the user can retrieve stats calling stop.
133    Finished,
134    /// The capture is stopped and can only return to Initial.
135    Stopped,
136}
137
138#[derive(Debug)]
139/// Represents the pcap statistics about a capture (from <https://docs.rs/pcap/latest/pcap/index.html>.)
140pub struct NasooneStats {
141    /// Number of packets received
142    pub received: u32,
143    /// Number of packets dropped because there was no room in the operating system's buffer when
144    /// they arrived, because packets weren't being read fast enough
145    pub dropped: u32,
146    /// Number of packets dropped by the network interface or its driver
147    pub if_dropped: u32,
148}
149
150impl From<Stat> for NasooneStats {
151    fn from(stat: Stat) -> Self {
152        Self {
153            received: stat.received,
154            dropped: stat.dropped,
155            if_dropped: stat.if_dropped,
156        }
157    }
158}
159
160/// Abstraction of capture types before starting
161enum InactiveNasooneCapture {
162    /// The capture is performed on a pcap file.
163    FromFile(Capture<Offline>),
164    /// The capture is performed on a live network interface.
165    FromDevice(Capture<Inactive>),
166}
167
168/// Abstraction of the pcap capture.
169enum NasooneCapture {
170    /// The capture is performed on a pcap file.
171    FromFile(Capture<Offline>),
172    /// The capture is performed on a live network interface.
173    FromDevice(Capture<Active>),
174}
175
176impl NasooneCapture {
177    fn next(&mut self) -> Result<pcap::Packet, pcap::Error> {
178        match self {
179            NasooneCapture::FromFile(capture) => capture.next(),
180            NasooneCapture::FromDevice(capture) => capture.next(),
181        }
182    }
183}
184
185#[derive(Debug)]
186/// A network interface that can be used for capturing.
187pub struct NetworkInterface {
188    /// The name of the network interface.
189    name: String,
190    /// The optional friendly description of the network interface.
191    desc: Option<String>,
192}
193
194impl Display for NetworkInterface {
195    fn fmt(&self, f: &mut Formatter) -> std::fmt::Result {
196        let name = self.desc.clone().unwrap_or_else(|| self.name.clone());
197        write!(f, "{}", name)
198    }
199}
200
201impl NetworkInterface {
202    fn new(name: String, desc: Option<String>) -> NetworkInterface {
203        NetworkInterface { name, desc }
204    }
205    /// Returns the name of the network interface.
206    pub fn get_name(&self) -> &str {
207        &self.name
208    }
209    /// Returns the optional friendly description of the network interface.
210    pub fn get_desc(&self) -> Option<&str> {
211        self.desc.as_deref()
212    }
213}
214
215#[derive(Debug)]
216/// An error that can occur while using the library.
217pub enum NasooneError {
218    /// An error from the underlying pcap library.
219    PcapError(pcap::Error),
220    /// Invalid Nasoone state.
221    InvalidState(String),
222    /// The specified output path is not valid.
223    InvalidOutputPath(String),
224    /// The capture type is not set.
225    UnsetCapture,
226    /// The capture output file is not set.
227    UnsetOutput,
228    /// The timeout is not valid.
229    InvalidTimeout,
230    /// The capture has finished by itself.
231    CaptureOver,
232}
233
234impl Display for NasooneError {
235    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
236        match self {
237            NasooneError::PcapError(e) => write!(f, "Pcap error: {}", e),
238            NasooneError::InvalidState(s) => write!(f, "Invalid state: {}", s),
239            NasooneError::InvalidOutputPath(s) => write!(f, "Invalid output path: {}", s),
240            NasooneError::UnsetCapture => write!(f, "Capture is not set"),
241            NasooneError::UnsetOutput => write!(f, "Output is not set"),
242            NasooneError::InvalidTimeout => write!(f, "Invalid timeout"),
243            NasooneError::CaptureOver => write!(f, "Capture is over"),
244        }
245    }
246}
247
248impl Error for NasooneError {}
249
250#[derive(Clone, Eq, PartialEq)]
251struct PacketData {
252    timestamp_ms: u64,
253    data: Vec<u8>,
254    bytes: u32,
255}
256
257/// A struct for capturing network traffic.
258pub struct Nasoone {
259    /// The state of the capture.
260    state: NasooneState,
261    /// The channel sender for sending the state change to the producer thread.
262    tx_main_prod: Option<Sender<Command>>,
263    /// The periodical timeout after which the output file is updated.
264    timeout: u32,
265    /// The pcap capture.
266    capture: Option<InactiveNasooneCapture>,
267    /// The temprorary store for the BPF filter string
268    filter: String,
269    /// The path to the output file.
270    output: Option<File>,
271    /// Producer thread handle.
272    producer_handle: Option<thread::JoinHandle<Result<Stat, pcap::Error>>>,
273    /// Parser threads handles.
274    parser_handles: Vec<thread::JoinHandle<()>>,
275    /// Writer thread handle.
276    writer_handle: Option<thread::JoinHandle<()>>,
277    /// the amount of packets captured in the current session.
278    total_packets: Arc<Mutex<usize>>,
279}
280
281impl Nasoone {
282    pub fn new() -> Self {
283        Self {
284            state: NasooneState::Initial,
285            tx_main_prod: None,
286            timeout: 1,
287            capture: None,
288            output: None,
289            filter: String::new(),
290            producer_handle: None,
291            parser_handles: Vec::new(),
292            writer_handle: None,
293            total_packets: Arc::new(Mutex::new(0)),
294        }
295    }
296
297    /// Set the capture from a network interface.
298    /// It returns an error in the following cases:
299    /// - Nasoone is not in the Initial state
300    /// - the interface name is not valid
301    /// - the capture cannot be activated
302    ///
303    /// # Arguments
304    ///
305    /// * `device` - A string slice that holds the name of the interface to capture from.
306    ///
307    /// # Examples
308    ///
309    /// Create a nasoone instance and set the capture from the interface "en0"
310    /// ```
311    /// use nasoone_lib::Nasoone;
312    /// let mut nasoone = Nasoone::new();
313    /// let _ = nasoone.set_capture_device("en0");
314    /// ```
315    pub fn set_capture_device(&mut self, device: &str) -> Result<(), NasooneError> {
316        match self.state {
317            NasooneState::Initial => {
318                let capture = Capture::from_device(device).map_err(NasooneError::PcapError)?;
319                self.capture = Some(InactiveNasooneCapture::FromDevice(capture));
320                Ok(())
321            }
322            _ => Err(NasooneError::InvalidState(
323                "Nasoone is not in initial state".to_string(),
324            )),
325        }
326    }
327
328    /// Set the capture from a pcap file.
329    ///
330    /// It returns an error in the following cases:
331    /// - Nasoone is not in the Initial state
332    /// - the capture file is not valid
333    ///
334    /// # Arguments
335    ///
336    /// * `file` - A string slice with the file path.
337    ///
338    /// # Examples
339    ///
340    /// Create a nasoone instance and set the capture from the file "capture.pcap":
341    /// ```
342    /// use nasoone_lib::Nasoone;
343    /// let mut nasoone = Nasoone::new();
344    /// let  _ = nasoone.set_capture_file("./capture.pcap");
345    /// ```
346    pub fn set_capture_file(&mut self, file: &str) -> Result<(), NasooneError> {
347        match self.state {
348            NasooneState::Initial => {
349                let capture = Capture::from_file(file).map_err(NasooneError::PcapError)?;
350                self.capture = Some(InactiveNasooneCapture::FromFile(capture));
351                Ok(())
352            }
353            _ => Err(NasooneError::InvalidState(
354                "Nasoone is not in initial state".to_string(),
355            )),
356        }
357    }
358
359    /// Set the timeout in seconds after which the output file is updated.
360    ///
361    /// The timeout must be greater than 0, it specifies the periodical update of the output file.
362    /// So, if the timeout is 1, the output file is updated every second.
363    ///
364    /// It returns an error in the following cases:
365    /// - Nasoone is not in the Initial state
366    /// - the timeout is 0
367    ///
368    /// # Arguments
369    ///
370    /// * `timeout` - The timeout in seconds.
371    ///
372    /// # Examples
373    ///
374    /// Create a nasoone instance and set the timeout to 1 second:
375    /// ```
376    /// use nasoone_lib::Nasoone;
377    /// let mut nasoone = Nasoone::new();
378    /// nasoone.set_timeout(1).expect("");
379    /// ```
380    pub fn set_timeout(&mut self, timeout: u32) -> Result<(), NasooneError> {
381        match self.state {
382            NasooneState::Initial => {
383                if timeout == 0 {
384                    Err(NasooneError::InvalidTimeout)
385                } else {
386                    self.timeout = timeout;
387                    Ok(())
388                }
389            }
390            _ => Err(NasooneError::InvalidState(
391                "Nasoone is not in initial state".to_string(),
392            )),
393        }
394    }
395    /// Set the raw filter for the capture.
396    /// The raw filter is a [BPF](https://biot.com/capstats/bpf.html) string that is passed to pcap.
397    /// Multiple calls to this function or to set_filter will overwrite the previous filter.
398    ///
399    /// It returns an error in the following cases:
400    /// - Nasoone is not in the Initial state
401    /// - The capture is not set
402    /// - the filter is not valid
403    ///
404    /// # Arguments
405    ///
406    /// * `filter` - The filter string in BPF syntax.
407    ///
408    /// # Examples
409    ///
410    /// create a nasoone instance and set filter to accept only packets with source port 80 to 88:
411    /// ```
412    /// use nasoone_lib::Nasoone;
413    /// let mut nasoone = Nasoone::new();
414    /// nasoone.set_capture_device("en0").expect("");
415    /// nasoone.set_filter("src portrange 80-88").expect("");
416    /// ```
417    pub fn set_raw_filter(&mut self, filter: &str) -> Result<(), NasooneError> {
418        match self.state {
419            NasooneState::Initial => {
420                self.filter = filter.to_string();
421                Ok(())
422            }
423            _ => Err(NasooneError::InvalidState(
424                "Filters can be set only in initial state".to_string(),
425            )),
426        }
427    }
428    /// Set the filter for the capture.
429    /// The filter is a Filter struct that will be transformed in BPF and passed to pcap.
430    /// Multiple calls to this or set_raw_filter function will overwrite the previous filter.
431    ///
432    /// It returns an error in the following cases:
433    /// - Nasoone is not in the Initial state
434    /// - The capture is not set
435    ///
436    /// # Arguments
437    ///
438    /// * `filter` - The filter struct already setted up.
439    ///
440    /// # Examples
441    ///
442    /// create a nasoone instance and set filter to accept only tcp traffic
443    /// ```
444    /// use nasoone_lib::{Nasoone, Filter};
445    /// let filter = Filter::new().set_tcp_only();
446    /// let mut nasoone = Nasoone::new();
447    /// nasoone.set_capture_device("en0").unwrap();
448    /// nasoone.set_filter(&filter).unwrap();
449    /// ```
450    pub fn set_filter(&mut self, filter: &Filter) -> Result<(), NasooneError> {
451        match self.state {
452            NasooneState::Initial => {
453                self.filter = filter.to_string();
454                Ok(())
455            }
456            _ => Err(NasooneError::InvalidState(
457                "Filters can be set only in initial state".to_string(),
458            )),
459        }
460    }
461    /// Set the output file.
462    /// The output file is a CSV textual report of the capture.
463    ///
464    /// It returns an error in the following cases:
465    /// - Nasoone is not in the Initial state
466    /// - the file already exists
467    /// - the target directory does not exist
468    /// - the file cannot be created
469    ///
470    /// # Arguments
471    ///
472    /// * `output_file` - The path of the output file.
473    ///
474    /// # Examples
475    ///
476    /// create a nasoone instance and set the output file to "./output.csv":
477    /// ```
478    /// use nasoone_lib::Nasoone;
479    /// let mut nasoone = Nasoone::new();
480    /// nasoone.set_output("./output.csv").expect("");
481    /// ```
482    pub fn set_output(&mut self, output_file: &str) -> Result<(), NasooneError> {
483        match self.state {
484            NasooneState::Initial => {
485                let path = Path::new(output_file);
486                if path.exists() {
487                    return Err(NasooneError::InvalidOutputPath(
488                        "Output file already exists".to_string(),
489                    ));
490                }
491                if path.parent().is_some() && !path.parent().unwrap().exists() {
492                    return Err(NasooneError::InvalidOutputPath(
493                        "Target directory does not exist".to_string(),
494                    ));
495                }
496                match File::create(path) {
497                    Ok(f) => {
498                        self.output = Some(f);
499                        Ok(())
500                    }
501                    Err(_) => Err(NasooneError::InvalidOutputPath(
502                        "Could not create output file".to_string(),
503                    )),
504                }
505            }
506            _ => Err(NasooneError::InvalidState(
507                "Output can be set only in initial state".to_string(),
508            )),
509        }
510    }
511
512    /// Start analyzing the network traffic.
513    ///
514    /// It returns an error in the following cases:
515    /// - Nasoone is not in the Initial state
516    /// - the capture is not set
517    /// - the output is not set
518    ///
519    /// # Examples
520    ///
521    /// create a nasoone instance, set the capture and the output file and start the analysis:
522    /// ```
523    /// use nasoone_lib::Nasoone;
524    /// let mut nasoone = Nasoone::new();
525    /// nasoone.set_capture_device("en0").expect("");
526    /// nasoone.set_output("./output.csv").expect("");
527    /// nasoone.start().expect("");
528    /// ```
529    pub fn start(&mut self) -> Result<(), NasooneError> {
530        match self.state {
531            NasooneState::Initial => {
532                // Create all the threads and start the capture.
533
534                if self.capture.is_none() {
535                    return Err(NasooneError::UnsetCapture);
536                }
537                if self.output.is_none() {
538                    return Err(NasooneError::UnsetOutput);
539                }
540
541                let (tx_main_prod, rx_main_prod) = crossbeam_channel::unbounded();
542                let (tx_prod_parser, rx_prod_parser) = crossbeam_channel::unbounded();
543                let (tx_parser_writer, rx_parser_writer) = crossbeam_channel::unbounded();
544
545                let capture = match self.capture.take().unwrap() {
546                    InactiveNasooneCapture::FromFile(mut c) => {
547                        if !self.filter.is_empty() {
548                            c.filter(self.filter.as_str(), true)
549                                .map_err(NasooneError::PcapError)?;
550                        }
551                        NasooneCapture::FromFile(c)
552                    }
553                    InactiveNasooneCapture::FromDevice(c) => {
554                        let mut activated_c = c
555                            .promisc(true)
556                            .immediate_mode(true)
557                            .timeout(200)
558                            .open()
559                            .map_err(NasooneError::PcapError)?;
560                        if !self.filter.is_empty() {
561                            activated_c
562                                .filter(&self.filter, true)
563                                .map_err(NasooneError::PcapError)?;
564                        }
565                        if cfg!(target_os = "linux") {
566                            activated_c =
567                                activated_c.setnonblock().map_err(NasooneError::PcapError)?;
568                        }
569                        NasooneCapture::FromDevice(activated_c)
570                    }
571                };
572                self.tx_main_prod = Some(tx_main_prod);
573                self.producer_handle = Some(thread::spawn(move || {
574                    producer_task(capture, tx_prod_parser, rx_main_prod)
575                }));
576
577                let num_cpus = num_cpus::get();
578
579                for _ in 0..num_cpus {
580                    let rx_prod_parser = rx_prod_parser.clone();
581                    let tx_parser_writer = tx_parser_writer.clone();
582                    let timeout = self.timeout;
583                    let total_packets = self.total_packets.clone();
584                    self.parser_handles.push(thread::spawn(move || {
585                        parser_task(rx_prod_parser, tx_parser_writer, timeout, total_packets)
586                    }));
587                }
588
589                let mut output = self.output.take().unwrap();
590                let timeout = self.timeout;
591                self.writer_handle = Some(thread::spawn(move || {
592                    writer_task(rx_parser_writer, &mut output, timeout);
593                }));
594
595                self.state = NasooneState::Running;
596                Ok(())
597            }
598            _ => Err(NasooneError::InvalidState(
599                "Nasoone is not in initial state".to_string(),
600            )),
601        }
602    }
603
604    /// Pause the analysis of the network traffic.
605    ///
606    /// If the capture is set from a file, the analysis stop reading the file.
607    /// Otherwise, it continues to receive packets from the network interface but it does not analyze them.
608    ///
609    /// It returns an error in the following cases:
610    /// - Nasoone is not in the Running state
611    /// - the capture is from a file and the file is over (the analysis is already finished)
612    ///
613    /// # Examples
614    ///
615    /// create a nasoone instance, start the analysis and then pause it:
616    /// ```
617    /// use std::thread::sleep;
618    /// use nasoone_lib::Nasoone;
619    /// let mut nasoone = Nasoone::new();
620    /// nasoone.set_capture_device("en0").expect("");
621    /// nasoone.set_output("./output.csv").expect("");
622    /// nasoone.start().expect("");
623    /// sleep(std::time::Duration::from_secs(5));
624    /// nasoone.pause().expect("");
625    /// ```
626    pub fn pause(&mut self) -> Result<(), NasooneError> {
627        match self.state {
628            NasooneState::Running => {
629                self.state = NasooneState::Paused;
630                match self.tx_main_prod.as_ref().unwrap().send(Command::Pause) {
631                    Ok(_) => Ok(()),
632                    Err(_) => Err(NasooneError::CaptureOver),
633                }
634            }
635            _ => Err(NasooneError::InvalidState(
636                "Nasoone is not running".to_string(),
637            )),
638        }
639    }
640
641    /// Resume the analysis if it was paused.
642    ///
643    /// It returns an error in the following cases:
644    /// - Nasoone is not in the Paused state
645    /// - the capture is from a file and the file is over (the analysis is already finished)
646    ///
647    /// # Examples
648    ///
649    /// create a nasoone instance, start the analysis, pause it and then resume it:
650    /// ```
651    /// use nasoone_lib::Nasoone;
652    /// let mut nasoone = Nasoone::new();
653    /// nasoone.set_capture_device("en0").expect("");
654    /// nasoone.set_output("./output.csv").expect("");
655    /// nasoone.start().expect("");
656    /// nasoone.pause().expect("");
657    /// nasoone.resume().expect("");
658    /// ```
659    pub fn resume(&mut self) -> Result<(), NasooneError> {
660        match self.state {
661            NasooneState::Paused => {
662                self.state = NasooneState::Running;
663                match self.tx_main_prod.as_ref().unwrap().send(Command::Resume) {
664                    Ok(_) => Ok(()),
665                    Err(_) => Err(NasooneError::CaptureOver),
666                }
667            }
668            _ => Err(NasooneError::InvalidState(
669                "Nasoone is not paused".to_string(),
670            )),
671        }
672    }
673
674    /// Stop the capture if it is running or paused. It will wait for the threads to finish,
675    /// so it could take some time (up to 200-250ms).
676    ///
677    /// It returns the statistics of the capture only if the capture is from a network interface.
678    /// Otherwise, it will return None.
679    ///
680    /// It returns an error in the following cases:
681    /// - Nasoone is not in the Running, Finished or Paused state
682    ///
683    /// # Examples
684    ///
685    /// create a nasoone instance, start the analysis and then stop it:
686    /// ```
687    /// use nasoone_lib::Nasoone;
688    /// let mut nasoone = Nasoone::new();
689    /// nasoone.set_capture_device("en0").expect("");
690    /// nasoone.set_output("./output.csv").expect("");
691    /// nasoone.start().expect("");
692    /// let stats = nasoone.stop().expect("");
693    /// ```
694    pub fn stop(&mut self) -> Result<Option<NasooneStats>, NasooneError> {
695        match self.state {
696            NasooneState::Running | NasooneState::Paused | NasooneState::Finished => {
697                self.state = NasooneState::Stopped;
698                println!("sending stop command");
699                let _ = self.tx_main_prod.as_ref().unwrap().send(Command::Stop); // ignore a possible error that would mean that the producer thread is already stopped
700                println!("stop command sent");
701                let stat = self.producer_handle.take().unwrap().join().unwrap();
702                println!("producer stopped");
703                for handle in self.parser_handles.drain(..) {
704                    handle.join().unwrap();
705                }
706                self.writer_handle.take().unwrap().join().unwrap();
707                if stat.is_err() {
708                    return Ok(None);
709                }
710                Ok(Some(NasooneStats::from(stat.unwrap())))
711            }
712            _ => Err(NasooneError::InvalidState(
713                "Nasoone is not running".to_string(),
714            )),
715        }
716    }
717
718    /// Get the total amount of packet received by the capture.
719    ///
720    /// # Examples
721    ///
722    /// Create a nasoone instance, start the analysis and then get the total amount of packet received:
723    /// ```
724    /// use std::thread::sleep;
725    /// use nasoone_lib::{Nasoone, NasooneState};
726    /// let mut nasoone = Nasoone::new();
727    /// nasoone.set_capture_device("en0").expect("");
728    /// nasoone.set_output("./output.csv").expect("");
729    /// nasoone.start().expect("");
730    /// sleep(std::time::Duration::from_secs(5));
731    /// assert!(nasoone.get_total_packets() > 0);
732    /// ```
733    pub fn get_total_packets(&mut self) -> usize {
734        *self.total_packets.lock().unwrap()
735    }
736
737    /// Get the current state of the capture.
738    ///
739    /// # Examples
740    ///
741    /// Create a nasoone instance, start the analysis, pause it and ask for the state:
742    /// ```
743    /// use nasoone_lib::{Nasoone, NasooneState};
744    /// let mut nasoone = Nasoone::new();
745    /// nasoone.set_capture_device("en0").expect("");
746    /// nasoone.set_output("./output.csv").expect("");
747    /// nasoone.start().expect("");
748    /// nasoone.pause().expect("");
749    /// assert_eq!(nasoone.get_state(), NasooneState::Paused);
750    /// ```
751    pub fn get_state(&mut self) -> NasooneState {
752        // control if the capture has finished by itself
753        if self.producer_handle.as_ref().is_some()
754            && self.producer_handle.as_ref().unwrap().is_finished()
755        {
756            self.state = NasooneState::Finished;
757        }
758        self.state.clone()
759    }
760
761    /// Get the list of available network interfaces.
762    /// It only returns interfaces that have a network address,
763    /// since the others can't receive any network packet.
764    ///
765    /// It could return underlined errors from the pcap library.
766    ///
767    /// # Examples
768    /// ```
769    /// use nasoone_lib::Nasoone;
770    /// let interfaces = Nasoone::list_devices().expect("");
771    /// ```
772    pub fn list_devices() -> Result<Vec<NetworkInterface>, NasooneError> {
773        let devices = Device::list()
774            .map_err(NasooneError::PcapError)?
775            .into_iter()
776            .filter(|d| !d.addresses.is_empty())
777            .map(|d| NetworkInterface::new(d.name, d.desc))
778            .collect();
779        Ok(devices)
780    }
781
782    /// Get the name of the default network interface.
783    pub fn get_default_device_name() -> Result<String, NasooneError> {
784        let device = Device::lookup().map_err(NasooneError::PcapError)?;
785        Ok(device.name)
786    }
787}
788
789impl Drop for Nasoone {
790    fn drop(&mut self) {
791        if self.state != NasooneState::Stopped {
792            // try to stop the capture
793            let _ = self.stop();
794        }
795    }
796}
797
798impl Default for Nasoone {
799    fn default() -> Self {
800        Self::new()
801    }
802}