]> jxnshi.xyz Git - mesange.git/commitdiff
Working on host
authorjxnshi <jxnshi@cock.li>
Sun, 5 Jan 2025 15:36:46 +0000 (16:36 +0100)
committerjxnshi <jxnshi@cock.li>
Sun, 5 Jan 2025 15:36:46 +0000 (16:36 +0100)
client-cli/client-cli
client-cli/command.odin
client-cli/main.odin
client-cli/profile.odin
client-cli/state.odin

index a0b1753148dae6dd130684d23223941d6a5d084b..57c4a208199d23eb385354550754123298094769 100755 (executable)
Binary files a/client-cli/client-cli and b/client-cli/client-cli differ
index 23902c3c892c9ceb8bfabad462cdbc76b22e7257..cb32f937b0c3010cb328b41ad84c6929d63c4982 100644 (file)
@@ -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
 }
index 038a31d074d7a4d7d00fc68a08be833c19bb0f03..7f140ed18b49990705afb1084f10da0d80b94970 100644 (file)
@@ -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)
 
index 1abe74a50806a6a5a8855e4406bc050b1beb6ac7..956e059fdb0c9199894a3ee648ca9df60a1e78ce 100644 (file)
@@ -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
     }
index 771632c95abd30a7abf30af89264add634d38211..334abdb7d71ec2706866211e1787420c262517e2 100644 (file)
@@ -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) {