diff --git a/capc/src/codegen/intrinsics.rs b/capc/src/codegen/intrinsics.rs index ef80c44..f33b5e3 100644 --- a/capc/src/codegen/intrinsics.rs +++ b/capc/src/codegen/intrinsics.rs @@ -172,6 +172,19 @@ pub fn register_runtime_intrinsics(ptr_ty: Type) -> HashMap { ret: AbiType::Handle, }; // Net. + let net_listen = FnSig { + params: vec![AbiType::Handle, AbiType::String, AbiType::I32], + ret: AbiType::Result(Box::new(AbiType::Handle), Box::new(AbiType::I32)), + }; + let net_listen_abi = FnSig { + params: vec![ + AbiType::Handle, + AbiType::String, + AbiType::I32, + AbiType::ResultOut(Box::new(AbiType::Handle), Box::new(AbiType::I32)), + ], + ret: AbiType::ResultOut(Box::new(AbiType::Handle), Box::new(AbiType::I32)), + }; let net_connect = FnSig { params: vec![AbiType::Handle, AbiType::String, AbiType::I32], ret: AbiType::Result(Box::new(AbiType::Handle), Box::new(AbiType::I32)), @@ -205,10 +218,25 @@ pub fn register_runtime_intrinsics(ptr_ty: Type) -> HashMap { ], ret: AbiType::ResultOut(Box::new(AbiType::Unit), Box::new(AbiType::I32)), }; + let net_accept = FnSig { + params: vec![AbiType::Handle], + ret: AbiType::Result(Box::new(AbiType::Handle), Box::new(AbiType::I32)), + }; + let net_accept_abi = FnSig { + params: vec![ + AbiType::Handle, + AbiType::ResultOut(Box::new(AbiType::Handle), Box::new(AbiType::I32)), + ], + ret: AbiType::ResultOut(Box::new(AbiType::Handle), Box::new(AbiType::I32)), + }; let net_close = FnSig { params: vec![AbiType::Handle], ret: AbiType::Unit, }; + let net_listener_close = FnSig { + params: vec![AbiType::Handle], + ret: AbiType::Unit, + }; let args_at = FnSig { params: vec![AbiType::Handle, AbiType::I32], ret: AbiType::Result(Box::new(AbiType::String), Box::new(AbiType::I32)), @@ -649,6 +677,16 @@ pub fn register_runtime_intrinsics(ptr_ty: Type) -> HashMap { }, ); // === Net === + map.insert( + "sys.net.Net__listen".to_string(), + FnInfo { + sig: net_listen, + abi_sig: Some(net_listen_abi), + symbol: "capable_rt_net_listen".to_string(), + runtime_symbol: None, + is_runtime: true, + }, + ); map.insert( "sys.net.Net__connect".to_string(), FnInfo { @@ -659,6 +697,26 @@ pub fn register_runtime_intrinsics(ptr_ty: Type) -> HashMap { is_runtime: true, }, ); + map.insert( + "sys.net.TcpListener__accept".to_string(), + FnInfo { + sig: net_accept, + abi_sig: Some(net_accept_abi), + symbol: "capable_rt_net_accept".to_string(), + runtime_symbol: None, + is_runtime: true, + }, + ); + map.insert( + "sys.net.TcpListener__close".to_string(), + FnInfo { + sig: net_listener_close, + abi_sig: None, + symbol: "capable_rt_net_listener_close".to_string(), + runtime_symbol: None, + is_runtime: true, + }, + ); map.insert( "sys.net.TcpConn__read_to_string".to_string(), FnInfo { diff --git a/examples/http_server/http_server.cap b/examples/http_server/http_server.cap new file mode 100644 index 0000000..7b876f3 --- /dev/null +++ b/examples/http_server/http_server.cap @@ -0,0 +1,182 @@ +// WIP! This is a sample serving to improve the language. + +package safe +module http_server +use sys::console +use sys::fs +use sys::net +use sys::args +use sys::system + +fn arg_or_default(args: Args, index: i32, default: string) -> string { + if (args.len() > index) { + let res = args.at(index) + match (res) { + Ok(value) => { + return value + } + Err(_) => { + } + } + } + return default +} + +fn strip_query(raw_path: string) -> string { + let parts = raw_path.split(63u8) + let res = parts.get(0) + return match (res) { + Ok(path) => { + path + } + Err(_) => { + "" + } + } +} + +fn sanitize_segment(parts: VecString, i: i32, acc: string, seg: string) -> Result[string, unit] { + if (seg.len() == 0) { + return sanitize_parts(parts, i + 1, acc) + } + if (seg.eq(".")) { + return sanitize_parts(parts, i + 1, acc) + } + if (seg.eq("..")) { + return Err(()) + } + if (acc.len() == 0) { + return sanitize_parts(parts, i + 1, seg) + } + let next = fs::join(acc, seg) + return sanitize_parts(parts, i + 1, next) +} + +fn sanitize_parts(parts: VecString, i: i32, acc: string) -> Result[string, unit] { + if (i >= parts.len()) { + return Ok(acc) + } + let seg_res = parts.get(i) + return match (seg_res) { + Ok(seg) => { + sanitize_segment(parts, i, acc, seg) + } + Err(_) => { + Err(()) + } + } +} + +fn sanitize_path(raw_path: string) -> Result[string, unit] { + let parts = raw_path.split(47u8) + let res = sanitize_parts(parts, 0, "") + match (res) { + Ok(path) => { + if (path.len() == 0) { + return Ok("index.html") + } + return Ok(path) + } + Err(_) => { + return Err(()) + } + } +} + +fn parse_request_line(line: string) -> Result[string, unit] { + let trimmed = line.trim() + let parts = trimmed.split(32u8) + let method_res = parts.get(0) + match (method_res) { + Ok(method) => { + if (!method.eq("GET")) { + return Err(()) + } + } + Err(_) => { + return Err(()) + } + } + let path_res = parts.get(1) + match (path_res) { + Ok(raw_path) => { + let cleaned = strip_query(raw_path) + return sanitize_path(cleaned) + } + Err(_) => { + return Err(()) + } + } +} + +fn parse_request_path(req: string) -> Result[string, unit] { + let lines = req.lines() + let line_res = lines.get(0) + return match (line_res) { + Ok(line) => { + parse_request_line(line) + } + Err(_) => { + Err(()) + } + } +} + +fn respond_ok(conn: &TcpConn, body: string) -> Result[unit, NetErr] { + conn.write("HTTP/1.0 200 OK\r\nContent-Type: text/plain\r\n\r\n")? + conn.write(body)? + return Ok(()) +} + +fn respond_not_found(conn: &TcpConn) -> Result[unit, NetErr] { + conn.write("HTTP/1.0 404 Not Found\r\nContent-Type: text/plain\r\n\r\nnot found\n")? + return Ok(()) +} + +fn respond_bad_request(conn: &TcpConn) -> Result[unit, NetErr] { + conn.write("HTTP/1.0 400 Bad Request\r\nContent-Type: text/plain\r\n\r\nbad request\n")? + return Ok(()) +} + +fn serve_once(c: Console, net: Net, readfs: ReadFS) -> Result[unit, NetErr] { + let listener = net.listen("127.0.0.1", 8080)? + let conn = listener.accept()? + let req = conn.read_to_string()? + let path_res = parse_request_path(req) + match (path_res) { + Ok(path) => { + let file_res = readfs.read_to_string(path) + match (file_res) { + Ok(body) => { + respond_ok(conn, body)? + } + Err(_) => { + respond_not_found(conn)? + } + } + } + Err(_) => { + respond_bad_request(conn)? + } + } + conn.close() + return Ok(()) +} + +pub fn main(rc: RootCap) -> i32 { + let c = rc.mint_console() + let net = rc.mint_net() + let args = rc.mint_args() + let root = arg_or_default(args, 1, ".") + let readfs = rc.mint_readfs(root) + c.println("listening on 127.0.0.1:8080") + let res = serve_once(c, net, readfs) + match (res) { + Ok(_) => { + } + Err(_) => { + c.println("server error") + } + } + return 0 +} diff --git a/justfile b/justfile index b66ac47..04f7ed4 100644 --- a/justfile +++ b/justfile @@ -48,5 +48,8 @@ extern-demo-build: extern-demo-run: cargo run -p capc -- run --link-search examples/extern_demo --link-lib extern_demo examples/extern_demo/extern_demo.cap +http-server: + cargo run -p capc -- run examples/http_server/http_server.cap + lsp: cargo run -p caplsp diff --git a/runtime/src/lib.rs b/runtime/src/lib.rs index 319d5f2..d66a5b6 100644 --- a/runtime/src/lib.rs +++ b/runtime/src/lib.rs @@ -1,7 +1,7 @@ use std::collections::HashMap; use std::ffi::OsStr; use std::io::{self, Read, Write}; -use std::net::TcpStream; +use std::net::{TcpListener, TcpStream}; use std::path::{Component, Path, PathBuf}; use std::sync::{LazyLock, Mutex}; @@ -28,6 +28,8 @@ static STDIN_CAPS: LazyLock>> = LazyLock::new(|| Mutex::new(HashMap::new())); static NET_CAPS: LazyLock>> = LazyLock::new(|| Mutex::new(HashMap::new())); +static TCP_LISTENERS: LazyLock>> = + LazyLock::new(|| Mutex::new(HashMap::new())); static TCP_CONNS: LazyLock>> = LazyLock::new(|| Mutex::new(HashMap::new())); static SLICES: LazyLock>> = @@ -772,6 +774,55 @@ pub extern "C" fn capable_rt_net_connect( } } +#[no_mangle] +pub extern "C" fn capable_rt_net_listen( + net: Handle, + host_ptr: *const u8, + host_len: usize, + port: i32, + out_ok: *mut Handle, + out_err: *mut i32, +) -> u8 { + if !has_handle(&NET_CAPS, net, "net table") { + return write_handle_result_code(out_ok, out_err, Err(NetErr::IoError as i32)); + } + let host = unsafe { read_str(host_ptr, host_len) }; + let Some(host) = host else { + return write_handle_result_code(out_ok, out_err, Err(NetErr::InvalidAddress as i32)); + }; + if host.is_empty() || port <= 0 || port > u16::MAX as i32 { + return write_handle_result_code(out_ok, out_err, Err(NetErr::InvalidAddress as i32)); + } + match TcpListener::bind((host.as_str(), port as u16)) { + Ok(listener) => { + let handle = new_handle(); + insert_handle(&TCP_LISTENERS, handle, listener, "tcp listener table"); + write_handle_result_code(out_ok, out_err, Ok(handle)) + } + Err(err) => write_handle_result_code(out_ok, out_err, Err(map_net_err(err) as i32)), + } +} + +#[no_mangle] +pub extern "C" fn capable_rt_net_accept( + listener: Handle, + out_ok: *mut Handle, + out_err: *mut i32, +) -> u8 { + let listener = take_handle(&TCP_LISTENERS, listener, "tcp listener table"); + let Some(listener) = listener else { + return write_handle_result_code(out_ok, out_err, Err(NetErr::IoError as i32)); + }; + match listener.accept() { + Ok((stream, _addr)) => { + let handle = new_handle(); + insert_handle(&TCP_CONNS, handle, stream, "tcp conn table"); + write_handle_result_code(out_ok, out_err, Ok(handle)) + } + Err(err) => write_handle_result_code(out_ok, out_err, Err(map_net_err(err) as i32)), + } +} + #[no_mangle] pub extern "C" fn capable_rt_net_read_to_string( conn: Handle, @@ -844,6 +895,11 @@ pub extern "C" fn capable_rt_net_close(conn: Handle) { take_handle(&TCP_CONNS, conn, "tcp conn table"); } +#[no_mangle] +pub extern "C" fn capable_rt_net_listener_close(listener: Handle) { + take_handle(&TCP_LISTENERS, listener, "tcp listener table"); +} + #[no_mangle] pub extern "C" fn capable_rt_start() -> i32 { let sys = new_handle(); diff --git a/stdlib/sys/net.cap b/stdlib/sys/net.cap index a34f95f..0403b9a 100644 --- a/stdlib/sys/net.cap +++ b/stdlib/sys/net.cap @@ -2,6 +2,7 @@ package unsafe module sys::net pub copy capability struct Net +pub linear capability struct TcpListener pub linear capability struct TcpConn pub enum NetErr { @@ -11,11 +12,25 @@ pub enum NetErr { } impl Net { + pub fn listen(self, host: string, port: i32) -> Result[TcpListener, NetErr] { + return Err(NetErr::IoError) + } + pub fn connect(self, host: string, port: i32) -> Result[TcpConn, NetErr] { return Err(NetErr::IoError) } } +impl TcpListener { + pub fn accept(self) -> Result[TcpConn, NetErr] { + return Err(NetErr::IoError) + } + + pub fn close(self) -> unit { + return () + } +} + impl TcpConn { pub fn read_to_string(self: &TcpConn) -> Result[string, NetErr] { return Err(NetErr::IoError) diff --git a/stdlib/sys/string.cap b/stdlib/sys/string.cap index 3b3c99e..e5f9c3e 100644 --- a/stdlib/sys/string.cap +++ b/stdlib/sys/string.cap @@ -108,4 +108,20 @@ impl string { } return self.byte_at(len - 1) == suffix } + + pub fn eq(self, other: string) -> bool { + let self_len = self.len() + let other_len = other.len() + if (self_len != other_len) { + return false + } + let i = 0 + while (i < self_len) { + if self.byte_at(i) != other.byte_at(i) { + return false + } + i = i + 1 + } + return true + } }