1
0
mirror of https://github.com/vlang/v.git synced 2023-08-10 21:13:21 +03:00

net: ipv6 support, merge unix+ip;[pack:x] attribute (#9904)

This commit is contained in:
Emily Hudson 2021-06-13 21:53:38 +01:00 committed by GitHub
parent fa9fa77a5f
commit 535dcac8fa
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
52 changed files with 1277 additions and 524 deletions

View File

@ -44,7 +44,7 @@ const (
] ]
vfmt_known_failing_exceptions = arrays.merge(verify_known_failing_exceptions, [ vfmt_known_failing_exceptions = arrays.merge(verify_known_failing_exceptions, [
'vlib/strconv/' /* prevent conflicts, till the new pure V string interpolation is merged */, 'vlib/strconv/' /* prevent conflicts, till the new pure V string interpolation is merged */,
'vlib/net/' /* prevent conflicts, till ipv6 support is merged */, 'vlib/net/http/' /* prevent conflicts, till ipv6 support is merged */,
'vlib/term/ui/input.v' /* comment after a struct embed is removed */, 'vlib/term/ui/input.v' /* comment after a struct embed is removed */,
'vlib/regex/regex_test.v' /* contains meaningfull formatting of the test case data */, 'vlib/regex/regex_test.v' /* contains meaningfull formatting of the test case data */,
'vlib/readline/readline_test.v' /* vfmt eats `{ Readline }` from `import readline { Readline }` */, 'vlib/readline/readline_test.v' /* vfmt eats `{ Readline }` from `import readline { Readline }` */,

View File

@ -7,6 +7,10 @@ fn main() {
defer { defer {
conn.close() or {} conn.close() or {}
} }
println(' peer: $conn.peer_addr()')
println('local: $conn.addr()')
// Simple http HEAD request for a file // Simple http HEAD request for a file
conn.write_string('HEAD /index.html HTTP/1.0\r\n\r\n') ? conn.write_string('HEAD /index.html HTTP/1.0\r\n\r\n') ?
// Read all the data that is waiting // Read all the data that is waiting

24
examples/net_resolve.v Normal file
View File

@ -0,0 +1,24 @@
import net
for addr in [
'vlang.io:80',
'google.com:80',
'steampowered.com:80',
'api.steampowered.com:80',
] {
println('$addr')
for @type in [net.SocketType.tcp, .udp] {
family := net.AddrFamily.unspec
addrs := net.resolve_addrs(addr, family, @type) or {
println('> None')
continue
}
for a in addrs {
f := a.family()
println('> $a $f ${@type}')
}
}
}

View File

@ -12,7 +12,7 @@ fn main() {
mut buf := []byte{len: 100} mut buf := []byte{len: 100}
if is_server { if is_server {
println('UDP echo server, listening for udp packets on port: $port') println('UDP echo server, listening for udp packets on port: $port')
mut c := net.listen_udp(port) ? mut c := net.listen_udp(':$port') ?
for { for {
read, addr := c.read(mut buf) or { continue } read, addr := c.read(mut buf) or { continue }
println('received $read bytes from $addr') println('received $read bytes from $addr')
@ -23,7 +23,7 @@ fn main() {
} }
} else { } else {
println('UDP client, sending packets to port: ${port}.\nType `exit` to exit.') println('UDP client, sending packets to port: ${port}.\nType `exit` to exit.')
mut c := net.dial_udp('localhost', 'localhost:$port') ? mut c := net.dial_udp('localhost:$port') ?
for { for {
mut line := os.input('client > ') mut line := os.input('client > ')
match line { match line {

View File

@ -0,0 +1,40 @@
import io
import net
// This file shows how a basic TCP echo server can be implemented using
// the net module. You can connect to the server by using netcat or telnet,
// in separate shells, for example:
// nc 127.0.0.1 12345
// or
// telnet 127.0.0.1 12345
fn main() {
mut server := net.listen_tcp(.ip6, ':12345') ?
laddr := server.addr() ?
eprintln('Listen on $laddr ...')
for {
mut socket := server.accept() ?
go handle_client(mut socket)
}
}
fn handle_client(mut socket net.TcpConn) {
defer {
socket.close() or { panic(err) }
}
client_addr := socket.peer_addr() or { return }
eprintln('> new client: $client_addr')
mut reader := io.new_buffered_reader(reader: socket)
defer {
reader.free()
}
socket.write_string('server: hello\n') or { return }
for {
received_line := reader.read_line() or { return }
if received_line == '' {
return
}
println('client $client_addr: $received_line')
socket.write_string('server: $received_line\n') or { return }
}
}

View File

@ -10,7 +10,7 @@ fn main() {
} }
fn start_server() ? { fn start_server() ? {
mut s := websocket.new_server(30000, '') mut s := websocket.new_server(.ip6, 30000, '')
// Make that in execution test time give time to execute at least one time // Make that in execution test time give time to execute at least one time
s.ping_interval = 100 s.ping_interval = 100
s.on_connect(fn (mut s websocket.ServerClient) ?bool { s.on_connect(fn (mut s websocket.ServerClient) ?bool {

View File

@ -13,7 +13,7 @@ fn main() {
} }
fn start_server() ? { fn start_server() ? {
mut s := websocket.new_server(30000, '') mut s := websocket.new_server(.ip6, 30000, '')
// Make that in execution test time give time to execute at least one time // Make that in execution test time give time to execute at least one time
s.ping_interval = 100 s.ping_interval = 100
s.on_connect(fn (mut s websocket.ServerClient) ?bool { s.on_connect(fn (mut s websocket.ServerClient) ?bool {

View File

@ -1,5 +1,11 @@
module net module net
$if windows {
// This is mainly here for tcc on windows
// which apparently doesnt have this definition
#include "@VROOT/vlib/net/ipv6_v6only.h"
}
// Select represents a select operation // Select represents a select operation
enum Select { enum Select {
read read
@ -11,55 +17,18 @@ enum Select {
pub enum SocketType { pub enum SocketType {
udp = C.SOCK_DGRAM udp = C.SOCK_DGRAM
tcp = C.SOCK_STREAM tcp = C.SOCK_STREAM
dgram = C.SOCK_DGRAM
stream = C.SOCK_STREAM
seqpacket = C.SOCK_SEQPACKET seqpacket = C.SOCK_SEQPACKET
} }
// SocketFamily are the available address families // AddrFamily are the available address families
pub enum SocketFamily { pub enum AddrFamily {
unix = C.AF_UNIX unix = C.AF_UNIX
inet = C.AF_INET ip = C.AF_INET
ip6 = C.AF_INET6
unspec = C.AF_UNSPEC
} }
struct C.in_addr { fn C.socket(domain AddrFamily, typ SocketType, protocol int) int
mut:
s_addr int
}
struct C.sockaddr {
sa_family u16
}
struct C.sockaddr_in {
mut:
sin_family int
sin_port int
sin_addr C.in_addr
}
struct C.sockaddr_un {
mut:
sun_family int
sun_path charptr
}
struct C.addrinfo {
mut:
ai_family int
ai_socktype int
ai_flags int
ai_protocol int
ai_addrlen int
ai_addr voidptr
ai_canonname voidptr
ai_next voidptr
}
struct C.sockaddr_storage {
}
fn C.socket(domain SocketFamily, typ SocketType, protocol int) int
// fn C.setsockopt(sockfd int, level int, optname int, optval voidptr, optlen C.socklen_t) int // fn C.setsockopt(sockfd int, level int, optname int, optval voidptr, optlen C.socklen_t) int
fn C.setsockopt(sockfd int, level int, optname int, optval voidptr, optlen u32) int fn C.setsockopt(sockfd int, level int, optname int, optval voidptr, optlen u32) int
@ -71,44 +40,48 @@ fn C.htons(netshort u16) int
// fn C.bind(sockfd int, addr &C.sockaddr, addrlen C.socklen_t) int // fn C.bind(sockfd int, addr &C.sockaddr, addrlen C.socklen_t) int
// use voidptr for arg 2 becasue sockaddr is a generic descriptor for any kind of socket operation, // use voidptr for arg 2 becasue sockaddr is a generic descriptor for any kind of socket operation,
// it can also take sockaddr_in depending on the type of socket used in arg 1 // it can also take sockaddr_in depending on the type of socket used in arg 1
fn C.bind(sockfd int, addr voidptr, addrlen u32) int fn C.bind(sockfd int, addr &Addr, addrlen u32) int
fn C.listen(sockfd int, backlog int) int fn C.listen(sockfd int, backlog int) int
// fn C.accept(sockfd int, addr &C.sockaddr, addrlen &C.socklen_t) int // fn C.accept(sockfd int, addr &C.sockaddr, addrlen &C.socklen_t) int
fn C.accept(sockfd int, addr &C.sockaddr, addrlen &u32) int fn C.accept(sockfd int, addr &Addr, addrlen &u32) int
fn C.getaddrinfo(node charptr, service charptr, hints &C.addrinfo, res &&C.addrinfo) int fn C.getaddrinfo(node &char, service &char, hints &C.addrinfo, res &&C.addrinfo) int
fn C.freeaddrinfo(info &C.addrinfo)
// fn C.connect(sockfd int, addr &C.sockaddr, addrlen C.socklen_t) int // fn C.connect(sockfd int, addr &C.sockaddr, addrlen C.socklen_t) int
fn C.connect(sockfd int, addr &C.sockaddr, addrlen u32) int fn C.connect(sockfd int, addr &Addr, addrlen u32) int
// fn C.send(sockfd int, buf voidptr, len size_t, flags int) size_t // fn C.send(sockfd int, buf voidptr, len size_t, flags int) size_t
fn C.send(sockfd int, buf voidptr, len size_t, flags int) int fn C.send(sockfd int, buf voidptr, len size_t, flags int) int
// fn C.sendto(sockfd int, buf voidptr, len size_t, flags int, dest_add &C.sockaddr, addrlen C.socklen_t) size_t // fn C.sendto(sockfd int, buf voidptr, len size_t, flags int, dest_add &C.sockaddr, addrlen C.socklen_t) size_t
fn C.sendto(sockfd int, buf voidptr, len size_t, flags int, dest_add &C.sockaddr, addrlen u32) int fn C.sendto(sockfd int, buf voidptr, len size_t, flags int, dest_add &Addr, addrlen u32) int
// fn C.recv(sockfd int, buf voidptr, len size_t, flags int) size_t // fn C.recv(sockfd int, buf voidptr, len size_t, flags int) size_t
fn C.recv(sockfd int, buf voidptr, len size_t, flags int) int fn C.recv(sockfd int, buf voidptr, len size_t, flags int) int
// fn C.recvfrom(sockfd int, buf voidptr, len size_t, flags int, src_addr &C.sockaddr, addrlen &C.socklen_t) size_t // fn C.recvfrom(sockfd int, buf voidptr, len size_t, flags int, src_addr &C.sockaddr, addrlen &C.socklen_t) size_t
fn C.recvfrom(sockfd int, buf voidptr, len size_t, flags int, src_addr &C.sockaddr, addrlen &u32) int fn C.recvfrom(sockfd int, buf voidptr, len size_t, flags int, src_addr &Addr, addrlen &u32) int
fn C.shutdown(socket int, how int) int fn C.shutdown(socket int, how int) int
fn C.ntohs(netshort u16) int fn C.ntohs(netshort u16) int
// fn C.getpeername(sockfd int, addr &C.sockaddr, addlen &C.socklen_t) int // fn C.getpeername(sockfd int, addr &C.sockaddr, addlen &C.socklen_t) int
fn C.getpeername(sockfd int, addr &C.sockaddr, addlen &u32) int fn C.getpeername(sockfd int, addr &Addr, addlen &u32) int
fn C.inet_ntop(af SocketFamily, src voidptr, dst charptr, dst_size int) charptr fn C.inet_ntop(af AddrFamily, src voidptr, dst &char, dst_size int) &char
fn C.WSAAddressToStringA(lpsaAddress &C.sockaddr, dwAddressLength u32, lpProtocolInfo voidptr, lpszAddressString charptr, lpdwAddressStringLength &u32) int fn C.WSAAddressToStringA(lpsaAddress &Addr, dwAddressLength u32, lpProtocolInfo voidptr, lpszAddressString &char, lpdwAddressStringLength &u32) int
// fn C.getsockname(sockfd int, addr &C.sockaddr, addrlen &C.socklen_t) int // fn C.getsockname(sockfd int, addr &C.sockaddr, addrlen &C.socklen_t) int
fn C.getsockname(sockfd int, addr &C.sockaddr, addrlen &u32) int fn C.getsockname(sockfd int, addr &C.sockaddr, addrlen &u32) int
fn C.getsockopt(sockfd int, level int, optname int, optval voidptr, optlen &u32) int
// defined in builtin // defined in builtin
// fn C.read() int // fn C.read() int
// fn C.close() int // fn C.close() int
@ -125,5 +98,7 @@ fn C.FD_SET(fd int, fdset &C.fd_set)
fn C.FD_ISSET(fd int, fdset &C.fd_set) bool fn C.FD_ISSET(fd int, fdset &C.fd_set) bool
fn C.inet_pton(family AddrFamily, saddr &char, addr voidptr) int
[typedef] [typedef]
pub struct C.fd_set {} pub struct C.fd_set {}

View File

@ -1,83 +1,259 @@
module net module net
// Addr represents an ip address import io.util
pub struct Addr { import os
addr C.sockaddr
len int
pub:
saddr string
port int
}
struct C.addrinfo { union AddrData {
} Unix
Ip
pub fn (a Addr) str() string { Ip6
return '$a.saddr:$a.port'
} }
const ( const (
max_ipv4_addr_len = 24 addr_ip6_any = [16]byte{init: byte(0)}
ipv4_addr_size = sizeof(C.sockaddr_in) addr_ip_any = [4]byte{init: byte(0)}
) )
fn new_addr(addr C.sockaddr) ?Addr { fn new_ip6(port u16, addr [16]byte) Addr {
addr_len := if addr.sa_family == int(SocketFamily.inet) { a := Addr{
sizeof(C.sockaddr) f: u16(AddrFamily.ip6)
} else { addr: {
// TODO NOOOOOOOOOOOO Ip6: {
0 port: u16(C.htons(port))
} }
// Convert to string representation
buf := []byte{len: net.max_ipv4_addr_len, init: 0}
$if windows {
res := C.WSAAddressToStringA(&addr, addr_len, C.NULL, buf.data, unsafe { &u32(&buf.len) })
if res != 0 {
socket_error(-1) ?
}
} $else {
res := &char(C.inet_ntop(SocketFamily.inet, &addr, buf.data, buf.len))
if res == 0 {
socket_error(-1) ?
} }
} }
mut saddr := buf.bytestr()
hport := unsafe { &C.sockaddr_in(&addr) }.sin_port copy(a.addr.Ip6.addr[0..], addr[0..])
port := C.ntohs(hport)
$if windows { return a
// strip the port from the address string
saddr = saddr.split(':')[0]
}
return Addr{addr, int(addr_len), saddr, port}
} }
pub fn resolve_addr(addr string, family SocketFamily, typ SocketType) ?Addr { fn new_ip(port u16, addr [4]byte) Addr {
a := Addr{
f: u16(AddrFamily.ip)
addr: {
Ip: {
port: u16(C.htons(port))
}
}
}
copy(a.addr.Ip6.addr[0..], addr[0..])
return a
}
fn temp_unix() ?Addr {
// create a temp file to get a filename
// close it
// remove it
// then reuse the filename
mut file, filename := util.temp_file({}) ?
file.close()
os.rm(filename) ?
addrs := resolve_addrs(filename, .unix, .udp) ?
return addrs[0]
}
pub fn (a Addr) family() AddrFamily {
return AddrFamily(a.f)
}
const (
max_ip_len = 24
max_ip6_len = 46
)
fn (a Ip) str() string {
buf := []byte{len: net.max_ip_len, init: 0}
res := &char(C.inet_ntop(.ip, &a.addr, buf.data, buf.len))
if res == 0 {
return '<Unknown>'
}
saddr := buf.bytestr()
port := C.ntohs(a.port)
return '$saddr:$port'
}
fn (a Ip6) str() string {
buf := []byte{len: net.max_ip6_len, init: 0}
res := &char(C.inet_ntop(.ip6, &a.addr, buf.data, buf.len))
if res == 0 {
return '<Unknown>'
}
saddr := buf.bytestr()
port := C.ntohs(a.port)
return '[$saddr]:$port'
}
const aoffset = __offsetof(Addr, addr)
fn (a Addr) len() u32 {
match a.family() {
.ip {
return sizeof(Ip) + aoffset
}
.ip6 {
return sizeof(Ip6) + aoffset
}
.unix {
return sizeof(Unix) + aoffset
}
else {
panic('Unknown address family')
}
}
}
pub fn resolve_addrs(addr string, family AddrFamily, @type SocketType) ?[]Addr {
match family {
.ip, .ip6, .unspec {
return resolve_ipaddrs(addr, family, @type)
}
.unix {
resolved := Unix{}
if addr.len > max_unix_path {
return error('net: resolve_addrs Unix socket address is too long')
}
// Copy the unix path into the address struct
unsafe {
C.memcpy(&resolved.path, addr.str, addr.len)
}
return [Addr{
f: u16(AddrFamily.unix)
addr: AddrData{
Unix: resolved
}
}]
}
}
}
pub fn resolve_addrs_fuzzy(addr string, @type SocketType) ?[]Addr {
if addr.len == 0 {
return none
}
// Use a small heuristic to figure out what address family this is
// (out of the ones that we support)
if addr.contains(':') {
// Colon is a reserved character in unix paths
// so this must be an ip address
return resolve_addrs(addr, .unspec, @type)
}
return resolve_addrs(addr, .unix, @type)
}
pub fn resolve_ipaddrs(addr string, family AddrFamily, typ SocketType) ?[]Addr {
address, port := split_address(addr) ? address, port := split_address(addr) ?
mut hints := C.addrinfo{} if addr[0] == `:` {
// Use in6addr_any
return [new_ip6(port, net.addr_ip6_any)]
}
mut hints := C.addrinfo{
// ai_family: int(family)
// ai_socktype: int(typ)
// ai_flags: C.AI_PASSIVE
}
hints.ai_family = int(family) hints.ai_family = int(family)
hints.ai_socktype = int(typ) hints.ai_socktype = int(typ)
hints.ai_flags = C.AI_PASSIVE hints.ai_flags = C.AI_PASSIVE
hints.ai_protocol = 0 hints.ai_protocol = 0
hints.ai_addrlen = 0 hints.ai_addrlen = 0
hints.ai_canonname = C.NULL hints.ai_addr = voidptr(0)
hints.ai_addr = C.NULL hints.ai_canonname = voidptr(0)
hints.ai_next = C.NULL hints.ai_next = voidptr(0)
info := &C.addrinfo(0) results := &C.addrinfo(0)
sport := '$port' sport := '$port'
// This might look silly but is recommended by MSDN // This might look silly but is recommended by MSDN
$if windows { $if windows {
socket_error(0 - C.getaddrinfo(&char(address.str), &char(sport.str), &hints, socket_error(0 - C.getaddrinfo(&char(address.str), &char(sport.str), &hints,
&info)) ? &results)) ?
} $else { } $else {
x := C.getaddrinfo(&char(address.str), &char(sport.str), &hints, &info) x := C.getaddrinfo(&char(address.str), &char(sport.str), &hints, &results)
wrap_error(x) ? wrap_error(x) ?
} }
return new_addr(*info.ai_addr) defer {
C.freeaddrinfo(results)
}
// Now that we have our linked list of addresses
// convert them into an array
mut addresses := []Addr{}
for result := results; !isnil(result); result = result.ai_next {
match AddrFamily(result.ai_family) {
.ip, .ip6 {
new_addr := Addr{
addr: {
Ip6: {}
}
}
unsafe {
C.memcpy(&new_addr, result.ai_addr, result.ai_addrlen)
}
addresses << new_addr
}
else {
panic('Unexpected address family $result.ai_family')
}
}
}
return addresses
}
fn (a Addr) str() string {
match AddrFamily(a.f) {
.ip {
unsafe {
return a.addr.Ip.str()
}
}
.ip6 {
unsafe {
return a.addr.Ip6.str()
}
}
.unix {
unsafe {
return tos_clone(a.addr.Unix.path[0..max_unix_path].data)
}
}
.unspec {
return '<.unspec>'
}
}
}
pub fn addr_from_socket_handle(handle int) Addr {
addr := Addr{
addr: {
Ip6: {}
}
}
size := sizeof(addr)
C.getsockname(handle, voidptr(&addr), &size)
return addr
} }

View File

@ -0,0 +1,74 @@
module net
const max_unix_path = 104
struct C.addrinfo {
mut:
ai_family int
ai_socktype int
ai_flags int
ai_protocol int
ai_addrlen int
ai_addr voidptr
ai_canonname voidptr
ai_next voidptr
}
struct C.sockaddr_in6 {
mut:
// 1 + 1 + 2 + 4 + 16 + 4 = 28;
sin6_len byte // 1
sin6_family byte // 1
sin6_port u16 // 2
sin6_flowinfo u32 // 4
sin6_addr [16]byte // 16
sin6_scope_id u32 // 4
}
struct C.sockaddr_in {
mut:
sin_len byte
sin_family byte
sin_port u16
sin_addr u32
sin_zero [8]char
}
struct C.sockaddr_un {
mut:
sun_len byte
sun_family byte
sun_path [max_unix_path]char
}
[_pack: '1']
struct Ip6 {
port u16
flow_info u32
addr [16]byte
scope_id u32
}
[_pack: '1']
struct Ip {
port u16
addr [4]byte
// Pad to size so that socket functions
// dont complain to us (see in.h and bind())
// TODO(emily): I would really like to use
// some constant calculations here
// so that this doesnt have to be hardcoded
sin_pad [8]byte
}
struct Unix {
path [max_unix_path]char
}
[_pack: '1']
struct Addr {
pub:
len u8
f u8
addr AddrData
}

View File

@ -0,0 +1,32 @@
module net
const max_unix_path = 104
struct C.addrinfo {
mut:
ai_family int
ai_socktype int
ai_flags int
ai_protocol int
ai_addrlen int
ai_addr voidptr
ai_canonname voidptr
ai_next voidptr
}
struct C.sockaddr_in {
sin_family byte
sin_port u16
sin_addr u32
}
struct C.sockaddr_in6 {
sin6_family byte
sin6_port u16
sin6_addr [4]u32
}
struct C.sockaddr_un {
sun_family byte
sun_path [max_unix_path]char
}

View File

@ -0,0 +1,63 @@
module net
const max_unix_path = 108
struct C.addrinfo {
mut:
ai_family int
ai_socktype int
ai_flags int
ai_protocol int
ai_addrlen int
ai_addr voidptr
ai_canonname voidptr
ai_next voidptr
}
struct C.sockaddr_in {
sin_family u16
sin_port u16
sin_addr u32
}
struct C.sockaddr_in6 {
sin6_family u16
sin6_port u16
sin6_addr [4]u32
}
struct C.sockaddr_un {
sun_family u16
sun_path [max_unix_path]char
}
[_pack: '1']
struct Ip6 {
port u16
flow_info u32
addr [16]byte
scope_id u32
}
[_pack: '1']
struct Ip {
port u16
addr [4]byte
// Pad to size so that socket functions
// dont complain to us (see in.h and bind())
// TODO(emily): I would really like to use
// some constant calculations here
// so that this doesnt have to be hardcoded
sin_pad [8]byte
}
struct Unix {
path [max_unix_path]byte
}
[_pack: '1']
struct Addr {
pub:
f u16
addr AddrData
}

98
vlib/net/address_test.v Normal file
View File

@ -0,0 +1,98 @@
module net
$if windows {
$if msvc {
// Force these to be included before afunix!
#include <winsock2.h>
#include <ws2tcpip.h>
#include <afunix.h>
} $else {
#include "@VROOT/vlib/net/afunix.h"
}
} $else {
#include <sys/un.h>
}
fn test_diagnostics() {
dump(net.aoffset)
eprintln('--------')
in6 := C.sockaddr_in6{}
our_ip6 := Ip6{}
$if macos {
dump(__offsetof(C.sockaddr_in6, sin6_len))
}
dump(__offsetof(C.sockaddr_in6, sin6_family))
dump(__offsetof(C.sockaddr_in6, sin6_port))
dump(__offsetof(C.sockaddr_in6, sin6_addr))
$if macos {
dump(sizeof(in6.sin6_len))
}
dump(sizeof(in6.sin6_family))
dump(sizeof(in6.sin6_port))
dump(sizeof(in6.sin6_addr))
dump(sizeof(in6))
eprintln('')
dump(__offsetof(Ip6, port))
dump(__offsetof(Ip6, addr))
dump(sizeof(our_ip6.port))
dump(sizeof(our_ip6.addr))
dump(sizeof(our_ip6))
eprintln('--------')
in4 := C.sockaddr_in{}
our_ip4 := Ip{}
$if macos {
dump(__offsetof(C.sockaddr_in, sin_len))
}
dump(__offsetof(C.sockaddr_in, sin_family))
dump(__offsetof(C.sockaddr_in, sin_port))
dump(__offsetof(C.sockaddr_in, sin_addr))
$if macos {
dump(sizeof(in4.sin_len))
}
dump(sizeof(in4.sin_family))
dump(sizeof(in4.sin_port))
dump(sizeof(in4.sin_addr))
dump(sizeof(in4))
eprintln('')
dump(__offsetof(Ip, port))
dump(__offsetof(Ip, addr))
dump(sizeof(our_ip4.port))
dump(sizeof(our_ip4.addr))
dump(sizeof(our_ip4))
eprintln('--------')
dump(__offsetof(C.sockaddr_un, sun_path))
dump(__offsetof(Unix, path))
eprintln('--------')
}
fn test_sizes_unix_sun_path() {
x1 := C.sockaddr_un{}
x2 := Unix{}
assert sizeof(x1.sun_path) == sizeof(x2.path)
}
fn test_offsets_ipv6() {
assert __offsetof(C.sockaddr_in6, sin6_addr) == __offsetof(Ip6, addr) + net.aoffset
assert __offsetof(C.sockaddr_in6, sin6_port) == __offsetof(Ip6, port) + net.aoffset
}
fn test_offsets_ipv4() {
assert __offsetof(C.sockaddr_in, sin_addr) == __offsetof(Ip, addr) + net.aoffset
assert __offsetof(C.sockaddr_in, sin_port) == __offsetof(Ip, port) + net.aoffset
}
fn test_offsets_unix() {
assert __offsetof(C.sockaddr_un, sun_path) == __offsetof(Unix, path) + net.aoffset
}
fn test_sizes_ipv6() {
assert sizeof(C.sockaddr_in6) == sizeof(Ip6) + aoffset
}
fn test_sizes_ipv4() {
assert sizeof(C.sockaddr_in) == sizeof(Ip) + aoffset
}
fn test_sizes_unix() {
assert sizeof(C.sockaddr_un) == sizeof(Unix) + aoffset
}

View File

@ -0,0 +1,58 @@
module net
const max_unix_path = 108
struct C.addrinfo {
mut:
ai_family int
ai_socktype int
ai_flags int
ai_protocol int
ai_addrlen int
ai_addr voidptr
ai_canonname voidptr
ai_next voidptr
}
struct C.sockaddr_in {
sin_family u16
sin_port u16
sin_addr u32
}
struct C.sockaddr_in6 {
sin6_family u16
sin6_port u16
sin6_addr [4]u32
}
struct C.sockaddr_un {
sun_family u16
sun_path [max_unix_path]char
}
[_pack: '1']
struct Ip6 {
port u16
flow_info u32
addr [16]byte
scope_id u32
}
[_pack: '1']
struct Ip {
port u16
addr [4]byte
sin_pad [8]byte
}
struct Unix {
path [max_unix_path]byte
}
[_pack: '1']
struct Addr {
pub:
f u16
addr AddrData
}

26
vlib/net/afunix.h Normal file
View File

@ -0,0 +1,26 @@
/**
* This file has no copyright assigned and is placed in the Public Domain.
* This file is part of the mingw-w64 runtime package.
* No warranty is given; refer to the file DISCLAIMER.PD within this package.
*/
#ifndef _AFUNIX_
#define _AFUNIX_
#define UNIX_PATH_MAX 108
#if !defined(ADDRESS_FAMILY)
#define UNDEF_ADDRESS_FAMILY
#define ADDRESS_FAMILY unsigned short
#endif
typedef struct sockaddr_un {
ADDRESS_FAMILY sun_family;
char sun_path[UNIX_PATH_MAX];
} SOCKADDR_UN, *PSOCKADDR_UN;
#if defined(UNDEF_ADDRESS_FAMILY)
#undef ADDRESS_FAMILY
#endif
#endif /* _AFUNIX_ */

View File

@ -2,6 +2,20 @@ module net
import time import time
// no_deadline should be given to functions when no deadline is wanted (i.e. all functions
// return instantly)
const no_deadline = time.Time{
unix: 0
}
// no_timeout should be given to functions when no timeout is wanted (i.e. all functions
// return instantly)
pub const no_timeout = time.Duration(0)
// infinite_timeout should be given to functions when an infinite_timeout is wanted (i.e. functions
// only ever return with data)
pub const infinite_timeout = time.Duration(-1)
// Shutdown shutsdown a socket and closes it // Shutdown shutsdown a socket and closes it
fn shutdown(handle int) ? { fn shutdown(handle int) ? {
$if windows { $if windows {
@ -11,8 +25,6 @@ fn shutdown(handle int) ? {
C.shutdown(handle, C.SHUT_RDWR) C.shutdown(handle, C.SHUT_RDWR)
socket_error(C.close(handle)) ? socket_error(C.close(handle)) ?
} }
return none
} }
// Select waits for an io operation (specified by parameter `test`) to be available // Select waits for an io operation (specified by parameter `test`) to be available
@ -23,7 +35,7 @@ fn @select(handle int, test Select, timeout time.Duration) ?bool {
C.FD_SET(handle, &set) C.FD_SET(handle, &set)
seconds := timeout.milliseconds() / 1000 seconds := timeout.milliseconds() / 1000
microseconds := timeout - (seconds * time.second) microseconds := time.Duration(timeout - (seconds * time.second)).microseconds()
mut tt := C.timeval{ mut tt := C.timeval{
tv_sec: u64(seconds) tv_sec: u64(seconds)
@ -63,7 +75,7 @@ fn wait_for_common(handle int, deadline time.Time, timeout time.Duration, test S
} }
ready := @select(handle, test, timeout) ? ready := @select(handle, test, timeout) ?
if ready { if ready {
return none return
} }
return err_timed_out return err_timed_out
} }
@ -78,7 +90,7 @@ fn wait_for_common(handle int, deadline time.Time, timeout time.Duration, test S
ready := @select(handle, test, d_timeout) ? ready := @select(handle, test, d_timeout) ?
if ready { if ready {
return none return
} }
return err_timed_out return err_timed_out
} }
@ -92,23 +104,3 @@ fn wait_for_write(handle int, deadline time.Time, timeout time.Duration) ? {
fn wait_for_read(handle int, deadline time.Time, timeout time.Duration) ? { fn wait_for_read(handle int, deadline time.Time, timeout time.Duration) ? {
return wait_for_common(handle, deadline, timeout, .read) return wait_for_common(handle, deadline, timeout, .read)
} }
// no_deadline should be given to functions when no deadline is wanted (i.e. all functions
// return instantly)
const (
no_deadline = time.Time{
unix: 0
}
)
// no_timeout should be given to functions when no timeout is wanted (i.e. all functions
// return instantly)
pub const (
no_timeout = time.Duration(0)
)
// infinite_timeout should be given to functions when an infinite_timeout is wanted (i.e. functions
// only ever return with data)
pub const (
infinite_timeout = time.Duration(-1)
)

View File

@ -52,12 +52,19 @@ pub fn wrap_error(error_code int) ? {
// wrap_read_result takes a read result and sees if it is 0 for graceful // wrap_read_result takes a read result and sees if it is 0 for graceful
// connection termination and returns none // connection termination and returns none
// e.g. res := wrap_read_result(C.recv(c.sock.handle, buf_ptr, len, 0))? // e.g. res := wrap_read_result(C.recv(c.sock.handle, voidptr(buf_ptr), len, 0))?
[inline] [inline]
fn wrap_read_result(result int) ?int { fn wrap_read_result(result int) ?int {
if result > 0 || result < 0 { if result == 0 {
return result return none
} }
return result
return none }
[inline]
fn wrap_write_result(result int) ?int {
if result == 0 {
return none
}
return result
} }

View File

@ -9,16 +9,16 @@ import time
pub struct Server { pub struct Server {
pub mut: pub mut:
port int = 8080 port int = 8080
handler fn(Request) Response handler fn (Request) Response
read_timeout time.Duration = 30 * time.second read_timeout time.Duration = 30 * time.second
write_timeout time.Duration = 30 * time.second write_timeout time.Duration = 30 * time.second
} }
pub fn (mut s Server) listen_and_serve() ? { pub fn (mut s Server) listen_and_serve() ? {
if voidptr(s.handler) == 0 { if voidptr(s.handler) == 0 {
eprintln('Server handler not set, using debug handler') eprintln('Server handler not set, using debug handler')
s.handler = fn(req Request) Response { s.handler = fn (req Request) Response {
$if debug { $if debug {
eprintln('[$time.now()] $req.method $req.url\n\r$req.header\n\r$req.data - 200 OK') eprintln('[$time.now()] $req.method $req.url\n\r$req.header\n\r$req.data - 200 OK')
} $else { } $else {
@ -33,7 +33,7 @@ pub fn (mut s Server) listen_and_serve() ? {
} }
} }
} }
mut l := net.listen_tcp(s.port) ? mut l := net.listen_tcp(.ip6, ':$s.port') ?
eprintln('Listening on :$s.port') eprintln('Listening on :$s.port')
for { for {
mut conn := l.accept() or { mut conn := l.accept() or {
@ -67,7 +67,5 @@ fn (mut s Server) parse_and_respond(mut conn net.TcpConn) {
if resp.version == .unknown { if resp.version == .unknown {
resp.version = req.version resp.version = req.version
} }
conn.write(resp.bytes()) or { conn.write(resp.bytes()) or { eprintln('error sending response: $err') }
eprintln('error sending response: $err')
}
} }

5
vlib/net/ipv6_v6only.h Normal file
View File

@ -0,0 +1,5 @@
#if !defined(IPV6_V6ONLY)
#define IPV6_V6ONLY 27
#endif

View File

@ -1,9 +1,5 @@
module net module net
// needed for unix domain sockets support
// is not included in CI rn
// #include <afunix.h>
// WsaError is all of the socket errors that WSA provides from WSAGetLastError // WsaError is all of the socket errors that WSA provides from WSAGetLastError
pub enum WsaError { pub enum WsaError {
// //
@ -770,11 +766,13 @@ mut:
szSystemStatus [129]byte szSystemStatus [129]byte
iMaxSockets u16 iMaxSockets u16
iMaxUdpDg u16 iMaxUdpDg u16
lpVendorInfo byteptr lpVendorInfo &byte
} }
fn init() { fn init() {
mut wsadata := C.WSAData{} mut wsadata := C.WSAData{
lpVendorInfo: 0
}
res := C.WSAStartup(net.wsa_v22, &wsadata) res := C.WSAStartup(net.wsa_v22, &wsadata)
if res != 0 { if res != 0 {
panic('socket: WSAStartup failed') panic('socket: WSAStartup failed')

View File

@ -37,7 +37,7 @@ pub struct SSL_METHOD {
fn C.BIO_new_ssl_connect(ctx &C.SSL_CTX) &C.BIO fn C.BIO_new_ssl_connect(ctx &C.SSL_CTX) &C.BIO
fn C.BIO_set_conn_hostname(b &C.BIO, name charptr) int fn C.BIO_set_conn_hostname(b &C.BIO, name &char) int
// there are actually 2 macros for BIO_get_ssl // there are actually 2 macros for BIO_get_ssl
// fn C.BIO_get_ssl(bp &C.BIO, ssl charptr, c int) // fn C.BIO_get_ssl(bp &C.BIO, ssl charptr, c int)
@ -48,7 +48,7 @@ fn C.BIO_do_connect(b &C.BIO) int
fn C.BIO_do_handshake(b &C.BIO) int fn C.BIO_do_handshake(b &C.BIO) int
fn C.BIO_puts(b &C.BIO, buf charptr) fn C.BIO_puts(b &C.BIO, buf &char)
fn C.BIO_read(b &C.BIO, buf voidptr, len int) int fn C.BIO_read(b &C.BIO, buf voidptr, len int) int
@ -60,7 +60,7 @@ fn C.SSL_CTX_set_options(ctx &C.SSL_CTX, options int)
fn C.SSL_CTX_set_verify_depth(s &C.SSL_CTX, depth int) fn C.SSL_CTX_set_verify_depth(s &C.SSL_CTX, depth int)
fn C.SSL_CTX_load_verify_locations(ctx &C.SSL_CTX, ca_file charptr, ca_path charptr) int fn C.SSL_CTX_load_verify_locations(ctx &C.SSL_CTX, ca_file &char, ca_path &char) int
fn C.SSL_CTX_free(ctx &C.SSL_CTX) fn C.SSL_CTX_free(ctx &C.SSL_CTX)
@ -70,7 +70,7 @@ fn C.SSL_set_fd(ssl &C.SSL, fd int) int
fn C.SSL_connect(&C.SSL) int fn C.SSL_connect(&C.SSL) int
fn C.SSL_set_cipher_list(ctx &SSL, str charptr) int fn C.SSL_set_cipher_list(ctx &SSL, str &char) int
fn C.SSL_get_peer_certificate(ssl &SSL) &C.X509 fn C.SSL_get_peer_certificate(ssl &SSL) &C.X509
@ -80,7 +80,7 @@ fn C.SSL_get_error(ssl &C.SSL, ret int) int
fn C.SSL_get_verify_result(ssl &SSL) int fn C.SSL_get_verify_result(ssl &SSL) int
fn C.SSL_set_tlsext_host_name(s &SSL, name charptr) int fn C.SSL_set_tlsext_host_name(s &SSL, name &char) int
fn C.SSL_shutdown(&C.SSL) int fn C.SSL_shutdown(&C.SSL) int

View File

@ -4,24 +4,29 @@ module openssl
pub fn ssl_error(ret int, ssl voidptr) ?SSLError { pub fn ssl_error(ret int, ssl voidptr) ?SSLError {
res := C.SSL_get_error(ssl, ret) res := C.SSL_get_error(ssl, ret)
match SSLError(res) { match SSLError(res) {
.ssl_error_syscall { return error_with_code('unrecoverable syscall ($res)', res) } .ssl_error_syscall {
.ssl_error_ssl { return error_with_code('unrecoverable ssl protocol error ($res)', return error_with_code('unrecoverable syscall ($res)', res)
res) } }
else { return SSLError(res) } .ssl_error_ssl {
return error_with_code('unrecoverable ssl protocol error ($res)', res)
}
else {
return SSLError(res)
}
} }
} }
pub enum SSLError { pub enum SSLError {
ssl_error_none = 0 //SSL_ERROR_NONE ssl_error_none = 0 // SSL_ERROR_NONE
ssl_error_ssl = 1 //SSL_ERROR_SSL ssl_error_ssl = 1 // SSL_ERROR_SSL
ssl_error_want_read = 2 //SSL_ERROR_WANT_READ ssl_error_want_read = 2 // SSL_ERROR_WANT_READ
ssl_error_want_write = 3 //SSL_ERROR_WANT_WRITE ssl_error_want_write = 3 // SSL_ERROR_WANT_WRITE
ssl_error_want_x509_lookup = 4 //SSL_ERROR_WANT_X509_LOOKUP ssl_error_want_x509_lookup = 4 // SSL_ERROR_WANT_X509_LOOKUP
ssl_error_syscall = 5 //SSL_ERROR_SYSCALL ssl_error_syscall = 5 // SSL_ERROR_SYSCALL
ssl_error_zero_return = 6 //SSL_ERROR_ZERO_RETURN ssl_error_zero_return = 6 // SSL_ERROR_ZERO_RETURN
ssl_error_want_connect = 7 //SSL_ERROR_WANT_CONNECT ssl_error_want_connect = 7 // SSL_ERROR_WANT_CONNECT
ssl_error_want_accept = 8 //SSL_ERROR_WANT_ACCEPT ssl_error_want_accept = 8 // SSL_ERROR_WANT_ACCEPT
ssl_error_want_async = 9 //SSL_ERROR_WANT_ASYNC ssl_error_want_async = 9 // SSL_ERROR_WANT_ASYNC
ssl_error_want_async_job = 10 //SSL_ERROR_WANT_ASYNC_JOB ssl_error_want_async_job = 10 // SSL_ERROR_WANT_ASYNC_JOB
ssl_error_want_early = 11 //SSL_ERROR_WANT_EARLY ssl_error_want_early = 11 // SSL_ERROR_WANT_EARLY
} }

View File

@ -1,5 +1,5 @@
import os import os
import smtp import net.smtp
import time import time
// Used to test that a function call returns an error // Used to test that a function call returns an error

View File

@ -3,7 +3,6 @@ module net
pub enum SocketOption { pub enum SocketOption {
// TODO: SO_ACCEPT_CONN is not here becuase windows doesnt support it // TODO: SO_ACCEPT_CONN is not here becuase windows doesnt support it
// and there is no easy way to define it // and there is no easy way to define it
broadcast = C.SO_BROADCAST broadcast = C.SO_BROADCAST
debug = C.SO_DEBUG debug = C.SO_DEBUG
dont_route = C.SO_DONTROUTE dont_route = C.SO_DONTROUTE
@ -11,27 +10,23 @@ pub enum SocketOption {
keep_alive = C.SO_KEEPALIVE keep_alive = C.SO_KEEPALIVE
linger = C.SO_LINGER linger = C.SO_LINGER
oob_inline = C.SO_OOBINLINE oob_inline = C.SO_OOBINLINE
reuse_addr = C.SO_REUSEADDR reuse_addr = C.SO_REUSEADDR
recieve_buf_size = C.SO_RCVBUF recieve_buf_size = C.SO_RCVBUF
recieve_low_size = C.SO_RCVLOWAT recieve_low_size = C.SO_RCVLOWAT
recieve_timeout = C.SO_RCVTIMEO recieve_timeout = C.SO_RCVTIMEO
send_buf_size = C.SO_SNDBUF send_buf_size = C.SO_SNDBUF
send_low_size = C.SO_SNDLOWAT send_low_size = C.SO_SNDLOWAT
send_timeout = C.SO_SNDTIMEO send_timeout = C.SO_SNDTIMEO
socket_type = C.SO_TYPE socket_type = C.SO_TYPE
ipv6_only = C.IPV6_V6ONLY
} }
const ( const (
opts_bool = [SocketOption.broadcast, .debug, .dont_route, .error, .keep_alive, .oob_inline] opts_bool = [SocketOption.broadcast, .debug, .dont_route, .error, .keep_alive, .oob_inline]
opts_int = [ opts_int = [
.recieve_buf_size, .recieve_buf_size,
.recieve_low_size, .recieve_low_size,
.recieve_timeout, .recieve_timeout,
.send_buf_size, .send_buf_size,
.send_low_size, .send_low_size,
.send_timeout, .send_timeout,
@ -47,9 +42,9 @@ const (
.recieve_buf_size, .recieve_buf_size,
.recieve_low_size, .recieve_low_size,
.recieve_timeout, .recieve_timeout,
.send_buf_size, .send_buf_size,
.send_low_size, .send_low_size,
.send_timeout, .send_timeout,
.ipv6_only,
] ]
) )

View File

@ -10,8 +10,7 @@ const (
[heap] [heap]
pub struct TcpConn { pub struct TcpConn {
pub mut: pub mut:
sock TcpSocket sock TcpSocket
max_write_chunk_size int = 4096
mut: mut:
write_deadline time.Time write_deadline time.Time
read_deadline time.Time read_deadline time.Time
@ -20,30 +19,36 @@ mut:
} }
pub fn dial_tcp(address string) ?&TcpConn { pub fn dial_tcp(address string) ?&TcpConn {
mut s := new_tcp_socket() ? addrs := resolve_addrs_fuzzy(address, .tcp) ?
s.connect(address) ?
return &TcpConn{ // Very simple dialer
sock: s for addr in addrs {
read_timeout: net.tcp_default_read_timeout mut s := new_tcp_socket(addr.family()) ?
write_timeout: net.tcp_default_write_timeout s.connect(addr) or {
// Connection failed
s.close() or { continue }
continue
}
return &TcpConn{
sock: s
read_timeout: net.tcp_default_read_timeout
write_timeout: net.tcp_default_write_timeout
}
} }
// failed
return error('dial_tcp failed')
} }
pub fn (mut c TcpConn) close() ? { pub fn (mut c TcpConn) close() ? {
$if trace_tcp ? {
eprintln(' TcpConn.close | c.sock.handle: ${c.sock.handle:6}')
}
c.sock.close() ? c.sock.close() ?
return none
} }
// write_ptr blocks and attempts to write all data // write_ptr blocks and attempts to write all data
pub fn (mut c TcpConn) write_ptr(b &byte, len int) ?int { pub fn (mut c TcpConn) write_ptr(b &byte, len int) ?int {
$if trace_tcp ? { $if trace_tcp ? {
eprintln('>>> TcpConn.write_ptr | c.sock.handle: ${c.sock.handle:6} | b: ${ptr_str(b)} len: ${len:6}') eprintln(
} '>>> TcpConn.write_ptr | c.sock.handle: $c.sock.handle | b: ${ptr_str(b)} len: $len |\n' +
$if trace_tcp_data_write ? {
eprintln('>>> TcpConn.write_ptr | data.len: ${len:6} | data: ' +
unsafe { b.vstring_with_len(len) }) unsafe { b.vstring_with_len(len) })
} }
unsafe { unsafe {
@ -51,14 +56,8 @@ pub fn (mut c TcpConn) write_ptr(b &byte, len int) ?int {
mut total_sent := 0 mut total_sent := 0
for total_sent < len { for total_sent < len {
ptr := ptr_base + total_sent ptr := ptr_base + total_sent
mut chunk_size := len - total_sent remaining := len - total_sent
if chunk_size > c.max_write_chunk_size { mut sent := C.send(c.sock.handle, ptr, remaining, msg_nosignal)
chunk_size = c.max_write_chunk_size
}
mut sent := C.send(c.sock.handle, ptr, chunk_size, msg_nosignal)
$if trace_tcp_data_write ? {
eprintln('>>> TcpConn.write_ptr | data chunk, total_sent: ${total_sent:6}, chunk_size: ${chunk_size:6}, sent: ${sent:6}, ptr: ${ptr_str(ptr)}')
}
if sent < 0 { if sent < 0 {
code := error_code() code := error_code()
if code == int(error_ewouldblock) { if code == int(error_ewouldblock) {
@ -91,29 +90,19 @@ pub fn (mut c TcpConn) write_string(s string) ?int {
} }
pub fn (mut c TcpConn) read_ptr(buf_ptr &byte, len int) ?int { pub fn (mut c TcpConn) read_ptr(buf_ptr &byte, len int) ?int {
mut res := wrap_read_result(C.recv(c.sock.handle, buf_ptr, len, 0)) ? mut res := wrap_read_result(C.recv(c.sock.handle, voidptr(buf_ptr), len, 0)) ?
$if trace_tcp ? { $if trace_tcp ? {
eprintln('<<< TcpConn.read_ptr | c.sock.handle: ${c.sock.handle:6} | buf_ptr: ${ptr_str(buf_ptr):8} len: ${len:6} | res: ${res:6}') eprintln('<<< TcpConn.read_ptr | c.sock.handle: $c.sock.handle | buf_ptr: ${ptr_str(buf_ptr)} len: $len | res: $res')
} }
if res > 0 { if res > 0 {
$if trace_tcp_data_read ? {
eprintln('<<< TcpConn.read_ptr | 1 data.len: ${res:6} | data: ' +
unsafe { buf_ptr.vstring_with_len(res) })
}
return res return res
} }
code := error_code() code := error_code()
if code == int(error_ewouldblock) { if code == int(error_ewouldblock) {
c.wait_for_read() ? c.wait_for_read() ?
res = wrap_read_result(C.recv(c.sock.handle, buf_ptr, len, 0)) ? res = wrap_read_result(C.recv(c.sock.handle, voidptr(buf_ptr), len, 0)) ?
$if trace_tcp ? { $if trace_tcp ? {
eprintln('<<< TcpConn.read_ptr | c.sock.handle: ${c.sock.handle:6} | buf_ptr: ${ptr_str(buf_ptr):8} len: ${len:6} | res: ${res:6}') eprintln('<<< TcpConn.read_ptr | c.sock.handle: $c.sock.handle | buf_ptr: ${ptr_str(buf_ptr)} len: $len | res: $res')
}
$if trace_tcp_data_read ? {
if res > 0 {
eprintln('<<< TcpConn.read_ptr | 2 data.len: ${res:6} | data: ' +
unsafe { buf_ptr.vstring_with_len(res) })
}
} }
return socket_error(res) return socket_error(res)
} else { } else {
@ -175,23 +164,22 @@ pub fn (mut c TcpConn) wait_for_write() ? {
} }
pub fn (c &TcpConn) peer_addr() ?Addr { pub fn (c &TcpConn) peer_addr() ?Addr {
mut addr := C.sockaddr{} mut addr := Addr{
len := sizeof(C.sockaddr) addr: {
socket_error(C.getpeername(c.sock.handle, &addr, &len)) ? Ip6: {}
return new_addr(addr) }
}
mut size := sizeof(Addr)
socket_error(C.getpeername(c.sock.handle, voidptr(&addr), &size)) ?
return addr
} }
pub fn (c &TcpConn) peer_ip() ?string { pub fn (c &TcpConn) peer_ip() ?string {
buf := [44]char{} return c.peer_addr() ?.str()
peeraddr := C.sockaddr_in{} }
speeraddr := sizeof(peeraddr)
socket_error(C.getpeername(c.sock.handle, unsafe { &C.sockaddr(&peeraddr) }, &speeraddr)) ? pub fn (c &TcpConn) addr() ?Addr {
cstr := &char(C.inet_ntop(SocketFamily.inet, &peeraddr.sin_addr, &buf[0], sizeof(buf))) return c.sock.address()
if cstr == 0 {
return error('net.peer_ip: inet_ntop failed')
}
res := unsafe { cstring_to_vstring(cstr) }
return res
} }
pub fn (c TcpConn) str() string { pub fn (c TcpConn) str() string {
@ -207,17 +195,18 @@ mut:
accept_deadline time.Time accept_deadline time.Time
} }
pub fn listen_tcp(port int) ?&TcpListener { pub fn listen_tcp(family AddrFamily, saddr string) ?&TcpListener {
s := new_tcp_socket() ? s := new_tcp_socket(family) ?
validate_port(port) ?
mut addr := C.sockaddr_in{} addrs := resolve_addrs(saddr, family, .tcp) ?
addr.sin_family = int(SocketFamily.inet)
addr.sin_port = C.htons(port) // TODO(logic to pick here)
addr.sin_addr.s_addr = C.htonl(C.INADDR_ANY) addr := addrs[0]
size := sizeof(C.sockaddr_in)
// cast to the correct type // cast to the correct type
sockaddr := unsafe { &C.sockaddr(&addr) } alen := addr.len()
socket_error(C.bind(s.handle, sockaddr, size)) ? bindres := C.bind(s.handle, voidptr(&addr), alen)
socket_error(bindres) ?
socket_error(C.listen(s.handle, 128)) ? socket_error(C.listen(s.handle, 128)) ?
return &TcpListener{ return &TcpListener{
sock: s sock: s
@ -227,26 +216,21 @@ pub fn listen_tcp(port int) ?&TcpListener {
} }
pub fn (mut l TcpListener) accept() ?&TcpConn { pub fn (mut l TcpListener) accept() ?&TcpConn {
$if trace_tcp ? { addr := Addr{
eprintln(' TcpListener.accept | l.sock.handle: ${l.sock.handle:6}') addr: {
Ip6: {}
}
} }
addr := C.sockaddr_storage{} size := sizeof(Addr)
unsafe { C.memset(&addr, 0, sizeof(C.sockaddr_storage)) } mut new_handle := C.accept(l.sock.handle, voidptr(&addr), &size)
size := sizeof(C.sockaddr_storage)
// cast to correct type
sock_addr := unsafe { &C.sockaddr(&addr) }
mut new_handle := C.accept(l.sock.handle, sock_addr, &size)
if new_handle <= 0 { if new_handle <= 0 {
l.wait_for_accept() ? l.wait_for_accept() ?
new_handle = C.accept(l.sock.handle, sock_addr, &size) new_handle = C.accept(l.sock.handle, voidptr(&addr), &size)
if new_handle == -1 || new_handle == 0 { if new_handle == -1 || new_handle == 0 {
return none return error('accept failed')
} }
} }
new_sock := tcp_socket_from_handle(new_handle) ? new_sock := tcp_socket_from_handle(new_handle) ?
$if trace_tcp ? {
eprintln(' TcpListener.accept | << new_sock.handle: ${new_sock.handle:6}')
}
return &TcpConn{ return &TcpConn{
sock: new_sock sock: new_sock
read_timeout: net.tcp_default_read_timeout read_timeout: net.tcp_default_read_timeout
@ -258,7 +242,7 @@ pub fn (c &TcpListener) accept_deadline() ?time.Time {
if c.accept_deadline.unix != 0 { if c.accept_deadline.unix != 0 {
return c.accept_deadline return c.accept_deadline
} }
return none return error('invalid deadline')
} }
pub fn (mut c TcpListener) set_accept_deadline(deadline time.Time) { pub fn (mut c TcpListener) set_accept_deadline(deadline time.Time) {
@ -278,14 +262,10 @@ pub fn (mut c TcpListener) wait_for_accept() ? {
} }
pub fn (mut c TcpListener) close() ? { pub fn (mut c TcpListener) close() ? {
$if trace_tcp ? {
eprintln(' TcpListener.close | c.sock.handle: ${c.sock.handle:6}')
}
c.sock.close() ? c.sock.close() ?
return none
} }
pub fn (c &TcpListener) address() ?Addr { pub fn (c &TcpListener) addr() ?Addr {
return c.sock.address() return c.sock.address()
} }
@ -294,23 +274,23 @@ pub:
handle int handle int
} }
fn new_tcp_socket() ?TcpSocket { fn new_tcp_socket(family AddrFamily) ?TcpSocket {
sockfd := socket_error(C.socket(SocketFamily.inet, SocketType.tcp, 0)) ? handle := socket_error(C.socket(family, SocketType.tcp, 0)) ?
mut s := TcpSocket{ mut s := TcpSocket{
handle: sockfd handle: handle
} }
$if trace_tcp ? { // TODO(emily):
eprintln(' new_tcp_socket | s.handle: ${s.handle:6}') // we shouldnt be using ioctlsocket in the 21st century
} // use the non-blocking socket option instead please :)
// s.set_option_bool(.reuse_addr, true)?
// TODO(emily):
// Move this to its own function on the socket
s.set_option_int(.reuse_addr, 1) ? s.set_option_int(.reuse_addr, 1) ?
$if !net_blocking_sockets ? { $if windows {
$if windows { t := u32(1) // true
t := u32(1) // true socket_error(C.ioctlsocket(handle, fionbio, &t)) ?
socket_error(C.ioctlsocket(sockfd, fionbio, &t)) ? } $else {
} $else { socket_error(C.fcntl(handle, C.F_SETFL, C.fcntl(handle, C.F_GETFL) | C.O_NONBLOCK)) ?
socket_error(C.fcntl(sockfd, C.F_SETFL, C.fcntl(sockfd, C.F_GETFL) | C.O_NONBLOCK)) ?
}
} }
return s return s
} }
@ -319,18 +299,16 @@ fn tcp_socket_from_handle(sockfd int) ?TcpSocket {
mut s := TcpSocket{ mut s := TcpSocket{
handle: sockfd handle: sockfd
} }
$if trace_tcp ? {
eprintln(' tcp_socket_from_handle | s.handle: ${s.handle:6}')
}
// s.set_option_bool(.reuse_addr, true)? // s.set_option_bool(.reuse_addr, true)?
s.set_option_int(.reuse_addr, 1) ? s.set_option_int(.reuse_addr, 1) ?
$if !net_blocking_sockets ? { s.set_dualstack(true) or {
$if windows { // Not ipv6, we dont care
t := u32(1) // true }
socket_error(C.ioctlsocket(sockfd, fionbio, &t)) ? $if windows {
} $else { t := u32(1) // true
socket_error(C.fcntl(sockfd, C.F_SETFL, C.fcntl(sockfd, C.F_GETFL) | C.O_NONBLOCK)) ? socket_error(C.ioctlsocket(sockfd, fionbio, &t)) ?
} } $else {
socket_error(C.fcntl(sockfd, C.F_SETFL, C.fcntl(sockfd, C.F_GETFL) | C.O_NONBLOCK)) ?
} }
return s return s
} }
@ -345,18 +323,19 @@ pub fn (mut s TcpSocket) set_option_bool(opt SocketOption, value bool) ? {
// } // }
x := int(value) x := int(value)
socket_error(C.setsockopt(s.handle, C.SOL_SOCKET, int(opt), &x, sizeof(int))) ? socket_error(C.setsockopt(s.handle, C.SOL_SOCKET, int(opt), &x, sizeof(int))) ?
return none }
pub fn (mut s TcpSocket) set_dualstack(on bool) ? {
x := int(!on)
socket_error(C.setsockopt(s.handle, C.IPPROTO_IPV6, int(SocketOption.ipv6_only), &x,
sizeof(int))) ?
} }
pub fn (mut s TcpSocket) set_option_int(opt SocketOption, value int) ? { pub fn (mut s TcpSocket) set_option_int(opt SocketOption, value int) ? {
socket_error(C.setsockopt(s.handle, C.SOL_SOCKET, int(opt), &value, sizeof(int))) ? socket_error(C.setsockopt(s.handle, C.SOL_SOCKET, int(opt), &value, sizeof(int))) ?
return none
} }
fn (mut s TcpSocket) close() ? { fn (mut s TcpSocket) close() ? {
$if trace_tcp ? {
eprintln(' TcpSocket.close | s.handle: ${s.handle:6}')
}
return shutdown(s.handle) return shutdown(s.handle)
} }
@ -368,35 +347,41 @@ const (
connect_timeout = 5 * time.second connect_timeout = 5 * time.second
) )
fn (mut s TcpSocket) connect(a string) ? { fn (mut s TcpSocket) connect(a Addr) ? {
$if trace_tcp ? { res := C.connect(s.handle, voidptr(&a), a.len())
eprintln(' TcpSocket.connect | s.handle: ${s.handle:6} | a: $a')
}
addr := resolve_addr(a, .inet, .tcp) ?
res := C.connect(s.handle, &addr.addr, addr.len)
if res == 0 { if res == 0 {
return none return
} }
_ := error_code()
// The socket is nonblocking and the connection cannot be completed
// immediately. (UNIX domain sockets failed with EAGAIN instead.)
// It is possible to select(2) or poll(2) for completion by selecting
// the socket for writing. After select(2) indicates writability,
// use getsockopt(2) to read the SO_ERROR option at level SOL_SOCKET to
// determine whether connect() completed successfully (SO_ERROR is zero) or
// unsuccessfully (SO_ERROR is one of the usual error codes listed here,
// ex plaining the reason for the failure).
write_result := s.@select(.write, net.connect_timeout) ? write_result := s.@select(.write, net.connect_timeout) ?
if write_result { if write_result {
// succeeded err := 0
return none len := sizeof(err)
} socket_error(C.getsockopt(s.handle, C.SOL_SOCKET, C.SO_ERROR, &err, &len)) ?
except_result := s.@select(.except, net.connect_timeout) ?
if except_result { if err != 0 {
return err_connect_failed return wrap_error(err)
}
// Succeeded
return
} }
// Get the error
socket_error(C.connect(s.handle, voidptr(&a), a.len())) ?
// otherwise we timed out // otherwise we timed out
return err_connect_timed_out return err_connect_timed_out
} }
// address gets the address of a socket // address gets the address of a socket
pub fn (s &TcpSocket) address() ?Addr { pub fn (s &TcpSocket) address() ?Addr {
mut addr := C.sockaddr_in{} return addr_from_socket_handle(s.handle)
size := sizeof(C.sockaddr_in)
// cast to the correct type
sockaddr := unsafe { &C.sockaddr(&addr) }
C.getsockname(s.handle, sockaddr, &size)
return new_addr(sockaddr)
} }

View File

@ -9,7 +9,7 @@ const (
// read_line is a *simple*, *non customizable*, blocking line reader. // read_line is a *simple*, *non customizable*, blocking line reader.
// It will *always* return a line, ending with CRLF, or just '', on EOF. // It will *always* return a line, ending with CRLF, or just '', on EOF.
// NB: if you want more control over the buffer, please use a buffered IO // NB: if you want more control over the buffer, please use a buffered IO
// reader instead: `io.new_buffered_reader(reader: con)` // reader instead: `io.new_buffered_reader({reader: io.make_reader(con)})`
pub fn (mut con TcpConn) read_line() string { pub fn (mut con TcpConn) read_line() string {
mut buf := [net.max_read]byte{} // where C.recv will store the network data mut buf := [net.max_read]byte{} // where C.recv will store the network data
mut res := '' // The final result, including the ending \n. mut res := '' // The final result, including the ending \n.

View File

@ -3,16 +3,24 @@ import net
import strings import strings
const ( const (
server_port = 22334 server_port = ':22443'
) )
fn accept(mut server net.TcpListener, c chan &net.TcpConn) {
c <- server.accept() or { panic(err) }
}
fn setup() (&net.TcpListener, &net.TcpConn, &net.TcpConn) { fn setup() (&net.TcpListener, &net.TcpConn, &net.TcpConn) {
mut server := net.listen_tcp(server_port) or { panic(err) } mut server := net.listen_tcp(.ip6, server_port) or { panic(err) }
mut client := net.dial_tcp('127.0.0.1:$server_port') or { panic(err) }
mut socket := server.accept() or { panic(err) } c := chan &net.TcpConn{}
go accept(mut server, c)
mut client := net.dial_tcp('localhost$server_port') or { panic(err) }
socket := <-c
$if debug_peer_ip ? { $if debug_peer_ip ? {
ip := con.peer_ip() or { '$err' } eprintln('$server.addr()\n$client.peer_addr(), $client.addr()\n$socket.peer_addr(), $socket.addr()')
eprintln('connection peer_ip: $ip')
} }
assert true assert true
return server, client, socket return server, client, socket

View File

@ -1,4 +1,5 @@
import net import net
import os
const ( const (
test_port = 45123 test_port = 45123
@ -18,19 +19,24 @@ fn handle_conn(mut c net.TcpConn) {
} }
} }
fn echo_server(mut l net.TcpListener) ? { fn one_shot_echo_server(mut l net.TcpListener, ch_started chan int) ? {
for { eprintln('> one_shot_echo_server')
mut new_conn := l.accept() or { continue } ch_started <- 1
go handle_conn(mut new_conn) mut new_conn := l.accept() or { return error('could not accept') }
} eprintln(' > new_conn: $new_conn')
return none handle_conn(mut new_conn)
new_conn.close() or {}
} }
fn echo() ? { fn echo(address string) ? {
mut c := net.dial_tcp('127.0.0.1:$test_port') ? mut c := net.dial_tcp(address) ?
defer { defer {
c.close() or { } c.close() or {}
} }
println('local: ' + c.addr() ?.str())
println(' peer: ' + c.peer_addr() ?.str())
data := 'Hello from vlib/net!' data := 'Hello from vlib/net!'
c.write_string(data) ? c.write_string(data) ?
mut buf := []byte{len: 4096} mut buf := []byte{len: 4096}
@ -40,12 +46,55 @@ fn echo() ? {
assert buf[i] == data[i] assert buf[i] == data[i]
} }
println('Got "$buf.bytestr()"') println('Got "$buf.bytestr()"')
return none
} }
fn test_tcp() { fn test_tcp_ip6() {
mut l := net.listen_tcp(test_port) or { panic(err) } eprintln('\n>>> ${@FN}')
go echo_server(mut l) address := 'localhost:$test_port'
echo() or { panic(err) } mut l := net.listen_tcp(.ip6, ':$test_port') or { panic(err) }
l.close() or { } dump(l)
start_echo_server(mut l)
echo(address) or { panic(err) }
l.close() or {}
// ensure there is at least one new socket created before the next test
l = net.listen_tcp(.ip6, ':${test_port + 1}') or { panic(err) }
}
fn start_echo_server(mut l net.TcpListener) {
ch_server_started := chan int{}
go one_shot_echo_server(mut l, ch_server_started)
_ := <-ch_server_started
}
fn test_tcp_ip() {
eprintln('\n>>> ${@FN}')
address := 'localhost:$test_port'
mut l := net.listen_tcp(.ip, address) or { panic(err) }
dump(l)
start_echo_server(mut l)
echo(address) or { panic(err) }
l.close() or {}
}
fn test_tcp_unix() {
eprintln('\n>>> ${@FN}')
// TODO(emily):
// whilst windows supposedly supports unix sockets
// this doesnt work (wsaeopnotsupp at the call to bind())
$if !windows {
address := os.real_path('tcp-test.sock')
// address := 'tcp-test.sock'
println('$address')
mut l := net.listen_tcp(.unix, address) or { panic(err) }
start_echo_server(mut l)
echo(address) or { panic(err) }
l.close() or {}
os.rm(address) or { panic('failed to remove socket file') }
}
}
fn testsuite_end() {
eprintln('\ndone')
} }

View File

@ -9,8 +9,12 @@ const (
struct UdpSocket { struct UdpSocket {
handle int handle int
local Addr l Addr
remote ?Addr // TODO(emily): replace with option again
// when i figure out how to coerce it properly
mut:
has_r bool
r Addr
} }
pub struct UdpConn { pub struct UdpConn {
@ -23,27 +27,36 @@ mut:
write_timeout time.Duration write_timeout time.Duration
} }
pub fn dial_udp(laddr string, raddr string) ?&UdpConn { pub fn dial_udp(raddr string) ?&UdpConn {
local := resolve_addr(laddr, .inet, .udp) ? addrs := resolve_addrs_fuzzy(raddr, .udp) ?
sbase := new_udp_socket(local.port) ?
sock := UdpSocket{ for addr in addrs {
handle: sbase.handle // create a local socket for this
local: local // bind to any port (or file) (we dont care in this
remote: resolve_wrapper(raddr) // case because we only care about the remote)
} if sock := new_udp_socket_for_remote(addr) {
return &UdpConn{ return &UdpConn{
sock: sock sock: sock
read_timeout: net.udp_default_read_timeout read_timeout: net.udp_default_read_timeout
write_timeout: net.udp_default_write_timeout write_timeout: net.udp_default_write_timeout
}
}
} }
return none
} }
fn resolve_wrapper(raddr string) ?Addr { // pub fn dial_udp(laddr string, raddr string) ?&UdpConn {
// Dont have to do this when its fixed // local := resolve_addr(laddr, .inet, .udp) ?
// this just allows us to store this `none` optional in a struct
x := resolve_addr(raddr, .inet, .udp) or { return none } // sbase := new_udp_socket() ?
return x
} // sock := UdpSocket{
// handle: sbase.handle
// l: local
// r: resolve_wrapper(raddr)
// }
// }
pub fn (mut c UdpConn) write_ptr(b &byte, len int) ?int { pub fn (mut c UdpConn) write_ptr(b &byte, len int) ?int {
remote := c.sock.remote() or { return err_no_udp_remote } remote := c.sock.remote() or { return err_no_udp_remote }
@ -64,14 +77,14 @@ pub fn (mut c UdpConn) write_string(s string) ?int {
} }
pub fn (mut c UdpConn) write_to_ptr(addr Addr, b &byte, len int) ?int { pub fn (mut c UdpConn) write_to_ptr(addr Addr, b &byte, len int) ?int {
res := C.sendto(c.sock.handle, b, len, 0, &addr.addr, addr.len) res := C.sendto(c.sock.handle, b, len, 0, voidptr(&addr), addr.len())
if res >= 0 { if res >= 0 {
return res return res
} }
code := error_code() code := error_code()
if code == int(error_ewouldblock) { if code == int(error_ewouldblock) {
c.wait_for_write() ? c.wait_for_write() ?
socket_error(C.sendto(c.sock.handle, b, len, 0, &addr.addr, addr.len)) ? socket_error(C.sendto(c.sock.handle, b, len, 0, voidptr(&addr), addr.len())) ?
} else { } else {
wrap_error(code) ? wrap_error(code) ?
} }
@ -90,22 +103,24 @@ pub fn (mut c UdpConn) write_to_string(addr Addr, s string) ?int {
// read reads from the socket into buf up to buf.len returning the number of bytes read // read reads from the socket into buf up to buf.len returning the number of bytes read
pub fn (mut c UdpConn) read(mut buf []byte) ?(int, Addr) { pub fn (mut c UdpConn) read(mut buf []byte) ?(int, Addr) {
mut addr_from := C.sockaddr{} mut addr := Addr{
len := sizeof(C.sockaddr) addr: {
mut res := wrap_read_result(C.recvfrom(c.sock.handle, buf.data, buf.len, 0, &addr_from, Ip6: {}
&len)) ? }
}
len := sizeof(Addr)
mut res := wrap_read_result(C.recvfrom(c.sock.handle, voidptr(buf.data), buf.len,
0, voidptr(&addr), &len)) ?
if res > 0 { if res > 0 {
addr := new_addr(addr_from) ?
return res, addr return res, addr
} }
code := error_code() code := error_code()
if code == int(error_ewouldblock) { if code == int(error_ewouldblock) {
c.wait_for_read() ? c.wait_for_read() ?
// same setup as in tcp // same setup as in tcp
res = wrap_read_result(C.recvfrom(c.sock.handle, buf.data, buf.len, 0, &addr_from, res = wrap_read_result(C.recvfrom(c.sock.handle, voidptr(buf.data), buf.len, 0,
&len)) ? voidptr(&addr), &len)) ?
res2 := socket_error(res) ? res2 := socket_error(res) ?
addr := new_addr(addr_from) ?
return res2, addr return res2, addr
} else { } else {
wrap_error(code) ? wrap_error(code) ?
@ -170,44 +185,79 @@ pub fn (mut c UdpConn) close() ? {
return c.sock.close() return c.sock.close()
} }
pub fn listen_udp(port int) ?&UdpConn { pub fn listen_udp(laddr string) ?&UdpConn {
s := new_udp_socket(port) ? addrs := resolve_addrs_fuzzy(laddr, .udp) ?
// TODO(emily):
// here we are binding to the first address
// and that is probably not ideal
addr := addrs[0]
return &UdpConn{ return &UdpConn{
sock: s sock: new_udp_socket(addr) ?
read_timeout: net.udp_default_read_timeout read_timeout: net.udp_default_read_timeout
write_timeout: net.udp_default_write_timeout write_timeout: net.udp_default_write_timeout
} }
} }
fn new_udp_socket(local_port int) ?&UdpSocket { fn new_udp_socket(local_addr Addr) ?&UdpSocket {
sockfd := socket_error(C.socket(SocketFamily.inet, SocketType.udp, 0)) ? family := local_addr.family()
sockfd := socket_error(C.socket(family, SocketType.udp, 0)) ?
mut s := &UdpSocket{ mut s := &UdpSocket{
handle: sockfd handle: sockfd
} l: local_addr
s.set_option_bool(.reuse_addr, true) ? r: Addr{
$if !net_blocking_sockets ? { addr: {
$if windows { Ip6: {}
t := u32(1) // true }
socket_error(C.ioctlsocket(sockfd, fionbio, &t)) ?
} $else {
socket_error(C.fcntl(sockfd, C.F_SETFD, C.O_NONBLOCK)) ?
} }
} }
// In UDP we always have to bind to a port
validate_port(local_port) ? s.set_option_bool(.reuse_addr, true) ?
mut addr := C.sockaddr_in{}
addr.sin_family = int(SocketFamily.inet) if family == .ip6 {
addr.sin_port = C.htons(local_port) s.set_dualstack(true) ?
addr.sin_addr.s_addr = C.htonl(C.INADDR_ANY) }
size := sizeof(C.sockaddr_in)
// NOTE: refer to comments in tcp.v
$if windows {
t := u32(1) // true
socket_error(C.ioctlsocket(sockfd, fionbio, &t)) ?
} $else {
socket_error(C.fcntl(sockfd, C.F_SETFD, C.O_NONBLOCK)) ?
}
// cast to the correct type // cast to the correct type
sockaddr := unsafe { &C.sockaddr(&addr) } socket_error(C.bind(s.handle, voidptr(&local_addr), local_addr.len())) ?
socket_error(C.bind(s.handle, sockaddr, size)) ?
return s return s
} }
pub fn (s &UdpSocket) remote() ?Addr { fn new_udp_socket_for_remote(raddr Addr) ?&UdpSocket {
return s.remote // Invent a sutible local address for this remote addr
addr := match raddr.family() {
.ip, .ip6 {
// Use ip6 dualstack
new_ip6(0, addr_ip6_any)
}
.unix {
x := temp_unix() ?
x
}
else {
panic('Invalid family')
// Appease compiler
Addr{
addr: {
Ip6: {}
}
}
}
}
mut sock := new_udp_socket(addr) ?
sock.has_r = true
sock.r = raddr
return sock
} }
pub fn (mut s UdpSocket) set_option_bool(opt SocketOption, value bool) ? { pub fn (mut s UdpSocket) set_option_bool(opt SocketOption, value bool) ? {
@ -220,7 +270,12 @@ pub fn (mut s UdpSocket) set_option_bool(opt SocketOption, value bool) ? {
// } // }
x := int(value) x := int(value)
socket_error(C.setsockopt(s.handle, C.SOL_SOCKET, int(opt), &x, sizeof(int))) ? socket_error(C.setsockopt(s.handle, C.SOL_SOCKET, int(opt), &x, sizeof(int))) ?
return none }
pub fn (mut s UdpSocket) set_dualstack(on bool) ? {
x := int(!on)
socket_error(C.setsockopt(s.handle, C.IPPROTO_IPV6, int(SocketOption.ipv6_only), &x,
sizeof(int))) ?
} }
fn (mut s UdpSocket) close() ? { fn (mut s UdpSocket) close() ? {
@ -230,3 +285,10 @@ fn (mut s UdpSocket) close() ? {
fn (mut s UdpSocket) @select(test Select, timeout time.Duration) ?bool { fn (mut s UdpSocket) @select(test Select, timeout time.Duration) ?bool {
return @select(s.handle, test, timeout) return @select(s.handle, test, timeout)
} }
fn (s &UdpSocket) remote() ?Addr {
if s.has_r {
return s.r
}
return none
}

View File

@ -5,6 +5,8 @@ fn echo_server(mut c net.UdpConn) {
mut buf := []byte{len: 100, init: 0} mut buf := []byte{len: 100, init: 0}
read, addr := c.read(mut buf) or { continue } read, addr := c.read(mut buf) or { continue }
println('Server got addr $addr')
c.write_to(addr, buf[..read]) or { c.write_to(addr, buf[..read]) or {
println('Server: connection dropped') println('Server: connection dropped')
return return
@ -12,10 +14,15 @@ fn echo_server(mut c net.UdpConn) {
} }
} }
const (
local_addr = ':40003'
remote_addr = 'localhost:40003'
)
fn echo() ? { fn echo() ? {
mut c := net.dial_udp('127.0.0.1:40003', '127.0.0.1:40001') ? mut c := net.dial_udp(remote_addr) ?
defer { defer {
c.close() or { } c.close() or {}
} }
data := 'Hello from vlib/net!' data := 'Hello from vlib/net!'
@ -37,12 +44,10 @@ fn echo() ? {
println('Got "$buf.bytestr()"') println('Got "$buf.bytestr()"')
c.close() ? c.close() ?
return none
} }
fn test_udp() { fn test_udp() {
mut l := net.listen_udp(40001) or { mut l := net.listen_udp(local_addr) or {
println(err) println(err)
assert false assert false
panic('') panic('')
@ -54,7 +59,7 @@ fn test_udp() {
assert false assert false
} }
l.close() or { } l.close() or {}
} }
fn main() { fn main() {

View File

@ -9,7 +9,7 @@ const (
fn C.SUN_LEN(ptr &C.sockaddr_un) int fn C.SUN_LEN(ptr &C.sockaddr_un) int
fn C.strncpy(charptr, charptr, int) fn C.strncpy(&char, &char, int)
// Shutdown shutsdown a socket and closes it // Shutdown shutsdown a socket and closes it
fn shutdown(handle int) ? { fn shutdown(handle int) ? {
@ -20,8 +20,6 @@ fn shutdown(handle int) ? {
C.shutdown(handle, C.SHUT_RDWR) C.shutdown(handle, C.SHUT_RDWR)
net.socket_error(C.close(handle)) ? net.socket_error(C.close(handle)) ?
} }
return none
} }
// Select waits for an io operation (specified by parameter `test`) to be available // Select waits for an io operation (specified by parameter `test`) to be available
@ -72,7 +70,7 @@ fn wait_for_common(handle int, deadline time.Time, timeout time.Duration, test S
} }
ready := @select(handle, test, timeout) ? ready := @select(handle, test, timeout) ?
if ready { if ready {
return none return
} }
return net.err_timed_out return net.err_timed_out
} }
@ -87,7 +85,7 @@ fn wait_for_common(handle int, deadline time.Time, timeout time.Duration, test S
ready := @select(handle, test, d_timeout) ? ready := @select(handle, test, d_timeout) ?
if ready { if ready {
return none return
} }
return net.err_timed_out return net.err_timed_out
} }

View File

@ -41,8 +41,7 @@ fn error_code() int {
} }
fn new_stream_socket() ?StreamSocket { fn new_stream_socket() ?StreamSocket {
sockfd := net.socket_error(C.socket(net.SocketFamily.unix, net.SocketType.stream, sockfd := net.socket_error(C.socket(net.AddrFamily.unix, net.SocketType.tcp, 0)) ?
0)) ?
mut s := StreamSocket{ mut s := StreamSocket{
handle: sockfd handle: sockfd
} }
@ -67,19 +66,18 @@ fn (mut s StreamSocket) connect(a string) ? {
addr.sun_family = C.AF_UNIX addr.sun_family = C.AF_UNIX
unsafe { C.strncpy(&addr.sun_path[0], &char(a.str), max_sun_path) } unsafe { C.strncpy(&addr.sun_path[0], &char(a.str), max_sun_path) }
size := C.SUN_LEN(&addr) size := C.SUN_LEN(&addr)
sockaddr := unsafe { &C.sockaddr(&addr) } res := C.connect(s.handle, voidptr(&addr), size)
res := C.connect(s.handle, sockaddr, size)
// if res != 1 { // if res != 1 {
// return none // return none
//} //}
if res == 0 { if res == 0 {
return none return
} }
_ := error_code() _ := error_code()
write_result := s.@select(.write, unix.connect_timeout) ? write_result := s.@select(.write, unix.connect_timeout) ?
if write_result { if write_result {
// succeeded // succeeded
return none return
} }
except_result := s.@select(.except, unix.connect_timeout) ? except_result := s.@select(.except, unix.connect_timeout) ?
if except_result { if except_result {
@ -100,8 +98,7 @@ pub fn listen_stream(sock string) ?&StreamListener {
addr.sun_family = C.AF_UNIX addr.sun_family = C.AF_UNIX
unsafe { C.strncpy(&addr.sun_path[0], &char(sock.str), max_sun_path) } unsafe { C.strncpy(&addr.sun_path[0], &char(sock.str), max_sun_path) }
size := C.SUN_LEN(&addr) size := C.SUN_LEN(&addr)
sockaddr := unsafe { &C.sockaddr(&addr) } net.socket_error(C.bind(s.handle, voidptr(&addr), size)) ?
net.socket_error(C.bind(s.handle, sockaddr, size)) ?
net.socket_error(C.listen(s.handle, 128)) ? net.socket_error(C.listen(s.handle, 128)) ?
return &StreamListener{ return &StreamListener{
sock: s sock: s
@ -124,7 +121,7 @@ pub fn (mut l StreamListener) accept() ?&StreamConn {
l.wait_for_accept() ? l.wait_for_accept() ?
new_handle = C.accept(l.sock.handle, 0, 0) new_handle = C.accept(l.sock.handle, 0, 0)
if new_handle == -1 || new_handle == 0 { if new_handle == -1 || new_handle == 0 {
return none return error('accept failed')
} }
} }
new_sock := StreamSocket{ new_sock := StreamSocket{
@ -141,7 +138,7 @@ pub fn (c &StreamListener) accept_deadline() ?time.Time {
if c.accept_deadline.unix != 0 { if c.accept_deadline.unix != 0 {
return c.accept_deadline return c.accept_deadline
} }
return none return error('no deadline')
} }
pub fn (mut c StreamListener) set_accept_deadline(deadline time.Time) { pub fn (mut c StreamListener) set_accept_deadline(deadline time.Time) {
@ -162,12 +159,10 @@ pub fn (mut c StreamListener) wait_for_accept() ? {
pub fn (mut c StreamListener) close() ? { pub fn (mut c StreamListener) close() ? {
c.sock.close() ? c.sock.close() ?
return none
} }
pub fn (mut c StreamConn) close() ? { pub fn (mut c StreamConn) close() ? {
c.sock.close() ? c.sock.close() ?
return none
} }
// write_ptr blocks and attempts to write all data // write_ptr blocks and attempts to write all data
@ -216,7 +211,7 @@ pub fn (mut c StreamConn) write_string(s string) ?int {
} }
pub fn (mut c StreamConn) read_ptr(buf_ptr &byte, len int) ?int { pub fn (mut c StreamConn) read_ptr(buf_ptr &byte, len int) ?int {
mut res := wrap_read_result(C.recv(c.sock.handle, buf_ptr, len, 0)) ? mut res := wrap_read_result(C.recv(c.sock.handle, voidptr(buf_ptr), len, 0)) ?
$if trace_unix ? { $if trace_unix ? {
eprintln('<<< StreamConn.read_ptr | c.sock.handle: $c.sock.handle | buf_ptr: ${ptr_str(buf_ptr)} len: $len | res: $res') eprintln('<<< StreamConn.read_ptr | c.sock.handle: $c.sock.handle | buf_ptr: ${ptr_str(buf_ptr)} len: $len | res: $res')
} }
@ -226,7 +221,7 @@ pub fn (mut c StreamConn) read_ptr(buf_ptr &byte, len int) ?int {
code := error_code() code := error_code()
if code == int(error_ewouldblock) { if code == int(error_ewouldblock) {
c.wait_for_read() ? c.wait_for_read() ?
res = wrap_read_result(C.recv(c.sock.handle, buf_ptr, len, 0)) ? res = wrap_read_result(C.recv(c.sock.handle, voidptr(buf_ptr), len, 0)) ?
$if trace_unix ? { $if trace_unix ? {
eprintln('<<< StreamConn.read_ptr | c.sock.handle: $c.sock.handle | buf_ptr: ${ptr_str(buf_ptr)} len: $len | res: $res') eprintln('<<< StreamConn.read_ptr | c.sock.handle: $c.sock.handle | buf_ptr: ${ptr_str(buf_ptr)} len: $len | res: $res')
} }
@ -234,7 +229,7 @@ pub fn (mut c StreamConn) read_ptr(buf_ptr &byte, len int) ?int {
} else { } else {
net.wrap_error(code) ? net.wrap_error(code) ?
} }
return none return net.socket_error(code)
} }
pub fn (mut c StreamConn) read(mut buf []byte) ?int { pub fn (mut c StreamConn) read(mut buf []byte) ?int {

View File

@ -4,11 +4,11 @@ import net.unix
const test_port = os.join_path(os.temp_dir(), 'unix_domain_socket') const test_port = os.join_path(os.temp_dir(), 'unix_domain_socket')
fn testsuite_begin() { fn testsuite_begin() {
os.rm(test_port) or { } os.rm(test_port) or {}
} }
fn testsuite_end() { fn testsuite_end() {
os.rm(test_port) or { } os.rm(test_port) or {}
} }
fn handle_conn(mut c unix.StreamConn) { fn handle_conn(mut c unix.StreamConn) {
@ -30,13 +30,12 @@ fn echo_server(mut l unix.StreamListener) ? {
mut new_conn := l.accept() or { continue } mut new_conn := l.accept() or { continue }
go handle_conn(mut new_conn) go handle_conn(mut new_conn)
} }
return none
} }
fn echo() ? { fn echo() ? {
mut c := unix.connect_stream(test_port) ? mut c := unix.connect_stream(test_port) ?
defer { defer {
c.close() or { } c.close() or {}
} }
data := 'Hello from vlib/net!' data := 'Hello from vlib/net!'
c.write_string(data) ? c.write_string(data) ?
@ -47,12 +46,12 @@ fn echo() ? {
assert buf[i] == data[i] assert buf[i] == data[i]
} }
println('Got "$buf.bytestr()"') println('Got "$buf.bytestr()"')
return none return
} }
fn test_tcp() { fn test_tcp() {
mut l := unix.listen_stream(test_port) or { panic(err) } mut l := unix.listen_stream(test_port) or { panic(err) }
go echo_server(mut l) go echo_server(mut l)
echo() or { panic(err) } echo() or { panic(err) }
l.close() or { } l.close() or {}
} }

View File

@ -7,7 +7,7 @@ const (
// validate_port checks whether a port is valid // validate_port checks whether a port is valid
// and returns the port or an error // and returns the port or an error
pub fn validate_port(port int) ?u16 { pub fn validate_port(port int) ?u16 {
if port <= socket_max_port { if port <= net.socket_max_port {
return u16(port) return u16(port)
} else { } else {
return err_port_out_of_range return err_port_out_of_range
@ -19,6 +19,9 @@ pub fn split_address(addr string) ?(string, u16) {
port := addr.all_after_last(':').int() port := addr.all_after_last(':').int()
address := addr.all_before_last(':') address := addr.all_before_last(':')
p := validate_port(port)? // TODO(emily): Maybe do some more checking here
// to validate ipv6 address sanity?
p := validate_port(port) ?
return address, p return address, p
} }

View File

@ -493,14 +493,32 @@ pub fn (mut f File) write_str(s string) ? {
f.write_string(s) or { return err } f.write_string(s) or { return err }
} }
pub struct ErrFileNotOpened {
msg string = 'os: file not opened'
code int
}
pub struct ErrSizeOfTypeIs0 {
msg string = 'os: size of type is 0'
code int
}
fn error_file_not_opened() IError {
return IError(&ErrFileNotOpened{})
}
fn error_size_of_type_0() IError {
return IError(&ErrSizeOfTypeIs0{})
}
// read_struct reads a single struct of type `T` // read_struct reads a single struct of type `T`
pub fn (mut f File) read_struct<T>(mut t T) ? { pub fn (mut f File) read_struct<T>(mut t T) ? {
if !f.is_opened { if !f.is_opened {
return none return error_file_not_opened()
} }
tsize := int(sizeof(*t)) tsize := int(sizeof(*t))
if tsize == 0 { if tsize == 0 {
return none return error_size_of_type_0()
} }
nbytes := fread(t, 1, tsize, f.cfile) ? nbytes := fread(t, 1, tsize, f.cfile) ?
if nbytes != tsize { if nbytes != tsize {
@ -511,11 +529,11 @@ pub fn (mut f File) read_struct<T>(mut t T) ? {
// read_struct_at reads a single struct of type `T` at position specified in file // read_struct_at reads a single struct of type `T` at position specified in file
pub fn (mut f File) read_struct_at<T>(mut t T, pos u64) ? { pub fn (mut f File) read_struct_at<T>(mut t T, pos u64) ? {
if !f.is_opened { if !f.is_opened {
return none return error_file_not_opened()
} }
tsize := int(sizeof(*t)) tsize := int(sizeof(*t))
if tsize == 0 { if tsize == 0 {
return none return error_size_of_type_0()
} }
mut nbytes := 0 mut nbytes := 0
$if x64 { $if x64 {
@ -542,11 +560,11 @@ pub fn (mut f File) read_struct_at<T>(mut t T, pos u64) ? {
// read_raw reads and returns a single instance of type `T` // read_raw reads and returns a single instance of type `T`
pub fn (mut f File) read_raw<T>() ?T { pub fn (mut f File) read_raw<T>() ?T {
if !f.is_opened { if !f.is_opened {
return none return error_file_not_opened()
} }
tsize := int(sizeof(T)) tsize := int(sizeof(T))
if tsize == 0 { if tsize == 0 {
return none return error_size_of_type_0()
} }
mut t := T{} mut t := T{}
nbytes := fread(&t, 1, tsize, f.cfile) ? nbytes := fread(&t, 1, tsize, f.cfile) ?
@ -559,11 +577,11 @@ pub fn (mut f File) read_raw<T>() ?T {
// read_raw_at reads and returns a single instance of type `T` starting at file byte offset `pos` // read_raw_at reads and returns a single instance of type `T` starting at file byte offset `pos`
pub fn (mut f File) read_raw_at<T>(pos u64) ?T { pub fn (mut f File) read_raw_at<T>(pos u64) ?T {
if !f.is_opened { if !f.is_opened {
return none return error_file_not_opened()
} }
tsize := int(sizeof(T)) tsize := int(sizeof(T))
if tsize == 0 { if tsize == 0 {
return none return error_size_of_type_0()
} }
mut nbytes := 0 mut nbytes := 0
mut t := T{} mut t := T{}
@ -605,11 +623,11 @@ pub fn (mut f File) read_raw_at<T>(pos u64) ?T {
// write_struct writes a single struct of type `T` // write_struct writes a single struct of type `T`
pub fn (mut f File) write_struct<T>(t &T) ? { pub fn (mut f File) write_struct<T>(t &T) ? {
if !f.is_opened { if !f.is_opened {
return error('file is not opened') return error_file_not_opened()
} }
tsize := int(sizeof(T)) tsize := int(sizeof(T))
if tsize == 0 { if tsize == 0 {
return error('struct size is 0') return error_size_of_type_0()
} }
C.errno = 0 C.errno = 0
nbytes := int(C.fwrite(t, 1, tsize, f.cfile)) nbytes := int(C.fwrite(t, 1, tsize, f.cfile))
@ -624,11 +642,11 @@ pub fn (mut f File) write_struct<T>(t &T) ? {
// write_struct_at writes a single struct of type `T` at position specified in file // write_struct_at writes a single struct of type `T` at position specified in file
pub fn (mut f File) write_struct_at<T>(t &T, pos u64) ? { pub fn (mut f File) write_struct_at<T>(t &T, pos u64) ? {
if !f.is_opened { if !f.is_opened {
return error('file is not opened') return error_file_not_opened()
} }
tsize := int(sizeof(T)) tsize := int(sizeof(T))
if tsize == 0 { if tsize == 0 {
return error('struct size is 0') return error_size_of_type_0()
} }
C.errno = 0 C.errno = 0
mut nbytes := 0 mut nbytes := 0
@ -661,11 +679,11 @@ pub fn (mut f File) write_struct_at<T>(t &T, pos u64) ? {
// write_raw writes a single instance of type `T` // write_raw writes a single instance of type `T`
pub fn (mut f File) write_raw<T>(t &T) ? { pub fn (mut f File) write_raw<T>(t &T) ? {
if !f.is_opened { if !f.is_opened {
return error('file is not opened') return error_file_not_opened()
} }
tsize := int(sizeof(T)) tsize := int(sizeof(T))
if tsize == 0 { if tsize == 0 {
return error('struct size is 0') return error_size_of_type_0()
} }
C.errno = 0 C.errno = 0
nbytes := int(C.fwrite(t, 1, tsize, f.cfile)) nbytes := int(C.fwrite(t, 1, tsize, f.cfile))
@ -680,11 +698,11 @@ pub fn (mut f File) write_raw<T>(t &T) ? {
// write_raw_at writes a single instance of type `T` starting at file byte offset `pos` // write_raw_at writes a single instance of type `T` starting at file byte offset `pos`
pub fn (mut f File) write_raw_at<T>(t &T, pos u64) ? { pub fn (mut f File) write_raw_at<T>(t &T, pos u64) ? {
if !f.is_opened { if !f.is_opened {
return error('file is not opened') return error_file_not_opened()
} }
tsize := int(sizeof(T)) tsize := int(sizeof(T))
if tsize == 0 { if tsize == 0 {
return error('struct size is 0') return error_size_of_type_0()
} }
mut nbytes := 0 mut nbytes := 0

View File

@ -14,6 +14,20 @@ import picohttpparser
#flag @VEXEROOT/thirdparty/picoev/picoev.o #flag @VEXEROOT/thirdparty/picoev/picoev.o
#include "src/picoev.h" #include "src/picoev.h"
struct C.in_addr {
mut:
s_addr int
}
struct C.sockaddr_in {
mut:
sin_family int
sin_port int
sin_addr C.in_addr
}
struct C.sockaddr_storage {}
fn C.atoi() int fn C.atoi() int
fn C.strncasecmp(s1 &char, s2 &char, n size_t) int fn C.strncasecmp(s1 &char, s2 &char, n size_t) int
@ -194,7 +208,7 @@ fn default_err_cb(data voidptr, req picohttpparser.Request, mut res picohttppars
} }
pub fn new(config Config) &Picoev { pub fn new(config Config) &Picoev {
fd := C.socket(net.SocketFamily.inet, net.SocketType.tcp, 0) fd := C.socket(net.AddrFamily.ip, net.SocketType.tcp, 0)
assert fd != -1 assert fd != -1
flag := 1 flag := 1
assert C.setsockopt(fd, C.SOL_SOCKET, C.SO_REUSEADDR, &flag, sizeof(int)) == 0 assert C.setsockopt(fd, C.SOL_SOCKET, C.SO_REUSEADDR, &flag, sizeof(int)) == 0
@ -211,7 +225,7 @@ pub fn new(config Config) &Picoev {
addr.sin_port = C.htons(config.port) addr.sin_port = C.htons(config.port)
addr.sin_addr.s_addr = C.htonl(C.INADDR_ANY) addr.sin_addr.s_addr = C.htonl(C.INADDR_ANY)
size := 16 // sizeof(C.sockaddr_in) size := 16 // sizeof(C.sockaddr_in)
bind_res := C.bind(fd, &addr, size) bind_res := C.bind(fd, unsafe { &net.Addr(&addr) }, size)
assert bind_res == 0 assert bind_res == 0
listen_res := C.listen(fd, C.SOMAXCONN) listen_res := C.listen(fd, C.SOMAXCONN)
assert listen_res == 0 assert listen_res == 0

View File

@ -627,11 +627,10 @@ pub fn (t &Table) array_cname(elem_type Type) string {
pub fn (t &Table) array_fixed_name(elem_type Type, size int, size_expr Expr) string { pub fn (t &Table) array_fixed_name(elem_type Type, size int, size_expr Expr) string {
elem_type_sym := t.get_type_symbol(elem_type) elem_type_sym := t.get_type_symbol(elem_type)
ptr := if elem_type.is_ptr() { '&'.repeat(elem_type.nr_muls()) } else { '' } ptr := if elem_type.is_ptr() { '&'.repeat(elem_type.nr_muls()) } else { '' }
mut size_str := size.str() size_str := if size_expr is EmptyExpr || size != 987654321 {
if t.is_fmt { size.str()
if size_expr is Ident { } else {
size_str = size_expr.name size_expr.str()
}
} }
return '[$size_str]$ptr$elem_type_sym.name' return '[$size_str]$ptr$elem_type_sym.name'
} }
@ -829,7 +828,7 @@ pub fn (mut t Table) find_or_register_array_fixed(elem_type Type, size int, size
info: ArrayFixed{ info: ArrayFixed{
elem_type: elem_type elem_type: elem_type
size: size size: size
expr: size_expr size_expr: size_expr
} }
} }
return t.register_type_symbol(array_fixed_type) return t.register_type_symbol(array_fixed_type)
@ -1062,7 +1061,7 @@ pub fn (mut t Table) bitsize_to_type(bit_size int) Type {
if bit_size % 8 != 0 { // there is no way to do `i2131(32)` so this should never be reached if bit_size % 8 != 0 { // there is no way to do `i2131(32)` so this should never be reached
t.panic('compiler bug: bitsizes must be multiples of 8') t.panic('compiler bug: bitsizes must be multiples of 8')
} }
return new_type(t.find_or_register_array_fixed(byte_type, bit_size / 8, EmptyExpr{})) return new_type(t.find_or_register_array_fixed(byte_type, bit_size / 8, empty_expr()))
} }
} }
} }

View File

@ -809,8 +809,8 @@ pub mut:
pub struct ArrayFixed { pub struct ArrayFixed {
pub: pub:
size int size int
expr Expr // used by fmt for e.g. ´[my_const]byte´ size_expr Expr // used by fmt for e.g. ´[my_const]byte´
pub mut: pub mut:
elem_type Type elem_type Type
} }
@ -890,13 +890,11 @@ pub fn (t &Table) type_to_str_using_aliases(typ Type, import_aliases map[string]
.array_fixed { .array_fixed {
info := sym.info as ArrayFixed info := sym.info as ArrayFixed
elem_str := t.type_to_str_using_aliases(info.elem_type, import_aliases) elem_str := t.type_to_str_using_aliases(info.elem_type, import_aliases)
mut size_str := info.size.str() if info.size_expr is EmptyExpr {
if t.is_fmt { res = '[$info.size]$elem_str'
if info.expr is Ident { } else {
size_str = info.expr.name res = '[$info.size_expr]$elem_str'
}
} }
res = '[$size_str]$elem_str'
} }
.chan { .chan {
// TODO currently the `chan` struct in builtin is not considered a struct but a chan // TODO currently the `chan` struct in builtin is not considered a struct but a chan

View File

@ -19,7 +19,7 @@ pub fn (i &Inspector) visit(node ast.Node) ? {
if i.inspector_callback(node, i.data) { if i.inspector_callback(node, i.data) {
return return
} }
return none return error('')
} }
// inspect traverses and checks the AST node on a depth-first order and based on the data given // inspect traverses and checks the AST node on a depth-first order and based on the data given

View File

@ -22,7 +22,7 @@ fn (mut n NodeByOffset) visit(node ast.Node) ? {
node_pos := node.position() node_pos := node.position()
if n.pos >= node_pos.pos && n.pos <= node_pos.pos + node_pos.len && node !is ast.File { if n.pos >= node_pos.pos && n.pos <= node_pos.pos + node_pos.len && node !is ast.File {
n.node = node n.node = node
return none return error('')
} }
return return
} }

View File

@ -45,7 +45,7 @@ const (
fn (mut v Builder) find_win_cc() ? { fn (mut v Builder) find_win_cc() ? {
$if !windows { $if !windows {
return none return
} }
ccompiler_version_res := os.execute('$v.pref.ccompiler -v') ccompiler_version_res := os.execute('$v.pref.ccompiler -v')
if ccompiler_version_res.exit_code != 0 { if ccompiler_version_res.exit_code != 0 {
@ -63,7 +63,7 @@ fn (mut v Builder) find_win_cc() ? {
if v.pref.is_verbose { if v.pref.is_verbose {
println('tcc not found') println('tcc not found')
} }
return none return error('tcc not found')
} }
v.pref.ccompiler = thirdparty_tcc v.pref.ccompiler = thirdparty_tcc
v.pref.ccompiler_type = .tinyc v.pref.ccompiler_type = .tinyc

View File

@ -144,9 +144,8 @@ fn new_windows_kit(kit_root string, target_arch string) ?WindowsKit {
fn find_windows_kit_root_by_env(target_arch string) ?WindowsKit { fn find_windows_kit_root_by_env(target_arch string) ?WindowsKit {
kit_root := os.getenv('WindowsSdkDir') kit_root := os.getenv('WindowsSdkDir')
if kit_root == '' { if kit_root == '' {
return none return error('empty WindowsSdkDir')
} }
return new_windows_kit(kit_root, target_arch) return new_windows_kit(kit_root, target_arch)
} }
@ -210,12 +209,12 @@ fn find_vs_by_reg(vswhere_dir string, host_arch string, target_arch string) ?VsI
fn find_vs_by_env(host_arch string, target_arch string) ?VsInstallation { fn find_vs_by_env(host_arch string, target_arch string) ?VsInstallation {
vs_dir := os.getenv('VSINSTALLDIR') vs_dir := os.getenv('VSINSTALLDIR')
if vs_dir == '' { if vs_dir == '' {
return none return error('empty VSINSTALLDIR')
} }
vc_tools_dir := os.getenv('VCToolsInstallDir') vc_tools_dir := os.getenv('VCToolsInstallDir')
if vc_tools_dir == '' { if vc_tools_dir == '' {
return none return error('empty VCToolsInstallDir')
} }
bin_dir := '${vc_tools_dir}bin\\Host$host_arch\\$target_arch' bin_dir := '${vc_tools_dir}bin\\Host$host_arch\\$target_arch'

View File

@ -332,10 +332,9 @@ pub fn (mut c Checker) check_types(got ast.Type, expected ast.Type) bool {
} }
pub fn (mut c Checker) check_expected(got ast.Type, expected ast.Type) ? { pub fn (mut c Checker) check_expected(got ast.Type, expected ast.Type) ? {
if c.check_types(got, expected) { if !c.check_types(got, expected) {
return return error(c.expected_msg(got, expected))
} }
return error(c.expected_msg(got, expected))
} }
[inline] [inline]

View File

@ -3026,6 +3026,10 @@ pub fn (mut c Checker) return_stmt(mut node ast.Return) {
got_types_0_idx := got_types[0].idx() got_types_0_idx := got_types[0].idx()
if exp_is_optional if exp_is_optional
&& got_types_0_idx in [ast.none_type_idx, ast.error_type_idx, option_type_idx] { && got_types_0_idx in [ast.none_type_idx, ast.error_type_idx, option_type_idx] {
if got_types_0_idx == ast.none_type_idx && expected_type == ast.ovoid_type {
c.error('returning `none` in functions, that have a `?` result type is not allowed anymore, either `return error(message)` or just `return` instead',
node.pos)
}
return return
} }
if expected_types.len > 0 && expected_types.len != got_types.len { if expected_types.len > 0 && expected_types.len != got_types.len {
@ -3802,7 +3806,7 @@ pub fn (mut c Checker) array_init(mut array_init ast.ArrayInit) ast.Type {
} }
if array_init.is_fixed { if array_init.is_fixed {
idx := c.table.find_or_register_array_fixed(elem_type, array_init.exprs.len, idx := c.table.find_or_register_array_fixed(elem_type, array_init.exprs.len,
ast.EmptyExpr{}) ast.empty_expr())
if elem_type.has_flag(.generic) { if elem_type.has_flag(.generic) {
array_init.typ = ast.new_type(idx).set_flag(.generic) array_init.typ = ast.new_type(idx).set_flag(.generic)
} else { } else {
@ -7375,7 +7379,7 @@ fn (mut c Checker) trace(fbase string, message string) {
fn (mut c Checker) ensure_type_exists(typ ast.Type, pos token.Position) ? { fn (mut c Checker) ensure_type_exists(typ ast.Type, pos token.Position) ? {
if typ == 0 { if typ == 0 {
c.error('unknown type', pos) c.error('unknown type', pos)
return none return
} }
sym := c.table.get_type_symbol(typ) sym := c.table.get_type_symbol(typ)
match sym.kind { match sym.kind {
@ -7383,7 +7387,7 @@ fn (mut c Checker) ensure_type_exists(typ ast.Type, pos token.Position) ? {
if sym.language == .v && !sym.name.starts_with('C.') { if sym.language == .v && !sym.name.starts_with('C.') {
c.error(util.new_suggestion(sym.name, c.table.known_type_names()).say('unknown type `$sym.name`'), c.error(util.new_suggestion(sym.name, c.table.known_type_names()).say('unknown type `$sym.name`'),
pos) pos)
return none return
} }
} }
.int_literal, .float_literal { .int_literal, .float_literal {
@ -7396,7 +7400,7 @@ fn (mut c Checker) ensure_type_exists(typ ast.Type, pos token.Position) ? {
'unknown type `$sym.name`.\nDid you mean `f64`?' 'unknown type `$sym.name`.\nDid you mean `f64`?'
} }
c.error(msg, pos) c.error(msg, pos)
return none return
} }
} }
.array { .array {

View File

@ -5371,21 +5371,23 @@ fn (mut g Gen) struct_init(struct_init ast.StructInit) {
if info.is_union && struct_init.fields.len > 1 { if info.is_union && struct_init.fields.len > 1 {
verror('union must not have more than 1 initializer') verror('union must not have more than 1 initializer')
} }
for embed in info.embeds { if !info.is_union {
embed_sym := g.table.get_type_symbol(embed) for embed in info.embeds {
embed_name := embed_sym.embed_name() embed_sym := g.table.get_type_symbol(embed)
if embed_name !in inited_fields { embed_name := embed_sym.embed_name()
default_init := ast.StructInit{ if embed_name !in inited_fields {
typ: embed default_init := ast.StructInit{
typ: embed
}
g.write('.$embed_name = ')
g.struct_init(default_init)
if is_multiline {
g.writeln(',')
} else {
g.write(',')
}
initialized = true
} }
g.write('.$embed_name = ')
g.struct_init(default_init)
if is_multiline {
g.writeln(',')
} else {
g.write(',')
}
initialized = true
} }
} }
// g.zero_struct_fields(info, inited_fields) // g.zero_struct_fields(info, inited_fields)
@ -5691,6 +5693,22 @@ fn (mut g Gen) write_types(types []ast.TypeSymbol) {
} }
// TODO avoid buffer manip // TODO avoid buffer manip
start_pos := g.type_definitions.len start_pos := g.type_definitions.len
mut pre_pragma := ''
mut post_pragma := ''
for attr in typ.info.attrs {
match attr.name {
'_pack' {
pre_pragma += '#pragma pack(push, $attr.arg)\n'
post_pragma += '#pragma pack(pop)'
}
else {}
}
}
g.type_definitions.writeln(pre_pragma)
if typ.info.is_union { if typ.info.is_union {
g.type_definitions.writeln('union $name {') g.type_definitions.writeln('union $name {')
} else { } else {
@ -5726,12 +5744,13 @@ fn (mut g Gen) write_types(types []ast.TypeSymbol) {
} }
// g.type_definitions.writeln('} $name;\n') // g.type_definitions.writeln('} $name;\n')
// //
attrs := if typ.info.attrs.contains('packed') { ti_attrs := if typ.info.attrs.contains('packed') {
'__attribute__((__packed__))' '__attribute__((__packed__))'
} else { } else {
'' ''
} }
g.type_definitions.writeln('}$attrs;\n') g.type_definitions.writeln('}$ti_attrs;\n')
g.type_definitions.writeln(post_pragma)
} }
ast.Alias { ast.Alias {
// ast.Alias { TODO // ast.Alias { TODO
@ -5916,7 +5935,7 @@ fn (mut g Gen) write_expr_to_string(expr ast.Expr) string {
fn (mut g Gen) or_block(var_name string, or_block ast.OrExpr, return_type ast.Type) { fn (mut g Gen) or_block(var_name string, or_block ast.OrExpr, return_type ast.Type) {
cvar_name := c_name(var_name) cvar_name := c_name(var_name)
mr_styp := g.base_type(return_type) mr_styp := g.base_type(return_type)
is_none_ok := mr_styp == 'void' is_none_ok := return_type == ast.ovoid_type
g.writeln(';') g.writeln(';')
if is_none_ok { if is_none_ok {
g.writeln('if (${cvar_name}.state != 0 && ${cvar_name}.err._typ != _IError_None___index) {') g.writeln('if (${cvar_name}.state != 0 && ${cvar_name}.err._typ != _IError_None___index) {')

View File

@ -512,13 +512,13 @@ fn (mut p Parser) fn_receiver(mut params []ast.Param, mut rec ReceiverParsingInf
rec.typ = p.parse_type_with_mut(rec.is_mut) rec.typ = p.parse_type_with_mut(rec.is_mut)
if rec.typ.idx() == 0 { if rec.typ.idx() == 0 {
// error is set in parse_type // error is set in parse_type
return none return error('void receiver type')
} }
rec.type_pos = rec.type_pos.extend(p.prev_tok.position()) rec.type_pos = rec.type_pos.extend(p.prev_tok.position())
if is_amp && rec.is_mut { if is_amp && rec.is_mut {
p.error_with_pos('use `(mut f Foo)` or `(f &Foo)` instead of `(mut f &Foo)`', p.error_with_pos('use `(mut f Foo)` or `(f &Foo)` instead of `(mut f &Foo)`',
lpar_pos.extend(p.tok.position())) lpar_pos.extend(p.tok.position()))
return none return error('invalid `mut f &Foo`')
} }
if is_shared { if is_shared {
rec.typ = rec.typ.set_flag(.shared_f) rec.typ = rec.typ.set_flag(.shared_f)

View File

@ -13,31 +13,38 @@ pub fn (mut p Parser) parse_array_type() ast.Type {
if p.tok.kind in [.number, .name] { if p.tok.kind in [.number, .name] {
mut fixed_size := 0 mut fixed_size := 0
size_expr := p.expr(0) size_expr := p.expr(0)
match size_expr { if p.pref.is_fmt {
ast.IntegerLiteral { fixed_size = 987654321
fixed_size = size_expr.val.int() } else {
} match size_expr {
ast.Ident { ast.IntegerLiteral {
if const_field := p.global_scope.find_const('${p.mod}.$size_expr.name') { fixed_size = size_expr.val.int()
if const_field.expr is ast.IntegerLiteral { }
fixed_size = const_field.expr.val.int() ast.Ident {
mut show_non_const_error := false
if const_field := p.global_scope.find_const('${p.mod}.$size_expr.name') {
if const_field.expr is ast.IntegerLiteral {
fixed_size = const_field.expr.val.int()
} else {
show_non_const_error = true
}
} else { } else {
p.error_with_pos('non-constant array bound `$size_expr.name`', if p.pref.is_fmt {
size_expr.pos) // for vfmt purposes, pretend the constant does exist
// it may have been defined in another .v file:
fixed_size = 1
} else {
show_non_const_error = true
}
} }
} else { if show_non_const_error {
if p.pref.is_fmt {
// for vfmt purposes, pretend the constant does exist, it may have
// been defined in another .v file:
fixed_size = 1
} else {
p.error_with_pos('non-constant array bound `$size_expr.name`', p.error_with_pos('non-constant array bound `$size_expr.name`',
size_expr.pos) size_expr.pos)
} }
} }
} else {
else { p.error('expecting `int` for fixed size')
p.error('expecting `int` for fixed size') }
} }
} }
p.check(.rsbr) p.check(.rsbr)

View File

@ -303,7 +303,8 @@ pub fn run<T>(global_app &T, port int) {
// mut app := &T{} // mut app := &T{}
// run_app<T>(mut app, port) // run_app<T>(mut app, port)
mut l := net.listen_tcp(port) or { panic('failed to listen') } mut l := net.listen_tcp(.ip6, ':$port') or { panic('failed to listen $err.code $err') }
println('[Vweb] Running app on http://localhost:$port') println('[Vweb] Running app on http://localhost:$port')
// app.Context = Context{ // app.Context = Context{
// conn: 0 // conn: 0

View File

@ -83,7 +83,6 @@ fn (mut ws Client) shutdown_socket() ? {
} else { } else {
ws.conn.close() ? ws.conn.close() ?
} }
return none
} }
// dial_socket connects tcp socket and initializes default configurations // dial_socket connects tcp socket and initializes default configurations

View File

@ -4,7 +4,7 @@ module main
import x.websocket import x.websocket
fn main() { fn main() {
mut s := websocket.new_server(9002, '/') mut s := websocket.new_server(.ip6, 9002, '/')
s.on_message(on_message) s.on_message(on_message)
s.listen() or { panic(err) } s.listen() or { panic(err) }
} }

View File

@ -15,6 +15,7 @@ mut:
message_callbacks []MessageEventHandler // new message callback functions message_callbacks []MessageEventHandler // new message callback functions
close_callbacks []CloseEventHandler // close message callback functions close_callbacks []CloseEventHandler // close message callback functions
pub: pub:
family net.AddrFamily = .ip
port int // port used as listen to incoming connections port int // port used as listen to incoming connections
is_ssl bool // true if secure connection (not supported yet on server) is_ssl bool // true if secure connection (not supported yet on server)
pub mut: pub mut:
@ -34,9 +35,10 @@ pub mut:
} }
// new_server instance a new websocket server on provided port and route // new_server instance a new websocket server on provided port and route
pub fn new_server(port int, route string) &Server { pub fn new_server(family net.AddrFamily, port int, route string) &Server {
return &Server{ return &Server{
ls: 0 ls: 0
family: family
port: port port: port
logger: &log.Log{ logger: &log.Log{
level: .info level: .info
@ -53,7 +55,7 @@ pub fn (mut s Server) set_ping_interval(seconds int) {
// listen start listen and process to incoming connections from websocket clients // listen start listen and process to incoming connections from websocket clients
pub fn (mut s Server) listen() ? { pub fn (mut s Server) listen() ? {
s.logger.info('websocket server: start listen on port $s.port') s.logger.info('websocket server: start listen on port $s.port')
s.ls = net.listen_tcp(s.port) ? s.ls = net.listen_tcp(s.family, ':$s.port') ?
s.set_state(.open) s.set_state(.open)
go s.handle_ping() go s.handle_ping()
for { for {

View File

@ -1,31 +1,51 @@
import os import os
import net
import x.websocket import x.websocket
import time import time
import rand import rand
// TODO: fix connecting to ipv4 websockets
// (the server seems to work with .ip, but
// Client can not connect, it needs to be passed
// .ip too?)
struct WebsocketTestResults { struct WebsocketTestResults {
pub mut: pub mut:
nr_messages int nr_messages int
nr_pong_received int nr_pong_received int
} }
// Do not run these tests everytime, since they are flaky.
// They have their own specialized CI runner.
const github_job = os.getenv('GITHUB_JOB') const github_job = os.getenv('GITHUB_JOB')
const should_skip = github_job != '' && github_job != 'websocket_tests'
// tests with internal ws servers // tests with internal ws servers
fn test_ws() { fn test_ws_ipv6() {
if github_job != '' && github_job != 'websocket_tests' { if should_skip {
// Do not run these tests everytime, since they are flaky.
// They have their own specialized CI runner.
return return
} }
port := 30000 + rand.intn(1024) port := 30000 + rand.intn(1024)
go start_server(port) go start_server(.ip6, port)
time.sleep(500 * time.millisecond) time.sleep(500 * time.millisecond)
ws_test('ws://localhost:$port') or { assert false } ws_test(.ip6, 'ws://localhost:$port') or { assert false }
} }
fn start_server(listen_port int) ? { // tests with internal ws servers
mut s := websocket.new_server(listen_port, '') fn test_ws_ipv4() {
// TODO: fix client
if true || should_skip {
return
}
port := 30000 + rand.intn(1024)
go start_server(.ip, port)
time.sleep(500 * time.millisecond)
ws_test(.ip, 'ws://localhost:$port') or { assert false }
}
fn start_server(family net.AddrFamily, listen_port int) ? {
mut s := websocket.new_server(family, listen_port, '')
// make that in execution test time give time to execute at least one time // make that in execution test time give time to execute at least one time
s.ping_interval = 1 s.ping_interval = 1
@ -52,7 +72,7 @@ fn start_server(listen_port int) ? {
} }
// ws_test tests connect to the websocket server from websocket client // ws_test tests connect to the websocket server from websocket client
fn ws_test(uri string) ? { fn ws_test(family net.AddrFamily, uri string) ? {
eprintln('connecting to $uri ...') eprintln('connecting to $uri ...')
mut test_results := WebsocketTestResults{} mut test_results := WebsocketTestResults{}