Browse Source

Documentation and serialization

master
Lev 3 years ago
parent
commit
84e1b5c879
  1. 16
      degeon/src/chat.rs
  2. 128
      degeon/src/degeon_worker.rs
  3. 5
      degeon/src/gui_events.rs
  4. 5
      degeon/src/main.rs
  5. 11
      degeon/src/message.rs
  6. 143
      degeon/src/state.rs
  7. 1
      degeon/src/styles.rs
  8. 6
      src/interfaces/ip.rs
  9. 17
      src/ironforce.rs

16
degeon/src/chat.rs

@ -5,17 +5,26 @@ use crate::gui_events::GuiEvent;
use crate::message::{DegMessage, DegMessageContent, Profile};
use crate::state;
use crate::styles::style;
use serde::{Serialize, Deserialize};
#[derive(Clone, Debug)]
/// A chat in the messenger
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct Chat {
/// Public key of the other user
pub pkey: PublicKey,
/// Messages in this chat
pub messages: Vec<DegMessage>,
/// Profile of the other user
pub profile: Profile,
/// Scroll position
pub scrolled: f32,
/// Message in the field
pub input: String,
}
impl Chat {
/// Create a new chat
pub fn new(pkey: PublicKey) -> Self {
Self {
pkey,
@ -28,6 +37,7 @@ impl Chat {
}
impl Chat {
/// Render header of the 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)
@ -37,6 +47,7 @@ impl Chat {
.into()
}
/// Render the sending field
pub fn send_field<'a>(
input: String,
text_input_state: &'a mut iced::text_input::State,
@ -64,6 +75,7 @@ impl Chat {
.into()
}
/// Render chat preview
pub fn preview<'a>(
&'a self,
state: &'a mut button::State,
@ -82,6 +94,7 @@ impl Chat {
.into()
}
/// Render the chat view
pub fn view<'a>(
&'a self,
text_input_state: &'a mut iced::text_input::State,
@ -110,6 +123,7 @@ impl Chat {
.into()
}
/// Create an example chat
pub fn example(i: usize, my_keys: &Keys) -> Chat {
let pkey = Keys::generate().get_public();
Self {

128
degeon/src/degeon_worker.rs

@ -7,24 +7,46 @@ use ironforce::{IronForce, Keys, Message, MessageType, PublicKey};
use std::pin::Pin;
use std::sync::{Arc, Mutex};
use std::task::{Context, Poll};
use serde::{Serialize, Deserialize};
/// The container for logic, data, IF and protocol interactions
#[derive(Clone)]
pub struct Degeon {
/// The list of all chats for this instance
pub chats: Vec<Chat>,
pub my_name: String,
/// Profile of this user
pub profile: Profile,
/// Keys of this user
pub keys: Keys,
/// The IF worker
pub ironforce: Arc<Mutex<IronForce>>,
}
/// Data for serialization
#[derive(Serialize, Deserialize)]
pub struct DegeonData {
pub chats: Vec<Chat>,
pub profile: Profile,
pub keys: Keys,
}
/// Load IF and launch the main loop
///
/// Returns ironforce and keys
fn get_initialized_ironforce() -> (Arc<Mutex<IronForce>>, Keys) {
let ironforce = IronForce::from_file("".to_string()).unwrap();
let keys = ironforce.keys.clone();
println!("ID: {}", keys.get_public().get_short_id());
let (_thread, ironforce) = ironforce.launch_main_loop(1000);
(ironforce, keys)
}
impl Default for Degeon {
fn default() -> Self {
let ironforce = IronForce::from_file("".to_string()).unwrap();
let keys = ironforce.keys.clone();
println!("ID: {}", keys.get_public().get_short_id());
let (_thread, ironforce) = ironforce.launch_main_loop(1000);
let (ironforce, keys) = get_initialized_ironforce();
Self {
chats: vec![],
my_name: "".to_string(),
profile: Profile::default(),
keys,
ironforce,
}
@ -32,16 +54,17 @@ impl Default for Degeon {
}
impl Degeon {
/// Get profile for the current user
pub fn get_profile(&self) -> Profile {
Profile {
name: self.my_name.clone(),
}
self.profile.clone()
}
/// Find a chat in the list for a given public key
pub fn chat_with(&self, pkey: &PublicKey) -> Option<usize> {
self.chats.iter().position(|chat| &chat.pkey == pkey)
}
/// Process the incoming message and act accordingly
pub fn process_message(&self, msg: ironforce::Message) -> IFResult<Option<GuiEvent>> {
let deg_msg: ProtocolMsg =
match serde_json::from_slice(msg.get_decrypted(&self.keys)?.as_slice()) {
@ -49,7 +72,11 @@ impl Degeon {
Err(_) => return Ok(None),
};
let sender = msg.get_sender(&self.keys).unwrap();
println!("check_rec: {:?}, sender==self: {:?}", msg.check_recipient(&self.keys), sender == self.keys.get_public());
println!(
"check_rec: {:?}, sender==self: {:?}",
msg.check_recipient(&self.keys),
sender == self.keys.get_public()
);
if !msg.check_recipient(&self.keys) || sender == self.keys.get_public() {
return Ok(None);
}
@ -65,6 +92,7 @@ impl Degeon {
})
}
/// Send a multicast message through the network
pub fn send_multicast(&self, msg: ProtocolMsg) -> IFResult<()> {
self.ironforce.lock().unwrap().send_to_all(
Message::build()
@ -75,6 +103,7 @@ impl Degeon {
)
}
/// Send a message to a target through the network
pub fn send_message(&self, msg: ProtocolMsg, target: &PublicKey) -> IFResult<()> {
// if self.ironforce.lock().unwrap().get_tunnel(target).is_none() {
// println!("Creating a tunnel");
@ -101,13 +130,14 @@ impl Degeon {
)
}
/// Created an iced command that sends a message to a target
pub fn get_send_command(
&self,
msg: ProtocolMsg,
target: &PublicKey,
) -> iced::Command<GuiEvent> {
let if_clone = self.ironforce.clone();
let target = target.clone();
let _target = target.clone();
let keys = self.keys.clone();
println!("Creating a send command: {:?}", msg);
@ -120,6 +150,7 @@ impl Degeon {
Message::build()
.message_type(MessageType::Broadcast)
.content(serde_json::to_vec(&msg).unwrap())
// todo:
// .recipient(&target)
.sign(&keys)
.build()
@ -130,6 +161,8 @@ impl Degeon {
})
}
/// Create an iced command that sends a message through the network to a target
#[allow(dead_code)]
pub fn get_send_multicast_command(&self, msg: ProtocolMsg) -> iced::Command<GuiEvent> {
let keys = self.keys.clone();
let if_clone = self.ironforce.clone();
@ -155,17 +188,84 @@ impl Degeon {
}
}
const DEFAULT_FILENAME: &str = ".degeon.json";
impl Degeon {
/// Store most of the necessary data to string
pub fn serialize_to_string(&self) -> serde_json::Result<String> {
let data = DegeonData {
chats: self.chats.clone(),
profile: self.get_profile(),
keys: self.keys.clone(),
};
serde_json::to_string(&data)
}
/// Restore `Degeon` from serialized data
pub fn restore_from_string(data: String) -> IFResult<Self> {
let data_res: serde_json::Result<DegeonData> = serde_json::from_str(data.as_str());
let data = match data_res {
Ok(r) => r,
Err(_) => return Ok(Self::default()),
};
let (ironforce, _keys) = get_initialized_ironforce();
ironforce.lock().unwrap().keys = data.keys.clone();
let deg = Degeon {
chats: data.chats,
profile: data.profile,
keys: data.keys,
ironforce
};
Ok(deg)
}
/// Save to a file. If no filename is provided, the default is used
pub fn save_to_file(&self, filename: String) -> IFResult<()> {
let data = self.serialize_to_string()?;
let filename = if filename.is_empty() {
DEFAULT_FILENAME.to_string()
} else {
filename
};
std::fs::write(filename, data)?;
Ok(())
}
/// Restore from a file. If no filename is provided, the default is used
pub fn restore_from_file(filename: String) -> IFResult<Self> {
let filename = if filename.is_empty() {
DEFAULT_FILENAME.to_string()
} else {
filename
};
let content = std::fs::read_to_string(filename).unwrap_or_default();
Self::restore_from_string(content)
}
}
impl Stream for Degeon {
type Item = GuiEvent;
fn poll_next(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
let timestamp_0 = std::time::Instant::now();
let msg_raw = self.ironforce.lock().unwrap().read_message();
let msg = msg_raw.as_ref().map(|msg| self.process_message(msg.clone()).unwrap());
let msg = msg_raw
.as_ref()
.map(|msg| self.process_message(msg.clone()).unwrap());
if msg_raw.is_some() {
let msg_deg: ProtocolMsg = match serde_json::from_slice(msg_raw.as_ref().unwrap().get_decrypted(&self.keys).unwrap().as_slice()) {
let msg_deg: ProtocolMsg = match serde_json::from_slice(
msg_raw
.as_ref()
.unwrap()
.get_decrypted(&self.keys)
.unwrap()
.as_slice(),
) {
Ok(r) => r,
Err(_) => {println!("Couldn't deserialize {:?}", msg_raw); return Poll::Ready(Some(GuiEvent::None))}
Err(_) => {
println!("Couldn't deserialize {:?}", msg_raw);
return Poll::Ready(Some(GuiEvent::None));
}
};
println!("{:?} -> {:?}", msg_deg, msg);
}

5
degeon/src/gui_events.rs

@ -1,5 +1,6 @@
use crate::message::{DegMessage, Profile};
use ironforce::PublicKey;
use crate::state::AppScreen;
/// An enum with all possible events for this application
#[derive(Clone, Debug)]
@ -10,14 +11,14 @@ pub enum GuiEvent {
Typed(String),
/// The user clicked "Send"
SendMessage,
/// New chat shall be created because we have an incoming request
NewChat(PublicKey),
/// A new messaged arrived
NewMessageInChat(PublicKey, DegMessage),
/// A profile response arrived and we should store it
SetProfile(PublicKey, Profile),
/// We should send profile (in response to profile request)
WeHaveToSendProfile(PublicKey),
/// Go to another screen
ChangeScreen(AppScreen),
/// Nothing happened
None,
}

5
degeon/src/main.rs

@ -7,13 +7,8 @@ mod styles;
mod degeon_worker;
use iced::Application;
use ironforce::res::IFResult;
use ironforce::{IronForce, Message, MessageType, PublicKey};
use crate::state::DegeonApp;
fn main() -> Result<(), Box<dyn std::error::Error>> {
// let ironforce = IronForce::from_file("".to_string()).unwrap();
// let _if_keys = ironforce.keys.clone();
Ok(DegeonApp::run(iced::Settings::default())?)
}

11
degeon/src/message.rs

@ -1,6 +1,7 @@
use ironforce::PublicKey;
use serde::{Deserialize, Serialize};
/// A message in the messenger
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct DegMessage {
pub sender: PublicKey,
@ -9,6 +10,7 @@ pub struct DegMessage {
}
impl DegMessage {
/// Create a simple text message
pub fn new_text(text: String, my_key: &PublicKey) -> DegMessage {
Self {
sender: my_key.clone(),
@ -18,6 +20,7 @@ impl DegMessage {
}
}
/// The content of the message
#[derive(Clone, Debug, Serialize, Deserialize)]
pub enum DegMessageContent {
Text(String),
@ -25,15 +28,21 @@ pub enum DegMessageContent {
Service,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
/// User's profile
#[derive(Clone, Debug, Serialize, Deserialize, Default)]
pub struct Profile {
pub name: String,
}
/// A protocol message (that's sent through IF)
#[derive(Clone, Debug, Serialize, Deserialize)]
pub enum ProtocolMsg {
/// Requesting profile
ProfileRequest,
/// Responding to the profile request with a profile
ProfileResponse(Profile),
/// A peer discovery
Ping,
/// A message is sent
NewMessage(DegMessage),
}

143
degeon/src/state.rs

@ -1,21 +1,18 @@
use crate::chat::Chat;
use crate::degeon_worker::Degeon;
use crate::gui_events::GuiEvent;
use crate::message::{DegMessage, DegMessageContent, Profile, ProtocolMsg};
use crate::message::{DegMessage, DegMessageContent, ProtocolMsg};
use crate::styles::style;
use core::default::Default;
use futures::Stream;
use iced::window::Mode;
use iced::{
button, Align, Application, Button, Column, Element, HorizontalAlignment, Length, Row, Text,
TextInput, VerticalAlignment,
button, Align, Application, Button, Color, Column, Element, HorizontalAlignment, Length, Row,
Settings, Text, TextInput, VerticalAlignment,
};
use ironforce::res::{IFError, IFResult};
use ironforce::{IronForce, Keys, Message, MessageType, PublicKey};
use std::hash::Hash;
use std::pin::Pin;
use std::sync::{Arc, Mutex};
use std::task::{Context, Poll};
use ironforce::PublicKey;
/// Render a message into an iced `Element`
pub fn view_message(msg: &DegMessage, pkey: PublicKey) -> Option<Element<GuiEvent>> {
let is_from_me = pkey != msg.sender;
match &msg.content {
@ -44,18 +41,44 @@ pub fn view_message(msg: &DegMessage, pkey: PublicKey) -> Option<Element<GuiEven
}
}
/// The screens (pages) of the app
#[derive(Copy, Clone, Debug)]
pub enum AppScreen {
/// The one with the chats
Main,
/// Settings and profile
ProfileEditor,
/// The screen that appears if no peers are available and asks for the IP address of at least one peer
PeerInput,
}
impl Default for AppScreen {
fn default() -> Self {
AppScreen::Main
}
}
/// The main application struct (for iced)
#[derive(Default)]
pub struct DegeonApp {
/// The container for logic, data and IF
pub data: Degeon,
/// Current screen
screen: AppScreen,
/// Selected chat (on the main screen)
selected_chat: usize,
/// Send button
send_button_state: iced::button::State,
/// Message input field
text_input_state: iced::text_input::State,
/// Buttons for chat previews
preview_button_states: Vec<button::State>,
}
impl DegeonApp {
/// Render the list of chats with all previews
fn chat_list<'a>(
chats: &'a Vec<Chat>,
chats: &'a [Chat],
preview_button_states: &'a mut Vec<button::State>,
selected: usize,
) -> Element<'a, GuiEvent> {
@ -77,8 +100,9 @@ impl DegeonApp {
.into()
}
/// Render active chat section
pub fn active_chat<'a>(
chats: &'a Vec<Chat>,
chats: &'a [Chat],
selected_chat: usize,
send_button_state: &'a mut button::State,
text_input_state: &'a mut iced::text_input::State,
@ -95,20 +119,52 @@ impl DegeonApp {
}
}
impl DegeonApp {
fn main_view(&mut self) -> Element<GuiEvent> {
let Self {
data: Degeon { 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()
}
}
impl Application for DegeonApp {
type Executor = iced::executor::Default;
type Message = GuiEvent;
type Flags = ();
fn new(_: ()) -> (Self, iced::Command<GuiEvent>) {
let mut st = Self::default();
st.data.chats = vec![
Chat::example(1, &st.data.keys),
Chat::example(2, &st.data.keys),
];
st.preview_button_states = vec![Default::default(), Default::default()];
st.data.my_name = "John".to_string();
st.data.send_multicast(ProtocolMsg::Ping).unwrap();
let mut data = Degeon::restore_from_file("".to_string()).unwrap();
data.chats = vec![Chat::example(1, &data.keys), Chat::example(2, &data.keys)];
data.profile.name = "John".to_string();
data.send_multicast(ProtocolMsg::Ping).unwrap();
let st = DegeonApp {
data,
screen: Default::default(),
selected_chat: 0,
send_button_state: Default::default(),
text_input_state: Default::default(),
preview_button_states: vec![Default::default(), Default::default()],
};
let data_clone = st.data.clone();
std::thread::spawn(move || {
std::thread::sleep(std::time::Duration::from_secs(10));
@ -142,19 +198,20 @@ impl Application for DegeonApp {
.push(new_msg.clone());
let data_cloned = self.data.clone();
let target = self.data.chats[self.selected_chat].pkey.clone();
std::thread::spawn(move || data_cloned.send_message(ProtocolMsg::NewMessage(new_msg), &target).unwrap());
}
GuiEvent::NewChat(pkey) => {
if self.data.chat_with(&pkey).is_none() {
self.data.chats.push(Chat::new(pkey))
}
std::thread::spawn(move || {
data_cloned
.send_message(ProtocolMsg::NewMessage(new_msg), &target)
.unwrap()
});
self.data.save_to_file("".to_string()).unwrap();
}
GuiEvent::NewMessageInChat(pkey, msg) => {
if self.data.chat_with(&pkey).is_none() {
self.data.chats.push(Chat::new(pkey.clone()))
}
let ind = self.data.chat_with(&pkey).unwrap();
self.data.chats[ind].messages.push(msg)
self.data.chats[ind].messages.push(msg);
self.data.save_to_file("".to_string()).unwrap();
}
GuiEvent::SetProfile(pkey, name) => {
if self.data.chat_with(&pkey).is_none() {
@ -162,6 +219,7 @@ impl Application for DegeonApp {
}
let ind = self.data.chat_with(&pkey).unwrap();
self.data.chats[ind].profile = name;
self.data.save_to_file("".to_string()).unwrap();
}
GuiEvent::None => {}
GuiEvent::WeHaveToSendProfile(target) => {
@ -169,8 +227,9 @@ impl Application for DegeonApp {
return self.data.get_send_command(
ProtocolMsg::ProfileResponse(self.data.get_profile()),
&target,
)
);
}
GuiEvent::ChangeScreen(sc) => self.screen = sc,
}
iced::Command::none()
}
@ -179,29 +238,11 @@ impl Application for DegeonApp {
iced::Subscription::from_recipe(self.data.clone())
}
fn view(&mut self) -> Element<GuiEvent> {
let Self {
data: Degeon { 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()
fn view(&mut self) -> Element<'_, Self::Message> {
match self.screen {
AppScreen::Main => self.main_view(),
AppScreen::ProfileEditor => todo!(),
AppScreen::PeerInput => todo!(),
}
}
}

1
degeon/src/styles.rs

@ -6,6 +6,7 @@ pub mod style {
pub enum Button {
Primary,
Secondary,
#[allow(dead_code)]
Destructive,
InactiveChat,
}

6
src/interfaces/ip.rs

@ -134,7 +134,7 @@ impl Interface for IPInterface {
.iter()
.find(|p| compare_addrs(p, connection_addr))
{
if let Some(Some(conn)) = IPInterface::new_connection(peer).ok() {
if let Ok(Some(conn)) = IPInterface::new_connection(peer) {
new_connections.push(conn)
}
}
@ -276,7 +276,7 @@ impl Interface for IPInterface {
println!("Error while sending: {:?}", e);
e
},
);
).unwrap_or_default();
}
}
}
@ -401,7 +401,7 @@ impl IPInterface {
.connections
.iter()
.filter_map(|conn| conn.peer_addr().ok())
.position(|addr| compare_addrs(&peer, addr))
.position(|addr| compare_addrs(peer, addr))
{
self.connections.remove(ind);
}

17
src/ironforce.rs

@ -16,7 +16,7 @@ const TUNNEL_MAX_REPEAT_COUNT: u32 = 3;
#[cfg(feature = "std")]
pub const DEFAULT_FILE: &str = ".if_data.json";
/// Main worker
/// Main IF worker
#[derive(Hash)]
pub struct IronForce {
/// Keys for this instance
@ -54,9 +54,13 @@ pub struct IronForce {
/// Data for the serialization of IF
#[derive(Serialize, Deserialize)]
pub struct IFSerializationData {
/// Worker's keys
pub keys: Keys,
/// Saved tunnels that go through this node
pub tunnels: Vec<Tunnel>,
/// Peers for transport
pub peers: Vec<PeerInfo>,
/// Data for all interfaces (in IP, for example, that's port and IPs of peers)
pub interfaces_data: Vec<alloc::string::String>,
}
@ -325,6 +329,7 @@ impl IronForce {
self.messages.pop()
}
/// Run one iteration of main loop: accepting incoming connections and messages, processing them
pub fn main_loop_iteration(&mut self) -> IFResult<()> {
self.transport.main_loop_iteration()?;
while let Some((msg, inc_peer)) = self.transport.receive() {
@ -333,10 +338,12 @@ impl IronForce {
Ok(())
}
/// Get an id for the public key of the worker
fn short_id(&self) -> alloc::string::String {
self.keys.get_public().get_short_id()
}
/// Get `IFSerializationData` that can be stored in a file
pub fn get_serialization_data(&self) -> IFSerializationData {
IFSerializationData {
keys: self.keys.clone(),
@ -346,6 +353,7 @@ impl IronForce {
}
}
/// Restore from `IFSerializationData`
pub fn from_serialization_data(data: IFSerializationData) -> IFResult<Self> {
Ok(Self {
keys: data.keys,
@ -361,6 +369,9 @@ impl IronForce {
})
}
/// Load from file (`filename`) with `IFSerializationData`
///
/// If the filename is empty, the default filename is used
#[cfg(feature = "std")]
pub fn from_file(filename: alloc::string::String) -> IFResult<Self> {
let filename = if filename.is_empty() {
@ -377,6 +388,9 @@ impl IronForce {
}
}
/// Save `IFSerializationData` to a file with `filename`
///
/// If `filename` is None, the default filename is used
#[cfg(feature = "std")]
pub fn save_to_file(&self, filename: Option<alloc::string::String>) -> IFResult<()> {
std::fs::write(
@ -386,6 +400,7 @@ impl IronForce {
Ok(())
}
/// Spawn a thread with IF main loop and return `Arc<Mutex<IF>>`
#[cfg(feature = "std")]
pub fn launch_main_loop(
mut self,

Loading…
Cancel
Save