diff --git a/api/Client.h b/api/Client.h index 5a1d99fe..5cf69125 100644 --- a/api/Client.h +++ b/api/Client.h @@ -27,14 +27,14 @@ namespace arduino { class Client : public Stream { public: - virtual int connect(IPAddress ip, uint16_t port) =0; - virtual int connect(const char *host, uint16_t port) =0; + virtual ErrorCode connect(IPAddress ip, uint16_t port) =0; + virtual ErrorCode connect(const char *host, uint16_t port) =0; virtual size_t write(uint8_t) =0; virtual size_t write(const uint8_t *buf, size_t size) =0; - virtual int available() = 0; - virtual int read() = 0; - virtual int read(uint8_t *buf, size_t size) = 0; - virtual int peek() = 0; + virtual ReturnValue available() = 0; + virtual ReturnValue read() = 0; + virtual ReturnValue read(uint8_t *buf, size_t size) = 0; + virtual ReturnValue peek() = 0; virtual void flush() = 0; virtual void stop() = 0; virtual uint8_t connected() = 0; @@ -43,4 +43,4 @@ class Client : public Stream { uint8_t* rawIPAddress(IPAddress& addr) { return addr.raw_address(); }; }; -} \ No newline at end of file +} diff --git a/api/ErrorCodes.h b/api/ErrorCodes.h new file mode 100644 index 00000000..c0b11254 --- /dev/null +++ b/api/ErrorCodes.h @@ -0,0 +1,142 @@ +#pragma once +#include + +namespace arduino { + +typedef int_fast32_t error_t; + +enum : error_t { + ArduinoSuccess = 0, + ArduinoError = -1, + + // TODO better names + ArduinoEPERM = -1, /**< Not owner */ + ArduinoENOENT = -2, /**< No such file or directory */ + ArduinoESRCH = -3, /**< No such context */ + ArduinoEINTR = -4, /**< Interrupted system call */ + ArduinoEIO = -5, /**< I/O error */ + ArduinoENXIO = -6, /**< No such device or address */ + ArduinoE2BIG = -7, /**< Arg list too long */ + ArduinoENOEXEC = -8, /**< Exec format error */ + ArduinoEBADF = -9, /**< Bad file number */ + ArduinoECHILD = -10, /**< No children */ + ArduinoEAGAIN = -11, /**< No more contexts */ + ArduinoENOMEM = -12, /**< Not enough core */ + ArduinoEACCES = -13, /**< Permission denied */ + ArduinoEFAULT = -14, /**< Bad address */ + ArduinoENOTBLK = -15, /**< Block device required */ + ArduinoEBUSY = -16, /**< Mount device busy */ + ArduinoEEXIST = -17, /**< File exists */ + ArduinoEXDEV = -18, /**< Cross-device link */ + ArduinoENODEV = -19, /**< No such device */ + ArduinoENOTDIR = -20, /**< Not a directory */ + ArduinoEISDIR = -21, /**< Is a directory */ + ArduinoEINVAL = -22, /**< Invalid argument */ + ArduinoENFILE = -23, /**< File table overflow */ + ArduinoEMFILE = -24, /**< Too many open files */ + ArduinoENOTTY = -25, /**< Not a typewriter */ + ArduinoETXTBSY = -26, /**< Text file busy */ + ArduinoEFBIG = -27, /**< File too large */ + ArduinoENOSPC = -28, /**< No space left on device */ + ArduinoESPIPE = -29, /**< Illegal seek */ + ArduinoEROFS = -30, /**< Read-only file system */ + ArduinoEMLINK = -31, /**< Too many links */ + ArduinoEPIPE = -32, /**< Broken pipe */ + ArduinoENOMSG = -35, /**< Unexpected message type */ + ArduinoEDEADLK = -45, /**< Resource deadlock avoided */ + ArduinoENOLCK = -46, /**< No locks available */ + ArduinoENOSTR = -60, /**< STREAMS device required */ + ArduinoENODATA = -61, /**< Missing expected message data */ + ArduinoETIME = -62, /**< STREAMS timeout occurred */ + ArduinoENOSR = -63, /**< Insufficient memory */ + ArduinoEPROTO = -71, /**< Generic STREAMS error */ + ArduinoEBADMSG = -77, /**< Invalid STREAMS message */ + ArduinoENOSYS = -88, /**< Function not implemented */ + ArduinoENOTEMPTY = -90, /**< Directory not empty */ + ArduinoENAMETOOLONG = -91, /**< File name too long */ + ArduinoELOOP = -92, /**< Too many levels of symbolic links */ + ArduinoEOPNOTSUPP = -95, /**< Operation not supported on socket */ + ArduinoEPFNOSUPPORT = -96, /**< Protocol family not supported */ + ArduinoECONNRESET = -104, /**< Connection reset by peer */ + ArduinoENOBUFS = -105, /**< No buffer space available */ + ArduinoEAFNOSUPPORT = -106, /**< Addr family not supported */ + ArduinoEPROTOTYPE = -107, /**< Protocol wrong type for socket */ + ArduinoENOTSOCK = -108, /**< Socket operation on non-socket */ + ArduinoENOPROTOOPT = -109, /**< Protocol not available */ + ArduinoESHUTDOWN = -110, /**< Can't send after socket shutdown */ + ArduinoECONNREFUSED = -111, /**< Connection refused */ + ArduinoEADDRINUSE = -112, /**< Address already in use */ + ArduinoECONNABORTED = -113, /**< Software caused connection abort */ + ArduinoENETUNREACH = -114, /**< Network is unreachable */ + ArduinoENETDOWN = -115, /**< Network is down */ + ArduinoETIMEDOUT = -116, /**< Connection timed out */ + ArduinoEHOSTDOWN = -117, /**< Host is down */ + ArduinoEHOSTUNREACH = -118, /**< No route to host */ + ArduinoEINPROGRESS = -119, /**< Operation now in progress */ + ArduinoEALREADY = -120, /**< Operation already in progress */ + ArduinoEDESTADDRREQ = -121, /**< Destination address required */ + ArduinoEMSGSIZE = -122, /**< Message size */ + ArduinoEPROTONOSUPPORT = -123, /**< Protocol not supported */ + ArduinoESOCKTNOSUPPORT = -124, /**< Socket type not supported */ + ArduinoEADDRNOTAVAIL = -125, /**< Can't assign requested address */ + ArduinoENETRESET = -126, /**< Network dropped connection on reset */ + ArduinoEISCONN = -127, /**< Socket is already connected */ + ArduinoENOTCONN = -128, /**< Socket is not connected */ + ArduinoETOOMANYREFS = -129, /**< Too many references: can't splice */ + ArduinoENOTSUP = -134, /**< Unsupported value */ + ArduinoEOVERFLOW = -139, /**< Value overflow */ + ArduinoECANCELED = -140, /**< Operation canceled */ + ArduinoEWOULDBLOCK = ArduinoEAGAIN, /**< Operation would block */ +}; + +/* Error Codes: + * In Arduino if a function returns 0 is considered to have failed, + * while any value different from 0 is considered success. + * Errors are generally represented with an int type that may vary in size depending on the platform. + * For this reason in this representation error_t type is defined with an integer type with a defined size. + */ +class ErrorCode { +public: + constexpr ErrorCode(int value): error(value != 0? ArduinoSuccess : ArduinoError) {} + constexpr ErrorCode(error_t value): error(value) {} + const error_t error; + + constexpr operator bool() const { + return error == ArduinoSuccess; + } +}; + +/* ReturnValueClass is meant to represent all the cases where with a single value + * we want to return an error, if the value is negative, or a meaningful result + * if greater than or equal to 0. + * In order to be retrocompatible with the previous definition boolean evaluation: + * - It must return true, if the value is greater than or equal 0 + * - It must return false, if the value is negatie + * - It must be evaluable as the primitive type associated with + */ +template +class ReturnValueClass { +public: + constexpr ReturnValueClass(T value) + : value(value >= 0? value : 0), error(value < 0? (error_t)value : ArduinoSuccess) {} + + // it would be nice to have a default value on error to Success + constexpr ReturnValueClass(T value, error_t error) + : value(value), error(error) {} + + const T value; // TODO should we leave the const modifier? + const error_t error; + + constexpr operator bool() const { + return error == ArduinoSuccess; + } + + constexpr operator T() const { + return error == ArduinoSuccess ? value : error; + } +}; + +using ReturnValue = ReturnValueClass; +using ReturnLongValue = ReturnValueClass; + +} diff --git a/api/HardwareCAN.h b/api/HardwareCAN.h index 57d9d582..45a21c7e 100644 --- a/api/HardwareCAN.h +++ b/api/HardwareCAN.h @@ -26,6 +26,7 @@ #include "CanMsg.h" #include "CanMsgRingbuffer.h" +#include "ErrorCodes.h" /************************************************************************************** * TYPEDEF @@ -53,7 +54,7 @@ namespace arduino class HardwareCAN { public: - virtual ~HardwareCAN() {} + virtual ~HardwareCAN() = default; /** @@ -62,7 +63,7 @@ class HardwareCAN * @param can_bitrate the bus bit rate * @return true if initialization succeeded and the controller is operational */ - virtual bool begin(CanBitRate const can_bitrate) = 0; + virtual ErrorCode begin(CanBitRate const can_bitrate) = 0; /** * Disable the CAN controller. diff --git a/api/HardwareI2C.h b/api/HardwareI2C.h index 98c6bfd7..a0cd779b 100644 --- a/api/HardwareI2C.h +++ b/api/HardwareI2C.h @@ -27,12 +27,12 @@ namespace arduino { class HardwareI2C : public Stream { public: - virtual void begin() = 0; - virtual void begin(uint8_t address) = 0; + virtual ErrorCode begin() = 0; + virtual ErrorCode begin(uint8_t address) = 0; virtual void end() = 0; virtual void setClock(uint32_t freq) = 0; - + virtual void beginTransmission(uint8_t address) = 0; virtual uint8_t endTransmission(bool stopBit) = 0; virtual uint8_t endTransmission(void) = 0; diff --git a/api/HardwareSPI.h b/api/HardwareSPI.h index 7f6aea2b..b9dd91eb 100644 --- a/api/HardwareSPI.h +++ b/api/HardwareSPI.h @@ -121,7 +121,7 @@ class HardwareSPI virtual void attachInterrupt() = 0; virtual void detachInterrupt() = 0; - virtual void begin() = 0; + virtual ErrorCode begin() = 0; virtual void end() = 0; }; diff --git a/api/HardwareSerial.h b/api/HardwareSerial.h index b687cdf1..a1bd85b1 100644 --- a/api/HardwareSerial.h +++ b/api/HardwareSerial.h @@ -88,12 +88,12 @@ namespace arduino { class HardwareSerial : public Stream { public: - virtual void begin(unsigned long) = 0; - virtual void begin(unsigned long baudrate, uint16_t config) = 0; + virtual ErrorCode begin(unsigned long) = 0; + virtual ErrorCode begin(unsigned long baudrate, uint16_t config) = 0; virtual void end() = 0; virtual int available(void) = 0; - virtual int peek(void) = 0; - virtual int read(void) = 0; + virtual ReturnValue peek(void) = 0; + virtual ReturnValue read(void) = 0; virtual void flush(void) = 0; virtual size_t write(uint8_t) = 0; using Print::write; // pull in write(str) and write(buf, size) from Print @@ -103,4 +103,4 @@ class HardwareSerial : public Stream // XXX: Are we keeping the serialEvent API? extern void serialEventRun(void) __attribute__((weak)); -} \ No newline at end of file +} diff --git a/api/Stream.h b/api/Stream.h index e81c71ba..45809914 100644 --- a/api/Stream.h +++ b/api/Stream.h @@ -23,6 +23,7 @@ #include #include "Print.h" +#include "ErrorCodes.h" // compatibility macros for testing /* @@ -57,9 +58,9 @@ class Stream : public Print int peekNextDigit(LookaheadMode lookahead, bool detectDecimal); // returns the next numeric digit in the stream or -1 if timeout public: - virtual int available() = 0; - virtual int read() = 0; - virtual int peek() = 0; + virtual ReturnValue available() = 0; + virtual ReturnValue read() = 0; + virtual ReturnValue peek() = 0; Stream() {_timeout=1000;} @@ -67,7 +68,7 @@ class Stream : public Print void setTimeout(unsigned long timeout); // sets maximum milliseconds to wait for stream data, default is 1 second unsigned long getTimeout(void) { return _timeout; } - + bool find(const char *target); // reads data from the stream until the target string is found bool find(const uint8_t *target) { return find ((const char *)target); } // returns true if target string is found, false if timed out (see setTimeout) @@ -130,4 +131,4 @@ class Stream : public Print } -using arduino::Stream; \ No newline at end of file +using arduino::Stream; diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 31b75483..a206015e 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -40,6 +40,8 @@ set(TEST_SRCS src/Common/test_map.cpp src/Common/test_max.cpp src/Common/test_min.cpp + src/ErrorCodes/test_ErrorCodes.cpp + src/ErrorCodes/test_ReturnValues.cpp src/IPAddress/test_toString.cpp src/IPAddress/test_fromString.cpp src/IPAddress/test_fromString6.cpp diff --git a/test/include/StreamMock.h b/test/include/StreamMock.h index afa4b487..805e59e3 100644 --- a/test/include/StreamMock.h +++ b/test/include/StreamMock.h @@ -26,9 +26,9 @@ class StreamMock : public arduino::Stream void operator << (char const * str); virtual size_t write(uint8_t ch) override; - virtual int available() override; - virtual int read() override; - virtual int peek() override; + arduino::ReturnValue available() override; + arduino::ReturnValue read() override; + arduino::ReturnValue peek() override; private: std::deque _stream; diff --git a/test/src/ErrorCodes/test_ErrorCodes.cpp b/test/src/ErrorCodes/test_ErrorCodes.cpp new file mode 100644 index 00000000..811d31cf --- /dev/null +++ b/test/src/ErrorCodes/test_ErrorCodes.cpp @@ -0,0 +1,57 @@ +/* + * Copyright (c) 2020 Arduino. All rights reserved. + * + * SPDX-License-Identifier: LGPL-2.1-or-later + */ + +/************************************************************************************** + * INCLUDE + **************************************************************************************/ + +#include + +#include + +using namespace arduino; + +/************************************************************************************** + * TEST CODE + **************************************************************************************/ + +TEST_CASE ("An ErrorCode can be evaluated as boolean and respect Arduino values", "[ErrorCodes-Evaluation]") { + REQUIRE((bool)ErrorCode(1) == true); + REQUIRE((bool)ErrorCode(0) == false); +} + +TEST_CASE ("An error is returned with a value different from 0 and 1", "[ErrorCodes-Evaluation]") { + THEN ("if the error is well defined the boolean evaluation of the error code respects Arduino standard") { + arduino::error_t err = 125; + ErrorCode ec(err); + REQUIRE((bool)ec == false); + REQUIRE(ec.error == err); + } + THEN ("if an int is provided the boolean evaluation of the error code respects Arduino standard and error is evaluated to ArduinoSuccess") { + int err = 1; + ErrorCode ec(err); + REQUIRE((bool)ec == true); + REQUIRE(ec.error == ArduinoSuccess); + } + THEN ("if an int is provided the boolean evaluation of the error code respects Arduino standard and error is evaluated to ArduinoError") { + int err = 0; + ErrorCode ec(err); + REQUIRE((bool)ec == false); + REQUIRE(ec.error == ArduinoError); + } + THEN ("if an int is provided the boolean evaluation of the error code respects Arduino standard and the value is not preserved") { + int err = 125; + ErrorCode ec(err); + REQUIRE((bool)ec == false); + REQUIRE(ec.error == ArduinoError); + } + THEN ("if an int is provided the boolean evaluation of the error code respects Arduino standard and the value is not preserved") { + ErrorCode ec(ArduinoELOOP); + REQUIRE((bool)ec == false); + REQUIRE(ec.error == ArduinoELOOP); + } +} + diff --git a/test/src/ErrorCodes/test_ReturnValues.cpp b/test/src/ErrorCodes/test_ReturnValues.cpp new file mode 100644 index 00000000..563128ae --- /dev/null +++ b/test/src/ErrorCodes/test_ReturnValues.cpp @@ -0,0 +1,55 @@ +/* + * Copyright (c) 2020 Arduino. All rights reserved. + * + * SPDX-License-Identifier: LGPL-2.1-or-later + */ + +/************************************************************************************** + * INCLUDE + **************************************************************************************/ + +#include + +#include + +using namespace arduino; + +/************************************************************************************** + * TEST CODE + **************************************************************************************/ + +int test_int(int i) { + return i; +} + +ReturnValue test(int i) { + return i; +} + +TEST_CASE ("A ReturnValue can be evaluated as boolean", "[ReturnValues-Evaluation]") { + // Default constructor takes an integer implicitly, if negative it means error + REQUIRE((bool)ReturnValue(1) == true); + REQUIRE((bool)ReturnValue(-12) == false); +} + +TEST_CASE ("A ReturnValue can be substituted to int in a function return value", "[ReturnValues-Evaluation]") { + REQUIRE((bool)test(1) == true); + REQUIRE((bool)test(-123) == false); + REQUIRE(test_int(1) == (int)test(1)); + REQUIRE(test_int(-123) == (int)test(-123)); +} + +TEST_CASE ("A ReturnValue can be instantiated with explicit error code and value", "[ReturnValues-Evaluation]") { + THEN("It can take a negative value as value and still be successful") { + auto rv = ReturnValue(-1231, ArduinoSuccess); + + REQUIRE((bool)rv == true); + } + THEN("It can take both a value and an error code ") { + auto rv = ReturnValue(-1231, ArduinoError); + + REQUIRE((bool)rv == false); + REQUIRE(rv.value == -1231); + REQUIRE(rv.error == ArduinoError); + } +} diff --git a/test/src/StreamMock.cpp b/test/src/StreamMock.cpp index c1768859..8c42964e 100644 --- a/test/src/StreamMock.cpp +++ b/test/src/StreamMock.cpp @@ -21,17 +21,17 @@ void StreamMock::operator << (char const * str) } size_t StreamMock::write(uint8_t ch) -{ +{ _stream.push_back(static_cast(ch)); return 1; } -int StreamMock::available() +arduino::ReturnValue StreamMock::available() { return _stream.size(); } -int StreamMock::read() +arduino::ReturnValue StreamMock::read() { if (available() == 0) return -1; @@ -44,7 +44,7 @@ int StreamMock::read() return c; } -int StreamMock::peek() +arduino::ReturnValue StreamMock::peek() { if (available() == 0) return -1;