hare

The Hare programming language
git clone https://git.torresjrjr.com/hare.git
Log | Files | Refs | README | LICENSE

commit 80dae68c0b0e2d1cb98f9f69e41d61e2f8630771
parent c1e639c21462f722c445066987da362b0abab7e1
Author: Mykyta Holubakha <hilobakho@gmail.com>
Date:   Sat,  3 Apr 2021 00:49:42 +0300

net: add high-level UNIX socket interface

Diffstat:
Mnet/+linux/socket.ha | 115++++++++++++++++++++++++++++++++++++++++++++++++++-----------------------------
Mnet/+linux/util.ha | 40+++++++++++++++++++++++++++++++++++-----
2 files changed, 108 insertions(+), 47 deletions(-)

diff --git a/net/+linux/socket.ha b/net/+linux/socket.ha @@ -1,51 +1,60 @@ use io; use net::ip; +use net::unix; use os; use rt; use strings; -// Opens a TCP connection to the given host and port. Blocks until the -// connection is established. -export fn connect( - addr: ip::addr, - port: u16, +fn connect_fd( + addr: rt::sockaddr, options: connect_option... -) (*io::stream | io::error) = { +) (int | io::error) = { const sockfd = mksockfd(addr)?; for (let i = 0z; i < len(options); i += 1) { // The only option is keepalive right now setsockopt(sockfd, rt::SO_KEEPALIVE, true); }; + wrap(rt::connect(sockfd, &addr, sockasz(addr)))?; + return sockfd; +}; +// Opens a TCP connection to the given host and port. Blocks until the +// connection is established. +export fn connect( + addr: ip::addr, + port: u16, + options: connect_option... +) (*io::stream | io::error) = { const sockaddr = ip::to_native(addr, port); - const asize = match (addr) { - v4: ip::addr4 => size(rt::sockaddr_in): u32, - v6: ip::addr6 => size(rt::sockaddr_in6): u32, - }; - wrap(rt::connect(sockfd, &sockaddr, asize))?; - - return os::fdopen(sockfd, ip::string(addr), + const sockfd = connect_fd(sockaddr, options...)?; + return os::fdopen(sockfd, string(addr), io::mode::READ | io::mode::WRITE); }; +// Opens a UNIX socket connection to the path. Blocks until the +// connection is established. export fn connect_unix( - path: str, + addr: unix::addr, options: connect_option... ) (*io::stream | io::error) = { - abort(); // TODO + let sockaddr = match (unix::to_native(addr)) { + a: rt::sockaddr => a, + unix::invalid => return io::unsupported, // path too long + }; + const sockfd = connect_fd(sockaddr, options...)?; + return os::fdopen(sockfd, string(addr), + io::mode::READ | io::mode::WRITE); }; -type tcp_listener = struct { +type stream_listener = struct { l: listener, fd: int, }; -// Binds a TCP listener to the given address. -export fn listen( - addr: ip::addr, - port: u16, +fn listen_fd( + addr: rt::sockaddr, options: listen_option... -) (*listener | io::error) = { +) (int | io::error) = { const sockfd = mksockfd(addr)?; let bk: u32 = 10; @@ -62,55 +71,77 @@ export fn listen( }; setfcntl(sockfd, rt::O_CLOEXEC); - let sockaddr = ip::to_native(addr, port); - const asize = match (addr) { - v4: ip::addr4 => size(rt::sockaddr_in): u32, - v6: ip::addr6 => size(rt::sockaddr_in6): u32, - }; - wrap(rt::bind(sockfd, &sockaddr, asize))?; + wrap(rt::bind(sockfd, &addr, sockasz(addr)))?; wrap(rt::listen(sockfd, bk))?; match (portout) { p: *u16 => { + if (addr.in.sin_family != rt::AF_INET && + addr.in.sin_family != rt::AF_INET6) { + return io::unsupported; + }; let sn = rt::sockaddr {...}; - let al = asize; + let al = sockasz(addr); wrap(rt::getsockname(sockfd, &sn, &al))?; const addr = ip::from_native(sn); *p = addr.1; }, null => void, }; - return alloc(tcp_listener { + + return sockfd; +}; + +// Binds a TCP listener to the given address. +export fn listen( + addr: ip::addr, + port: u16, + options: listen_option... +) (*listener | io::error) = { + let sockaddr = ip::to_native(addr, port); + let sockfd = listen_fd(sockaddr, options...)?; + return alloc(stream_listener { l = listener { - accept = &tcp_accept, - shutdown = &tcp_shutdown, + accept = &stream_accept, + shutdown = &stream_shutdown, }, fd = sockfd, }): *listener; }; -fn tcp_accept(l: *listener) (*io::stream | io::error) = { - assert(l.accept == &tcp_accept); - let l = l: *tcp_listener; +fn stream_accept(l: *listener) (*io::stream | io::error) = { + assert(l.accept == &stream_accept); + let l = l: *stream_listener; let sn = rt::sockaddr {...}; const sz = size(rt::sockaddr): u32; const fd = wrap(rt::accept(l.fd, &sn, &sz))?; - const addr = ip::from_native(sn).0; - return os::fdopen(fd, ip::string(addr), + const addr = from_native(sn); + return os::fdopen(fd, string(addr), io::mode::READ | io::mode::WRITE); }; -fn tcp_shutdown(l: *listener) void = { - assert(l.shutdown == &tcp_shutdown); - let l = l: *tcp_listener; +fn stream_shutdown(l: *listener) void = { + assert(l.shutdown == &stream_shutdown); + let l = l: *stream_listener; rt::close(l.fd); free(l); }; -// Binds a Unix socket at the given path. +// Binds a UNIX socket listener at the given path. export fn listen_unix( - path: str, + addr: unix::addr, options: listen_option... ) (*listener | io::error) = { - abort(); // TODO + let sockaddr = match (unix::to_native(addr)) { + a: rt::sockaddr => a, + unix::invalid => return io::unsupported, // path too long + }; + let sockfd = listen_fd(sockaddr, options...)?; + return alloc(stream_listener { + l = listener { + accept = &stream_accept, + shutdown = &stream_shutdown, + }, + fd = sockfd, + }): *listener; }; diff --git a/net/+linux/util.ha b/net/+linux/util.ha @@ -1,5 +1,7 @@ use io; +use fmt; use net::ip; +use net::unix; use os; use rt; @@ -35,12 +37,33 @@ fn wrap(ie: (int | rt::errno)) (int | io::error) = { }; }; -fn mksockfd(addr: ip::addr) (int | io::error) = { - const af = match (addr) { - ip::addr4 => rt::AF_INET: int, - ip::addr6 => rt::AF_INET6: int, +fn mksockfd(addr: rt::sockaddr) (int | io::error) = { + return wrap(rt::socket(addr.in.sin_family: int, rt::SOCK_STREAM, 0))?; +}; + +fn sockasz(addr: rt::sockaddr) u32 = { + return switch (addr.in.sin_family) { + rt::AF_INET => size(rt::sockaddr_in): u32, + rt::AF_INET6 => size(rt::sockaddr_in6): u32, + rt::AF_UNIX => size(rt::sockaddr_un): u32, + * => size(rt::sockaddr): u32, + }; +}; + +fn string(addr: (ip::addr | unix::addr)) str = { + return match (addr) { + ip: ip::addr => ip::string(ip), + un: unix::addr => un, + }; +}; + +fn from_native(addr: rt::sockaddr) (ip::addr | unix::addr) = { + return switch (addr.in.sin_family) { + rt::AF_INET => ip::from_native(addr).0, + rt::AF_INET6 => ip::from_native(addr).0, + rt::AF_UNIX => unix::from_native(addr), + * => abort("Wrong address family"), }; - return wrap(rt::socket(af, rt::SOCK_STREAM, 0))?; }; // Returns the remote address for a given connection, or void if none is @@ -57,3 +80,10 @@ export fn peeraddr(stream: *io::stream) ((ip::addr, u16) | void) = { }; return ip::from_native(sn); }; + +// Gets the fd of the listener's socket. +export fn listenerfd(l: *listener) (int | void) = { + if (l.accept == &stream_accept) { + return (l: *stream_listener).fd; + }; +};