2018-05-16 08:22:34 +00:00
|
|
|
#
|
|
|
|
# Asyncdispatch2 Transport Common Types
|
|
|
|
# (c) Copyright 2018
|
|
|
|
# Status Research & Development GmbH
|
|
|
|
#
|
|
|
|
# Licensed under either of
|
|
|
|
# Apache License, version 2.0, (LICENSE-APACHEv2)
|
|
|
|
# MIT license (LICENSE-MIT)
|
|
|
|
|
2018-06-02 14:25:26 +00:00
|
|
|
import net, nativesockets, strutils
|
2018-06-05 20:21:07 +00:00
|
|
|
import ../asyncloop
|
2018-05-16 08:22:34 +00:00
|
|
|
|
|
|
|
const
|
|
|
|
DefaultStreamBufferSize* = 4096 ## Default buffer size for stream
|
|
|
|
## transports
|
|
|
|
DefaultDatagramBufferSize* = 65536 ## Default buffer size for datagram
|
|
|
|
## transports
|
|
|
|
type
|
|
|
|
ServerFlags* = enum
|
|
|
|
## Server's flags
|
2018-05-31 08:03:58 +00:00
|
|
|
ReuseAddr, ReusePort, NoAutoRead
|
2018-05-16 08:22:34 +00:00
|
|
|
|
|
|
|
TransportAddress* = object
|
|
|
|
## Transport network address
|
|
|
|
address*: IpAddress # IP Address
|
|
|
|
port*: Port # IP port
|
|
|
|
|
|
|
|
ServerCommand* = enum
|
|
|
|
## Server's commands
|
|
|
|
Start, # Start server
|
|
|
|
Pause, # Pause server
|
|
|
|
Stop # Stop server
|
|
|
|
|
|
|
|
ServerStatus* = enum
|
|
|
|
## Server's statuses
|
|
|
|
Starting, # Server created
|
|
|
|
Stopped, # Server stopped
|
|
|
|
Running, # Server running
|
2018-06-04 09:57:17 +00:00
|
|
|
Closed # Server closed
|
2018-05-16 08:22:34 +00:00
|
|
|
|
2018-06-05 20:21:07 +00:00
|
|
|
FutureGCString*[T] = ref object of Future[T]
|
|
|
|
## Future to hold GC strings
|
|
|
|
gcholder*: string
|
|
|
|
|
|
|
|
FutureGCSeq*[A, B] = ref object of Future[A]
|
|
|
|
## Future to hold GC seqs
|
|
|
|
gcholder*: seq[B]
|
|
|
|
|
2018-06-04 09:57:17 +00:00
|
|
|
when defined(windows):
|
|
|
|
type
|
|
|
|
SocketServer* = ref object of RootRef
|
|
|
|
## Socket server object
|
|
|
|
sock*: AsyncFD # Socket
|
|
|
|
local*: TransportAddress # Address
|
|
|
|
status*: ServerStatus # Current server status
|
|
|
|
udata*: pointer # User-defined pointer
|
|
|
|
flags*: set[ServerFlags] # Flags
|
|
|
|
bufferSize*: int # Size of internal transports' buffer
|
|
|
|
loopFuture*: Future[void] # Server's main Future
|
|
|
|
domain*: Domain # Current server domain (IPv4 or IPv6)
|
|
|
|
apending*: bool
|
|
|
|
asock*: AsyncFD # Current AcceptEx() socket
|
|
|
|
abuffer*: array[128, byte] # Windows AcceptEx() buffer
|
|
|
|
aovl*: CustomOverlapped # AcceptEx OVERLAPPED structure
|
|
|
|
else:
|
|
|
|
type
|
|
|
|
SocketServer* = ref object of RootRef
|
|
|
|
## Socket server object
|
|
|
|
sock*: AsyncFD # Socket
|
|
|
|
local*: TransportAddress # Address
|
|
|
|
status*: ServerStatus # Current server status
|
|
|
|
udata*: pointer # User-defined pointer
|
|
|
|
flags*: set[ServerFlags] # Flags
|
|
|
|
bufferSize*: int # Size of internal transports' buffer
|
|
|
|
loopFuture*: Future[void] # Server's main Future
|
2018-05-16 08:22:34 +00:00
|
|
|
|
2018-06-04 09:57:17 +00:00
|
|
|
type
|
2018-05-16 08:22:34 +00:00
|
|
|
TransportError* = object of Exception
|
|
|
|
## Transport's specific exception
|
|
|
|
TransportOsError* = object of TransportError
|
|
|
|
## Transport's OS specific exception
|
|
|
|
TransportIncompleteError* = object of TransportError
|
|
|
|
## Transport's `incomplete data received` exception
|
|
|
|
TransportLimitError* = object of TransportError
|
|
|
|
## Transport's `data limit reached` exception
|
|
|
|
|
|
|
|
TransportState* = enum
|
|
|
|
## Transport's state
|
|
|
|
ReadPending, # Read operation pending (Windows)
|
|
|
|
ReadPaused, # Read operations paused
|
|
|
|
ReadClosed, # Read operations closed
|
|
|
|
ReadEof, # Read at EOF
|
|
|
|
ReadError, # Read error
|
|
|
|
WritePending, # Writer operation pending (Windows)
|
|
|
|
WritePaused, # Writer operations paused
|
|
|
|
WriteClosed, # Writer operations closed
|
|
|
|
WriteError # Write error
|
|
|
|
|
|
|
|
var
|
|
|
|
AnyAddress* = TransportAddress(
|
|
|
|
address: IpAddress(family: IpAddressFamily.IPv4), port: Port(0)
|
|
|
|
) ## Default INADDR_ANY address for IPv4
|
|
|
|
AnyAddress6* = TransportAddress(
|
|
|
|
address: IpAddress(family: IpAddressFamily.IPv6), port: Port(0)
|
|
|
|
) ## Default INADDR_ANY address for IPv6
|
|
|
|
|
|
|
|
proc getDomain*(address: IpAddress): Domain =
|
|
|
|
## Returns OS specific Domain from IP Address.
|
|
|
|
case address.family
|
|
|
|
of IpAddressFamily.IPv4:
|
|
|
|
result = Domain.AF_INET
|
|
|
|
of IpAddressFamily.IPv6:
|
|
|
|
result = Domain.AF_INET6
|
|
|
|
|
2018-05-28 23:35:15 +00:00
|
|
|
proc getDomain*(address: TransportAddress): Domain =
|
|
|
|
## Returns OS specific Domain from TransportAddress.
|
|
|
|
result = address.address.getDomain()
|
|
|
|
|
2018-05-16 08:22:34 +00:00
|
|
|
proc `$`*(address: TransportAddress): string =
|
|
|
|
## Returns string representation of ``address``.
|
|
|
|
case address.address.family
|
|
|
|
of IpAddressFamily.IPv4:
|
|
|
|
result = $address.address
|
|
|
|
result.add(":")
|
|
|
|
of IpAddressFamily.IPv6:
|
|
|
|
result = "[" & $address.address & "]"
|
|
|
|
result.add(":")
|
|
|
|
result.add($int(address.port))
|
|
|
|
|
2018-06-02 14:25:26 +00:00
|
|
|
proc initTAddress*(address: string): TransportAddress =
|
2018-05-21 21:52:57 +00:00
|
|
|
## Parses string representation of ``address``.
|
2018-05-29 09:59:39 +00:00
|
|
|
##
|
2018-05-21 21:52:57 +00:00
|
|
|
## IPv4 transport address format is ``a.b.c.d:port``.
|
|
|
|
## IPv6 transport address format is ``[::]:port``.
|
|
|
|
var parts = address.rsplit(":", maxsplit = 1)
|
|
|
|
doAssert(len(parts) == 2, "Format is <address>:<port>!")
|
|
|
|
let port = parseInt(parts[1])
|
2018-06-02 14:25:26 +00:00
|
|
|
doAssert(port >= 0 and port < 65536, "Illegal port number!")
|
2018-05-21 21:52:57 +00:00
|
|
|
result.port = Port(port)
|
|
|
|
if parts[0][0] == '[' and parts[0][^1] == ']':
|
|
|
|
result.address = parseIpAddress(parts[0][1..^2])
|
|
|
|
else:
|
|
|
|
result.address = parseIpAddress(parts[0])
|
2018-05-16 08:22:34 +00:00
|
|
|
|
2018-06-02 14:25:26 +00:00
|
|
|
proc initTAddress*(address: string, port: Port): TransportAddress =
|
|
|
|
## Initialize ``TransportAddress`` with IP address ``address`` and
|
|
|
|
## port number ``port``.
|
|
|
|
result.address = parseIpAddress(address)
|
|
|
|
result.port = port
|
|
|
|
|
|
|
|
proc initTAddress*(address: string, port: int): TransportAddress =
|
|
|
|
## Initialize ``TransportAddress`` with IP address ``address`` and
|
|
|
|
## port number ``port``.
|
|
|
|
result.address = parseIpAddress(address)
|
2018-06-02 23:03:48 +00:00
|
|
|
result.port = Port(port and 0xFFFF)
|
2018-06-02 14:25:26 +00:00
|
|
|
|
|
|
|
proc resolveTAddress*(address: string,
|
|
|
|
family = IpAddressFamily.IPv4): seq[TransportAddress] =
|
|
|
|
## Resolve string representation of ``address``.
|
|
|
|
##
|
|
|
|
## Supported formats are:
|
|
|
|
## IPv4 numeric address ``a.b.c.d:port``
|
|
|
|
## IPv6 numeric address ``[::]:port``
|
|
|
|
## Hostname address ``hostname:port``
|
|
|
|
##
|
|
|
|
## If hostname address is detected, then network address translation via DNS
|
|
|
|
## will be performed.
|
|
|
|
var
|
|
|
|
ta: TransportAddress
|
|
|
|
ap: Port
|
|
|
|
result = newSeq[TransportAddress]()
|
|
|
|
var parts = address.rsplit(":", maxsplit = 1)
|
|
|
|
doAssert(len(parts) == 2, "Format is <address>:<port>!")
|
|
|
|
let port = parseInt(parts[1])
|
|
|
|
doAssert(port >= 0 and port < 65536, "Illegal port number!")
|
|
|
|
if parts[0][0] == '[' and parts[0][^1] == ']':
|
|
|
|
ta = TransportAddress(address: parseIpAddress(parts[0][1..^2]),
|
|
|
|
port: Port(port))
|
|
|
|
result.add(ta)
|
|
|
|
else:
|
|
|
|
if isIpAddress(parts[0]):
|
|
|
|
ta = TransportAddress(address: parseIpAddress(parts[0]),
|
|
|
|
port: Port(port))
|
|
|
|
result.add(ta)
|
|
|
|
else:
|
|
|
|
var domain = if family == IpAddressFamily.IPv4: Domain(AF_INET) else:
|
|
|
|
Domain(AF_INET6)
|
|
|
|
var aiList = getAddrInfo(parts[0], Port(port), domain)
|
|
|
|
var it = aiList
|
|
|
|
while it != nil:
|
|
|
|
fromSockAddr(cast[ptr Sockaddr_storage](it.ai_addr)[],
|
|
|
|
SockLen(it.ai_addrlen), ta.address, ta.port)
|
|
|
|
result.add(ta)
|
|
|
|
it = it.ai_next
|
|
|
|
freeAddrInfo(aiList)
|
|
|
|
|
2018-05-16 08:22:34 +00:00
|
|
|
template checkClosed*(t: untyped) =
|
|
|
|
if (ReadClosed in (t).state) or (WriteClosed in (t).state):
|
|
|
|
raise newException(TransportError, "Transport is already closed!")
|
|
|
|
|
2018-06-05 20:21:07 +00:00
|
|
|
template checkClosed*(t: untyped, future: untyped) =
|
|
|
|
if (ReadClosed in (t).state) or (WriteClosed in (t).state):
|
|
|
|
future.fail(newException(TransportError, "Transport is already closed!"))
|
|
|
|
return future
|
|
|
|
|
2018-05-16 08:22:34 +00:00
|
|
|
template getError*(t: untyped): ref Exception =
|
|
|
|
var err = (t).error
|
|
|
|
(t).error = nil
|
|
|
|
err
|
|
|
|
|
|
|
|
when defined(windows):
|
|
|
|
import winlean
|
|
|
|
|
|
|
|
const ERROR_OPERATION_ABORTED* = 995
|
2018-05-16 12:29:19 +00:00
|
|
|
const ERROR_SUCCESS* = 0
|
2018-05-16 08:22:34 +00:00
|
|
|
proc cancelIo*(hFile: HANDLE): WINBOOL
|
|
|
|
{.stdcall, dynlib: "kernel32", importc: "CancelIo".}
|
2018-06-02 14:25:26 +00:00
|
|
|
|
|
|
|
when isMainModule:
|
|
|
|
echo $resolveTAddress("localhost:443", IpAddressFamily.IPv6)
|