summary refs log tree commit diff stats
path: root/lib/pure/sockets.nim
diff options
context:
space:
mode:
authorAndreas Rumpf <andreas@andreas-desktop>2010-01-13 00:38:39 +0100
committerAndreas Rumpf <andreas@andreas-desktop>2010-01-13 00:38:39 +0100
commitfb2ae12c4969d41fbb0857a0b9121ad947807a1e (patch)
tree90a268cc659d6c5c28408976a368cc503720fe1f /lib/pure/sockets.nim
parent55e900bba2be06cf91789e749d9fc31f017a0dd0 (diff)
downloadNim-fb2ae12c4969d41fbb0857a0b9121ad947807a1e.tar.gz
sockets module complete
Diffstat (limited to 'lib/pure/sockets.nim')
-rwxr-xr-xlib/pure/sockets.nim381
1 files changed, 280 insertions, 101 deletions
diff --git a/lib/pure/sockets.nim b/lib/pure/sockets.nim
index be2e4434a..9419b82b9 100755
--- a/lib/pure/sockets.nim
+++ b/lib/pure/sockets.nim
@@ -7,26 +7,7 @@
 #    distribution, for details about the copyright.
 #
 
-## This module implements a simple portable type-safe sockets layer. **Note**:
-## This module is incomplete and probably buggy. It does not work on Windows
-## yet. Help if you are interested.
-
-# TODO:
-# getservbyname(name, proto)
-# getservbyport(port, proto)
-# gethostbyname(name)
-# gethostbyaddr(addr)
-# shutdown(sock, how)
-# connect(sock, address, port)
-# select({ socket, ... }, timeout)
-
-# sendto
-# recvfrom
-
-# bind(socket, address, port)
-
-# getsockopt(socket, level, optname)
-# setsockopt(socket, level, optname, value)
+## This module implements a simple portable type-safe sockets layer.
 
 import os
 
@@ -35,6 +16,8 @@ when defined(Windows):
 else:
   import posix
 
+# Note: The enumerations are mapped to Window's constants.
+
 type
   TSocket* = distinct cint ## socket type
   TPort* = distinct int16  ## port type
@@ -42,87 +25,149 @@ type
   TDomain* = 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).
-    AF_INET,        ## for network protocol IPv4 or
-    AF_INET6        ## for network protocol IPv6.
-
-  TType* = enum     ## second argument to `socket` proc
-    SOCK_STREAM,    ## reliable stream-oriented service or Stream Sockets
-    SOCK_DGRAM,     ## datagram service or Datagram Sockets
-    SOCK_SEQPACKET, ## reliable sequenced packet service, or
-    SOCK_RAW        ## raw protocols atop the network layer.
-
-  TProtocol* = enum ## third argument to `socket` proc
-    IPPROTO_TCP,    ## Transmission control protocol. 
-    IPPROTO_UDP,    ## User datagram protocol.
-    IPPROTO_IP,     ## Internet protocol. 
-    IPPROTO_IPV6,   ## Internet Protocol Version 6. 
-    IPPROTO_RAW,    ## Raw IP Packets Protocol. 
-    IPPROTO_ICMP    ## Control message protocol. 
+    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.
+
+  TType* = 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, or
+
+  TProtocol* = 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.
+
+  TServent* {.pure, final.} = object ## information about a service
+    name*: string
+    aliases*: seq[string]
+    port*: TPort
+    proto*: string
+
+  Thostent* {.pure, final.} = object ## information about a given host
+    name*: string
+    aliases*: seq[string]
+    addrtype*: TDomain
+    length*: int
+    addrList*: seq[string]
 
 const
   InvalidSocket* = TSocket(-1'i32) ## invalid socket number
 
 proc `==`*(a, b: TSocket): bool {.borrow.}
+  ## ``==`` for sockets. 
+
 proc `==`*(a, b: TPort): bool {.borrow.}
+  ## ``==`` for ports.
 
-proc `$`* (p: TPort): string = result = $ze(int16(p))
-
-proc ToInt(domain: TDomain): cint =
-  case domain
-  of AF_UNIX:        result = posix.AF_UNIX
-  of AF_INET:        result = posix.AF_INET
-  of AF_INET6:       result = posix.AF_INET6
-
-proc ToInt(typ: TType): 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
-
-proc ToInt(p: TProtocol): 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
-
-proc socket*(domain: TDomain = AF_INET6, typ: TType = SOCK_STREAM,
+proc `$`*(p: TPort): string = 
+  ## returns the port number as a string
+  result = $ze(int16(p))
+
+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: TDomain): 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: nil
+
+  proc ToInt(typ: TType): 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: nil
+
+  proc ToInt(p: TProtocol): 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: nil
+
+proc socket*(domain: TDomain = AF_INET, typ: TType = SOCK_STREAM,
              protocol: TProtocol = IPPROTO_TCP): TSocket =
   ## creates a new socket; returns `InvalidSocket` if an error occurs.  
-  result = TSocket(posix.socket(ToInt(domain), ToInt(typ), ToInt(protocol)))
+  when defined(Windows):
+    result = TSocket(winlean.socket(ord(domain), ord(typ), ord(protocol)))
+  else:
+    result = TSocket(posix.socket(ToInt(domain), ToInt(typ), ToInt(protocol)))
 
 proc listen*(socket: TSocket, attempts = 5) =
   ## listens to socket.
-  if posix.listen(cint(socket), cint(attempts)) < 0'i32: OSError()
+  if listen(cint(socket), cint(attempts)) < 0'i32: OSError()
 
 proc bindAddr*(socket: TSocket, port = TPort(0)) =
+  ## binds a port number to a socket.
   var name: Tsockaddr_in
-  name.sin_family = posix.AF_INET
-  name.sin_port = htons(int16(port))
-  name.sin_addr.s_addr = htonl(INADDR_ANY)
+  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(cint(socket), cast[ptr TSockAddr](addr(name)),
                 sizeof(name)) < 0'i32:
     OSError()
   
 proc getSockName*(socket: TSocket): TPort = 
+  ## returns the socket's associated port number.
   var name: Tsockaddr_in
-  name.sin_family = posix.AF_INET
+  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: cint = sizeof(name)
   if getsockname(cint(socket), cast[ptr TSockAddr](addr(name)),
                  addr(namelen)) == -1'i32:
     OSError()
-  result = TPort(ntohs(name.sin_port))
+  result = TPort(sockets.ntohs(name.sin_port))
 
 proc accept*(server: TSocket): TSocket =
   ## waits for a client and returns its socket
   var client: Tsockaddr_in
-  var clientLen: TsockLen = sizeof(client)
+  var clientLen: cint = sizeof(client)
   result = TSocket(accept(cint(server), cast[ptr TSockAddr](addr(client)),
                           addr(clientLen)))
 
@@ -133,8 +178,163 @@ proc close*(socket: TSocket) =
   else:
     discard posix.close(cint(socket))
 
+proc getServByName*(name, proto: string): TServent =
+  ## well-known getservbyname proc.
+  when defined(Windows):
+    var s = winlean.getservbyname(name, proto)
+  else:
+    var s = posix.getservbyname(name, proto)
+  if s == nil: OSError()
+  result.name = $s.s_name
+  result.aliases = cstringArrayToSeq(s.s_aliases)
+  result.port = TPort(s.s_port)
+  result.proto = $s.s_proto
+  
+proc getServByPort*(port: TPort, proto: string): TServent = 
+  ## well-known getservbyport proc.
+  when defined(Windows):
+    var s = winlean.getservbyport(ze(int16(port)), proto)
+  else:
+    var s = posix.getservbyport(ze(int16(port)), proto)
+  if s == nil: OSError()
+  result.name = $s.s_name
+  result.aliases = cstringArrayToSeq(s.s_aliases)
+  result.port = TPort(s.s_port)
+  result.proto = $s.s_proto
+
+proc getHostByName*(name: string): THostEnt = 
+  ## well-known gethostbyname proc.
+  when defined(Windows):
+    var s = winlean.gethostbyname(name)
+  else:
+    var s = posix.gethostbyname(name)
+  if s == nil: OSError()
+  result.name = $s.h_name
+  result.aliases = cstringArrayToSeq(s.h_aliases)
+  when defined(windows): 
+    result.addrType = TDomain(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:
+      OSError("unknown h_addrtype")
+  result.addrList = cstringArrayToSeq(s.h_addr_list)
+  result.length = int(s.h_length)
+
+proc getSockOptInt*(socket: TSocket, level, optname: int): int = 
+  ## getsockopt for integer options.
+  var res: cint
+  var size: cint = sizeof(res)
+  if getsockopt(cint(socket), cint(level), cint(optname), 
+                addr(res), addr(size)) < 0'i32:
+    OSError()
+  result = int(res)
+
+proc setSockOptInt*(socket: TSocket, level, optname, optval: int) =
+  ## setsockopt for integer options.
+  var value = cint(optval)
+  if setsockopt(cint(socket), cint(level), cint(optname), addr(value),  
+                sizeof(value)) < 0'i32:
+    OSError()
+
+proc connect*(socket: TSocket, name: string, port = TPort(0), 
+              af: TDomain = AF_INET) =
+  ## well-known connect operation. Already does ``htons`` on the port number,
+  ## so you shouldn't do it.
+  var s: TSockAddrIn
+  s.sin_addr.s_addr = inet_addr(name)
+  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(cint(socket), cast[ptr TSockAddr](addr(s)), sizeof(s)) < 0'i32:
+    OSError()
+
+#proc recvfrom*(s: TWinSocket, buf: cstring, len, flags: cint, 
+#               fromm: ptr TSockAddr, fromlen: ptr cint): cint 
+
+#proc sendto*(s: TWinSocket, buf: cstring, len, flags: cint,
+#             to: ptr TSockAddr, tolen: cint): cint
+
+proc createFdSet(fd: var TFdSet, s: seq[TSocket], m: var int) = 
+  FD_ZERO(fd)
+  for i in items(s): 
+    m = max(m, int(i))
+    FD_SET(cint(i), fd)
+   
+proc pruneSocketSet(s: var seq[TSocket], fd: var TFdSet) = 
+  var i = 0
+  var L = s.len
+  while i < L:
+    if FD_ISSET(cint(s[i]), fd) != 0'i32:
+      s[i] = s[L-1]
+      dec(L)
+    else:
+      inc(i)
+  setLen(s, L)
+
+proc select*(readfds, writefds, exceptfds: var seq[TSocket], 
+             timeout = 500): int = 
+  ## select with a sensible Nimrod interface. `timeout` is in miliseconds.
+  var tv: TTimeVal
+  tv.tv_sec = 0
+  tv.tv_usec = timeout * 1000
+  
+  var rd, wr, ex: TFdSet
+  var m = 0
+  createFdSet((rd), readfds, m)
+  createFdSet((wr), writefds, m)
+  createFdSet((ex), exceptfds, m)
+  
+  result = int(select(cint(m), addr(rd), addr(wr), addr(ex), addr(tv)))
+  
+  pruneSocketSet(readfds, (rd))
+  pruneSocketSet(writefds, (wr))
+  pruneSocketSet(exceptfds, (ex))
+
+proc select*(readfds, writefds: var seq[TSocket], 
+             timeout = 500): int = 
+  ## select with a sensible Nimrod interface. `timeout` is in miliseconds.
+  var tv: TTimeVal
+  tv.tv_sec = 0
+  tv.tv_usec = timeout * 1000
+  
+  var rd, wr: TFdSet
+  var m = 0
+  createFdSet((rd), readfds, m)
+  createFdSet((wr), writefds, m)
+  
+  result = int(select(cint(m), addr(rd), addr(wr), nil, addr(tv)))
+  
+  pruneSocketSet(readfds, (rd))
+  pruneSocketSet(writefds, (wr))
+
+
+proc select*(readfds: var seq[TSocket], timeout = 500): int = 
+  ## select with a sensible Nimrod interface. `timeout` is in miliseconds.
+  var tv: TTimeVal
+  tv.tv_sec = 0
+  tv.tv_usec = timeout * 1000
+  
+  var rd: TFdSet
+  var m = 0
+  createFdSet((rd), readfds, m)
+  
+  result = int(select(cint(m), addr(rd), nil, nil, addr(tv)))
+  
+  pruneSocketSet(readfds, (rd))
+
+
 proc recvLine*(socket: TSocket, line: var string): bool =
-  ## returns false if no further data is available.
+  ## returns false if no further data is available. `line` must be initalized
+  ## and not nil!
   setLen(line, 0)
   while true:
     var c: char
@@ -150,11 +350,11 @@ proc recvLine*(socket: TSocket, line: var string): bool =
     add(line, c)
 
 proc recv*(socket: TSocket, data: pointer, size: int): int =
-  ## receive data from a socket
-  result = posix.recv(cint(socket), data, size, 0'i32)
+  ## receives data from a socket
+  result = recv(cint(socket), data, size, 0'i32)
 
 proc recv*(socket: TSocket): string =
-  ## receive all the data from the socket
+  ## receives all the data from the socket
   const bufSize = 200
   var buf = newString(bufSize)
   result = ""
@@ -173,36 +373,15 @@ proc skip*(socket: TSocket) =
   dealloc(buf)
 
 proc send*(socket: TSocket, data: pointer, size: int): int =
-  result = posix.send(cint(socket), data, size, 0'i32)
+  ## sends data to a socket.
+  result = send(cint(socket), data, size, 0'i32)
 
 proc send*(socket: TSocket, data: string) =
+  ## sends data to a socket.
   if send(socket, cstring(data), data.len) != data.len: OSError()
 
-proc ntohl*(x: int32): int32 = 
-  ## Convert 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 =
-  ## Convert 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)
+when defined(Windows):
+  var wsa: TWSADATA
+  if WSAStartup(0x0101'i16, wsa) != 0: OSError()
 
-proc htonl*(x: int32): int32 =
-  ## Convert 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 =
-  ## Convert 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)