From 27a2225f641c7177275dd71b02c2a48ce91c524e Mon Sep 17 00:00:00 2001 From: Eugene Gershnik Date: Sat, 23 Mar 2024 00:22:45 -0700 Subject: [PATCH] Adding WiFiSocket class Nina firmware now exposes plain BSD sockets. Using those provides a powerful and straightforward tool for people familiar with BSD sockets and lets them avoid many long standing issues with simplified WiFiServer/WiFiClient interfaces. This commit adds a WiFiSocket class that exposes all the available socket operations in a convenient and safe wrapper. All operations tested on Nano RP2040 Connect with firmware 1.5. --- src/WiFi.h | 1 + src/WiFiSocket.cpp | 88 ++++ src/WiFiSocket.h | 428 +++++++++++++++++++ src/utility/socket_drv.cpp | 823 +++++++++++++++++++++++++++++++++++++ src/utility/socket_drv.h | 66 +++ src/utility/spi_drv.cpp | 39 ++ src/utility/spi_drv.h | 2 + src/utility/wifi_spi.h | 18 + 8 files changed, 1465 insertions(+) create mode 100644 src/WiFiSocket.cpp create mode 100644 src/WiFiSocket.h create mode 100644 src/utility/socket_drv.cpp create mode 100644 src/utility/socket_drv.h diff --git a/src/WiFi.h b/src/WiFi.h index b4518df7..b2c78f52 100644 --- a/src/WiFi.h +++ b/src/WiFi.h @@ -36,6 +36,7 @@ extern "C" { #include "WiFiSSLClient.h" #include "WiFiServer.h" #include "WiFiStorage.h" +#include "WiFiSocket.h" typedef void(*FeedHostProcessorWatchdogFuncPointer)(); diff --git a/src/WiFiSocket.cpp b/src/WiFiSocket.cpp new file mode 100644 index 00000000..73390c61 --- /dev/null +++ b/src/WiFiSocket.cpp @@ -0,0 +1,88 @@ +/* + This file is part of the WiFiNINA library. + Copyright (c) 2018 Arduino SA. All rights reserved. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +/* + Created on: Mar 6, 2024 + Author: gershnik +*/ + +#include "WiFiSocket.h" + + +void WiFiSocket::close() { + if (m_handle != s_invalidHandle) { + SocketDrv::close(m_handle); + m_handle = s_invalidHandle; + } +} + + +int32_t WiFiSocket::send(const void * buf, uint16_t size) { + auto ret = SocketDrv::send(m_handle, buf, size); + if (ret == 0 && lastError() != 0) + ret = -1; + return ret; +} + +int32_t WiFiSocket::recv(void * buf, uint16_t size) { + auto ret = SocketDrv::recv(m_handle, buf, size); + if (ret == 0 && lastError() != 0) + ret = -1; + return ret; +} + +int32_t WiFiSocket::sendTo(const void * buf, uint16_t size, + const arduino::IPAddress & ipAddress, uint16_t port) { + auto ret = SocketDrv::sendTo(m_handle, buf, size, ipAddress, port); + if (ret == 0 && lastError() != 0) + ret = -1; + return ret; +} + +int32_t WiFiSocket::recvFrom(void * buf, uint16_t size, + arduino::IPAddress & remoteIpAddress, uint16_t & remotePort) { + auto ret = SocketDrv::recvFrom(m_handle, buf, size, remoteIpAddress, remotePort); + if (ret == 0 && lastError() != 0) + ret = -1; + return ret; +} + +bool WiFiSocket::setNonBlocking(bool val) { + uint32_t value = val; + auto size = SocketDrv::ioctl(m_handle, uint32_t(IOControl::NonBlockingIO), + &value, sizeof(value)); + return size != 0; +} + +int32_t WiFiSocket::availableToRead() const { + int32_t value = 0; + auto size = SocketDrv::ioctl(m_handle, uint32_t(IOControl::NRead), + &value, sizeof(value)); + if (size == 0) + return -1; + return value; +} + +bool WiFiSocket::poll(State & state) const { + auto res = SocketDrv::poll(m_handle); + if (res == SocketDrv::SocketPollFailed) + return false; + state = State(res); + return true; +} diff --git a/src/WiFiSocket.h b/src/WiFiSocket.h new file mode 100644 index 00000000..fc3634fc --- /dev/null +++ b/src/WiFiSocket.h @@ -0,0 +1,428 @@ +/* + This file is part of the WiFiNINA library. + Copyright (c) 2018 Arduino SA. All rights reserved. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +/* + Created on: Mar 6, 2024 + Author: gershnik +*/ + +#ifndef HEADER_WIFISOCKET_H_INCLUDED +#define HEADER_WIFISOCKET_H_INCLUDED + +#include + +#include + +#include "utility/socket_drv.h" + + +/** + * A plain socket + * + * This class owns the underlying socket lifetime. It is movable and + * move assignable but not copyable or copy assignable. +*/ +class WiFiSocket { +private: + static constexpr uint8_t s_invalidHandle = 255; +public: + /** + * Socket type + * + * Semantics equivalent to Unix sockets SOCK_STREAM, SOCK_DGRAM etc. + */ + enum class Type: uint8_t { + //see lwip/sockets.h SOCK_STREAM etc. for values + Stream = 1, + DGram = 2, + Raw = 3 + }; + + /** + * Socket protocol + * + * Semantics equivalent to Unix sockets IPPROTO_TCP etc. + * Not all values are implemented/supported in firmware + */ + enum Protocol : uint8_t { + //see lwip/sockets.h IPPROTO_IP etc. + IP = 0, + ICMP = 1, + TCP = 6, + UDP = 17, + IPv6 = 41, + ICMPv6 = 58, + UDPLite = 136, + Raw = 255 + }; + + /** + * Socket state bitmask + * + * These values can be obtained via poll() + */ + enum State : uint8_t { + Readable = SocketDrv::SocketReadable, + Writable = SocketDrv::SocketWritable, + ErroredOut = SocketDrv::SocketErroredOut + }; + + /** + * Custom error codes for lastError() + * + * These represent issues communicating with NINA chip + * and won't clash with errno codes returned from + * socket calls. + */ + enum class Error : uint8_t { + SpiFailure = SocketDrv::Failure + }; + +private: + /// Any numeric/boolean valued socket option + template + class ValueOption { + static_assert(sizeof(ExternalType) <= sizeof(StorageType)); + + friend WiFiSocket; + public: + static constexpr uint32_t name = Name; + + ValueOption() : m_value(0) {} + explicit ValueOption(ExternalType val): m_value(StorageType(val)) {} + + explicit operator ExternalType() const + { return ExternalType(m_value);} + ExternalType value() const + { return ExternalType(m_value);} + + private: + const void * data() const { return &m_value; } + void * data() { return &m_value; } + uint8_t size() const { return sizeof(m_value); } + + void resize(uint8_t size) {} + private: + StorageType m_value; + }; + + /// Any struct-valued socket option + template + class StructOption : public Struct { + friend WiFiSocket; + public: + static constexpr uint32_t name = Name; + + StructOption() : Struct{} {} + StructOption(const Struct & src): Struct(src) {} + + private: + const void * data() const { return static_cast(this); } + void * data() { return static_cast(this); } + uint8_t size() const { return sizeof(Struct); } + + void resize(uint8_t size) {} + }; + +public: + struct TimeVal { + long long tv_sec; + long tv_usec; + }; + + /// Available socket options types + struct Option { + //see lwip/sockets.h SO_REUSEADDR etc. + + /// SO_REUSEADDR option + using ReuseAddress = ValueOption<0x0004, bool, uint32_t>; + /// SO_KEEPALIVE option + using KeepAlive = ValueOption<0x0008, bool, uint32_t>; + /// SO_BROADCAST option + using Broadcast = ValueOption<0x0020, bool, uint32_t>; + + /// SO_ACCEPTCONN option + using AcceptsConnections = ValueOption<0x0002, bool, uint32_t>; + /// SO_RCVBUF option + using RecvBufferSize = ValueOption<0x1002, uint32_t>; + /// SO_SNDTIMEO option + using SendTimeout = StructOption<0x1005, TimeVal>; + /// SO_RCVTIMEO option + using RecvTimeout = StructOption<0x1006, TimeVal>; + /// SO_ERROR option + using GetAndClearError = ValueOption<0x1007, uint8_t, uint32_t>; + /// SO_TYPE option + using SocketType = ValueOption<0x1008, WiFiSocket::Type, uint32_t>; + /// SO_NO_CHECK option + using NoUdpChecksum = ValueOption<0x100a, bool, uint32_t>; + }; + +private: + enum class IOControl : uint32_t { + //see lwip/sockets.h + NRead = 0x4004667F, //FIONREAD + NonBlockingIO = 0x8004667E //FIONBIO + }; +public: + /** + * Retrieves error (if any) of the last method call + * + * Last error is always set, whether the call failed or succeeded. + * + * The returned value is either a standard errno value from + * the underlying socket call or one of the Error enumeration values + * Their ranges are guaranteed to be distinct. + * In case of success the value is 0. + */ + static uint8_t lastError() + { return SocketDrv::lastError(); } + + /// Creates an invalid socket + WiFiSocket() = default; + + /** + * Creates a socket + * + * This is equivalent to socket() API call. + * In case of failure the socket is created as invalid which + * can be tested via operator bool. + */ + WiFiSocket(Type type, Protocol proto): + m_handle(SocketDrv::socket(uint8_t(type), uint8_t(proto))) + {} + + /// Closes the socket + ~WiFiSocket() { + close(); + } + + /** + * Manually close the socket + * + * This makes this object an invalid socket + */ + void close(); + + /** + * Moving a socket + * + * The source socket is left in an invalid state + */ + WiFiSocket(WiFiSocket && src): m_handle(src.m_handle) { + src.m_handle = s_invalidHandle; + } + + /** + * Move-assigning a socket + * + * The source socket is left in an invalid state + */ + WiFiSocket & operator=(WiFiSocket && src) { + if (this != &src) { + close(); + m_handle = src.m_handle; + src.m_handle = s_invalidHandle; + } + return *this; + } + + /** + * Tests whether the socket is invalid. + * + * A socket is in an invalid state when it represents "no socket". + * A valid socket never becomes invalid unless it is moved out or closed. + * Similarly an invalid socket never becomes valid + * unless moved-in from a valid socket. + * + */ + explicit operator bool() const + { return m_handle != s_invalidHandle; } + + /** + * Binds a socket to given port + * @returns success flag. Check lastError() for more information about failure + */ + bool bind(uint16_t port) + { return SocketDrv::bind(m_handle, port); } + + /** + * Starts listening for incoming connections + * @returns success flag. Check lastError() for more information about failure + */ + bool listen(uint8_t backlog) + { return SocketDrv::listen(m_handle, backlog); } + + /** + * Accepts an incoming connection + * + * @returns a valid socket, if successful or invalid otherwise. Check lastError() + * for more information about the failure. + * + * @param remoteIpAddress if successful populated by the address of the remote client + * @param remotePort if successful populated by the port of the remote client + */ + WiFiSocket accept(arduino::IPAddress & remoteIpAddress, uint16_t & remotePort) + { return WiFiSocket(SocketDrv::accept(m_handle, remoteIpAddress, remotePort)); } + + /** + * Connects a socket to remote endpoint + * + * @returns success flag. Check lastError() for more information about failure + * @param ipAddress host to connect to + * @param port port to connect to + */ + bool connect(const arduino::IPAddress & ipAddress, uint16_t port) + { return SocketDrv::connect(m_handle, ipAddress, port); } + + + /** + * Sends data to remote endpoint + * + * @return the amount of data actually sent or -1 on failure. Check lastError() + * for more information about failure. The type of the return value is int32_t + * to accommodate -1. When non-negative it will never be bigger than the size parameter. + */ + int32_t send(const void * buf, uint16_t size); + + /** + * Receives data from remote endpoint + * + * @return the amount of data actually read or -1 on failure. Check lastError() + * for more information about failure. The type of the return value is int32_t + * to accommodate -1. When non-negative it will never be bigger than the size parameter. + */ + int32_t recv(void * buf, uint16_t size); + + /** + * Sends data to remote endpoint + * + * @return the amount of data actually sent or -1 on failure. Check lastError() + * for more information about failure. The type of the return value is int32_t + * to accommodate -1. When non-negative it will never be bigger than the size parameter. + */ + int32_t sendTo(const void * buf, uint16_t size, const arduino::IPAddress & ipAddress, uint16_t port); + + /** + * Receives data from remote endpoint + * + * @return the amount of data actually read or -1 on failure. Check lastError() + * for more information about failure. The type of the return value is int32_t + * to accommodate -1. When non-negative it will never be bigger than the size parameter. + */ + int32_t recvFrom(void * buf, uint16_t size, arduino::IPAddress & remoteIpAddress, uint16_t & remotePort); + + /** + * Sets the socket into non-blocking or blocking mode + * + * This is equivalent to ioctl(...FIONBIO...) + * + * @returns success flag. Check lastError() for more information about failure + */ + bool setNonBlocking(bool val); + + /** + * Retrieves the number of bytes available for reading. + * + * This is equivalent to ioctl(...FIONREAD...) + * + * @returns success flag. Check lastError() for more information about failure + */ + int32_t availableToRead() const; + + /** + * Retrieves current socket state bitmask + * + * See State enum for possible flags. This call is similar in semantics to + * calling select() on the socket. + * + * @returns success flag. Check lastError() for more information about failure + * + */ + bool poll(State & state) const; + + /** + * Sets a socket option + * + * Only a small subset of SO_SOCKET options are supported. + * These are represented by strongly typed values of types + * declared in Option struct. + * + * For example here is how to set ReuseAddress option: + * + * ``` + * if (socket.setOption(WiFiSocket::Option::ReuseAddress(true))) { + * ... + * } + * ``` + * @tparam Option one of types declared in WiFiSocket::Option + * @param option value to set + * @returns success flag. Check lastError() for more information about failure + */ + template + inline bool setOption(const Option & opt) { + return SocketDrv::setsockopt(m_handle, opt.name, opt.data(), opt.size()); + } + + /** + * Reads a socket option + * + * Only a small subset of SO_SOCKET options are supported. + * These are represented by strongly typed values of types + * declared in Option struct. + * + * For example here is how to read ReuseAddress option: + * + * ``` + * WiFiSocket::Option::ReuseAddress reuseAddress; + * if (socket.setOption(reuseAddress)) { + * ... + * } + * ``` + * + * @tparam Option one of types declared in WiFiSocket::Option + * @param option value to fill in + * @returns success flag. Check lastError() for more information about failure + */ + template + inline bool getOption(Option & opt) { + uint8_t size = opt.size(); + bool ret = SocketDrv::getsockopt(m_handle, opt.name, opt.data(), size); + if (ret) + opt.resize(size); + return ret; + } + + bool getPeerName(arduino::IPAddress & remoteIpAddress, uint16_t & remotePort) + { return SocketDrv::getPeerName(m_handle, remoteIpAddress, remotePort); } + + /** + * Retrieves underlying socket handle + * + * This is for debugging purposes only. + */ + uint8_t handle() const + { return m_handle; } +private: + explicit WiFiSocket(uint8_t handle): m_handle(handle) + {} +private: + uint8_t m_handle = s_invalidHandle; +}; + +#endif \ No newline at end of file diff --git a/src/utility/socket_drv.cpp b/src/utility/socket_drv.cpp new file mode 100644 index 00000000..d73966fa --- /dev/null +++ b/src/utility/socket_drv.cpp @@ -0,0 +1,823 @@ +/* + This file is part of the WiFiNINA library. + Copyright (c) 2018 Arduino SA. All rights reserved. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +/* + Created on: Mar 6, 2024 + Author: gershnik +*/ + +#include +#include +#include + +#include "socket_drv.h" +#include "spi_drv.h" + +extern "C" { + #include "wifi_spi.h" + #include "wl_types.h" + #include "debug.h" +} + +namespace { + class SelectSlave { + public: + SelectSlave() { + SpiDrv::waitForSlaveReady(); + SpiDrv::spiSlaveSelect(); + } + ~SelectSlave() { + SpiDrv::spiSlaveDeselect(); + } + + SelectSlave(const SelectSlave &) = delete; + SelectSlave & operator=(const SelectSlave &) = delete; + }; + + struct LastError { + bool present = false; + uint8_t value = 0; + + void reset() + { this->present = false; } + void operator=(uint8_t val) { + this->present = true; + this->value = val; + } + operator bool() const + { return this->present; } + }; +} + +static LastError g_lastError = {}; + + +uint8_t SocketDrv::socket(uint8_t type, uint8_t proto) { + + g_lastError.reset(); + if (!SpiDrv::initialized) + SpiDrv::begin(); + + + uint8_t cmd = SOCKET_SOCKET_CMD; + { + SelectSlave sel; + + // Send Command + int commandSize = 4; + SpiDrv::sendCmd(cmd, PARAM_NUMS_2); + SpiDrv::sendParam(&type, sizeof(type), NO_LAST_PARAM); + commandSize += (1 + sizeof(type)); + SpiDrv::sendParam(&proto, sizeof(proto), LAST_PARAM); + commandSize += (1 + sizeof(proto)); + + // pad to multiple of 4 + while (commandSize % 4) { + SpiDrv::readChar(); + commandSize++; + } + } + { + //Wait the reply elaboration + SelectSlave sel; + + // Wait for reply + uint8_t data = 0; + uint8_t dataLen = 0; + if (!SpiDrv::waitResponseCmd(cmd, PARAM_NUMS_1, &data, &dataLen)) { + WARN("error waitResponse"); + g_lastError = SocketDrv::Failure; + return 255; + } + + if (data != 255) //255 on remote failure + g_lastError = 0; + return data; + } +} + +bool SocketDrv::close(uint8_t s) { + + g_lastError.reset(); + if (!SpiDrv::initialized) + SpiDrv::begin(); + + uint8_t cmd = SOCKET_CLOSE_CMD; + { + SelectSlave sel; + + // Send Command + int commandSize = 4; + SpiDrv::sendCmd(cmd, PARAM_NUMS_1); + SpiDrv::sendParam(&s, sizeof(s), LAST_PARAM); + commandSize += (1 + sizeof(s)); + + // pad to multiple of 4 + while (commandSize % 4) { + SpiDrv::readChar(); + commandSize++; + } + } + + { + //Wait the reply elaboration + SelectSlave sel; + + // Wait for reply + uint8_t data = 0; + uint8_t dataLen = 0; + if (!SpiDrv::waitResponseCmd(cmd, PARAM_NUMS_1, &data, &dataLen)) { + WARN("error waitResponse"); + g_lastError = SocketDrv::Failure; + return false; + } + + if (data != 0) { + g_lastError = 0; + return true; + } + return false; + } +} + +uint8_t SocketDrv::lastError() { + + if (g_lastError) + return g_lastError.value; + + if (!SpiDrv::initialized) + SpiDrv::begin(); + + uint8_t cmd = SOCKET_ERRNO_CMD; + { + SelectSlave sel; + + // Send Command + SpiDrv::sendCmd(cmd, PARAM_NUMS_0); + } + { + //Wait the reply elaboration + SelectSlave sel; + + // Wait for reply + uint8_t data = 0; + uint8_t dataLen = 0; + if (!SpiDrv::waitResponseCmd(cmd, PARAM_NUMS_1, &data, &dataLen)) { + WARN("error waitResponse"); + g_lastError = SocketDrv::Failure; + return SocketDrv::Failure; + } + + g_lastError = data; + return data; + } +} + +bool SocketDrv::bind(uint8_t s, uint16_t port) { + + g_lastError.reset(); + if (!SpiDrv::initialized) + SpiDrv::begin(); + + uint8_t cmd = SOCKET_BIND_CMD; + { + SelectSlave sel; + + // Send Command + int commandSize = 4; + SpiDrv::sendCmd(cmd, PARAM_NUMS_2); + SpiDrv::sendParam(&s, sizeof(s), NO_LAST_PARAM); + commandSize += (1 + sizeof(s)); + SpiDrv::sendParam(port, LAST_PARAM); + commandSize += (1 + sizeof(port)); + + // pad to multiple of 4 + while (commandSize % 4) { + SpiDrv::readChar(); + commandSize++; + } + } + { + //Wait the reply elaboration + SelectSlave sel; + + // Wait for reply + uint8_t data = 0; + uint8_t dataLen = 0; + if (!SpiDrv::waitResponseCmd(cmd, PARAM_NUMS_1, &data, &dataLen)) { + WARN("error waitResponse"); + g_lastError = SocketDrv::Failure; + return false; + } + + if (data != 0) { + g_lastError = 0; + return true; + } + return false; + } +} + +bool SocketDrv::listen(uint8_t s, uint8_t backlog) { + g_lastError.reset(); + if (!SpiDrv::initialized) + SpiDrv::begin(); + + uint8_t cmd = SOCKET_LISTEN_CMD; + { + SelectSlave sel; + + // Send Command + int commandSize = 4; + SpiDrv::sendCmd(cmd, PARAM_NUMS_2); + SpiDrv::sendParam(&s, sizeof(s), NO_LAST_PARAM); + commandSize += (1 + sizeof(s)); + SpiDrv::sendParam(&backlog, sizeof(backlog), LAST_PARAM); + commandSize += (1 + sizeof(backlog)); + + // pad to multiple of 4 + while (commandSize % 4) { + SpiDrv::readChar(); + commandSize++; + } + } + { + //Wait the reply elaboration + SelectSlave sel; + + // Wait for reply + uint8_t data = 0; + uint8_t dataLen = 0; + if (!SpiDrv::waitResponseCmd(cmd, PARAM_NUMS_1, &data, &dataLen)) { + WARN("error waitResponse"); + g_lastError = SocketDrv::Failure; + return false; + } + + if (data != 0) { + g_lastError = 0; + return true; + } + return false; + } +} + +uint8_t SocketDrv::accept(uint8_t s, arduino::IPAddress & remoteIpAddress, uint16_t & remotePort) { + g_lastError.reset(); + if (!SpiDrv::initialized) + SpiDrv::begin(); + + uint8_t cmd = SOCKET_ACCEPT_CMD; + { + SelectSlave sel; + + // Send Command + int commandSize = 4; + SpiDrv::sendCmd(cmd, PARAM_NUMS_1); + SpiDrv::sendParam(&s, sizeof(s), LAST_PARAM); + commandSize += (1 + sizeof(s)); + + // pad to multiple of 4 + while (commandSize % 4) { + SpiDrv::readChar(); + commandSize++; + } + } + { + //Wait the reply elaboration + SelectSlave sel; + + // Wait for reply + uint8_t resultSocket; + uint8_t remoteIpBuf[4]; + uint16_t remotePortTmp; + tParam params[PARAM_NUMS_3] = { + {0, (char*)&resultSocket}, + {0, (char*)&remoteIpBuf}, + {0, (char*)&remotePortTmp} + }; + if (!SpiDrv::waitResponseParams(cmd, PARAM_NUMS_3, params)) { + WARN("error waitResponse"); + g_lastError = SocketDrv::Failure; + return 255; + } + + if (resultSocket != 255) { + g_lastError = 0; + remoteIpAddress = IPAddress(arduino::IPv4, remoteIpBuf); //buf is in network order + remotePort = remotePortTmp; //port is host order + } + return resultSocket; + } +} + +bool SocketDrv::connect(uint8_t s, const arduino::IPAddress & ipAddress, uint16_t port) { + + if (ipAddress.type() != arduino::IPv4) { + g_lastError = EINVAL; + return false; + } + auto ipv4Address = uint32_t(ipAddress); + + g_lastError.reset(); + if (!SpiDrv::initialized) + SpiDrv::begin(); + + uint8_t cmd = SOCKET_CONNECT_CMD; + + { + SelectSlave sel; + + // Send Command + int commandSize = 4; + SpiDrv::sendCmd(cmd, PARAM_NUMS_3); + SpiDrv::sendParam(&s, sizeof(s), NO_LAST_PARAM); + commandSize += (1 + sizeof(s)); + SpiDrv::sendParam((uint8_t *)&ipv4Address, sizeof(ipv4Address), NO_LAST_PARAM); + commandSize += (1 + sizeof(ipv4Address)); + SpiDrv::sendParam(port, LAST_PARAM); + commandSize += (1 + sizeof(port)); + + // pad to multiple of 4 + while (commandSize % 4) { + SpiDrv::readChar(); + commandSize++; + } + } + { + //Wait the reply elaboration + SelectSlave sel; + + // Wait for reply + uint8_t data = 0; + uint8_t dataLen = 0; + if (!SpiDrv::waitResponseCmd(cmd, PARAM_NUMS_1, &data, &dataLen)) { + WARN("error waitResponse"); + g_lastError = SocketDrv::Failure; + return false; + } + + if (data != 0) { + g_lastError = 0; + return true; + } + return false; + } +} + +int32_t SocketDrv::send(uint8_t s, const void * buf, uint16_t size) { + g_lastError.reset(); + if (!SpiDrv::initialized) + SpiDrv::begin(); + + //keep the size well below 4096 which seems to be SPI_MAX_DMA_LEN + if (size > 4000) + size = 4000; + + uint8_t cmd = SOCKET_SEND_CMD; + + { + SelectSlave sel; + + // Send Command + int commandSize = 4; + SpiDrv::sendCmd(cmd, PARAM_NUMS_2); + SpiDrv::sendBuffer(&s, sizeof(s), NO_LAST_PARAM); + commandSize += (2 + sizeof(s)); + SpiDrv::sendBuffer((uint8_t *)buf, size, LAST_PARAM); + commandSize += (2 + size); + + // pad to multiple of 4 + while (commandSize % 4) { + SpiDrv::readChar(); + commandSize++; + } + } + { + //Wait the reply elaboration + SelectSlave sel; + + // Wait for reply + uint8_t data[2] = {}; + uint8_t dataLen = 0; + if (!SpiDrv::waitResponseCmd(cmd, PARAM_NUMS_1, data, &dataLen)) { + WARN("error waitResponse"); + g_lastError = SocketDrv::Failure; + return -1; + } + + uint16_t ret = (uint16_t(data[0]) << 8) | uint16_t(data[1]); + if (ret) + g_lastError = 0; + return ret; + } +} + +int32_t SocketDrv::recv(uint8_t s, void * buf, uint16_t size) { + g_lastError.reset(); + if (!SpiDrv::initialized) + SpiDrv::begin(); + + uint8_t cmd = SOCKET_RECV_CMD; + + { + SelectSlave sel; + + // Send Command + int commandSize = 4; + SpiDrv::sendCmd(cmd, PARAM_NUMS_2); + SpiDrv::sendParam(&s, sizeof(s), NO_LAST_PARAM); + commandSize += (1 + sizeof(s)); + SpiDrv::sendParam((uint8_t*)&size, sizeof(size), LAST_PARAM); + commandSize += (1 + sizeof(size)); + + // pad to multiple of 4 + while (commandSize % 4) { + SpiDrv::readChar(); + commandSize++; + } + } + { + //Wait the reply elaboration + SelectSlave sel; + + // Wait for reply + uint16_t dataLen = 0; + if (!SpiDrv::waitResponseData16(cmd, (uint8_t*)buf, &dataLen)) { + WARN("error waitResponse"); + g_lastError = SocketDrv::Failure; + return -1; + } + + if (dataLen) + g_lastError = 0; + return dataLen; + } +} + +int32_t SocketDrv::sendTo(uint8_t s, const void * buf, uint16_t size, + const arduino::IPAddress & ipAddress, uint16_t port) { + + if (ipAddress.type() != arduino::IPv4) { + g_lastError = EINVAL; + return false; + } + auto ipv4Address = uint32_t(ipAddress); + uint8_t portNetOrder[2] = {uint8_t(port >> 8), uint8_t(port)}; + + g_lastError.reset(); + if (!SpiDrv::initialized) + SpiDrv::begin(); + + //keep the size well below 4096 which seems to be SPI_MAX_DMA_LEN + if (size > 4000) + size = 4000; + + uint8_t cmd = SOCKET_SENDTO_CMD; + + { + SelectSlave sel; + + // Send Command + int commandSize = 4; + SpiDrv::sendCmd(cmd, PARAM_NUMS_4); + SpiDrv::sendBuffer(&s, sizeof(s), NO_LAST_PARAM); + commandSize += (2 + sizeof(s)); + SpiDrv::sendBuffer((uint8_t *)&ipv4Address, sizeof(ipv4Address), NO_LAST_PARAM); + commandSize += (2 + sizeof(ipv4Address)); + SpiDrv::sendBuffer(portNetOrder, sizeof(portNetOrder), NO_LAST_PARAM); + commandSize += (2 + sizeof(portNetOrder)); + SpiDrv::sendBuffer((uint8_t *)buf, size, LAST_PARAM); + commandSize += (2 + size); + + // pad to multiple of 4 + while (commandSize % 4) { + SpiDrv::readChar(); + commandSize++; + } + } + { + //Wait the reply elaboration + SelectSlave sel; + + // Wait for reply + uint8_t data[2] = {}; + uint8_t dataLen = 0; + if (!SpiDrv::waitResponseCmd(cmd, PARAM_NUMS_1, data, &dataLen)) { + WARN("error waitResponse"); + g_lastError = SocketDrv::Failure; + return -1; + } + + uint16_t ret = (uint16_t(data[0]) << 8) | uint16_t(data[1]); + if (ret) + g_lastError = 0; + return ret; + } +} + +int32_t SocketDrv::recvFrom(uint8_t s, void * buf, uint16_t size, + arduino::IPAddress & remoteIpAddress, uint16_t & remotePort) { + g_lastError.reset(); + if (!SpiDrv::initialized) + SpiDrv::begin(); + + //keep the size well below 4096 which seems to be SPI_MAX_DMA_LEN + if (size > 4000) + size = 4000; + + uint8_t cmd = SOCKET_RECVFROM_CMD; + + { + SelectSlave sel; + + // Send Command + int commandSize = 4; + SpiDrv::sendCmd(cmd, PARAM_NUMS_2); + SpiDrv::sendParam(&s, sizeof(s), NO_LAST_PARAM); + commandSize += (1 + sizeof(s)); + SpiDrv::sendParam((uint8_t*)&size, sizeof(size), LAST_PARAM); + commandSize += (1 + sizeof(size)); + + // pad to multiple of 4 + while (commandSize % 4) { + SpiDrv::readChar(); + commandSize++; + } + } + { + //Wait the reply elaboration + SelectSlave sel; + + // Wait for reply + uint8_t remoteIpBuf[4]; + uint16_t remotePortTmp; + tDataParam params[PARAM_NUMS_3] = { + {0, (char*)&remoteIpBuf}, + {0, (char*)&remotePortTmp}, + {0, (char*)buf} + }; + if (!SpiDrv::waitResponseParams(cmd, PARAM_NUMS_3, params)) { + WARN("error waitResponse"); + g_lastError = SocketDrv::Failure; + return -1; + } + + if (params[2].dataLen) { + g_lastError = 0; + remoteIpAddress = IPAddress(arduino::IPv4, remoteIpBuf); //buf is in network order + remotePort = remotePortTmp; //port is in host order + } + + return params[2].dataLen; + } +} + + +uint8_t SocketDrv::ioctl(uint8_t s, uint32_t code, void * buf, uint8_t bufSize) { + + g_lastError.reset(); + if (!SpiDrv::initialized) + SpiDrv::begin(); + + uint8_t cmd = SOCKET_IOCTL_CMD; + + { + SelectSlave sel; + + // Send Command + int commandSize = 4; + SpiDrv::sendCmd(cmd, PARAM_NUMS_3); + SpiDrv::sendParam(&s, sizeof(s), NO_LAST_PARAM); + commandSize += (1 + sizeof(s)); + SpiDrv::sendParam((uint8_t*)&code, sizeof(code), NO_LAST_PARAM); + commandSize += (1 + sizeof(code)); + SpiDrv::sendParam((uint8_t *)buf, bufSize, LAST_PARAM); + commandSize += (1 + bufSize); + + // pad to multiple of 4 + while (commandSize % 4) { + SpiDrv::readChar(); + commandSize++; + } + } + { + //Wait the reply elaboration + SelectSlave sel; + + // Wait for reply + uint8_t dataLen = 0; + if (!SpiDrv::waitResponseCmd(cmd, PARAM_NUMS_1, (uint8_t*)buf, &dataLen)) { + WARN("error waitResponse"); + g_lastError = SocketDrv::Failure; + return 0; + } + + if (dataLen != 0) + g_lastError = 0; + return dataLen; + } +} + +SocketDrv::SocketState SocketDrv::poll(uint8_t s) { + g_lastError.reset(); + if (!SpiDrv::initialized) + SpiDrv::begin(); + + uint8_t cmd = SOCKET_POLL_CMD; + + { + SelectSlave sel; + + // Send Command + int commandSize = 4; + SpiDrv::sendCmd(cmd, PARAM_NUMS_1); + SpiDrv::sendParam(&s, sizeof(s), LAST_PARAM); + commandSize += (1 + sizeof(s)); + + // pad to multiple of 4 + while (commandSize % 4) { + SpiDrv::readChar(); + commandSize++; + } + } + { + //Wait the reply elaboration + SelectSlave sel; + + // Wait for reply + uint8_t data = 0; + uint8_t dataLen = 0; + if (!SpiDrv::waitResponseCmd(cmd, PARAM_NUMS_1, &data, &dataLen)) { + WARN("error waitResponse"); + g_lastError = SocketDrv::Failure; + return SocketDrv::SocketPollFailed; + } + + auto ret = SocketState(data); + if (ret != SocketDrv::SocketPollFailed) + g_lastError = 0; + return ret; + } +} + +bool SocketDrv::setsockopt(uint8_t s, uint32_t optionName, const void * option, uint8_t optLen) { + g_lastError.reset(); + if (!SpiDrv::initialized) + SpiDrv::begin(); + + uint8_t cmd = SOCKET_SETSOCKOPT_CMD; + + { + SelectSlave sel; + + // Send Command + int commandSize = 4; + SpiDrv::sendCmd(cmd, PARAM_NUMS_3); + SpiDrv::sendParam(&s, sizeof(s), NO_LAST_PARAM); + commandSize += (1 + sizeof(s)); + SpiDrv::sendParam((uint8_t*)&optionName, sizeof(optionName), NO_LAST_PARAM); + commandSize += (1 + sizeof(optionName)); + SpiDrv::sendParam((uint8_t *)option, optLen, LAST_PARAM); + commandSize += (1 + optLen); + + // pad to multiple of 4 + while (commandSize % 4) { + SpiDrv::readChar(); + commandSize++; + } + } + { + //Wait the reply elaboration + SelectSlave sel; + + // Wait for reply + uint8_t data = 0; + uint8_t dataLen = 0; + if (!SpiDrv::waitResponseCmd(cmd, PARAM_NUMS_1, &data, &dataLen)) { + WARN("error waitResponse"); + g_lastError = SocketDrv::Failure; + return false; + } + + if (data != 0) { + g_lastError = 0; + return true; + } + return false; + } +} + +bool SocketDrv::getsockopt(uint8_t s, uint32_t optionName, void * option, uint8_t & optLen) { + g_lastError.reset(); + if (!SpiDrv::initialized) + SpiDrv::begin(); + + uint8_t cmd = SOCKET_GETSOCKOPT_CMD; + + { + SelectSlave sel; + + // Send Command + int commandSize = 4; + SpiDrv::sendCmd(cmd, PARAM_NUMS_3); + SpiDrv::sendParam(&s, sizeof(s), NO_LAST_PARAM); + commandSize += (1 + sizeof(s)); + SpiDrv::sendParam((uint8_t*)&optionName, sizeof(optionName), NO_LAST_PARAM); + commandSize += (1 + sizeof(optionName)); + SpiDrv::sendParam((uint8_t *)&optLen, sizeof(optLen), LAST_PARAM); + commandSize += (1 + sizeof(optLen)); + + // pad to multiple of 4 + while (commandSize % 4) { + SpiDrv::readChar(); + commandSize++; + } + } + { + //Wait the reply elaboration + SelectSlave sel; + + // Wait for reply + if (!SpiDrv::waitResponseCmd(cmd, PARAM_NUMS_1, (uint8_t *)option, &optLen)) { + WARN("error waitResponse"); + g_lastError = SocketDrv::Failure; + return false; + } + + if (optLen != 0) { + g_lastError = 0; + return true; + } + return false; + } +} + +bool SocketDrv::getPeerName(uint8_t s, arduino::IPAddress & remoteIpAddress, uint16_t & remotePort) { + g_lastError.reset(); + if (!SpiDrv::initialized) + SpiDrv::begin(); + + uint8_t cmd = SOCKET_GETPEERNAME_CMD; + + { + SelectSlave sel; + + // Send Command + int commandSize = 4; + SpiDrv::sendCmd(cmd, PARAM_NUMS_1); + SpiDrv::sendParam(&s, sizeof(s), LAST_PARAM); + commandSize += (1 + sizeof(s)); + + // pad to multiple of 4 + while (commandSize % 4) { + SpiDrv::readChar(); + commandSize++; + } + } + { + //Wait the reply elaboration + SelectSlave sel; + + // Wait for reply + uint8_t res; + uint8_t remoteIpBuf[4]; + uint16_t remotePortTmp; + tParam params[PARAM_NUMS_3] = { + {0, (char*)&res}, + {0, (char*)&remoteIpBuf}, + {0, (char*)&remotePortTmp} + }; + if (!SpiDrv::waitResponseParams(cmd, PARAM_NUMS_3, params)) { + WARN("error waitResponse"); + g_lastError = SocketDrv::Failure; + return -1; + } + + if (res) { + g_lastError = 0; + remoteIpAddress = IPAddress(arduino::IPv4, remoteIpBuf); //buf is in network order + remotePort = remotePortTmp; //port is in host order + return true; + } + + return false; + } +} + diff --git a/src/utility/socket_drv.h b/src/utility/socket_drv.h new file mode 100644 index 00000000..1853df6e --- /dev/null +++ b/src/utility/socket_drv.h @@ -0,0 +1,66 @@ +/* + This file is part of the WiFiNINA library. + Copyright (c) 2018 Arduino SA. All rights reserved. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +/* + Created on: Mar 6, 2024 + Author: gershnik +*/ + +#ifndef HEADER_UTILITY_SOCKET_DRV_H_INCLUDED +#define HEADER_UTILITY_SOCKET_DRV_H_INCLUDED + +#include + +#include + +class IPAddress; + +namespace SocketDrv { + + enum CommunicationError : uint8_t { + Failure = 255 + }; + + enum SocketState : uint8_t { + SocketReadable = 0x01, + SocketWritable = 0x02, + SocketErroredOut = 0x04, + SocketPollFailed = 0x80 + }; + + uint8_t socket(uint8_t type, uint8_t proto); + bool close(uint8_t s); + uint8_t lastError(); + bool bind(uint8_t s, uint16_t port); + bool listen(uint8_t s, uint8_t backlog); + uint8_t accept(uint8_t s, arduino::IPAddress & remoteIpAddress, uint16_t & remotePort); + bool connect(uint8_t s, const arduino::IPAddress & ipAddress, uint16_t port); + int32_t send(uint8_t s, const void * buf, uint16_t size); + int32_t recv(uint8_t s, void * buf, uint16_t size); + int32_t sendTo(uint8_t s, const void * buf, uint16_t size, const arduino::IPAddress & ipAddress, uint16_t port); + int32_t recvFrom(uint8_t s, void * buf, uint16_t size, arduino::IPAddress & remoteIpAddress, uint16_t & remotePort); + uint8_t ioctl(uint8_t s, uint32_t code, void * buf, uint8_t bufSize); + SocketState poll(uint8_t s); + bool setsockopt(uint8_t s, uint32_t optionName, const void * option, uint8_t optLen); + bool getsockopt(uint8_t s, uint32_t optionName, void * option, uint8_t & optLen); + bool getPeerName(uint8_t s, arduino::IPAddress & remoteIpAddress, uint16_t & remotePort); +} + + +#endif \ No newline at end of file diff --git a/src/utility/spi_drv.cpp b/src/utility/spi_drv.cpp index 2673112d..0dec1121 100644 --- a/src/utility/spi_drv.cpp +++ b/src/utility/spi_drv.cpp @@ -378,6 +378,45 @@ int SpiDrv::waitResponseParams(uint8_t cmd, uint8_t numParam, tParam* params) return 1; } +int SpiDrv::waitResponseParams(uint8_t cmd, uint8_t numParam, tDataParam* params) +{ + char _data = 0; + int i =0, ii = 0; + + + IF_CHECK_START_CMD(_data) + { + CHECK_DATA(cmd | REPLY_FLAG, _data){}; + + uint8_t _numParam = readChar(); + if (_numParam != 0) + { + for (i=0; i<_numParam; ++i) + { + params[i].dataLen = readParamLen16(); + for (ii=0; ii