Compare commits

...

30 Commits

Author SHA1 Message Date
Lev 888f77c6bb The send button in interface 3 years ago
Lev f5c61c5aa5 Wrote a pygame button 3 years ago
Lev 456e8940b1 Input field in interface 3 years ago
Lev d7524cb4bf Merge interface branch 3 years ago
Lev ff5e99ceb0 Rendering messages in interface 3 years ago
Lev 67b8c7cfe5 Rendering active chat in interface (header) 3 years ago
Lev 833e3e4547 Design in interface: changed colors and sizes 3 years ago
Lev 10e79d57e4 Split interface code into separate files 3 years ago
Lev 4769b2eead ActiveChat and Degeon.main_loop in interface 3 years ago
Lev 093e011fc8 Degeon_py: started writing interface 3 years ago
Lev 8680d3379a Wrote a messenger) 3 years ago
Lev df827fc656 Improved binary 3 years ago
Lev 1caa0324cf Add binary 3 years ago
Lev 2574061bed Serialization + main loop starter 3 years ago
Lev f0326cea8c Sending messages is now working 3 years ago
Lev 02c88de37a The testnet can now create tunnels through IP protocol 3 years ago
Lev af5d27dc97 Sending messages 3 years ago
Lev 6c69c80d7a Tunnel generation (it's even working) 3 years ago
Lev aa15911403 Peer sharing in IP 3 years ago
Lev 82b5973aa0 Wrote some logic in TestInterface, fixed some warnings 3 years ago
Lev 2a7a97f592 Wrote TestInterface 3 years ago
Prokhor 3e25fbb4bb Created IPPackage header. Added receive queue 3 years ago
Prokhor 50ecec2aa3 Created IPInterface constructor. Made nonblocking mainloop in IP 3 years ago
Lev 2b6e2b46ad Cryptography in message (signing, checks), new serialization for PublicKey 3 years ago
Lev 07902bb027 Hashing of everything 3 years ago
Lev 8f5a562ff8 Move test for ip from examples back to the file 3 years ago
Lev 3b17d437d3 Fix clippy warnings and no_std build 3 years ago
Prokhor 38eb9b2e9e Fixed incoming connections in IP interface 3 years ago
Prokhor f0fea9d45b Removed unfinished line 3 years ago
Prokhor b266a0928f Created IP inteface. Implemented send method 3 years ago
  1. 3275
      Cargo.lock
  2. 10
      Cargo.toml
  3. 3238
      degeon/Cargo.lock
  4. 12
      degeon/Cargo.toml
  5. 6
      degeon/src/gui_events.rs
  6. 70
      degeon/src/main.rs
  7. 14
      degeon/src/message.rs
  8. 270
      degeon/src/state.rs
  9. BIN
      degeon_py/CRC35.otf
  10. 103
      degeon_py/active_chat.py
  11. 99
      degeon_py/button.py
  12. 76
      degeon_py/chat_selector.py
  13. 31
      degeon_py/config.py
  14. 96
      degeon_py/degeon.py
  15. 82
      degeon_py/input_field.py
  16. 11
      degeon_py/main.py
  17. 42
      degeon_py/message.py
  18. 49
      examples/test_ip_connection.rs
  19. 57
      src/bin/worker.rs
  20. 149
      src/crypto.rs
  21. 127
      src/interface.rs
  22. 507
      src/interfaces/ip.rs
  23. 31
      src/interfaces/mod.rs
  24. 546
      src/ironforce.rs
  25. 17
      src/lib.rs
  26. 276
      src/message.rs
  27. 17
      src/res.rs
  28. 185
      src/transport.rs
  29. 70
      src/tunnel.rs

3275
Cargo.lock generated

File diff suppressed because it is too large Load Diff

10
Cargo.toml

@ -5,6 +5,9 @@ edition = "2018"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[workspace]
members = ["degeon"]
[features]
default = []
std = ["rayon"]
@ -20,6 +23,13 @@ serde = { version = "1.0", features = ["derive", "alloc"], default-features = fa
rayon = { version = "1.5.1", optional = true }
core-error = "0.0.1-rc4"
serde_cbor = "0.11.2"
serde_json = "1.0.72"
spin = "0.9.2"
base64 = "0.13.0"
[profile.dev.package.num-bigint-dig]
opt-level = 3
[[bin]]
name = "worker"
required-features = ["std"]

3238
degeon/Cargo.lock generated

File diff suppressed because it is too large Load Diff

12
degeon/Cargo.toml

@ -0,0 +1,12 @@
[package]
name = "degeon"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
iced = { version = "0.3", features = ["glow"] }
ironforce = { path = "../", features = ["std"] }
base64 = "0.13.0"
serde = { version = "1.0" }

6
degeon/src/gui_events.rs

@ -0,0 +1,6 @@
#[derive(Clone, Debug)]
pub enum GuiEvent {
ChatSelect(usize),
Typed(String),
SendClick,
}

70
degeon/src/main.rs

@ -0,0 +1,70 @@
extern crate serde;
mod message;
mod state;
mod gui_events;
use iced::Sandbox;
use ironforce::res::IFResult;
use ironforce::{IronForce, Message, MessageType, PublicKey};
use crate::state::State;
fn main_if() -> IFResult<()> {
let ironforce = IronForce::from_file("".to_string())?;
let if_keys = ironforce.keys.clone();
println!(
"Our public key: {}",
base64::encode(if_keys.get_public().to_vec().as_slice())
);
let (_thread, if_mutex) = ironforce.launch_main_loop(100);
let stdin = std::io::stdin();
let if_mutex_clone = if_mutex.clone();
let if_keys_clone = if_keys.clone();
std::thread::spawn(move || loop {
if let Some(msg) = if_mutex_clone.lock().unwrap().read_message() {
println!(
"New message: {}",
String::from_utf8(msg.get_decrypted(&if_keys_clone).unwrap()).unwrap()
);
}
std::thread::sleep(std::time::Duration::from_millis(300))
});
loop {
let mut buf = String::new();
stdin.read_line(&mut buf)?;
let msg_base = if buf.starts_with('>') {
let target_base64 = buf
.split(')')
.next()
.unwrap()
.trim_start_matches(">(")
.to_string();
let target = if let Ok(res) = base64::decode(target_base64) {
res
} else {
println!("Wrong b64.");
continue;
};
buf = buf
.split(')')
.skip(1)
.map(|s| s.to_string())
.collect::<Vec<String>>()
.join(")");
Message::build()
.message_type(MessageType::SingleCast)
.recipient(&PublicKey::from_vec(target).unwrap())
} else {
Message::build().message_type(MessageType::Broadcast)
};
if_mutex
.lock()
.unwrap()
.send_to_all(msg_base.content(buf.into_bytes()).sign(&if_keys).build()?)?;
}
}
fn main() -> Result<(), Box<dyn std::error::Error>> {
// let ironforce = IronForce::from_file("".to_string()).unwrap();
// let _if_keys = ironforce.keys.clone();
Ok(State::run(iced::Settings::default())?)
}

14
degeon/src/message.rs

@ -0,0 +1,14 @@
use serde::{Deserialize, Serialize};
#[derive(Clone, Debug, Serialize, Deserialize)]
pub enum Message {
Text(String),
File(Vec<u8>),
Service(ServiceMsg),
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub enum ServiceMsg {
NameRequest,
NameStatement(String),
}

270
degeon/src/state.rs

@ -0,0 +1,270 @@
use crate::gui_events::GuiEvent;
use crate::message::Message;
use core::default::Default;
use iced::{
button, Align, Button, Column, Element, HorizontalAlignment, Length, Row, Sandbox, Settings,
Text, TextInput, VerticalAlignment,
};
use ironforce::{Keys, PublicKey};
use serde::{Deserialize, Serialize};
#[derive(Clone, Debug)]
pub struct Chat {
pkey: PublicKey,
messages: Vec<(bool, Message)>,
name: String,
scrolled: f32,
pub input: String,
}
pub fn view_message(msg: &(bool, Message)) -> Option<Element<GuiEvent>> {
let msg = &msg.1;
match msg {
Message::Text(t) => Some(
iced::Container::new(Text::new(t.as_str()))
.padding(10)
.style(style::Container::Message)
.into(),
),
Message::File(_) => None,
Message::Service(_) => None,
}
}
mod style {
use iced::container::Style;
use iced::{button, Background, Color, Vector};
#[derive(Clone, Copy, PartialEq, Eq)]
pub enum Button {
Primary,
Secondary,
Destructive,
InactiveChat,
}
impl button::StyleSheet for Button {
fn active(&self) -> button::Style {
button::Style {
background: Some(Background::Color(match self {
Button::Primary => Color::from_rgb(0.11, 0.35, 0.75),
Button::Secondary => Color::from_rgb(0.3, 0.1, 0.7),
Button::Destructive => Color::from_rgb(0.8, 0.2, 0.2),
Button::InactiveChat => Color::from_rgb(0.3, 0.52, 0.9),
})),
border_radius: 5.0,
shadow_offset: Vector::new(1.0, 1.0),
text_color: if self != &Button::InactiveChat { Color::WHITE } else { Color::BLACK },
..button::Style::default()
}
}
}
pub enum Container {
Primary,
Message,
}
impl iced::container::StyleSheet for Container {
fn style(&self) -> Style {
iced::container::Style {
text_color: Some(Color::WHITE),
background: Some(Background::Color(match self {
Container::Primary => Color::from_rgb(18. / 256., 25. / 256., 70. / 256.),
Container::Message => Color::from_rgb(0., 0.1, 0.8),
})),
border_radius: 5.0,
border_width: 0.6,
border_color: Color::TRANSPARENT,
}
}
}
}
impl Chat {
pub fn header<'a>(name: String) -> Element<'a, GuiEvent> {
iced::container::Container::new(Text::new(name.as_str()).color(iced::Color::WHITE))
.style(style::Container::Primary)
.width(Length::Fill)
.height(Length::Units(50))
.padding(10)
.into()
}
pub fn send_field<'a>(
input: String,
text_input_state: &'a mut iced::text_input::State,
send_button_state: &'a mut iced::button::State,
) -> Element<'a, GuiEvent> {
Row::new()
.width(Length::Fill)
.padding(15)
.push(
TextInput::new(text_input_state, "Message", input.as_str(), |st| {
GuiEvent::Typed(st)
})
.padding(8)
.width(Length::Fill),
)
.push(
Button::new(send_button_state, Text::new("Send"))
.on_press(GuiEvent::SendClick)
.style(style::Button::Secondary)
.padding(20)
.width(Length::Units(80)),
)
.spacing(25)
.height(Length::Units(100))
.into()
}
pub fn preview<'a>(&'a self, state: &'a mut button::State, i: usize, is_selected: bool) -> Element<'a, GuiEvent> {
Button::new(state, Text::new(self.name.as_str()))
.width(Length::Fill)
.padding(10)
.style(if is_selected { style::Button::Primary } else { style::Button::InactiveChat })
.on_press(GuiEvent::ChatSelect(i))
.into()
}
pub fn view<'a>(
&'a self,
text_input_state: &'a mut iced::text_input::State,
send_button_state: &'a mut iced::button::State,
) -> Element<'a, GuiEvent> {
let msgs = self.messages.iter().filter_map(view_message).collect();
Column::new()
.align_items(Align::End)
.height(Length::Fill)
.width(Length::FillPortion(4))
.push(Self::header(self.name.clone()))
.push(
Column::with_children(msgs)
.padding(20)
.spacing(10)
.align_items(Align::End)
.height(Length::FillPortion(9)),
)
.spacing(10)
.push(Self::send_field(
self.input.to_string(),
text_input_state,
send_button_state,
))
.into()
}
pub fn example(i: usize) -> Chat {
Self {
pkey: Keys::generate().get_public(),
messages: vec![(false, Message::Text(format!("Example message {}", i)))],
name: format!("Example user ({})", i),
scrolled: 0.0,
input: "".to_string(),
}
}
}
#[derive(Default, Clone, Debug)]
pub struct State {
chats: Vec<Chat>,
my_name: String,
selected_chat: usize,
pub send_button_state: iced::button::State,
text_input_state: iced::text_input::State,
preview_button_states: Vec<button::State>,
}
impl State {
fn chat_list<'a>(
chats: &'a Vec<Chat>,
preview_button_states: &'a mut Vec<button::State>,
selected: usize
) -> Element<'a, GuiEvent> {
Column::with_children(
chats
.iter()
.zip(preview_button_states.iter_mut())
.enumerate()
.map(|(i, (chat, state))| chat.preview(state, i, i == selected))
.collect(),
)
.padding(20)
.spacing(10)
.align_items(Align::Start)
.width(Length::FillPortion(1))
.into()
}
pub fn active_chat<'a>(
chats: &'a Vec<Chat>,
selected_chat: usize,
send_button_state: &'a mut button::State,
text_input_state: &'a mut iced::text_input::State,
) -> Element<'a, GuiEvent> {
if selected_chat >= chats.len() {
Text::new("No chat")
.horizontal_alignment(HorizontalAlignment::Center)
.vertical_alignment(VerticalAlignment::Center)
.width(Length::FillPortion(4))
.into()
} else {
chats[selected_chat].view(text_input_state, send_button_state)
}
}
}
impl Sandbox for State {
type Message = GuiEvent;
fn new() -> Self {
let mut st = Self::default();
st.chats = vec![Chat::example(1), Chat::example(2)];
st.preview_button_states = vec![Default::default(), Default::default()];
st
}
fn title(&self) -> String {
String::from("Degeon")
}
fn update(&mut self, message: GuiEvent) {
match message {
GuiEvent::ChatSelect(i) => self.selected_chat = i,
GuiEvent::Typed(st) => self.chats[self.selected_chat].input = st,
GuiEvent::SendClick => {
if self.chats[self.selected_chat].input.is_empty() {
return;
}
let new_msg = Message::Text(self.chats[self.selected_chat].input.clone());
self.chats[self.selected_chat].input = String::new();
self.chats[self.selected_chat]
.messages
.push((true, new_msg));
// todo
}
}
}
fn view(&mut self) -> Element<GuiEvent> {
let Self {
chats,
selected_chat,
send_button_state,
text_input_state,
preview_button_states,
..
} = self;
Row::new()
.padding(20)
.push(Self::chat_list(chats, preview_button_states, *selected_chat))
.push(Self::active_chat(
chats,
*selected_chat,
send_button_state,
text_input_state,
))
.height(Length::Fill)
.into()
}
}

BIN
degeon_py/CRC35.otf

Binary file not shown.

103
degeon_py/active_chat.py

@ -0,0 +1,103 @@
from __future__ import annotations
import typing
from dataclasses import dataclass, field
import pygame
from button import Button
from config import HEIGHT, WIDTH, CHAT_SELECTOR_WIDTH, DARKER_BLUE, CHAT_PREVIEW_HEIGHT, WHITE, font, DARK_BLUE, \
MESSAGE_HEIGHT
import degeon_core as dc
from input_field import TextField
from message import Message
@dataclass
class ActiveChat:
"""
The widget with the current chat
Attributes:
:param chat (dc.Chat): the chat (Rust Chat type)
:param height (int): the height of the active chat widget
:param width (int): its width
:param delta_x (int): distance from the left edge of the application to the left edge of the screen
:param header_height (int): height of the header (the title)
"""
chat: dc.Chat
input_field: TextField = field(default_factory=lambda: TextField())
send_button: Button = field(default_factory=lambda: Button(height=CHAT_PREVIEW_HEIGHT, width=100, text='Send',
top_left=(WIDTH - 112, HEIGHT - 85)))
height: int = HEIGHT
width: int = WIDTH - CHAT_SELECTOR_WIDTH - 10
delta_x: int = CHAT_SELECTOR_WIDTH + 10
header_height: int = int(CHAT_PREVIEW_HEIGHT * 1.5)
@classmethod
def new(cls, chat: dc.Chat, **kwargs) -> ActiveChat:
"""
Create a new `Chat` from a rust Chat object
:param chat: rusty chat
:param kwargs: optional other paraeters
:return: the `Chat`
"""
return cls(chat=chat, **kwargs)
def get_messages(self) -> typing.Iterable[Message]:
"""
Get an iterator over all messages in this chat in the backwards order
This function creates a python `message.Message` object from rust instances
:return: an iterator of `message.Message` objects
"""
for msg in reversed(self.chat.messages):
yield Message(text=msg.get_content_py().text, is_from_me=False)
def get_header(self) -> pygame.Surface:
"""
Render a pygame surface with the header.
The header is (for now) just a name of the user written on a background
:return: the header
"""
surface: pygame.Surface = pygame.Surface((self.width, self.header_height))
surface.fill(DARK_BLUE)
name_surface: pygame.Surface = font.render(self.chat.profile.name, True, WHITE)
surface.blit(name_surface, (20, 20))
return surface
def render(self) -> pygame.Surface:
"""
Creates a pygame surface and draws the chat on it
:return: the surface with the chat on it
"""
surface: pygame.Surface = pygame.Surface((self.width, self.height))
surface.fill(DARKER_BLUE)
# Render messages
# This is the y0 for the last message
last_message_y = self.height - MESSAGE_HEIGHT * 2
for i, message in zip(range(30), self.get_messages()):
msg_surface = message.render()
surface.blit(msg_surface, (0, last_message_y - (MESSAGE_HEIGHT + 30) * (i + 1)))
# Render header
header = self.get_header()
surface.blit(header, (0, 10))
# Render message input
input_field_surface: pygame.Surface = self.input_field.render()
surface.blit(input_field_surface, (0, self.height - input_field_surface.get_height()))
# Render sending button
sending_button_surface: pygame.Surface = self.send_button.render()
surface.blit(sending_button_surface,
(self.send_button.top_left[0] - self.delta_x, self.send_button.top_left[1]))
return surface
def process_event(self, event: pygame.event.Event) -> typing.Optional[str]:
"""
Process clicks in the active chat widget. Return a message to send if needed
:param event: a pygame event
:return: A message to send if there is one
"""
self.input_field.process_event(event)
if self.send_button.process_event(event):
return self.input_field.collect()

99
degeon_py/button.py

@ -0,0 +1,99 @@
import pygame
import typing
from config import font_large, WHITE, BLUE, BLACK
from dataclasses import dataclass
@dataclass
class Button:
"""
Just a button element for pygame
Attributes:
:param text (str): Text on the button
:param top_left ((int, int)): the coordinates of the top-left point of the button
:param width (int): the width of the button
:param height (int): the height of the button
:param padding (int): padding - the distance between rectangle border and button content
:param bg_color (Color): the background color
:param text_color (Color): color of the text
:param hovered_color (Color): the background color when the button is hovered
:param hovered_text_color (Color): the text color when the button is hovered
:param pressed_color (Color): the background color when the button is pressed
:param pressed_text_color (Color): the text color when the button is pressed
"""
text: str
top_left: typing.Tuple[int, int]
width: int
height: int
padding: int = 13
bg_color: pygame.Color = BLUE
text_color: pygame.Color = WHITE
hovered_color: pygame.Color = (BLUE * 3 + WHITE) // 4
hovered_text_color: pygame.Color = BLACK
pressed_color: pygame.Color = (BLUE + WHITE * 3) // 4
pressed_text_color: pygame.Color = BLACK
is_hovered: bool = False
is_pressed: bool = False
@property
def bottom(self) -> int:
"""
Return the y coordinate of the bottom of the button
:return: y_bottom
"""
return self.top_left[1] + self.height
@property
def right(self) -> int:
"""
Return the x coordinate of the right edge of the button
:return: x_right
"""
return self.top_left[0] + self.width
def get_colors(self) -> typing.Tuple[pygame.Color, pygame.Color]:
"""
Get background and text color considering hovered and pressed states
:return: the button's background color and the button's text color
"""
if self.is_pressed:
return self.pressed_color, self.pressed_text_color
if self.is_hovered:
return self.hovered_color, self.hovered_text_color
return self.bg_color, self.text_color
def render(self) -> pygame.Surface:
"""
Draw the button
:return: a pygame surface with the button
"""
surface: pygame.Surface = pygame.Surface((self.width, self.height))
bg_color, text_color = self.get_colors()
surface.fill(bg_color)
text_surface: pygame.Surface = font_large.render(self.text, True, text_color)
text_height: int = self.height - 2 * self.padding
text_width: int = round(text_surface.get_width() * text_height / text_surface.get_height())
text_surface: pygame.Surface = pygame.transform.scale(text_surface, (text_width, text_height))
surface.blit(text_surface, ((self.width - text_width) // 2, self.padding))
return surface
def process_event(self, event: pygame.event.Event) -> bool:
"""
Process a pygame event. If it's a click on this button, return true
:param event: a pygame event
:return: True if there was a click on the button
"""
# if this is a mouse event
if event.type in [pygame.MOUSEBUTTONUP, pygame.MOUSEMOTION, pygame.MOUSEBUTTONDOWN]:
# if the mouse event is inside the button
if self.top_left[0] <= event.pos[0] < self.bottom and self.top_left[1] <= event.pos[1] < self.right:
if event.type == pygame.MOUSEBUTTONUP:
self.is_pressed = False
return True
elif event.type == pygame.MOUSEBUTTONDOWN:
self.is_hovered = False
self.is_pressed = True
elif event.type == pygame.MOUSEMOTION:
self.is_hovered = True
return False

76
degeon_py/chat_selector.py

@ -0,0 +1,76 @@
from __future__ import annotations
import typing
from dataclasses import dataclass, field
import pygame
from config import CHAT_SELECTOR_WIDTH, HEIGHT, DARK_BLUE, WHITE, GREY, BLUE, font, CHAT_PREVIEW_HEIGHT, MEDIUM_BLUE
@dataclass
class ChatSelector:
"""
The widget with the list of chats.
It's a dataclass, it should be initialized using the `from_chats` classmethod
Attributes:
chats (List[dc.Chat]): list of all chats, where each chat is a native Rust struct Chat
active_chat (int): the index of the current selected chat
hovered_chat (int or None): the index of the current hovered chat
width (int): the width of this widget
height (int): the height of this widget
chat_height (int): height of one chat
"""
chats: typing.List['dc.Chat'] = field(default_factory=list)
active_chat: int = -1
hovered_chat: typing.Optional[int] = None
width: int = CHAT_SELECTOR_WIDTH
height: int = HEIGHT
chat_height: int = CHAT_PREVIEW_HEIGHT
def render(self) -> pygame.Surface:
"""
Creates a pygame surface and draws the list of chats on it
:return: the surface with the chat selector
"""
surface: pygame.Surface = pygame.Surface((self.width, self.height))
surface.fill(GREY)
for i, chat in enumerate(self.chats):
bg_color, text_color = DARK_BLUE, WHITE
if i == self.hovered_chat:
bg_color = MEDIUM_BLUE
if i == self.active_chat:
bg_color = BLUE
title_surface: pygame.Surface = font.render(chat.profile.name, True, text_color)
pygame.draw.rect(surface, bg_color, (3, i * self.chat_height + 1, self.width - 6, self.chat_height - 2))
surface.blit(title_surface, (7, i * self.chat_height + 10))
return surface
def process_event(self, event: pygame.event.Event) -> bool:
"""
Process a click: select the necessary chat if this click is in the widget
:param event: a pygame event
:return: True if a chat was changed
"""
if event.type == pygame.MOUSEMOTION:
if 0 < event.pos[0] < self.width \
and 0 < event.pos[1] < min(self.height, len(self.chats) * self.chat_height) - 2:
self.hovered_chat = event.pos[1] // self.chat_height
else:
self.hovered_chat = None
if event.type == pygame.MOUSEBUTTONUP and event.pos[0] < self.width:
self.hovered_chat = None
self.active_chat = event.pos[1] // self.chat_height
return True
return False
@classmethod
def from_chats(cls, chats: typing.List['dc.Chat'], **kwargs) -> ChatSelector:
"""
Create a new ChatSelector from a list of Rust chats
:param chats: the list of chats
:param kwargs: optional additional arguments
"""
return cls(chats, **kwargs)

31
degeon_py/config.py

@ -0,0 +1,31 @@
import pygame
pygame.init()
# Fontss
font = pygame.font.Font('CRC35.otf', 32)
font_large = pygame.font.Font('CRC35.otf', 50)
# Colors used in the app
RED = 0xFF0000
BLUE = 0x0000FF
YELLOW = 0xFFC91F
GREEN = 0x00FF00
MAGENTA = 0xFF03B8
CYAN = 0x00FFCC
BLACK = 0x000
WHITE = 0xFFFFFF
MEDIUM_BLUE = 0x2f2f4e
DARK_BLUE = 0x282e46
DARKER_BLUE = 0x202033
GREY = 0x383e4F
# Geometrical parameters
WIDTH = 1000
HEIGHT = 800
CHAT_PREVIEW_HEIGHT = 80
CHAT_SELECTOR_WIDTH = WIDTH // 3
MESSAGE_HEIGHT = 60
FPS = 30

96
degeon_py/degeon.py

@ -0,0 +1,96 @@
from __future__ import annotations
from dataclasses import dataclass, field
import typing
import pygame
from chat_selector import ChatSelector
from active_chat import ActiveChat
from config import FPS, DARKER_BLUE, font, WHITE, WIDTH, CHAT_SELECTOR_WIDTH, HEIGHT
import degeon_core as dc
@dataclass
class Degeon:
"""
The main class with everything connected to the app: the data, the
Attributes
"""
core: 'dc.Degeon'
chat_selector: ChatSelector
active_chat: typing.Optional[ActiveChat] = None
has_profile_popup_opened: bool = False
has_no_peers_popup: bool = False
clock: pygame.time.Clock = field(default_factory=pygame.time.Clock)
fps: int = FPS
@classmethod
def new(cls) -> Degeon:
"""
Create a new default instance with settings from file
:return: a Degeon instance
"""
core: dc.Degeon = dc.new_degeon()
chat_selector = ChatSelector()
return cls(core=core, chat_selector=chat_selector)
def render(self, screen: pygame.Surface):
"""
Render everything on the screen
:param screen: the main screen
"""
chats_surface = self.chat_selector.render()
screen.blit(chats_surface, (0, 0))
if self.active_chat is not None:
active_chat_surface = self.active_chat.render()
screen.blit(active_chat_surface, (self.active_chat.delta_x, 0))
else:
text_surface: pygame.Surface = font.render('<- Select chat in the menu', True, WHITE)
screen.blit(text_surface,
(round(WIDTH / 2 + CHAT_SELECTOR_WIDTH / 2 - text_surface.get_width() / 2), HEIGHT // 2))
def process_core_messages(self):
"""
Do all the necessary Rust work
"""
pass # todo
def tick(self):
"""
Handle incoming messages, update chats, create no_peers popup if necessary
"""
# process events in core
self.process_core_messages()
self.chat_selector.chats = self.core.chats
if 0 <= self.chat_selector.active_chat < len(self.chat_selector.chats) and self.active_chat is None:
self.active_chat = ActiveChat.new(self.chat_selector.chats[self.chat_selector.active_chat])
def process_event(self, event: pygame.event.Event):
"""
Process an event
:param event: pygame event
"""
if self.chat_selector.process_event(event):
if 0 <= self.chat_selector.active_chat < len(self.chat_selector.chats):
self.active_chat = ActiveChat.new(self.chat_selector.chats[self.chat_selector.active_chat])
else:
self.active_chat = None
if self.active_chat is not None:
self.active_chat.process_event(event)
def main_loop(self, screen: pygame.Surface):
"""
Drawing everything and handling events
"""
while True:
screen.fill(DARKER_BLUE)
for event in pygame.event.get():
if event.type == pygame.QUIT:
return
self.process_event(event)
self.tick()
self.render(screen)
self.clock.tick(self.fps)
pygame.display.update()

82
degeon_py/input_field.py

@ -0,0 +1,82 @@
import typing
import pygame
from config import font, MESSAGE_HEIGHT, WIDTH, CHAT_SELECTOR_WIDTH, HEIGHT, WHITE, DARKER_BLUE, GREY
from dataclasses import dataclass
@dataclass
class TextField:
"""
Field for message input
"""
value: str = ''
width: int = WIDTH - CHAT_SELECTOR_WIDTH - 140
height: int = MESSAGE_HEIGHT * 1.5
top_left_corner: typing.Tuple[int, int] = (CHAT_SELECTOR_WIDTH, HEIGHT - MESSAGE_HEIGHT * 2)
is_focused: bool = False
placeholder: str = ''
cursor_position: int = 0
def render(self) -> pygame.Surface:
"""
Render the text field onto a pygame surface
:return: a surface with the field
"""
surface = pygame.Surface((self.width, self.height))
surface.fill(WHITE)
padding = 5
pygame.draw.rect(surface, DARKER_BLUE, (padding, padding, self.width - padding * 2, self.height - padding * 2))
if not self.value and self.placeholder:
placeholder_text: pygame.Surface = font.render(self.placeholder, True, GREY)
surface.blit(
placeholder_text,
(
(self.width - placeholder_text.get_width()) // 2,
(self.height - placeholder_text.get_height()) // 2
)
)
if self.value:
message_text: pygame.Surface = font.render(self.value, True, WHITE)
surface.blit(
message_text,
(
40, # or, if we want to center, `(self.width - message_text.get_width()) // 2,`
(self.height - message_text.get_height()) // 2
)
)
return surface
def process_event(self, event: pygame.event.Event):
"""
Handle a typing event or a click (to focus)
:param event: a pygame event
"""
# If we have a click, we should focus the field if the click was inside or unfocus if it was outside
if event.type == pygame.MOUSEBUTTONUP:
self.is_focused = self.top_left_corner[0] <= event.pos[0] < self.top_left_corner[0] + self.width \
and self.top_left_corner[1] <= event.pos[1] < self.top_left_corner[1] + self.height
if self.is_focused and hasattr(event, 'key') and event.type == 768:
if event.key == pygame.K_BACKSPACE:
self.value = self.value[:-1]
self.cursor_position -= 1
elif event.key == pygame.K_LEFT:
self.cursor_position -= 1
elif event.key == pygame.K_RIGHT:
self.cursor_position += 1
elif event.unicode:
self.value = self.value[:self.cursor_position] + event.unicode + self.value[self.cursor_position:]
self.cursor_position += 1
# print(self.is_focused, event.type, getattr(event, 'key', None), getattr(event, 'unicode', None), self.value)
def collect(self) -> str:
"""
Get the current value and clear the field
:return: the value of the text input
"""
value = self.value
self.value = ''
self.cursor_position = 0
return value

11
degeon_py/main.py

@ -0,0 +1,11 @@
from __future__ import annotations
import pygame
from degeon import Degeon
from config import WIDTH, HEIGHT
deg = Degeon.new()
screen = pygame.display.set_mode((WIDTH, HEIGHT))
deg.main_loop(screen)
pygame.quit()

42
degeon_py/message.py

@ -0,0 +1,42 @@
from __future__ import annotations
from dataclasses import dataclass
import pygame
from config import WIDTH, CHAT_SELECTOR_WIDTH, MESSAGE_HEIGHT, font, BLUE, DARK_BLUE, WHITE, DARKER_BLUE
@dataclass
class Message:
"""
The message (for now, it consists only of text)
Attributes:
:param text (str): the message text
:param is_from_me (bool): False if the message is not from the current user
:param chat_width (int): the width of the active chat widget
"""
text: str
is_from_me: bool
chat_width: int = WIDTH - CHAT_SELECTOR_WIDTH - 10
height: int = MESSAGE_HEIGHT
def render(self) -> pygame.Surface:
"""
Creates a surface with a rectangle and the message text written on it
:return: the surface with rendered message
"""
surface = pygame.Surface((self.chat_width, self.height))
surface.fill(DARKER_BLUE)
bg_color = BLUE * self.is_from_me + DARK_BLUE * (not self.is_from_me)
text_surface: pygame.Surface = font.render(self.text, True, WHITE)
padding = 5
# Size of the scaled text surface
blit_height: int = self.height - padding * 2
blit_width: int = round(text_surface.get_width() * blit_height / text_surface.get_height())
x: int = 0 if not self.is_from_me else self.chat_width - blit_width - padding * 2
pygame.draw.rect(surface, bg_color, (x, 0, blit_width + padding * 2, self.height))
text_surface: pygame.Surface = pygame.transform.smoothscale(text_surface, (blit_width, blit_height))
surface.blit(text_surface, (x + padding, padding))
return surface

49
examples/test_ip_connection.rs

@ -0,0 +1,49 @@
#[cfg(feature = "std")]
use ironforce::interface::Interface;
#[cfg(feature = "std")]
use ironforce::interfaces::ip::IPInterface;
#[cfg(feature = "std")]
use std::net;
use std::thread;
use std::time::Duration;
#[cfg(feature = "std")]
use ironforce::res::IFResult;
#[cfg(feature = "std")]
fn main() {
println!("hello");
test();
}
#[cfg(feature = "std")]
fn test() -> IFResult<()> {
let message = *b"Hello world from iron forest";
let mut interface1 = IPInterface::new()?;
let mut interface2 = IPInterface::new()?;
let t1 = std::thread::spawn(move || {
interface1.send(&message, Some(String::from("127.0.0.1:50001"))).unwrap();
interface1
});
thread::sleep(Duration::from_millis(10));
let t2 = std::thread::spawn(move || {
interface2.main_loop_iteration().unwrap();
interface2
});
let res1 = t1.join();
match res1 {
Ok(_) => println!("Ok"),
Err(e) => println!("{:?}", e)
}
let res2 = t2.join();
match res2 {
Ok(_) => println!("Ok"),
Err(e) => println!("{:?}", e)
}
Ok(())
}
#[cfg(not(feature = "std"))]
fn main() {}

57
src/bin/worker.rs

@ -0,0 +1,57 @@
use ironforce::res::IFResult;
use ironforce::{IronForce, Message, MessageType, PublicKey};
fn main() -> IFResult<()> {
let ironforce = IronForce::from_file("".to_string())?;
let if_keys = ironforce.keys.clone();
println!(
"Our public key: {}",
base64::encode(if_keys.get_public().to_vec().as_slice())
);
let (_thread, if_mutex) = ironforce.launch_main_loop(100);
let stdin = std::io::stdin();
let if_mutex_clone = if_mutex.clone();
let if_keys_clone = if_keys.clone();
std::thread::spawn(move || loop {
if let Some(msg) = if_mutex_clone.lock().unwrap().read_message() {
println!(
"New message: {}",
String::from_utf8(msg.get_decrypted(&if_keys_clone).unwrap()).unwrap()
);
}
std::thread::sleep(std::time::Duration::from_millis(300))
});
loop {
let mut buf = String::new();
stdin.read_line(&mut buf)?;
let msg_base = if buf.starts_with('>') {
let target_base64 = buf
.split(')')
.next()
.unwrap()
.trim_start_matches(">(")
.to_string();
let target = if let Ok(res) = base64::decode(target_base64) {
res
} else {
println!("Wrong b64.");
continue;
};
buf = buf
.split(')')
.skip(1)
.map(|s| s.to_string())
.collect::<Vec<String>>()
.join(")");
Message::build()
.message_type(MessageType::SingleCast)
.recipient(&PublicKey::from_vec(target).unwrap())
} else {
Message::build().message_type(MessageType::Broadcast)
};
if_mutex
.lock()
.unwrap()
.send_to_all(msg_base.content(buf.into_bytes()).sign(&if_keys).build()?)?;
}
}

149
src/crypto.rs

@ -1,13 +1,18 @@
use crate::res::{IFError, IFResult};
use alloc::string::String;
use alloc::vec;
/// This module has wrappers for cryptography with RSA algorithms.
/// Its main structs - `PublicKey` and `Keys` implement all functions for key generation, signatures and asymmetric encryption
use alloc::vec::Vec;
use serde::{Deserialize, Serialize};
use rsa::{RsaPublicKey, RsaPrivateKey, PaddingScheme, PublicKey as RPK};
use rsa::errors::Result as RsaRes;
use rand::rngs::OsRng;
use rsa::errors::Result as RsaRes;
use rsa::{BigUint, PaddingScheme, PublicKey as RPK, PublicKeyParts, RsaPrivateKey, RsaPublicKey};
use serde::{Deserialize, Serialize};
use sha2::{Digest, Sha224};
static KEY_LENGTH: usize = 2048;
static ENCRYPTION_CHUNK_SIZE: usize = 240;
static ENCRYPTION_OUTPUT_CHUNK_SIZE: usize = 256;
/// Public key of a node
#[derive(Debug, PartialEq, Serialize, Deserialize, Clone)]
@ -18,16 +23,85 @@ pub struct PublicKey {
impl PublicKey {
/// Check if the sign is valid for given data and key
pub fn verify_sign(&self, data: &[u8], sign: &[u8]) -> bool {
self.key.verify(PaddingScheme::PKCS1v15Sign { hash: None }, data, sign).is_ok()
self.key
.verify(
PaddingScheme::PKCS1v15Sign { hash: None },
&Sha224::new().chain(data).result().to_vec(),
sign,
)
.is_ok()
}
/// Encrypt some data for a user with this public key
pub fn encrypt_data(&self, data: &[u8]) -> RsaRes<Vec<u8>> {
self.key.encrypt(&mut OsRng {}, PaddingScheme::PKCS1v15Encrypt, data)
if data.len() <= ENCRYPTION_CHUNK_SIZE {
self.key
.encrypt(&mut OsRng {}, PaddingScheme::PKCS1v15Encrypt, data)
} else {
let mut res = self.key.encrypt(
&mut OsRng {},
PaddingScheme::PKCS1v15Encrypt,
&data[..ENCRYPTION_CHUNK_SIZE],
)?;
res.extend(self.encrypt_data(&data[ENCRYPTION_CHUNK_SIZE..])?);
Ok(res)
}
}
pub fn to_vec(&self) -> Vec<u8> {
let n_bytes = self.key.n().to_bytes_be();
let e_bytes = self.key.e().to_bytes_be();
let mut res = vec![
(n_bytes.len() / 256) as u8,
(n_bytes.len() % 256) as u8,
(e_bytes.len() / 256) as u8,
(e_bytes.len() % 256) as u8,
];
res.extend(n_bytes);
res.extend(e_bytes);
res
}
pub fn from_vec(data: Vec<u8>) -> IFResult<Self> {
if data.len() < 4 {
return Err(IFError::SerializationError(String::from(
"Not enough bytes in serialized PublicKey",
)));
}
let n_len = data[0] as usize * 256 + data[1] as usize;
let e_len = data[2] as usize * 256 + data[3] as usize;
if data.len() != e_len + n_len + 4 {
return Err(IFError::SerializationError(String::from(
"Not enough bytes in serialized PublicKey",
)));
}
let n_bytes = &data[4..n_len + 4];
let e_bytes = &data[4 + n_len..e_len + n_len + 4];
Ok(Self {
key: RsaPublicKey::new(
BigUint::from_bytes_be(n_bytes),
BigUint::from_bytes_be(e_bytes),
)?,
})
}
pub fn to_vec(&self) -> serde_cbor::Result<Vec<u8>> {
serde_cbor::to_vec(&self)
/// Get a short string that's kind of a hash
pub fn get_short_id(&self) -> String {
alloc::string::String::from_utf8(
self.to_vec()
.iter()
.skip(90)
.take(5)
.map(|c| c % 26 + 97)
.collect::<Vec<u8>>(),
)
.unwrap()
}
}
impl PublicKey {
fn hash(&self) -> Vec<u8> {
self.to_vec()
}
}
@ -42,7 +116,8 @@ impl Keys {
/// Generate new random key
pub fn generate() -> Self {
let mut rng = OsRng;
let private_key = RsaPrivateKey::new(&mut rng, KEY_LENGTH).expect("failed to generate a key");
let private_key =
RsaPrivateKey::new(&mut rng, KEY_LENGTH).expect("failed to generate a key");
let public_key = RsaPublicKey::from(&private_key);
Self {
private_key,
@ -54,46 +129,68 @@ impl Keys {
impl Keys {
/// Sign content using these keys
pub fn sign(&self, content: &[u8]) -> RsaRes<Vec<u8>> {
self.private_key.sign(PaddingScheme::PKCS1v15Sign { hash: None }, content)
self.private_key.sign(
PaddingScheme::PKCS1v15Sign { hash: None },
&Sha224::new().chain(content).result().to_vec(),
)
}
/// Decrypt data
pub fn decrypt_data(&self, data_encrypted: &[u8]) -> RsaRes<Vec<u8>> {
self.private_key.decrypt(PaddingScheme::PKCS1v15Encrypt, data_encrypted)
if data_encrypted.len() <= ENCRYPTION_OUTPUT_CHUNK_SIZE {
self.private_key
.decrypt(PaddingScheme::PKCS1v15Encrypt, data_encrypted)
} else {
let mut res = self.private_key.decrypt(
PaddingScheme::PKCS1v15Encrypt,
&data_encrypted[..ENCRYPTION_OUTPUT_CHUNK_SIZE],
)?;
res.extend(self.decrypt_data(&data_encrypted[ENCRYPTION_OUTPUT_CHUNK_SIZE..])?);
Ok(res)
}
}
/// Get public key
pub fn get_public(&self) -> PublicKey {
PublicKey { key: self.public_key.clone() }
PublicKey {
key: self.public_key.clone(),
}
}
}
#[test]
fn test_encrypt() {
let data = vec![0, 5, 8, 135, 67];
let data = vec![0, 5, 8, 135, 67, 45, 32, 5];
let keys = Keys::generate();
let data_encrypted = keys.get_public().encrypt_data(&data).unwrap();
assert_eq!(keys.decrypt_data(&data_encrypted).unwrap(), data);
assert_eq!(
keys.decrypt_data(&keys.get_public().encrypt_data(&data).unwrap()).unwrap(),
data
keys.decrypt_data(&keys.get_public().encrypt_data(&data.repeat(300)).unwrap())
.unwrap(),
data.repeat(300)
);
}
#[cfg(test)]
use alloc::vec;
#[test]
fn test_invalid_encrypt() {
let data = vec![0, 5, 8, 135, 67];
let keys_1 = Keys::generate();
let keys_2 = Keys::generate();
assert!(keys_2.decrypt_data(&keys_1.get_public().encrypt_data(&data).unwrap()).is_err());
assert!(keys_2
.decrypt_data(&keys_1.get_public().encrypt_data(&data).unwrap())
.is_err());
}
#[test]
fn test_signing() {
let data = vec![0, 5, 8, 135, 67];
let keys = Keys::generate();
assert!(keys.get_public().verify_sign(&data, &keys.sign(&data).unwrap()));
assert!(keys
.get_public()
.verify_sign(&data, &keys.sign(&data).unwrap()));
assert!(keys
.get_public()
.verify_sign(&data.repeat(340), &keys.sign(&data.repeat(340)).unwrap()));
}
#[test]
@ -101,5 +198,15 @@ fn test_invalid_signing() {
let data = vec![0, 5, 8, 135, 67];
let keys_1 = Keys::generate();
let keys_2 = Keys::generate();
assert!(!keys_2.get_public().verify_sign(&data, &keys_1.sign(&data).unwrap()));
assert!(!keys_2
.get_public()
.verify_sign(&data, &keys_1.sign(&data).unwrap()));
}
#[test]
fn test_pkey_caching() {
assert_ne!(
Keys::generate().get_public().hash(),
Keys::generate().get_public().hash()
)
}

127
src/interface.rs

@ -1,7 +1,6 @@
use alloc::string::String;
use crate::message::MessageBytes;
use crate::res::IFResult;
use alloc::string::String;
/// Some data that can be provided to the interface to send the message to a target.
///
@ -10,12 +9,11 @@ use crate::res::IFResult;
pub(crate) type TargetingData = String;
/// In an std environment we require that the interface can be send safely between threads
#[cfg(not(std))]
#[cfg(not(feature = "std"))]
pub trait InterfaceRequirements {}
#[cfg(std)]
pub trait InterfaceRequirements = Send + Sync;
#[cfg(feature = "std")]
pub trait InterfaceRequirements: Send + Sync {}
/// An interface that can be used to
pub trait Interface: InterfaceRequirements {
@ -26,19 +24,33 @@ pub trait Interface: InterfaceRequirements {
/// For systems that don't support concurrency, there can be only one interface in this function waits for a message (to avoid blocking).
/// That's why it's necessary to check if it is the case for this interface, and it's done using function `Interface::has_blocking_main()`
fn main_loop_iteration(&mut self) -> IFResult<()>;
/// Check if `main_loop_iteration` stops execution and waits for a message
fn has_blocking_main(&self) -> bool {
false // hopefully...
false // hopefully...
}
/// Get some way of identification for this interface
fn id(&self) -> &str;
/// Send a message. If no `interface_data` is provided, we should consider it to be a broadcast.
/// If, on the other hand, `interface_data` is not `None`, it should be used to send the message to the target.
fn send(&mut self, message: &[u8] /*MessageBytes*/, interface_data: Option<TargetingData>) -> IFResult<()>;
fn send(
&mut self,
message: &[u8], /*MessageBytes*/
interface_data: Option<TargetingData>,
) -> IFResult<()>;
/// Receive a message through this interface. Returns a result with an option of (message bytes, target).
/// `None` means there is no message available at the time.
/// The implementations of this function shouldn't wait for new messages, but
fn receive(&mut self) -> IFResult<Option<(MessageBytes, TargetingData /*interface data*/)>>;
/// Dump the interface to string
fn get_dump_data(&self) -> String;
/// Create the interface from dumped data
fn from_dump(data: String) -> IFResult<Self> where Self: Sized;
}
#[cfg(test)]
@ -46,16 +58,21 @@ pub mod test_interface {
use crate::interface::{Interface, InterfaceRequirements, TargetingData};
use crate::message::MessageBytes;
use crate::res::IFResult;
use alloc::string::String;
use alloc::string::ToString;
use alloc::sync::Arc;
use alloc::vec;
use alloc::vec::Vec;
use spin::Mutex;
#[derive(Default)]
pub struct TestInterface {
pub struct SimpleTestInterface {
messages: Vec<(Vec<u8>, TargetingData)>,
}
impl InterfaceRequirements for TestInterface {}
impl InterfaceRequirements for SimpleTestInterface {}
impl Interface for TestInterface {
impl Interface for SimpleTestInterface {
fn main_loop_iteration(&mut self) -> IFResult<()> {
Ok(())
}
@ -65,14 +82,94 @@ pub mod test_interface {
}
fn send(&mut self, message: &[u8], interface_data: Option<TargetingData>) -> IFResult<()> {
self.messages.push((Vec::from(message), interface_data.unwrap_or_default()));
self.messages
.push((Vec::from(message), interface_data.unwrap_or_default()));
Ok(())
}
fn receive(&mut self) -> IFResult<Option<(MessageBytes, TargetingData)>> {
Ok(
self.messages.pop()
)
Ok(self.messages.pop())
}
fn get_dump_data(&self) -> String {
"".to_string()
}
fn from_dump(_data: String) -> IFResult<Self> {
Ok(Default::default())
}
}
pub type Storage = Vec<(Vec<u8>, TargetingData)>;
#[derive(Default)]
pub struct TestInterface {
this_peer_id: String,
storage: Arc<Mutex<Storage>>,
messages: Vec<(Vec<u8>, TargetingData)>,
}
impl Interface for TestInterface {
fn main_loop_iteration(&mut self) -> IFResult<()> {
let mut storage_locked = self.storage.lock();
while let Some(i) = storage_locked
.iter()
.position(|msg| msg.1 == self.this_peer_id || msg.1.is_empty())
{
self.messages.push(storage_locked.remove(i));
}
Ok(())
}
fn id(&self) -> &str {
"test_interface"
}
fn send(&mut self, message: &[u8], target: Option<TargetingData>) -> IFResult<()> {
self.storage
.lock()
.push((Vec::from(message), target.unwrap_or_default()));
Ok(())
}
fn receive(&mut self) -> IFResult<Option<(MessageBytes, TargetingData)>> {
Ok(self.messages.pop())
}
fn get_dump_data(&self) -> String {
"".to_string()
}
fn from_dump(_data: String) -> IFResult<Self> {
Ok(TestInterface {
this_peer_id: "".to_string(),
storage: Arc::new(Default::default()),
messages: vec![],
})
}
}
impl InterfaceRequirements for TestInterface {}
pub fn create_test_interfaces(n: usize) -> Vec<TestInterface> {
let storage_mutex = Arc::new(Mutex::new(vec![]));
(0..n)
.map(|i| TestInterface {
this_peer_id: i.to_string(),
storage: storage_mutex.clone(),
messages: vec![],
})
.collect()
}
#[test]
fn test_test_interface() {
let mut interfaces = create_test_interfaces(2);
interfaces[0].send(b"123", Some("1".to_string())).unwrap();
interfaces[1].main_loop_iteration().unwrap();
assert_eq!(
interfaces[1].receive().unwrap().unwrap().0.as_slice(),
b"123"
);
}
}

507
src/interfaces/ip.rs

@ -0,0 +1,507 @@
use alloc::borrow::ToOwned;
use alloc::string::{String, ToString};
use alloc::vec;
use alloc::vec::Vec;
use core::ops::RangeInclusive;
use core::str::FromStr;
use core::time::Duration;
use rayon::prelude::*;
use serde::{Deserialize, Serialize};
use std::net::TcpStream;
use std::{format, net};
use crate::interface::{Interface, InterfaceRequirements, TargetingData};
use crate::message::MessageBytes;
use crate::res::{IFError, IFResult};
use crate::std::io::{Read, Write};
use crate::std::println;
pub const DEFAULT_PORT: u16 = 50000;
const SOCKET_RANGE: RangeInclusive<u16> = 50000..=50010;
/// The threshold for the number of peers below which we are desperate
const PEER_THRESHOLD: usize = 70;
type Peer = (net::IpAddr, u16);
/// Interface for interactions using tcp sockets
pub struct IPInterface {
id: String,
connections: Vec<net::TcpStream>,
listener: net::TcpListener,
peers: Vec<Peer>,
package_queue: Vec<(IPPackage, String /* from_peer */)>,
main_loop_iterations: u64,
}
/// Data for the serialization of `IPInterface`
#[derive(Serialize, Deserialize)]
pub struct SerData {
pub peers: Vec<Peer>,
pub port: u16,
}
#[derive(Debug, Clone)]
struct IPPackage {
version: u8,
package_type: MessageType,
size: u32,
message: MessageBytes,
}
#[derive(Debug, Copy, Clone)]
enum MessageType {
Common,
PeerRequest,
PeersShared,
}
impl MessageType {
fn from_u8(id: u8) -> IFResult<MessageType> {
match id {
0 => Ok(MessageType::Common),
1 => Ok(MessageType::PeerRequest),
2 => Ok(MessageType::PeersShared),
_ => Err(IFError::General("Incorrect message type".to_string())),
}
}
fn as_u8(&self) -> u8 {
match self {
MessageType::Common => 0,
MessageType::PeerRequest => 1,
MessageType::PeersShared => 2,
}
}
}
fn compare_addrs(peer: &Peer, addr: net::SocketAddr) -> bool {
addr.ip() == peer.0 && addr.port() == peer.1
}
impl InterfaceRequirements for IPInterface {}
impl Interface for IPInterface {
fn main_loop_iteration(&mut self) -> IFResult<()> {
if let Some(conn) = self.listener.incoming().next() {
match conn {
Ok(stream) => {
stream.set_nonblocking(true)?;
let addr = stream.peer_addr()?;
println!(
"({:?}): New client: {:?}",
addr,
self.listener.local_addr().unwrap()
);
if self.peers.iter().all(|(ip, _)| *ip != addr.ip()) {
self.peers.push((addr.ip(), addr.port()));
}
self.connections.push(stream)
}
Err(ref e) if e.kind() == std::io::ErrorKind::WouldBlock => {}
Err(e) => return Err(IFError::from(e)),
}
}
let mut new_connections: Vec<TcpStream> = vec![];
for connection in &mut self.connections {
connection.set_nonblocking(true)?;
let mut buf = [0u8; 6];
let peek_res = connection.peek(&mut buf);
if peek_res.is_err() || peek_res.unwrap() < 6 {
continue
}
let mut header: [u8; 6] = [0, 0, 0, 0, 0, 0];
match connection.read_exact(&mut header) {
Ok(_) => {}
Err(ref e)
if e.kind() == std::io::ErrorKind::WouldBlock
|| e.kind() == std::io::ErrorKind::UnexpectedEof =>
{
continue
}
Err(e) => {
println!("Error: {:?}", e);
continue;
}
};
let version = header[0];
let package_type = MessageType::from_u8(header[1])?;
let size = bytes_to_size([header[2], header[3], header[4], header[5]]);
connection.set_nonblocking(false)?;
connection.set_read_timeout(Some(std::time::Duration::from_millis(500)))?;
let mut message_take = connection.take(size as u64);
let mut message: Vec<u8> = vec![];
message_take.read_to_end(&mut message)?;
match package_type {
MessageType::PeerRequest => {
let peers_to_share = if self.peers.len() < PEER_THRESHOLD {
self.peers.clone()
} else {
self.peers.iter().skip(7).step_by(2).cloned().collect()
};
let message = serde_cbor::to_vec(&peers_to_share)?;
IPInterface::send_package(
connection,
IPPackage {
version,
package_type: MessageType::PeersShared,
size: message.len() as u32,
message,
},
)?;
}
MessageType::Common => {
let package = IPPackage {
version,
package_type,
size,
message,
};
self.package_queue
.push((package, format!("{:?}", connection.peer_addr()?)));
}
MessageType::PeersShared => {
let peers: Vec<Peer> = serde_cbor::from_slice(message.as_slice())?;
for peer in peers {
if !self.peers.contains(&peer) {
if let Some(conn) = IPInterface::new_connection(&peer)? {
new_connections.push(conn)
}
self.peers.push(peer);
}
}
}
}
}
for conn in new_connections.iter_mut() {
self.initialize_connection(conn)?;
}
self.connections.extend(new_connections);
self.main_loop_iterations += 1;
// Every 50 iterations we connect to everyone we know
if self.main_loop_iterations % 50 == 0 {
let connected_addresses = self
.connections
.iter()
.filter_map(|conn| conn.peer_addr().ok())
.collect::<Vec<_>>();
let peers_we_do_not_have_connections_with = self
.peers
.iter()
.filter(|p| {
!connected_addresses
.iter()
.any(|addr| compare_addrs(p, *addr))
})
.copied()
.collect::<Vec<_>>();
self.connections
.extend(IPInterface::get_connections_to_peers(
&peers_we_do_not_have_connections_with,
self.peers.len() < PEER_THRESHOLD * 2,
));
}
// We do a peer exchange every 30 iterations
if self.main_loop_iterations % 30 == 0 && !self.connections.is_empty() {
let connection_index =
(self.main_loop_iterations / 30) as usize % self.connections.len();
IPInterface::request_peers(&mut self.connections[connection_index])?;
}
Ok(())
}
fn has_blocking_main(&self) -> bool {
false
}
fn id(&self) -> &str {
&*self.id
}
fn send(&mut self, message: &[u8], interface_data: Option<TargetingData>) -> IFResult<()> {
let package = IPPackage {
version: 0,
package_type: MessageType::Common,
size: message.len() as u32,
message: Vec::from(message),
};
match interface_data {
Some(ip_string) => {
let addr: net::SocketAddr = ip_string.parse().expect("Unable to parse address");
let index = self.obtain_connection(&(addr.ip(), addr.port()))?;
IPInterface::send_package(&mut self.connections[index], package)?;
}
None => {
for conn in &mut self.connections {
IPInterface::send_package(conn, package.clone())?;
}
}
};
Ok(())
}
fn receive(&mut self) -> IFResult<Option<(MessageBytes, TargetingData)>> {
// if !self.package_queue.is_empty() {
// println!(
// "({:?}): New message from {}. By the way, I know {} peers and have {} connections",
// self.listener.local_addr().unwrap(),
// self.package_queue.last().unwrap().1,
// self.peers.len(),
// self.connections.len()
// );
// }
match self.package_queue.pop() {
Some((ip_package, data)) => Ok(Some((ip_package.message, data))),
None => Ok(None),
}
}
fn get_dump_data(&self) -> String {
let data = SerData {
peers: self.peers.clone(),
port: self.listener.local_addr().unwrap().port(),
};
serde_json::to_string(&data).unwrap()
}
fn from_dump(data: String) -> IFResult<Self> {
if !data.is_empty() {
let data: SerData = serde_json::from_str(data.as_str()).unwrap();
IPInterface::new(data.port, data.peers)
} else {
let ip_path = std::path::Path::new(".if_ip_peers");
let peers = if ip_path.exists() {
std::fs::read_to_string(ip_path)
.unwrap()
.split('\n')
.filter_map(|line| net::SocketAddr::from_str(line).ok())
.map(|addr| (addr.ip(), addr.port()))
.collect()
} else {
println!("Warning: there are no peers in IP, which makes it essentially useless");
vec![]
};
IPInterface::new(DEFAULT_PORT, peers)
}
}
}
impl IPInterface {
fn get_connections_to_peers(peers: &[Peer], do_peer_request: bool) -> Vec<TcpStream> {
peers
.par_iter()
.map(Self::new_connection)
.filter_map(|r| r.ok())
.filter_map(|r| r)
.map(|mut c| -> IFResult<TcpStream> {
println!("Requesting peers from {:?}", c.peer_addr().unwrap());
if do_peer_request {
Self::request_peers(&mut c)?;
}
Ok(c)
})
.filter_map(|r| r.ok())
.collect::<Vec<_>>()
}
pub fn new(port: u16, peers: Vec<Peer>) -> IFResult<Self> {
let listener = match create_tcp_listener(port) {
Some(listener) => listener,
None => {
return Err(IFError::General(String::from(
"Unable to open TCP listener",
)));
}
};
listener.set_nonblocking(true)?;
let connections = Self::get_connections_to_peers(&peers, true);
Ok(IPInterface {
id: String::from("IP Interface"),
connections,
listener,
peers,
package_queue: vec![],
main_loop_iterations: 0,
})
}
pub fn dump(&self) -> IFResult<Vec<u8>> {
Ok(serde_cbor::to_vec(&self.peers)?)
}
pub fn load(&mut self, data: Vec<u8>) -> IFResult<()> {
let peers: Vec<Peer> = serde_cbor::from_slice(&data)?;
self.peers = peers;
Ok(())
}
fn send_package(stream: &mut net::TcpStream, package: IPPackage) -> IFResult<()> {
stream.set_write_timeout(Some(std::time::Duration::from_millis(700)))?;
#[cfg(test)]
stream.set_nonblocking(false)?;
let mut header: Vec<u8> = vec![package.version, package.package_type.as_u8()];
for byte in size_to_bytes(package.size) {
header.push(byte);
}
stream.write_all(&*header)?;
stream.write_all(&*package.message)?;
Ok(())
}
fn initialize_connection(&self, conn: &mut TcpStream) -> IFResult<()> {
if self.peers.len() < PEER_THRESHOLD {
Self::request_peers(conn)?;
}
Ok(())
}
fn request_peers(conn: &mut TcpStream) -> IFResult<()> {
IPInterface::send_package(
conn,
IPPackage {
version: 0,
package_type: MessageType::PeerRequest,
size: 0,
message: vec![],
},
)?;
Ok(())
}
fn obtain_connection(&mut self, addr: &Peer) -> IFResult<usize> {
if let Some(pos) = self.connections.iter().position(|con| {
con.peer_addr().is_ok() && compare_addrs(addr, con.peer_addr().unwrap())
}) {
return Ok(pos);
}
if let Some(conn) = Self::new_connection(addr)? {
self.connections.push(conn);
Ok(self.connections.len() - 1)
} else {
Err(IFError::CouldNotConnect)
}
}
fn new_connection(addr: &Peer) -> IFResult<Option<TcpStream>> {
for port in addr.1..addr.1 + 3 {
match net::TcpStream::connect_timeout(
&net::SocketAddr::new(addr.0, port as u16),
Duration::from_millis(300),
) {
Ok(connection) => {
return Ok(Some(connection));
}
Err(_) => continue,
};
}
Ok(None)
}
}
fn create_tcp_listener(port: u16) -> Option<net::TcpListener> {
for port in port..port + 5 {
match net::TcpListener::bind("0.0.0.0:".to_owned() + &port.to_string()) {
Ok(listener) => return Some(listener),
Err(_e) => {}
}
}
None
}
fn parse_header(data: Vec<u8>) -> IFResult<IPPackage> {
Ok(IPPackage {
version: data[0],
package_type: MessageType::from_u8(data[1])?,
size: bytes_to_size([data[3], data[4], data[5], data[6]]),
message: vec![],
})
}
fn size_to_bytes(mut a: u32) -> [u8; 4] {
let mut arr: [u8; 4] = [0, 0, 0, 0];
for i in [3, 2, 1, 0] {
arr[i] = (a % 256) as u8;
a /= 256;
}
arr
}
fn bytes_to_size(arr: [u8; 4]) -> u32 {
let mut size = 0;
for size_byte in &arr {
size = size * 256 + *size_byte as u32;
}
size
}
#[test]
fn test_creating_connection() -> IFResult<()> {
let message = *b"Hello world from ironforest";
let original_msg_copy = message;
let mut interface1 = IPInterface::new(50000, vec![])?;
let mut interface2 = IPInterface::new(50001, vec![])?;
let t2 = std::thread::spawn(move || {
for _ in 0..30 {
interface2.main_loop_iteration().unwrap();
std::thread::sleep(std::time::Duration::from_millis(150));
}
interface2
});
let t1 = std::thread::spawn(move || {
interface1
.send(&message, Some(String::from("0.0.0.0:50001")))
.unwrap();
interface1
});
let res1 = t1.join();
match res1 {
Ok(_res) => {
println!("Thread Ok");
}
Err(e) => println!("{:?}", e),
}
let res2 = t2.join();
match res2 {
Ok(mut res) => {
println!("Thread Ok");
match res.receive() {
Ok(tmp) => match tmp {
Some((message, _metadata)) => {
println!("Received {:?}", message);
assert_eq!(message, original_msg_copy)
}
None => {
println!("None");
panic!();
}
},
Err(e) => println!("{:?}", e),
}
}
Err(e) => println!("{:?}", e),
}
Ok(())
}
#[cfg(test)]
pub fn create_test_interfaces(n: usize) -> impl Iterator<Item = IPInterface> {
let ip_addr = std::net::IpAddr::from_str("0.0.0.0").unwrap();
(0..n).map(move |i| {
IPInterface::new(
(5000 + 5 * i) as u16,
// (0..n)
// .filter(|j| *j != i)
// .map(|j| (ip_addr, (5000 + 5 * j) as u16))
// .collect(),
vec![(ip_addr, (5000 + 5 * ((i + 1) % n)) as u16)],
)
.unwrap()
})
}

31
src/interfaces/mod.rs

@ -1,6 +1,35 @@
#[cfg(feature = "std")]
pub mod ip;
use crate::interface::Interface;
use alloc::vec;
use alloc::vec::Vec;
use alloc::boxed::Box;
use alloc::string::String;
#[cfg(feature = "std")]
use crate::interfaces::ip::IPInterface;
use crate::res::IFResult;
pub fn get_interfaces() -> alloc::vec::Vec<alloc::boxed::Box<dyn Interface>> {
#[cfg(not(feature = "std"))]
pub fn get_interfaces() -> Vec<Box<dyn Interface>> {
vec![]
}
#[cfg(feature = "std")]
pub fn get_interfaces() -> Vec<Box<dyn Interface>> {
vec![Box::new(IPInterface::from_dump(Default::default()).unwrap())]
}
#[cfg(not(feature = "std"))]
pub fn restore_interfaces(_data: Vec<String>) -> IFResult<Vec<Box<dyn Interface>>> {
Ok(vec![])
}
#[cfg(feature = "std")]
pub fn restore_interfaces(data: Vec<String>) -> IFResult<Vec<Box<dyn Interface>>> {
if data.is_empty() {
Ok(get_interfaces())
} else {
Ok(vec![Box::new(IPInterface::from_dump(data[0].clone())?)])
}
}

546
src/ironforce.rs

@ -1,14 +1,25 @@
use alloc::vec::Vec;
use alloc::vec;
use crate::crypto::PublicKey;
use crate::crypto::{Keys, PublicKey};
use crate::message::{Message, MessageType, ServiceMessageType};
use crate::res::{IFError, IFResult};
use crate::transport::Transport;
use crate::tunnel::Tunnel;
use crate::transport::{PeerInfo, Transport};
use crate::tunnel::{Tunnel, TunnelPublic};
use alloc::collections::BTreeMap;
#[cfg(feature = "std")]
use alloc::string::ToString;
use alloc::vec;
use alloc::vec::Vec;
use serde::{Deserialize, Serialize};
#[cfg(feature = "std")]
use std::println;
const TUNNEL_MAX_REPEAT_COUNT: u32 = 3;
#[cfg(feature = "std")]
pub const DEFAULT_FILE: &str = ".if_data.json";
/// Main worker
pub struct IronForce {
/// Keys for this instance
pub keys: Keys,
/// the struct that manages communicating with neighbor nodes
transport: Transport,
/// Tunnels that are known to this node
@ -19,40 +30,135 @@ pub struct IronForce {
additional_modules: Vec<()>,
/// Non-service messages to give outside
messages: Vec<Message>,
/// Tunnels that has not been confirmed yet (no backward spread)
///
/// `[(Tunnel, Optional target node, local peer ids)]`
tunnels_pending: Vec<(
TunnelPublic,
Option<PublicKey>, /* target node */
(u64, u64), /* local peer ids */
)>,
/// True if this instance has background thread
has_background_worker: bool,
/// Messages that were already processed (stored to avoid "echo chambers")
processed_messages: Vec<u64>,
/// Counters of how many times the tunnel has passed through this node on its forward movement (so that we don't do a shitposting)
///
/// Maps tunnel's first local_id to the number
tunnel_counters: BTreeMap<u64, u32>,
/// Auto save
auto_save: bool,
}
/// Data for the serialization of IF
#[derive(Serialize, Deserialize)]
pub struct IFSerializationData {
pub keys: Keys,
pub tunnels: Vec<Tunnel>,
pub peers: Vec<PeerInfo>,
pub interfaces_data: Vec<alloc::string::String>,
}
impl IFSerializationData {
pub fn default() -> IFSerializationData {
IFSerializationData {
keys: Keys::generate(),
tunnels: vec![],
peers: vec![],
interfaces_data: vec![],
}
}
}
impl IronForce {
/// Create new worker
pub fn new() -> Self {
Self {
keys: Keys::generate(),
transport: Transport::new(crate::interfaces::get_interfaces()),
tunnels: vec![],
additional_modules: vec![],
messages: vec![],
tunnels_pending: vec![],
has_background_worker: false,
processed_messages: vec![],
tunnel_counters: Default::default(),
auto_save: true,
}
}
/// Create a new tunnel to another node
fn create_new_tunnel(&mut self, _destination: PublicKey) -> IFResult<Tunnel> {
todo!()
fn initialize_tunnel_creation(&mut self, destination: &PublicKey) -> IFResult<()> {
let tunnel = TunnelPublic::new_singlecast();
self.tunnels_pending
.push((tunnel.clone(), Some(destination.clone()), (0, 0)));
let message = Message::build()
.message_type(MessageType::Service(
ServiceMessageType::TunnelBuildingForwardMovement(
tunnel,
destination.encrypt_data(&self.keys.get_public().to_vec())?,
),
))
.recipient(destination)
.sign(&self.keys)
.build()?;
self.send_to_all(message)?;
Ok(())
}
/// Send a multicast or broadcast message
fn send_to_all(&mut self, _message: Message) -> IFResult<()> {
todo!()
pub fn send_to_all(&mut self, message: Message) -> IFResult<()> {
self.transport
.send_message(serde_cbor::to_vec(&message)?, None)
}
/// Send a message through tunnel
fn send_through_tunnel(&mut self, _tunnel_id: u64, _message: Message, _direction: Option<bool>) -> IFResult<()> {
todo!()
fn send_through_tunnel(
&mut self,
tunnel_id: u64,
mut message: Message,
direction: Option<bool>,
) -> IFResult<()> {
let tunnel: Tunnel = if let Some(tun) = self
.tunnels
.iter()
.cloned()
.find(|t| t.id == Some(tunnel_id))
{
tun
} else {
return Err(IFError::TunnelNotFound);
};
message.tunnel_id = (tunnel_id, tunnel.peer_ids.0 != 0);
let peer_ids = match (direction, tunnel.peer_ids) {
(_, (x, 0)) => vec![x],
(_, (0, x)) => vec![x],
(None, (x1, x2)) => vec![x1, x2],
(Some(true), (x1, _x2)) => vec![x1],
(Some(false), (_x1, x2)) => vec![x2],
};
let msg_bytes = serde_cbor::to_vec(&message)?;
for peer in peer_ids {
self.transport.send_message(msg_bytes.clone(), Some(peer))?;
}
Ok(())
}
/// Send a message to another node,
/// creating a new tunnel if needed
pub fn send_message(&mut self, message: Message, destination: PublicKey) -> IFResult<()> {
if let Some(Some(tunnel_id)) = self.tunnels.iter()
.find(|t| t.target_node.as_ref() == Some(&destination) || t.nodes_in_tunnel.as_ref().map(|nodes| nodes.contains(&destination)) == Some(true))
.map(|tunnel| tunnel.id) {
pub fn send_message(&mut self, message: Message, destination: &PublicKey) -> IFResult<()> {
if let Some(Some(tunnel_id)) = self
.tunnels
.iter()
.find(|t| {
t.target_node.as_ref() == Some(destination)
|| t.nodes_in_tunnel
.as_ref()
.map(|nodes| nodes.contains(destination))
== Some(true)
})
.map(|tunnel| tunnel.id)
{
self.send_through_tunnel(tunnel_id, message, None)
} else {
Err(IFError::TunnelNotFound)
@ -61,19 +167,417 @@ impl IronForce {
/// Process a message: if it's a service message, act accordingly.
/// Otherwise, add to `self.messages`
fn process_message(&mut self, message: Message) {
match message.message_type {
MessageType::Service(msg_type) => match msg_type {
ServiceMessageType::TunnelBuilding(_tunnel) => {
todo!()
fn process_message(&mut self, message: Message, inc_peer: u64) -> IFResult<()> {
if self.processed_messages.contains(&message.message_id) {
return Ok(());
}
self.processed_messages.push(message.message_id);
match &message.message_type {
MessageType::Service(msg_type) => {
match msg_type {
ServiceMessageType::TunnelBuildingForwardMovement(tunnel, sender_enc) => {
let count = *self
.tunnel_counters
.get(&tunnel.local_ids[0])
.unwrap_or(&0u32);
if count > TUNNEL_MAX_REPEAT_COUNT {
return Ok(());
}
self.tunnel_counters.insert(tunnel.local_ids[0], count + 1);
if message.check_recipient(&self.keys) {
let mut tunnel_pub = tunnel.clone();
tunnel_pub.id = Some(rand::random());
let sender = PublicKey::from_vec(self.keys.decrypt_data(sender_enc)?)?;
let tunnel = Tunnel {
id: tunnel_pub.id,
local_ids: tunnel_pub.local_ids.clone(),
peer_ids: (0, inc_peer),
ttd: 0,
nodes_in_tunnel: None,
is_multicast: false,
target_node: Some(sender),
};
self.tunnels.push(tunnel);
self.transport.send_message(
serde_cbor::to_vec(
&Message::build()
.message_type(MessageType::Service(
ServiceMessageType::TunnelBuildingBackwardMovement(
tunnel_pub.clone(),
),
))
.tunnel((tunnel_pub.id.unwrap(), false))
.sign(&self.keys)
.build()?,
)?,
Some(inc_peer),
)?;
} else {
let mut tunnel = tunnel.clone();
tunnel.add_local_id();
self.tunnels_pending
.push((tunnel.clone(), None, (inc_peer, 0)));
self.send_to_all(message)?;
}
}
ServiceMessageType::TunnelBuildingBackwardMovement(tunnel_p) => {
match self.tunnels_pending.iter().find(|tun| {
tunnel_p.local_ids.contains(tun.0.local_ids.last().unwrap())
}) {
// This doesn't concern us
None => {}
// This is a tunnel initialization proposed by us (and we got it back, yay)
Some((_, Some(target), peers)) => {
let tunnel = Tunnel {
id: tunnel_p.id,
local_ids: tunnel_p.local_ids.clone(),
peer_ids: (peers.0, inc_peer),
ttd: 0,
nodes_in_tunnel: None,
is_multicast: false,
target_node: Some(target.clone()),
};
self.tunnels.push(tunnel);
#[cfg(feature = "std")]
println!("[{}] Successfully created a new tunnel", self.short_id());
// Send some initialization message or something
}
// This is a tunnel initialization proposed by someone else that has passed through us on its forward movement
Some((_, None, peers)) => {
let tunnel = Tunnel {
id: tunnel_p.id,
local_ids: tunnel_p.local_ids.clone(),
peer_ids: (peers.0, inc_peer),
ttd: 0,
nodes_in_tunnel: None,
is_multicast: false,
target_node: None,
};
self.tunnels.push(tunnel);
#[cfg(feature = "std")]
println!("[{}] Successfully created a new tunnel", self.short_id());
self.transport
.send_message(serde_cbor::to_vec(&message)?, Some(peers.0))?;
}
}
}
}
}
MessageType::SingleCast | MessageType::Broadcast => { self.messages.push(message) }
MessageType::SingleCast if message.check_recipient(&self.keys) => {
#[cfg(feature = "std")]
println!("New message: {:?}", message.get_decrypted(&self.keys));
self.messages.push(message.clone())
}
MessageType::SingleCast => {
if let Some(tunnel) = self
.tunnels
.iter()
.find(|tun| tun.id == Some(message.tunnel_id.0))
{
let peer_id = if message.tunnel_id.1 {
tunnel.peer_ids.0
} else {
tunnel.peer_ids.1
};
self.transport
.send_message(serde_cbor::to_vec(&message)?, Some(peer_id))?;
}
}
MessageType::Broadcast => {
self.messages.push(message.clone());
self.send_to_all(message)?;
}
}
Ok(())
}
/// Get a message from `self.messages`
pub fn read_message(&mut self) -> Option<Message> {
self.messages.pop()
}
pub fn main_loop_iteration(&mut self) -> IFResult<()> {
self.transport.main_loop_iteration()?;
while let Some((msg, inc_peer)) = self.transport.receive() {
self.process_message(serde_cbor::from_slice(msg.as_slice())?, inc_peer)?
}
Ok(())
}
fn short_id(&self) -> alloc::string::String {
self.keys.get_public().get_short_id()
}
pub fn get_serialization_data(&self) -> IFSerializationData {
IFSerializationData {
keys: self.keys.clone(),
tunnels: self.tunnels.clone(),
peers: self.transport.peers.clone(),
interfaces_data: self.transport.get_interfaces_data(),
}
}
pub fn from_serialization_data(data: IFSerializationData) -> IFResult<Self> {
Ok(Self {
keys: data.keys,
transport: Transport::restore(data.peers.clone(), data.interfaces_data.clone())?,
tunnels: data.tunnels,
additional_modules: vec![],
messages: vec![],
tunnels_pending: vec![],
has_background_worker: false,
processed_messages: vec![],
tunnel_counters: Default::default(),
auto_save: true,
})
}
#[cfg(feature = "std")]
pub fn from_file(filename: alloc::string::String) -> IFResult<Self> {
let filename = if filename.is_empty() { DEFAULT_FILE.to_string() } else { filename };
if std::path::Path::new(&filename).exists() {
Self::from_serialization_data(serde_json::from_str(std::fs::read_to_string(filename)?.as_str())?)
} else {
Ok(Self::new())
}
}
#[cfg(feature = "std")]
pub fn save_to_file(&self, filename: Option<alloc::string::String>) -> IFResult<()> {
std::fs::write(
filename.unwrap_or_else(|| DEFAULT_FILE.to_string()),
serde_json::to_string(&self.get_serialization_data())?,
)?;
Ok(())
}
#[cfg(feature = "std")]
pub fn launch_main_loop(
mut self,
sleep_millis: u64,
) -> (
std::thread::JoinHandle<!>,
std::sync::Arc<std::sync::Mutex<Self>>,
) {
self.has_background_worker = true;
let container = std::sync::Arc::new(std::sync::Mutex::new(self));
let container_clone = container.clone();
let thread = std::thread::spawn(move || {
let mut counter: u64 = 0;
loop {
match container_clone.lock().unwrap().main_loop_iteration() {
Ok(_) => {}
Err(e) => println!("An error happened in the main loop: {:?}", e),
}
counter += 1;
std::thread::sleep(std::time::Duration::from_millis(sleep_millis));
if counter % 50 == 0 {
container_clone.lock().unwrap().save_to_file(None).unwrap()
}
}
});
(thread, container)
}
}
#[cfg(test)]
mod if_testing {
use crate::crypto::Keys;
use crate::interface::test_interface::create_test_interfaces;
use crate::ironforce::IronForce;
use crate::message::{Message, MessageType};
use crate::res::IFResult;
use crate::transport::Transport;
use alloc::boxed::Box;
use alloc::vec;
use alloc::vec::Vec;
fn create_test_network() -> Vec<IronForce> {
let interfaces = create_test_interfaces(5);
let transports = interfaces
.into_iter()
.map(|interface| Transport::new(vec![Box::new(interface)]));
transports
.map(|tr| IronForce {
keys: Keys::generate(),
transport: tr,
tunnels: vec![],
additional_modules: vec![],
messages: vec![],
tunnels_pending: vec![],
has_background_worker: false,
processed_messages: vec![],
tunnel_counters: Default::default(),
auto_save: false,
})
.collect()
}
#[test]
fn test_creating_a_tunnel() -> IFResult<()> {
let mut network = create_test_network();
let key_1 = network[1].keys.get_public();
network[0].initialize_tunnel_creation(&key_1)?;
network[0].main_loop_iteration()?;
network[1].main_loop_iteration()?;
network[0].main_loop_iteration()?;
assert!(!network[0].tunnels.is_empty());
Ok(())
}
#[test]
fn test_sending_message() -> IFResult<()> {
let mut network = create_test_network();
let key_1 = network[1].keys.get_public();
network[0].initialize_tunnel_creation(&key_1)?;
network[0].main_loop_iteration()?;
network[1].main_loop_iteration()?;
network[0].main_loop_iteration()?;
let zero_keys = network[0].keys.clone();
network[0].send_message(
Message::build()
.message_type(MessageType::SingleCast)
.sign(&zero_keys)
.recipient(&key_1)
.content(b"hello".to_vec())
.build()?,
&key_1,
)?;
network[1].main_loop_iteration()?;
let msg = network[1].read_message();
assert!(msg.is_some());
assert_eq!(
msg.unwrap()
.get_decrypted(&network[1].keys)
.unwrap()
.as_slice(),
b"hello"
);
Ok(())
}
}
#[cfg(test)]
#[cfg(feature = "std")]
mod test_with_ip {
use crate::crypto::Keys;
use crate::interfaces::ip::create_test_interfaces;
use crate::ironforce::IronForce;
use crate::message::{Message, MessageType};
use crate::res::IFResult;
use crate::transport::Transport;
use alloc::boxed::Box;
use alloc::vec;
use alloc::vec::Vec;
use std::println;
// fn create_test_interfaces(n: usize) -> impl Iterator<Item = IPInterface> {
// let ip_addr = std::net::IpAddr::from_str("127.0.0.1").unwrap();
// (0..n).map(move |i| {
// IPInterface::new(
// (5000 + 5 * i) as u16,
// (0..n)
// .filter(|j| *j != i)
// .map(|j| (ip_addr, (5000 + 5 * j) as u16))
// .collect(),
// )
// .unwrap()
// })
// }
fn create_test_network() -> Vec<IronForce> {
let interfaces = create_test_interfaces(4);
let transports = interfaces
.into_iter()
.map(|interface| Transport::new(vec![Box::new(interface)]));
transports
.map(|tr| IronForce {
keys: Keys::generate(),
transport: tr,
additional_modules: vec![],
tunnels: vec![],
messages: vec![],
tunnels_pending: vec![],
has_background_worker: false,
processed_messages: vec![],
tunnel_counters: Default::default(),
auto_save: false,
})
.collect()
}
// MAIN TEST RIGHT HERE
#[test]
fn test_creating_a_tunnel_and_sending_message() -> IFResult<()> {
let mut network = create_test_network();
let key_1 = network[1].keys.get_public();
let (mut node0, mut node1) = (network.remove(0), network.remove(0));
let node0_keys = node0.keys.clone();
println!("node0 id: {}", node0.short_id());
println!("node1 id: {}", node1.short_id());
let (mut node2, mut node3) = (network.remove(0), network.remove(0));
let t1 = std::thread::spawn(move || {
for _i in 0..170 {
// println!("Iteration {} (1)", i);
node0.main_loop_iteration().unwrap();
std::thread::sleep(std::time::Duration::from_millis(10));
}
node0
});
let t2 = std::thread::spawn(move || {
for _i in 0..250 {
// println!("Iteration {} (2)", i);
node1.main_loop_iteration().unwrap();
std::thread::sleep(std::time::Duration::from_millis(10));
}
node1
});
std::thread::spawn(move || loop {
node2.main_loop_iteration().unwrap();
std::thread::sleep(std::time::Duration::from_millis(10));
});
std::thread::spawn(move || loop {
std::thread::sleep(std::time::Duration::from_millis(10));
node3.main_loop_iteration().unwrap();
});
let mut node0 = t1.join().unwrap();
node0.initialize_tunnel_creation(&key_1)?;
let mut node1 = t2.join().unwrap();
let t1 = std::thread::spawn(move || {
for _ in 0..18 {
node0.main_loop_iteration().unwrap();
std::thread::sleep(std::time::Duration::from_millis(50));
}
node0
});
let t2 = std::thread::spawn(move || {
for _ in 0..18 {
node1.main_loop_iteration().unwrap();
}
node1
});
let mut node0 = t1.join().unwrap();
let mut node1 = t2.join().unwrap();
assert!(!node0.tunnels.is_empty());
node0.send_message(
Message::build()
.message_type(MessageType::SingleCast)
.content(b"Hello!".to_vec())
.recipient(&key_1)
.sign(&node0_keys)
.build()?,
&key_1,
)?;
let t2 = std::thread::spawn(move || {
for _ in 0..18 {
node1.main_loop_iteration().unwrap();
}
node1
});
let mut node1 = t2.join().unwrap();
let msg = node1.read_message();
assert!(msg.is_some());
assert_eq!(msg.unwrap().get_decrypted(&node1.keys)?, b"Hello!".to_vec());
Ok(())
}
}

17
src/lib.rs

@ -1,21 +1,32 @@
#![no_std]
#![allow(dead_code)]
#![feature(trait_alias)]
#![feature(never_type)]
#[cfg(feature = "std")]
extern crate std;
extern crate alloc;
extern crate rand;
extern crate rsa;
extern crate serde;
extern crate core_error;
extern crate spin;
mod crypto;
mod ironforce;
mod message;
mod transport;
mod interface;
mod interfaces;
mod res;
pub mod interface;
pub mod interfaces;
pub mod res;
mod tunnel;
pub use ironforce::IronForce;
pub use message::{Message, MessageType};
pub use crypto::{Keys, PublicKey};
#[cfg(test)]
mod tests {
}

276
src/message.rs

@ -1,16 +1,16 @@
use alloc::string::String;
use alloc::vec::Vec;
use crate::crypto::{Keys, PublicKey};
use crate::res::IFResult;
use crate::tunnel::TunnelPublic;
use serde::{Serialize, Deserialize};
use alloc::string::String;
use alloc::vec::Vec;
use serde::{Deserialize, Serialize};
use sha2::Digest;
/// A serialized message
pub(crate) type MessageBytes = Vec<u8>;
/// Signature of the message: optional and optionally encrypted sender's key and signed hash
#[derive(Serialize, Deserialize, Clone)]
#[derive(Serialize, Deserialize, Clone, Debug)]
pub enum Signature {
/// The message is signed. Author is unknown
NotSigned,
@ -26,32 +26,73 @@ pub enum Signature {
},
}
impl Signature {
/// Get sender's key or its encrypted version for hashing
pub(crate) fn sender_or_encrypted_sender(&self) -> Option<Vec<u8>> {
match &self {
Signature::NotSigned => None,
Signature::Signed { sender, .. } => Some(sender.to_vec()),
Signature::SignedPrivately {
sender_encrypted, ..
} => Some(sender_encrypted.clone()),
}
}
}
/// Network name and version
#[derive(Serialize, Deserialize, Clone)]
struct NetworkInfo {
#[derive(Serialize, Deserialize, Clone, Debug)]
pub struct NetworkInfo {
network_name: String,
version: String,
}
impl Default for NetworkInfo {
fn default() -> Self {
Self { version: String::from("0.1.0"), network_name: String::from("test") }
Self {
version: String::from("0.1.0"),
network_name: String::from("test"),
}
}
}
#[derive(Serialize, Deserialize, Clone)]
#[derive(Serialize, Deserialize, Clone, Debug)]
pub enum MessageType {
SingleCast,
Broadcast,
Service(ServiceMessageType),
}
#[derive(Serialize, Deserialize, Clone)]
impl MessageType {
fn hash(&self) -> Vec<u8> {
match self {
MessageType::SingleCast => Vec::from([0]),
MessageType::Broadcast => Vec::from([1]),
MessageType::Service(ServiceMessageType::TunnelBuildingForwardMovement(
tunnel,
sender_enc,
)) => [2, 0]
.iter()
.chain(tunnel.hash().iter())
.chain(sender_enc)
.copied()
.collect(),
MessageType::Service(ServiceMessageType::TunnelBuildingBackwardMovement(tunnel)) => {
[3, 0].iter().chain(tunnel.hash().iter()).copied().collect()
}
}
}
}
#[derive(Serialize, Deserialize, Clone, Debug)]
pub enum ServiceMessageType {
TunnelBuilding(TunnelPublic)
/// Creating a tunnel - stage 1
///
/// (tunnel to be created, sending node encrypted for the recipient)
TunnelBuildingForwardMovement(TunnelPublic, Vec<u8>),
TunnelBuildingBackwardMovement(TunnelPublic),
}
#[derive(Serialize, Deserialize, Clone)]
#[derive(Serialize, Deserialize, Clone, Debug)]
pub enum MessageContent {
/// Just plaintext message content
Plain(Vec<u8>),
@ -61,8 +102,26 @@ pub enum MessageContent {
None,
}
impl MessageContent {
pub fn hash(&self) -> Vec<u8> {
match self {
MessageContent::Plain(v) => sha2::Sha512::new()
.chain(&[0u8; 1])
.chain(v.as_slice())
.result()
.to_vec(),
MessageContent::Encrypted(v) => sha2::Sha512::new()
.chain(&[1; 1])
.chain(v.as_slice())
.result()
.to_vec(),
MessageContent::None => Vec::new(),
}
}
}
/// The struct for messages that are sent in the network
#[derive(Serialize, Deserialize, Clone)]
#[derive(Serialize, Deserialize, Clone, Debug)]
pub struct Message {
/// Content of the message (not to be confused with the bytes that we are sending through interfaces)
///
@ -73,43 +132,119 @@ pub struct Message {
/// Sender's signature
pub signature: Signature,
/// A random number that is used in hash together with the content
salt: u64,
pub message_id: u64,
/// Hash of message content and the salt
hash: Vec<u8>,
/// Optional: hash of the message encrypted for the recipient, so that the recipient can know that this message is for them, but nobody else
recipient_verification: Option<Vec<u8>>,
/// ID of the tunnel that is used
tunnel_id: u64,
/// ID of the tunnel that is used and the direction
pub tunnel_id: (u64, bool),
/// Network info
network_info: NetworkInfo,
}
impl Message {
/// Verify message's hash
pub fn verify(&self) -> bool {
todo!()
pub fn verify_hash(&self) -> bool {
self.hash
== Self::calculate_hash(
&self.content,
self.message_type.clone(),
self.signature.sender_or_encrypted_sender(),
&self.network_info,
)
}
/// Verify sender's signature
pub fn verify_signature(&self, recipient_keys: Keys) -> bool {
match &self.signature {
Signature::NotSigned => true,
Signature::Signed { signature, sender } => {
sender.verify_sign(self.hash.as_slice(), signature.as_slice())
}
Signature::SignedPrivately { signature, .. } => {
if let Some(sender) = self.get_sender(&recipient_keys) {
sender.verify_sign(
self.hash.as_slice(),
&match recipient_keys.decrypt_data(signature.as_slice()) {
Ok(r) => r,
Err(_e) => return false,
},
)
} else {
false
}
}
}
}
/// Check if this message is for this set of keys
pub fn check_recipient(&self, _keys: Keys) -> bool {
todo!()
pub fn check_recipient(&self, keys: &Keys) -> bool {
keys.decrypt_data(&self.recipient_verification.clone().unwrap())
.is_ok()
}
/// Get decrypted content of the message
pub fn get_decrypted(&self, _keys: Keys) -> IFResult<Vec<u8>> {
todo!()
pub fn get_decrypted(&self, keys: &Keys) -> IFResult<Vec<u8>> {
Ok(match &self.content {
MessageContent::Plain(c) => c.clone(),
MessageContent::Encrypted(encrypted_content) => {
keys.decrypt_data(encrypted_content.as_slice())?
}
MessageContent::None => Vec::new(),
})
}
pub fn calculate_hash(_content: &MessageContent, _message_type: MessageType, _sender_or_encrypted_sender: Option<Vec<u8>>) -> Vec<u8> {
todo!()
pub fn calculate_hash(
content: &MessageContent,
message_type: MessageType,
sender_or_encrypted_sender: Option<Vec<u8>>,
network_info: &NetworkInfo,
) -> Vec<u8> {
sha2::Sha512::new()
.chain(content.hash().as_slice())
.chain(message_type.hash().as_slice())
.chain(sender_or_encrypted_sender.unwrap_or_default().as_slice())
.chain(network_info.network_name.as_bytes())
.chain(network_info.version.as_bytes())
.result()
.to_vec()
}
/// Encrypt hash of the message for the recipient
pub fn generate_recipient_verification(hash: Vec<u8>, recipient: PublicKey) -> rsa::errors::Result<Vec<u8>> {
pub fn generate_recipient_verification(
hash: Vec<u8>,
recipient: PublicKey,
) -> rsa::errors::Result<Vec<u8>> {
recipient.encrypt_data(&hash)
}
}
/// Try to get sender from the signature
fn get_sender(&self, keys: &Keys) -> Option<PublicKey> {
match &self.signature {
Signature::NotSigned => None,
Signature::Signed { sender, .. } => Some(sender.clone()),
Signature::SignedPrivately {
sender_encrypted, ..
} => {
if let Some(Some(res)) = keys
.decrypt_data(sender_encrypted.as_slice())
.ok()
.map(|k| PublicKey::from_vec(k).ok())
{
Some(res)
} else {
None
}
}
}
}
/// Create new MessageBuilder
pub fn build() -> MessageBuilder {
MessageBuilder::new()
}
}
/// Message builder to create a new message step-by-step, like `Message::build().message_type(...).sign(...)`
pub struct MessageBuilder {
@ -121,7 +256,7 @@ pub struct MessageBuilder {
/// Recipient's public key (if present, the content will be encrypted and recipient verification field will be set)
recipient: Option<PublicKey>,
/// ID of the tunnel that is used
tunnel_id: u64,
tunnel_id: (u64, bool),
}
impl MessageBuilder {
@ -132,7 +267,7 @@ impl MessageBuilder {
message_type: None,
sender: None,
recipient: None,
tunnel_id: 0,
tunnel_id: (0, false),
}
}
@ -148,13 +283,13 @@ impl MessageBuilder {
}
/// Set message's recipient (and therefore set recipient verification and encrypt the content)
pub fn recipient(mut self, recipient: PublicKey) -> Self {
self.recipient = Some(recipient);
pub fn recipient(mut self, recipient: &PublicKey) -> Self {
self.recipient = Some(recipient.clone());
self
}
/// Set tunnel id
pub fn tunnel(mut self, tunnel_id: u64) -> Self {
pub fn tunnel(mut self, tunnel_id: (u64, bool)) -> Self {
self.tunnel_id = tunnel_id;
self
}
@ -168,36 +303,91 @@ impl MessageBuilder {
/// Get the resulting message
pub fn build(self) -> IFResult<Message> {
let salt = rand::random();
let sender_encrypted = if let (Some(sender_keys), Some(recipient)) = (self.sender.as_ref(), self.recipient.as_ref()) {
Some(recipient.encrypt_data(&sender_keys.get_public().to_vec()?)?)
} else { None };
let sender_encrypted = if let (Some(sender_keys), Some(recipient)) =
(self.sender.as_ref(), self.recipient.as_ref())
{
Some(recipient.encrypt_data(&sender_keys.get_public().to_vec())?)
} else {
None
};
let network_info = NetworkInfo::default();
let hash = Message::calculate_hash(
&self.content,
self.message_type.clone().unwrap(),
sender_encrypted.clone()
.or_else(
|| self.sender.as_ref()
.map(|sender_keys| sender_keys.get_public().to_vec().unwrap())
),
sender_encrypted.clone().or_else(|| {
self.sender
.as_ref()
.map(|sender_keys| sender_keys.get_public().to_vec())
}),
&network_info,
);
let recipient_verification = self.recipient.as_ref().map(|rec| rec.encrypt_data(&hash).unwrap());
let recipient_verification = self
.recipient
.as_ref()
.map(|rec| rec.encrypt_data(&hash).unwrap());
let signature = match (self.sender, self.recipient) {
(Some(sender_keys), Some(recipient_key)) => Signature::SignedPrivately {
sender_encrypted: sender_encrypted.unwrap(),
signature: recipient_key.encrypt_data(&sender_keys.sign(&hash)?)?,
},
(Some(sender_keys), None) => Signature::Signed { sender: sender_keys.get_public(), signature: sender_keys.sign(&hash)? },
(Some(sender_keys), None) => Signature::Signed {
sender: sender_keys.get_public(),
signature: sender_keys.sign(&hash)?,
},
(None, _) => Signature::NotSigned,
};
Ok(Message {
content: self.content,
message_type: self.message_type.unwrap(),
signature,
salt,
message_id: salt,
hash,
recipient_verification,
tunnel_id: self.tunnel_id,
network_info: Default::default(),
network_info,
})
}
}
#[cfg(test)]
use alloc::vec;
#[test]
fn test_hashing_message_type() {
let msg_type_1 = MessageType::Broadcast;
let msg_type_2 = MessageType::Service(ServiceMessageType::TunnelBuildingForwardMovement(
TunnelPublic::new_for_test(),
vec![1, 2, 3],
));
assert_eq!(msg_type_1.hash(), msg_type_1.hash());
assert_eq!(msg_type_2.hash(), msg_type_2.hash());
assert_ne!(msg_type_1.hash(), msg_type_2.hash())
}
#[test]
fn test_hash_message_content() {
let content_1 = MessageContent::Plain(vec![1, 2, 4, 5]);
let content_2 = MessageContent::Encrypted(vec![1, 2, 4, 5]);
let content_3 = MessageContent::Plain(vec![1, 3, 4, 5]);
assert_eq!(content_1.hash(), content_1.hash());
assert_ne!(content_1.hash(), MessageContent::None.hash());
assert_ne!(content_1.hash(), content_2.hash());
assert_ne!(content_1.hash(), content_3.hash());
assert_ne!(content_3.hash(), content_2.hash());
}
#[test]
fn test_building_message() -> IFResult<()> {
let keys_1 = Keys::generate();
let keys_2 = Keys::generate();
let msg = Message::build()
.content(b"hello".to_vec())
.sign(&keys_1)
.recipient(&keys_2.get_public())
.tunnel((1, false))
.message_type(MessageType::SingleCast)
.build()?;
assert!(msg.verify_hash());
assert!(msg.verify_signature(keys_2));
Ok(())
}

17
src/res.rs

@ -9,10 +9,14 @@ pub enum IFError {
General(String),
/// A tunnel satisfying some conditions has not been found
TunnelNotFound,
/// Could not establish a connection
CouldNotConnect,
/// Error during serialization
SerializationError(String),
/// Error in rsa
CryptoError(String),
/// Error in std::io
IoError(String),
}
@ -23,12 +27,25 @@ pub enum IFError {
// }
// }
#[cfg(feature = "std")]
impl From<std::io::Error> for IFError {
fn from(e: std::io::Error) -> Self {
Self::IoError(format!("{:?}", e))
}
}
impl From<serde_cbor::Error> for IFError {
fn from(e: serde_cbor::Error) -> Self {
Self::SerializationError(format!("{:?}", e))
}
}
impl From<serde_json::Error> for IFError {
fn from(e: serde_json::Error) -> Self {
Self::SerializationError(format!("{:?}", e))
}
}
impl From<rsa::errors::Error> for IFError {
fn from(e: rsa::errors::Error) -> Self {
Self::CryptoError(format!("{:?}", e))

185
src/transport.rs

@ -1,17 +1,19 @@
use alloc::boxed::Box;
use alloc::string::{String, ToString};
use alloc::vec::Vec;
use alloc::vec;
use crate::interface::{Interface, TargetingData};
use crate::message::MessageBytes;
use crate::res::IFResult;
use alloc::boxed::Box;
use alloc::string::{String, ToString};
use alloc::vec;
use alloc::vec::Vec;
use serde::{Deserialize, Serialize};
#[cfg(std)]
#[cfg(feature = "std")]
use rayon::prelude::*;
#[cfg(feature = "std")]
use std::println;
/// An identification of a peer - something that we can use to send a message to id
#[derive(Clone, Debug, PartialEq)]
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub struct PeerInfo {
/// Something to locally identify this peer
pub peer_id: u64,
@ -31,31 +33,47 @@ impl PeerInfo {
}
}
/// The struct that manages all the communication with peers
pub struct Transport {
pub interfaces: Vec<Box<dyn Interface>>,
peers: Vec<PeerInfo>,
pub(crate) peers: Vec<PeerInfo>,
}
impl Transport {
/// Create new transport with given interfaces
pub fn new(interfaces: Vec<Box<dyn Interface>>) -> Self {
#[cfg(not(std))]
if interfaces.iter().map(|interface| interface.has_blocking_main() as u8).sum::<u8>() > 1 {
#[cfg(not(feature = "std"))]
if interfaces
.iter()
.map(|interface| interface.has_blocking_main() as u8)
.sum::<u8>()
> 1
{
panic!("There is two interfaces with blocking main loops and we have no threads because this is no_std!");
}
Self { interfaces, peers: vec![] }
Self {
interfaces,
peers: vec![],
}
}
/// Find a peer in `self.peers` by its id
fn get_peer_by_id(&self, peer_id: u64) -> Option<PeerInfo> {
self.peers.iter().find(|peer| peer.peer_id == peer_id).cloned()
self.peers
.iter()
.find(|peer| peer.peer_id == peer_id)
.cloned()
}
/// Try to find a peer in `self.peers` by interface_id and targeting data
fn get_peer_by_parameters(&self, interface_id: &str, data: &str /*&TargetingData*/) -> Option<&PeerInfo> {
self.peers.iter().find(|peer| peer.interface_id == interface_id && peer.interface_targeting_data == data)
fn get_peer_by_parameters(
&self,
interface_id: &str,
data: &str, /*&TargetingData*/
) -> Option<&PeerInfo> {
self.peers
.iter()
.find(|peer| peer.interface_id == interface_id && peer.interface_targeting_data == data)
}
/// Insert a new peer into out database and return its id or find an existing one with these parameters
@ -67,32 +85,42 @@ impl Transport {
self.peers.push(new_peer);
peer_id
}
Some(peer) => peer.peer_id
Some(peer) => peer.peer_id,
}
}
/// Get interface index by its ID
fn interface_index_by_id(&self, interface_id: &str) -> usize {
self.interfaces.iter().position(|interface| interface.id() == interface_id).unwrap_or_else(|| panic!("Invalid interface id"))
self.interfaces
.iter()
.position(|interface| interface.id() == interface_id)
.unwrap_or_else(|| panic!("Invalid interface id"))
}
/// Send message bytes to a peer if data is provided or broadcast the data if `peer == None`
pub fn send_message(&mut self, message: MessageBytes, peer_id: Option<u64>) -> IFResult<()> {
let peer = if let Some(peer_id) = peer_id { self.get_peer_by_id(peer_id) } else { None };
let peer = if let Some(peer_id) = peer_id {
self.get_peer_by_id(peer_id)
} else {
None
};
match peer {
// Broadcast
None => {
#[cfg(not(std))]
{
for interface in &mut self.interfaces {
interface.send(&message, None)?;
}
#[cfg(not(feature = "std"))]
{
for interface in &mut self.interfaces {
interface.send(&message, None)?;
}
}
// If we have concurrency, we will do it concurrently
#[cfg(std)]
{
self.interfaces.par_iter_mut().map(|interface| interface.send(&message, None)).for_each(drop);
}
#[cfg(feature = "std")]
{
self.interfaces
.par_iter_mut()
.map(|interface| interface.send(&message, None))
.for_each(drop);
}
Ok(())
}
// Singlecast
@ -107,81 +135,130 @@ impl Transport {
///
/// Returns a result with an option of `(message, peer_id)`
pub fn receive(&mut self) -> Option<(MessageBytes, u64 /* peer id*/)> {
if let Some((interface_id, (msg, peer_data))) = self.interfaces
if let Some((interface_id, (msg, peer_data))) = self
.interfaces
.iter_mut()
// For each interface return (interface id, message result)
.map(|interface| (interface.id().to_string(), interface.receive()))
// If there was an error, print it
.map(|res| match res {
(id, Err(e)) => {
#[cfg(std)]
#[cfg(feature = "std")]
println!("An error occurred while receiving: {:?}", e);
(id, Err(e))
}
(id, Ok(r)) => (id, Ok(r))
(id, Ok(r)) => (id, Ok(r)),
})
// Find a result where there is a message
.find(|r| matches!(r, (_, Ok(Some(_)))))
// Safely unwrap this result (we already matched `Ok(Some(_))`)
.map(|(id, r)| (id, r.unwrap().unwrap())) {
.map(|(id, r)| (id, r.unwrap().unwrap()))
{
Some((msg, self.find_or_add_peer(interface_id, peer_data)))
} else { None }
} else {
None
}
}
/// Run one iteration of the main loop
pub fn main_loop_iteration(&mut self) -> IFResult<()> {
#[cfg(std)]
self.interfaces.par_iter_mut().map(|interface| interface.main_loop_iteration()).collect::<IFResult<_>>()?;
#[cfg(not(std))]
{
self.interfaces.iter_mut().try_for_each(|interface| if !interface.has_blocking_main() { interface.main_loop_iteration() } else { Ok(()) })?;
let blocking_interface_index = self.interfaces.iter().position(|interface| interface.has_blocking_main());
if let Some(ind) = blocking_interface_index {
self.interfaces[ind].main_loop_iteration()?;
#[cfg(feature = "std")]
self.interfaces
.par_iter_mut()
.map(|interface| interface.main_loop_iteration())
.collect::<IFResult<_>>()?;
#[cfg(not(feature = "std"))]
{
self.interfaces.iter_mut().try_for_each(|interface| {
if !interface.has_blocking_main() {
interface.main_loop_iteration()
} else {
Ok(())
}
})?;
let blocking_interface_index = self
.interfaces
.iter()
.position(|interface| interface.has_blocking_main());
if let Some(ind) = blocking_interface_index {
self.interfaces[ind].main_loop_iteration()?;
}
}
Ok(())
}
pub fn get_interfaces_data(&self) -> Vec<String> {
self.interfaces.iter().map(|interface| interface.get_dump_data()).collect()
}
pub fn restore(peers: Vec<PeerInfo>, interfaces_data: Vec<String>) -> IFResult<Self> {
Ok(Transport { interfaces: crate::interfaces::restore_interfaces(interfaces_data)?, peers })
}
}
#[cfg(test)]
use crate::interface::test_interface::TestInterface;
use crate::interface::test_interface::SimpleTestInterface;
#[test]
fn test_adding_peer_to_transport() {
let mut transport = Transport::new(vec![Box::new(TestInterface::default())]);
let mut transport = Transport::new(vec![Box::new(SimpleTestInterface::default())]);
let (interface_id, interface_targeting_data) = ("test_interface".to_string(), "hi".to_string());
assert!(transport.get_peer_by_parameters(interface_id.as_str(), interface_targeting_data.as_str()).is_none());
let peer_id = transport.find_or_add_peer(interface_id.clone(), interface_targeting_data.clone());
assert!(transport
.get_peer_by_parameters(interface_id.as_str(), interface_targeting_data.as_str())
.is_none());
let peer_id =
transport.find_or_add_peer(interface_id.clone(), interface_targeting_data.clone());
let peer = PeerInfo {
peer_id,
interface_id: interface_id.clone(),
interface_targeting_data: interface_targeting_data.clone(),
};
assert_eq!(transport.get_peer_by_parameters(interface_id.as_str(), interface_targeting_data.as_str()), Some(&peer));
assert_eq!(
transport.get_peer_by_parameters(interface_id.as_str(), interface_targeting_data.as_str()),
Some(&peer)
);
assert_eq!(transport.get_peer_by_id(peer_id), Some(peer));
}
#[test]
fn test_transport_sending() {
let mut transport = Transport::new(vec![Box::new(TestInterface::default())]);
let mut transport = Transport::new(vec![Box::new(SimpleTestInterface::default())]);
let (interface_id, interface_targeting_data) = ("test_interface".to_string(), "hi".to_string());
let peer_id = transport.find_or_add_peer(interface_id.clone(), interface_targeting_data.clone());
let peer_id = transport.find_or_add_peer(interface_id, interface_targeting_data.clone());
transport.send_message(vec![239, 123], None).unwrap();
assert_eq!(transport.interfaces[0].receive().unwrap(), Some((vec![239u8, 123], "".to_string())));
assert_eq!(
transport.interfaces[0].receive().unwrap(),
Some((vec![239u8, 123], "".to_string()))
);
assert!(transport.interfaces[0].receive() == IFResult::Ok(None));
transport.send_message(vec![239, 123], Some(peer_id)).unwrap();
assert_eq!(transport.interfaces[0].receive(), IFResult::Ok(Some((vec![239, 123], interface_targeting_data))));
transport
.send_message(vec![239, 123], Some(peer_id))
.unwrap();
assert_eq!(
transport.interfaces[0].receive(),
IFResult::Ok(Some((vec![239, 123], interface_targeting_data)))
);
}
#[test]
fn test_transport_receiving() {
let mut transport = Transport::new(vec![Box::new(TestInterface::default())]);
let mut transport = Transport::new(vec![Box::new(SimpleTestInterface::default())]);
let (interface_id, interface_targeting_data) = ("test_interface".to_string(), "hi".to_string());
let peer_id = transport.find_or_add_peer(interface_id.clone(), interface_targeting_data.clone());
let peer_id = transport.find_or_add_peer(interface_id.clone(), interface_targeting_data);
transport.send_message(vec![239, 123], None).unwrap();
assert_eq!(transport.receive(), Some((vec![239u8, 123], transport.get_peer_by_parameters(interface_id.as_str(), "").unwrap().peer_id)));
assert_eq!(
transport.receive(),
Some((
vec![239u8, 123],
transport
.get_peer_by_parameters(interface_id.as_str(), "")
.unwrap()
.peer_id
))
);
assert!(transport.receive().is_none());
transport.send_message(vec![239, 123], Some(peer_id)).unwrap();
transport
.send_message(vec![239, 123], Some(peer_id))
.unwrap();
assert_eq!(transport.receive(), Some((vec![239, 123], peer_id)));
}

70
src/tunnel.rs

@ -1,9 +1,11 @@
use alloc::vec::Vec;
use crate::crypto::PublicKey;
use serde::{Serialize, Deserialize};
use sha2::Digest;
use alloc::vec;
/// A tunnel that is used for communication
#[derive(Serialize, Clone, Deserialize)]
#[derive(Serialize, Clone, Deserialize, Debug)]
pub struct Tunnel {
/// Tunnel's id.
/// By the way, this id is `None` until the tunnel is validated in the backward movement
@ -23,14 +25,72 @@ pub struct Tunnel {
}
/// Tunnel, but only the fields that are ok to share
#[derive(Serialize, Clone, Deserialize)]
#[derive(Serialize, Clone, Deserialize, Debug)]
pub struct TunnelPublic {
/// Tunnel's id
id: Option<u64>,
pub id: Option<u64>,
/// Ids, each of them is just for local storage on each node until a final global id is created
local_ids: Vec<u64>,
pub local_ids: Vec<u64>,
/// Time at which this tunnel should be destroyed (UNIX epoch)
ttd: u64,
pub ttd: u64,
/// Public keys of nodes in the tunnel
nodes_in_tunnel: Option<Vec<PublicKey>>,
/// Is this tunnel used for multicast?
pub is_multicast: bool,
}
impl TunnelPublic {
pub fn new_singlecast() -> Self {
let mut tun = Self {
id: None,
local_ids: vec![],
ttd: 0,
nodes_in_tunnel: None,
is_multicast: false,
};
tun.add_local_id();
tun
}
/// Get the hash of the tunnel for verification
pub fn hash(&self) -> Vec<u8> {
sha2::Sha224::new()
.chain(serde_cbor::to_vec(self).unwrap().as_slice())
.result().to_vec()
}
#[cfg(test)]
pub fn new_for_test() -> Self {
TunnelPublic {
id: Some(56),
local_ids: vec![5, 500, 120],
ttd: 56,
nodes_in_tunnel: Some(vec![crate::crypto::Keys::generate().get_public()]),
is_multicast: true,
}
}
pub fn add_local_id(&mut self) -> u64 {
let local_id = rand::random();
// Add 0 to 7 random ids so it's impossible to get the length of the tunnel
// todo: enable it (after debug)
// for _ in 0..(rand::random() as u64 % 7) {
// self.local_ids.push(rand::random())
// }
self.local_ids.push(local_id);
local_id
}
}
#[test]
fn test_tunnel_hashing() {
let tun = TunnelPublic::new_for_test();
assert_eq!(tun.hash(), tun.hash());
assert_ne!(tun.hash(), TunnelPublic {
id: Some(56),
local_ids: vec![5, 500, 120],
ttd: 56,
nodes_in_tunnel: Some(vec![crate::crypto::Keys::generate().get_public()]),
is_multicast: false,
}.hash());
}

Loading…
Cancel
Save