# Copyright (c) 2023 Yuki Kimoto
# MIT License
class IO::Socket extends IO::Handle {
version_from IO;
use Sys::Socket;
use Sys::Socket::Constant as SOCKET;
use Sys::Socket::Sockaddr;
use Sys::Socket::Sockaddr::In;
use Sys::Socket::Sockaddr::In6;
use Sys::Socket::Sockaddr::Un;
use Sys::Socket::In_addr;
use Hash;
use Errno;
use Go;
use Sys::Socket::Errno;
use Fn;
use Native::MethodCall;
# Enemerations
protected enum {
SOCKET_CATEGORY_UNKNOWN,
SOCKET_CATEGORY_CLIENT,
SOCKET_CATEGORY_SERVER,
SOCKET_CATEGORY_ACCEPTED,
}
# Fields
has SocketCategory : int;
has Domain : protected int;
has Type : protected int;
has Proto : protected int;
has Listen : protected int;
has Timeout : protected double;
has Sockaddr : protected Sys::Socket::Sockaddr;
# Instance Methods
protected method init : void ($options : object[] = undef) {
my $options_h = Hash->new($options);
$self->SUPER::init(Fn->merge_options($options, {}));
# Domain option
my $domain = $options_h->get_or_default_int("Domain", 0);
$self->{Domain} = $domain;
# Proto option
my $proto = $options_h->get_or_default_int("Proto", 0);
$self->{Proto} = $proto;
# Type option
my $type = $options_h->get_or_default_int("Type", 0);
$self->{Type} = $type;
# Listen option
my $listen_backlog = $options_h->get_or_default_int("Listen", 0);
$self->{Listen} = $listen_backlog;
# Timeout option
my $timeout = $options_h->get_or_default_double("Timeout", 0.0);
$self->{Timeout} = $timeout;
# Blocking option
if ($options_h->exists("Blocking")) {
die "Blocking option is not allowed in IO::Socket class and its child classes.";
}
}
method option_names : string[] () {
my $option_names = Array->merge_string(
$self->SUPER::option_names,
[
"Domain",
"Type",
"Proto",
"Listen",
"Timeout",
]
);
return $option_names;
}
method timeout : double () {
return $self->{Timeout};
}
method set_timeout : void ($timeout : double) {
$self->{Timeout} = $timeout;
}
method sockdomain : int () {
return $self->{Domain};
}
method socktype : int (){
return $self->{Type};
}
method protocol : int () {
return $self->{Proto};
}
method shutdown : void ($how : int) {
my $fd = $self->{FD};
Sys->shutdown($fd, $how);
}
method close : void () {
unless ($self->opened) {
die "This socket is not opened or already closed.";
}
my $fd = $self->{FD};
Sys::Socket->close($fd);
$self->{FD} = -1;
}
method accept : IO::Socket ($peer_ref : Sys::Socket::Sockaddr[] = undef) {
my $fd = $self->{FD};
my $timeout = $self->{Timeout};
my $client_fd = -1;
my $client_sockaddr = (Sys::Socket::Sockaddr)undef;
while (1) {
eval { $client_sockaddr = Sys->accept(\$client_fd, $fd); }
if ($@) {
my $again = Sys::Socket::Errno->is_accept_again(Sys::Socket::Errno->errno);
if ($again) {
Go->gosched_io_read($fd, $timeout);
next;
}
else {
die $@;
}
}
else {
last;
}
}
my $options = {
Timeout => $timeout,
FD => $client_fd,
Domain => $self->{Domain},
Type => $self->{Type},
Proto => $self->{Proto},
};
my $class = type_name $self;
my $client = (IO::Socket)Native::MethodCall->call_class_method($class, "new", [(object)$options]);
if ($peer_ref) {
$peer_ref->[0] = $client_sockaddr;
}
return $client;
}
method recvfrom : int ($buffer : mutable string, $length : int, $flags : int, $from_ref : Sys::Socket::Sockaddr[], $offset : int = 0) {
unless ($length >= 0) {
$length = length $buffer;
}
my $timeout = $self->{Timeout};
my $fd = $self->{FD};
my $recv_length = -1;
while (1) {
eval { $recv_length = Sys->recvfrom($fd, $buffer, $length, $flags, $from_ref, $offset); }
if ($@) {
my $again = Sys::Socket::Errno->is_read_again(Sys::Socket::Errno->errno);
if ($again) {
Go->gosched_io_read($fd, $timeout);
next;
}
else {
die $@;
}
}
else {
last;
}
}
return $recv_length;
}
method sendto : int ($buffer : string, $flags : int, $to : Sys::Socket::Sockaddr, $length : int = -1, $offset : int = 0) {
unless ($length >= 0) {
$length = length $buffer;
}
my $timeout = $self->{Timeout};
my $fd = $self->{FD};
my $send_length = -1;
while (1) {
eval { $send_length = Sys->sendto($fd, $buffer, $flags, $to, $length, $offset); }
if ($@) {
my $again = Sys::Socket::Errno->is_write_again(Sys::Socket::Errno->errno);
if ($again) {
Go->gosched_io_write($fd, $timeout);
next;
}
else {
die $@;
}
}
else {
last;
}
}
return $send_length;
}
method recv : int ($buffer : mutable string, $length : int = -1, $flags : int = 0, $offset : int = 0) {
my $from_ref = (Sys::Socket::Sockaddr[])undef;
return $self->recvfrom($buffer, $length, $flags, $from_ref, $offset);
}
method send : int ($buffer : string, $flags : int = 0, $length : int = -1, $offset : int = 0) {
my $to = (Sys::Socket::Sockaddr)undef;
return $self->sendto($buffer, $flags, $to, $length, $offset);
}
method read : int ($buffer : mutable string, $length : int = -1, $offset : int = 0) {
my $flags = 0;
return $self->recv($buffer, $length, $flags, $offset);
}
method write : int ($buffer : string, $length : int = -1, $offset : int = 0) {
my $flags = 0;
return $self->send($buffer, $flags, $length, $offset);
}
method sockname : Sys::Socket::Sockaddr () {
my $fd = $self->{FD};
my $sockaddr_sock = Sys->getsockname($fd);
return $sockaddr_sock;
}
method peername : Sys::Socket::Sockaddr () {
my $fd = $self->{FD};
my $sockaddr_peer = Sys->getpeername($fd);
return $sockaddr_peer;
}
method connected : Sys::Socket::Sockaddr () {
my $sockaddr = (Sys::Socket::Sockaddr)undef;
eval { $sockaddr = $self->peername; };
return $sockaddr;
}
method atmark : int () {
my $fd = $self->{FD};
my $atmark = Sys::Socket->sockatmark($fd);
return $atmark;
}
method sockopt : string ($level : int, $option_name : int) {
my $fd = $self->{FD};
my $option_value = Sys->getsockopt($fd, $level, $option_name);
return $option_value;
}
method setsockopt : void ($level : int, $option_name : int, $option_value : object of string|Int) {
my $fd = $self->{FD};
Sys->setsockopt($fd, $level, $option_name, $option_value);
}
protected method socket : void () {
my $domain = $self->{Domain};
my $type = $self->{Type};
my $protocol = $self->{Proto};
my $fd = -1;
Sys->socket(\$fd, $domain, $type, $protocol);
$self->{FD} = $fd;
}
protected method connect : void () {
my $fd = $self->{FD};
my $sockaddr = $self->{Sockaddr};
my $timeout = $self->{Timeout};
while (1) {
eval { Sys->connect($fd, $sockaddr); }
if ($@) {
my $again = Sys::Socket::Errno->is_connect_again(Sys::Socket::Errno->errno);
if ($again) {
Go->gosched_io_write($fd, $timeout);
next;
}
else {
die $@;
}
}
else {
last;
}
}
}
protected method bind : void () {
my $sockaddr = $self->{Sockaddr};
my $fd = $self->{FD};
Sys->bind($fd, $sockaddr);
}
protected method listen : void () {
my $fd = $self->{FD};
my $listen = $self->{Listen};
unless ($listen > 0) {
die "\"Listen\" field must be greater than 0.";
}
Sys->listen($fd, $listen);
}
method set_blocking : void ($blocking : int) {
if ($blocking) {
die "Calling set_blocking method given a true value on an IO::Socket object is forbidden.";
}
$self->SUPER::set_blocking($blocking);
}
}