# Copyright (c) 2024 Yuki Kimoto
# MIT License
class IO::Socket::IP extends IO::Socket {
version_from IO;
use Sys::Socket::Constant as SOCKET;
use Sys::Socket::Sockaddr;
use Sys::Socket::In_addr_base;
use IO::Socket::IP::Import::IPv4;
use IO::Socket::IP::Import::IPv6;
# Fields
has LocalAddr : protected string;
has LocalPort : protected int;
has PeerAddr : protected string;
has PeerPort : protected int;
has ReuseAddr : protected byte;
has ReusePort : protected byte;
has Broadcast : protected byte;
has V6Only : byte;
has V6OnlySpecified : byte;
# Class Methods
static method new : IO::Socket::IP ($options : object[] = undef) {
my $self = new IO::Socket::IP;
$self->init($options);
$self->configure;
return $self;
}
# Instance Methods
protected method option_names : string[] () {
my $option_names = Array->merge_string(
$self->SUPER::option_names,
[
"PeerAddr",
"PeerPort",
"LocalAddr",
"LocalPort",
"ReuseAddr",
"ReusePort",
"Broadcast",
"V6Only",
]
);
return $option_names;
}
protected method init : void ($options : object[] = undef) {
my $options_h = Hash->new($options);
# Domain option
my $domain = $options_h->delete_or_default_int("Domain", SOCKET->AF_INET);
# Proto option
my $proto = $options_h->delete_or_default_int("Proto", SOCKET->IPPROTO_TCP);
# Type option
my $type = $options_h->delete_or_default_int("Type", -1);
unless ($type >= 0) {
if ($proto == SOCKET->IPPROTO_TCP) {
$type = SOCKET->SOCK_STREAM;
}
elsif ($proto == SOCKET->IPPROTO_UDP) {
$type = SOCKET->SOCK_DGRAM;
}
}
$self->SUPER::init(Fn->merge_options($options, {Domain => $domain, Proto => $proto, Type => $type}));
# LocalAddr option
my $local_address = $options_h->get_or_default_string("LocalAddr", undef);
$self->{LocalAddr} = $local_address;
# LocalPort option
my $local_port = $options_h->get_or_default_int("LocalPort", 0);
$self->{LocalPort} = $local_port;
# PeerAddr option
my $peer_address = $options_h->get_or_default_string("PeerAddr", undef);
$self->{PeerAddr} = $peer_address;
# PeerPort option
my $peer_port = $options_h->get_or_default_int("PeerPort", 0);
$self->{PeerPort} = $peer_port;
# ReuseAddr option
my $reuse_addr = $options_h->get_or_default_int("ReuseAddr", 0);
$self->{ReuseAddr} = (byte)$reuse_addr;
# ReusePort option
my $reuse_port = $options_h->get_or_default_int("ReusePort", 0);
$self->{ReusePort} = (byte)$reuse_port;
# Broadcast option
my $broadcast = $options_h->get_or_default_int("Broadcast", 0);
$self->{Broadcast} = (byte)$broadcast;
# V6Only option
if ($options_h->exists("V6Only")) {
$self->{V6OnlySpecified} = 1;
my $v6only = $options_h->get_int("V6Only");
$self->{V6Only} = (byte)$v6only;
}
my $listen = $self->{Listen};
my $fd = $self->{FD};
if ($peer_address && $listen > 0) {
die "PeerAddr option and Listen option cannot be used together.";
}
if ($peer_address) {
$self->{SocketCategory} = IO::Socket->SOCKET_CATEGORY_CLIENT;
}
elsif ($listen) {
$self->{SocketCategory} = IO::Socket->SOCKET_CATEGORY_SERVER;
}
elsif ($fd >= 0) {
$self->{SocketCategory} = IO::Socket->SOCKET_CATEGORY_ACCEPTED;
}
else {
die "[Unexpected Error]Unexpected socket category.";
}
}
protected method configure : void () {
my $reuse_addr = $self->{ReuseAddr};
my $reuse_port = $self->{ReusePort};
my $broadcast = $self->{Broadcast};
my $peer_address = $self->{PeerAddr};
my $peer_port = $self->{PeerPort};
my $local_address = $self->{LocalAddr};
my $local_port = $self->{LocalPort};
my $listen = $self->{Listen};
my $fd = $self->{FD};
unless ($fd >= 0) {
$self->socket;
}
if ($reuse_addr) {
$self->setsockopt(SOCKET->SOL_SOCKET, SOCKET->SO_REUSEADDR, 1);
}
if ($reuse_port) {
$self->setsockopt(SOCKET->SOL_SOCKET, SOCKET->SO_REUSEPORT, 1);
}
if ($broadcast) {
$self->setsockopt(SOCKET->SOL_SOCKET, SOCKET->SO_BROADCAST, 1);
}
if ($self->{V6OnlySpecified}) {
$self->setsockopt(SOCKET->IPPROTO_IPV6, SOCKET->IPV6_V6ONLY, !!$self->{V6Only});
}
my $SocketCategory = $self->{SocketCategory};
switch ($SocketCategory) {
case IO::Socket->SOCKET_CATEGORY_CLIENT : {
my $sockaddr = $self->create_sockaddr($peer_address, $peer_port);
$self->{Sockaddr} = $sockaddr;
$self->connect;
}
case IO::Socket->SOCKET_CATEGORY_SERVER : {
my $sockaddr = $self->create_sockaddr($local_address, $local_port);
$self->{Sockaddr} = $sockaddr;
$self->bind;
$self->listen;
}
}
# This must be after calling connect method because Mac does not support non-blocking mode to non-connected socket.
$self->set_blocking(0);
}
method sockaddr : Sys::Socket::In_addr_base () {
my $sockaddr = $self->sockname;
my $in_addr_base = (Sys::Socket::In_addr_base)undef;
my $domain = $self->{Domain};
if ($domain == SOCKET->AF_INET) {
$in_addr_base = $self->IO::Socket::IP::Import::IPv4::sockaddr;
}
elsif ($domain == SOCKET->AF_INET6) {
$in_addr_base = $self->IO::Socket::IP::Import::IPv6::sockaddr;
}
else {
die "[Unexpected Error]\$domain:$domain.";
}
return $in_addr_base;
}
method sockhost : string () {
my $sockhost = (string)undef;
my $domain = $self->{Domain};
if ($domain == SOCKET->AF_INET) {
$sockhost = $self->IO::Socket::IP::Import::IPv4::sockhost;
}
elsif ($domain == SOCKET->AF_INET6) {
$sockhost = $self->IO::Socket::IP::Import::IPv6::sockhost;
}
else {
die "[Unexpected Error]\$domain:$domain.";
}
return $sockhost;
}
method sockport : int () {
my $sockport = -1;;
my $domain = $self->{Domain};
if ($domain == SOCKET->AF_INET) {
$sockport = $self->IO::Socket::IP::Import::IPv4::sockport;
}
elsif ($domain == SOCKET->AF_INET6) {
$sockport = $self->IO::Socket::IP::Import::IPv6::sockport;
}
else {
die "[Unexpected Error]\$domain:$domain.";
}
return $sockport;
}
method peeraddr : Sys::Socket::In_addr_base () {
my $in_addr_base = (Sys::Socket::In_addr_base)undef;
my $domain = $self->{Domain};
if ($domain == SOCKET->AF_INET) {
$in_addr_base = $self->IO::Socket::IP::Import::IPv4::peeraddr;
}
elsif ($domain == SOCKET->AF_INET6) {
$in_addr_base = $self->IO::Socket::IP::Import::IPv6::peeraddr;
}
else {
die "[Unexpected Error]\$domain:$domain.";
}
return $in_addr_base;
}
method peerhost : string () {
my $peerhost = (string)undef;
my $domain = $self->{Domain};
if ($domain == SOCKET->AF_INET) {
$peerhost = $self->IO::Socket::IP::Import::IPv4::peerhost;
}
elsif ($domain == SOCKET->AF_INET6) {
$peerhost = $self->IO::Socket::IP::Import::IPv6::peerhost;
}
else {
die "[Unexpected Error]\$domain:$domain.";
}
return $peerhost;
}
method peerport : int () {
my $peerport = -1;;
my $domain = $self->{Domain};
if ($domain == SOCKET->AF_INET) {
$peerport = $self->IO::Socket::IP::Import::IPv4::peerport;
}
elsif ($domain == SOCKET->AF_INET6) {
$peerport = $self->IO::Socket::IP::Import::IPv6::peerport;
}
else {
die "[Unexpected Error]\$domain:$domain.";
}
return $peerport;
}
method accept : IO::Socket::IP ($peer_ref : Sys::Socket::Sockaddr[] = undef) {
return (IO::Socket::IP)$self->SUPER::accept($peer_ref);
}
protected method create_sockaddr : Sys::Socket::Sockaddr ($address : string, $port : int) {
my $sockaddr = (Sys::Socket::Sockaddr)undef;
my $domain = $self->{Domain};
if ($domain == SOCKET->AF_INET) {
$sockaddr = $self->IO::Socket::IP::Import::IPv4::create_sockaddr($address, $port);
}
elsif ($domain == SOCKET->AF_INET6) {
$sockaddr = $self->IO::Socket::IP::Import::IPv6::create_sockaddr($address, $port);
}
else {
die "[Unexpected Error]\$domain:$domain.";
}
return $sockaddr;
}
}