-
Notifications
You must be signed in to change notification settings - Fork 3k
Create abstract Socket interface #7192
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
@kjbracey-arm For early review, please check. |
Testing on K64F+GCC_ARM
So I'm assuming I did not break anything major. |
: _stack(0) | ||
, _socket(0) | ||
, _timeout(osWaitForever) | ||
, _remote_peer() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No need to pop this in for something with a default constructor
* | ||
* Closes socket if the socket is still open | ||
*/ | ||
virtual ~Socket() {} | ||
|
||
/** Opens a socket | ||
/** Close the socket. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think listen() and accept() should go into the base level. They're as theoretically generic as all the other functions, and it would be harder to change later - particularly if you're trying to keep Socket
totally abstract to the point of excluding default "unsupported" errors.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Accept is a bit painfull because it does not return any abstract type, it needs a pointer of already allocated object which it then fills in.
nsapi_error_t accept(TCPSocket *connection, SocketAddress *address = NULL);
One option would be that we create new accept()
call that is able to work with abstract types. After all, accepting from TCPserver is fine to return Socket*
and just use it and call close()
after you are done.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah, right, I recall this discussion now. Any generic accept
would need a different prototype, yes. So maybe define that now, and leave the other one deprecated in TCPServer
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Generic accept()
call now implemented
features/netsocket/TCPServer.cpp
Outdated
|
||
nsapi_error_t TCPServer::connect(const SocketAddress &address) | ||
{ | ||
(void) address; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As it's C++, how's about just eliminating/commenting the parameter name?
features/netsocket/TCPServer.h
Outdated
@@ -28,7 +28,7 @@ | |||
/** TCP socket server | |||
* @addtogroup netsocket | |||
*/ | |||
class TCPServer : public Socket { | |||
class TCPServer : public InternetSocket { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not totally convinced that TCPServer
needs to exist as a separate class, but I guess it does avoid the implementations of listen
and accept
being pulled in unless needed. Would it be more or less efficient to derive from TCPSocket
, do you think?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
TCPSocket now support listen()
and accept()
. TCPServer deprecated, but left the implementation in place to support old code.
features/netsocket/UDPSocket.cpp
Outdated
nsapi_size_or_error_t UDPSocket::recv(void *buffer, nsapi_size_t size) | ||
{ | ||
SocketAddress ignored; // Dangerous, I'm spending ~50 bytes from stack for address space that I'll ignore. | ||
return recvfrom(&ignored, buffer, size); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we believe NSAPI stacks won't cope with being passed NULL
? If so, this dummy actually needs to be in UDPSocket::recvfrom
, as we want recv
to be equivalent to recvfrom(NULL)
.
features/netsocket/InternetSocket.h
Outdated
mbed::Callback<void()> _event; | ||
mbed::Callback<void()> _callback; | ||
rtos::Mutex _lock; | ||
SocketAddress _remote_peer; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
To minimise bloat, could we store the underlying ns_addr, or whatever it is, excluding the string rep?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I refactored the SocketAddress to allocate string only if its requested.
features/netsocket/TCPServer.h
Outdated
|
||
/** Not supported on TCPServer. | ||
* @param data unused | ||
* @param sizez unused |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Typo - showed up in CI
features/netsocket/TCPSocket.cpp
Outdated
@@ -79,6 +79,10 @@ nsapi_error_t TCPSocket::connect(const SocketAddress &address) | |||
|
|||
_write_in_progress = false; | |||
|
|||
if (ret == NSAPI_ERROR_OK) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This needs to be below the following IS_CONNECTED->OK conversion.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
But is it then creating a loophole where you first set socket to non blocking mode. Then call connect(A)
and later connect(B)
would actually change the connection address here, even that the actual connection is with peer A
?
I'm unsure whether IP stacks actually check the connection addresses after a first call.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Okay, but in non-blocking there's possibly never an "OK" - it's "INPROGESS", "ALREADY", "ISCONN". If you don't want to note it on "ISCONN" then note it on "INPROGRESS". That should be the return code for "I've accepted these parameters and am acting on them, but I've not finished yet".
/** Receive data from a socket. | ||
* | ||
* Receive data from connected socket or in case of connectionless socket | ||
* this is equivalent of calling recvfrom(NULL, data, size). |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I still can't decide whether we should actually have final implementations of send
/recv
here to enforce the equivalence.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do you mean that send()
would be just sendto(NULL, data, size);
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's what I'm pondering, but looking more closely at the POSIX specs I actually don't see the sendto(NULL)
== send()
equivalence - only send(0)
== write
and connection-mode sendto()
== send()
.
0ed3740
to
6507744
Compare
6507744
to
a6d85f5
Compare
@kjbracey-arm Please re-review |
@mikaleppanen Please review |
features/netsocket/SocketAddress.h
Outdated
@@ -147,6 +153,12 @@ class SocketAddress { | |||
*/ | |||
operator bool() const; | |||
|
|||
/** Copy addres from another SocketAddress |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
address
@@ -180,7 +183,8 @@ const char *SocketAddress::get_ip_address() const | |||
return NULL; | |||
} | |||
|
|||
if (!_ip_address[0]) { | |||
if (!_ip_address) { | |||
_ip_address = new char[NSAPI_IP_SIZE]; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I guess you need an "else" here now, as this will leave the address undefined, not "", if somehow version is not v4 or v6.
Maybe you could actually put the "new" in the ifs? Would allow you to have a smaller buffer for IPv4.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
(Another option would be to convert into stack temporary, then alloc exactly the right size. But a bit wary of using more stack).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Couple of lines up, it is already checked that address type is not NSAPI_UNSPEC
if so, it returns NULL before allocating the space.
Yes, using a stack space, then copying the string by strdup()
would use less heap, but do we want to optimise for this? For me, it sounds like should be used only once per application.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah. And we assume it's not an illegal value as a precondition I guess.
Maybe make it just else /* if (_addr.version == NSAPI_IPv6) */ {
then to save an instruction or two, as the compiler's going to feel the need to test.
Although I now wonder if there should maybe be a system-level IPv4/IPv6 support switch to leave out unneeded conversion stuff. We've only got the lwIP-level switches. Something for later...
features/netsocket/SocketAddress.h
Outdated
/** Get the IP address | ||
|
||
/** Get the human readable IP address |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
human-readable with a hyphen. Same below.
features/netsocket/TCPSocket.cpp
Outdated
_pending = 0; | ||
void *socket; | ||
SocketAddress address; | ||
*error = _stack->socket_accept(_socket, &socket, &address); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
API says error
can be NULL.
features/netsocket/UDPSocket.cpp
Outdated
@@ -37,6 +32,13 @@ nsapi_protocol_t UDPSocket::get_proto() | |||
return NSAPI_UDP; | |||
} | |||
|
|||
nsapi_error_t UDPSocket::connect(const SocketAddress &address) | |||
{ | |||
if (!address) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
POSIX permits a null address to undo the connection, so I think you can just take this check out.
nsapi_size_or_error_t UDPSocket::recvfrom(SocketAddress *address, void *buffer, nsapi_size_t size) | ||
{ | ||
_lock.lock(); | ||
nsapi_size_or_error_t ret; | ||
SocketAddress ignored; | ||
|
||
if (!address) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Possible refinement - connect()
is supposed to act as a filter on received packets, as well as be the destination for send()
.
Maybe we should have a if (recv >= 0 && _remote_peer && address != _remote_peer) continue
in the loop?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm OK with that idea, but are we allowed to trash the buffers of caller, if we don't provide any data for them?
For example:
Socket is set to non-blocking mode and connected
Packet comes in
sigio() happens
Application calls recvfrom()
packet is not from the _peer_address, so continue happens
timeout
return WOULD_BLOCK
Now we are telling that we did not receive anything, but we did write the incoming packet to callers buffer, and address is recorded in the provided storage.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also, POSIX says For SOCK_DGRAM sockets, the peer address identifies where all datagrams are sent on subsequent send() functions, and limits the remote sender for subsequent recv() functions.
So should we filter only on recv()
or both recv()
and recvfrom()
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
we did write the incoming packet to callers buffer
Good point. Can the caller assume buffer is untouched if error is returned? It certainly would be in a real POSIX system, cos of the way system calls work, but I'm not sure it's a useful guarantee for applications, whereas the ability to use input buffer as "scratch" seems like it's potentially generally useful for drivers/stacks.
I'd be inclined to maybe add a little API note that buffer and address may be modified even if the call ends up returning an error.
Also, POSIX says
I saw that, but concluded it was poor wording - they must mean recv*
. The equivalence between recv
and recvfrom
forces it to affect both, I believe:
The recv() function is equivalent to recvfrom() with a zero address_len argument
I guess the connect
specifically makes recv()
useful, in that you can assume the remote address, but the filtering must apply generally. (In a real stack the filtering would happen on entry to the socket buffer, not exit, so call chosen to read the buffer can't be a factor).
Added incoming UDP packet filtering based on connected address. |
features/netsocket/UDPSocket.cpp
Outdated
@@ -122,6 +122,13 @@ nsapi_size_or_error_t UDPSocket::recvfrom(SocketAddress *address, void *buffer, | |||
|
|||
_pending = 0; | |||
nsapi_size_or_error_t recv = _stack->socket_recvfrom(_socket, address, buffer, size); | |||
|
|||
// Filter incomming packets using connected peer address | |||
if (recv > 0 && _remote_peer && _remote_peer == *address) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
>= 0
. Can have zero-size datagrams.
* Move IP Socket stuff to InternetSocket class which is inherited by TCP/UDP * Implement sendto() and recvfrom() on TCP socket * Implement connect() call on UDP * Implement send() and recv() calls on UDP socket
Deprecate TCPServer in favor of just TCPSocket::accept()
This saves RAM/stack on typical socket usage where human readable format is not exchanged so often.
3c2d6d9
to
1ca9ccd
Compare
Fixed based on Kevin's comments and rebased. |
@0xc0170 Who can check the
|
Memory Impact:
Build for K64F using GCC_ARM, Ethernet+LwIP |
I got a fix opened as PR, should be resolved soon. I restarted it now (will be green) |
/morph build |
Build : SUCCESSBuild number : 2442 Triggering tests/morph test |
Exporter Build : SUCCESSBuild number : 2073 |
Test : SUCCESSBuild number : 2221 |
Was open() removed intentionally from the Socket class? |
@juhoeskeli Yes.
TCPSocket and UDPSocket contains It is a bit weird that somebody have been using the baseclass pointer when the base class did not offer any way to send or receive data. Basically everything that was on the |
Plus if anything was using |
This is clear, thank you! 👍 |
Description
Socket::accept()
calllisten()
andaccept()
calls.Pull request type