AdafruitTCP

AdafruitTCP makes it easier to work with raw TCP sockets. You can open sockets -- including SSL based secure socket connections -- and send and receive data using a few basic commands.

The class also and exposes two convenient (optional) callbacks:

  • Data Received Callback: Fires whenever incoming data is available (which can then be read via the .read() and related commands)
  • Disconnect Callback: Fires whenever the TCP server cause you to disconnect

You're also free to 'poll' for incoming data and connection status, but these callbacks help keep your TCP code easy to understand and more maintainable as your project grows in complexity.

TCP Socket API

The AdafruitTCP class includes the following functions:

// Misc Functions
void     usePacketBuffering     (bool enable);
void     tlsRequireVerification (bool required);
uint32_t getHandle              (void);

// Client API
virtual int     connect    	(IPAddress ip, uint16_t port);
virtual int     connect    	(const char * host, uint16_t port);
virtual int     connectSSL 	(IPAddress ip, uint16_t port);
virtual int     connectSSL 	(const char* host, uint16_t port);
virtual uint8_t connected  	(void);
virtual void    stop       	(void);

// Stream API
virtual int     read       	(void);
virtual int     read       	(uint8_t * buf, size_t size);
virtual size_t  write      	(uint8_t);
virtual size_t  write      	(const uint8_t *content, size_t len);
virtual int     available  	(void);
virtual int     peek       	(void);
virtual void    flush      	(void);

// Set callback handlers
void setReceivedCallback    (tcpcallback_t fp);
void setDisconnectCallback  (tcpcallback_t fp);

Packet Buffering

The AdafruitTCP class includes the option to enable or disable packet buffering.  

If packet buffering is enabled, outgoing data will be buffered until the buffer is full (~1500 bytes) or until .flush() is called to manually force the buffered data to be sent. 

If packet buffering is disabled, any write commands will send the data immediately, regardless of the packet or data size.  This ensure writes happen right away, but at the cost of slower overall throughput since data can't be grouped together into larger packets.

By default packet buffering is DISABLED in AdafruitTCP

void usePacketBuffering (bool enable)

This will enable or disable packet buffering with AdafruitTCP data.

Parameters:

  • enable: Set this to 'true' (1) to enable packet buffering, otherwise 'false' (0)

Returns: Nothing

TLS/SSL Certificate Verification

When opening a secure TCP connection to a TCP server, the client and server will begin to communicate with each other in an open connection to choose their cipher suite (AES, etc.), and the server will then send the client it's certificate and public key data to start the secure connection.

Normally at this point, the client will verify the server's certificate using it's root certificate chains. If verification is OK, the connection will continue, otherwise the connection will be rejected since the server has probably provided a false or invalid certificate and can't be trusted.

The problem with this approach on small embedded systems is that it takes a great deal of space (in embedded terms) to store all root certificate chains to verify server certificates against all certificate issuing authorities.  We do store a default list of the most common root certificate chains, but it isn't possible on a small MCU with limited flash storage space to store every possible root certificate option.

The WICED Feather proposes two solutions to this problem, depending on if you prefer a more secure or a simpler solution:

Verifying Certificates with the WICED Feather (Safer)

Instead of storing all root certificates, the WICED Feather allows you to generate a certificate chain for a specific domain, and then use that in your sketch, which typically requries 1-4KB of flash memory or less per domain.

This is the most secure choice but requires some additional work on your part, and you have to know in advance which sites you will access.

The procedure to convert, load and use a custom root certificate list is as follows:

  1. You use a python script (provided in the '/tools/pycert' folder) to read the root certificate data for your target domain. The script then converts the binary .der format data into a byte array in a C header (.h) file.
  2. You then pass the root certificate data into the WICED API via Feather.addRootCA (from AdafruitFeather), which allows you to add your root certificate chain to the list of default certificates used when verifying the target domain
  3. You can then enable certificate verification via tlsRequireVerification(true) in this class, which means that all server certificates must pass verification against the root certificate list on the WICED Feather or the certificate and connection will be rejected.

Ignoring Certificate Verification (Easier)

If you aren't able to store the certificate data for a specific site, or don't know which sites you will access, you can also ignore the verification process which has the effect of accepting every certificate as valid.  

This still allows for an encrypted connection (using AES, etc.), but there is no guarantee that the server you are talking to is actually the server you think you're talking to, making it a less secure option.

By default certificate verification is enabled on WICED Feather boards. You can disable verification via 'tlsRequireVerification(false)', which will cause any certificate to be accepted, but it will also allow man-in-the-middle type attacks.

The approach you take will depend on your project requirements, but in either case you can indicate to the WICED Feather API whether you want to verify server certificates via the following function:

Default Root Certificates

By default, the following root certificates are included in Featherlib, meaning you only need to add a root certificate authority if it isn't included in the list below.  

These default root certificates cover many common websites without any additional effort on your part:

  • Baltimore CyberTrust Root
    • adafruit-download.s3.amazonaws.com (may include other Amazon S3 servers)
  • DigiCert High Assurance EV Root CA
    • twitter.com
    • facebook.com
    • github.com
  • GeoTrust Global CA
    • google.com
  • GeoTrust Primary Certification Authority - G3
    • adafruit.com
  • Starfield Services Root Certificate Authority - G2
    • aws.amazon.com

void tlsRequireVerification (bool required)

Indicates whether the certificate data provided by the remote server should be verified against the local root certificate list or not. (Note: you can add new records to the root certificate list is set in the AdafruitFeather class via 'Feather.addRootCA'.)

Parameters:

  • required: Set this to 'true' (1) if certificate validation is required, or 'false' (0) if no verification is required (meaning that every certificate provided by a remote server will be considered valid!).

Returns: Nothing

Socket Handler Functions

In specialised cases (mostly when implementing sub-classes of AdafruitTCP) you may need access to the 'handle' for the TCP socket.  The .getHandle function provides access to this.

void getHandle (void)

Returns the internal TCP socket handler value that uniquely identifies this TCP socket.  This might be necessary when creating special sub-classes based on AdafruitTCP.

Parameters: None

Returns: The uint32_t socket handler value that uniquely identifies this TCP socket.

Client API

The Client API includes the following functions to connect to a TCP server:

int connect (IPAddress ip, uint16_t port)

Attempts to connect to the specified IP address and port

Parameters:

  • ip: The IPAddress where the TCP server is located
  • port: The port number to connect to (0..65535)

Returns: 'true' (1) if the connection was successfully established, otherwise 'false' (0).

int connect (const char * host, uint16_t port)

Attempts to connect to the specified domain name and port

Parameters:

  • host: A string containing the domain name to connect to
  • port: The port number to connect to (0..65536)

Returns: 'true' (1) if the connection was successfully established, otherwise 'false' (0).

int connectSSL (IPAddress ip, uint16_t port)

Connects to a secure server using SSL/TLS at the specified IP address and port.

Parameters:

  • ip: The IPAddress where the TCP server is located
  • port: The port number to connect to (0..65536)

Returns: 'true' (1) if the connection was successfully established, otherwise 'false' (0).

If certificate verification fails when trying to connect to a secure server you will get ERROR_TLS_UNTRUSTED_CERTIFICATE (5035).

Note: A set of common root certificates are already included in the WICED Feather SDK, so most HTTPS websites will work out of the box, but if you need to add a new root certificate chain the TLS/certificate data is set using the following function in the Adafruit Feather class (accessible as `Feather.addRootCA(...)`):

bool addRootCA(uint8_t const* root_certs_der, uint16_t len);

int connectSSL (const char* host, uint16_t port)

Attempts to connect to a secure server using SSL/TLS at the specified domain name and port.

Parameters:

  • host: A string containing the domain name to connect to
  • port: The port number to connect to (0..65536)

Returns: 'true' (1) if the connection was successfully established, otherwise 'false' (0).

If certificate verification fails when trying to connect to a secure server you will get ERROR_TLS_UNTRUSTED_CERTIFICATE (5035).

Note: A set of common root certificates are already included in the WICED Feather SDK, so most HTTPS websites will work out of the box, but if you need to add a new root certificate chain the TLS/certificate data is set using the following function in the Adafruit Feather class (accessible as `Feather.addRootCA(...)`):

bool addRootCA(uint8_t const* root_certs_der, uint16_t len);

uint8_t connected (void)

Indicates whether we are currently connected to the TCP server or not

Parameters: None

Returns: 'true' (1) if we are currently connected to the TCP server, otherwise 'false' (0).

void stop (void)

Closes the current connection to the TCP server (if a connection is open).

Parameters: None

Returns: Nothing

Stream API

AdafruitTCP implements the Stream class, with the following method overrides present in AdafruitTCP:

int read (void)

Reads the first available byte from the data buffer (if any data is available).

Parameters: None

Returns: The first byte of incoming data available, or -1 if no data is available.

int read (uint8_t * buf, size_t size)

Reads up to the specified number of bytes from the data buffer (if any data is available).

Parameters:

  • buf: A pointer to the buffer where data should be written if any data is available
  • size: The maximum number of bytes to read and copy into buf.

Returns: The actual number of bytes read back, and written in buf.

size_t write (uint8_t data)

Transmits a single byte to the TCP Server (or into the outgoing buffer until it can be sent if buffering is enabled).

Parameters:

  • data: The byte of data to transmit

Returns: The number of bytes written. It is normally not necessary to read this value.

size_t write (const uint8_t *content, size_t len)

Transmits a number of bytes to the TCP Server (or into the outgoing buffer until the data can be sent if buffering is enabled).

Parameters:

  • content: A pointer to the buffer containing the data to send
  • len: The number of bytes contained in content

Returns: The number of bytes successfully written.

int available (void)

Checks the number of bytes available in the incoming data buffer.

Parameters: None

Returns: The number of bytes available in the incoming data buffer, or 0 if no data is available.

int peek (void)

Reads the first available byte from the incoming data buffer without removing it from the buffer.

Parameters: None

Returns: The value of the first available byte, or -1 if no data is available.

void flush (void)

Forces any buffered data to be transmitted to the TCP server, regardless of the size of the content.

Parameters: None

Returns: Nothing

Callback API

To make working with TCP sockets easier, a simple callback API is available in AdafruitTCP based on the following functions:

void setReceivedCallback (tcpcallback_t fp)

Registers the data received callback handler.

Parameters:

  • fp: The name of the function that will be executed when received data is available from the TCP server.  See the example below for details on the function signature.

Returns: Nothing

void setDisconnectCallback (tcpcallback_t fp)

Registers the disconnect callback handler (fired when you are disconnected from the TCP server).

Parameters:

  • fp: The name of the function that will be executed when you are disconnected from the TCP server.  See the example below for details on the function signature.

Returns: Nothing

Callback Function Signatures

The data received and disconnect callbacks both require a specific function definition to work.  The function names ('receive_callback' and 'disconnect_callback') can change, but the exact signatures are shown below:

void receive_callback    ( void );
void disconnect_callback ( void );

You then register the callbacks with the dedicated set callback functions:

Make sure you register the callbacks BEFORE calling the .connect function!
// Set the callback handlers for RX and disconnect
tcp.setReceivedCallback(receive_callback);
tcp.setDisconnectCallback(disconnect_callback);

To read incoming data in the receive callback handler, you need to use the pTCP pointer, as shown in the sample code below:

void receive_callback(void)
{
  int c;

  // Print out any bytes available from the TCP server
  while ( (c = tcp.read())> 0 )
  {
    Serial.write( (isprint(c) || iscntrl(c)) ? ((char)c) : '.');
  }
}

void disconnect_callback(void)
{
  Serial.println();
  Serial.println("-------------------");
  Serial.println("DISCONNECT CALLBACK");
  Serial.println("-------------------");
  Serial.println();
}

Example: Callback Based HTTP Request

The following example shows how you can register and use the two TCP callbacks, and performs a simple TCP operation.  It opens a TCP socket to an HTTP server using port 80, requests a page, displays any incoming response data, and then waits for the HTTP server to close the TCP connection (which will show up as a disconnect callback):

#include <adafruit_feather.h>

#define WLAN_SSID       "SSID"
#define WLAN_PASS       "PASSWORD"
#define WLAN_SECURITY   ENC_TYPE_AUTO

#define TCP_DOMAIN      "www.adafruit.com"
#define TCP_FILENAME    "/testwifi/index.html"
#define TCP_PORT        80

void receive_callback    ( void );
void disconnect_callback ( void );

AdafruitTCP tcp;

void setup() 
{
  Serial.begin(115200);

  // Wait for Serial port to connect. Needed for native USB port only
  while (!Serial) { delay(1); }

  // Attempt to connect to the AP using the specified SSID/key/encoding
  if ( !Feather.connect(WLAN_SSID, WLAN_PASS, WLAN_SECURITY ) )
  {
    err_t err = Feather.errno();
    Serial.println("Connection Error:");
    switch (err)
    {
      case ERROR_WWD_ACCESS_POINT_NOT_FOUND:
        // SSID wasn't found when scanning for APs
        Serial.println("Invalid SSID");
        break;
      case ERROR_WWD_INVALID_KEY:
        // Invalid SSID passkey
        Serial.println("Invalid Password");
        break;
      default:
        // The most likely cause of errors at this point is that
        // you are just out of the device/AP operating range
        Serial.print(err);
        Serial.print(":");
        Serial.println(Feather.errstr());
        break;
    }
    // Wait around here forever!
    while(1);
  }

  // Optional: Disable TLS certificate verification (accept any server)
  Feather.tlsRequireVerification(false);

  // Optional: Set the default TCP timeout to 10s
  tcp.setTimeout(10000);

  // Set the callback handlers for RX and disconnect
  tcp.setReceivedCallback(receive_callback);
  tcp.setDisconnectCallback(disconnect_callback);

  // Try to connect to the HTTP Server
  if ( tcp.connect(TCP_DOMAIN, TCP_PORT) )
  {
    Serial.println("Connected to server");
    // Make a basic HTTP request
    tcp.printf("GET %s HTTP/1.1\r\n", TCP_FILENAME);
    tcp.printf("host: %s\r\n", TCP_DOMAIN);
    tcp.println();
  }
  else
  {
    Serial.printf("TCP connection failed: %s (%d)", tcp.errstr(), tcp.errno());
    Serial.println();
    while(1);
  }
}

void loop()
{
  // put your main code here, to run repeatedly:
}

void receive_callback(void)
{
  int c;

  // Print out any bytes available from the TCP server
  while ( (c = tcp.read())> 0 )
  {
    Serial.write( (isprint(c) || iscntrl(c)) ? ((char)c) : '.');
  }
}

void disconnect_callback(void)
{
  Serial.println();
  Serial.println("-------------------");
  Serial.println("DISCONNECT CALLBACK");
  Serial.println("-------------------");
  Serial.println();
}
Last updated on Apr 19, 2016 Published on Mar 23, 2016