pnacl: remove target and related projects
[nit.git] / lib / socket / socket.nit
1 # This file is part of NIT ( http://www.nitlanguage.org ).
2 #
3 # Copyright 2013 Matthieu Lucas <lucasmatthieu@gmail.com>
4 #
5 # Licensed under the Apache License, Version 2.0 (the "License");
6 # you may not use this file except in compliance with the License.
7 # You may obtain a copy of the License at
8 #
9 # http://www.apache.org/licenses/LICENSE-2.0
10 #
11 # Unless required by applicable law or agreed to in writing, software
12 # distributed under the License is distributed on an "AS IS" BASIS,
13 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 # See the License for the specific language governing permissions and
15 # limitations under the License.
16
17 # Socket services
18 module socket
19
20 private import socket_c
21 intrude import core::stream
22
23 # A general Socket, either TCP or UDP
24 abstract class Socket
25
26 # Underlying C socket
27 private var native: NativeSocket is noinit
28
29 # Is this socket closed?
30 var closed = false
31
32 # Set whether calls to `accept` are blocking
33 fun blocking=(value: Bool)
34 do
35 # We use the opposite from the native version as the native API
36 # is closer to the C API. In the Nity API, we use a positive version
37 # of the name.
38 native.non_blocking = not value
39 end
40 end
41
42 # A general TCP socket, either a `TCPStream` or a `TCPServer`
43 abstract class TCPSocket
44 super Socket
45
46 # Port used by the socket
47 var port: Int
48
49 # IPv4 address to which `self` is connected
50 #
51 # Formatted as xxx.xxx.xxx.xxx.
52 var address: String is noinit
53 end
54
55 # Simple communication stream with a remote socket
56 class TCPStream
57 super TCPSocket
58 super BufferedReader
59 super Writer
60 super PollableReader
61
62 # Real canonical name of the host to which `self` is connected
63 var host: String
64
65 private var addrin: NativeSocketAddrIn is noinit
66
67 redef var end_reached = false
68
69 # TODO make init private
70
71 # Creates a socket connection to host `host` on port `port`
72 init connect(host: String, port: Int)
73 do
74 _buffer = new CString(1024)
75 _buffer_pos = 0
76 native = new NativeSocket.socket(new NativeSocketAddressFamilies.af_inet,
77 new NativeSocketTypes.sock_stream, new NativeSocketProtocolFamilies.pf_null)
78 if native.address_is_null then
79 end_reached = true
80 closed = true
81 return
82 end
83 if not native.setsockopt(new NativeSocketOptLevels.socket, new NativeSocketOptNames.reuseaddr, 1) then
84 end_reached = true
85 closed = true
86 return
87 end
88
89 var hostent = sys.gethostbyname(host.to_cstring)
90 if hostent.address_is_null then
91 # Error in name lookup
92 last_error = new IOError.from_h_errno
93
94 closed = true
95 end_reached = true
96
97 return
98 end
99
100 addrin = new NativeSocketAddrIn
101 addrin.fill_from_hostent hostent
102 addrin.port = port
103
104 address = addrin.address.to_s
105 init(addrin.port, hostent.h_name.to_s)
106
107 closed = not internal_connect
108 end_reached = closed
109 if closed then
110 # Connection failed
111 last_error = new IOError.from_errno
112 end
113
114 prepare_buffer(1024)
115 end
116
117 # Creates a client socket, this is meant to be used by accept only
118 private init server_side(h: SocketAcceptResult)
119 do
120 native = h.socket
121 addrin = h.addr_in
122 address = addrin.address.to_s
123
124 init(addrin.port, address)
125
126 prepare_buffer(1024)
127 end
128
129 redef fun poll_in do return ready_to_read(0)
130
131 # Returns an array containing an enum of the events ready to be read
132 #
133 # event_types : Combination of several event types to watch
134 #
135 # timeout : Time in milliseconds before stopping listening for events on this socket
136 private fun pollin(event_types: Array[NativeSocketPollValues], timeout: Int): Array[NativeSocketPollValues] do
137 if end_reached then return new Array[NativeSocketPollValues]
138 return native.socket_poll(new PollFD(native.descriptor, event_types), timeout)
139 end
140
141 # Easier use of pollin to check for something to read on all channels of any priority
142 #
143 # timeout : Time in milliseconds before stopping to wait for events
144 fun ready_to_read(timeout: Int): Bool
145 do
146 if _buffer_pos < _buffer_length then return true
147 if end_reached then return false
148 var events = [new NativeSocketPollValues.pollin]
149 return pollin(events, timeout).length != 0
150 end
151
152 # Is this socket still connected?
153 fun connected: Bool
154 do
155 if closed then return false
156 if native.poll_hup_err == 0 then
157 return true
158 else
159 closed = true
160 return false
161 end
162 end
163
164 redef fun is_writable do return not end_reached
165
166 # Establishes a connection to socket addrin
167 #
168 # REQUIRES : not self.end_reached
169 private fun internal_connect: Bool
170 do
171 assert not closed
172 return native.connect(addrin) >= 0
173 end
174
175 # If socket.end_reached, nothing will happen
176 redef fun write(msg)
177 do
178 if closed then return
179 native.write(msg.to_cstring, msg.length)
180 end
181
182 redef fun write_byte(value)
183 do
184 if closed then return
185 native.write_byte value
186 end
187
188 redef fun write_bytes_from_cstring(ns, len) do
189 if closed then return
190 native.write(ns, len)
191 end
192
193 fun write_ln(msg: Text)
194 do
195 if closed then return
196 write msg.to_s
197 write "\n"
198 end
199
200 redef fun fill_buffer
201 do
202 if not connected then return
203
204 var read = native.read(_buffer, _buffer_capacity)
205 if read == -1 then
206 close
207 end_reached = true
208 end
209
210 _buffer_length = read
211 _buffer_pos = 0
212 end
213
214 fun enlarge(len: Int) do
215 if _buffer_capacity >= len then return
216 _buffer_capacity = len
217
218 var ns = new CString(_buffer_capacity)
219 _buffer.copy_to(ns, _buffer_length - _buffer_pos, _buffer_pos, 0)
220 _buffer = ns
221 end
222
223 redef fun close
224 do
225 if closed then return
226 if native.close >= 0 then
227 closed = true
228 end_reached = true
229 end
230 end
231
232 # Send the data present in the socket buffer
233 fun flush
234 do
235 if not native.setsockopt(new NativeSocketOptLevels.tcp, new NativeSocketOptNames.tcp_nodelay, 1) or
236 not native.setsockopt(new NativeSocketOptLevels.tcp, new NativeSocketOptNames.tcp_nodelay, 0) then
237 closed = true
238 end
239 end
240 end
241
242 # A socket listening on a given `port` for incomming connections
243 #
244 # Create streams to communicate with clients using `accept`.
245 class TCPServer
246 super TCPSocket
247
248 private var addrin: NativeSocketAddrIn is noinit
249
250 # Create and bind a listening server socket on port `port`
251 init
252 do
253 native = new NativeSocket.socket(new NativeSocketAddressFamilies.af_inet,
254 new NativeSocketTypes.sock_stream, new NativeSocketProtocolFamilies.pf_null)
255 assert not native.address_is_null
256 if not native.setsockopt(new NativeSocketOptLevels.socket, new NativeSocketOptNames.reuseaddr, 1) then
257 closed = true
258 return
259 end
260
261 addrin = new NativeSocketAddrIn
262 addrin.family = new NativeSocketAddressFamilies.af_inet
263 addrin.port = port
264 addrin.address_any
265
266 address = addrin.address.to_s
267
268 # Bind it
269 closed = not bind
270 end
271
272 # Associates the socket to a local address and port
273 #
274 # Returns whether the socket has been be bound.
275 private fun bind: Bool do
276 return native.bind(addrin) >= 0
277 end
278
279 # Sets the socket as ready to accept incoming connections, `size` is the maximum number of queued clients
280 #
281 # Returns `true` if the socket could be set, `false` otherwise
282 fun listen(size: Int): Bool do
283 return native.listen(size) >= 0
284 end
285
286 # Accepts an incoming connection from a client
287 #
288 # Create and return a new socket to the client. May return null if not
289 # `blocking` and there's no waiting clients, or upon an interruption
290 # (whether `blocking` or not).
291 #
292 # Require: not closed
293 fun accept: nullable TCPStream
294 do
295 assert not closed
296 var native = native.accept
297 if native == null then return null
298 return new TCPStream.server_side(native)
299 end
300
301 # Close this socket
302 fun close
303 do
304 # FIXME unify with `SocketStream::close` when we can use qualified names
305
306 if closed then return
307 if native.close >= 0 then
308 closed = true
309 end
310 end
311 end
312
313 # A simple set of sockets used by `SocketObserver`
314 class SocketSet
315 private var native = new NativeSocketSet
316
317 init do clear
318
319 # Add `socket` to this set
320 fun add(socket: Socket) do native.set(socket.native)
321
322 # Remove `socket` from this set
323 fun remove(socket: Socket) do native.clear(socket.native)
324
325 # Does this set has `socket`?
326 fun has(socket: Socket): Bool do return native.is_set(socket.native)
327
328 # Clear all sockets from this set
329 fun clear do native.zero
330 end
331
332 # Service class to manage calls to `select`
333 class SocketObserver
334 private var native = new NativeSocketObserver
335
336 var read_set: nullable SocketSet = null
337
338 var write_set: nullable SocketSet = null
339
340 var except_set: nullable SocketSet = null
341
342 init(read: Bool, write: Bool, except: Bool)
343 is old_style_init do
344 if read then read_set = new SocketSet
345 if write then write_set = new SocketSet
346 if except then except_set = new SocketSet
347 end
348
349 fun select(max: Socket, seconds: Int, microseconds: Int): Bool
350 do
351 # FIXME this implementation (see the call to nullable attributes below) and
352 # `NativeSockectObserver::select` is not stable.
353
354 var timeval = new NativeTimeval(seconds, microseconds)
355 return native.select(max.native, read_set.native, write_set.native, except_set.native, timeval) > 0
356 end
357 end
358
359 # Socket over UDP, sends and receive data without the need for a connection
360 class UDPSocket
361 super Socket
362
363 # Last error raised by this socket
364 var error: nullable Error = null
365
366 init do native = new NativeSocket.socket(
367 new NativeSocketAddressFamilies.af_inet,
368 new NativeSocketTypes.sock_dgram,
369 new NativeSocketProtocolFamilies.pf_null)
370
371 # Bind this socket to an `address`, on `port` (to all addresses if `null`)
372 #
373 # On error, sets `error` appropriately.
374 fun bind(address: nullable Text, port: Int)
375 do
376 var addr_in = new NativeSocketAddrIn
377 addr_in.port = port
378 if address != null then
379 # FIXME replace all use of gethostbyname with something not obsolete
380 var hostent = sys.gethostbyname(address.to_cstring)
381 if hostent.address_is_null then
382 error = new IOError.from_h_errno
383 addr_in.free
384 return
385 end
386
387 addr_in.fill_from_hostent hostent
388 else
389 addr_in.family = new NativeSocketAddressFamilies.af_inet
390 addr_in.address_any
391 end
392
393 if native.bind(addr_in) != 0 then error = new IOError.from_errno
394
395 addr_in.free
396 end
397
398 # Receive `length` bytes of data from any sender
399 #
400 # On error, returns an empty string and sets `error` appropriately.
401 fun recv(length: Int): String
402 do
403 var buf = new CString(length)
404 var len = native.recvfrom(buf, length, 0, new NativeSocketAddrIn.nul)
405 if len == -1 then
406 error = new IOError.from_errno
407 return ""
408 end
409 return buf.to_s_unsafe(len, copy=false)
410 end
411
412 # Receive `length` bytes of data from any sender and store the sender info in `sender.item`
413 #
414 # On error, returns an empty string and sets `error` appropriately.
415 fun recv_from(length: Int, sender: Ref[nullable SocketAddress]): String
416 do
417 var src = new NativeSocketAddrIn
418 var buf = new CString(length)
419
420 var len = native.recvfrom(buf, length, 0, src)
421 if len == -1 then
422 error = new IOError.from_errno
423 src.free
424 return ""
425 end
426
427 sender.item = new SocketAddress(src)
428 return buf.to_s_unsafe(len, copy=false)
429 end
430
431 # Send `data` to `dest_address` on `port`
432 #
433 # On error, sets `error` appropriately.
434 fun send_to(dest_address: Text, port: Int, data: Text)
435 do
436 var hostent = sys.gethostbyname(dest_address.to_cstring)
437 if hostent.address_is_null then
438 error = new IOError.from_h_errno
439 return
440 end
441
442 var dest = new NativeSocketAddrIn
443 dest.fill_from_hostent hostent
444 dest.port = port
445 native.setsockopt(new NativeSocketOptLevels.socket, new NativeSocketOptNames.broadcast, 1)
446
447 var buf = data.to_cstring
448 if native.sendto(buf, data.length, 0, dest) == -1 then
449 error = new IOError.from_errno
450 end
451 dest.free
452 end
453
454 # Enable broadcasting for this socket
455 #
456 # On error, sets `error` appropriately.
457 fun enable_broadcast=(value: Bool) do
458 var res = native.setsockopt(new NativeSocketOptLevels.socket, new NativeSocketOptNames.broadcast, value.to_i)
459 if res == -1 then error = new IOError.from_errno
460 end
461
462 # Broadcast `data` on the network on `port`
463 #
464 # On error, sets `error` appropriately.
465 #
466 # Require: setting `enable_broadcast = true`
467 fun broadcast(port: Int, data: Text)
468 do
469 var addr_in = new NativeSocketAddrIn
470 addr_in.port = port
471 addr_in.family = new NativeSocketAddressFamilies.af_inet
472 addr_in.address_broadcast
473
474 var buf = data.to_cstring
475 if native.sendto(buf, data.length, 0, addr_in) == -1 then
476 error = new IOError.from_errno
477 end
478
479 addr_in.free
480 end
481 end
482
483 # Address of a socket in the Internet namespace
484 #
485 # Used in one of the out parameters of `UDPSocket::recv_from`.
486 class SocketAddress
487 super FinalizableOnce
488
489 # FIXME make init private
490
491 private var native: NativeSocketAddrIn
492
493 init
494 do
495 address = native.address.to_s
496 port = native.port
497 end
498
499 # Internet address
500 var address: String is noinit
501
502 # Port of the socket
503 var port: Int is noinit
504
505 redef fun ==(o) do return o isa SocketAddress and o.address == address and o.port == port
506
507 redef fun finalize_once do native.free
508 end
509
510 redef class IOError
511 # Fill a new `IOError` from the message of `errno`
512 init from_errno do init errno.strerror
513
514 # Fill a new `IOError` from the message of `h_errno`
515 #
516 # Used with `gethostbyname`.
517 init from_h_errno do init h_errno.to_s
518 end