diff --git a/Cargo.toml b/Cargo.toml index a63c89a..d973934 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "polychat-plugin" -version = "0.3.1" +version = "0.4.0" edition = "2018" description = "A library containing types required to make a polychat plugin" @@ -14,6 +14,7 @@ crate_type = ["dylib", "rlib"] [dependencies] libc = "0.2.113" +log = "0.4.14" [build-dependencies] -cbindgen = "0.20.0" \ No newline at end of file +cbindgen = "0.20.0" diff --git a/src/plugin/auth_method.rs b/src/plugin/auth_method.rs new file mode 100644 index 0000000..32730c2 --- /dev/null +++ b/src/plugin/auth_method.rs @@ -0,0 +1,9 @@ +use libc::c_char; +use super::Field; + +#[repr(C)] +pub struct AuthMethod { + pub name: *const c_char, + pub num_fields: u32, + pub fields: *const Field +} \ No newline at end of file diff --git a/src/plugin/auth_result.rs b/src/plugin/auth_result.rs new file mode 100644 index 0000000..8fc8797 --- /dev/null +++ b/src/plugin/auth_result.rs @@ -0,0 +1,7 @@ +#[repr(C)] +pub enum AuthResult { + Success, + FailRejected, + FailConnectionError, + Connecting +} diff --git a/src/plugin/conversation.rs b/src/plugin/conversation.rs new file mode 100644 index 0000000..b88ffa8 --- /dev/null +++ b/src/plugin/conversation.rs @@ -0,0 +1,10 @@ +use libc::c_char; +use super::ConversationType; + +#[repr(C)] +pub struct Conversation { + pub id: *const c_char, + pub name: *const c_char, + pub team_id: *const c_char, + pub conversation_type: ConversationType +} \ No newline at end of file diff --git a/src/plugin/conversation_type.rs b/src/plugin/conversation_type.rs new file mode 100644 index 0000000..3d09579 --- /dev/null +++ b/src/plugin/conversation_type.rs @@ -0,0 +1,7 @@ +#[repr(C)] +pub enum ConversationType { + DirectMessage, + GroupDirectMessage, + PublicChannel, + PrivateChannel +} diff --git a/src/plugin/core_interface.rs b/src/plugin/core_interface.rs new file mode 100644 index 0000000..8aa97f5 --- /dev/null +++ b/src/plugin/core_interface.rs @@ -0,0 +1,9 @@ +pub trait CoreInterface { + // Note: All functions must have self reference to allow + // this trait to be made into an object. + /*fn get_teams(&self, _acc: Account) -> *mut Team { + return ptr::null_mut(); + }*/ + + fn test(&self, _test_msg: String) {} +} \ No newline at end of file diff --git a/src/plugin/field.rs b/src/plugin/field.rs new file mode 100644 index 0000000..84f166f --- /dev/null +++ b/src/plugin/field.rs @@ -0,0 +1,11 @@ +use libc::c_char; +use super::FieldType; + +#[repr(C)] +pub struct Field { + pub name: *const c_char, + pub field_type: FieldType, + pub value: *const c_char, + pub required: bool, + pub sensitive: bool +} \ No newline at end of file diff --git a/src/plugin/field_type.rs b/src/plugin/field_type.rs new file mode 100644 index 0000000..4615c8e --- /dev/null +++ b/src/plugin/field_type.rs @@ -0,0 +1,6 @@ +#[repr(C)] +pub enum FieldType { + Integer, + String, + Url +} diff --git a/src/plugin/init_plugin.rs b/src/plugin/init_plugin.rs index a73b99b..aa4cb72 100644 --- a/src/plugin/init_plugin.rs +++ b/src/plugin/init_plugin.rs @@ -1,22 +1,31 @@ -use super::{PluginInfo, APIVersion, Message, SendStatus}; +use super::{PluginInfo, APIVersion, Message, SendStatus, AuthMethod, + AuthResult, Conversation}; use crate::types::Account; use libc::c_char; use std::ffi::CString; +use log::debug; #[derive(Debug)] pub struct InitializedPlugin { pub supported_api: APIVersion, pub name: String, + pub protocol_name: String, +// pub auth_methods: *const AuthMethod, pub create_account: extern fn() -> Account, pub destroy_account: extern fn(acc: Account), - pub post_message: extern fn(msg: * const Message) -> SendStatus, + pub post_message: extern fn(acc: Account, msg: * const Message) -> SendStatus, + pub login: extern fn(acc: Account, * const AuthMethod, * const c_char) -> AuthResult, + pub request_messages: extern fn(acc: Account, conv: Conversation, + timestamp: u64, limit: u32), pub print: extern fn(acc: Account), } impl InitializedPlugin { pub fn new(plugin: &PluginInfo) -> Result { + debug!("Verifying functions exists"); + //TODO programatically check is_none/null for the fields if plugin.create_account.is_none() { return Err("create_account is not defined".to_string()); } else if plugin.destroy_account.is_none() { @@ -27,25 +36,49 @@ impl InitializedPlugin { return Err("print is not defined".to_string()); } else if plugin.name.is_null() { return Err("name is not defined".to_string()); + } else if plugin.protocol_name.is_null() { + return Err("protocol_name is not defined".to_string()); + } else if plugin.request_messages.is_none() { + return Err("request_messages is undefined".to_string()); + } else if plugin.login.is_none() { + return Err("login is undefined".to_string()); } + debug!("Functions do exists"); + let name: String; + let protocol_name: String; unsafe { + debug!("Reading plugin name"); let name_res = CString::from_raw(plugin.name as *mut c_char).into_string(); if name_res.is_err() { return Err("Could not decode plugin name".to_string()); } name = name_res.unwrap(); + debug!("Got plugin name as {}", name); + + debug!("Trying to read protocol name"); + let protocol_name_res = CString::from_raw(plugin.protocol_name as *mut c_char).into_string(); + if protocol_name_res.is_err() { + return Err("Could not decode plugin protocol name".to_string()); + } + + protocol_name = protocol_name_res.unwrap(); + debug!("Got protocol name as {}", protocol_name); } Ok(InitializedPlugin { supported_api: plugin.supported_api, create_account: plugin.create_account.unwrap(), destroy_account: plugin.destroy_account.unwrap(), + login: plugin.login.unwrap(), + request_messages: plugin.request_messages.unwrap(), post_message: plugin.post_message.unwrap(), print: plugin.print.unwrap(), - name, + name: name, + protocol_name: protocol_name, +// auth_methods: plugin.auth_methods }) } } diff --git a/src/plugin/message.rs b/src/plugin/message.rs index 8b812f1..872aacc 100644 --- a/src/plugin/message.rs +++ b/src/plugin/message.rs @@ -3,4 +3,10 @@ use libc::c_char; #[repr(C)] pub struct Message { pub body: *const c_char -} \ No newline at end of file +} +/* +impl InitializedPlugin { + pub fn set_body(body: String) { + + } +}*/ \ No newline at end of file diff --git a/src/plugin/mod.rs b/src/plugin/mod.rs index 8a7bec7..d4f3bbb 100644 --- a/src/plugin/mod.rs +++ b/src/plugin/mod.rs @@ -2,16 +2,35 @@ mod api_version; mod init_plugin; mod plugin_info; mod send_status; +mod field_type; +mod conversation_type; +mod auth_result; +mod auth_method; mod message; +mod team; +mod conversation; +mod field; +mod polychat_api; +pub mod core_interface; pub use plugin_info::PluginInfo; pub use init_plugin::InitializedPlugin; pub use api_version::APIVersion; pub use send_status::SendStatus; +pub use field_type::FieldType; +pub use conversation_type::ConversationType; +pub use auth_result::AuthResult; +pub use team::Team; +pub use conversation::Conversation; +pub use field::Field; +pub use auth_method::AuthMethod; +pub use polychat_api::PolyChatApiV1; +pub use core_interface::CoreInterface; + pub use message::Message; pub const INITIALIZE_FN_NAME: &str = "initialize"; extern "C" { - pub fn initialize(thing: *mut PluginInfo); + pub fn initialize(plugin_info: *mut PluginInfo, core_api: *const PolyChatApiV1); } diff --git a/src/plugin/plugin_info.rs b/src/plugin/plugin_info.rs index cae1d31..e7a5b7b 100644 --- a/src/plugin/plugin_info.rs +++ b/src/plugin/plugin_info.rs @@ -2,6 +2,9 @@ use crate::types::*; use super::api_version::APIVersion; use super::send_status::SendStatus; use super::message::Message; +use super::auth_method::AuthMethod; +use super::auth_result::AuthResult; +use super::conversation::Conversation; use std::option::Option; use std::ptr; @@ -12,14 +15,35 @@ use libc::c_char; pub struct PluginInfo { pub supported_api: APIVersion, pub name: *const c_char, + pub protocol_name: *const c_char, + pub auth_methods: *const AuthMethod, + pub create_account: Option Account>, + //pub init_account: Option, pub destroy_account: Option, + /** + * Tries to login with the given authmethod, with the given array of strings. + * + * Async, so the final auth result may not be immediately available. + * TODO: The way to update auth status. + */ + pub login: Option AuthResult>, + /** + * Sends a request to retrieve messages for the conversation that the account + * has access to. + * The third parameter is the timestamp for which all messages should be older than + * The fourth parameter is the maximum quantity of messages to retrieve. + * + * Messages should be added using the function TODO. + */ + pub request_messages: Option, /// Instructs the plugin to post a message in the associated channel. /// The lifetime of msg is only guaranteed during the function call. /// To keep the message for longer (likely required), make a copy. /// TODO: Add way to update future message status as it is done async. - pub post_message: Option SendStatus>, + pub post_message: Option SendStatus>, pub print: Option, } @@ -33,9 +57,13 @@ impl PluginInfo { }, create_account: None, destroy_account: None, + login: None, + request_messages: None, post_message: None, print: None, name: ptr::null(), + protocol_name: ptr::null(), + auth_methods: ptr::null(), } } } diff --git a/src/plugin/polychat_api.rs b/src/plugin/polychat_api.rs new file mode 100644 index 0000000..01d2ef5 --- /dev/null +++ b/src/plugin/polychat_api.rs @@ -0,0 +1,63 @@ + +use super::CoreInterface; + +use libc::{c_void, c_char}; +use std::{ + ffi::CString, + fmt::Debug, + fmt::Result, + fmt::Formatter +}; + +use log::error; + + +/** + * This is the interface that is passed into C that allows the core's functions to be called. + * It takes in a boxed CoreInterface, allowing the plugin to call items from the trait. + */ +#[repr(C)] +pub struct PolyChatApiV1 { + core: *mut c_void, // Hidden *mut Box, + // TODO: Eventually add fields identifying info about core + //get_teams: Option *mut Team>, + pub test: Option, +} + +impl PolyChatApiV1 { + pub fn new(core: *mut &Box) -> PolyChatApiV1 { + PolyChatApiV1 { + core: core as *mut c_void, + //get_teams: Some(PolyChatApiV1::get_teams_impl) + test: Some(PolyChatApiV1::test_impl) + } + } + + /*extern fn get_teams_impl(&self, acc: Account) -> *mut Team { + let interface: Box> = unsafe { Box::from_raw(self.core as *mut _) }; + return interface.get_teams(acc); + }*/ + extern fn test_impl(&self, test_msg: *const c_char) { + let interface: Box> = unsafe { Box::from_raw(self.core as *mut _) }; + // Convert to rust string + unsafe { + let rust_c_str = CString::from_raw(test_msg as *mut c_char).into_string(); + match rust_c_str { + Ok(string) => { + interface.test(string); + }, + Err(error) => { + // Complain to logs. This should never happen. Incompatible ABI? + error!("Error converting from c string to rust string in test_impl {}", error.to_string()); + } + }; + } + } +} + +impl Debug for PolyChatApiV1 { + fn fmt(&self, f: &mut Formatter<'_>) -> Result { + f.debug_tuple("") + .finish() + } +} \ No newline at end of file diff --git a/src/plugin/team.rs b/src/plugin/team.rs new file mode 100644 index 0000000..0a2c8b9 --- /dev/null +++ b/src/plugin/team.rs @@ -0,0 +1,7 @@ +use libc::c_char; + +#[repr(C)] +pub struct Team { + pub id: *const c_char, + pub name: *const c_char, +} \ No newline at end of file