Skip to content

Instantly share code, notes, and snippets.

@vfreex
Last active July 11, 2019 14:56
Show Gist options
  • Select an option

  • Save vfreex/fc5b8750deabb70dc6911973cc4c47ef to your computer and use it in GitHub Desktop.

Select an option

Save vfreex/fc5b8750deabb70dc6911973cc4c47ef to your computer and use it in GitHub Desktop.
Demo TeamTalk client implementation in Rust
extern crate protoc_rust;
extern crate glob;
#[macro_use]
extern crate log;
extern crate env_logger;
use std::error;
use std::io;
use protoc_rust::Customize;
fn list_files(pattern: &str) -> Result<Vec<String>, io::Error> {
let paths = glob::glob(pattern)
.expect("invalid pattern")
.filter_map(Result::ok)
.map(|it| it.to_str().unwrap().to_owned())
.collect();
info!("{:?}", paths);
Ok(paths)
}
fn build_protobuf(input: &[&str]) -> Result<(), io::Error> {
protoc_rust::run(protoc_rust::Args {
out_dir: "src/protos",
input: input,
includes: &["protobuf"],
customize: Customize {
// Rust-protobuf can be used with bytes crate.
// carllerche_bytes_for_bytes: Some(true),
// carllerche_bytes_for_string: Some(true),
..Default::default()
},
})?;
Ok(())
}
fn build_protobufs() -> Result<(), io::Error> {
let input = list_files("protobuf/*.proto")?;
let srcs: Vec<&str> = input.iter()
.map(|f| f.as_str()).collect();
build_protobuf(&srcs)?;
// for src in srcs.into_iter() {
// build_protobuf(src.as_str());
// }
//info!("protos: {:?}", protos);
Ok(())
}
fn main() -> Result<(), Box<error::Error>> {
env_logger::init();
build_protobufs()?;
Ok(())
}
[package]
name = "ttr-communicator"
version = "0.1.0"
authors = ["Yuxiang Zhu <[email protected]>"]
edition = "2018"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[lib]
name = "ttrcomm"
crate-type = ["rlib", "cdylib", "staticlib"]
[dependencies]
reqwest = "0.9.18"
serde = "1"
serde_json = "1"
byteorder = "1"
protobuf = { version = "2", features = ["with-bytes"] }
rust-crypto = "^0.2"
# nom = "5"
# tokio = "0.1.22"
[build-dependencies]
protoc-rust = "2"
glob = "0.3.0"
log = "0.4.7"
env_logger = "0.6.2"
Discovering services via Login service http://localhost:8080/msg_server
Status: 200 OK
Headers:
{
"connection": "close",
"content-length": "253",
"content-type": "text/html;charset=utf-8",
}
Body:
{
"backupIP" : "172.17.0.1",
"code" : 0,
"discovery" : "http://172.17.0.1/api/discovery",
"msfsBackup" : "http://172.17.0.1:8700/",
"msfsPrior" : "http://172.17.0.1:8700/",
"msg" : "",
"port" : "8000",
"priorIP" : "172.17.0.1"
}
Login service response: LoginResponse {
code: 0,
prior_ip: "172.17.0.1",
backup_ip: "172.17.0.1",
port: "8000",
msfs_prior: "http://172.17.0.1:8700/",
msfs_backup: "http://172.17.0.1:8700/",
discovery: "http://172.17.0.1/api/discovery",
}
Message service endpoint: 172.17.0.1:8000
Connecting to message service: 172.17.0.1:8000
sending request header... TtrPduHeader { length: 64, version: 1, flag: 0, service_id: 1, command_id: 259, seq_num: 1, reversed: 0 }: [0, 0, 0, 64, 0, 1, 0, 0, 0, 1, 1, 3, 0, 1, 0, 0]
sending request body... IMLoginReq { user_name: Some("yux"), password: Some("202cb962ac59075b964b07152d234b70"), online_status: Some(USER_STATUS_ONLINE), client_type: Some(CLIENT_TYPE_MAC), client_version: Some("1.0"), unknown_fields: UnknownFields { fields: None }, cached_size: CachedSize { size: 48 } } [10, 3, 121, 117, 120, 18, 32, 50, 48, 50, 99, 98, 57, 54, 50, 97, 99, 53, 57, 48, 55, 53, 98, 57, 54, 52, 98, 48, 55, 49, 53, 50, 100, 50, 51, 52, 98, 55, 48, 24, 1, 32, 2, 42, 3, 49, 46, 48]
receiving response header...
response header: TtrPduHeader { length: 69, version: 1, flag: 0, service_id: 1, command_id: 260, seq_num: 1, reversed: 0 } [0, 0, 0, 69, 0, 1, 0, 0, 0, 1, 1, 4, 0, 1, 0, 0]
response body: [8, 172, 194, 156, 233, 5, 16, 0, 26, 6, 230, 136, 144, 229, 138, 159, 32, 1, 42, 33, 8, 1, 16, 1, 26, 7, 89, 117, 120, 105, 97, 110, 103, 34, 0, 40, 1, 50, 0, 58, 3, 121, 117, 120, 66, 0, 74, 1, 48, 80, 0, 90, 0]
response body: IMLoginRes { server_time: Some(1562845484), result_code: Some(REFUSE_REASON_NONE), result_string: Some("成功"), online_status: Some(USER_STATUS_ONLINE), user_info: Some(UserInfo { user_id: Some(1), user_gender: Some(1), user_nick_name: Some("Yuxiang"), avatar_url: Some(""), department_id: Some(1), email: Some(""), user_real_name: Some("yux"), user_tel: Some(""), user_domain: Some("0"), status: Some(0), sign_info: Some(""), unknown_fields: UnknownFields { fields: None }, cached_size: CachedSize { size: 0 } }), unknown_fields: UnknownFields { fields: None }, cached_size: CachedSize { size: 0 } }
extern crate byteorder;
extern crate crypto;
extern crate protobuf;
extern crate reqwest;
extern crate serde;
extern crate serde_json;
mod protos;
use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt};
use crypto::digest::Digest;
use crypto::md5::Md5;
use protobuf::Message;
use std::error;
use std::io;
use std::io::{Cursor, Read, Write};
use std::net;
#[derive(serde::Serialize, serde::Deserialize, Debug)]
struct LoginResponse {
code: i32,
#[serde(rename = "priorIP")]
prior_ip: String,
#[serde(rename = "backupIP")]
backup_ip: String,
port: String,
#[serde(rename = "msfsPrior")]
msfs_prior: String,
#[serde(rename = "msfsBackup")]
msfs_backup: String,
discovery: String,
}
/*
C struct of original PduHeader
typedef struct {
uint32_t length; // the whole pdu length
uint16_t version; // pdu version number
uint16_t flag; // not used
uint16_t service_id; //
uint16_t command_id; //
uint16_t seq_num; // 包序号
uint16_t reversed; // 保留
} PduHeader_t;
*/
#[derive(Debug, Default, PartialEq)]
struct TtrPduHeader {
length: u32,
version: u16,
flag: u16,
service_id: u16,
command_id: u16,
seq_num: u16,
reversed: u16,
}
impl TtrPduHeader {
fn write_to_bytes(&self) -> Vec<u8> {
let mut request_header_buf: Vec<u8> = Vec::with_capacity(16);
request_header_buf
.write_u32::<BigEndian>(self.length)
.unwrap();
request_header_buf
.write_u16::<BigEndian>(self.version)
.unwrap();
request_header_buf
.write_u16::<BigEndian>(self.flag)
.unwrap();
request_header_buf
.write_u16::<BigEndian>(self.service_id)
.unwrap();
request_header_buf
.write_u16::<BigEndian>(self.command_id)
.unwrap();
request_header_buf
.write_u16::<BigEndian>(self.seq_num)
.unwrap();
request_header_buf
.write_u16::<BigEndian>(self.reversed)
.unwrap();
request_header_buf
}
fn read_from_bytes(&mut self, data: &[u8]) -> Result<(), io::Error> {
let mut c = Cursor::new(data);
self.length = c.read_u32::<BigEndian>()?;
self.version = c.read_u16::<BigEndian>()?;
self.flag = c.read_u16::<BigEndian>()?;
self.service_id = c.read_u16::<BigEndian>()?;
self.command_id = c.read_u16::<BigEndian>()?;
self.seq_num = c.read_u16::<BigEndian>()?;
self.reversed = c.read_u16::<BigEndian>()?;
Ok(())
}
fn from_bytes(data: &[u8]) -> Result<TtrPduHeader, io::Error> {
let mut header = TtrPduHeader::default();
header.read_from_bytes(data)?;
Ok(header)
}
}
fn get_login_response(login_url: &str) -> Result<LoginResponse, Box<error::Error>> {
let mut res = reqwest::get(login_url)?;
let mut body = String::new();
res.read_to_string(&mut body)?;
println!("Status: {}", res.status());
println!("Headers:\n{:#?}", res.headers());
println!("Body:\n{}", body);
let res_obj: LoginResponse = serde_json::from_str(body.as_str())?;
Ok(res_obj)
}
fn auth_to_message_server(
username: &str,
password: &str,
msg_service_endpoint: &str,
) -> Result<(), Box<error::Error>> {
let mut req = protos::IM_Login::IMLoginReq::default();
req.set_user_name(username.to_string());
let mut md5 = Md5::new();
md5.input_str(password);
let hashed_password = md5.result_str();
req.set_password(hashed_password);
req.set_online_status(protos::IM_BaseDefine::UserStatType::USER_STATUS_ONLINE);
req.set_client_type(protos::IM_BaseDefine::ClientType::CLIENT_TYPE_MAC);
req.set_client_version("1.0".to_string());
let message_bytes = req.write_to_bytes()?;
let mut pdu_header = TtrPduHeader::default();
pdu_header.length = message_bytes.len() as u32 + 16;
pdu_header.version = 1;
pdu_header.service_id = protos::IM_BaseDefine::ServiceID::SID_LOGIN as u16;
pdu_header.command_id = protos::IM_BaseDefine::LoginCmdID::CID_LOGIN_REQ_USERLOGIN as u16;
//uint32_t nSeqNo = m_pSeqAlloctor->getSeq(ALLOCTOR_PACKET);
pdu_header.seq_num = 1;
// connect to message service
println!("Connecting to message service: {}", msg_service_endpoint);
let mut message_service_steam = net::TcpStream::connect(msg_service_endpoint)?;
// write PDU header
let request_header_buf: Vec<u8> = pdu_header.write_to_bytes();
println!(
"sending request header... {:?}: {:?}",
pdu_header, request_header_buf
);
message_service_steam.write_all(&request_header_buf)?;
// write PDU body
println!("sending request body... {:?} {:?}", req, message_bytes);
message_service_steam.write_all(&message_bytes)?;
message_service_steam.flush()?;
// receive
println!("receiving response header...");
let mut response_header_buf = [0; 16];
message_service_steam.read_exact(&mut response_header_buf)?;
let response_header = TtrPduHeader::from_bytes(&response_header_buf)?;
println!(
"response header: {:?} {:?}",
response_header, response_header_buf
);
let mut response_body_buf = vec![0; (response_header.length - 16) as usize];
message_service_steam.read_exact(&mut response_body_buf)?;
println!("response body: {:?}", response_body_buf);
let response_body: protos::IM_Login::IMLoginRes =
protobuf::parse_from_bytes(&response_body_buf)?;
println!("response body: {:?}", response_body);
Ok(())
}
const TTR_LOGIN_SERVER_URL: &str = "http://localhost:8080/msg_server";
fn main() -> Result<(), Box<error::Error>> {
println!(
"Discovering services via Login service {}",
TTR_LOGIN_SERVER_URL
);
let login_res = get_login_response(TTR_LOGIN_SERVER_URL)?;
println!("Login service response: {:#?}", login_res);
let msg_service_endpoint = format!("{}:{}", login_res.prior_ip, login_res.port.parse::<u16>()?);
println!("Message service endpoint: {}", msg_service_endpoint);
auth_to_message_server("yux", "123", msg_service_endpoint.as_str())?;
Ok(())
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment