- Concepts and Definitions
- Basis of spec
- Transport layer support
- Socket lifecycle
- Connecting (outbound) sockets
- Listening (accepting) sockets
- Accepted sockets
- Changes to existing Ti.Network.TCPSocket on iOS
- Security issues
- I/O Layer
- Proposed API
- Pseudo Code Examples
Sockets have been a part of the iOS networking infrastructure for over a year, and while their implementation has been satisfactory enough to allow users to add more advanced networking support beyond single-session HTTP requests to their products, there is room for improvement. Currently there are many problems with the iOS implementation of TCP sockets, most notabily with data chunking, the usage of an event-based system, insufficient distinction between 'listening' (accepts incoming connections) and 'connecting' (represents outbound connection) types, and inconsistent representation of sockets (some are objects, some are BSD file descriptors).
With the advent of sockets for Android, this is an ideal opportunity to address these issues and make sockets able to integrate future technologies, such as I/O streaming (or to represent an I/O stream themselves as a ducktype) and zero-configuration networking support for Android.
Where possible, comparisons will be drawn to the existing socket implementation on iOS and the proposed spec.
It should be noted that this spec is in flux based on the pending definition of the IO stream spec. Major changes are not expected, but further changes are possible.
Concepts and Definitions
- Socket: A socket is essentially a data stream that is connected to a host/port pair.
- Transport layer: The portion of a networking protocol which determines the parameters of data transmission (reliability, streaming, multiplexing, etc.) The two most common are UDP and TCP.
- BSD sockets: The standard POSIX implementation of sockets.
- INADDR_ANY: A specialized BSD host identifier for listening sockets which indicates that ALL available local network interfaces should be listened on. Practically, for us, this means sockets would listen on loopback (127.0.0.1), wifi, and data network.
- Listener: A socket which actively listens on a specified host/port for incoming connections.
- Connector: A socket which connects to a specified host/port.
Throughout this document, except where specified, the word "socket" indicates a socket that uses the TCP transport layer.
Basis of spec
The basis of the spec, from an outward facing functionality standpoint, is the BSD socket specification. Functions, where possible, correspond directly to their BSD counterparts.
The reason for this is to keep the interface simple, flexible, minimal, and familiar to developers who may already have experience with developing networking code. Events fired based on the socket lifecycle state (see below) provide the opportunity for more advanced handling than the standard C paradigm and allow a BSD-like interface to fit comfortably into Titanium.
There is precedence for this, as the iOS TCPSocket presents a stripped-down version of the BSD interface with some magic.
It is proposed that we create a new
Ti.Network.Socket namespace within
Ti.Network, which will house socket objects which correspond to different transport layers. The rationale for this is as follows:
- Certain socket transport types (in particular, UDP) are incompatible with TCP from an interface standpoint. In particular, UDP sockets use a "datagram" model in which there are no listening/connecting sockets, and they do not behave as I/O streams.
- The name
Ti.Network.TCPSocketis currently reserved on iOS and cannot be deprecated until a later time (see below). This makes it impractical to use the
Ti.Networknamespace as a container for TCPSocket, UDPSocket, etc.
- This allows us to reserve a specific namespace for implementation of further transport layers, and give users a convenient space to present any custom transport layers they implement.
Transport layer support
As per this spec, only
Ti.Network.Socket.TCP (TCP support) will be initially available. There are plans to introduce UDP sockets, but that will be covered in a separate spec.
A socket goes through three distinct states through its lifecycle, with one additional state to represent errors. 2a and 2b are mutually exclusive.
INITIALIZED : The socket is ready to have either
CONNECTED : The socket is connected to its specified host/port. Set before the
connected callback is called.
LISTENING : The socket is listening on its specified host/port.
CLOSED : The socket has been cleaned up via a call to
close(). A socket in this state may be re-initialized via a new call to
ERROR : The socket encountered an error. A socket in this state may be re-initialized via a new call to
error callback is called if the socket was in either the
CONNECTED state when the error occurred. When a socket enters an ERROR state, the socket is closed.
Currently, iOS TCPSocket only has the concept of a socket being valid. Distinct states provide the user with more information regarding the socket lifecycle, give clearly defined points at which callbacks are triggered, and also allow us to prevent reuse of sockets (undesirable in the case of connections accepted by a listener).
Connecting (outbound) sockets
Connecting sockets are straightforward; they connect to the specified host/port endpoint and act as I/O streams (both read and write) to that endpoint. A user-created socket enters the CONNECTED state by calling
connect() on it.
All operations other than
accept() are valid on a connecting socket.
This is equivalent to how a connecting socket functions under iOS TCPSocket.
Listening (accepting) sockets
Listening sockets accept incoming connections. A user-created socket enters the
LISTENING state by calling
listen() on it. The user is responsible for manually accepting incoming connections with the
accept() command, which flags the socket to accept the next incoming connection (however, unlike BSD, it does not block). The rationale behind giving users control over inbound connection acceptance is to allow fine-grained control over system resources; in particular, due to the limitations of mobile device network device speeds or the OS, the user may want to only allow a limited number of concurrent connections at one time.
A listening socket only may have the
close() operations called on it.
This is distinctly different from how iOS TCPSocket handles listening right now. In particular, iOS listening sockets both auto accept all incoming connections and act as a "hub" for all connections, rather than creating distinct socket objects for them; this introduces peculiarities such as associating file descriptors, rather than objects, with read/write data, and allowing the listener to "broadcast" information to all of its connected sockets. No such functionality will exist in the new socket implementation.
This current implementation is inefficient, cumbersome, presents a fragmented interface, and does not conform to what developers with network experience would consider a standard socket interface. These are all excellent reasons for changing existing behavior.
Sockets which are connecting to the local host as an endpoint are created when a listening socket accepts a new connection. These sockets arrive in the CONNECTED state and are functionally equivalent to outbound sockets.
Currently, iOS TCPSocket represents inbound sockets as file descriptors. This is unacceptable.
Changes to existing
Ti.Network.TCPSocket on iOS
For the present,
Ti.Network.TCPSocket will remain as-is on iOS, and continue to be the only way to interface with
Ti.Network.BonjourBrowser as these are iOS-only features. For release 1.7.0
Ti.Network.TCPSocket will not become deprecated (due to it being the exclusive way to interface with other iOS-only features). There will not be a
Ti.Network.TCPSocket namespace alias backported to Android as these sockets behave in a fundamentally different manner.
Ti.Network.TCPSocket will not be removed until there is Bonjour/Zero-configuration networking support for Android. At this point it will become deprecated for removal in the following release.
Due to the fundamental differences in interface and operation, there is no plan to provide a transitional bridge from
Ti.Network.Socket at any point on iOS.
Presenting sockets to the world introduces a host of security issues, including but not limited to malicious data injection via a connection over the CDN (celluar data network) via a host listening on the IP address assigned to the radio (or, equivalently, via
INADDR_ANY). In order to try and reduce the potency of these attacks, or the ability to conduct them, we should consider:
- Revoking access to any connection that comes in directly to the CDN. NOTE: This is not necessarily feasible; socket implementations on both iOS and Android contain information about the originating host, but not necessarily the interface the connection came in over.
- Limiting the possibility for standard attacks (i.e. buffer overflow)
- Providing specialized training for end developers specifically for advanced network programming
It is worth noting that on iOS, an application listening over the CDN is considered grounds for rejection.
As above, we do not support BSD's
INADDR_ANY. References to it are included in the spec for historical reasons and to clarify why it is not supported. In particular, we have no way to filter connections (inbound or outbound) based on the CDN, and
INADDR_ANY is global with no scoping, meaning we can't have it translate to "all interfaces except for CDN." For these reasons, support for this feature is removed.
The I/O Layer is intended to only be available on CONNECTED sockets. I/O does not make sense on LISTENING sockets, and sockets in the ERROR state do not have an "active" buffer.
The current iOS TCPSocket does not present a unified I/O layer with any other interface, and also confuses availability of I/O on LISTENING and CONNECTED sockets, and handles it in a clumsy way.
Ti.Network.Socket: Namespace for all socket types and related constants.
INITIALIZED: Constant representing the "initialized" state a socket
CONNECTED: Constant representing the "connected" state for a socket
LISTENING: Constant representing the "listening" state for a socket
CLOSED: Constant representing the "closed" state for a socket
ERROR: Constant representing the "error" state for a socket
Ti.Network.Socket.TCPcreateTCP(Object args)}} : Creates a new TCP socket.
Ti.Network.Socket.UDPcreateUDP(Object args)}} : Creates a new UDP socket. RESERVED; not intended to be implemented immediately.
While not currently used in this proposal, the "options" property name would be reserved for setting socket options in the future.
host: The host to connect to. Cannot be modified when not in the
INITIALIZEDstate. Supports both IPv4 and IPv6.
port: The port to connect to. Cannot be modified when not in the
listenQueueSize: Max number of pending incoming connections to be allowed when
listen()is called. Any incoming connections received while the max number of pending connections has been reached will be rejected.
timeout: The timeout for
connect()and all I/O
write()operations. Cannot be modified when not in the
options: Options for the socket (such as reuse, multicast, etc.) RESERVED; not to be implemented immediately.
connected: The callback to be fired after the socket enters the "connected" state. Only invoked following a successful
- Argument parameters:
socket: The socket which was connected
- Argument parameters:
error: The callback to be fired after the socket enters the
- Argument parameters:
socket: The socket that experienced the error
error: A stringified description of the error
errorCode: The error code of the error (potentially system-dependent)
- Argument parameters:
accepted: The callback to be fired when a listener accepts a connection.
- Argument parameters:
socket: The socket which received the connection
Ti.Network.Socketobject which represents the inbound connection; this should be considered a "connected" socket and is created in the CONNECTED state.
- Argument parameters:
state[spe:read-only] : The current state of the socket.
void connect(): Attempts to connect the socket to its host/port. Throws exception if the socket is in a CONNECTED or LISTENING state. Throws exception if a valid
porthas not been set on the proxy. Nonblocking; connection attempts are asynchronous.
void listen(): Attempts to start listening on the socket's host/port. listen() call will attempt to listen on the specified host and/or port property for the socket if they are set. This function blocks execution and throws an exception on error (and sets the socket state to
ERROR) but does not fire the
errorcallback in this event. Throws exception if the socket is in a LISTENING or CONNECTED state.
void accept(Object params): Tells a
LISTENINGsocket to accept a connection request at the top of a listener's request queue when one becomes available. Takes an argument, a box object which assigns callbacks to the created socket. Note that the
connectedcallback is not called (the socket does not "transition to" the
CONNECTEDstate - it's created in the
CONNECTEDstate) on the newly created socket. The accepted callback is called when a new connection is accepted as a result of calling accept(). If the socket is already flagged to accept the next connection, the existing accept options will be update to use the newly specified options object. Throws an exception if the socket is not in a LISTENING state.
void close(): Closes a socket. Throws exception if the socket is not in a