refactored a bit, in process of documenting

This commit is contained in:
Noah Laptop 2019-03-15 17:01:50 -07:00
parent 292dd7a807
commit 6389c24bc1
4 changed files with 334 additions and 106 deletions

View file

@ -24,11 +24,8 @@
* This library was created to provide SSL functionality to the {@link https://learn.adafruit.com/adafruit-wiz5500-wiznet-ethernet-featherwing/overview} * This library was created to provide SSL functionality to the {@link https://learn.adafruit.com/adafruit-wiz5500-wiznet-ethernet-featherwing/overview}
* Adafruit Ethernet shield. Since this shield does not implement SSL functionality on * Adafruit Ethernet shield. Since this shield does not implement SSL functionality on
* its own, we need to use an external library: in this case BearSSL {@link https://bearssl.org/}, * its own, we need to use an external library: in this case BearSSL {@link https://bearssl.org/},
* which is also use in the Arduino ESP8266 core. SSLClient will serve to implement the * which is also used in the Arduino ESP8266 core. SSLClient will serve to implement the
* BearSSL functionality inbetween EthernetCLient and the User, such that the user will * BearSSL functionality inbetween EthernetClient and the User, such that the user.
* simply need to start with:
* SSLCLient client(ethCLient);
* And then call the functions they normally would with EthernetClient using SSLCLient.
* *
* This file specifically controls the class templating used to allow SSLClient to interface * This file specifically controls the class templating used to allow SSLClient to interface
* with all of the CLient-based classes. To see details on the implementations of the functions * with all of the CLient-based classes. To see details on the implementations of the functions
@ -38,12 +35,17 @@
#include <type_traits> #include <type_traits>
#include "Client.h" #include "Client.h"
#include "SSLClientImpl.h" #include "SSLClientImpl.h"
#include "SSLSession.h"
#ifndef SSLClient_H_ #ifndef SSLClient_H_
#define SSLClient_H_ #define SSLClient_H_
/** /**
* \brief This class serves as a templating proxy class for the SSLClientImpl to do the real work. * \brief The main SSLClient class
*
* TODO: fix this blurb
*
* This class serves as a templating proxy class for the SSLClientImpl to do the real work.
* *
* A problem arose when writing this class: I wanted the user to be able to construct * A problem arose when writing this class: I wanted the user to be able to construct
* this class in a single line of code (e.g. SSLClient(EthernetClient())), but I also * this class in a single line of code (e.g. SSLClient(EthernetClient())), but I also
@ -59,16 +61,23 @@
template <class C, size_t SessionCache = 1> template <class C, size_t SessionCache = 1>
class SSLClient : public SSLClientImpl { class SSLClient : public SSLClientImpl {
/** static type checks /**
* static checks
* I'm a java developer, so I want to ensure that my inheritance is safe. * I'm a java developer, so I want to ensure that my inheritance is safe.
* These checks ensure that all the functions we use on class C are * These checks ensure that all the functions we use on class C are
* actually present on class C. It does this by first checking that the * actually present on class C. It does this by checking that the
* class inherits from Client, and then that it contains a status() function. * class inherits from Client.
*
* Additionally, I ran into a lot of memory issues with large sessions caches.
* Since each session contains at max 352 bytes of memory, they eat of the
* stack quite quickly and can cause overflows. As a result, I have added a
* warning here to discourage the use of more than 3 sessions at a time. Any
* amount past that will require special modification of this library, and
* assumes you know what you are doing.
*/ */
static_assert(std::is_base_of<Client, C>::value, "C must be a Client Class!"); static_assert(std::is_base_of<Client, C>::value, "C must be a Client Class!");
static_assert(SessionCache > 0 && SessionCache < 255, "There can be no less than one and no more than 255 sessions in the cache!"); static_assert(SessionCache > 0 && SessionCache < 255, "There can be no less than one and no more than 255 sessions in the cache!");
static_assert(SessionCache <= 3, "You need to decrease the size of m_iobuf in order to have more than 3 sessions at once, otherwise memory issues will occur."); static_assert(SessionCache <= 3, "You need to decrease the size of m_iobuf in order to have more than 3 sessions at once, otherwise memory issues will occur.");
// static_assert(std::is_function<decltype(C::status)>::value, "C must have a status() function!");
public: public:
/** /**
@ -77,9 +86,10 @@ public:
* We copy the client because we aren't sure the Client object * We copy the client because we aren't sure the Client object
* is going to exists past the inital creation of the SSLClient. * is going to exists past the inital creation of the SSLClient.
* *
* @pre The client class must be able to access the internet, as SSLClient * @pre You will need to generate an array of trust_anchors (root certificates)
* cannot manage this for you. Additionally it is recommended that the analog_pin * based off of the domains you want to make SSL connections to. Check out the
* be set to input. * Wiki on the pycert-bearssl tool for a simple way to do this.
* @pre The analog_pin should be set to input.
* *
* @param trust_anchors Trust anchors used in the verification * @param trust_anchors Trust anchors used in the verification
* of the SSL server certificate, generated using the `brssl` command * of the SSL server certificate, generated using the `brssl` command
@ -88,13 +98,12 @@ public:
* @param analog_pin An analog pin to pull random bytes from, used in seeding the RNG * @param analog_pin An analog pin to pull random bytes from, used in seeding the RNG
* @param debug whether to enable or disable debug logging, must be constexpr * @param debug whether to enable or disable debug logging, must be constexpr
*/ */
explicit SSLClient(const C& client, const br_x509_trust_anchor *trust_anchors, const size_t trust_anchors_num, const int analog_pin, const DebugLevel debug = SSL_ERROR) explicit SSLClient(const C& client, const br_x509_trust_anchor *trust_anchors, const size_t trust_anchors_num, const int analog_pin, const DebugLevel debug = SSL_WARN)
: SSLClientImpl(NULL, trust_anchors, trust_anchors_num, analog_pin, debug) : SSLClientImpl(NULL, trust_anchors, trust_anchors_num, analog_pin, debug)
, m_client(client) , m_client(client)
, m_sessions{SSLSession()} , m_sessions{SSLSession()}
, m_index(0) , m_index(0)
{ {
// for (uint8_t i = 0; i < SessionCache; i++) m_sessions[i] = SSLSession();
// since we are copying the client in the ctor, we have to set // since we are copying the client in the ctor, we have to set
// the client pointer after the class is constructed // the client pointer after the class is constructed
set_client(&m_client); set_client(&m_client);
@ -106,23 +115,69 @@ public:
/* /*
* The special functions most clients have are below * The special functions most clients have are below
* Most of them smply pass through * Most of them smply pass through
*/
/**
* @brief Equivalent to SSLClient::connected() > 0
* @returns true if connected, false if not
*/ */
virtual operator bool() { return connected() > 0; } virtual operator bool() { return connected() > 0; }
/** {@link SSLClient::bool()} */
virtual bool operator==(const bool value) { return bool() == value; } virtual bool operator==(const bool value) { return bool() == value; }
/** {@link SSLClient::bool()} */
virtual bool operator!=(const bool value) { return bool() != value; } virtual bool operator!=(const bool value) { return bool() != value; }
/** @brief Returns whether or not two SSLClient objects have the same underlying client object */
virtual bool operator==(const C& rhs) { return m_client == rhs; } virtual bool operator==(const C& rhs) { return m_client == rhs; }
/** @brief Returns whether or not two SSLClient objects do not have the same underlying client object */
virtual bool operator!=(const C& rhs) { return m_client != rhs; } virtual bool operator!=(const C& rhs) { return m_client != rhs; }
virtual uint16_t localPort() { return std::is_member_function_pointer<decltype(&C::localPort)>::value ? m_client.localPort() : 0; } /** @brief Returns the local port, if the Client class has a localPort() function. Else return 0. */
virtual IPAddress remoteIP() { return std::is_member_function_pointer<decltype(&C::remoteIP)>::value ? m_client.remoteIP() : INADDR_NONE; } virtual uint16_t localPort() {
virtual uint16_t remotePort() { return std::is_member_function_pointer<decltype(&C::remotePort)>::value ? m_client.remotePort() : 0; } if (std::is_member_function_pointer<decltype(&C::localPort)>::value) return m_client.localPort();
else {
m_warn("Client class has no localPort function, so localPort() will always return 0", __func__);
return 0;
}
}
/** @brief Returns the remote IP, if the Client class has a remoteIP() function. Else return INADDR_NONE. */
virtual IPAddress remoteIP() {
if (std::is_member_function_pointer<decltype(&C::remoteIP)>::value) return m_client.remoteIP();
else {
m_warn("Client class has no remoteIP function, so remoteIP() will always return INADDR_NONE. This means that sessions caching will always be disabled.", __func__);
return INADDR_NONE;
}
}
/** @brief Returns the remote port, if the Client class has a remotePort() function. Else return 0. */
virtual uint16_t remotePort() {
if (std::is_member_function_pointer<decltype(&C::remotePort)>::value) return m_client.remotePort();
else {
m_warn("Client class has no remotePort function, so remotePort() will always return 0", __func__);
return 0;
}
}
//! get the client object /** @brief returns a refernence to the client object stored in this class. Take care not to break it. */
C& getClient() { return m_client; } C& getClient() { return m_client; }
/**
* @brief Get a sesssion reference corressponding to a host and IP, or a reference to a emptey session if none exist
*
* If no session corresponding to the host and ip exist, then this function will cycle through
* sessions in a rotating order. This allows the ssession cache to continuially store sessions,
* however it will also result in old sessions being cleared and returned. In general, it is a
* good idea to use a SessionCache size equal to the number of domains you plan on connecting to.
*
* @param host A hostname c string, or NULL if one is not availible
* @param ip An IP address
* @returns A reference to an SSLSession object
*/
virtual SSLSession& getSession(const char* host, const IPAddress& addr); virtual SSLSession& getSession(const char* host, const IPAddress& addr);
/**
* @brief Clear the session corresponding to a host and IP
*
* @param host A hostname c string, or NULL if one is not availible
* @param ip An IP address
*/
virtual void removeSession(const char* host, const IPAddress& addr); virtual void removeSession(const char* host, const IPAddress& addr);
private: private:
// utility function to find a session index based off of a host and IP // utility function to find a session index based off of a host and IP
int m_getSessionIndex(const char* host, const IPAddress& addr) const; int m_getSessionIndex(const char* host, const IPAddress& addr) const;

View file

@ -20,6 +20,35 @@
#include "SSLClient.h" #include "SSLClient.h"
// system reset definitions
static constexpr auto SYSRESETREQ = (1<<2);
static constexpr auto VECTKEY = (0x05fa0000UL);
static constexpr auto VECTKEY_MASK = (0x0000ffffUL);
/** Trigger a software reset. Only use if in unrecoverable state */
[[ noreturn ]] static void RESET() {
(*(uint32_t*)0xe000ed0cUL)=((*(uint32_t*)0xe000ed0cUL)&VECTKEY_MASK)|VECTKEY|SYSRESETREQ;
while(1) { }
}
#ifdef __arm__
// should use uinstd.h to define sbrk but Due causes a conflict
extern "C" char* sbrk(int incr);
#else // __ARM__
extern char *__brkval;
#endif // __arm__
/** Get the free memory availible in bytes (stack - heap) */
static int freeMemory() {
char top;
#ifdef __arm__
return &top - reinterpret_cast<char*>(sbrk(0));
#elif defined(CORE_TEENSY) || (ARDUINO > 103 && ARDUINO != 151)
return &top - __brkval;
#else // __arm__
return __brkval ? &top - __brkval : &top - __malloc_heap_start;
#endif // __arm__
}
/** see SSLClientImpl.h */ /** see SSLClientImpl.h */
SSLClientImpl::SSLClientImpl(Client *client, const br_x509_trust_anchor *trust_anchors, SSLClientImpl::SSLClientImpl(Client *client, const br_x509_trust_anchor *trust_anchors,
const size_t trust_anchors_num, const int analog_pin, const DebugLevel debug) const size_t trust_anchors_num, const int analog_pin, const DebugLevel debug)
@ -473,6 +502,16 @@ unsigned SSLClientImpl::m_update_engine() {
const auto avail = m_client->available(); const auto avail = m_client->available();
if (avail >= len) { if (avail >= len) {
int mem = freeMemory(); int mem = freeMemory();
// check for a stack overflow
// if the stack overflows we basically have to crash, and
// hope the user is ok with that
// since all memory is garbage we can't trust the cpu to
// execute anything properly
if (mem > 32 * 1024) {
// software reset
RESET();
}
// debug info
m_info("Memory: ", func_name); m_info("Memory: ", func_name);
m_info(mem, func_name); m_info(mem, func_name);
// free memory check // free memory check
@ -485,17 +524,6 @@ unsigned SSLClientImpl::m_update_engine() {
stop(); stop();
return 0; return 0;
} }
// check for a stack overflow
// if the stack overflows we basically have to crash, and
// hope the user is ok with that
// since all memory is garbage we can't trust the cpu to
// execute anything properly
if (mem > 32 * 1024) {
// software reset
REQUEST_EXTERNAL_RESET;
// can't print anything, so pray for reset
while (1) { }
}
m_info("Read bytes from client: ", func_name); m_info("Read bytes from client: ", func_name);
m_info(avail, func_name); m_info(avail, func_name);
m_info(len, func_name); m_info(len, func_name);

View file

@ -26,68 +26,60 @@
#ifndef SSLClientImpl_H_ #ifndef SSLClientImpl_H_
#define SSLClientImpl_H_ #define SSLClientImpl_H_
// system reset definitions /**
#define SYSRESETREQ (1<<2) * @brief Static constants defining the possible errors encountered.
#define VECTKEY (0x05fa0000UL) *
#define VECTKEY_MASK (0x0000ffffUL) * If SSLClient encounters an error, it will generally output
#define AIRCR (*(uint32_t*)0xe000ed0cUL) // fixed arch-defined address * logs into the serial moniter. If you need a way of programmatically
#define REQUEST_EXTERNAL_RESET (AIRCR=(AIRCR&VECTKEY_MASK)|VECTKEY|SYSRESETREQ) * checking the errors, you can do so with SSLCLient.getWriteError(),
* which will return one of these values.
/** error enums
* Static constants defining the possible errors encountered
* Read from getWriteError();
*/ */
enum Error { enum Error {
SSL_OK = 0, SSL_OK = 0,
/** The underlying client failed to connect, probably not an issue with SSL */
SSL_CLIENT_CONNECT_FAIL, SSL_CLIENT_CONNECT_FAIL,
/** BearSSL failed to complete the SSL handshake, check logs for bear ssl error output */
SSL_BR_CONNECT_FAIL, SSL_BR_CONNECT_FAIL,
/** The underlying client failed to write a payload, probably not an issue with SSL */
SSL_CLIENT_WRTIE_ERROR, SSL_CLIENT_WRTIE_ERROR,
/** An internal error occurred with BearSSL, check logs for diagnosis. */
SSL_BR_WRITE_ERROR, SSL_BR_WRITE_ERROR,
/** An internal error occured with SSLClient, and you probably need to submit an issue on Github. */
SSL_INTERNAL_ERROR, SSL_INTERNAL_ERROR,
/** SSLClient detected that there was not enough memory (>8000 bytes) to continue. */
SSL_OUT_OF_MEMORY SSL_OUT_OF_MEMORY
}; };
/** Debug level enum /**
* Static enum defining the debugging levels to print * @brief Level of verbosity used in logging for SSLClient.
* into the Serial monitor *
* Use these values when initializing SSLCLient to set how many logs you
* would like to see in the Serial moniter.
*/ */
enum DebugLevel { enum DebugLevel {
/** No logging output */
SSL_NONE = 0, SSL_NONE = 0,
/** Only output errors that result in connection failure */
SSL_ERROR = 1, SSL_ERROR = 1,
/** Ouput errors and warnings (useful when just starting to develop) */
SSL_WARN = 2, SSL_WARN = 2,
/** Output errors, warnings, and internal information (very verbose) */
SSL_INFO = 3, SSL_INFO = 3,
}; };
/**
#ifdef __arm__ * On error, any function in this class will terminate the socket.
// should use uinstd.h to define sbrk but Due causes a conflict * TODO: Write what this is */
extern "C" char* sbrk(int incr);
#else // __ARM__
extern char *__brkval;
#endif // __arm__
static int freeMemory() {
char top;
#ifdef __arm__
return &top - reinterpret_cast<char*>(sbrk(0));
#elif defined(CORE_TEENSY) || (ARDUINO > 103 && ARDUINO != 151)
return &top - __brkval;
#else // __arm__
return __brkval ? &top - __brkval : &top - __malloc_heap_start;
#endif // __arm__
}
/** TODO: Write what this is */
class SSLClientImpl : public Client { class SSLClientImpl : public Client {
public: public:
/** /**
* @brief initializes SSL contexts for bearSSL * @brief initializes SSL contexts for bearSSL
* *
* @pre The client class must be able to access the internet, as SSLClient * @pre You will need to generate an array of trust_anchors (root certificates)
* cannot manage this for you. Additionally it is recommended that the analog_pin * based off of the domains you want to make SSL connections to. Check out the
* be set to input. * Wiki on the pycert-bearssl tool for a simple way to do this.
* @pre The analog_pin should be set to input.
* *
* @post set_client must be called immediatly after to set the client class * @post set_client must be called immediatly after to set the client class
* pointer. * pointer.
@ -104,13 +96,8 @@ public:
*/ */
explicit SSLClientImpl(Client* client, const br_x509_trust_anchor *trust_anchors, explicit SSLClientImpl(Client* client, const br_x509_trust_anchor *trust_anchors,
const size_t trust_anchors_num, const int analog_pin, const DebugLevel debug); const size_t trust_anchors_num, const int analog_pin, const DebugLevel debug);
/** Dtor is implicit since unique_ptr handles it fine */
/** functions specific to the EthernetClient which I'll have to override */ /* functions dealing with read/write that BearSSL will be injected into */
// uint8_t status();
// uint8_t getSocketNumber() const;
/** functions dealing with read/write that BearSSL will be injected into */
/** /**
* @brief Connect over SSL to a host specified by an ip address * @brief Connect over SSL to a host specified by an ip address
* *
@ -122,46 +109,162 @@ public:
* *
* This function initializes EthernetClient by calling EthernetClient::connect * This function initializes EthernetClient by calling EthernetClient::connect
* with the parameters supplied, then once the socket is open initializes * with the parameters supplied, then once the socket is open initializes
* the appropriete bearssl contexts using the TLS_only_profile. Due to the * the appropriete bearssl contexts. Due to the design of the SSL standard,
* design of the SSL standard, this function will probably take an extended * this function will probably take an extended period (1-4sec) to negotiate
* period (1-2sec) to negotiate the handshake and finish the connection. * the handshake and finish the connection. This function runs until the SSL
* handshake succeeds or fails, as found in most Arduino libraries, so be
* sure to design around this in your code.
*
* @pre The underlying client object (passed in through the ctor) in a non-
* error state, and must be able to access the server being connected to.
* @pre SSLCLient can only have one connection at a time, so the client
* object must not already have a socket open.
* @pre There must be sufficient memory availible on the device to verify
* the certificate (if the free memory drops below 8000 bytes during certain
* points in the connection, SSLCLient will fail).
* @pre There must be a trust anchor given to the ctor that corresponds to
* the certificate provided by the IP address being connected to. For more
* information check out the wiki on the pycert-bearssl tool.
* @pre The analog pin passed to the ctor must be set to input, and must
* be wired to something sort of random (floating is fine).
* *
* @param ip The ip address to connect to * @param ip The ip address to connect to
* @param port the port to connect to * @param port the port to connect to
* @returns 1 if success, 0 if failure (as found in EthernetClient) * @returns 1 if success, 0 if failure (as found in EthernetClient)
*
* @error SSL_CLIENT_CONNECT_FAIL The client object could not connect to the host or port
* @error SSL_BR_CONNECT_FAIL BearSSL could not initialize the SSL connection.
*/ */
virtual int connect(IPAddress ip, uint16_t port); virtual int connect(IPAddress ip, uint16_t port);
/** /**
* @brief Connect over SSL using connect(ip, port), but use a DNS lookup to * @brief Connect over SSL using connect(ip, port), using a DNS lookup to
* get the IP Address first. * get the IP Address first.
* *
* This function initializes EthernetClient by calling EthernetClient::connect * This function initializes EthernetClient by calling EthernetClient::connect
* with the parameters supplied, then once the socket is open initializes * with the parameters supplied, then once the socket is open uses BearSSL to
* the appropriete bearssl contexts using the TLS_only_profile. * to complete a SSL handshake. This function runs until the SSL handshake
* succeeds or fails, as found in most Arduino libraries.
*
* SSL requires the client to generate some random bits (to be later combined
* with some random bits from the server), so SSLClient uses the least signinigant
* bits from the analog pin supplied in the ctor. The random bits are generated
* from 16 consecutive analogReads, and given to BearSSL before the handshake
* starts.
* *
* Due to the design of the SSL standard, this function will probably take an * Due to the design of the SSL standard, this function will probably take an
* extended period (1-2sec) to negotiate the handshake and finish the * extended period (1-4sec) to negotiate the handshake and finish the
* connection. Since the hostname is provided, however, BearSSL is able to keep * connection. Since the hostname is provided, however, BearSSL is able to keep
* a session cache of the clients we have connected to. This should reduce * a session cache of the clients we have connected to. This should reduce
* connection time greatly. In order to use this feature, you must reuse the * connection time to about 100-200ms. In order to use this feature, the website
* same SSLClient object to connect to the reused host. Doing this will allow * you are connecting to must support it (most do by default), you must
* BearSSL to automatically match the hostname to a cached session. * reuse the same SSLClient object, and you must reconnect to the same server.
* SSLClient automatcally stores an IP address and hostname in each session,
* ensuring that if you call connect("www.google.com") SSLClient will use a
* cached IP address instead of another DNS lookup. Because some websites have
* multiple servers on a single IP address (github.com is an example), however,
* you may find that even if you are connecting to the same host the connection
* does not resume. This is a flaw in the SSL session protocol, and has been
* resolved in future versions. On top of all that, SSL sessions can expire
* based on server criteria, which will result in a regular connection time.
* Because of all these factors, it is generally prudent to assume the
* connection will not be resumed, and go from there.
*
* @pre The underlying client object (passed in through the ctor) in a non-
* error state, and must be able to access the server being connected to.
* @pre SSLCLient can only have one connection at a time, so the client
* object must not already have a socket open.
* @pre There must be sufficient memory availible on the device to verify
* the certificate (if the free memory drops below 8000 bytes during certain
* points in the connection, SSLCLient will fail).
* @pre There must be a trust anchor given to the ctor that corresponds to
* the certificate provided by the IP address being connected to. For more
* information check out the wiki on the pycert-bearssl tool.
* @pre The analog pin passed to the ctor must be set to input, and must
* be wired to something sort of random (floating is fine).
* *
* @param host The cstring host ("www.google.com") * @param host The cstring host ("www.google.com")
* @param port the port to connect to * @param port the port to connect to (443)
* @returns 1 of success, 0 if failure (as found in EthernetClient) * @returns 1 of success, 0 if failure (as found in EthernetClient).
*
* @error SSL_CLIENT_CONNECT_FAIL The client object could not connect to the host or port
* @error SSL_BR_CONNECT_FAIL BearSSL could not initialize the SSL connection.
*/ */
virtual int connect(const char *host, uint16_t port); virtual int connect(const char *host, uint16_t port);
/** @see SSLClient::write(uint8_t*, size_t) */
virtual size_t write(uint8_t b) { return write(&b, 1); } virtual size_t write(uint8_t b) { return write(&b, 1); }
/**
* @brief Write some bytes to the SSL connection
*
* Assuming all preconditions are met, this function waits for BearSSL
* to be ready for data to be sent, then writes data to the BearSSL IO
* buffer, BUT does not initally send the data. Insead, it is
* then checked if the BearSSL IO buffer is full, and if so, this function
* waits until BearSSL has flushed the buffer (written it to the
* network client) and fills the buffer again. If the function finds
* that the BearSSL buffer is not full, it returns the number of
* bytes written. In other words, this function will only write data
* to the network if the BearSSL IO buffer is full. Instead, you must call
* SSLClient::availible or SSLClient::flush, which will detect that
* the buffer is ready for writing, and will write the data to the network.
*
* This was implemented as a buffered function because users of Arduino Client
* libraries will often write to the network as such:
* @code{.cpp}
* Client client;
* ...
* client.println("GET /asciilogo.txt HTTP/1.1");
* client.println("Host: arduino.cc");
* client.println("Connection: close");
* while (!client.available()) { ... }
* ...
* @endcode
* This is fine with most network clients. With SSL, however, if we are encryting and
* writing to the network every write() call this will result in a lot of
* small encryption tasks. Encryption takes a lot of time and code, and in general
* the larger the batch we can do it in the better. For this reason, write()
* implicitly buffers until SSLClient::availible is called, or until the buffer is full.
* If you would like to trigger a network write manually without using the SSLClient::available,
* you can also call SSLClient::flush, which will write all data and return when finished.
*
* @pre The socket and SSL layer must be connected, meaning SSLClient::connected must be true.
* @pre BearSSL must not be waiting for the recipt of user data (if it is, there is
* probably an error with how the protocol in implemented in your code).
*
* @param buf the pointer to a buffer of bytes to copy
* @param size the number of bytes to copy from the buffer
* @returns The number of bytes copied to the buffer (size), or zero if the BearSSL engine
* fails to become ready for writing data.
*/
virtual size_t write(const uint8_t *buf, size_t size); virtual size_t write(const uint8_t *buf, size_t size);
/**
* @brief Returns the number of bytes availible to read from the SSL Socket
*
* This function updates the state of the SSL engine (including writing any data,
* see SSLClient::write) and as a result should be called periodically when writing
* or expecting data. Additionally, since this function returns zero if there are
* no bytes and if SSLClient::connected is false (this same behavior is found
* in EthernetClient), it is prudent to ensure in your own code that the
* preconditions are met before checking this function to prevent an ambigious
* result.
*
* @pre SSLClient::connected must be true.
*
* @returns The number of bytes availible (can be zero), or zero if any of the pre
* conditions aren't satisfied.
*/
virtual int available(); virtual int available();
/** @see SSLClient::read(uint8_t*, size_t) */
virtual int read() { uint8_t read_val; return read(&read_val, 1) > 0 ? read_val : -1; } virtual int read() { uint8_t read_val; return read(&read_val, 1) > 0 ? read_val : -1; }
/**
* @brief Read size bytes from the SSL socket buffer, copying them into *buf, and return the number of bytes read.
*
* This function checks if bytes are ready to be read by calling SSLClient::availible,
* and if there are some copies size number of bytes from the IO buffer into buf.
*
* It should be noted that a common problem I encountered with SSL connections is
* buffer overflow, caused by the server sending too much data at once. This problem
* only occurs...
*
* TODO: finish
*/
virtual int read(uint8_t *buf, size_t size); virtual int read(uint8_t *buf, size_t size);
virtual int peek(); virtual int peek();
virtual void flush(); virtual void flush();

View file

@ -50,13 +50,59 @@
class SSLSession : public br_ssl_session_parameters { class SSLSession : public br_ssl_session_parameters {
public: public:
/**
* @brief SSLSession constructor
*
* Sets all parameters to zero, and invalidates the session
*/
explicit SSLSession() explicit SSLSession()
: m_valid_session(false) : m_valid_session(false)
, m_hostname() , m_hostname()
, m_ip(INADDR_NONE) {} , m_ip(INADDR_NONE) {}
/** @brief use clear_parameters or set_parameters instead */
SSLSession& operator=(const SSLSession&) = delete;
/** /**
* \pre must call br_ssl_engine_get_session_parameters(engine, toBearSSlSession()); * @brief Get the hostname string associated with this session
*
* @returns A String object or "" if there is no hostname
* @pre must check isValidSession before getting this value,
* as if this session in invalid this value is not guarented
* to be reset to "".
*/
const String& get_hostname() const { return m_hostname; }
/**
* @brief Get ::IPAddress associated with this session
*
* @returns A ::IPAddress object, #INADDR_NONE if there is no IP
* @pre must check isValidSession before getting this value,
* as if this session in invalid this value is not guarented
* to be reset to #INADDR_NONE.
*/
const IPAddress& get_ip() const { return m_ip; }
bool is_valid_session() const { return m_valid_session; }
/**
* @brief Set the ip address and hostname of the session.
*
* This function stores the ip Address object and hostname object into
* the session object. If hostname is not null or ip address is
* not blank, and the ::br_ssl_session_parameters values are non-zero
* it then validates the session.
*
* @pre You must call
* ::br_ssl_engine_get_session_parameters
* with this session before calling this function. This is because
* there is no way to completly validate the ::br_ssl_session_parameters
* and the session may end up in a corrupted state if this is not observed.
*
* @param ip The IP address of the host associated with the session
* @param hostname The string hostname ("www.google.com") associated with the session.
* Take care that this value is corrent, SSLSession performs no validation
* of the hostname.
*/ */
void set_parameters(const IPAddress& ip, const char* hostname = NULL) { void set_parameters(const IPAddress& ip, const char* hostname = NULL) {
// copy the hostname // copy the hostname
@ -68,8 +114,16 @@ public:
// check if both values are valid, and if so set valid to true // check if both values are valid, and if so set valid to true
if (m_ip != INADDR_NONE && session_id_len > 0 if (m_ip != INADDR_NONE && session_id_len > 0
&& (hostname == NULL || m_hostname)) m_valid_session = true; && (hostname == NULL || m_hostname)) m_valid_session = true;
// else clear
else clear_parameters();
} }
/**
* @brief delete the parameters and invalidate the session
* Roughly equivalent to this_session = SSLSession(), however
* this function preserves the String object, allowing it
* to better handle the dynamic memory needed.
*/
void clear_parameters() { void clear_parameters() {
// clear the hostname , ip, and valid session flags // clear the hostname , ip, and valid session flags
m_hostname = ""; m_hostname = "";
@ -77,21 +131,9 @@ public:
m_valid_session = false; m_valid_session = false;
} }
SSLSession& operator=(const SSLSession&) = delete; /** @brief returns a pointer to the ::br_ssl_session_parameters component of this class */
br_ssl_session_parameters* to_br_session() { return (br_ssl_session_parameters *)this; } br_ssl_session_parameters* to_br_session() { return (br_ssl_session_parameters *)this; }
/**
* \pre must check isValidSession
*/
const String& get_hostname() const { return m_hostname; }
/**
* \pre must check isValidSession
*/
const IPAddress& get_ip() const { return m_ip; }
bool is_valid_session() const { return m_valid_session; }
private: private:
bool m_valid_session; bool m_valid_session;
// aparently a hostname has a max length of 256 chars. Go figure. // aparently a hostname has a max length of 256 chars. Go figure.