From: jxnshi Date: Mon, 27 Jan 2025 16:18:21 +0000 (+0100) Subject: Working on requests X-Git-Url: https://jxnshi.xyz/repos?a=commitdiff_plain;h=7c30626cfaa3c9fe268588e6731b2413dd919aa3;p=mesange.git Working on requests --- diff --git a/client-cli/client-cli b/client-cli/client-cli index 07388ad..6974ee4 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 71ed5ee..8c1e76e 100644 --- a/client-cli/command.odin +++ b/client-cli/command.odin @@ -10,6 +10,8 @@ import "core:sync" import fpath "core:path/filepath" +import "../common" + Handle_Command_Error :: enum { Not_A_Command, Unknown_Command, @@ -68,7 +70,7 @@ handle_command :: proc(app: ^App, command: string) -> Maybe(Handle_Command_Error return nil } - case .Room_List: + case .Room_List, .Room: switch command_name { case ":add": if len(args) == 0 { diff --git a/client-cli/main.odin b/client-cli/main.odin index 38aaee0..8846d4b 100644 --- a/client-cli/main.odin +++ b/client-cli/main.odin @@ -710,7 +710,7 @@ main :: proc() { // Setup state thread context. handle_state_thread_context := runtime.default_context() - handle_state_thread_context.allocator := context.allocator + handle_state_thread_context.allocator = context.allocator handle_state_thread_context.logger = context.logger handle_state_thread_context.random_generator = context.random_generator diff --git a/client-cli/profile.odin b/client-cli/profile.odin index 11a1697..63eb2f2 100644 --- a/client-cli/profile.odin +++ b/client-cli/profile.odin @@ -72,6 +72,17 @@ room_get_name :: proc(room: ^Room) -> string { return string(bytes) } +room_get_id :: proc(room: ^Room) -> [sha3.DIGEST_SIZE_256]u8 { + sha3_context: sha3.Context + sha3.init_256(&sha3_context) + sha3.update(&sha3_context, room.key[:]) + + id: [sha3.DIGEST_SIZE_256]u8 + sha3.final(&sha3_context, id[:]) + + return id +} + Profile :: struct { private_key: ed25519.Private_Key, hosts: string, diff --git a/client-cli/state.odin b/client-cli/state.odin index e58a7d7..39cf6a6 100644 --- a/client-cli/state.odin +++ b/client-cli/state.odin @@ -1,6 +1,5 @@ package main -import "core:crypto/aes" import "core:crypto/sha3" import "core:encoding/json" import "core:fmt" @@ -14,8 +13,11 @@ import "core:strings" import "core:sync" import "core:thread" +import chacha "core:crypto/chacha20" import fpath "core:path/filepath" +import "../common" + State :: enum { Load_Config, Invalid_Config, @@ -531,6 +533,29 @@ state_room :: proc(app: ^App) { return } + selected_room := &app.profile.rooms[app.selected_room] + + iv: [chacha.IV_SIZE]u8 + _ = rand.read(iv[:]) + + chacha_context: chacha.Context + chacha.init(&chacha_context, selected_room.key[:], iv[:]) + + message: [common.MESSAGE_SIZE]u8 + chacha.xor_bytes(&chacha_context, message[:], transmute([]u8)input) + + request := common.Write_Request { + room_id = room_get_id(selected_room), + message = message + } + + err := common.request_server(app.host.?, request, &app.profile.private_key) + + if err != nil { + app_set_info_bar(app, "Failed to send message.") + return + } + case Event: event := thing diff --git a/common/request.odin b/common/request.odin index bd141bf..1066bfc 100644 --- a/common/request.odin +++ b/common/request.odin @@ -3,72 +3,274 @@ package common import "core:bytes" import "core:crypto/ed25519" import "core:crypto/sha3" +import "core:encoding/endian" import "core:io" import "core:net" import "core:mem" +import "core:time" ROOM_KEY_SIZE :: 32 +MESSAGE_SIZE :: 1_000 * 4 -From_Bytes_Error { - Not_Enough_Bytes, +Request_Validity_Error :: enum { + Invalid_Inner_Kind, Invalid_Public_Key, } -Write :: struct { - room_key: [ROOM_KEY_SIZE]u8, - message: string, +From_Bytes_Error :: union { + Request_Validity_Error, + io.Error, } -Client_Request_Content :: union { - Write, +Request_Verify_Error :: enum { + Expired, + Invalid_Signature, +} + +Join_Request :: struct { + room_id: [sha3.DIGEST_SIZE_256]u8, +} + +join_request_from_bytes :: proc(stream: io.Stream) -> (request: Join_Request, err: From_Bytes_Error) { + room_id: [sha3.DIGEST_SIZE_256]u8 + _ = io.read(stream, room_id[:]) or_return + + return { + room_id = room_id, + }, + nil +} + +join_request_to_bytes :: proc(stream: io.Stream, request: Join_Request) -> io.Error { + request := request + _ = io.write(stream, request.room_id[:]) or_return + + return nil +} + +Leave_Request :: struct { + room_id: [sha3.DIGEST_SIZE_256]u8, +} + +leave_request_from_bytes :: proc(stream: io.Stream) -> (request: Leave_Request, err: From_Bytes_Error) { + room_id: [sha3.DIGEST_SIZE_256]u8 + _ = io.write(stream, request.room_id[:]) or_return + + return { + room_id = room_id, + }, + nil +} + +leave_request_to_bytes :: proc(stream: io.Stream, request: Leave_Request) -> io.Error { + request := request + _ = io.write(stream, request.room_id[:]) or_return + + return nil +} + +Write_Request :: struct { + room_id: [sha3.DIGEST_SIZE_256]u8, + message: [MESSAGE_SIZE]u8, +} + +write_request_from_bytes :: proc(stream: io.Stream) -> (request: Write_Request, err: From_Bytes_Error) { + room_id: [sha3.DIGEST_SIZE_256]u8 + _ = io.read(stream, room_id[:]) or_return + + message: [MESSAGE_SIZE]u8 + _ = io.read(stream, message[:]) or_return + + return { + room_id = room_id, + message = message, + }, + nil +} + +write_request_to_bytes :: proc(stream: io.Stream, request: Write_Request) -> io.Error { + request := request + + _ = io.write(stream, request.room_id[:]) or_return + _ = io.write(stream, request.message[:]) or_return + + return nil +} + +Client_Request_Inner :: union { + Join_Request, + Leave_Request, + Write_Request, +} + +client_request_inner_from_bytes :: proc(stream: io.Stream) -> (request: Client_Request_Inner, err: From_Bytes_Error) { + kind, err1 := io.read_byte(stream) + if err1 != nil do return {}, err1 + + inner: Client_Request_Inner + + switch kind { + case 0: inner = join_request_from_bytes(stream) or_return + case 1: inner = leave_request_from_bytes(stream) or_return + case 2: inner = write_request_from_bytes(stream) or_return + case: return {}, Request_Validity_Error.Invalid_Inner_Kind + } + + return inner, nil +} + +client_request_inner_to_bytes :: proc(stream: io.Stream, inner: Client_Request_Inner) -> io.Error { + switch i in inner { + case Join_Request: + io.write_byte(stream, 0) or_return + join_request_to_bytes(stream, i) or_return + + case Leave_Request: + io.write_byte(stream, 1) or_return + leave_request_to_bytes(stream, i) or_return + + case Write_Request: + io.write_byte(stream, 2) or_return + write_request_to_bytes(stream, i) or_return + } + + return nil } Client_Request :: struct { public_key: ed25519.Public_Key, - content: Client_Request_Content, + inner: Client_Request_Inner, timestamp: i64, - signature: [32]u8, + signature: [ed25519.SIGNATURE_SIZE]u8, } -client_request_from_bytes :: proc(bytes: []u8) -> (Client_Request, From_Bytes_Error) { - bytes := bytes - bytes_buffer := bytes.Buffer{ buf = mem.buffer_from_slice(bytes) } +client_request_from_bytes :: proc(input: []u8) -> (request: Client_Request, err: From_Bytes_Error) { + input := input + + bytes_buffer := bytes.Buffer{ buf = mem.buffer_from_slice(input) } stream := bytes.buffer_to_stream(&bytes_buffer) public_key_bytes: [ed25519.PUBLIC_KEY_SIZE]u8 - err1 := io.read_at_least(stream, public_key_bytes, len(public_key_bytes)) - - if err1 != nil { - return {}, .Not_Enough_Bytes - } + _ = io.read(stream, public_key_bytes[:]) or_return public_key: ed25519.Public_Key ok := ed25519.public_key_set_bytes(&public_key, public_key_bytes[:]) + if !ok do return {}, .Invalid_Public_Key - if !ok { - return {}, .Invalid_Public_Key - } + inner := client_request_inner_from_bytes(stream) or_return + + timestamp_bytes: [size_of(i64)]u8 + _ = io.read(stream, timestamp_bytes[:]) or_return + + timestamp, _ := endian.get_i64(timestamp_bytes[:], .Little) + + signature: [ed25519.SIGNATURE_SIZE]u8 + _ = io.read(stream, signature[:]) or_return return { public_key = public_key, - content = content, + inner = inner, timestamp = timestamp, signature = signature, }, nil } -client_request_to_bytes :: proc(stream: io.Stream, request: Client_Request) { - +client_request_to_bytes_signatureless :: proc(stream: io.Stream, request: Client_Request) -> io.Error { + request := request + + public_key_bytes: [ed25519.PUBLIC_KEY_SIZE]u8 + ed25519.public_key_bytes(&request.public_key, public_key_bytes[:]) + _ = io.write(stream, public_key_bytes[:]) or_return + + client_request_inner_to_bytes(stream, request.inner) or_return + + timestamp_bytes: [size_of(i64)]u8 + _ = endian.put_i64(timestamp_bytes[:], .Little, request.timestamp) + _ = io.write(stream, timestamp_bytes[:]) or_return + + return nil } -client_request_verify :: proc(request: Client_Request, expiration: Maybe(i64)) -> bool { - panic("TODO") +client_request_to_bytes :: proc(stream: io.Stream, request: Client_Request) -> io.Error { + request := request + + client_request_to_bytes_signatureless(stream, request) or_return + _ = io.write(stream, request.signature[:]) or_return + + return nil } -send_client_request :: proc(server: net.TCP_Socket, request: Client_Request) { - panic("TODO") +client_request_signature :: proc(request: Client_Request, private_key: ^ed25519.Private_Key) -> [ed25519.SIGNATURE_SIZE]u8 { + bytes_buf: [10_000]u8 + bytes_buffer: bytes.Buffer + bytes.buffer_init(&bytes_buffer, bytes_buf[:]) + request_stream := bytes.buffer_to_stream(&bytes_buffer) + + _ = client_request_to_bytes_signatureless(request_stream, request) + + request_bytes := bytes.buffer_to_bytes(&bytes_buffer) + + signature: [ed25519.SIGNATURE_SIZE]u8 + ed25519.sign(private_key, request_bytes, signature[:]) + + return signature +} + +client_request_verify :: proc(request: Client_Request, expiration: Maybe(i64)) -> Maybe(Request_Verify_Error) { + request := request + + now := time.time_to_unix_nano(time.now()) + + if expir, ok := expiration.?; ok { + if now - request.timestamp > expir { + return .Expired + } + } + + bytes_buf: [10_000]u8 + bytes_buffer: bytes.Buffer + bytes.buffer_init(&bytes_buffer, bytes_buf[:]) + request_stream := bytes.buffer_to_stream(&bytes_buffer) + + _ = client_request_to_bytes_signatureless(request_stream, request) + + request_bytes := bytes.buffer_to_bytes(&bytes_buffer) + + if !ed25519.verify(&request.public_key, request_bytes, request.signature[:]) { + return .Invalid_Signature + } + + return nil +} + +send_client_request :: proc(server: net.TCP_Socket, request: Client_Request, private_key: ^ed25519.Private_Key) -> net.Network_Error { + bytes_buf: [10_000]u8 + bytes_buffer: bytes.Buffer + bytes.buffer_init(&bytes_buffer, bytes_buf[:]) + request_stream := bytes.buffer_to_stream(&bytes_buffer) + + _ = client_request_to_bytes(request_stream, request) + _ = io.write_byte(request_stream, 0) + + request_bytes := bytes.buffer_to_bytes(&bytes_buffer) + + _ = net.send_tcp(server, request_bytes) or_return + + return nil +} + +request_server :: proc(server: net.TCP_Socket, inner: Client_Request_Inner, private_key: ^ed25519.Private_Key) -> net.Network_Error { + client_request: Client_Request + ed25519.public_key_set_priv(&client_request.public_key, private_key) + client_request.inner = inner + client_request.timestamp = time.time_to_unix_nano(time.now()) + client_request.signature = client_request_signature(client_request, private_key) + + send_client_request(server, client_request, private_key) or_return + + return nil } Server_Request :: union {} diff --git a/server/main.odin b/server/main.odin index 62a3ec4..0d7a937 100644 --- a/server/main.odin +++ b/server/main.odin @@ -2,7 +2,9 @@ package main import "base:runtime" +import "core:bytes" import "core:fmt" +import "core:io" import "core:log" import "core:mem" import "core:net" @@ -11,6 +13,11 @@ import "core:thread" import "../common" +Recv_Packet_Error :: union { + io.Error, + net.Network_Error, +} + Handle_Client_Data :: struct { clients_sockets: ^map[net.Endpoint]net.TCP_Socket, mutex: ^sync.Mutex, @@ -19,6 +26,34 @@ Handle_Client_Data :: struct { client_endpoint: net.Endpoint, } +receive_packet :: proc(socket: net.TCP_Socket, buffer: ^bytes.Buffer) -> (packet: []u8, err: Recv_Packet_Error) { + escape := false + + for { + byte_buf: [1]u8 + _ = net.recv_tcp(socket, byte_buf[:]) or_return + + b := byte_buf[0] + + if escape { + bytes.buffer_write_byte(buffer, b) or_return + escape = false + continue + } + + switch b { + case 0: + return bytes.buffer_to_bytes(buffer), nil + + case '\\': + escape = true + continue + } + + bytes.buffer_write_byte(buffer, b) or_return + } +} + handle_client :: proc(data: Handle_Client_Data) { using data @@ -33,44 +68,42 @@ handle_client :: proc(data: Handle_Client_Data) { delete_key(clients_sockets, client_endpoint) } - recv_buffer: [4_096]u8 - send_buffer: [4_096]u8 + recv_buf: [10_000]u8 + recv_buffer: bytes.Buffer + bytes.buffer_init(&recv_buffer, recv_buf[:]) + + send_buffer: [10_000]u8 response: []u8 for { - bytes_read, err := net.recv(client_socket, recv_buffer[:]) + packet, err1 := receive_packet(client_socket, &recv_buffer) - if err != nil { - log.errorf("Failed to receive message with error %v.", err) - return + if err1 != nil { + log.errorf("Failed to receive packet with error %v", err1) } - if bytes_read == 0 { - break - } - - bytes := recv_buffer[:bytes_read] - defer net.send(client_socket, response) - request, ok := common.client_request_parse(bytes) + request, err2 := common.client_request_from_bytes(packet) - if !ok { + if err2 != nil { response_string := "Invalid request" response = transmute([]u8)response_string continue } - if !common.client_request_verify(request, 1 * 60 * 60 * 1_000_000_000) { + if err3 := common.client_request_verify(request, 1 * 60 * 60 * 1_000_000_000); err3 != nil { response_string := "Invalid authentication" response = transmute([]u8)response_string continue } - #partial switch content in request.content { + log.infof("Received request: %v", request) + + #partial switch inner in request.inner { case: response_string := "Unhandled request type" response = transmute([]u8)response_string diff --git a/server/server b/server/server index 0043fa6..6d0bb28 100755 Binary files a/server/server and b/server/server differ