66 Commits

Author SHA1 Message Date
idk
86ddae4fc7 Update sync.yaml
Some checks are pending
Sync Primary Repository to GitHub Mirror / sync (push) Waiting to run
2025-07-17 23:10:43 +00:00
eyedeekay
8f8948b89d Add github sync for i2p-rs
Some checks failed
Sync Primary Repository to GitHub Mirror / sync (push) Has been cancelled
2025-05-10 18:54:40 -04:00
idk
0e8d8a2959 Merge pull request #22 from snex/master 2024-04-03 11:32:12 -04:00
David Havlicek
87b140e3b4 remove erroneous linebreak in SAM client 2024-04-01 14:30:58 -07:00
idk
fbde12b629 Merge pull request #20 from a-0-dev/master
Allow passing SAM options on I2PListener
2023-11-23 09:08:45 -05:00
Philip (a-0)
286b06d2c4 Revert change: SAMOptions are passed by value again (as opposed to call by reference, introduced in last commit) 2023-11-21 16:01:13 +01:00
Philip (a-0)
1d4ce43a70 Improved creation of I2pListeners
- Added `I2pListenerBuilder`, which covers the functionality of `I2pListener::bind()`, `I2pListener::bind_with_session(...)`, `I2pListener::bind_via(...)` and `I2pListener::bind_addr(...)`.
- Added parameter `options: &SAMOptions` to functions creating/instanciating streams or listeners, except the "light versions" `I2pStream::connect()` and `I2pDatagramSocket::bind(addr)`
2023-11-21 15:44:04 +01:00
idk
771f7d0918 bump version 2022-05-20 15:28:15 -04:00
idk
c1bcad33b8 Merge pull request #14 from bonedaddy/serde
Derive Serde Serialize And Deserialize For SAM Options
2022-04-27 15:13:16 -04:00
idk
9218edde07 Merge pull request #16 from bonedaddy/session-watcher
Enable Handling SAM Session Errors
2022-04-27 15:12:48 -04:00
idk
594f82861f Merge pull request #13 from bonedaddy/timeouts
enable setting read/write timeouts
2022-04-27 15:10:02 -04:00
bonedaddy
21a627af73 add basic session watcher 2022-04-13 19:21:48 -07:00
bonedaddy
675cc7d9d4 fix double import 2022-04-11 00:21:22 -07:00
bonedaddy
f4eae49326 derive serde Serialize and Deserialize for sam options 2022-04-11 00:13:29 -07:00
idk
0f3229809c Merge pull request #10 from bonedaddy/feat/sam-options
I2CP Options And Signature Control
2022-04-08 14:28:37 +00:00
bonedaddy
4ed87d4364 enable setting read/write timeouts 2022-04-06 23:47:29 -07:00
bonedaddy
4c468456ef add some comments to the example 2022-04-06 22:14:38 -07:00
bonedaddy
9050880415 run rustfmt 2022-04-06 19:31:39 -07:00
bonedaddy
c88d64b542 call shutdown on connections 2022-04-06 19:30:59 -07:00
bonedaddy
fa25e92839 add basic echo server example with tokio, and crossbeam to...
showcase usage of popular rust concurrency and thread synchronization primitives with an i2p application
2022-04-06 19:26:07 -07:00
bonedaddy
9c1638ae58 fix options comments, and add debug/clone traits where applicable 2022-04-06 19:22:38 -07:00
bonedaddy
962437fde3 Merge branch 'feat/sam-options' into main-testing 2022-04-06 00:22:49 -07:00
bonedaddy
b7545178cc add sam options deduplication 2022-04-06 00:20:15 -07:00
bonedaddy
f237600542 add whitespace at the end of options string 2022-04-06 00:20:15 -07:00
bonedaddy
a58a6dc3e6 add sam options, and destination generation signature control 2022-04-06 00:20:15 -07:00
idk
6e89b7205e Merge pull request #9 from bonedaddy/feat/public-fields
Enable External Access To Underlying TCPStream(Tokio requirement)
2022-03-31 10:50:24 -04:00
idk
5f57d23847 Merge pull request #11 from bonedaddy/fix/b32
Add I2P Specific Base32 Encoding
2022-03-31 10:47:28 -04:00
bonedaddy
6b3c1e17d1 Merge branch 'fix/b32' into main-testing 2022-03-31 00:32:17 -07:00
bonedaddy
c367f94fd4 add i2p specific base32 encoding 2022-03-31 00:31:05 -07:00
bonedaddy
1d67e9898d fix optiosn deduping 2022-03-31 00:13:33 -07:00
bonedaddy
0f856e4693 deduplicate parameters when stringifying options 2022-03-31 00:11:14 -07:00
bonedaddy
86f9e39a99 Merge branch 'feat/sam-options' into main-testing 2022-03-31 00:06:03 -07:00
bonedaddy
b0580f4453 add whitespace at the end of options string 2022-03-31 00:05:48 -07:00
bonedaddy
3e088be97f Merge branch 'feat/public-fields' into main 2022-03-30 20:58:12 -07:00
bonedaddy
40d016469d Merge branch 'feat/sam-options' into main 2022-03-30 20:58:02 -07:00
bonedaddy
8fa702c5aa add sam options, and destination generation signature control 2022-03-30 02:01:21 -07:00
bonedaddy
ae6fdf68e3 sam: enable external access to the underlying tcp stream 2022-03-29 23:16:15 -07:00
idk
f0dd4e5b92 Merge pull request #6 from bonedaddy/master
Combined #1 And #4 PRs
2022-03-24 16:15:10 -04:00
bonedaddy
526c3882ae format code 2022-03-19 23:23:19 -07:00
bonedaddy
8dccbe45c1 remove use of deprecated nom functions 2022-03-19 23:21:21 -07:00
bonedaddy
0a885d753e set default leaseSetEncType to 4,0 2022-03-19 22:25:16 -07:00
bonedaddy
85ab04992a sest default signature type 2022-03-19 22:15:56 -07:00
bonedaddy
0b17a147b6 fix examples 2022-03-19 21:36:22 -07:00
bonedaddy
1dd099d5cb restructure examples folder 2022-03-19 21:21:32 -07:00
bonedaddy
bd86da9973 make modules public 2022-03-19 20:23:19 -07:00
Ignotus Peverell
e952420fd1 Convenience SAM types export 2022-03-19 20:20:57 -07:00
Ignotus Peverell
aa6122564f Enable nonblockcing streams 2022-03-19 20:20:57 -07:00
Ignotus Peverell
223a1675bd Bump version to reflect new functionalities 2022-03-19 20:20:57 -07:00
Ignotus Peverell
8c71438340 Auto-derive common traits and serde support
Removes some boilerplate that auto-derivation provides and add
serde on address structures for convenience.
2022-03-19 20:20:57 -07:00
Ignotus Peverell
e92f36e54d Add SAM facility to get a new destination 2022-03-19 20:20:57 -07:00
Ignotus Peverell
1605fa03e1 Fix API docs 2022-03-19 20:20:57 -07:00
Ignotus Peverell
65c4729f8d Allow session reuse
Allow sessions to be provided to accept and connect methods to
avoid recreating one for every connection. Also allows destination
reuse by initializing the session with known keys.
2022-03-19 20:20:57 -07:00
Ignotus Peverell
14d587d71d Various listener fixes, full ping-pong example 2022-03-19 20:20:57 -07:00
Ignotus Peverell
606539db64 Support for i2p b32 address format
Accept a base64 destination and convert it to a base32 i2p
address with the b32.i2p extension. Uses the unusual i2p base64
encoding.
2022-03-19 20:20:57 -07:00
Ignotus Peverell
5715a66e77 Use SAM ACCEPT instead of LISTEN, first example
Generally ACCEPT seems better supported than LISTEN and also fits
the bill. Has the advantage of not requiring another TCP server.

First example with server connections, not working yet (need to
implement b32 address support).
2022-03-19 20:20:57 -07:00
Ignotus Peverell
4bfa880554 First implementation of a listener and accept 2022-03-19 20:20:57 -07:00
Ignotus Peverell
4f8a6abc10 New nom test compilation fix 2022-03-19 20:20:57 -07:00
Ignotus Peverell
a18dadda5e Update nom crate 2022-03-19 20:20:57 -07:00
Ignotus Peverell
5bc995a072 Proper error handling using failure 2022-03-19 20:20:57 -07:00
Ignotus Peverell
1f5fa1793a Test imports fixes 2022-03-19 20:20:57 -07:00
Ignotus Peverell
9f95a08e5f Latest formatting from cargo fmt 2022-03-19 20:20:57 -07:00
Ignotus Peverell
c23a90ca1c More Rust 2018-ification 2022-03-19 20:20:13 -07:00
Ignotus Peverell
70fd0f7758 Minor cleanup and upgrades
* Rust 2018 edition
* Upgrade rand an log crate dependencies
* Cleanup unusued variables warnings
2022-03-19 20:20:00 -07:00
Ignotus Peverell
3174fd9a6f Fix connect destination
SAMv3 spec says the destination should be the full b64, so a name
lookup is required on the i2p address before initiating the
connection. Perhaps Java i2p doesn't require it, but i2pd sure
does.
2022-03-19 20:19:37 -07:00
Ignotus Peverell
0c26d824ed Ignore vim swap files 2022-03-19 20:19:23 -07:00
str4d
6bdfca69ef Merge pull request #2 from i2p/2018ification
2018ification
2019-04-21 08:17:16 +01:00
31 changed files with 2904 additions and 1174 deletions

66
.github/workflows/sync.yaml vendored Normal file
View File

@@ -0,0 +1,66 @@
# GitHub Actions workflow file to sync an external repository to this GitHub mirror.
# This file was automatically generated by go-github-sync.
#
# The workflow does the following:
# - Runs on a scheduled basis (and can also be triggered manually)
# - Clones the GitHub mirror repository
# - Fetches changes from the primary external repository
# - Applies those changes to the mirror repository
# - Pushes the updated content back to the GitHub mirror
#
# Authentication is handled by the GITHUB_TOKEN secret provided by GitHub Actions.
jobs:
sync:
runs-on: ubuntu-latest
steps:
- name: Validate Github Actions Environment
run: if [ "$GITHUB_ACTIONS" != "true" ]; then echo 'This script must be run in a GitHub Actions environment.'; exit 1; fi
- name: Checkout GitHub Mirror
uses: actions/checkout@v3
with:
fetch-depth: 0
- name: Configure Git
run: |-
git config user.name 'GitHub Actions'
git config user.email 'actions@github.com'
- env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
name: Sync Primary Repository
run: |-
# Add the primary repository as a remote
git remote add primary https://i2pgit.org/I2P_Developers/i2p-rs.git
# Fetch the latest changes from the primary repository
git fetch primary
# Check if the primary branch exists in the primary repository
if git ls-remote --heads primary master | grep -q master; then
echo "Primary branch master found in primary repository"
else
echo "Error: Primary branch master not found in primary repository"
exit 1
fi
# Check if we're already on the mirror branch
if git rev-parse --verify --quiet master; then
git checkout master
else
# Create the mirror branch if it doesn't exist
git checkout -b master
fi
# Force-apply all changes from primary, overriding any conflicts
echo "Performing force sync from primary/master to master"
git reset --hard primary/master
# Push changes back to the mirror repository
git push origin master
name: Sync Primary Repository to GitHub Mirror
"on":
push: {}
schedule:
- cron: 0 0 * * *
workflow_dispatch: {}

1
.gitignore vendored
View File

@@ -10,3 +10,4 @@ Cargo.lock
auto-save-list
tramp
.\#*
*.swp

View File

@@ -1,6 +1,6 @@
[package]
name = "i2p"
version = "0.0.1"
version = "0.1.1"
authors = ["Jack Grigg <str4d@i2pmail.org>"]
description = "I2P client library with a std::net-like API"
homepage = "https://github.com/i2p/i2p-rs"
@@ -11,10 +11,23 @@ keywords = ["i2p", "net", "network", "sam"]
license = "MIT"
edition = "2018"
[features]
default = ["public-conn"]
public-conn = []
[badges]
travis-ci = { repository = "i2p/i2p-rs" }
[dependencies]
log = "0.3.7"
nom = "^2.2"
rand = "0.3.15"
data-encoding = "2.1.2"
failure = "0.1"
failure_derive = "0.1"
lazy_static = "1.3.0"
log = "0.4.6"
nom = "^4.2"
rand = "0.5"
serde = "1"
serde_derive = "1"
sha2 = "0.8.0"
[dev-dependencies]
env_logger = "0.5"

3
Makefile Normal file
View File

@@ -0,0 +1,3 @@
.PHONY: fmt
fmt:
find -type f -name "*.rs" -not -path "*target*" -exec rustfmt --edition 2018 {} \;

View File

@@ -1,40 +0,0 @@
use i2p::net::I2pStream;
use std::env;
use std::io::{BufReader, Read, Write};
fn help() {
println!("Usage: eepget <host.i2p> [port]")
}
fn print_homepage(host: &str, port: u16) {
let mut stream = I2pStream::connect(format!("{}:{}", host, port)).unwrap();
let msg = "GET / HTTP/1.1\r\n\r\n";
let _ = stream.write(msg.as_bytes());
let mut reader = BufReader::new(stream);
let mut buffer = String::new();
let _ = reader.read_to_string(&mut buffer);
println!("{}", buffer);
}
fn main() {
let args: Vec<String> = env::args().collect();
match args.len() {
2 => print_homepage(&args[1], 80),
3 => {
let host = &args[1];
let port = &args[2];
let port_num: u16 = match port.parse() {
Ok(n) => n,
Err(_) => {
println!("Port must be an integer");
help();
return;
}
};
print_homepage(host, port_num)
}
_ => help(),
}
}

View File

@@ -0,0 +1,14 @@
[package]
name = "eepget"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
i2p = {path = "../../", version = "0.1.0"}
env_logger = "0.5"
[[bin]]
name = "eepget"
path = "src/main.rs"

View File

@@ -0,0 +1,42 @@
use env_logger;
use i2p::net::I2pStream;
use std::env;
use std::io::{BufReader, Read, Write};
fn help() {
println!("Usage: eepget <host.i2p> [port]")
}
fn print_homepage(host: &str, port: u16) {
let mut stream = I2pStream::connect(format!("{}:{}", host, port)).unwrap();
let msg = "GET / HTTP/1.1\r\n\r\n";
let _ = stream.write(msg.as_bytes());
let mut reader = BufReader::new(stream);
let mut buffer = String::new();
let _ = reader.read_to_string(&mut buffer);
println!("{}", buffer);
}
fn main() {
env_logger::init();
let args: Vec<String> = env::args().collect();
match args.len() {
2 => print_homepage(&args[1], 80),
3 => {
let host = &args[1];
let port = &args[2];
let port_num: u16 = match port.parse() {
Ok(n) => n,
Err(_) => {
println!("Port must be an integer");
help();
return;
}
};
print_homepage(host, port_num)
}
_ => help(),
}
}

View File

@@ -0,0 +1,14 @@
[package]
name = "gen_gest"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
i2p = {path = "../../", version = "0.1.0"}
env_logger = "0.5"
[[bin]]
name = "gen_dest"
path = "src/main.rs"

View File

@@ -0,0 +1,20 @@
extern crate env_logger;
extern crate i2p;
extern crate log;
use log::*;
use std::io::{Read, Write};
use std::str::from_utf8;
use std::{thread, time};
use i2p::sam::{SamConnection, DEFAULT_API};
// Run with RUST_LOG=debug to see the action
fn main() {
env_logger::init();
let mut sam_conn = SamConnection::connect(DEFAULT_API).unwrap();
let (pubkey, seckey) = sam_conn.generate_destination().unwrap();
println!("New public key: {}", pubkey);
println!("New secret key: {}", seckey);
}

View File

@@ -0,0 +1,18 @@
[package]
name = "hello_world"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
i2p = {path = "../../", version = "0.1.0"}
env_logger = "0.5"
log = "0.4"
crossbeam = "0.8"
crossbeam-utils = "0.8"
crossbeam-channel = "0.5"
tokio = {version = "1.17.0", features = ["full"]}
[[bin]]
name = "hello_world"
path = "src/main.rs"

View File

@@ -0,0 +1,5 @@
# Usage
```shell
$> RUST_LOG=debug cargo run
```

View File

@@ -0,0 +1,191 @@
use crossbeam::sync::WaitGroup;
use env_logger;
use i2p;
use crossbeam_channel::select;
use i2p::net::{I2pListener, I2pStream};
use i2p::sam_options::{
I2CPClientOptions, I2CPOptions, I2CPRouterOptions, I2CPTunnelInboundOptions,
I2CPTunnelOutboundOptions, SAMOptions, SignatureType,
};
use log::*;
use std::io::{Read, Write};
use std::net::Shutdown;
use std::str::from_utf8;
use std::{thread, time};
use i2p::sam::{SamConnection, SessionStyle, DEFAULT_API};
// Run with RUST_LOG=debug to see the action
#[tokio::main]
async fn main() {
env_logger::init();
let (pubkey, seckey) = {
let mut sam_conn = SamConnection::connect(DEFAULT_API).unwrap();
sam_conn
.generate_destination(SignatureType::EdDsaSha512Ed25519)
.unwrap()
};
info!("New public key: {}", pubkey);
info!("New secret key: {}", seckey);
// thread synchronization primitive
let wg = WaitGroup::new();
// message channel primitive
let (tx, rx) = crossbeam_channel::bounded::<bool>(1);
{
let sam_session = i2p::sam::Session::create(
DEFAULT_API,
seckey.as_str(),
"hello_world",
SessionStyle::Stream,
SAMOptions {
i2cp_options: Some(I2CPOptions {
router_options: Some(I2CPRouterOptions {
inbound: Some(I2CPTunnelInboundOptions {
length: Some(1),
quantity: Some(2),
backup_quantity: Some(2),
..Default::default()
}),
outbound: Some(I2CPTunnelOutboundOptions {
length: Some(1),
quantity: Some(2),
backup_quantity: Some(2),
..Default::default()
}),
..Default::default()
}),
..Default::default()
}),
signature_type: SignatureType::EdDsaSha512Ed25519,
..Default::default()
},
)
.unwrap();
let local_dest = i2p::net::I2pAddr::from_b64(&sam_session.local_dest).unwrap();
info!("local_dest {}", local_dest);
let rx = rx;
let listener = match I2pListener::bind_with_session(&sam_session) {
Ok(listener) => listener,
Err(err) => panic!("failed to establish listener with session {:#?}", err),
};
// force the connection into non-blocking mode
sam_session.sam.set_nonblocking(true).unwrap();
// akin to Golang's sync.WaitGroup, and allows synchronizing execution across different threads
// particularly useful for enabling graceful shutdowns within server applications
let wg = wg.clone();
// spawns a background task, somewhat similar to Golang's goroutines
tokio::task::spawn_blocking(move || {
loop {
// check to see if we received a message through the crossbeam channel
// if not, immediately activate default case, which will continue onto
// the following listener.accept() statement
select! {
recv(rx) -> _msg => {
warn!("server received exit signal, goodbye...");
match sam_session.sam.conn.shutdown(Shutdown::Both) {
Ok(_) => info!("server shutdown ok"),
Err(err) => error!("server failed to properly shutdown {:#?}", err)
}
drop(wg);
return;
},
default() => {}
}
match listener.accept() {
Ok((mut incoming_conn, conn_addr)) => {
info!("server accepted connection from {}", conn_addr);
let mut buf = [0_u8; 512];
match incoming_conn.read(&mut buf) {
Ok(n) => {
// dont do this outside of an example
unsafe {
info!(
"server read {} bytes. msg {}",
n,
String::from_utf8_unchecked(buf[0..n].to_vec())
.replace("\n", "")
);
}
match incoming_conn.write(&buf[0..n]) {
Ok(n) => {
info!("server wrote {} bytes", n)
}
Err(err) => {
error!(
"server failed to write response for {}: {:#?}",
conn_addr, err
);
}
}
}
Err(err) => {
error!("server failed to read data from {}: {:#?}", conn_addr, err);
}
}
}
Err(err) => {
error!("server failed to accept connection {:#?}", err);
}
}
// because we called spawn_blocking this wont block main thread
std::thread::sleep(std::time::Duration::from_millis(125));
}
});
}
info!("waiting 10 seconds for tunnel things to happen");
// because we used tokio::task::spawn_blocking, we can sleep here
// or in the spawned task without either sleeping blocking a thread
// and pausing execution.
std::thread::sleep(std::time::Duration::from_secs(10));
let mut client_conn = match I2pStream::connect(&format!("{}:0", pubkey)) {
Ok(client_conn) => client_conn,
Err(err) => {
if let Err(err) = tx.send(true) {
error!("client failed to signal server task to exit {:#?}", err);
}
panic!(
"client failed to connect to destination {}, {:#?}",
i2p::net::I2pAddr::from_b64(&pubkey).unwrap(),
err
);
}
};
match client_conn.write(b"hello_world") {
Ok(n) => info!("client wrote {} bytes", n),
Err(err) => {
if let Err(err) = tx.send(true) {
error!("client failed to signal server task to exit {:#?}", err);
}
panic!("client failed to write into stream {:#?}", err);
}
}
let mut buf = [0_u8; 512];
match client_conn.read(&mut buf) {
Ok(n) => unsafe {
info!(
"client read {} bytes. msg {}",
n,
String::from_utf8_unchecked(buf[0..n].to_vec()).replace("\n", "")
);
},
Err(err) => {
if let Err(err) = tx.send(true) {
error!("client failed to signal server task to exit {:#?}", err);
}
panic!("client failed to read from stream {:#?}", err);
}
}
// send a message through the channel to notify the tokio task
// to exit
if let Err(err) = tx.send(true) {
info!("client failed to signal server task to exit {:#?}", err);
}
// wait for all references to the wait group to be dropped
wg.wait();
match client_conn.shutdown(Shutdown::Both) {
Ok(_) => info!("client shutdown ok"),
Err(err) => error!("client failed to properly shutdown {:#?}", err),
}
info!("all background processes exited, goodbye...");
}

View File

@@ -0,0 +1,15 @@
[package]
name = "selfserve"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
i2p = {path = "../../", version = "0.1.0"}
env_logger = "0.5"
log = "0.4.6"
[[bin]]
name = "selfserve"
path = "src/main.rs"

View File

@@ -0,0 +1,42 @@
use env_logger;
use log::*;
use std::io::{Read, Write};
use std::str::from_utf8;
use std::{thread, time};
use i2p::net::{I2pListener, I2pStream};
fn main() {
env_logger::init();
// start a TCP server that will get forwards from i2p
let server = I2pListener::bind().unwrap();
let our_dest = server.local_addr().unwrap();
thread::spawn(move || {
for stream in server.incoming() {
match stream {
Ok(mut stream) => {
thread::spawn(move || {
let mut buffer = [0; 100];
loop {
let n = stream.read(&mut buffer).unwrap();
info!("< {:?}", from_utf8(&buffer[0..n]).unwrap());
stream.write("pong".as_bytes()).unwrap();
}
});
}
Err(e) => error!("Error on incoming connection: {:?}", e),
}
}
});
thread::sleep(time::Duration::from_millis(1000));
// connect through i2p to our local destination
let mut client = I2pStream::connect(our_dest).unwrap();
let msg = "ping";
client.write(msg.as_bytes()).unwrap();
let mut buffer = [0; 100];
let n = client.read(&mut buffer).unwrap();
info!("> {:?}", from_utf8(&buffer[0..n]).unwrap());
}

View File

@@ -0,0 +1,18 @@
[package]
name = "session_watcher"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
i2p = {path = "../../", version = "0.1.0"}
env_logger = "0.5"
log = "0.4"
crossbeam = "0.8"
crossbeam-utils = "0.8"
crossbeam-channel = "0.5"
tokio = {version = "1.17.0", features = ["full"]}
[[bin]]
name = "session_watcher"
path = "src/main.rs"

View File

@@ -0,0 +1,5 @@
# Usage
```shell
$> RUST_LOG=debug cargo run
```

View File

@@ -0,0 +1,49 @@
use crossbeam::sync::WaitGroup;
use env_logger;
use i2p;
use crossbeam_channel::select;
use i2p::net::{I2pListener, I2pStream};
use i2p::sam_options::{
I2CPClientOptions, I2CPOptions, I2CPRouterOptions, I2CPTunnelInboundOptions,
I2CPTunnelOutboundOptions, SAMOptions, SignatureType,
};
use log::*;
use std::io::{Read, Write};
use std::net::Shutdown;
use std::str::from_utf8;
use std::{thread, time};
use i2p::sam::{SamConnection, SessionStyle, DEFAULT_API};
// Run with RUST_LOG=debug to see the action
#[tokio::main]
async fn main() {
env_logger::init();
let (pubkey, seckey) = {
let mut sam_conn = SamConnection::connect(DEFAULT_API).unwrap();
sam_conn
.generate_destination(SignatureType::EdDsaSha512Ed25519)
.unwrap()
};
info!("New public key: {}", pubkey);
info!("New secret key: {}", seckey);
let mut watcher = i2p::session_watcher::SamSessionWatcher::new(
DEFAULT_API,
&seckey,
SessionStyle::Stream,
Default::default()
).unwrap();
loop {
match watcher.accept() {
Ok((conn, addr)) => {
info!("receiving incoming connection {}", addr);
let _ = conn.shutdown(Shutdown::Both).unwrap();
}
Err(err) => {
error!("failed to accept connection {:#?}", err);
}
}
}
}

1
rustfmt.toml Normal file
View File

@@ -0,0 +1 @@
hard_tabs = true

117
src/error.rs Normal file
View File

@@ -0,0 +1,117 @@
use std::fmt::{self, Display};
use std::io;
use failure::{Backtrace, Context, Fail};
use nom;
/// I2P/SAM error definition
#[derive(Debug, Fail)]
pub struct Error {
inner: Context<ErrorKind>,
}
/// Kinds of I2P/SAM errors
#[derive(Clone, Eq, PartialEq, Debug, Fail)]
pub enum ErrorKind {
/// Wraps io errors
#[fail(display = "IO error occurred (is i2p running?): {}", _0)]
Io(String),
/// Wraps nom parser errors
#[fail(display = "Failed to parse an I2P/SAM message")]
MessageParsing,
#[fail(display = "Failed to parse an I2P/SAM message")]
UnresolvableAddress,
#[fail(display = "Invalid or unrecognized I2P/SAM message: {}", _0)]
SAMInvalidMessage(String),
#[fail(display = "Can't reach peer: {}", _0)]
SAMCantReachPeer(String),
#[fail(display = "Destination key not found: {}", _0)]
SAMKeyNotFound(String),
#[fail(display = "Peer not found: {}", _0)]
SAMPeerNotFound(String),
#[fail(display = "Duplicate peer destination: {}", _0)]
SAMDuplicatedDest(String),
#[fail(display = "Invalid destination key: {}", _0)]
SAMInvalidKey(String),
#[fail(display = "Invalid stream id: {}", _0)]
SAMInvalidId(String),
#[fail(display = "I2P/SAM Timeout: {}", _0)]
SAMTimeout(String),
#[fail(display = "Unknown I2P/SAM error: {}", _0)]
SAMI2PError(String),
#[fail(display = "I2P address isn't a valid b32 or b64 encoding: {}", _0)]
BadAddressEncoding(String),
#[fail(display = "Accept encountered error, and session was recreated. try operation again")]
SessionRecreated,
}
impl ErrorKind {
pub fn to_err(self) -> Error {
Error {
inner: Context::new(self),
}
}
}
impl Display for Error {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let cause = match self.cause() {
Some(c) => format!("{}", c),
None => String::from("Unknown"),
};
let backtrace = match self.backtrace() {
Some(b) => format!("{}", b),
None => String::from("Unknown"),
};
let output = format!(
"{} \n Cause: {} \n Backtrace: {}",
self.inner, cause, backtrace
);
Display::fmt(&output, f)
}
}
impl Error {
/// get kind
pub fn kind(&self) -> ErrorKind {
self.inner.get_context().clone()
}
/// get cause
pub fn cause(&self) -> Option<&dyn Fail> {
self.inner.cause()
}
/// get backtrace
pub fn backtrace(&self) -> Option<&Backtrace> {
self.inner.backtrace()
}
}
impl From<ErrorKind> for Error {
fn from(kind: ErrorKind) -> Error {
Error {
inner: Context::new(kind),
}
}
}
impl From<Context<ErrorKind>> for Error {
fn from(inner: Context<ErrorKind>) -> Error {
Error { inner: inner }
}
}
impl From<io::Error> for Error {
fn from(err: io::Error) -> Error {
Error {
inner: Context::new(ErrorKind::Io(err.to_string())),
}
}
}
impl<I, E> From<nom::Err<I, E>> for Error {
fn from(_err: nom::Err<I, E>) -> Error {
Error {
inner: Context::new(ErrorKind::MessageParsing),
}
}
}

View File

@@ -1,9 +1,10 @@
#[macro_use]
extern crate nom;
#[macro_use]
extern crate log;
pub mod error;
pub mod net;
pub mod sam;
pub mod sam_options;
pub mod session_watcher;
mod parsers;
pub use crate::error::{Error, ErrorKind};
pub use crate::sam::{SamConnection, Session};

View File

@@ -1,125 +1,102 @@
use std::fmt;
use std::hash;
use std::io;
use std::iter;
use std::option;
use std::slice;
use std::vec;
use serde_derive::{Deserialize, Serialize};
use crate::net::i2p::I2pAddr;
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize, Hash)]
pub struct I2pSocketAddr {
port: u16,
dest: I2pAddr,
port: u16,
dest: I2pAddr,
}
impl I2pSocketAddr {
/// Creates a new socket address from the (dest, port) pair.
///
/// # Examples
///
/// ```
/// use i2p::net::{I2pAddr, I2pSocketAddr};
///
/// let socket = I2pSocketAddr::new(I2pAddr::new("example.i2p"), 8080);
/// assert_eq!(socket.dest(), I2pAddr::new("example.i2p"));
/// assert_eq!(socket.port(), 8080);
/// ```
pub fn new(dest: I2pAddr, port: u16) -> I2pSocketAddr {
I2pSocketAddr {
port: port,
dest: dest,
}
}
/// Creates a new socket address from the (dest, port) pair.
///
/// # Examples
///
/// ```
/// use i2p::net::{I2pAddr, I2pSocketAddr};
///
/// let socket = I2pSocketAddr::new(I2pAddr::new("example.i2p"), 8080);
/// assert_eq!(socket.dest(), I2pAddr::new("example.i2p"));
/// assert_eq!(socket.port(), 8080);
/// ```
pub fn new(dest: I2pAddr, port: u16) -> I2pSocketAddr {
I2pSocketAddr {
port: port,
dest: dest,
}
}
/// Returns the I2P address associated with this socket address.
///
/// # Examples
///
/// ```
/// use i2p::net::{I2pAddr, I2pSocketAddr};
///
/// let socket = I2pSocketAddr::new(I2pAddr::new("example.i2p"), 8080);
/// assert_eq!(socket.dest(), I2pAddr::new("example.i2p"));
/// ```
pub fn dest(&self) -> I2pAddr {
self.dest.clone()
}
/// Returns the I2P address associated with this socket address.
///
/// # Examples
///
/// ```
/// use i2p::net::{I2pAddr, I2pSocketAddr};
///
/// let socket = I2pSocketAddr::new(I2pAddr::new("example.i2p"), 8080);
/// assert_eq!(socket.dest(), I2pAddr::new("example.i2p"));
/// ```
pub fn dest(&self) -> I2pAddr {
self.dest.clone()
}
/// Change the I2P address associated with this socket address.
///
/// # Examples
///
/// ```
/// use i2p::net::{I2pAddr, I2pSocketAddr};
///
/// let mut socket = I2pSocketAddr::new(I2pAddr::new("example.i2p"), 8080);
/// socket.set_dest(I2pAddr::new("foobar.i2p"));
/// assert_eq!(socket.dest(), I2pAddr::new("foobar.i2p"));
/// ```
pub fn set_dest(&mut self, new_dest: I2pAddr) {
self.dest = new_dest;
}
/// Change the I2P address associated with this socket address.
///
/// # Examples
///
/// ```
/// use i2p::net::{I2pAddr, I2pSocketAddr};
///
/// let mut socket = I2pSocketAddr::new(I2pAddr::new("example.i2p"), 8080);
/// socket.set_dest(I2pAddr::new("foobar.i2p"));
/// assert_eq!(socket.dest(), I2pAddr::new("foobar.i2p"));
/// ```
pub fn set_dest(&mut self, new_dest: I2pAddr) {
self.dest = new_dest;
}
/// Returns the port number associated with this socket address.
///
/// # Examples
///
/// ```
/// use i2p::net::{I2pAddr, I2pSocketAddr};
///
/// let socket = I2pSocketAddr::new(I2pAddr::new("example.i2p"), 8080);
/// assert_eq!(socket.port(), 8080);
/// ```
pub fn port(&self) -> u16 {
self.port
}
/// Returns the port number associated with this socket address.
///
/// # Examples
///
/// ```
/// use i2p::net::{I2pAddr, I2pSocketAddr};
///
/// let socket = I2pSocketAddr::new(I2pAddr::new("example.i2p"), 8080);
/// assert_eq!(socket.port(), 8080);
/// ```
pub fn port(&self) -> u16 {
self.port
}
/// Change the port number associated with this socket address.
///
/// # Examples
///
/// ```
/// use i2p::net::{I2pAddr, I2pSocketAddr};
///
/// let mut socket = I2pSocketAddr::new(I2pAddr::new("example.i2p"), 8080);
/// socket.set_port(1025);
/// assert_eq!(socket.port(), 1025);
/// ```
pub fn set_port(&mut self, new_port: u16) {
self.port = new_port;
}
/// Change the port number associated with this socket address.
///
/// # Examples
///
/// ```
/// use i2p::net::{I2pAddr, I2pSocketAddr};
///
/// let mut socket = I2pSocketAddr::new(I2pAddr::new("example.i2p"), 8080);
/// socket.set_port(1025);
/// assert_eq!(socket.port(), 1025);
/// ```
pub fn set_port(&mut self, new_port: u16) {
self.port = new_port;
}
}
impl fmt::Display for I2pSocketAddr {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}:{}", self.dest(), self.port())
}
}
impl fmt::Debug for I2pSocketAddr {
fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Display::fmt(self, fmt)
}
}
impl Clone for I2pSocketAddr {
fn clone(&self) -> I2pSocketAddr {
I2pSocketAddr::new(self.dest.clone(), self.port)
}
}
impl PartialEq for I2pSocketAddr {
fn eq(&self, other: &I2pSocketAddr) -> bool {
self.port == other.port && self.dest == other.dest
}
}
impl Eq for I2pSocketAddr {}
impl hash::Hash for I2pSocketAddr {
fn hash<H: hash::Hasher>(&self, s: &mut H) {
(self.port, &self.dest).hash(s)
}
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}:{}", self.dest(), self.port())
}
}
/// A trait for objects which can be converted or resolved to one or more
@@ -169,168 +146,168 @@ impl hash::Hash for I2pSocketAddr {
///
/// // I2pListener::bind(), I2pDatagramSocket::bind() and I2pDatagramSocket::send_to()
/// // behave similarly
/// let i2p_l = I2pListener::bind("localhost:12345");
/// let i2p_l = I2pListener::bind();
///
/// let mut i2p_dg_s = I2pDatagramSocket::bind(("127.0.0.1", port)).unwrap();
/// i2p_dg_s.send_to(&[7], (dest, 23451)).unwrap();
/// }
/// ```
pub trait ToI2pSocketAddrs {
/// Returned iterator over socket addresses which this type may correspond
/// to.
type Iter: Iterator<Item = I2pSocketAddr>;
/// Returned iterator over socket addresses which this type may correspond
/// to.
type Iter: Iterator<Item = I2pSocketAddr>;
/// Converts this object to an iterator of resolved `I2pSocketAddr`s.
///
/// The returned iterator may not actually yield any values depending on the
/// outcome of any resolution performed.
///
/// Note that this function may block the current thread while resolution is
/// performed.
///
/// # Errors
///
/// Any errors encountered during resolution will be returned as an `Err`.
fn to_socket_addrs(&self) -> io::Result<Self::Iter>;
/// Converts this object to an iterator of resolved `I2pSocketAddr`s.
///
/// The returned iterator may not actually yield any values depending on the
/// outcome of any resolution performed.
///
/// Note that this function may block the current thread while resolution is
/// performed.
///
/// # Errors
///
/// Any errors encountered during resolution will be returned as an `Err`.
fn to_socket_addrs(&self) -> io::Result<Self::Iter>;
}
impl ToI2pSocketAddrs for I2pSocketAddr {
type Iter = option::IntoIter<I2pSocketAddr>;
fn to_socket_addrs(&self) -> io::Result<option::IntoIter<I2pSocketAddr>> {
Ok(Some(self.clone()).into_iter())
}
type Iter = option::IntoIter<I2pSocketAddr>;
fn to_socket_addrs(&self) -> io::Result<option::IntoIter<I2pSocketAddr>> {
Ok(Some(self.clone()).into_iter())
}
}
impl ToI2pSocketAddrs for (I2pAddr, u16) {
type Iter = option::IntoIter<I2pSocketAddr>;
fn to_socket_addrs(&self) -> io::Result<option::IntoIter<I2pSocketAddr>> {
let (dest, port) = self.clone();
I2pSocketAddr::new(dest, port).to_socket_addrs()
}
type Iter = option::IntoIter<I2pSocketAddr>;
fn to_socket_addrs(&self) -> io::Result<option::IntoIter<I2pSocketAddr>> {
let (dest, port) = self.clone();
I2pSocketAddr::new(dest, port).to_socket_addrs()
}
}
impl<'a> ToI2pSocketAddrs for (&'a str, u16) {
type Iter = vec::IntoIter<I2pSocketAddr>;
fn to_socket_addrs(&self) -> io::Result<vec::IntoIter<I2pSocketAddr>> {
let (host, port) = *self;
let addr = I2pSocketAddr::new(I2pAddr::new(host), port);
Ok(vec![addr].into_iter())
}
type Iter = vec::IntoIter<I2pSocketAddr>;
fn to_socket_addrs(&self) -> io::Result<vec::IntoIter<I2pSocketAddr>> {
let (host, port) = *self;
let addr = I2pSocketAddr::new(I2pAddr::new(host), port);
Ok(vec![addr].into_iter())
}
}
// accepts strings like 'example.i2p:12345'
impl ToI2pSocketAddrs for str {
type Iter = vec::IntoIter<I2pSocketAddr>;
fn to_socket_addrs(&self) -> io::Result<vec::IntoIter<I2pSocketAddr>> {
macro_rules! try_opt {
($e:expr, $msg:expr) => {
match $e {
Some(r) => r,
None => return Err(io::Error::new(io::ErrorKind::InvalidInput, $msg)),
}
};
}
type Iter = vec::IntoIter<I2pSocketAddr>;
fn to_socket_addrs(&self) -> io::Result<vec::IntoIter<I2pSocketAddr>> {
macro_rules! try_opt {
($e:expr, $msg:expr) => {
match $e {
Some(r) => r,
None => return Err(io::Error::new(io::ErrorKind::InvalidInput, $msg)),
}
};
}
// split the string by ':' and convert the second part to u16
let mut parts_iter = self.rsplitn(2, ':');
let port_str = try_opt!(parts_iter.next(), "invalid I2P socket address");
let host = try_opt!(parts_iter.next(), "invalid I2P socket address");
let port: u16 = try_opt!(port_str.parse().ok(), "invalid port value");
(host, port).to_socket_addrs()
}
// split the string by ':' and convert the second part to u16
let mut parts_iter = self.rsplitn(2, ':');
let port_str = try_opt!(parts_iter.next(), "invalid I2P socket address");
let host = try_opt!(parts_iter.next(), "invalid I2P socket address");
let port: u16 = try_opt!(port_str.parse().ok(), "invalid port value");
(host, port).to_socket_addrs()
}
}
impl<'a> ToI2pSocketAddrs for &'a [I2pSocketAddr] {
type Iter = iter::Cloned<slice::Iter<'a, I2pSocketAddr>>;
type Iter = iter::Cloned<slice::Iter<'a, I2pSocketAddr>>;
fn to_socket_addrs(&self) -> io::Result<Self::Iter> {
Ok(self.iter().cloned())
}
fn to_socket_addrs(&self) -> io::Result<Self::Iter> {
Ok(self.iter().cloned())
}
}
impl<'a, T: ToI2pSocketAddrs + ?Sized> ToI2pSocketAddrs for &'a T {
type Iter = T::Iter;
fn to_socket_addrs(&self) -> io::Result<T::Iter> {
(**self).to_socket_addrs()
}
type Iter = T::Iter;
fn to_socket_addrs(&self) -> io::Result<T::Iter> {
(**self).to_socket_addrs()
}
}
impl ToI2pSocketAddrs for String {
type Iter = vec::IntoIter<I2pSocketAddr>;
fn to_socket_addrs(&self) -> io::Result<vec::IntoIter<I2pSocketAddr>> {
(&**self).to_socket_addrs()
}
type Iter = vec::IntoIter<I2pSocketAddr>;
fn to_socket_addrs(&self) -> io::Result<vec::IntoIter<I2pSocketAddr>> {
(&**self).to_socket_addrs()
}
}
#[cfg(test)]
mod tests {
use crate::net::test::{isa, tsa};
use crate::net::*;
use crate::net::test::{isa, tsa};
use crate::net::*;
#[test]
fn to_socket_addr_i2paddr_u16() {
let a = I2pAddr::new("example.i2p");
let p = 12345;
let e = I2pSocketAddr::new(a.clone(), p);
assert_eq!(Ok(vec![e]), tsa((a, p)));
}
#[test]
fn to_socket_addr_i2paddr_u16() {
let a = I2pAddr::new("example.i2p");
let p = 12345;
let e = I2pSocketAddr::new(a.clone(), p);
assert_eq!(Ok(vec![e]), tsa((a, p)));
}
#[test]
fn to_socket_addr_str_u16() {
let a = isa(I2pAddr::new("example.i2p"), 24352);
assert_eq!(Ok(vec![a]), tsa(("example.i2p", 24352)));
#[test]
fn to_socket_addr_str_u16() {
let a = isa(I2pAddr::new("example.i2p"), 24352);
assert_eq!(Ok(vec![a]), tsa(("example.i2p", 24352)));
let a = isa(I2pAddr::new("example.i2p"), 23924);
assert!(tsa(("example.i2p", 23924)).unwrap().contains(&a));
}
let a = isa(I2pAddr::new("example.i2p"), 23924);
assert!(tsa(("example.i2p", 23924)).unwrap().contains(&a));
}
#[test]
fn to_socket_addr_str() {
let a = isa(I2pAddr::new("example.i2p"), 24352);
assert_eq!(Ok(vec![a]), tsa("example.i2p:24352"));
#[test]
fn to_socket_addr_str() {
let a = isa(I2pAddr::new("example.i2p"), 24352);
assert_eq!(Ok(vec![a]), tsa("example.i2p:24352"));
let a = isa(I2pAddr::new("example.i2p"), 23924);
assert!(tsa("example.i2p:23924").unwrap().contains(&a));
}
let a = isa(I2pAddr::new("example.i2p"), 23924);
assert!(tsa("example.i2p:23924").unwrap().contains(&a));
}
#[test]
fn to_socket_addr_string() {
let a = isa(I2pAddr::new("example.i2p"), 24352);
assert_eq!(
Ok(vec![a.clone()]),
tsa(&*format!("{}:{}", "example.i2p", "24352"))
);
assert_eq!(
Ok(vec![a.clone()]),
tsa(&format!("{}:{}", "example.i2p", "24352"))
);
assert_eq!(
Ok(vec![a.clone()]),
tsa(format!("{}:{}", "example.i2p", "24352"))
);
#[test]
fn to_socket_addr_string() {
let a = isa(I2pAddr::new("example.i2p"), 24352);
assert_eq!(
Ok(vec![a.clone()]),
tsa(&*format!("{}:{}", "example.i2p", "24352"))
);
assert_eq!(
Ok(vec![a.clone()]),
tsa(&format!("{}:{}", "example.i2p", "24352"))
);
assert_eq!(
Ok(vec![a.clone()]),
tsa(format!("{}:{}", "example.i2p", "24352"))
);
let s = format!("{}:{}", "example.i2p", "24352");
assert_eq!(Ok(vec![a]), tsa(s));
// s has been moved into the tsa call
}
let s = format!("{}:{}", "example.i2p", "24352");
assert_eq!(Ok(vec![a]), tsa(s));
// s has been moved into the tsa call
}
#[test]
fn set_dest() {
fn i2p(low: u8) -> I2pAddr {
I2pAddr::new(&format!("example{}.i2p", low))
}
#[test]
fn set_dest() {
fn i2p(low: u8) -> I2pAddr {
I2pAddr::new(&format!("example{}.i2p", low))
}
let mut addr = I2pSocketAddr::new(i2p(12), 80);
assert_eq!(addr.dest(), i2p(12));
addr.set_dest(i2p(13));
assert_eq!(addr.dest(), i2p(13));
}
let mut addr = I2pSocketAddr::new(i2p(12), 80);
assert_eq!(addr.dest(), i2p(12));
addr.set_dest(i2p(13));
assert_eq!(addr.dest(), i2p(13));
}
#[test]
fn set_port() {
let mut addr = I2pSocketAddr::new(I2pAddr::new("example.i2p"), 80);
assert_eq!(addr.port(), 80);
addr.set_port(8080);
assert_eq!(addr.port(), 8080);
}
#[test]
fn set_port() {
let mut addr = I2pSocketAddr::new(I2pAddr::new("example.i2p"), 80);
assert_eq!(addr.port(), 80);
addr.set_port(8080);
assert_eq!(addr.port(), 8080);
}
}

View File

@@ -1,8 +1,9 @@
use std::io::{self, Error, ErrorKind};
use std::net::{SocketAddr, ToSocketAddrs};
use crate::error::{Error, ErrorKind};
use crate::net::{I2pSocketAddr, ToI2pSocketAddrs};
use crate::sam::DEFAULT_API;
use crate::sam_options::SAMOptions;
/// Unimplemented
///
@@ -15,8 +16,9 @@ use crate::sam::DEFAULT_API;
///
/// ```no_run
/// use i2p::net::I2pDatagramSocket;
/// use i2p::Error;
///
/// # fn foo() -> std::io::Result<()> {
/// # fn foo() -> Result<(), Error> {
/// {
/// let mut socket = I2pDatagramSocket::bind("127.0.0.1:34254")?;
///
@@ -35,224 +37,227 @@ use crate::sam::DEFAULT_API;
pub struct I2pDatagramSocket {}
impl I2pDatagramSocket {
/// Creates an I2P datagram socket from the given address.
///
/// The address type can be any implementor of [`ToI2pSocketAddrs`] trait. See
/// its documentation for concrete examples.
///
/// [`ToI2pSocketAddrs`]: ../../i2p/net/trait.ToI2pSocketAddrs.html
///
/// # Examples
///
/// ```no_run
/// use i2p::net::I2pDatagramSocket;
///
/// let socket = I2pDatagramSocket::bind("127.0.0.1:34254").expect("couldn't bind to address");
/// ```
pub fn bind<A: ToI2pSocketAddrs>(addr: A) -> io::Result<I2pDatagramSocket> {
I2pDatagramSocket::bind_via(DEFAULT_API, addr)
}
/// Creates an I2P datagram socket from the given address.
///
/// The address type can be any implementor of [`ToI2pSocketAddrs`] trait. See
/// its documentation for concrete examples.
///
/// [`ToI2pSocketAddrs`]: ../../i2p/net/trait.ToI2pSocketAddrs.html
///
/// # Examples
///
/// ```no_run
/// use i2p::net::I2pDatagramSocket;
///
/// let socket = I2pDatagramSocket::bind("127.0.0.1:34254").expect("couldn't bind to address");
/// ```
pub fn bind<A: ToI2pSocketAddrs>(addr: A) -> Result<I2pDatagramSocket, Error> {
I2pDatagramSocket::bind_via(DEFAULT_API, addr, SAMOptions::default())
}
pub fn bind_via<A: ToSocketAddrs, B: ToI2pSocketAddrs>(
sam_addr: A,
addr: B,
) -> io::Result<I2pDatagramSocket> {
super::each_addr(sam_addr, addr, I2pDatagramSocket::bind_addr)
}
pub fn bind_via<A: ToSocketAddrs, B: ToI2pSocketAddrs>(
sam_addr: A,
addr: B,
options: SAMOptions
) -> Result<I2pDatagramSocket, Error> {
super::each_i2p_addr(sam_addr, addr, options,I2pDatagramSocket::bind_addr).map_err(|e| e.into())
}
fn bind_addr(_sam_addr: &SocketAddr, _addr: &I2pSocketAddr) -> io::Result<I2pDatagramSocket> {
unimplemented!();
}
fn bind_addr(
_sam_addr: &SocketAddr,
_addr: &I2pSocketAddr,
_options: SAMOptions
) -> Result<I2pDatagramSocket, Error> {
unimplemented!();
}
/// Receives data from the socket. On success, returns the number of bytes
/// read and the address from whence the data came.
///
/// # Examples
///
/// ```no_run
/// use i2p::net::I2pDatagramSocket;
///
/// let socket = I2pDatagramSocket::bind("127.0.0.1:34254").expect("couldn't bind to address");
/// let mut buf = [0; 10];
/// let (number_of_bytes, src_addr) = socket.recv_from(&mut buf)
/// .expect("Didn't receive data");
/// ```
pub fn recv_from(&self, _buf: &mut [u8]) -> io::Result<(usize, I2pSocketAddr)> {
unimplemented!()
}
/// Receives data from the socket. On success, returns the number of bytes
/// read and the address from whence the data came.
///
/// # Examples
///
/// ```no_run
/// use i2p::net::I2pDatagramSocket;
///
/// let socket = I2pDatagramSocket::bind("127.0.0.1:34254").expect("couldn't bind to address");
/// let mut buf = [0; 10];
/// let (number_of_bytes, src_addr) = socket.recv_from(&mut buf)
/// .expect("Didn't receive data");
/// ```
pub fn recv_from(&self, _buf: &mut [u8]) -> Result<(usize, I2pSocketAddr), Error> {
unimplemented!()
}
/// Receives data from the socket, without removing it from the queue.
///
/// Successive calls return the same data.
///
/// On success, returns the number of bytes peeked and the address from
/// whence the data came.
///
/// # Examples
///
/// ```no_run
/// use i2p::net::I2pDatagramSocket;
///
/// let socket = I2pDatagramSocket::bind("127.0.0.1:34254").expect("couldn't bind to address");
/// let mut buf = [0; 10];
/// let (number_of_bytes, src_addr) = socket.peek_from(&mut buf)
/// .expect("Didn't receive data");
/// ```
pub fn peek_from(&self, _buf: &mut [u8]) -> io::Result<(usize, I2pSocketAddr)> {
unimplemented!()
}
/// Receives data from the socket, without removing it from the queue.
///
/// Successive calls return the same data.
///
/// On success, returns the number of bytes peeked and the address from
/// whence the data came.
///
/// # Examples
///
/// ```no_run
/// use i2p::net::I2pDatagramSocket;
///
/// let socket = I2pDatagramSocket::bind("127.0.0.1:34254").expect("couldn't bind to address");
/// let mut buf = [0; 10];
/// let (number_of_bytes, src_addr) = socket.peek_from(&mut buf)
/// .expect("Didn't receive data");
/// ```
pub fn peek_from(&self, _buf: &mut [u8]) -> Result<(usize, I2pSocketAddr), Error> {
unimplemented!()
}
/// Sends data on the socket to the given address. On success, returns the
/// number of bytes written.
///
/// Address type can be any implementor of [`ToI2pSocketAddrs`] trait. See
/// its documentation for concrete examples.
///
/// [`ToI2pSocketAddrs`]: ../../std/net/trait.ToI2pSocketAddrs.html
///
/// # Examples
///
/// ```no_run
/// use i2p::net::I2pDatagramSocket;
///
/// let socket = I2pDatagramSocket::bind("127.0.0.1:34254").expect("couldn't bind to address");
/// socket.send_to(&[0; 10], "127.0.0.1:4242").expect("couldn't send data");
/// ```
pub fn send_to<A: ToI2pSocketAddrs>(&self, _buf: &[u8], addr: A) -> io::Result<usize> {
match addr.to_socket_addrs()?.next() {
Some(_addr) => unimplemented!(),
None => Err(Error::new(
ErrorKind::InvalidInput,
"no addresses to send data to",
)),
}
}
/// Sends data on the socket to the given address. On success, returns the
/// number of bytes written.
///
/// Address type can be any implementor of [`ToI2pSocketAddrs`] trait. See
/// its documentation for concrete examples.
///
/// [`ToI2pSocketAddrs`]: ../../std/net/trait.ToI2pSocketAddrs.html
///
/// # Examples
///
/// ```no_run
/// use i2p::net::I2pDatagramSocket;
///
/// let socket = I2pDatagramSocket::bind("127.0.0.1:34254").expect("couldn't bind to address");
/// socket.send_to(&[0; 10], "127.0.0.1:4242").expect("couldn't send data");
/// ```
pub fn send_to<A: ToI2pSocketAddrs>(&self, _buf: &[u8], addr: A) -> Result<usize, Error> {
match addr.to_socket_addrs()?.next() {
Some(_addr) => unimplemented!(),
None => Err(ErrorKind::UnresolvableAddress.into()),
}
}
/// Returns the socket address that this socket was created from.
///
/// # Examples
///
/// ```no_run
/// use i2p::net::{I2pAddr, I2pSocketAddr, I2pDatagramSocket};
///
/// let socket = I2pDatagramSocket::bind("127.0.0.1:34254").expect("couldn't bind to address");
/// assert_eq!(socket.local_addr().unwrap(),
/// I2pSocketAddr::new(I2pAddr::new("example.i2p"), 34254));
/// ```
pub fn local_addr(&self) -> io::Result<I2pSocketAddr> {
unimplemented!()
}
/// Returns the socket address that this socket was created from.
///
/// # Examples
///
/// ```no_run
/// use i2p::net::{I2pAddr, I2pSocketAddr, I2pDatagramSocket};
///
/// let socket = I2pDatagramSocket::bind("127.0.0.1:34254").expect("couldn't bind to address");
/// assert_eq!(socket.local_addr().unwrap(),
/// I2pSocketAddr::new(I2pAddr::new("example.i2p"), 34254));
/// ```
pub fn local_addr(&self) -> Result<I2pSocketAddr, Error> {
unimplemented!()
}
/// Creates a new independently owned handle to the underlying socket.
///
/// The returned `I2pDatagramSocket` is a reference to the same socket that this
/// object references. Both handles will read and write the same port, and
/// options set on one socket will be propagated to the other.
///
/// # Examples
///
/// ```no_run
/// use i2p::net::I2pDatagramSocket;
///
/// let socket = I2pDatagramSocket::bind("127.0.0.1:34254").expect("couldn't bind to address");
/// let socket_clone = socket.try_clone().expect("couldn't clone the socket");
/// ```
pub fn try_clone(&self) -> io::Result<I2pDatagramSocket> {
unimplemented!()
}
/// Creates a new independently owned handle to the underlying socket.
///
/// The returned `I2pDatagramSocket` is a reference to the same socket that this
/// object references. Both handles will read and write the same port, and
/// options set on one socket will be propagated to the other.
///
/// # Examples
///
/// ```no_run
/// use i2p::net::I2pDatagramSocket;
///
/// let socket = I2pDatagramSocket::bind("127.0.0.1:34254").expect("couldn't bind to address");
/// let socket_clone = socket.try_clone().expect("couldn't clone the socket");
/// ```
pub fn try_clone(&self) -> Result<I2pDatagramSocket, Error> {
unimplemented!()
}
/// Connects this datagram socket to a remote address, allowing the `send` and
/// `recv` calls to be used to send data and also applies filters to only
/// receive data from the specified address.
///
/// # Examples
///
/// ```no_run
/// use i2p::net::I2pDatagramSocket;
///
/// let socket = I2pDatagramSocket::bind("127.0.0.1:34254").expect("couldn't bind to address");
/// socket.connect("127.0.0.1:8080").expect("connect function failed");
/// ```
pub fn connect<A: ToI2pSocketAddrs>(&self, addr: A) -> io::Result<()> {
self.connect_via(DEFAULT_API, addr)
}
/// Connects this datagram socket to a remote address, allowing the `send` and
/// `recv` calls to be used to send data and also applies filters to only
/// receive data from the specified address.
///
/// # Examples
///
/// ```no_run
/// use i2p::net::I2pDatagramSocket;
///
/// let socket = I2pDatagramSocket::bind("127.0.0.1:34254").expect("couldn't bind to address");
/// socket.connect("127.0.0.1:8080").expect("connect function failed");
/// ```
pub fn connect<A: ToI2pSocketAddrs>(&self, addr: A) -> Result<(), Error> {
self.connect_via(DEFAULT_API, addr, SAMOptions::default())
}
pub fn connect_via<A: ToSocketAddrs, B: ToI2pSocketAddrs>(
&self,
sam_addr: A,
addr: B,
) -> io::Result<()> {
super::each_addr(sam_addr, addr, |_sam_addr, _addr| unimplemented!())
}
pub fn connect_via<A: ToSocketAddrs, B: ToI2pSocketAddrs>(
&self,
sam_addr: A,
addr: B,
options: SAMOptions,
) -> Result<(), Error> {
super::each_i2p_addr(sam_addr, addr, options, |_sam_addr, _addr, _opts| unimplemented!())
}
/// Sends data on the socket to the remote address to which it is connected.
///
/// The [`connect()`] method will connect this socket to a remote address. This
/// method will fail if the socket is not connected.
///
/// [`connect()`]: #method.connect
///
/// # Examples
///
/// ```no_run
/// use i2p::net::I2pDatagramSocket;
///
/// let socket = I2pDatagramSocket::bind("127.0.0.1:34254").expect("couldn't bind to address");
/// socket.connect("127.0.0.1:8080").expect("connect function failed");
/// socket.send(&[0, 1, 2]).expect("couldn't send message");
/// ```
pub fn send(&self, _buf: &[u8]) -> io::Result<usize> {
unimplemented!()
}
/// Sends data on the socket to the remote address to which it is connected.
///
/// The [`connect()`] method will connect this socket to a remote address. This
/// method will fail if the socket is not connected.
///
/// [`connect()`]: #method.connect
///
/// # Examples
///
/// ```no_run
/// use i2p::net::I2pDatagramSocket;
///
/// let socket = I2pDatagramSocket::bind("127.0.0.1:34254").expect("couldn't bind to address");
/// socket.connect("127.0.0.1:8080").expect("connect function failed");
/// socket.send(&[0, 1, 2]).expect("couldn't send message");
/// ```
pub fn send(&self, _buf: &[u8]) -> Result<usize, Error> {
unimplemented!()
}
/// Receives data on the socket from the remote address to which it is
/// connected.
///
/// The `connect` method will connect this socket to a remote address. This
/// method will fail if the socket is not connected.
///
/// # Examples
///
/// ```no_run
/// use i2p::net::I2pDatagramSocket;
///
/// let socket = I2pDatagramSocket::bind("127.0.0.1:34254").expect("couldn't bind to address");
/// socket.connect("127.0.0.1:8080").expect("connect function failed");
/// let mut buf = [0; 10];
/// match socket.recv(&mut buf) {
/// Ok(received) => println!("received {} bytes", received),
/// Err(e) => println!("recv function failed: {:?}", e),
/// }
/// ```
pub fn recv(&self, _buf: &mut [u8]) -> io::Result<usize> {
unimplemented!()
}
/// Receives data on the socket from the remote address to which it is
/// connected.
///
/// The `connect` method will connect this socket to a remote address. This
/// method will fail if the socket is not connected.
///
/// # Examples
///
/// ```no_run
/// use i2p::net::I2pDatagramSocket;
///
/// let socket = I2pDatagramSocket::bind("127.0.0.1:34254").expect("couldn't bind to address");
/// socket.connect("127.0.0.1:8080").expect("connect function failed");
/// let mut buf = [0; 10];
/// match socket.recv(&mut buf) {
/// Ok(received) => println!("received {} bytes", received),
/// Err(e) => println!("recv function failed: {:?}", e),
/// }
/// ```
pub fn recv(&self, _buf: &mut [u8]) -> Result<usize, Error> {
unimplemented!()
}
/// Receives data on the socket from the remote adress to which it is
/// connected, without removing that data from the queue. On success,
/// returns the number of bytes peeked.
///
/// Successive calls return the same data.
///
/// # Errors
///
/// This method will fail if the socket is not connected. The `connect` method
/// will connect this socket to a remote address.
///
/// # Examples
///
/// ```no_run
/// use i2p::net::I2pDatagramSocket;
///
/// let socket = I2pDatagramSocket::bind("127.0.0.1:34254").expect("couldn't bind to address");
/// socket.connect("127.0.0.1:8080").expect("connect function failed");
/// let mut buf = [0; 10];
/// match socket.peek(&mut buf) {
/// Ok(received) => println!("received {} bytes", received),
/// Err(e) => println!("peek function failed: {:?}", e),
/// }
/// ```
pub fn peek(&self, _buf: &mut [u8]) -> io::Result<usize> {
unimplemented!()
}
/// Receives data on the socket from the remote adress to which it is
/// connected, without removing that data from the queue. On success,
/// returns the number of bytes peeked.
///
/// Successive calls return the same data.
///
/// # Errors
///
/// This method will fail if the socket is not connected. The `connect` method
/// will connect this socket to a remote address.
///
/// # Examples
///
/// ```no_run
/// use i2p::net::I2pDatagramSocket;
///
/// let socket = I2pDatagramSocket::bind("127.0.0.1:34254").expect("couldn't bind to address");
/// socket.connect("127.0.0.1:8080").expect("connect function failed");
/// let mut buf = [0; 10];
/// match socket.peek(&mut buf) {
/// Ok(received) => println!("received {} bytes", received),
/// Err(e) => println!("peek function failed: {:?}", e),
/// }
/// ```
pub fn peek(&self, _buf: &mut [u8]) -> Result<usize, Error> {
unimplemented!()
}
}

View File

@@ -1,6 +1,34 @@
use std::cmp::Ordering;
use std::fmt;
use std::hash;
use data_encoding::{Encoding, Specification, BASE32};
use lazy_static::lazy_static;
use log::error;
use serde_derive::{Deserialize, Serialize};
use sha2::{Digest, Sha256};
use crate::error::{Error, ErrorKind};
pub const B32_EXT: &'static str = ".b32.i2p";
lazy_static! {
static ref BASE32_I2P: Encoding = {
let mut spec = Specification::new();
spec.symbols.push_str("abcdefghijklmnopqrstuvwxyz234567");
spec.padding = None;
spec.encoding().unwrap()
};
}
lazy_static! {
static ref BASE64_I2P: Encoding = {
let mut spec = Specification::new();
spec.symbols
.push_str("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-~");
spec.padding = Some('=');
spec.encoding().unwrap()
};
}
/// An I2P address, as a Destination, B32 address or hostname.
///
@@ -21,81 +49,59 @@ use std::hash;
///
/// I2pAddr::new("abcdefghijklmnopqrstuvwxyz234567abcdefghijklmnopqrst.b32.i2p");
/// ```
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize, Hash)]
pub struct I2pAddr {
inner: String,
inner: String,
}
impl I2pAddr {
/// Creates a new I2p address from a given string.
///
/// # Examples
///
/// ```
/// use i2p::net::I2pAddr;
///
/// let addr = I2pAddr::new("example.i2p");
/// ```
pub fn new(dest: &str) -> I2pAddr {
I2pAddr {
inner: dest.to_string(),
}
}
/// Creates a new I2p address from a given string.
///
/// # Examples
///
/// ```
/// use i2p::net::I2pAddr;
///
/// let addr = I2pAddr::new("example.i2p");
/// ```
pub fn new(dest: &str) -> I2pAddr {
I2pAddr {
inner: dest.to_string(),
}
}
/// Returns the String that makes up this address.
///
/// # Examples
///
/// ```
/// use i2p::net::I2pAddr;
///
/// let addr = I2pAddr::new("example.i2p");
/// assert_eq!(addr.string(), "example.i2p");
/// ```
pub fn string(&self) -> String {
self.inner.clone()
}
/// Creates a new I2P address from a full base64 destination string. This
/// will internally convert it to a common base32 addresse, using the
/// b32.i2p extension.
pub fn from_b64(dest: &str) -> Result<I2pAddr, Error> {
let bin_data = BASE64_I2P.decode(dest.as_bytes()).map_err(|e| {
error!("Base64 decoding error: {:?}", e);
ErrorKind::BadAddressEncoding(dest.to_string()).to_err()
})?;
let mut hasher = Sha256::new();
hasher.input(bin_data);
let mut b32 = BASE32_I2P.encode(&hasher.result());
b32.push_str(B32_EXT);
Ok(I2pAddr { inner: b32 })
}
/// Returns the String that makes up this address.
///
/// # Examples
///
/// ```
/// use i2p::net::I2pAddr;
///
/// let addr = I2pAddr::new("example.i2p");
/// assert_eq!(addr.string(), "example.i2p");
/// ```
pub fn string(&self) -> String {
self.inner.clone()
}
}
impl fmt::Display for I2pAddr {
fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(fmt, "{}", self.inner)
}
}
impl fmt::Debug for I2pAddr {
fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Display::fmt(self, fmt)
}
}
impl Clone for I2pAddr {
fn clone(&self) -> I2pAddr {
I2pAddr::new(&self.inner)
}
}
impl PartialEq for I2pAddr {
fn eq(&self, other: &I2pAddr) -> bool {
self.inner == other.inner
}
}
impl Eq for I2pAddr {}
impl hash::Hash for I2pAddr {
fn hash<H: hash::Hasher>(&self, s: &mut H) {
self.inner.hash(s)
}
}
impl PartialOrd for I2pAddr {
fn partial_cmp(&self, other: &I2pAddr) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl Ord for I2pAddr {
fn cmp(&self, other: &I2pAddr) -> Ordering {
self.inner.cmp(&other.inner)
}
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
write!(fmt, "{}", self.inner)
}
}

View File

@@ -1,10 +1,11 @@
use std::io;
use crate::error::{Error, ErrorKind};
use crate::sam_options::SAMOptions;
use std::net::{SocketAddr, ToSocketAddrs};
pub use self::addr::{I2pSocketAddr, ToI2pSocketAddrs};
pub use self::datagram::I2pDatagramSocket;
pub use self::i2p::I2pAddr;
pub use self::streaming::{I2pListener, I2pStream};
pub use self::streaming::{I2pListenerBuilder, I2pListener, I2pStream};
mod addr;
mod datagram;
@@ -13,27 +14,37 @@ mod streaming;
#[cfg(test)]
mod test;
fn each_addr<A: ToSocketAddrs, B: ToI2pSocketAddrs, F, T>(
sam_addr: A,
addr: B,
mut f: F,
) -> io::Result<T>
fn each_i2p_addr<A: ToSocketAddrs, B: ToI2pSocketAddrs, F, T>(
sam_addr: A,
addr: B,
opts: SAMOptions,
mut f: F,
) -> Result<T, Error>
where
F: FnMut(&SocketAddr, &I2pSocketAddr) -> io::Result<T>,
F: FnMut(&SocketAddr, &I2pSocketAddr, SAMOptions) -> Result<T, Error>,
{
let mut last_err = None;
for addr in addr.to_socket_addrs()? {
for sam_addr in sam_addr.to_socket_addrs()? {
match f(&sam_addr, &addr) {
Ok(l) => return Ok(l),
Err(e) => last_err = Some(e),
}
}
}
Err(last_err.unwrap_or_else(|| {
io::Error::new(
io::ErrorKind::InvalidInput,
"could not resolve to any addresses",
)
}))
let mut last_err = None;
for addr in addr.to_socket_addrs()? {
for sam_addr in sam_addr.to_socket_addrs()? {
match f(&sam_addr, &addr, opts.clone()) {
Ok(l) => return Ok(l),
Err(e) => last_err = Some(e),
}
}
}
Err(last_err.unwrap_or(ErrorKind::UnresolvableAddress.into()))
}
fn each_addr<A: ToSocketAddrs, F, T>(sam_addr: A, opts: SAMOptions, mut f: F) -> Result<T, Error>
where
F: FnMut(&SocketAddr, SAMOptions) -> Result<T, Error>,
{
let mut last_err = None;
for sam_addr in sam_addr.to_socket_addrs()? {
match f(&sam_addr, opts.clone()) {
Ok(l) => return Ok(l),
Err(e) => last_err = Some(e),
}
}
Err(last_err.unwrap_or(ErrorKind::UnresolvableAddress.into()))
}

View File

@@ -3,12 +3,12 @@ use std::io::prelude::*;
use std::fmt;
use std::io;
use std::net::{Shutdown, SocketAddr, ToSocketAddrs};
use std::time::Duration;
use rand;
use rand::Rng;
use crate::error::{Error, ErrorKind};
use crate::net::{I2pAddr, I2pSocketAddr, ToI2pSocketAddrs};
use crate::sam::{StreamConnect, DEFAULT_API};
use crate::sam::{Session, StreamConnect, StreamForward, DEFAULT_API};
use crate::sam_options::SAMOptions;
/// A structure which represents an I2P stream between a local socket and a
/// remote socket.
@@ -30,11 +30,215 @@ use crate::sam::{StreamConnect, DEFAULT_API};
/// } // the stream is closed here
/// ```
pub struct I2pStream {
inner: StreamConnect,
#[cfg(feature = "public-conn")]
pub inner: StreamConnect,
#[cfg(not(feature = "public-conn"))]
inner: StreamConnect,
}
/// Unimplemented
/// An infinite iterator over the connections from an `I2pListener`.
///
/// This iterator will infinitely yield [`Some`] of the accepted connections. It
/// is equivalent to calling `accept` in a loop.
///
/// This `struct` is created by the [`incoming`] method on [`I2pListener`].
///
/// [`Some`]: ../../std/option/enum.Option.html#variant.Some
/// [`incoming`]: struct.I2pListener.html#method.incoming
/// [`I2pListener`]: struct.I2pListener.html
pub struct Incoming<'a> {
#[cfg(feature = "public-conn")]
pub listener: &'a I2pListener,
#[cfg(not(feature = "public-conn"))]
listener: &'a I2pListener,
}
impl I2pStream {
/// Opens a TCP-like connection to a remote host.
///
/// `addr` is an address of the remote host. Anything which implements
/// `ToI2pSocketAddrs` trait can be supplied for the address; see this trait
/// documentation for concrete examples.
/// In case `ToI2pSocketAddrs::to_socket_addrs()` returns more than one
/// entry (which should never be the case), then the first valid and
/// reachable address is used.
///
/// # Examples
///
/// ```no_run
/// use i2p::net::I2pStream;
///
/// if let Ok(stream) = I2pStream::connect("example.i2p:8080") {
/// println!("Connected to the server!");
/// } else {
/// println!("Couldn't connect to server...");
/// }
/// ```
pub fn connect<A: ToI2pSocketAddrs>(addr: A) -> Result<I2pStream, Error> {
I2pStream::connect_via(DEFAULT_API, addr, SAMOptions::default())
}
/// Same as `connect` but reuses an existing SAM session.
pub fn connect_with_session<A: ToI2pSocketAddrs>(
session: &Session,
addr: A,
) -> Result<I2pStream, Error> {
let addr: Result<_, Error> = addr
.to_socket_addrs()?
.next()
.ok_or(ErrorKind::UnresolvableAddress.into());
I2pStream::connect_addr_with_session(session, &addr?)
}
pub fn connect_via<A: ToSocketAddrs, B: ToI2pSocketAddrs>(
sam_addr: A,
addr: B,
options: SAMOptions,
) -> Result<I2pStream, Error> {
super::each_i2p_addr(sam_addr, addr, options, I2pStream::connect_addr).map_err(|e| e.into())
}
fn connect_addr(sam_addr: &SocketAddr, addr: &I2pSocketAddr, options: SAMOptions) -> Result<I2pStream, Error> {
let stream = StreamConnect::new(sam_addr, &addr.dest().string(), addr.port(), options)?;
Ok(I2pStream { inner: stream })
}
fn connect_addr_with_session(
session: &Session,
addr: &I2pSocketAddr,
) -> Result<I2pStream, Error> {
let stream = StreamConnect::with_session(session, &addr.dest().string(), addr.port())?;
Ok(I2pStream { inner: stream })
}
/// Returns the socket address of the remote peer of this I2P connection.
///
/// # Examples
///
/// ```no_run
/// use i2p::net::{I2pAddr, I2pSocketAddr, I2pStream};
///
/// let stream = I2pStream::connect("example.i2p:8080")
/// .expect("Couldn't connect to the server...");
/// assert_eq!(stream.peer_addr().unwrap(),
/// I2pSocketAddr::new(I2pAddr::new("example.i2p"), 8080));
/// ```
pub fn peer_addr(&self) -> Result<I2pSocketAddr, Error> {
self.inner
.peer_addr()
.map(|(d, p)| I2pSocketAddr::new(I2pAddr::new(&d), p))
}
/// Returns the socket address of the local half of this I2P connection.
///
/// # Examples
///
/// ```no_run
/// use i2p::net::{I2pAddr, I2pSocketAddr, I2pStream};
///
/// let stream = I2pStream::connect("example.i2p:8080")
/// .expect("Couldn't connect to the server...");
/// assert_eq!(stream.local_addr().unwrap(),
/// I2pSocketAddr::new(I2pAddr::new("example.i2p"), 8080));
/// ```
pub fn local_addr(&self) -> Result<I2pSocketAddr, Error> {
self.inner
.local_addr()
.map(|(d, p)| I2pSocketAddr::new(I2pAddr::new(&d), p))
}
/// Moves this I2P stream into or out of nonblocking mode. This will
/// result in read, write, recv and send operations becoming nonblocking,
/// i.e., immediately returning from their calls. If the IO operation is
/// successful, Ok is returned and no further action is required. If the
/// IO operation could not be completed and needs to be retried, a wrapped
/// Io error with kind io::ErrorKind::WouldBlock is returned.
pub fn set_nonblocking(&self, nonblocking: bool) -> Result<(), Error> {
self.inner.set_nonblocking(nonblocking)
}
pub fn set_read_timeout(&self, duration: Option<Duration>) -> std::io::Result<()> {
self.inner.set_read_timeout(duration)
}
pub fn set_write_timeout(&self, duration: Option<Duration>) -> std::io::Result<()> {
self.inner.set_write_timeout(duration)
}
/// Shuts down the read, write, or both halves of this connection.
///
/// This function will cause all pending and future I/O on the specified
/// portions to return immediately with an appropriate value (see the
/// documentation of [`Shutdown`]).
///
/// [`Shutdown`]: ../../std/net/enum.Shutdown.html
///
/// # Examples
///
/// ```no_run
/// use std::net::Shutdown;
/// use i2p::net::I2pStream;
///
/// let stream = I2pStream::connect("127.0.0.1:8080")
/// .expect("Couldn't connect to the server...");
/// stream.shutdown(Shutdown::Both).expect("shutdown call failed");
/// ```
pub fn shutdown(&self, how: Shutdown) -> Result<(), Error> {
self.inner.shutdown(how)
}
/// Creates a new independently owned handle to the underlying socket.
///
/// The returned `I2pStream` is a reference to the same stream that this
/// object references. Both handles will read and write the same stream of
/// data, and options set on one stream will be propagated to the other
/// stream.
///
/// # Examples
///
/// ```no_run
/// use i2p::net::I2pStream;
///
/// let stream = I2pStream::connect("example.i2p:8080")
/// .expect("Couldn't connect to the server...");
/// let stream_clone = stream.try_clone().expect("clone failed...");
/// ```
pub fn try_clone(&self) -> Result<I2pStream, Error> {
self.inner.duplicate().map(|s| I2pStream { inner: s })
}
}
impl Read for I2pStream {
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
self.inner.read(buf)
}
}
impl Write for I2pStream {
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
self.inner.write(buf)
}
fn flush(&mut self) -> io::Result<()> {
Ok(())
}
}
impl fmt::Debug for I2pStream {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let mut res = f.debug_struct("I2pStream");
if let Ok(addr) = self.local_addr() {
res.field("addr", &addr);
}
if let Ok(peer) = self.peer_addr() {
res.field("peer", &peer);
}
res.finish()
}
}
/// A structure representing a socket server.
///
/// # Examples
@@ -42,7 +246,7 @@ pub struct I2pStream {
/// ```no_run
/// use i2p::net::{I2pListener, I2pStream};
///
/// let listener = I2pListener::bind("127.0.0.1:80").unwrap();
/// let listener = I2pListener::bind().unwrap();
///
/// fn handle_client(stream: I2pStream) {
/// // ...
@@ -58,300 +262,240 @@ pub struct I2pStream {
/// }
/// }
/// ```
pub struct I2pListener {}
/// An infinite iterator over the connections from an `I2pListener`.
///
/// This iterator will infinitely yield [`Some`] of the accepted connections. It
/// is equivalent to calling `accept` in a loop.
///
/// This `struct` is created by the [`incoming`] method on [`I2pListener`].
///
/// [`Some`]: ../../std/option/enum.Option.html#variant.Some
/// [`incoming`]: struct.I2pListener.html#method.incoming
/// [`I2pListener`]: struct.I2pListener.html
#[derive(Debug)]
pub struct Incoming<'a> {
listener: &'a I2pListener,
}
impl I2pStream {
/// Opens a TCP-like connection to a remote host.
///
/// `addr` is an address of the remote host. Anything which implements
/// `ToI2pSocketAddrs` trait can be supplied for the address; see this trait
/// documentation for concrete examples.
/// In case `ToI2pSocketAddrs::to_socket_addrs()` returns more than one
/// entry (which should never be the case), then the first valid and
/// reachable address is used.
///
/// # Examples
///
/// ```no_run
/// use i2p::net::I2pStream;
///
/// if let Ok(stream) = I2pStream::connect("example.i2p:8080") {
/// println!("Connected to the server!");
/// } else {
/// println!("Couldn't connect to server...");
/// }
/// ```
pub fn connect<A: ToI2pSocketAddrs>(addr: A) -> io::Result<I2pStream> {
I2pStream::connect_via(DEFAULT_API, addr)
}
pub fn connect_via<A: ToSocketAddrs, B: ToI2pSocketAddrs>(
sam_addr: A,
addr: B,
) -> io::Result<I2pStream> {
super::each_addr(sam_addr, addr, I2pStream::connect_addr)
}
fn connect_addr(sam_addr: &SocketAddr, addr: &I2pSocketAddr) -> io::Result<I2pStream> {
let suffix: String = rand::thread_rng().gen_ascii_chars().take(8).collect();
let nickname = format!("i2prs-{}", suffix);
let stream = StreamConnect::new(sam_addr, &addr.dest().string(), addr.port(), &nickname)?;
Ok(I2pStream { inner: stream })
}
/// Returns the socket address of the remote peer of this I2P connection.
///
/// # Examples
///
/// ```no_run
/// use i2p::net::{I2pAddr, I2pSocketAddr, I2pStream};
///
/// let stream = I2pStream::connect("example.i2p:8080")
/// .expect("Couldn't connect to the server...");
/// assert_eq!(stream.peer_addr().unwrap(),
/// I2pSocketAddr::new(I2pAddr::new("example.i2p"), 8080));
/// ```
pub fn peer_addr(&self) -> io::Result<I2pSocketAddr> {
self.inner
.peer_addr()
.map(|(d, p)| I2pSocketAddr::new(I2pAddr::new(&d), p))
}
/// Returns the socket address of the local half of this I2P connection.
///
/// # Examples
///
/// ```no_run
/// use i2p::net::{I2pAddr, I2pSocketAddr, I2pStream};
///
/// let stream = I2pStream::connect("example.i2p:8080")
/// .expect("Couldn't connect to the server...");
/// assert_eq!(stream.local_addr().unwrap(),
/// I2pSocketAddr::new(I2pAddr::new("example.i2p"), 8080));
/// ```
pub fn local_addr(&self) -> io::Result<I2pSocketAddr> {
self.inner
.local_addr()
.map(|(d, p)| I2pSocketAddr::new(I2pAddr::new(&d), p))
}
/// Shuts down the read, write, or both halves of this connection.
///
/// This function will cause all pending and future I/O on the specified
/// portions to return immediately with an appropriate value (see the
/// documentation of [`Shutdown`]).
///
/// [`Shutdown`]: ../../std/net/enum.Shutdown.html
///
/// # Examples
///
/// ```no_run
/// use std::net::Shutdown;
/// use i2p::net::I2pStream;
///
/// let stream = I2pStream::connect("127.0.0.1:8080")
/// .expect("Couldn't connect to the server...");
/// stream.shutdown(Shutdown::Both).expect("shutdown call failed");
/// ```
pub fn shutdown(&self, how: Shutdown) -> io::Result<()> {
self.inner.shutdown(how)
}
/// Creates a new independently owned handle to the underlying socket.
///
/// The returned `I2pStream` is a reference to the same stream that this
/// object references. Both handles will read and write the same stream of
/// data, and options set on one stream will be propagated to the other
/// stream.
///
/// # Examples
///
/// ```no_run
/// use i2p::net::I2pStream;
///
/// let stream = I2pStream::connect("example.i2p:8080")
/// .expect("Couldn't connect to the server...");
/// let stream_clone = stream.try_clone().expect("clone failed...");
/// ```
pub fn try_clone(&self) -> io::Result<I2pStream> {
self.inner.duplicate().map(|s| I2pStream { inner: s })
}
}
impl Read for I2pStream {
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
self.inner.read(buf)
}
}
impl Write for I2pStream {
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
self.inner.write(buf)
}
fn flush(&mut self) -> io::Result<()> {
Ok(())
}
}
impl fmt::Debug for I2pStream {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let mut res = f.debug_struct("I2pStream");
if let Ok(addr) = self.local_addr() {
res.field("addr", &addr);
}
if let Ok(peer) = self.peer_addr() {
res.field("peer", &peer);
}
res.finish()
}
pub struct I2pListener {
#[cfg(feature = "public-conn")]
pub forward: StreamForward,
#[cfg(not(feature = "public-conn"))]
forward: StreamForward,
}
impl I2pListener {
/// Creates a new `I2pListener` which will be bound to the specified
/// address.
///
/// The returned listener is ready for accepting connections.
///
/// Binding with a port number of 0 is equivalent to binding on every port.
///
/// The address type can be any implementor of `ToI2pSocketAddrs` trait. See
/// its documentation for concrete examples.
///
/// # Examples
///
/// ```no_run
/// use i2p::net::I2pListener;
///
/// let listener = I2pListener::bind("127.0.0.1:80").unwrap();
/// ```
pub fn bind<A: ToI2pSocketAddrs>(addr: A) -> io::Result<I2pListener> {
I2pListener::bind_via(DEFAULT_API, addr)
}
/// Creates a new `I2pListener` which will be bound to the specified
/// address.
///
/// The returned listener is ready for accepting connections.
///
/// Binding with a port number of 0 is equivalent to binding on every port.
///
/// The address type can be any implementor of `ToI2pSocketAddrs` trait. See
/// its documentation for concrete examples.
///
/// # Examples
///
/// ```no_run
/// use i2p::net::I2pListener;
///
/// let listener = I2pListener::bind().unwrap();
/// ```
pub fn bind() -> Result<I2pListener, Error> {
I2pListener::bind_via(DEFAULT_API)
}
pub fn bind_via<A: ToSocketAddrs, B: ToI2pSocketAddrs>(
sam_addr: A,
addr: B,
) -> io::Result<I2pListener> {
super::each_addr(sam_addr, addr, I2pListener::bind_addr)
}
pub fn bind_with_session(session: &Session) -> Result<I2pListener, Error> {
let forward = StreamForward::with_session(session)?;
Ok(I2pListener { forward })
}
fn bind_addr(_sam_addr: &SocketAddr, _addr: &I2pSocketAddr) -> io::Result<I2pListener> {
unimplemented!();
}
pub fn bind_via<A: ToSocketAddrs>(sam_addr: A) -> Result<I2pListener, Error> {
super::each_addr(sam_addr, SAMOptions::default(), I2pListener::bind_addr).map_err(|e| e.into())
}
/// Returns the local socket address of this listener.
///
/// # Examples
///
/// ```no_run
/// use i2p::net::{I2pAddr, I2pSocketAddr, I2pListener};
///
/// let listener = I2pListener::bind("127.0.0.1:8080").unwrap();
/// assert_eq!(listener.local_addr().unwrap(),
/// I2pSocketAddr::new(I2pAddr::new("example.i2p"), 8080));
/// ```
pub fn local_addr(&self) -> io::Result<I2pSocketAddr> {
unimplemented!()
}
fn bind_addr(sam_addr: &SocketAddr, options: SAMOptions) -> Result<I2pListener, Error> {
let forward = StreamForward::new(sam_addr, options)?;
Ok(I2pListener { forward })
}
/// Creates a new independently owned handle to the underlying socket.
///
/// The returned `TcpListener` is a reference to the same socket that this
/// object references. Both handles can be used to accept incoming
/// connections and options set on one listener will affect the other.
///
/// # Examples
///
/// ```no_run
/// use i2p::net::I2pListener;
///
/// let listener = I2pListener::bind("127.0.0.1:8080").unwrap();
/// let listener_clone = listener.try_clone().unwrap();
/// ```
pub fn try_clone(&self) -> io::Result<I2pListener> {
unimplemented!()
}
/// Returns the local socket address of this listener.
///
/// # Examples
///
/// ```no_run
/// use i2p::net::{I2pAddr, I2pSocketAddr, I2pListener};
///
/// let listener = I2pListener::bind().unwrap();
/// assert_eq!(listener.local_addr().unwrap(),
/// I2pSocketAddr::new(I2pAddr::new("example.i2p"), 8080));
/// ```
pub fn local_addr(&self) -> Result<I2pSocketAddr, Error> {
self.forward
.local_addr()
.map(|(d, p)| I2pSocketAddr::new(I2pAddr::new(&d), p))
}
/// Accept a new incoming connection from this listener.
///
/// This function will block the calling thread until a new TCP connection
/// is established. When established, the corresponding `TcpStream` and the
/// remote peer's address will be returned.
///
/// # Examples
///
/// ```no_run
/// use i2p::net::I2pListener;
///
/// let listener = I2pListener::bind("127.0.0.1:8080").unwrap();
/// match listener.accept() {
/// Ok((_socket, addr)) => println!("new client: {:?}", addr),
/// Err(e) => println!("couldn't get client: {:?}", e),
/// }
/// ```
pub fn accept(&self) -> io::Result<(I2pStream, I2pSocketAddr)> {
unimplemented!()
}
/// Creates a new independently owned handle to the underlying socket.
///
/// The returned `TcpListener` is a reference to the same socket that this
/// object references. Both handles can be used to accept incoming
/// connections and options set on one listener will affect the other.
///
/// # Examples
///
/// ```no_run
/// use i2p::net::I2pListener;
///
/// let listener = I2pListener::bind().unwrap();
/// let listener_clone = listener.try_clone().unwrap();
/// ```
pub fn try_clone(&self) -> Result<I2pListener, Error> {
let forward = self.forward.duplicate()?;
Ok(I2pListener { forward })
}
/// Returns an iterator over the connections being received on this
/// listener.
///
/// The returned iterator will never return [`None`] and will also not yield
/// the peer's [`I2pSocketAddr`] structure.
///
/// [`None`]: ../../std/option/enum.Option.html#variant.None
/// [`I2pSocketAddr`]: ../../std/net/struct.I2pSocketAddr.html
///
/// # Examples
///
/// ```no_run
/// use i2p::net::I2pListener;
///
/// let listener = I2pListener::bind("127.0.0.1:80").unwrap();
///
/// for stream in listener.incoming() {
/// match stream {
/// Ok(stream) => {
/// println!("new client!");
/// }
/// Err(e) => { /* connection failed */ }
/// }
/// }
/// ```
pub fn incoming(&self) -> Incoming<'_> {
Incoming { listener: self }
}
/// Accept a new incoming connection from this listener.
///
/// This function will block the calling thread until a new TCP connection
/// is established. When established, the corresponding `TcpStream` and the
/// remote peer's address will be returned.
///
/// # Examples
///
/// ```no_run
/// use i2p::net::I2pListener;
///
/// let listener = I2pListener::bind().unwrap();
/// match listener.accept() {
/// Ok((_socket, addr)) => println!("new client: {:?}", addr),
/// Err(e) => println!("couldn't get client: {:?}", e),
/// }
/// ```
pub fn accept(&self) -> Result<(I2pStream, I2pSocketAddr), Error> {
let (i2p_stream, addr) = self.forward.accept()?;
Ok((I2pStream { inner: i2p_stream }, addr))
}
/// Returns an iterator over the connections being received on this
/// listener.
///
/// The returned iterator will never return [`None`] and will also not yield
/// the peer's [`I2pSocketAddr`] structure.
///
/// [`None`]: ../../std/option/enum.Option.html#variant.None
/// [`I2pSocketAddr`]: ../../std/net/struct.I2pSocketAddr.html
///
/// # Examples
///
/// ```no_run
/// use i2p::net::I2pListener;
///
/// let listener = I2pListener::bind().unwrap();
///
/// for stream in listener.incoming() {
/// match stream {
/// Ok(stream) => {
/// println!("new client!");
/// }
/// Err(e) => { /* connection failed */ }
/// }
/// }
/// ```
pub fn incoming(&self) -> Incoming {
Incoming { listener: self }
}
}
impl<'a> Iterator for Incoming<'a> {
type Item = io::Result<I2pStream>;
fn next(&mut self) -> Option<io::Result<I2pStream>> {
Some(self.listener.accept().map(|p| p.0))
}
type Item = Result<I2pStream, Error>;
fn next(&mut self) -> Option<Result<I2pStream, Error>> {
Some(self.listener.accept().map(|p| p.0))
}
}
impl fmt::Debug for I2pListener {
fn fmt(&self, _f: &mut fmt::Formatter<'_>) -> fmt::Result {
unimplemented!()
}
/// A helper struct for creating `I2pListener`s.
///
/// # Examples
///
/// ```no_run
/// use i2p::net::{I2pListenerBuilder, I2pStream};
///
/// let listener = I2pListenerBuilder::new().build()?;
///
/// fn handle_client(stream: I2pStream) {
/// // ...
/// }
///
/// // accept connections and process them serially
/// for stream in listener.incoming() {
/// match stream {
/// Ok(stream) => {
/// handle_client(stream);
/// }
/// Err(e) => { /* connection failed */ }
/// }
/// }
/// ```
pub struct I2pListenerBuilder {
session: Option<Session>,
addrs: Vec<SocketAddr>,
options: SAMOptions,
}
impl Default for I2pListenerBuilder {
fn default() -> Self {
I2pListenerBuilder {
session: None,
addrs: vec![],
options: Default::default()
}
}
}
impl I2pListenerBuilder {
/// Build an `I2pListener` and bind to socket addresses using previously
/// defined settings.
///
/// If none of `with_session`, `with_addr` or `with_addrs` were called, the
/// listener will bind to `crate::sam::DEFAULT_API`.
///
/// If `with_options` was not called, defaults to `SAMOptions::default()`
///
/// If `with_session` was called, all other settings will be ignored.
pub fn build(mut self) -> Result<I2pListener, Error> {
if let Some(s) = self.session {
Ok(I2pListener {
forward: StreamForward::with_session(&s)?
})
}
else {
// Default to DEFAULT_API if no socket address has been set manually
if self.addrs.len() == 0 {
self.addrs.extend(ToSocketAddrs::to_socket_addrs(DEFAULT_API)?);
}
super::each_addr(
self.addrs.as_slice(),
self.options,
I2pListener::bind_addr
).map_err(|e| e.into())
}
}
/// Makes this builder recreate an I2pListener from the specified session.
/// According to the SAMv3 protocol, SAM options can only be set upon
/// session creation. Therefore, `I2pListenerBuilder::build(...)` will
/// ignore any options set using `with_options` if recreating a listener
/// using a previous session.
pub fn with_session(mut self, session: Session) -> Self {
self.session = Some(session);
self
}
/// Add `address` to the list of socket addresses to bind to
pub fn with_addr(mut self, address: SocketAddr) -> Self {
self.addrs.push(address);
self
}
/// Add all addresses derived from `addresses` to the list of socket
/// addresses to bind to
pub fn with_addrs<A: ToSocketAddrs>(mut self, addresses: A) -> Result<Self, Error> {
self.addrs.extend(addresses.to_socket_addrs()?);
Ok(self)
}
/// Use the SAMOptions specified when building the `I2pListener`
pub fn with_options(mut self, opts: SAMOptions) -> Self {
self.options = opts;
self
}
}

View File

@@ -1,12 +1,12 @@
use crate::net::{I2pAddr, I2pSocketAddr, ToI2pSocketAddrs};
pub fn isa(a: I2pAddr, p: u16) -> I2pSocketAddr {
I2pSocketAddr::new(a, p)
I2pSocketAddr::new(a, p)
}
pub fn tsa<A: ToI2pSocketAddrs>(a: A) -> Result<Vec<I2pSocketAddr>, String> {
match a.to_socket_addrs() {
Ok(a) => Ok(a.collect()),
Err(e) => Err(e.to_string()),
}
match a.to_socket_addrs() {
Ok(a) => Ok(a.collect()),
Err(e) => Err(e.to_string()),
}
}

View File

@@ -1,184 +1,186 @@
use nom::{alphanumeric, space};
use nom::{alphanumeric, alt, do_parse, named, separated_list, space, tag, take_till};
fn is_space(chr: char) -> bool {
chr == ' ' || chr == '\t'
chr == ' ' || chr == '\t'
}
fn is_next_line(chr: char) -> bool {
chr == '\n'
chr == '\n'
}
fn is_space_or_next_line(chr: char) -> bool {
is_space(chr) || is_next_line(chr)
is_space(chr) || is_next_line(chr)
}
fn is_double_quote(chr: char) -> bool {
chr == '\"'
chr == '\"'
}
named!(quoted_value <&str, &str>,
do_parse!(
tag_s!("\"") >>
val: take_till_s!(is_double_quote) >>
tag_s!("\"") >>
(val)
)
do_parse!(
tag!("\"") >>
val: take_till!(is_double_quote) >>
tag!("\"") >>
(val)
)
);
named!(value <&str, &str>, take_till_s!(is_space_or_next_line));
named!(value <&str, &str>, take_till!(is_space_or_next_line));
named!(key_value <&str, (&str, &str)>,
do_parse!(
key: alphanumeric >>
tag_s!("=") >>
val: alt!(quoted_value | value) >>
(key, val)
)
do_parse!(
key: alphanumeric >>
tag!("=") >>
val: alt!(quoted_value | value) >>
(key, val)
)
);
named!(keys_and_values<&str, Vec<(&str, &str)> >, separated_list!(space, key_value));
named!(pub sam_hello <&str, Vec<(&str, &str)> >,
do_parse!(
tag_s!("HELLO REPLY ") >>
opts: keys_and_values >>
tag_s!("\n") >>
(opts)
)
do_parse!(
tag!("HELLO REPLY ") >>
opts: keys_and_values >>
tag!("\n") >>
(opts)
)
);
named!(pub sam_session_status <&str, Vec<(&str, &str)> >,
do_parse!(
tag_s!("SESSION STATUS ") >>
opts: keys_and_values >>
tag_s!("\n") >>
(opts)
)
do_parse!(
tag!("SESSION STATUS ") >>
opts: keys_and_values >>
tag!("\n") >>
(opts)
)
);
named!(pub sam_stream_status <&str, Vec<(&str, &str)> >,
do_parse!(
tag_s!("STREAM STATUS ") >>
opts: keys_and_values >>
tag_s!("\n") >>
(opts)
)
do_parse!(
tag!("STREAM STATUS ") >>
opts: keys_and_values >>
tag!("\n") >>
(opts)
)
);
named!(pub sam_naming_reply <&str, Vec<(&str, &str)> >,
do_parse!(
tag_s!("NAMING REPLY ") >>
opts: keys_and_values >>
tag_s!("\n") >>
(opts)
)
do_parse!(
tag!("NAMING REPLY ") >>
opts: keys_and_values >>
tag!("\n") >>
(opts)
)
);
named!(pub sam_dest_reply <&str, Vec<(&str, &str)> >,
do_parse!(
tag_s!("DEST REPLY ") >>
opts: keys_and_values >>
tag_s!("\n") >>
(opts)
)
do_parse!(
tag!("DEST REPLY ") >>
opts: keys_and_values >>
tag!("\n") >>
(opts)
)
);
#[cfg(test)]
mod tests {
use nom::ErrorKind;
use nom::IResult::Done;
use nom::IResult::Error;
use nom::ErrorKind;
#[test]
fn hello() {
use crate::parsers::sam_hello;
#[test]
fn hello() {
use crate::parsers::sam_hello;
assert_eq!(
sam_hello("HELLO REPLY RESULT=OK VERSION=3.1\n"),
Done("", vec![("RESULT", "OK"), ("VERSION", "3.1")])
);
assert_eq!(
sam_hello("HELLO REPLY RESULT=NOVERSION\n"),
Done("", vec![("RESULT", "NOVERSION")])
);
assert_eq!(
sam_hello("HELLO REPLY RESULT=I2P_ERROR MESSAGE=\"Something failed\"\n"),
Done(
"",
vec![("RESULT", "I2P_ERROR"), ("MESSAGE", "Something failed")]
)
);
}
assert_eq!(
sam_hello("HELLO REPLY RESULT=OK VERSION=3.1\n"),
Ok(("", vec![("RESULT", "OK"), ("VERSION", "3.1")]))
);
assert_eq!(
sam_hello("HELLO REPLY RESULT=NOVERSION\n"),
Ok(("", vec![("RESULT", "NOVERSION")]))
);
assert_eq!(
sam_hello("HELLO REPLY RESULT=I2P_ERROR MESSAGE=\"Something failed\"\n"),
Ok((
"",
vec![("RESULT", "I2P_ERROR"), ("MESSAGE", "Something failed")]
))
);
}
#[test]
fn session_status() {
use crate::parsers::sam_session_status;
#[test]
fn session_status() {
use crate::parsers::sam_session_status;
assert_eq!(
sam_session_status("SESSION STATUS RESULT=OK DESTINATION=privkey\n"),
Done("", vec![("RESULT", "OK"), ("DESTINATION", "privkey")])
);
assert_eq!(
sam_session_status("SESSION STATUS RESULT=DUPLICATED_ID\n"),
Done("", vec![("RESULT", "DUPLICATED_ID")])
);
}
assert_eq!(
sam_session_status("SESSION STATUS RESULT=OK DESTINATION=privkey\n"),
Ok(("", vec![("RESULT", "OK"), ("DESTINATION", "privkey")]))
);
assert_eq!(
sam_session_status("SESSION STATUS RESULT=DUPLICATED_ID\n"),
Ok(("", vec![("RESULT", "DUPLICATED_ID")]))
);
}
#[test]
fn stream_status() {
use crate::parsers::sam_stream_status;
#[test]
fn stream_status() {
use crate::parsers::sam_stream_status;
assert_eq!(
sam_stream_status("STREAM STATUS RESULT=OK\n"),
Done("", vec![("RESULT", "OK")])
);
assert_eq!(
sam_stream_status(
"STREAM STATUS RESULT=CANT_REACH_PEER MESSAGE=\"Can't reach peer\"\n"
),
Done(
"",
vec![
("RESULT", "CANT_REACH_PEER"),
("MESSAGE", "Can't reach peer")
]
)
);
}
assert_eq!(
sam_stream_status("STREAM STATUS RESULT=OK\n"),
Ok(("", vec![("RESULT", "OK")]))
);
assert_eq!(
sam_stream_status(
"STREAM STATUS RESULT=CANT_REACH_PEER MESSAGE=\"Can't reach peer\"\n"
),
Ok((
"",
vec![
("RESULT", "CANT_REACH_PEER"),
("MESSAGE", "Can't reach peer")
]
))
);
}
#[test]
fn naming_reply() {
use crate::parsers::sam_naming_reply;
#[test]
fn naming_reply() {
use crate::parsers::sam_naming_reply;
assert_eq!(
sam_naming_reply("NAMING REPLY RESULT=OK NAME=name VALUE=dest\n"),
Done(
"",
vec![("RESULT", "OK"), ("NAME", "name"), ("VALUE", "dest")]
)
);
assert_eq!(
sam_naming_reply("NAMING REPLY RESULT=KEY_NOT_FOUND\n"),
Done("", vec![("RESULT", "KEY_NOT_FOUND")])
);
assert_eq!(
sam_naming_reply("NAMING REPLY RESULT=OK NAME=name VALUE=dest\n"),
Ok((
"",
vec![("RESULT", "OK"), ("NAME", "name"), ("VALUE", "dest")]
))
);
assert_eq!(
sam_naming_reply("NAMING REPLY RESULT=KEY_NOT_FOUND\n"),
Ok(("", vec![("RESULT", "KEY_NOT_FOUND")]))
);
assert_eq!(
sam_naming_reply("NAMINGREPLY RESULT=KEY_NOT_FOUND\n"),
Error(ErrorKind::Tag)
);
assert_eq!(
sam_naming_reply("NAMING REPLY RESULT=KEY_NOT_FOUND\n"),
Error(ErrorKind::Tag)
);
}
assert_eq!(
sam_naming_reply("NAMINGREPLY RESULT=KEY_NOT_FOUND\n")
.unwrap_err()
.into_error_kind(),
ErrorKind::Tag
);
assert_eq!(
sam_naming_reply("NAMING REPLY RESULT=KEY_NOT_FOUND\n")
.unwrap_err()
.into_error_kind(),
ErrorKind::Tag
);
}
#[test]
fn dest_reply() {
use crate::parsers::sam_dest_reply;
#[test]
fn dest_reply() {
use crate::parsers::sam_dest_reply;
assert_eq!(
sam_dest_reply("DEST REPLY PUB=foo PRIV=foobar\n"),
Done("", vec![("PUB", "foo"), ("PRIV", "foobar")])
);
}
assert_eq!(
sam_dest_reply("DEST REPLY PUB=foo PRIV=foobar\n"),
Ok(("", vec![("PUB", "foo"), ("PRIV", "foobar")]))
);
}
}

View File

@@ -2,228 +2,425 @@ use std::io::prelude::*;
use std::clone::Clone;
use std::collections::HashMap;
use std::io;
use std::io::{BufReader, Error, ErrorKind};
use std::io::{self, BufReader};
use std::net::{Shutdown, SocketAddr, TcpStream, ToSocketAddrs};
use std::time::Duration;
use log::debug;
use nom::IResult;
use rand::distributions::Alphanumeric;
use rand::{self, Rng};
use crate::parsers::{sam_hello, sam_naming_reply, sam_session_status, sam_stream_status};
use crate::error::{Error, ErrorKind};
use crate::net::{I2pAddr, I2pSocketAddr};
use crate::parsers::{
sam_dest_reply, sam_hello, sam_naming_reply, sam_session_status, sam_stream_status,
};
use crate::sam_options::{SAMOptions, SignatureType};
pub static DEFAULT_API: &'static str = "127.0.0.1:7656";
static SAM_MIN: &'static str = "3.0";
static SAM_MAX: &'static str = "3.1";
static SAM_MAX: &'static str = "3.2";
#[derive(Clone, Debug)]
pub enum SessionStyle {
Datagram,
Raw,
Stream,
Datagram,
Raw,
Stream,
}
#[derive(Debug)]
pub struct SamConnection {
conn: TcpStream,
#[cfg(feature = "public-conn")]
pub conn: TcpStream,
#[cfg(not(feature = "public-conn"))]
conn: TcpStream,
}
#[derive(Debug)]
pub struct Session {
sam: SamConnection,
local_dest: String,
#[cfg(feature = "public-conn")]
pub sam: SamConnection,
#[cfg(not(feature = "public-conn"))]
sam: SamConnection,
pub local_dest: String,
pub nickname: String,
}
#[derive(Debug)]
pub struct StreamConnect {
sam: SamConnection,
session: Session,
peer_dest: String,
peer_port: u16,
local_port: u16,
#[cfg(feature = "public-conn")]
pub sam: SamConnection,
#[cfg(not(feature = "public-conn"))]
sam: SamConnection,
#[cfg(feature = "public-conn")]
pub session: Session,
#[cfg(not(feature = "public-conn"))]
session: Session,
pub peer_dest: String,
pub peer_port: u16,
pub local_port: u16,
}
impl SessionStyle {
fn string(&self) -> &str {
match *self {
SessionStyle::Datagram => "DATAGRAM",
SessionStyle::Raw => "RAW",
SessionStyle::Stream => "STREAM",
}
}
fn string(&self) -> &str {
match *self {
SessionStyle::Datagram => "DATAGRAM",
SessionStyle::Raw => "RAW",
SessionStyle::Stream => "STREAM",
}
}
}
fn verify_response<'a>(vec: &'a [(&str, &str)]) -> Result<HashMap<&'a str, &'a str>, Error> {
let new_vec = vec.clone();
let map: HashMap<&str, &str> = new_vec.iter().map(|&(k, v)| (k, v)).collect();
let res = map.get("RESULT").unwrap_or(&"OK").clone();
let msg = map.get("MESSAGE").unwrap_or(&"").clone();
match res {
"OK" => Ok(map),
"CANT_REACH_PEER" | "KEY_NOT_FOUND" | "PEER_NOT_FOUND" => {
Err(Error::new(ErrorKind::NotFound, msg))
}
"DUPLICATED_DEST" => Err(Error::new(ErrorKind::AddrInUse, msg)),
"INVALID_KEY" | "INVALID_ID" => Err(Error::new(ErrorKind::InvalidInput, msg)),
"TIMEOUT" => Err(Error::new(ErrorKind::TimedOut, msg)),
"I2P_ERROR" => Err(Error::new(ErrorKind::Other, msg)),
_ => Err(Error::new(ErrorKind::Other, msg)),
}
let new_vec = vec.clone();
let map: HashMap<&str, &str> = new_vec.iter().map(|&(k, v)| (k, v)).collect();
let res = map.get("RESULT").unwrap_or(&"OK").clone();
let msg = map.get("MESSAGE").unwrap_or(&"").clone();
match res {
"OK" => Ok(map),
"CANT_REACH_PEER" => Err(ErrorKind::SAMCantReachPeer(msg.to_string()).into()),
"KEY_NOT_FOUND" => Err(ErrorKind::SAMKeyNotFound(msg.to_string()).into()),
"PEER_NOT_FOUND" => Err(ErrorKind::SAMPeerNotFound(msg.to_string()).into()),
"DUPLICATED_DEST" => Err(ErrorKind::SAMDuplicatedDest(msg.to_string()).into()),
"INVALID_KEY" => Err(ErrorKind::SAMInvalidKey(msg.to_string()).into()),
"INVALID_ID" => Err(ErrorKind::SAMInvalidId(msg.to_string()).into()),
"TIMEOUT" => Err(ErrorKind::SAMTimeout(msg.to_string()).into()),
"I2P_ERROR" => Err(ErrorKind::SAMI2PError(msg.to_string()).into()),
_ => Err(ErrorKind::SAMInvalidMessage(msg.to_string()).into()),
}
}
impl SamConnection {
fn send<F>(&mut self, msg: String, reply_parser: F) -> Result<HashMap<String, String>, Error>
where
F: Fn(&str) -> IResult<&str, Vec<(&str, &str)>>,
{
debug!("-> {}", &msg);
self.conn.write_all(&msg.into_bytes())?;
fn send<F>(&mut self, msg: String, reply_parser: F) -> Result<HashMap<String, String>, Error>
where
F: Fn(&str) -> IResult<&str, Vec<(&str, &str)>>,
{
debug!("-> {}", &msg);
self.conn.write_all(&msg.into_bytes())?;
let mut reader = BufReader::new(&self.conn);
let mut buffer = String::new();
reader.read_line(&mut buffer)?;
debug!("<- {}", &buffer);
let mut reader = BufReader::new(&self.conn);
let mut buffer = String::new();
reader.read_line(&mut buffer)?;
debug!("<- {}", &buffer);
let response = reply_parser(&buffer);
let vec_opts = response.unwrap().1;
verify_response(&vec_opts).map(|m| {
m.iter()
.map(|(k, v)| (k.to_string(), v.to_string()))
.collect()
})
}
let vec_opts = reply_parser(&buffer)?.1;
verify_response(&vec_opts).map(|m| {
m.iter()
.map(|(k, v)| (k.to_string(), v.to_string()))
.collect()
})
}
fn handshake(&mut self) -> Result<HashMap<String, String>, Error> {
let hello_msg = format!(
"HELLO VERSION MIN={min} MAX={max} \n",
min = SAM_MIN,
max = SAM_MAX
);
self.send(hello_msg, sam_hello)
}
fn handshake(&mut self) -> Result<HashMap<String, String>, Error> {
let hello_msg = format!(
"HELLO VERSION MIN={min} MAX={max} \n",
min = SAM_MIN,
max = SAM_MAX
);
self.send(hello_msg, sam_hello)
}
pub fn connect<A: ToSocketAddrs>(addr: A) -> Result<SamConnection, Error> {
let tcp_stream = TcpStream::connect(addr)?;
pub fn connect<A: ToSocketAddrs>(addr: A) -> Result<SamConnection, Error> {
let tcp_stream = TcpStream::connect(addr)?;
let mut socket = SamConnection { conn: tcp_stream };
let mut socket = SamConnection { conn: tcp_stream };
socket.handshake()?;
socket.handshake()?;
Ok(socket)
}
Ok(socket)
}
// TODO: Implement a lookup table
pub fn naming_lookup(&mut self, name: &str) -> Result<String, Error> {
let naming_lookup_msg = format!("NAMING LOOKUP NAME={name} \n", name = name);
let ret = self.send(naming_lookup_msg, sam_naming_reply)?;
Ok(ret["VALUE"].clone())
}
// TODO: Implement a lookup table
pub fn naming_lookup(&mut self, name: &str) -> Result<String, Error> {
let create_naming_lookup_msg = format!("NAMING LOOKUP NAME={name} \n", name = name);
let ret = self.send(create_naming_lookup_msg, sam_naming_reply)?;
Ok(ret["VALUE"].clone())
}
pub fn generate_destination(
&mut self,
signature_type: SignatureType,
) -> Result<(String, String), Error> {
let dest_gen_msg = format!(
"DEST GENERATE SIGNATURE_TYPE={signature_type} \n",
signature_type = signature_type.to_string(),
);
let ret = self.send(dest_gen_msg, sam_dest_reply)?;
Ok((ret["PUB"].clone(), ret["PRIV"].clone()))
}
pub fn duplicate(&self) -> io::Result<SamConnection> {
self.conn.try_clone().map(|s| SamConnection { conn: s })
}
pub fn set_nonblocking(&self, nonblocking: bool) -> Result<(), Error> {
self.conn.set_nonblocking(nonblocking).map_err(|e| e.into())
}
pub fn set_read_timeout(&self, duration: Option<Duration>) -> std::io::Result<()> {
self.conn.set_read_timeout(duration)
}
pub fn set_write_timeout(&self, duration: Option<Duration>) -> std::io::Result<()> {
self.conn.set_write_timeout(duration)
}
pub fn duplicate(&self) -> Result<SamConnection, Error> {
self.conn
.try_clone()
.map(|s| SamConnection { conn: s })
.map_err(|e| e.into())
}
/// attempts to return a handle to the underlying socket
pub fn try_clone(&self) -> std::io::Result<TcpStream> {
self.conn.try_clone()
}
}
impl Session {
pub fn create<A: ToSocketAddrs>(
sam_addr: A,
destination: &str,
nickname: &str,
style: SessionStyle,
) -> Result<Session, Error> {
let mut sam = SamConnection::connect(sam_addr).unwrap();
let create_session_msg = format!(
"SESSION CREATE STYLE={style} ID={nickname} DESTINATION={destination} \n",
style = style.string(),
nickname = nickname,
destination = destination
);
/// Create a new session using all provided parameters
pub fn create<A: ToSocketAddrs>(
sam_addr: A,
destination: &str,
nickname: &str,
style: SessionStyle,
options: SAMOptions,
) -> Result<Session, Error> {
let mut sam = SamConnection::connect(sam_addr)?;
let create_session_msg = format!(
// values for SIGNATURE_TYPE and leaseSetEncType taken from
// https://github.com/eyedeekay/goSam/blob/62cade9ebc26e48ff32a517ef94212fc90aa92cd/client.go#L169
// https://github.com/eyedeekay/goSam/blob/62cade9ebc26e48ff32a517ef94212fc90aa92cd/client.go#L166
"SESSION CREATE STYLE={style} ID={nickname} DESTINATION={destination} {options}\n",
style = style.string(),
nickname = nickname,
destination = destination,
options = options.options(),
);
sam.send(create_session_msg, sam_session_status)?;
sam.send(create_session_msg, sam_session_status)?;
let local_dest = sam.naming_lookup("ME")?;
let local_dest = sam.naming_lookup("ME")?;
Ok(Session {
sam: sam,
local_dest: local_dest,
})
}
Ok(Session {
sam: sam,
local_dest: local_dest,
nickname: nickname.to_string(),
})
}
pub fn sam_api(&self) -> io::Result<SocketAddr> {
self.sam.conn.peer_addr()
}
/// Create a new session identified by the provided destination. Auto-generates
/// a nickname uniquely associated with the new session.
pub fn from_destination<A: ToSocketAddrs>(
sam_addr: A,
destination: &str,
options: SAMOptions,
) -> Result<Session, Error> {
Self::create(
sam_addr,
destination,
&nickname(),
SessionStyle::Stream,
options,
)
}
pub fn naming_lookup(&mut self, name: &str) -> io::Result<String> {
self.sam.naming_lookup(name)
}
/// Convenience constructor to create a new transient session with an
/// auto-generated nickname.
pub fn transient<A: ToSocketAddrs>(sam_addr: A, options: SAMOptions) -> Result<Session, Error> {
Self::create(
sam_addr,
"TRANSIENT",
&nickname(),
SessionStyle::Stream,
options,
)
}
pub fn duplicate(&self) -> io::Result<Session> {
self.sam.duplicate().map(|s| Session {
sam: s,
local_dest: self.local_dest.clone(),
})
}
pub fn sam_api(&self) -> Result<SocketAddr, Error> {
self.sam.conn.peer_addr().map_err(|e| e.into())
}
pub fn naming_lookup(&mut self, name: &str) -> Result<String, Error> {
self.sam.naming_lookup(name)
}
pub fn duplicate(&self) -> Result<Session, Error> {
self.sam
.duplicate()
.map(|s| Session {
sam: s,
local_dest: self.local_dest.clone(),
nickname: self.nickname.clone(),
})
.map_err(|e| e.into())
}
/// attempts to return a handle to the underlying socket
pub fn try_clone(&self) -> std::io::Result<TcpStream> {
self.sam.try_clone()
}
}
impl StreamConnect {
pub fn new<A: ToSocketAddrs>(
sam_addr: A,
destination: &str,
port: u16,
nickname: &str,
) -> io::Result<StreamConnect> {
let mut session = Session::create(sam_addr, "TRANSIENT", nickname, SessionStyle::Stream)?;
/// Create a new SAM client connection to the provided destination and port.
/// Also creates a new transient session to support the connection.
pub fn new<A: ToSocketAddrs>(
sam_addr: A,
destination: &str,
port: u16,
options: SAMOptions,
) -> Result<StreamConnect, Error> {
let session = Session::transient(sam_addr, options)?;
Self::with_session(&session, destination, port)
}
let mut sam = SamConnection::connect(session.sam_api()?).unwrap();
let create_stream_msg = format!(
"STREAM CONNECT ID={nickname} DESTINATION={destination} SILENT=false TO_PORT={port}\n",
nickname = nickname,
destination = destination,
port = port
);
/// Create a new SAM client connection to the provided destination and port
/// using the provided session.
pub fn with_session(session: &Session, dest: &str, port: u16) -> Result<StreamConnect, Error> {
let mut sam = SamConnection::connect(session.sam_api()?).unwrap();
let dest = sam.naming_lookup(dest)?;
sam.send(create_stream_msg, sam_stream_status)?;
let mut stream_msg = format!(
"STREAM CONNECT ID={nickname} DESTINATION={destination} SILENT=false",
nickname = session.nickname,
destination = dest,
);
if port > 0 {
stream_msg.push_str(&format!(" TO_PORT={port}\n", port = port));
} else {
stream_msg.push_str("\n");
}
let peer_dest = session.naming_lookup(destination)?;
sam.send(stream_msg, sam_stream_status)?;
Ok(StreamConnect {
sam: sam,
session: session,
peer_dest: peer_dest,
peer_port: port,
local_port: 0,
})
}
Ok(StreamConnect {
sam: sam,
session: session.duplicate()?,
peer_dest: dest,
peer_port: port,
local_port: 0,
})
}
pub fn peer_addr(&self) -> io::Result<(String, u16)> {
Ok((self.peer_dest.clone(), self.peer_port))
}
pub fn peer_addr(&self) -> Result<(String, u16), Error> {
Ok((self.peer_dest.clone(), self.peer_port))
}
pub fn local_addr(&self) -> io::Result<(String, u16)> {
Ok((self.session.local_dest.clone(), self.local_port))
}
pub fn local_addr(&self) -> Result<(String, u16), Error> {
Ok((self.session.local_dest.clone(), self.local_port))
}
pub fn shutdown(&self, how: Shutdown) -> io::Result<()> {
self.sam.conn.shutdown(how)
}
pub fn set_nonblocking(&self, nonblocking: bool) -> Result<(), Error> {
self.sam.set_nonblocking(nonblocking)
}
pub fn set_read_timeout(&self, duration: Option<Duration>) -> std::io::Result<()> {
self.sam.set_read_timeout(duration)
}
pub fn set_write_timeout(&self, duration: Option<Duration>) -> std::io::Result<()> {
self.sam.set_write_timeout(duration)
}
pub fn shutdown(&self, how: Shutdown) -> Result<(), Error> {
self.sam.conn.shutdown(how).map_err(|e| e.into())
}
pub fn duplicate(&self) -> io::Result<StreamConnect> {
Ok(StreamConnect {
sam: self.sam.duplicate()?,
session: self.session.duplicate()?,
peer_dest: self.peer_dest.clone(),
peer_port: self.peer_port,
local_port: self.local_port,
})
}
pub fn duplicate(&self) -> Result<StreamConnect, Error> {
Ok(StreamConnect {
sam: self.sam.duplicate()?,
session: self.session.duplicate()?,
peer_dest: self.peer_dest.clone(),
peer_port: self.peer_port,
local_port: self.local_port,
})
}
/// calls try_clone against the Session object
pub fn try_clone_session(&self) -> std::io::Result<TcpStream> {
self.session.try_clone()
}
/// calls try_clone against the SamConnection object
pub fn try_clone_sam(&self) -> std::io::Result<TcpStream> {
self.sam.try_clone()
}
}
impl Read for StreamConnect {
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
self.sam.conn.read(buf)
}
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
self.sam.conn.read(buf)
}
}
impl Write for StreamConnect {
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
self.sam.conn.write(buf)
}
fn flush(&mut self) -> io::Result<()> {
self.sam.conn.flush()
}
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
self.sam.conn.write(buf)
}
fn flush(&mut self) -> io::Result<()> {
self.sam.conn.flush()
}
}
pub struct StreamForward {
session: Session,
}
impl StreamForward {
pub fn new<A: ToSocketAddrs>(sam_addr: A, options: SAMOptions) -> Result<StreamForward, Error> {
Ok(StreamForward {
session: Session::transient(sam_addr, options)?,
})
}
/// Create a new SAM client connection to the provided destination and port
/// using the provided session.
pub fn with_session(session: &Session) -> Result<StreamForward, Error> {
Ok(StreamForward {
session: session.duplicate()?,
})
}
pub fn accept(&self) -> Result<(StreamConnect, I2pSocketAddr), Error> {
let mut sam_conn = SamConnection::connect(self.session.sam_api()?).unwrap();
let accept_stream_msg = format!(
"STREAM ACCEPT ID={nickname} SILENT=false\n",
nickname = self.session.nickname,
);
sam_conn.send(accept_stream_msg, sam_stream_status)?;
let mut stream = StreamConnect {
sam: sam_conn,
session: self.session.duplicate()?,
peer_dest: "".to_string(),
// port only provided with SAM v3.2+ (not on i2pd)
peer_port: 0,
local_port: 0,
};
// TODO use a parser combinator, perhaps move down to sam.rs
let destination: String = {
let mut buf_read = io::BufReader::new(stream.duplicate()?);
let mut dest_line = String::new();
buf_read.read_line(&mut dest_line)?;
dest_line.split(" ").next().unwrap_or("").trim().to_string()
};
if destination.is_empty() {
return Err(
ErrorKind::SAMKeyNotFound("No b64 destination in accept".to_string()).into(),
);
}
let addr = I2pSocketAddr::new(I2pAddr::from_b64(&destination)?, 0);
stream.peer_dest = destination;
Ok((stream, addr))
}
pub fn local_addr(&self) -> Result<(String, u16), Error> {
Ok((self.session.local_dest.clone(), 0))
}
pub fn duplicate(&self) -> Result<StreamForward, Error> {
Ok(StreamForward {
session: self.session.duplicate()?,
})
}
}
pub fn nickname() -> String {
let suffix: String = rand::thread_rng()
.sample_iter(&Alphanumeric)
.take(8)
.collect();
format!("i2prs-{}", suffix)
}

695
src/sam_options.rs Normal file
View File

@@ -0,0 +1,695 @@
//! objects used for configuration SAM sessions
//! I2CP client and router options taken from https://geti2p.net/en/docs/protocol/i2cp
//! SAMv3 options taken from https://geti2p.net/en/docs/api/samv3#options
use serde_derive::{Deserialize, Serialize};
/// options used when interacting with the SAM bridge
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SAMOptions {
pub from_port: Option<u16>,
pub to_port: Option<u16>,
pub i2cp_options: Option<I2CPOptions>,
pub signature_type: SignatureType,
}
#[derive(Debug, Default, Clone, Serialize, Deserialize)]
pub struct I2CPOptions {
pub router_options: Option<I2CPRouterOptions>,
pub client_options: Option<I2CPClientOptions>,
}
#[derive(Debug, Default, Clone, Serialize, Deserialize)]
pub struct I2CPRouterOptions {
/// The timeout (ms) for all sent messages. Unused. See the protocol specification for per-message settings.
pub client_message_timeout: Option<u32>,
pub crypto_options: Option<I2CPRouterCryptoOptions>,
/// Should generally be set to true for clients and false for servers
pub dont_publish_lease_set: Option<bool>,
/// If true, the router just sends the MessagePayload instead of sending a MessageStatus and awaiting a ReceiveMessageBegin.
pub fast_receive: Option<bool>,
/// The type of authentication for encrypted LS2. 0 for no per-client authentication (the default); 1 for DH per-client authentication; 2 for PSK per-client authentication. See proposal 123.
pub lease_set_auth_type: Option<LeaseSetAuthType>,
/// The encryption type to be used, as of 0.9.38. Interpreted client-side, but also passed to the router in the SessionConfig, to declare intent and check support. As of 0.9.39, may be comma-separated values for multiple types. See PublicKey in common strutures spec for values. See proposals 123, 144, and 145.
pub lease_set_enc_type: Option<LeaseSetEncType>,
/// The expiration of the offline signature, 4 bytes, seconds since the epoch. See proposal 123.
pub lease_set_offline_expiration: Option<LeaseSetOfflineExpiration>,
/// The base 64 of the offline signature. See proposal 123.
pub lease_set_offline_signature: Option<LeaseSetOfflineSignature>,
/// A base 64 X25519 private key for the router to use to decrypt the encrypted LS2 locally, only if per-client authentication is enabled. Optionally preceded by the key type and ':'. Only "ECIES_X25519:" is supported, which is the default. See proposal 123. Do not confuse with i2cp.leaseSetPrivateKey which is for the leaseset encryption keys.
pub lease_set_priv_key: Option<LeaseSetPrivKey>,
/// Base 64 encoded UTF-8 secret used to blind the leaseset address. See proposal 123.
pub lease_set_secret: Option<LeaseSetSecret>,
/// The base 64 of the transient private key, prefixed by an optional sig type number or name, default DSA_SHA1. See proposal 123.
pub lease_set_transient_public_key: Option<LeaseSetTransientPublicKey>,
/// The type of leaseset to be sent in the CreateLeaseSet2 Message. Interpreted client-side, but also passed to the router in the SessionConfig, to declare intent and check support. See proposal 123.
pub lease_set_type: Option<LeaseSetType>,
/// Guaranteed is disabled; None implemented in 0.8.1; the streaming lib default is None as of 0.8.1, the client side default is None as of 0.9.4
pub message_reliability: Option<MessageReliability>,
pub username: Option<String>,
pub password: Option<String>,
/// inbound tunnel optoins
pub inbound: Option<I2CPTunnelInboundOptions>,
pub outbound: Option<I2CPTunnelOutboundOptions>,
/// Set to false to disable ever bundling a reply LeaseSet. For clients that do not publish their LeaseSet, this option must be true for any reply to be possible. "true" is also recommended for multihomed servers with long connection times.
///
/// Setting to "false" may save significant outbound bandwidth, especially if the client is configured with a large number of inbound tunnels (Leases). If replies are still required, this may shift the bandwidth burden to the far-end client and the floodfill. There are several cases where "false" may be appropriate:
///
/// Unidirectional communication, no reply required
/// LeaseSet is published and higher reply latency is acceptable
/// LeaseSet is published, client is a "server", all connections are inbound so the connecting far-end destination obviously has the leaseset already. Connections are either short, or it is acceptable for latency on a long-lived connection to temporarily increase while the other end re-fetches the LeaseSet after expiration. HTTP servers may fit these requirements.
///
pub should_bundle_reply_info: Option<bool>,
}
#[derive(Debug, Default, Clone, Serialize, Deserialize)]
pub struct I2CPClientOptions {
/// (ms) Idle time required (default 30 minutes)
pub close_idle_time: Option<u64>,
/// Close I2P session when idle
pub close_on_idle: Option<bool>,
/// Encrypt the lease
pub encrypt_lease_set: Option<bool>,
/// If true, the router just sends the MessagePayload instead of sending a MessageStatus and awaiting a ReceiveMessageBegin.
pub fast_receive: Option<bool>,
/// Gzip outbound data
pub gzip: Option<bool>,
/// The type of authentication for encrypted LS2. 0 for no per-client authentication (the default); 1 for DH per-client authentication; 2 for PSK per-client authentication. See proposal 123.
pub lease_set_auth_type: Option<LeaseSetAuthType>,
/// The sig type of the blinded key for encrypted LS2. Default depends on the destination sig type. See proposal 123.
pub lease_set_blinded_type: Option<LeaseSetBlindedType>,
/// The encryption type to be used, as of 0.9.38. Interpreted client-side, but also passed to the router in the SessionConfig, to declare intent and check support. As of 0.9.39, may be comma-separated values for multiple types. See also i2cp.leaseSetPrivateKey. See PublicKey in common strutures spec for values. See proposals 123, 144, and 145.
pub lease_set_enc_type: Option<LeaseSetEncType>,
/// For encrypted leasesets. Base 64 SessionKey (44 characters)
pub lease_set_key: Option<LeaseSetKey>,
/// Base 64 private keys for encryption. Optionally preceded by the encryption type name or number and ':'. For LS1, only one key is supported, and only "0:" or "ELGAMAL_2048:" is supported, which is the default. As of 0.9.39, for LS2, multiple keys may be comma-separated, and each key must be a different encryption type. I2CP will generate the public key from the private key. Use for persistent leaseset keys across restarts. See proposals 123, 144, and 145. See also i2cp.leaseSetEncType. Do not confuse with i2cp.leaseSetPrivKey which is for encrypted LS2.
pub lease_set_private_key: Option<LeaseSetPrivateKey>,
/// Base 64 encoded UTF-8 secret used to blind the leaseset address. See proposal 123.
pub lease_set_secret: Option<LeaseSetSecret>,
/// The type of leaseset to be sent in the CreateLeaseSet2 Message. Interpreted client-side, but also passed to the router in the SessionConfig, to declare intent and check support. See proposal 123.
pub lease_set_signing_private_key: Option<LeaseSetSigningPrivateKey>,
/// Guaranteed is disabled; None implemented in 0.8.1; None is the default as of 0.9.4
pub message_reliability: Option<MessageReliability>,
/// (ms) Idle time required (default 20 minutes, minimum 5 minutes)
pub reduce_idle_time: Option<u64>,
/// Reduce tunnel quantity when idle
pub reduce_on_idle: Option<bool>,
/// Tunnel quantity when reduced (applies to both inbound and outbound)
pub reduce_quantity: Option<u8>,
/// Connect to the router using SSL. If the client is running in the same JVM as a router, this option is ignored, and the client connects to that router internally.
pub ssl: Option<bool>,
/// Router hostname. If the client is running in the same JVM as a router, this option is ignored, and the client connects to that router internally
pub tcp_host: Option<String>,
/// Router I2CP port. If the client is running in the same JVM as a router, this option is ignored, and the client connects to that router internally.
pub tcp_port: Option<u8>,
}
#[derive(Debug, Default, Clone, Serialize, Deserialize)]
pub struct I2CPRouterCryptoOptions {
/// Minimum number of ElGamal/AES Session Tags before we send more. Recommended: approximately tagsToSend * 2/3
pub low_tag_threshold: Option<u8>,
/// Inbound tag window for ECIES-X25519-AEAD-Ratchet. Local inbound tagset size. See proposal 144.
pub ratchet_inbound_tags: Option<u64>,
/// Outbound tag window for ECIES-X25519-AEAD-Ratchet. Advisory to send to the far-end in the options block. See proposal 144.
pub ratchet_outbound_tags: Option<u64>,
/// Number of ElGamal/AES Session Tags to send at a time. For clients with relatively low bandwidth per-client-pair (IRC, some UDP apps), this may be set lower.
pub tags_to_send: Option<u8>,
}
#[derive(Debug, Default, Clone, Serialize, Deserialize)]
pub struct I2CPTunnelInboundOptions {
/// If incoming zero hop tunnel is allowed
pub allow_zero_hop: Option<bool>,
/// Number of redundant fail-over for tunnels in
pub backup_quantity: Option<u8>,
/// Number of IP bytes to match to determine if two routers should not be in the same tunnel. 0 to disable.
pub ip_restriction: Option<u8>,
/// Length of tunnels in
pub length: Option<u8>,
/// Random amount to add or subtract to the length of tunnels in. A positive number x means add a random amount from 0 to x inclusive. A negative number -x means add a random amount from -x to x inclusive. The router will limit the total length of the tunnel to 0 to 7 inclusive. The default variance was 1 prior to release 0.7.6.
pub length_variance: Option<i8>,
/// Number of tunnels in. Limit was increased from 6 to 16 in release 0.9; however, numbers higher than 6 are incompatible with older releases.
pub quantity: Option<u8>,
/// Used for consistent peer ordering across restarts.
pub random_key: Option<String>,
}
#[derive(Debug, Default, Clone, Serialize, Deserialize)]
pub struct I2CPTunnelOutboundOptions {
/// If outgoing zero hop tunnel is allowed
pub allow_zero_hop: Option<bool>,
/// Number of redundant fail-over for tunnels out
pub backup_quantity: Option<u8>,
/// Number of IP bytes to match to determine if two routers should not be in the same tunnel. 0 to disable.
pub ip_restriction: Option<u8>,
/// Length of tunnels out
pub length: Option<u8>,
/// Random amount to add or subtract to the length of tunnels in. A positive number x means add a random amount from 0 to x inclusive. A negative number -x means add a random amount from -x to x inclusive. The router will limit the total length of the tunnel to 0 to 7 inclusive. The default variance was 1 prior to release 0.7.6.
pub length_variance: Option<i8>,
/// Priority adjustment for outbound messages. Higher is higher priority.
pub priority: Option<i8>,
/// Number of tunnels in. Limit was increased from 6 to 16 in release 0.9; however, numbers higher than 6 are incompatible with older releases.
pub quantity: Option<u8>,
/// Used for consistent peer ordering across restarts.
pub random_key: Option<String>,
}
#[derive(Debug, Default, Clone, Serialize, Deserialize)]
/// The base 64 of the offline signature. See proposal 123.
pub struct LeaseSetOfflineSignature(String);
#[derive(Debug, Clone, Serialize, Deserialize)]
/// The encryption type to be used, as of 0.9.38. Interpreted client-side, but also passed to the router in the SessionConfig, to declare intent and check support. As of 0.9.39, may be comma-separated values for multiple types. See PublicKey in common strutures spec for values. See proposals 123, 144, and 145.
/// https://doc.rust-lang.org/book/ch19-03-advanced-traits.html#using-the-newtype-pattern-to-implement-external-traits-on-external-types
pub struct LeaseSetEncType(String);
#[derive(Debug, Default, Clone, Serialize, Deserialize)]
/// A base 64 X25519 private key for the router to use to decrypt the encrypted LS2 locally, only if per-client authentication is enabled. Optionally preceded by the key type and ':'. Only "ECIES_X25519:" is supported, which is the default. See proposal 123. Do not confuse with i2cp.leaseSetPrivateKey which is for the leaseset encryption keys.
/// https://doc.rust-lang.org/book/ch19-03-advanced-traits.html#using-the-newtype-pattern-to-implement-external-traits-on-external-types
pub struct LeaseSetPrivKey(String);
#[derive(Debug, Default, Clone, Serialize, Deserialize)]
/// Base 64 private keys for encryption. Optionally preceded by the encryption type name or number and ':'. For LS1, only one key is supported, and only "0:" or "ELGAMAL_2048:" is supported, which is the default. As of 0.9.39, for LS2, multiple keys may be comma-separated, and each key must be a different encryption type. I2CP will generate the public key from the private key. Use for persistent leaseset keys across restarts. See proposals 123, 144, and 145. See also i2cp.leaseSetEncType. Do not confuse with i2cp.leaseSetPrivKey which is for encrypted LS2.
pub struct LeaseSetPrivateKey(String);
#[derive(Debug, Default, Clone, Serialize, Deserialize)]
/// For encrypted leasesets. Base 64 SessionKey (44 characters)
pub struct LeaseSetKey(String);
#[derive(Debug, Default, Clone, Serialize, Deserialize)]
/// Base 64 encoded UTF-8 secret used to blind the leaseset address. See proposal 123.
/// https://doc.rust-lang.org/book/ch19-03-advanced-traits.html#using-the-newtype-pattern-to-implement-external-traits-on-external-types
pub struct LeaseSetSecret(String);
#[derive(Debug, Default, Clone, Serialize, Deserialize)]
/// The base 64 of the transient private key, prefixed by an optional sig type number or name, default DSA_SHA1. See proposal 123.
/// https://doc.rust-lang.org/book/ch19-03-advanced-traits.html#using-the-newtype-pattern-to-implement-external-traits-on-external-types
pub struct LeaseSetTransientPublicKey(String);
#[derive(Debug, Default, Clone, Serialize, Deserialize)]
/// Base 64 private key for signatures. Optionally preceded by the key type and ':'. DSA_SHA1 is the default. Key type must match the signature type in the destination. I2CP will generate the public key from the private key. Use for persistent leaseset keys across restarts.
pub struct LeaseSetSigningPrivateKey(String);
/// The expiration of the offline signature, 4 bytes, seconds since the epoch. See proposal 123.
pub type LeaseSetOfflineExpiration = [u8; 4];
#[derive(Debug, Clone, Serialize, Deserialize)]
/// The type of leaseset to be sent in the CreateLeaseSet2 Message. Interpreted client-side, but also passed to the router in the SessionConfig, to declare intent and check support. See proposal 123.
pub struct LeaseSetType(u8);
#[derive(Debug, Default, Clone, Serialize, Deserialize)]
/// The sig type of the blinded key for encrypted LS2. Default depends on the destination sig type. See proposal 123.
pub struct LeaseSetBlindedType(u16);
/// The type of authentication for encrypted LS2. 0 for no per-client authentication (the default); 1 for DH per-client authentication; 2 for PSK per-client authentication. See proposal 123.
#[derive(Debug, Clone, Serialize, Deserialize)]
#[repr(u64)]
pub enum LeaseSetAuthType {
NoPerClient = 0_u64,
DHPerClient = 1_u64,
PSKPerClient = 2_u64,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum SignatureType {
DsaSha1,
EcdsaSha256P256,
EcdsaSha384P384,
EcdsaSha512P21,
EdDsaSha512Ed25519,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
/// Guaranteed is disabled; None implemented in 0.8.1; the streaming lib default is None as of 0.8.1, the client side default is None as of 0.9.4
pub enum MessageReliability {
BestEffort,
None,
}
impl SignatureType {
fn string(&self) -> &str {
match self {
Self::DsaSha1 => "DSA_SHA1",
Self::EcdsaSha256P256 => "ECDSA_SHA256_P256",
Self::EcdsaSha384P384 => "ECDSA_SHA384_P384",
Self::EcdsaSha512P21 => "ECDSA_SHA512_P521",
Self::EdDsaSha512Ed25519 => "EdDSA_SHA512_Ed25519",
}
}
}
/// returns the default settings for a connection to the
/// SAM bridge. Intentionally the only configured values are
/// the signature type, and lease set encryption types. All other
/// "default" options will be handled from within the SAM bridge itself,
/// this is needed because during testing sending no values and letting defaults
/// be determined entirely by the router resulted in being unable to connect
/// to destination leasesets
impl Default for SAMOptions {
fn default() -> Self {
SAMOptions {
to_port: None,
from_port: None,
i2cp_options: Some(I2CPOptions {
client_options: Some(I2CPClientOptions {
lease_set_enc_type: Some(LeaseSetEncType::default()),
..Default::default()
}),
router_options: Some(I2CPRouterOptions {
lease_set_enc_type: Some(LeaseSetEncType::default()),
..Default::default()
}),
}),
signature_type: SignatureType::EdDsaSha512Ed25519,
}
}
}
impl SAMOptions {
/// parses the SAMOptions object returning
/// any client-side and router-side options as defined in the
/// i2cp specs https://geti2p.net/en/docs/protocol/i2cp
pub fn options(&self) -> String {
self.to_string()
}
}
impl I2CPOptions {
pub fn string(&self) -> String {
let mut options = String::default();
if let Some(router_options) = &self.router_options {
let router_options_str = router_options.string();
if router_options_str != "" {
options.push_str(router_options_str.as_str());
}
}
if let Some(client_options) = &self.client_options {
let client_options_str = client_options.string();
if client_options_str != "" {
options.push_str(client_options_str.as_str());
}
}
options
}
}
impl I2CPRouterOptions {
pub fn string(&self) -> String {
let mut options = String::default();
if let Some(client_message_timeout) = &self.client_message_timeout {
options.push_str(&format!("clientMessageTimeout={} ", client_message_timeout));
}
if let Some(crypto_options) = &self.crypto_options {
let crypto_options_str = crypto_options.string();
if crypto_options_str != "" {
options.push_str(&crypto_options_str.as_str());
}
}
if let Some(dont_publish_lease_set) = &self.dont_publish_lease_set {
options.push_str(&format!(
"i2cp.dontPublishLeaseSet={} ",
dont_publish_lease_set
));
}
if let Some(fast_receive) = &self.fast_receive {
options.push_str(&format!("i2cp.fastReceive={} ", fast_receive));
}
if let Some(lease_set_auth_type) = &self.lease_set_auth_type {
options.push_str(&format!(
"i2cp.leaseSetAuthType={} ",
lease_set_auth_type.to_string()
));
}
if let Some(lease_set_enc_type) = &self.lease_set_enc_type {
options.push_str(&format!(
"i2cp.leaseSetEncType={} ",
lease_set_enc_type.to_string()
));
}
if let Some(lease_set_offline_expiration) = &self.lease_set_offline_expiration {
options.push_str(&format!(
"i2cp.leaseSetOfflineExpiration={} ",
String::from_utf8(lease_set_offline_expiration[..].to_vec()).unwrap()
))
}
if let Some(lease_set_priv_key) = &self.lease_set_priv_key {
options.push_str(&format!(
"i2cp.leaseSetPrivKey={} ",
lease_set_priv_key.to_string()
))
}
if let Some(lease_set_secret) = &self.lease_set_secret {
options.push_str(&format!(
"i2cp.leaseSetSecret={} ",
lease_set_secret.to_string()
));
}
if let Some(lease_set_transient_public_key) = &self.lease_set_transient_public_key {
options.push_str(&format!(
"i2cp.leaseSetTransientPublicKey={} ",
lease_set_transient_public_key.to_string()
));
}
if let Some(lease_set_type) = &self.lease_set_type {
options.push_str(&format!(
"i2cp.leaseSetType={} ",
lease_set_type.to_string()
));
}
if let Some(message_reliability) = &self.message_reliability {
options.push_str(&format!(
"i2cp.messageReliability={} ",
message_reliability.to_string()
));
}
if let Some(password) = &self.password {
options.push_str(&format!("i2cp.password={} ", password));
}
if let Some(username) = &self.username {
options.push_str(&format!("i2cp.username={} ", username));
}
if let Some(inbound) = &self.inbound {
let inbound_str = inbound.string();
if inbound_str != "" {
options.push_str(inbound_str.as_str());
}
}
if let Some(outbound) = &self.outbound {
let outbound_str = outbound.string();
if outbound_str != "" {
options.push_str(outbound_str.as_str());
}
}
if let Some(should_bundle_reply_info) = &self.should_bundle_reply_info {
options.push_str(&format!(
"shouldBundleReplyInfo={} ",
should_bundle_reply_info
));
}
options
}
}
impl I2CPClientOptions {
pub fn string(&self) -> String {
let mut options = String::default();
if let Some(close_idle_time) = &self.close_idle_time {
options.push_str(&format!("i2cp.closeIdleTime={} ", close_idle_time));
}
if let Some(close_on_idle) = &self.close_on_idle {
options.push_str(&format!("i2cp.closeOnIdle={} ", close_on_idle));
}
if let Some(encrypt_lease_set) = &self.encrypt_lease_set {
options.push_str(&format!("i2cp.encryptLeaseSet={} ", encrypt_lease_set));
}
if let Some(fast_receive) = &self.fast_receive {
options.push_str(&format!("i2cp.fastReceive={} ", fast_receive));
}
if let Some(gzip) = &self.gzip {
options.push_str(&format!("i2cp.gzip={} ", gzip));
}
if let Some(lease_set_auth_type) = &self.lease_set_auth_type {
options.push_str(&format!(
"i2cp.leaseSetAuthType={} ",
lease_set_auth_type.to_string()
));
}
if let Some(lease_set_blinded_type) = &self.lease_set_blinded_type {
options.push_str(&format!(
"i2cp.leaseSetBlindedType={} ",
lease_set_blinded_type.to_string()
));
}
if let Some(lease_set_enc_type) = &self.lease_set_enc_type {
options.push_str(&format!(
"i2cp.leaseSetEncType={} ",
lease_set_enc_type.to_string()
));
}
if let Some(lease_set_key) = &self.lease_set_key {
options.push_str(&format!("i2cp.leaseSetKey={} ", lease_set_key.to_string()));
}
if let Some(lease_set_private_key) = &self.lease_set_private_key {
options.push_str(&format!(
"i2cp.leaseSetPrivateKey={} ",
lease_set_private_key.to_string()
));
}
if let Some(lease_set_secret) = &self.lease_set_secret {
options.push_str(&format!(
"i2cp.leaseSetSecret={} ",
lease_set_secret.to_string()
));
}
if let Some(lease_set_signing_private_key) = &self.lease_set_signing_private_key {
options.push_str(&format!(
"i2cp.leaseSetSigningPrivateKey={} ",
lease_set_signing_private_key.to_string()
));
}
if let Some(message_reliability) = &self.message_reliability {
options.push_str(&format!(
"i2cp.messageReliability={} ",
message_reliability.to_string()
));
}
if let Some(reduce_idle_time) = &self.reduce_idle_time {
options.push_str(&format!("i2cp.reduceIdleTime={} ", reduce_idle_time));
}
if let Some(reduce_on_idle) = &self.reduce_on_idle {
options.push_str(&format!("i2cp.reduceOnIdle={} ", reduce_on_idle));
}
if let Some(ssl) = &self.ssl {
options.push_str(&format!("i2cp.ssl={} ", ssl));
}
if let Some(tcp_host) = &self.tcp_host {
options.push_str(&format!("i2cp.tcp.host={} ", tcp_host));
}
if let Some(tcp_port) = &self.tcp_port {
options.push_str(&format!("i2cp.tcp.port={} ", tcp_port));
}
options
}
}
impl I2CPRouterCryptoOptions {
pub fn string(&self) -> String {
let mut options = String::default();
if let Some(low_tag_threshold) = &self.low_tag_threshold {
options.push_str(&format!("crypto.lowTagThreshold={} ", low_tag_threshold));
}
if let Some(inbound_tags) = &self.ratchet_inbound_tags {
options.push_str(&format!("crypto.ratchet.inboundTags={} ", inbound_tags));
}
if let Some(outbound_tags) = &self.ratchet_outbound_tags {
options.push_str(&format!("crypto.ratchet.outboundTags={} ", outbound_tags));
}
if let Some(tags_to_send) = &self.tags_to_send {
options.push_str(&format!("crypto.tagsToSend={} ", tags_to_send));
}
options
}
}
impl I2CPTunnelInboundOptions {
pub fn string(&self) -> String {
let mut options = String::default();
if let Some(allow_zero_hop) = &self.allow_zero_hop {
options.push_str(&format!("inbound.allowZeroHop={} ", allow_zero_hop));
}
if let Some(backup_quantity) = &self.backup_quantity {
options.push_str(&format!("inbound.backupQuantity={} ", backup_quantity));
}
if let Some(ip_restriction) = &self.ip_restriction {
options.push_str(&format!("inbound.IPRestriction={} ", ip_restriction));
}
if let Some(length) = &self.length {
options.push_str(&format!("inbound.length={} ", length));
}
if let Some(length_variance) = &self.length_variance {
options.push_str(&format!("inbound.lengthVariance{} ", length_variance));
}
if let Some(quantity) = &self.quantity {
options.push_str(&format!("inbound.quantity={} ", quantity));
}
if let Some(random_key) = &self.random_key {
options.push_str(&format!("inbound.randomKey={} ", random_key));
}
options
}
}
impl I2CPTunnelOutboundOptions {
pub fn string(&self) -> String {
let mut options = String::default();
if let Some(allow_zero_hop) = &self.allow_zero_hop {
options.push_str(&format!("outbound.allowZeroHop={} ", allow_zero_hop));
}
if let Some(backup_quantity) = &self.backup_quantity {
options.push_str(&format!("outbound.backupQuantity={} ", backup_quantity));
}
if let Some(ip_restriction) = &self.ip_restriction {
options.push_str(&format!("outbound.IPRestriction={} ", ip_restriction));
}
if let Some(length) = &self.length {
options.push_str(&format!("outbound.length={} ", length));
}
if let Some(length_variance) = &self.length_variance {
options.push_str(&format!("outbound.lengthVariance{} ", length_variance));
}
if let Some(priority) = &self.priority {
options.push_str(&format!("outbound.priority={} ", priority));
}
if let Some(quantity) = &self.quantity {
options.push_str(&format!("outbound.quantity={} ", quantity));
}
if let Some(random_key) = &self.random_key {
options.push_str(&format!("outbound.randomKey={} ", random_key));
}
options
}
}
impl ToString for LeaseSetType {
fn to_string(&self) -> String {
format!("{}", self.0)
}
}
impl ToString for LeaseSetBlindedType {
fn to_string(&self) -> String {
format!("{}", self.0)
}
}
impl ToString for LeaseSetAuthType {
fn to_string(&self) -> String {
match self {
Self::NoPerClient => String::from("0"),
Self::DHPerClient => String::from("1"),
Self::PSKPerClient => String::from("2"),
}
}
}
impl ToString for LeaseSetOfflineSignature {
fn to_string(&self) -> String {
self.0.clone()
}
}
impl ToString for LeaseSetEncType {
fn to_string(&self) -> String {
self.0.clone()
}
}
impl ToString for LeaseSetPrivKey {
fn to_string(&self) -> String {
self.0.clone()
}
}
impl ToString for LeaseSetPrivateKey {
fn to_string(&self) -> String {
self.0.clone()
}
}
impl ToString for LeaseSetKey {
fn to_string(&self) -> String {
self.0.clone()
}
}
impl ToString for LeaseSetSecret {
fn to_string(&self) -> String {
self.0.clone()
}
}
impl ToString for LeaseSetTransientPublicKey {
fn to_string(&self) -> String {
self.0.clone()
}
}
impl ToString for LeaseSetSigningPrivateKey {
fn to_string(&self) -> String {
self.0.clone()
}
}
impl Default for LeaseSetEncType {
fn default() -> LeaseSetEncType {
LeaseSetEncType::from("4,0")
}
}
impl From<String> for LeaseSetEncType {
fn from(val: String) -> LeaseSetEncType {
LeaseSetEncType(val)
}
}
impl From<&str> for LeaseSetEncType {
fn from(val: &str) -> LeaseSetEncType {
LeaseSetEncType(val.to_string())
}
}
impl Default for LeaseSetAuthType {
fn default() -> Self {
Self::NoPerClient
}
}
impl Default for LeaseSetType {
fn default() -> Self {
LeaseSetType(1)
}
}
impl Default for MessageReliability {
fn default() -> Self {
Self::None
}
}
impl ToString for MessageReliability {
fn to_string(&self) -> String {
match self {
Self::BestEffort => String::from("BestEffort"),
Self::None => String::from("None"),
}
}
}
impl ToString for SAMOptions {
fn to_string(&self) -> String {
let mut options = String::default();
if let Some(from_port) = &self.from_port {
options.push_str(&format!("FROM_PORT={} ", from_port));
}
if let Some(to_port) = &self.to_port {
options.push_str(&format!("TO_PORT={} ", to_port));
}
if let Some(i2cp_options) = &self.i2cp_options {
let i2cp_options_str = i2cp_options.string();
if i2cp_options_str != "" {
options.push_str(i2cp_options_str.as_str());
}
}
// make sure to remove duplicate options
// todo: there is likely a better way of handling this
let mut options_parts: Vec<_> = options.split(" ").collect();
options_parts.sort_unstable();
options_parts.dedup();
let mut options_parsed = String::default();
for option_part in options_parts.clone() {
options_parsed.push_str(&format!("{} ", option_part));
}
options_parsed
}
}
impl ToString for SignatureType {
fn to_string(&self) -> String {
match self {
Self::DsaSha1 => "DSA_SHA1".to_string(),
Self::EcdsaSha256P256 => "ECDSA_SHA256_P256".to_string(),
Self::EcdsaSha384P384 => "ECDSA_SHA384_P384".to_string(),
Self::EcdsaSha512P21 => "ECDSA_SHA512_P521".to_string(),
Self::EdDsaSha512Ed25519 => "EdDSA_SHA512_Ed25519".to_string(),
}
}
}

98
src/session_watcher.rs Normal file
View File

@@ -0,0 +1,98 @@
//! provides a basic session watcher which wraps [I2pListener::accept] ensuring that
//! any errors which result in the session being terminated, such as clients improperly disconnecting
//! or other network/transport level issues are handled gracefully.
//!
//! any calls to accept which result in an error will cause the existing session and i2plistener to be dropped,
//! before they are recreated and an error is returned information the caller to try the operation again
//!
use std::net::Shutdown;
use crate::{sam::{StreamConnect, SessionStyle, nickname}, net::{I2pSocketAddr, I2pListener}, Session, sam_options::SAMOptions, Error, ErrorKind};
use log::{info, warn, error};
/// SamSessionWatcher provides the ability to gracefully handle
/// runtime errors by restarting the sam session, and recreating the listener
/// any time errors are detected.
///
/// note: should implement better detection of which errors cause us
/// to recreate the connection
pub struct SamSessionWatcher {
opts: SAMOptions,
session: Session,
destination: String,
sam_endpoint: String,
session_style: SessionStyle,
pub listener: I2pListener,
}
impl SamSessionWatcher {
pub fn new(
sam_endpoint: &str,
destination: &str,
session_style: SessionStyle,
opts: SAMOptions,
) -> Result<Box<SamSessionWatcher>, Error> {
let (session, listener) = SamSessionWatcher::__recreate(
sam_endpoint,
destination,
&nickname(),
session_style.clone(),
opts.clone()
)?;
Ok(Box::new(SamSessionWatcher {
opts,
session,
listener,
session_style,
destination: destination.to_string(),
sam_endpoint: sam_endpoint.to_string(),
}))
}
pub fn accept(self: &mut Box<Self>) -> Result<(StreamConnect, I2pSocketAddr), Error> {
match self.listener.forward.accept() {
Ok(res) => Ok(res),
Err(err) => {
error!("accept encountered error, recreating stream: {:#?}", err);
{
drop(&mut self.listener);
self.session.sam.conn.shutdown(Shutdown::Both)?;
drop(&mut self.session);
}
self.recreate()?;
Err(ErrorKind::SessionRecreated.into())
}
}
}
fn recreate(self: &mut Box<Self>) -> Result<(), Error> {
let (session, listener) = SamSessionWatcher::__recreate(
&self.sam_endpoint,
&self.destination,
&nickname(),
self.session_style.clone(),
self.opts.clone()
)?;
self.session = session;
self.listener = listener;
Ok(())
}
fn __recreate(
sam_endpoint: &str,
destination: &str,
nickname: &str,
session_style: SessionStyle,
opts: SAMOptions,
) -> Result<(Session, I2pListener), Error> {
let session = Session::create(
sam_endpoint,
destination,
nickname,
session_style,
opts.clone(),
)?;
let listener = I2pListener::bind_with_session(&session)?;
Ok((session, listener))
}
}

View File

@@ -1,18 +1,18 @@
#[test]
#[ignore]
fn naming_lookup() {
use i2p::sam::SamConnection;
let mut sam = SamConnection::connect("127.0.0.1:7656").unwrap();
{
let res = sam.naming_lookup("zzz.i2p");
match res {
Ok(d) => {
assert_eq!(
use i2p::sam::SamConnection;
let mut sam = SamConnection::connect("127.0.0.1:7656").unwrap();
{
let res = sam.naming_lookup("zzz.i2p");
match res {
Ok(d) => {
assert_eq!(
d,
"GKapJ8koUcBj~jmQzHsTYxDg2tpfWj0xjQTzd8BhfC9c3OS5fwPBNajgF-eOD6eCjFTqTlorlh7Hnd8kXj1qblUGXT-tDoR9~YV8dmXl51cJn9MVTRrEqRWSJVXbUUz9t5Po6Xa247Vr0sJn27R4KoKP8QVj1GuH6dB3b6wTPbOamC3dkO18vkQkfZWUdRMDXk0d8AdjB0E0864nOT~J9Fpnd2pQE5uoFT6P0DqtQR2jsFvf9ME61aqLvKPPWpkgdn4z6Zkm-NJOcDz2Nv8Si7hli94E9SghMYRsdjU-knObKvxiagn84FIwcOpepxuG~kFXdD5NfsH0v6Uri3usE3XWD7Pw6P8qVYF39jUIq4OiNMwPnNYzy2N4mDMQdsdHO3LUVh~DEppOy9AAmEoHDjjJxt2BFBbGxfdpZCpENkwvmZeYUyNCCzASqTOOlNzdpne8cuesn3NDXIpNnqEE6Oe5Qm5YOJykrX~Vx~cFFT3QzDGkIjjxlFBsjUJyYkFjBQAEAAcAAA=="
)
}
Err(ref e) => panic!("An error occurred: {}", e),
}
}
}
Err(ref e) => panic!("An error occurred: {}", e),
}
}
}