diff --git a/chronos/asyncfutures2.nim b/chronos/asyncfutures2.nim index eb46e35..249bb98 100644 --- a/chronos/asyncfutures2.nim +++ b/chronos/asyncfutures2.nim @@ -8,7 +8,7 @@ # Apache License, version 2.0, (LICENSE-APACHEv2) # MIT license (LICENSE-MIT) -import std/[os, tables, strutils, heapqueue, deques, sequtils] +import std/sequtils import stew/base10 import ./srcloc export srcloc diff --git a/chronos/asyncloop.nim b/chronos/asyncloop.nim index 13a71bf..ed8b43d 100644 --- a/chronos/asyncloop.nim +++ b/chronos/asyncloop.nim @@ -13,12 +13,13 @@ when (NimMajor, NimMinor) < (1, 4): else: {.push raises: [].} -import std/[os, tables, strutils, heapqueue, lists, nativesockets, net, - deques] -import ./timer +from nativesockets import Port +import std/[tables, strutils, heapqueue, lists, options, deques] +import stew/results +import "."/[osdefs, osutils, timer] -export Port, SocketFlag -export timer +export Port +export timer, results #{.injectStmt: newGcInvariant().} @@ -143,20 +144,17 @@ export timer # TODO: Check if yielded future is nil and throw a more meaningful exception const - unixPlatform = defined(macosx) or defined(freebsd) or - defined(netbsd) or defined(openbsd) or - defined(dragonfly) or defined(macos) or - defined(linux) or defined(android) or - defined(solaris) MaxEventsCount* = 64 when defined(windows): - import winlean, sets, hashes -elif unixPlatform: - import ./selectors2 + import std/[sets, hashes] +elif defined(macosx) or defined(freebsd) or defined(netbsd) or + defined(openbsd) or defined(dragonfly) or defined(macos) or + defined(linux) or defined(android) or defined(solaris): + import "."/selectors2 from posix import EINTR, EAGAIN, EINPROGRESS, EWOULDBLOCK, MSG_PEEK, - MSG_NOSIGNAL - from posix import SIGHUP, SIGINT, SIGQUIT, SIGILL, SIGTRAP, SIGABRT, + MSG_NOSIGNAL, + SIGHUP, SIGINT, SIGQUIT, SIGILL, SIGTRAP, SIGABRT, SIGBUS, SIGFPE, SIGKILL, SIGUSR1, SIGSEGV, SIGUSR2, SIGPIPE, SIGALRM, SIGTERM, SIGPIPE export SIGHUP, SIGINT, SIGQUIT, SIGILL, SIGTRAP, SIGABRT, @@ -190,7 +188,7 @@ type idlers*: Deque[AsyncCallback] trackers*: Table[string, TrackerBase] -proc sentinelCallbackImpl(arg: pointer) {.gcsafe, raises: [Defect].} = +proc sentinelCallbackImpl(arg: pointer) {.gcsafe.} = raiseAssert "Sentinel callback MUST not be scheduled" const @@ -270,38 +268,32 @@ template processCallbacks(loop: untyped) = if not(isNil(callable.function)): callable.function(callable.udata) -proc raiseAsDefect*(exc: ref Exception, msg: string) {. - raises: [Defect], noreturn, noinline.} = +proc raiseAsDefect*(exc: ref Exception, msg: string) {.noreturn, noinline.} = # Reraise an exception as a Defect, where it's unexpected and can't be handled # We include the stack trace in the message because otherwise, it's easily # lost - Nim doesn't print it for `parent` exceptions for example (!) raise (ref Defect)( msg: msg & "\n" & exc.msg & "\n" & exc.getStackTrace(), parent: exc) +proc raiseOsDefect*(error: OSErrorCode, msg = "") {.noreturn, noinline.} = + # Reraise OS error code as a Defect, where it's unexpected and can't be + # handled. We include the stack trace in the message because otherwise, + # it's easily lost. + raise (ref Defect)(msg: msg & "\n[" & $int(error) & "] " & osErrorMsg(error) & + "\n" & getStackTrace()) + +func toException*(v: OSErrorCode): ref OSError = newOSError(v) + # This helper will allow to use `tryGet()` and raise OSError for + # Result[T, OSErrorCode] values. + when defined(windows): type - WSAPROC_TRANSMITFILE = proc(hSocket: SocketHandle, hFile: Handle, - nNumberOfBytesToWrite: DWORD, - nNumberOfBytesPerSend: DWORD, - lpOverlapped: POVERLAPPED, - lpTransmitBuffers: pointer, - dwReserved: DWORD): cint {. - gcsafe, stdcall, raises: [].} - - LPFN_GETQUEUEDCOMPLETIONSTATUSEX = proc(completionPort: Handle, - lpPortEntries: ptr OVERLAPPED_ENTRY, - ulCount: DWORD, - ulEntriesRemoved: var ULONG, - dwMilliseconds: DWORD, - fAlertable: WINBOOL): WINBOOL {. - gcsafe, stdcall, raises: [].} - CompletionKey = ULONG_PTR CompletionData* = object cb*: CallbackFunc errCode*: OSErrorCode - bytesCount*: int32 + bytesCount*: uint32 udata*: pointer CustomOverlapped* = object of OVERLAPPED @@ -314,7 +306,7 @@ when defined(windows): dwNumberOfBytesTransferred: DWORD PDispatcher* = ref object of PDispatcherBase - ioPort: Handle + ioPort: HANDLE handles: HashSet[AsyncFD] connectEx*: WSAPROC_CONNECTEX acceptEx*: WSAPROC_ACCEPTEX @@ -328,86 +320,82 @@ when defined(windows): AsyncFD* = distinct int - proc getModuleHandle(lpModuleName: WideCString): Handle {. - stdcall, dynlib: "kernel32", importc: "GetModuleHandleW", sideEffect.} - proc getProcAddress(hModule: Handle, lpProcName: cstring): pointer {. - stdcall, dynlib: "kernel32", importc: "GetProcAddress", sideEffect.} - proc rtlNtStatusToDosError(code: uint64): ULONG {. - stdcall, dynlib: "ntdll", importc: "RtlNtStatusToDosError", sideEffect.} - proc hash(x: AsyncFD): Hash {.borrow.} proc `==`*(x: AsyncFD, y: AsyncFD): bool {.borrow, gcsafe.} - proc getFunc(s: SocketHandle, fun: var pointer, guid: var GUID): bool = + proc getFunc(s: SocketHandle, fun: var pointer, guid: GUID): bool = var bytesRet: DWORD fun = nil - result = WSAIoctl(s, SIO_GET_EXTENSION_FUNCTION_POINTER, addr guid, + result = wsaIoctl(s, SIO_GET_EXTENSION_FUNCTION_POINTER, unsafeAddr(guid), sizeof(GUID).DWORD, addr fun, sizeof(pointer).DWORD, - addr bytesRet, nil, nil) == 0 + addr(bytesRet), nil, nil) == 0 - proc globalInit() {.raises: [Defect, OSError].} = - var wsa: WSAData - if wsaStartup(0x0202'i16, addr wsa) != 0: - raiseOSError(osLastError()) + proc globalInit() = + var wsa = WSAData() + let res = wsaStartup(0x0202'u16, addr wsa) + if res != 0: + raiseOsDefect(osLastError(), + "globalInit(): Unable to initialize Windows Sockets API") - proc initAPI(loop: PDispatcher) {.raises: [Defect, CatchableError].} = - var - WSAID_TRANSMITFILE = GUID( - D1: 0xb5367df0'i32, D2: 0xcbac'i16, D3: 0x11cf'i16, - D4: [0x95'i8, 0xca'i8, 0x00'i8, 0x80'i8, - 0x5f'i8, 0x48'i8, 0xa1'i8, 0x92'i8]) + proc initAPI(loop: PDispatcher) = + var funcPointer: pointer = nil let kernel32 = getModuleHandle(newWideCString("kernel32.dll")) loop.getQueuedCompletionStatusEx = cast[LPFN_GETQUEUEDCOMPLETIONSTATUSEX]( getProcAddress(kernel32, "GetQueuedCompletionStatusEx")) - let sock = winlean.socket(winlean.AF_INET, 1, 6) - if sock == INVALID_SOCKET: - raiseOSError(osLastError()) + let sock = osdefs.socket(osdefs.AF_INET, 1, 6) + if sock == osdefs.INVALID_SOCKET: + raiseOsDefect(osLastError(), "initAPI(): Unable to create control socket") - var funcPointer: pointer = nil - if not getFunc(sock, funcPointer, WSAID_CONNECTEX): - let err = osLastError() - close(sock) - raiseOSError(err) - loop.connectEx = cast[WSAPROC_CONNECTEX](funcPointer) - if not getFunc(sock, funcPointer, WSAID_ACCEPTEX): - let err = osLastError() - close(sock) - raiseOSError(err) - loop.acceptEx = cast[WSAPROC_ACCEPTEX](funcPointer) - if not getFunc(sock, funcPointer, WSAID_GETACCEPTEXSOCKADDRS): - let err = osLastError() - close(sock) - raiseOSError(err) - loop.getAcceptExSockAddrs = cast[WSAPROC_GETACCEPTEXSOCKADDRS](funcPointer) - if not getFunc(sock, funcPointer, WSAID_TRANSMITFILE): - let err = osLastError() - close(sock) - raiseOSError(err) - loop.transmitFile = cast[WSAPROC_TRANSMITFILE](funcPointer) - close(sock) + block: + let res = getFunc(sock, funcPointer, WSAID_CONNECTEX) + if not(res): + raiseOsDefect(osLastError(), "initAPI(): Unable to initialize " & + "dispatcher's ConnectEx()") + loop.connectEx = cast[WSAPROC_CONNECTEX](funcPointer) - proc newDispatcher*(): PDispatcher {.raises: [Defect, CatchableError].} = + block: + let res = getFunc(sock, funcPointer, WSAID_ACCEPTEX) + if not(res): + raiseOsDefect(osLastError(), "initAPI(): Unable to initialize " & + "dispatcher's AcceptEx()") + loop.acceptEx = cast[WSAPROC_ACCEPTEX](funcPointer) + + block: + let res = getFunc(sock, funcPointer, WSAID_GETACCEPTEXSOCKADDRS) + if not(res): + raiseOsDefect(osLastError(), "initAPI(): Unable to initialize " & + "dispatcher's GetAcceptExSockAddrs()") + loop.getAcceptExSockAddrs = + cast[WSAPROC_GETACCEPTEXSOCKADDRS](funcPointer) + + block: + let res = getFunc(sock, funcPointer, WSAID_TRANSMITFILE) + if not(res): + raiseOsDefect(osLastError(), "initAPI(): Unable to initialize " & + "dispatcher's TransmitFile()") + loop.transmitFile = cast[WSAPROC_TRANSMITFILE](funcPointer) + + if closeFd(sock) != 0: + raiseOsDefect(osLastError(), "initAPI(): Unable to close control socket") + + proc newDispatcher*(): PDispatcher = ## Creates a new Dispatcher instance. - var res = PDispatcher() - res.ioPort = createIoCompletionPort(INVALID_HANDLE_VALUE, 0, 0, 1) - when declared(initHashSet): - # After 0.20.0 Nim's stdlib version - res.handles = initHashSet[AsyncFD]() - else: - # Pre 0.20.0 Nim's stdlib version - res.handles = initSet[AsyncFD]() - when declared(initHeapQueue): - # After 0.20.0 Nim's stdlib version - res.timers = initHeapQueue[TimerCallback]() - else: - # Pre 0.20.0 Nim's stdlib version - res.timers = newHeapQueue[TimerCallback]() - res.callbacks = initDeque[AsyncCallback](64) + let port = createIoCompletionPort(osdefs.INVALID_HANDLE_VALUE, + HANDLE(0), 0, 1) + if port == osdefs.INVALID_HANDLE_VALUE: + raiseOsDefect(osLastError(), "newDispatcher(): Unable to create " & + "IOCP port") + var res = PDispatcher( + ioPort: port, + handles: initHashSet[AsyncFD](), + timers: initHeapQueue[TimerCallback](), + callbacks: initDeque[AsyncCallback](64), + idlers: initDeque[AsyncCallback](), + trackers: initTable[string, TrackerBase]() + ) res.callbacks.addLast(SentinelCallback) - res.idlers = initDeque[AsyncCallback]() - res.trackers = initTable[string, TrackerBase]() initAPI(res) res @@ -416,24 +404,29 @@ when defined(windows): proc setThreadDispatcher*(disp: PDispatcher) {.gcsafe, raises: [Defect].} proc getThreadDispatcher*(): PDispatcher {.gcsafe, raises: [Defect].} - proc getIoHandler*(disp: PDispatcher): Handle = + proc getIoHandler*(disp: PDispatcher): HANDLE = ## Returns the underlying IO Completion Port handle (Windows) or selector ## (Unix) for the specified dispatcher. - return disp.ioPort + disp.ioPort - proc register*(fd: AsyncFD) {.raises: [Defect, CatchableError].} = + proc register2*(fd: AsyncFD): Result[void, OSErrorCode] = ## Register file descriptor ``fd`` in thread's dispatcher. let loop = getThreadDispatcher() - if createIoCompletionPort(fd.Handle, loop.ioPort, - cast[CompletionKey](fd), 1) == 0: - raiseOSError(osLastError()) + if createIoCompletionPort(HANDLE(fd), loop.ioPort, cast[CompletionKey](fd), + 1) == osdefs.INVALID_HANDLE_VALUE: + return err(osLastError()) loop.handles.incl(fd) + ok() - proc unregister*(fd: AsyncFD) {.raises: [Defect, CatchableError].} = + proc register*(fd: AsyncFD) {.raises: [Defect, OSError].} = + ## Register file descriptor ``fd`` in thread's dispatcher. + register2(fd).tryGet() + + proc unregister*(fd: AsyncFD) = ## Unregisters ``fd``. getThreadDispatcher().handles.excl(fd) - proc poll*() {.raises: [Defect, CatchableError].} = + proc poll*() = ## Perform single asynchronous step, processing timers and completing ## tasks. Blocks until at least one event has completed. ## @@ -444,7 +437,7 @@ when defined(windows): var curTime = Moment.now() curTimeout = DWORD(0) - events: array[MaxEventsCount, OVERLAPPED_ENTRY] + events: array[MaxEventsCount, osdefs.OVERLAPPED_ENTRY] # On reentrant `poll` calls from `processCallbacks`, e.g., `waitFor`, # complete pending work of the outer `processCallbacks` call. @@ -463,13 +456,13 @@ when defined(windows): cast[ptr POVERLAPPED](addr events[0].lpOverlapped), curTimeout ) - if res == WINBOOL(0): + if res == FALSE: let errCode = osLastError() if not(isNil(events[0].lpOverlapped)): 1 else: - if int32(errCode) != WAIT_TIMEOUT: - raiseOSError(errCode) + if uint32(errCode) != WAIT_TIMEOUT: + raiseOsDefect(errCode, "poll(): Unable to get OS events") 0 else: 1 @@ -483,16 +476,16 @@ when defined(windows): curTimeout, WINBOOL(0) ) - if res == WINBOOL(0): + if res == FALSE: let errCode = osLastError() - if int32(errCode) != WAIT_TIMEOUT: - raiseOSError(errCode) + if uint32(errCode) != WAIT_TIMEOUT: + raiseOsDefect(errCode, "poll(): Unable to get OS events") 0 else: - eventsReceived + int(eventsReceived) for i in 0 ..< networkEventsCount: - var customOverlapped = events[i].lpOverlapped + var customOverlapped = PtrCustomOverlapped(events[i].lpOverlapped) customOverlapped.data.errCode = block: let res = cast[uint64](customOverlapped.internal) @@ -525,25 +518,35 @@ when defined(windows): ## Closes a socket and ensures that it is unregistered. let loop = getThreadDispatcher() loop.handles.excl(fd) - close(SocketHandle(fd)) + let param = + if closeFd(SocketHandle(fd)) == 0: + OSErrorCode(0) + else: + osLastError() if not isNil(aftercb): - var acb = AsyncCallback(function: aftercb) + var acb = AsyncCallback(function: aftercb, udata: cast[pointer](param)) loop.callbacks.addLast(acb) proc closeHandle*(fd: AsyncFD, aftercb: CallbackFunc = nil) = ## Closes a (pipe/file) handle and ensures that it is unregistered. let loop = getThreadDispatcher() loop.handles.excl(fd) - discard closeHandle(Handle(fd)) + let param = + if closeFd(HANDLE(fd)) == 0: + OSErrorCode(0) + else: + osLastError() if not isNil(aftercb): - var acb = AsyncCallback(function: aftercb) + var acb = AsyncCallback(function: aftercb, udata: cast[pointer](param)) loop.callbacks.addLast(acb) proc contains*(disp: PDispatcher, fd: AsyncFD): bool = ## Returns ``true`` if ``fd`` is registered in thread's dispatcher. - return fd in disp.handles + fd in disp.handles -elif unixPlatform: +elif defined(macosx) or defined(freebsd) or defined(netbsd) or + defined(openbsd) or defined(dragonfly) or defined(macos) or + defined(linux) or defined(android) or defined(solaris): const SIG_IGN = cast[proc(x: cint) {.raises: [], noconv, gcsafe.}](1) @@ -564,24 +567,27 @@ elif unixPlatform: # We are ignoring SIGPIPE signal, because we are working with EPIPE. posix.signal(cint(SIGPIPE), SIG_IGN) - proc initAPI(disp: PDispatcher) {.raises: [Defect, CatchableError].} = + proc initAPI(disp: PDispatcher) {.raises: [Defect].} = discard - proc newDispatcher*(): PDispatcher {.raises: [Defect, CatchableError].} = + proc newDispatcher*(): PDispatcher {.raises: [Defect].} = ## Create new dispatcher. - var res = PDispatcher() - res.selector = newSelector[SelectorData]() - when declared(initHeapQueue): - # After 0.20.0 Nim's stdlib version - res.timers = initHeapQueue[TimerCallback]() - else: - # Before 0.20.0 Nim's stdlib version - res.timers.newHeapQueue() - res.callbacks = initDeque[AsyncCallback](64) + let selector = + try: + newSelector[SelectorData]() + except IOSelectorsException as exc: + raiseAsDefect exc, "Could not initialize selector" + except CatchableError as exc: + raiseAsDefect exc, "Could not initialize selector" + var res = PDispatcher( + selector: selector, + timers: initHeapQueue[TimerCallback](), + callbacks: initDeque[AsyncCallback](64), + idlers: initDeque[AsyncCallback](), + keys: newSeq[ReadyKey](64), + trackers: initTable[string, TrackerBase]() + ) res.callbacks.addLast(SentinelCallback) - res.idlers = initDeque[AsyncCallback]() - res.keys = newSeq[ReadyKey](64) - res.trackers = initTable[string, TrackerBase]() initAPI(res) res @@ -592,24 +598,33 @@ elif unixPlatform: proc getIoHandler*(disp: PDispatcher): Selector[SelectorData] = ## Returns system specific OS queue. - return disp.selector + disp.selector - proc register*(fd: AsyncFD) {.raises: [Defect, CatchableError].} = + proc register2*(fd: AsyncFD): Result[void, OSErrorCode] = ## Register file descriptor ``fd`` in thread's dispatcher. let loop = getThreadDispatcher() - var data: SelectorData - loop.selector.registerHandle(int(fd), {}, data) + try: + var data: SelectorData + loop.selector.registerHandle(int(fd), {}, data) + except CatchableError: + return err(osLastError()) + ok() - proc unregister*(fd: AsyncFD) {.raises: [Defect, CatchableError].} = + proc unregister2*(fd: AsyncFD): Result[void, OSErrorCode] = ## Unregister file descriptor ``fd`` from thread's dispatcher. - getThreadDispatcher().selector.unregister(int(fd)) + let loop = getThreadDispatcher() + try: + loop.selector.unregister(int(fd)) + except CatchableError: + return err(osLastError()) + ok() proc contains*(disp: PDispatcher, fd: AsyncFD): bool {.inline.} = ## Returns ``true`` if ``fd`` is registered in thread's dispatcher. - result = int(fd) in disp.selector + int(fd) in disp.selector - proc addReader*(fd: AsyncFD, cb: CallbackFunc, udata: pointer = nil) {. - raises: [Defect, IOSelectorsException, ValueError].} = + proc addReader2*(fd: AsyncFD, cb: CallbackFunc, + udata: pointer = nil): Result[void, OSErrorCode] = ## Start watching the file descriptor ``fd`` for read availability and then ## call the callback ``cb`` with specified argument ``udata``. let loop = getThreadDispatcher() @@ -617,15 +632,18 @@ elif unixPlatform: withData(loop.selector, int(fd), adata) do: let acb = AsyncCallback(function: cb, udata: udata) adata.reader = acb - newEvents.incl(Event.Read) if not(isNil(adata.writer.function)): newEvents.incl(Event.Write) do: - raise newException(ValueError, "File descriptor not registered.") - loop.selector.updateHandle(int(fd), newEvents) + return err(OSErrorCode(osdefs.EBADF)) - proc removeReader*(fd: AsyncFD) {. - raises: [Defect, IOSelectorsException, ValueError].} = + try: + loop.selector.updateHandle(int(fd), newEvents) + except CatchableError: + return err(osLastError()) + ok() + + proc removeReader2*(fd: AsyncFD): Result[void, OSErrorCode] = ## Stop watching the file descriptor ``fd`` for read availability. let loop = getThreadDispatcher() var newEvents: set[Event] @@ -635,11 +653,16 @@ elif unixPlatform: if not(isNil(adata.writer.function)): newEvents.incl(Event.Write) do: - raise newException(ValueError, "File descriptor not registered.") - loop.selector.updateHandle(int(fd), newEvents) + return err(OSErrorCode(osdefs.EBADF)) - proc addWriter*(fd: AsyncFD, cb: CallbackFunc, udata: pointer = nil) {. - raises: [Defect, IOSelectorsException, ValueError].} = + try: + loop.selector.updateHandle(int(fd), newEvents) + except CatchableError: + return err(osLastError()) + ok() + + proc addWriter2*(fd: AsyncFD, cb: CallbackFunc, + udata: pointer = nil): Result[void, OSErrorCode] = ## Start watching the file descriptor ``fd`` for write availability and then ## call the callback ``cb`` with specified argument ``udata``. let loop = getThreadDispatcher() @@ -647,15 +670,18 @@ elif unixPlatform: withData(loop.selector, int(fd), adata) do: let acb = AsyncCallback(function: cb, udata: udata) adata.writer = acb - newEvents.incl(Event.Write) if not(isNil(adata.reader.function)): newEvents.incl(Event.Read) do: - raise newException(ValueError, "File descriptor not registered.") - loop.selector.updateHandle(int(fd), newEvents) + return err(OSErrorCode(osdefs.EBADF)) - proc removeWriter*(fd: AsyncFD) {. - raises: [Defect, IOSelectorsException, ValueError].} = + try: + loop.selector.updateHandle(int(fd), newEvents) + except CatchableError: + return err(osLastError()) + ok() + + proc removeWriter2*(fd: AsyncFD): Result[void, OSErrorCode] = ## Stop watching the file descriptor ``fd`` for write availability. let loop = getThreadDispatcher() var newEvents: set[Event] @@ -665,8 +691,54 @@ elif unixPlatform: if not(isNil(adata.reader.function)): newEvents.incl(Event.Read) do: - raise newException(ValueError, "File descriptor not registered.") - loop.selector.updateHandle(int(fd), newEvents) + return err(OSErrorCode(osdefs.EBADF)) + + try: + loop.selector.updateHandle(int(fd), newEvents) + except CatchableError: + return err(osLastError()) + ok() + + proc register*(fd: AsyncFD) {.raises: [Defect, OSError].} = + ## Register file descriptor ``fd`` in thread's dispatcher. + register2(fd).tryGet() + + proc unregister*(fd: AsyncFD) {.raises: [Defect, OSError].} = + ## Unregister file descriptor ``fd`` from thread's dispatcher. + unregister2(fd).tryGet() + + proc addReader*(fd: AsyncFD, cb: CallbackFunc, udata: pointer = nil) {. + raises: [Defect, OSError].} = + ## Start watching the file descriptor ``fd`` for read availability and then + ## call the callback ``cb`` with specified argument ``udata``. + addReader2(fd, cb, udata).tryGet() + + proc removeReader*(fd: AsyncFD) {.raises: [Defect, OSError].} = + ## Stop watching the file descriptor ``fd`` for read availability. + removeReader2(fd).tryGet() + + proc addWriter*(fd: AsyncFD, cb: CallbackFunc, udata: pointer = nil) {. + raises: [Defect, OSError].} = + ## Start watching the file descriptor ``fd`` for write availability and then + ## call the callback ``cb`` with specified argument ``udata``. + addWriter2(fd, cb, udata).tryGet() + + proc removeWriter*(fd: AsyncFD) {.raises: [Defect, OSError].} = + ## Stop watching the file descriptor ``fd`` for write availability. + removeWriter2(fd).tryGet() + + proc unregisterAndCloseFd*(fd: AsyncFD): Result[void, OSErrorCode] = + ## Unregister from system queue and close asynchronous socket. + ## + ## NOTE: Use this function to close temporary sockets/pipes only (which + ## are not exposed to the public and not supposed to be used/reused). + ## Please use closeSocket(AsyncFD) and closeHandle(AsyncFD) instead. + doAssert(fd != AsyncFD(osdefs.INVALID_SOCKET)) + ? unregister2(fd) + if closeFd(cint(fd)) != 0: + err(osLastError()) + else: + ok() proc closeSocket*(fd: AsyncFD, aftercb: CallbackFunc = nil) = ## Close asynchronous socket. @@ -678,15 +750,21 @@ elif unixPlatform: let loop = getThreadDispatcher() proc continuation(udata: pointer) = - if SocketHandle(fd) in loop.selector: - try: - unregister(fd) - except CatchableError as exc: - raiseAsDefect(exc, "unregister failed") - - close(SocketHandle(fd)) + let param = + if SocketHandle(fd) in loop.selector: + let ures = unregister2(fd) + if ures.isErr(): + discard closeFd(cint(fd)) + ures.error() + else: + if closeFd(cint(fd)) != 0: + osLastError() + else: + OSErrorCode(0) + else: + OSErrorCode(osdefs.EBADF) if not isNil(aftercb): - aftercb(nil) + aftercb(cast[pointer](param)) withData(loop.selector, int(fd), adata) do: # We are scheduling reader and writer callbacks to be called @@ -739,7 +817,7 @@ elif unixPlatform: let loop = getThreadDispatcher() loop.selector.unregister(sigfd) - proc poll*() {.raises: [Defect, CatchableError].} = + proc poll*() {.gcsafe.} = ## Perform single asynchronous step. let loop = getThreadDispatcher() var curTime = Moment.now() @@ -758,17 +836,21 @@ elif unixPlatform: loop.processTimersGetTimeout(curTimeout) # Processing IO descriptors and all hardware events. - let count = loop.selector.selectInto(curTimeout, loop.keys) + let count = + try: + loop.selector.selectInto(curTimeout, loop.keys) + except IOSelectorsException: + raiseOsDefect(osLastError(), "poll(): Unable to get OS events") for i in 0..= cint(0) + osdefs.setsockopt(SocketHandle(socket), cint(level), cint(optname), + addr(value), SockLen(sizeof(value))) >= cint(0) proc setSockOpt*(socket: AsyncFD, level, optname: int, value: pointer, valuelen: int): bool = ## `setsockopt()` for custom options (pointer and length). ## Returns ``true`` on success, ``false`` on error. - setsockopt(SocketHandle(socket), cint(level), cint(optname), value, - SockLen(valuelen)) >= cint(0) + osdefs.setsockopt(SocketHandle(socket), cint(level), cint(optname), value, + SockLen(valuelen)) >= cint(0) proc getSockOpt*(socket: AsyncFD, level, optname: int, value: var int): bool = ## `getsockopt()` for integer options. ## Returns ``true`` on success, ``false`` on error. var res: cint var size = SockLen(sizeof(res)) - if getsockopt(SocketHandle(socket), cint(level), cint(optname), - addr(res), addr(size)) >= cint(0): + if osdefs.getsockopt(SocketHandle(socket), cint(level), cint(optname), + addr(res), addr(size)) >= cint(0): value = int(res) true else: @@ -90,36 +71,122 @@ proc getSockOpt*(socket: AsyncFD, level, optname: int, value: pointer, valuelen: var int): bool = ## `getsockopt()` for custom options (pointer and length). ## Returns ``true`` on success, ``false`` on error. - getsockopt(SocketHandle(socket), cint(level), cint(optname), - value, cast[ptr SockLen](addr valuelen)) >= cint(0) + osdefs.getsockopt(SocketHandle(socket), cint(level), cint(optname), + value, cast[ptr SockLen](addr valuelen)) >= cint(0) proc getSocketError*(socket: AsyncFD, err: var int): bool = ## Recover error code associated with socket handle ``socket``. - getSockOpt(socket, cint(SOL_SOCKET), cint(SO_ERROR), err) + getSockOpt(socket, cint(osdefs.SOL_SOCKET), cint(osdefs.SO_ERROR), err) + +proc createAsyncSocket2*(domain: Domain, sockType: SockType, + protocol: Protocol, + inherit = true): Result[AsyncFD, OSErrorCode] = + ## Creates new asynchronous socket. + when defined(windows): + let flags = + if inherit: + osdefs.WSA_FLAG_OVERLAPPED + else: + osdefs.WSA_FLAG_OVERLAPPED or osdefs.WSA_FLAG_NO_HANDLE_INHERIT + let fd = wsaSocket(toInt(domain), toInt(sockType), toInt(protocol), + nil, GROUP(0), flags) + if fd == osdefs.INVALID_SOCKET: + return err(osLastError()) + + let bres = setDescriptorBlocking(fd, false) + if bres.isErr(): + discard closeFd(fd) + return err(bres.error()) + + let res = register2(AsyncFD(fd)) + if res.isErr(): + discard closeFd(fd) + return err(res.error()) + + ok(AsyncFD(fd)) + else: + when declared(SOCK_NONBLOCK) and declared(SOCK_CLOEXEC): + let socketType = + if inherit: + toInt(sockType) or osdefs.SOCK_NONBLOCK + else: + toInt(sockType) or osdefs.SOCK_NONBLOCK or osdefs.SOCK_CLOEXEC + let fd = osdefs.socket(toInt(domain), socketType, toInt(protocol)) + if fd == -1: + return err(osLastError()) + let res = register2(AsyncFD(fd)) + if res.isErr(): + discard closeFd(fd) + return err(res.error()) + ok(AsyncFD(fd)) + else: + let fd = osdefs.socket(toInt(domain), toInt(sockType), toInt(protocol)) + if fd == -1: + return err(osLastError()) + let bres = setDescriptorFlags(cint(fd), true, true) + if bres.isErr(): + discard closeFd(fd) + return err(bres.error()) + let res = register2(AsyncFD(fd)) + if res.isErr(): + discard closeFd(fd) + return err(bres.error()) + ok(AsyncFD(fd)) + +proc wrapAsyncSocket2*(sock: cint|SocketHandle): Result[AsyncFD, OSErrorCode] = + ## Wraps socket to asynchronous socket handle. + let fd = + when defined(windows): + sock + else: + when sock is cint: sock else: cint(sock) + ? setDescriptorFlags(fd, true, true) + ? register2(AsyncFD(fd)) + ok(AsyncFD(fd)) proc createAsyncSocket*(domain: Domain, sockType: SockType, - protocol: Protocol): AsyncFD {. - raises: [Defect, CatchableError].} = + protocol: Protocol, + inherit = true): AsyncFD = ## Creates new asynchronous socket. ## Returns ``asyncInvalidSocket`` on error. - let handle = createNativeSocket(domain, sockType, protocol) - if handle == osInvalidSocket: + createAsyncSocket2(domain, sockType, protocol, inherit).valueOr: return asyncInvalidSocket - if not setSocketBlocking(handle, false): - close(handle) - return asyncInvalidSocket - register(AsyncFD(handle)) - AsyncFD(handle) -proc wrapAsyncSocket*(sock: SocketHandle): AsyncFD {. +proc wrapAsyncSocket*(sock: cint|SocketHandle): AsyncFD {. raises: [Defect, CatchableError].} = ## Wraps socket to asynchronous socket handle. ## Return ``asyncInvalidSocket`` on error. - if not setSocketBlocking(sock, false): - close(sock) + wrapAsyncSocket2(sock).valueOr: return asyncInvalidSocket - register(AsyncFD(sock)) - AsyncFD(sock) + +proc getMaxOpenFiles2*(): Result[int, OSErrorCode] = + ## Returns maximum file descriptor number that can be opened by this process. + ## + ## Note: On Windows its impossible to obtain such number, so getMaxOpenFiles() + ## will return constant value of 16384. You can get more information on this + ## link https://docs.microsoft.com/en-us/archive/blogs/markrussinovich/pushing-the-limits-of-windows-handles + when defined(windows) or defined(nimdoc): + ok(16384) + else: + var limits: RLimit + if osdefs.getrlimit(osdefs.RLIMIT_NOFILE, limits) != 0: + return err(osLastError()) + ok(int(limits.rlim_cur)) + +proc setMaxOpenFiles2*(count: int): Result[void, OSErrorCode] = + ## Set maximum file descriptor number that can be opened by this process. + ## + ## Note: On Windows its impossible to set this value, so it just a nop call. + when defined(windows) or defined(nimdoc): + ok() + else: + var limits: RLimit + if getrlimit(osdefs.RLIMIT_NOFILE, limits) != 0: + return err(osLastError()) + limits.rlim_cur = count + if setrlimit(osdefs.RLIMIT_NOFILE, limits) != 0: + return err(osLastError()) + ok() proc getMaxOpenFiles*(): int {.raises: [Defect, OSError].} = ## Returns maximum file descriptor number that can be opened by this process. @@ -127,93 +194,39 @@ proc getMaxOpenFiles*(): int {.raises: [Defect, OSError].} = ## Note: On Windows its impossible to obtain such number, so getMaxOpenFiles() ## will return constant value of 16384. You can get more information on this ## link https://docs.microsoft.com/en-us/archive/blogs/markrussinovich/pushing-the-limits-of-windows-handles - when defined(windows) or defined(nimdoc): - 16384 - else: - var limits: RLimit - if getrlimit(posix.RLIMIT_NOFILE, limits) != 0: - raiseOSError(osLastError()) - int(limits.rlim_cur) + let res = getMaxOpenFiles2() + if res.isErr(): + raiseOSError(res.error()) + res.get() proc setMaxOpenFiles*(count: int) {.raises: [Defect, OSError].} = ## Set maximum file descriptor number that can be opened by this process. ## ## Note: On Windows its impossible to set this value, so it just a nop call. - when defined(windows) or defined(nimdoc): - discard + let res = setMaxOpenFiles2(count) + if res.isErr(): + raiseOSError(res.error()) + +proc getInheritable*(fd: AsyncFD): Result[bool, OSErrorCode] = + ## Returns ``true`` if ``fd`` is inheritable handle. + when defined(windows): + var flags = 0'u32 + if getHandleInformation(HANDLE(fd), flags) == FALSE: + return err(osLastError()) + ok((flags and HANDLE_FLAG_INHERIT) == HANDLE_FLAG_INHERIT) else: - var limits: RLimit - if getrlimit(posix.RLIMIT_NOFILE, limits) != 0: - raiseOSError(osLastError()) - limits.rlim_cur = count - if setrlimit(posix.RLIMIT_NOFILE, limits) != 0: - raiseOSError(osLastError()) + let flags = osdefs.fcntl(cint(fd), osdefs.F_GETFD) + if flags == -1: + return err(osLastError()) + ok((flags and osdefs.FD_CLOEXEC) == osdefs.FD_CLOEXEC) proc createAsyncPipe*(): tuple[read: AsyncFD, write: AsyncFD] = ## Create new asynchronouse pipe. ## Returns tuple of read pipe handle and write pipe handle``asyncInvalidPipe`` ## on error. - when defined(windows): - var pipeIn, pipeOut: Handle - var pipeName: string - var uniq = 0'u64 - var sa = SECURITY_ATTRIBUTES(nLength: sizeof(SECURITY_ATTRIBUTES).cint, - lpSecurityDescriptor: nil, bInheritHandle: 0) - while true: - QueryPerformanceCounter(uniq) - pipeName = pipeHeaderName & Base10.toString(uniq) - - var openMode = FILE_FLAG_FIRST_PIPE_INSTANCE or FILE_FLAG_OVERLAPPED or - PIPE_ACCESS_INBOUND - var pipeMode = PIPE_TYPE_BYTE or PIPE_READMODE_BYTE or PIPE_WAIT - pipeIn = createNamedPipe(newWideCString(pipeName), openMode, pipeMode, - 1'i32, DEFAULT_PIPE_SIZE, DEFAULT_PIPE_SIZE, - 0'i32, addr sa) - if pipeIn == INVALID_HANDLE_VALUE: - let err = osLastError() - # If error in {ERROR_ACCESS_DENIED, ERROR_PIPE_BUSY}, then named pipe - # with such name already exists. - if int32(err) != ERROR_ACCESS_DENIED and int32(err) != ERROR_PIPE_BUSY: - return (read: asyncInvalidPipe, write: asyncInvalidPipe) - continue - else: - break - - var openMode = (GENERIC_WRITE or FILE_WRITE_DATA or SYNCHRONIZE) - pipeOut = createFileW(newWideCString(pipeName), openMode, 0, addr(sa), - OPEN_EXISTING, FILE_FLAG_OVERLAPPED, 0) - if pipeOut == INVALID_HANDLE_VALUE: - discard closeHandle(pipeIn) - return (read: asyncInvalidPipe, write: asyncInvalidPipe) - - var ovl = OVERLAPPED() - let res = connectNamedPipe(pipeIn, cast[pointer](addr ovl)) - if res == 0: - let err = osLastError() - case int32(err) - of ERROR_PIPE_CONNECTED: - discard - of ERROR_IO_PENDING: - var bytesRead = 0.DWORD - if getOverlappedResult(pipeIn, addr ovl, bytesRead, 1) == 0: - discard closeHandle(pipeIn) - discard closeHandle(pipeOut) - return (read: asyncInvalidPipe, write: asyncInvalidPipe) - else: - discard closeHandle(pipeIn) - discard closeHandle(pipeOut) - return (read: asyncInvalidPipe, write: asyncInvalidPipe) - - (read: AsyncFD(pipeIn), write: AsyncFD(pipeOut)) - elif defined(nimdoc): discard + let res = createOsPipe(AsyncDescriptorDefault, AsyncDescriptorDefault) + if res.isErr(): + (read: asyncInvalidPipe, write: asyncInvalidPipe) else: - var fds: array[2, cint] - - if posix.pipe(fds) == -1: - return (read: asyncInvalidPipe, write: asyncInvalidPipe) - - if not(setSocketBlocking(SocketHandle(fds[0]), false)) or - not(setSocketBlocking(SocketHandle(fds[1]), false)): - return (read: asyncInvalidPipe, write: asyncInvalidPipe) - - (read: AsyncFD(fds[0]), write: AsyncFD(fds[1])) + let pipes = res.get() + (read: AsyncFD(pipes.read), write: AsyncFD(pipes.write)) diff --git a/chronos/ioselects/ioselectors_epoll.nim b/chronos/ioselects/ioselectors_epoll.nim index 8207cd3..7a9fdd2 100644 --- a/chronos/ioselects/ioselectors_epoll.nim +++ b/chronos/ioselects/ioselectors_epoll.nim @@ -424,14 +424,14 @@ proc selectInto*[T](s: Selector[T], timeout: int, rkey.events.incl(Event.Timer) elif Event.Signal in pkey.events: var data = SignalFdInfo() - if posix.read(cint(fdi), addr data, - sizeof(SignalFdInfo)) != sizeof(SignalFdInfo): + if posix.read(cint(fdi), addr data, sizeof(SignalFdInfo)) != + sizeof(SignalFdInfo): raiseIOSelectorsError(osLastError()) rkey.events.incl(Event.Signal) elif Event.Process in pkey.events: var data = SignalFdInfo() - if posix.read(cint(fdi), addr data, - sizeof(SignalFdInfo)) != sizeof(SignalFdInfo): + if posix.read(cint(fdi), addr data, sizeof(SignalFdInfo)) != + sizeof(SignalFdInfo): raiseIOSelectorsError(osLastError()) if data.ssi_pid == uint32(pkey.param): rkey.events.incl(Event.Process) diff --git a/chronos/osdefs.nim b/chronos/osdefs.nim new file mode 100644 index 0000000..f5e5479 --- /dev/null +++ b/chronos/osdefs.nim @@ -0,0 +1,1532 @@ +# +# Chronos' OS declarations +# +# (c) Copyright 2022-Present Status Research & Development GmbH +# +# Licensed under either of +# Apache License, version 2.0, (LICENSE-APACHEv2) +# MIT license (LICENSE-MIT) + +from std/os import osLastError, osErrorMsg, OSErrorCode, raiseOSError, + newOSError +export osLastError, osErrorMsg, OSError, OSErrorCode, raiseOSError, newOSError + +when defined(windows): + from std/winlean import SocketHandle, SockLen, SockAddr, InAddr, + In6_addr, Sockaddr_in, Sockaddr_in6, Sockaddr_storage, + AddrInfo + export SocketHandle, SockLen, SockAddr, InAddr, + In6_addr, Sockaddr_in, Sockaddr_in6, Sockaddr_storage, AddrInfo + + # Prerequisites for constants + template WSAIORW*(x, y): untyped = (IOC_INOUT or x or y) + template WSAIOW*(x, y): untyped = + clong(-2147483648) or + ((clong(sizeof(int32)) and clong(IOCPARM_MASK)) shl 16) or (x shl 8) or y + + type + HANDLE* = distinct uint + GUID* {.final, pure.} = object + D1*: uint32 + D2*: uint16 + D3*: uint16 + D4*: array[0..7, byte] + + const + WSADESCRIPTION_LEN* = 256 + WSASYS_STATUS_LEN* = 128 + MAX_ADAPTER_DESCRIPTION_LENGTH* = 128 + MAX_ADAPTER_NAME_LENGTH* = 256 + MAX_ADAPTER_ADDRESS_LENGTH* = 8 + + SOCK_STREAM* = 1 + SOCK_DGRAM* = 2 + SOCK_RAW* = 3 + + AF_UNSPEC* = 0 + AF_UNIX* = 1 + AF_INET* = 2 + AF_INET6* = 23 + AF_BTH* = 32 + AF_MAX* = 33 + + IPPROTO_IP* = 0 + IPPROTO_ICMP* = 1 + IPPROTO_IPV4* = 4 + IPPROTO_UDP* = 17 + IPPROTO_IPV6* = 41 + IPPROTO_ICMPV6* = 58 + IPPROTO_RAW* = 255 + IPPROTO_MAX* = 256 + + INADDR_ANY* = 0x0000_0000'u32 + INADDR_LOOPBACK* = 0x7f00_0001'u32 + INADDR_BROADCAST* = 0xffff_ffff'u32 + INADDR_NONE* = 0xffff_ffff'u32 + + WAIT_ABANDONED* = 0x80'u32 + WAIT_OBJECT_0* = 0x00'u32 + WAIT_TIMEOUT* = 0x102'u32 + WAIT_FAILED* = 0xFFFF_FFFF'u32 + TRUE* = 1'u32 + FALSE* = 0'u32 + INFINITE* = 0xFFFF_FFFF'u32 + + WT_EXECUTEDEFAULT* = 0x00000000'u32 + WT_EXECUTEINIOTHREAD* = 0x00000001'u32 + WT_EXECUTEINUITHREAD* = 0x00000002'u32 + WT_EXECUTEINWAITTHREAD* = 0x00000004'u32 + WT_EXECUTEONLYONCE* = 0x00000008'u32 + WT_EXECUTELONGFUNCTION* = 0x00000010'u32 + WT_EXECUTEINTIMERTHREAD* = 0x00000020'u32 + WT_EXECUTEINPERSISTENTIOTHREAD* = 0x00000040'u32 + WT_EXECUTEINPERSISTENTTHREAD* = 0x00000080'u32 + WT_TRANSFER_IMPERSONATION* = 0x00000100'u32 + + GENERIC_READ* = 0x80000000'u32 + GENERIC_WRITE* = 0x40000000'u32 + GENERIC_ALL* = 0x10000000'u32 + FILE_SHARE_READ* = 1'u32 + FILE_SHARE_DELETE* = 4'u32 + FILE_SHARE_WRITE* = 2'u32 + + OPEN_EXISTING* = 3'u32 + + FILE_READ_DATA* = 0x00000001'u32 + FILE_WRITE_DATA* = 0x00000002'u32 + + IPPROTO_TCP* = 6 + PIPE_TYPE_BYTE* = 0x00000000'u32 + PIPE_READMODE_BYTE* = 0x00000000'u32 + PIPE_WAIT* = 0x00000000'u32 + PIPE_TYPE_MESSAGE* = 0x4'u32 + PIPE_READMODE_MESSAGE* = 0x2'u32 + PIPE_UNLIMITED_INSTANCES* = 255'u32 + DEFAULT_PIPE_SIZE* = 65536'u32 + + ERROR_SUCCESS* = 0 + ERROR_FILE_NOT_FOUND* = 2 + ERROR_TOO_MANY_OPEN_FILES* = 4 + ERROR_ACCESS_DENIED* = 5 + ERROR_BROKEN_PIPE* = 109 + ERROR_BUFFER_OVERFLOW* = 111 + ERROR_PIPE_BUSY* = 231 + ERROR_NO_DATA* = 232 + ERROR_PIPE_NOT_CONNECTED* = 233 + ERROR_PIPE_CONNECTED* = 535 + ERROR_OPERATION_ABORTED* = 995 + ERROR_IO_PENDING* = 997 + ERROR_CONNECTION_REFUSED* = 1225 + ERROR_CONNECTION_ABORTED* = 1236 + + WSAEMFILE* = 10024 + WSAENETDOWN* = 10050 + WSAENETRESET* = 10052 + WSAECONNABORTED* = 10053 + WSAECONNRESET* = 10054 + WSAENOBUFS* = 10055 + WSAETIMEDOUT* = 10060 + WSAEADDRINUSE* = 10048 + WSAEDISCON* = 10101 + WSANOTINITIALISED* = 10093 + WSAENOTSOCK* = 10038 + WSAEINPROGRESS* = 10036 + WSAEINTR* = 10004 + WSAEWOULDBLOCK* = 10035 + ERROR_NETNAME_DELETED* = 64 + STATUS_PENDING* = 0x103 + + IOCPARM_MASK* = 0x7f'u32 + IOC_OUT* = 0x40000000'u32 + IOC_IN* = 0x80000000'u32 + IOC_WS2* = 0x08000000'u32 + IOC_INOUT* = IOC_IN or IOC_OUT + + INVALID_SOCKET* = SocketHandle(-1) + INVALID_HANDLE_VALUE* = HANDLE(uint(not(0'u))) + + SIO_GET_EXTENSION_FUNCTION_POINTER* = uint32(WSAIORW(IOC_WS2, 6)) + SO_UPDATE_ACCEPT_CONTEXT* = 0x700B + SO_CONNECT_TIME* = 0x700C + SO_UPDATE_CONNECT_CONTEXT* = 0x7010 + + FILE_FLAG_FIRST_PIPE_INSTANCE* = 0x00080000'u32 + FILE_FLAG_OPEN_NO_RECALL* = 0x00100000'u32 + FILE_FLAG_OPEN_REPARSE_POINT* = 0x00200000'u32 + FILE_FLAG_POSIX_SEMANTICS* = 0x01000000'u32 + FILE_FLAG_BACKUP_SEMANTICS* = 0x02000000'u32 + FILE_FLAG_DELETE_ON_CLOSE* = 0x04000000'u32 + FILE_FLAG_SEQUENTIAL_SCAN* = 0x08000000'u32 + FILE_FLAG_RANDOM_ACCESS* = 0x10000000'u32 + FILE_FLAG_NO_BUFFERING* = 0x20000000'u32 + FILE_FLAG_OVERLAPPED* = 0x40000000'u32 + FILE_FLAG_WRITE_THROUGH* = 0x80000000'u32 + + PIPE_ACCESS_DUPLEX* = 0x00000003'u32 + PIPE_ACCESS_INBOUND* = 1'u32 + PIPE_ACCESS_OUTBOUND* = 2'u32 + PIPE_NOWAIT* = 0x00000001'u32 + + IOC_VENDOR* = 0x18000000'u32 + SIO_UDP_CONNRESET* = IOC_IN or IOC_VENDOR or 12'u32 + IP_TTL* = 4 + SOMAXCONN* = 2147483647 + SOL_SOCKET* = 0xFFFF + + SO_DEBUG* = 0x0001 + SO_ACCEPTCONN* = 0x0002 + SO_REUSEADDR* = 0x0004 + SO_REUSEPORT* = SO_REUSEADDR + SO_KEEPALIVE* = 0x0008 + SO_DONTROUTE* = 0x0010 + SO_BROADCAST* = 0x0020 + SO_USELOOPBACK* = 0x0040 + SO_LINGER* = 0x0080 + SO_OOBINLINE* = 0x0100 + SO_DONTLINGER* = not(SO_LINGER) + SO_EXCLUSIVEADDRUSE* = not(SO_REUSEADDR) + SO_SNDBUF* = 0x1001 + SO_RCVBUF* = 0x1002 + SO_SNDLOWAT* = 0x1003 + SO_RCVLOWAT* = 0x1004 + SO_SNDTIMEO* = 0x1005 + SO_RCVTIMEO* = 0x1006 + SO_ERROR* = 0x1007 + SO_TYPE* = 0x1008 + TCP_NODELAY* = 1 + + STD_INPUT_HANDLE* = 0xFFFF_FFF6'u32 + STD_OUTPUT_HANDLE* = 0xFFFF_FFF5'u32 + STD_ERROR_HANDLE* = 0xFFFF_FFF4'u32 + + STARTF_USESHOWWINDOW* = 0x00000001'u32 + STARTF_USESIZE* = 0x00000002'u32 + STARTF_USEPOSITION* = 0x00000004'u32 + STARTF_USECOUNTCHARS* = 0x00000008'u32 + STARTF_USEFILLATTRIBUTE* = 0x00000010'u32 + STARTF_RUNFULLSCREEN* = 0x00000020'u32 + STARTF_FORCEONFEEDBACK* = 0x00000040'u32 + STARTF_FORCEOFFFEEDBACK* = 0x00000080'u32 + STARTF_USESTDHANDLES* = 0x00000100'u32 + STARTF_USEHOTKEY* = 0x00000200'u32 + STARTF_TITLEISLINKNAME* = 0x00000800'u32 + STARTF_TITLEISAPPID* = 0x00001000'u32 + STARTF_PREVENTPINNING* = 0x00002000'u32 + STARTF_UNTRUSTEDSOURCE* = 0x00008000'u32 + + DEBUG_PROCESS* = 0x1'u32 + DEBUG_ONLY_THIS_PROCESS* = 0x2'u32 + CREATE_SUSPENDED* = 0x4'u32 + DETACHED_PROCESS* = 0x8'u32 + CREATE_NEW_CONSOLE* = 0x10'u32 + NORMAL_PRIORITY_CLASS* = 0x20'u32 + IDLE_PRIORITY_CLASS* = 0x40'u32 + HIGH_PRIORITY_CLASS* = 0x80'u32 + REALTIME_PRIORITY_CLASS* = 0x100'u32 + CREATE_NEW_PROCESS_GROUP* = 0x200'u32 + CREATE_UNICODE_ENVIRONMENT* = 0x400'u32 + CREATE_SEPARATE_WOW_VDM* = 0x800'u32 + CREATE_SHARED_WOW_VDM* = 0x1000'u32 + CREATE_FORCEDOS* = 0x2000'u32 + BELOW_NORMAL_PRIORITY_CLASS* = 0x4000'u32 + ABOVE_NORMAL_PRIORITY_CLASS* = 0x8000'u32 + INHERIT_PARENT_AFFINITY* = 0x10000'u32 + INHERIT_CALLER_PRIORITY* = 0x20000'u32 + CREATE_PROTECTED_PROCESS* = 0x40000'u32 + EXTENDED_STARTUPINFO_PRESENT* = 0x80000'u32 + PROCESS_MODE_BACKGROUND_BEGIN* = 0x100000'u32 + PROCESS_MODE_BACKGROUND_END* = 0x200000'u32 + CREATE_SECURE_PROCESS* = 0x400000'u32 + CREATE_BREAKAWAY_FROM_JOB* = 0x1000000'u32 + CREATE_PRESERVE_CODE_AUTHZ_LEVEL* = 0x2000000'u32 + CREATE_DEFAULT_ERROR_MODE* = 0x4000000'u32 + CREATE_NO_WINDOW* = 0x8000000'u32 + PROFILE_USER* = 0x10000000'u32 + PROFILE_KERNEL* = 0x20000000'u32 + PROFILE_SERVER* = 0x40000000'u32 + CREATE_IGNORE_SYSTEM_DEFAULT* = 0x80000000'u32 + + STILL_ACTIVE* = 0x00000103'u32 + + WSAID_CONNECTEX* = + GUID(D1: 0x25a207b9'u32, D2: 0xddf3'u16, D3: 0x4660'u16, + D4: [0x8e'u8, 0xe9'u8, 0x76'u8, 0xe5'u8, + 0x8c'u8, 0x74'u8, 0x06'u8, 0x3e'u8]) + WSAID_ACCEPTEX* = + GUID(D1: 0xb5367df1'u32, D2: 0xcbac'u16, D3: 0x11cf'u16, + D4: [0x95'u8, 0xca'u8, 0x00'u8, 0x80'u8, + 0x5f'u8, 0x48'u8, 0xa1'u8, 0x92'u8]) + WSAID_GETACCEPTEXSOCKADDRS* = + GUID(D1: 0xb5367df2'u32, D2: 0xcbac'u16, D3: 0x11cf'u16, + D4: [0x95'u8, 0xca'u8, 0x00'u8, 0x80'u8, + 0x5f'u8, 0x48'u8, 0xa1'u8, 0x92'u8]) + WSAID_TRANSMITFILE* = + GUID(D1: 0xb5367df0'u32, D2: 0xcbac'u16, D3: 0x11cf'u16, + D4: [0x95'u8, 0xca'u8, 0x00'u8, 0x80'u8, + 0x5f'u8, 0x48'u8, 0xa1'u8, 0x92'u8]) + + GAA_FLAG_INCLUDE_PREFIX* = 0x0010'u32 + + DELETE* = 0x00010000'u32 + READ_CONTROL* = 0x00020000'u32 + WRITE_DAC* = 0x00040000'u32 + WRITE_OWNER* = 0x00080000'u32 + SYNCHRONIZE* = 0x00100000'u32 + + CP_UTF8* = 65001'u32 + + WSA_FLAG_OVERLAPPED* = 0x01'u32 + WSA_FLAG_NO_HANDLE_INHERIT* = 0x80'u32 + + FIONBIO* = WSAIOW(102, 126) + + HANDLE_FLAG_INHERIT* = 1'u32 + + type + LONG* = int32 + ULONG* = uint32 + PULONG* = ptr uint32 + WINBOOL* = uint32 + DWORD* = uint32 + WORD* = uint16 + UINT* = uint32 + PDWORD* = ptr DWORD + LPINT* = ptr int32 + LPSTR* = cstring + ULONG_PTR* = uint + PULONG_PTR* = ptr uint + WCHAR* = distinct uint16 + LPWSTR* = ptr WCHAR + GROUP* = uint32 + + WSAData* {.final, pure.} = object + wVersion*, wHighVersion*: WORD + when sizeof(int) == 8: + iMaxSockets*, iMaxUdpDg*: WORD + lpVendorInfo*: cstring + szDescription*: array[WSADESCRIPTION_LEN + 1, char] + szSystemStatus*: array[WSASYS_STATUS_LEN + 1, char] + else: + szDescription*: array[WSADESCRIPTION_LEN + 1, char] + szSystemStatus*: array[WSASYS_STATUS_LEN + 1, char] + iMaxSockets*, iMaxUdpDg*: WORD + lpVendorInfo*: cstring + + SECURITY_ATTRIBUTES* {.final, pure.} = object + nLength*: DWORD + lpSecurityDescriptor*: pointer + bInheritHandle*: WINBOOL + + OVERLAPPED* {.pure, inheritable.} = object + internal*: PULONG + internalHigh*: PULONG + offset*: DWORD + offsetHigh*: DWORD + hEvent*: HANDLE + + WSABUF* {.final, pure.} = object + len*: ULONG + buf*: cstring + + POVERLAPPED* = ptr OVERLAPPED + + POVERLAPPED_COMPLETION_ROUTINE* = proc (para1: DWORD, para2: DWORD, + para3: POVERLAPPED) {. + stdcall, gcsafe, raises: [].} + + OSVERSIONINFO* {.final, pure.} = object + dwOSVersionInfoSize*: DWORD + dwMajorVersion*: DWORD + dwMinorVersion*: DWORD + dwBuildNumber*: DWORD + dwPlatformId*: DWORD + szCSDVersion*: array[0..127, WORD] + + STARTUPINFO* {.final, pure.} = object + cb*: DWORD + lpReserved*: LPSTR + lpDesktop*: LPSTR + lpTitle*: LPSTR + dwX*: DWORD + dwY*: DWORD + dwXSize*: DWORD + dwYSize*: DWORD + dwXCountChars*: DWORD + dwYCountChars*: DWORD + dwFillAttribute*: DWORD + dwFlags*: DWORD + wShowWindow*: WORD + cbReserved2*: WORD + lpReserved2*: pointer + hStdInput*: HANDLE + hStdOutput*: HANDLE + hStdError*: HANDLE + + PROCESS_INFORMATION* {.final, pure.} = object + hProcess*: HANDLE + hThread*: HANDLE + dwProcessId*: DWORD + dwThreadId*: DWORD + + FILETIME* {.final, pure.} = object + dwLowDateTime*: DWORD + dwHighDateTime*: DWORD + + SocketAddress* {.final, pure.} = object + lpSockaddr*: ptr SockAddr + iSockaddrLength*: cint + + IpAdapterUnicastAddressXpLh* {.final, pure.} = object + length*: uint32 + flags*: uint32 + next*: ptr IpAdapterUnicastAddressXpLh + address*: SocketAddress + prefixOrigin*: cint + suffixOrigin*: cint + dadState*: cint + validLifetime*: uint32 + preferredLifetime*: uint32 + leaseLifetime*: uint32 + onLinkPrefixLength*: byte # This field is available only from Vista + + IpAdapterAnycastAddressXp* {.final, pure.} = object + length*: uint32 + flags*: uint32 + next*: ptr IpAdapterAnycastAddressXp + address*: SocketAddress + + IpAdapterMulticastAddressXp* {.final, pure.} = object + length*: uint32 + flags*: uint32 + next*: ptr IpAdapterMulticastAddressXp + address*: SocketAddress + + IpAdapterDnsServerAddressXp* {.final, pure.} = object + length*: uint32 + flags*: uint32 + next*: ptr IpAdapterDnsServerAddressXp + address*: SocketAddress + + IpAdapterPrefixXp* {.final, pure.} = object + length*: uint32 + flags*: uint32 + next*: ptr IpAdapterPrefixXp + address*: SocketAddress + prefixLength*: uint32 + + IpAdapterAddressesXp* {.final, pure.} = object + length*: uint32 + ifIndex*: uint32 + next*: ptr IpAdapterAddressesXp + adapterName*: cstring + unicastAddress*: ptr IpAdapterUnicastAddressXpLh + anycastAddress*: ptr IpAdapterAnycastAddressXp + multicastAddress*: ptr IpAdapterMulticastAddressXp + dnsServerAddress*: ptr IpAdapterDnsServerAddressXp + dnsSuffix*: ptr WCHAR + description*: ptr WCHAR + friendlyName*: ptr WCHAR + physicalAddress*: array[MAX_ADAPTER_ADDRESS_LENGTH, byte] + physicalAddressLength*: uint32 + flags*: uint32 + mtu*: uint32 + ifType*: uint32 + operStatus*: cint + ipv6IfIndex*: uint32 + zoneIndices*: array[16, uint32] + firstPrefix*: ptr IpAdapterPrefixXp + + MibIpForwardRow* {.final, pure.} = object + dwForwardDest*: uint32 + dwForwardMask*: uint32 + dwForwardPolicy*: uint32 + dwForwardNextHop*: uint32 + dwForwardIfIndex*: uint32 + dwForwardType*: uint32 + dwForwardProto*: uint32 + dwForwardAge*: uint32 + dwForwardNextHopAS*: uint32 + dwForwardMetric1*: uint32 + dwForwardMetric2*: uint32 + dwForwardMetric3*: uint32 + dwForwardMetric4*: uint32 + dwForwardMetric5*: uint32 + + SOCKADDR_INET* {.union.} = object + ipv4*: Sockaddr_in + ipv6*: Sockaddr_in6 + si_family*: uint16 + + IPADDRESS_PREFIX* {.final, pure.} = object + prefix*: SOCKADDR_INET + prefixLength*: uint8 + + MibIpForwardRow2* {.final, pure.} = object + interfaceLuid*: uint64 + interfaceIndex*: uint32 + destinationPrefix*: IPADDRESS_PREFIX + nextHop*: SOCKADDR_INET + sitePrefixLength*: byte + validLifetime*: uint32 + preferredLifetime*: uint32 + metric*: uint32 + protocol*: uint32 + loopback*: bool + autoconfigureAddress*: bool + publish*: bool + immortal*: bool + age*: uint32 + origin*: uint32 + + OVERLAPPED_ENTRY* = object + lpCompletionKey*: ULONG_PTR + lpOverlapped*: ptr OVERLAPPED + internal*: ULONG_PTR + dwNumberOfBytesTransferred*: DWORD + + GETBESTROUTE2* = proc ( + interfaceLuid: ptr uint64, interfaceIndex: uint32, + sourceAddress: ptr SOCKADDR_INET, destinationAddress: ptr SOCKADDR_INET, + addressSortOptions: uint32, bestRoute: ptr MibIpForwardRow2, + bestSourceAddress: ptr SOCKADDR_INET): DWORD {. + gcsafe, stdcall, raises: [].} + + WAITORTIMERCALLBACK* = proc ( + p1: pointer, p2: DWORD): void {. + gcsafe, stdcall, raises: [].} + + WSAPROC_ACCEPTEX* = proc ( + sListenSocket: SocketHandle, sAcceptSocket: SocketHandle, + lpOutputBuffer: pointer, dwReceiveDataLength: DWORD, + dwLocalAddressLength: DWORD, dwRemoteAddressLength: DWORD, + lpdwBytesReceived: ptr DWORD, lpOverlapped: POVERLAPPED): WINBOOL {. + stdcall, gcsafe, raises: [].} + + WSAPROC_CONNECTEX* = proc ( + s: SocketHandle, name: ptr SockAddr, namelen: cint, lpSendBuffer: pointer, + dwSendDataLength: DWORD, lpdwBytesSent: ptr DWORD, + lpOverlapped: POVERLAPPED): WINBOOL {. + stdcall, gcsafe, raises: [].} + + WSAPROC_GETACCEPTEXSOCKADDRS* = proc ( + lpOutputBuffer: pointer, dwReceiveDataLength: DWORD, + dwLocalAddressLength: DWORD, dwRemoteAddressLength: DWORD, + localSockaddr: ptr ptr SockAddr, localSockaddrLength: LPINT, + remoteSockaddr: ptr ptr SockAddr, remoteSockaddrLength: LPINT) {. + stdcall, gcsafe, raises: [].} + + WSAPROC_TRANSMITFILE* = proc ( + hSocket: SocketHandle, hFile: HANDLE, nNumberOfBytesToWrite: DWORD, + nNumberOfBytesPerSend: DWORD, lpOverlapped: POVERLAPPED, + lpTransmitBuffers: pointer, dwReserved: DWORD): WINBOOL {. + stdcall, gcsafe, raises: [].} + + LPFN_GETQUEUEDCOMPLETIONSTATUSEX* = proc ( + completionPort: HANDLE, lpPortEntries: ptr OVERLAPPED_ENTRY, + ulCount: ULONG, ulEntriesRemoved: var ULONG, + dwMilliseconds: DWORD, fAlertable: WINBOOL): WINBOOL {. + stdcall, gcsafe, raises: [].} + + proc getVersionEx*(lpVersionInfo: ptr OSVERSIONINFO): WINBOOL {. + stdcall, dynlib: "kernel32", importc: "GetVersionExW", sideEffect.} + + proc createProcess*(lpApplicationName, lpCommandLine: LPWSTR, + lpProcessAttributes: ptr SECURITY_ATTRIBUTES, + lpThreadAttributes: ptr SECURITY_ATTRIBUTES, + bInheritHandles: WINBOOL, dwCreationFlags: DWORD, + lpEnvironment, lpCurrentDirectory: LPWSTR, + lpStartupInfo: var STARTUPINFO, + lpProcessInformation: var PROCESS_INFORMATION): WINBOOL {. + stdcall, dynlib: "kernel32", importc: "CreateProcessW", sideEffect.} + + proc terminateProcess*(hProcess: HANDLE, uExitCode: UINT): WINBOOL {. + stdcall, dynlib: "kernel32", importc: "TerminateProcess", sideEffect.} + + proc getExitCodeProcess*(hProcess: HANDLE, lpExitCode: var DWORD): WINBOOL {. + stdcall, dynlib: "kernel32", importc: "GetExitCodeProcess", sideEffect.} + + proc getStdHandle*(nStdHandle: DWORD): HANDLE {. + stdcall, dynlib: "kernel32", importc: "GetStdHandle", sideEffect.} + + proc setStdHandle*(nStdHandle: DWORD, hHandle: HANDLE): WINBOOL {. + stdcall, dynlib: "kernel32", importc: "SetStdHandle", sideEffect.} + + proc suspendThread*(hThread: HANDLE): DWORD {. + stdcall, dynlib: "kernel32", importc: "SuspendThread", sideEffect.} + + proc resumeThread*(hThread: HANDLE): DWORD {. + stdcall, dynlib: "kernel32", importc: "ResumeThread", sideEffect.} + + proc closeHandle*(hObject: HANDLE): WINBOOL {. + stdcall, dynlib: "kernel32", importc: "CloseHandle", sideEffect.} + + proc flushFileBuffers*(hFile: HANDLE): WINBOOL {. + stdcall, dynlib: "kernel32", importc: "FlushFileBuffers", sideEffect.} + + proc getCurrentDirectory*(nBufferLength: DWORD, + lpBuffer: LPWSTR): DWORD {. + stdcall, dynlib: "kernel32", importc: "GetCurrentDirectoryW", + sideEffect.} + + proc setCurrentDirectory*(lpPathName: LPWSTR): DWORD {. + stdcall, dynlib: "kernel32", importc: "SetCurrentDirectoryW", + sideEffect.} + + proc setEnvironmentVariable*(lpName, lpValue: LPWSTR): DWORD {. + stdcall, dynlib: "kernel32", importc: "SetEnvironmentVariableW", + sideEffect.} + + proc getModuleFileName*(handle: HANDLE, buf: LPWSTR, + size: DWORD): DWORD {. + stdcall, dynlib: "kernel32", importc: "GetModuleFileNameW", sideEffect.} + + proc postQueuedCompletionStatus*(completionPort: HANDLE, + dwNumberOfBytesTransferred: DWORD, + dwCompletionKey: ULONG_PTR, + lpOverlapped: pointer): WINBOOL {. + stdcall, dynlib: "kernel32", importc: "PostQueuedCompletionStatus", + sideEffect.} + + proc registerWaitForSingleObject*(phNewWaitObject: ptr HANDLE, + hObject: HANDLE, + callback: WAITORTIMERCALLBACK, + context: pointer, + dwMilliseconds: ULONG, + dwFlags: ULONG): WINBOOL {. + stdcall, dynlib: "kernel32", importc: "RegisterWaitForSingleObject", + sideEffect.} + + proc waitForSingleObject*(hHandle: HANDLE, dwMilliseconds: DWORD): DWORD {. + stdcall, dynlib: "kernel32", importc: "WaitForSingleObject", + sideEffect.} + + proc unregisterWait*(waitHandle: HANDLE): DWORD {. + stdcall, dynlib: "kernel32", importc: "UnregisterWait", sideEffect.} + + proc openProcess*(dwDesiredAccess: DWORD, bInheritHandle: WINBOOL, + dwProcessId: DWORD): HANDLE {. + stdcall, dynlib: "kernel32", importc: "OpenProcess", sideEffect.} + + proc duplicateHandle*(hSourceProcessHandle: HANDLE, hSourceHandle: HANDLE, + hTargetProcessHandle: HANDLE, + lpTargetHandle: ptr HANDLE, + dwDesiredAccess: DWORD, bInheritHandle: WINBOOL, + dwOptions: DWORD): WINBOOL {. + stdcall, dynlib: "kernel32", importc: "DuplicateHandle", sideEffect.} + + proc setHandleInformation*(hObject: HANDLE, dwMask: DWORD, + dwFlags: DWORD): WINBOOL {. + stdcall, dynlib: "kernel32", importc: "SetHandleInformation", + sideEffect.} + + proc getHandleInformation*(hObject: HANDLE, lpdwFlags: var DWORD): WINBOOL {. + stdcall, dynlib: "kernel32", importc: "GetHandleInformation", + sideEffect.} + + proc getCurrentProcess*(): HANDLE {. + stdcall, dynlib: "kernel32", importc: "GetCurrentProcess", sideEffect.} + + proc getSystemTimeAsFileTime*(lpSystemTimeAsFileTime: var FILETIME) {. + stdcall, dynlib: "kernel32", importc: "GetSystemTimeAsFileTime", + sideEffect.} + + proc wsaIoctl*(s: SocketHandle, dwIoControlCode: DWORD, lpvInBuffer: pointer, + cbInBuffer: DWORD, lpvOutBuffer: pointer, cbOutBuffer: DWORD, + lpcbBytesReturned: PDWORD, lpOverlapped: POVERLAPPED, + lpCompletionRoutine: POVERLAPPED_COMPLETION_ROUTINE): cint {. + stdcall, dynlib: "ws2_32", importc: "WSAIoctl", sideEffect.} + + proc wsaStartup*(wVersionRequired: WORD, WSData: ptr WSAData): cint {. + stdcall, dynlib: "ws2_32", importc: "WSAStartup", sideEffect.} + + proc wsaRecv*(s: SocketHandle, buf: ptr WSABUF, bufCount: DWORD, + bytesReceived, flags: PDWORD, lpOverlapped: POVERLAPPED, + completionProc: POVERLAPPED_COMPLETION_ROUTINE): cint {. + stdcall, dynlib: "ws2_32", importc: "WSARecv", sideEffect.} + + proc wsaRecvFrom*(s: SocketHandle, buf: ptr WSABUF, bufCount: DWORD, + bytesReceived: PDWORD, flags: PDWORD, name: ptr SockAddr, + namelen: ptr cint, lpOverlapped: POVERLAPPED, + completionProc: POVERLAPPED_COMPLETION_ROUTINE): cint {. + stdcall, dynlib: "ws2_32", importc: "WSARecvFrom", sideEffect.} + + proc wsaSend*(s: SocketHandle, buf: ptr WSABUF, bufCount: DWORD, + bytesSent: PDWORD, flags: DWORD, lpOverlapped: POVERLAPPED, + completionProc: POVERLAPPED_COMPLETION_ROUTINE): cint {. + stdcall, dynlib: "ws2_32", importc: "WSASend", sideEffect.} + + proc wsaSendTo*(s: SocketHandle, buf: ptr WSABUF, bufCount: DWORD, + bytesSent: PDWORD, flags: DWORD, name: ptr SockAddr, + namelen: cint, lpOverlapped: POVERLAPPED, + completionProc: POVERLAPPED_COMPLETION_ROUTINE): cint {. + stdcall, dynlib: "ws2_32", importc: "WSASendTo", sideEffect.} + + proc wsaGetLastError*(): cint {. + stdcall, dynlib: "ws2_32", importc: "WSAGetLastError", sideEffect.} + + proc wsaSocket*(af: cint, stype: cint, protocol: cint, + lpProtocolInfo: pointer, g: GROUP, + dwFlags: DWORD): SocketHandle {. + stdcall, dynlib: "ws2_32", importc: "WSASocketW", sideEffect.} + + proc socket*(af, typ, protocol: cint): SocketHandle {. + stdcall, dynlib: "ws2_32", importc: "socket", sideEffect.} + + proc closesocket*(s: SocketHandle): cint {. + stdcall, dynlib: "ws2_32", importc: "closesocket", sideEffect.} + + proc shutdown*(s: SocketHandle, how: cint): cint {. + stdcall, dynlib: "ws2_32", importc: "shutdown", sideEffect.} + + proc getsockopt*(s: SocketHandle, level, optname: cint, optval: pointer, + optlen: ptr SockLen): cint {. + stdcall, dynlib: "ws2_32", importc: "getsockopt", sideEffect.} + + proc setsockopt*(s: SocketHandle, level, optname: cint, optval: pointer, + optlen: SockLen): cint {. + stdcall, dynlib: "ws2_32", importc: "setsockopt", sideEffect.} + + proc getsockname*(s: SocketHandle, name: ptr SockAddr, + namelen: ptr SockLen): cint {. + stdcall, dynlib: "ws2_32", importc: "getsockname", sideEffect.} + + proc getpeername*(s: SocketHandle, name: ptr SockAddr, + namelen: ptr SockLen): cint {. + stdcall, dynlib: "ws2_32", importc: "getpeername", sideEffect.} + + proc ioctlsocket*(s: SocketHandle, cmd: clong, argp: ptr clong): cint {. + stdcall, dynlib: "ws2_32", importc: "ioctlsocket", sideEffect.} + + proc listen*(s: SocketHandle, backlog: cint): cint {. + stdcall, dynlib: "ws2_32", importc: "listen", sideEffect.} + + proc connect*(s: SocketHandle, name: ptr SockAddr, namelen: SockLen): cint {. + stdcall, dynlib: "ws2_32", importc: "connect", sideEffect.} + + proc accept*(s: SocketHandle, a: ptr SockAddr, + addrlen: ptr SockLen): SocketHandle {. + stdcall, dynlib: "ws2_32", importc: "accept", sideEffect.} + + proc bindSocket*(s: SocketHandle, name: ptr SockAddr, + namelen: SockLen): cint {. + stdcall, dynlib: "ws2_32", importc: "bind", sideEffect.} + + proc send*(s: SocketHandle, buf: pointer, len, flags: cint): cint {. + stdcall, dynlib: "ws2_32", importc: "send", sideEffect.} + + proc getaddrinfo*(nodename, servname: cstring, hints: ptr AddrInfo, + res: var ptr AddrInfo): cint {. + stdcall, dynlib: "ws2_32", importc: "getaddrinfo", sideEffect.} + + proc freeaddrinfo*(ai: ptr AddrInfo) {. + stdcall, dynlib: "ws2_32", importc: "freeaddrinfo", sideEffect.} + + proc createIoCompletionPort*(fileHandle: HANDLE, + existingCompletionPort: HANDLE, + completionKey: ULONG_PTR, + numberOfConcurrentThreads: DWORD): HANDLE {. + stdcall, dynlib: "kernel32", importc: "CreateIoCompletionPort", + sideEffect.} + + proc getQueuedCompletionStatus*(completionPort: HANDLE, + lpNumberOfBytesTransferred: PDWORD, + lpCompletionKey: PULONG_PTR, + lpOverlapped: ptr POVERLAPPED, + dwMilliseconds: DWORD): WINBOOL {. + stdcall, dynlib: "kernel32", importc: "GetQueuedCompletionStatus", + sideEffect.} + + proc getOverlappedResult*(hFile: HANDLE, lpOverlapped: POVERLAPPED, + lpNumberOfBytesTransferred: var DWORD, + bWait: WINBOOL): WINBOOL {. + stdcall, dynlib: "kernel32", importc: "GetOverlappedResult", sideEffect.} + + proc createEvent*(lpEventAttributes: ptr SECURITY_ATTRIBUTES, + bManualReset: DWORD, bInitialState: DWORD, + lpName: LPWSTR): HANDLE {. + stdcall, dynlib: "kernel32", importc: "CreateEventW", sideEffect.} + + proc setEvent*(hEvent: HANDLE): WINBOOL {. + stdcall, dynlib: "kernel32", importc: "SetEvent", sideEffect.} + + proc createNamedPipe*(lpName: LPWSTR, + dwOpenMode, dwPipeMode, nMaxInstances, nOutBufferSize, + nInBufferSize, nDefaultTimeOut: DWORD, + lpSecurityAttributes: ptr SECURITY_ATTRIBUTES + ): HANDLE {. + stdcall, dynlib: "kernel32", importc: "CreateNamedPipeW", sideEffect.} + + proc createFile*(lpFileName: LPWSTR, dwDesiredAccess, dwShareMode: DWORD, + lpSecurityAttributes: pointer, + dwCreationDisposition, dwFlagsAndAttributes: DWORD, + hTemplateFile: HANDLE): HANDLE {. + stdcall, dynlib: "kernel32", importc: "CreateFileW", sideEffect.} + + proc reOpenFile*(hOriginalFile: HANDLE, dwDesiredAccess, dwShareMode, + dwFlagsAndAttributes: DWORD): HANDLE {. + stdcall, dynlib: "kernel32", importc: "ReOpenFile", sideEffect.} + + proc writeFile*(hFile: HANDLE, buffer: pointer, nNumberOfBytesToWrite: DWORD, + lpNumberOfBytesWritten: ptr DWORD, + lpOverlapped: pointer): WINBOOL {. + stdcall, dynlib: "kernel32", importc: "WriteFile", sideEffect.} + + proc readFile*(hFile: HANDLE, buffer: pointer, nNumberOfBytesToRead: DWORD, + lpNumberOfBytesRead: ptr DWORD, + lpOverlapped: pointer): WINBOOL {. + stdcall, dynlib: "kernel32", importc: "ReadFile", sideEffect.} + + proc cancelIo*(hFile: HANDLE): WINBOOL {. + stdcall, dynlib: "kernel32", importc: "CancelIo", sideEffect.} + + proc connectNamedPipe*(hPipe: HANDLE, + lpOverlapped: ptr OVERLAPPED): WINBOOL {. + stdcall, dynlib: "kernel32", importc: "ConnectNamedPipe", sideEffect.} + + proc disconnectNamedPipe*(hPipe: HANDLE): WINBOOL {. + stdcall, dynlib: "kernel32", importc: "DisconnectNamedPipe", sideEffect.} + + proc setNamedPipeHandleState*(hPipe: HANDLE, lpMode, lpMaxCollectionCount, + lpCollectDataTimeout: ptr DWORD): WINBOOL {. + stdcall, dynlib: "kernel32", importc: "SetNamedPipeHandleState", + sideEffect.} + + proc resetEvent*(hEvent: HANDLE): WINBOOL {. + stdcall, dynlib: "kernel32", importc: "ResetEvent", sideEffect.} + + proc getAdaptersAddresses*(family: uint32, flags: uint32, reserved: pointer, + addresses: ptr IpAdapterAddressesXp, + sizeptr: ptr uint32): uint32 {. + stdcall, dynlib: "iphlpapi", importc: "GetAdaptersAddresses", + sideEffect.} + + proc wideCharToMultiByte*(codePage: uint32, dwFlags: uint32, + lpWideCharStr: LPWSTR, cchWideChar: cint, + lpMultiByteStr: ptr char, cbMultiByte: cint, + lpDefaultChar: ptr char, + lpUsedDefaultChar: ptr uint32): cint {. + stdcall, dynlib: "kernel32", importc: "WideCharToMultiByte", sideEffect.} + + proc multiByteToWideChar*(codePage: uint32, dwFlags: uint32, + lpMultiByteStr: ptr char, cbMultiByte: cint, + lpWideCharStr: LPWSTR, cchWideChar: cint): cint {. + stdcall, dynlib: "kernel32", importc: "MultiByteToWideChar", sideEffect.} + + proc getBestRouteXp*(dwDestAddr: uint32, dwSourceAddr: uint32, + pBestRoute: ptr MibIpForwardRow): uint32 {. + stdcall, dynlib: "iphlpapi", importc: "GetBestRoute", sideEffect.} + + proc queryPerformanceCounter*(res: var uint64) {. + stdcall, dynlib: "kernel32", importc: "QueryPerformanceCounter", + sideEffect.} + + proc queryPerformanceFrequency*(res: var uint64) {. + stdcall, dynlib: "kernel32", importc: "QueryPerformanceFrequency", + sideEffect.} + + proc getEnvironmentStringsW*(): LPWSTR {. + stdcall, dynlib: "kernel32", importc: "GetEnvironmentStringsW", + sideEffect.} + + proc freeEnvironmentStringsW*(penv: LPWSTR): WINBOOL {. + stdcall, dynlib: "kernel32", importc: "FreeEnvironmentStringsW", + sideEffect.} + + proc wcschr*(ws: LPWSTR, wc: WCHAR): LPWSTR {. + stdcall, dynlib: "ntdll", importc: "wcschr", sideEffect.} + + proc getModuleHandle*(lpModuleName: WideCString): HANDLE {. + stdcall, dynlib: "kernel32", importc: "GetModuleHandleW", sideEffect.} + + proc getProcAddress*(hModule: HANDLE, lpProcName: cstring): pointer {. + stdcall, dynlib: "kernel32", importc: "GetProcAddress", sideEffect.} + + proc rtlNtStatusToDosError*(code: uint64): ULONG {. + stdcall, dynlib: "ntdll", importc: "RtlNtStatusToDosError", sideEffect.} + + proc `==`*(x, y: SocketHandle): bool {.borrow.} + proc `==`*(x, y: HANDLE): bool {.borrow.} + + proc getSecurityAttributes*(inheritHandle = false): SECURITY_ATTRIBUTES = + SECURITY_ATTRIBUTES( + nLength: DWORD(sizeof(SECURITY_ATTRIBUTES)), + lpSecurityDescriptor: nil, + bInheritHandle: if inheritHandle: TRUE else: FALSE + ) + +elif defined(macos) or defined(macosx): + import std/posix + export posix + + type + MachTimebaseInfo* {.importc: "struct mach_timebase_info", + header: "", pure, final.} = object + numer*: uint32 + denom*: uint32 + + proc posix_gettimeofday*(tp: var Timeval, unused: pointer = nil) {. + importc: "gettimeofday", header: "".} + + proc mach_timebase_info*(info: var MachTimebaseInfo) {. + importc, header: "".} + + proc mach_absolute_time*(): uint64 {. + importc, header: "".} + +elif defined(linux): + import std/[posix] + export posix + + when not defined(android) and defined(amd64): + const IP_MULTICAST_TTL*: cint = 33 + else: + var IP_MULTICAST_TTL* {.importc: "IP_MULTICAST_TTL", + header: "".}: cint + const + EPOLLIN* = 0x00000001 + EPOLLPRI* = 0x00000002 + EPOLLOUT* = 0x00000004 + EPOLLERR* = 0x00000008 + EPOLLHUP* = 0x00000010 + EPOLLRDNORM* = 0x00000040 + EPOLLRDBAND* = 0x00000080 + EPOLLWRNORM* = 0x00000100 + EPOLLWRBAND* = 0x00000200 + EPOLLMSG* = 0x00000400 + EPOLLRDHUP* = 0x00002000 + EPOLLEXCLUSIVE* = 1 shl 28 + EPOLLWAKEUP* = 1 shl 29 + EPOLLONESHOT* = 1 shl 30 + EPOLLET* = 1 shl 31 + + SFD_CLOEXEC* = cint(0x80000) + SFD_NONBLOCK* = cint(0x800) + EFD_CLOEXEC* = cint(0x80000) + EFD_NONBLOCK* = cint(0x800) + TFD_CLOEXEC* = cint(0x80000) + TFD_NONBLOCK* = cint(0x800) + + EPOLL_CTL_ADD* = 1 + EPOLL_CTL_DEL* = 2 + EPOLL_CTL_MOD* = 3 + + type + EpollData* {.importc: "union epoll_data", + header: "", pure, final.} = object + u64* {.importc: "u64".}: uint64 + + EpollEvent* {.importc: "struct epoll_event", header: "", + pure, final.} = object + events*: uint32 # Epoll events + data*: EpollData # User data variable + + proc epoll_create*(size: cint): cint {.importc: "epoll_create", + header: "", sideEffect.} + + proc epoll_create1*(flags: cint): cint {.importc: "epoll_create1", + header: "", sideEffect.} + + proc epoll_ctl*(epfd: cint; op: cint; fd: cint; event: ptr EpollEvent): cint {. + importc: "epoll_ctl", header: "", sideEffect.} + + proc epoll_wait*(epfd: cint; events: ptr EpollEvent; maxevents: cint; + timeout: cint): cint {. + importc: "epoll_wait", header: "", sideEffect.} + +else: + import std/[posix, os] + export posix, os + + var IP_MULTICAST_TTL* {.importc: "IP_MULTICAST_TTL", + header: "".}: cint + +when defined(linux) or defined(freebsd) or defined(openbsd) or + defined(netbsd) or defined(dragonfly): + + proc pipe2*(a: array[0..1, cint], flags: cint): cint {. + importc, header: "", sideEffect.} + + proc accept4*(a1: cint, a2: ptr SockAddr, a3: ptr SockLen, a4: cint): cint {. + importc, header: "", sideEffect.} + +when defined(linux): + const + SOCK_NONBLOCK* = 0x800 + SOCK_CLOEXEC* = 0x80000 + TCP_NODELAY* = cint(1) + IPPROTO_TCP* = 6 +elif defined(freebsd) or defined(netbsd) or defined(dragonfly): + const + SOCK_NONBLOCK* = 0x20000000 + SOCK_CLOEXEC* = 0x10000000 + TCP_NODELAY* = cint(1) + IPPROTO_TCP* = 6 +elif defined(openbsd): + const + SOCK_CLOEXEC* = 0x8000 + SOCK_NONBLOCK* = 0x4000 + TCP_NODELAY* = cint(1) + IPPROTO_TCP* = 6 +elif defined(macos) or defined(macosx): + const + TCP_NODELAY* = cint(1) + IP_MULTICAST_TTL* = cint(10) + IPPROTO_TCP* = 6 + +when defined(linux): + const O_CLOEXEC* = 0x80000 +elif defined(freebsd): + const O_CLOEXEC* = 0x00100000 +elif defined(openbsd): + const O_CLOEXEC* = 0x10000 +elif defined(netbsd): + const O_CLOEXEC* = 0x00400000 +elif defined(dragonfly): + const O_CLOEXEC* = 0x00020000 + +when defined(linux) or defined(macos) or defined(macosx) or defined(freebsd) or + defined(openbsd) or defined(netbsd) or defined(dragonfly): + export SIGHUP, SIGINT, SIGQUIT, SIGILL, SIGTRAP, SIGABRT, + SIGBUS, SIGFPE, SIGKILL, SIGUSR1, SIGSEGV, SIGUSR2, + SIGPIPE, SIGALRM, SIGTERM, SIGPIPE + + const + POSIX_SPAWN_RESETIDS* = 0x01 + POSIX_SPAWN_SETPGROUP* = 0x02 + POSIX_SPAWN_SETSCHEDPARAM* = 0x04 + POSIX_SPAWN_SETSCHEDULER* = 0x08 + POSIX_SPAWN_SETSIGDEF* = 0x10 + POSIX_SPAWN_SETSIGMASK* = 0x20 + + type + SchedParam* {.importc: "struct sched_param", header: "", + final, pure.} = object ## struct sched_param + sched_priority*: cint + sched_ss_low_priority*: cint ## Low scheduling priority for + ## sporadic server. + sched_ss_repl_period*: Timespec ## Replenishment period for + ## sporadic server. + sched_ss_init_budget*: Timespec ## Initial budget for sporadic server. + sched_ss_max_repl*: cint ## Maximum pending replenishments for + ## sporadic server. + + PosixSpawnAttr* {.importc: "posix_spawnattr_t", + header: "", final, pure.} = object + flags*: cshort + pgrp*: Pid + sd*: Sigset + ss*: Sigset + sp*: SchedParam + policy*: cint + pad*: array[16, cint] + + PosixSpawnFileActions* {.importc: "posix_spawn_file_actions_t", + header: "", final, pure.} = object + allocated*: cint + used*: cint + actions*: pointer + pad*: array[16, cint] + + proc posixSpawn*(a1: var Pid, a2: cstring, a3: var PosixSpawnFileActions, + a4: var PosixSpawnAttr, a5, a6: cstringArray): cint {. + importc: "posix_spawn", header: "", sideEffect.} + proc posixSpawnp*(a1: var Pid, a2: cstring, a3: var PosixSpawnFileActions, + a4: var PosixSpawnAttr, a5, a6: cstringArray): cint {. + importc: "posix_spawnp", header: "", sideEffect.} + + proc posixSpawnFileActionsInit*(a1: var PosixSpawnFileActions): cint {. + importc: "posix_spawn_file_actions_init", header: "", + sideEffect.} + proc posixSpawnFileActionsDestroy*(a1: var PosixSpawnFileActions): cint {. + importc: "posix_spawn_file_actions_destroy", header: "", + sideEffect.} + + proc posixSpawnFileActionsAddClose*(a1: var PosixSpawnFileActions, + a2: cint): cint {. + importc: "posix_spawn_file_actions_addclose", header: "", + sideEffect.} + proc posixSpawnFileActionsAddDup2*(a1: var PosixSpawnFileActions, + a2, a3: cint): cint {. + importc: "posix_spawn_file_actions_adddup2", header: "", + sideEffect.} + proc posixSpawnFileActionsAddOpen*(a1: var PosixSpawnFileActions, + a2: cint, a3: cstring, a4: cint, + a5: Mode): cint {. + importc: "posix_spawn_file_actions_addopen", header: "", + sideEffect.} + + proc posixSpawnAttrInit*(a1: var PosixSpawnAttr): cint {. + importc: "posix_spawnattr_init", header: "", + sideEffect.} + proc posixSpawnAttrDestroy*(a1: var PosixSpawnAttr): cint {. + importc: "posix_spawnattr_destroy", header: "", + sideEffect.} + + proc posixSpawnAttrGetSigDefault*(a1: var PosixSpawnAttr, + a2: var Sigset): cint {. + importc: "posix_spawnattr_getsigdefault", header: "", + sideEffect.} + proc posixSpawnAttrSetSigDefault*(a1: var PosixSpawnAttr, + a2: var Sigset): cint {. + importc: "posix_spawnattr_setsigdefault", header: "", + sideEffect.} + proc posixSpawnAttrGetFlags*(a1: var PosixSpawnAttr, + a2: var cshort): cint {. + importc: "posix_spawnattr_getflags", header: "", + sideEffect.} + proc posixSpawnAttrSetFlags*(a1: var PosixSpawnAttr, a2: cint): cint {. + importc: "posix_spawnattr_setflags", header: "", + sideEffect.} + proc posixSpawnAttrGetPgroup*(a1: var PosixSpawnAttr, + a2: var Pid): cint {. + importc: "posix_spawnattr_getpgroup", header: "", + sideEffect.} + proc posixSpawnAttrSetPgroup*(a1: var PosixSpawnAttr, a2: Pid): cint {. + importc: "posix_spawnattr_setpgroup", header: "", + sideEffect.} + proc posixSpawnAttrGetSchedParam*(a1: var PosixSpawnAttr, + a2: var SchedParam): cint {. + importc: "posix_spawnattr_getschedparam", header: "", + sideEffect.} + proc posixSpawnAttrSetSchedParam*(a1: var PosixSpawnAttr, + a2: var SchedParam): cint {. + importc: "posix_spawnattr_setschedparam", header: "", + sideEffect.} + proc posixSpawnAttrGetSchedPolicy*(a1: var PosixSpawnAttr, + a2: var cint): cint {. + importc: "posix_spawnattr_getschedpolicy", header: "", + sideEffect.} + proc posixSpawnAttrSetSchedPolicy*(a1: var PosixSpawnAttr, + a2: cint): cint {. + importc: "posix_spawnattr_setschedpolicy", header: "", + sideEffect.} + proc posixSpawnAttrGetSigMask*(a1: var PosixSpawnAttr, + a2: var Sigset): cint {. + importc: "posix_spawnattr_getsigmask", header: "", + sideEffect.} + proc posixSpawnAttrSetSigMask*(a1: var PosixSpawnAttr, + a2: var Sigset): cint {. + importc: "posix_spawnattr_setsigmask", header: "", + sideEffect.} + +when defined(posix): + when defined(linux): + const + P_PID* = cint(1) + WNOHANG* = cint(1) + WSTOPPED* = cint(2) + WEXITTED* = cint(4) + WNOWAIT* = cint(0x01000000) + template WSTATUS(s: cint): cint = + s and 0x7F + template WAITEXITSTATUS*(s: cint): cint = + (s and 0xFF00) shr 8 + template WAITTERMSIG*(s: cint): cint = + WSTATUS(s) + template WAITSTOPSIG*(s: cint): cint = + WAITEXITSTATUS(s) + template WAITIFEXITED*(s: cint): bool = + WSTATUS(s) == 0 + template WAITIFSIGNALED*(s: cint): bool = + (cast[int8](WSTATUS(s) + 1) shr 1) > 0 + template WAITIFSTOPPED*(s: cint): bool = + (s and 0xFF) == 0x7F + template WAITIFCONTINUED*(s: cint): bool = + s == 0xFFFF + elif defined(openbsd): + const WNOHANG* = 1 + template WSTATUS(s: cint): cint = + s and 0x7F + template WAITEXITSTATUS*(s: cint): cint = + (s shr 8) and 0xFF + template WAITTERMSIG*(s: cint): cint = + WSTATUS(s) + template WAITSTOPSIG*(s: cint): cint = + WAITEXITSTATUS(s) + template WAITIFEXITED*(s: cint): bool = + WSTATUS(s) == 0 + template WAITIFSIGNALED*(s: cint): bool = + (WAITTERMSIG(s) != 0x7F) and (WSTATUS(s) != 0) + template WAITIFSTOPPED*(s: cint): bool = + WSTATUS(s) == 0x7F + template WAITIFCONTINUED*(s: cint): bool = + s == 0xFFFF + elif defined(dragonfly): + const WNOHANG* = 1 + template WSTATUS(s: cint): cint = + s and 0x7F + template WAITEXITSTATUS*(s: cint): cint = + (s shr 8) + template WAITTERMSIG*(s: cint): cint = + WSTATUS(s) + template WAITSTOPSIG*(s: cint): cint = + WAITEXITSTATUS(s) + template WAITIFEXITED*(s: cint): bool = + WSTATUS(s) == 0 + template WAITIFSIGNALED*(s: cint): bool = + (WAITTERMSIG(s) != 0x7F) and (WSTATUS(s) != 0) + template WAITIFSTOPPED*(s: cint): bool = + WSTATUS(s) == 0x7F + template WAITIFCONTINUED*(s: cint): bool = + s == 19 + elif defined(netbsd): + const WNOHANG* = 1 + template WSTATUS(s: cint): cint = + s and 0x7F + template WAITEXITSTATUS*(s: cint): cint = + (s shr 8) and 0xFF + template WAITTERMSIG*(s: cint): cint = + WSTATUS(s) + template WAITSTOPSIG*(s: cint): cint = + WAITEXITSTATUS(s) + template WAITIFEXITED*(s: cint): bool = + WSTATUS(s) == 0 + template WAITIFSIGNALED*(s: cint): bool = + not(WAITIFSTOPPED(s)) and not(WAITIFCONTINUED(s)) and not(WAITIFEXITED(s)) + template WAITIFSTOPPED*(s: cint): bool = + (WSTATUS(s) == 0x7F) and not(WAITIFCONTINUED(s)) + template WAITIFCONTINUED*(s: cint): bool = + s == 0xFFFF + elif defined(freebsd): + const WNOHANG* = 1 + template WSTATUS(s: cint): cint = + s and 0x7F + template WAITEXITSTATUS*(s: cint): cint = + s shr 8 + template WAITTERMSIG*(s: cint): cint = + WSTATUS(s) + template WAITSTOPSIG*(s: cint): cint = + s shr 8 + template WAITIFEXITED*(s: cint): bool = + WSTATUS(s) == 0 + template WAITIFSIGNALED*(s: cint): bool = + let wstatus = WSTATUS(s) + (wstatus != 0x7F) and (wstatus != 0) and (s != 0x13) + template WAITIFSTOPPED*(s: cint): bool = + WSTATUS(s) == 0x7F + template WAITIFCONTINUED*(s: cint): bool = + x == 0x13 + elif defined(macos) or defined(macosx): + const WNOHANG* = 1 + template WSTATUS(s: cint): cint = + s and 0x7F + template WAITEXITSTATUS*(s: cint): cint = + (s shr 8) and 0xFF + template WAITTERMSIG*(s: cint): cint = + WSTATUS(s) + template WAITSTOPSIG*(s: cint): cint = + s shr 8 + template WAITIFEXITED*(s: cint): bool = + WSTATUS(s) == 0 + template WAITIFSIGNALED*(s: cint): bool = + let wstatus = WSTATUS(s) + (wstatus != 0x7F) and (wstatus != 0) + template WAITIFSTOPPED*(s: cint): bool = + (WSTATUS(s) == 0x7F) and (WAITSTOPSIG(s) != 0x13) + template WAITIFCONTINUED*(s: cint): bool = + (WSTATUS(s) == 0x7F) and (WAITSTOPSIG(s) == 0x13) + else: + proc WAITEXITSTATUS*(s: cint): cint {. + importc: "WEXITSTATUS", header: "".} + ## Exit code, iff WIFEXITED(s) + proc WAITTERMSIG*(s: cint): cint {. + importc: "WTERMSIG", header: "".} + ## Termination signal, iff WIFSIGNALED(s) + proc WAITSTOPSIG*(s: cint): cint {. + importc: "WSTOPSIG", header: "".} + ## Stop signal, iff WIFSTOPPED(s) + proc WAITIFEXITED*(s: cint): bool {. + importc: "WIFEXITED", header: "".} + ## True if child exited normally. + proc WAITIFSIGNALED*(s: cint): bool {. + importc: "WIFSIGNALED", header: "".} + ## True if child exited due to uncaught signal. + proc WAITIFSTOPPED*(s: cint): bool {. + importc: "WIFSTOPPED", header: "".} + ## True if child is currently stopped. + proc WAITIFCONTINUED*(s: cint): bool {. + importc: "WIFCONTINUED", header: "".} + ## True if child has been continued. + +when defined(posix): + const + INVALID_SOCKET* = SocketHandle(-1) + INVALID_HANDLE_VALUE* = cint(-1) + +proc `==`*(x: OSErrorCode, y: int): bool = + int(x) == y +proc `==`*(x: SocketHandle, y: int): bool = + x == SocketHandle(y) + +when defined(macosx) or defined(macos) or defined(bsd): + const + AF_LINK* = 18 + IFF_UP* = 0x01 + IFF_RUNNING* = 0x40 + + PF_ROUTE* = cint(17) + RTM_GET* = 0x04'u8 + RTF_UP* = 0x01 + RTF_GATEWAY* = 0x02 + RTM_VERSION* = 5'u8 + + RTA_DST* = 0x01 + RTA_GATEWAY* = 0x02 + + type + IfAddrs* {.importc: "struct ifaddrs", header: "", + pure, final.} = object + ifa_next* {.importc: "ifa_next".}: ptr IfAddrs + ifa_name* {.importc: "ifa_name".}: ptr cchar + ifa_flags* {.importc: "ifa_flags".}: cuint + ifa_addr* {.importc: "ifa_addr".}: ptr SockAddr + ifa_netmask* {.importc: "ifa_netmask".}: ptr SockAddr + ifa_dstaddr* {.importc: "ifa_dstaddr".}: ptr SockAddr + ifa_data* {.importc: "ifa_data".}: pointer + + PIfAddrs* = ptr IfAddrs + + IfData* {.importc: "struct if_data", header: "", + pure, final.} = object + ifi_type* {.importc: "ifi_type".}: byte + ifi_typelen* {.importc: "ifi_typelen".}: byte + ifi_physical* {.importc: "ifi_physical".}: byte + ifi_addrlen* {.importc: "ifi_addrlen".}: byte + ifi_hdrlen* {.importc: "ifi_hdrlen".}: byte + ifi_recvquota* {.importc: "ifi_recvquota".}: byte + ifi_xmitquota* {.importc: "ifi_xmitquota".}: byte + ifi_unused1* {.importc: "ifi_unused1".}: byte + ifi_mtu* {.importc: "ifi_mtu".}: uint32 + ifi_metric* {.importc: "ifi_metric".}: uint32 + ifi_baudrate* {.importc: "ifi_baudrate".}: uint32 + ifi_ipackets* {.importc: "ifi_ipackets".}: uint32 + ifi_ierrors* {.importc: "ifi_ierrors".}: uint32 + ifi_opackets* {.importc: "ifi_opackets".}: uint32 + ifi_oerrors* {.importc: "ifi_oerrors".}: uint32 + ifi_collisions* {.importc: "ifi_collisions".}: uint32 + ifi_ibytes* {.importc: "ifi_ibytes".}: uint32 + ifi_obytes* {.importc: "ifi_obytes".}: uint32 + ifi_imcasts* {.importc: "ifi_imcasts".}: uint32 + ifi_omcasts* {.importc: "ifi_omcasts".}: uint32 + ifi_iqdrops* {.importc: "ifi_iqdrops".}: uint32 + ifi_noproto* {.importc: "ifi_noproto".}: uint32 + ifi_recvtiming* {.importc: "ifi_recvtiming".}: uint32 + ifi_xmittiming* {.importc: "ifi_xmittiming".}: uint32 + ifi_lastchange* {.importc: "ifi_lastchange".}: Timeval + ifi_unused2* {.importc: "ifi_unused2".}: uint32 + ifi_hwassist* {.importc: "ifi_hwassist".}: uint32 + ifi_reserved1* {.importc: "ifi_reserved1".}: uint32 + ifi_reserved2* {.importc: "ifi_reserved2".}: uint32 + + Sockaddr_dl* = object + sdl_len*: byte + sdl_family*: byte + sdl_index*: uint16 + sdl_type*: byte + sdl_nlen*: byte + sdl_alen*: byte + sdl_slen*: byte + sdl_data*: array[12, byte] + + RtMetrics* = object + rmx_locks*: uint32 + rmx_mtu*: uint32 + rmx_hopcount*: uint32 + rmx_expire*: int32 + rmx_recvpipe*: uint32 + rmx_sendpipe*: uint32 + rmx_ssthresh*: uint32 + rmx_rtt*: uint32 + rmx_rttvar*: uint32 + rmx_pksent*: uint32 + rmx_state*: uint32 + rmx_filler*: array[3, uint32] + + RtMsgHeader* = object + rtm_msglen*: uint16 + rtm_version*: byte + rtm_type*: byte + rtm_index*: uint16 + rtm_flags*: cint + rtm_addrs*: cint + rtm_pid*: Pid + rtm_seq*: cint + rtm_errno*: cint + rtm_use*: cint + rtm_inits*: uint32 + rtm_rmx*: RtMetrics + + RtMessage* = object + rtm*: RtMsgHeader + space*: array[512, byte] + + proc getIfAddrs*(ifap: ptr PIfAddrs): cint {.importc: "getifaddrs", + header: """#include + #include + #include """.} + proc freeIfAddrs*(ifap: ptr IfAddrs) {.importc: "freeifaddrs", + header: """#include + #include + #include """.} +elif defined(linux): + const + AF_NETLINK* = cint(16) + AF_PACKET* = cint(17) + NETLINK_ROUTE* = cint(0) + NLMSG_ALIGNTO* = 4'u + RTA_ALIGNTO* = 4'u + # RTA_UNSPEC* = 0'u16 + RTA_DST* = 1'u16 + # RTA_SRC* = 2'u16 + # RTA_IIF* = 3'u16 + RTA_OIF* = 4'u16 + RTA_GATEWAY* = 5'u16 + # RTA_PRIORITY* = 6'u16 + RTA_PREFSRC* = 7'u16 + # RTA_METRICS* = 8'u16 + + RTM_F_LOOKUP_TABLE* = 0x1000 + + RTM_GETLINK* = 18 + RTM_GETADDR* = 22 + RTM_GETROUTE* = 26 + NLM_F_REQUEST* = 1 + NLM_F_ROOT* = 0x100 + NLM_F_MATCH* = 0x200 + NLM_F_DUMP* = NLM_F_ROOT or NLM_F_MATCH + IFLIST_REPLY_BUFFER* = 8192 + InvalidSocketHandle* = SocketHandle(-1) + NLMSG_DONE* = 0x03 + # NLMSG_MIN_TYPE* = 0x10 + NLMSG_ERROR* = 0x02 + # MSG_TRUNC* = 0x20 + + IFLA_ADDRESS* = 1 + IFLA_IFNAME* = 3 + IFLA_MTU* = 4 + IFLA_OPERSTATE* = 16 + + IFA_ADDRESS* = 1 + IFA_LOCAL* = 2 + # IFA_BROADCAST* = 4 + + # ARPHRD_NETROM* = 0 + ARPHRD_ETHER* = 1 + ARPHRD_EETHER* = 2 + # ARPHRD_AX25* = 3 + # ARPHRD_PRONET* = 4 + # ARPHRD_CHAOS* = 5 + # ARPHRD_IEEE802* = 6 + ARPHRD_ARCNET* = 7 + # ARPHRD_APPLETLK* = 8 + # ARPHRD_DLCI* = 15 + ARPHRD_ATM* = 19 + # ARPHRD_METRICOM* = 23 + ARPHRD_IEEE1394* = 24 + # ARPHRD_EUI64* = 27 + # ARPHRD_INFINIBAND* = 32 + ARPHRD_SLIP* = 256 + ARPHRD_CSLIP* = 257 + ARPHRD_SLIP6* = 258 + ARPHRD_CSLIP6* = 259 + # ARPHRD_RSRVD* = 260 + # ARPHRD_ADAPT* = 264 + # ARPHRD_ROSE* = 270 + # ARPHRD_X25* = 271 + # ARPHRD_HWX25* = 272 + # ARPHRD_CAN* = 280 + ARPHRD_PPP* = 512 + ARPHRD_CISCO* = 513 + ARPHRD_HDLC* = ARPHRD_CISCO + ARPHRD_LAPB* = 516 + # ARPHRD_DDCMP* = 517 + # ARPHRD_RAWHDLC* = 518 + # ARPHRD_TUNNEL* = 768 + # ARPHRD_TUNNEL6* = 769 + ARPHRD_FRAD* = 770 + # ARPHRD_SKIP* = 771 + ARPHRD_LOOPBACK* = 772 + # ARPHRD_LOCALTLK* = 773 + # ARPHRD_FDDI* = 774 + # ARPHRD_BIF* = 775 + # ARPHRD_SIT* = 776 + # ARPHRD_IPDDP* = 777 + # ARPHRD_IPGRE* = 778 + # ARPHRD_PIMREG* = 779 + ARPHRD_HIPPI* = 780 + # ARPHRD_ASH* = 781 + # ARPHRD_ECONET* = 782 + # ARPHRD_IRDA* = 783 + # ARPHRD_FCPP* = 784 + # ARPHRD_FCAL* = 785 + # ARPHRD_FCPL* = 786 + # ARPHRD_FCFABRIC* = 787 + # ARPHRD_IEEE802_TR* = 800 + ARPHRD_IEEE80211* = 801 + ARPHRD_IEEE80211_PRISM* = 802 + ARPHRD_IEEE80211_RADIOTAP* = 803 + # ARPHRD_IEEE802154* = 804 + # ARPHRD_IEEE802154_MONITOR* = 805 + # ARPHRD_PHONET* = 820 + # ARPHRD_PHONET_PIPE* = 821 + # ARPHRD_CAIF* = 822 + # ARPHRD_IP6GRE* = 823 + # ARPHRD_NETLINK* = 824 + # ARPHRD_6LOWPAN* = 825 + # ARPHRD_VOID* = 0xFFFF + # ARPHRD_NONE* = 0xFFFE + + type + Sockaddr_nl* = object + family*: cushort + pad*: cushort + pid*: uint32 + groups*: uint32 + + NlMsgHeader* = object + nlmsg_len*: uint32 + nlmsg_type*: uint16 + nlmsg_flags*: uint16 + nlmsg_seq*: uint32 + nlmsg_pid*: uint32 + + IfInfoMessage* = object + ifi_family*: byte + ifi_pad*: byte + ifi_type*: cushort + ifi_index*: cint + ifi_flags*: cuint + ifi_change*: cuint + + IfAddrMessage* = object + ifa_family*: byte + ifa_prefixlen*: byte + ifa_flags*: byte + ifa_scope*: byte + ifa_index*: uint32 + + RtMessage* = object + rtm_family*: byte + rtm_dst_len*: byte + rtm_src_len*: byte + rtm_tos*: byte + rtm_table*: byte + rtm_protocol*: byte + rtm_scope*: byte + rtm_type*: byte + rtm_flags*: cuint + + RtAttr* = object + rta_len*: cushort + rta_type*: cushort + + RtGenMsg* = object + rtgen_family*: byte + + NLReq* = object + hdr*: NlMsgHeader + msg*: RtGenMsg + + NLRouteReq* = object + hdr*: NlMsgHeader + msg*: RtMessage diff --git a/chronos/osutils.nim b/chronos/osutils.nim new file mode 100644 index 0000000..2359469 --- /dev/null +++ b/chronos/osutils.nim @@ -0,0 +1,341 @@ +# +# Chronos' OS helpers +# +# (c) Copyright 2022-Present Status Research & Development GmbH +# +# Licensed under either of +# Apache License, version 2.0, (LICENSE-APACHEv2) +# MIT license (LICENSE-MIT) +import stew/results +import osdefs + +export results + +when (NimMajor, NimMinor) < (1, 4): + {.push raises: [Defect].} +else: + {.push raises: [].} + +when defined(windows) or defined(nimdoc): + import stew/base10 + const PipeHeaderName* = r"\\.\pipe\LOCAL\chronos\" + +type + DescriptorFlag* {.pure.} = enum + CloseOnExec, NonBlock + +const + AsyncDescriptorDefault* = { + DescriptorFlag.CloseOnExec, DescriptorFlag.NonBlock} + +when defined(windows): + type + WINDESCRIPTOR* = SocketHandle|HANDLE + + template handleEintr*(body: untyped): untyped = + discard + + proc setDescriptorInheritance*(s: WINDESCRIPTOR, + value: bool): Result[void, OSErrorCode] = + var flags = 0'u32 + let fd = when s is SocketHandle: HANDLE(s) else: s + if getHandleInformation(fd, flags) == FALSE: + return err(osLastError()) + if value != ((flags and HANDLE_FLAG_INHERIT) == HANDLE_FLAG_INHERIT): + let mode = if value: HANDLE_FLAG_INHERIT else: 0'u32 + if setHandleInformation(fd, HANDLE_FLAG_INHERIT, mode) == FALSE: + return err(osLastError()) + ok() + + proc getDescriptorInheritance*(s: WINDESCRIPTOR + ): Result[bool, OSErrorCode] = + var flags = 0'u32 + let fd = when s is SocketHandle: HANDLE(s) else: s + if getHandleInformation(fd, flags) == FALSE: + return err(osLastError()) + ok((flags and HANDLE_FLAG_INHERIT) == HANDLE_FLAG_INHERIT) + + proc setDescriptorBlocking*(s: SocketHandle, + value: bool): Result[void, OSErrorCode] = + var mode = clong(ord(not value)) + if ioctlsocket(s, osdefs.FIONBIO, addr(mode)) == -1: + return err(osLastError()) + ok() + + proc setDescriptorFlags*(s: WINDESCRIPTOR, nonblock, + cloexec: bool): Result[void, OSErrorCode] = + ? setDescriptorBlocking(s, not(nonblock)) + ? setDescriptorInheritance(s, not(cloexec)) + ok() + + proc closeFd*(s: SocketHandle): int = + int(osdefs.closesocket(s)) + + proc closeFd*(s: HANDLE): int = + if osdefs.closeHandle(s) == TRUE: 0 else: -1 + + proc toWideString*(s: string): Result[LPWSTR, OSErrorCode] = + if len(s) == 0: + ok(cast[LPWSTR](alloc0(sizeof(WCHAR)))) + else: + let charsNeeded = multiByteToWideChar(CP_UTF8, 0'u32, + cast[ptr char](unsafeAddr s[0]), + cint(len(s)), nil, cint(0)) + if charsNeeded <= cint(0): + return err(osLastError()) + var buffer = cast[LPWSTR](alloc0((charsNeeded + 1) * sizeof(WCHAR))) + let res = multiByteToWideChar(CP_UTF8, 0'u32, + cast[ptr char](unsafeAddr s[0]), + cint(len(s)), buffer, charsNeeded) + if res != charsNeeded: + err(osLastError()) + else: + ok(buffer) + + proc toString*(w: LPWSTR): Result[string, OSErrorCode] = + if isNil(w): + ok("") + else: + let bytesNeeded = wideCharToMultiByte(CP_UTF8, 0'u32, w, cint(-1), nil, + cint(0), nil, nil) + if bytesNeeded <= cint(0): + return err(osLastError()) + + var buffer = newString(bytesNeeded) + let res = wideCharToMultiByte(CP_UTF8, 0'u32, w, cint(-1), + addr buffer[0], cint(len(buffer)), nil, nil) + if res != bytesNeeded: + err(osLastError()) + else: + # We need to strip trailing `\x00`. + for i in countdown(len(buffer) - 1, 0): + if buffer[i] != '\x00': + buffer.setLen(i + 1) + break + ok(buffer) + + proc free*(w: LPWSTR) = + if not(isNil(w)): + dealloc(cast[pointer](w)) + + proc createOsPipe*(readset, writeset: set[DescriptorFlag] + ): Result[tuple[read: HANDLE, write: HANDLE], OSErrorCode] = + var + pipeIn, pipeOut: HANDLE + widePipeName: LPWSTR + uniq = 0'u64 + rsa = getSecurityAttributes(DescriptorFlag.CloseOnExec notin readset) + wsa = getSecurityAttributes(DescriptorFlag.CloseOnExec notin writeset) + + while true: + queryPerformanceCounter(uniq) + let pipeName = PipeHeaderName & Base10.toString(uniq) + + let openMode = + if DescriptorFlag.NonBlock in readset: + osdefs.FILE_FLAG_FIRST_PIPE_INSTANCE or osdefs.FILE_FLAG_OVERLAPPED or + osdefs.PIPE_ACCESS_INBOUND + else: + osdefs.FILE_FLAG_FIRST_PIPE_INSTANCE or osdefs.PIPE_ACCESS_INBOUND + + let pipeMode = osdefs.PIPE_TYPE_BYTE or osdefs.PIPE_READMODE_BYTE or + osdefs.PIPE_WAIT + widePipeName = + block: + let res = pipeName.toWideString() + if res.isErr(): + return err(res.error()) + res.get() + pipeIn = createNamedPipe(widePipeName, openMode, pipeMode, + 1'u32, osdefs.DEFAULT_PIPE_SIZE, + osdefs.DEFAULT_PIPE_SIZE, 0'u32, addr rsa) + if pipeIn == osdefs.INVALID_HANDLE_VALUE: + let errorCode = osLastError() + free(widePipeName) + # If error in {ERROR_ACCESS_DENIED, ERROR_PIPE_BUSY}, then named pipe + # with such name already exists. + if (errorCode == osdefs.ERROR_ACCESS_DENIED) or + (errorCode == osdefs.ERROR_PIPE_BUSY): + continue + return err(errorCode) + else: + break + + let openMode = osdefs.GENERIC_WRITE or osdefs.FILE_WRITE_DATA or + osdefs.SYNCHRONIZE + let openFlags = + if DescriptorFlag.NonBlock in writeset: + osdefs.FILE_FLAG_OVERLAPPED + else: + DWORD(0) + + pipeOut = createFile(widePipeName, openMode, 0, addr wsa, + osdefs.OPEN_EXISTING, openFlags, HANDLE(0)) + if pipeOut == osdefs.INVALID_HANDLE_VALUE: + let errorCode = osLastError() + free(widePipeName) + discard closeFd(pipeIn) + return err(errorCode) + + var ovl = osdefs.OVERLAPPED() + let res = + if DescriptorFlag.NonBlock in writeset: + connectNamedPipe(pipeIn, addr ovl) + else: + connectNamedPipe(pipeIn, nil) + if res == 0: + let cleanupFlag = + block: + let errorCode = osLastError() + case int(errorCode) + of osdefs.ERROR_PIPE_CONNECTED: + false + of osdefs.ERROR_IO_PENDING: + if DescriptorFlag.NonBlock in writeset: + var bytesRead = 0.DWORD + if getOverlappedResult(pipeIn, addr ovl, bytesRead, 1) == FALSE: + true + else: + false + else: + true + else: + true + if cleanupFlag: + let errorCode = osLastError() + free(widePipeName) + discard closeFd(pipeIn) + discard closeFd(pipeOut) + return err(errorCode) + ok((read: pipeIn, write: pipeOut)) + +else: + + template handleEintr*(body: untyped): untyped = + var res = 0 + while true: + res = body + if not((res == -1) and (osLastError() == EINTR)): + break + res + + proc setDescriptorBlocking*(s: cint, + value: bool): Result[void, OSErrorCode] = + let flags = handleEintr(osdefs.fcntl(s, osdefs.F_GETFL)) + if flags == -1: + return err(osLastError()) + if value != not((flags and osdefs.O_NONBLOCK) == osdefs.O_NONBLOCK): + let mode = + if value: + flags and not(osdefs.O_NONBLOCK) + else: + flags or osdefs.O_NONBLOCK + if handleEintr(osdefs.fcntl(s, osdefs.F_SETFL, mode)) == -1: + return err(osLastError()) + ok() + + proc setDescriptorInheritance*(s: cint, + value: bool): Result[void, OSErrorCode] = + let flags = handleEintr(osdefs.fcntl(s, osdefs.F_GETFD)) + if flags == -1: + return err(osLastError()) + if value != not((flags and osdefs.FD_CLOEXEC) == osdefs.FD_CLOEXEC): + let mode = + if value: + flags and not(osdefs.FD_CLOEXEC) + else: + flags or osdefs.FD_CLOEXEC + if handleEintr(osdefs.fcntl(s, osdefs.F_SETFD, mode)) == -1: + return err(osLastError()) + ok() + + proc getDescriptorInheritance*(s: cint): Result[bool, OSErrorCode] = + let flags = handleEintr(osdefs.fcntl(s, osdefs.F_GETFD)) + if flags == -1: + return err(osLastError()) + ok((flags and osdefs.FD_CLOEXEC) == osdefs.FD_CLOEXEC) + + proc setDescriptorFlags*(s: cint, nonblock, + cloexec: bool): Result[void, OSErrorCode] = + ? setDescriptorBlocking(s, not(nonblock)) + ? setDescriptorInheritance(s, not(cloexec)) + ok() + + proc closeFd*(s: cint): int = + handleEintr(osdefs.close(s)) + + proc closeFd*(s: SocketHandle): int = + handleEintr(osdefs.close(cint(s))) + + proc acceptConn*(a1: cint, a2: ptr SockAddr, a3: ptr SockLen, + a4: set[DescriptorFlag]): Result[cint, OSErrorCode] = + when declared(accept4): + let flags = + block: + var res: cint = 0 + if DescriptorFlag.CloseOnExec in a4: + res = res or osdefs.SOCK_CLOEXEC + if DescriptorFlag.NonBlock in a4: + res = res or osdefs.SOCK_NONBLOCK + res + let res = cint(handleEintr(accept4(a1, a2, a3, flags))) + if res == -1: + return err(osLastError()) + ok(res) + else: + let sock = cint(handleEintr(cint(accept(SocketHandle(a1), a2, a3)))) + if sock == -1: + return err(osLastError()) + let + cloexec = DescriptorFlag.CloseOnExec in a4 + nonblock = DescriptorFlag.NonBlock in a4 + let res = setDescriptorFlags(sock, nonblock, cloexec) + if res.isErr(): + discard closeFd(sock) + return err(res.error()) + ok(sock) + + proc createOsPipe*(readset, writeset: set[DescriptorFlag] + ): Result[tuple[read: cint, write: cint], OSErrorCode] = + when declared(pipe2): + var fds: array[2, cint] + let readFlags = + block: + var res = cint(0) + if DescriptorFlag.CloseOnExec in readset: + res = res or osdefs.O_CLOEXEC + if DescriptorFlag.NonBlock in readset: + res = res or osdefs.O_NONBLOCK + res + if osdefs.pipe2(fds, readFlags) == -1: + return err(osLastError()) + if readset != writeset: + let res = setDescriptorFlags(fds[1], + DescriptorFlag.NonBlock in writeset, + DescriptorFlag.CloseOnExec in writeset) + if res.isErr(): + discard closeFd(fds[0]) + discard closeFd(fds[1]) + return err(res.error()) + ok((read: fds[0], write: fds[1])) + else: + var fds: array[2, cint] + if osdefs.pipe(fds) == -1: + return err(osLastError()) + block: + let res = setDescriptorFlags(fds[0], + DescriptorFlag.NonBlock in readset, + DescriptorFlag.CloseOnExec in readset) + if res.isErr(): + discard closeFd(fds[0]) + discard closeFd(fds[1]) + return err(res.error()) + block: + let res = setDescriptorFlags(fds[1], + DescriptorFlag.NonBlock in writeset, + DescriptorFlag.CloseOnExec in writeset) + if res.isErr(): + discard closeFd(fds[0]) + discard closeFd(fds[1]) + return err(res.error()) + ok((read: fds[0], write: fds[1])) diff --git a/chronos/timer.nim b/chronos/timer.nim index f8668a9..c469c83 100644 --- a/chronos/timer.nim +++ b/chronos/timer.nim @@ -23,6 +23,7 @@ ## ## You can specify which timer you want to use ``-d:asyncTimer=``. import stew/base10 +import "."/osdefs const asyncTimer* {.strdefine.} = "mono" @@ -33,47 +34,38 @@ else: when defined(windows): when asyncTimer == "system": - from winlean import getSystemTimeAsFileTime, FILETIME - proc fastEpochTime*(): uint64 {. inline, deprecated: "Use Moment.now()".} = ## Timer resolution is millisecond. var t: FILETIME getSystemTimeAsFileTime(t) - ((cast[uint64](t.dwHighDateTime) shl 32) or - cast[uint64](t.dwLowDateTime)) div 10_000 + ((uint64(t.dwHighDateTime) shl 32) or uint64(t.dwLowDateTime)) div 10_000 proc fastEpochTimeNano(): uint64 {.inline.} = ## Timer resolution is nanosecond. var t: FILETIME getSystemTimeAsFileTime(t) - ((cast[uint64](t.dwHighDateTime) shl 32) or - cast[uint64](t.dwLowDateTime)) * 100 + ((uint64(t.dwHighDateTime) shl 32) or uint64(t.dwLowDateTime)) * 100 else: - proc QueryPerformanceCounter*(res: var uint64) {. - importc: "QueryPerformanceCounter", stdcall, dynlib: "kernel32".} - proc QueryPerformanceFrequency(res: var uint64) {. - importc: "QueryPerformanceFrequency", stdcall, dynlib: "kernel32".} - var queryFrequencyM: uint64 var queryFrequencyN: uint64 proc fastEpochTimeNano(): uint64 {.inline.} = ## Procedure's resolution is nanosecond. var res: uint64 - QueryPerformanceCounter(res) + queryPerformanceCounter(res) res * queryFrequencyN proc fastEpochTime*(): uint64 {.inline, deprecated: "Use Moment.now()".} = ## Procedure's resolution is millisecond. var res: uint64 - QueryPerformanceCounter(res) + queryPerformanceCounter(res) res div queryFrequencyM proc setupQueryFrequence() = var freq: uint64 - QueryPerformanceFrequency(freq) + queryPerformanceFrequency(freq) if freq < 1000: queryFrequencyM = freq else: @@ -85,13 +77,8 @@ when defined(windows): elif defined(macosx): when asyncTimer == "system": - from posix import Timeval - proc posix_gettimeofday(tp: var Timeval, unused: pointer = nil) {. - importc: "gettimeofday", header: "".} - - proc fastEpochTime*(): uint64 {. - inline, deprecated: "Use Moment.now()".} = + proc fastEpochTime*(): uint64 {.inline, deprecated: "Use Moment.now()".} = ## Procedure's resolution is millisecond. var t: Timeval posix_gettimeofday(t) @@ -103,16 +90,6 @@ elif defined(macosx): posix_gettimeofday(t) uint64(t.tv_sec) * 1_000_000_000 + uint64(t.tv_usec) * 1_000 else: - type - MachTimebaseInfo {.importc: "struct mach_timebase_info", - header: "", pure, final.} = object - numer: uint32 - denom: uint32 - - proc mach_timebase_info(info: var MachTimebaseInfo) {.importc, - header: "".} - proc mach_absolute_time(): uint64 {.importc, header: "".} - var queryFrequencyN: uint64 var queryFrequencyD: uint64 @@ -122,8 +99,7 @@ elif defined(macosx): queryFrequencyN = info.numer queryFrequencyD = info.denom - proc fastEpochTime*(): uint64 {. - inline, deprecated: "Use Moment.now()".} = + proc fastEpochTime*(): uint64 {.inline, deprecated: "Use Moment.now()".} = ## Procedure's resolution is millisecond. let res = (mach_absolute_time() * queryFrequencyN) div queryFrequencyD res div 1_000_000 @@ -135,11 +111,8 @@ elif defined(macosx): setupQueryFrequence() elif defined(posix): - from posix import clock_gettime, Timespec, CLOCK_REALTIME, CLOCK_MONOTONIC - when asyncTimer == "system": - proc fastEpochTime*(): uint64 {. - inline, deprecated: "Use Moment.now()".} = + proc fastEpochTime*(): uint64 {.inline, deprecated: "Use Moment.now()".} = ## Procedure's resolution is millisecond. var t: Timespec discard clock_gettime(CLOCK_REALTIME, t) @@ -152,8 +125,7 @@ elif defined(posix): uint64(t.tv_sec) * 1_000_000_000'u64 + uint64(t.tv_nsec) else: - proc fastEpochTime*(): uint64 {. - inline, deprecated: "Use Moment.now()".} = + proc fastEpochTime*(): uint64 {.inline, deprecated: "Use Moment.now()".} = ## Procedure's resolution is millisecond. var t: Timespec discard clock_gettime(CLOCK_MONOTONIC, t) @@ -166,10 +138,7 @@ elif defined(posix): uint64(t.tv_sec) * 1_000_000_000'u64 + uint64(t.tv_nsec) elif defined(nimdoc): - - proc fastEpochTime*(): uint64 {.deprecated: "Use Moment.now()".} - ## Returns system's timer in milliseconds. - + discard else: error("Sorry, your operation system is not yet supported!") diff --git a/chronos/transports/common.nim b/chronos/transports/common.nim index fad913e..8d75a5a 100644 --- a/chronos/transports/common.nim +++ b/chronos/transports/common.nim @@ -12,16 +12,11 @@ when (NimMajor, NimMinor) < (1, 4): else: {.push raises: [].} -import std/[os, strutils, nativesockets, net] -import stew/[base10, endians2, byteutils] -import ../asyncloop +import std/[strutils, nativesockets, net] +import stew/[base10, byteutils] +import ".."/[asyncloop, osdefs] export net -when defined(windows) or defined(nimdoc): - import winlean -else: - import posix - const DefaultStreamBufferSize* = 4096 ## Default buffer size for stream ## transports @@ -79,7 +74,7 @@ when defined(windows) or defined(nimdoc): errorCode*: OSErrorCode # Current error code abuffer*: array[128, byte] # Windows AcceptEx() buffer when defined(windows): - aovl*: CustomOverlapped # AcceptEx OVERLAPPED structure + aovl*: CustomOverlapped # AcceptEx OVERLAPPED structure else: type SocketServer* = ref object of RootRef @@ -605,46 +600,6 @@ proc isLiteral*[T](s: seq[T]): bool {.inline.} = else: (cast[ptr SeqHeader](s).reserved and (1 shl (sizeof(int) * 8 - 2))) != 0 -when defined(windows): - import winlean - - const - ERROR_OPERATION_ABORTED* = 995 - ERROR_PIPE_CONNECTED* = 535 - ERROR_PIPE_BUSY* = 231 - ERROR_SUCCESS* = 0 - ERROR_CONNECTION_REFUSED* = 1225 - PIPE_TYPE_BYTE* = 0 - PIPE_READMODE_BYTE* = 0 - PIPE_TYPE_MESSAGE* = 0x4 - PIPE_READMODE_MESSAGE* = 0x2 - PIPE_WAIT* = 0 - PIPE_UNLIMITED_INSTANCES* = 255 - ERROR_BROKEN_PIPE* = 109 - ERROR_PIPE_NOT_CONNECTED* = 233 - ERROR_NO_DATA* = 232 - ERROR_CONNECTION_ABORTED* = 1236 - ERROR_TOO_MANY_OPEN_FILES* = 4 - WSAEMFILE* = 10024 - WSAENETDOWN* = 10050 - WSAENETRESET* = 10052 - WSAECONNABORTED* = 10053 - WSAECONNRESET* = 10054 - WSAENOBUFS* = 10055 - WSAETIMEDOUT* = 10060 - - proc cancelIo*(hFile: Handle): WINBOOL - {.stdcall, dynlib: "kernel32", importc: "CancelIo".} - proc connectNamedPipe*(hPipe: Handle, lpOverlapped: ptr OVERLAPPED): WINBOOL - {.stdcall, dynlib: "kernel32", importc: "ConnectNamedPipe".} - proc disconnectNamedPipe*(hPipe: Handle): WINBOOL - {.stdcall, dynlib: "kernel32", importc: "DisconnectNamedPipe".} - proc setNamedPipeHandleState*(hPipe: Handle, lpMode, lpMaxCollectionCount, - lpCollectDataTimeout: ptr DWORD): WINBOOL - {.stdcall, dynlib: "kernel32", importc: "SetNamedPipeHandleState".} - proc resetEvent*(hEvent: Handle): WINBOOL - {.stdcall, dynlib: "kernel32", importc: "ResetEvent".} - template getTransportTooManyError*(code: int = 0): ref TransportTooManyError = let msg = when defined(posix): @@ -664,11 +619,11 @@ template getTransportTooManyError*(code: int = 0): ref TransportTooManyError = case code of 0: "Too many open transports" - of ERROR_TOO_MANY_OPEN_FILES: + of osdefs.ERROR_TOO_MANY_OPEN_FILES: "[ERROR_TOO_MANY_OPEN_FILES] Too many open files" - of WSAENOBUFS: + of osdefs.WSAENOBUFS: "[WSAENOBUFS] No buffer space available" - of WSAEMFILE: + of osdefs.WSAEMFILE: "[WSAEMFILE] Too many open sockets" else: "[" & $code & "] Too many open transports" @@ -697,15 +652,15 @@ template getConnectionAbortedError*(code: int): ref TransportAbortedError = "[" & $code & "] Connection has been aborted" elif defined(windows): case code - of 0, WSAECONNABORTED: + of 0, osdefs.WSAECONNABORTED: "[ECONNABORTED] Connection has been aborted before being accepted" - of WSAENETDOWN: + of osdefs.WSAENETDOWN: "[ENETDOWN] Network is down" - of WSAENETRESET: + of osdefs.WSAENETRESET: "[ENETRESET] Network dropped connection on reset" - of WSAECONNRESET: + of osdefs.WSAECONNRESET: "[ECONNRESET] Connection reset by peer" - of WSAETIMEDOUT: + of osdefs.WSAETIMEDOUT: "[ETIMEDOUT] Connection timed out" else: "[" & $code & "] Connection has been aborted" diff --git a/chronos/transports/datagram.nim b/chronos/transports/datagram.nim index 2f195a6..32eba32 100644 --- a/chronos/transports/datagram.nim +++ b/chronos/transports/datagram.nim @@ -12,19 +12,10 @@ when (NimMajor, NimMinor) < (1, 4): else: {.push raises: [].} -import std/[net, nativesockets, os, deques] -import ".."/[selectors2, asyncloop, handles] -import ./common - -when defined(windows) or defined(nimdoc): - import winlean -else: - import posix - when (defined(linux) and not defined(android)) and defined(amd64): - const IP_MULTICAST_TTL: cint = 33 - else: - var IP_MULTICAST_TTL* {.importc: "IP_MULTICAST_TTL", - header: "".}: cint +import std/deques +when not(defined(windows)): import ".."/selectors2 +import ".."/[asyncloop, osdefs, handles] +import "."/common type VectorKind = enum @@ -61,9 +52,9 @@ type when defined(windows): rovl: CustomOverlapped # Reader OVERLAPPED structure wovl: CustomOverlapped # Writer OVERLAPPED structure - rflag: int32 # Reader flags storage - rwsabuf: TWSABuf # Reader WSABUF structure - wwsabuf: TWSABuf # Writer WSABUF structure + rflag: uint32 # Reader flags storage + rwsabuf: WSABUF # Reader WSABUF structure + wwsabuf: WSABUF # Writer WSABUF structure DgramTransportTracker* = ref object of TrackerBase opened*: int64 @@ -82,7 +73,7 @@ proc remoteAddress*(transp: DatagramTransport): TransportAddress {. addr slen) != 0: raiseTransportOsError(osLastError()) fromSAddr(addr saddr, slen, transp.remote) - result = transp.remote + transp.remote proc localAddress*(transp: DatagramTransport): TransportAddress {. raises: [Defect, TransportOsError].} = @@ -94,7 +85,7 @@ proc localAddress*(transp: DatagramTransport): TransportAddress {. addr slen) != 0: raiseTransportOsError(osLastError()) fromSAddr(addr saddr, slen, transp.local) - result = transp.local + transp.local template setReadError(t, e: untyped) = (t).state.incl(ReadError) @@ -104,18 +95,20 @@ proc setupDgramTransportTracker(): DgramTransportTracker {. gcsafe, raises: [Defect].} proc getDgramTransportTracker(): DgramTransportTracker {.inline.} = - result = cast[DgramTransportTracker](getTracker(DgramTransportTrackerName)) - if isNil(result): - result = setupDgramTransportTracker() + var res = cast[DgramTransportTracker](getTracker(DgramTransportTrackerName)) + if isNil(res): + res = setupDgramTransportTracker() + doAssert(not(isNil(res))) + res proc dumpTransportTracking(): string {.gcsafe.} = var tracker = getDgramTransportTracker() - result = "Opened transports: " & $tracker.opened & "\n" & - "Closed transports: " & $tracker.closed + "Opened transports: " & $tracker.opened & "\n" & + "Closed transports: " & $tracker.closed proc leakTransport(): bool {.gcsafe.} = - var tracker = getDgramTransportTracker() - result = (tracker.opened != tracker.closed) + let tracker = getDgramTransportTracker() + tracker.opened != tracker.closed proc trackDgram(t: DatagramTransport) {.inline.} = var tracker = getDgramTransportTracker() @@ -126,45 +119,18 @@ proc untrackDgram(t: DatagramTransport) {.inline.} = inc(tracker.closed) proc setupDgramTransportTracker(): DgramTransportTracker {.gcsafe.} = - result = new DgramTransportTracker - result.opened = 0 - result.closed = 0 - result.dump = dumpTransportTracking - result.isLeaked = leakTransport - addTracker(DgramTransportTrackerName, result) + let res = DgramTransportTracker( + opened: 0, closed: 0, dump: dumpTransportTracking, isLeaked: leakTransport) + addTracker(DgramTransportTrackerName, res) + res -when defined(nimdoc): - proc newDatagramTransportCommon(cbproc: DatagramCallback, - remote: TransportAddress, - local: TransportAddress, - sock: AsyncFD, - flags: set[ServerFlags], - udata: pointer, - child: DatagramTransport, - bufferSize: int, - ttl: int): DatagramTransport {. - raises: [Defect, CatchableError].} = - discard - - proc resumeRead(transp: DatagramTransport) {.inline.} = - discard - - proc resumeWrite(transp: DatagramTransport) {.inline.} = - discard - -elif defined(windows): +when defined(windows): template setWriterWSABuffer(t, v: untyped) = (t).wwsabuf.buf = cast[cstring](v.buf) - (t).wwsabuf.len = cast[int32](v.buflen) - - const - IOC_VENDOR = DWORD(0x18000000) - SIO_UDP_CONNRESET = DWORD(winlean.IOC_IN) or IOC_VENDOR or DWORD(12) - IPPROTO_IP = DWORD(0) - IP_TTL = DWORD(4) + (t).wwsabuf.len = cast[ULONG](v.buflen) proc writeDatagramLoop(udata: pointer) = - var bytesCount: int32 + var bytesCount: uint32 var ovl = cast[PtrCustomOverlapped](udata) var transp = cast[DatagramTransport](ovl.data.udata) while len(transp.queue) > 0: @@ -176,7 +142,7 @@ elif defined(windows): if err == OSErrorCode(-1): if not(vector.writer.finished()): vector.writer.complete() - elif int(err) == ERROR_OPERATION_ABORTED: + elif int(err) == osdefs.ERROR_OPERATION_ABORTED: # CancelIO() interrupt transp.state.incl(WritePaused) if not(vector.writer.finished()): @@ -191,26 +157,26 @@ elif defined(windows): let fd = SocketHandle(transp.fd) var vector = transp.queue.popFirst() transp.setWriterWSABuffer(vector) - var ret: cint - if vector.kind == WithAddress: - var fixedAddress = windowsAnyAddressFix(vector.address) - toSAddr(fixedAddress, transp.waddr, transp.walen) - ret = WSASendTo(fd, addr transp.wwsabuf, DWORD(1), addr bytesCount, - DWORD(0), cast[ptr SockAddr](addr transp.waddr), - cint(transp.walen), - cast[POVERLAPPED](addr transp.wovl), nil) - else: - ret = WSASend(fd, addr transp.wwsabuf, DWORD(1), addr bytesCount, - DWORD(0), cast[POVERLAPPED](addr transp.wovl), nil) + let ret = + if vector.kind == WithAddress: + var fixedAddress = windowsAnyAddressFix(vector.address) + toSAddr(fixedAddress, transp.waddr, transp.walen) + wsaSendTo(fd, addr transp.wwsabuf, DWORD(1), addr bytesCount, + DWORD(0), cast[ptr SockAddr](addr transp.waddr), + cint(transp.walen), + cast[POVERLAPPED](addr transp.wovl), nil) + else: + wsaSend(fd, addr transp.wwsabuf, DWORD(1), addr bytesCount, + DWORD(0), cast[POVERLAPPED](addr transp.wovl), nil) if ret != 0: let err = osLastError() - if int(err) == ERROR_OPERATION_ABORTED: + if int(err) == osdefs.ERROR_OPERATION_ABORTED: # CancelIO() interrupt transp.state.excl(WritePending) transp.state.incl(WritePaused) if not(vector.writer.finished()): vector.writer.complete() - elif int(err) == ERROR_IO_PENDING: + elif int(err) == osdefs.ERROR_IO_PENDING: transp.queue.addFirst(vector) else: transp.state.excl(WritePending) @@ -226,7 +192,7 @@ elif defined(windows): proc readDatagramLoop(udata: pointer) = var - bytesCount: int32 + bytesCount: uint32 raddr: TransportAddress var ovl = cast[PtrCustomOverlapped](udata) var transp = cast[DatagramTransport](ovl.data.udata) @@ -240,9 +206,9 @@ elif defined(windows): if bytesCount == 0: transp.state.incl({ReadEof, ReadPaused}) fromSAddr(addr transp.raddr, transp.ralen, raddr) - transp.buflen = bytesCount - asyncCheck transp.function(transp, raddr) - elif int(err) == ERROR_OPERATION_ABORTED: + transp.buflen = int(bytesCount) + asyncSpawn transp.function(transp, raddr) + elif int(err) == osdefs.ERROR_OPERATION_ABORTED: # CancelIO() interrupt or closeSocket() call. transp.state.incl(ReadPaused) if ReadClosed in transp.state and not(transp.future.finished()): @@ -256,7 +222,7 @@ elif defined(windows): transp.setReadError(err) transp.state.incl(ReadPaused) transp.buflen = 0 - asyncCheck transp.function(transp, raddr) + asyncSpawn transp.function(transp, raddr) else: ## Initiation if transp.state * {ReadEof, ReadClosed, ReadError} == {}: @@ -264,29 +230,29 @@ elif defined(windows): let fd = SocketHandle(transp.fd) transp.rflag = 0 transp.ralen = SockLen(sizeof(Sockaddr_storage)) - let ret = WSARecvFrom(fd, addr transp.rwsabuf, DWORD(1), + let ret = wsaRecvFrom(fd, addr transp.rwsabuf, DWORD(1), addr bytesCount, addr transp.rflag, cast[ptr SockAddr](addr transp.raddr), cast[ptr cint](addr transp.ralen), cast[POVERLAPPED](addr transp.rovl), nil) if ret != 0: let err = osLastError() - if int(err) == ERROR_OPERATION_ABORTED: + if int(err) == osdefs.ERROR_OPERATION_ABORTED: # CancelIO() interrupt transp.state.excl(ReadPending) transp.state.incl(ReadPaused) - elif int(err) == common.WSAECONNRESET: + elif int(err) == osdefs.WSAECONNRESET: transp.state.excl(ReadPending) transp.state.incl({ReadPaused, ReadEof}) break - elif int(err) == ERROR_IO_PENDING: + elif int(err) == osdefs.ERROR_IO_PENDING: discard else: transp.state.excl(ReadPending) transp.state.incl(ReadPaused) transp.setReadError(err) transp.buflen = 0 - asyncCheck transp.function(transp, raddr) + asyncSpawn transp.function(transp, raddr) else: # Transport closure happens in callback, and we not started new # WSARecvFrom session. @@ -297,13 +263,17 @@ elif defined(windows): GC_unref(transp) break - proc resumeRead(transp: DatagramTransport) {.inline.} = - transp.state.excl(ReadPaused) - readDatagramLoop(cast[pointer](addr transp.rovl)) + proc resumeRead(transp: DatagramTransport): Result[void, OSErrorCode] = + if ReadPaused in transp.state: + transp.state.excl(ReadPaused) + readDatagramLoop(cast[pointer](addr transp.rovl)) + ok() - proc resumeWrite(transp: DatagramTransport) {.inline.} = - transp.state.excl(WritePaused) - writeDatagramLoop(cast[pointer](addr transp.wovl)) + proc resumeWrite(transp: DatagramTransport): Result[void, OSErrorCode] = + if WritePaused in transp.state: + transp.state.excl(WritePaused) + writeDatagramLoop(cast[pointer](addr transp.wovl)) + ok() proc newDatagramTransportCommon(cbproc: DatagramCallback, remote: TransportAddress, @@ -314,16 +284,13 @@ elif defined(windows): child: DatagramTransport, bufferSize: int, ttl: int): DatagramTransport {. - raises: [Defect, CatchableError].} = + raises: [Defect, TransportOsError].} = var localSock: AsyncFD doAssert(remote.family == local.family) doAssert(not isNil(cbproc)) doAssert(remote.family in {AddressFamily.IPv4, AddressFamily.IPv6}) - if isNil(child): - result = DatagramTransport() - else: - result = child + var res = if isNil(child): DatagramTransport() else: child if sock == asyncInvalidSocket: localSock = createAsyncSocket(local.getDomain(), SockType.SOCK_DGRAM, @@ -335,25 +302,27 @@ elif defined(windows): if not setSocketBlocking(SocketHandle(sock), false): raiseTransportOsError(osLastError()) localSock = sock - register(localSock) + let bres = register2(localSock) + if bres.isErr(): + raiseTransportOsError(bres.error()) ## Apply ServerFlags here if ServerFlags.ReuseAddr in flags: - if not setSockOpt(localSock, SOL_SOCKET, SO_REUSEADDR, 1): + if not setSockOpt(localSock, osdefs.SOL_SOCKET, osdefs.SO_REUSEADDR, 1): let err = osLastError() if sock == asyncInvalidSocket: closeSocket(localSock) raiseTransportOsError(err) if ServerFlags.Broadcast in flags: - if not setSockOpt(localSock, SOL_SOCKET, SO_BROADCAST, 1): + if not setSockOpt(localSock, osdefs.SOL_SOCKET, osdefs.SO_BROADCAST, 1): let err = osLastError() if sock == asyncInvalidSocket: closeSocket(localSock) raiseTransportOsError(err) if ttl > 0: - if not setSockOpt(localSock, IPPROTO_IP, IP_TTL, DWORD(ttl)): + if not setSockOpt(localSock, osdefs.IPPROTO_IP, osdefs.IP_TTL, ttl): let err = osLastError() if sock == asyncInvalidSocket: closeSocket(localSock) @@ -362,7 +331,7 @@ elif defined(windows): ## Fix for Q263823. var bytesRet: DWORD var bval = WINBOOL(0) - if WSAIoctl(SocketHandle(localSock), SIO_UDP_CONNRESET, addr bval, + if wsaIoctl(SocketHandle(localSock), osdefs.SIO_UDP_CONNRESET, addr bval, sizeof(WINBOOL).DWORD, nil, DWORD(0), addr bytesRet, nil, nil) != 0: raiseTransportOsError(osLastError()) @@ -372,8 +341,8 @@ elif defined(windows): var slen: SockLen toSAddr(local, saddr, slen) - if bindAddr(SocketHandle(localSock), cast[ptr SockAddr](addr saddr), - slen) != 0: + if bindSocket(SocketHandle(localSock), cast[ptr SockAddr](addr saddr), + slen) != 0: let err = osLastError() if sock == asyncInvalidSocket: closeSocket(localSock) @@ -382,8 +351,8 @@ elif defined(windows): var saddr: Sockaddr_storage var slen: SockLen saddr.ss_family = type(saddr.ss_family)(local.getDomain()) - if bindAddr(SocketHandle(localSock), cast[ptr SockAddr](addr saddr), - slen) != 0: + if bindSocket(SocketHandle(localSock), cast[ptr SockAddr](addr saddr), + slen) != 0: let err = osLastError() if sock == asyncInvalidSocket: closeSocket(localSock) @@ -400,28 +369,28 @@ elif defined(windows): if sock == asyncInvalidSocket: closeSocket(localSock) raiseTransportOsError(err) - result.remote = fixedAddress + res.remote = fixedAddress - result.fd = localSock - result.function = cbproc - result.buffer = newSeq[byte](bufferSize) - result.queue = initDeque[GramVector]() - result.udata = udata - result.state = {WritePaused} - result.future = newFuture[void]("datagram.transport") - result.rovl.data = CompletionData(cb: readDatagramLoop, - udata: cast[pointer](result)) - result.wovl.data = CompletionData(cb: writeDatagramLoop, - udata: cast[pointer](result)) - result.rwsabuf = TWSABuf(buf: cast[cstring](addr result.buffer[0]), - len: int32(len(result.buffer))) - GC_ref(result) + res.fd = localSock + res.function = cbproc + res.buffer = newSeq[byte](bufferSize) + res.queue = initDeque[GramVector]() + res.udata = udata + res.state = {ReadPaused, WritePaused} + res.future = newFuture[void]("datagram.transport") + res.rovl.data = CompletionData(cb: readDatagramLoop, + udata: cast[pointer](res)) + res.wovl.data = CompletionData(cb: writeDatagramLoop, + udata: cast[pointer](res)) + res.rwsabuf = WSABUF(buf: cast[cstring](addr res.buffer[0]), + len: ULONG(len(res.buffer))) + GC_ref(res) # Start tracking transport - trackDgram(result) + trackDgram(res) if NoAutoRead notin flags: - result.resumeRead() - else: - result.state.incl(ReadPaused) + let rres = res.resumeRead() + if rres.isErr(): raiseTransportOsError(rres.error()) + res else: # Linux/BSD/MacOS part @@ -440,14 +409,14 @@ else: else: while true: transp.ralen = SockLen(sizeof(Sockaddr_storage)) - var res = posix.recvfrom(fd, addr transp.buffer[0], - cint(len(transp.buffer)), cint(0), - cast[ptr SockAddr](addr transp.raddr), - addr transp.ralen) + var res = osdefs.recvfrom(fd, addr transp.buffer[0], + cint(len(transp.buffer)), cint(0), + cast[ptr SockAddr](addr transp.raddr), + addr transp.ralen) if res >= 0: fromSAddr(addr transp.raddr, transp.ralen, raddr) transp.buflen = res - asyncCheck transp.function(transp, raddr) + asyncSpawn transp.function(transp, raddr) else: let err = osLastError() if int(err) == EINTR: @@ -455,7 +424,7 @@ else: else: transp.buflen = 0 transp.setReadError(err) - asyncCheck transp.function(transp, raddr) + asyncSpawn transp.function(transp, raddr) break proc writeDatagramLoop(udata: pointer) = @@ -475,11 +444,11 @@ else: while true: if vector.kind == WithAddress: toSAddr(vector.address, transp.waddr, transp.walen) - res = posix.sendto(fd, vector.buf, vector.buflen, MSG_NOSIGNAL, - cast[ptr SockAddr](addr transp.waddr), - transp.walen) + res = osdefs.sendto(fd, vector.buf, vector.buflen, MSG_NOSIGNAL, + cast[ptr SockAddr](addr transp.waddr), + transp.walen) elif vector.kind == WithoutAddress: - res = posix.send(fd, vector.buf, vector.buflen, MSG_NOSIGNAL) + res = osdefs.send(fd, vector.buf, vector.buflen, MSG_NOSIGNAL) if res >= 0: if not(vector.writer.finished()): vector.writer.complete() @@ -492,31 +461,20 @@ else: vector.writer.fail(getTransportOsError(err)) break else: - transp.state.incl(WritePaused) - try: - transp.fd.removeWriter() - except IOSelectorsException as exc: - raiseAsDefect exc, "removeWriter" - except ValueError as exc: - raiseAsDefect exc, "removeWriter" + transp.state.incl({WritePaused}) + discard removeWriter2(transp.fd) - proc resumeWrite(transp: DatagramTransport) {.inline.} = - transp.state.excl(WritePaused) - try: - addWriter(transp.fd, writeDatagramLoop, cast[pointer](transp)) - except IOSelectorsException as exc: - raiseAsDefect exc, "addWriter" - except ValueError as exc: - raiseAsDefect exc, "addWriter" + proc resumeWrite(transp: DatagramTransport): Result[void, OSErrorCode] = + if WritePaused in transp.state: + ? addWriter2(transp.fd, writeDatagramLoop, cast[pointer](transp)) + transp.state.excl(WritePaused) + ok() - proc resumeRead(transp: DatagramTransport) {.inline.} = - transp.state.excl(ReadPaused) - try: - addReader(transp.fd, readDatagramLoop, cast[pointer](transp)) - except IOSelectorsException as exc: - raiseAsDefect exc, "addReader" - except ValueError as exc: - raiseAsDefect exc, "addReader" + proc resumeRead(transp: DatagramTransport): Result[void, OSErrorCode] = + if ReadPaused in transp.state: + ? addReader2(transp.fd, readDatagramLoop, cast[pointer](transp)) + transp.state.excl(ReadPaused) + ok() proc newDatagramTransportCommon(cbproc: DatagramCallback, remote: TransportAddress, @@ -527,15 +485,12 @@ else: child: DatagramTransport, bufferSize: int, ttl: int): DatagramTransport {. - raises: [Defect, CatchableError].} = + raises: [Defect, TransportOsError].} = var localSock: AsyncFD doAssert(remote.family == local.family) doAssert(not isNil(cbproc)) - if isNil(child): - result = DatagramTransport() - else: - result = child + var res = if isNil(child): DatagramTransport() else: child if sock == asyncInvalidSocket: var proto = Protocol.IPPROTO_UDP @@ -551,32 +506,37 @@ else: if not setSocketBlocking(SocketHandle(sock), false): raiseTransportOsError(osLastError()) localSock = sock - register(localSock) + let bres = register2(localSock) + if bres.isErr(): + raiseTransportOsError(bres.error()) ## Apply ServerFlags here if ServerFlags.ReuseAddr in flags: - if not setSockOpt(localSock, SOL_SOCKET, SO_REUSEADDR, 1): + if not setSockOpt(localSock, osdefs.SOL_SOCKET, osdefs.SO_REUSEADDR, 1): let err = osLastError() if sock == asyncInvalidSocket: closeSocket(localSock) raiseTransportOsError(err) if ServerFlags.Broadcast in flags: - if not setSockOpt(localSock, SOL_SOCKET, SO_BROADCAST, 1): + if not setSockOpt(localSock, osdefs.SOL_SOCKET, osdefs.SO_BROADCAST, 1): let err = osLastError() if sock == asyncInvalidSocket: closeSocket(localSock) raiseTransportOsError(err) if ttl > 0: - var res: bool - if local.family == AddressFamily.IPv4: - res = setSockOpt(localSock, posix.IPPROTO_IP, IP_MULTICAST_TTL, - cint(ttl)) - elif local.family == AddressFamily.IPv6: - res = setSockOpt(localSock, posix.IPPROTO_IP, IPV6_MULTICAST_HOPS, - cint(ttl)) - if not res: + let tres = + if local.family == AddressFamily.IPv4: + setSockOpt(localSock, osdefs.IPPROTO_IP, osdefs.IP_MULTICAST_TTL, + cint(ttl)) + elif local.family == AddressFamily.IPv6: + setSockOpt(localSock, osdefs.IPPROTO_IP, osdefs.IPV6_MULTICAST_HOPS, + cint(ttl)) + else: + raiseAssert "Unsupported address bound to local socket" + + if not tres: let err = osLastError() if sock == asyncInvalidSocket: closeSocket(localSock) @@ -586,8 +546,8 @@ else: var saddr: Sockaddr_storage var slen: SockLen toSAddr(local, saddr, slen) - if bindAddr(SocketHandle(localSock), cast[ptr SockAddr](addr saddr), - slen) != 0: + if bindSocket(SocketHandle(localSock), cast[ptr SockAddr](addr saddr), + slen) != 0: let err = osLastError() if sock == asyncInvalidSocket: closeSocket(localSock) @@ -603,23 +563,23 @@ else: if sock == asyncInvalidSocket: closeSocket(localSock) raiseTransportOsError(err) - result.remote = remote + res.remote = remote - result.fd = localSock - result.function = cbproc - result.flags = flags - result.buffer = newSeq[byte](bufferSize) - result.queue = initDeque[GramVector]() - result.udata = udata - result.state = {WritePaused} - result.future = newFuture[void]("datagram.transport") - GC_ref(result) + res.fd = localSock + res.function = cbproc + res.flags = flags + res.buffer = newSeq[byte](bufferSize) + res.queue = initDeque[GramVector]() + res.udata = udata + res.state = {ReadPaused, WritePaused} + res.future = newFuture[void]("datagram.transport") + GC_ref(res) # Start tracking transport - trackDgram(result) + trackDgram(res) if NoAutoRead notin flags: - result.resumeRead() - else: - result.state.incl(ReadPaused) + let rres = res.resumeRead() + if rres.isErr(): raiseTransportOsError(rres.error()) + res proc close*(transp: DatagramTransport) = ## Closes and frees resources of transport ``transp``. @@ -656,7 +616,7 @@ proc newDatagramTransport*(cbproc: DatagramCallback, bufSize: int = DefaultDatagramBufferSize, ttl: int = 0 ): DatagramTransport {. - raises: [Defect, CatchableError].} = + raises: [Defect, TransportOsError].} = ## Create new UDP datagram transport (IPv4). ## ## ``cbproc`` - callback which will be called, when new datagram received. @@ -669,8 +629,8 @@ proc newDatagramTransport*(cbproc: DatagramCallback, ## ``bufSize`` - size of internal buffer. ## ``ttl`` - TTL for UDP datagram packet (only usable when flags has ## ``Broadcast`` option). - result = newDatagramTransportCommon(cbproc, remote, local, sock, - flags, udata, child, bufSize, ttl) + newDatagramTransportCommon(cbproc, remote, local, sock, flags, udata, child, + bufSize, ttl) proc newDatagramTransport*[T](cbproc: DatagramCallback, udata: ref T, @@ -682,12 +642,11 @@ proc newDatagramTransport*[T](cbproc: DatagramCallback, bufSize: int = DefaultDatagramBufferSize, ttl: int = 0 ): DatagramTransport {. - raises: [Defect, CatchableError].} = + raises: [Defect, TransportOsError].} = var fflags = flags + {GCUserData} GC_ref(udata) - result = newDatagramTransportCommon(cbproc, remote, local, sock, - fflags, cast[pointer](udata), - child, bufSize, ttl) + newDatagramTransportCommon(cbproc, remote, local, sock, fflags, + cast[pointer](udata), child, bufSize, ttl) proc newDatagramTransport6*(cbproc: DatagramCallback, remote: TransportAddress = AnyAddress6, @@ -699,7 +658,7 @@ proc newDatagramTransport6*(cbproc: DatagramCallback, bufSize: int = DefaultDatagramBufferSize, ttl: int = 0 ): DatagramTransport {. - raises: [Defect, CatchableError].} = + raises: [Defect, TransportOsError].} = ## Create new UDP datagram transport (IPv6). ## ## ``cbproc`` - callback which will be called, when new datagram received. @@ -712,8 +671,8 @@ proc newDatagramTransport6*(cbproc: DatagramCallback, ## ``bufSize`` - size of internal buffer. ## ``ttl`` - TTL for UDP datagram packet (only usable when flags has ## ``Broadcast`` option). - result = newDatagramTransportCommon(cbproc, remote, local, sock, - flags, udata, child, bufSize, ttl) + newDatagramTransportCommon(cbproc, remote, local, sock, flags, udata, child, + bufSize, ttl) proc newDatagramTransport6*[T](cbproc: DatagramCallback, udata: ref T, @@ -725,12 +684,11 @@ proc newDatagramTransport6*[T](cbproc: DatagramCallback, bufSize: int = DefaultDatagramBufferSize, ttl: int = 0 ): DatagramTransport {. - raises: [Defect, CatchableError].} = + raises: [Defect, TransportOsError].} = var fflags = flags + {GCUserData} GC_ref(udata) - result = newDatagramTransportCommon(cbproc, remote, local, sock, - fflags, cast[pointer](udata), - child, bufSize, ttl) + newDatagramTransportCommon(cbproc, remote, local, sock, fflags, + cast[pointer](udata), child, bufSize, ttl) proc join*(transp: DatagramTransport): Future[void] = ## Wait until the transport ``transp`` will be closed. @@ -753,7 +711,7 @@ proc join*(transp: DatagramTransport): Future[void] = proc closeWait*(transp: DatagramTransport): Future[void] = ## Close transport ``transp`` and release all resources. transp.close() - result = transp.join() + transp.join() proc send*(transp: DatagramTransport, pbytes: pointer, nbytes: int): Future[void] = @@ -768,7 +726,9 @@ proc send*(transp: DatagramTransport, pbytes: pointer, writer: retFuture) transp.queue.addLast(vector) if WritePaused in transp.state: - transp.resumeWrite() + let wres = transp.resumeWrite() + if wres.isErr(): + retFuture.fail(getTransportOsError(wres.error())) return retFuture proc send*(transp: DatagramTransport, msg: sink string, @@ -790,7 +750,9 @@ proc send*(transp: DatagramTransport, msg: sink string, writer: cast[Future[void]](retFuture)) transp.queue.addLast(vector) if WritePaused in transp.state: - transp.resumeWrite() + let wres = transp.resumeWrite() + if wres.isErr(): + retFuture.fail(getTransportOsError(wres.error())) return retFuture proc send*[T](transp: DatagramTransport, msg: sink seq[T], @@ -812,7 +774,9 @@ proc send*[T](transp: DatagramTransport, msg: sink seq[T], writer: cast[Future[void]](retFuture)) transp.queue.addLast(vector) if WritePaused in transp.state: - transp.resumeWrite() + let wres = transp.resumeWrite() + if wres.isErr(): + retFuture.fail(getTransportOsError(wres.error())) return retFuture proc sendTo*(transp: DatagramTransport, remote: TransportAddress, @@ -825,7 +789,9 @@ proc sendTo*(transp: DatagramTransport, remote: TransportAddress, writer: retFuture, address: remote) transp.queue.addLast(vector) if WritePaused in transp.state: - transp.resumeWrite() + let wres = transp.resumeWrite() + if wres.isErr(): + retFuture.fail(getTransportOsError(wres.error())) return retFuture proc sendTo*(transp: DatagramTransport, remote: TransportAddress, @@ -848,7 +814,9 @@ proc sendTo*(transp: DatagramTransport, remote: TransportAddress, address: remote) transp.queue.addLast(vector) if WritePaused in transp.state: - transp.resumeWrite() + let wres = transp.resumeWrite() + if wres.isErr(): + retFuture.fail(getTransportOsError(wres.error())) return retFuture proc sendTo*[T](transp: DatagramTransport, remote: TransportAddress, @@ -871,7 +839,9 @@ proc sendTo*[T](transp: DatagramTransport, remote: TransportAddress, address: remote) transp.queue.addLast(vector) if WritePaused in transp.state: - transp.resumeWrite() + let wres = transp.resumeWrite() + if wres.isErr(): + retFuture.fail(getTransportOsError(wres.error())) return retFuture proc peekMessage*(transp: DatagramTransport, msg: var seq[byte], @@ -889,17 +859,21 @@ proc peekMessage*(transp: DatagramTransport, msg: var seq[byte], proc getMessage*(transp: DatagramTransport): seq[byte] {. raises: [Defect, CatchableError].} = ## Copy data from internal message buffer and return result. + var default: seq[byte] if ReadError in transp.state: transp.state.excl(ReadError) raise transp.getError() if transp.buflen > 0: - result = newSeq[byte](transp.buflen) - copyMem(addr result[0], addr transp.buffer[0], transp.buflen) + var res = newSeq[byte](transp.buflen) + copyMem(addr res[0], addr transp.buffer[0], transp.buflen) + res + else: + default proc getUserData*[T](transp: DatagramTransport): T {.inline.} = ## Obtain user data stored in ``transp`` object. - result = cast[T](transp.udata) + cast[T](transp.udata) proc closed*(transp: DatagramTransport): bool {.inline.} = ## Returns ``true`` if transport in closed state. - result = ({ReadClosed, WriteClosed} * transp.state != {}) + {ReadClosed, WriteClosed} * transp.state != {} diff --git a/chronos/transports/osnet.nim b/chronos/transports/osnet.nim index d388369..23d1a7b 100644 --- a/chronos/transports/osnet.nim +++ b/chronos/transports/osnet.nim @@ -17,11 +17,16 @@ else: import std/algorithm from std/strutils import toHex -import ./ipnet +import ".."/osdefs +import "."/ipnet export ipnet const - MaxAdapterAddressLength* = 8 + MaxAdapterAddressLength* = + when defined(windows): + MAX_ADAPTER_ADDRESS_LENGTH + else: + 8 type InterfaceType* = enum @@ -344,169 +349,7 @@ proc cmp*(a, b: NetworkInterface): int = cmp(a.ifIndex, b.ifIndex) when defined(linux): - import posix - - const - AF_NETLINK = cint(16) - AF_PACKET = cint(17) - NETLINK_ROUTE = cint(0) - NLMSG_ALIGNTO = 4'u - RTA_ALIGNTO = 4'u - # RTA_UNSPEC = 0'u16 - RTA_DST = 1'u16 - # RTA_SRC = 2'u16 - # RTA_IIF = 3'u16 - RTA_OIF = 4'u16 - RTA_GATEWAY = 5'u16 - # RTA_PRIORITY = 6'u16 - RTA_PREFSRC = 7'u16 - # RTA_METRICS = 8'u16 - - RTM_F_LOOKUP_TABLE = 0x1000 - - RTM_GETLINK = 18 - RTM_GETADDR = 22 - RTM_GETROUTE = 26 - NLM_F_REQUEST = 1 - NLM_F_ROOT = 0x100 - NLM_F_MATCH = 0x200 - NLM_F_DUMP = NLM_F_ROOT or NLM_F_MATCH - IFLIST_REPLY_BUFFER = 8192 - InvalidSocketHandle = SocketHandle(-1) - NLMSG_DONE = 0x03 - # NLMSG_MIN_TYPE = 0x10 - NLMSG_ERROR = 0x02 - # MSG_TRUNC = 0x20 - - IFLA_ADDRESS = 1 - IFLA_IFNAME = 3 - IFLA_MTU = 4 - IFLA_OPERSTATE = 16 - - IFA_ADDRESS = 1 - IFA_LOCAL = 2 - # IFA_BROADCAST = 4 - - # ARPHRD_NETROM = 0 - ARPHRD_ETHER = 1 - ARPHRD_EETHER = 2 - # ARPHRD_AX25 = 3 - # ARPHRD_PRONET = 4 - # ARPHRD_CHAOS = 5 - # ARPHRD_IEEE802 = 6 - ARPHRD_ARCNET = 7 - # ARPHRD_APPLETLK = 8 - # ARPHRD_DLCI = 15 - ARPHRD_ATM = 19 - # ARPHRD_METRICOM = 23 - ARPHRD_IEEE1394 = 24 - # ARPHRD_EUI64 = 27 - # ARPHRD_INFINIBAND = 32 - ARPHRD_SLIP = 256 - ARPHRD_CSLIP = 257 - ARPHRD_SLIP6 = 258 - ARPHRD_CSLIP6 = 259 - # ARPHRD_RSRVD = 260 - # ARPHRD_ADAPT = 264 - # ARPHRD_ROSE = 270 - # ARPHRD_X25 = 271 - # ARPHRD_HWX25 = 272 - # ARPHRD_CAN = 280 - ARPHRD_PPP = 512 - ARPHRD_CISCO = 513 - ARPHRD_HDLC = ARPHRD_CISCO - ARPHRD_LAPB = 516 - # ARPHRD_DDCMP = 517 - # ARPHRD_RAWHDLC = 518 - # ARPHRD_TUNNEL = 768 - # ARPHRD_TUNNEL6 = 769 - ARPHRD_FRAD = 770 - # ARPHRD_SKIP = 771 - ARPHRD_LOOPBACK = 772 - # ARPHRD_LOCALTLK = 773 - # ARPHRD_FDDI = 774 - # ARPHRD_BIF = 775 - # ARPHRD_SIT = 776 - # ARPHRD_IPDDP = 777 - # ARPHRD_IPGRE = 778 - # ARPHRD_PIMREG = 779 - ARPHRD_HIPPI = 780 - # ARPHRD_ASH = 781 - # ARPHRD_ECONET = 782 - # ARPHRD_IRDA = 783 - # ARPHRD_FCPP = 784 - # ARPHRD_FCAL = 785 - # ARPHRD_FCPL = 786 - # ARPHRD_FCFABRIC = 787 - # ARPHRD_IEEE802_TR = 800 - ARPHRD_IEEE80211 = 801 - ARPHRD_IEEE80211_PRISM = 802 - ARPHRD_IEEE80211_RADIOTAP = 803 - # ARPHRD_IEEE802154 = 804 - # ARPHRD_IEEE802154_MONITOR = 805 - # ARPHRD_PHONET = 820 - # ARPHRD_PHONET_PIPE = 821 - # ARPHRD_CAIF = 822 - # ARPHRD_IP6GRE = 823 - # ARPHRD_NETLINK = 824 - # ARPHRD_6LOWPAN = 825 - # ARPHRD_VOID = 0xFFFF - # ARPHRD_NONE = 0xFFFE - - type - Sockaddr_nl = object - family: cushort - pad: cushort - pid: uint32 - groups: uint32 - - NlMsgHeader = object - nlmsg_len: uint32 - nlmsg_type: uint16 - nlmsg_flags: uint16 - nlmsg_seq: uint32 - nlmsg_pid: uint32 - - IfInfoMessage = object - ifi_family: byte - ifi_pad: byte - ifi_type: cushort - ifi_index: cint - ifi_flags: cuint - ifi_change: cuint - - IfAddrMessage = object - ifa_family: byte - ifa_prefixlen: byte - ifa_flags: byte - ifa_scope: byte - ifa_index: uint32 - - RtMessage = object - rtm_family: byte - rtm_dst_len: byte - rtm_src_len: byte - rtm_tos: byte - rtm_table: byte - rtm_protocol: byte - rtm_scope: byte - rtm_type: byte - rtm_flags: cuint - - RtAttr = object - rta_len: cushort - rta_type: cushort - - RtGenMsg = object - rtgen_family: byte - - NLReq = object - hdr: NlMsgHeader - msg: RtGenMsg - - NLRouteReq = object - hdr: NlMsgHeader - msg: RtMessage + import ".."/osutils template NLMSG_ALIGN(length: uint): uint = (length + NLMSG_ALIGNTO - 1) and not(NLMSG_ALIGNTO - 1) @@ -621,11 +464,11 @@ when defined(linux): address.family = cushort(AF_NETLINK) address.groups = 0 address.pid = cast[uint32](pid) - var res = posix.socket(AF_NETLINK, posix.SOCK_DGRAM, NETLINK_ROUTE) + var res = osdefs.socket(AF_NETLINK, osdefs.SOCK_DGRAM, NETLINK_ROUTE) if res != SocketHandle(-1): - if posix.bindSocket(res, cast[ptr SockAddr](addr address), + if osdefs.bindSocket(res, cast[ptr SockAddr](addr address), SockLen(sizeof(Sockaddr_nl))) != 0: - discard posix.close(res) + discard osdefs.close(res) res = SocketHandle(-1) res @@ -652,7 +495,7 @@ when defined(linux): rmsg.msg_iovlen = 1 rmsg.msg_name = cast[pointer](addr address) rmsg.msg_namelen = SockLen(sizeof(Sockaddr_nl)) - let res = posix.sendmsg(fd, addr rmsg, 0).TIovLen + let res = osdefs.sendmsg(fd, addr rmsg, 0).TIovLen (res == iov.iov_len) proc sendRouteMessage(fd: SocketHandle, pid: Pid, seqno: uint32, @@ -680,14 +523,14 @@ when defined(linux): req.msg.rtm_flags = RTM_F_LOOKUP_TABLE attr.rta_type = RTA_DST if dest.family == AddressFamily.IPv4: - req.msg.rtm_family = byte(posix.AF_INET) + req.msg.rtm_family = byte(osdefs.AF_INET) attr.rta_len = cast[cushort](RTA_LENGTH(4)) copyMem(RTA_DATA(attr), cast[ptr byte](unsafeAddr dest.address_v4[0]), 4) req.hdr.nlmsg_len = uint32(NLMSG_ALIGN(uint(req.hdr.nlmsg_len)) + RTA_ALIGN(uint(attr.rta_len))) req.msg.rtm_dst_len = 4 * 8 elif dest.family == AddressFamily.IPv6: - req.msg.rtm_family = byte(posix.AF_INET6) + req.msg.rtm_family = byte(osdefs.AF_INET6) attr.rta_len = cast[cushort](RTA_LENGTH(16)) copyMem(RTA_DATA(attr), cast[ptr byte](unsafeAddr dest.address_v6[0]), 16) req.hdr.nlmsg_len = uint32(NLMSG_ALIGN(uint(req.hdr.nlmsg_len)) + @@ -700,7 +543,7 @@ when defined(linux): rmsg.msg_iovlen = 1 rmsg.msg_name = cast[pointer](addr address) rmsg.msg_namelen = SockLen(sizeof(Sockaddr_nl)) - let res = posix.sendmsg(fd, addr rmsg, 0).TIovLen + let res = osdefs.sendmsg(fd, addr rmsg, 0).TIovLen (res == iov.iov_len) proc readNetlinkMessage(fd: SocketHandle, data: var seq[byte]): bool = @@ -715,7 +558,7 @@ when defined(linux): rmsg.msg_iovlen = 1 rmsg.msg_name = cast[pointer](addr address) rmsg.msg_namelen = SockLen(sizeof(Sockaddr_nl)) - var length = posix.recvmsg(fd, addr rmsg, 0) + var length = osdefs.recvmsg(fd, addr rmsg, 0) if length >= 0: data.setLen(length) true @@ -757,11 +600,11 @@ when defined(linux): res proc getAddress(f: int, p: pointer): TransportAddress = - if f == posix.AF_INET: + if f == osdefs.AF_INET: var res = TransportAddress(family: AddressFamily.IPv4) copyMem(addr res.address_v4[0], p, len(res.address_v4)) res - elif f == posix.AF_INET6: + elif f == osdefs.AF_INET6: var res = TransportAddress(family: AddressFamily.IPv6) copyMem(addr res.address_v6[0], p, len(res.address_v6)) res @@ -910,7 +753,7 @@ when defined(linux): proc getInterfaces*(): seq[NetworkInterface] {.raises: [Defect].} = ## Return list of available interfaces. var res: seq[NetworkInterface] - var pid = posix.getpid() + var pid = osdefs.getpid() var sock = createNetlinkSocket(pid) if sock == InvalidSocketHandle: return res @@ -918,134 +761,23 @@ when defined(linux): res = getLinks(sock, pid) getAddresses(sock, pid, res) sort(res, cmp) - discard posix.close(sock) + discard osdefs.close(sock) res proc getBestRoute*(address: TransportAddress): Route {.raises: [Defect].} = ## Return best applicable OS route, which will be used for connecting to ## address ``address``. - var pid = posix.getpid() + var pid = osdefs.getpid() var res = Route() var sock = createNetlinkSocket(pid) if sock == InvalidSocketHandle: res else: res = getRoute(sock, pid, address) - discard posix.close(sock) + discard osdefs.close(sock) res -elif defined(macosx) or defined(bsd): - import posix - - const - AF_LINK = 18 - IFF_UP = 0x01 - IFF_RUNNING = 0x40 - - PF_ROUTE = cint(17) - RTM_GET = 0x04'u8 - RTF_UP = 0x01 - RTF_GATEWAY = 0x02 - RTM_VERSION = 5'u8 - - RTA_DST = 0x01 - RTA_GATEWAY = 0x02 - - type - IfAddrs {.importc: "struct ifaddrs", header: "", - pure, final.} = object - ifa_next {.importc: "ifa_next".}: ptr IfAddrs - ifa_name {.importc: "ifa_name".}: ptr cchar - ifa_flags {.importc: "ifa_flags".}: cuint - ifa_addr {.importc: "ifa_addr".}: ptr SockAddr - ifa_netmask {.importc: "ifa_netmask".}: ptr SockAddr - ifa_dstaddr {.importc: "ifa_dstaddr".}: ptr SockAddr - ifa_data {.importc: "ifa_data".}: pointer - - PIfAddrs = ptr IfAddrs - - IfData {.importc: "struct if_data", header: "", - pure, final.} = object - ifi_type {.importc: "ifi_type".}: byte - ifi_typelen {.importc: "ifi_typelen".}: byte - ifi_physical {.importc: "ifi_physical".}: byte - ifi_addrlen {.importc: "ifi_addrlen".}: byte - ifi_hdrlen {.importc: "ifi_hdrlen".}: byte - ifi_recvquota {.importc: "ifi_recvquota".}: byte - ifi_xmitquota {.importc: "ifi_xmitquota".}: byte - ifi_unused1 {.importc: "ifi_unused1".}: byte - ifi_mtu {.importc: "ifi_mtu".}: uint32 - ifi_metric {.importc: "ifi_metric".}: uint32 - ifi_baudrate {.importc: "ifi_baudrate".}: uint32 - ifi_ipackets {.importc: "ifi_ipackets".}: uint32 - ifi_ierrors {.importc: "ifi_ierrors".}: uint32 - ifi_opackets {.importc: "ifi_opackets".}: uint32 - ifi_oerrors {.importc: "ifi_oerrors".}: uint32 - ifi_collisions {.importc: "ifi_collisions".}: uint32 - ifi_ibytes {.importc: "ifi_ibytes".}: uint32 - ifi_obytes {.importc: "ifi_obytes".}: uint32 - ifi_imcasts {.importc: "ifi_imcasts".}: uint32 - ifi_omcasts {.importc: "ifi_omcasts".}: uint32 - ifi_iqdrops {.importc: "ifi_iqdrops".}: uint32 - ifi_noproto {.importc: "ifi_noproto".}: uint32 - ifi_recvtiming {.importc: "ifi_recvtiming".}: uint32 - ifi_xmittiming {.importc: "ifi_xmittiming".}: uint32 - ifi_lastchange {.importc: "ifi_lastchange".}: Timeval - ifi_unused2 {.importc: "ifi_unused2".}: uint32 - ifi_hwassist {.importc: "ifi_hwassist".}: uint32 - ifi_reserved1 {.importc: "ifi_reserved1".}: uint32 - ifi_reserved2 {.importc: "ifi_reserved2".}: uint32 - - Sockaddr_dl = object - sdl_len: byte - sdl_family: byte - sdl_index: uint16 - sdl_type: byte - sdl_nlen: byte - sdl_alen: byte - sdl_slen: byte - sdl_data: array[12, byte] - - RtMetrics = object - rmx_locks: uint32 - rmx_mtu: uint32 - rmx_hopcount: uint32 - rmx_expire: int32 - rmx_recvpipe: uint32 - rmx_sendpipe: uint32 - rmx_ssthresh: uint32 - rmx_rtt: uint32 - rmx_rttvar: uint32 - rmx_pksent: uint32 - rmx_state: uint32 - rmx_filler: array[3, uint32] - - RtMsgHeader = object - rtm_msglen: uint16 - rtm_version: byte - rtm_type: byte - rtm_index: uint16 - rtm_flags: cint - rtm_addrs: cint - rtm_pid: Pid - rtm_seq: cint - rtm_errno: cint - rtm_use: cint - rtm_inits: uint32 - rtm_rmx: RtMetrics - - RtMessage = object - rtm: RtMsgHeader - space: array[512, byte] - - proc getIfAddrs(ifap: ptr PIfAddrs): cint {.importc: "getifaddrs", - header: """#include - #include - #include """.} - proc freeIfAddrs(ifap: ptr IfAddrs) {.importc: "freeifaddrs", - header: """#include - #include - #include """.} +elif defined(macosx) or defined(macos) or defined(bsd): proc toInterfaceType(f: byte): InterfaceType = var ft = int(f) @@ -1070,7 +802,7 @@ elif defined(macosx) or defined(bsd): var iface: NetworkInterface var ifaddress: InterfaceAddress - iface.name = string($cstring(ifap.ifa_name)) + iface.name = $cast[cstring](ifap.ifa_name) iface.flags = uint64(ifap.ifa_flags) var i = 0 while i < len(res): @@ -1093,20 +825,20 @@ elif defined(macosx) or defined(bsd): res[i].maclen = int(link.sdl_alen) res[i].ifType = toInterfaceType(data.ifi_type) res[i].state = toInterfaceState(ifap.ifa_flags) - res[i].mtu = int(data.ifi_mtu) - elif family == posix.AF_INET: + res[i].mtu = cast[int](data.ifi_mtu) + elif family == osdefs.AF_INET: fromSAddr(cast[ptr Sockaddr_storage](ifap.ifa_addr), SockLen(sizeof(Sockaddr_in)), ifaddress.host) - elif family == posix.AF_INET6: + elif family == osdefs.AF_INET6: fromSAddr(cast[ptr Sockaddr_storage](ifap.ifa_addr), SockLen(sizeof(Sockaddr_in6)), ifaddress.host) if not isNil(ifap.ifa_netmask): var na: TransportAddress - var family = cint(ifap.ifa_netmask.sa_family) - if family == posix.AF_INET: + var family = cast[cint](ifap.ifa_netmask.sa_family) + if family == osdefs.AF_INET: fromSAddr(cast[ptr Sockaddr_storage](ifap.ifa_netmask), SockLen(sizeof(Sockaddr_in)), na) - elif family == posix.AF_INET6: + elif family == osdefs.AF_INET6: fromSAddr(cast[ptr Sockaddr_storage](ifap.ifa_netmask), SockLen(sizeof(Sockaddr_in6)), na) ifaddress.net = IpNet.init(ifaddress.host, na) @@ -1137,15 +869,15 @@ elif defined(macosx) or defined(bsd): var sock: cint var msg: RtMessage var res = Route() - var pid = posix.getpid() + var pid = osdefs.getpid() if address.family notin {AddressFamily.IPv4, AddressFamily.IPv6}: return if address.family == AddressFamily.IPv4: - sock = cint(posix.socket(PF_ROUTE, posix.SOCK_RAW, posix.AF_INET)) + sock = cint(osdefs.socket(PF_ROUTE, osdefs.SOCK_RAW, osdefs.AF_INET)) elif address.family == AddressFamily.IPv6: - sock = cint(posix.socket(PF_ROUTE, posix.SOCK_RAW, posix.AF_INET6)) + sock = cint(osdefs.socket(PF_ROUTE, osdefs.SOCK_RAW, osdefs.AF_INET6)) if sock != -1: var sastore: Sockaddr_storage @@ -1162,13 +894,13 @@ elif defined(macosx) or defined(bsd): msg.rtm.rtm_addrs = RTA_DST msg.space[0] = cast[byte](salen) msg.rtm.rtm_msglen = uint16(sizeof(RtMessage)) - let wres = posix.write(sock, addr msg, sizeof(RtMessage)) + let wres = osdefs.write(sock, addr msg, sizeof(RtMessage)) if wres >= 0: let rres = block: var pres = 0 while true: - pres = posix.read(sock, addr msg, sizeof(RtMessage)) + pres = osdefs.read(sock, addr msg, sizeof(RtMessage)) if ((pres >= 0) and (msg.rtm.rtm_pid == pid) and (msg.rtm.rtm_seq == 0xCAFE)) or (pres < 0): break @@ -1198,142 +930,17 @@ elif defined(macosx) or defined(bsd): if a.host.family == address.family: res.source = a.host break - discard posix.close(sock) + discard osdefs.close(sock) res elif defined(windows): - import winlean, dynlib + import dynlib + import ".."/osutils const WorkBufferSize = 16384'u32 MaxTries = 3 - AF_UNSPEC = 0x00'u32 - - GAA_FLAG_INCLUDE_PREFIX = 0x0010'u32 - - CP_UTF8 = 65001'u32 - - ERROR_BUFFER_OVERFLOW* = 111'u32 - ERROR_SUCCESS* = 0'u32 - - type - WCHAR = distinct uint16 - - SocketAddress = object - lpSockaddr: ptr SockAddr - iSockaddrLength: cint - - IpAdapterUnicastAddressXpLh = object - length: uint32 - flags: uint32 - next: ptr IpAdapterUnicastAddressXpLh - address: SocketAddress - prefixOrigin: cint - suffixOrigin: cint - dadState: cint - validLifetime: uint32 - preferredLifetime: uint32 - leaseLifetime: uint32 - onLinkPrefixLength: byte # This field is available only from Vista - - IpAdapterAnycastAddressXp = object - length: uint32 - flags: uint32 - next: ptr IpAdapterAnycastAddressXp - address: SocketAddress - - IpAdapterMulticastAddressXp = object - length: uint32 - flags: uint32 - next: ptr IpAdapterMulticastAddressXp - address: SocketAddress - - IpAdapterDnsServerAddressXp = object - length: uint32 - flags: uint32 - next: ptr IpAdapterDnsServerAddressXp - address: SocketAddress - - IpAdapterPrefixXp = object - length: uint32 - flags: uint32 - next: ptr IpAdapterPrefixXp - address: SocketAddress - prefixLength: uint32 - - IpAdapterAddressesXp = object - length: uint32 - ifIndex: uint32 - next: ptr IpAdapterAddressesXp - adapterName: cstring - unicastAddress: ptr IpAdapterUnicastAddressXpLh - anycastAddress: ptr IpAdapterAnycastAddressXp - multicastAddress: ptr IpAdapterMulticastAddressXp - dnsServerAddress: ptr IpAdapterDnsServerAddressXp - dnsSuffix: ptr WCHAR - description: ptr WCHAR - friendlyName: ptr WCHAR - physicalAddress: array[MaxAdapterAddressLength, byte] - physicalAddressLength: uint32 - flags: uint32 - mtu: uint32 - ifType: uint32 - operStatus: cint - ipv6IfIndex: uint32 - zoneIndices: array[16, uint32] - firstPrefix: ptr IpAdapterPrefixXp - - MibIpForwardRow = object - dwForwardDest: uint32 - dwForwardMask: uint32 - dwForwardPolicy: uint32 - dwForwardNextHop: uint32 - dwForwardIfIndex: uint32 - dwForwardType: uint32 - dwForwardProto: uint32 - dwForwardAge: uint32 - dwForwardNextHopAS: uint32 - dwForwardMetric1: uint32 - dwForwardMetric2: uint32 - dwForwardMetric3: uint32 - dwForwardMetric4: uint32 - dwForwardMetric5: uint32 - - SOCKADDR_INET {.union.} = object - ipv4: Sockaddr_in - ipv6: Sockaddr_in6 - si_family: uint16 - - IPADDRESS_PREFIX = object - prefix: SOCKADDR_INET - prefixLength: uint8 - - MibIpForwardRow2 = object - interfaceLuid: uint64 - interfaceIndex: uint32 - destinationPrefix: IPADDRESS_PREFIX - nextHop: SOCKADDR_INET - sitePrefixLength: byte - validLifetime: uint32 - preferredLifetime: uint32 - metric: uint32 - protocol: uint32 - loopback: bool - autoconfigureAddress: bool - publish: bool - immortal: bool - age: uint32 - origin: uint32 - - GETBESTROUTE2 = proc(InterfaceLuid: ptr uint64, InterfaceIndex: uint32, - SourceAddress: ptr SOCKADDR_INET, - DestinationAddress: ptr SOCKADDR_INET, - AddressSortOptions: uint32, - BestRoute: ptr MibIpForwardRow2, - BestSourceAddress: ptr SOCKADDR_INET): DWORD {. - gcsafe, stdcall, raises: [].} - proc toInterfaceType(ft: uint32): InterfaceType {.inline.} = if (ft >= 1'u32 and ft <= 196'u32) or (ft == 237) or (ft == 243) or (ft == 244) or (ft == 259) or (ft == 281): @@ -1347,45 +954,14 @@ elif defined(windows): else: StatusUnknown - proc GetAdaptersAddresses(family: uint32, flags: uint32, reserved: pointer, - addresses: ptr IpAdapterAddressesXp, - sizeptr: ptr uint32): uint32 {. - stdcall, dynlib: "iphlpapi", importc: "GetAdaptersAddresses", - raises: [].} - - proc WideCharToMultiByte(CodePage: uint32, dwFlags: uint32, - lpWideCharStr: ptr WCHAR, cchWideChar: cint, - lpMultiByteStr: ptr char, cbMultiByte: cint, - lpDefaultChar: ptr char, - lpUsedDefaultChar: ptr uint32): cint - {.stdcall, dynlib: "kernel32.dll", importc: "WideCharToMultiByte", - raises: [].} - - proc getBestRouteXp(dwDestAddr: uint32, dwSourceAddr: uint32, - pBestRoute: ptr MibIpForwardRow): uint32 {. - stdcall, dynlib: "iphlpapi", importc: "GetBestRoute", - raises: [].} - proc `$`(bstr: ptr WCHAR): string = - var buffer: char - var count = WideCharToMultiByte(CP_UTF8, 0, bstr, -1, addr(buffer), 0, - nil, nil) - if count > 0: - var res = newString(count + 8) - let wres = WideCharToMultiByte(CP_UTF8, 0, bstr, -1, addr(res[0]), - count, nil, nil) - if wres > 0: - res.setLen(wres - 1) - else: - res.setLen(0) - res - else: - "" + let res = toString(bstr) + if res.isErr(): "" else: res.get() proc isVista(): bool = var ver: OSVERSIONINFO ver.dwOSVersionInfoSize = DWORD(sizeof(ver)) - let res = getVersionExW(addr(ver)) + let res = getVersionEx(addr(ver)) if res == 0: false else: @@ -1466,8 +1042,8 @@ elif defined(windows): while true: buffer = newSeq[byte](size) var addresses = cast[ptr IpAdapterAddressesXp](addr buffer[0]) - gres = GetAdaptersAddresses(AF_UNSPEC, GAA_FLAG_INCLUDE_PREFIX, nil, - addresses, addr size) + gres = getAdaptersAddresses(osdefs.AF_UNSPEC, GAA_FLAG_INCLUDE_PREFIX, + nil, addresses, addr size) if gres == ERROR_SUCCESS: buffer.setLen(size) break @@ -1525,14 +1101,14 @@ elif defined(windows): addr bestRoute, cast[ptr SOCKADDR_INET](addr src)) if gres == 0: - if src.ss_family == winlean.AF_INET: + if src.ss_family == osdefs.AF_INET: fromSAddr(addr src, SockLen(sizeof(Sockaddr_in)), res.source) - elif src.ss_family == winlean.AF_INET6: + elif src.ss_family == osdefs.AF_INET6: fromSAddr(addr src, SockLen(sizeof(Sockaddr_in6)), res.source) - if bestRoute.nextHop.si_family == winlean.AF_INET: + if bestRoute.nextHop.si_family == osdefs.AF_INET: fromSAddr(cast[ptr Sockaddr_storage](addr bestRoute.nextHop), SockLen(sizeof(Sockaddr_in)), res.gateway) - elif bestRoute.nextHop.si_family == winlean.AF_INET6: + elif bestRoute.nextHop.si_family == osdefs.AF_INET6: fromSAddr(cast[ptr Sockaddr_storage](addr bestRoute.nextHop), SockLen(sizeof(Sockaddr_in6)), res.gateway) if res.gateway.isZero(): diff --git a/chronos/transports/stream.nim b/chronos/transports/stream.nim index a467d96..a5f9788 100644 --- a/chronos/transports/stream.nim +++ b/chronos/transports/stream.nim @@ -12,15 +12,10 @@ when (NimMajor, NimMinor) < (1, 4): else: {.push raises: [].} -import std/[net, nativesockets, os, deques] -import ".."/[asyncloop, handles, selectors2] +import std/deques +import ".."/[asyncloop, handles, osdefs, osutils] import common -when defined(windows) or defined(nimdoc): - import winlean -else: - import posix - type VectorKind = enum DataBuffer, # Simple buffer pointer/length @@ -75,8 +70,6 @@ const StreamServerTrackerName = "stream.server" when defined(windows): - const SO_UPDATE_CONNECT_CONTEXT = 0x7010 - type StreamTransport* = ref object of RootRef fd*: AsyncFD # File descriptor @@ -88,8 +81,8 @@ when defined(windows): queue: Deque[StreamVector] # Writer queue future: Future[void] # Stream life future # Windows specific part - rwsabuf: TWSABuf # Reader WSABUF - wwsabuf: TWSABuf # Writer WSABUF + rwsabuf: WSABUF # Reader WSABUF + wwsabuf: WSABUF # Writer WSABUF rovl: CustomOverlapped # Reader OVERLAPPED structure wovl: CustomOverlapped # Writer OVERLAPPED structure roffset: int # Pending reading offset @@ -126,13 +119,15 @@ else: type StreamCallback* = proc(server: StreamServer, - client: StreamTransport): Future[void] {.gcsafe, raises: [Defect].} + client: StreamTransport): Future[void] {. + gcsafe, raises: [Defect].} ## New remote client connection callback ## ``server`` - StreamServer object. ## ``client`` - accepted client transport. TransportInitCallback* = proc(server: StreamServer, - fd: AsyncFD): StreamTransport {.gcsafe, raises: [Defect].} + fd: AsyncFD): StreamTransport {. + gcsafe, raises: [Defect].} ## Custom transport initialization procedure, which can allocate inherited ## StreamTransport object. @@ -155,7 +150,7 @@ proc remoteAddress*(transp: StreamTransport): TransportAddress {. addr slen) != 0: raiseTransportOsError(osLastError()) fromSAddr(addr saddr, slen, transp.remote) - result = transp.remote + transp.remote proc localAddress*(transp: StreamTransport): TransportAddress {. raises: [Defect, TransportError].} = @@ -169,11 +164,11 @@ proc localAddress*(transp: StreamTransport): TransportAddress {. addr slen) != 0: raiseTransportOsError(osLastError()) fromSAddr(addr saddr, slen, transp.local) - result = transp.local + transp.local proc localAddress*(server: StreamServer): TransportAddress = ## Returns ``server`` bound local socket address. - result = server.local + server.local template completeReader(stream: StreamTransport) = if not(isNil(transp.reader)) and not(transp.reader.finished()): @@ -210,32 +205,36 @@ proc setupStreamServerTracker(): StreamServerTracker {. gcsafe, raises: [Defect].} proc getStreamTransportTracker(): StreamTransportTracker {.inline.} = - result = cast[StreamTransportTracker](getTracker(StreamTransportTrackerName)) - if isNil(result): - result = setupStreamTransportTracker() + var res = cast[StreamTransportTracker](getTracker(StreamTransportTrackerName)) + if isNil(res): + res = setupStreamTransportTracker() + doAssert(not(isNil(res))) + res proc getStreamServerTracker(): StreamServerTracker {.inline.} = - result = cast[StreamServerTracker](getTracker(StreamServerTrackerName)) - if isNil(result): - result = setupStreamServerTracker() + var res = cast[StreamServerTracker](getTracker(StreamServerTrackerName)) + if isNil(res): + res = setupStreamServerTracker() + doAssert(not(isNil(res))) + res proc dumpTransportTracking(): string {.gcsafe.} = var tracker = getStreamTransportTracker() - result = "Opened transports: " & $tracker.opened & "\n" & - "Closed transports: " & $tracker.closed + "Opened transports: " & $tracker.opened & "\n" & + "Closed transports: " & $tracker.closed proc dumpServerTracking(): string {.gcsafe.} = var tracker = getStreamServerTracker() - result = "Opened servers: " & $tracker.opened & "\n" & - "Closed servers: " & $tracker.closed + "Opened servers: " & $tracker.opened & "\n" & + "Closed servers: " & $tracker.closed proc leakTransport(): bool {.gcsafe.} = var tracker = getStreamTransportTracker() - result = tracker.opened != tracker.closed + tracker.opened != tracker.closed proc leakServer(): bool {.gcsafe.} = var tracker = getStreamServerTracker() - result = tracker.opened != tracker.closed + tracker.opened != tracker.closed proc trackStream(t: StreamTransport) {.inline.} = var tracker = getStreamTransportTracker() @@ -254,20 +253,16 @@ proc untrackServer(s: StreamServer) {.inline.} = inc(tracker.closed) proc setupStreamTransportTracker(): StreamTransportTracker {.gcsafe.} = - result = new StreamTransportTracker - result.opened = 0 - result.closed = 0 - result.dump = dumpTransportTracking - result.isLeaked = leakTransport - addTracker(StreamTransportTrackerName, result) + let res = StreamTransportTracker( + opened: 0, closed: 0, dump: dumpTransportTracking, isLeaked: leakTransport) + addTracker(StreamTransportTrackerName, res) + res proc setupStreamServerTracker(): StreamServerTracker {.gcsafe.} = - result = new StreamServerTracker - result.opened = 0 - result.closed = 0 - result.dump = dumpServerTracking - result.isLeaked = leakServer - addTracker(StreamServerTrackerName, result) + let res = StreamServerTracker( + opened: 0, closed: 0, dump: dumpServerTracking, isLeaked: leakServer) + addTracker(StreamServerTrackerName, res) + res proc completePendingWriteQueue(queue: var Deque[StreamVector], v: int) {.inline.} = @@ -297,48 +292,38 @@ proc clean(transp: StreamTransport) {.inline.} = transp.future.complete() GC_unref(transp) -when defined(nimdoc): - proc pauseAccept(server: StreamServer) {.inline.} = discard - proc resumeAccept(server: StreamServer) {.inline.} = discard - proc resumeRead(transp: StreamTransport) {.inline.} = discard - proc accept*(server: StreamServer): Future[StreamTransport] = discard - proc resumeWrite(transp: StreamTransport) {.inline.} = discard - proc newStreamPipeTransport(fd: AsyncFD, bufsize: int, - child: StreamTransport, - flags: set[TransportFlags] = {}): StreamTransport = - discard -elif defined(windows): +when defined(windows): template zeroOvelappedOffset(t: untyped) = (t).offset = 0 (t).offsetHigh = 0 template setOverlappedOffset(t, o: untyped) = - (t).offset = cast[int32](cast[uint64](o) and 0xFFFFFFFF'u64) - (t).offsetHigh = cast[int32](cast[uint64](o) shr 32) + (t).offset = cast[DWORD](cast[uint64](o) and 0xFFFFFFFF'u64) + (t).offsetHigh = cast[DWORD](cast[uint64](o) shr 32) template getFileSize(v: untyped): uint = cast[uint]((v).buf) - template getFileHandle(v: untyped): Handle = - cast[Handle]((v).buflen) + template getFileHandle(v: untyped): HANDLE = + cast[HANDLE]((v).buflen) template setReaderWSABuffer(t: untyped) = (t).rwsabuf.buf = cast[cstring]( cast[uint](addr t.buffer[0]) + uint((t).roffset)) - (t).rwsabuf.len = int32(len((t).buffer) - (t).roffset) + (t).rwsabuf.len = ULONG(len((t).buffer) - (t).roffset) template setWriterWSABuffer(t, v: untyped) = (t).wwsabuf.buf = cast[cstring](v.buf) - (t).wwsabuf.len = cast[int32](v.buflen) + (t).wwsabuf.len = cast[ULONG](v.buflen) proc isConnResetError(err: OSErrorCode): bool {.inline.} = - result = (err == OSErrorCode(common.WSAECONNRESET)) or - (err == OSErrorCode(common.WSAECONNABORTED)) or - (err == OSErrorCode(common.ERROR_PIPE_NOT_CONNECTED)) + result = (err == OSErrorCode(osdefs.WSAECONNRESET)) or + (err == OSErrorCode(osdefs.WSAECONNABORTED)) or + (err == OSErrorCode(osdefs.ERROR_PIPE_NOT_CONNECTED)) proc writeStreamLoop(udata: pointer) {.gcsafe, nimcall.} = - var bytesCount: int32 + var bytesCount: uint32 var ovl = cast[PtrCustomOverlapped](udata) var transp = cast[StreamTransport](ovl.data.udata) @@ -367,7 +352,9 @@ elif defined(windows): transp.queue.addFirst(vector) else: if not(vector.writer.finished()): - vector.writer.complete(transp.wwsabuf.len) + # This conversion to `int` safe, because its impossible + # to call write() with size bigger than `int`. + vector.writer.complete(int(transp.wwsabuf.len)) else: if uint(bytesCount) < getFileSize(vector): vector.shiftVectorFile(bytesCount) @@ -382,8 +369,10 @@ elif defined(windows): transp.queue.addFirst(vector) else: if not(vector.writer.finished()): - vector.writer.complete(transp.wwsabuf.len) - elif int(err) == ERROR_OPERATION_ABORTED: + # This conversion to `int` safe, because its impossible + # to call write() with size bigger than `int`. + vector.writer.complete(int(transp.wwsabuf.len)) + elif int(err) == osdefs.ERROR_OPERATION_ABORTED: # CancelIO() interrupt transp.state.incl({WritePaused, WriteEof}) let vector = transp.queue.popFirst() @@ -417,12 +406,12 @@ elif defined(windows): if vector.kind == VectorKind.DataBuffer: transp.wovl.zeroOvelappedOffset() transp.setWriterWSABuffer(vector) - let ret = WSASend(sock, addr transp.wwsabuf, 1, + let ret = wsaSend(sock, addr transp.wwsabuf, 1, addr bytesCount, DWORD(0), cast[POVERLAPPED](addr transp.wovl), nil) if ret != 0: let err = osLastError() - if int(err) == ERROR_OPERATION_ABORTED: + if int(err) == osdefs.ERROR_OPERATION_ABORTED: # CancelIO() interrupt transp.state.excl(WritePending) transp.state.incl({WritePaused, WriteEof}) @@ -430,7 +419,7 @@ elif defined(windows): vector.writer.complete(0) completePendingWriteQueue(transp.queue, 0) break - elif int(err) == ERROR_IO_PENDING: + elif int(err) == osdefs.ERROR_IO_PENDING: transp.queue.addFirst(vector) else: transp.state.excl(WritePending) @@ -453,21 +442,16 @@ elif defined(windows): transp.queue.addFirst(vector) else: let loop = getThreadDispatcher() - var size: int32 - var flags: int32 - - if getFileSize(vector) > 2_147_483_646'u: - size = 2_147_483_646 - else: - size = int32(getFileSize(vector)) + let size = min(uint32(getFileSize(vector)), 2_147_483_646'u32) transp.wovl.setOverlappedOffset(vector.offset) - var ret = loop.transmitFile(sock, getFileHandle(vector), size, 0, + var ret = loop.transmitFile(sock, getFileHandle(vector), size, + 0'u32, cast[POVERLAPPED](addr transp.wovl), - nil, flags) + nil, 0'u32) if ret == 0: let err = osLastError() - if int(err) == ERROR_OPERATION_ABORTED: + if int(err) == osdefs.ERROR_OPERATION_ABORTED: # CancelIO() interrupt transp.state.excl(WritePending) transp.state.incl({WritePaused, WriteEof}) @@ -475,7 +459,7 @@ elif defined(windows): vector.writer.complete(0) completePendingWriteQueue(transp.queue, 0) break - elif int(err) == ERROR_IO_PENDING: + elif int(err) == osdefs.ERROR_IO_PENDING: transp.queue.addFirst(vector) else: transp.state.excl(WritePending) @@ -497,7 +481,7 @@ elif defined(windows): else: transp.queue.addFirst(vector) elif transp.kind == TransportKind.Pipe: - let pipe = Handle(transp.fd) + let pipe = HANDLE(transp.fd) var vector = transp.queue.popFirst() if vector.kind == VectorKind.DataBuffer: transp.wovl.zeroOvelappedOffset() @@ -507,7 +491,8 @@ elif defined(windows): cast[POVERLAPPED](addr transp.wovl)) if ret == 0: let err = osLastError() - if int(err) in {ERROR_OPERATION_ABORTED, ERROR_NO_DATA}: + if int(err) in {osdefs.ERROR_OPERATION_ABORTED, + osdefs.ERROR_NO_DATA}: # CancelIO() interrupt transp.state.excl(WritePending) transp.state.incl({WritePaused, WriteEof}) @@ -515,7 +500,7 @@ elif defined(windows): vector.writer.complete(0) completePendingWriteQueue(transp.queue, 0) break - elif int(err) == ERROR_IO_PENDING: + elif int(err) == osdefs.ERROR_IO_PENDING: transp.queue.addFirst(vector) else: transp.state.excl(WritePending) @@ -558,19 +543,22 @@ elif defined(windows): moveMem(addr transp.buffer[transp.offset], addr transp.buffer[transp.roffset], bytesCount) - transp.offset += bytesCount + transp.offset += int(bytesCount) transp.roffset = transp.offset if transp.offset == len(transp.buffer): transp.state.incl(ReadPaused) - elif int(err) in {ERROR_OPERATION_ABORTED, ERROR_CONNECTION_ABORTED, - ERROR_BROKEN_PIPE, ERROR_NETNAME_DELETED}: + elif int(err) in {osdefs.ERROR_OPERATION_ABORTED, + osdefs.ERROR_CONNECTION_ABORTED, + osdefs.ERROR_BROKEN_PIPE, + osdefs.ERROR_NETNAME_DELETED}: # CancelIO() interrupt or closeSocket() call. transp.state.incl(ReadPaused) elif transp.kind == TransportKind.Socket and - (int(err) in {ERROR_NETNAME_DELETED, common.WSAECONNABORTED}): + (int(err) in {osdefs.ERROR_NETNAME_DELETED, + osdefs.WSAECONNABORTED}): transp.state.incl({ReadEof, ReadPaused}) elif transp.kind == TransportKind.Pipe and - (int(err) in {ERROR_PIPE_NOT_CONNECTED}): + (int(err) in {osdefs.ERROR_PIPE_NOT_CONNECTED}): transp.state.incl({ReadEof, ReadPaused}) else: transp.setReadError(err) @@ -588,34 +576,34 @@ elif defined(windows): ## Initiation if transp.state * {ReadEof, ReadClosed, ReadError} == {}: var flags = DWORD(0) - var bytesCount: int32 = 0 + var bytesCount = 0'u32 transp.state.excl(ReadPaused) transp.state.incl(ReadPending) if transp.kind == TransportKind.Socket: let sock = SocketHandle(transp.fd) transp.roffset = transp.offset transp.setReaderWSABuffer() - let ret = WSARecv(sock, addr transp.rwsabuf, 1, + let ret = wsaRecv(sock, addr transp.rwsabuf, 1, addr bytesCount, addr flags, cast[POVERLAPPED](addr transp.rovl), nil) if ret != 0: let err = osLastError() - if int32(err) == ERROR_OPERATION_ABORTED: + if int32(err) == osdefs.ERROR_OPERATION_ABORTED: # CancelIO() interrupt transp.state.excl(ReadPending) transp.state.incl(ReadPaused) - elif int32(err) in {common.WSAECONNRESET, common.WSAENETRESET, - common.WSAECONNABORTED}: + elif int32(err) in {osdefs.WSAECONNRESET, osdefs.WSAENETRESET, + osdefs.WSAECONNABORTED}: transp.state.excl(ReadPending) transp.state.incl({ReadEof, ReadPaused}) transp.completeReader() - elif int32(err) != ERROR_IO_PENDING: + elif int32(err) != osdefs.ERROR_IO_PENDING: transp.state.excl(ReadPending) transp.state.incl(ReadPaused) transp.setReadError(err) transp.completeReader() elif transp.kind == TransportKind.Pipe: - let pipe = Handle(transp.fd) + let pipe = HANDLE(transp.fd) transp.roffset = transp.offset transp.setReaderWSABuffer() let ret = readFile(pipe, cast[pointer](transp.rwsabuf.buf), @@ -623,15 +611,16 @@ elif defined(windows): cast[POVERLAPPED](addr transp.rovl)) if ret == 0: let err = osLastError() - if int32(err) == ERROR_OPERATION_ABORTED: + if int32(err) == osdefs.ERROR_OPERATION_ABORTED: # CancelIO() interrupt transp.state.excl(ReadPending) transp.state.incl(ReadPaused) - elif int32(err) in {ERROR_BROKEN_PIPE, ERROR_PIPE_NOT_CONNECTED}: + elif int32(err) in {osdefs.ERROR_BROKEN_PIPE, + osdefs.ERROR_PIPE_NOT_CONNECTED}: transp.state.excl(ReadPending) transp.state.incl({ReadEof, ReadPaused}) transp.completeReader() - elif int32(err) != ERROR_IO_PENDING: + elif int32(err) != osdefs.ERROR_IO_PENDING: transp.state.excl(ReadPending) transp.state.incl(ReadPaused) transp.setReadError(err) @@ -664,7 +653,7 @@ elif defined(windows): transp.queue = initDeque[StreamVector]() transp.future = newFuture[void]("stream.socket.transport") GC_ref(transp) - result = transp + transp proc newStreamPipeTransport(fd: AsyncFD, bufsize: int, child: StreamTransport, @@ -685,22 +674,27 @@ elif defined(windows): transp.queue = initDeque[StreamVector]() transp.future = newFuture[void]("stream.pipe.transport") GC_ref(transp) - result = transp + transp proc bindToDomain(handle: AsyncFD, domain: Domain): bool = - result = true if domain == Domain.AF_INET6: var saddr: Sockaddr_in6 - saddr.sin6_family = type(saddr.sin6_family)(toInt(domain)) - if bindAddr(SocketHandle(handle), cast[ptr SockAddr](addr(saddr)), - sizeof(saddr).SockLen) != 0'i32: - result = false + saddr.sin6_family = type(saddr.sin6_family)(osdefs.AF_INET6) + if osdefs.bindSocket(SocketHandle(handle), + cast[ptr SockAddr](addr(saddr)), + sizeof(saddr).SockLen) != 0'i32: + return false + true elif domain == Domain.AF_INET: var saddr: Sockaddr_in - saddr.sin_family = type(saddr.sin_family)(toInt(domain)) - if bindAddr(SocketHandle(handle), cast[ptr SockAddr](addr(saddr)), - sizeof(saddr).SockLen) != 0'i32: - result = false + saddr.sin_family = type(saddr.sin_family)(osdefs.AF_INET) + if osdefs.bindSocket(SocketHandle(handle), + cast[ptr SockAddr](addr(saddr)), + sizeof(saddr).SockLen) != 0'i32: + return false + true + else: + raiseAssert "Unsupported domain" proc connect*(address: TransportAddress, bufferSize = DefaultStreamBufferSize, @@ -710,7 +704,6 @@ elif defined(windows): ## new transport object ``StreamTransport`` for established connection. ## ``bufferSize`` is size of internal buffer for transport. let loop = getThreadDispatcher() - var retFuture = newFuture[StreamTransport]("stream.transport.connect") if address.family in {AddressFamily.IPv4, AddressFamily.IPv6}: ## Socket handling part @@ -725,11 +718,8 @@ elif defined(windows): toSAddr(raddress, saddr, slen) proto = Protocol.IPPROTO_TCP - sock = try: createAsyncSocket(raddress.getDomain(), SockType.SOCK_STREAM, + sock = createAsyncSocket(raddress.getDomain(), SockType.SOCK_STREAM, proto) - except CatchableError as exc: - retFuture.fail(exc) - return retFuture if sock == asyncInvalidSocket: retFuture.fail(getTransportOsError(osLastError())) return retFuture @@ -744,8 +734,8 @@ elif defined(windows): var ovl = cast[RefCustomOverlapped](udata) if not(retFuture.finished()): if ovl.data.errCode == OSErrorCode(-1): - if setsockopt(SocketHandle(sock), cint(SOL_SOCKET), - cint(SO_UPDATE_CONNECT_CONTEXT), nil, + if setsockopt(SocketHandle(sock), cint(osdefs.SOL_SOCKET), + cint(osdefs.SO_UPDATE_CONNECT_CONTEXT), nil, SockLen(0)) != 0'i32: let err = wsaGetLastError() sock.closeSocket() @@ -768,12 +758,12 @@ elif defined(windows): povl.data = CompletionData(cb: socketContinuation) let res = loop.connectEx(SocketHandle(sock), cast[ptr SockAddr](addr saddr), - DWORD(slen), nil, 0, nil, + cint(slen), nil, 0, nil, cast[POVERLAPPED](povl)) # We will not process immediate completion, to avoid undefined behavior. - if not(res): + if res == osdefs.FALSE: let err = osLastError() - if int32(err) != ERROR_IO_PENDING: + if int32(err) != osdefs.ERROR_IO_PENDING: GC_unref(povl) sock.closeSocket() retFuture.fail(getTransportOsError(err)) @@ -782,36 +772,41 @@ elif defined(windows): elif address.family == AddressFamily.Unix: ## Unix domain socket emulation with Windows Named Pipes. - var pipeHandle = INVALID_HANDLE_VALUE + # For some reason Nim compiler does not detect `pipeHandle` usage in + # pipeContinuation() procedure, so we marking it as {.used.} here. + var pipeHandle {.used.} = osdefs.INVALID_HANDLE_VALUE var pipeContinuation: proc (udata: pointer) {.gcsafe, raises: [Defect].} + pipeContinuation = proc (udata: pointer) {.gcsafe, raises: [Defect].} = # Continue only if `retFuture` is not cancelled. if not(retFuture.finished()): - var pipeSuffix = $cast[cstring](unsafeAddr address.address_un[0]) - var pipeName = newWideCString(r"\\.\pipe\" & pipeSuffix[1 .. ^1]) - pipeHandle = createFileW(pipeName, GENERIC_READ or GENERIC_WRITE, - FILE_SHARE_READ or FILE_SHARE_WRITE, - nil, OPEN_EXISTING, - FILE_FLAG_OVERLAPPED, Handle(0)) - if pipeHandle == INVALID_HANDLE_VALUE: + let + pipeSuffix = $cast[cstring](unsafeAddr address.address_un[0]) + pipeAsciiName = PipeHeaderName & pipeSuffix[1 .. ^1] + pipeName = toWideString(pipeAsciiName).valueOr: + retFuture.fail(getTransportOsError(error)) + return + genericFlags = osdefs.GENERIC_READ or osdefs.GENERIC_WRITE + shareFlags = osdefs.FILE_SHARE_READ or osdefs.FILE_SHARE_WRITE + pipeHandle = createFile(pipeName, genericFlags, shareFlags, + nil, osdefs.OPEN_EXISTING, + osdefs.FILE_FLAG_OVERLAPPED, HANDLE(0)) + free(pipeName) + if pipeHandle == osdefs.INVALID_HANDLE_VALUE: let err = osLastError() - if int32(err) == ERROR_PIPE_BUSY: + if int32(err) == osdefs.ERROR_PIPE_BUSY: discard setTimer(Moment.fromNow(50.milliseconds), pipeContinuation, nil) else: retFuture.fail(getTransportOsError(err)) else: - try: - register(AsyncFD(pipeHandle)) - except CatchableError as exc: - retFuture.fail(exc) + let res = register2(AsyncFD(pipeHandle)) + if res.isErr(): + retFuture.fail(getTransportOsError(res.error())) return - let transp = try: newStreamPipeTransport(AsyncFD(pipeHandle), + let transp = newStreamPipeTransport(AsyncFD(pipeHandle), bufferSize, child) - except CatchableError as exc: - retFuture.fail(exc) - return # Start tracking transport trackStream(transp) retFuture.complete(transp) @@ -819,26 +814,33 @@ elif defined(windows): return retFuture - proc createAcceptPipe(server: StreamServer) {. - raises: [Defect, CatchableError].} = - let pipeSuffix = $cast[cstring](addr server.local.address_un) - let pipeName = newWideCString(r"\\.\pipe\" & pipeSuffix[1 .. ^1]) - var openMode = PIPE_ACCESS_DUPLEX or FILE_FLAG_OVERLAPPED - if FirstPipe notin server.flags: - openMode = openMode or FILE_FLAG_FIRST_PIPE_INSTANCE - server.flags.incl(FirstPipe) - let pipeMode = int32(PIPE_TYPE_BYTE or PIPE_READMODE_BYTE or PIPE_WAIT) - let pipeHandle = createNamedPipe(pipeName, openMode, pipeMode, - PIPE_UNLIMITED_INSTANCES, - DWORD(server.bufferSize), - DWORD(server.bufferSize), - DWORD(0), nil) - if pipeHandle != INVALID_HANDLE_VALUE: - server.sock = AsyncFD(pipeHandle) - register(server.sock) - else: - server.sock = asyncInvalidPipe - server.errorCode = osLastError() + proc createAcceptPipe(server: StreamServer): Result[AsyncFD, OSErrorCode] = + let + pipeSuffix = $cast[cstring](addr server.local.address_un) + pipeName = ? toWideString(PipeHeaderName & pipeSuffix) + openMode = + if FirstPipe notin server.flags: + server.flags.incl(FirstPipe) + osdefs.PIPE_ACCESS_DUPLEX or osdefs.FILE_FLAG_OVERLAPPED or + osdefs.FILE_FLAG_FIRST_PIPE_INSTANCE + else: + osdefs.PIPE_ACCESS_DUPLEX or osdefs.FILE_FLAG_OVERLAPPED + pipeMode = osdefs.PIPE_TYPE_BYTE or osdefs.PIPE_READMODE_BYTE or + osdefs.PIPE_WAIT + pipeHandle = createNamedPipe(pipeName, openMode, pipeMode, + osdefs.PIPE_UNLIMITED_INSTANCES, + DWORD(server.bufferSize), + DWORD(server.bufferSize), + DWORD(0), nil) + free(pipeName) + if pipeHandle == osdefs.INVALID_HANDLE_VALUE: + return err(osLastError()) + let res = register2(AsyncFD(pipeHandle)) + if res.isErr(): + discard osdefs.closeHandle(pipeHandle) + return err(res.error()) + + ok(AsyncFD(pipeHandle)) proc acceptPipeLoop(udata: pointer) {.gcsafe, nimcall.} = var ovl = cast[PtrCustomOverlapped](udata) @@ -863,17 +865,18 @@ elif defined(windows): nil, flags) # Start tracking transport trackStream(ntransp) - asyncCheck server.function(server, ntransp) - elif int32(ovl.data.errCode) == ERROR_OPERATION_ABORTED: + asyncSpawn server.function(server, ntransp) + elif int32(ovl.data.errCode) == osdefs.ERROR_OPERATION_ABORTED: # CancelIO() interrupt or close call. if server.status in {ServerStatus.Closed, ServerStatus.Stopped}: server.clean() break else: # We should not raise defects in this loop. - discard disconnectNamedPipe(Handle(server.sock)) - discard closeHandle(Handle(server.sock)) - raiseAssert osErrorMsg(osLastError()) + discard disconnectNamedPipe(HANDLE(server.sock)) + discard closeHandle(HANDLE(server.sock)) + raiseOsDefect(osLastError(), "acceptPipeLoop(): Unable to accept " & + "new pipe connection") else: # Server close happens in callback, and we are not started new # connectNamedPipe session. @@ -884,37 +887,49 @@ elif defined(windows): ## Initiation if server.status notin {ServerStatus.Stopped, ServerStatus.Closed}: server.apending = true - var pipeSuffix = $cast[cstring](addr server.local.address_un) - var pipeName = newWideCString(r"\\.\pipe\" & pipeSuffix[1 .. ^1]) - var openMode = PIPE_ACCESS_DUPLEX or FILE_FLAG_OVERLAPPED - if FirstPipe notin server.flags: - openMode = openMode or FILE_FLAG_FIRST_PIPE_INSTANCE - server.flags.incl(FirstPipe) - let pipeMode = int32(PIPE_TYPE_BYTE or PIPE_READMODE_BYTE or PIPE_WAIT) - let pipeHandle = createNamedPipe(pipeName, openMode, pipeMode, - PIPE_UNLIMITED_INSTANCES, - DWORD(server.bufferSize), - DWORD(server.bufferSize), - DWORD(0), nil) - if pipeHandle == INVALID_HANDLE_VALUE: - raiseAssert osErrorMsg(osLastError()) + let + pipeSuffix = $cast[cstring](addr server.local.address_un) + pipeAsciiName = PipeHeaderName & pipeSuffix + pipeName = toWideString(pipeAsciiName).valueOr: + raiseOsDefect(error, "acceptPipeLoop(): Unable to create name " & + "for new pipe connection") + openMode = + if FirstPipe notin server.flags: + server.flags.incl(FirstPipe) + osdefs.PIPE_ACCESS_DUPLEX or osdefs.FILE_FLAG_OVERLAPPED or + osdefs.FILE_FLAG_FIRST_PIPE_INSTANCE + else: + osdefs.PIPE_ACCESS_DUPLEX or osdefs.FILE_FLAG_OVERLAPPED + pipeMode = osdefs.PIPE_TYPE_BYTE or osdefs.PIPE_READMODE_BYTE or + osdefs.PIPE_WAIT + pipeHandle = createNamedPipe(pipeName, openMode, pipeMode, + osdefs.PIPE_UNLIMITED_INSTANCES, + DWORD(server.bufferSize), + DWORD(server.bufferSize), + DWORD(0), nil) + free(pipeName) + if pipeHandle == osdefs.INVALID_HANDLE_VALUE: + raiseOsDefect(osLastError(), "acceptPipeLoop(): Unable to create " & + "new pipe") server.sock = AsyncFD(pipeHandle) - try: register(server.sock) - except CatchableError as exc: - raiseAsDefect exc, "register" + let wres = register2(server.sock) + if wres.isErr(): + raiseOsDefect(wres.error(), "acceptPipeLoop(): Unable to " & + "register new pipe in dispatcher") let res = connectNamedPipe(pipeHandle, cast[POVERLAPPED](addr server.aovl)) if res == 0: - let err = osLastError() - if int32(err) == ERROR_OPERATION_ABORTED: + let errCode = osLastError() + if errCode == osdefs.ERROR_OPERATION_ABORTED: server.apending = false break - elif int32(err) == ERROR_IO_PENDING: + elif errCode == osdefs.ERROR_IO_PENDING: discard - elif int32(err) == ERROR_PIPE_CONNECTED: + elif errCode == osdefs.ERROR_PIPE_CONNECTED: discard else: - raiseAssert osErrorMsg(err) + raiseOsDefect(errCode, "acceptPipeLoop(): Unable to establish " & + "pipe connection") break else: # Server close happens in callback, and we are not started new @@ -934,12 +949,14 @@ elif defined(windows): server.apending = false if server.status notin {ServerStatus.Stopped, ServerStatus.Closed}: if ovl.data.errCode == OSErrorCode(-1): - if setsockopt(SocketHandle(server.asock), cint(SOL_SOCKET), - cint(SO_UPDATE_ACCEPT_CONTEXT), addr server.sock, + if setsockopt(SocketHandle(server.asock), cint(osdefs.SOL_SOCKET), + cint(osdefs.SO_UPDATE_ACCEPT_CONTEXT), + addr server.sock, SockLen(sizeof(SocketHandle))) != 0'i32: - let err = OSErrorCode(wsaGetLastError()) + let errCode = OSErrorCode(wsaGetLastError()) server.asock.closeSocket() - raiseAssert osErrorMsg(err) + raiseOsDefect(errCode, "acceptLoop(): Unable to set accept " & + "context for socket") else: var ntransp: StreamTransport if not(isNil(server.init)): @@ -952,9 +969,9 @@ elif defined(windows): server.bufferSize, nil) # Start tracking transport trackStream(ntransp) - asyncCheck server.function(server, ntransp) + asyncSpawn server.function(server, ntransp) - elif int32(ovl.data.errCode) == ERROR_OPERATION_ABORTED: + elif int32(ovl.data.errCode) == osdefs.ERROR_OPERATION_ABORTED: # CancelIO() interrupt or close. server.asock.closeSocket() if server.status in {ServerStatus.Closed, ServerStatus.Stopped}: @@ -964,7 +981,8 @@ elif defined(windows): break else: server.asock.closeSocket() - raiseAssert $(ovl.data.errCode) + raiseOsDefect(ovl.data.errCode, "acceptLoop(): Unable to accept " & + "new connection") else: # Server close happens in callback, and we are not started new # AcceptEx session. @@ -977,13 +995,13 @@ elif defined(windows): server.apending = true # TODO No way to report back errors! server.asock = - try: - createAsyncSocket(server.domain, SockType.SOCK_STREAM, - Protocol.IPPROTO_TCP) - except CatchableError as exc: - raiseAsDefect exc, "createAsyncSocket" - if server.asock == asyncInvalidSocket: - raiseAssert osErrorMsg(OSErrorCode(wsaGetLastError())) + block: + let sock = createAsyncSocket(server.domain, SockType.SOCK_STREAM, + Protocol.IPPROTO_TCP) + if sock == asyncInvalidSocket: + raiseOsDefect(osLastError(), + "acceptLoop(): Unablet to create new socket") + sock var dwBytesReceived = DWORD(0) let dwReceiveDataLength = DWORD(0) @@ -996,15 +1014,16 @@ elif defined(windows): dwReceiveDataLength, dwLocalAddressLength, dwRemoteAddressLength, addr dwBytesReceived, cast[POVERLAPPED](addr server.aovl)) - if not(res): - let err = osLastError() - if int32(err) == ERROR_OPERATION_ABORTED: + if res == osdefs.FALSE: + let errCode = osLastError() + if errCode == osdefs.ERROR_OPERATION_ABORTED: server.apending = false break - elif int32(err) == ERROR_IO_PENDING: + elif errCode == osdefs.ERROR_IO_PENDING: discard else: - raiseAssert osErrorMsg(err) + raiseOsDefect(errCode, "acceptLoop(): Unable to accept " & + "connection") break else: # Server close happens in callback, and we are not started new @@ -1013,23 +1032,27 @@ elif defined(windows): server.clean() break - proc resumeRead(transp: StreamTransport) {.inline.} = + proc resumeRead(transp: StreamTransport): Result[void, OSErrorCode] = if ReadPaused in transp.state: transp.state.excl(ReadPaused) readStreamLoop(cast[pointer](addr transp.rovl)) + ok() - proc resumeWrite(transp: StreamTransport) {.inline.} = + proc resumeWrite(transp: StreamTransport): Result[void, OSErrorCode] = if WritePaused in transp.state: transp.state.excl(WritePaused) writeStreamLoop(cast[pointer](addr transp.wovl)) + ok() - proc pauseAccept(server: StreamServer) {.inline.} = + proc pauseAccept(server: StreamServer): Result[void, OSErrorCode] = if server.apending: - discard cancelIo(Handle(server.sock)) + discard cancelIo(HANDLE(server.sock)) + ok() - proc resumeAccept(server: StreamServer) {.inline.} = + proc resumeAccept(server: StreamServer): Result[void, OSErrorCode] = if not(server.apending): server.aovl.data.cb(addr server.aovl) + ok() proc accept*(server: StreamServer): Future[StreamTransport] = var retFuture = newFuture[StreamTransport]("stream.server.accept") @@ -1054,12 +1077,13 @@ elif defined(windows): else: case ovl.data.errCode of OSErrorCode(-1): - if setsockopt(SocketHandle(server.asock), cint(SOL_SOCKET), - cint(SO_UPDATE_ACCEPT_CONTEXT), addr server.sock, + if setsockopt(SocketHandle(server.asock), cint(osdefs.SOL_SOCKET), + cint(osdefs.SO_UPDATE_ACCEPT_CONTEXT), + addr server.sock, SockLen(sizeof(SocketHandle))) != 0'i32: - let err = OSErrorCode(wsaGetLastError()) + let err = osLastError() server.asock.closeSocket() - if int32(err) == WSAENOTSOCK: + if err == osdefs.WSAENOTSOCK: # This can be happened when server get closed, but continuation # was already scheduled, so we failing it not with OS error. retFuture.fail(getServerUseClosedError()) @@ -1079,15 +1103,16 @@ elif defined(windows): # Start tracking transport trackStream(ntransp) retFuture.complete(ntransp) - of OSErrorCode(ERROR_OPERATION_ABORTED): + of OSErrorCode(osdefs.ERROR_OPERATION_ABORTED): # CancelIO() interrupt or close. server.asock.closeSocket() retFuture.fail(getServerUseClosedError()) server.clean() - of OSErrorCode(common.WSAENETDOWN), OSErrorCode(common.WSAENETRESET), - OSErrorCode(common.WSAECONNABORTED), - OSErrorCode(common.WSAECONNRESET), - OSErrorCode(common.WSAETIMEDOUT): + of OSErrorCode(osdefs.WSAENETDOWN), + OSErrorCode(osdefs.WSAENETRESET), + OSErrorCode(osdefs.WSAECONNABORTED), + OSErrorCode(osdefs.WSAECONNRESET), + OSErrorCode(osdefs.WSAETIMEDOUT): server.asock.closeSocket() retFuture.fail(getConnectionAbortedError(int(ovl.data.errCode))) server.clean() @@ -1123,31 +1148,27 @@ elif defined(windows): else: ntransp = newStreamPipeTransport(server.sock, server.bufferSize, nil, flags) - # Start tracking transport - try: - server.createAcceptPipe() - except CatchableError as exc: - closeHandle(server.sock) - retFuture.fail(exc) + server.sock = server.createAcceptPipe().valueOr: + server.sock = asyncInvalidSocket + server.errorCode = error + retFuture.fail(getTransportOsError(error)) return + trackStream(ntransp) retFuture.complete(ntransp) - elif int32(ovl.data.errCode) in {ERROR_OPERATION_ABORTED, - ERROR_PIPE_NOT_CONNECTED}: + elif int32(ovl.data.errCode) in {osdefs.ERROR_OPERATION_ABORTED, + osdefs.ERROR_PIPE_NOT_CONNECTED}: # CancelIO() interrupt or close call. retFuture.fail(getServerUseClosedError()) server.clean() else: - let sock = server.sock - try: - server.createAcceptPipe() - except CatchableError as exc: - closeHandle(sock) - retFuture.fail(exc) + discard closeHandle(HANDLE(server.sock)) + server.sock = server.createAcceptPipe().valueOr: + server.sock = asyncInvalidSocket + server.errorCode = error + retFuture.fail(getTransportOsError(error)) return - closeHandle(sock) - retFuture.fail(getTransportOsError(ovl.data.errCode)) proc cancellationPipe(udata: pointer) {.gcsafe.} = @@ -1158,18 +1179,13 @@ elif defined(windows): if server.local.family in {AddressFamily.IPv4, AddressFamily.IPv6}: # TCP Sockets part var loop = getThreadDispatcher() - server.asock = - try: - createAsyncSocket(server.domain, SockType.SOCK_STREAM, - Protocol.IPPROTO_TCP) - except CatchableError as exc: - retFuture.fail(exc) - return retFuture - + server.asock = createAsyncSocket(server.domain, SockType.SOCK_STREAM, + Protocol.IPPROTO_TCP) if server.asock == asyncInvalidSocket: let err = osLastError() case int(err) - of ERROR_TOO_MANY_OPEN_FILES, WSAENOBUFS, WSAEMFILE: + of osdefs.ERROR_TOO_MANY_OPEN_FILES, + osdefs.WSAENOBUFS, osdefs.WSAEMFILE: retFuture.fail(getTransportTooManyError(int(err))) else: retFuture.fail(getTransportOsError(err)) @@ -1189,17 +1205,17 @@ elif defined(windows): dwReceiveDataLength, dwLocalAddressLength, dwRemoteAddressLength, addr dwBytesReceived, cast[POVERLAPPED](addr server.aovl)) - if not(res): + if res == osdefs.FALSE: let err = osLastError() case int(err) - of ERROR_OPERATION_ABORTED: + of osdefs.ERROR_OPERATION_ABORTED: server.apending = false retFuture.fail(getServerUseClosedError()) return retFuture - of ERROR_IO_PENDING: + of osdefs.ERROR_IO_PENDING: discard - of common.WSAECONNRESET, common.WSAECONNABORTED, common.WSAENETDOWN, - common.WSAENETRESET, common.WSAETIMEDOUT: + of osdefs.WSAECONNRESET, osdefs.WSAECONNABORTED, osdefs.WSAENETDOWN, + osdefs.WSAENETRESET, osdefs.WSAETIMEDOUT: server.apending = false retFuture.fail(getConnectionAbortedError(int(err))) return retFuture @@ -1215,7 +1231,7 @@ elif defined(windows): server.apending = true if server.sock == asyncInvalidPipe: let err = server.errorCode - if int32(err) == ERROR_TOO_MANY_OPEN_FILES: + if int32(err) == osdefs.ERROR_TOO_MANY_OPEN_FILES: retFuture.fail(getTransportTooManyError()) else: retFuture.fail(getTransportOsError(err)) @@ -1224,15 +1240,16 @@ elif defined(windows): server.aovl.data = CompletionData(cb: continuationPipe, udata: cast[pointer](server)) server.apending = true - let res = connectNamedPipe(Handle(server.sock), + let res = connectNamedPipe(HANDLE(server.sock), cast[POVERLAPPED](addr server.aovl)) if res == 0: let err = osLastError() - if int32(err) == ERROR_OPERATION_ABORTED: + if int32(err) == osdefs.ERROR_OPERATION_ABORTED: server.apending = false retFuture.fail(getServerUseClosedError()) return retFuture - elif int32(err) in {ERROR_IO_PENDING, ERROR_PIPE_CONNECTED}: + elif int32(err) in {osdefs.ERROR_IO_PENDING, + osdefs.ERROR_PIPE_CONNECTED}: discard else: server.apending = false @@ -1247,18 +1264,7 @@ else: import ../sendfile proc isConnResetError(err: OSErrorCode): bool {.inline.} = - result = (err == OSErrorCode(ECONNRESET)) or - (err == OSErrorCode(EPIPE)) - - proc removeWriter(transp: StreamTransport) = - try: - transp.fd.removeWriter() - # For debugging, record that we're no longer getting write notifications - transp.state.incl WritePaused - except IOSelectorsException as exc: - raiseAsDefect exc, "removeWriter" - except ValueError as exc: - raiseAsDefect exc, "removeWriter" + (err == OSErrorCode(ECONNRESET)) or (err == OSErrorCode(EPIPE)) proc writeStreamLoop(udata: pointer) = if isNil(udata): @@ -1274,9 +1280,8 @@ else: if WriteClosed in transp.state: if transp.queue.len > 0: - transp.removeWriter() - let error = getTransportUseClosedError() + discard removeWriter2(transp.fd) failPendingWriteQueue(transp.queue, error) return @@ -1321,9 +1326,9 @@ else: let res = case transp.kind of TransportKind.Socket: - posix.send(fd, vector.buf, vector.buflen, MSG_NOSIGNAL) + osdefs.send(fd, vector.buf, vector.buflen, MSG_NOSIGNAL) of TransportKind.Pipe: - posix.write(cint(fd), vector.buf, vector.buflen) + osdefs.write(cint(fd), vector.buf, vector.buflen) else: raiseAssert "Unsupported transport kind: " & $transp.kind if res >= 0: @@ -1357,7 +1362,10 @@ else: handleError() # Nothing left in the queue - no need for further write notifications - transp.removeWriter() + # All writers are already scheduled, so its impossible to notify about an + # error. + transp.state.incl(WritePaused) + discard removeWriter2(transp.fd) proc readStreamLoop(udata: pointer) = if isNil(udata): @@ -1377,84 +1385,61 @@ else: else: if transp.kind == TransportKind.Socket: while true: - var res = posix.recv(fd, addr transp.buffer[transp.offset], - len(transp.buffer) - transp.offset, cint(0)) + let res = handleEintr( + osdefs.recv(fd, addr transp.buffer[transp.offset], + len(transp.buffer) - transp.offset, cint(0))) if res < 0: let err = osLastError() - if int(err) == EINTR: - continue - elif int(err) == ECONNRESET: + if int(err) == ECONNRESET: transp.state.incl({ReadEof, ReadPaused}) - try: - transp.fd.removeReader() - except IOSelectorsException as exc: - raiseAsDefect exc, "removeReader" - except ValueError as exc: - raiseAsDefect exc, "removeReader" + let rres = removeReader2(transp.fd) + if rres.isErr(): + transp.state.incl(ReadError) + transp.setReadError(rres.error()) else: - transp.state.incl(ReadPaused) + transp.state.incl({ReadPaused, ReadError}) transp.setReadError(err) - try: - transp.fd.removeReader() - except IOSelectorsException as exc: - raiseAsDefect exc, "removeReader" - except ValueError as exc: - raiseAsDefect exc, "removeReader" + discard removeReader2(transp.fd) elif res == 0: transp.state.incl({ReadEof, ReadPaused}) - try: - transp.fd.removeReader() - except IOSelectorsException as exc: - raiseAsDefect exc, "removeReader" - except ValueError as exc: - raiseAsDefect exc, "removeReader" + let rres = removeReader2(transp.fd) + if rres.isErr(): + transp.state.incl(ReadError) + transp.setReadError(rres.error()) else: transp.offset += res if transp.offset == len(transp.buffer): transp.state.incl(ReadPaused) - try: - transp.fd.removeReader() - except IOSelectorsException as exc: - raiseAsDefect exc, "removeReader" - except ValueError as exc: - raiseAsDefect exc, "removeReader" + let rres = removeReader2(transp.fd) + if rres.isErr(): + transp.state.incl(ReadError) + transp.setReadError(rres.error()) transp.completeReader() break elif transp.kind == TransportKind.Pipe: while true: - var res = posix.read(cint(fd), addr transp.buffer[transp.offset], - len(transp.buffer) - transp.offset) + let res = handleEintr( + osdefs.read(cint(fd), addr transp.buffer[transp.offset], + len(transp.buffer) - transp.offset)) if res < 0: let err = osLastError() - if int(err) == EINTR: - continue - else: - transp.state.incl(ReadPaused) - transp.setReadError(err) - try: - transp.fd.removeReader() - except IOSelectorsException as exc: - raiseAsDefect exc, "removeReader" - except ValueError as exc: - raiseAsDefect exc, "removeReader" + transp.state.incl(ReadPaused) + transp.setReadError(err) + discard removeReader2(transp.fd) elif res == 0: transp.state.incl({ReadEof, ReadPaused}) - try: - transp.fd.removeReader() - except IOSelectorsException as exc: - raiseAsDefect exc, "removeReader" - except ValueError as exc: - raiseAsDefect exc, "removeReader" + let rres = removeReader2(transp.fd) + if rres.isErr(): + transp.state.incl(ReadError) + transp.setReadError(rres.error()) else: transp.offset += res if transp.offset == len(transp.buffer): transp.state.incl(ReadPaused) - try: - transp.fd.removeReader() - except IOSelectorsException as exc: - raiseAsDefect exc, "removeReader" - except ValueError as exc: - raiseAsDefect exc, "removeReader" + let rres = removeReader2(transp.fd) + if rres.isErr(): + transp.state.incl(ReadError) + transp.setReadError(rres.error()) transp.completeReader() break @@ -1472,7 +1457,7 @@ else: transp.queue = initDeque[StreamVector]() transp.future = newFuture[void]("socket.stream.transport") GC_ref(transp) - result = transp + transp proc newStreamPipeTransport(fd: AsyncFD, bufsize: int, child: StreamTransport): StreamTransport = @@ -1488,7 +1473,7 @@ else: transp.queue = initDeque[StreamVector]() transp.future = newFuture[void]("pipe.stream.transport") GC_ref(transp) - result = transp + transp proc connect*(address: TransportAddress, bufferSize = DefaultStreamBufferSize, @@ -1509,12 +1494,8 @@ else: # `Protocol` enum will not support IPPROTO_IP == 0. proto = cast[Protocol](0) - let sock = try: createAsyncSocket(address.getDomain(), SockType.SOCK_STREAM, - proto) - except CatchableError as exc: - retFuture.fail(exc) - return retFuture - + let sock = createAsyncSocket(address.getDomain(), SockType.SOCK_STREAM, + proto) if sock == asyncInvalidSocket: let err = osLastError() if int(err) == EMFILE: @@ -1523,11 +1504,9 @@ else: retFuture.fail(getTransportOsError(err)) return retFuture - if address.family in {AddressFamily.IPv4, AddressFamily.IPv6}: if TransportFlags.TcpNoDelay in flags: - if not(setSockOpt(sock, handles.IPPROTO_TCP, - handles.TCP_NODELAY, 1)): + if not(setSockOpt(sock, osdefs.IPPROTO_TCP, osdefs.TCP_NODELAY, 1)): let err = osLastError() sock.closeSocket() retFuture.fail(getTransportOsError(err)) @@ -1536,34 +1515,35 @@ else: proc continuation(udata: pointer) = if not(retFuture.finished()): var err = 0 - try: - sock.removeWriter() - except IOSelectorsException as exc: - retFuture.fail(exc) - return - except ValueError as exc: - retFuture.fail(exc) + + let res = removeWriter2(sock) + if res.isErr(): + discard unregisterAndCloseFd(sock) + retFuture.fail(getTransportOsError(res.error())) return if not(sock.getSocketError(err)): - closeSocket(sock) - retFuture.fail(getTransportOsError(osLastError())) + discard unregisterAndCloseFd(sock) + retFuture.fail(getTransportOsError(res.error())) return + if err != 0: - closeSocket(sock) + discard unregisterAndCloseFd(sock) retFuture.fail(getTransportOsError(OSErrorCode(err))) return + let transp = newStreamSocketTransport(sock, bufferSize, child) # Start tracking transport trackStream(transp) retFuture.complete(transp) proc cancel(udata: pointer) = - closeSocket(sock) + if not(retFuture.finished()): + closeSocket(sock) while true: - var res = posix.connect(SocketHandle(sock), - cast[ptr SockAddr](addr saddr), slen) + let res = osdefs.connect(SocketHandle(sock), + cast[ptr SockAddr](addr saddr), slen) if res == 0: let transp = newStreamSocketTransport(sock, bufferSize, child) # Start tracking transport @@ -1571,27 +1551,24 @@ else: retFuture.complete(transp) break else: - let err = osLastError() + let errorCode = osLastError() # If connect() is interrupted by a signal that is caught while blocked # waiting to establish a connection, connect() shall fail and set # connect() to [EINTR], but the connection request shall not be aborted, # and the connection shall be established asynchronously. # # http://www.madore.org/~david/computers/connect-intr.html - if int(err) == EINPROGRESS or int(err) == EINTR: - try: - sock.addWriter(continuation) - except CatchableError as exc: - closeSocket(sock) - retFuture.fail(exc) + if (errorCode == EINPROGRESS) or (errorCode == EINTR): + let res = addWriter2(sock, continuation) + if res.isErr(): + discard unregisterAndCloseFd(sock) + retFuture.fail(getTransportOsError(res.error())) return retFuture - retFuture.cancelCallback = cancel break else: - sock.closeSocket() - - retFuture.fail(getTransportOsError(err)) + discard unregisterAndCloseFd(sock) + retFuture.fail(getTransportOsError(errorCode)) break return retFuture @@ -1607,74 +1584,59 @@ else: saddr: Sockaddr_storage slen: SockLen let server = cast[StreamServer](udata) - while true: - if server.status in {ServerStatus.Stopped, ServerStatus.Closed}: - break + if server.status in {ServerStatus.Stopped, ServerStatus.Closed}: + return - let res = posix.accept(SocketHandle(server.sock), - cast[ptr SockAddr](addr saddr), addr slen) - if int(res) > 0: - let sock = try: wrapAsyncSocket(res) - except CatchableError as exc: - raiseAsDefect exc, "wrapAsyncSocket" - if sock != asyncInvalidSocket: - var ntransp: StreamTransport + let + flags = {DescriptorFlag.CloseOnExec, DescriptorFlag.NonBlock} + sres = acceptConn(cint(server.sock), cast[ptr SockAddr](addr saddr), + addr slen, flags) + if sres.isOk(): + let sock = AsyncFD(sres.get()) + let rres = register2(sock) + if rres.isOk(): + let ntransp = if not(isNil(server.init)): let transp = server.init(server, sock) - ntransp = newStreamSocketTransport(sock, server.bufferSize, transp) + newStreamSocketTransport(sock, server.bufferSize, transp) else: - ntransp = newStreamSocketTransport(sock, server.bufferSize, nil) - # Start tracking transport - trackStream(ntransp) - asyncCheck server.function(server, ntransp) - break + newStreamSocketTransport(sock, server.bufferSize, nil) + trackStream(ntransp) + asyncSpawn server.function(server, ntransp) else: - let err = osLastError() - if int(err) == EINTR: - continue - elif int(err) == EAGAIN: - # This error appears only when server get closed, while acceptLoop() - # reader callback is already scheduled. - break - else: - ## Critical unrecoverable error - raiseAssert $err + # Client was accepted, so we not going to raise assertion, but + # we need to close the socket. + discard closeFd(cint(sock)) + else: + let errorCode = sres.error() + if errorCode != EAGAIN: + # This EAGAIN error appears only when server get closed, while + # acceptLoop() reader callback is already scheduled. + raiseOsDefect(errorCode, "acceptLoop(): Unable to accept connection") - proc resumeAccept(server: StreamServer) {. - raises: [Defect, IOSelectorsException, ValueError].} = - addReader(server.sock, acceptLoop, cast[pointer](server)) + proc resumeAccept(server: StreamServer): Result[void, OSErrorCode] = + addReader2(server.sock, acceptLoop, cast[pointer](server)) - proc pauseAccept(server: StreamServer) {. - raises: [Defect, IOSelectorsException, ValueError].} = - removeReader(server.sock) + proc pauseAccept(server: StreamServer): Result[void, OSErrorCode] = + removeReader2(server.sock) - proc resumeRead(transp: StreamTransport) {.inline.} = + proc resumeRead(transp: StreamTransport): Result[void, OSErrorCode] = if ReadPaused in transp.state: + ? addReader2(transp.fd, readStreamLoop, cast[pointer](transp)) transp.state.excl(ReadPaused) - # TODO reset flag on exception?? - try: - addReader(transp.fd, readStreamLoop, cast[pointer](transp)) - except IOSelectorsException as exc: - raiseAsDefect exc, "addReader" - except ValueError as exc: - raiseAsDefect exc, "addReader" + ok() - proc resumeWrite(transp: StreamTransport) {.inline.} = + proc resumeWrite(transp: StreamTransport): Result[void, OSErrorCode] = if transp.queue.len() == 1: # writeStreamLoop keeps writing until queue is empty - we should not call # resumeWrite under any other condition than when the items are # added to a queue - if the flag is not set here, it means that the socket # was not removed from write notifications at the right time, and this # would mean an imbalance in registration and deregistration - doAssert WritePaused in transp.state - try: - addWriter(transp.fd, writeStreamLoop, cast[pointer](transp)) - - transp.state.excl WritePaused - except IOSelectorsException as exc: - raiseAsDefect exc, "addWriter" - except ValueError as exc: - raiseAsDefect exc, "addWriter" + doAssert(WritePaused in transp.state) + ? addWriter2(transp.fd, writeStreamLoop, cast[pointer](transp)) + transp.state.excl(WritePaused) + ok() proc accept*(server: StreamServer): Future[StreamTransport] = var retFuture = newFuture[StreamTransport]("stream.server.accept") @@ -1694,92 +1656,90 @@ else: if server.status in {ServerStatus.Stopped, ServerStatus.Closed}: retFuture.fail(getServerUseClosedError()) else: - while true: - let res = posix.accept(SocketHandle(server.sock), - cast[ptr SockAddr](addr saddr), addr slen) - if int(res) > 0: - let sock = - try: - wrapAsyncSocket(res) - except CatchableError as exc: - close(res) - retFuture.fail(getConnectionAbortedError($exc.msg)) - return - - if sock != asyncInvalidSocket: - var ntransp: StreamTransport - if not(isNil(server.init)): - let transp = server.init(server, sock) - ntransp = newStreamSocketTransport(sock, server.bufferSize, - transp) - else: - ntransp = newStreamSocketTransport(sock, server.bufferSize, - nil) + let + flags = {DescriptorFlag.CloseOnExec, DescriptorFlag.NonBlock} + sres = acceptConn(cint(server.sock), cast[ptr SockAddr](addr saddr), + addr slen, flags) + if sres.isErr(): + let errorCode = sres.error() + if errorCode == EAGAIN: + # This error appears only when server get closed, while accept() + # continuation is already scheduled. + retFuture.fail(getServerUseClosedError()) + elif cint(errorCode) in {EMFILE, ENFILE, ENOBUFS, ENOMEM}: + retFuture.fail(getTransportTooManyError(cint(errorCode))) + elif cint(errorCode) in {ECONNABORTED, EPERM, ETIMEDOUT}: + retFuture.fail(getConnectionAbortedError(cint(errorCode))) + else: + retFuture.fail(getTransportOsError(errorCode)) + # Error is already happened so we ignore removeReader2() errors. + discard removeReader2(server.sock) + else: + let + sock = AsyncFD(sres.get()) + rres = register2(sock) + if rres.isOk(): + let res = removeReader2(server.sock) + if res.isOk(): + let ntransp = + if not(isNil(server.init)): + let transp = server.init(server, sock) + newStreamSocketTransport(sock, server.bufferSize, transp) + else: + newStreamSocketTransport(sock, server.bufferSize, nil) # Start tracking transport trackStream(ntransp) retFuture.complete(ntransp) else: - let errorMsg = osErrorMsg(osLastError()) + discard closeFd(cint(sock)) + let errorMsg = osErrorMsg(res.error()) retFuture.fail(getConnectionAbortedError(errorMsg)) else: - let err = osLastError().int32 - if err == EINTR: - continue - elif err == EAGAIN: - # This error appears only when server get closed, while accept() - # continuation is already scheduled. - retFuture.fail(getServerUseClosedError()) - elif err in [EMFILE, ENFILE, ENOBUFS, ENOMEM]: - retFuture.fail(getTransportTooManyError(err)) - elif err in [ECONNABORTED, EPERM, ETIMEDOUT]: - retFuture.fail(getConnectionAbortedError(err)) - else: - retFuture.fail(getTransportOsError(OSErrorCode(err))) - break - - try: - removeReader(server.sock) - except IOSelectorsException as exc: - raiseAsDefect exc, "removeReader" - except ValueError as exc: - raiseAsDefect exc, "removeReader" + # Error is already happened so we ignore errors. + discard removeReader2(server.sock) + discard closeFd(cint(sock)) + let errorMsg = osErrorMsg(rres.error()) + retFuture.fail(getConnectionAbortedError(errorMsg)) proc cancellation(udata: pointer) = - try: - removeReader(server.sock) - except IOSelectorsException as exc: - raiseAsDefect exc, "removeReader" - except ValueError as exc: - raiseAsDefect exc, "removeReader" + if not(retFuture.finished()): + discard removeReader2(server.sock) - try: - addReader(server.sock, continuation, nil) - except IOSelectorsException as exc: - raiseAsDefect exc, "addReader" - except ValueError as exc: - raiseAsDefect exc, "addReader" - - retFuture.cancelCallback = cancellation + let res = addReader2(server.sock, continuation, nil) + if res.isErr(): + retFuture.fail(getTransportOsError(res.error())) + else: + retFuture.cancelCallback = cancellation return retFuture -proc start*(server: StreamServer) {. - raises: [Defect, IOSelectorsException, ValueError].} = +proc start2*(server: StreamServer): Result[void, OSErrorCode] = ## Starts ``server``. - doAssert(not(isNil(server.function)), - "You should not start server, if you have not set processing callback!") + doAssert(not(isNil(server.function)), "You should not start the server " & + "unless you have processing callback configured!") if server.status == ServerStatus.Starting: - server.resumeAccept() + ? server.resumeAccept() server.status = ServerStatus.Running + ok() -proc stop*(server: StreamServer) {. - raises: [Defect, IOSelectorsException, ValueError].} = +proc stop2*(server: StreamServer): Result[void, OSErrorCode] = ## Stops ``server``. if server.status == ServerStatus.Running: if not(isNil(server.function)): - server.pauseAccept() + ? server.pauseAccept() server.status = ServerStatus.Stopped elif server.status == ServerStatus.Starting: server.status = ServerStatus.Stopped + ok() + +proc start*(server: StreamServer) {.raises: [Defect, TransportOsError].} = + ## Starts ``server``. + let res = start2(server) + if res.isErr(): raiseTransportOsError(res.error()) + +proc stop*(server: StreamServer) {.raises: [Defect, TransportOsError].} = + ## Stops ``server``. + let res = stop2(server) + if res.isErr(): raiseTransportOsError(res.error()) proc join*(server: StreamServer): Future[void] = ## Waits until ``server`` is not closed. @@ -1818,8 +1778,8 @@ proc close*(server: StreamServer) = server.sock.closeSocket(continuation) elif server.local.family in {AddressFamily.Unix}: if NoPipeFlash notin server.flags: - discard flushFileBuffers(Handle(server.sock)) - discard disconnectNamedPipe(Handle(server.sock)) + discard flushFileBuffers(HANDLE(server.sock)) + discard disconnectNamedPipe(HANDLE(server.sock)) server.sock.closeHandle(continuation) else: server.sock.closeSocket(continuation) @@ -1838,7 +1798,7 @@ proc createStreamServer*(host: TransportAddress, child: StreamServer = nil, init: TransportInitCallback = nil, udata: pointer = nil): StreamServer {. - raises: [Defect, CatchableError].} = + raises: [Defect, TransportOsError].} = ## Create new TCP stream server. ## ## ``host`` - address to which server will be bound. @@ -1871,31 +1831,35 @@ proc createStreamServer*(host: TransportAddress, if serverSocket == asyncInvalidSocket: raiseTransportOsError(osLastError()) else: - if not(setSocketBlocking(SocketHandle(sock), false)): - raiseTransportOsError(osLastError()) - register(sock) + let bres = setDescriptorBlocking(SocketHandle(sock), false) + if bres.isErr(): + raiseTransportOsError(bres.error()) + let wres = register2(sock) + if wres.isErr(): + raiseTransportOsError(wres.error()) serverSocket = sock # SO_REUSEADDR is not useful for Unix domain sockets. if ServerFlags.ReuseAddr in flags: - if not(setSockOpt(serverSocket, SOL_SOCKET, SO_REUSEADDR, 1)): + if not(setSockOpt(serverSocket, osdefs.SOL_SOCKET, + osdefs.SO_REUSEADDR, 1)): let err = osLastError() if sock == asyncInvalidSocket: - serverSocket.closeSocket() + discard closeFd(SocketHandle(serverSocket)) raiseTransportOsError(err) # TCP flags are not useful for Unix domain sockets. if ServerFlags.TcpNoDelay in flags: - if not(setSockOpt(serverSocket, handles.IPPROTO_TCP, - handles.TCP_NODELAY, 1)): + if not(setSockOpt(serverSocket, osdefs.IPPROTO_TCP, + osdefs.TCP_NODELAY, 1)): let err = osLastError() if sock == asyncInvalidSocket: - serverSocket.closeSocket() + discard closeFd(SocketHandle(serverSocket)) raiseTransportOsError(err) host.toSAddr(saddr, slen) - if bindAddr(SocketHandle(serverSocket), cast[ptr SockAddr](addr saddr), - slen) != 0: + if osdefs.bindSocket(SocketHandle(serverSocket), + cast[ptr SockAddr](addr saddr), slen) != 0: let err = osLastError() if sock == asyncInvalidSocket: - serverSocket.closeSocket() + discard closeFd(SocketHandle(serverSocket)) raiseTransportOsError(err) slen = SockLen(sizeof(saddr)) @@ -1903,15 +1867,15 @@ proc createStreamServer*(host: TransportAddress, addr slen) != 0: let err = osLastError() if sock == asyncInvalidSocket: - serverSocket.closeSocket() + discard closeFd(SocketHandle(serverSocket)) raiseTransportOsError(err) fromSAddr(addr saddr, slen, localAddress) - if nativesockets.listen(SocketHandle(serverSocket), cint(backlog)) != 0: + if listen(SocketHandle(serverSocket), cint(backlog)) != 0: let err = osLastError() if sock == asyncInvalidSocket: - serverSocket.closeSocket() - raiseTransportOsError(err) + discard closeFd(SocketHandle(serverSocket)) + raiseTransportOsError(err) elif host.family == AddressFamily.Unix: serverSocket = AsyncFD(0) else: @@ -1928,10 +1892,12 @@ proc createStreamServer*(host: TransportAddress, if serverSocket == asyncInvalidSocket: raiseTransportOsError(osLastError()) else: - if not(setSocketBlocking(SocketHandle(sock), false)): + let bres = setDescriptorFlags(cint(sock), true, true) + if bres.isErr(): + raiseTransportOsError(osLastError()) + let rres = register2(sock) + if rres.isErr(): raiseTransportOsError(osLastError()) - register(sock) - serverSocket = sock if host.family in {AddressFamily.IPv4, AddressFamily.IPv6}: @@ -1940,33 +1906,33 @@ proc createStreamServer*(host: TransportAddress, if not(setSockOpt(serverSocket, SOL_SOCKET, SO_REUSEADDR, 1)): let err = osLastError() if sock == asyncInvalidSocket: - serverSocket.closeSocket() + discard unregisterAndCloseFd(serverSocket) raiseTransportOsError(err) if ServerFlags.ReusePort in flags: if not(setSockOpt(serverSocket, SOL_SOCKET, SO_REUSEPORT, 1)): let err = osLastError() if sock == asyncInvalidSocket: - serverSocket.closeSocket() + discard unregisterAndCloseFd(serverSocket) raiseTransportOsError(err) # TCP flags are not useful for Unix domain sockets. if ServerFlags.TcpNoDelay in flags: - if not(setSockOpt(serverSocket, handles.IPPROTO_TCP, - handles.TCP_NODELAY, 1)): + if not(setSockOpt(serverSocket, osdefs.IPPROTO_TCP, + osdefs.TCP_NODELAY, 1)): let err = osLastError() if sock == asyncInvalidSocket: - serverSocket.closeSocket() + discard unregisterAndCloseFd(serverSocket) raiseTransportOsError(err) elif host.family in {AddressFamily.Unix}: # We do not care about result here, because if file cannot be removed, - # `bindAddr` will return EADDRINUSE. - discard posix.unlink(cast[cstring](unsafeAddr host.address_un[0])) + # `bindSocket` will return EADDRINUSE. + discard osdefs.unlink(cast[cstring](unsafeAddr host.address_un[0])) host.toSAddr(saddr, slen) - if bindAddr(SocketHandle(serverSocket), cast[ptr SockAddr](addr saddr), - slen) != 0: + if osdefs.bindSocket(SocketHandle(serverSocket), + cast[ptr SockAddr](addr saddr), slen) != 0: let err = osLastError() if sock == asyncInvalidSocket: - serverSocket.closeSocket() + discard unregisterAndCloseFd(serverSocket) raiseTransportOsError(err) # Obtain real address @@ -1979,29 +1945,26 @@ proc createStreamServer*(host: TransportAddress, raiseTransportOsError(err) fromSAddr(addr saddr, slen, localAddress) - if nativesockets.listen(SocketHandle(serverSocket), cint(backlog)) != 0: + if listen(SocketHandle(serverSocket), cint(backlog)) != 0: let err = osLastError() if sock == asyncInvalidSocket: - serverSocket.closeSocket() + discard unregisterAndCloseFd(serverSocket) raiseTransportOsError(err) - if not(isNil(child)): - result = child - else: - result = StreamServer() + var sres = if not(isNil(child)): child else: StreamServer() - result.sock = serverSocket - result.flags = flags - result.function = cbproc - result.init = init - result.bufferSize = bufferSize - result.status = Starting - result.loopFuture = newFuture[void]("stream.transport.server") - result.udata = udata + sres.sock = serverSocket + sres.flags = flags + sres.function = cbproc + sres.init = init + sres.bufferSize = bufferSize + sres.status = Starting + sres.loopFuture = newFuture[void]("stream.transport.server") + sres.udata = udata if localAddress.family == AddressFamily.None: - result.local = host + sres.local = host else: - result.local = localAddress + sres.local = localAddress when defined(windows): var cb: CallbackFunc @@ -2011,18 +1974,24 @@ proc createStreamServer*(host: TransportAddress, cb = acceptPipeLoop if not(isNil(cbproc)): - result.aovl.data = CompletionData(cb: cb, - udata: cast[pointer](result)) + sres.aovl.data = CompletionData(cb: cb, + udata: cast[pointer](sres)) else: if host.family == AddressFamily.Unix: - result.createAcceptPipe() + sres.sock = + block: + let res = sres.createAcceptPipe() + if res.isErr(): + raiseTransportOsError(res.error()) + res.get() - result.domain = host.getDomain() - result.apending = false + sres.domain = host.getDomain() + sres.apending = false # Start tracking server - trackServer(result) - GC_ref(result) + trackServer(sres) + GC_ref(sres) + sres proc createStreamServer*(host: TransportAddress, flags: set[ServerFlags] = {}, @@ -2033,8 +2002,8 @@ proc createStreamServer*(host: TransportAddress, init: TransportInitCallback = nil, udata: pointer = nil): StreamServer {. raises: [Defect, CatchableError].} = - result = createStreamServer(host, nil, flags, sock, backlog, bufferSize, - child, init, cast[pointer](udata)) + createStreamServer(host, nil, flags, sock, backlog, bufferSize, + child, init, cast[pointer](udata)) proc createStreamServer*[T](host: TransportAddress, cbproc: StreamCallback, @@ -2048,8 +2017,8 @@ proc createStreamServer*[T](host: TransportAddress, raises: [Defect, CatchableError].} = var fflags = flags + {GCUserData} GC_ref(udata) - result = createStreamServer(host, cbproc, fflags, sock, backlog, bufferSize, - child, init, cast[pointer](udata)) + createStreamServer(host, cbproc, fflags, sock, backlog, bufferSize, + child, init, cast[pointer](udata)) proc createStreamServer*[T](host: TransportAddress, flags: set[ServerFlags] = {}, @@ -2062,8 +2031,8 @@ proc createStreamServer*[T](host: TransportAddress, raises: [Defect, CatchableError].} = var fflags = flags + {GCUserData} GC_ref(udata) - result = createStreamServer(host, nil, fflags, sock, backlog, bufferSize, - child, init, cast[pointer](udata)) + createStreamServer(host, nil, fflags, sock, backlog, bufferSize, + child, init, cast[pointer](udata)) proc getUserData*[T](server: StreamServer): T {.inline.} = ## Obtain user data stored in ``server`` object. @@ -2080,10 +2049,10 @@ template fastWrite(transp: auto, pbytes: var ptr byte, rbytes: var int, let res = case transp.kind of TransportKind.Socket: - posix.send(SocketHandle(transp.fd), pbytes, rbytes, + osdefs.send(SocketHandle(transp.fd), pbytes, rbytes, MSG_NOSIGNAL) of TransportKind.Pipe: - posix.write(cint(transp.fd), pbytes, rbytes) + osdefs.write(cint(transp.fd), pbytes, rbytes) else: raiseAssert "Unsupported transport kind: " & $transp.kind if res > 0: @@ -2129,7 +2098,9 @@ proc write*(transp: StreamTransport, pbytes: pointer, var vector = StreamVector(kind: DataBuffer, writer: retFuture, buf: pbytes, buflen: rbytes, size: nbytes) transp.queue.addLast(vector) - transp.resumeWrite() + let wres = transp.resumeWrite() + if wres.isErr(): + retFuture.fail(getTransportOsError(wres.error())) return retFuture proc write*(transp: StreamTransport, msg: sink string, @@ -2166,7 +2137,9 @@ proc write*(transp: StreamTransport, msg: sink string, var vector = StreamVector(kind: DataBuffer, writer: retFuture, buf: pbytes, buflen: rbytes, size: nbytes) transp.queue.addLast(vector) - transp.resumeWrite() + let wres = transp.resumeWrite() + if wres.isErr(): + retFuture.fail(getTransportOsError(wres.error())) return retFuture proc write*[T](transp: StreamTransport, msg: sink seq[T], @@ -2203,7 +2176,9 @@ proc write*[T](transp: StreamTransport, msg: sink seq[T], var vector = StreamVector(kind: DataBuffer, writer: retFuture, buf: pbytes, buflen: rbytes, size: nbytes) transp.queue.addLast(vector) - transp.resumeWrite() + let wres = transp.resumeWrite() + if wres.isErr(): + retFuture.fail(getTransportOsError(wres.error())) return retFuture proc writeFile*(transp: StreamTransport, handle: int, @@ -2224,21 +2199,15 @@ proc writeFile*(transp: StreamTransport, handle: int, buf: cast[pointer](size), offset: offset, buflen: handle) transp.queue.addLast(vector) - transp.resumeWrite() + let wres = transp.resumeWrite() + if wres.isErr(): + retFuture.fail(getTransportOsError(wres.error())) return retFuture proc atEof*(transp: StreamTransport): bool {.inline.} = ## Returns ``true`` if ``transp`` is at EOF. - result = (transp.offset == 0) and (ReadEof in transp.state) and - (ReadPaused in transp.state) - -template prepareReader(transp: StreamTransport, - name: static string): Future[void] = - checkPending(transp) - var fut = newFuture[void](name) - transp.reader = fut - resumeRead(transp) - fut + (transp.offset == 0) and (ReadEof in transp.state) and + (ReadPaused in transp.state) template readLoop(name, body: untyped): untyped = # Read data until a predicate is satisfied - the body should return a tuple @@ -2259,7 +2228,29 @@ template readLoop(name, body: untyped): untyped = if done: break else: - await transp.prepareReader(name) + checkPending(transp) + var fut = newFuture[void](name) + transp.reader = fut + let res = resumeRead(transp) + if res.isErr(): + let errorCode = res.error() + when defined(windows): + # This assertion could be changed, because at this moment + # resumeRead() could not return any error. + raiseOsDefect(errorCode, "readLoop(): Unable to resume reading") + else: + transp.reader.complete() + if errorCode == ESRCH: + # ESRCH 3 "No such process" + # This error could be happened on pipes only, when process which + # owns and communicates through this pipe (stdin, stdout, stderr) is + # already dead. In such case we need to send notification that this + # pipe is at EOF. + transp.state.incl({ReadEof, ReadPaused}) + else: + raiseTransportOsError(errorCode) + else: + await fut proc readExactly*(transp: StreamTransport, pbytes: pointer, nbytes: int) {.async.} = @@ -2512,11 +2503,11 @@ proc close*(transp: StreamTransport) = if transp.kind == TransportKind.Pipe: if WinServerPipe in transp.flags: if WinNoPipeFlash notin transp.flags: - discard flushFileBuffers(Handle(transp.fd)) - discard disconnectNamedPipe(Handle(transp.fd)) + discard flushFileBuffers(HANDLE(transp.fd)) + discard disconnectNamedPipe(HANDLE(transp.fd)) else: if WinNoPipeFlash notin transp.flags: - discard flushFileBuffers(Handle(transp.fd)) + discard flushFileBuffers(HANDLE(transp.fd)) if ReadPaused in transp.state: # If readStreamLoop() is not running we need to finish in # continuation step. @@ -2543,7 +2534,7 @@ proc close*(transp: StreamTransport) = proc closeWait*(transp: StreamTransport): Future[void] = ## Close and frees resources of transport ``transp``. transp.close() - result = transp.join() + transp.join() proc closed*(transp: StreamTransport): bool {.inline.} = ## Returns ``true`` if transport in closed state. @@ -2562,14 +2553,24 @@ proc running*(transp: StreamTransport): bool {.inline.} = ({ReadClosed, ReadEof, ReadError, WriteClosed, WriteEof, WriteError} * transp.state == {}) -proc fromPipe*(fd: AsyncFD, child: StreamTransport = nil, - bufferSize = DefaultStreamBufferSize): StreamTransport {. - raises: [Defect, CatchableError].} = +proc fromPipe2*(fd: AsyncFD, child: StreamTransport = nil, + bufferSize = DefaultStreamBufferSize + ): Result[StreamTransport, OSErrorCode] = ## Create new transport object using pipe's file descriptor. ## ## ``bufferSize`` is size of internal buffer for transport. - register(fd) - - result = newStreamPipeTransport(fd, bufferSize, child) + ? register2(fd) + var res = newStreamPipeTransport(fd, bufferSize, child) # Start tracking transport - trackStream(result) + trackStream(res) + ok(res) + +proc fromPipe*(fd: AsyncFD, child: StreamTransport = nil, + bufferSize = DefaultStreamBufferSize): StreamTransport {. + raises: [Defect, TransportOsError].} = + ## Create new transport object using pipe's file descriptor. + ## + ## ``bufferSize`` is size of internal buffer for transport. + let res = fromPipe2(fd, child, bufferSize) + if res.isErr(): raiseTransportOsError(res.error()) + res.get() diff --git a/tests/teststream.nim b/tests/teststream.nim index 42ac7b1..e0ee9f3 100644 --- a/tests/teststream.nim +++ b/tests/teststream.nim @@ -7,14 +7,13 @@ # MIT license (LICENSE-MIT) import std/[strutils, os] import unittest2 -import ../chronos +import ".."/chronos, ".."/chronos/osdefs when defined(nimHasUsed): {.used.} when defined(windows): - import winlean -else: - import posix + proc get_osfhandle*(fd: FileHandle): HANDLE {. + importc: "_get_osfhandle", header:"".} suite "Stream Transport test suite": const @@ -1251,7 +1250,7 @@ suite "Stream Transport test suite": -1 return res - var fut = wtransp.writer() + var fut {.used.} = wtransp.writer() try: await rtransp.readExactly(addr buffer[0], 16384 * 1024) except CatchableError: