]> jxnshi.xyz Git - mesange.git/commitdiff
Add room related commands
authorjxnshi <jxnshi@cock.li>
Tue, 14 Jan 2025 17:04:58 +0000 (18:04 +0100)
committerjxnshi <jxnshi@cock.li>
Tue, 14 Jan 2025 17:04:58 +0000 (18:04 +0100)
README.md
client-cli/client-cli
client-cli/command.odin
client-cli/main.odin
client-cli/profile.odin
server/main.odin
server/server

index 0b82319dc98dd3372656e96de330a4a835f35811..f1a91f264e31df97589baa34aaa8dcbc01653ab0 100644 (file)
--- a/README.md
+++ b/README.md
@@ -4,6 +4,5 @@
 
 Mesange is a federated messaging project focused on privacy.
 
-I'm working on a server and CLI client implementation and I plan on making a GUI
-client too, but you're obviously free to make your own servers and clients or
-even branch the ones I made.
+I'm working on a server and CLI client implementation, but you're obviously free
+to make your own servers and clients or even branch the ones I made.
index 7f7d12a80d0d0b60b8362e6ce6386216968e9b67..0f0eb987e19c9bba1df4d64cd523f5afa24dbd66 100755 (executable)
Binary files a/client-cli/client-cli and b/client-cli/client-cli differ
index 85103c3080216d77f34e6f1dd6ea924cc401fa3c..0c871257a5bf9ad7eed1c340081582bc93a21bed 100644 (file)
@@ -1,6 +1,9 @@
 package main
 
+import "core:log"
+import "core:math/rand"
 import "core:os"
+import "core:slice"
 import "core:strings"
 import "core:sync"
 
@@ -10,6 +13,7 @@ Handle_Command_Error :: enum {
     Not_A_Command,
     Unknown_Command,
     Invalid_Arguments,
+    Command_Failed,
 }
 
 handle_command :: proc(app: ^App, command: string) -> Maybe(Handle_Command_Error) {
@@ -65,6 +69,83 @@ handle_command :: proc(app: ^App, command: string) -> Maybe(Handle_Command_Error
             app_set_state(app, .Connect_To_Host)
             return nil
         }
+
+    case .Main:
+        switch command_name {
+        case ":add":
+            if len(args) == 0 {
+                app_set_info_bar(app, "No room details provided.")
+                return .Invalid_Arguments
+            }
+
+            room_details := strings.split_n(args[0], ":", 2)
+
+            room, ok := room_init(room_details[0], room_details[1])
+
+            if !ok {
+                app_set_info_bar(app, "Invalid room.")
+                return .Invalid_Arguments
+            }
+
+            ok = app_add_room(app, room)
+
+            if !ok {
+                app_set_info_bar(app, "A room with this name already exists.")
+                return .Command_Failed
+            }
+
+            app_set_info_bar(app, "Room added.")
+
+            return nil
+
+        case ":gen", ":generate":
+            if len(args) == 0 {
+                app_set_info_bar(app, "No room name provided.")
+                return .Invalid_Arguments
+            }
+
+            room_name := args[0]
+
+            key: [ROOM_KEY_SIZE]u8
+            _ = rand.read(key[:])
+
+            room, ok := room_init(room_name, string(key[:]))
+
+            if !ok {
+                app_set_info_bar(app, "Invalid name.")
+                return .Invalid_Arguments
+            }
+
+            ok = app_add_room(app, room)
+
+            if !ok {
+                app_set_info_bar(app, "A room with this name already exists.")
+                return .Command_Failed
+            }
+
+            app_set_info_bar(app, "Room generated.")
+
+            return nil
+
+        case ":rm",  ":remove":
+            if len(args) == 0 {
+                app_set_info_bar(app, "No room name provided.")
+                return .Invalid_Arguments
+            }
+
+            room_name := args[0]
+
+            ok := app_remove_room(app, room_name)
+
+            if !ok {
+                app_set_info_bar(app, "No room with that name.")
+                return .Command_Failed
+            }
+
+            app_set_info_bar(app, "Room removed.")
+
+            return nil
+        }
     }
 
     // General commands.
index dff970d577f628d4c18c4610ad3b5612467437d4..0df2174c0693f9418a2020715fe56bdb85d2e359 100644 (file)
@@ -29,8 +29,6 @@ DEFAULT_PORT :: 42069
 
 json_marshal_options := json.Marshal_Options{
     spec = .JSON,
-    pretty = true,
-    use_spaces = true,
     use_enum_names = true,
 }
 
@@ -68,10 +66,11 @@ App :: struct {
     seed_phrase_checksum: u8,
 
     connecting: bool,
-    nav_action: Nav_Action,
-    room_selected: Maybe(u32),
     host: Maybe(net.TCP_Socket),
     connection_thread: Maybe(^thread.Thread),
+
+    nav_action: Nav_Action,
+    selected_room: u32,
 }
 
 app_init :: proc(storage_path: string) -> App {
@@ -139,13 +138,13 @@ app_deinit :: proc(app: ^App) {
         net.close(host)
     }
 
-    profile_deinit(app.profile)
+    // profile_deinit(app.profile)
 
     if len(app.profile_password) != 0 {
         delete(app.profile_password)
     }
 
-    config_deinit(app.config)
+    // config_deinit(app.config)
 
     if len(app.info_bar_content) != 0 {
         delete(app.info_bar_content)
@@ -168,6 +167,46 @@ app_deinit :: proc(app: ^App) {
     delete(app.profiles_path)
 }
 
+app_update_room_list_window :: proc(app: ^App) {
+    if app.state != .Main {
+        return
+    }
+
+    nc.wclear(app.room_list_window)
+
+    c_room_name_buffer: [ROOM_NAME_MAX_SIZE + 1]u8
+
+    for &room, i in app.profile.rooms {
+        room_color := nc.COLOR_PAIR(3)
+
+        if u32(i) == app.selected_room {
+            room_color = nc.COLOR_PAIR(1)
+        }
+
+        room_name := room_get_name(&room)
+        room_name_bytes := transmute([]u8)room_name
+
+        mem.set(raw_data(c_room_name_buffer[:]), ' ', len(c_room_name_buffer))
+        mem.copy_non_overlapping(raw_data(c_room_name_buffer[1:]), raw_data(room_name_bytes), len(room_name))
+
+        c_room_name_buffer[len(c_room_name_buffer) - 1] = 0
+
+        c_room_name := strings.unsafe_string_to_cstring(string(c_room_name_buffer[:]))
+
+        nc.wattron(app.room_list_window, room_color)
+        nc.mvwprintw(app.room_list_window, i32(i + 1), 1, c_room_name)
+        nc.wattroff(app.room_list_window, room_color)
+    }
+
+    color := nc.COLOR_PAIR(3)
+
+    nc.wattron(app.room_list_window, color)
+    nc.box(app.room_list_window, 0, 0)
+    nc.wattroff(app.room_list_window, color)
+
+    nc.wrefresh(app.room_list_window)
+}
+
 app_set_state :: proc(app: ^App, state: State) {
     nc.clear()
     nc.refresh()
@@ -177,25 +216,32 @@ app_set_state :: proc(app: ^App, state: State) {
 
     screen_height, screen_width := nc.getmaxyx(app.screen)
 
-    if app.state != .Main && state == .Main {
+    old_state := app.state
+    app.state = state
+
+    color := nc.COLOR_PAIR(3)
+
+    if old_state != .Main && state == .Main {
         app.room_list_window = nc.newwin(screen_height - 2, 30, 0, 0)
         app.room_window = nc.newwin(screen_height - 2, screen_width - 30, 0, 30)
 
-        color := nc.COLOR_PAIR(3)
-
-        nc.wattron(app.room_list_window, color)
-        nc.box(app.room_list_window, 0, 0)
-        nc.wattroff(app.room_list_window, color)
+        app_update_room_list_window(app)
 
         nc.wattron(app.room_window, color)
         nc.box(app.room_window, 0, 0)
         nc.wattroff(app.room_window, color)
 
+        nc.wrefresh(app.room_window)
+    } else if old_state == .Main && state != .Main {
+        nc.wclear(app.room_list_window)
+        nc.wclear(app.room_window)
+
         nc.wrefresh(app.room_list_window)
         nc.wrefresh(app.room_window)
-    }
 
-    app.state = state
+        nc.delwin(app.room_list_window)
+        nc.delwin(app.room_window)
+    }
 }
 
 app_set_info_bar :: proc(app: ^App, format: string, args: ..any) {
@@ -402,6 +448,40 @@ app_reset_seed_phrase :: proc(app: ^App) {
     app.seed_phrase_checksum = entropy_hash[0] & 0b1111
 }
 
+app_add_room :: proc(app: ^App, room: Room) -> bool {
+    room := room
+
+    for &profile_room in app.profile.rooms {
+        if room_get_name(&profile_room) == room_get_name(&room) {
+            return false
+        }
+    }
+
+    append(&app.profile.rooms, room)
+
+    profile_update(app.profile, app)
+    app_update_room_list_window(app)
+
+    return true
+}
+
+app_remove_room :: proc(app: ^App, name: string) -> bool {
+    for &room, i in app.profile.rooms {
+        if room_get_name(&room) != name {
+            continue
+        }
+
+        ordered_remove(&app.profile.rooms, i)
+
+        profile_update(app.profile, app)
+        app_update_room_list_window(app)
+
+        return true
+    }
+
+    return false
+}
+
 handle_state :: proc(app: ^App) {
     for {
         defer free_all(context.temp_allocator)
@@ -517,6 +597,8 @@ main :: proc() {
                     app.connection_thread = nil
                 }
             }
+
+            
         }
     }
 }
index abca94801b351cb9741eed45a04d5e2cae2e9556..1b986bcfdf79957b94e7805d916f38f35701cec7 100644 (file)
@@ -1,5 +1,6 @@
 package main
 
+import "core:container/small_array"
 import "core:crypto/ed25519"
 import "core:crypto/sha3"
 import "core:encoding/json"
@@ -13,14 +14,52 @@ import "core:sync"
 import chacha "core:crypto/chacha20poly1305"
 import fpath "core:path/filepath"
 
+ROOM_NAME_MAX_SIZE :: 128
+ROOM_KEY_SIZE :: 32
+
+Room_Name ::  small_array.Small_Array(ROOM_NAME_MAX_SIZE, u8)
+
+Room :: struct {
+    name: Room_Name,
+    key: [ROOM_KEY_SIZE]u8,
+}
+
+room_init :: proc(name: string, key: string) -> (Room, bool) {
+    if len(name) > ROOM_NAME_MAX_SIZE || len(key) != ROOM_KEY_SIZE {
+        return {}, false
+    }
+
+    name_bytes := transmute([]u8)name
+    key_bytes := transmute([]u8)key
+
+    room_name: Room_Name
+    small_array.append_elems(&room_name, ..name_bytes)
+
+    room_key: [ROOM_KEY_SIZE]u8
+    mem.copy_non_overlapping(raw_data(room_key[:]), raw_data(key_bytes), ROOM_KEY_SIZE)
+
+    return {
+        name = room_name,
+        key = room_key,
+    },
+        true
+}
+
+room_get_name :: proc(room: ^Room) -> string {
+    bytes := small_array.slice(&room.name)
+    return string(bytes)
+}
+
 Profile :: struct {
     private_key: ed25519.Private_Key,
     host: string,
+    rooms: [dynamic]Room,
 }
 
 Profile_Parsed :: struct {
     private_key: [ed25519.PRIVATE_KEY_SIZE]u8,
     host: string,
+    rooms: [dynamic]Room,
 }
 
 Profile_Load_Error :: enum {
@@ -44,6 +83,7 @@ profile_from_parsed :: proc(parsed_profile: Profile_Parsed, allocator := context
     return {
         private_key = private_key,
         host = parsed_profile.host,
+        rooms = parsed_profile.rooms,
     }
 }
 
@@ -56,9 +96,15 @@ profile_to_parsed :: proc(profile: Profile) -> Profile_Parsed {
     return {
         private_key = private_key,
         host = profile.host,
+        rooms = profile.rooms,
     }
 }
 
+profile_deinit :: proc(profile: Profile) {
+    delete(profile.rooms)
+    delete(profile.host)
+}
+
 profile_load_from_name :: proc(name: string, app: ^App) -> (Profile, Maybe(Profile_Load_Error)) {
     buffer: [4096]u8
 
@@ -102,10 +148,6 @@ profile_load_from_name :: proc(name: string, app: ^App) -> (Profile, Maybe(Profi
     return profile_from_parsed(parsed_profile), nil
 }
 
-profile_deinit :: proc(profile: Profile) {
-    delete(profile.host)
-}
-
 profile_update :: proc(profile: Profile, app: ^App) {
     profile_filename := strings.concatenate({ app.config.selected_profile.?, ".json" }, context.temp_allocator)
     profile_path := fpath.join({ app.profiles_path, profile_filename }, context.temp_allocator)
index 2c7c3940433a2c752401efc52260bb69c1e705f2..62a3ec424557bea0e56ec84e3706009386e76990 100644 (file)
@@ -4,6 +4,7 @@ import "base:runtime"
 
 import "core:fmt"
 import "core:log"
+import "core:mem"
 import "core:net"
 import "core:sync"
 import "core:thread"
@@ -142,17 +143,6 @@ main :: proc() {
     handle_client_thread_context.logger = context.logger
     handle_client_thread_context.random_generator = context.random_generator
 
-    clients_threads := make(map[net.Endpoint]^thread.Thread)
-
-    defer {
-        for _, handle_client_thread in clients_threads {
-            thread.terminate(handle_client_thread, 0)
-            thread.destroy(handle_client_thread)
-        }
-
-        delete(clients_threads)
-    }
-
     for {
         client_socket, client_endpoint, err2 := net.accept_tcp(server_socket)
 
@@ -179,14 +169,12 @@ main :: proc() {
             client_socket = client_socket,
             client_endpoint = client_endpoint,
         }
-        
-        client_thread := thread.create_and_start_with_poly_data(client_handle_data, handle_client, handle_client_thread_context)
 
-        {
-            sync.mutex_lock(&mutex)
-            defer sync.mutex_unlock(&mutex)
-
-            clients_threads[client_endpoint] = client_thread
-        }
+        _ = thread.create_and_start_with_poly_data(
+            client_handle_data,
+            handle_client,
+            handle_client_thread_context,
+            self_cleanup = true,
+        )
     }
 }
index ad99931e99ac211c4342dd899b1314bbf6c31031..f9dce45af6a5a1e7f96827a96c55f40ecdfd09c0 100755 (executable)
Binary files a/server/server and b/server/server differ