diff --git a/examples/GSM/GSM.ino b/examples/GSM/GSM.ino index c8ed4e6..da218c6 100644 --- a/examples/GSM/GSM.ino +++ b/examples/GSM/GSM.ino @@ -13,7 +13,7 @@ // This example shows how to use TTGO T-A7670 (ESP32 with SIMCom SIMA7670) and TinyGSMClient to create OAuth2 access token. -// To allow TinyGSM library integration, the following macro should be defined in src/FS_Config.h. +// To allow TinyGSM library integration, the following macro should be defined in src/FS_Config.h or your own config file src/Custom_FS_Config.h. // #define TINY_GSM_MODEM_SIM7600 #define TINY_GSM_MODEM_SIM7600 // SIMA7670 Compatible with SIM7600 AT instructions @@ -42,6 +42,9 @@ const char apn[] = "YourAPN"; const char gprsUser[] = ""; const char gprsPass[] = ""; +#define uS_TO_S_FACTOR 1000000ULL // Conversion factor for micro seconds to seconds +#define TIME_TO_SLEEP 600 // Time ESP32 will go to sleep (in seconds) + #define UART_BAUD 115200 #define PIN_DTR 25 #define PIN_TX 26 @@ -58,9 +61,7 @@ const char gprsPass[] = ""; #define SD_SCLK 14 #define SD_CS 13 - #include - #include /** These credentials are taken from Service Account key file (JSON) @@ -72,27 +73,56 @@ const char gprsPass[] = ""; #define CLIENT_EMAIL "Client Email" // Taken from "client_email" key in JSON file. const char PRIVATE_KEY[] PROGMEM = "-----BEGIN PRIVATE KEY-----\\n-----END PRIVATE KEY-----\n"; // Taken from "private_key" key in JSON file. - -// Set serial for debug console -#define SerialMon Serial - -// Set serial for AT commands (to the module) -#define SerialAT Serial1 - TinyGsm modem(SerialAT); TinyGsmClient gsm_client(modem); SignerConfig config; -void initModem() +void tokenStatusCallback(TokenInfo info) { - - if (modem.isGprsConnected()) + if (info.status == esp_signer_token_status_error) { - modem.gprsDisconnect(); - SerialMon.println(F("GPRS disconnected")); + Signer.printf("Token info: type = %s, status = %s\n", Signer.getTokenType(info).c_str(), Signer.getTokenStatus(info).c_str()); + Signer.printf("Token error: %s\n", Signer.getTokenError(info).c_str()); } + else + { + Signer.printf("Token info: type = %s, status = %s\n", Signer.getTokenType(info).c_str(), Signer.getTokenStatus(info).c_str()); + if (info.status == esp_signer_token_status_ready) + Signer.printf("Token: %s\n", Signer.accessToken().c_str()); + } +} + +void setup() +{ + + SerialMon.begin(115200); + + delay(10); + pinMode(BAT_EN, OUTPUT); + digitalWrite(BAT_EN, HIGH); + + // A7670 Reset + pinMode(RESET, OUTPUT); + digitalWrite(RESET, LOW); + delay(100); + digitalWrite(RESET, HIGH); + delay(3000); + digitalWrite(RESET, LOW); + + pinMode(PWR_PIN, OUTPUT); + digitalWrite(PWR_PIN, LOW); + delay(100); + digitalWrite(PWR_PIN, HIGH); + delay(1000); + digitalWrite(PWR_PIN, LOW); + + DBG("Wait..."); + + delay(3000); + + SerialAT.begin(UART_BAUD, SERIAL_8N1, PIN_RX, PIN_TX); // Restart takes quite some time // To skip it, call init() instead of restart() @@ -113,31 +143,7 @@ void initModem() if (modem.waitResponse(10000L) != 1) { DBG(" setNetworkMode faill"); - return; } -} - -void tokenStatusCallback(TokenInfo info) -{ - if (info.status == esp_signer_token_status_error) - { - Signer.printf("Token info: type = %s, status = %s\n", Signer.getTokenType(info).c_str(), Signer.getTokenStatus(info).c_str()); - Signer.printf("Token error: %s\n", Signer.getTokenError(info).c_str()); - } - else - { - Signer.printf("Token info: type = %s, status = %s\n", Signer.getTokenType(info).c_str(), Signer.getTokenStatus(info).c_str()); - if (info.status == esp_signer_token_status_ready) - Signer.printf("Token: %s\n", Signer.accessToken().c_str()); - } -} - -void setup() -{ - - SerialMon.begin(115200); - - SerialAT.begin(UART_BAUD, SERIAL_8N1, PIN_RX, PIN_TX); String name = modem.getModemName(); DBG("Modem Name:", name); @@ -145,8 +151,6 @@ void setup() String modemInfo = modem.getModemInfo(); DBG("Modem Info:", modemInfo); - initModem(); - /* Assign the sevice account credentials and private key (required) */ config.service_account.data.client_email = CLIENT_EMAIL; config.service_account.data.project_id = PROJECT_ID; diff --git a/library.json b/library.json index 1701578..4a826ae 100644 --- a/library.json +++ b/library.json @@ -1,6 +1,6 @@ { "name": "ESP Signer", - "version": "1.4.1", + "version": "1.4.2", "keywords": "communication, REST, esp32, esp8266, raspberrypi, arduino", "description": "The Google OAuth2.0 access token generation for Arduino devices. This library also supports external networking devices wirh Arduino Clients e.g. WiFiClient, EthernetClient, and GSMClient.", "repository": { diff --git a/library.properties b/library.properties index 24d3db0..bfc821d 100644 --- a/library.properties +++ b/library.properties @@ -1,6 +1,6 @@ name=ESP Signer -version=1.4.1 +version=1.4.2 author=Mobizt diff --git a/src/ESP_Signer_Error.h b/src/ESP_Signer_Error.h index 2b4ad15..d52bb16 100644 --- a/src/ESP_Signer_Error.h +++ b/src/ESP_Signer_Error.h @@ -1,5 +1,5 @@ /** - * Created August 12, 2023 + * Created August 21, 2023 */ #ifndef ESP_SIGNER_ERROR_H @@ -69,18 +69,16 @@ #define ESP_SIGNER_ERROR_EXTERNAL_CLIENT_DISABLED /* */ (ESP_SIGNER_ERROR_RANGE - 4) #define ESP_SIGNER_ERROR_EXTERNAL_CLIENT_NOT_INITIALIZED /* */ (ESP_SIGNER_ERROR_RANGE - 5) #define ESP_SIGNER_ERROR_TOKEN_SET_TIME /* */ (ESP_SIGNER_ERROR_RANGE - 6) -#define ESP_SIGNER_ERROR_TOKEN_CREATE_HASH /* */ (ESP_SIGNER_ERROR_RANGE - 7) -#define ESP_SIGNER_ERROR_TOKEN_PARSE_PK /* */ (ESP_SIGNER_ERROR_RANGE - 8) -#define ESP_SIGNER_ERROR_TOKEN_SIGN /* */ (ESP_SIGNER_ERROR_RANGE - 9) -#define ESP_SIGNER_ERROR_TOKEN_EXCHANGE /* */ (ESP_SIGNER_ERROR_RANGE - 10) -#define ESP_SIGNER_ERROR_TOKEN_NOT_READY /* */ (ESP_SIGNER_ERROR_RANGE - 11) -#define ESP_SIGNER_ERROR_SYS_TIME_IS_NOT_READY /* */ (ESP_SIGNER_ERROR_RANGE - 12) -#define ESP_SIGNER_ERROR_NTP_SYNC_TIMED_OUT /* */ (ESP_SIGNER_ERROR_RANGE - 13) -#define ESP_SIGNER_ERROR_TOKEN_COMPLETE_NOTIFY /* */ (ESP_SIGNER_ERROR_RANGE - 14) -#define ESP_SIGNER_ERROR_TOKEN_COMPLETE_UNNOTIFY /* */ (ESP_SIGNER_ERROR_RANGE - 15) -#define ESP_SIGNER_ERROR_TOKEN_ERROR_UNNOTIFY /* */ (ESP_SIGNER_ERROR_RANGE - 16) -#define ESP_SIGNER_ERROR_UDP_CLIENT_REQUIRED /* */ (ESP_SIGNER_ERROR_RANGE - 17) -#define ESP_SIGNER_ERROR_MISSING_SERVICE_ACCOUNT_CREDENTIALS /* */ (ESP_SIGNER_ERROR_RANGE - 18) -#define ESP_SIGNER_ERROR_SERVICE_ACCOUNT_JSON_FILE_PARSING_ERROR /* */ (ESP_SIGNER_ERROR_RANGE - 19) +#define ESP_SIGNER_ERROR_TOKEN_PARSE_PK /* */ (ESP_SIGNER_ERROR_RANGE - 7) +#define ESP_SIGNER_ERROR_TOKEN_SIGN /* */ (ESP_SIGNER_ERROR_RANGE - 8) +#define ESP_SIGNER_ERROR_TOKEN_EXCHANGE /* */ (ESP_SIGNER_ERROR_RANGE - 9) +#define ESP_SIGNER_ERROR_TOKEN_NOT_READY /* */ (ESP_SIGNER_ERROR_RANGE - 10) +#define ESP_SIGNER_ERROR_SYS_TIME_IS_NOT_READY /* */ (ESP_SIGNER_ERROR_RANGE - 11) +#define ESP_SIGNER_ERROR_NTP_SYNC_TIMED_OUT /* */ (ESP_SIGNER_ERROR_RANGE - 12) +#define ESP_SIGNER_ERROR_TOKEN_COMPLETE_NOTIFY /* */ (ESP_SIGNER_ERROR_RANGE - 13) +#define ESP_SIGNER_ERROR_TOKEN_COMPLETE_UNNOTIFY /* */ (ESP_SIGNER_ERROR_RANGE - 14) +#define ESP_SIGNER_ERROR_TOKEN_ERROR_UNNOTIFY /* */ (ESP_SIGNER_ERROR_RANGE - 15) +#define ESP_SIGNER_ERROR_MISSING_SERVICE_ACCOUNT_CREDENTIALS /* */ (ESP_SIGNER_ERROR_RANGE - 16) +#define ESP_SIGNER_ERROR_SERVICE_ACCOUNT_JSON_FILE_PARSING_ERROR /* */ (ESP_SIGNER_ERROR_RANGE - 17) #endif \ No newline at end of file diff --git a/src/ESP_Signer_Helper.h b/src/ESP_Signer_Helper.h index 885a5cb..25e82ed 100644 --- a/src/ESP_Signer_Helper.h +++ b/src/ESP_Signer_Helper.h @@ -1,5 +1,5 @@ /** - * Created August 12, 2023 + * Created August 21, 2023 */ #ifndef ESP_SIGNER_HELPER_H @@ -253,36 +253,6 @@ namespace StringHelper return mbfs->getReservedLen(len); } - inline void splitString(const MB_String &str, MB_VECTOR out, const char delim) - { - size_t current = 0, previous = 0; - current = str.find(delim, 0); - MB_String s; - while (current != MB_String::npos) - { - s.clear(); - str.substr(s, previous, current - previous); - s.trim(); - if (s.length() > 0) - out.push_back(s); - - previous = current + 1; - current = str.find(delim, previous); - Utils::idle(); - } - - s.clear(); - - if (previous > 0 && current == MB_String::npos) - str.substr(s, previous, str.length() - previous); - else - s = str; - s.trim(); - if (s.length() > 0) - out.push_back(s); - s.clear(); - } - inline void pushTk(const MB_String &str, MB_VECTOR &tk) { MB_String s = str; diff --git a/src/ESP_Signer_Network.h b/src/ESP_Signer_Network.h index 787e833..b201e1e 100644 --- a/src/ESP_Signer_Network.h +++ b/src/ESP_Signer_Network.h @@ -1,5 +1,5 @@ /** - * Created August 12, 2023 + * Created August 21, 2023 */ #ifndef ESP_SIGNER_NETWORK_H #define ESP_SIGNER_NETWORK_H @@ -7,6 +7,9 @@ #include "FS_Config.h" #include "ESP_Signer_ESP8266_Supports.h" +#if __has_include() +#include +#endif #if defined(ESP32) || defined(ESP8266) || defined(ARDUINO_RASPBERRY_PI_PICO_W) || \ defined(ARDUINO_UNOWIFIR4) || defined(ARDUINO_PORTENTA_C33) || \ diff --git a/src/FS_Config.h b/src/FS_Config.h index 8564fe6..efd9487 100644 --- a/src/FS_Config.h +++ b/src/FS_Config.h @@ -85,13 +85,13 @@ static SdFat sd_fat_fs; // should declare as static here // #define ESP_SIGNER_DISABLE_NATIVE_ETHERNET // For ESP8266 ENC28J60 Ethernet module -// #define ESP_SIGNER_ENABLE_ESP8266_ENC28J60_ETH +// #define ENABLE_ESP8266_ENC28J60_ETH // For ESP8266 W5100 Ethernet module -// #define ESP_SIGNER_ENABLE_ESP8266_W5100_ETH +// #define ENABLE_ESP8266_W5100_ETH // For ESP8266 W5500 Ethernet module -// #define ESP_SIGNER_ENABLE_ESP8266_W5500_ETH +// #define ENABLE_ESP8266_W5500_ETH // To use your custom config, create Custom_FS_Config.h in the same folder of this FS_Config.h file #if __has_include("Custom_FS_Config.h") diff --git a/src/auth/GAuth_OAUth2_Client.cpp b/src/auth/GAuth_OAUth2_Client.cpp index 4c56b3b..3f128cc 100644 --- a/src/auth/GAuth_OAUth2_Client.cpp +++ b/src/auth/GAuth_OAUth2_Client.cpp @@ -1,9 +1,9 @@ /** - * Google OAuth2.0 Client v1.0.2 + * Google OAuth2.0 Client v1.0.3 * * This library supports Espressif ESP8266, ESP32 and Raspberry Pi Pico MCUs. * - * Created August 12, 2023 + * Created August 21, 2023 * * The MIT License (MIT) * Copyright (c) 2022 K. Suwatchai (Mobizt) @@ -53,7 +53,7 @@ void GAuth_OAuth2_Client::begin(esp_signer_gauth_cfg_t *cfg, MB_FS *mbfs, uint32 void GAuth_OAuth2_Client::end() { freeJson(); -#if defined(HAS_WIFIMULTI) +#if defined(ESP_SIGNER_HAS_WIFIMULTI) if (multi) delete multi; multi = nullptr; @@ -68,9 +68,15 @@ void GAuth_OAuth2_Client::newClient(GAuth_TCP_Client **client) if (!*client) { *client = new GAuth_TCP_Client(); - // restore only external client (gsm client integration cannot restore) + if (_cli_type == esp_signer_client_type_external_basic_client) (*client)->setClient(_cli, _net_con_cb, _net_stat_cb); + else if (_cli_type == esp_signer_client_type_external_gsm_client) + { +#if defined(ESP_SIGNER_GSM_MODEM_IS_AVAILABLE) + (*client)->setGSMClient(_cli, _modem, _pin.c_str(), _apn.c_str(), _user.c_str(), _password.c_str()); +#endif + } else (*client)->_client_type = _cli_type; } @@ -80,11 +86,24 @@ void GAuth_OAuth2_Client::freeClient(GAuth_TCP_Client **client) { if (*client) { - // Keep external client pointers _cli_type = (*client)->type(); - _net_con_cb = (*client)->_network_connection_cb; - _net_stat_cb = (*client)->_network_status_cb; _cli = (*client)->_basic_client; + if (_cli_type == esp_signer_client_type_external_basic_client) + { + _net_con_cb = (*client)->_network_connection_cb; + _net_stat_cb = (*client)->_network_status_cb; + } + else if (_cli_type == esp_signer_client_type_external_gsm_client) + { +#if defined(ESP_SIGNER_GSM_MODEM_IS_AVAILABLE) + _pin = (*client)->_pin; + _apn = (*client)->_apn; + _user = (*client)->_user; + _password = (*client)->_password; + _modem = (*client)->_modem; +#endif + } + delete *client; } *client = nullptr; @@ -402,6 +421,28 @@ void GAuth_OAuth2_Client::freeJson() resultPtr = nullptr; } +void GAuth_OAuth2_Client::tryGetTime() +{ + + if (!tcpClient || config->internal.clock_rdy) + return; + + _cli_type = tcpClient->type(); + + if (tcpClient->type() == esp_signer_client_type_external_gsm_client) + { + uint32_t _time = tcpClient->gprsGetTime(); + if (_time > 0) + { + *mb_ts = _time; + TimeHelper::setTimestamp(_time, mb_ts_offset); + config->internal.clock_rdy = TimeHelper::clockReady(mb_ts, mb_ts_offset); + } + } + else + TimeHelper::syncClock(mb_ts, mb_ts_offset, config->time_zone, config); +} + void GAuth_OAuth2_Client::tokenProcessingTask() { // We don't have to use memory reserved tasks e.g., RTOS task in ESP32 for this JWT @@ -443,17 +484,7 @@ void GAuth_OAuth2_Client::tokenProcessingTask() } // check or set time again - if (_cli_type == esp_signer_client_type_external_gsm_client) - { - uint32_t _time = tcpClient->gprsGetTime(); - if (_time > 0) - { - *mb_ts = _time; - TimeHelper::setTimestamp(_time, mb_ts_offset); - } - } - else - TimeHelper::syncClock(mb_ts, mb_ts_offset, config->time_zone, config); + tryGetTime(); // exit task immediately if time is not ready synched // which handleToken function should run repeatedly to enter this function again. @@ -471,7 +502,7 @@ void GAuth_OAuth2_Client::tokenProcessingTask() { // time must be set first - TimeHelper::syncClock(mb_ts, mb_ts_offset, config->time_zone, config); + tryGetTime(); config->internal.last_jwt_begin_step_millis = millis(); if (config->internal.clock_rdy) @@ -1440,9 +1471,6 @@ void GAuth_OAuth2_Client::errorToString(int httpCode, MB_String &buff) case ESP_SIGNER_ERROR_TOKEN_PARSE_PK: buff += F("RSA private key parsing failed"); break; - case ESP_SIGNER_ERROR_TOKEN_CREATE_HASH: - buff += F("create message digest"); - break; case ESP_SIGNER_ERROR_TOKEN_SIGN: buff += F("JWT token signing failed"); break; @@ -1479,10 +1507,6 @@ void GAuth_OAuth2_Client::errorToString(int httpCode, MB_String &buff) buff += F("External client is not yet initialized."); return; - case ESP_SIGNER_ERROR_UDP_CLIENT_REQUIRED: - buff += F("UDP client is required for NTP server time synching based on your network type \ne.g. WiFiUDP or EthernetUDP. Please call Signer.setUDPClient(&udpClient, gmtOffset); to assign the UDP client."); - return; - case ESP_SIGNER_ERROR_MISSING_SERVICE_ACCOUNT_CREDENTIALS: buff += F("The Service Account Credentials are missing."); return; diff --git a/src/auth/GAuth_OAUth2_Client.h b/src/auth/GAuth_OAUth2_Client.h index ec85529..ee9a466 100644 --- a/src/auth/GAuth_OAUth2_Client.h +++ b/src/auth/GAuth_OAUth2_Client.h @@ -1,9 +1,9 @@ /** - * Google OAuth2.0 Client v1.0.2 + * Google OAuth2.0 Client v1.0.3 * * This library supports Espressif ESP8266, ESP32 and Raspberry Pi Pico MCUs. * - * Created August 12, 2023 + * Created August 21, 2023 * * The MIT License (MIT) * Copyright (c) 2022 K. Suwatchai (Mobizt) @@ -70,10 +70,15 @@ class GAuth_OAuth2_Client uint16_t reconnect_tmo = 10 * 1000; esp_signer_client_type _cli_type = esp_signer_client_type_undefined; - ESP_Signer_NetworkConnectionRequestCallback _net_con_cb = NULL; + ESP_Signer_NetworkConnectionRequestCallback _net_con_cb = NULL; ESP_Signer_NetworkStatusRequestCallback _net_stat_cb = NULL; Client *_cli = nullptr; +#if defined(ESP_SIGNER_GSM_MODEM_IS_AVAILABLE) + MB_String _pin, _apn, _user, _password; + void *_modem = nullptr; +#endif + /* intitialize the class */ void begin(esp_signer_gauth_cfg_t *cfg, MB_FS *mbfs, uint32_t *mb_ts, uint32_t *mb_ts_offset); void end(); @@ -113,6 +118,8 @@ class GAuth_OAuth2_Client bool handleTaskError(int code, int httpCode = 0); // parse the auth token response bool handleResponse(GAuth_TCP_Client *client, int &httpCode, MB_String &payload, bool stopSession = true); + /* Get time */ + void tryGetTime(); /* process the tokens (generation, signing, request and refresh) */ void tokenProcessingTask(); /* encode and sign the JWT token */ @@ -160,12 +167,9 @@ class GAuth_OAuth2_Client } #endif -#if defined(MB_ARDUINO_PICO) -#if __has_include() -#define HAS_WIFIMULTI +#if defined(ESP_SIGNER_HAS_WIFIMULTI) WiFiMulti *multi = nullptr; #endif -#endif }; #endif \ No newline at end of file diff --git a/src/client/GAuth_TCP_Client.h b/src/client/GAuth_TCP_Client.h index 8982d1f..472847d 100644 --- a/src/client/GAuth_TCP_Client.h +++ b/src/client/GAuth_TCP_Client.h @@ -1,9 +1,9 @@ /** - * GAuth TCP Client v1.0.2 + * GAuth TCP Client v1.0.3 * * This library supports Espressif ESP8266, ESP32 and Raspberry Pi Pico MCUs. * - * Created August 12, 2023 + * Created August 21, 2023 * * The MIT License (MIT) * Copyright (c) 2022 K. Suwatchai (Mobizt) @@ -40,6 +40,16 @@ #if defined(ESP32) #include "IPAddress.h" #include "lwip/sockets.h" + +#if defined(ESP_SIGNER_WIFI_IS_AVAILABLE) +#define WIFI_HAS_HOST_BY_NAME +#endif +#include "../client/WiFiClientImpl.h" +#define BASE_WIFICLIENT WiFiClientImpl + +#elif defined(ESP_SIGNER_WIFI_IS_AVAILABLE) +#include "WiFiClient.h" +#define BASE_WIFICLIENT WiFiClient #endif #pragma GCC diagnostic ignored "-Wdelete-non-virtual-dtor" @@ -105,6 +115,7 @@ class GAuth_TCP_Client : public Client _user = user; _password = password; _modem = modem; + _basic_client = client; _client_type = esp_signer_client_type_external_gsm_client; #endif } @@ -276,7 +287,7 @@ class GAuth_TCP_Client : public Client void ethDNSWorkAround(SPI_ETH_Module *eth, const char *host, uint16_t port) { - +#if defined(ESP_SIGNER_ETH_IS_AVAILABLE) if (!eth) return; @@ -304,10 +315,12 @@ class GAuth_TCP_Client : public Client #if defined(INC_ENC28J60_LWIP) || defined(INC_W5100_LWIP) || defined(INC_W5500_LWIP) ex: #if defined(ESP_SIGNER_WIFI_IS_AVAILABLE) - WiFiClient _client; + BASE_WIFICLIENT _client; _client.connect(host, port); _client.stop(); #endif +#endif + #endif } @@ -510,7 +523,7 @@ class GAuth_TCP_Client : public Client { // Device has no built-in WiFi, external client required. #if defined(ESP_SIGNER_WIFI_IS_AVAILABLE) - _basic_client = new WiFiClient(); + _basic_client = new BASE_WIFICLIENT(); _client_type = esp_signer_client_type_internal_basic_client; #else _last_error = 1; @@ -526,14 +539,9 @@ class GAuth_TCP_Client : public Client #if defined(ESP_SIGNER_WIFI_IS_AVAILABLE) && (defined(ESP32) || defined(ESP8266) || defined(MB_ARDUINO_PICO)) if (_client_type == esp_signer_client_type_internal_basic_client) - reinterpret_cast(_basic_client)->setNoDelay(true); + reinterpret_cast(_basic_client)->setNoDelay(true); #endif - // For TCP keepalive should work in ESP8266 core > 3.1.2. - // https://github.com/esp8266/Arduino/pull/8940 - - // Not currently supported by WiFiClientSecure in Arduino Pico core - if (_client_type == esp_signer_client_type_internal_basic_client) { if (isKeepAliveSet()) @@ -542,9 +550,9 @@ class GAuth_TCP_Client : public Client #if defined(ESP8266) if (_tcpKeepIdleSeconds == 0 || _tcpKeepIntervalSeconds == 0 || _tcpKeepCount == 0) - reinterpret_cast(_basic_client)->disableKeepAlive(); + reinterpret_cast(_basic_client)->disableKeepAlive(); else - reinterpret_cast(_basic_client)->keepAlive(_tcpKeepIdleSeconds, _tcpKeepIntervalSeconds, _tcpKeepCount); + reinterpret_cast(_basic_client)->keepAlive(_tcpKeepIdleSeconds, _tcpKeepIntervalSeconds, _tcpKeepCount); #elif defined(ESP32) @@ -820,7 +828,7 @@ class GAuth_TCP_Client : public Client if (_basic_client && _client_type == esp_signer_client_type_internal_basic_client) { #if defined(ESP_SIGNER_WIFI_IS_AVAILABLE) - delete (WiFiClient *)_basic_client; + delete (BASE_WIFICLIENT *)_basic_client; #else delete _basic_client; #endif @@ -929,7 +937,8 @@ class GAuth_TCP_Client : public Client { if (gsmModem->getNetworkTime(&year3, &month3, &day3, &hour3, &min3, &sec3, &timezone)) { - return TimeHelper::getTimestamp(year3, month3, day3, hour3, min3, sec3); + //We have to subtract the local GSM network timezone to get GMT time + return TimeHelper::getTimestamp(year3, month3, day3, hour3, min3, sec3) - timezone * 3600; } } #endif @@ -939,9 +948,7 @@ class GAuth_TCP_Client : public Client int setOption(int option, int *value) { #if defined(ESP32) && defined(ESP_SIGNER_WIFI_IS_AVAILABLE) - // Actually we wish to use setSocketOption directly but it is ambiguous in old ESP32 core v1.0.x.; - // Use setOption instead for old core support. - return reinterpret_cast(_basic_client)->setOption(option, value); + return reinterpret_cast(_basic_client)->setOption(option, value); #endif return 0; } diff --git a/src/client/WiFiClientImpl.h b/src/client/WiFiClientImpl.h new file mode 100644 index 0000000..99be542 --- /dev/null +++ b/src/client/WiFiClientImpl.h @@ -0,0 +1,565 @@ +/** + * WiFiClientImpl v1.0.1 + * + * This library provides the base client in replacement of ESP32 WiFiClient. + * + * The WiFiClient in ESP32 cannot be used in multithreading environment as in FreeRTOS task + * which can (always) lead to the assetion error "pbuf_free: p->ref > 0". + * + * Created August 20, 2023 + * + * The MIT License (MIT) + * Copyright (c) 2022 K. Suwatchai (Mobizt) + * + * + * Permission is hereby granted, free of charge, to any person returning a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +#if !defined(WIFICLIENT_IMPL_H) && defined(ESP32) +#define WIFICLIENT_IMPL_H + +#include +class WiFiClientImpl : public Client +{ +public: + WiFiClientImpl(){}; + virtual ~WiFiClientImpl() { tcpClose(); }; + int connect(IPAddress ip, uint16_t port) { return tcpConnect(ip, port, _timeout); } + int connect(IPAddress ip, uint16_t port, int32_t timeout_ms) { return tcpConnect(ip, port, timeout_ms); } + int connect(const char *host, uint16_t port) + { + IPAddress address((uint32_t)0); +#if defined(WIFI_HAS_HOST_BY_NAME) + if (!WiFiGenericClass::hostByName(host, address)) + return -1; +#endif + return tcpConnect(address, port, _timeout); + } + int connect(const char *host, uint16_t port, int32_t timeout_ms) + { + _timeout = timeout_ms; + return connect(host, port); + } + size_t write(uint8_t data) { return write(&data, 1); } + size_t write(const uint8_t *buf, size_t size) { return tcpWrite(buf, size); } + size_t write_P(PGM_P buf, size_t size) { return write(buf, size); } + size_t write(Stream &stream) + { + uint8_t *buf = (uint8_t *)malloc(1360); + if (!buf) + { + return 0; + } + size_t toRead = 0, toWrite = 0, written = 0; + size_t available = stream.available(); + while (available) + { + toRead = (available > 1360) ? 1360 : available; + toWrite = stream.readBytes(buf, toRead); + written += write(buf, toWrite); + available = stream.available(); + } + free(buf); + buf = nullptr; + return written; + } + int available() { return tcpAavailable(); } + int read() + { + uint8_t data = 0; + int res = read(&data, 1); + if (res < 0) + { + return res; + } + if (res == 0) + { // No data available. + return -1; + } + return data; + } + int read(uint8_t *buf, size_t size) { return tcpRead(buf, size); } + int peek() { return tcpPeek(); } + void flush() + { + if (r_available()) + fillRxBuffer(); + _fillPos = _fillSize; + } + + void stop() { tcpClose(); } + uint8_t connected() { return tcpConnected(); } + + operator bool() + { + return connected(); + } + WiFiClientImpl &operator=(const WiFiClientImpl &other); + bool operator==(const bool value) + { + return bool() == value; + } + bool operator!=(const bool value) + { + return bool() != value; + } + bool operator==(const WiFiClientImpl &); + bool operator!=(const WiFiClientImpl &rhs) + { + return !this->operator==(rhs); + }; + + virtual int fd() const { return _socket; } + + int setSocketOption(int option, char *value, size_t len) + { + return setSocketOption(SOL_SOCKET, option, (const void *)value, len); + } + int setSocketOption(int level, int option, const void *value, size_t len) + { + int res = setsockopt(_socket, level, option, value, len); + if (res < 0) + { + log_e("fail on %d, errno: %d, \"%s\"", _socket, errno, strerror(errno)); + } + return res; + } + int setOption(int option, int *value) + { + return setSocketOption(IPPROTO_TCP, option, (const void *)value, sizeof(int)); + } + int getOption(int option, int *value) + { + socklen_t size = sizeof(int); + int res = getsockopt(_socket, IPPROTO_TCP, option, (char *)value, &size); + if (res < 0) + { + log_e("fail on fd %d, errno: %d, \"%s\"", _socket, errno, strerror(errno)); + } + return res; + } + + int setTimeout(uint32_t seconds) + { + Client::setTimeout(seconds * 1000); // This should be here? + _timeout = seconds * 1000; + if (_socket >= 0) + { + struct timeval tv; + tv.tv_sec = seconds; + tv.tv_usec = 0; + if (setSocketOption(SO_RCVTIMEO, (char *)&tv, sizeof(struct timeval)) < 0) + { + return -1; + } + return setSocketOption(SO_SNDTIMEO, (char *)&tv, sizeof(struct timeval)); + } + else + { + return 0; + } + } + + int setNoDelay(bool nodelay) + { + int flag = nodelay; + return setOption(TCP_NODELAY, &flag); + } + + bool getNoDelay() + { + int flag = 0; + getOption(TCP_NODELAY, &flag); + return flag; + } + + IPAddress remoteIP() const + { + return remoteIP(_socket); + } + + IPAddress remoteIP(int fd) const + { + struct sockaddr_storage addr; + socklen_t len = sizeof addr; + getpeername(fd, (struct sockaddr *)&addr, &len); + struct sockaddr_in *s = (struct sockaddr_in *)&addr; + return IPAddress((uint32_t)(s->sin_addr.s_addr)); + } + + uint16_t remotePort() const + { + return remotePort(_socket); + } + + uint16_t remotePort(int fd) const + { + struct sockaddr_storage addr; + socklen_t len = sizeof addr; + getpeername(fd, (struct sockaddr *)&addr, &len); + struct sockaddr_in *s = (struct sockaddr_in *)&addr; + return ntohs(s->sin_port); + } + + IPAddress localIP() const + { + return localIP(_socket); + } + + IPAddress localIP(int fd) const + { + struct sockaddr_storage addr; + socklen_t len = sizeof addr; + getsockname(fd, (struct sockaddr *)&addr, &len); + struct sockaddr_in *s = (struct sockaddr_in *)&addr; + return IPAddress((uint32_t)(s->sin_addr.s_addr)); + } + + uint16_t localPort() const + { + return localPort(_socket); + } + + uint16_t localPort(int fd) const + { + struct sockaddr_storage addr; + socklen_t len = sizeof addr; + getsockname(fd, (struct sockaddr *)&addr, &len); + struct sockaddr_in *s = (struct sockaddr_in *)&addr; + return ntohs(s->sin_port); + } + + // friend class WiFiServer; + using Print::write; + +private: + int _socket = -1; + int _timeout = 30000; + size_t _rxBuffSize = 2048; + uint8_t *_rxBuff = nullptr; + size_t _fillPos = 0; + size_t _fillSize = 0; + bool _failed = false; + bool _connected = false; + + size_t r_available() + { + if (_socket < 0) + { + return 0; + } + int count; +#ifdef ESP_IDF_VERSION_MAJOR + int res = lwip_ioctl(_socket, FIONREAD, &count); +#else + int res = lwip_ioctl_r(_socket, FIONREAD, &count); +#endif + if (res < 0) + { + _failed = true; + return 0; + } + return count; + } + + size_t fillRxBuffer() + { + if (!_rxBuff && !allocRxBuffer(_rxBuffSize)) + return 0; + + if (_fillSize && _fillPos == _fillSize) + { + _fillSize = 0; + _fillPos = 0; + } + + if (!_rxBuff || _rxBuffSize <= _fillSize || !r_available()) + { + return 0; + } + int res = recv(_socket, _rxBuff + _fillSize, _rxBuffSize - _fillSize, MSG_DONTWAIT); + if (res < 0) + { + if (errno != EWOULDBLOCK) + { + _failed = true; + } + return 0; + } + _fillSize += res; + return res; + } + + bool failed() + { + return _failed; + } + + int tcpConnect(const IPAddress &ip, uint32_t port, int timeout) + { + int enable = 1; + + log_v("Starting socket"); + + _socket = -1; + + _socket = lwip_socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); + if (_socket < 0) + { + return _socket; + } + + struct sockaddr_in serv_addr; + memset(&serv_addr, 0, sizeof(serv_addr)); + serv_addr.sin_family = AF_INET; + serv_addr.sin_addr.s_addr = ip; + serv_addr.sin_port = htons(port); + + if (timeout <= 0) + timeout = 30000; // Milli seconds. + + fd_set fdset; + struct timeval tv; + FD_ZERO(&fdset); + FD_SET(_socket, &fdset); + tv.tv_sec = timeout / 1000; + tv.tv_usec = (timeout % 1000) * 1000; + + int res = lwip_connect(_socket, (struct sockaddr *)&serv_addr, sizeof(serv_addr)); + + if (res < 0 && errno != EINPROGRESS) + { + log_e("connect on fd %d, errno: %d, \"%s\"", _socket, errno, strerror(errno)); + tcpClose(); + return -1; + } + + res = select(_socket + 1, nullptr, &fdset, nullptr, timeout < 0 ? nullptr : &tv); + if (res < 0) + { + log_e("select on fd %d, errno: %d, \"%s\"", _socket, errno, strerror(errno)); + tcpClose(); + return -1; + } + else if (res == 0) + { + log_i("select returned due to timeout %d ms for fd %d", timeout, _socket); + tcpClose(); + return -1; + } + else + { + int sockerr; + socklen_t len = (socklen_t)sizeof(int); + res = getsockopt(_socket, SOL_SOCKET, SO_ERROR, &sockerr, &len); + + if (res < 0) + { + log_e("getsockopt on fd %d, errno: %d, \"%s\"", _socket, errno, strerror(errno)); + tcpClose(); + return -1; + } + + if (sockerr != 0) + { + log_e("socket error on fd %d, errno: %d, \"%s\"", _socket, sockerr, strerror(sockerr)); + tcpClose(); + return -1; + } + } + + lwip_setsockopt(_socket, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv)); + lwip_setsockopt(_socket, SOL_SOCKET, SO_SNDTIMEO, &tv, sizeof(tv)); + + lwip_setsockopt(_socket, IPPROTO_TCP, TCP_NODELAY, &enable, sizeof(enable)); + lwip_setsockopt(_socket, SOL_SOCKET, SO_KEEPALIVE, &enable, sizeof(enable)); + + fcntl(_socket, F_SETFL, fcntl(_socket, F_GETFL, 0) | O_NONBLOCK); + + _connected = true; + + return 1; + } + + void tcpClose() + { + lwip_close(_socket); + _socket = -1; + _connected = false; + freeRxBuffer(); + } + + int tcpAavailable() + { + return _fillSize - _fillPos + r_available(); + } + + int tcpPeek() + { + if (!_rxBuff || _fillPos == _fillSize && !fillRxBuffer()) + { + return -1; + } + return _rxBuff[_fillPos]; + } + + size_t tcpWrite(const uint8_t *buf, size_t size) + { + if (!tcpConnected() || !size) + return 0; + + int res = 0; + int retry = 10; + int socketFileDescriptor = _socket; + size_t totalBytesSent = 0; + size_t bytesRemaining = size; + + while (retry) + { + // use select to make sure the socket is ready for writing + fd_set set; + struct timeval tv; + FD_ZERO(&set); // empties the set + FD_SET(socketFileDescriptor, &set); // adds FD to the set + tv.tv_sec = 0; + tv.tv_usec = 1000000; + retry--; + + if (select(socketFileDescriptor + 1, NULL, &set, NULL, &tv) < 0) + { + return 0; + } + + if (FD_ISSET(socketFileDescriptor, &set)) + { + res = send(socketFileDescriptor, (void *)buf, bytesRemaining, MSG_DONTWAIT); + if (res > 0) + { + totalBytesSent += res; + if (totalBytesSent >= size) + { + // completed successfully + retry = 0; + } + else + { + buf += res; + bytesRemaining -= res; + retry = 10; + } + } + else if (res < 0) + { + log_e("fail on fd %d, errno: %d, \"%s\"", _socket, errno, strerror(errno)); + if (errno != EAGAIN) + { + // if resource was busy, can try again, otherwise give up + res = 0; + retry = 0; + } + } + else + { + // Try again + } + } + } + return totalBytesSent; + + // return ssize_t or signed size_t for error + return lwip_write(_socket, buf, size); + } + + size_t tcpRead(uint8_t *dst, size_t len) + { + + if (!dst || !len || (_fillPos == _fillSize && !fillRxBuffer())) + { + return _failed ? -1 : 0; + } + + int remain = _fillSize - _fillPos; + if (len <= remain || ((len - remain) <= (_rxBuffSize - _fillSize) && fillRxBuffer() >= (len - remain))) + { + if (len == 1) + { + *dst = _rxBuff[_fillPos]; + } + else + { + memcpy(dst, _rxBuff + _fillPos, len); + } + _fillPos += len; + return len; + } + + size_t left = len; + size_t toRead = remain; + uint8_t *buf = dst; + memcpy(buf, _rxBuff + _fillPos, toRead); + _fillPos += toRead; + left -= toRead; + buf += toRead; + while (left) + { + if (!fillRxBuffer()) + { + return len - left; + } + remain = _fillSize - _fillPos; + toRead = (remain > left) ? left : remain; + memcpy(buf, _rxBuff + _fillPos, toRead); + _fillPos += toRead; + left -= toRead; + buf += toRead; + } + return len; + } + + int tcpConnected() + { + return _socket >= 0; + } + + bool allocRxBuffer(size_t size) + { + if (_rxBuff) + freeRxBuffer(); + + _rxBuff = (uint8_t *)malloc(size); + if (!_rxBuff) + { + + log_e("Not enough memory to allocate buffer"); + _failed = true; + return false; + } + + return true; + } + + void freeRxBuffer() + { + if (_rxBuff) + free(_rxBuff); + + _rxBuff = nullptr; + } +}; + +#endif /* WIFICLIENT_IMPL_H */