]> jxnshi.xyz Git - mesange.git/commitdiff
Working on requests
authorjxnshi <jxnshi@proton.me>
Mon, 27 Jan 2025 16:18:21 +0000 (17:18 +0100)
committerjxnshi <jxnshi@proton.me>
Mon, 27 Jan 2025 16:18:21 +0000 (17:18 +0100)
client-cli/client-cli
client-cli/command.odin
client-cli/main.odin
client-cli/profile.odin
client-cli/state.odin
common/request.odin
server/main.odin
server/server

index 07388ad0ecf78951996783ec5e1e644210420e74..6974ee45380698b0a969cea4412ad10648e91575 100755 (executable)
Binary files a/client-cli/client-cli and b/client-cli/client-cli differ
index 71ed5eebf7c632ededb623969194361b522dfe09..8c1e76ee974c4ce59179a3d737581c528387c28b 100644 (file)
@@ -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 {
index 38aaee0c25b5e1b9dc017a1a15b5aff32230699c..8846d4b253d1043ed727564de04024c86127f7ce 100644 (file)
@@ -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
 
index 11a169784915a8b0f522e060f8f3b5a30ddf1d41..63eb2f2977c4218440a6aface4d94fa48873710d 100644 (file)
@@ -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,
index e58a7d7ecae594d3dc7922b27f9f11ef94a0edd9..39cf6a6e9074acef23340fc6ac33c2fb2f004fd5 100644 (file)
@@ -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
 
index bd141bfd2ec6474436c3bb5f5ac7652ad2a65a5c..1066bfc4c744b7343c4f072ab2ee0bc9ee925670 100644 (file)
@@ -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 {}
index 62a3ec424557bea0e56ec84e3706009386e76990..0d7a937318b237f9a7ecf27a5752da4c452d87f1 100644 (file)
@@ -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
index 0043fa61fdf70c0663dfcf0f74aa658982e0d023..6d0bb28ef21184e9f38a3ab93263037b192ba842 100755 (executable)
Binary files a/server/server and b/server/server differ