OLD | NEW |
1 // Copyright (c) 2013 The Chromium Authors. All rights reserved. | 1 // Copyright (c) 2013 The Chromium Authors. All rights reserved. |
2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
4 | 4 |
5 | 5 |
6 #include "nacl_io/ossocket.h" | 6 #include "nacl_io/ossocket.h" |
7 #ifdef PROVIDES_SOCKET_API | 7 #ifdef PROVIDES_SOCKET_API |
8 | 8 |
| 9 #include <assert.h> |
9 #include <errno.h> | 10 #include <errno.h> |
10 #include <string.h> | 11 #include <string.h> |
11 #include <algorithm> | 12 #include <algorithm> |
12 | 13 |
| 14 #include "nacl_io/kernel_handle.h" |
13 #include "nacl_io/mount_node_tcp.h" | 15 #include "nacl_io/mount_node_tcp.h" |
14 #include "nacl_io/mount_stream.h" | 16 #include "nacl_io/mount_stream.h" |
15 #include "nacl_io/pepper_interface.h" | 17 #include "nacl_io/pepper_interface.h" |
16 | 18 |
17 namespace { | 19 namespace { |
18 const size_t kMaxPacketSize = 65536; | 20 const size_t kMaxPacketSize = 65536; |
19 const size_t kDefaultFifoSize = kMaxPacketSize * 8; | 21 const size_t kDefaultFifoSize = kMaxPacketSize * 8; |
20 } | 22 } |
21 | 23 |
22 namespace nacl_io { | 24 namespace nacl_io { |
(...skipping 27 matching lines...) Expand all Loading... |
50 virtual bool Start(int32_t val) { | 52 virtual bool Start(int32_t val) { |
51 AUTO_LOCK(emitter_->GetLock()); | 53 AUTO_LOCK(emitter_->GetLock()); |
52 MountNodeTCP* stream = static_cast<MountNodeTCP*>(emitter_->stream()); | 54 MountNodeTCP* stream = static_cast<MountNodeTCP*>(emitter_->stream()); |
53 | 55 |
54 // Does the stream exist, and can it send? | 56 // Does the stream exist, and can it send? |
55 if (NULL == stream || !stream->TestStreamFlags(SSF_CAN_SEND)) | 57 if (NULL == stream || !stream->TestStreamFlags(SSF_CAN_SEND)) |
56 return false; | 58 return false; |
57 | 59 |
58 // If not currently sending... | 60 // If not currently sending... |
59 if (!stream->TestStreamFlags(SSF_SENDING)) { | 61 if (!stream->TestStreamFlags(SSF_SENDING)) { |
60 size_t tx_data_avail = emitter_->out_fifo()->ReadAvailable(); | 62 size_t tx_data_avail = emitter_->BytesInOutputFIFO(); |
61 int capped_len = std::min(tx_data_avail, kMaxPacketSize); | 63 int capped_len = std::min(tx_data_avail, kMaxPacketSize); |
62 | 64 |
63 if (capped_len == 0) | 65 if (capped_len == 0) |
64 return false; | 66 return false; |
65 | 67 |
66 data_ = new char[capped_len]; | 68 data_ = new char[capped_len]; |
67 emitter_->ReadOut_Locked(data_, capped_len); | 69 emitter_->ReadOut_Locked(data_, capped_len); |
68 | 70 |
69 stream->SetStreamFlags(SSF_SENDING); | 71 stream->SetStreamFlags(SSF_SENDING); |
70 int err = TCPInterface()->Write(stream->socket_resource(), | 72 int err = TCPInterface()->Write(stream->socket_resource(), |
(...skipping 35 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
106 virtual bool Start(int32_t val) { | 108 virtual bool Start(int32_t val) { |
107 AUTO_LOCK(emitter_->GetLock()); | 109 AUTO_LOCK(emitter_->GetLock()); |
108 MountNodeTCP* stream = static_cast<MountNodeTCP*>(emitter_->stream()); | 110 MountNodeTCP* stream = static_cast<MountNodeTCP*>(emitter_->stream()); |
109 | 111 |
110 // Does the stream exist, and can it recv? | 112 // Does the stream exist, and can it recv? |
111 if (NULL == stream || !stream->TestStreamFlags(SSF_CAN_RECV)) | 113 if (NULL == stream || !stream->TestStreamFlags(SSF_CAN_RECV)) |
112 return false; | 114 return false; |
113 | 115 |
114 // If we are not currently receiving | 116 // If we are not currently receiving |
115 if (!stream->TestStreamFlags(SSF_RECVING)) { | 117 if (!stream->TestStreamFlags(SSF_RECVING)) { |
116 size_t rx_space_avail = emitter_->in_fifo()->WriteAvailable(); | 118 size_t rx_space_avail = emitter_->SpaceInInputFIFO(); |
117 int capped_len = | 119 int capped_len = |
118 static_cast<int32_t>(std::min(rx_space_avail, kMaxPacketSize)); | 120 static_cast<int32_t>(std::min(rx_space_avail, kMaxPacketSize)); |
119 | 121 |
120 if (capped_len == 0) | 122 if (capped_len == 0) |
121 return false; | 123 return false; |
122 | 124 |
123 stream->SetStreamFlags(SSF_RECVING); | 125 stream->SetStreamFlags(SSF_RECVING); |
124 data_ = new char[capped_len]; | 126 data_ = new char[capped_len]; |
125 int err = TCPInterface()->Read(stream->socket_resource(), | 127 int err = TCPInterface()->Read(stream->socket_resource(), |
126 data_, | 128 data_, |
(...skipping 18 matching lines...) Expand all Loading... |
145 emitter_->WriteIn_Locked(data_, length_error); | 147 emitter_->WriteIn_Locked(data_, length_error); |
146 stream->ClearStreamFlags(SSF_RECVING); | 148 stream->ClearStreamFlags(SSF_RECVING); |
147 stream->QueueInput(); | 149 stream->QueueInput(); |
148 } else { | 150 } else { |
149 stream->SetError_Locked(length_error); | 151 stream->SetError_Locked(length_error); |
150 } | 152 } |
151 } | 153 } |
152 } | 154 } |
153 }; | 155 }; |
154 | 156 |
| 157 class TCPAcceptWork : public MountStream::Work { |
| 158 public: |
| 159 explicit TCPAcceptWork(MountStream* stream, |
| 160 const ScopedEventEmitterTCP& emitter) |
| 161 : MountStream::Work(stream), |
| 162 emitter_(emitter) {} |
| 163 |
| 164 TCPSocketInterface* TCPInterface() { |
| 165 return mount()->ppapi()->GetTCPSocketInterface(); |
| 166 } |
| 167 |
| 168 virtual bool Start(int32_t val) { |
| 169 AUTO_LOCK(emitter_->GetLock()); |
| 170 MountNodeTCP* node = static_cast<MountNodeTCP*>(emitter_->stream()); |
| 171 |
| 172 // Does the stream exist, and can it accept? |
| 173 if (NULL == node) |
| 174 return false; |
| 175 |
| 176 // If we are not currently accepting |
| 177 if (!node->TestStreamFlags(SSF_LISTENING)) |
| 178 return false; |
| 179 |
| 180 int err = TCPInterface()->Accept(node->socket_resource(), |
| 181 &new_socket_, |
| 182 mount()->GetRunCompletion(this)); |
| 183 |
| 184 if (err != PP_OK_COMPLETIONPENDING) |
| 185 // Anything else, we should assume the socket has gone bad. |
| 186 node->SetError_Locked(err); |
| 187 |
| 188 return true; |
| 189 } |
| 190 |
| 191 virtual void Run(int32_t error) { |
| 192 AUTO_LOCK(emitter_->GetLock()); |
| 193 MountNodeTCP* node = static_cast<MountNodeTCP*>(emitter_->stream()); |
| 194 |
| 195 if (node == NULL) |
| 196 return; |
| 197 |
| 198 if (error != PP_OK) { |
| 199 node->SetError_Locked(error); |
| 200 return; |
| 201 } |
| 202 |
| 203 emitter_->SetAcceptedSocket_Locked(new_socket_); |
| 204 } |
| 205 |
| 206 protected: |
| 207 PP_Resource new_socket_; |
| 208 ScopedEventEmitterTCP emitter_; |
| 209 }; |
| 210 |
| 211 class TCPConnectWork : public MountStream::Work { |
| 212 public: |
| 213 explicit TCPConnectWork(MountStream* stream, |
| 214 const ScopedEventEmitterTCP& emitter) |
| 215 : MountStream::Work(stream), |
| 216 emitter_(emitter) {} |
| 217 |
| 218 TCPSocketInterface* TCPInterface() { |
| 219 return mount()->ppapi()->GetTCPSocketInterface(); |
| 220 } |
| 221 |
| 222 virtual bool Start(int32_t val) { |
| 223 AUTO_LOCK(emitter_->GetLock()); |
| 224 MountNodeTCP* node = static_cast<MountNodeTCP*>(emitter_->stream()); |
| 225 |
| 226 // Does the stream exist, and can it connect? |
| 227 if (NULL == node) |
| 228 return false; |
| 229 |
| 230 int err = TCPInterface()->Connect(node->socket_resource(), |
| 231 node->remote_addr(), |
| 232 mount()->GetRunCompletion(this)); |
| 233 |
| 234 if (err != PP_OK_COMPLETIONPENDING) |
| 235 // Anything else, we should assume the socket has gone bad. |
| 236 node->SetError_Locked(err); |
| 237 |
| 238 return true; |
| 239 } |
| 240 |
| 241 virtual void Run(int32_t error) { |
| 242 AUTO_LOCK(emitter_->GetLock()); |
| 243 MountNodeTCP* node = static_cast<MountNodeTCP*>(emitter_->stream()); |
| 244 |
| 245 if (node == NULL) |
| 246 return; |
| 247 |
| 248 if (error != PP_OK) { |
| 249 node->ConnectFailed_Locked(); |
| 250 node->SetError_Locked(error); |
| 251 return; |
| 252 } |
| 253 |
| 254 node->ConnectDone_Locked(); |
| 255 } |
| 256 |
| 257 protected: |
| 258 ScopedEventEmitterTCP emitter_; |
| 259 }; |
| 260 |
155 MountNodeTCP::MountNodeTCP(Mount* mount) | 261 MountNodeTCP::MountNodeTCP(Mount* mount) |
156 : MountNodeSocket(mount), | 262 : MountNodeSocket(mount), |
157 emitter_(new EventEmitterTCP(kDefaultFifoSize, kDefaultFifoSize)) { | 263 emitter_(new EventEmitterTCP(kDefaultFifoSize, kDefaultFifoSize)) { |
158 emitter_->AttachStream(this); | 264 emitter_->AttachStream(this); |
159 } | 265 } |
160 | 266 |
161 MountNodeTCP::MountNodeTCP(Mount* mount, PP_Resource socket) | 267 MountNodeTCP::MountNodeTCP(Mount* mount, PP_Resource socket) |
162 : MountNodeSocket(mount, socket), | 268 : MountNodeSocket(mount, socket), |
163 emitter_(new EventEmitterTCP(kDefaultFifoSize, kDefaultFifoSize)) { | 269 emitter_(new EventEmitterTCP(kDefaultFifoSize, kDefaultFifoSize)) { |
164 emitter_->AttachStream(this); | 270 emitter_->AttachStream(this); |
165 } | 271 } |
166 | 272 |
167 void MountNodeTCP::Destroy() { | 273 void MountNodeTCP::Destroy() { |
168 emitter_->DetachStream(); | 274 emitter_->DetachStream(); |
169 MountNodeSocket::Destroy(); | 275 MountNodeSocket::Destroy(); |
170 } | 276 } |
171 | 277 |
172 Error MountNodeTCP::Init(int open_flags) { | 278 Error MountNodeTCP::Init(int open_flags) { |
173 Error err = MountNodeSocket::Init(open_flags); | 279 Error err = MountNodeSocket::Init(open_flags); |
174 if (err != 0) | 280 if (err != 0) |
175 return err; | 281 return err; |
176 | 282 |
177 if (TCPInterface() == NULL) | 283 if (TCPInterface() == NULL) |
178 return EACCES; | 284 return EACCES; |
179 | 285 |
| 286 SetStreamFlags(SSF_CAN_CONNECT); |
| 287 |
180 if (socket_resource_ != 0) { | 288 if (socket_resource_ != 0) { |
181 // TCP sockets that are contructed with an existing socket_resource_ | 289 // TCP sockets that are contructed with an existing socket_resource_ |
182 // are those that generated from calls to Accept() and therefore are | 290 // are those that generated from calls to Accept() and therefore are |
183 // already connected. | 291 // already connected. |
184 remote_addr_ = TCPInterface()->GetRemoteAddress(socket_resource_); | 292 remote_addr_ = TCPInterface()->GetRemoteAddress(socket_resource_); |
185 ConnectDone(); | 293 ConnectDone_Locked(); |
186 } else { | 294 } else { |
187 socket_resource_ = TCPInterface()->Create(mount_->ppapi()->GetInstance()); | 295 socket_resource_ = TCPInterface()->Create(mount_->ppapi()->GetInstance()); |
188 if (0 == socket_resource_) | 296 if (0 == socket_resource_) |
189 return EACCES; | 297 return EACCES; |
190 } | 298 } |
191 | 299 |
192 return 0; | 300 return 0; |
193 } | 301 } |
194 | 302 |
195 EventEmitterTCP* MountNodeTCP::GetEventEmitter() { | 303 EventEmitter* MountNodeTCP::GetEventEmitter() { |
196 return emitter_.get(); | 304 return emitter_.get(); |
197 } | 305 } |
198 | 306 |
| 307 void MountNodeTCP::QueueAccept() { |
| 308 MountStream::Work* work = new TCPAcceptWork(mount_stream(), emitter_); |
| 309 mount_stream()->EnqueueWork(work); |
| 310 } |
| 311 |
| 312 void MountNodeTCP::QueueConnect() { |
| 313 MountStream::Work* work = new TCPConnectWork(mount_stream(), emitter_); |
| 314 mount_stream()->EnqueueWork(work); |
| 315 } |
| 316 |
199 void MountNodeTCP::QueueInput() { | 317 void MountNodeTCP::QueueInput() { |
200 TCPRecvWork* work = new TCPRecvWork(emitter_); | 318 MountStream::Work* work = new TCPRecvWork(emitter_); |
201 mount_stream()->EnqueueWork(work); | 319 mount_stream()->EnqueueWork(work); |
202 } | 320 } |
203 | 321 |
204 void MountNodeTCP::QueueOutput() { | 322 void MountNodeTCP::QueueOutput() { |
205 TCPSendWork* work = new TCPSendWork(emitter_); | 323 MountStream::Work* work = new TCPSendWork(emitter_); |
206 mount_stream()->EnqueueWork(work); | 324 mount_stream()->EnqueueWork(work); |
207 } | 325 } |
208 | 326 |
209 Error MountNodeTCP::Accept(PP_Resource* out_sock, | 327 Error MountNodeTCP::Accept(const HandleAttr& attr, |
| 328 PP_Resource* out_sock, |
210 struct sockaddr* addr, | 329 struct sockaddr* addr, |
211 socklen_t* len) { | 330 socklen_t* len) { |
212 AUTO_LOCK(node_lock_); | 331 EventListenerLock wait(GetEventEmitter()); |
213 int err = TCPInterface()->Accept(socket_resource_, | |
214 out_sock, | |
215 PP_BlockUntilComplete()); | |
216 | 332 |
217 if (err != PP_OK) | 333 if (!TestStreamFlags(SSF_LISTENING)) |
218 return PPErrorToErrno(err); | 334 return EINVAL; |
219 | 335 |
| 336 // Either block forever or not at all |
| 337 int ms = attr.IsBlocking() ? -1 : 0; |
| 338 |
| 339 Error err = wait.WaitOnEvent(POLLIN, ms); |
| 340 |
| 341 if (ETIMEDOUT == err) |
| 342 return EWOULDBLOCK; |
| 343 |
| 344 int s = emitter_->GetAcceptedSocket_Locked(); |
| 345 // Non-blocking case. |
| 346 if (s == 0) |
| 347 return EAGAIN; |
| 348 |
| 349 // Consume the new socket and start listening for the next one |
| 350 *out_sock = s; |
| 351 emitter_->ClearEvents_Locked(POLLIN); |
| 352 |
| 353 // Set the out paramaters |
220 PP_Resource remote_addr = TCPInterface()->GetRemoteAddress(*out_sock); | 354 PP_Resource remote_addr = TCPInterface()->GetRemoteAddress(*out_sock); |
221 *len = ResourceToSockAddr(remote_addr, *len, addr); | 355 *len = ResourceToSockAddr(remote_addr, *len, addr); |
222 mount_->ppapi()->ReleaseResource(remote_addr); | 356 mount_->ppapi()->ReleaseResource(remote_addr); |
| 357 |
| 358 QueueAccept(); |
223 return 0; | 359 return 0; |
224 } | 360 } |
225 | 361 |
226 // We can not bind a client socket with PPAPI. For now we ignore the | 362 // We can not bind a client socket with PPAPI. For now we ignore the |
227 // bind but report the correct address later, just in case someone is | 363 // bind but report the correct address later, just in case someone is |
228 // binding without really caring what the address is (for example to | 364 // binding without really caring what the address is (for example to |
229 // select a more optimized interface/route.) | 365 // select a more optimized interface/route.) |
230 Error MountNodeTCP::Bind(const struct sockaddr* addr, socklen_t len) { | 366 Error MountNodeTCP::Bind(const struct sockaddr* addr, socklen_t len) { |
231 AUTO_LOCK(node_lock_); | 367 AUTO_LOCK(node_lock_); |
232 | 368 |
233 /* Only bind once. */ | 369 /* Only bind once. */ |
234 if (local_addr_ != 0) | 370 if (local_addr_ != 0) |
235 return EINVAL; | 371 return EINVAL; |
236 | 372 |
237 local_addr_ = SockAddrToResource(addr, len); | 373 local_addr_ = SockAddrToResource(addr, len); |
238 int err = TCPInterface()->Bind(socket_resource_, | 374 int err = TCPInterface()->Bind(socket_resource_, |
239 local_addr_, | 375 local_addr_, |
240 PP_BlockUntilComplete()); | 376 PP_BlockUntilComplete()); |
241 | 377 |
242 // If we fail, release the local addr resource | 378 // If we fail, release the local addr resource |
243 if (err != PP_OK) { | 379 if (err != PP_OK) { |
244 mount_->ppapi()->ReleaseResource(local_addr_); | 380 mount_->ppapi()->ReleaseResource(local_addr_); |
245 local_addr_ = 0; | 381 local_addr_ = 0; |
246 return PPErrorToErrno(err); | 382 return PPErrorToErrno(err); |
247 } | 383 } |
248 | 384 |
249 return 0; | 385 return 0; |
250 } | 386 } |
251 | 387 |
252 Error MountNodeTCP::Connect(const struct sockaddr* addr, socklen_t len) { | 388 Error MountNodeTCP::Connect(const HandleAttr& attr, |
253 AUTO_LOCK(node_lock_); | 389 const struct sockaddr* addr, |
| 390 socklen_t len) { |
| 391 EventListenerLock wait(GetEventEmitter()); |
254 | 392 |
255 if (remote_addr_ != 0) | 393 if (TestStreamFlags(SSF_CONNECTING)) |
| 394 return EALREADY; |
| 395 |
| 396 if (remote_addr_ != 0) { |
256 return EISCONN; | 397 return EISCONN; |
| 398 } |
257 | 399 |
258 remote_addr_ = SockAddrToResource(addr, len); | 400 remote_addr_ = SockAddrToResource(addr, len); |
259 if (0 == remote_addr_) | 401 if (0 == remote_addr_) |
260 return EINVAL; | 402 return EINVAL; |
261 | 403 |
262 int err = TCPInterface()->Connect(socket_resource_, | 404 int ms = attr.IsBlocking() ? -1 : 0; |
263 remote_addr_, | 405 |
264 PP_BlockUntilComplete()); | 406 SetStreamFlags(SSF_CONNECTING); |
| 407 QueueConnect(); |
| 408 |
| 409 Error err = wait.WaitOnEvent(POLLOUT, ms); |
| 410 if (ETIMEDOUT == err) |
| 411 return EINPROGRESS; |
265 | 412 |
266 // If we fail, release the dest addr resource | 413 // If we fail, release the dest addr resource |
267 if (err != PP_OK) { | 414 if (err != 0) { |
268 mount_->ppapi()->ReleaseResource(remote_addr_); | 415 ConnectFailed_Locked(); |
269 remote_addr_ = 0; | 416 return err; |
270 return PPErrorToErrno(err); | |
271 } | 417 } |
272 | 418 |
273 ConnectDone(); | 419 ConnectDone_Locked(); |
274 return 0; | 420 return 0; |
275 } | 421 } |
276 | 422 |
277 void MountNodeTCP::ConnectDone() { | 423 void MountNodeTCP::ConnectDone_Locked() { |
278 local_addr_ = TCPInterface()->GetLocalAddress(socket_resource_); | 424 local_addr_ = TCPInterface()->GetLocalAddress(socket_resource_); |
279 | 425 |
280 // Now that we are connected, we can start sending and receiving. | 426 // Now that we are connected, we can start sending and receiving. |
| 427 ClearStreamFlags(SSF_CONNECTING | SSF_CAN_CONNECT); |
281 SetStreamFlags(SSF_CAN_SEND | SSF_CAN_RECV); | 428 SetStreamFlags(SSF_CAN_SEND | SSF_CAN_RECV); |
282 | 429 |
| 430 emitter_->ConnectDone_Locked(); |
| 431 |
283 // Begin the input pump | 432 // Begin the input pump |
284 QueueInput(); | 433 QueueInput(); |
285 } | 434 } |
286 | 435 |
| 436 void MountNodeTCP::ConnectFailed_Locked() { |
| 437 mount_->ppapi()->ReleaseResource(remote_addr_); |
| 438 remote_addr_ = 0; |
| 439 } |
| 440 |
287 Error MountNodeTCP::Listen(int backlog) { | 441 Error MountNodeTCP::Listen(int backlog) { |
| 442 AUTO_LOCK(node_lock_); |
| 443 if (0 == local_addr_) |
| 444 return EINVAL; |
| 445 |
288 int err = TCPInterface()->Listen(socket_resource_, | 446 int err = TCPInterface()->Listen(socket_resource_, |
289 backlog, | 447 backlog, |
290 PP_BlockUntilComplete()); | 448 PP_BlockUntilComplete()); |
291 if (err != PP_OK) | 449 if (err != PP_OK) |
292 return PPErrorToErrno(err); | 450 return PPErrorToErrno(err); |
293 | 451 |
| 452 ClearStreamFlags(SSF_CAN_CONNECT); |
| 453 SetStreamFlags(SSF_LISTENING); |
| 454 QueueAccept(); |
294 return 0; | 455 return 0; |
295 } | 456 } |
296 | 457 |
297 Error MountNodeTCP::Recv_Locked(void* buf, | 458 Error MountNodeTCP::Recv_Locked(void* buf, |
298 size_t len, | 459 size_t len, |
299 PP_Resource* out_addr, | 460 PP_Resource* out_addr, |
300 int* out_len) { | 461 int* out_len) { |
| 462 assert(emitter_.get()); |
301 *out_len = emitter_->ReadIn_Locked((char*)buf, len); | 463 *out_len = emitter_->ReadIn_Locked((char*)buf, len); |
302 *out_addr = remote_addr_; | 464 *out_addr = remote_addr_; |
303 | 465 |
304 // Ref the address copy we pass back. | 466 // Ref the address copy we pass back. |
305 mount_->ppapi()->AddRefResource(remote_addr_); | 467 mount_->ppapi()->AddRefResource(remote_addr_); |
306 return 0; | 468 return 0; |
307 } | 469 } |
308 | 470 |
309 // TCP ignores dst addr passed to send_to, and always uses bound address | 471 // TCP ignores dst addr passed to send_to, and always uses bound address |
310 Error MountNodeTCP::Send_Locked(const void* buf, | 472 Error MountNodeTCP::Send_Locked(const void* buf, |
311 size_t len, | 473 size_t len, |
312 PP_Resource, | 474 PP_Resource, |
313 int* out_len) { | 475 int* out_len) { |
| 476 assert(emitter_.get()); |
314 *out_len = emitter_->WriteOut_Locked((char*)buf, len); | 477 *out_len = emitter_->WriteOut_Locked((char*)buf, len); |
315 return 0; | 478 return 0; |
316 } | 479 } |
317 | 480 |
318 | 481 |
319 } // namespace nacl_io | 482 } // namespace nacl_io |
320 | 483 |
321 #endif // PROVIDES_SOCKET_API | 484 #endif // PROVIDES_SOCKET_API |
OLD | NEW |