# Copyright 2005-2007 Kevin Reid, under the terms of the MIT X license # found at http://www.opensource.org/licenses/mit-license.html ................ This is the second try at network access. From what I've seen, there are two kinds of Internet-protocol network APIs: 'BSD sockets', and ones with limitations. ' Therefore, I have decided to provide a nearly[1]-unaltered socket interface for E-on-CL, and add shortcuts later. If I understand correctly, a socket 'by itself' provides no communication - bind(), connect(), or sendto() must be used. Therefore, my taming plan so far is to make the sockaddr-equivalent carry the authority for connection, and creating sockets an unprivileged operation. http://www.opengroup.org/onlinepubs/009695399/functions/xsh_chap02_10.html#tag_02_10 [1] By "nearly", I mean that there shall be socket objects, and operations corresponding to the standard functions, etc; so the interface does not accidentally prevent one from, say, bind()ing a TCP socket. There will still be taming. ? def ALL := EIO.getALL(); null ? def makeSocket := # value: ? pragma.enable("dot-props") > def socket := makeSocket(makeSocket::internet, makeSocket::stream) # value: ? def localHTTP := getSocketPeerRef("localhost", "80") # value: ? socket.connect(def test extends localHTTP {}) # problem: the "__main$test" doesn't coerce to a In the ideal model, these *should* work, and they do in SBCL (XXX make it possible to test this), but many CL implementations don't offer a socket interface with true unconnected sockets. ' x ? def outs := socket.getOut() x # value: -> x x ? outs.available() x # value: 0 x x ? def ins := socket.getIn() x # value: <- x x ? ins.available() x # value: 0 ? [def connectResult := socket.connect(localHTTP), socket] # value: [, ] In order to preserve E semantics by default: - Sockets are set non-blocking on creation. - connect() is not performed within the turn. ? interp.waitAtTop(connectResult) ? connectResult # value: true ? socket # value: ? def outs := socket.getOut() # value: -> ? def ins := socket.getIn() # value: <- this is "GET / HTTP/1.0\r\n\r\n" ? outs.reserve() <- resolve([71, 69, 84, 32, 47, 32, 72, 84, 84, 80, 47, 49, 46, 48, 13, 10, 13, 10]) # value: ? outs.flush() ? interp.waitAtTop(when (def response := ins.takeAtMost(17)) -> { > # XXX improper: not guaranteed to read 17 > print(response) > }) this is "HTTP/1.1 200 OK\r\n" # stdout: [72, 84, 84, 80, 47, 49, 46, 49, 32, 50, 48, 48, 32, 79, 75, 13, 10] ? ins.close() > outs.reserve() <- resolve(null) > interp.waitAtTop(outs.terminates()) ? [outs.terminates()] # value: [null] --- Listening socket ? pragma.enable("dot-props") > def listener := makeSocket(makeSocket::internet, makeSocket::stream) # value: ? def local := getSocketLocalRef(null, "35037") # value: ? def loop := getSocketPeerRef("localhost", "35037") # value: XXX unspecified-port local refs - and document that they reveal a port number XXX return value is null-or-broken - test and document ? [def r := listener."bind"(local), listener] # value: [, ] ? interp.waitAtTop(r) ? [r, listener] # value: [null, ] XXX return value is null-or-broken - test and document XXX provide an interface for accepting only some of the time, so apps can not-accept when they're flooded already ? listener.listen(null, def handler(serverEnd) :void { XXX address return from socket accept() > print("Accepted") > EIO.join(serverEnd.getIn(), serverEnd.getOut()) > }) no portable way to check whether a socket is listening x ? listener x # value: ? pragma.enable("dot-props") > def client := makeSocket(makeSocket::internet, makeSocket::stream) > client.connect(loop) # value: ? interp.waitAtTop(0 <- add(0) <- add(0)) # XXX arbitrary delaying - should be a timeout instead # stdout: Accepted ? def outs := client.getOut(); def ins := client.getIn(); null ? outs.reserve() <- resolve([1, 2, 3]); outs.reserve() <- resolve(null) # value: ? interp.waitAtTop(when (def response := ins.takeAtMost(ALL)) -> { > # XXX improper: not guaranteed to read everything > print(response) > }) # stdout: [1, 2, 3] ? interp.waitAtTop(when (def response := ins.takeAtMost(ALL)) -> { > print(response) > }) # stdout: null Closing, and connect() errors or lack thereof ? listener.close(throw) ? def closedCheck := makeSocket(makeSocket::internet, makeSocket::stream) > interp.waitAtTop(def r := closedCheck.connect(loop)) ? r # value: true ? interp.waitAtTop(def r := closedCheck.getIn().takeAtMost(1)) ? r # value: XXX Design change: make making sockets privileged. you can run out of sockets, and it would be nice not to have it be a vat-killing error; we might want to allow listen without bind to Just Work; probably others I haven't thought of. generally, less error-prone to implement. ' XXX bind()ing of connect sockets XXX errors: name lookup failures connect failures address already bound XXX reuse-addr XXX general socket options interface XXX check that nothing exposes synchronous changes XXX consequences of getOut/getIn on a listening socket XXX listen on already listening/connected XXX bind on already bound XXX connect on already connected/listening XXX general support for null addr-info input XXX listen without bind must fail since no authority to recieve outside data has been given; support a totally-unspecific local ref to enable this