Last active
July 11, 2019 14:56
-
-
Save vfreex/fc5b8750deabb70dc6911973cc4c47ef to your computer and use it in GitHub Desktop.
Demo TeamTalk client implementation in Rust
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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(()) | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| [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" |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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 } } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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