Browse Source

Move degeon protocol and worker into degeon_core

master
Lev 3 years ago
parent
commit
8e58048c4e
  1. 117
      Cargo.lock
  2. 2
      Cargo.toml
  3. 3
      degeon/Cargo.toml
  4. 87
      degeon/src/chat.rs
  5. 287
      degeon/src/degeon_worker.rs
  6. 27
      degeon/src/gui_events.rs
  7. 49
      degeon/src/message.rs
  8. 23
      degeon/src/state.rs
  9. 25
      degeon_core/Cargo.toml
  10. 56
      degeon_core/src/chat.rs
  11. 257
      degeon_core/src/degeon_worker.rs
  12. 52
      degeon_core/src/gui_events.rs
  13. 25
      degeon_core/src/lib.rs
  14. 48
      degeon_core/src/message.rs

117
Cargo.lock generated

@ -567,6 +567,7 @@ version = "0.1.0"
dependencies = [ dependencies = [
"base64", "base64",
"chrono", "chrono",
"degeon_core",
"futures", "futures",
"iced", "iced",
"iced_native", "iced_native",
@ -575,6 +576,20 @@ dependencies = [
"serde_json", "serde_json",
] ]
[[package]]
name = "degeon_core"
version = "0.1.0"
dependencies = [
"base64",
"chrono",
"futures",
"iced",
"ironforce",
"pyo3",
"serde",
"serde_json",
]
[[package]] [[package]]
name = "der" name = "der"
version = "0.4.4" version = "0.4.4"
@ -1580,6 +1595,29 @@ dependencies = [
"hashbrown 0.11.2", "hashbrown 0.11.2",
] ]
[[package]]
name = "indoc"
version = "0.3.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "47741a8bc60fb26eb8d6e0238bbb26d8575ff623fdc97b1a2c00c050b9684ed8"
dependencies = [
"indoc-impl",
"proc-macro-hack",
]
[[package]]
name = "indoc-impl"
version = "0.3.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ce046d161f000fffde5f432a0d034d0341dc152643b2598ed5bfce44c4f3a8f0"
dependencies = [
"proc-macro-hack",
"proc-macro2",
"quote",
"syn",
"unindent",
]
[[package]] [[package]]
name = "inplace_it" name = "inplace_it"
version = "0.3.3" version = "0.3.3"
@ -2180,6 +2218,25 @@ dependencies = [
"winapi 0.3.9", "winapi 0.3.9",
] ]
[[package]]
name = "paste"
version = "0.1.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "45ca20c77d80be666aef2b45486da86238fabe33e38306bd3118fe4af33fa880"
dependencies = [
"paste-impl",
"proc-macro-hack",
]
[[package]]
name = "paste-impl"
version = "0.1.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d95a7db200b97ef370c8e6de0088252f7e0dfff7d047a28528e47456c0fc98b6"
dependencies = [
"proc-macro-hack",
]
[[package]] [[package]]
name = "pathfinder_geometry" name = "pathfinder_geometry"
version = "0.5.1" version = "0.5.1"
@ -2290,6 +2347,12 @@ dependencies = [
"toml", "toml",
] ]
[[package]]
name = "proc-macro-hack"
version = "0.5.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dbf0c48bc1d91375ae5c3cd81e3722dff1abcf81a30960240640d223f59fe0e5"
[[package]] [[package]]
name = "proc-macro2" name = "proc-macro2"
version = "1.0.32" version = "1.0.32"
@ -2299,6 +2362,54 @@ dependencies = [
"unicode-xid", "unicode-xid",
] ]
[[package]]
name = "pyo3"
version = "0.14.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "35100f9347670a566a67aa623369293703322bb9db77d99d7df7313b575ae0c8"
dependencies = [
"cfg-if 1.0.0",
"indoc",
"libc",
"parking_lot",
"paste",
"pyo3-build-config",
"pyo3-macros",
"unindent",
]
[[package]]
name = "pyo3-build-config"
version = "0.14.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d12961738cacbd7f91b7c43bc25cfeeaa2698ad07a04b3be0aa88b950865738f"
dependencies = [
"once_cell",
]
[[package]]
name = "pyo3-macros"
version = "0.14.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fc0bc5215d704824dfddddc03f93cb572e1155c68b6761c37005e1c288808ea8"
dependencies = [
"pyo3-macros-backend",
"quote",
"syn",
]
[[package]]
name = "pyo3-macros-backend"
version = "0.14.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "71623fc593224afaab918aa3afcaf86ed2f43d34f6afde7f3922608f253240df"
dependencies = [
"proc-macro2",
"pyo3-build-config",
"quote",
"syn",
]
[[package]] [[package]]
name = "quote" name = "quote"
version = "1.0.10" version = "1.0.10"
@ -2938,6 +3049,12 @@ version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3" checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3"
[[package]]
name = "unindent"
version = "0.1.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f14ee04d9415b52b3aeab06258a3f07093182b88ba0f9b8d203f211a7a7d41c7"
[[package]] [[package]]
name = "url" name = "url"
version = "2.2.2" version = "2.2.2"

2
Cargo.toml

@ -6,7 +6,7 @@ edition = "2018"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[workspace] [workspace]
members = ["degeon"] members = ["degeon", "degeon_core"]
[features] [features]
default = [] default = []

3
degeon/Cargo.toml

@ -7,7 +7,8 @@ edition = "2021"
[dependencies] [dependencies]
iced = { version = "0.3.0", features = ["glow"] } iced = { version = "0.3.0", features = ["glow"] }
ironforce = { path = "../", features = ["std"] } ironforce = { path = "..", features = ["std"] }
degeon_core = { path = "../degeon_core" }
base64 = "0.13.0" base64 = "0.13.0"
serde = { version = "1.0" } serde = { version = "1.0" }
serde_json = "1.0.72" serde_json = "1.0.72"

87
degeon/src/chat.rs

@ -1,44 +1,41 @@
use iced::{Align, Button, Column, Element, Length, Row, Text, TextInput}; use iced::{Align, Button, Column, Element, Length, Row, Text, TextInput};
use iced_native::button; use iced_native::button;
use ironforce::{Keys, PublicKey};
use crate::gui_events::GuiEvent; use crate::gui_events::GuiEvent;
use crate::message::{DegMessage, DegMessageContent, Profile};
use crate::state; use crate::state;
use crate::styles::style; use crate::styles::style;
use serde::{Serialize, Deserialize}; pub use degeon_core::Chat;
pub trait RenderableChat {
/// Render header of the chat
fn header<'a>(name: String) -> Element<'a, GuiEvent>;
/// A chat in the messenger /// Render the sending field
#[derive(Clone, Debug, Serialize, Deserialize)] fn send_field<'a>(
pub struct Chat { input: String,
/// Public key of the other user text_input_state: &'a mut iced::text_input::State,
pub pkey: PublicKey, send_button_state: &'a mut iced::button::State,
/// Messages in this chat ) -> Element<'a, GuiEvent>;
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 { /// Render chat preview
/// Create a new chat fn preview<'a>(
pub fn new(pkey: PublicKey) -> Self { &'a self,
Self { state: &'a mut button::State,
pkey, i: usize,
messages: vec![], is_selected: bool,
profile: Profile { name: "".to_string() }, ) -> Element<'a, GuiEvent>;
scrolled: 0.0,
input: "".to_string(), /// Render the chat view
} 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>;
} }
impl Chat {
impl RenderableChat for Chat {
/// Render header of the chat /// Render header of the chat
pub fn header<'a>(name: String) -> Element<'a, GuiEvent> { fn header<'a>(name: String) -> Element<'a, GuiEvent> {
iced::container::Container::new(Text::new(name.as_str()).color(iced::Color::WHITE)) iced::container::Container::new(Text::new(name.as_str()).color(iced::Color::WHITE))
.style(style::Container::Primary) .style(style::Container::Primary)
.width(Length::Fill) .width(Length::Fill)
@ -48,7 +45,7 @@ impl Chat {
} }
/// Render the sending field /// Render the sending field
pub fn send_field<'a>( fn send_field<'a>(
input: String, input: String,
text_input_state: &'a mut iced::text_input::State, text_input_state: &'a mut iced::text_input::State,
send_button_state: &'a mut iced::button::State, send_button_state: &'a mut iced::button::State,
@ -76,7 +73,7 @@ impl Chat {
} }
/// Render chat preview /// Render chat preview
pub fn preview<'a>( fn preview<'a>(
&'a self, &'a self,
state: &'a mut button::State, state: &'a mut button::State,
i: usize, i: usize,
@ -95,7 +92,7 @@ impl Chat {
} }
/// Render the chat view /// Render the chat view
pub fn view<'a>( fn view<'a>(
&'a self, &'a self,
text_input_state: &'a mut iced::text_input::State, text_input_state: &'a mut iced::text_input::State,
send_button_state: &'a mut iced::button::State, send_button_state: &'a mut iced::button::State,
@ -122,28 +119,4 @@ impl Chat {
)) ))
.into() .into()
} }
/// Create an example chat
pub fn example(i: usize, my_keys: &Keys) -> Chat {
let pkey = Keys::generate().get_public();
Self {
messages: vec![
DegMessage {
sender: pkey.clone(),
timestamp: chrono::Utc::now().timestamp(),
content: DegMessageContent::Text(format!("Example message {}", 2 * i))
},
DegMessage {
sender: my_keys.get_public(),
timestamp: chrono::Utc::now().timestamp(),
content: DegMessageContent::Text(format!("Example message {}", 2 * i + 1))
},
],
profile: Profile { name: format!("Example user ({})", i) },
scrolled: 0.0,
pkey,
input: "".to_string(),
}
}
} }

287
degeon/src/degeon_worker.rs

@ -1,286 +1,17 @@
use crate::chat::Chat;
use crate::gui_events::GuiEvent; use crate::gui_events::GuiEvent;
use crate::message::{Profile, ProtocolMsg}; pub use degeon_core::{Degeon, DegeonData};
use futures::Stream;
use ironforce::res::IFResult;
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 pub(crate) struct DegeonContainer {
#[derive(Clone)] inner: Degeon,
pub struct Degeon {
/// The list of all chats for this instance
pub chats: Vec<Chat>,
/// 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 impl DegeonContainer {
#[derive(Serialize, Deserialize)] pub fn new(data: &Degeon) -> Self {
pub struct DegeonData { Self { inner: data.clone() }
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, keys) = get_initialized_ironforce();
Self {
chats: vec![],
profile: Profile::default(),
keys,
ironforce,
}
}
}
impl Degeon {
/// Get profile for the current user
pub fn get_profile(&self) -> Profile {
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()) {
Ok(r) => r,
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()
);
if !msg.check_recipient(&self.keys) || sender == self.keys.get_public() {
return Ok(None);
}
println!("{:?}", deg_msg);
Ok(match &deg_msg {
ProtocolMsg::NewMessage(deg_msg) => {
Some(GuiEvent::NewMessageInChat(sender, deg_msg.clone()))
}
ProtocolMsg::ProfileRequest | ProtocolMsg::Ping => {
Some(GuiEvent::WeHaveToSendProfile(sender))
}
ProtocolMsg::ProfileResponse(prof) => Some(GuiEvent::SetProfile(sender, prof.clone())),
})
}
/// Send a multicast message through the network
pub fn send_multicast(&self, msg: ProtocolMsg) -> IFResult<()> {
self.ironforce.lock().unwrap().send_to_all(
Message::build()
.message_type(MessageType::Broadcast)
.content(serde_json::to_vec(&msg)?)
.sign(&self.keys)
.build()?,
)
}
/// 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");
// self.ironforce
// .lock()
// .unwrap()
// .initialize_tunnel_creation(target)?;
// let mut counter = 0;
// while self.ironforce.lock().unwrap().get_tunnel(target).is_none() {
// std::thread::sleep(std::time::Duration::from_millis(350));
// counter += 1;
// if counter > 100 {
// return Err(IFError::TunnelNotFound);
// }
// }
// }
self.ironforce.lock().unwrap().send_to_all(
Message::build()
.message_type(MessageType::Broadcast)
.content(serde_json::to_vec(&msg)?)
.recipient(target)
.sign(&self.keys)
.build()?,
)
}
/// 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 keys = self.keys.clone();
println!("Creating a send command: {:?}", msg);
iced::Command::perform(async {}, move |_| {
println!("Sending message: {:?}", msg);
if_clone
.lock()
.unwrap()
.send_to_all(
Message::build()
.message_type(MessageType::Broadcast)
.content(serde_json::to_vec(&msg).unwrap())
// todo:
// .recipient(&target)
.sign(&keys)
.build()
.unwrap(),
)
.unwrap();
GuiEvent::None
})
}
/// 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();
println!("Created a send command");
iced::Command::perform(
async move {
println!("Sending message: {:?}", msg);
if_clone
.lock()
.unwrap()
.send_to_all(
Message::build()
.message_type(MessageType::Broadcast)
.content(serde_json::to_vec(&msg).unwrap())
.sign(&keys)
.build()
.unwrap(),
)
.unwrap()
},
|_| GuiEvent::None,
)
}
}
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());
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(),
) {
Ok(r) => r,
Err(_) => {
println!("Couldn't deserialize {:?}", msg_raw);
return Poll::Ready(Some(GuiEvent::None));
}
};
println!("{:?} -> {:?}", msg_deg, msg);
}
if timestamp_0.elapsed() < std::time::Duration::from_millis(5) {
std::thread::sleep(std::time::Duration::from_millis(5));
}
match msg {
None => Poll::Ready(Some(GuiEvent::None)),
Some(None) => Poll::Ready(Some(GuiEvent::None)),
Some(Some(msg)) => Poll::Ready(Some(msg)),
}
} }
} }
impl<H, I> iced_native::subscription::Recipe<H, I> for Degeon impl<H, I> iced_native::subscription::Recipe<H, I> for DegeonContainer
where where
H: std::hash::Hasher, H: std::hash::Hasher,
{ {
@ -290,7 +21,7 @@ where
use std::hash::Hash; use std::hash::Hash;
std::any::TypeId::of::<Self>().hash(state); std::any::TypeId::of::<Self>().hash(state);
self.ironforce.lock().unwrap().hash(state); self.inner.ironforce.lock().unwrap().hash(state);
// std::time::SystemTime::now().hash(state); // std::time::SystemTime::now().hash(state);
// std::time::UNIX_EPOCH // std::time::UNIX_EPOCH
@ -304,6 +35,6 @@ where
self: Box<Self>, self: Box<Self>,
_input: futures::stream::BoxStream<'static, I>, _input: futures::stream::BoxStream<'static, I>,
) -> futures::stream::BoxStream<'static, Self::Output> { ) -> futures::stream::BoxStream<'static, Self::Output> {
Box::pin(self) Box::pin(self.inner)
} }
} }

27
degeon/src/gui_events.rs

@ -1,26 +1 @@
use crate::message::{DegMessage, Profile}; pub use degeon_core::{GuiEvent, AppScreen};
use ironforce::PublicKey;
use crate::state::AppScreen;
/// An enum with all possible events for this application
#[derive(Clone, Debug)]
pub enum GuiEvent {
/// Selection of a chat
ChatSelect(usize),
/// The user changed the value of "new message" field
Typed(String),
/// The user clicked "Send"
SendMessage,
/// 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),
/// Changed the name in the field on profile screen
ChangeName(String),
/// Nothing happened
None,
}

49
degeon/src/message.rs

@ -1,48 +1 @@
use ironforce::PublicKey; pub use degeon_core::{ProtocolMsg, Profile, DegMessage, DegMessageContent};
use serde::{Deserialize, Serialize};
/// A message in the messenger
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct DegMessage {
pub sender: PublicKey,
pub timestamp: i64,
pub content: DegMessageContent,
}
impl DegMessage {
/// Create a simple text message
pub fn new_text(text: String, my_key: &PublicKey) -> DegMessage {
Self {
sender: my_key.clone(),
timestamp: chrono::Utc::now().timestamp(),
content: DegMessageContent::Text(text),
}
}
}
/// The content of the message
#[derive(Clone, Debug, Serialize, Deserialize)]
pub enum DegMessageContent {
Text(String),
File(Vec<u8>),
Service,
}
/// 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),
}

23
degeon/src/state.rs

@ -1,5 +1,5 @@
use crate::chat::Chat; use crate::chat::Chat;
use crate::degeon_worker::Degeon; use crate::degeon_worker::{Degeon, DegeonContainer};
use crate::gui_events::GuiEvent; use crate::gui_events::GuiEvent;
use crate::message::{DegMessage, DegMessageContent, ProtocolMsg}; use crate::message::{DegMessage, DegMessageContent, ProtocolMsg};
use crate::styles::style; use crate::styles::style;
@ -9,6 +9,8 @@ use iced::{
Text, TextInput, VerticalAlignment, Text, TextInput, VerticalAlignment,
}; };
use ironforce::PublicKey; use ironforce::PublicKey;
use degeon_core::AppScreen;
use crate::chat::RenderableChat;
/// Render a message into an iced `Element` /// Render a message into an iced `Element`
pub fn view_message(msg: &DegMessage, pkey: PublicKey) -> Option<Element<GuiEvent>> { pub fn view_message(msg: &DegMessage, pkey: PublicKey) -> Option<Element<GuiEvent>> {
@ -39,23 +41,6 @@ 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
#[allow(dead_code)]
PeerInput,
}
impl Default for AppScreen {
fn default() -> Self {
AppScreen::Main
}
}
/// The main application struct (for iced) /// The main application struct (for iced)
#[derive(Default)] #[derive(Default)]
@ -287,7 +272,7 @@ impl Application for DegeonApp {
} }
fn subscription(&self) -> iced::Subscription<GuiEvent> { fn subscription(&self) -> iced::Subscription<GuiEvent> {
iced::Subscription::from_recipe(self.data.clone()) iced::Subscription::from_recipe(DegeonContainer::new(&self.data))
} }
fn view(&mut self) -> Element<'_, Self::Message> { fn view(&mut self) -> Element<'_, Self::Message> {

25
degeon_core/Cargo.toml

@ -0,0 +1,25 @@
[package]
name = "degeon_core"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
ironforce = { path = "..", features = ["std"] }
base64 = "0.13.0"
serde = { version = "1.0" }
serde_json = "1.0.72"
futures = "0.3.18"
chrono = "0.4.19"
iced = { version = "0.3.0", features = ["glow"] }
[dependencies.pyo3]
version = "0.14.0"
features = ["extension-module"]
[lib]
name = "degeon_core"
path = "src/lib.rs"
crate-type = ["cdylib", "rlib"]

56
degeon_core/src/chat.rs

@ -0,0 +1,56 @@
use ironforce::{Keys, PublicKey};
use crate::message::{DegMessage, DegMessageContent, Profile};
use serde::{Serialize, Deserialize};
/// 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,
messages: vec![],
profile: Profile { name: "".to_string() },
scrolled: 0.0,
input: "".to_string(),
}
}
/// Create an example chat
pub fn example(i: usize, my_keys: &Keys) -> Chat {
let pkey = Keys::generate().get_public();
Self {
messages: vec![
DegMessage {
sender: pkey.clone(),
timestamp: chrono::Utc::now().timestamp(),
content: DegMessageContent::Text(format!("Example message {}", 2 * i))
},
DegMessage {
sender: my_keys.get_public(),
timestamp: chrono::Utc::now().timestamp(),
content: DegMessageContent::Text(format!("Example message {}", 2 * i + 1))
},
],
profile: Profile { name: format!("Example user ({})", i) },
scrolled: 0.0,
pkey,
input: "".to_string(),
}
}
}

257
degeon_core/src/degeon_worker.rs

@ -0,0 +1,257 @@
use crate::chat::Chat;
use crate::gui_events::GuiEvent;
use crate::message::{Profile, ProtocolMsg};
use futures::Stream;
use ironforce::res::IFResult;
use ironforce::{IronForce, Keys, Message, MessageType, PublicKey};
use std::pin::Pin;
use std::sync::{Arc, Mutex};
use std::task::{Context, Poll};
use pyo3::prelude::*;
use serde::{Serialize, Deserialize};
/// The container for logic, data, IF and protocol interactions
#[derive(Clone)]
#[pyclass]
pub struct Degeon {
/// The list of all chats for this instance
pub chats: Vec<Chat>,
/// 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, keys) = get_initialized_ironforce();
Self {
chats: vec![],
profile: Profile::default(),
keys,
ironforce,
}
}
}
impl Degeon {
/// Get profile for the current user
pub fn get_profile(&self) -> Profile {
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()) {
Ok(r) => r,
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()
);
if !msg.check_recipient(&self.keys) || sender == self.keys.get_public() {
return Ok(None);
}
println!("{:?}", deg_msg);
Ok(match &deg_msg {
ProtocolMsg::NewMessage(deg_msg) => {
Some(GuiEvent::NewMessageInChat(sender, deg_msg.clone()))
}
ProtocolMsg::ProfileRequest | ProtocolMsg::Ping => {
Some(GuiEvent::WeHaveToSendProfile(sender))
}
ProtocolMsg::ProfileResponse(prof) => Some(GuiEvent::SetProfile(sender, prof.clone())),
})
}
/// Send a multicast message through the network
pub fn send_multicast(&self, msg: ProtocolMsg) -> IFResult<()> {
self.ironforce.lock().unwrap().send_to_all(
Message::build()
.message_type(MessageType::Broadcast)
.content(serde_json::to_vec(&msg)?)
.sign(&self.keys)
.build()?,
)
}
/// 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");
// self.ironforce
// .lock()
// .unwrap()
// .initialize_tunnel_creation(target)?;
// let mut counter = 0;
// while self.ironforce.lock().unwrap().get_tunnel(target).is_none() {
// std::thread::sleep(std::time::Duration::from_millis(350));
// counter += 1;
// if counter > 100 {
// return Err(IFError::TunnelNotFound);
// }
// }
// }
self.ironforce.lock().unwrap().send_to_all(
Message::build()
.message_type(MessageType::Broadcast)
.content(serde_json::to_vec(&msg)?)
.recipient(target)
.sign(&self.keys)
.build()?,
)
}
/// 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 keys = self.keys.clone();
println!("Creating a send command: {:?}", msg);
iced::Command::perform(async {}, move |_| {
println!("Sending message: {:?}", msg);
if_clone
.lock()
.unwrap()
.send_to_all(
Message::build()
.message_type(MessageType::Broadcast)
.content(serde_json::to_vec(&msg).unwrap())
// todo:
// .recipient(&target)
.sign(&keys)
.build()
.unwrap(),
)
.unwrap();
GuiEvent::None
})
}
}
pub 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());
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(),
) {
Ok(r) => r,
Err(_) => {
println!("Couldn't deserialize {:?}", msg_raw);
return Poll::Ready(Some(GuiEvent::None));
}
};
println!("{:?} -> {:?}", msg_deg, msg);
}
if timestamp_0.elapsed() < std::time::Duration::from_millis(5) {
std::thread::sleep(std::time::Duration::from_millis(5));
}
match msg {
None => Poll::Ready(Some(GuiEvent::None)),
Some(None) => Poll::Ready(Some(GuiEvent::None)),
Some(Some(msg)) => Poll::Ready(Some(msg)),
}
}
}

52
degeon_core/src/gui_events.rs

@ -0,0 +1,52 @@
use crate::message::{DegMessage, Profile};
use ironforce::PublicKey;
use serde::{Serialize, Deserialize};
/// The screens (pages) of the app
#[derive(Copy, Clone, Debug, Serialize, Deserialize)]
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
#[allow(dead_code)]
PeerInput,
}
impl Default for AppScreen {
fn default() -> Self {
AppScreen::Main
}
}
/// An enum with all possible events for this application
#[derive(Clone, Debug, Serialize, Deserialize)]
pub enum GuiEvent {
/// Selection of a chat
ChatSelect(usize),
/// The user changed the value of "new message" field
Typed(String),
/// The user clicked "Send"
SendMessage,
/// 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),
/// Changed the name in the field on profile screen
ChangeName(String),
/// Nothing happened
None,
}
impl GuiEvent {
pub fn get_json(&self) -> String {
serde_json::to_string(&self).unwrap()
}
}

25
degeon_core/src/lib.rs

@ -0,0 +1,25 @@
extern crate serde;
extern crate pyo3;
mod chat;
mod degeon_worker;
mod gui_events;
mod message;
pub use chat::Chat;
pub use degeon_worker::{Degeon, DegeonData, DEFAULT_FILENAME};
pub use message::{DegMessage, Profile, ProtocolMsg, DegMessageContent};
pub use gui_events::{AppScreen, GuiEvent};
use pyo3::prelude::*;
use pyo3::wrap_pyfunction;
#[pyfunction]
fn new_degeon() -> PyResult<Degeon> {
Ok(Degeon::default())
}
#[pymodule]
fn degeon_core(_py: Python, m: &PyModule) -> PyResult<()> {
m.add_class::<Degeon>()?;
m.add_function(wrap_pyfunction!(new_degeon, m)?)?;
Ok(())
}

48
degeon_core/src/message.rs

@ -0,0 +1,48 @@
use ironforce::PublicKey;
use serde::{Deserialize, Serialize};
/// A message in the messenger
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct DegMessage {
pub sender: PublicKey,
pub timestamp: i64,
pub content: DegMessageContent,
}
impl DegMessage {
/// Create a simple text message
pub fn new_text(text: String, my_key: &PublicKey) -> DegMessage {
Self {
sender: my_key.clone(),
timestamp: chrono::Utc::now().timestamp(),
content: DegMessageContent::Text(text),
}
}
}
/// The content of the message
#[derive(Clone, Debug, Serialize, Deserialize)]
pub enum DegMessageContent {
Text(String),
File(Vec<u8>),
Service,
}
/// 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),
}
Loading…
Cancel
Save