# # # Nim's Runtime Library # (c) Copyright 2013 Andreas Rumpf, Dominik Picheta # # See the file "copying.txt", included in this # distribution, for details about the copyright. # ## **Warning:** Since version 0.10.2 this module is deprecated. ## Use the `net `_ or the ## `rawsockets `_ module instead. ## ## This module implements portable sockets, it supports a mix of different types ## of sockets. Sockets are buffered by default meaning that data will be ## received in ``BufferSize`` (4000) sized chunks, buffering ## behaviour can be disabled by setting the ``buffered`` parameter when calling ## the ``socket`` function to `false`. Be aware that some functions may not yet ## support buffered sockets (mainly the recvFrom function). ## ## Most procedures raise OSError on error, but some may return ``-1`` or a ## boolean ``false``. ## ## SSL is supported through the OpenSSL library. This support can be activated ## by compiling with the ``-d:ssl`` switch. When an SSL socket is used it will ## raise ESSL exceptions when SSL errors occur. ## ## Asynchronous sockets are supported, however a better alternative is to use ## the `asyncio `_ module. {.deprecated.} include "system/inclrtl" {.deadCodeElim: on.} when hostOS == "solaris": {.passl: "-lsocket -lnsl".} import os, parseutils from times import epochTime import unsigned when defined(ssl): import openssl when defined(Windows): import winlean else: import posix # Note: The enumerations are mapped to Window's constants. when defined(ssl): type SSLError* = object of Exception SSLCVerifyMode* = enum CVerifyNone, CVerifyPeer SSLProtVersion* = enum protSSLv2, protSSLv3, protTLSv1, protSSLv23 SSLContext* = distinct SSLCTX SSLAcceptResult* = enum AcceptNoClient = 0, AcceptNoHandshake, AcceptSuccess {.deprecated: [ESSL: SSLError, TSSLCVerifyMode: SSLCVerifyMode, TSSLProtVersion: SSLProtVersion, PSSLContext: SSLContext, TSSLAcceptResult: SSLAcceptResult].} const BufferSize*: int = 4000 ## size of a buffered socket's buffer type TSocketImpl = object ## socket type fd: SocketHandle case isBuffered: bool # determines whether this socket is buffered. of true: buffer: array[0..BufferSize, char] currPos: int # current index in buffer bufLen: int # current length of buffer of false: nil when defined(ssl): case isSsl: bool of true: sslHandle: SSLPtr sslContext: SSLContext sslNoHandshake: bool # True if needs handshake. sslHasPeekChar: bool sslPeekChar: char of false: nil nonblocking: bool Socket* = ref TSocketImpl Port* = distinct uint16 ## port type Domain* = enum ## domain, which specifies the protocol family of the ## created socket. Other domains than those that are listed ## here are unsupported. AF_UNIX, ## for local socket (using a file). Unsupported on Windows. AF_INET = 2, ## for network protocol IPv4 or AF_INET6 = 23 ## for network protocol IPv6. SockType* = enum ## second argument to `socket` proc SOCK_STREAM = 1, ## reliable stream-oriented service or Stream Sockets SOCK_DGRAM = 2, ## datagram service or Datagram Sockets SOCK_RAW = 3, ## raw protocols atop the network layer. SOCK_SEQPACKET = 5 ## reliable sequenced packet service Protocol* = enum ## third argument to `socket` proc IPPROTO_TCP = 6, ## Transmission control protocol. IPPROTO_UDP = 17, ## User datagram protocol. IPPROTO_IP, ## Internet protocol. Unsupported on Windows. IPPROTO_IPV6, ## Internet Protocol Version 6. Unsupported on Windows. IPPROTO_RAW, ## Raw IP Packets Protocol. Unsupported on Windows. IPPROTO_ICMP ## Control message protocol. Unsupported on Windows. Servent* = object ## information about a service name*: string aliases*: seq[string] port*: Port proto*: string Hostent* = object ## information about a given host name*: string aliases*: seq[string] addrtype*: Domain length*: int addrList*: seq[string] SOBool* = enum ## Boolean socket options. OptAcceptConn, OptBroadcast, OptDebug, OptDontRoute, OptKeepAlive, OptOOBInline, OptReuseAddr RecvLineResult* = enum ## result for recvLineAsync RecvFullLine, RecvPartialLine, RecvDisconnected, RecvFail ReadLineResult* = enum ## result for readLineAsync ReadFullLine, ReadPartialLine, ReadDisconnected, ReadNone TimeoutError* = object of Exception {.deprecated: [TSocket: Socket, TType: SockType, TPort: Port, TDomain: Domain, TProtocol: Protocol, TServent: Servent, THostent: Hostent, TSOBool: SOBool, TRecvLineResult: RecvLineResult, TReadLineResult: ReadLineResult, ETimeout: TimeoutError].} when defined(booting): let invalidSocket*: Socket = nil ## invalid socket else: const invalidSocket*: Socket = nil ## invalid socket when defined(windows): let osInvalidSocket = winlean.INVALID_SOCKET else: let osInvalidSocket = posix.INVALID_SOCKET proc newTSocket(fd: SocketHandle, isBuff: bool): Socket = if fd == osInvalidSocket: return nil new(result) result.fd = fd result.isBuffered = isBuff if isBuff: result.currPos = 0 result.nonblocking = false proc `==`*(a, b: Port): bool {.borrow.} ## ``==`` for ports. proc `$`*(p: Port): string {.borrow.} ## returns the port number as a string proc ntohl*(x: int32): int32 = ## Converts 32-bit integers from network to host byte order. ## On machines where the host byte order is the same as network byte order, ## this is a no-op; otherwise, it performs a 4-byte swap operation. when cpuEndian == bigEndian: result = x else: result = (x shr 24'i32) or (x shr 8'i32 and 0xff00'i32) or (x shl 8'i32 and 0xff0000'i32) or (x shl 24'i32) proc ntohs*(x: int16): int16 = ## Converts 16-bit integers from network to host byte order. On machines ## where the host byte order is the same as network byte order, this is ## a no-op; otherwise, it performs a 2-byte swap operation. when cpuEndian == bigEndian: result = x else: result = (x shr 8'i16) or (x shl 8'i16) proc htonl*(x: int32): int32 = ## Converts 32-bit integers from host to network byte order. On machines ## where the host byte order is the same as network byte order, this is ## a no-op; otherwise, it performs a 4-byte swap operation. result = sockets.ntohl(x) proc htons*(x: int16): int16 = ## Converts 16-bit positive integers from host to network byte order. ## On machines where the host byte order is the same as network byte ## order, this is a no-op; otherwise, it performs a 2-byte swap operation. result = sockets.ntohs(x) when defined(Posix): proc toInt(domain: Domain): cint = case domain of AF_UNIX: result = posix.AF_UNIX of AF_INET: result = posix.AF_INET of AF_INET6: result = posix.AF_INET6 else: discard proc toInt(typ: SockType): cint = case typ of SOCK_STREAM: result = posix.SOCK_STREAM of SOCK_DGRAM: result = posix.SOCK_DGRAM of SOCK_SEQPACKET: result = posix.SOCK_SEQPACKET of SOCK_RAW: result = posix.SOCK_RAW else: discard proc toInt(p: Protocol): cint = case p of IPPROTO_TCP: result = posix.IPPROTO_TCP of IPPROTO_UDP: result = posix.IPPROTO_UDP of IPPROTO_IP: result = posix.IPPROTO_IP of IPPROTO_IPV6: result = posix.IPPROTO_IPV6 of IPPROTO_RAW: result = posix.IPPROTO_RAW of IPPROTO_ICMP: result = posix.IPPROTO_ICMP else: discard else: proc toInt(domain: Domain): cint = result = toU16(ord(domain)) proc toInt(typ: SockType): cint = result = cint(ord(typ)) proc toInt(p: Protocol): cint = result = cint(ord(p)) proc socket*(domain: Domain = AF_INET, typ: SockType = SOCK_STREAM, protocol: Protocol = IPPROTO_TCP, buffered = true): Socket = ## Creates a new socket; returns `InvalidSocket` if an error occurs. # TODO: Perhaps this should just raise EOS when an error occurs. when defined(Windows): result = newTSocket(winlean.socket(ord(domain), ord(typ), ord(protocol)), buffered) else: result = newTSocket(posix.socket(toInt(domain), toInt(typ), toInt(protocol)), buffered) when defined(ssl): CRYPTO_malloc_init() SslLibraryInit() SslLoadErrorStrings() ErrLoadBioStrings() OpenSSL_add_all_algorithms() proc raiseSSLError(s = "") = if s != "": raise newException(SSLError, s) let err = ErrPeekLastError() if err == 0: raise newException(SSLError, "No error reported.") if err == -1: raiseOSError(osLastError()) var errStr = ErrErrorString(err, nil) raise newException(SSLError, $errStr) # http://simplestcodings.blogspot.co.uk/2010/08/secure-server-client-using-openssl-in-c.html proc loadCertificates(ctx: SSL_CTX, certFile, keyFile: string) = if certFile != "" and not existsFile(certFile): raise newException(system.IOError, "Certificate file could not be found: " & certFile) if keyFile != "" and not existsFile(keyFile): raise newException(system.IOError, "Key file could not be found: " & keyFile) if certFile != "": var ret = SSLCTXUseCertificateChainFile(ctx, certFile) if ret != 1: raiseSslError() # TODO: Password? www.rtfm.com/openssl-examples/part1.pdf if keyFile != "": if SSL_CTX_use_PrivateKey_file(ctx, keyFile, SSL_FILETYPE_PEM) != 1: raiseSslError() if SSL_CTX_check_private_key(ctx) != 1: raiseSslError("Verification of private key file failed.") proc newContext*(protVersion = protSSLv23, verifyMode = CVerifyPeer, certFile = "", keyFile = ""): SSLContext = ## Creates an SSL context. ## ## Protocol version specifies the protocol to use. SSLv2, SSLv3, TLSv1 are ## are available with the addition of ``ProtSSLv23`` which allows for ## compatibility with all of them. ## ## There are currently only two options for verify mode; ## one is ``CVerifyNone`` and with it certificates will not be verified ## the other is ``CVerifyPeer`` and certificates will be verified for ## it, ``CVerifyPeer`` is the safest choice. ## ## The last two parameters specify the certificate file path and the key file ## path, a server socket will most likely not work without these. ## Certificates can be generated using the following command: ## ``openssl req -x509 -nodes -days 365 -newkey rsa:1024 -keyout mycert.pem -out mycert.pem``. var newCTX: SSL_CTX case protVersion of protSSLv23: newCTX = SSL_CTX_new(SSLv23_method()) # SSlv2,3 and TLS1 support. of protSSLv2: when not defined(linux) and not defined(OpenBSD): newCTX = SSL_CTX_new(SSLv2_method()) else: raiseSslError() of protSSLv3: newCTX = SSL_CTX_new(SSLv3_method()) of protTLSv1: newCTX = SSL_CTX_new(TLSv1_method()) if newCTX.SSLCTXSetCipherList("ALL") != 1: raiseSslError() case verifyMode of CVerifyPeer: newCTX.SSLCTXSetVerify(SSLVerifyPeer, nil) of CVerifyNone: newCTX.SSLCTXSetVerify(SSLVerifyNone, nil) if newCTX == nil: raiseSslError() discard newCTX.SSLCTXSetMode(SSL_MODE_AUTO_RETRY) newCTX.loadCertificates(certFile, keyFile) return SSLContext(newCTX) proc wrapSocket*(ctx: SSLContext, socket: Socket) = ## Wraps a socket in an SSL context. This function effectively turns ## ``socket`` into an SSL socket. ## ## **Disclaimer**: This code is not well tested, may be very unsafe and ## prone to security vulnerabilities. socket.isSSL = true socket.sslContext = ctx socket.sslHandle = SSLNew(SSLCTX(socket.sslContext)) socket.sslNoHandshake = false socket.sslHasPeekChar = false if socket.sslHandle == nil: raiseSslError() if SSLSetFd(socket.sslHandle, socket.fd) != 1: raiseSslError() proc raiseSocketError*(socket: Socket, err: int = -1, async = false) = ## Raises proper errors based on return values of ``recv`` functions. ## ## If ``async`` is ``True`` no error will be thrown in the case when the ## error was caused by no data being available to be read. ## ## If ``err`` is not lower than 0 no exception will be raised. when defined(ssl): if socket.isSSL: if err <= 0: var ret = SSLGetError(socket.sslHandle, err.cint) case ret of SSL_ERROR_ZERO_RETURN: raiseSslError("TLS/SSL connection failed to initiate, socket closed prematurely.") of SSL_ERROR_WANT_CONNECT, SSL_ERROR_WANT_ACCEPT: if async: return else: raiseSslError("Not enough data on socket.") of SSL_ERROR_WANT_WRITE, SSL_ERROR_WANT_READ: if async: return else: raiseSslError("Not enough data on socket.") of SSL_ERROR_WANT_X509_LOOKUP: raiseSslError("Function for x509 lookup has been called.") of SSL_ERROR_SYSCALL, SSL_ERROR_SSL: raiseSslError() else: raiseSslError("Unknown Error") if err == -1 and not (when defined(ssl): socket.isSSL else: false): let lastError = osLastError() if async: when defined(windows): if lastError.int32 == WSAEWOULDBLOCK: return else: raiseOSError(lastError) else: if lastError.int32 == EAGAIN or lastError.int32 == EWOULDBLOCK: return else: raiseOSError(lastError) else: raiseOSError(lastError) proc listen*(socket: Socket, backlog = SOMAXCONN) {.tags: [ReadIOEffect].} = ## Marks ``socket`` as accepting connections. ## ``Backlog`` specifies the maximum length of the ## queue of pending connections. if listen(socket.fd, cint(backlog)) < 0'i32: raiseOSError(osLastError()) proc invalidIp4(s: string) {.noreturn, noinline.} = raise newException(ValueError, "invalid ip4 address: " & s) proc parseIp4*(s: string): BiggestInt = ## parses an IP version 4 in dotted decimal form like "a.b.c.d". ## ## This is equivalent to `inet_ntoa`:idx:. ## ## Raises EInvalidValue in case of an error. var a, b, c, d: int var i = 0 var j = parseInt(s, a, i) if j <= 0: invalidIp4(s) inc(i, j) if s[i] == '.': inc(i) else: invalidIp4(s) j = parseInt(s, b, i) if j <= 0: invalidIp4(s) inc(i, j) if s[i] == '.': inc(i) else: invalidIp4(s) j = parseInt(s, c, i) if j <= 0: invalidIp4(s) inc(i, j) if s[i] == '.': inc(i) else: invalidIp4(s) j = parseInt(s, d, i) if j <= 0: invalidIp4(s) inc(i, j) if s[i] != '\0': invalidIp4(s) result = BiggestInt(a shl 24 or b shl 16 or c shl 8 or d) template gaiNim(a, p, h, list: expr): stmt = block: var gaiResult = getaddrinfo(a, $p, addr(h), list) if gaiResult != 0'i32: when defined(windows): raiseOSError(osLastError()) else: raise newException(OSError, $gai_strerror(gaiResult)) proc bindAddr*(socket: Socket, port = Port(0), address = "") {. tags: [ReadIOEffect].} = ## binds an address/port number to a socket. ## Use address string in dotted decimal form like "a.b.c.d" ## or leave "" for any address. if address == "": var name: Sockaddr_in when defined(Windows): name.sin_family = int16(ord(AF_INET)) else: name.sin_family = posix.AF_INET name.sin_port = sockets.htons(int16(port)) name.sin_addr.s_addr = sockets.htonl(INADDR_ANY) if bindSocket(socket.fd, cast[ptr SockAddr](addr(name)), sizeof(name).SockLen) < 0'i32: raiseOSError(osLastError()) else: var hints: AddrInfo var aiList: ptr AddrInfo = nil hints.ai_family = toInt(AF_INET) hints.ai_socktype = toInt(SOCK_STREAM) hints.ai_protocol = toInt(IPPROTO_TCP) gaiNim(address, port, hints, aiList) if bindSocket(socket.fd, aiList.ai_addr, aiList.ai_addrlen.SockLen) < 0'i32: raiseOSError(osLastError()) proc getSockName*(socket: Socket): Port = ## returns the socket's associated port number. var name: Sockaddr_in when defined(Windows): name.sin_family = int16(ord(AF_INET)) else: name.sin_family = posix.AF_INET #name.sin_port = htons(cint16(port)) #name.sin_addr.s_addr = htonl(INADDR_ANY) var namelen = sizeof(name).SockLen if getsockname(socket.fd, cast[ptr SockAddr](addr(name)), addr(namelen)) == -1'i32: raiseOSError(osLastError()) result = Port(sockets.ntohs(name.sin_port)) template acceptAddrPlain(noClientRet, successRet: expr, sslImplementation: stmt): stmt {.immediate.} = assert(client != nil) var sockAddress: Sockaddr_in var addrLen = sizeof(sockAddress).SockLen var sock = accept(server.fd, cast[ptr SockAddr](addr(sockAddress)), addr(addrLen)) if sock == osInvalidSocket: let err = osLastError() when defined(windows): if err.int32 == WSAEINPROGRESS: client = invalidSocket address = "" when noClientRet.int == -1: return else: return noClientRet else: raiseOSError(err) else: if err.int32 == EAGAIN or err.int32 == EWOULDBLOCK: client = invalidSocket address = "" when noClientRet.int == -1: return else: return noClientRet else: raiseOSError(err) else: client.fd = sock client.isBuffered = server.isBuffered sslImplementation # Client socket is set above. address = $inet_ntoa(sockAddress.sin_addr) when successRet.int == -1: return else: return successRet proc acceptAddr*(server: Socket, client: var Socket, address: var string) {. tags: [ReadIOEffect].} = ## Blocks until a connection is being made from a client. When a connection ## is made sets ``client`` to the client socket and ``address`` to the address ## of the connecting client. ## If ``server`` is non-blocking then this function returns immediately, and ## if there are no connections queued the returned socket will be ## ``InvalidSocket``. ## This function will raise EOS if an error occurs. ## ## The resulting client will inherit any properties of the server socket. For ## example: whether the socket is buffered or not. ## ## **Note**: ``client`` must be initialised (with ``new``), this function ## makes no effort to initialise the ``client`` variable. ## ## **Warning:** When using SSL with non-blocking sockets, it is best to use ## the acceptAddrSSL procedure as this procedure will most likely block. acceptAddrPlain(-1, -1): when defined(ssl): if server.isSSL: # We must wrap the client sock in a ssl context. server.sslContext.wrapSocket(client) let ret = SSLAccept(client.sslHandle) while ret <= 0: let err = SSLGetError(client.sslHandle, ret) if err != SSL_ERROR_WANT_ACCEPT: case err of SSL_ERROR_ZERO_RETURN: raiseSslError("TLS/SSL connection failed to initiate, socket closed prematurely.") of SSL_ERROR_WANT_READ, SSL_ERROR_WANT_WRITE, SSL_ERROR_WANT_CONNECT, SSL_ERROR_WANT_ACCEPT: raiseSslError("acceptAddrSSL should be used for non-blocking SSL sockets.") of SSL_ERROR_WANT_X509_LOOKUP: raiseSslError("Function for x509 lookup has been called.") of SSL_ERROR_SYSCALL, SSL_ERROR_SSL: raiseSslError() else: raiseSslError("Unknown error") proc setBlocking*(s: Socket, blocking: bool) {.tags: [], gcsafe.} ## Sets blocking mode on socket when defined(ssl): proc acceptAddrSSL*(server: Socket, client: var Socket, address: var string): SSLAcceptResult {. tags: [ReadIOEffect].} = ## This procedure should only be used for non-blocking **SSL** sockets. ## It will immediately return with one of the following values: ## ## ``AcceptSuccess`` will be returned when a client has been successfully ## accepted and the handshake has been successfully performed between ## ``server`` and the newly connected client. ## ## ``AcceptNoHandshake`` will be returned when a client has been accepted ## but no handshake could be performed. This can happen when the client ## connects but does not yet initiate a handshake. In this case ## ``acceptAddrSSL`` should be called again with the same parameters. ## ## ``AcceptNoClient`` will be returned when no client is currently attempting ## to connect. template doHandshake(): stmt = when defined(ssl): if server.isSSL: client.setBlocking(false) # We must wrap the client sock in a ssl context. if not client.isSSL or client.sslHandle == nil: server.sslContext.wrapSocket(client) let ret = SSLAccept(client.sslHandle) while ret <= 0: let err = SSLGetError(client.sslHandle, ret) if err != SSL_ERROR_WANT_ACCEPT: case err of SSL_ERROR_ZERO_RETURN: raiseSslError("TLS/SSL connection failed to initiate, socket closed prematurely.") of SSL_ERROR_WANT_READ, SSL_ERROR_WANT_WRITE, SSL_ERROR_WANT_CONNECT, SSL_ERROR_WANT_ACCEPT: client.sslNoHandshake = true return AcceptNoHandshake of SSL_ERROR_WANT_X509_LOOKUP: raiseSslError("Function for x509 lookup has been called.") of SSL_ERROR_SYSCALL, SSL_ERROR_SSL: raiseSslError() else: raiseSslError("Unknown error") client.sslNoHandshake = false if client.isSSL and client.sslNoHandshake: doHandshake() return AcceptSuccess else: acceptAddrPlain(AcceptNoClient, AcceptSuccess): doHandshake() proc accept*(server: Socket, client: var Socket) {.tags: [ReadIOEffect].} = ## Equivalent to ``acceptAddr`` but doesn't return the address, only the ## socket. ## ## **Note**: ``client`` must be initialised (with ``new``), this function ## makes no effort to initialise the ``client`` variable. var addrDummy = "" acceptAddr(server, client, addrDummy) proc acceptAddr*(server: Socket): tuple[client: Socket, address: string] {. deprecated, tags: [ReadIOEffect].} = ## Slightly different version of ``acceptAddr``. ## ## **Deprecated since version 0.9.0:** Please use the function above. var client: Socket new(client) var address = "" acceptAddr(server, client, address) return (client, address) proc accept*(server: Socket): Socket {.deprecated, tags: [ReadIOEffect].} = ## **Deprecated since version 0.9.0:** Please use the function above. new(result) var address = "" acceptAddr(server, result, address) proc close*(socket: Socket) = ## closes a socket. when defined(windows): discard winlean.closesocket(socket.fd) else: discard posix.close(socket.fd) # TODO: These values should not be discarded. An EOS should be raised. # http://stackoverflow.com/questions/12463473/what-happens-if-you-call-close-on-a-bsd-socket-multiple-times when defined(ssl): if socket.isSSL: discard SSLShutdown(socket.sslHandle) SSLFree(socket.sslHandle) socket.sslHandle = nil proc getServByName*(name, proto: string): Servent {.tags: [ReadIOEffect].} = ## Searches the database from the beginning and finds the first entry for ## which the service name specified by ``name`` matches the s_name member ## and the protocol name specified by ``proto`` matches the s_proto member. ## ## On posix this will search through the ``/etc/services`` file. when defined(Windows): var s = winlean.getservbyname(name, proto) else: var s = posix.getservbyname(name, proto) if s == nil: raise newException(OSError, "Service not found.") result.name = $s.s_name result.aliases = cstringArrayToSeq(s.s_aliases) result.port = Port(s.s_port) result.proto = $s.s_proto proc getServByPort*(port: Port, proto: string): Servent {.tags: [ReadIOEffect].} = ## Searches the database from the beginning and finds the first entry for ## which the port specified by ``port`` matches the s_port member and the ## protocol name specified by ``proto`` matches the s_proto member. ## ## On posix this will search through the ``/etc/services`` file. when defined(Windows): var s = winlean.getservbyport(ze(int16(port)).cint, proto) else: var s = posix.getservbyport(ze(int16(port)).cint, proto) if s == nil: raise newException(OSError, "Service not found.") result.name = $s.s_name result.aliases = cstringArrayToSeq(s.s_aliases) result.port = Port(s.s_port) result.proto = $s.s_proto proc getHostByAddr*(ip: string): Hostent {.tags: [ReadIOEffect].} = ## This function will lookup the hostname of an IP Address. var myaddr: InAddr myaddr.s_addr = inet_addr(ip) when defined(windows): var s = winlean.gethostbyaddr(addr(myaddr), sizeof(myaddr).cuint, cint(sockets.AF_INET)) if s == nil: raiseOSError(osLastError()) else: var s = posix.gethostbyaddr(addr(myaddr), sizeof(myaddr).Socklen, cint(posix.AF_INET)) if s == nil: raise newException(OSError, $hstrerror(h_errno)) result.name = $s.h_name result.aliases = cstringArrayToSeq(s.h_aliases) when defined(windows): result.addrtype = Domain(s.h_addrtype) else: if s.h_addrtype == posix.AF_INET: result.addrtype = AF_INET elif s.h_addrtype == posix.AF_INET6: result.addrtype = AF_INET6 else: raise newException(OSError, "unknown h_addrtype") result.addrList = cstringArrayToSeq(s.h_addr_list) result.length = int(s.h_length) proc getHostByName*(name: string): Hostent {.tags: [ReadIOEffect].} = ## This function will lookup the IP address of a hostname. when defined(Windows): var s = winlean.gethostbyname(name) else: var s = posix.gethostbyname(name) if s == nil: raiseOSError(osLastError()) result.name = $s.h_name result.aliases = cstringArrayToSeq(s.h_aliases) when defined(windows): result.addrtype = Domain(s.h_addrtype) else: if s.h_addrtype == posix.AF_INET: result.addrtype = AF_INET elif s.h_addrtype == posix.AF_INET6: result.addrtype = AF_INET6 else: raise newException(OSError, "unknown h_addrtype") result.addrList = cstringArrayToSeq(s.h_addr_list) result.length = int(s.h_length) proc getSockOptInt*(socket: Socket, level, optname: int): int {. tags: [ReadIOEffect].} = ## getsockopt for integer options. var res: cint var size = sizeof(res).SockLen if getsockopt(socket.fd, cint(level), cint(optname), addr(res), addr(size)) < 0'i32: raiseOSError(osLastError()) result = int(res) proc setSockOptInt*(socket: Socket, level, optname, optval: int) {. tags: [WriteIOEffect].} = ## setsockopt for integer options. var value = cint(optval) if setsockopt(socket.fd, cint(level), cint(optname), addr(value), sizeof(value).SockLen) < 0'i32: raiseOSError(osLastError()) proc toCInt(opt: SOBool): cint = case opt of OptAcceptConn: SO_ACCEPTCONN of OptBroadcast: SO_BROADCAST of OptDebug: SO_DEBUG of OptDontRoute: SO_DONTROUTE of OptKeepAlive: SO_KEEPALIVE of OptOOBInline: SO_OOBINLINE of OptReuseAddr: SO_REUSEADDR proc getSockOpt*(socket: Socket, opt: SOBool, level = SOL_SOCKET): bool {. tags: [ReadIOEffect].} = ## Retrieves option ``opt`` as a boolean value. var res: cint var size = sizeof(res).SockLen if getsockopt(socket.fd, cint(level), toCInt(opt), addr(res), addr(size)) < 0'i32: raiseOSError(osLastError()) result = res != 0 proc setSockOpt*(socket: Socket, opt: SOBool, value: bool, level = SOL_SOCKET) {. tags: [WriteIOEffect].} = ## Sets option ``opt`` to a boolean value specified by ``value``. var valuei = cint(if value: 1 else: 0) if setsockopt(socket.fd, cint(level), toCInt(opt), addr(valuei), sizeof(valuei).SockLen) < 0'i32: raiseOSError(osLastError()) proc connect*(socket: Socket, address: string, port = Port(0), af: Domain = AF_INET) {.tags: [ReadIOEffect].} = ## Connects socket to ``address``:``port``. ``Address`` can be an IP address or a ## host name. If ``address`` is a host name, this function will try each IP ## of that host name. ``htons`` is already performed on ``port`` so you must ## not do it. ## ## If ``socket`` is an SSL socket a handshake will be automatically performed. var hints: AddrInfo var aiList: ptr AddrInfo = nil hints.ai_family = toInt(af) hints.ai_socktype = toInt(SOCK_STREAM) hints.ai_protocol = toInt(IPPROTO_TCP) gaiNim(address, port, hints, aiList) # try all possibilities: var success = false var lastError: OSErrorCode var it = aiList while it != nil: if connect(socket.fd, it.ai_addr, it.ai_addrlen.SockLen) == 0'i32: success = true break else: lastError = osLastError() it = it.ai_next freeaddrinfo(aiList) if not success: raiseOSError(lastError) when defined(ssl): if socket.isSSL: let ret = SSLConnect(socket.sslHandle) if ret <= 0: let err = SSLGetError(socket.sslHandle, ret) case err of SSL_ERROR_ZERO_RETURN: raiseSslError("TLS/SSL connection failed to initiate, socket closed prematurely.") of SSL_ERROR_WANT_READ, SSL_ERROR_WANT_WRITE, SSL_ERROR_WANT_CONNECT, SSL_ERROR_WANT_ACCEPT: raiseSslError("The operation did not complete. Perhaps you should use connectAsync?") of SSL_ERROR_WANT_X509_LOOKUP: raiseSslError("Function for x509 lookup has been called.") of SSL_ERROR_SYSCALL, SSL_ERROR_SSL: raiseSslError() else: raiseSslError("Unknown error") when false: var s: TSockAddrIn s.sin_addr.s_addr = inet_addr(address) s.sin_port = sockets.htons(int16(port)) when defined(windows): s.sin_family = toU16(ord(af)) else: case af of AF_UNIX: s.sin_family = posix.AF_UNIX of AF_INET: s.sin_family = posix.AF_INET of AF_INET6: s.sin_family = posix.AF_INET6 else: nil if connect(socket.fd, cast[ptr TSockAddr](addr(s)), sizeof(s).cint) < 0'i32: OSError() proc connectAsync*(socket: Socket, name: string, port = Port(0), af: Domain = AF_INET) {.tags: [ReadIOEffect].} = ## A variant of ``connect`` for non-blocking sockets. ## ## This procedure will immediately return, it will not block until a connection ## is made. It is up to the caller to make sure the connection has been established ## by checking (using ``select``) whether the socket is writeable. ## ## **Note**: For SSL sockets, the ``handshake`` procedure must be called ## whenever the socket successfully connects to a server. var hints: AddrInfo var aiList: ptr AddrInfo = nil hints.ai_family = toInt(af) hints.ai_socktype = toInt(SOCK_STREAM) hints.ai_protocol = toInt(IPPROTO_TCP) gaiNim(name, port, hints, aiList) # try all possibilities: var success = false var lastError: OSErrorCode var it = aiList while it != nil: var ret = connect(socket.fd, it.ai_addr, it.ai_addrlen.SockLen) if ret == 0'i32: success = true break else: lastError = osLastError() when defined(windows): # Windows EINTR doesn't behave same as POSIX. if lastError.int32 == WSAEWOULDBLOCK: success = true break else: if lastError.int32 == EINTR or lastError.int32 == EINPROGRESS: success = true break it = it.ai_next freeaddrinfo(aiList) if not success: raiseOSError(lastError) when defined(ssl): if socket.isSSL: socket.sslNoHandshake = true when defined(ssl): proc handshake*(socket: Socket): bool {.tags: [ReadIOEffect, WriteIOEffect].} = ## This proc needs to be called on a socket after it connects. This is ## only applicable when using ``connectAsync``. ## This proc performs the SSL handshake. ## ## Returns ``False`` whenever the socket is not yet ready for a handshake, ## ``True`` whenever handshake completed successfully. ## ## A ESSL error is raised on any other errors. result = true if socket.isSSL: var ret = SSLConnect(socket.sslHandle) if ret <= 0: var errret = SSLGetError(socket.sslHandle, ret) case errret of SSL_ERROR_ZERO_RETURN: raiseSslError("TLS/SSL connection failed to initiate, socket closed prematurely.") of SSL_ERROR_WANT_CONNECT, SSL_ERROR_WANT_ACCEPT, SSL_ERROR_WANT_READ, SSL_ERROR_WANT_WRITE: return false of SSL_ERROR_WANT_X509_LOOKUP: raiseSslError("Function for x509 lookup has been called.") of SSL_ERROR_SYSCALL, SSL_ERROR_SSL: raiseSslError() else: raiseSslError("Unknown Error") socket.sslNoHandshake = false else: raiseSslError("Socket is not an SSL socket.") proc gotHandshake*(socket: Socket): bool = ## Determines whether a handshake has occurred between a client (``socket``) ## and the server that ``socket`` is connected to. ## ## Throws ESSL if ``socket`` is not an SSL socket. if socket.isSSL: return not socket.sslNoHandshake else: raiseSslError("Socket is not an SSL socket.") proc timeValFromMilliseconds(timeout = 500): Timeval = if timeout != -1: var seconds = timeout div 1000 result.tv_sec = seconds.int32 result.tv_usec = ((timeout - seconds * 1000) * 1000).int32 proc createFdSet(fd: var TFdSet, s: seq[Socket], m: var int) = FD_ZERO(fd) for i in items(s): m = max(m, int(i.fd)) FD_SET(i.fd, fd) proc pruneSocketSet(s: var seq[Socket], fd: var TFdSet) = var i = 0 var L = s.len while i < L: if FD_ISSET(s[i].fd, fd) == 0'i32: # not set. s[i] = s[L-1] dec(L) else: inc(i) setLen(s, L) proc hasDataBuffered*(s: Socket): bool = ## Determines whether a socket has data buffered. result = false if s.isBuffered: result = s.bufLen > 0 and s.currPos != s.bufLen when defined(ssl): if s.isSSL and not result: result = s.sslHasPeekChar proc checkBuffer(readfds: var seq[Socket]): int = ## Checks the buffer of each socket in ``readfds`` to see whether there is data. ## Removes the sockets from ``readfds`` and returns the count of removed sockets. var res: seq[Socket] = @[] result = 0 for s in readfds: if hasDataBuffered(s): inc(result) res.add(s) if result > 0: readfds = res proc select*(readfds, writefds, exceptfds: var seq[Socket], timeout = 500): int {.tags: [ReadIOEffect].} = ## Traditional select function. This function will return the number of ## sockets that are ready to be read from, written to, or which have errors. ## If there are none; 0 is returned. ## ``Timeout`` is in miliseconds and -1 can be specified for no timeout. ## ## Sockets which are **not** ready for reading, writing or which don't have ## errors waiting on them are removed from the ``readfds``, ``writefds``, ## ``exceptfds`` sequences respectively. let buffersFilled = checkBuffer(readfds) if buffersFilled > 0: return buffersFilled var tv {.noInit.}: Timeval = timeValFromMilliseconds(timeout) var rd, wr, ex: TFdSet var m = 0 createFdSet((rd), readfds, m) createFdSet((wr), writefds, m) createFdSet((ex), exceptfds, m) if timeout != -1: result = int(select(cint(m+1), addr(rd), addr(wr), addr(ex), addr(tv))) else: result = int(select(cint(m+1), addr(rd), addr(wr), addr(ex), nil)) pruneSocketSet(readfds, (rd)) pruneSocketSet(writefds, (wr)) pruneSocketSet(exceptfds, (ex)) proc select*(readfds, writefds: var seq[Socket], timeout = 500): int {.tags: [ReadIOEffect].} = ## Variant of select with only a read and write list. let buffersFilled = checkBuffer(readfds) if buffersFilled > 0: return buffersFilled var tv {.noInit.}: Timeval = timeValFromMilliseconds(timeout) var rd, wr: TFdSet var m = 0 createFdSet((rd), readfds, m) createFdSet((wr), writefds, m) if timeout != -1: result = int(select(cint(m+1), addr(rd), addr(wr), nil, addr(tv))) else: result = int(select(cint(m+1), addr(rd), addr(wr), nil, nil)) pruneSocketSet(readfds, (rd)) pruneSocketSet(writefds, (wr)) proc selectWrite*(writefds: var seq[Socket], timeout = 500): int {.tags: [ReadIOEffect].} = ## When a socket in ``writefds`` is ready to be written to then a non-zero ## value will be returned specifying the count of the sockets which can be ## written to. The sockets which **cannot** be written to will also be removed ## from ``writefds``. ## ## ``timeout`` is specified in miliseconds and ``-1`` can be specified for ## an unlimited time. var tv {.noInit.}: Timeval = timeValFromMilliseconds(timeout) var wr: TFdSet var m = 0 createFdSet((wr), writefds, m) if timeout != -1: result = int(select(cint(m+1), nil, addr(wr), nil, addr(tv))) else: result = int(select(cint(m+1), nil, addr(wr), nil, nil)) pruneSocketSet(writefds, (wr)) proc select*(readfds: var seq[Socket], timeout = 500): int = ## variant of select with a read list only let buffersFilled = checkBuffer(readfds) if buffersFilled > 0: return buffersFilled var tv {.noInit.}: Timeval = timeValFromMilliseconds(timeout) var rd: TFdSet var m = 0 createFdSet((rd), readfds, m) if timeout != -1: result = int(select(cint(m+1), addr(rd), nil, nil, addr(tv))) else: result = int(select(cint(m+1), addr(rd), nil, nil, nil)) pruneSocketSet(readfds, (rd)) proc readIntoBuf(socket: Socket, flags: int32): int = result = 0 when defined(ssl): if socket.isSSL: result = SSLRead(socket.sslHandle, addr(socket.buffer), int(socket.buffer.high)) else: result = recv(socket.fd, addr(socket.buffer), cint(socket.buffer.high), flags) else: result = recv(socket.fd, addr(socket.buffer), cint(socket.buffer.high), flags) if result <= 0: socket.bufLen = 0 socket.currPos = 0 return result socket.bufLen = result socket.currPos = 0 template retRead(flags, readBytes: int) {.dirty.} = let res = socket.readIntoBuf(flags.int32) if res <= 0: if readBytes > 0: return readBytes else: return res proc recv*(socket: Socket, data: pointer, size: int): int {.tags: [ReadIOEffect].} = ## Receives data from a socket. ## ## **Note**: This is a low-level function, you may be interested in the higher ## level versions of this function which are also named ``recv``. if size == 0: return if socket.isBuffered: if socket.bufLen == 0: retRead(0'i32, 0) var read = 0 while read < size: if socket.currPos >= socket.bufLen: retRead(0'i32, read) let chunk = min(socket.bufLen-socket.currPos, size-read) var d = cast[cstring](data) copyMem(addr(d[read]), addr(socket.buffer[socket.currPos]), chunk) read.inc(chunk) socket.currPos.inc(chunk) result = read else: when defined(ssl): if socket.isSSL: if socket.sslHasPeekChar: copyMem(data, addr(socket.sslPeekChar), 1) socket.sslHasPeekChar = false if size-1 > 0: var d = cast[cstring](data) result = SSLRead(socket.sslHandle, addr(d[1]), size-1) + 1 else: result = 1 else: result = SSLRead(socket.sslHandle, data, size) else: result = recv(socket.fd, data, size.cint, 0'i32) else: result = recv(socket.fd, data, size.cint, 0'i32) proc waitFor(socket: Socket, waited: var float, timeout, size: int, funcName: string): int {.tags: [TimeEffect].} = ## determines the amount of characters that can be read. Result will never ## be larger than ``size``. For unbuffered sockets this will be ``1``. ## For buffered sockets it can be as big as ``BufferSize``. ## ## If this function does not determine that there is data on the socket ## within ``timeout`` ms, an ETimeout error will be raised. result = 1 if size <= 0: assert false if timeout == -1: return size if socket.isBuffered and socket.bufLen != 0 and socket.bufLen != socket.currPos: result = socket.bufLen - socket.currPos result = min(result, size) else: if timeout - int(waited * 1000.0) < 1: raise newException(TimeoutError, "Call to '" & funcName & "' timed out.") when defined(ssl): if socket.isSSL: if socket.hasDataBuffered: # sslPeekChar is present. return 1 let sslPending = SSLPending(socket.sslHandle) if sslPending != 0: return sslPending var s = @[socket] var startTime = epochTime() let selRet = select(s, timeout - int(waited * 1000.0)) if selRet < 0: raiseOSError(osLastError()) if selRet != 1: raise newException(TimeoutError, "Call to '" & funcName & "' timed out.") waited += (epochTime() - startTime) proc recv*(socket: Socket, data: pointer, size: int, timeout: int): int {. tags: [ReadIOEffect, TimeEffect].} = ## overload with a ``timeout`` parameter in miliseconds. var waited = 0.0 # number of seconds already waited var read = 0 while read < size: let avail = waitFor(socket, waited, timeout, size-read, "recv") var d = cast[cstring](data) result = recv(socket, addr(d[read]), avail) if result == 0: break if result < 0: return result inc(read, result) result = read proc recv*(socket: Socket, data: var string, size: int, timeout = -1): int = ## Higher-level version of ``recv``. ## ## When 0 is returned the socket's connection has been closed. ## ## This function will throw an EOS exception when an error occurs. A value ## lower than 0 is never returned. ## ## A timeout may be specified in miliseconds, if enough data is not received ## within the time specified an ETimeout exception will be raised. ## ## **Note**: ``data`` must be initialised. data.setLen(size) result = recv(socket, cstring(data), size, timeout) if result < 0: data.setLen(0) socket.raiseSocketError(result) data.setLen(result) proc recvAsync*(socket: Socket, data: var string, size: int): int = ## Async version of ``recv``. ## ## When socket is non-blocking and no data is available on the socket, ## ``-1`` will be returned and ``data`` will be ``""``. ## ## **Note**: ``data`` must be initialised. data.setLen(size) result = recv(socket, cstring(data), size) if result < 0: data.setLen(0) socket.raiseSocketError(async = true) result = -1 data.setLen(result) proc peekChar(socket: Socket, c: var char): int {.tags: [ReadIOEffect].} = if socket.isBuffered: result = 1 if socket.bufLen == 0 or socket.currPos > socket.bufLen-1: var res = socket.readIntoBuf(0'i32) if res <= 0: result = res c = socket.buffer[socket.currPos] else: when defined(ssl): if socket.isSSL: if not socket.sslHasPeekChar: result = SSLRead(socket.sslHandle, addr(socket.sslPeekChar), 1) socket.sslHasPeekChar = true c = socket.sslPeekChar return result = recv(socket.fd, addr(c), 1, MSG_PEEK) proc recvLine*(socket: Socket, line: var TaintedString, timeout = -1): bool {. tags: [ReadIOEffect, TimeEffect], deprecated.} = ## Receive a line of data from ``socket``. ## ## If a full line is received ``\r\L`` is not ## added to ``line``, however if solely ``\r\L`` is received then ``line`` ## will be set to it. ## ## ``True`` is returned if data is available. ``False`` suggests an ## error, EOS exceptions are not raised and ``False`` is simply returned ## instead. ## ## If the socket is disconnected, ``line`` will be set to ``""`` and ``True`` ## will be returned. ## ## A timeout can be specified in miliseconds, if data is not received within ## the specified time an ETimeout exception will be raised. ## ## **Deprecated since version 0.9.2**: This function has been deprecated in ## favour of readLine. template addNLIfEmpty(): stmt = if line.len == 0: line.add("\c\L") var waited = 0.0 setLen(line.string, 0) while true: var c: char discard waitFor(socket, waited, timeout, 1, "recvLine") var n = recv(socket, addr(c), 1) if n < 0: return elif n == 0: return true if c == '\r': discard waitFor(socket, waited, timeout, 1, "recvLine") n = peekChar(socket, c) if n > 0 and c == '\L': discard recv(socket, addr(c), 1) elif n <= 0: return false addNLIfEmpty() return true elif c == '\L': addNLIfEmpty() return true add(line.string, c) proc readLine*(socket: Socket, line: var TaintedString, timeout = -1) {. tags: [ReadIOEffect, TimeEffect].} = ## Reads a line of data from ``socket``. ## ## If a full line is read ``\r\L`` is not ## added to ``line``, however if solely ``\r\L`` is read then ``line`` ## will be set to it. ## ## If the socket is disconnected, ``line`` will be set to ``""``. ## ## An EOS exception will be raised in the case of a socket error. ## ## A timeout can be specified in miliseconds, if data is not received within ## the specified time an ETimeout exception will be raised. template addNLIfEmpty(): stmt = if line.len == 0: line.add("\c\L") var waited = 0.0 setLen(line.string, 0) while true: var c: char discard waitFor(socket, waited, timeout, 1, "readLine") var n = recv(socket, addr(c), 1) if n < 0: socket.raiseSocketError() elif n == 0: return if c == '\r': discard waitFor(socket, waited, timeout, 1, "readLine") n = peekChar(socket, c) if n > 0 and c == '\L': discard recv(socket, addr(c), 1) elif n <= 0: socket.raiseSocketError() addNLIfEmpty() return elif c == '\L': addNLIfEmpty() return add(line.string, c) proc recvLineAsync*(socket: Socket, line: var TaintedString): RecvLineResult {.tags: [ReadIOEffect], deprecated.} = ## Similar to ``recvLine`` but designed for non-blocking sockets. ## ## The values of the returned enum should be pretty self explanatory: ## ## * If a full line has been retrieved; ``RecvFullLine`` is returned. ## * If some data has been retrieved; ``RecvPartialLine`` is returned. ## * If the socket has been disconnected; ``RecvDisconnected`` is returned. ## * If call to ``recv`` failed; ``RecvFail`` is returned. ## ## **Deprecated since version 0.9.2**: This function has been deprecated in ## favour of readLineAsync. setLen(line.string, 0) while true: var c: char var n = recv(socket, addr(c), 1) if n < 0: return (if line.len == 0: RecvFail else: RecvPartialLine) elif n == 0: return (if line.len == 0: RecvDisconnected else: RecvPartialLine) if c == '\r': n = peekChar(socket, c) if n > 0 and c == '\L': discard recv(socket, addr(c), 1) elif n <= 0: return (if line.len == 0: RecvFail else: RecvPartialLine) return RecvFullLine elif c == '\L': return RecvFullLine add(line.string, c) proc readLineAsync*(socket: Socket, line: var TaintedString): ReadLineResult {.tags: [ReadIOEffect].} = ## Similar to ``recvLine`` but designed for non-blocking sockets. ## ## The values of the returned enum should be pretty self explanatory: ## ## * If a full line has been retrieved; ``ReadFullLine`` is returned. ## * If some data has been retrieved; ``ReadPartialLine`` is returned. ## * If the socket has been disconnected; ``ReadDisconnected`` is returned. ## * If no data could be retrieved; ``ReadNone`` is returned. ## * If call to ``recv`` failed; **an EOS exception is raised.** setLen(line.string, 0) template errorOrNone = socket.raiseSocketError(async = true) return ReadNone while true: var c: char var n = recv(socket, addr(c), 1) #echo(n) if n < 0: if line.len == 0: errorOrNone else: return ReadPartialLine elif n == 0: return (if line.len == 0: ReadDisconnected else: ReadPartialLine) if c == '\r': n = peekChar(socket, c) if n > 0 and c == '\L': discard recv(socket, addr(c), 1) elif n <= 0: if line.len == 0: errorOrNone else: return ReadPartialLine return ReadFullLine elif c == '\L': return ReadFullLine add(line.string, c) proc recv*(socket: Socket): TaintedString {.tags: [ReadIOEffect], deprecated.} = ## receives all the available data from the socket. ## Socket errors will result in an ``EOS`` error. ## If socket is not a connectionless socket and socket is not connected ## ``""`` will be returned. ## ## **Deprecated since version 0.9.2**: This function is not safe for use. const bufSize = 4000 result = newStringOfCap(bufSize).TaintedString var pos = 0 while true: var bytesRead = recv(socket, addr(string(result)[pos]), bufSize-1) if bytesRead == -1: raiseOSError(osLastError()) setLen(result.string, pos + bytesRead) if bytesRead != bufSize-1: break # increase capacity: setLen(result.string, result.string.len + bufSize) inc(pos, bytesRead) when false: var buf = newString(bufSize) result = TaintedString"" while true: var bytesRead = recv(socket, cstring(buf), bufSize-1) # Error if bytesRead == -1: OSError(osLastError()) buf[bytesRead] = '\0' # might not be necessary setLen(buf, bytesRead) add(result.string, buf) if bytesRead != bufSize-1: break {.push warning[deprecated]: off.} proc recvTimeout*(socket: Socket, timeout: int): TaintedString {. tags: [ReadIOEffect], deprecated.} = ## overloaded variant to support a ``timeout`` parameter, the ``timeout`` ## parameter specifies the amount of miliseconds to wait for data on the ## socket. ## ## **Deprecated since version 0.9.2**: This function is not safe for use. if socket.bufLen == 0: var s = @[socket] if s.select(timeout) != 1: raise newException(TimeoutError, "Call to recv() timed out.") return socket.recv {.pop.} proc recvAsync*(socket: Socket, s: var TaintedString): bool {. tags: [ReadIOEffect], deprecated.} = ## receives all the data from a non-blocking socket. If socket is non-blocking ## and there are no messages available, `False` will be returned. ## Other socket errors will result in an ``EOS`` error. ## If socket is not a connectionless socket and socket is not connected ## ``s`` will be set to ``""``. ## ## **Deprecated since version 0.9.2**: This function is not safe for use. const bufSize = 1000 # ensure bufSize capacity: setLen(s.string, bufSize) setLen(s.string, 0) var pos = 0 while true: var bytesRead = recv(socket, addr(string(s)[pos]), bufSize-1) when defined(ssl): if socket.isSSL: if bytesRead <= 0: var ret = SSLGetError(socket.sslHandle, bytesRead.cint) case ret of SSL_ERROR_ZERO_RETURN: raiseSslError("TLS/SSL connection failed to initiate, socket closed prematurely.") of SSL_ERROR_WANT_CONNECT, SSL_ERROR_WANT_ACCEPT: raiseSslError("Unexpected error occurred.") # This should just not happen. of SSL_ERROR_WANT_WRITE, SSL_ERROR_WANT_READ: return false of SSL_ERROR_WANT_X509_LOOKUP: raiseSslError("Function for x509 lookup has been called.") of SSL_ERROR_SYSCALL, SSL_ERROR_SSL: raiseSslError() else: raiseSslError("Unknown Error") if bytesRead == -1 and not (when defined(ssl): socket.isSSL else: false): let err = osLastError() when defined(windows): if err.int32 == WSAEWOULDBLOCK: return false else: raiseOSError(err) else: if err.int32 == EAGAIN or err.int32 == EWOULDBLOCK: return false else: raiseOSError(err) setLen(s.string, pos + bytesRead) if bytesRead != bufSize-1: break # increase capacity: setLen(s.string, s.string.len + bufSize) inc(pos, bytesRead) result = true proc recvFrom*(socket: Socket, data: var string, length: int, address: var string, port: var Port, flags = 0'i32): int {. tags: [ReadIOEffect].} = ## Receives data from ``socket``. This function should normally be used with ## connection-less sockets (UDP sockets). ## ## If an error occurs the return value will be ``-1``. Otherwise the return ## value will be the length of data received. ## ## **Warning:** This function does not yet have a buffered implementation, ## so when ``socket`` is buffered the non-buffered implementation will be ## used. Therefore if ``socket`` contains something in its buffer this ## function will make no effort to return it. # TODO: Buffered sockets data.setLen(length) var sockAddress: Sockaddr_in var addrLen = sizeof(sockAddress).SockLen result = recvfrom(socket.fd, cstring(data), length.cint, flags.cint, cast[ptr SockAddr](addr(sockAddress)), addr(addrLen)) if result != -1: data.setLen(result) address = $inet_ntoa(sockAddress.sin_addr) port = ntohs(sockAddress.sin_port).Port proc recvFromAsync*(socket: Socket, data: var string, length: int, address: var string, port: var Port, flags = 0'i32): bool {.tags: [ReadIOEffect].} = ## Variant of ``recvFrom`` for non-blocking sockets. Unlike ``recvFrom``, ## this function will raise an EOS error whenever a socket error occurs. ## ## If there is no data to be read from the socket ``False`` will be returned. result = true var callRes = recvFrom(socket, data, length, address, port, flags) if callRes < 0: let err = osLastError() when defined(windows): if err.int32 == WSAEWOULDBLOCK: return false else: raiseOSError(err) else: if err.int32 == EAGAIN or err.int32 == EWOULDBLOCK: return false else: raiseOSError(err) proc skip*(socket: Socket) {.tags: [ReadIOEffect], deprecated.} = ## skips all the data that is pending for the socket ## ## **Deprecated since version 0.9.2**: This function is not safe for use. const bufSize = 1000 var buf = alloc(bufSize) while recv(socket, buf, bufSize) == bufSize: discard dealloc(buf) proc skip*(socket: Socket, size: int, timeout = -1) = ## Skips ``size`` amount of bytes. ## ## An optional timeout can be specified in miliseconds, if skipping the ## bytes takes longer than specified an ETimeout exception will be raised. ## ## Returns the number of skipped bytes. var waited = 0.0 var dummy = alloc(size) var bytesSkipped = 0 while bytesSkipped != size: let avail = waitFor(socket, waited, timeout, size-bytesSkipped, "skip") bytesSkipped += recv(socket, dummy, avail) dealloc(dummy) proc send*(socket: Socket, data: pointer, size: int): int {. tags: [WriteIOEffect].} = ## sends data to a socket. when defined(ssl): if socket.isSSL: return SSLWrite(socket.sslHandle, cast[cstring](data), size) when defined(windows) or defined(macosx): result = send(socket.fd, data, size.cint, 0'i32) else: when defined(solaris): const MSG_NOSIGNAL = 0 result = send(socket.fd, data, size, int32(MSG_NOSIGNAL)) proc send*(socket: Socket, data: string) {.tags: [WriteIOEffect].} = ## sends data to a socket. if socket.nonblocking: raise newException(ValueError, "This function cannot be used on non-blocking sockets.") let sent = send(socket, cstring(data), data.len) if sent < 0: when defined(ssl): if socket.isSSL: raiseSslError() raiseOSError(osLastError()) if sent != data.len: raise newException(OSError, "Could not send all data.") proc sendAsync*(socket: Socket, data: string): int {.tags: [WriteIOEffect].} = ## sends data to a non-blocking socket. ## Returns ``0`` if no data could be sent, if data has been sent ## returns the amount of bytes of ``data`` that was successfully sent. This ## number may not always be the length of ``data`` but typically is. ## ## An EOS (or ESSL if socket is an SSL socket) exception is raised if an error ## occurs. result = send(socket, cstring(data), data.len) when defined(ssl): if socket.isSSL: if result <= 0: let ret = SSLGetError(socket.sslHandle, result.cint) case ret of SSL_ERROR_ZERO_RETURN: raiseSslError("TLS/SSL connection failed to initiate, socket closed prematurely.") of SSL_ERROR_WANT_CONNECT, SSL_ERROR_WANT_ACCEPT: raiseSslError("Unexpected error occurred.") # This should just not happen. of SSL_ERROR_WANT_WRITE, SSL_ERROR_WANT_READ: return 0 of SSL_ERROR_WANT_X509_LOOKUP: raiseSslError("Function for x509 lookup has been called.") of SSL_ERROR_SYSCALL, SSL_ERROR_SSL: raiseSslError() else: raiseSslError("Unknown Error") else: return if result == -1: let err = osLastError() when defined(windows): if err.int32 == WSAEINPROGRESS: return 0 else: raiseOSError(err) else: if err.int32 == EAGAIN or err.int32 == EWOULDBLOCK: return 0 else: raiseOSError(err) proc trySend*(socket: Socket, data: string): bool {.tags: [WriteIOEffect].} = ## safe alternative to ``send``. Does not raise an EOS when an error occurs, ## and instead returns ``false`` on failure. result = send(socket, cstring(data), data.len) == data.len proc sendTo*(socket: Socket, address: string, port: Port, data: pointer, size: int, af: Domain = AF_INET, flags = 0'i32): int {. tags: [WriteIOEffect].} = ## low-level sendTo proc. This proc sends ``data`` to the specified ``address``, ## which may be an IP address or a hostname, if a hostname is specified ## this function will try each IP of that hostname. ## ## **Note:** This proc is not available for SSL sockets. var hints: AddrInfo var aiList: ptr AddrInfo = nil hints.ai_family = toInt(af) hints.ai_socktype = toInt(SOCK_STREAM) hints.ai_protocol = toInt(IPPROTO_TCP) gaiNim(address, port, hints, aiList) # try all possibilities: var success = false var it = aiList while it != nil: result = sendto(socket.fd, data, size.cint, flags.cint, it.ai_addr, it.ai_addrlen.SockLen) if result != -1'i32: success = true break it = it.ai_next freeaddrinfo(aiList) proc sendTo*(socket: Socket, address: string, port: Port, data: string): int {.tags: [WriteIOEffect].} = ## Friendlier version of the low-level ``sendTo``. result = socket.sendTo(address, port, cstring(data), data.len) when defined(Windows): const IOCPARM_MASK = 127 IOC_IN = int(-2147483648) FIONBIO = IOC_IN.int32 or ((sizeof(int32) and IOCPARM_MASK) shl 16) or (102 shl 8) or 126 proc ioctlsocket(s: SocketHandle, cmd: clong, argptr: ptr clong): cint {. stdcall, importc:"ioctlsocket", dynlib: "ws2_32.dll".} proc setBlocking(s: Socket, blocking: bool) = when defined(Windows): var mode = clong(ord(not blocking)) # 1 for non-blocking, 0 for blocking if ioctlsocket(s.fd, FIONBIO, addr(mode)) == -1: raiseOSError(osLastError()) else: # BSD sockets var x: int = fcntl(s.fd, F_GETFL, 0) if x == -1: raiseOSError(osLastError()) else: var mode = if blocking: x and not O_NONBLOCK else: x or O_NONBLOCK if fcntl(s.fd, F_SETFL, mode) == -1: raiseOSError(osLastError()) s.nonblocking = not blocking discard """ proc setReuseAddr*(s: Socket) = var blah: int = 1 var mode = SO_REUSEADDR if setsockopt(s.fd, SOL_SOCKET, mode, addr blah, TSOcklen(sizeof(int))) == -1: raiseOSError(osLastError()) """ proc connect*(socket: Socket, address: string, port = Port(0), timeout: int, af: Domain = AF_INET) {.tags: [ReadIOEffect, WriteIOEffect].} = ## Connects to server as specified by ``address`` on port specified by ``port``. ## ## The ``timeout`` paremeter specifies the time in miliseconds to allow for ## the connection to the server to be made. let originalStatus = not socket.nonblocking socket.setBlocking(false) socket.connectAsync(address, port, af) var s: seq[Socket] = @[socket] if selectWrite(s, timeout) != 1: raise newException(TimeoutError, "Call to 'connect' timed out.") else: when defined(ssl): if socket.isSSL: socket.setBlocking(true) doAssert socket.handshake() socket.setBlocking(originalStatus) proc isSSL*(socket: Socket): bool = return socket.isSSL ## Determines whether ``socket`` is a SSL socket. proc getFD*(socket: Socket): SocketHandle = return socket.fd ## Returns the socket's file descriptor proc isBlocking*(socket: Socket): bool = not socket.nonblocking ## Determines whether ``socket`` is blocking. when defined(Windows): var wsa: WSAData if wsaStartup(0x0101'i16, addr wsa) != 0: raiseOSError(osLastError())