Merge pull request #1 from hargoniX/chatnet

WIP chat protocol
This commit is contained in:
Henrik Böving
2019-10-11 13:09:25 +02:00
committed by GitHub
19 changed files with 11240 additions and 6 deletions

View File

@@ -7,8 +7,18 @@ license = "MIT"
[build-dependencies] [build-dependencies]
bindgen = "0.50" bindgen = "0.50"
protoc-rust = "2.8.1"
[lib] [lib]
crate-type = ["dylib"] crate-type = ["dylib"]
[dependencies] [dependencies]
diesel = { version = "1.4.2", features=["sqlite"] }
cryptobox = { git = "https://github.com/wireapp/cryptobox.git", branch = "develop" }
proteus = { git = "https://github.com/wireapp/proteus", branch = "develop" }
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
protobuf = "2.8.1"
chrono = { version = "0.4", features = ["serde"] }
uuid = { version = "0.7", features = ["serde", "v4"] }
base64 = "0.10.1"

6
README.md Normal file
View File

@@ -0,0 +1,6 @@
# irssi-wire
[![Actions Status](https://github.com/hargonix/irssi-wire/workflows/rust/badge.svg)]
A, not yet working, irssi module, written in Rust which is supposed to bring
the ability to communicate with the Wire servers to irssi.

View File

@@ -1,11 +1,12 @@
extern crate bindgen;
use std::path::PathBuf; use std::path::PathBuf;
use std::env; use std::env;
use std::io::prelude::*; use std::io::prelude::*;
use std::fs::File; use std::fs::File;
use protoc_rust::Customize;
fn main() { fn main() {
// C bindings
let server_rec = " let server_rec = "
typedef struct _WireServerRec { typedef struct _WireServerRec {
int x; int x;
@@ -17,9 +18,9 @@ typedef struct _WireServerRec {
// Generate bindings for headers.h // Generate bindings for headers.h
eprintln!("Generating C bindings");
let mut irssi_path = "-I".to_string(); let mut irssi_path = "-I".to_string();
irssi_path.push_str(env::var("IRSSI_INCLUDE").unwrap().as_str()); irssi_path.push_str(env::var("IRSSI_INCLUDE").unwrap().as_str());
println!("I AM IRSSI PATH: {}", irssi_path);
let bindings = bindgen::Builder::default() let bindings = bindgen::Builder::default()
.header("headers.h") .header("headers.h")
.clang_arg("-I/usr/include/glib-2.0/") .clang_arg("-I/usr/include/glib-2.0/")
@@ -32,4 +33,15 @@ typedef struct _WireServerRec {
bindings bindings
.write_to_file(out_path.join("bindings.rs")) .write_to_file(out_path.join("bindings.rs"))
.expect("Couldn't write bindings!"); .expect("Couldn't write bindings!");
eprintln!("Generating protobuf code");
// Protobuffer code generation
protoc_rust::run(protoc_rust::Args {
out_dir: "src/net/protos",
input: &["wire-api.proto"],
includes: &[],
customize: Customize {
..Default::default()
},
}).expect("Couln't generate code from protobuffers for wire api");
} }

1
src/irssi/mod.rs Normal file
View File

@@ -0,0 +1 @@
pub mod bindings;

View File

@@ -1,8 +1,13 @@
use std::os::raw::{c_char, c_int}; use std::os::raw::c_int;
use std::ffi::CString; use std::ffi::CString;
mod bindings; use std::thread;
use std::time::Duration;
use bindings::*; mod irssi;
mod net;
mod storage;
use irssi::bindings::*;
#[no_mangle] #[no_mangle]
pub extern fn wire_core_abicheck(version: *mut c_int) { pub extern fn wire_core_abicheck(version: *mut c_int) {
@@ -17,6 +22,14 @@ pub extern fn wire_core_init() {
unsafe { unsafe {
module_register_full(CString::new("wire").unwrap().as_ptr(), CString::new("core").unwrap().as_ptr(), CString::new("wire/core").unwrap().as_ptr()); module_register_full(CString::new("wire").unwrap().as_ptr(), CString::new("core").unwrap().as_ptr(), CString::new("wire/core").unwrap().as_ptr());
} }
thread::spawn(|| {
let mut i = 0;
loop {
thread::sleep(Duration::from_secs(10));
println!("{} iteration", i);
i += 1;
}
});
} }
#[no_mangle] #[no_mangle]

2
src/net/mod.rs Normal file
View File

@@ -0,0 +1,2 @@
pub(crate) mod protos;
pub(crate) mod model;

97
src/net/model/client.rs Normal file
View File

@@ -0,0 +1,97 @@
#![allow(non_camel_case_types)]
use std::collections::{HashMap, HashSet};
use chrono::{DateTime, Utc};
use serde::{Deserialize, Deserializer, Serialize, Serializer};
use uuid::Uuid;
use crate::net::model::prekeys::{PreKey, LastPreKey};
// Client
#[derive(Serialize, Deserialize, Clone, Debug)]
pub struct Client {
pub id: String,
pub class: Option<Class>,
pub time: Option<DateTime<Utc>>,
pub r#type: Option<ClientType>,
pub cookie_label: Option<String>,
pub label: Option<String>,
pub model: Option<String>
}
#[derive(Serialize, Deserialize, Copy, Clone, Debug)]
pub enum Class {
phone,
tablet,
desktop
}
#[derive(Serialize, Deserialize, Copy, Clone, Debug)]
pub enum ClientType {
permanent,
temporary
}
// PubClientView
#[derive(Serialize, Deserialize, Clone, Debug)]
pub struct PubClientView {
pub id: String,
pub class: Class
}
// SignalingKeys
#[derive(Serialize, Deserialize, Clone, Debug)]
pub struct SignalingKeys {
#[serde(serialize_with = "b64_encode", deserialize_with = "b64_decode")]
pub enc: Vec<u8>,
#[serde(serialize_with = "b64_encode", deserialize_with = "b64_decode")]
pub mac: Vec<u8>
}
fn b64_encode<'a, S>(bytes: &'a Vec<u8>, serialzer: S) -> Result<S::Ok, S::Error>
where S: Serializer
{
serialzer.serialize_str(&base64::encode(&bytes))
}
fn b64_decode<'de, D>(deserialzer: D) -> Result<Vec<u8>, D::Error>
where D: Deserializer<'de>
{
use serde::de::Error;
String::deserialize(deserialzer)
.and_then(|string|
base64::decode(&string)
.map_err(|e| Error::custom(e.to_string()))
)
}
#[derive(Serialize, Deserialize, Clone, Debug)]
pub struct User2Clients {
pub value: HashMap<Uuid, HashSet<String>>
}
#[derive(Serialize, Deserialize, Clone, Debug)]
pub struct ClientMismatch {
pub time: DateTime<Utc>,
pub redundant: User2Clients,
pub missing: User2Clients,
pub deleted: User2Clients
}
#[derive(Serialize, Deserialize, Clone, Debug)]
pub struct RegisterParams{
pub prekeys: Vec<PreKey>,
pub last_prekey: LastPreKey,
pub sig_keys: SignalingKeys,
pub ctype: ClientType,
pub class: Class,
pub cookie_label: String,
pub label: Option<String>,
pub password: Option<String>,
pub model: Option<String>
}
#[derive(Serialize, Deserialize, Clone, Debug)]
struct DeleteParams {
password: String
}

View File

0
src/net/model/events.rs Normal file
View File

View File

2
src/net/model/mod.rs Normal file
View File

@@ -0,0 +1,2 @@
pub mod client;
pub mod prekeys;

63
src/net/model/prekeys.rs Normal file
View File

@@ -0,0 +1,63 @@
#![allow(non_camel_case_types)]
use std::collections::HashMap;
use proteus::keys::{IdentityKey, PreKeyBundle};
use serde::{Deserialize, Serialize};
use uuid::Uuid;
#[derive(Serialize, Deserialize, Clone, Debug)]
pub struct PreKeyMap {
pub value: HashMap<Uuid, HashMap<String, Option<PreKey>>>
}
impl PreKeyMap {
pub fn new() -> PreKeyMap {
PreKeyMap {
value: HashMap::new()
}
}
pub fn get(&self, id: &Uuid) -> Option<&HashMap<String, Option<PreKey>>> {
self.value.get(id)
}
}
#[derive(Serialize, Deserialize, Clone, Debug)]
pub struct PreKey {
pub key: PreKeyBundle
}
impl PreKey {
pub fn last_resort(k: &IdentityKey) -> LastPreKey {
let pk = proteus::PreKey::last_resort();
LastPreKey(PreKey { key: PreKeyBundle::new(k.clone(), &pk) })
}
pub fn from_str(s: &str) -> Option<PreKey> {
base64::decode(s).ok().and_then(|xs| {
PreKeyBundle::deserialise(xs.as_slice()).ok().map(|k| PreKey { key: k })
})
}
}
#[derive(Serialize, Deserialize, Clone, Debug)]
pub struct LastPreKey(PreKey);
impl LastPreKey {
pub fn new(p: PreKey) -> Option<LastPreKey> {
if p.key.prekey_id == proteus::MAX_PREKEY_ID {
Some(LastPreKey(p))
} else {
None
}
}
}
#[derive(Serialize, Deserialize, Clone, Debug)]
pub struct ClientPreKey {
pub id: String,
pub key: PreKey
}
#[derive(Serialize, Deserialize, Clone, Debug)]
pub struct ClientPreKeys(Vec<ClientPreKey>);

0
src/net/model/token.rs Normal file
View File

0
src/net/model/user.rs Normal file
View File

1
src/net/protos/mod.rs Normal file
View File

@@ -0,0 +1 @@
pub mod wire_api;

10758
src/net/protos/wire_api.rs Normal file

File diff suppressed because it is too large Load Diff

0
src/storage/mod.rs Normal file
View File

269
wire-api.proto Normal file
View File

@@ -0,0 +1,269 @@
// syntax = "proto2";
option java_package = "com.waz.model";
message GenericMessage {
required string message_id = 1; // client generated random id, preferably UUID
oneof content {
Text text = 2;
ImageAsset image = 3; // deprecated in favour of Asset
Knock knock = 4;
LastRead lastRead = 6;
Cleared cleared = 7;
External external = 8;
ClientAction clientAction = 9;
Calling calling = 10;
Asset asset = 11;
MessageHide hidden = 12;
Location location = 13;
MessageDelete deleted = 14;
MessageEdit edited = 15;
Confirmation confirmation = 16;
Reaction reaction = 17;
Ephemeral ephemeral = 18;
Availability availability = 19;
}
}
message Availability {
enum Type {
NONE = 0;
AVAILABLE = 1;
AWAY = 2;
BUSY = 3;
}
required Type type = 1;
}
message Ephemeral {
required int64 expire_after_millis = 1;
oneof content {
Text text = 2;
ImageAsset image = 3; // deprecated in favour of Asset
Knock knock = 4;
Asset asset = 5;
Location location = 6;
}
}
message Text {
required string content = 1;
// reserved 2; // reserved keyword is not available in older protoc versions
repeated LinkPreview link_preview = 3;
repeated Mention mentions = 4;
optional Quote quote = 5; // if this Text is part of a MessageEdit, this field is ignored
optional bool expects_read_confirmation = 6 [default = false]; // whether the sender is expecting to receive a read confirmation
optional LegalHoldStatus legal_hold_status = 7 [default = UNKNOWN]; // whether this message was sent to legal hold
}
message Knock {
required bool hot_knock = 1 [default = false];
optional bool expects_read_confirmation = 2 [default = false]; // whether the sender is expecting to receive a read confirmation
optional LegalHoldStatus legal_hold_status = 3 [default = UNKNOWN]; // whether this message was sent to legal hold
}
message LinkPreview {
required string url = 1;
required int32 url_offset = 2; // url offset from beginning of text message
oneof preview {
Article article = 3; // deprecated - use meta_data
}
optional string permanent_url = 5;
optional string title = 6;
optional string summary = 7;
optional Asset image = 8;
oneof meta_data {
Tweet tweet = 9;
}
}
message Tweet {
optional string author = 1;
optional string username = 2;
}
// deprecated - use the additional fields in LinkPreview
message Article {
required string permanent_url = 1;
optional string title = 2;
optional string summary = 3;
optional Asset image = 4;
}
message Mention {
required int32 start = 1; // offset from beginning of the message counting in utf16 characters
required int32 length = 2;
oneof mention_type {
string user_id = 3;
}
}
message LastRead {
required string conversation_id = 1;
required int64 last_read_timestamp = 2;
}
message Cleared {
required string conversation_id = 1;
required int64 cleared_timestamp = 2;
}
message MessageHide {
required string conversation_id = 1;
required string message_id = 2;
}
message MessageDelete {
required string message_id = 1;
}
message MessageEdit {
required string replacing_message_id = 1;
oneof content {
Text text = 2;
// Reply can also be edited, but the edit will only affect the Text part
}
}
message Quote {
required string quoted_message_id = 1;
optional bytes quoted_message_sha256 = 2;
}
message Confirmation {
enum Type {
DELIVERED = 0;
READ = 1;
}
required Type type = 2;
required string first_message_id = 1;
repeated string more_message_ids = 3;
}
message Location {
required float longitude = 1;
required float latitude = 2;
optional string name = 3; // location description/name
optional int32 zoom = 4; // google maps zoom level (check maps api documentation)
optional bool expects_read_confirmation = 5 [default = false]; // whether the sender is expecting to receive a read confirmation
optional LegalHoldStatus legal_hold_status = 6 [default = UNKNOWN]; // whether this message was sent to legal hold
}
// deprecated in favour of Asset.Original.ImageMetaData
message ImageAsset {
required string tag = 1;
required int32 width = 2;
required int32 height = 3;
required int32 original_width = 4;
required int32 original_height = 5;
required string mime_type = 6;
required int32 size = 7;
optional bytes otr_key = 8;
optional bytes mac_key = 9; // deprecated - use sha256
optional bytes mac = 10; // deprecated - use sha256
optional bytes sha256 = 11; // sha256 of ciphertext
}
message Asset {
message Original {
required string mime_type = 1;
required uint64 size = 2;
optional string name = 3;
oneof meta_data {
ImageMetaData image = 4;
VideoMetaData video = 5;
AudioMetaData audio = 6;
}
optional string source = 7; // link to source e.g. http://giphy.com/234245
optional string caption = 8; // caption of the asset, e.g. "dog" for a Giphy "dog" search result
}
message Preview {
required string mime_type = 1;
required uint64 size = 2;
optional RemoteData remote = 3;
oneof meta_data {
ImageMetaData image = 4;
}
}
message ImageMetaData {
required int32 width = 1;
required int32 height = 2;
optional string tag = 3;
}
message VideoMetaData {
optional int32 width = 1;
optional int32 height = 2;
optional uint64 duration_in_millis = 3;
}
message AudioMetaData {
optional uint64 duration_in_millis = 1;
// repeated float normalized_loudness = 2 [packed=true]; // deprecated - Switched to bytes instead
optional bytes normalized_loudness = 3; // each byte represent one loudness value as a byte (char) value.
// e.g. a 100-bytes field here represents 100 loudness values.
// Values are in chronological order and range from 0 to 255.
}
enum NotUploaded {
CANCELLED = 0;
FAILED = 1;
}
message RemoteData {
required bytes otr_key = 1;
required bytes sha256 = 2; // obsolete but required for backward compatibility
optional string asset_id = 3;
// optional bytes asset_token = 4; // deprecated - changed type to string
optional string asset_token = 5;
optional EncryptionAlgorithm encryption = 6;
}
optional Original original = 1;
// optional Preview preview = 2; // deprecated - preview was completely replaced
oneof status {
NotUploaded not_uploaded = 3;
RemoteData uploaded = 4;
}
optional Preview preview = 5;
optional bool expects_read_confirmation = 6 [default = false]; // whether the sender is expecting to receive a read confirmation
optional LegalHoldStatus legal_hold_status = 7 [default = UNKNOWN]; // whether this message was sent to legal hold
}
// Actual message is encrypted with AES and sent as additional data
message External {
required bytes otr_key = 1;
optional bytes sha256 = 2; // sha256 of ciphertext, obsolete but required for backward compatibility
optional EncryptionAlgorithm encryption = 3;
}
message Reaction {
optional string emoji = 1; // some emoji reaction or the empty string to remove previous reaction(s)
required string message_id = 2;
optional LegalHoldStatus legal_hold_status = 3 [default = UNKNOWN]; // whether this message was sent to legal hold
}
enum ClientAction {
RESET_SESSION = 0;
}
message Calling {
required string content = 1;
}
enum EncryptionAlgorithm {
AES_CBC = 0;
AES_GCM = 1;
}
enum LegalHoldStatus {
UNKNOWN = 0;
DISABLED = 1;
ENABLED = 2;
}