Skip to content

Commit

Permalink
v0.1.0 for "Writing Matrix Bridge in Rust"
Browse files Browse the repository at this point in the history
Signed-off-by: Harshil Jani <harshiljani2002@gmail.com>
  • Loading branch information
Harshil-Jani committed Oct 1, 2023
1 parent aa51ad3 commit f95f2f6
Show file tree
Hide file tree
Showing 10 changed files with 254 additions and 17 deletions.
2 changes: 1 addition & 1 deletion book.toml
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,4 @@ authors = ["Harshil Jani"]
language = "en"
multilingual = false
src = "src"
title = "GSoC 2.0 @Freifunk, ft. Harshil Jani"
title = "Writing a Matrix Bridge in Rust"
3 changes: 2 additions & 1 deletion src/SUMMARY.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,8 @@

[Chaos Communication Camp](./chapter_12.md)
[GSoC Lightening Talks 2023](./chapter_13.md)
[Matrix Communication Camp](./chapter_14.md)
[Matrix Summit 2023](./chapter_14.md)

---
[Project Resources](./chapter16.md)
[Closing Notes](./chapter_15.md)
4 changes: 2 additions & 2 deletions src/author.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,9 @@ Harshil Jani is an Engineering Student at National Institute of Technology, Sura
He is quite passionate about helping and volunteering in building great ecosystem of open source tools, softwares or even infrastructures. His, Exploration was so deep that starting from the very first year of his hands on developing digital products, He have luckily set upon the best opportunity coming at the best time.

A good combination of luck and rigorous hardwork with some insomniac nights, He has been able to pull of many open source fellowships cum internships. Talking more about this, He has done his projects in
**Google Summer of Code** with Organization **CERN-HSF** in 2022 and with Freifunk in 2023. Parallely he was also involved with **Code for GovTech 2022** under Sunbird-RC. Looking at his performance in respective programs, The organisers of C4GT'22 which was Samagra Developement offered him an internship which was **Work From Campus Internship** at **SamagraX** which is a tech consultancy firm of Indian State Governments. He got a project slot in **Summer of Bitcoin** under **Rust Bitcoin**
**Google Summer of Code** with Organization **CERN-HSF** in 2022 and with **Freifunk** in 2023. Parallely he was also involved with **Code for GovTech 2022** under Sunbird-RC. Looking at his performance in respective programs, The organisers of C4GT'22 which was Samagra Developement offered him an internship which was **Work From Campus Internship** at **SamagraX** which is a tech consultancy firm of Indian State Governments. He got a project slot in **Summer of Bitcoin** under **Rust Bitcoin**

With such a good plethora of open source experience, He have decided to leave the intern offer which was offered by Texas Instrumentations for the job role of Software Archietecture and working on MPU Sitara sending patches to Linux Kernel. There are spreading rumours that he may think about doing any legacy corporate interns in his last semester of university degree which would be in Jan 2024 with still a condition of the projects being open sourced.
With such a good plethora of open source experience, He have decided to leave the intern offer which was offered by Texas Instrumentations for the job role of Software Archietecture Intern and working on MPU Sitara sending patches to Linux Kernel in order of conflict of timings. There are spreading rumours that he may think about doing any legacy corporate interns in his last semester of university degree which would be in Jan 2024 with still a condition of the projects being open sourced.

Looking ahead in his career, He aims to just keep doing the good work irrespective of any conflicts and stay peaceful in life and enjoying the work assigned or taken.

Expand Down
16 changes: 16 additions & 0 deletions src/chapter16.md
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`


83 changes: 83 additions & 0 deletions src/chapter_10.md
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");
};
```
2 changes: 1 addition & 1 deletion src/chapter_11.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ if msg_sender != bot_matrix_id {
room.send(content, None).await.unwrap();
}

// on receiving !qaul in matrix, Send message
// on receiving !invite in matrix
if msg_body.contains("!invite") {
let matrix_user =
room.get_member(&msg_sender).await.unwrap().unwrap();
Expand Down
14 changes: 12 additions & 2 deletions src/chapter_14.md
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.
5 changes: 1 addition & 4 deletions src/chapter_15.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,8 @@

This marks the completion of my Google Summer of Code 2023 project. With all the project work and the conferences I have been part of, I am thankful to my mentor who have helped me in each stage to achieve better success.

As an individual, I have evolved a lot as a different person which stormy winds flowing through my life span. But towards the end it was all worth it since I found things that gave me the correct direction and purpose [Not talking about Career].

I am happy that this GSoC project was not another bro project which was just out there for playing around and checking that if it works then it works else leave. This was a project which I started with very little knowledge but gained massive insights of various concepts. Being from a non computer science domain, I got to learn and apply core concepts which my peers never even talk about.

The best achievement in open source world in my life is that "I wrote the world's first Matrix Bridge in Rust which made it to Production." And We were invited my `Matrix Community` to showcase our work.
As far as the project is concerned, We are still iterating on the parts where we lack the lower version of Matrix-rust-SDK which I have described in [this chapter](https://harshil-jani.github.io/GSoC-Book-2.0/chapter_3.html#dependencies-conflicts). Probably we would still plan a different architecture of bridge which would be more compatible when libqaul starts using `libp2p 0.52`.

I would urge developers to focus on FOSS principles and keep the source code as open as possible since the growth in computer science is research oriented and that's the main reason it has the term 'science' in it. Life is too short to earn money from code. Prefer farming, cooking, gardening, plumbing etc. to earn side money but for sake of FOSS please do not write proprietary codes.

Expand Down
131 changes: 130 additions & 1 deletion src/chapter_9.md
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.
Loading

0 comments on commit f95f2f6

Please sign in to comment.