From: jxnshi Date: Sun, 5 Jan 2025 15:36:46 +0000 (+0100) Subject: Working on host X-Git-Url: https://jxnshi.xyz/repos?a=commitdiff_plain;h=ca791289edf4edfffed3ee821d89e0222144441d;p=mesange.git Working on host --- diff --git a/client-cli/client-cli b/client-cli/client-cli index a0b1753..57c4a20 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 23902c3..cb32f93 100644 --- a/client-cli/command.odin +++ b/client-cli/command.odin @@ -53,6 +53,12 @@ handle_command :: proc(app: ^App, command: string) -> Maybe(Handle_Command_Error app_reset_seed_phrase(app) return nil } + + case .Invalid_Host: + switch command { + case ":r", ":retry": + return nil + } } // General commands. @@ -62,7 +68,7 @@ handle_command :: proc(app: ^App, command: string) -> Maybe(Handle_Command_Error return nil } - app_set_info_bar(app, "Unknown_command.") + app_set_info_bar(app, "Unknown command.") return .Unknown_Command } diff --git a/client-cli/main.odin b/client-cli/main.odin index 038a31d..7f140ed 100644 --- a/client-cli/main.odin +++ b/client-cli/main.odin @@ -14,6 +14,7 @@ import "core:strings" import "core:sync" import "core:thread" import "core:time" +import "core:unicode/utf8" import fpath "core:path/filepath" @@ -24,6 +25,8 @@ COLOR_SECOND :: 17 COLOR_THIRD :: 18 COLOR_FOURTH :: 19 +DEFAULT_PORT :: 42069 + json_marshal_options := json.Marshal_Options{ spec = .JSON, pretty = true, @@ -53,38 +56,47 @@ App :: struct { seed_phrase_entropy: u128, seed_phrase_checksum: u8, + + host: net.TCP_Socket, } app_init :: proc(storage_path: string) -> App { screen := nc.initscr() nc.cbreak() - nc.keypad(screen, true) - nc.curs_set(0) + nc.curs_set(0) + + nc.use_default_colors() + nc.start_color() - nc.use_default_colors() - nc.start_color() + nc.init_color(COLOR_FIRST, 800, 800, 800) + nc.init_color(COLOR_SECOND, 500, 500, 500) + nc.init_color(COLOR_THIRD, 200, 200, 200) + nc.init_color(COLOR_FOURTH, 000, 000, 000) - nc.init_color(COLOR_FIRST, 800, 800, 800) - nc.init_color(COLOR_SECOND, 500, 500, 500) - nc.init_color(COLOR_THIRD, 200, 200, 200) - nc.init_color(COLOR_FOURTH, 000, 000, 000) + nc.init_pair(1, COLOR_FIRST, COLOR_THIRD) + nc.init_pair(2, COLOR_FIRST, COLOR_FOURTH) - nc.init_pair(1, COLOR_FIRST, COLOR_THIRD) - nc.init_pair(2, COLOR_FIRST, COLOR_FOURTH) + screen_height, screen_width := nc.getmaxyx(screen) - screen_height, screen_width := nc.getmaxyx(screen) + info_bar := nc.newwin(1, screen_width, screen_height - 2, 0) + input_window := nc.newwin(1, screen_width, screen_height - 1, 0) - info_bar := nc.newwin(1, screen_width, screen_height - 2, 0) - input_window := nc.newwin(1, screen_width, screen_height - 1, 0) + nc.keypad(input_window, true) + + nc.refresh() - nc.refresh() + profiles_path := fpath.join({ storage_path, "profiles" }) + if !os.exists(profiles_path) { + _ = os.make_directory(profiles_path) + } + app := App{ running = true, storage_path = storage_path, - profiles_path = fpath.join({ storage_path, "profiles" }), + profiles_path = profiles_path, screen = screen, @@ -100,8 +112,13 @@ app_init :: proc(storage_path: string) -> App { } app_deinit :: proc(app: ^App) { + sync.mutex_lock(&app.mutex) + defer sync.mutex_unlock(&app.mutex) + + net.close(app.host) + profile_deinit(app.profile) - delete(app.password) + delete(app.profile_password) config_deinit(app.config) nc.delwin(app.input_window) @@ -187,7 +204,7 @@ app_update_info_bar :: proc(app: ^App) { nc.mvwprintw(app.info_bar, 0, 0, c_content) nc.wattroff(app.info_bar, color) - nc.wrefresh(app.info_bar) + nc.wrefresh(app.info_bar) } app_clear_box_message :: proc(app: ^App) { @@ -200,7 +217,7 @@ app_clear_box_message :: proc(app: ^App) { } } -app_get_input :: proc(app: ^App, allocator := context.allocator) -> string { +app_get_input :: proc(app: ^App, hidden := false, allow_empty := false, allocator := context.allocator) -> string { context.allocator = allocator for { @@ -209,30 +226,84 @@ app_get_input :: proc(app: ^App, allocator := context.allocator) -> string { buffer: [256]u8 + buffer_len: u32 + cursor_pos: u32 + screen_height, screen_width := nc.getmaxyx(app.screen) - nc.curs_set(1) - nc.wgetnstr(app.input_window, raw_data(buffer[:]), len(buffer)) - nc.curs_set(0) + nc.curs_set(1) + + input_loop: for { + char := nc.wgetch(app.input_window) + + switch char { + case '\n': + break input_loop + + case 27: + buffer_len = 0 + cursor_pos = 0 + + case nc.KEY_BACKSPACE: + if cursor_pos != 0 { + buffer_len -= 1 + cursor_pos -= 1 + } + + case nc.KEY_LEFT: + if cursor_pos != 0 { + cursor_pos -= 1 + } + + case nc.KEY_RIGHT: + if cursor_pos != buffer_len { + cursor_pos += 1 + } + + case 0o400..=0o777: - nc.wclear(app.input_window) + case: + buffer_len += 1 - c_input := cstring(raw_data(buffer[:])) + mem.copy( + raw_data(buffer[cursor_pos + 1 : buffer_len]), + raw_data(buffer[cursor_pos : buffer_len - 1]), + int(buffer_len - cursor_pos - 1) + ) - if len(c_input) == 0 { - continue - } + buffer[cursor_pos] = u8(char) - app_set_info_bar(app, "") + cursor_pos += 1 + } + + buffer[buffer_len] = 0 + output := string(buffer[:buffer_len + 1]) + + if !hidden { + nc.wclear(app.input_window) + nc.mvwprintw(app.input_window, 0, 0, strings.unsafe_string_to_cstring(output)) + nc.wrefresh(app.input_window) + nc.wmove(app.input_window, 0, i32(cursor_pos)) + } + } + + nc.curs_set(0) + + nc.wclear(app.input_window) + + c_input := cstring(raw_data(buffer[:])) + + if len(c_input) == 0 && !allow_empty { + continue + } + + app_set_info_bar(app, "") return strings.clone_from_cstring(cstring(raw_data(buffer[:]))) } } app_reset_seed_phrase :: proc(app: ^App) { - sync.mutex_lock(&app.mutex) - defer sync.mutex_unlock(&app.mutex) - app.seed_phrase_entropy = rand.uint128() sha_context: sha3.Context @@ -263,6 +334,9 @@ handle_state :: proc(app: ^App) { case .Ask_Profile_Confirm_Password: state_ask_profile_confirm_password(app) case .Ask_Profile_Password: state_ask_profile_password(app) + case .Connect_To_Host: state_connect_to_host(app) + case .Invalid_Host: state_invalid_host(app) + case .Main: state_main(app) } } @@ -292,32 +366,32 @@ main :: proc() { context.random_generator = rand.default_random_generator(&rng_state) when ODIN_DEBUG { - track: mem.Tracking_Allocator - mem.tracking_allocator_init(&track, context.allocator) + track: mem.Tracking_Allocator + mem.tracking_allocator_init(&track, context.allocator) - context.allocator = mem.tracking_allocator(&track) + context.allocator = mem.tracking_allocator(&track) - defer { - if len(track.allocation_map) > 0 { - log.errorf("%v allocations not freed:", len(track.allocation_map)) + defer { + if len(track.allocation_map) > 0 { + log.errorf("%v allocations not freed:", len(track.allocation_map)) - for _, entry in track.allocation_map { - log.errorf("- %v bytes at %v\n", entry.size, entry.location) - } - } + for _, entry in track.allocation_map { + log.errorf("- %v bytes at %v\n", entry.size, entry.location) + } + } - if len(track.bad_free_array) > 0 { - log.errof("%v incorrect frees:\n", len(track.bad_free_array)) + if len(track.bad_free_array) > 0 { + log.errof("%v incorrect frees:\n", len(track.bad_free_array)) - for entry in track.bad_free_array { - log.errorf("- %p at %v\n", entry.memory, entry.location) - } - } + for entry in track.bad_free_array { + log.errorf("- %p at %v\n", entry.memory, entry.location) + } + } - mem.tracking_allocator_destroy(&track) - } - } - + mem.tracking_allocator_destroy(&track) + } + } + app := app_init(storage_path) defer app_deinit(&app) diff --git a/client-cli/profile.odin b/client-cli/profile.odin index 1abe74a..956e059 100644 --- a/client-cli/profile.odin +++ b/client-cli/profile.odin @@ -88,7 +88,7 @@ profile_load_from_name :: proc(name: string, app: ^App) -> (Profile, Maybe(Profi tag := buffer[len(iv):][:chacha.TAG_SIZE] ciphertext := buffer[len(iv) + len(tag):] - if !chacha.open(&chacha_context, buffer[:], iv, {}, ciphertext, tag) { + if !chacha.open(&chacha_context, buffer[:len(ciphertext)], iv, {}, ciphertext, tag) { return {}, .Invalid_Password } @@ -104,19 +104,20 @@ profile_load_from_name :: proc(name: string, app: ^App) -> (Profile, Maybe(Profi return profile_from_parsed(parsed_profile), nil } -profile_deinit :: proc(profile: ^Profile) { +profile_deinit :: proc(profile: Profile) { delete(profile.host) } -profile_update :: proc(profile: ^Profile, app: ^App) { +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) - profile_file, err1 := os.open(profile_path, os.O_CREATE | os.O_TRUNC, 0o664) + profile_file, err1 := os.open(profile_path, os.O_WRONLY | os.O_CREATE | os.O_TRUNC, 0o664) - parsed_profile := profile_to_parsed(profile^) + parsed_profile := profile_to_parsed(profile) if err1 != nil { + log.errorf("Failed to open profile file with error: %v", err1) app_set_info_bar(app, "Failed to save profile.") return } @@ -127,6 +128,7 @@ profile_update :: proc(profile: ^Profile, app: ^App) { err2 := json.marshal_to_builder(&json_string_builder, parsed_profile, &json_marshal_options) if err2 != nil { + log.errorf("Failed to marshal profile with error: %v", err2) app_set_info_bar(app, "Failed to save profile.") return } @@ -149,17 +151,19 @@ profile_update :: proc(profile: ^Profile, app: ^App) { tag := buffer[len(iv):][:chacha.TAG_SIZE] if rand.read(iv[:]) < len(iv) { + log.error("Failed to generate iv.") app_set_info_bar(app, "Failed to save profile.") return } - chacha.seal(&chacha_context, buffer[len(iv) + len(tag):], tag, iv, {}, transmute([]u8)json_string) + chacha.seal(&chacha_context, buffer[len(iv) + len(tag):][:len(json_string)], tag, iv, {}, transmute([]u8)json_string) content := string(buffer[:len(iv) + len(tag) + len(json_string)]) _, err3 := os.write_string(profile_file, content) if err3 != nil { + log.errorf("Failed to write profile to file with error: %v", err3) app_set_info_bar(app, "Failed to save profile.") return } diff --git a/client-cli/state.odin b/client-cli/state.odin index 771632c..334abdb 100644 --- a/client-cli/state.odin +++ b/client-cli/state.odin @@ -28,6 +28,9 @@ State :: enum { Ask_Profile_Confirm_Password, Ask_Profile_Password, + Connect_To_Host, + Invalid_Host, + Main, } @@ -60,7 +63,7 @@ state_invalid_config :: proc(app: ^App) { }, ) - input := app_get_input(app, context.temp_allocator) + input := app_get_input(app, allocator = context.temp_allocator) if err, ok := handle_command(app, input).?; ok { if err != .Not_A_Command { @@ -82,7 +85,7 @@ state_load_profile :: proc(app: ^App) { return } - profile_filename := strings.concatenate({ name, ".json" }, context.temp_allocator) + profile_filename := strings.concatenate({ selected_profile, ".json" }, context.temp_allocator) profile_path := fpath.join({ app.storage_path, profile_filename }, context.temp_allocator) profile, profile_error := profile_load_from_name(selected_profile, app) @@ -122,7 +125,7 @@ state_invalid_profile :: proc(app: ^App) { }, ) - input := app_get_input(app, context.temp_allocator) + input := app_get_input(app, allocator = context.temp_allocator) if err, ok := handle_command(app, input).?; ok { if err != .Not_A_Command { @@ -180,7 +183,7 @@ state_ask_profile_password :: proc(app: ^App) { }, ) - app.profile_password = app_get_input(app) + app.profile_password = app_get_input(app, true) app_set_state(app, .Load_Profile) } @@ -253,7 +256,7 @@ state_ask_profile_seed_phrase :: proc(app: ^App) { ) } - input := app_get_input(app) + input := app_get_input(app, allocator = context.temp_allocator) if err, ok := handle_command(app, input).?; ok { if err != .Not_A_Command { @@ -311,13 +314,97 @@ state_ask_profile_set_password :: proc(app: ^App) { }, ) - input := app_get_input(app) + app.profile_password = app_get_input(app, true, true) app_set_state(app, .Ask_Profile_Confirm_Password) } state_ask_profile_confirm_password :: proc(app: ^App) { - + app_set_box_message( + app, + { + "Please enter your password again.", + }, + ) + + input := app_get_input(app, true, true, context.temp_allocator) + + if input != app.profile_password { + app_set_info_bar(app, "Passwords don't match.") + return + } + + profile_update(app.profile, app) + + app_set_state(app, .Connect_To_Host) +} + +state_connect_to_host :: proc(app: ^App) { + app_set_box_message( + app, + { + "Connecting to host.", + } + ) + + host_endpoint, err1 := net.resolve_ip4(app.profile.host) + + if err1 != nil { + log.errorf("Failed to resolve host with error: %v", err1) + app_set_state(app, .Invalid_Host) + return + } + + host_endpoint.port = DEFAULT_PORT + + host, err2 := net.dial_tcp_from_endpoint(host_endpoint) + + if err2 != nil { + log.errorf("Failed to connect to host with error: %v", err2) + app_set_state(app, .Invalid_Host) + return + } + + { + sync.mutex_lock(&app.mutex) + defer sync.mutex_unlock(&app.mutex) + + app.host = host + } + + app_set_state(app, .Main) +} + +state_invalid_host :: proc(app: ^App) { + app_set_box_message( + app, + { + "Failed to connect to host.", + "Either enter a new host,", + "or :retry." + } + ) + + input := app_get_input(app) + + if err, ok := handle_command(app, input).?; ok { + if err != .Not_A_Command { + return + } + } else { + delete(input) + return + } + + { + sync.mutex_lock(&app.mutex) + defer sync.mutex_unlock(&app.mutex) + + delete(app.profile.host) + app.profile.host = input + } + + app_set_state(app, .Connect_To_Host) } state_main :: proc(app: ^App) {