2019-06-30 16:11:55 +02:00
|
|
|
module net
|
|
|
|
|
2019-07-01 14:55:45 +02:00
|
|
|
struct Socket {
|
|
|
|
pub:
|
2019-06-30 16:11:55 +02:00
|
|
|
sockfd int
|
2019-07-01 14:55:45 +02:00
|
|
|
family int
|
|
|
|
_type int
|
|
|
|
proto int
|
2019-06-30 16:11:55 +02:00
|
|
|
}
|
|
|
|
|
2019-07-01 14:55:45 +02:00
|
|
|
import const (
|
|
|
|
AF_INET
|
|
|
|
AF_INET6
|
|
|
|
AF_UNSPEC
|
|
|
|
SOCK_STREAM
|
|
|
|
SOL_SOCKET
|
|
|
|
SO_REUSEADDR
|
|
|
|
SO_REUSEPORT
|
|
|
|
INADDR_ANY
|
|
|
|
AI_PASSIVE
|
|
|
|
SHUT_RD
|
|
|
|
SHUT_WR
|
|
|
|
SHUT_RDWR
|
2019-07-13 23:29:00 +02:00
|
|
|
SD_BOTH
|
2019-07-01 14:55:45 +02:00
|
|
|
)
|
|
|
|
|
2019-07-13 23:29:00 +02:00
|
|
|
struct C.WSAData {
|
|
|
|
mut:
|
|
|
|
wVersion u16
|
|
|
|
wHighVersion u16
|
|
|
|
szDescription [257]byte
|
|
|
|
szSystemStatus [129]byte
|
|
|
|
iMaxSockets u16
|
|
|
|
iMaxUdpDg u16
|
|
|
|
lpVendorInfo byteptr
|
|
|
|
}
|
|
|
|
|
2019-07-20 05:51:45 +02:00
|
|
|
const (
|
|
|
|
WSA_V1 = 0x100 // C.MAKEWORD(1, 0)
|
|
|
|
WSA_V11 = 0x101 // C.MAKEWORD(1, 1)
|
|
|
|
WSA_V2 = 0x200 // C.MAKEWORD(2, 0)
|
|
|
|
WSA_V21 = 0x201 // C.MAKEWORD(2, 1)
|
|
|
|
WSA_V22 = 0x202 // C.MAKEWORD(2, 2)
|
|
|
|
)
|
|
|
|
|
2019-07-01 14:55:45 +02:00
|
|
|
struct C.in_addr {
|
|
|
|
mut:
|
|
|
|
s_addr int
|
2019-06-30 16:11:55 +02:00
|
|
|
}
|
|
|
|
|
2019-07-01 14:55:45 +02:00
|
|
|
struct C.sockaddr_in {
|
|
|
|
mut:
|
|
|
|
sin_family int
|
|
|
|
sin_port int
|
|
|
|
sin_addr C.in_addr
|
|
|
|
}
|
2019-06-30 16:11:55 +02:00
|
|
|
|
|
|
|
struct C.addrinfo {
|
2019-07-01 14:55:45 +02:00
|
|
|
mut:
|
|
|
|
ai_family int
|
|
|
|
ai_socktype int
|
|
|
|
ai_flags int
|
|
|
|
ai_protocol int
|
|
|
|
ai_addrlen int
|
|
|
|
ai_next voidptr
|
|
|
|
ai_addr voidptr
|
|
|
|
}
|
2019-06-30 16:11:55 +02:00
|
|
|
|
2019-07-01 14:55:45 +02:00
|
|
|
struct C.sockaddr_storage {}
|
2019-06-30 20:57:25 +02:00
|
|
|
|
2019-07-01 14:55:45 +02:00
|
|
|
// create socket
|
2019-07-20 05:51:45 +02:00
|
|
|
pub fn socket(family int, _type int, proto int) ?Socket {
|
2019-07-13 23:29:00 +02:00
|
|
|
$if windows {
|
|
|
|
mut wsadata := C.WSAData{}
|
2019-07-20 05:51:45 +02:00
|
|
|
res := C.WSAStartup(WSA_V22, &wsadata)
|
|
|
|
if res != 0 {
|
|
|
|
return error('socket: WSAStartup failed')
|
|
|
|
}
|
2019-07-13 23:29:00 +02:00
|
|
|
}
|
|
|
|
|
2019-07-01 14:55:45 +02:00
|
|
|
sockfd := C.socket(family, _type, proto)
|
2019-07-29 18:21:36 +02:00
|
|
|
one:=1
|
|
|
|
// This is needed so that there are no problems with reusing the
|
|
|
|
// same port after the application exits.
|
|
|
|
C.setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(int))
|
2019-07-20 05:51:45 +02:00
|
|
|
if sockfd == 0 {
|
|
|
|
return error('socket: init failed')
|
|
|
|
}
|
2019-07-01 14:55:45 +02:00
|
|
|
s := Socket {
|
|
|
|
sockfd: sockfd
|
|
|
|
family: family
|
|
|
|
_type: _type
|
|
|
|
proto: proto
|
2019-06-30 16:11:55 +02:00
|
|
|
}
|
2019-07-01 14:55:45 +02:00
|
|
|
return s
|
|
|
|
}
|
|
|
|
|
|
|
|
// set socket options
|
2019-07-20 05:51:45 +02:00
|
|
|
pub fn (s Socket) setsockopt(level int, optname int, optvalue *int) ?int {
|
2019-07-01 14:55:45 +02:00
|
|
|
res := C.setsockopt(s.sockfd, level, optname, optvalue, C.sizeof(optvalue))
|
2019-07-20 05:51:45 +02:00
|
|
|
if res < 0 {
|
|
|
|
return error('socket: setsockopt failed')
|
|
|
|
}
|
|
|
|
return int(res)
|
2019-07-01 14:55:45 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// bind socket to port
|
2019-07-20 05:51:45 +02:00
|
|
|
pub fn (s Socket) bind(port int) ?int {
|
2019-07-01 14:55:45 +02:00
|
|
|
mut addr := C.sockaddr_in{}
|
|
|
|
addr.sin_family = s.family
|
|
|
|
addr.sin_port = C.htons(port)
|
|
|
|
addr.sin_addr.s_addr = C.htonl(INADDR_ANY)
|
|
|
|
size := 16 // sizeof(C.sockaddr_in)
|
|
|
|
res := C.bind(s.sockfd, &addr, size)
|
2019-07-20 05:51:45 +02:00
|
|
|
if res < 0 {
|
|
|
|
return error('socket: bind failed')
|
|
|
|
}
|
|
|
|
return int(res)
|
2019-07-01 14:55:45 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// put socket into passive mode and wait to receive
|
2019-07-20 05:51:45 +02:00
|
|
|
pub fn (s Socket) listen() ?int {
|
2019-07-01 14:55:45 +02:00
|
|
|
backlog := 128
|
2019-07-29 18:21:36 +02:00
|
|
|
res := int(C.listen(s.sockfd, backlog))
|
2019-07-20 05:51:45 +02:00
|
|
|
if res < 0 {
|
|
|
|
return error('socket: listen failed')
|
|
|
|
}
|
2019-08-15 20:14:49 +02:00
|
|
|
println('listen res = $res')
|
2019-07-29 18:21:36 +02:00
|
|
|
return res
|
2019-07-01 14:55:45 +02:00
|
|
|
}
|
|
|
|
|
2019-07-03 10:25:31 +02:00
|
|
|
// put socket into passive mode with user specified backlog and wait to receive
|
2019-07-20 05:51:45 +02:00
|
|
|
pub fn (s Socket) listen_backlog(backlog int) ?int {
|
2019-07-03 10:25:31 +02:00
|
|
|
mut n := 0
|
|
|
|
if backlog > 0 {
|
|
|
|
n = backlog
|
|
|
|
}
|
|
|
|
res := C.listen(s.sockfd, n)
|
2019-07-20 05:51:45 +02:00
|
|
|
if res < 0 {
|
|
|
|
return error('socket: listen_backlog failed')
|
|
|
|
}
|
|
|
|
return int(res)
|
2019-07-03 10:25:31 +02:00
|
|
|
}
|
|
|
|
|
2019-07-01 14:55:45 +02:00
|
|
|
// helper method to create, bind, and listen given port number
|
2019-07-20 05:51:45 +02:00
|
|
|
pub fn listen(port int) ?Socket {
|
2019-07-29 18:21:36 +02:00
|
|
|
println('net.listen($port)')
|
2019-07-20 05:51:45 +02:00
|
|
|
s := socket(AF_INET, SOCK_STREAM, 0) or {
|
|
|
|
return error(err)
|
|
|
|
}
|
|
|
|
bind_res := s.bind(port) or {
|
|
|
|
return error(err)
|
|
|
|
}
|
|
|
|
listen_res := s.listen() or {
|
|
|
|
return error(err)
|
2019-06-30 16:11:55 +02:00
|
|
|
}
|
2019-07-01 14:55:45 +02:00
|
|
|
return s
|
2019-06-30 16:11:55 +02:00
|
|
|
}
|
|
|
|
|
2019-07-01 14:55:45 +02:00
|
|
|
// accept first connection request from socket queue
|
2019-07-20 05:51:45 +02:00
|
|
|
pub fn (s Socket) accept() ?Socket {
|
2019-07-29 18:21:36 +02:00
|
|
|
println('accept()')
|
2019-07-01 14:55:45 +02:00
|
|
|
addr := C.sockaddr_storage{}
|
|
|
|
size := 128 // sizeof(sockaddr_storage)
|
|
|
|
sockfd := C.accept(s.sockfd, &addr, &size)
|
|
|
|
if sockfd < 0 {
|
2019-07-20 05:51:45 +02:00
|
|
|
return error('socket: accept failed')
|
2019-06-30 21:00:22 +02:00
|
|
|
}
|
2019-07-01 14:55:45 +02:00
|
|
|
c := Socket {
|
|
|
|
sockfd: sockfd
|
|
|
|
family: s.family
|
|
|
|
_type: s._type
|
|
|
|
proto: s.proto
|
2019-06-30 21:00:22 +02:00
|
|
|
}
|
2019-07-01 14:55:45 +02:00
|
|
|
return c
|
|
|
|
}
|
|
|
|
|
|
|
|
// connect to given addrress and port
|
2019-07-20 05:51:45 +02:00
|
|
|
pub fn (s Socket) connect(address string, port int) ?int {
|
2019-07-01 14:55:45 +02:00
|
|
|
mut hints := C.addrinfo{}
|
|
|
|
hints.ai_family = AF_UNSPEC
|
|
|
|
hints.ai_socktype = SOCK_STREAM
|
|
|
|
hints.ai_flags = AI_PASSIVE
|
2019-07-02 17:16:32 +02:00
|
|
|
|
2019-07-01 14:55:45 +02:00
|
|
|
info := &C.addrinfo{!}
|
|
|
|
sport := '$port'
|
2019-07-22 16:51:33 +02:00
|
|
|
info_res := C.getaddrinfo(address.str, sport.str, &hints, &info)
|
2019-07-01 14:55:45 +02:00
|
|
|
if info_res != 0 {
|
2019-07-20 05:51:45 +02:00
|
|
|
return error('socket: connect failed')
|
2019-06-30 21:00:22 +02:00
|
|
|
}
|
2019-08-04 00:56:12 +02:00
|
|
|
res := int(C.connect(s.sockfd, info.ai_addr, info.ai_addrlen))
|
2019-07-20 05:51:45 +02:00
|
|
|
if res < 0 {
|
|
|
|
return error('socket: connect failed')
|
|
|
|
}
|
|
|
|
return int(res)
|
2019-06-30 20:57:25 +02:00
|
|
|
}
|
|
|
|
|
2019-07-01 14:55:45 +02:00
|
|
|
// helper method to create socket and connect
|
2019-07-20 05:51:45 +02:00
|
|
|
pub fn dial(address string, port int) ?Socket {
|
|
|
|
s := socket(AF_INET, SOCK_STREAM, 0) or {
|
|
|
|
return error(err)
|
|
|
|
}
|
|
|
|
res := s.connect(address, port) or {
|
|
|
|
return error(err)
|
2019-07-01 14:55:45 +02:00
|
|
|
}
|
|
|
|
return s
|
2019-06-30 20:57:25 +02:00
|
|
|
}
|
|
|
|
|
2019-07-02 17:25:21 +02:00
|
|
|
// send string data to socket
|
|
|
|
pub fn (s Socket) send(buf byteptr, len int) int {
|
|
|
|
res := C.send(s.sockfd, buf, len, 0)
|
2019-07-20 05:51:45 +02:00
|
|
|
// if res < 0 {
|
|
|
|
// return error('socket: send failed')
|
|
|
|
// }
|
2019-07-02 17:25:21 +02:00
|
|
|
return res
|
|
|
|
}
|
|
|
|
|
|
|
|
// receive string data from socket
|
|
|
|
pub fn (s Socket) recv(bufsize int) byteptr {
|
|
|
|
buf := malloc(bufsize)
|
|
|
|
res := C.recv(s.sockfd, buf, bufsize, 0)
|
2019-07-20 05:51:45 +02:00
|
|
|
// if res < 0 {
|
|
|
|
// return error('socket: recv failed')
|
|
|
|
// }
|
2019-07-02 17:25:21 +02:00
|
|
|
return buf
|
|
|
|
}
|
|
|
|
|
2019-07-01 14:55:45 +02:00
|
|
|
// shutdown and close socket
|
2019-07-20 05:51:45 +02:00
|
|
|
pub fn (s Socket) close() ?int {
|
|
|
|
mut shutdown_res := 0
|
2019-07-13 23:29:00 +02:00
|
|
|
$if windows {
|
2019-07-20 05:51:45 +02:00
|
|
|
shutdown_res = C.shutdown(s.sockfd, SD_BOTH)
|
2019-07-13 23:29:00 +02:00
|
|
|
}
|
|
|
|
$else {
|
2019-07-20 05:51:45 +02:00
|
|
|
shutdown_res = C.shutdown(s.sockfd, SHUT_RDWR)
|
2019-07-13 23:29:00 +02:00
|
|
|
}
|
2019-07-20 05:51:45 +02:00
|
|
|
// TODO: should shutdown throw an error? close will
|
|
|
|
// continue even if shutdown failed
|
|
|
|
// if shutdown_res < 0 {
|
|
|
|
// return error('socket: shutdown failed')
|
|
|
|
// }
|
2019-07-13 23:29:00 +02:00
|
|
|
|
2019-07-20 05:51:45 +02:00
|
|
|
mut res := 0
|
2019-07-13 23:29:00 +02:00
|
|
|
$if windows {
|
2019-07-20 05:51:45 +02:00
|
|
|
res = C.closesocket(s.sockfd)
|
2019-07-01 14:55:45 +02:00
|
|
|
}
|
2019-07-13 23:29:00 +02:00
|
|
|
$else {
|
2019-07-20 05:51:45 +02:00
|
|
|
res = C.close(s.sockfd)
|
|
|
|
}
|
|
|
|
if res < 0 {
|
|
|
|
return error('socket: close failed')
|
2019-07-13 23:29:00 +02:00
|
|
|
}
|
|
|
|
|
2019-07-01 14:55:45 +02:00
|
|
|
return 0
|
|
|
|
}
|
2019-07-29 18:21:36 +02:00
|
|
|
|
|
|
|
const (
|
|
|
|
MAX_READ = 400
|
|
|
|
)
|
|
|
|
pub fn (s Socket) write(str string) {
|
|
|
|
line := '$str\r\n'
|
|
|
|
C.write(s.sockfd, line.str, line.len)
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn (s Socket) read_line() string {
|
|
|
|
mut res := ''
|
|
|
|
for {
|
|
|
|
println('.')
|
|
|
|
mut buf := malloc(MAX_READ)
|
|
|
|
n := int(C.recv(s.sockfd, buf, MAX_READ-1, 0))
|
|
|
|
println('numbytes=$n')
|
|
|
|
if n == -1 {
|
|
|
|
println('recv failed')
|
|
|
|
// TODO
|
|
|
|
return ''
|
|
|
|
}
|
|
|
|
if n == 0 {
|
|
|
|
break
|
|
|
|
}
|
|
|
|
// println('resp len=$numbytes')
|
|
|
|
buf[n] = `\0`
|
|
|
|
// C.printf('!!buf= "%s" n=%d\n', buf,n)
|
|
|
|
line := string(buf)
|
|
|
|
res += line
|
|
|
|
// Reached a newline. That's an end of an IRC message
|
|
|
|
// TODO dont need ends_with check ?
|
|
|
|
if line.ends_with('\n') || n < MAX_READ - 1 {
|
|
|
|
// println('NL')
|
|
|
|
break
|
|
|
|
}
|
|
|
|
if line.ends_with('\r\n') {
|
|
|
|
// println('RNL')
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return res
|
|
|
|
}
|
|
|
|
|
|
|
|
|