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.
package main
+import "core:log"
+import "core:math/rand"
import "core:os"
+import "core:slice"
import "core:strings"
import "core:sync"
Not_A_Command,
Unknown_Command,
Invalid_Arguments,
+ Command_Failed,
}
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.
json_marshal_options := json.Marshal_Options{
spec = .JSON,
- pretty = true,
- use_spaces = true,
use_enum_names = true,
}
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 {
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)
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()
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) {
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)
app.connection_thread = nil
}
}
+
+
}
}
}
package main
+import "core:container/small_array"
import "core:crypto/ed25519"
import "core:crypto/sha3"
import "core:encoding/json"
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 {
return {
private_key = private_key,
host = parsed_profile.host,
+ rooms = parsed_profile.rooms,
}
}
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
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)
import "core:fmt"
import "core:log"
+import "core:mem"
import "core:net"
import "core:sync"
import "core:thread"
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)
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,
+ )
}
}