From 96bc218c46a0275396ba3ce6fae852c23b570011 Mon Sep 17 00:00:00 2001 From: Spencer Brower Date: Thu, 18 Jun 2026 07:55:11 -0400 Subject: [PATCH] style: Ordered procedures by usage, with main at the top. --- cli.odin | 65 +++++------ config.odin | 169 ++++++++++++++------------- crypto.odin | 125 ++++++++++---------- db.odin | 328 +++++++++++++++++++++++++--------------------------- ssh.odin | 36 +++--- 5 files changed, 355 insertions(+), 368 deletions(-) diff --git a/cli.odin b/cli.odin index 26ff45c..f23b166 100644 --- a/cli.odin +++ b/cli.odin @@ -54,14 +54,6 @@ key somewhere, otherwise your data could be lost forever.`, }, } -delete_command :: proc(cmd: ^Command) { - delete(cmd.args) - delete(cmd.flags) - delete(cmd.bool_set) - bufio.writer_destroy(cmd.out_buf) - free(cmd.out_buf) -} - // Caller is responsible for calling delete_command(cmd). // FIXME: Works in kinda a wonky and awkward way. parse_args :: proc(args: []string, out: io.Stream, err: io.Stream) -> (cmd: Command, ok: bool) { @@ -130,27 +122,12 @@ parse_args :: proc(args: []string, out: io.Stream, err: io.Stream) -> (cmd: Comm return cmd, true } -has_flag :: proc(cmd: ^Command, name: string) -> bool { - _, ok := cmd.flags[name] - if ok { - return true +print_command_help :: proc(cmd: ^Command) { + ok := write_command_help(cmd.name, cmd.out) + if !ok { + fmt.wprintf(cmd.err, "Unknown command: %s\n", cmd.name) + write_usage(cmd.out) } - _, ok2 := cmd.bool_set[name] - return ok2 -} - -find_command :: proc(name: string) -> (CommandInfo, bool) { - for c in COMMANDS { - if c.name == name { - return c, true - } - for a in c.aliases { - if a == name { - return c, true - } - } - } - return CommandInfo{}, false } write_command_help :: proc(name: string, w: io.Writer) -> bool { @@ -183,12 +160,18 @@ write_command_help :: proc(name: string, w: io.Writer) -> bool { return true } -print_command_help :: proc(cmd: ^Command) { - ok := write_command_help(cmd.name, cmd.out) - if !ok { - fmt.wprintf(cmd.err, "Unknown command: %s\n", cmd.name) - write_usage(cmd.out) +find_command :: proc(name: string) -> (CommandInfo, bool) { + for c in COMMANDS { + if c.name == name { + return c, true + } + for a in c.aliases { + if a == name { + return c, true + } + } } + return CommandInfo{}, false } // TODO: command args should be shown in usage. @@ -263,3 +246,19 @@ Use "envr [command] --help" for more information about a command. ) } +has_flag :: proc(cmd: ^Command, name: string) -> bool { + _, ok := cmd.flags[name] + if ok { + return true + } + _, ok2 := cmd.bool_set[name] + return ok2 +} + +delete_command :: proc(cmd: ^Command) { + delete(cmd.args) + delete(cmd.flags) + delete(cmd.bool_set) + bufio.writer_destroy(cmd.out_buf) + free(cmd.out_buf) +} diff --git a/config.odin b/config.odin index 2bfa99c..ea8225b 100644 --- a/config.odin +++ b/config.odin @@ -25,14 +25,6 @@ Config :: struct { config_path: string `json:"-"`, } -default_config_path :: proc(home: string, allocator := context.allocator) -> string { - path, err := filepath.join([]string{home, ".envr", "config.json"}, allocator) - if err != nil { - panic("Ran out of memory when building config path") - } - return path -} - load_config :: proc(config_path: string) -> (Config, bool) { data, read_err := os.read_entire_file_from_path(config_path, context.allocator) if read_err != nil { @@ -53,6 +45,14 @@ load_config :: proc(config_path: string) -> (Config, bool) { return cfg, true } +default_config_path :: proc(home: string, allocator := context.allocator) -> string { + path, err := filepath.join([]string{home, ".envr", "config.json"}, allocator) + if err != nil { + panic("Ran out of memory when building config path") + } + return path +} + delete_config :: proc(cfg: ^Config) { for key in cfg.Keys { delete(key.Private) @@ -73,13 +73,73 @@ delete_config :: proc(cfg: ^Config) { delete(cfg.ScanConfig.Include) } -envr_dir :: proc(config_path: string) -> string { - return filepath.dir(config_path) +save_config :: proc(cfg: Config, force: bool = false) -> bool { + config_dir := envr_dir(cfg.config_path) + + if !os.exists(config_dir) { + mkdir_err := os.make_directory(config_dir) + if mkdir_err != nil { + fmt.printf("Error creating %s directory: %v\n", config_dir, mkdir_err) + return false + } + } + + if os.exists(cfg.config_path) && !force { + info, stat_err := os.stat(cfg.config_path, context.allocator) + if stat_err == nil { + defer os.file_info_delete(info, context.allocator) + if info.size > 0 { + fmt.println("Config file already exists. Run again with --force to reinitialize.") + return false + } + } + } + + data, marshal_err := json.marshal(cfg, {pretty = true, use_spaces = true, spaces = 2}) + if marshal_err != nil { + fmt.printf("Error marshaling config: %v\n", marshal_err) + return false + } + defer delete(data) + + write_err := os.write_entire_file(cfg.config_path, data) + if write_err != nil { + fmt.printf("Error writing config: %v\n", write_err) + return false + } + + return true } -data_path :: proc(config_path: string) -> string { - path, _ := filepath.join([]string{envr_dir(config_path), "data.envr"}) - return path +// Caller is responsible for calling delete_config() +new_config :: proc( + private_key_paths: []string, + cfg_path: string = "~/.envr/config.json", +) -> Config { + keys := make([dynamic]SshKeyPair, 0, len(private_key_paths)) + for priv in private_key_paths { + // TODO: Is this bad? + priv_key := strings.clone(priv) + pub, _ := strings.concatenate([]string{priv_key, ".pub"}) + append(&keys, SshKeyPair{Private = priv_key, Public = pub}) + } + + exclude := make([dynamic]string, 0, 4) + append(&exclude, strings.clone("*\\.envrc")) + append(&exclude, strings.clone("\\.local/")) + append(&exclude, strings.clone("node_modules")) + append(&exclude, strings.clone("vendor")) + + include := make([dynamic]string, 0, 1) + append(&include, strings.clone("~")) + + scan_cfg := ScanConfig { + Matcher = strings.clone("\\.env"), + Exclude = exclude, + Include = include, + } + + return Config{Keys = keys, ScanConfig = scan_cfg, config_path = cfg_path} } find_ssh_private_keys :: proc() -> (keys: [dynamic]string, ok: bool) { @@ -128,73 +188,11 @@ find_ssh_private_keys :: proc() -> (keys: [dynamic]string, ok: bool) { return } -// Caller is responsible for calling delete_config() -new_config :: proc( - private_key_paths: []string, - cfg_path: string = "~/.envr/config.json", -) -> Config { - keys := make([dynamic]SshKeyPair, 0, len(private_key_paths)) - for priv in private_key_paths { - // TODO: Is this bad? - priv_key := strings.clone(priv) - pub, _ := strings.concatenate([]string{priv_key, ".pub"}) - append(&keys, SshKeyPair{Private = priv_key, Public = pub}) - } - - exclude := make([dynamic]string, 0, 4) - append(&exclude, strings.clone("*\\.envrc")) - append(&exclude, strings.clone("\\.local/")) - append(&exclude, strings.clone("node_modules")) - append(&exclude, strings.clone("vendor")) - - include := make([dynamic]string, 0, 1) - append(&include, strings.clone("~")) - - scan_cfg := ScanConfig { - Matcher = strings.clone("\\.env"), - Exclude = exclude, - Include = include, - } - - return Config{Keys = keys, ScanConfig = scan_cfg, config_path = cfg_path} -} - -save_config :: proc(cfg: Config, force: bool = false) -> bool { - config_dir := envr_dir(cfg.config_path) - - if !os.exists(config_dir) { - mkdir_err := os.make_directory(config_dir) - if mkdir_err != nil { - fmt.printf("Error creating %s directory: %v\n", config_dir, mkdir_err) - return false - } - } - - if os.exists(cfg.config_path) && !force { - info, stat_err := os.stat(cfg.config_path, context.allocator) - if stat_err == nil { - defer os.file_info_delete(info, context.allocator) - if info.size > 0 { - fmt.println("Config file already exists. Run again with --force to reinitialize.") - return false - } - } - } - - data, marshal_err := json.marshal(cfg, {pretty = true, use_spaces = true, spaces = 2}) - if marshal_err != nil { - fmt.printf("Error marshaling config: %v\n", marshal_err) - return false - } - defer delete(data) - - write_err := os.write_entire_file(cfg.config_path, data) - if write_err != nil { - fmt.printf("Error writing config: %v\n", write_err) - return false - } - - return true +find_git_roots :: proc(cfg: Config) -> (roots: [dynamic]string, ok: bool) { + paths := search_paths(cfg) + findr.find_repos(paths[:], &roots, os.get_processor_core_count()) + ok = true + return } search_paths :: proc(cfg: Config) -> (paths: [dynamic]string) { @@ -218,10 +216,11 @@ search_paths :: proc(cfg: Config) -> (paths: [dynamic]string) { return } -find_git_roots :: proc(cfg: Config) -> (roots: [dynamic]string, ok: bool) { - paths := search_paths(cfg) - findr.find_repos(paths[:], &roots, os.get_processor_core_count()) - ok = true - return +envr_dir :: proc(config_path: string) -> string { + return filepath.dir(config_path) } +data_path :: proc(config_path: string) -> string { + path, _ := filepath.join([]string{envr_dir(config_path), "data.envr"}) + return path +} diff --git a/crypto.odin b/crypto.odin index 30a4b42..7d3eea8 100644 --- a/crypto.odin +++ b/crypto.odin @@ -20,74 +20,12 @@ RecipientEntry :: struct { EncryptedKey: [CRYPTO_SECRETBOX_KEY_BYTES + CRYPTO_BOX_MAC_BYTES]u8, } -sodium_initialized: bool - -ensure_sodium :: proc() -> bool { - if sodium_initialized { - return true - } - rc := sodium_init() - if rc < 0 { - fmt.println("Error: libsodium initialization failed") - return false - } - sodium_initialized = true - return true -} - X25519Keypair :: struct { Public: [CRYPTO_BOX_PUBLICKEY_BYTES]u8, Private: [CRYPTO_BOX_SECRETKEY_BYTES]u8, } -ssh_to_x25519 :: proc(keys: []SshKeyPair) -> (pairs: []X25519Keypair, ok: bool) { - if len(keys) == 0 { - return - } - - pairs = make([]X25519Keypair, len(keys)) - - for i in 0 ..< len(keys) { - ssh_kp, parse_ok := parse_ssh_private_key(keys[i].Private) - if !parse_ok { - fmt.printf("Error: failed to parse SSH private key: %s\n", keys[i].Private) - delete(pairs) - return - } - - ssh_pub, pub_ok := parse_ssh_public_key(keys[i].Public) - if !pub_ok { - fmt.printf("Error: failed to parse SSH public key: %s\n", keys[i].Public) - delete(pairs) - return - } - - pk_rc := crypto_sign_ed25519_pk_to_curve25519(&pairs[i].Public[0], &ssh_pub[0]) - if pk_rc != 0 { - fmt.println("Error: failed to convert ed25519 public key to curve25519") - delete(pairs) - return - } - - ed25519_sk: [64]u8 - for j in 0 ..< 32 { - ed25519_sk[j] = ssh_kp.Private[j] - } - for j in 0 ..< 32 { - ed25519_sk[32 + j] = ssh_kp.Public[j] - } - - sk_rc := crypto_sign_ed25519_sk_to_curve25519(&pairs[i].Private[0], &ed25519_sk[0]) - if sk_rc != 0 { - fmt.println("Error: failed to convert ed25519 private key to curve25519") - delete(pairs) - return - } - } - - ok = true - return -} +sodium_initialized: bool encrypt :: proc(plaintext: []u8, keys: []SshKeyPair) -> (ciphertext: []u8, ok: bool) { if !ensure_sodium() { @@ -336,3 +274,64 @@ decrypt :: proc(ciphertext: []u8, keys: []SshKeyPair) -> (plaintext: []u8, ok: b return } +ssh_to_x25519 :: proc(keys: []SshKeyPair) -> (pairs: []X25519Keypair, ok: bool) { + if len(keys) == 0 { + return + } + + pairs = make([]X25519Keypair, len(keys)) + + for i in 0 ..< len(keys) { + ssh_kp, parse_ok := parse_ssh_private_key(keys[i].Private) + if !parse_ok { + fmt.printf("Error: failed to parse SSH private key: %s\n", keys[i].Private) + delete(pairs) + return + } + + ssh_pub, pub_ok := parse_ssh_public_key(keys[i].Public) + if !pub_ok { + fmt.printf("Error: failed to parse SSH public key: %s\n", keys[i].Public) + delete(pairs) + return + } + + pk_rc := crypto_sign_ed25519_pk_to_curve25519(&pairs[i].Public[0], &ssh_pub[0]) + if pk_rc != 0 { + fmt.println("Error: failed to convert ed25519 public key to curve25519") + delete(pairs) + return + } + + ed25519_sk: [64]u8 + for j in 0 ..< 32 { + ed25519_sk[j] = ssh_kp.Private[j] + } + for j in 0 ..< 32 { + ed25519_sk[32 + j] = ssh_kp.Public[j] + } + + sk_rc := crypto_sign_ed25519_sk_to_curve25519(&pairs[i].Private[0], &ed25519_sk[0]) + if sk_rc != 0 { + fmt.println("Error: failed to convert ed25519 private key to curve25519") + delete(pairs) + return + } + } + + ok = true + return +} + +ensure_sodium :: proc() -> bool { + if sodium_initialized { + return true + } + rc := sodium_init() + if rc < 0 { + fmt.println("Error: libsodium initialization failed") + return false + } + sodium_initialized = true + return true +} diff --git a/db.odin b/db.odin index 1234891..d360c2f 100644 --- a/db.odin +++ b/db.odin @@ -51,7 +51,6 @@ delete_envfile :: proc(f: ^EnvFile) { delete(f.contents) } - db_open :: proc(cfg_path: string) -> (Db, bool) { cfg, ok := load_config(cfg_path) if !ok { @@ -86,6 +85,49 @@ db_open :: proc(cfg_path: string) -> (Db, bool) { return Db{db = db, cfg = cfg, changed = stat_err != nil}, true } +db_restore_from_encrypted :: proc(db: ^rawptr, cfg: Config) -> bool { + encrypted_data, read_err := os.read_entire_file_from_path( + data_path(cfg.config_path), + context.allocator, + ) + defer delete(encrypted_data) + if read_err != nil { + fmt.printf("Error reading encrypted database: %v\n", read_err) + return false + } + + plaintext, dec_ok := decrypt(encrypted_data, cfg.Keys[:]) + if !dec_ok { + fmt.println("Error: decryption failed") + return false + } + defer delete(plaintext) + + n := i64(len(plaintext)) + buf := sqlite.malloc64(n) + if buf == nil { + fmt.println("Error: failed to allocate buffer for deserialization") + return false + } + copy(buf[:len(plaintext)], plaintext) + + rc := sqlite.deserialize( + db, + "main", + buf, + n, + n, + sqlite.DESERIALIZE_FREEONCLOSE | sqlite.DESERIALIZE_RESIZEABLE, + ) + if rc != sqlite.OK { + sqlite.free(buf) + fmt.printf("Error deserializing database: %s\n", sqlite.db_errmsg(db)) + return false + } + + return true +} + db_close :: proc(d: ^Db) { defer sqlite.db_close(d.db) @@ -126,13 +168,6 @@ db_close :: proc(d: ^Db) { } } -// Caller is responsible for calling: -// ```odin -// delete(results) -// for &result in results { -// delete(&result) -// } -// ``` db_list :: proc(d: ^Db, allocator := context.allocator) -> (results: [dynamic]EnvFile, ok: bool) { stmt: ^rawptr rc := sqlite.prepare_v2( @@ -181,110 +216,6 @@ db_list :: proc(d: ^Db, allocator := context.allocator) -> (results: [dynamic]En return } -db_restore_from_encrypted :: proc(db: ^rawptr, cfg: Config) -> bool { - encrypted_data, read_err := os.read_entire_file_from_path( - data_path(cfg.config_path), - context.allocator, - ) - defer delete(encrypted_data) - if read_err != nil { - fmt.printf("Error reading encrypted database: %v\n", read_err) - return false - } - - plaintext, dec_ok := decrypt(encrypted_data, cfg.Keys[:]) - if !dec_ok { - fmt.println("Error: decryption failed") - return false - } - defer delete(plaintext) - - n := i64(len(plaintext)) - buf := sqlite.malloc64(n) - if buf == nil { - fmt.println("Error: failed to allocate buffer for deserialization") - return false - } - copy(buf[:len(plaintext)], plaintext) - - rc := sqlite.deserialize( - db, - "main", - buf, - n, - n, - sqlite.DESERIALIZE_FREEONCLOSE | sqlite.DESERIALIZE_RESIZEABLE, - ) - if rc != sqlite.OK { - sqlite.free(buf) - fmt.printf("Error deserializing database: %s\n", sqlite.db_errmsg(db)) - return false - } - - return true -} - - -get_git_remotes :: proc(dir: string) -> [dynamic]string { - remotes: [dynamic]string - remote_set: map[string]bool - defer delete(remote_set) - - config_path, _ := filepath.join({dir, ".git", "config"}, context.temp_allocator) - m, _, ok := ini.load_map_from_path(config_path, context.allocator) - if !ok { - return remotes - } - defer ini.delete_map(m) - - for section_name, section in m { - if strings.has_prefix(section_name, "remote ") { - if url, ok := section["url"]; ok { - remote_set[url] = true - } - } - } - - for remote in remote_set { - cloned, _ := strings.clone(remote) - append(&remotes, cloned) - } - - return remotes -} - -new_env_file :: proc(path: string) -> (EnvFile, bool) { - abs_path, abs_err := filepath.abs(path) - if abs_err != nil { - fmt.printf("Error getting absolute path: %v\n", abs_err) - return EnvFile{}, false - } - - dir := filepath.dir(abs_path) - - remotes := get_git_remotes(dir) - - data, read_err := os.read_entire_file_from_path(abs_path, context.allocator) - defer delete(data) - if read_err != nil { - fmt.printf("Error reading file %s: %v\n", abs_path, read_err) - return EnvFile{}, false - } - - digest := hash.hash_bytes(hash.Algorithm.SHA256, data, context.temp_allocator) - // TODO: Handle error - hex_bytes, _ := hex.encode(digest) - - return EnvFile { - Path = abs_path, - Dir = dir, - Remotes = remotes, - Sha256 = string(hex_bytes), - contents = string(data), - }, - true -} - db_insert :: proc(d: ^Db, file: EnvFile) -> bool { remotes_json, marshal_err := json.marshal(file.Remotes) if marshal_err != nil { @@ -424,69 +355,36 @@ db_delete :: proc(d: ^Db, path: string) -> bool { return true } -to_cstring :: proc { - string_to_cstring, - strings.to_cstring, -} - -string_to_cstring :: proc(s: string, allocator := context.allocator) -> cstring { - cs, err := strings.clone_to_cstring(s, allocator) - if err != nil { - fmt.printf("Failed to convert string to cstring: %v\n", err) - panic("Allocation Exception") - } - return cs -} - -clone_cstring :: proc(c: cstring, allocator := context.allocator) -> string { - str, err := strings.clone_from_cstring(c, allocator) - if err != nil { - fmt.printf("Failed to convert string to cstring: %v\n", err) - delete(str) - panic("Allocation Exception") +new_env_file :: proc(path: string) -> (EnvFile, bool) { + abs_path, abs_err := filepath.abs(path) + if abs_err != nil { + fmt.printf("Error getting absolute path: %v\n", abs_err) + return EnvFile{}, false } - return str -} + dir := filepath.dir(abs_path) -db_update_required :: proc(status: SyncFlag) -> bool { - return .BackedUp in status || .DirUpdated in status -} + remotes := get_git_remotes(dir) -shares_remote :: proc(f: ^EnvFile, remotes: []string) -> bool { - for r1 in f.Remotes { - for r2 in remotes { - if r1 == r2 { - return true - } - } - } - return false -} - -update_dir :: proc(f: ^EnvFile, new_dir: string) { - f.Dir = new_dir - base := filepath.base(f.Path) - new_path, _ := strings.concatenate({new_dir, "/", base}) - f.Path = new_path - f.Remotes = get_git_remotes(new_dir) -} - -find_moved_dirs :: proc(d: ^Db, f: ^EnvFile) -> ([dynamic]string, bool) { - roots, roots_ok := find_git_roots(d.cfg) - if !roots_ok { - return {}, false + data, read_err := os.read_entire_file_from_path(abs_path, context.allocator) + defer delete(data) + if read_err != nil { + fmt.printf("Error reading file %s: %v\n", abs_path, read_err) + return EnvFile{}, false } - moved: [dynamic]string - for root in roots { - remotes := get_git_remotes(root) - if shares_remote(f, remotes[:]) { - cloned, _ := strings.clone(root) - append(&moved, cloned) - } - } - return moved, true + digest := hash.hash_bytes(hash.Algorithm.SHA256, data, context.temp_allocator) + // TODO: Handle error + hex_bytes, _ := hex.encode(digest) + + return EnvFile { + Path = abs_path, + Dir = dir, + Remotes = remotes, + Sha256 = string(hex_bytes), + contents = string(data), + }, + true } db_sync :: proc(d: ^Db, f: ^EnvFile) -> (SyncFlag, string) { @@ -565,6 +463,31 @@ env_file_sync :: proc(f: ^EnvFile, dir: SyncDirection, d: ^Db) -> (SyncFlag, str return result, "" } +find_moved_dirs :: proc(d: ^Db, f: ^EnvFile) -> ([dynamic]string, bool) { + roots, roots_ok := find_git_roots(d.cfg) + if !roots_ok { + return {}, false + } + + moved: [dynamic]string + for root in roots { + remotes := get_git_remotes(root) + if shares_remote(f, remotes[:]) { + cloned, _ := strings.clone(root) + append(&moved, cloned) + } + } + return moved, true +} + +update_dir :: proc(f: ^EnvFile, new_dir: string) { + f.Dir = new_dir + base := filepath.base(f.Path) + new_path, _ := strings.concatenate({new_dir, "/", base}) + f.Path = new_path + f.Remotes = get_git_remotes(new_dir) +} + // Loads the contents of the the file at f.Path into f.contents // // Caller is responsible for calling delete on f.contents and f.Sha256 @@ -586,3 +509,70 @@ env_file_backup :: proc(f: ^EnvFile) -> bool { return true } +shares_remote :: proc(f: ^EnvFile, remotes: []string) -> bool { + for r1 in f.Remotes { + for r2 in remotes { + if r1 == r2 { + return true + } + } + } + return false +} + +get_git_remotes :: proc(dir: string) -> [dynamic]string { + remotes: [dynamic]string + remote_set: map[string]bool + defer delete(remote_set) + + config_path, _ := filepath.join({dir, ".git", "config"}, context.temp_allocator) + m, _, ok := ini.load_map_from_path(config_path, context.allocator) + if !ok { + return remotes + } + defer ini.delete_map(m) + + for section_name, section in m { + if strings.has_prefix(section_name, "remote ") { + if url, ok := section["url"]; ok { + remote_set[url] = true + } + } + } + + for remote in remote_set { + cloned, _ := strings.clone(remote) + append(&remotes, cloned) + } + + return remotes +} + +db_update_required :: proc(status: SyncFlag) -> bool { + return .BackedUp in status || .DirUpdated in status +} + +to_cstring :: proc { + string_to_cstring, + strings.to_cstring, +} + +string_to_cstring :: proc(s: string, allocator := context.allocator) -> cstring { + cs, err := strings.clone_to_cstring(s, allocator) + if err != nil { + fmt.printf("Failed to convert string to cstring: %v\n", err) + panic("Allocation Exception") + } + return cs +} + +clone_cstring :: proc(c: cstring, allocator := context.allocator) -> string { + str, err := strings.clone_from_cstring(c, allocator) + if err != nil { + fmt.printf("Failed to convert string to cstring: %v\n", err) + delete(str) + panic("Allocation Exception") + } + + return str +} diff --git a/ssh.odin b/ssh.odin index 6371b1d..2b9dff9 100644 --- a/ssh.odin +++ b/ssh.odin @@ -12,24 +12,6 @@ Ed25519Keypair :: struct { Private: [32]u8, } -read_wire_string :: proc(data: []u8, offset: ^int) -> (s: string, ok: bool) { - if offset^ + 4 > len(data) { - return - } - length := u32(data[offset^]) << 24 | u32(data[offset^ + 1]) << 16 | - u32(data[offset^ + 2]) << 8 | u32(data[offset^ + 3]) - offset^ += 4 - - if offset^ + int(length) > len(data) { - return - } - - s = string(data[offset^ : offset^ + int(length)]) - offset^ += int(length) - ok = true - return -} - parse_ssh_public_key :: proc(pub_path: string) -> (pub: [32]u8, ok: bool) { data, err := os.read_entire_file_from_path(pub_path, context.temp_allocator) if err != nil { @@ -253,3 +235,21 @@ is_encrypted_key :: proc(priv_path: string) -> bool { return ciphername != "none" } + +read_wire_string :: proc(data: []u8, offset: ^int) -> (s: string, ok: bool) { + if offset^ + 4 > len(data) { + return + } + length := u32(data[offset^]) << 24 | u32(data[offset^ + 1]) << 16 | + u32(data[offset^ + 2]) << 8 | u32(data[offset^ + 3]) + offset^ += 4 + + if offset^ + int(length) > len(data) { + return + } + + s = string(data[offset^ : offset^ + int(length)]) + offset^ += int(length) + ok = true + return +}