aboutsummaryrefslogtreecommitdiff
path: root/src/message/handshake
diff options
context:
space:
mode:
Diffstat (limited to 'src/message/handshake')
-rw-r--r--src/message/handshake/clientinit.rs91
-rw-r--r--src/message/handshake/clientinitack.rs74
-rw-r--r--src/message/handshake/clientinitreject.rs46
-rw-r--r--src/message/handshake/clientlogin.rs49
-rw-r--r--src/message/handshake/clientloginack.rs35
-rw-r--r--src/message/handshake/clientloginreject.rs46
-rw-r--r--src/message/handshake/connack.rs59
-rw-r--r--src/message/handshake/init.rs61
-rw-r--r--src/message/handshake/mod.rs23
-rw-r--r--src/message/handshake/protocol.rs36
-rw-r--r--src/message/handshake/sessioninit.rs61
11 files changed, 581 insertions, 0 deletions
diff --git a/src/message/handshake/clientinit.rs b/src/message/handshake/clientinit.rs
new file mode 100644
index 0000000..17ec9a1
--- /dev/null
+++ b/src/message/handshake/clientinit.rs
@@ -0,0 +1,91 @@
+use crate::error::ProtocolError;
+use crate::primitive::{StringList, Variant, VariantMap};
+use crate::{HandshakeDeserialize, HandshakeSerialize};
+
+use failure::Error;
+
+/// ClientInit is the Initial message send to the core after establishing a base layer comunication.
+///
+/// Features
+///
+/// | Flag | Name | Description |
+/// | ---- | ---- | ----------- |
+/// | 0x00000001 | SynchronizedMarkerLine | -- |
+/// | 0x00000002 | SaslAuthentication | -- |
+/// | 0x00000004 | SaslExternal | -- |
+/// | 0x00000008 | HideInactiveNetworks | -- |
+/// | 0x00000010 | PasswordChange | -- |
+/// | 0x00000020 | CapNegotiation | IRCv3 capability negotiation, account tracking |
+/// | 0x00000040 | VerifyServerSSL | IRC server SSL validation |
+/// | 0x00000080 | CustomRateLimits | IRC server custom message rate limits |
+/// | 0x00000100 | DccFileTransfer | Currently not supported |
+/// | 0x00000200 | AwayFormatTimestamp | Timestamp formatting in away (e.g. %%hh:mm%%) |
+/// | 0x00000400 | Authenticators | Support for exchangeable auth backends |
+/// | 0x00000800 | BufferActivitySync | Sync buffer activity status |
+/// | 0x00001000 | CoreSideHighlights | Core-Side highlight configuration and matching |
+/// | 0x00002000 | SenderPrefixes | Show prefixes for senders in backlog |
+/// | 0x00004000 | RemoteDisconnect | Supports RPC call disconnectFromCore to remotely disconnect a client |
+/// | 0x00008000 | ExtendedFeatures | Transmit features as list of strings |
+/// | -- | LongTime | Serialize message time as 64-bit |
+/// | -- | RichMessages | Real Name and Avatar URL in backlog |
+/// | -- | BacklogFilterType | Backlogmanager supports filtering backlog by messagetype |
+/// | -- | EcdsaCertfpKeys | ECDSA keys for CertFP in identities |
+/// | -- | LongMessageId | 64-bit IDs for messages |
+/// | -- | SyncedCoreInfo | CoreInfo dynamically updated using signals |
+#[derive(Debug)]
+pub struct ClientInit {
+ /// Version of the client
+ pub client_version: String,
+ /// Build date of the client
+ pub client_date: String,
+ /// supported features as bitflags
+ pub client_features: u32,
+ /// List of supported extended features
+ pub feature_list: StringList,
+}
+
+impl HandshakeSerialize for ClientInit {
+ fn serialize(&self) -> Result<Vec<u8>, Error> {
+ let mut values: VariantMap = VariantMap::with_capacity(5);
+ values.insert(
+ "MsgType".to_string(),
+ Variant::String("ClientInit".to_string()),
+ );
+ values.insert(
+ "ClientVersion".to_string(),
+ Variant::String(self.client_version.clone()),
+ );
+ values.insert(
+ "ClientDate".to_string(),
+ Variant::String(self.client_date.clone()),
+ );
+ values.insert("Features".to_string(), Variant::u32(self.client_features));
+ values.insert(
+ "FeatureList".to_string(),
+ Variant::StringList(self.feature_list.clone()),
+ );
+ return HandshakeSerialize::serialize(&values);
+ }
+}
+
+impl HandshakeDeserialize for ClientInit {
+ fn parse(b: &[u8]) -> Result<(usize, Self), Error> {
+ let (len, values): (usize, VariantMap) = HandshakeDeserialize::parse(b)?;
+
+ let msgtype = match_variant!(&values["MsgType"], Variant::StringUTF8);
+
+ if msgtype == "ClientInit" {
+ return Ok((
+ len,
+ Self {
+ client_version: match_variant!(values["ClientVersion"], Variant::String),
+ client_date: match_variant!(values["ClientDate"], Variant::String),
+ feature_list: match_variant!(values["FeatureList"], Variant::StringList),
+ client_features: match_variant!(values["Features"], Variant::u32),
+ },
+ ));
+ } else {
+ bail!(ProtocolError::WrongMsgType);
+ }
+ }
+}
diff --git a/src/message/handshake/clientinitack.rs b/src/message/handshake/clientinitack.rs
new file mode 100644
index 0000000..637b989
--- /dev/null
+++ b/src/message/handshake/clientinitack.rs
@@ -0,0 +1,74 @@
+use crate::error::ProtocolError;
+use crate::primitive::{StringList, Variant, VariantList, VariantMap};
+use crate::{HandshakeDeserialize, HandshakeSerialize};
+
+use failure::Error;
+
+/// ClientInitAck is received when the initialization was successfull
+#[derive(Debug)]
+pub struct ClientInitAck {
+ /// Flags of supported legacy features
+ pub core_features: u32,
+ /// If the core has already been configured
+ pub core_configured: bool,
+ /// List of VariantMaps of info on available backends
+ pub storage_backends: VariantList,
+ /// List of VariantMaps of info on available authenticators
+ pub authenticators: VariantList,
+ /// List of supported extended features
+ pub feature_list: StringList,
+}
+
+impl HandshakeSerialize for ClientInitAck {
+ fn serialize(&self) -> Result<Vec<u8>, Error> {
+ let mut values: VariantMap = VariantMap::with_capacity(6);
+ values.insert(
+ "MsgType".to_string(),
+ Variant::String("ClientInitAck".to_string()),
+ );
+ values.insert("CoreFeatures".to_string(), Variant::u32(self.core_features));
+ values.insert(
+ "Configured".to_string(),
+ Variant::bool(self.core_configured),
+ );
+ values.insert(
+ "StorageBackends".to_string(),
+ Variant::VariantList(self.storage_backends.clone()),
+ );
+ values.insert(
+ "Authenticators".to_string(),
+ Variant::VariantList(self.authenticators.clone()),
+ );
+ values.insert(
+ "FeatureList".to_string(),
+ Variant::StringList(self.feature_list.clone()),
+ );
+ return HandshakeSerialize::serialize(&values);
+ }
+}
+
+impl HandshakeDeserialize for ClientInitAck {
+ fn parse(b: &[u8]) -> Result<(usize, Self), Error> {
+ let (len, values): (usize, VariantMap) = HandshakeDeserialize::parse(b)?;
+
+ let msgtype = match_variant!(&values["MsgType"], Variant::StringUTF8);
+
+ if msgtype == "ClientInitAck" {
+ return Ok((
+ len,
+ Self {
+ core_features: 0x00008000,
+ core_configured: match_variant!(values["Configured"], Variant::bool),
+ storage_backends: match_variant!(
+ values["StorageBackends"],
+ Variant::VariantList
+ ),
+ authenticators: match_variant!(values["Authenticators"], Variant::VariantList),
+ feature_list: match_variant!(values["FeatureList"], Variant::StringList),
+ },
+ ));
+ } else {
+ bail!(ProtocolError::WrongMsgType);
+ }
+ }
+}
diff --git a/src/message/handshake/clientinitreject.rs b/src/message/handshake/clientinitreject.rs
new file mode 100644
index 0000000..06960b7
--- /dev/null
+++ b/src/message/handshake/clientinitreject.rs
@@ -0,0 +1,46 @@
+use crate::error::ProtocolError;
+use crate::primitive::{Variant, VariantMap};
+use crate::{HandshakeDeserialize, HandshakeSerialize};
+
+use failure::Error;
+
+/// ClientInitReject is received when the initialization fails
+#[derive(Debug)]
+pub struct ClientInitReject {
+ /// String with an error message of what went wrong
+ pub error_string: String,
+}
+
+impl HandshakeSerialize for ClientInitReject {
+ fn serialize(&self) -> Result<Vec<u8>, Error> {
+ let mut values: VariantMap = VariantMap::with_capacity(2);
+ values.insert(
+ "MsgType".to_string(),
+ Variant::String("ClientInitReject".to_string()),
+ );
+ values.insert(
+ "ErrorString".to_string(),
+ Variant::String(self.error_string.clone()),
+ );
+ return HandshakeSerialize::serialize(&values);
+ }
+}
+
+impl HandshakeDeserialize for ClientInitReject {
+ fn parse(b: &[u8]) -> Result<(usize, Self), Error> {
+ let (len, values): (usize, VariantMap) = HandshakeDeserialize::parse(b)?;
+
+ let msgtype = match_variant!(&values["MsgType"], Variant::StringUTF8);
+
+ if msgtype == "ClientInitReject" {
+ return Ok((
+ len,
+ Self {
+ error_string: match_variant!(values["ErrorString"], Variant::String),
+ },
+ ));
+ } else {
+ bail!(ProtocolError::WrongMsgType);
+ }
+ }
+}
diff --git a/src/message/handshake/clientlogin.rs b/src/message/handshake/clientlogin.rs
new file mode 100644
index 0000000..769245b
--- /dev/null
+++ b/src/message/handshake/clientlogin.rs
@@ -0,0 +1,49 @@
+use crate::error::ProtocolError;
+use crate::primitive::{Variant, VariantMap};
+use crate::{HandshakeDeserialize, HandshakeSerialize};
+
+use failure::Error;
+
+/// Login to the core with user data
+/// username and password are transmitted in plain text
+#[derive(Debug)]
+pub struct ClientLogin {
+ pub user: String,
+ pub password: String,
+}
+
+impl HandshakeSerialize for ClientLogin {
+ fn serialize(&self) -> Result<Vec<u8>, Error> {
+ let mut values: VariantMap = VariantMap::new();
+ values.insert(
+ "MsgType".to_string(),
+ Variant::String("ClientLogin".to_string()),
+ );
+ values.insert("User".to_string(), Variant::String(self.user.clone()));
+ values.insert(
+ "Password".to_string(),
+ Variant::String(self.password.clone()),
+ );
+ return HandshakeSerialize::serialize(&values);
+ }
+}
+
+impl HandshakeDeserialize for ClientLogin {
+ fn parse(b: &[u8]) -> Result<(usize, Self), Error> {
+ let (len, values): (usize, VariantMap) = HandshakeDeserialize::parse(b)?;
+
+ let msgtype = match_variant!(&values["MsgType"], Variant::StringUTF8);
+
+ if msgtype == "ClientLogin" {
+ return Ok((
+ len,
+ Self {
+ user: match_variant!(values["User"], Variant::String),
+ password: match_variant!(values["Password"], Variant::String),
+ },
+ ));
+ } else {
+ bail!(ProtocolError::WrongMsgType);
+ }
+ }
+}
diff --git a/src/message/handshake/clientloginack.rs b/src/message/handshake/clientloginack.rs
new file mode 100644
index 0000000..674d307
--- /dev/null
+++ b/src/message/handshake/clientloginack.rs
@@ -0,0 +1,35 @@
+use crate::error::ProtocolError;
+use crate::primitive::{Variant, VariantMap};
+use crate::{HandshakeDeserialize, HandshakeSerialize};
+
+use failure::Error;
+
+/// ClientLoginAck is received after the client has successfully logged in
+/// it has no fields
+#[derive(Debug)]
+pub struct ClientLoginAck;
+
+impl HandshakeSerialize for ClientLoginAck {
+ fn serialize(&self) -> Result<Vec<u8>, Error> {
+ let mut values: VariantMap = VariantMap::with_capacity(1);
+ values.insert(
+ "MsgType".to_string(),
+ Variant::String("ClientLoginAck".to_string()),
+ );
+ return HandshakeSerialize::serialize(&values);
+ }
+}
+
+impl HandshakeDeserialize for ClientLoginAck {
+ fn parse(b: &[u8]) -> Result<(usize, Self), Error> {
+ let (len, values): (usize, VariantMap) = HandshakeDeserialize::parse(b)?;
+
+ let msgtype = match_variant!(&values["MsgType"], Variant::StringUTF8);
+
+ if msgtype == "ClientLogin" {
+ return Ok((len, Self {}));
+ } else {
+ bail!(ProtocolError::WrongMsgType);
+ }
+ }
+}
diff --git a/src/message/handshake/clientloginreject.rs b/src/message/handshake/clientloginreject.rs
new file mode 100644
index 0000000..e8380d6
--- /dev/null
+++ b/src/message/handshake/clientloginreject.rs
@@ -0,0 +1,46 @@
+use crate::error::ProtocolError;
+use crate::primitive::{Variant, VariantMap};
+use crate::{HandshakeDeserialize, HandshakeSerialize};
+
+use failure::Error;
+
+/// ClientLoginReject is received after the client failed to login
+/// It contains an error message as String
+#[derive(Debug)]
+pub struct ClientLoginReject {
+ error: String,
+}
+
+impl HandshakeSerialize for ClientLoginReject {
+ fn serialize(&self) -> Result<Vec<u8>, Error> {
+ let mut values: VariantMap = VariantMap::with_capacity(1);
+ values.insert(
+ "MsgType".to_string(),
+ Variant::String("ClientLoginReject".to_string()),
+ );
+ values.insert(
+ "ErrorString".to_string(),
+ Variant::String(self.error.clone()),
+ );
+ return HandshakeSerialize::serialize(&values);
+ }
+}
+
+impl HandshakeDeserialize for ClientLoginReject {
+ fn parse(b: &[u8]) -> Result<(usize, Self), Error> {
+ let (len, values): (usize, VariantMap) = HandshakeDeserialize::parse(b)?;
+
+ let msgtype = match_variant!(&values["MsgType"], Variant::StringUTF8);
+
+ if msgtype == "ClientLogin" {
+ return Ok((
+ len,
+ Self {
+ error: match_variant!(values["ErrorString"], Variant::String),
+ },
+ ));
+ } else {
+ bail!(ProtocolError::WrongMsgType);
+ }
+ }
+}
diff --git a/src/message/handshake/connack.rs b/src/message/handshake/connack.rs
new file mode 100644
index 0000000..222c08c
--- /dev/null
+++ b/src/message/handshake/connack.rs
@@ -0,0 +1,59 @@
+use failure::Error;
+
+/// Data received right after initializing the connection
+///
+/// ConnAck is serialized sequentially
+#[derive(Debug)]
+pub struct ConnAck {
+ /// The Flag 0x01 for TLS
+ /// and 0x02 for Deflate Compression
+ pub flags: u8,
+ /// Some extra protocol version specific data
+ /// So far unused
+ pub extra: i16,
+ /// The version of the protocol
+ /// 0x00000001 for the legacy protocol
+ /// 0x00000002 for the datastream protocol
+ ///
+ /// Only the datastream protocol is supported by this crate
+ pub version: i8,
+}
+
+impl Default for ConnAck {
+ fn default() -> Self {
+ Self {
+ flags: 0x00,
+ extra: 0x00,
+ version: 0x00000002,
+ }
+ }
+}
+
+impl crate::Serialize for ConnAck {
+ fn serialize(&self) -> Result<Vec<std::primitive::u8>, Error> {
+ let mut bytes: Vec<u8> = Vec::new();
+
+ bytes.append(&mut self.flags.serialize()?);
+ bytes.append(&mut self.extra.serialize()?);
+ bytes.append(&mut self.version.serialize()?);
+
+ Ok(bytes)
+ }
+}
+
+impl crate::Deserialize for ConnAck {
+ fn parse(b: &[u8]) -> Result<(usize, Self), Error> {
+ let (flen, flags) = u8::parse(b)?;
+ let (elen, extra) = i16::parse(&b[flen..])?;
+ let (vlen, version) = i8::parse(&b[(flen + elen)..])?;
+
+ return Ok((
+ flen + elen + vlen,
+ Self {
+ flags,
+ extra,
+ version,
+ },
+ ));
+ }
+}
diff --git a/src/message/handshake/init.rs b/src/message/handshake/init.rs
new file mode 100644
index 0000000..e4c0fa9
--- /dev/null
+++ b/src/message/handshake/init.rs
@@ -0,0 +1,61 @@
+use crate::Deserialize;
+use crate::Serialize;
+
+/// The first few bytes sent to the core to initialize the connection and setup if we want to use tls and compression
+pub struct Init {
+ pub tls: bool,
+ pub compression: bool,
+}
+
+impl Init {
+ pub fn new() -> Self {
+ Self {
+ tls: false,
+ compression: false,
+ }
+ }
+
+ pub fn compression(mut self, v: bool) {
+ self.compression = v
+ }
+
+ pub fn tls(mut self, v: bool) {
+ self.tls = v
+ }
+
+ pub fn serialize(self) -> Vec<u8> {
+ // The handshake message
+ let mut handshake: u32 = 0x42b33f00;
+
+ // If TLS is enabled set the TLS bit on the handshake
+ if self.tls {
+ handshake |= 0x01;
+ }
+
+ // If COMPRESSION is enabled set the COMPRESSION bit on the handshake
+ if self.compression {
+ handshake |= 0x02;
+ }
+
+ return handshake.serialize().unwrap();
+ }
+
+ pub fn parse(buf: &[u8]) -> Self {
+ let (_, handshake) = u32::parse(&buf[0..4]).unwrap();
+
+ let mut init = Self {
+ tls: false,
+ compression: false,
+ };
+
+ if (handshake & 0x01) >= 1 {
+ init.tls = true
+ }
+
+ if (handshake & 0x02) >= 1 {
+ init.tls = true
+ }
+
+ return init;
+ }
+}
diff --git a/src/message/handshake/mod.rs b/src/message/handshake/mod.rs
new file mode 100644
index 0000000..9b3bcee
--- /dev/null
+++ b/src/message/handshake/mod.rs
@@ -0,0 +1,23 @@
+mod clientinit;
+mod clientinitack;
+mod clientinitreject;
+mod clientlogin;
+mod clientloginack;
+mod clientloginreject;
+mod connack;
+mod init;
+mod protocol;
+mod sessioninit;
+mod types;
+
+pub use clientinit::*;
+pub use clientinitack::*;
+pub use clientinitreject::*;
+pub use clientlogin::*;
+pub use clientloginack::*;
+pub use clientloginreject::*;
+pub use connack::*;
+pub use init::*;
+pub use protocol::*;
+pub use sessioninit::*;
+pub use types::*;
diff --git a/src/message/handshake/protocol.rs b/src/message/handshake/protocol.rs
new file mode 100644
index 0000000..d020f33
--- /dev/null
+++ b/src/message/handshake/protocol.rs
@@ -0,0 +1,36 @@
+use crate::Deserialize;
+use crate::Serialize;
+
+pub enum Protocol {
+ Legacy = 0x00000001,
+ Datastream = 0x00000002,
+}
+
+impl Protocol {
+ pub fn new() -> Self {
+ Protocol::Datastream
+ }
+
+ pub fn serialize(self) -> Vec<u8> {
+ let proto: u32 = 0x80000002;
+
+ proto.serialize().unwrap()
+ }
+
+ pub fn parse(buf: &[u8]) -> Self {
+ let mut protolist: Vec<u32> = Vec::new();
+ let mut pos = 0;
+ loop {
+ let (_, proto) = u32::parse(&buf[pos..(pos + 4)]).unwrap();
+ if (proto & 0x80000000) >= 1 {
+ protolist.push(proto - 0x80000000);
+ break;
+ } else {
+ protolist.push(proto);
+ pos += 4;
+ }
+ }
+
+ Protocol::Datastream
+ }
+}
diff --git a/src/message/handshake/sessioninit.rs b/src/message/handshake/sessioninit.rs
new file mode 100644
index 0000000..eca4c10
--- /dev/null
+++ b/src/message/handshake/sessioninit.rs
@@ -0,0 +1,61 @@
+use crate::error::ProtocolError;
+use crate::primitive::{Variant, VariantList, VariantMap};
+use crate::{HandshakeDeserialize, HandshakeSerialize};
+
+use failure::Error;
+
+/// SessionInit is received along with ClientLoginAck to initialize that user Session
+// TODO Replace with proper types
+#[derive(Debug)]
+pub struct SessionInit {
+ /// List of all configured identities
+ identities: VariantList,
+ /// List of all existing buffers
+ buffers: VariantList,
+ /// Ids of all networks
+ network_ids: VariantList,
+}
+
+impl HandshakeSerialize for SessionInit {
+ fn serialize(&self) -> Result<Vec<u8>, Error> {
+ let mut values: VariantMap = VariantMap::with_capacity(1);
+ values.insert(
+ "MsgType".to_string(),
+ Variant::String("SessionInit".to_string()),
+ );
+ values.insert(
+ "Identities".to_string(),
+ Variant::VariantList(self.identities.clone()),
+ );
+ values.insert(
+ "BufferInfos".to_string(),
+ Variant::VariantList(self.buffers.clone()),
+ );
+ values.insert(
+ "NetworkIds".to_string(),
+ Variant::VariantList(self.network_ids.clone()),
+ );
+ return HandshakeSerialize::serialize(&values);
+ }
+}
+
+impl HandshakeDeserialize for SessionInit {
+ fn parse(b: &[u8]) -> Result<(usize, Self), Error> {
+ let (len, values): (usize, VariantMap) = HandshakeDeserialize::parse(b)?;
+
+ let msgtype = match_variant!(&values["MsgType"], Variant::StringUTF8);
+
+ if msgtype == "ClientLogin" {
+ return Ok((
+ len,
+ Self {
+ identities: match_variant!(values["Identities"], Variant::VariantList),
+ buffers: match_variant!(values["BufferInfos"], Variant::VariantList),
+ network_ids: match_variant!(values["NetworkIds"], Variant::VariantList),
+ },
+ ));
+ } else {
+ bail!(ProtocolError::WrongMsgType);
+ }
+ }
+}