-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
v0.1.0 for "Writing Matrix Bridge in Rust"
Signed-off-by: Harshil Jani <harshiljani2002@gmail.com>
- Loading branch information
1 parent
aa51ad3
commit f95f2f6
Showing
10 changed files
with
254 additions
and
17 deletions.
There are no files selected for viewing
This file contains 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
This file contains 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
This file contains 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
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
# Project Resources | ||
|
||
## Blog Posts | ||
|
||
- [Qaul Introduction and need for Matrix Bridge](https://blog.freifunk.net/2023/05/14/gsoc23-qaul-an-internet-independent-communication-application/) | ||
- [Planning the Relay Bot](https://blog.freifunk.net/2023/07/09/gsoc-qaul-matrix-bridge-relay-bot-implementation/) | ||
- [Qaul Matrix Bridge Tutorial](https://blog.freifunk.net/2023/08/28/gsoc23-final-report-qaul-matrix-bridge-tutorial/) | ||
|
||
## Pull Requests and Active Branch | ||
|
||
- [Bridge Branch for testing and further coding](https://github.com/qaul/qaul.net/tree/matrix-bridge-rebased-23092023) `Active` | ||
- [Build Pipelines for Bridge Binary](https://github.com/qaul/qaul.net/pull/597) `Active` | ||
- [Original PR-563](https://github.com/qaul/qaul.net/pull/563) `Stale` | ||
- [Rebased PR-563](https://github.com/qaul/qaul.net/pull/591) `Stale` | ||
|
||
|
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1 +1,84 @@ | ||
# Version-2 : Sending files and receiving files | ||
|
||
Another part of the major bridging features was a choice between puppeting and choosing to work for File exchanges. The file communication was more important since we already gave the users their name in the message as identifier. Puppetting was on the less priorty use case. So we decided to move ahead with the file messages. | ||
|
||
From Matrix it was straightforward that you upload a file in room. It gets encoded into MxcURI (Matrix File Specific URL). From this, We got an helper function from SDK to download the file on the device which would Run the bridge (Assume RPi). Now, This would first download the file and send it further to target peer device (Your Phone). | ||
|
||
Once it completely sends the file, We delete it from the bridge host (RPi). | ||
Well, There can be debate on why would we not just pass the MxcURI directly to target device and decode it on bridge (which is not secure way to do this). This when I have asked to my mentor, He says that Qaul Application has bunch of version keeping. If we change any single part of code in qaul then it possible means that the new users have to upgrade the application to use it. Since Qaul is application which goes in cases of internet shutdowns and not for regular use, This would not be a great way to introduce it. And the bridge would be part of a local network too. So, It does not harm the security. | ||
|
||
```rust | ||
MessageType::File(FileMessageEventContent { | ||
body: file_name,url: file_url, .. }) => { | ||
// We don't consider message in matrix from the bot | ||
// since it would be the response being sent from qaul. | ||
if msg_sender != bot_matrix_id { | ||
// generate the File Request Body | ||
let request = MediaRequest { | ||
format: MediaFormat::File, | ||
media_type: MediaType::Uri(file_url.as_ref().unwrap().clone()), | ||
}; | ||
|
||
// get the bytes data decrypted from the matrix into qaul | ||
let client = MATRIX_CLIENT.get(); | ||
let file_bytes = | ||
client.get_media_content(&request, true).await.unwrap(); | ||
|
||
// Save the file to local storage | ||
let path_string = Storage::get_path(); | ||
let path = Path::new(path_string.as_str()); | ||
let output_file_path = path.join(file_name); | ||
let mut file = std::fs::File::create(output_file_path).unwrap(); | ||
let _ = file.write_all(&file_bytes); | ||
log::info!("File Saved Successfully"); | ||
|
||
// Send the file to qaul world | ||
send_file_to_qaul( | ||
room.room_id(), | ||
file_name, | ||
format!("{} by {}", file_name, msg_sender), | ||
); | ||
} | ||
} | ||
``` | ||
When we want to send the file from qaul to matrix we have many helper functions written in bridge which can help to analyze the chat contents and files and then keep track of their upload status. Based on it, It will help us to send file to matrix from qaul which is reverse communication of what you have seen above. | ||
|
||
You can refer `chat.rs` and `chatfile.rs` for the exact codewise implementation of handling files in chat. | ||
|
||
Here is code to review how we worked on the entire logic from bridge to matrix. | ||
```rust | ||
fn send_file_to_matrix(file_path: String, room_id: &RoomId, extension: String, file_name: String) { | ||
let path = std::env::current_dir().unwrap(); | ||
let mut storage_path = path.as_path().to_str().unwrap().to_string(); | ||
let user = BOT_USER_ACCOUNT_ID.get(); | ||
storage_path.push_str(&format!("/{}", user)); | ||
storage_path.push_str(&format!("/files/{}", file_path)); | ||
|
||
let matrix_client = MATRIX_CLIENT.get(); | ||
let room = matrix_client.get_room(&room_id).unwrap(); | ||
if let Room::Joined(room) = room { | ||
// Build the message content to send to matrix | ||
let rt = Runtime::new().unwrap(); | ||
rt.block_on(async { | ||
// Sends messages into the matrix room | ||
log::info!("{}", storage_path); | ||
let file_buff = PathBuf::from(storage_path.clone()); | ||
let mut buff = File::open(file_buff).unwrap(); | ||
let mut content_type: &Mime = &STAR_STAR; | ||
log::info!("{}", extension); | ||
match extension.as_str() { | ||
"jpg" | "png" | "jpeg" | "gif" | "bmp" | "svg" => content_type = &mime::IMAGE_STAR, | ||
"pdf" => content_type = &mime::APPLICATION_PDF, | ||
_ => { | ||
log::info!("Please raise a github ticket since we don't allow this file-type.") | ||
} | ||
} | ||
room.send_attachment(&file_name, content_type, &mut buff, None) | ||
.await | ||
.unwrap(); | ||
}); | ||
// Delete the file from bot server. | ||
log::info!("Deleting file from : {}", storage_path); | ||
fs::remove_file(storage_path).expect("could not remove file"); | ||
}; | ||
``` |
This file contains 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
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,13 @@ | ||
# Matrix Communication Camp | ||
# Matrix Communication Summit 2023 | ||
|
||
To be given on 23rd Sept 2023 | ||
The Matrix summit was an awesome outcome of our project showcase. There is a back story which I would like to share between me and my mentor. It was 48 hours before our presentation where we tested again for the second time on the community node. Somehow, We got lots of panicks which were due to cyclic initialization. The problem was that if we don't wait for certain miliseconds then there was a cyclic loop asking for initialization of Matrix from Qaul and Qaul from Matrix. | ||
|
||
We both tried up straight for hours and finally, I implemented the temporary hack that we can wait for few seconds for the both parts to respond. | ||
|
||
We did a lot of testing before the actual meeting and we faced latency issues. This was casued because while testing on community node, It finds device via mesh networks. So, Technically someone from Europe would need their device to get routed to mine and forward the connection to my mentor's device and thus the high latency. To resolve this huge latecy, We decided that we will be presenting them on our own local qaul network which would actually be the ideal case for average use. | ||
|
||
--- | ||
|
||
Coming to the presentation part, It went very impressive. There were bunch of people from Element, RuMa, Matrix-SDK etc. in the Matrix Summit and they all were curious to learn first about qaul (given by my mentor) and then I explained them about the need of the bridge. | ||
|
||
Then we had a smooth live demo. In the demo we did the most impressive thing. We opened a public chat room in matrix, Invited them all to join the room and interact with the bridge. They were happy to see it working. And finally, We got appraised for creating this beautiful project using their tools and SDKs. |
This file contains 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
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1 +1,130 @@ | ||
# Version-2 : Invite and remove from group | ||
# Version-2 : Invite and remove from group | ||
|
||
We have thought of invitation and removal of the qaul users from the matrix chat. Now since the matrix room was open for participation by multiple members we were more inclined to set restrictions on who can `!invite` or `!remove` the members from the matrix chat. | ||
|
||
Architecturally, We implemented that the users who have Admin Powers in Matrix can only perform the invitation and removal commands. This would restrict the unmonitered chaos which would happen if we allow everyone to invite or remove. | ||
|
||
In order to invite a member you can write the command in matrix room as | ||
`!invite {peer-id of qaul user}` | ||
|
||
The code from matrix side looks as below : | ||
```rust | ||
// on receiving !invite in matrix | ||
if msg_body.contains("!invite") { | ||
let matrix_user = | ||
room.get_member(&msg_sender).await.unwrap().unwrap(); | ||
// Check for Admin Powers | ||
if matrix_user.power_level() == 100 { | ||
let mut iter = msg_body.split_whitespace(); | ||
let _command = iter.next().unwrap(); | ||
let qaul_user_id = iter.next().unwrap().to_string(); | ||
let room_id_string = room.room_id().to_string(); | ||
let sender_string = msg_sender.to_string(); | ||
let request_id = format!( | ||
"invite#{}#{}#{}", | ||
room_id_string, sender_string, qaul_user_id | ||
); | ||
log::info!("{}", request_id); | ||
// Create group only if the mapping between a qaul grp and matrix room doesn't exist. | ||
// If it exist then please check if user already exist or not. If not then invite | ||
let config = MATRIX_CONFIG.get().write().unwrap().clone(); | ||
let room_id = room.room_id(); | ||
let qaul_group_id: Option<Uuid> = find_key_for_value( | ||
config.room_map.clone(), | ||
room_id.clone(), | ||
); | ||
if qaul_group_id == None { | ||
group::Group::create_group( | ||
format!("{}", msg_sender.to_owned()).to_owned(), | ||
request_id, | ||
); | ||
// Acknowledge about sent invitation to qaul user. | ||
let content = AnyMessageEventContent::RoomMessage( | ||
MessageEventContent::text_plain("User has been invited. Please wait until user accepts the invitation."), | ||
); | ||
room.send(content, None).await.unwrap(); | ||
} else { | ||
// Get the list of users who are members to the given room. | ||
group::Group::group_info( | ||
chat::Chat::uuid_string_to_bin( | ||
qaul_group_id.unwrap().to_string(), | ||
) | ||
.unwrap(), | ||
request_id, | ||
); | ||
log::info!("The Room Mapping already exist for this room"); | ||
// Else Invite the given user in same mapping of the matrix room. | ||
} | ||
} else { | ||
// Not Admin | ||
let content = AnyMessageEventContent::RoomMessage( | ||
MessageEventContent::text_plain( | ||
"Only Admins can perform this operation.", | ||
), | ||
); | ||
room.send(content, None).await.unwrap(); | ||
} | ||
} | ||
``` | ||
|
||
As of now, In qaul we do not have request numbering mechanism, so we don't know that which request was received first and others following. There may be chances that the sequence would be much different in case of traffic on matrix servers and qaul peer network. So we changed our libqaul code a bit to support the id based recognition since we were using RPC. In case of all the invite request the RPC request id is given as `#invite#matrix_room_id#msg_sender_id#qaul_user_peer_id` | ||
|
||
This would then be decoded on the RPC Response. | ||
|
||
```rust | ||
// Receiving GroupInfoResponse which we are polling every 10 ms. | ||
Some(proto::group::Message::GroupInfoResponse(group_info_response)) => { | ||
let group_id = uuid::Uuid::from_bytes( | ||
group_info_response.group_id.try_into().unwrap(), | ||
); | ||
|
||
if request_id != "" { | ||
// reqeust_id = qaul_user_id#room_id | ||
let mut iter = request_id.split('#'); | ||
let cmd = iter.next().unwrap(); | ||
log::info!("cmd : {}", cmd); | ||
let room_id = iter.next().unwrap(); | ||
log::info!("room : {}", room_id); | ||
let _sender = iter.next().unwrap(); | ||
log::info!("sender : {}", _sender); | ||
let qaul_user_id = iter.next().unwrap(); | ||
log::info!("qaul user : {}", qaul_user_id); | ||
|
||
if cmd == "invite" { | ||
let grp_members = group_info_response.members.clone(); | ||
let user_id = | ||
chat::Chat::id_string_to_bin(qaul_user_id.to_owned()).unwrap(); | ||
let mut all_members = Vec::new(); | ||
for member in grp_members { | ||
all_members.push(member.user_id); | ||
} | ||
if all_members.contains(&user_id) { | ||
matrix_rpc( | ||
"User already exist in the qaul group".to_owned(), | ||
RoomId::try_from(room_id).unwrap(), | ||
); | ||
} else { | ||
// Invite user into this group. | ||
let users = QAUL_USERS.get().read().unwrap(); | ||
let user_name = chat::Chat::find_user_for_given_id( | ||
users.clone(), | ||
qaul_user_id.to_owned(), | ||
) | ||
.unwrap(); | ||
matrix_rpc( | ||
format!("User {} has been invited. Please wait until user accepts the invitation.", | ||
user_name | ||
).to_owned(), RoomId::try_from(room_id).unwrap()); | ||
matrix_rpc("User has been invited. Please wait until user accepts the invitation.".to_owned(), RoomId::try_from(room_id).unwrap()); | ||
Self::invite( | ||
chat::Chat::uuid_string_to_bin(group_id.to_string()) | ||
.unwrap(), | ||
user_id, | ||
); | ||
} | ||
} | ||
} | ||
} | ||
``` | ||
|
||
Similarly we do have operations for removal. Do checkout our codebase to understand the implementations at their present state. |
Oops, something went wrong.