Add auth
parent
18ed991b93
commit
533260edd8
|
@ -497,6 +497,7 @@ dependencies = [
|
|||
"ruma-client-api",
|
||||
"ruma-events",
|
||||
"ruma-identifiers",
|
||||
"serde_json",
|
||||
"sled",
|
||||
]
|
||||
|
||||
|
@ -807,9 +808,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "ruma-api"
|
||||
version = "0.15.0-dev.1"
|
||||
version = "0.15.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "44987d5fefcf801a6fb5c5843c17f876a53852fa07e5e4d99e0dca3670f1441a"
|
||||
checksum = "120f0cd8625b842423ef3a63cabb8c309ca35a02de87cc4b377fb2cdd43f1fe5"
|
||||
dependencies = [
|
||||
"http",
|
||||
"percent-encoding 2.1.0",
|
||||
|
@ -824,9 +825,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "ruma-api-macros"
|
||||
version = "0.12.0-dev.1"
|
||||
version = "0.12.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "36931db94874129f9202f650d91447d8317b099bae1e12cdd5769ba4eced07d2"
|
||||
checksum = "bfc523efc9c1ba7033ff17888551c1d378e12eae087cfbe4fcee938ff516759e"
|
||||
dependencies = [
|
||||
"proc-macro2 1.0.9",
|
||||
"quote 1.0.3",
|
||||
|
@ -835,8 +836,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "ruma-client-api"
|
||||
version = "0.6.0"
|
||||
source = "git+https://github.com/ruma/ruma-client-api#57f5e8d66168a54128426c8e34b26fa78f739c3e"
|
||||
version = "0.7.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5a64241cdc0cff76038484451d7a5d2689f8ea4e59b6695cd3c8448af7bcc016"
|
||||
dependencies = [
|
||||
"http",
|
||||
"js_int",
|
||||
|
@ -851,9 +853,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "ruma-events"
|
||||
version = "0.17.0"
|
||||
version = "0.18.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "11951235b25c72a82eb988aabf5af23cae883562665e0cb73954ffe4ae81f11c"
|
||||
checksum = "80e34bfc20462f18d7f0beb6f1863db62d29438f2dcf390b625e9b20696cb2b3"
|
||||
dependencies = [
|
||||
"js_int",
|
||||
"ruma-events-macros",
|
||||
|
@ -864,9 +866,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "ruma-events-macros"
|
||||
version = "0.2.0"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "962d93056619ed61826a9d8872c863560e4892ff6a69b70f593baa5ae8b19dc8"
|
||||
checksum = "ff95b6b4480c570db471b490b35ad70add5470651654e75faf0b97052b4f29e1"
|
||||
dependencies = [
|
||||
"proc-macro2 1.0.9",
|
||||
"quote 1.0.3",
|
||||
|
@ -994,9 +996,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "serde_json"
|
||||
version = "1.0.49"
|
||||
version = "1.0.50"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "02044a6a92866fd61624b3db4d2c9dccc2feabbc6be490b87611bf285edbac55"
|
||||
checksum = "78a7a12c167809363ec3bd7329fc0a3369056996de43c4b37ef3cd54a6ce4867"
|
||||
dependencies = [
|
||||
"itoa",
|
||||
"ryu",
|
||||
|
|
|
@ -9,12 +9,13 @@ edition = "2018"
|
|||
[dependencies]
|
||||
rocket = { git = "https://github.com/SergioBenitez/Rocket.git", features = ["tls"] }
|
||||
http = "0.2.1"
|
||||
ruma-client-api = { git = "https://github.com/ruma/ruma-client-api" }
|
||||
ruma-client-api = "0.7.0"
|
||||
pretty_env_logger = "0.4.0"
|
||||
log = "0.4.8"
|
||||
sled = "0.31.0"
|
||||
directories = "2.0.2"
|
||||
ruma-identifiers = "0.14.1"
|
||||
ruma-api = "0.15.0-dev.1"
|
||||
ruma-events = "0.17.0"
|
||||
ruma-api = "0.15.0"
|
||||
ruma-events = "0.18.0"
|
||||
js_int = "0.1.3"
|
||||
serde_json = "1.0.50"
|
||||
|
|
110
src/data.rs
110
src/data.rs
|
@ -1,16 +1,18 @@
|
|||
use crate::utils;
|
||||
use directories::ProjectDirs;
|
||||
use ruma_events::collections::all::RoomEvent;
|
||||
use ruma_identifiers::UserId;
|
||||
use std::convert::TryInto;
|
||||
|
||||
const USERID_PASSWORD: &str = "userid_password";
|
||||
const USERID_DEVICEIDS: &str = "userid_deviceids";
|
||||
const DEVICEID_TOKEN: &str = "deviceid_token";
|
||||
const TOKEN_USERID: &str = "token_userid";
|
||||
|
||||
pub struct Data(sled::Db);
|
||||
|
||||
impl Data {
|
||||
pub fn set_hostname(&self, hostname: &str) {
|
||||
self.0.insert("hostname", hostname).unwrap();
|
||||
}
|
||||
pub fn hostname(&self) -> String {
|
||||
String::from_utf8(self.0.get("hostname").unwrap().unwrap().to_vec()).unwrap()
|
||||
}
|
||||
/// Load an existing database or create a new one.
|
||||
pub fn load_or_create() -> Self {
|
||||
Data(
|
||||
sled::open(
|
||||
|
@ -22,21 +24,109 @@ impl Data {
|
|||
)
|
||||
}
|
||||
|
||||
/// Set the hostname of the server. Warning: Hostname changes will likely break things.
|
||||
pub fn set_hostname(&self, hostname: &str) {
|
||||
self.0.insert("hostname", hostname).unwrap();
|
||||
}
|
||||
|
||||
/// Get the hostname of the server.
|
||||
pub fn hostname(&self) -> String {
|
||||
utils::bytes_to_string(&self.0.get("hostname").unwrap().unwrap())
|
||||
}
|
||||
|
||||
/// Check if a user has an account by looking for an assigned password.
|
||||
pub fn user_exists(&self, user_id: &UserId) -> bool {
|
||||
self.0
|
||||
.open_tree("username_password")
|
||||
.open_tree(USERID_PASSWORD)
|
||||
.unwrap()
|
||||
.contains_key(user_id.to_string())
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
pub fn user_add(&self, user_id: UserId, password: Option<String>) {
|
||||
/// Create a new user account by assigning them a password.
|
||||
pub fn user_add(&self, user_id: &UserId, password: Option<String>) {
|
||||
self.0
|
||||
.open_tree("username_password")
|
||||
.open_tree(USERID_PASSWORD)
|
||||
.unwrap()
|
||||
.insert(user_id.to_string(), &*password.unwrap_or_default())
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
pub fn room_event_add(&self, room_event: &RoomEvent) {}
|
||||
/// Find out which user an access token belongs to.
|
||||
pub fn user_from_token(&self, token: &str) -> Option<UserId> {
|
||||
self.0
|
||||
.open_tree(TOKEN_USERID)
|
||||
.unwrap()
|
||||
.get(token)
|
||||
.unwrap()
|
||||
.and_then(|bytes| (*utils::bytes_to_string(&bytes)).try_into().ok())
|
||||
}
|
||||
|
||||
/// Checks if the given password is equal to the one in the database.
|
||||
pub fn password_get(&self, user_id: &UserId) -> Option<String> {
|
||||
self.0
|
||||
.open_tree(USERID_PASSWORD)
|
||||
.unwrap()
|
||||
.get(user_id.to_string())
|
||||
.unwrap()
|
||||
.map(|bytes| utils::bytes_to_string(&bytes))
|
||||
}
|
||||
|
||||
/// Add a new device to a user.
|
||||
pub fn device_add(&self, user_id: &UserId, device_id: &str) {
|
||||
self.0
|
||||
.open_tree(USERID_DEVICEIDS)
|
||||
.unwrap()
|
||||
.insert(user_id.to_string(), device_id)
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
/// Replace the access token of one device.
|
||||
pub fn token_replace(&self, user_id: &UserId, device_id: &String, token: String) {
|
||||
// Make sure the device id belongs to the user
|
||||
debug_assert!(self
|
||||
.0
|
||||
.open_tree(USERID_DEVICEIDS)
|
||||
.unwrap()
|
||||
.get(&user_id.to_string()) // Does the user exist?
|
||||
.unwrap()
|
||||
.map(|bytes| utils::bytes_to_vec(&bytes))
|
||||
.filter(|devices| devices.contains(device_id)) // Does the user have that device?
|
||||
.is_some());
|
||||
|
||||
// Remove old token
|
||||
if let Some(old_token) = self
|
||||
.0
|
||||
.open_tree(DEVICEID_TOKEN)
|
||||
.unwrap()
|
||||
.get(device_id)
|
||||
.unwrap()
|
||||
{
|
||||
self.0
|
||||
.open_tree(TOKEN_USERID)
|
||||
.unwrap()
|
||||
.remove(old_token)
|
||||
.unwrap();
|
||||
// It will be removed from DEVICEID_TOKEN by the insert later
|
||||
}
|
||||
|
||||
// Assign token to device_id
|
||||
self.0
|
||||
.open_tree(DEVICEID_TOKEN)
|
||||
.unwrap()
|
||||
.insert(device_id, &*token)
|
||||
.unwrap();
|
||||
|
||||
// Assign token to user
|
||||
self.0
|
||||
.open_tree(TOKEN_USERID)
|
||||
.unwrap()
|
||||
.insert(token, &*user_id.to_string())
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
/// Create a new room event.
|
||||
pub fn room_event_add(&self, _room_event: &RoomEvent) {
|
||||
todo!();
|
||||
}
|
||||
}
|
||||
|
|
154
src/main.rs
154
src/main.rs
|
@ -14,9 +14,10 @@ use ruma_client_api::{
|
|||
},
|
||||
unversioned::get_supported_versions,
|
||||
};
|
||||
use ruma_events::room::message::MessageEvent;
|
||||
use ruma_events::{room::message::MessageEvent, EventResult};
|
||||
use ruma_identifiers::{EventId, UserId};
|
||||
use ruma_wrapper::{MatrixResult, Ruma};
|
||||
use serde_json::map::Map;
|
||||
use std::convert::TryFrom;
|
||||
use std::{collections::HashMap, convert::TryInto};
|
||||
|
||||
|
@ -41,6 +42,7 @@ fn register_route(
|
|||
data: State<Data>,
|
||||
body: Ruma<register::Request>,
|
||||
) -> MatrixResult<register::Response> {
|
||||
// Validate user id
|
||||
let user_id: UserId = match (*format!(
|
||||
"@{}:{}",
|
||||
body.username.clone().unwrap_or("randomname".to_owned()),
|
||||
|
@ -59,6 +61,7 @@ fn register_route(
|
|||
Ok(user_id) => user_id,
|
||||
};
|
||||
|
||||
// Check if username is creative enough
|
||||
if data.user_exists(&user_id) {
|
||||
debug!("ID already taken");
|
||||
return MatrixResult(Err(Error {
|
||||
|
@ -68,68 +71,115 @@ fn register_route(
|
|||
}));
|
||||
}
|
||||
|
||||
data.user_add(user_id.clone(), body.password.clone());
|
||||
// Create user
|
||||
data.user_add(&user_id, body.password.clone());
|
||||
|
||||
// Generate new device id if the user didn't specify one
|
||||
let device_id = body
|
||||
.device_id
|
||||
.clone()
|
||||
.unwrap_or_else(|| "TODO:randomdeviceid".to_owned());
|
||||
|
||||
// Add device
|
||||
data.device_add(&user_id, &device_id);
|
||||
|
||||
// Generate new token for the device
|
||||
let token = "TODO:randomtoken".to_owned();
|
||||
data.token_replace(&user_id, &device_id, token.clone());
|
||||
|
||||
MatrixResult(Ok(register::Response {
|
||||
access_token: "randomtoken".to_owned(),
|
||||
access_token: token,
|
||||
home_server: data.hostname(),
|
||||
user_id,
|
||||
device_id: body.device_id.clone().unwrap_or("randomid".to_owned()),
|
||||
device_id,
|
||||
}))
|
||||
}
|
||||
|
||||
#[post("/_matrix/client/r0/login", data = "<body>")]
|
||||
fn login_route(data: State<Data>, body: Ruma<login::Request>) -> MatrixResult<login::Response> {
|
||||
let username = if let login::UserInfo::MatrixId(mut username) = body.user.clone() {
|
||||
if !username.contains(':') {
|
||||
username = format!("@{}:{}", username, data.hostname());
|
||||
}
|
||||
if let Ok(user_id) = (*username).try_into() {
|
||||
if !data.user_exists(&user_id) {
|
||||
debug!("Userid does not exist. Can't log in.");
|
||||
// Validate login method
|
||||
let user_id =
|
||||
if let (login::UserInfo::MatrixId(mut username), login::LoginInfo::Password { password }) =
|
||||
(body.user.clone(), body.login_info.clone())
|
||||
{
|
||||
if !username.contains(':') {
|
||||
username = format!("@{}:{}", username, data.hostname());
|
||||
}
|
||||
if let Ok(user_id) = (*username).try_into() {
|
||||
if !data.user_exists(&user_id) {}
|
||||
|
||||
// Check password
|
||||
if let Some(correct_password) = data.password_get(&user_id) {
|
||||
if password == correct_password {
|
||||
// Success!
|
||||
user_id
|
||||
} else {
|
||||
debug!("Invalid password.");
|
||||
return MatrixResult(Err(Error {
|
||||
kind: ErrorKind::Unknown,
|
||||
message: "".to_owned(),
|
||||
status_code: http::StatusCode::FORBIDDEN,
|
||||
}));
|
||||
}
|
||||
} else {
|
||||
debug!("UserId does not exist (has no assigned password). Can't log in.");
|
||||
return MatrixResult(Err(Error {
|
||||
kind: ErrorKind::Forbidden,
|
||||
message: "".to_owned(),
|
||||
status_code: http::StatusCode::FORBIDDEN,
|
||||
}));
|
||||
}
|
||||
} else {
|
||||
debug!("Invalid UserId.");
|
||||
return MatrixResult(Err(Error {
|
||||
kind: ErrorKind::Forbidden,
|
||||
message: "UserId not found.".to_owned(),
|
||||
kind: ErrorKind::Unknown,
|
||||
message: "Bad login type.".to_owned(),
|
||||
status_code: http::StatusCode::BAD_REQUEST,
|
||||
}));
|
||||
}
|
||||
user_id
|
||||
} else {
|
||||
debug!("Invalid UserId.");
|
||||
debug!("Bad login type");
|
||||
return MatrixResult(Err(Error {
|
||||
kind: ErrorKind::Unknown,
|
||||
message: "Bad login type.".to_owned(),
|
||||
status_code: http::StatusCode::BAD_REQUEST,
|
||||
}));
|
||||
}
|
||||
} else {
|
||||
debug!("Bad login type");
|
||||
return MatrixResult(Err(Error {
|
||||
kind: ErrorKind::Unknown,
|
||||
message: "Bad login type.".to_owned(),
|
||||
status_code: http::StatusCode::BAD_REQUEST,
|
||||
}));
|
||||
};
|
||||
};
|
||||
|
||||
// Generate new device id if the user didn't specify one
|
||||
let device_id = body
|
||||
.device_id
|
||||
.clone()
|
||||
.unwrap_or("TODO:randomdeviceid".to_owned());
|
||||
|
||||
// Add device (TODO: We might not want to call it when using an existing device)
|
||||
data.device_add(&user_id, &device_id);
|
||||
|
||||
// Generate a new token for the device
|
||||
let token = "TODO:randomtoken".to_owned();
|
||||
data.token_replace(&user_id, &device_id, token.clone());
|
||||
|
||||
return MatrixResult(Ok(login::Response {
|
||||
user_id: username.try_into().unwrap(), // Unwrap is okay because the user is already registered
|
||||
access_token: "randomtoken".to_owned(),
|
||||
home_server: Some("localhost".to_owned()),
|
||||
device_id: body.device_id.clone().unwrap_or("randomid".to_owned()),
|
||||
user_id,
|
||||
access_token: token,
|
||||
home_server: Some(data.hostname()),
|
||||
device_id,
|
||||
well_known: None,
|
||||
}));
|
||||
}
|
||||
|
||||
#[get("/_matrix/client/r0/directory/room/<room_alias>")]
|
||||
fn get_alias_route(room_alias: String) -> MatrixResult<get_alias::Response> {
|
||||
// TODO
|
||||
let room_id = match &*room_alias {
|
||||
"#room:localhost" => "!xclkjvdlfj:localhost",
|
||||
_ => {
|
||||
debug!("Room not found.");
|
||||
return MatrixResult(Err(Error {
|
||||
kind: ErrorKind::NotFound,
|
||||
message: "Room not found.".to_owned(),
|
||||
status_code: http::StatusCode::NOT_FOUND,
|
||||
}))
|
||||
}));
|
||||
}
|
||||
}
|
||||
.try_into()
|
||||
|
@ -146,6 +196,7 @@ fn join_room_by_id_route(
|
|||
_room_id: String,
|
||||
body: Ruma<join_room_by_id::Request>,
|
||||
) -> MatrixResult<join_room_by_id::Response> {
|
||||
// TODO
|
||||
MatrixResult(Ok(join_room_by_id::Response {
|
||||
room_id: body.room_id.clone(),
|
||||
}))
|
||||
|
@ -162,23 +213,34 @@ fn create_message_event_route(
|
|||
_txn_id: String,
|
||||
body: Ruma<create_message_event::Request>,
|
||||
) -> MatrixResult<create_message_event::Response> {
|
||||
dbg!(&body);
|
||||
if let Ok(content) = body.data.clone().into_result() {
|
||||
data.room_event_add(
|
||||
&MessageEvent {
|
||||
content,
|
||||
event_id: EventId::try_from("$randomeventid:localhost").unwrap(),
|
||||
origin_server_ts: utils::millis_since_unix_epoch(),
|
||||
room_id: Some(body.room_id.clone()),
|
||||
sender: UserId::try_from("@TODO:localhost").unwrap(),
|
||||
unsigned: None,
|
||||
}
|
||||
.into(),
|
||||
);
|
||||
}
|
||||
MatrixResult(Ok(create_message_event::Response {
|
||||
event_id: "$randomeventid:localhost".try_into().unwrap(),
|
||||
}))
|
||||
// Check if content is valid
|
||||
let content = match body.data.clone() {
|
||||
EventResult::Ok(content) => content,
|
||||
EventResult::Err(_) => {
|
||||
debug!("No content.");
|
||||
return MatrixResult(Err(Error {
|
||||
kind: ErrorKind::NotFound,
|
||||
message: "No content.".to_owned(),
|
||||
status_code: http::StatusCode::BAD_REQUEST,
|
||||
}));
|
||||
}
|
||||
};
|
||||
|
||||
let event_id = EventId::try_from("$TODOrandomeventid:localhost").unwrap();
|
||||
|
||||
data.room_event_add(
|
||||
&MessageEvent {
|
||||
content,
|
||||
event_id: event_id.clone(),
|
||||
origin_server_ts: utils::millis_since_unix_epoch(),
|
||||
room_id: Some(body.room_id.clone()),
|
||||
sender: body.user_id.expect("user is authenticated"),
|
||||
unsigned: Map::default(),
|
||||
}
|
||||
.into(),
|
||||
);
|
||||
|
||||
MatrixResult(Ok(create_message_event::Response { event_id }))
|
||||
}
|
||||
|
||||
fn main() {
|
||||
|
|
|
@ -10,10 +10,10 @@ use {
|
|||
Endpoint, Outgoing,
|
||||
},
|
||||
ruma_client_api::error::Error,
|
||||
ruma_identifiers::UserId,
|
||||
std::ops::Deref,
|
||||
std::{
|
||||
convert::{TryFrom, TryInto},
|
||||
fmt,
|
||||
io::{Cursor, Read},
|
||||
},
|
||||
};
|
||||
|
@ -22,9 +22,10 @@ const MESSAGE_LIMIT: u64 = 65535;
|
|||
|
||||
/// This struct converts rocket requests into ruma structs by converting them into http requests
|
||||
/// first.
|
||||
#[derive(Debug)]
|
||||
pub struct Ruma<T: Outgoing> {
|
||||
body: T::Incoming,
|
||||
headers: http::HeaderMap<http::header::HeaderValue>,
|
||||
pub user_id: Option<UserId>,
|
||||
}
|
||||
|
||||
impl<T: Endpoint> FromDataSimple for Ruma<T>
|
||||
|
@ -37,9 +38,34 @@ where
|
|||
Error = FromHttpResponseError<<T as Endpoint>::ResponseError>,
|
||||
>,
|
||||
{
|
||||
type Error = ();
|
||||
type Error = (); // TODO: Better error handling
|
||||
|
||||
fn from_data(request: &Request, data: rocket::Data) -> Outcome<Self, Self::Error> {
|
||||
let user_id = if T::METADATA.requires_authentication {
|
||||
let data = request.guard::<State<crate::Data>>().unwrap();
|
||||
|
||||
// Get token from header or query value
|
||||
let token = match request
|
||||
.headers()
|
||||
.get_one("Authorization")
|
||||
.map(|s| s.to_owned())
|
||||
.or_else(|| request.get_query_value("access_token").and_then(|r| r.ok()))
|
||||
{
|
||||
// TODO: M_MISSING_TOKEN
|
||||
None => return Failure((Status::Unauthorized, ())),
|
||||
Some(token) => token,
|
||||
};
|
||||
|
||||
// Check if token is valid
|
||||
match data.user_from_token(&token) {
|
||||
// TODO: M_UNKNOWN_TOKEN
|
||||
None => return Failure((Status::Unauthorized, ())),
|
||||
Some(user_id) => Some(user_id),
|
||||
}
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let mut http_request = http::Request::builder()
|
||||
.uri(request.uri().to_string())
|
||||
.method(&*request.method().to_string());
|
||||
|
@ -52,17 +78,10 @@ where
|
|||
handle.read_to_end(&mut body).unwrap();
|
||||
|
||||
let http_request = http_request.body(body).unwrap();
|
||||
let headers = http_request.headers().clone();
|
||||
|
||||
log::info!("{:?}", http_request);
|
||||
match T::Incoming::try_from(http_request) {
|
||||
Ok(t) => {
|
||||
if T::METADATA.requires_authentication {
|
||||
let data = request.guard::<State<crate::Data>>();
|
||||
// TODO: auth
|
||||
}
|
||||
Success(Ruma { body: t, headers })
|
||||
}
|
||||
Ok(t) => Success(Ruma { body: t, user_id }),
|
||||
Err(e) => {
|
||||
log::error!("{:?}", e);
|
||||
Failure((Status::InternalServerError, ()))
|
||||
|
@ -79,18 +98,6 @@ impl<T: Outgoing> Deref for Ruma<T> {
|
|||
}
|
||||
}
|
||||
|
||||
impl<T: Outgoing> fmt::Debug for Ruma<T>
|
||||
where
|
||||
T::Incoming: fmt::Debug,
|
||||
{
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.debug_struct("Ruma")
|
||||
.field("body", &self.body)
|
||||
.field("headers", &self.headers)
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
/// This struct converts ruma responses into rocket http responses.
|
||||
pub struct MatrixResult<T>(pub std::result::Result<T, Error>);
|
||||
impl<T: TryInto<http::Response<Vec<u8>>>> TryInto<http::Response<Vec<u8>>> for MatrixResult<T> {
|
||||
|
|
18
src/utils.rs
18
src/utils.rs
|
@ -7,3 +7,21 @@ pub fn millis_since_unix_epoch() -> js_int::UInt {
|
|||
.as_millis() as u32)
|
||||
.into()
|
||||
}
|
||||
|
||||
pub fn bytes_to_string(bytes: &[u8]) -> String {
|
||||
String::from_utf8(bytes.to_vec()).expect("convert bytes to string")
|
||||
}
|
||||
|
||||
pub fn vec_to_bytes(vec: Vec<String>) -> Vec<u8> {
|
||||
vec.into_iter()
|
||||
.map(|string| string.into_bytes())
|
||||
.collect::<Vec<Vec<u8>>>()
|
||||
.join(&0)
|
||||
}
|
||||
|
||||
pub fn bytes_to_vec(bytes: &[u8]) -> Vec<String> {
|
||||
bytes
|
||||
.split(|&b| b == 0)
|
||||
.map(|bytes_string| bytes_to_string(bytes_string))
|
||||
.collect::<Vec<String>>()
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue