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

vlib/net: add buffered IO, x.net -> net (#6754)

This commit is contained in:
Emily Hudson
2020-11-15 20:54:47 +00:00
committed by GitHub
parent 20bec81678
commit cd2a2cef25
55 changed files with 741 additions and 1648 deletions

100
vlib/net/aasocket.c.v Normal file
View File

@@ -0,0 +1,100 @@
module net
// Select represents a select operation
enum Select {
read write except
}
// SocketType are the available sockets
pub enum SocketType {
udp = C.SOCK_DGRAM
tcp = C.SOCK_STREAM
}
// SocketFamily are the available address families
pub enum SocketFamily {
inet = C. AF_INET
}
struct C.in_addr {
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.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() int
fn C.setsockopt() int
fn C.htonl() int
fn C.htons() int
fn C.bind() int
fn C.listen() int
fn C.accept() int
fn C.getaddrinfo() int
fn C.connect() int
fn C.send() int
fn C.sendto() int
fn C.recv() int
fn C.recvfrom() int
fn C.shutdown() int
fn C.ntohs() int
fn C.getpeername() int
fn C.inet_ntop(af int, src voidptr, dst charptr, dst_size int) charptr
fn C.WSAAddressToStringA() int
fn C.getsockname() int
// defined in builtin
// fn C.read() int
// fn C.close() int
fn C.ioctlsocket() int
fn C.fcntl() int
fn C.@select() int
fn C.FD_ZERO()
fn C.FD_SET()
fn C.FD_ISSET() bool
[typedef]
pub struct C.fd_set {}

81
vlib/net/address.v Normal file
View File

@@ -0,0 +1,81 @@
module net
// Addr represents an ip address
pub struct Addr {
addr C.sockaddr
len int
pub:
saddr string
port int
}
pub fn (a Addr) str() string {
return '${a.saddr}:${a.port}'
}
const (
max_ipv4_addr_len = 24
ipv4_addr_size = sizeof(C.sockaddr_in)
)
fn new_addr(addr C.sockaddr) ?Addr {
addr_len := if addr.sa_family == SocketFamily.inet {
sizeof(C.sockaddr)
} else {
// TODO NOOOOOOOOOOOO
0
}
// Convert to string representation
buf := []byte{ len: max_ipv4_addr_len, init: 0 }
$if windows {
res := C.WSAAddressToStringA(&addr, addr_len, C.NULL, buf.data, &buf.len)
if res != 0 {
socket_error(-1)?
}
} $else {
res := C.inet_ntop(SocketFamily.inet, &addr, buf.data, buf.len)
if res == 0 {
socket_error(-1)?
}
}
mut saddr := buf.bytestr()
hport := (&C.sockaddr_in(&addr)).sin_port
port := C.ntohs(hport)
$if windows {
// 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 {
address, port := split_address(addr)?
mut hints := C.addrinfo{}
hints.ai_family = family
hints.ai_socktype = typ
hints.ai_flags = C.AI_PASSIVE
hints.ai_protocol = 0
hints.ai_addrlen = 0
hints.ai_canonname = C.NULL
hints.ai_addr = C.NULL
hints.ai_next = C.NULL
info := &C.addrinfo(0)
sport := '$port'
// This might look silly but is recommended by MSDN
$if windows {
socket_error(0-C.getaddrinfo(address.str, sport.str, &hints, &info))?
} $else {
x := C.getaddrinfo(address.str, sport.str, &hints, &info)
wrap_error(x)?
}
return new_addr(*info.ai_addr)
}

122
vlib/net/common.v Normal file
View File

@@ -0,0 +1,122 @@
module net
import time
// Shutdown shutsdown a socket and closes it
fn shutdown(handle int) ? {
$if windows {
C.shutdown(handle, C.SD_BOTH)
socket_error(C.closesocket(handle))?
} $else {
C.shutdown(handle, C.SHUT_RDWR)
socket_error(C.close(handle))?
}
return none
}
// Select waits for an io operation (specified by parameter `test`) to be available
fn @select(handle int, test Select, timeout time.Duration) ?bool {
set := C.fd_set{}
C.FD_ZERO(&set)
C.FD_SET(handle, &set)
seconds := timeout.milliseconds() / 1000
microseconds := timeout - (seconds * time.second)
mut tt := C.timeval{
tv_sec: u64(seconds)
tv_usec: u64(microseconds)
}
mut timeval_timeout := &tt
// infinite timeout is signaled by passing null as the timeout to
// select
if timeout == infinite_timeout {
timeval_timeout = &C.timeval(0)
}
match test {
.read {
socket_error(C.@select(handle+1, &set, C.NULL, C.NULL, timeval_timeout))?
}
.write {
socket_error(C.@select(handle+1, C.NULL, &set, C.NULL, timeval_timeout))?
}
.except {
socket_error(C.@select(handle+1, C.NULL, C.NULL, &set, timeval_timeout))?
}
}
return C.FD_ISSET(handle, &set)
}
// wait_for_common wraps the common wait code
fn wait_for_common(
handle int,
deadline time.Time,
timeout time.Duration,
test Select) ? {
if deadline.unix == 0 {
// only accept infinite_timeout as a valid
// negative timeout - it is handled in @select however
if timeout < 0 && timeout != infinite_timeout {
return err_timed_out
}
ready := @select(handle, test, timeout)?
if ready {
return none
}
return err_timed_out
}
// Convert the deadline into a timeout
// and use that
d_timeout := deadline.unix - time.now().unix
if d_timeout < 0 {
// deadline is in the past so this has already
// timed out
return err_timed_out
}
ready := @select(handle, test, d_timeout)?
if ready {
return none
}
return err_timed_out
}
// wait_for_write waits for a write io operation to be available
fn wait_for_write(
handle int,
deadline time.Time,
timeout time.Duration) ? {
return wait_for_common(handle, deadline, timeout, .write)
}
// wait_for_read waits for a read io operation to be available
fn wait_for_read(
handle int,
deadline time.Time,
timeout time.Duration) ? {
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)
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)
const (
infinite_timeout = time.Duration(-1)
)

59
vlib/net/errors.v Normal file
View File

@@ -0,0 +1,59 @@
module net
// Well defined errors that are returned from socket functions
const (
errors_base = 0
err_new_socket_failed = error_with_code('net: new_socket failed to create socket', errors_base+1)
err_option_not_settable = error_with_code('net: set_option_xxx option not settable', errors_base+2)
err_option_wrong_type = error_with_code('net: set_option_xxx option wrong type', errors_base+3)
err_port_out_of_range = error_with_code('', errors_base+5)
err_no_udp_remote = error_with_code('', errors_base+6)
err_connect_failed = error_with_code('net: connect failed', errors_base+7)
err_connect_timed_out = error_with_code('net: connect timed out', errors_base+8)
err_timed_out = error_with_code('net: op timed out', errors_base+9)
err_timed_out_code = errors_base+9
)
pub fn socket_error(potential_code int) ?int {
$if windows {
if potential_code < 0 {
last_error_int := C.WSAGetLastError()
last_error := wsa_error(last_error_int)
return error_with_code('net: socket error: ($last_error_int) $last_error', last_error)
}
}
$else {
if potential_code < 0 {
last_error := error_code()
return error_with_code('net: socket error: $last_error', last_error)
}
}
return potential_code
}
pub fn wrap_error(error_code int) ? {
$if windows {
enum_error := wsa_error(error_code)
return error_with_code('socket error: $enum_error', error_code)
}
$else {
if error_code == 0 {
return
}
return error_with_code('net: socket error: $error_code', error_code)
}
}
// wrap_read_result takes a read result and sees if it is 0 for graceful
// connection termination and returns none
// e.g. res := wrap_read_result(C.recv(c.sock.handle, buf_ptr, len, 0))?
[inline]
fn wrap_read_result(result int) ?int {
if result > 0 || result < 0 {
return result
}
return none
}

View File

@@ -1,3 +1,5 @@
module ftp
/*
basic ftp module
RFC-959
@@ -15,9 +17,9 @@ basic ftp module
dtp.close()
ftp.close()
*/
module ftp
import net
import io
const (
connected = 220
@@ -35,33 +37,26 @@ const (
struct DTP {
mut:
sock net.Socket
conn net.TcpConn
reader io.BufferedReader
ip string
port int
}
fn (dtp DTP) read() []byte {
mut data := []byte{}
for {
buf, len := dtp.sock.recv(1024)
if len == 0 {
break
}
for i in 0 .. len {
data << unsafe {buf[i]}
}
unsafe {free(buf)}
}
fn (mut dtp DTP) read() ?[]byte {
mut data := []byte{len: 1024}
dtp.reader.read(mut data)?
return data
}
fn (dtp DTP) close() {
dtp.sock.close() or { }
dtp.conn.close()
}
struct FTP {
mut:
sock net.Socket
conn net.TcpConn
reader io.BufferedReader
buffer_size int
}
@@ -71,18 +66,15 @@ pub fn new() FTP {
return f
}
fn (ftp FTP) write(data string) ?int {
fn (mut ftp FTP) write(data string) ? {
$if debug {
println('FTP.v >>> $data')
}
n := ftp.sock.send_string('$data\r\n') or {
return error('Cannot send data')
}
return n
ftp.conn.write('$data\r\n'.bytes())?
}
fn (ftp FTP) read() (int, string) {
mut data := ftp.sock.read_line()
fn (mut ftp FTP) read() ?(int, string) {
mut data := ftp.reader.read_line()?
$if debug {
println('FTP.v <<< $data')
}
@@ -92,7 +84,7 @@ fn (ftp FTP) read() (int, string) {
code := data[..3].int()
if data[3] == `-` {
for {
data = ftp.sock.read_line()
data = ftp.reader.read_line()?
if data[..3].int() == code && data[3] != `-` {
break
}
@@ -101,26 +93,25 @@ fn (ftp FTP) read() (int, string) {
return code, data
}
pub fn (mut ftp FTP) connect(ip string) bool {
sock := net.dial(ip, 21) or {
return false
}
ftp.sock = sock
code, _ := ftp.read()
pub fn (mut ftp FTP) connect(ip string) ?bool {
conn := net.dial_tcp('$ip:21')?
ftp.conn = conn
code, _ := ftp.read()?
if code == connected {
return true
}
ftp.reader = io.new_buffered_reader(reader: io.make_reader(ftp.conn))
return false
}
pub fn (ftp FTP) login(user string, passwd string) bool {
pub fn (mut ftp FTP) login(user string, passwd string) ?bool {
ftp.write('USER $user') or {
$if debug {
println('ERROR sending user')
}
return false
}
mut code, _ := ftp.read()
mut code, _ := ftp.read()?
if code == logged_in {
return true
}
@@ -133,24 +124,21 @@ pub fn (ftp FTP) login(user string, passwd string) bool {
}
return false
}
code, _ = ftp.read()
code, _ = ftp.read()?
if code == logged_in {
return true
}
return false
}
pub fn (ftp FTP) close() {
send_quit := 'QUIT\r\n'
ftp.sock.send_string(send_quit) or { }
ftp.sock.close() or { }
pub fn ( mut ftp FTP) close() ? {
ftp.write('QUIT')?
ftp.conn.close()
}
pub fn (ftp FTP) pwd() string {
ftp.write('PWD') or {
return ''
}
_, data := ftp.read()
pub fn ( mut ftp FTP) pwd() ?string {
ftp.write('PWD')?
_, data := ftp.read()?
spl := data.split('"') // "
if spl.len >= 2 {
return spl[1]
@@ -158,11 +146,11 @@ pub fn (ftp FTP) pwd() string {
return data
}
pub fn (ftp FTP) cd(dir string) {
pub fn ( mut ftp FTP) cd(dir string) ? {
ftp.write('CWD $dir') or {
return
}
mut code, mut data := ftp.read()
mut code, mut data := ftp.read()?
match int(code) {
denied {
$if debug {
@@ -170,7 +158,7 @@ pub fn (ftp FTP) cd(dir string) {
}
}
complete {
code, data = ftp.read()
code, data = ftp.read()?
}
else {}
}
@@ -184,20 +172,21 @@ fn new_dtp(msg string) ?DTP {
return error('Bad message')
}
ip, port := get_host_ip_from_dtp_message(msg)
sock := net.dial(ip, port) or {
conn := net.dial_tcp('$ip:$port') or {
return error('Cannot connect to the data channel')
}
dtp := DTP{
sock: sock
conn: conn
ip: ip
port: port
}
return dtp
}
fn (ftp FTP) pasv() ?DTP {
ftp.write('PASV') or { }
code, data := ftp.read()
fn ( mut ftp FTP) pasv() ?DTP {
ftp.write('PASV') or {
}
code, data := ftp.read()?
$if debug {
println('pass: $data')
}
@@ -208,20 +197,21 @@ fn (ftp FTP) pasv() ?DTP {
return dtp
}
pub fn (ftp FTP) dir() ?[]string {
dtp := ftp.pasv() or {
pub fn ( mut ftp FTP) dir() ?[]string {
mut dtp := ftp.pasv() or {
return error('cannot establish data connection')
}
ftp.write('LIST') or { }
code, _ := ftp.read()
ftp.write('LIST') or {
}
code, _ := ftp.read()?
if code == denied {
return error('LIST denied')
}
if code != open_data_connection {
return error('data channel empty')
}
list_dir := dtp.read()
result, _ := ftp.read()
list_dir := dtp.read()?
result, _ := ftp.read()?
if result != close_data_connection {
println('LIST not ok')
}
@@ -237,19 +227,20 @@ pub fn (ftp FTP) dir() ?[]string {
return dir
}
pub fn (ftp FTP) get(file string) ?[]byte {
dtp := ftp.pasv() or {
pub fn (mut ftp FTP) get(file string) ?[]byte {
mut dtp := ftp.pasv() or {
return error('Cannot stablish data connection')
}
ftp.write('RETR $file') or { }
code, _ := ftp.read()
ftp.write('RETR $file') or {
}
code, _ := ftp.read()?
if code == denied {
return error('Permission denied')
}
if code != open_data_connection {
return error('Data connection not ready')
}
blob := dtp.read()
blob := dtp.read()?
dtp.close()
return blob
}

View File

@@ -3,15 +3,17 @@ import net.ftp
// NB: this function makes network calls to external servers,
// that is why it is not a very good idea to run it in CI.
// If you want to run it manually, use `v -d network vlib/net/ftp/ftp_test.v`
fn test_ftp_client() {
fn ftp_client_test_inside() ? {
$if !network ? { return }
mut ftp := ftp.new()
defer {
ftp.close()
}
assert ftp.connect('ftp.redhat.com')
assert ftp.login('ftp', 'ftp')
pwd := ftp.pwd()
connect_result := ftp.connect('ftp.redhat.com')?
assert connect_result
login_result := ftp.login('ftp', 'ftp')?
assert login_result
pwd := ftp.pwd()?
assert pwd.len > 0
ftp.cd('/')
dir_list1 := ftp.dir() or {
@@ -31,3 +33,10 @@ fn test_ftp_client() {
}
assert blob.len > 0
}
fn test_ftp_cleint() {
ftp_client_test_inside() or {
panic(err)
}
}

View File

@@ -5,8 +5,9 @@ module http
import net.urllib
import net.http.chunked
import strings
import net
import time
import io
const (
max_redirects = 4
@@ -276,7 +277,7 @@ fn (req &Request) method_and_url_to_response(method Method, url urllib.URL) ?Res
return res
} else if scheme == 'http' {
// println('http_do( $nport, $method, $host_name, $path )')
res := req.http_do(nport, method, host_name, path) ?
res := req.http_do('$host_name:$nport', method, path)?
return res
}
return error('http.request.method_and_url_to_response: unsupported scheme: "$scheme"')
@@ -392,24 +393,16 @@ pub fn escape(s string) string {
panic('http.escape() was replaced with http.escape_url()')
}
fn (req &Request) http_do(port int, method Method, host_name string, path string) ?Response {
rbuffer := [bufsize]byte{}
mut sb := strings.new_builder(100)
fn (req &Request) http_do(host string, method Method, path string) ?Response {
host_name, _ := net.split_address(host)?
s := req.build_request_headers(method, host_name, path)
client := net.dial(host_name, port) ?
client.send(s.str, s.len) or { }
for {
readbytes := client.crecv(rbuffer, bufsize)
if readbytes < 0 {
return error('http.request.http_do: error reading response. readbytes=$readbytes')
}
if readbytes == 0 {
break
}
sb.write(tos(rbuffer, readbytes))
}
client.close() or { }
return parse_response(sb.str())
mut client := net.dial_tcp(host)?
// TODO this really needs to be exposed somehow
client.set_read_timeout(time.second * 30)
client.write(s.bytes())?
mut bytes := io.read_all(client)?
client.close()
return parse_response(bytes.bytestr())
}
pub fn (req &Request) referer() string {

View File

@@ -1,37 +0,0 @@
module net
#flag -lws2_32
#include <winsock2.h>
#include <ws2tcpip.h>
struct C.WSAData {
mut:
wVersion u16
wHighVersion u16
szDescription [257]byte
szSystemStatus [129]byte
iMaxSockets u16
iMaxUdpDg u16
lpVendorInfo byteptr
}
const (
wsa_v22 = 0x202 // C.MAKEWORD(2, 2)
)
fn init() {
mut wsadata := C.WSAData{}
res := C.WSAStartup(wsa_v22, &wsadata)
if res != 0 {
panic('socket: WSAStartup failed')
}
}
fn error_code() int {
return C.WSAGetLastError()
}
pub const (
msg_nosignal = 0
)

View File

@@ -1,16 +0,0 @@
module net
fn C.gethostname() int
// hostname returns the host name reported by the kernel.
pub fn hostname() ?string {
mut name := [256]byte{}
// https://www.ietf.org/rfc/rfc1035.txt
// The host name is returned as a null-terminated string.
namebp := byteptr(name)
res := C.gethostname(namebp, 256)
if res != 0 {
return error('net.hostname: failed with $res')
}
return tos_clone(namebp)
}

View File

@@ -1,17 +1,28 @@
module net
#include <sys/socket.h>
#include <unistd.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <sys/select.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <netdb.h>
#include <errno.h>
#include <fcntl.h>
fn error_code() int {
return C.errno
}
fn init() {
}
pub const (
msg_nosignal = 0x4000
)
const (
error_ewouldblock = C.EWOULDBLOCK
)
#flag solaris -lsocket

868
vlib/net/net_windows.c.v Normal file
View File

@@ -0,0 +1,868 @@
module net
// WsaError is all of the socket errors that WSA provides from WSAGetLastError
pub enum WsaError {
//
// MessageId: WSAEINTR
//
// MessageText:
//
// A blocking operation was interrupted by a call to WSACancelBlockingCall.
//
wsaeintr = 10004
//
// MessageId: WSAEBADF
//
// MessageText:
//
// The file handle supplied is not valid.
//
wsaebadf = 10009
//
// MessageId: WSAEACCES
//
// MessageText:
//
// An attempt was made to access a socket in a way forbidden by its access permissions.
//
wsaeacces = 10013
//
// MessageId: WSAEFAULT
//
// MessageText:
//
// The system detected an invalid pointer address in attempting to use a pointer argument in a call.
//
wsaefault = 10014
//
// MessageId: WSAEINVAL
//
// MessageText:
//
// An invalid argument was supplied.
//
wsaeinval = 10022
//
// MessageId: WSAEMFILE
//
// MessageText:
//
// Too many open sockets.
//
wsaemfile = 10024
//
// MessageId: WSAEWOULDBLOCK
//
// MessageText:
//
// A non-blocking socket operation could not be completed immediately.
//
wsaewouldblock = 10035
//
// MessageId: WSAEINPROGRESS
//
// MessageText:
//
// A blocking operation is currently executing.
//
wsaeinprogress = 10036
//
// MessageId: WSAEALREADY
//
// MessageText:
//
// An operation was attempted on a non-blocking socket that already had an operation in progress.
//
wsaealready = 10037
//
// MessageId: WSAENOTSOCK
//
// MessageText:
//
// An operation was attempted on something that is not a socket.
//
wsaenotsock = 10038
//
// MessageId: WSAEDESTADDRREQ
//
// MessageText:
//
// A required address was omitted from an operation on a socket.
//
wsaedestaddrreq = 10039
//
// MessageId: WSAEMSGSIZE
//
// MessageText:
//
// A message sent on a datagram socket was larger than the internal message buffer or some other network limit, or the buffer used to receive a datagram into was smaller than the datagram itself.
//
wsaemsgsize = 10040
//
// MessageId: WSAEPROTOTYPE
//
// MessageText:
//
// A protocol was specified in the socket function call that does not support the semantics of the socket type requested.
//
wsaeprototype = 10041
//
// MessageId: WSAENOPROTOOPT
//
// MessageText:
//
// An unknown, invalid, or unsupported option or level was specified in a getsockopt or setsockopt call.
//
wsaenoprotoopt = 10042
//
// MessageId: WSAEPROTONOSUPPORT
//
// MessageText:
//
// The requested protocol has not been configured into the system, or no implementation for it exists.
//
wsaeprotonosupport = 10043
//
// MessageId: WSAESOCKTNOSUPPORT
//
// MessageText:
//
// The support for the specified socket type does not exist in this address family.
//
wsaesocktnosupport = 10044
//
// MessageId: WSAEOPNOTSUPP
//
// MessageText:
//
// The attempted operation is not supported for the type of object referenced.
//
wsaeopnotsupp = 10045
//
// MessageId: WSAEPFNOSUPPORT
//
// MessageText:
//
// The protocol family has not been configured into the system or no implementation for it exists.
//
wsaepfnosupport = 10046
//
// MessageId: WSAEAFNOSUPPORT
//
// MessageText:
//
// An address incompatible with the requested protocol was used.
//
wsaeafnosupport = 10047
//
// MessageId: WSAEADDRINUSE
//
// MessageText:
//
// Only one usage of each socket address (protocol/network address/port) is normally permitted.
//
wsaeaddrinuse = 10048
//
// MessageId: WSAEADDRNOTAVAIL
//
// MessageText:
//
// The requested address is not valid in its context.
//
wsaeaddrnotavail = 10049
//
// MessageId: WSAENETDOWN
//
// MessageText:
//
// A socket operation encountered a dead network.
//
wsaenetdown = 10050
//
// MessageId: WSAENETUNREACH
//
// MessageText:
//
// A socket operation was attempted to an unreachable network.
//
wsaenetunreach = 10051
//
// MessageId: WSAENETRESET
//
// MessageText:
//
// The connection has been broken due to keep-alive activity detecting a failure while the operation was in progress.
//
wsaenetreset = 10052
//
// MessageId: WSAECONNABORTED
//
// MessageText:
//
// An established connection was aborted by the software in your host machine.
//
wsaeconnaborted = 10053
//
// MessageId: WSAECONNRESET
//
// MessageText:
//
// An existing connection was forcibly closed by the remote host.
//
wsaeconnreset = 10054
//
// MessageId: WSAENOBUFS
//
// MessageText:
//
// An operation on a socket could not be performed because the system lacked sufficient buffer space or because a queue was full.
//
wsaenobufs = 10055
//
// MessageId: WSAEISCONN
//
// MessageText:
//
// A connect request was made on an already connected socket.
//
wsaeisconn = 10056
//
// MessageId: WSAENOTCONN
//
// MessageText:
//
// A request to send or receive data was disallowed because the socket is not connected and (when sending on a datagram socket using a sendto call) no address was supplied.
//
wsaenotconn = 10057
//
// MessageId: WSAESHUTDOWN
//
// MessageText:
//
// A request to send or receive data was disallowed because the socket had already been shut down in that direction with a previous shutdown call.
//
wsaeshutdown = 10058
//
// MessageId: WSAETOOMANYREFS
//
// MessageText:
//
// Too many references to some kernel object.
//
wsaetoomanyrefs = 10059
//
// MessageId: WSAETIMEDOUT
//
// MessageText:
//
// A connection attempt failed because the connected party did not properly respond after a period of time, or established connection failed because connected host has failed to respond.
//
wsaetimedout = 10060
//
// MessageId: WSAECONNREFUSED
//
// MessageText:
//
// No connection could be made because the target machine actively refused it.
//
wsaeconnrefused = 10061
//
// MessageId: WSAELOOP
//
// MessageText:
//
// Cannot translate name.
//
wsaeloop = 10062
//
// MessageId: WSAENAMETOOLONG
//
// MessageText:
//
// Name component or name was too long.
//
wsaenametoolong = 10063
//
// MessageId: WSAEHOSTDOWN
//
// MessageText:
//
// A socket operation failed because the destination host was down.
//
wsaehostdown = 10064
//
// MessageId: WSAEHOSTUNREACH
//
// MessageText:
//
// A socket operation was attempted to an unreachable host.
//
wsaehostunreach = 10065
//
// MessageId: WSAENOTEMPTY
//
// MessageText:
//
// Cannot remove a directory that is not empty.
//
wsaenotempty = 10066
//
// MessageId: WSAEPROCLIM
//
// MessageText:
//
// A Windows Sockets implementation may have a limit on the number of applications that may use it simultaneously.
//
wsaeproclim = 10067
//
// MessageId: WSAEUSERS
//
// MessageText:
//
// Ran out of quota.
//
wsaeusers = 10068
//
// MessageId: WSAEDQUOT
//
// MessageText:
//
// Ran out of disk quota.
//
wsaedquot = 10069
//
// MessageId: WSAESTALE
//
// MessageText:
//
// File handle reference is no longer available.
//
wsaestale = 10070
//
// MessageId: WSAEREMOTE
//
// MessageText:
//
// Item is not available locally.
//
wsaeremote = 10071
//
// MessageId: WSASYSNOTREADY
//
// MessageText:
//
// WSAStartup cannot function at this time because the underlying system it uses to provide network services is currently unavailable.
//
wsasysnotready = 10091
//
// MessageId: WSAVERNOTSUPPORTED
//
// MessageText:
//
// The Windows Sockets version requested is not supported.
//
wsavernotsupported = 10092
//
// MessageId: WSANOTINITIALISED
//
// MessageText:
//
// Either the application has not called WSAStartup, or WSAStartup failed.
//
wsanotinitialised = 10093
//
// MessageId: WSAEDISCON
//
// MessageText:
//
// Returned by WSARecv or WSARecvFrom to indicate the remote party has initiated a graceful shutdown sequence.
//
wsaediscon = 10101
//
// MessageId: WSAENOMORE
//
// MessageText:
//
// No more results can be returned by WSALookupServiceNext.
//
wsaenomore = 10102
//
// MessageId: WSAECANCELLED
//
// MessageText:
//
// A call to WSALookupServiceEnd was made while this call was still processing. The call has been canceled.
//
wsaecancelled = 10103
//
// MessageId: WSAEINVALIDPROCTABLE
//
// MessageText:
//
// The procedure call table is invalid.
//
wsaeinvalidproctable = 10104
//
// MessageId: WSAEINVALIDPROVIDER
//
// MessageText:
//
// The requested service provider is invalid.
//
wsaeinvalidprovider = 10105
//
// MessageId: WSAEPROVIDERFAILEDINIT
//
// MessageText:
//
// The requested service provider could not be loaded or initialized.
//
wsaeproviderfailedinit = 10106
//
// MessageId: WSASYSCALLFAILURE
//
// MessageText:
//
// A system call has failed.
//
wsasyscallfailure = 10107
//
// MessageId: WSASERVICE_NOT_FOUND
//
// MessageText:
//
// No such service is known. The service cannot be found in the specified name space.
//
wsaservice_not_found = 10108
//
// MessageId: WSATYPE_NOT_FOUND
//
// MessageText:
//
// The specified class was not found.
//
wsatype_not_found = 10109
//
// MessageId: WSA_E_NO_MORE
//
// MessageText:
//
// No more results can be returned by WSALookupServiceNext.
//
wsa_e_no_more = 10110
//
// MessageId: WSA_E_CANCELLED
//
// MessageText:
//
// A call to WSALookupServiceEnd was made while this call was still processing. The call has been canceled.
//
wsa_e_cancelled = 10111
//
// MessageId: WSAEREFUSED
//
// MessageText:
//
// A database query failed because it was actively refused.
//
wsaerefused = 10112
//
// MessageId: WSAHOST_NOT_FOUND
//
// MessageText:
//
// No such host is known.
//
wsahost_not_found = 11001
//
// MessageId: WSATRY_AGAIN
//
// MessageText:
//
// This is usually a temporary error during hostname resolution and means that the local server did not receive a response from an authoritative server.
//
wsatry_again = 11002
//
// MessageId: WSANO_RECOVERY
//
// MessageText:
//
// A non-recoverable error occurred during a database lookup.
//
wsano_recovery = 11003
//
// MessageId: WSANO_DATA
//
// MessageText:
//
// The requested name is valid, but no data of the requested type was found.
//
wsano_data = 11004
//
// MessageId: WSA_QOS_RECEIVERS
//
// MessageText:
//
// At least one reserve has arrived.
//
wsa_qos_receivers = 11005
//
// MessageId: WSA_QOS_SENDERS
//
// MessageText:
//
// At least one path has arrived.
//
wsa_qos_senders = 11006
//
// MessageId: WSA_QOS_NO_SENDERS
//
// MessageText:
//
// There are no senders.
//
wsa_qos_no_senders = 11007
//
// MessageId: WSA_QOS_NO_RECEIVERS
//
// MessageText:
//
// There are no receivers.
//
wsa_qos_no_receivers = 11008
//
// MessageId: WSA_QOS_REQUEST_CONFIRMED
//
// MessageText:
//
// Reserve has been confirmed.
//
wsa_qos_request_confirmed = 11009
//
// MessageId: WSA_QOS_ADMISSION_FAILURE
//
// MessageText:
//
// Error due to lack of resources.
//
wsa_qos_admission_failure = 11010
//
// MessageId: WSA_QOS_POLICY_FAILURE
//
// MessageText:
//
// Rejected for administrative reasons - bad credentials.
//
wsa_qos_policy_failure = 11011
//
// MessageId: WSA_QOS_BAD_STYLE
//
// MessageText:
//
// Unknown or conflicting style.
//
wsa_qos_bad_style = 11012
//
// MessageId: WSA_QOS_BAD_OBJECT
//
// MessageText:
//
// Problem with some part of the filterspec or providerspecific buffer in general.
//
wsa_qos_bad_object = 11013
//
// MessageId: WSA_QOS_TRAFFIC_CTRL_ERROR
//
// MessageText:
//
// Problem with some part of the flowspec.
//
wsa_qos_traffic_ctrl_error = 11014
//
// MessageId: WSA_QOS_GENERIC_ERROR
//
// MessageText:
//
// General QOS error.
//
wsa_qos_generic_error = 11015
//
// MessageId: WSA_QOS_ESERVICETYPE
//
// MessageText:
//
// An invalid or unrecognized service type was found in the flowspec.
//
wsa_qos_eservicetype = 11016
//
// MessageId: WSA_QOS_EFLOWSPEC
//
// MessageText:
//
// An invalid or inconsistent flowspec was found in the QOS structure.
//
wsa_qos_eflowspec = 11017
//
// MessageId: WSA_QOS_EPROVSPECBUF
//
// MessageText:
//
// Invalid QOS provider-specific buffer.
//
wsa_qos_eprovspecbuf = 11018
//
// MessageId: WSA_QOS_EFILTERSTYLE
//
// MessageText:
//
// An invalid QOS filter style was used.
//
wsa_qos_efilterstyle = 11019
//
// MessageId: WSA_QOS_EFILTERTYPE
//
// MessageText:
//
// An invalid QOS filter type was used.
//
wsa_qos_efiltertype = 11020
//
// MessageId: WSA_QOS_EFILTERCOUNT
//
// MessageText:
//
// An incorrect number of QOS FILTERSPECs were specified in the FLOWDESCRIPTOR.
//
wsa_qos_efiltercount = 11021
//
// MessageId: WSA_QOS_EOBJLENGTH
//
// MessageText:
//
// An object with an invalid ObjectLength field was specified in the QOS provider-specific buffer.
//
wsa_qos_eobjlength = 11022
//
// MessageId: WSA_QOS_EFLOWCOUNT
//
// MessageText:
//
// An incorrect number of flow descriptors was specified in the QOS structure.
//
wsa_qos_eflowcount = 11023
//
// MessageId: WSA_QOS_EUNKOWNPSOBJ
//
// MessageText:
//
// An unrecognized object was found in the QOS provider-specific buffer.
//
wsa_qos_eunkownpsobj = 11024
//
// MessageId: WSA_QOS_EPOLICYOBJ
//
// MessageText:
//
// An invalid policy object was found in the QOS provider-specific buffer.
//
wsa_qos_epolicyobj = 11025
//
// MessageId: WSA_QOS_EFLOWDESC
//
// MessageText:
//
// An invalid QOS flow descriptor was found in the flow descriptor list.
//
wsa_qos_eflowdesc = 11026
//
// MessageId: WSA_QOS_EPSFLOWSPEC
//
// MessageText:
//
// An invalid or inconsistent flowspec was found in the QOS provider specific buffer.
//
wsa_qos_epsflowspec = 11027
//
// MessageId: WSA_QOS_EPSFILTERSPEC
//
// MessageText:
//
// An invalid FILTERSPEC was found in the QOS provider-specific buffer.
//
wsa_qos_epsfilterspec = 11028
//
// MessageId: WSA_QOS_ESDMODEOBJ
//
// MessageText:
//
// An invalid shape discard mode object was found in the QOS provider specific buffer.
//
wsa_qos_esdmodeobj = 11029
//
// MessageId: WSA_QOS_ESHAPERATEOBJ
//
// MessageText:
//
// An invalid shaping rate object was found in the QOS provider-specific buffer.
//
wsa_qos_eshaperateobj = 11030
//
// MessageId: WSA_QOS_RESERVED_PETYPE
//
// MessageText:
//
// A reserved policy element was found in the QOS provider-specific buffer.
//
wsa_qos_reserved_petype = 11031
//
// MessageId: WSA_SECURE_HOST_NOT_FOUND
//
// MessageText:
//
// No such host is known securely.
//
wsa_secure_host_not_found = 11032
//
// MessageId: WSA_IPSEC_NAME_POLICY_ERROR
//
// MessageText:
//
// Name based IPSEC policy could not be added.
//
wsa_ipsec_name_policy_error = 11033
}
// wsa_error casts an int to its WsaError value
pub fn wsa_error(code int) WsaError {
return WsaError(code)
}
const (
error_ewouldblock = WsaError.wsaewouldblock
)
// Link to Winsock library
#flag -lws2_32
#include <winsock2.h>
#include <Ws2tcpip.h>
// Constants that windows needs
const (
fionbio = C.FIONBIO
msg_nosignal = 0
wsa_v22 = 0x202 // C.MAKEWORD(2, 2)
)
// Error code returns the last socket error
fn error_code() int {
return C.WSAGetLastError()
}
struct C.WSAData {
mut:
wVersion u16
wHighVersion u16
szDescription [257]byte
szSystemStatus [129]byte
iMaxSockets u16
iMaxUdpDg u16
lpVendorInfo byteptr
}
fn init() {
mut wsadata := C.WSAData{}
res := C.WSAStartup(wsa_v22, &wsadata)
if res != 0 {
panic('socket: WSAStartup failed')
}
}

View File

@@ -9,6 +9,7 @@ import net
import encoding.base64
import strings
import time
import io
const (
recv_size = 128
@@ -29,7 +30,8 @@ pub enum BodyType {
pub struct Client {
mut:
socket net.Socket
conn net.TcpConn
reader io.BufferedReader
pub:
server string
port int = 25
@@ -62,8 +64,10 @@ pub fn new_client(config Client) ?Client {
pub fn (mut c Client) reconnect() ? {
if c.is_open { return error('Already connected to server') }
socket := net.dial(c.server, c.port) or { return error('Connecting to server failed') }
c.socket = socket
conn := net.dial_tcp('$c.server:$c.port') or { return error('Connecting to server failed') }
c.conn = conn
c.reader = io.new_buffered_reader(reader: io.make_reader(c.conn))
c.expect_reply(.ready) or { return error('Received invalid response from server') }
c.send_ehlo() or { return error('Sending EHLO packet failed') }
@@ -85,35 +89,21 @@ pub fn (c Client) send(config Mail) ? {
pub fn (mut c Client) quit() ? {
c.send_str('QUIT\r\n')
c.expect_reply(.close)
c.socket.close()?
c.conn.close()?
c.is_open = false
}
// expect_reply checks if the SMTP server replied with the expected reply code
fn (c Client) expect_reply(expected ReplyCode) ? {
bytes, len := c.socket.recv(recv_size)
bytes := io.read_all(c.conn)?
str := tos(bytes, len).trim_space()
str := bytes.bytestr().trim_space()
$if smtp_debug? {
eprintln('\n\n[RECV START]')
eprintln('\n\n[RECV]')
eprint(str)
}
// Read remaining data in the socket
if len >= recv_size {
for {
tbytes, tlen := c.socket.recv(recv_size)
str2 := tos(tbytes, tlen)
$if smtp_debug? { eprint(str2) }
if tlen < recv_size { break }
}
}
$if smtp_debug? {
eprintln('\n[RECV END]')
}
if len >= 3 {
if str.len >= 3 {
status := str[..3].int()
if status != expected {
return error('Received unexpected status code $status, expecting $expected')
@@ -128,7 +118,7 @@ fn (c Client) send_str(s string) ? {
eprint(s.trim_space())
eprintln('\n[SEND END]')
}
c.socket.send_string(s)?
c.conn.write(s.bytes())?
}
[inline]

View File

@@ -1,392 +0,0 @@
module net
import os
pub struct Socket {
pub:
sockfd int
family int
typ int
proto int
pub mut:
max_single_send_size int = 64000
}
struct C.in_addr {
mut:
s_addr int
}
struct C.sockaddr {
}
struct C.sockaddr_in {
mut:
sin_family int
sin_port int
sin_addr C.in_addr
}
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() int
fn C.setsockopt() int
fn C.htonl() int
fn C.htons() int
fn C.bind() int
fn C.listen() int
fn C.accept() int
fn C.getaddrinfo() int
fn C.connect() int
fn C.send() int
fn C.recv() int
fn C.shutdown() int
fn C.ntohs() int
fn C.getsockname() int
fn C.inet_ntop(af int, src voidptr, dst charptr, dst_size int) charptr
fn C.getpeername(sockfd int, addr &C.sockaddr_in, addrsize &int) int
// create socket
pub fn new_socket(family int, typ int, proto int) ?Socket {
sockfd := C.socket(family, typ, proto)
one := 1
// This is needed so that there are no problems with reusing the
// same port after the application exits.
C.setsockopt(sockfd, C.SOL_SOCKET, C.SO_REUSEADDR, &one, sizeof(voidptr))
if sockfd == -1 {
return error('net.socket: failed')
}
s := Socket{
sockfd: sockfd
family: family
typ: typ
proto: proto
}
return s
}
pub fn socket_udp() ?Socket {
return new_socket(C.AF_INET, C.SOCK_DGRAM, C.IPPROTO_UDP)
}
// set socket options
pub fn (s Socket) setsockopt(level int, optname int, optvalue &int) ?int {
res := C.setsockopt(s.sockfd, level, optname, optvalue, sizeof(int))
if res < 0 {
return error('net.setsocketopt: failed with $res')
}
return res
}
// bind socket to port
pub fn (s Socket) bind(port int) ?int {
mut addr := C.sockaddr_in{}
addr.sin_family = s.family
addr.sin_port = C.htons(port)
addr.sin_addr.s_addr = C.htonl(C.INADDR_ANY)
size := 16 // sizeof(C.sockaddr_in)
tmp := voidptr(&addr)
skaddr := &C.sockaddr(tmp)
res := C.bind(s.sockfd, skaddr, size)
if res < 0 {
return error('net.bind: failed with $res')
}
return res
}
// put socket into passive mode and wait to receive
pub fn (s Socket) listen() ?int {
backlog := 128
res := C.listen(s.sockfd, backlog)
if res < 0 {
return error('net.listen: failed with $res')
}
$if debug {
println('listen res = $res')
}
return res
}
// put socket into passive mode with user specified backlog and wait to receive
pub fn (s Socket) listen_backlog(backlog int) ?int {
mut n := 0
if backlog > 0 {
n = backlog
}
res := C.listen(s.sockfd, n)
if res < 0 {
return error('net.listen_backlog: failed with $res')
}
return res
}
// helper method to create, bind, and listen given port number
pub fn listen(port int) ?Socket {
$if debug {
println('net.listen($port)')
}
s := new_socket(C.AF_INET, C.SOCK_STREAM, 0) ?
s.bind(port) ?
s.listen() ?
return s
}
// accept first connection request from socket queue
pub fn (s Socket) accept() ?Socket {
$if debug {
println('accept()')
}
addr := C.sockaddr{}
size := sizeof(addr)
sockfd := C.accept(s.sockfd, &addr, &size)
if sockfd < 0 {
return error('net.accept: failed with $sockfd')
}
c := Socket{
sockfd: sockfd
family: s.family
typ: s.typ
proto: s.proto
}
return c
}
pub fn (s Socket) peer_ip() ?string {
buf := [44]byte{}
peeraddr := C.sockaddr_in{}
speeraddr := sizeof(peeraddr)
ok := C.getpeername(s.sockfd, &C.sockaddr(&peeraddr), &speeraddr)
if ok == -1 {
return error('net.peer_ip: getpeername failed')
}
cstr := C.inet_ntop(C.AF_INET, &peeraddr.sin_addr, buf, sizeof(buf))
if cstr == 0 {
return error('net.peer_ip: inet_ntop failed')
}
res := cstring_to_vstring(cstr)
return res
}
// connect to given addrress and port
pub fn (s Socket) connect(address string, port int) ?int {
mut hints := C.addrinfo{}
hints.ai_family = s.family
hints.ai_socktype = s.typ
hints.ai_flags = C.AI_PASSIVE
hints.ai_protocol = s.proto
hints.ai_addrlen = 0
hints.ai_canonname = C.NULL
hints.ai_addr = C.NULL
hints.ai_next = C.NULL
info := &C.addrinfo(0)
sport := '$port'
info_res := C.getaddrinfo(address.str, sport.str, &hints, &info)
if info_res != 0 {
error_message := os.get_error_msg(error_code())
return error('net.connect: getaddrinfo failed "$error_message"')
}
res := C.connect(s.sockfd, info.ai_addr, info.ai_addrlen)
if res < 0 {
error_message := os.get_error_msg(error_code())
return error('net.connect: connect failed "$error_message"')
}
return res
}
// helper method to create socket and connect
pub fn dial(address string, port int) ?Socket {
s := new_socket(C.AF_INET, C.SOCK_STREAM, 0) ?
s.connect(address, port) ?
return s
}
// send data to socket (when you have a memory buffer)
pub fn (s Socket) send(buf byteptr, len int) ?int {
mut dptr := buf
mut dlen := len
for {
send_size := if dlen > s.max_single_send_size { s.max_single_send_size } else { dlen }
sbytes := C.send(s.sockfd, dptr, send_size, msg_nosignal)
if sbytes < 0 {
return error('net.send: failed with $sbytes')
}
dlen -= sbytes
if dlen <= 0 {
break
}
unsafe {
dptr += sbytes
}
}
return len
}
// send string data to socket (when you have a v string)
pub fn (s Socket) send_string(sdata string) ?int {
return s.send(sdata.str, sdata.len)
}
// receive string data from socket. NB: you are responsible for freeing the returned byteptr
pub fn (s Socket) recv(bufsize int) (byteptr, int) {
mut buf := byteptr(0)
unsafe {
buf = malloc(bufsize)
}
res := C.recv(s.sockfd, buf, bufsize, 0)
return buf, res
}
// TODO: remove cread/2 and crecv/2 when the Go net interface is done
pub fn (s Socket) cread(buffer byteptr, buffersize int) int {
res := C.read(s.sockfd, buffer, buffersize)
return res
}
// Receive a message from the socket, and place it in a preallocated buffer buf,
// with maximum message size bufsize. Returns the length of the received message.
pub fn (s Socket) crecv(buffer voidptr, buffersize int) int {
res := C.recv(s.sockfd, byteptr(buffer), buffersize, 0)
return res
}
// shutdown and close socket
pub fn (s Socket) close() ?int {
mut shutdown_res := 0
$if windows {
shutdown_res = C.shutdown(s.sockfd, C.SD_BOTH)
} $else {
shutdown_res = C.shutdown(s.sockfd, C.SHUT_RDWR)
}
// TODO: should shutdown throw an error? close will
// continue even if shutdown failed
// if shutdown_res < 0 {
// return error('net.close: shutdown failed with $shutdown_res')
// }
mut res := 0
$if windows {
res = C.closesocket(s.sockfd)
} $else {
res = C.close(s.sockfd)
}
if res < 0 {
return error('net.close: failed with $res')
}
return 0
}
pub const (
crlf = '\r\n'
max_read = 400
msg_peek = 0x02
)
// write - write a string with CRLF after it over the socket s
pub fn (s Socket) write(str string) ?int {
line := '$str$crlf'
res := C.send(s.sockfd, line.str, line.len, msg_nosignal)
if res < 0 {
return error('net.write: failed with $res')
}
return res
}
// read_line - retrieves a line from the socket s (i.e. a string ended with \n)
pub fn (s Socket) read_line() string {
mut buf := [max_read]byte{} // where C.recv will store the network data
mut res := '' // The final result, including the ending \n.
for {
mut line := '' // The current line. Can be a partial without \n in it.
n := C.recv(s.sockfd, buf, max_read - 1, msg_peek)
if n == -1 {
return res
}
if n == 0 {
return res
}
buf[n] = `\0`
mut eol_idx := -1
for i in 0 .. n {
if int(buf[i]) == `\n` {
eol_idx = i
// Ensure that tos_clone(buf) later,
// will return *only* the first line (including \n),
// and ignore the rest
buf[i + 1] = `\0`
break
}
}
bufbp := byteptr(buf)
line = tos_clone(bufbp)
if eol_idx > 0 {
// At this point, we are sure that recv returned valid data,
// that contains *at least* one line.
// Ensure that the block till the first \n (including it)
// is removed from the socket's receive queue, so that it does
// not get read again.
C.recv(s.sockfd, buf, eol_idx + 1, 0)
res += line
break
}
// recv returned a buffer without \n in it .
C.recv(s.sockfd, buf, n, 0)
res += line
res += crlf
break
}
return res
}
// TODO
pub fn (s Socket) read_all() string {
mut buf := [max_read]byte{} // where C.recv will store the network data
mut res := '' // The final result, including the ending \n.
for {
n := C.recv(s.sockfd, buf, max_read - 1, 0)
if n == -1 {
return res
}
if n == 0 {
return res
}
bufbp := byteptr(buf)
res += tos_clone(bufbp)
}
return res
}
pub fn (s Socket) get_port() int {
mut addr := C.sockaddr_in{}
size := 16 // sizeof(sockaddr_in)
tmp := voidptr(&addr)
skaddr := &C.sockaddr(tmp)
C.getsockname(s.sockfd, skaddr, &size)
return C.ntohs(addr.sin_port)
}

View File

@@ -0,0 +1,55 @@
module net
enum SocketOption {
// TODO: SO_ACCEPT_CONN is not here becuase windows doesnt support it
// and there is no easy way to define it
broadcast = C.SO_BROADCAST
debug = C.SO_DEBUG
dont_route = C.SO_DONTROUTE
error = C.SO_ERROR
keep_alive = C.SO_KEEPALIVE
linger = C.SO_LINGER
oob_inline = C.SO_OOBINLINE
reuse_addr = C.SO_REUSEADDR
recieve_buf_size = C.SO_RCVBUF
recieve_low_size = C.SO_RCVLOWAT
recieve_timeout = C.SO_RCVTIMEO
send_buf_size = C.SO_SNDBUF
send_low_size = C.SO_SNDLOWAT
send_timeout = C.SO_SNDTIMEO
socket_type = C.SO_TYPE
}
const (
opts_bool = [SocketOption.broadcast, .debug, .dont_route, .error, .keep_alive, .oob_inline]
opts_int = [
.recieve_buf_size,
.recieve_low_size,
.recieve_timeout,
.send_buf_size,
.send_low_size,
.send_timeout,
]
opts_can_set = [
SocketOption.broadcast,
.debug,
.dont_route,
.keep_alive,
.linger,
.oob_inline,
.recieve_buf_size,
.recieve_low_size,
.recieve_timeout,
.send_buf_size,
.send_low_size,
.send_timeout,
]
)

View File

@@ -1,106 +0,0 @@
import net
fn setup() (net.Socket, net.Socket, net.Socket) {
server := net.listen(0) or {
panic(err)
}
server_port := server.get_port()
client := net.dial('127.0.0.1', server_port) or {
panic(err)
}
socket := server.accept() or {
panic(err)
}
$if debug_peer_ip ? {
ip := socket.peer_ip() or {
'$err'
}
eprintln('socket peer_ip: $ip')
}
return server, client, socket
}
fn cleanup(server &net.Socket, client &net.Socket, socket &net.Socket) {
server.close() or { }
client.close() or { }
socket.close() or { }
}
fn test_socket() {
server, client, socket := setup()
defer {
cleanup(server, client, socket)
}
message := 'Hello World'
socket.send(message.str, message.len) or {
assert false
}
$if debug {
println('message send: $message')
}
$if debug {
println('send socket: $socket.sockfd')
}
bytes, blen := client.recv(1024)
received := tos(bytes, blen)
$if debug {
println('message received: $received')
}
$if debug {
println('client: $client.sockfd')
}
assert message == received
}
fn test_socket_read_line() {
server, client, socket := setup()
defer {
cleanup(server, client, socket)
}
message1, message2 := 'message1', 'message2'
message := '$message1\n$message2'
socket.write(message) or {
assert false
}
line1, line2 := client.read_line(), client.read_line()
assert line1 != message1
assert line1.trim_space() == message1
assert line2 != message2
assert line2.trim_space() == message2
}
fn test_socket_write() {
server, client, socket := setup()
defer {
cleanup(server, client, socket)
}
message1 := 'a message 1'
socket.write(message1) or {
assert false
}
line1 := client.read_line()
assert line1 != message1
assert line1.trim_space() == message1
}
fn test_socket_write_fail_without_panic() {
server, client, socket := setup()
defer {
cleanup(server, client, socket)
}
message2 := 'a message 2'
// ensure that socket.write (i.e. done on the server side)
// continues to work, even when the client side has been disconnected
// this test is important for a stable long standing server
client.close() or { }
$if solaris {
return
}
// TODO: fix segfaulting on Solaris
for i := 0; i < 3; i++ {
socket.write(message2) or {
println('write to a socket without a recipient should produce an option fail: $err | $message2')
assert true
}
}
}

View File

@@ -1,18 +0,0 @@
import net
fn start_socket_udp_server() {
bufsize := 1024
bytes := [1024]byte{}
s := net.socket_udp() or { panic(err) }
s.bind( 9876 ) or { panic(err) }
println('Waiting for udp packets:')
for {
res := s.crecv(bytes, bufsize)
if res < 0 { break }
print('Received $res bytes: ' + tos(bytes, res))
}
}
fn test_udp_server() {
// start_socket_udp_server()
}

365
vlib/net/tcp.v Normal file
View File

@@ -0,0 +1,365 @@
module net
import time
pub struct TcpConn {
pub:
sock TcpSocket
mut:
write_deadline time.Time
read_deadline time.Time
read_timeout time.Duration
write_timeout time.Duration
}
pub fn dial_tcp(address string) ?TcpConn {
s := new_tcp_socket()?
s.connect(address)?
return TcpConn {
sock: s
read_timeout: 30 * time.second
write_timeout: 30 * time.second
}
}
pub fn (c TcpConn) close() ? {
c.sock.close()?
return none
}
// write_ptr blocks and attempts to write all data
pub fn (c TcpConn) write_ptr(b byteptr, len int) ? {
unsafe {
mut ptr_base := byteptr(b)
mut total_sent := 0
for total_sent < len {
ptr := ptr_base + total_sent
remaining := len - total_sent
mut sent := C.send(c.sock.handle, ptr, remaining, msg_nosignal)
if sent < 0 {
code := error_code()
match code {
error_ewouldblock {
c.wait_for_write()
continue
}
else {
wrap_error(code)?
}
}
}
total_sent += sent
}
}
return none
}
// write blocks and attempts to write all data
pub fn (c TcpConn) write(bytes []byte) ? {
return c.write_ptr(bytes.data, bytes.len)
}
// write_str blocks and attempts to write all data
pub fn (c TcpConn) write_str(s string) ? {
return c.write_ptr(s.str, s.len)
}
pub fn (c TcpConn) read_ptr(buf_ptr byteptr, len int) ?int {
mut res := wrap_read_result(C.recv(c.sock.handle, buf_ptr, len, 0))?
if res > 0 {
return res
}
code := error_code()
match code {
error_ewouldblock {
c.wait_for_read()?
res = wrap_read_result(C.recv(c.sock.handle, buf_ptr, len, 0))?
return socket_error(res)
}
else {
wrap_error(code)?
}
}
}
pub fn (c TcpConn) read(mut buf []byte) ?int {
return c.read_ptr(buf.data, buf.len)
}
pub fn (c TcpConn) read_deadline() ?time.Time {
if c.read_deadline.unix == 0 {
return c.read_deadline
}
return none
}
pub fn (mut c TcpConn) set_read_deadline(deadline time.Time) {
c.read_deadline = deadline
}
pub fn (c TcpConn) write_deadline() ?time.Time {
if c.write_deadline.unix == 0 {
return c.write_deadline
}
return none
}
pub fn (mut c TcpConn) set_write_deadline(deadline time.Time) {
c.write_deadline = deadline
}
pub fn (c TcpConn) read_timeout() time.Duration {
return c.read_timeout
}
pub fn(mut c TcpConn) set_read_timeout(t time.Duration) {
c.read_timeout = t
}
pub fn (c TcpConn) write_timeout() time.Duration {
return c.write_timeout
}
pub fn (mut c TcpConn) set_write_timeout(t time.Duration) {
c.write_timeout = t
}
[inline]
pub fn (c TcpConn) wait_for_read() ? {
return wait_for_read(c.sock.handle, c.read_deadline, c.read_timeout)
}
[inline]
pub fn (c TcpConn) wait_for_write() ? {
return wait_for_write(c.sock.handle, c.write_deadline, c.write_timeout)
}
pub fn (c TcpConn) peer_addr() ?Addr {
mut addr := C.sockaddr{}
len := sizeof(C.sockaddr)
socket_error(C.getpeername(c.sock.handle, &addr, &len))?
return new_addr(addr)
}
pub fn (c TcpConn) peer_ip() ?string {
buf := [44]byte{}
peeraddr := C.sockaddr_in{}
speeraddr := sizeof(peeraddr)
socket_error(C.getpeername(c.sock.handle, &C.sockaddr(&peeraddr), &speeraddr))?
cstr := C.inet_ntop(C.AF_INET, &peeraddr.sin_addr, buf, sizeof(buf))
if cstr == 0 {
return error('net.peer_ip: inet_ntop failed')
}
res := cstring_to_vstring(cstr)
return res
}
pub fn (c TcpConn) str() string {
// TODO
return 'TcpConn'
}
pub struct TcpListener {
sock TcpSocket
mut:
accept_timeout time.Duration
accept_deadline time.Time
}
pub fn listen_tcp(port int) ?TcpListener {
s := new_tcp_socket()?
validate_port(port)?
mut addr := C.sockaddr_in{}
addr.sin_family = SocketFamily.inet
addr.sin_port = C.htons(port)
addr.sin_addr.s_addr = C.htonl(C.INADDR_ANY)
size := sizeof(C.sockaddr_in)
// cast to the correct type
sockaddr := &C.sockaddr(&addr)
socket_error(C.bind(s.handle, sockaddr, size))?
socket_error(C.listen(s.handle, 128))?
return TcpListener {
sock: s
accept_deadline: no_deadline
accept_timeout: infinite_timeout
}
}
pub fn (l TcpListener) accept() ?TcpConn {
addr := C.sockaddr_storage{}
unsafe {
C.memset(&addr, 0, sizeof(C.sockaddr_storage))
}
size := sizeof(C.sockaddr_storage)
// cast to correct type
sock_addr := &C.sockaddr(&addr)
mut new_handle := C.accept(l.sock.handle, sock_addr, &size)
if new_handle <= 0 {
l.wait_for_accept()?
new_handle = C.accept(l.sock.handle, sock_addr, &size)
if new_handle == -1 || new_handle == 0 {
return none
}
}
new_sock := tcp_socket_from_handle(new_handle)?
return TcpConn{
sock: new_sock
}
}
pub fn (c TcpListener) accept_deadline() ?time.Time {
if c.accept_deadline.unix != 0 {
return c.accept_deadline
}
return none
}
pub fn (mut c TcpListener) set_accept_deadline(deadline time.Time) {
c.accept_deadline = deadline
}
pub fn (c TcpListener) accept_timeout() time.Duration {
return c.accept_timeout
}
pub fn(mut c TcpListener) set_accept_timeout(t time.Duration) {
c.accept_timeout = t
}
pub fn (c TcpListener) wait_for_accept() ? {
return wait_for_read(c.sock.handle, c.accept_deadline, c.accept_timeout)
}
pub fn (c TcpListener) close() ? {
c.sock.close()?
return none
}
pub fn (c TcpListener) address() ?Addr {
return c.sock.address()
}
struct TcpSocket {
pub:
handle int
}
fn new_tcp_socket() ?TcpSocket {
sockfd := socket_error(C.socket(SocketFamily.inet, SocketType.tcp, 0))?
s := TcpSocket {
handle: sockfd
}
//s.set_option_bool(.reuse_addr, true)?
s.set_option_int(.reuse_addr, 1)?
$if windows {
t := true
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
}
fn tcp_socket_from_handle(sockfd int) ?TcpSocket {
s := TcpSocket {
handle: sockfd
}
//s.set_option_bool(.reuse_addr, true)?
s.set_option_int(.reuse_addr, 1)?
$if windows {
t := true
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
}
pub fn (s TcpSocket) set_option_bool(opt SocketOption, value bool) ? {
// TODO reenable when this `in` operation works again
// if opt !in opts_can_set {
// return err_option_not_settable
// }
// if opt !in opts_bool {
// return err_option_wrong_type
// }
socket_error(C.setsockopt(s.handle, C.SOL_SOCKET, int(opt), &value, sizeof(bool)))?
return none
}
pub fn (s TcpSocket) set_option_int(opt SocketOption, value int) ? {
socket_error(C.setsockopt(s.handle, C.SOL_SOCKET, int(opt), &value, sizeof(int)))?
return none
}
fn (s TcpSocket) close() ? {
return shutdown(s.handle)
}
fn (s TcpSocket) @select(test Select, timeout time.Duration) ?bool {
return @select(s.handle, test, timeout)
}
const (
connect_timeout = 5 * time.second
)
fn (s TcpSocket) connect(a string) ? {
addr := resolve_addr(a, .inet, .tcp)?
res := C.connect(s.handle, &addr.addr, addr.len)
if res == 0 {
return none
}
_ := error_code()
write_result := s.@select(.write, connect_timeout)?
if write_result {
// succeeded
return none
}
except_result := s.@select(.except, connect_timeout)?
if except_result {
return err_connect_failed
}
// otherwise we timed out
return err_connect_timed_out
}
// address gets the address of a socket
pub fn (s TcpSocket) address() ?Addr {
mut addr := C.sockaddr_in{}
size := sizeof(C.sockaddr_in)
// cast to the correct type
sockaddr := &C.sockaddr(&addr)
C.getsockname(s.handle, sockaddr, &size)
return new_addr(sockaddr)
}

71
vlib/net/tcp_test.v Normal file
View File

@@ -0,0 +1,71 @@
import net
import time
const (
test_port = 45123
)
fn handle_conn(_c net.TcpConn) {
mut c := _c
// arbitrary timeouts to ensure that it doesnt
// instantly throw its hands in the air and give up
c.set_read_timeout(10 * time.second)
c.set_write_timeout(10 * time.second)
for {
mut buf := []byte{len: 100, init: 0}
read := c.read(mut buf) or {
println('Server: connection dropped')
return
}
c.write(buf[..read]) or {
println('Server: connection dropped')
return
}
}
}
fn echo_server(l net.TcpListener) ? {
for {
new_conn := l.accept() or {
continue
}
go handle_conn(new_conn)
}
return none
}
fn echo() ? {
mut c := net.dial_tcp('127.0.0.1:$test_port')?
defer {
c.close() or { }
}
// arbitrary timeouts to ensure that it doesnt
// instantly throw its hands in the air and give up
c.set_read_timeout(10 * time.second)
c.set_write_timeout(10 * time.second)
data := 'Hello from vlib/net!'
c.write_str(data)?
mut buf := []byte{len: 4096}
read := c.read(mut buf)?
assert read == data.len
for i := 0; i < read; i++ {
assert buf[i] == data[i]
}
println('Got "$buf.bytestr()"')
return none
}
fn test_tcp() {
l := net.listen_tcp(test_port) or {
panic(err)
}
go echo_server(l)
echo() or {
panic(err)
}
l.close() or { }
}
fn main() {
test_tcp()
}

241
vlib/net/udp.v Normal file
View File

@@ -0,0 +1,241 @@
module net
import time
pub struct UdpConn {
sock UdpSocket
mut:
write_deadline time.Time
read_deadline time.Time
read_timeout time.Duration
write_timeout time.Duration
}
pub fn dial_udp(laddr string, raddr string) ?UdpConn {
// Dont have to do this when its fixed
// this just allows us to store this `none` optional in a struct
resolve_wrapper := fn(raddr string) ?Addr {
x := resolve_addr(raddr, .inet, .udp) or { return none }
return x
}
local := resolve_addr(laddr, .inet, .udp)?
sbase := new_udp_socket(local.port)?
sock := UdpSocket {
handle: sbase.handle
l: local
r: resolve_wrapper(raddr)
}
return UdpConn {
sock: sock
}
}
pub fn (c UdpConn) write_ptr(b byteptr, len int) ? {
remote := c.sock.remote() or {
return err_no_udp_remote
}
return c.write_to_ptr(remote, b, len)
}
pub fn (c UdpConn) write(buf []byte) ? {
return c.write_ptr(buf.data, buf.len)
}
pub fn (c UdpConn) write_str(s string) ? {
return c.write_ptr(s.str, s.len)
}
pub fn (c UdpConn) write_to_ptr(addr Addr, b byteptr, len int) ? {
res := C.sendto(c.sock.handle, b, len, 0, &addr.addr, addr.len)
if res >= 0 {
return none
}
code := error_code()
match code {
error_ewouldblock {
c.wait_for_write()?
socket_error(C.sendto(c.sock.handle, b, len, 0, &addr.addr, addr.len))?
}
else {
wrap_error(code)?
}
}
return none
}
// write_to blocks and writes the buf to the remote addr specified
pub fn (c UdpConn) write_to(addr Addr, buf []byte) ? {
return c.write_to_ptr(addr, buf.data, buf.len)
}
// write_to_string blocks and writes the buf to the remote addr specified
pub fn (c UdpConn) write_to_string(addr Addr, s string) ? {
return c.write_to_ptr(addr, s.str, s.len)
}
// read reads from the socket into buf up to buf.len returning the number of bytes read
pub fn (c UdpConn) read(mut buf []byte) ?(int, Addr) {
mut addr_from := C.sockaddr{}
len := sizeof(C.sockaddr)
mut res := wrap_read_result(C.recvfrom(c.sock.handle, buf.data, buf.len, 0, &addr_from, &len))?
if res > 0 {
addr := new_addr(addr_from)?
return res, addr
}
code := error_code()
match code {
error_ewouldblock {
c.wait_for_read()?
// same setup as in tcp
res = wrap_read_result(C.recvfrom(c.sock.handle, buf.data, buf.len, 0, &addr_from, &len))?
res2 := socket_error(res)?
addr := new_addr(addr_from)?
return res2, addr
}
else {
wrap_error(code)?
}
}
return none
}
pub fn (c UdpConn) read_deadline() ?time.Time {
if c.read_deadline.unix == 0 {
return c.read_deadline
}
return none
}
pub fn (mut c UdpConn) set_read_deadline(deadline time.Time) {
c.read_deadline = deadline
}
pub fn (c UdpConn) write_deadline() ?time.Time {
if c.write_deadline.unix == 0 {
return c.write_deadline
}
return none
}
pub fn (mut c UdpConn) set_write_deadline(deadline time.Time) {
c.write_deadline = deadline
}
pub fn (c UdpConn) read_timeout() time.Duration {
return c.read_timeout
}
pub fn(mut c UdpConn) set_read_timeout(t time.Duration) {
c.read_timeout = t
}
pub fn (c UdpConn) write_timeout() time.Duration {
return c.write_timeout
}
pub fn (mut c UdpConn) set_write_timeout(t time.Duration) {
c.write_timeout = t
}
[inline]
pub fn (c UdpConn) wait_for_read() ? {
return wait_for_read(c.sock.handle, c.read_deadline, c.read_timeout)
}
[inline]
pub fn (c UdpConn) wait_for_write() ? {
return wait_for_write(c.sock.handle, c.write_deadline, c.write_timeout)
}
pub fn (c UdpConn) str() string {
// TODO
return 'UdpConn'
}
pub fn (c UdpConn) close() ? {
return c.sock.close()
}
pub fn listen_udp(port int) ?UdpConn {
s := new_udp_socket(port)?
return UdpConn {
sock: s
}
}
struct UdpSocket {
handle int
l Addr
r ?Addr
}
fn new_udp_socket(local_port int) ?UdpSocket {
sockfd := socket_error(C.socket(SocketFamily.inet, SocketType.udp, 0))?
s := UdpSocket {
handle: sockfd
}
s.set_option_bool(.reuse_addr, true)?
$if windows {
t := 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)?
mut addr := C.sockaddr_in{}
addr.sin_family = SocketFamily.inet
addr.sin_port = C.htons(local_port)
addr.sin_addr.s_addr = C.htonl(C.INADDR_ANY)
size := sizeof(C.sockaddr_in)
// cast to the correct type
sockaddr := &C.sockaddr(&addr)
socket_error(C.bind(s.handle, sockaddr, size))?
return s
}
pub fn (s UdpSocket) remote() ?Addr {
return s.r
}
pub fn (s UdpSocket) set_option_bool(opt SocketOption, value bool) ? {
// TODO reenable when this `in` operation works again
// if opt !in opts_can_set {
// return err_option_not_settable
// }
// if opt !in opts_bool {
// return err_option_wrong_type
// }
socket_error(C.setsockopt(s.handle, C.SOL_SOCKET, int(opt), &value, sizeof(bool)))?
return none
}
fn (s UdpSocket) close() ? {
return shutdown(s.handle)
}
fn (s UdpSocket) @select(test Select, timeout time.Duration) ?bool {
return @select(s.handle, test, timeout)
}

74
vlib/net/udp_test.v Normal file
View File

@@ -0,0 +1,74 @@
import net
import time
fn echo_server(_c net.UdpConn) {
mut c := _c
// arbitrary timeouts to ensure that it doesnt
// instantly throw its hands in the air and give up
c.set_read_timeout(10 * time.second)
c.set_write_timeout(10 * time.second)
for {
mut buf := []byte{ len: 100, init: 0 }
read, addr := c.read(mut buf) or {
continue
}
c.write_to(addr, buf[..read]) or {
println('Server: connection dropped')
return
}
}
}
fn echo() ? {
mut c := net.dial_udp('127.0.0.1:40003', '127.0.0.1:40001')?
defer { c.close() or { } }
// arbitrary timeouts to ensure that it doesnt
// instantly throw its hands in the air and give up
c.set_read_timeout(10 * time.second)
c.set_write_timeout(10 * time.second)
data := 'Hello from vlib/net!'
c.write_str(data)?
mut buf := []byte{ len: 100, init: 0 }
read, addr := c.read(mut buf)?
assert read == data.len
println('Got address $addr')
// Can't test this here because loopback addresses
// are mapped to other addresses
// assert addr.str() == '127.0.0.1:30001'
for i := 0; i < read; i++ {
assert buf[i] == data[i]
}
println('Got "${buf.bytestr()}"')
c.close()?
return none
}
fn test_udp() {
l := net.listen_udp(40001) or {
println(err)
assert false
panic('')
}
go echo_server(l)
echo() or {
println(err)
assert false
}
l.close() or { }
}
fn main() {
test_udp()
}

24
vlib/net/util.v Normal file
View File

@@ -0,0 +1,24 @@
module net
const (
socket_max_port = u16(0xFFFF)
)
// validate_port checks whether a port is valid
// and returns the port or an error
pub fn validate_port(port int) ?u16 {
if port <= socket_max_port {
return u16(port)
} else {
return err_port_out_of_range
}
}
// split address splits an address into its host name and its port
pub fn split_address(addr string) ?(string, u16) {
port := addr.all_after_last(':').int()
address := addr.all_before_last(':')
p := validate_port(port)?
return address, p
}