Skip to main content

ort_openrouter_cli/net/
socket.rs

1//! ort: Open Router CLI
2//! https://github.com/grahamking/ort
3//!
4//! MIT License
5//! Copyright (c) 2025 Graham King
6
7use core::ffi::{c_int, c_void};
8use core::mem::size_of;
9use core::net::{Ipv4Addr, SocketAddrV4};
10
11use crate::{ErrorKind, OrtResult, Read, Write, ort_error, syscall, utils};
12
13pub struct TcpSocket {
14    fd: i32,
15}
16
17impl TcpSocket {
18    pub fn new() -> OrtResult<Self> {
19        let fd = syscall::socket(
20            syscall::AF_INET,
21            syscall::SOCK_STREAM | syscall::SOCK_CLOEXEC,
22            0,
23        );
24        if fd == -1 {
25            return Err(ort_error(ErrorKind::SocketCreateFailed, ""));
26        }
27        set_tcp_fastopen(fd);
28        Ok(TcpSocket { fd })
29    }
30
31    pub fn connect(&self, addr: &SocketAddrV4) -> OrtResult<()> {
32        let c_addr = socket_addr_v4_to_c(addr);
33        let len = size_of::<syscall::sockaddr_in>() as syscall::socklen_t;
34        let res = syscall::connect(
35            self.fd,
36            &c_addr as *const _ as *const syscall::sockaddr,
37            len,
38        );
39        if res == -1 {
40            return Err(ort_error(ErrorKind::SocketConnectFailed, ""));
41        }
42        Ok(())
43    }
44}
45
46impl super::AsFd for TcpSocket {
47    fn as_fd(&self) -> i32 {
48        self.fd
49    }
50}
51
52impl Read for TcpSocket {
53    fn read(&mut self, buf: &mut [u8]) -> OrtResult<usize> {
54        let bytes_read = syscall::read(self.fd, buf.as_mut_ptr() as *mut c_void, buf.len());
55        if bytes_read < 0 {
56            if bytes_read == syscall::EAGAIN {
57                return Err(ort_error(ErrorKind::WouldBlock, ""));
58            }
59            // see /usr/include/asm-generic/errno.h to translate the codes
60            let err_code = utils::num_to_string(-bytes_read);
61            utils::print_string(c"socket read err: ", &err_code);
62            Err(ort_error(ErrorKind::SocketReadFailed, "syscall read error"))
63        } else {
64            Ok(bytes_read as usize)
65        }
66    }
67}
68
69impl Write for TcpSocket {
70    fn write(&mut self, buf: &[u8]) -> OrtResult<usize> {
71        let bytes_written = syscall::write(self.fd, buf.as_ptr() as *const c_void, buf.len());
72        if bytes_written < 0 {
73            // see /usr/include/asm-generic/errno.h to translate the codes
74            let err_code = utils::num_to_string(-bytes_written);
75            utils::print_string(c"socket write err: ", &err_code);
76            Err(ort_error(
77                ErrorKind::SocketWriteFailed,
78                "syscall write error",
79            ))
80        } else {
81            Ok(bytes_written as usize)
82        }
83    }
84
85    fn flush(&mut self) -> OrtResult<()> {
86        Ok(())
87    }
88}
89
90fn set_tcp_fastopen(fd: i32) {
91    let optval: c_int = 1; // Enable
92    syscall::setsockopt(
93        fd,
94        syscall::IPPROTO_TCP,
95        syscall::TCP_FASTOPEN_CONNECT,
96        &optval as *const _ as *const core::ffi::c_void,
97        size_of::<i32>() as u32,
98    );
99}
100
101fn socket_addr_v4_to_c(addr: &SocketAddrV4) -> syscall::sockaddr_in {
102    syscall::sockaddr_in {
103        sin_family: syscall::AF_INET as syscall::sa_family_t,
104        sin_port: addr.port().to_be(),
105        sin_addr: ip_v4_addr_to_c(addr.ip()),
106        ..unsafe { core::mem::zeroed() }
107    }
108}
109fn ip_v4_addr_to_c(addr: &Ipv4Addr) -> syscall::in_addr {
110    // `s_addr` is stored as BE on all machines and the array is in BE order.
111    // So the native endian conversion method is used so that it's never swapped.
112    syscall::in_addr {
113        s_addr: u32::from_ne_bytes(addr.octets()),
114    }
115}