niti: fix CString::fast_cstring semantics
[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_unspec)
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.from_poll_values(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 # Write `msg`, with a trailing `\n`
194 fun write_ln(msg: Text)
195 do
196 if closed then return
197 write msg.to_s
198 write "\n"
199 end
200
201 redef fun fill_buffer
202 do
203 if not connected then return
204
205 var read = native.read(_buffer, _buffer_capacity)
206 if read == -1 then
207 close
208 end_reached = true
209 end
210
211 _buffer_length = read
212 _buffer_pos = 0
213 end
214
215 # Enlarge `_buffer` to at least `len` bytes
216 fun enlarge(len: Int) do
217 if _buffer_capacity >= len then return
218 _buffer_capacity = len
219
220 var ns = new CString(_buffer_capacity)
221 _buffer.copy_to(ns, _buffer_length - _buffer_pos, _buffer_pos, 0)
222 _buffer = ns
223 end
224
225 redef fun close
226 do
227 if closed then return
228 if native.close >= 0 then
229 closed = true
230 end_reached = true
231 end
232 end
233
234 # Send the data present in the socket buffer
235 fun flush
236 do
237 if not native.setsockopt(new NativeSocketOptLevels.tcp, new NativeSocketOptNames.tcp_nodelay, 1) or
238 not native.setsockopt(new NativeSocketOptLevels.tcp, new NativeSocketOptNames.tcp_nodelay, 0) then
239 closed = true
240 end
241 end
242 end
243
244 # A socket listening on a given `port` for incomming connections
245 #
246 # Create streams to communicate with clients using `accept`.
247 class TCPServer
248 super TCPSocket
249
250 private var addrin: NativeSocketAddrIn is noinit
251
252 # Create and bind a listening server socket on port `port`
253 init
254 do
255 native = new NativeSocket.socket(new NativeSocketAddressFamilies.af_inet,
256 new NativeSocketTypes.sock_stream, new NativeSocketProtocolFamilies.pf_unspec)
257 assert not native.address_is_null
258 if not native.setsockopt(new NativeSocketOptLevels.socket, new NativeSocketOptNames.reuseaddr, 1) then
259 closed = true
260 return
261 end
262
263 addrin = new NativeSocketAddrIn
264 addrin.family = new NativeSocketAddressFamilies.af_inet
265 addrin.port = port
266 addrin.address_any
267
268 address = addrin.address.to_s
269
270 # Bind it
271 closed = not bind
272 end
273
274 # Associates the socket to a local address and port
275 #
276 # Returns whether the socket has been be bound.
277 private fun bind: Bool do
278 return native.bind(addrin) >= 0
279 end
280
281 # Sets the socket as ready to accept incoming connections, `size` is the maximum number of queued clients
282 #
283 # Returns `true` if the socket could be set, `false` otherwise
284 fun listen(size: Int): Bool do
285 return native.listen(size) >= 0
286 end
287
288 # Accepts an incoming connection from a client
289 #
290 # Create and return a new socket to the client. May return null if not
291 # `blocking` and there's no waiting clients, or upon an interruption
292 # (whether `blocking` or not).
293 #
294 # Require: not closed
295 fun accept: nullable TCPStream
296 do
297 assert not closed
298 var native = native.accept
299 if native == null then return null
300 return new TCPStream.server_side(native)
301 end
302
303 # Close this socket
304 fun close
305 do
306 # FIXME unify with `SocketStream::close` when we can use qualified names
307
308 if closed then return
309 if native.close >= 0 then
310 closed = true
311 end
312 end
313 end
314
315 # A simple set of sockets used by `SocketObserver`
316 class SocketSet
317 private var native = new NativeSocketSet
318
319 init do clear
320
321 # Add `socket` to this set
322 fun add(socket: Socket) do native.set(socket.native)
323
324 # Remove `socket` from this set
325 fun remove(socket: Socket) do native.clear(socket.native)
326
327 # Does this set has `socket`?
328 fun has(socket: Socket): Bool do return native.is_set(socket.native)
329
330 # Clear all sockets from this set
331 fun clear do native.zero
332 end
333
334 # Service class to manage calls to `select`
335 class SocketObserver
336 private var native = new NativeSocketObserver
337
338 # Set of file descriptors on which to watch read events
339 var read_set: nullable SocketSet = null
340
341 # Set of file descriptors on which to watch write events
342 var write_set: nullable SocketSet = null
343
344 # Set of file descriptors on which to watch exception events
345 var except_set: nullable SocketSet = null
346
347 # Initialize a socket observer
348 init with_sets(read: Bool, write: Bool, except: Bool) do
349 if read then read_set = new SocketSet
350 if write then write_set = new SocketSet
351 if except then except_set = new SocketSet
352 end
353
354 # Watch for changes in the states of several sockets.
355 fun select(max: Socket, seconds: Int, microseconds: Int): Bool
356 do
357 # FIXME this implementation (see the call to nullable attributes below) and
358 # `NativeSockectObserver::select` is not stable.
359
360 var timeval = new NativeTimeval(seconds, microseconds)
361 var rd = if read_set != null then read_set.as(not null).native else null
362 var wrt = if write_set != null then write_set.as(not null).native else null
363 var expt = if except_set != null then except_set.as(not null).native else null
364 return native.select(max.native, rd, wrt, expt, timeval) > 0
365 end
366 end
367
368 # Socket over UDP, sends and receive data without the need for a connection
369 class UDPSocket
370 super Socket
371
372 # Last error raised by this socket
373 var error: nullable Error = null
374
375 init do native = new NativeSocket.socket(
376 new NativeSocketAddressFamilies.af_inet,
377 new NativeSocketTypes.sock_dgram,
378 new NativeSocketProtocolFamilies.pf_unspec)
379
380 # Bind this socket to an `address`, on `port` (to all addresses if `null`)
381 #
382 # On error, sets `error` appropriately.
383 fun bind(address: nullable Text, port: Int)
384 do
385 var addr_in = new NativeSocketAddrIn
386 addr_in.port = port
387 if address != null then
388 # FIXME replace all use of gethostbyname with something not obsolete
389 var hostent = sys.gethostbyname(address.to_cstring)
390 if hostent.address_is_null then
391 error = new IOError.from_h_errno
392 addr_in.free
393 return
394 end
395
396 addr_in.fill_from_hostent hostent
397 else
398 addr_in.family = new NativeSocketAddressFamilies.af_inet
399 addr_in.address_any
400 end
401
402 if native.bind(addr_in) != 0 then error = new IOError.from_errno
403
404 addr_in.free
405 end
406
407 # Receive `length` bytes of data from any sender
408 #
409 # On error, returns an empty string and sets `error` appropriately.
410 fun recv(length: Int): String
411 do
412 var buf = new CString(length)
413 var len = native.recvfrom(buf, length, 0, new NativeSocketAddrIn.nul)
414 if len == -1 then
415 error = new IOError.from_errno
416 return ""
417 end
418 return buf.to_s_unsafe(len, copy=false)
419 end
420
421 # Receive `length` bytes of data from any sender and store the sender info in `sender.item`
422 #
423 # On error, returns an empty string and sets `error` appropriately.
424 fun recv_from(length: Int, sender: Ref[nullable SocketAddress]): String
425 do
426 var src = new NativeSocketAddrIn
427 var buf = new CString(length)
428
429 var len = native.recvfrom(buf, length, 0, src)
430 if len == -1 then
431 error = new IOError.from_errno
432 src.free
433 return ""
434 end
435
436 sender.item = new SocketAddress(src)
437 return buf.to_s_unsafe(len, copy=false)
438 end
439
440 # Send `data` to `dest_address` on `port`
441 #
442 # On error, sets `error` appropriately.
443 fun send_to(dest_address: Text, port: Int, data: Text)
444 do
445 var hostent = sys.gethostbyname(dest_address.to_cstring)
446 if hostent.address_is_null then
447 error = new IOError.from_h_errno
448 return
449 end
450
451 var dest = new NativeSocketAddrIn
452 dest.fill_from_hostent hostent
453 dest.port = port
454 native.setsockopt(new NativeSocketOptLevels.socket, new NativeSocketOptNames.broadcast, 1)
455
456 var buf = data.to_cstring
457 if native.sendto(buf, data.length, 0, dest) == -1 then
458 error = new IOError.from_errno
459 end
460 dest.free
461 end
462
463 # Enable broadcasting for this socket
464 #
465 # On error, sets `error` appropriately.
466 fun enable_broadcast=(value: Bool) do
467 var res = native.setsockopt(new NativeSocketOptLevels.socket, new NativeSocketOptNames.broadcast, value.to_i)
468 if res == -1 then error = new IOError.from_errno
469 end
470
471 # Broadcast `data` on the network on `port`
472 #
473 # On error, sets `error` appropriately.
474 #
475 # Require: setting `enable_broadcast = true`
476 fun broadcast(port: Int, data: Text)
477 do
478 var addr_in = new NativeSocketAddrIn
479 addr_in.port = port
480 addr_in.family = new NativeSocketAddressFamilies.af_inet
481 addr_in.address_broadcast
482
483 var buf = data.to_cstring
484 if native.sendto(buf, data.length, 0, addr_in) == -1 then
485 error = new IOError.from_errno
486 end
487
488 addr_in.free
489 end
490 end
491
492 # Address of a socket in the Internet namespace
493 #
494 # Used in one of the out parameters of `UDPSocket::recv_from`.
495 class SocketAddress
496 super FinalizableOnce
497
498 # FIXME make init private
499
500 private var native: NativeSocketAddrIn
501
502 init
503 do
504 address = native.address.to_s
505 port = native.port
506 end
507
508 # Internet address
509 var address: String is noinit
510
511 # Port of the socket
512 var port: Int is noinit
513
514 redef fun ==(o) do return o isa SocketAddress and o.address == address and o.port == port
515
516 redef fun finalize_once do native.free
517 end
518
519 redef class IOError
520 # Fill a new `IOError` from the message of `errno`
521 init from_errno do init errno.strerror
522
523 # Fill a new `IOError` from the message of `h_errno`
524 #
525 # Used with `gethostbyname`.
526 init from_h_errno do init h_errno.to_s
527 end