From 3baadb2825f0937e8dd881be10199edc0ba9e399 Mon Sep 17 00:00:00 2001 From: Biandratti Date: Sun, 8 Sep 2024 17:30:00 +0200 Subject: [PATCH 1/2] process packages --- Cargo.lock | 242 ++++++++++++++++++++++++++++++++++++++++++++++++++++ Cargo.toml | 1 + README.md | 10 +++ src/main.rs | 79 ++++++++++++++++- 4 files changed, 331 insertions(+), 1 deletion(-) create mode 100644 README.md diff --git a/Cargo.lock b/Cargo.lock index c3280ea..ecc9a09 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,6 +2,248 @@ # It is not intended for manual editing. version = 3 +[[package]] +name = "aho-corasick" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" +dependencies = [ + "memchr", +] + +[[package]] +name = "glob" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" + +[[package]] +name = "ipnetwork" +version = "0.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf466541e9d546596ee94f9f69590f89473455f88372423e0008fc1a7daf100e" +dependencies = [ + "serde", +] + +[[package]] +name = "libc" +version = "0.2.158" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8adc4bb1803a324070e64a98ae98f38934d91957a99cfb3a43dcbc01bc56439" + +[[package]] +name = "memchr" +version = "2.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" + +[[package]] +name = "no-std-net" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43794a0ace135be66a25d3ae77d41b91615fb68ae937f904090203e81f755b65" + [[package]] name = "p0f" version = "0.1.0" +dependencies = [ + "pnet", +] + +[[package]] +name = "pnet" +version = "0.35.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "682396b533413cc2e009fbb48aadf93619a149d3e57defba19ff50ce0201bd0d" +dependencies = [ + "ipnetwork", + "pnet_base", + "pnet_datalink", + "pnet_packet", + "pnet_sys", + "pnet_transport", +] + +[[package]] +name = "pnet_base" +version = "0.35.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffc190d4067df16af3aba49b3b74c469e611cad6314676eaf1157f31aa0fb2f7" +dependencies = [ + "no-std-net", +] + +[[package]] +name = "pnet_datalink" +version = "0.35.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e79e70ec0be163102a332e1d2d5586d362ad76b01cec86f830241f2b6452a7b7" +dependencies = [ + "ipnetwork", + "libc", + "pnet_base", + "pnet_sys", + "winapi", +] + +[[package]] +name = "pnet_macros" +version = "0.35.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13325ac86ee1a80a480b0bc8e3d30c25d133616112bb16e86f712dcf8a71c863" +dependencies = [ + "proc-macro2", + "quote", + "regex", + "syn", +] + +[[package]] +name = "pnet_macros_support" +version = "0.35.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eed67a952585d509dd0003049b1fc56b982ac665c8299b124b90ea2bdb3134ab" +dependencies = [ + "pnet_base", +] + +[[package]] +name = "pnet_packet" +version = "0.35.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c96ebadfab635fcc23036ba30a7d33a80c39e8461b8bd7dc7bb186acb96560f" +dependencies = [ + "glob", + "pnet_base", + "pnet_macros", + "pnet_macros_support", +] + +[[package]] +name = "pnet_sys" +version = "0.35.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d4643d3d4db6b08741050c2f3afa9a892c4244c085a72fcda93c9c2c9a00f4b" +dependencies = [ + "libc", + "winapi", +] + +[[package]] +name = "pnet_transport" +version = "0.35.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f604d98bc2a6591cf719b58d3203fd882bdd6bf1db696c4ac97978e9f4776bf" +dependencies = [ + "libc", + "pnet_base", + "pnet_packet", + "pnet_sys", +] + +[[package]] +name = "proc-macro2" +version = "1.0.86" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "regex" +version = "1.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4219d74c6b67a3654a9fbebc4b419e22126d13d2f3c4a07ee0cb61ff79a79619" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38caf58cc5ef2fed281f89292ef23f6365465ed9a41b7a7754eb4e26496c92df" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a66a03ae7c801facd77a29370b4faec201768915ac14a721ba36f20bc9c209b" + +[[package]] +name = "serde" +version = "1.0.210" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8e3592472072e6e22e0a54d5904d9febf8508f65fb8552499a1abc7d1078c3a" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.210" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "243902eda00fad750862fc144cea25caca5e20d615af0a81bee94ca738f1df1f" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "syn" +version = "2.0.77" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f35bcdf61fd8e7be6caf75f429fdca8beb3ed76584befb503b1569faee373ed" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "unicode-ident" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" diff --git a/Cargo.toml b/Cargo.toml index ada6aee..60bb446 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,3 +4,4 @@ version = "0.1.0" edition = "2021" [dependencies] +pnet = "0.35.0" diff --git a/README.md b/README.md new file mode 100644 index 0000000..7ad587b --- /dev/null +++ b/README.md @@ -0,0 +1,10 @@ +### Get network +``` +ip link show +``` + +### Process packages +``` +cargo build --release +sudo RUST_BACKTRACE=1 ./target/release/p0f +``` \ No newline at end of file diff --git a/src/main.rs b/src/main.rs index e7a11a9..d496756 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,3 +1,80 @@ +extern crate pnet; + +use pnet::datalink::{self, Channel::Ethernet, Config, NetworkInterface}; +use pnet::packet::{ipv6::Ipv6Packet, tcp::TcpPacket, Packet}; +use std::net::Ipv6Addr; + fn main() { - println!("Hello, world!"); + println!("Program started"); + let interface_name = "wlp0s20f3"; // Your interface name here + let interfaces: Vec = datalink::interfaces(); + let interface: NetworkInterface = interfaces.into_iter() + .filter(|iface| iface.name == interface_name) + .next() + .expect("Could not find the interface"); + + let mut config = Config::default(); + config.promiscuous = true; // Enable promiscuous mode + + // Open the channel + let (mut _tx, mut rx) = match datalink::channel(&interface, config) { + Ok(Ethernet(tx, rx)) => (tx, rx), + Ok(_) => panic!("Unhandled channel type"), + Err(e) => panic!("Unable to create channel: {}", e), + }; + + // Loop to capture packets + loop { + match rx.next() { + Ok(packet) => { + process_packet(packet); + } + Err(_) => { + eprintln!("Failed to capture packet"); + } + } + } } + +fn process_packet(packet: &[u8]) { + if let Some(ipv6_packet) = Ipv6Packet::new(packet) { + let client_ip = ipv6_packet.get_source(); + let server_ip = ipv6_packet.get_destination(); + + // Extract TCP segment + if let Some(tcp_packet) = TcpPacket::new(ipv6_packet.payload()) { + let client_port = tcp_packet.get_source(); + let server_port = tcp_packet.get_destination(); + let payload = tcp_packet.payload(); + + process_http_payload(payload, client_ip, client_port, server_ip, server_port); + } + } +} + +// Function to process the HTTP payload and log relevant details +fn process_http_payload(payload: &[u8], client_ip: Ipv6Addr, client_port: u16, server_ip: Ipv6Addr, server_port: u16) { + let payload_str = match std::str::from_utf8(payload) { + Ok(v) => v, + Err(_) => return, // Not valid UTF-8, skip processing + }; + log_http_signature(client_ip, client_port, server_ip, server_port, payload_str); +} + +fn log_http_signature(client_ip: Ipv6Addr, client_port: u16, server_ip: Ipv6Addr, server_port: u16, headers: &str) { + println!( + ".-[ {}/{} -> {}/{} ]-", + client_ip, client_port, server_ip, server_port + ); + println!("|"); + println!("| client = {}/{}", client_ip, client_port); + println!("| headers = {}", headers); + println!("| raw_sig = {}", extract_raw_signature(headers)); + println!("|"); + println!("`----"); +} + +fn extract_raw_signature(headers: &str) -> String { + headers.to_string() +} + From 7e36a825de9c90cc5625956552339c22ee743680 Mon Sep 17 00:00:00 2001 From: Biandratti Date: Sun, 8 Sep 2024 19:12:09 +0200 Subject: [PATCH 2/2] getting network interface by parameter --- .gitignore | 1 + Cargo.lock | 193 ++++++++++++++++++++++++++++++++++++++++++++++++++++ Cargo.toml | 1 + README.md | 4 +- src/main.rs | 45 ++++++++---- 5 files changed, 229 insertions(+), 15 deletions(-) diff --git a/.gitignore b/.gitignore index ea8c4bf..c403c34 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ /target +.idea/ diff --git a/Cargo.lock b/Cargo.lock index ecc9a09..cd4c713 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -11,12 +11,113 @@ dependencies = [ "memchr", ] +[[package]] +name = "anstream" +version = "0.6.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64e15c1ab1f89faffbf04a634d5e1962e9074f2741eef6d97f3c4e322426d526" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "is_terminal_polyfill", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bec1de6f59aedf83baf9ff929c98f2ad654b97c9510f4e70cf6f661d49fd5b1" + +[[package]] +name = "anstyle-parse" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb47de1e80c2b463c735db5b217a0ddc39d612e7ac9e2e96a5aed1f57616c1cb" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d36fc52c7f6c869915e99412912f22093507da8d9e942ceaf66fe4b7c14422a" +dependencies = [ + "windows-sys", +] + +[[package]] +name = "anstyle-wincon" +version = "3.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5bf74e1b6e971609db8ca7a9ce79fd5768ab6ae46441c572e46cf596f59e57f8" +dependencies = [ + "anstyle", + "windows-sys", +] + +[[package]] +name = "clap" +version = "4.5.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e5a21b8495e732f1b3c364c9949b201ca7bae518c502c80256c96ad79eaf6ac" +dependencies = [ + "clap_builder", + "clap_derive", +] + +[[package]] +name = "clap_builder" +version = "4.5.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8cf2dd12af7a047ad9d6da2b6b249759a22a7abc0f474c1dae1777afa4b21a73" +dependencies = [ + "anstream", + "anstyle", + "clap_lex", + "strsim", +] + +[[package]] +name = "clap_derive" +version = "4.5.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "501d359d5f3dcaf6ecdeee48833ae73ec6e42723a1e52419c79abf9507eec0a0" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "clap_lex" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1462739cb27611015575c0c11df5df7601141071f07518d56fcc1be504cbec97" + +[[package]] +name = "colorchoice" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3fd119d74b830634cea2a0f58bbd0d54540518a14397557951e79340abc28c0" + [[package]] name = "glob" version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + [[package]] name = "ipnetwork" version = "0.20.0" @@ -26,6 +127,12 @@ dependencies = [ "serde", ] +[[package]] +name = "is_terminal_polyfill" +version = "1.70.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" + [[package]] name = "libc" version = "0.2.158" @@ -48,6 +155,7 @@ checksum = "43794a0ace135be66a25d3ae77d41b91615fb68ae937f904090203e81f755b65" name = "p0f" version = "0.1.0" dependencies = [ + "clap", "pnet", ] @@ -209,6 +317,12 @@ dependencies = [ "syn", ] +[[package]] +name = "strsim" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + [[package]] name = "syn" version = "2.0.77" @@ -226,6 +340,12 @@ version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" +[[package]] +name = "utf8parse" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" + [[package]] name = "winapi" version = "0.3.9" @@ -247,3 +367,76 @@ name = "winapi-x86_64-pc-windows-gnu" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_gnullvm", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" diff --git a/Cargo.toml b/Cargo.toml index 60bb446..054752b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,4 +4,5 @@ version = "0.1.0" edition = "2021" [dependencies] +clap = { version = "4.5.17", features = ["derive"] } pnet = "0.35.0" diff --git a/README.md b/README.md index 7ad587b..166d007 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -### Get network +### Get network Interface ``` ip link show ``` @@ -6,5 +6,5 @@ ip link show ### Process packages ``` cargo build --release -sudo RUST_BACKTRACE=1 ./target/release/p0f +sudo RUST_BACKTRACE=1 ./target/release/p0f --interface ``` \ No newline at end of file diff --git a/src/main.rs b/src/main.rs index d496756..a1c0f62 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,20 +1,29 @@ -extern crate pnet; - +use clap::Parser; use pnet::datalink::{self, Channel::Ethernet, Config, NetworkInterface}; use pnet::packet::{ipv6::Ipv6Packet, tcp::TcpPacket, Packet}; use std::net::Ipv6Addr; +#[derive(Parser, Debug)] +#[command(version, about, long_about = None)] +struct Args { + #[arg(short, long)] + interface: String, +} + fn main() { - println!("Program started"); - let interface_name = "wlp0s20f3"; // Your interface name here + let args = Args::parse(); + let interface_name = args.interface; let interfaces: Vec = datalink::interfaces(); - let interface: NetworkInterface = interfaces.into_iter() - .filter(|iface| iface.name == interface_name) - .next() + + let interface: NetworkInterface = interfaces + .into_iter() + .find(|iface| iface.name == interface_name) .expect("Could not find the interface"); - let mut config = Config::default(); - config.promiscuous = true; // Enable promiscuous mode + let config = Config { + promiscuous: true, + ..Config::default() + }; // Open the channel let (mut _tx, mut rx) = match datalink::channel(&interface, config) { @@ -23,7 +32,6 @@ fn main() { Err(e) => panic!("Unable to create channel: {}", e), }; - // Loop to capture packets loop { match rx.next() { Ok(packet) => { @@ -53,7 +61,13 @@ fn process_packet(packet: &[u8]) { } // Function to process the HTTP payload and log relevant details -fn process_http_payload(payload: &[u8], client_ip: Ipv6Addr, client_port: u16, server_ip: Ipv6Addr, server_port: u16) { +fn process_http_payload( + payload: &[u8], + client_ip: Ipv6Addr, + client_port: u16, + server_ip: Ipv6Addr, + server_port: u16, +) { let payload_str = match std::str::from_utf8(payload) { Ok(v) => v, Err(_) => return, // Not valid UTF-8, skip processing @@ -61,7 +75,13 @@ fn process_http_payload(payload: &[u8], client_ip: Ipv6Addr, client_port: u16, s log_http_signature(client_ip, client_port, server_ip, server_port, payload_str); } -fn log_http_signature(client_ip: Ipv6Addr, client_port: u16, server_ip: Ipv6Addr, server_port: u16, headers: &str) { +fn log_http_signature( + client_ip: Ipv6Addr, + client_port: u16, + server_ip: Ipv6Addr, + server_port: u16, + headers: &str, +) { println!( ".-[ {}/{} -> {}/{} ]-", client_ip, client_port, server_ip, server_port @@ -77,4 +97,3 @@ fn log_http_signature(client_ip: Ipv6Addr, client_port: u16, server_ip: Ipv6Addr fn extract_raw_signature(headers: &str) -> String { headers.to_string() } -