Basic design of the tsocket abstraction ======================================= The tsocket layer is designed to match more or less the bsd socket layer, but it hides the filedescriptor within a opaque 'tsocket_context' structure to make virtual sockets possible. The virtual sockets can be encrypted tunnels (like TLS, SASL or GSSAPI) or named pipes over smb. The tsocket layer is a bit like an abstract class, which defines common methods to work with sockets in a non blocking fashion. The whole library is based on the talloc(3) and 'tevent' libraries. The 'tsocket_address' structure is the 2nd abstracted class which represends the address of a socket endpoint. Each different type of socket has its own constructor. Typically the constructor for a tsocket_context is attached to the tsocket_address of the source endpoint. That means the tsocket_address_create_socket() function takes the tsocket_address of the local endpoint and creates a tsocket_context for the communication. For some usecases it's possible to wrap an existing socket into a tsocket_context, e.g. to wrap an existing pipe(2) into tsocket_context, so that you can use the same functions to communicate over the pipe. The tsocket_address abstraction =============================== The tsocket_address represents an socket endpoint genericly. As it's like an abstract class it has no specific constructor. The specific constructors are descripted later sections. There's a function get the string representation of the endpoint for debugging. Callers should not try to parse the string! The should use additional methods of the specific tsocket_address implemention to get more details. char *tsocket_address_string(const struct tsocket_address *addr, TALLOC_CTX *mem_ctx); There's a function to create a copy of the tsocket_address. This is useful when before doing modifications to a socket via additional methods of the specific tsocket_address implementation. struct tsocket_address *tsocket_address_copy(const struct tsocket_address *addr, TALLOC_CTX *mem_ctx); There's a function to create a tsocket_context based on the given local socket endpoint. The return value is 0 on success and -1 on failure with errno holding the specific error. Specific details are descripted in later sections. Note not all specific implementation have to implement all socket types. enum tsocket_type { TSOCKET_TYPE_STREAM = 1, TSOCKET_TYPE_DGRAM, TSOCKET_TYPE_MESSAGE }; int tsocket_address_create_socket(const struct tsocket_address *addr, enum tsocket_type type, TALLOC_CTX *mem_ctx, struct tsocket_context **sock); The tsocket_context abstraction =============================== The tsocket_context is like an abstract class and represents a socket similar to bsd style sockets. The methods are more or less equal to the bsd socket api, while the filedescriptor is replaced by tsocket_context and sockaddr, socklen_t pairs are replaced by tsocket_address. The 'bind' operation happens in the specific constructor as the constructor is typically based on tsocket_address of local socket endpoint. All operations are by design non blocking and can return error values like EAGAIN, EINPROGRESS, EWOULDBLOCK or EINTR which indicate that the caller should retry the operation later. Also read the "The glue to tevent" section. The socket can of types: - TSOCKET_TYPE_STREAM is the equivalent to SOCK_STREAM in the bsd socket api. - TSOCKET_TYPE_DGRAM is the equivalent to SOCK_DGRAM in the bsd socket api. - TSOCKET_TYPE_MESSAGE operates on a connected socket and is therefore like TSOCKET_TYPE_STREAM, but the consumer needs to first read all data of a message, which was generated by one message 'write' on the sender, before the consumer gets data of the next message. This matches a bit like message mode pipes on windows. The concept is to transfer ordered messages between to endpoints. There's a function to connect to a remote endpoint. The behavior and error codes match the connect(2) function of the bsd socket api. Maybe the specific tsocket_context implementation speficied some further details. int tsocket_connect(struct tsocket_context *sock, const struct tsocket_address *remote_addr); There's a function to listen for incoming connections. The behavior and error codes match the listen(2) function of the bsd socket api. Maybe the specific tsocket_context implementation speficied some further details. int tsocket_listen(struct tsocket_context *sock, int queue_size); There's a function to accept incoming connections. The behavior and error codes match the accept(2) function of the bsd socket api. Maybe the specific tsocket_context implementation speficied some further details. int tsocket_accept(struct tsocket_context *sock, TALLOC_CTX *mem_ctx, struct tsocket_context **new_sock); There's a function to ask how many bytes are in input buffer of the connection. For sockets of type TSOCKET_TYPE_DGRAM or TSOCKET_TYPE_MESSAGE the size of the next available dgram/message is returned. A return value of -1 indicates a socket error and errno will hold the specific error code. If no data is available 0 is returned, but retry error codes like EINTR can also be returned. ssize_t tsocket_pending(struct tsocket_context *sock); There's a function to read data from the socket. The behavior and error codes match the readv(3) function, also take a look at the recv(2) function of the bsd socket api. Maybe the specific tsocket_context implementation speficied some further details. int tsocket_readv(struct tsocket_context *sock, const struct iovec *vector, size_t count); There's a function to write data from the socket. The behavior and error codes match the writev(3) function, also take a look at the send(2) function of the bsd socket api. Maybe the specific tsocket_context implementation speficied some further details. int tsocket_writev(struct tsocket_context *sock, const struct iovec *vector, size_t count); There's a function to read a datagram from a remote endpoint. The behavior and error codes match the recvfrom(2) function of the bsd socket api. As TSOCKET_TYPE_DGRAM sockets can also be used in connected mode src_addr can be NULL, if the caller don't want to get the source address. Maybe the specific tsocket_context implementation speficied some further details. ssize_t tsocket_recvfrom(struct tsocket_context *sock, uint8_t *data, size_t len, TALLOC_CTX *addr_ctx, struct tsocket_address **src_addr); There's a function to send a datagram to a remote endpoint the socket. The behavior and error codes match the recvfrom(2) function of the bsd socket api. As TSOCKET_TYPE_DGRAM sockets can also be used in connected mode dest_addr must be NULL in connected mode and a valid tsocket_address otherwise. Maybe the specific tsocket_context implementation speficied some further details. ssize_t tsocket_sendto(struct tsocket_context *sock, const uint8_t *data, size_t len, const struct tsocket_address *dest_addr); There's a function to get the current status of the socket. The behavior and error codes match the getsockopt(2) function of the bsd socket api, with SOL_SOCKET and SO_ERROR as arguments. Maybe the specific tsocket_context implementation speficied some further details. int tsocket_get_status(const struct tsocket_context *sock); There's a function to get tsocket_address of the local endpoint. The behavior and error codes match the getsockname(2) function of the bsd socket api. Maybe the specific tsocket_context implementation speficied some further details. int tsocket_get_local_address(const struct tsocket_context *sock, TALLOC_CTX *mem_ctx, struct tsocket_address **local_addr); There's a function to get tsocket_address of the remote endpoint of a connected socket. The behavior and error codes match the getpeername(2) function of the bsd socket api. Maybe the specific tsocket_context implementation speficied some further details. int tsocket_get_remote_address(const struct tsocket_context *sock, TALLOC_CTX *mem_ctx, struct tsocket_address **remote_addr, const char *location); There's a function to ask for specific options of the socket. The behavior and error codes match the getsockopt(2) function of the bsd socket api. The option and value are represented as string values, where the 'value' parameter can be NULL is the caller don't want to get the value. The supported options and values are up to the specific tsocket_context implementation. int tsocket_get_option(const struct tsocket_context *sock, const char *option, TALLOC_CTX *mem_ctx, char **value); There's a function to set specific options of the socket. The behavior and error codes match the setsockopt(2) function of the bsd socket api. The option and value are represented as string values, where the 'value' parameter can be NULL. The supported options and values are up to the specific tsocket_context implementation. The 'force' parameter specifies whether an error should be returned for unsupported options. int tsocket_set_option(const struct tsocket_context *sock, const char *option, bool force, const char *value); There's a function to disconnect the socket. The behavior and error codes match the close(2) function of the bsd socket api. Maybe the specific tsocket_context implementation speficied some further details. void tsocket_disconnect(struct tsocket_context *sock); The glue to tevent ================== As the tsocket library is based on the tevent library, there need to be functions to let the caller register callback functions, which are triggered when the socket is writeable or readable. Typically one would use tevent fd events, but in order to hide the filedescriptor the tsocket_context abstraction has their own functions. There's a function to set the currently active tevent_context for the socket. It's important there's only one tevent_context actively used with the socket. A second call will cancel all low level events made on the old tevent_context, it will also resets the send and recv handlers to NULL. If the caller sets attaches a new event context to the socket, the callback function also need to be registered again. It's important that the caller keeps the given tevent_context in memory and actively calls tsocket_set_event_context(sock, NULL) before calling talloc_free(event_context). The function returns 0 on success and -1 together with an errno on failure. int tsocket_set_event_context(struct tsocket_context *sock, struct tevent_context *ev); There's a function to register a callback function which is called when the socket is readable. If the caller don't want to get notified anymore the function should be called with NULL as handler. The function returns 0 on success and -1 together with an errno on failure. typedef void (*tsocket_event_handler_t)(struct tsocket_context *, void *); int tsocket_set_readable_handler(struct tsocket_context *sock, tsocket_event_handler_t handler, void *private_data); There's a function to register a callback function which is called when the socket is writeable. If the caller don't want to get notified anymore the function should be called with NULL as handler. The function returns 0 on success and -1 together with an errno on failure. typedef void (*tsocket_event_handler_t)(struct tsocket_context *, void *); int tsocket_set_writeable_handler(struct tsocket_context *sock, tsocket_event_handler_t handler, void *private_data); Note: if the socket is readable and writeable, only the writeable handler is called, this avoids deadlocks at the application level.