From: jxnshi Date: Tue, 14 Jan 2025 17:04:58 +0000 (+0100) Subject: Add room related commands X-Git-Url: https://jxnshi.xyz/repos?a=commitdiff_plain;h=2be8aafa302edf99fef217f0dcbe467fef281164;p=mesange.git Add room related commands --- diff --git a/README.md b/README.md index 0b82319..f1a91f2 100644 --- 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. diff --git a/client-cli/client-cli b/client-cli/client-cli index 7f7d12a..0f0eb98 100755 Binary files a/client-cli/client-cli and b/client-cli/client-cli differ diff --git a/client-cli/command.odin b/client-cli/command.odin index 85103c3..0c87125 100644 --- a/client-cli/command.odin +++ b/client-cli/command.odin @@ -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. diff --git a/client-cli/main.odin b/client-cli/main.odin index dff970d..0df2174 100644 --- a/client-cli/main.odin +++ b/client-cli/main.odin @@ -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 } } + + } } } diff --git a/client-cli/profile.odin b/client-cli/profile.odin index abca948..1b986bc 100644 --- a/client-cli/profile.odin +++ b/client-cli/profile.odin @@ -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) diff --git a/server/main.odin b/server/main.odin index 2c7c394..62a3ec4 100644 --- a/server/main.odin +++ b/server/main.odin @@ -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, + ) } } diff --git a/server/server b/server/server index ad99931..f9dce45 100755 Binary files a/server/server and b/server/server differ