diff options
| author | MDC Service <michael.schmid@mdc-service.de> | 2022-05-26 10:11:55 +0200 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2022-05-26 10:11:55 +0200 |
| commit | 92c5a9cf419f2d9ed8ca105ccfea2f84e6e381a7 (patch) | |
| tree | ea58e6ecdab2a30031280f5ede42558a8339e428 /software | |
| parent | 16d74f96ac773e9d3cd5b5f2a5a5714d509dfbf9 (diff) | |
initial check in libpq_esp32 files
Diffstat (limited to 'software')
| -rw-r--r-- | software/libs/libpq_esp32.cpp | 1546 | ||||
| -rw-r--r-- | software/libs/libpq_esp32.h | 270 |
2 files changed, 1816 insertions, 0 deletions
diff --git a/software/libs/libpq_esp32.cpp b/software/libs/libpq_esp32.cpp new file mode 100644 index 0000000..0a89c2a --- /dev/null +++ b/software/libs/libpq_esp32.cpp @@ -0,0 +1,1546 @@ +/* + * libpq_esp32.cpp + * + * Created on: 26.02.2022 + * Author: michi + * SPDX-FileCopyrightText: 2022 MDC Service <info@mdc-service.de> + * SPDX-License-Identifier: GPL-3.0-or-later + * Ported from SimplePgSQL (https://github.com/ethanak/SimplePgSQL) to ESP IDF + * original SimplePgSQL: * Copyright (C) Bohdan R. Rau 2016 <ethanak@polip.com> + */ + +#include "libpq_esp32.h" + + + +#ifdef ESP32 +#define strchr_P strchr +#endif + +#ifdef PG_USE_MD5 + +/** + * bytesToHex: convert bytes to HEX + * + * @param b byte array to con.vert + * @param s string result + */ + +static void +bytesToHex(const uint8_t b[16], char *s) +{ + int q, w; +#ifndef ESP32 + static PROGMEM const char hex[] = "0123456789abcdef"; + for (q = 0, w = 0; q < 16; q++) + { + s[w++] = pgm_read_byte(&hex[(b[q] >> 4) & 0x0F]); + s[w++] = pgm_read_byte(&hex[b[q] & 0x0F]); + } +#else + static const char hex[] = "0123456789abcdef"; + for (q = 0, w = 0; q < 16; q++) + { + s[w++] = hex[(b[q] >> 4) & 0x0F]; + s[w++] = hex[b[q] & 0x0F]; + } +#endif + s[w] = '\0'; +} + +#ifdef ESP32_IDF +#include "esp_log.h" +#endif + +#ifdef ESP32 +#include <mbedtls/md5.h> + +/** + * pg_md5_encrypt: md5 encryption for PostgreSQL + * + * @param password as unecrypted string + * @param salt parameter string for the md5 algorithm + * @param salt_len length of the salt parameter + * @param outbuf is the resulting md5 encrypted password + */ + +static void pg_md5_encrypt(const char *password, char *salt, int salt_len, char *outbuf) +{ + md5_context_t context; + uint8_t sum[16]; + *outbuf++ = 'm'; + *outbuf++ = 'd'; + *outbuf++ = '5'; + mbedtls_md5_init(&context); + mbedtls_md5_update_ret(&context, (uint8_t *)password, strlen(password)); + mbedtls_md5_update_ret(&context, (uint8_t *)salt, salt_len); + mbedtls_md5_finish_ret( &context, sum); + bytesToHex(sum, outbuf); +} +#elif defined(ESP8266) +#include <md5.h> +/** + * pg_md5_encrypt: md5 encryption for PostgreSQL + * + * @param password as unecrypted string + * @param salt parameter string for the md5 algorithm + * @param salt_len length of the salt parameter + * @param outbuf is the resulting md5 encrypted password + */ +static void pg_md5_encrypt(const char *password, char *salt, int salt_len, char *outbuf) +{ + md5_context_t context; + uint8_t sum[16]; + *outbuf++ = 'm'; + *outbuf++ = 'd'; + *outbuf++ = '5'; + MD5Init(&context); + MD5Update(&context, (uint8_t *)password, strlen(password)); + MD5Update(&context, (uint8_t *)salt, salt_len); + MD5Final(sum, &context); + bytesToHex(sum, outbuf); +} +#else +//#include <MD5.h> +/** + * pg_md5_encrypt: md5 encryption for PostgreSQL + * + * @param password as unecrypted string + * @param salt parameter string for the md5 algorithm + * @param salt_len length of the salt parameter + * @param outbuf is the resulting md5 encrypted password + */ +static void pg_md5_encrypt(const char *password, char *salt, int salt_len, char *outbuf) +{ + MD5_CTX context; + uint8_t sum[16]; + *outbuf++ = 'm'; + *outbuf++ = 'd'; + *outbuf++ = '5'; + + MD5::MD5Init(&context); + MD5::MD5Update(&context, (uint8_t *)password, strlen(password)); + MD5::MD5Update(&context, (uint8_t *)salt, salt_len); + MD5::MD5Final(sum, &context); + bytesToHex(sum, outbuf); +} +#endif +#endif + +#define MD5_PASSWD_LEN 35 +#define AUTH_REQ_OK 0 /* User is authenticated */ +#define AUTH_REQ_PASSWORD 3 /* Password */ +#define AUTH_REQ_MD5 5 /* md5 password */ + +#ifndef ESP32_IDF +static PROGMEM const char EM_OOM [] = "Out of memory"; +static PROGMEM const char EM_READ [] = "Backend read error"; +static PROGMEM const char EM_WRITE [] = "Backend write error"; +static PROGMEM const char EM_CONN [] = "Cannot connect to server"; +static PROGMEM const char EM_SYNC [] = "Backend out of sync"; +static PROGMEM const char EM_INTR [] = "Internal error"; +static PROGMEM const char EM_UAUTH [] = "Unsupported auth method"; +static PROGMEM const char EM_BIN [] = "Binary format not supported"; +static PROGMEM const char EM_EXEC [] = "Previous execution not finished"; +static PROGMEM const char EM_PASSWD [] = "Password required"; +static PROGMEM const char EM_EMPTY [] = "Query is empty"; +static PROGMEM const char EM_FORMAT [] = "Illegal formatting character"; +#else +static const char EM_OOM [] = "Out of memory"; +static const char EM_READ [] = "Backend read error"; +static const char EM_WRITE [] = "Backend write error"; +static const char EM_CONN [] = "Cannot connect to server"; +static const char EM_SYNC [] = "Backend out of sync"; +static const char EM_INTR [] = "Internal error"; +static const char EM_UAUTH [] = "Unsupported auth method"; +static const char EM_BIN [] = "Binary format not supported"; +static const char EM_EXEC [] = "Previous execution not finished"; +static const char EM_PASSWD [] = "Password required"; +static const char EM_EMPTY [] = "Query is empty"; +static const char EM_FORMAT [] = "Illegal formatting character"; + +#define PGTAG "PGSQL" +#endif + +#ifndef ESP32_IDF +/** + * PGconnection constructor for Arduino + * + * @param c the Arduino Client pointer + * @param flags for the connection. See the postgresql libpq documentation for more details + * @param memory specify buffer size + * @param foreignBuffer specify is internal or external memory is used. + */ +PGconnection::PGconnection(Client *c, + int flags, + int memory, + char *foreignBuffer) +{ + conn_status = CONNECTION_NEEDED; + client = c; + Buffer = foreignBuffer; + _passwd = NULL; + _flags = flags & ~PG_FLAG_STATIC_BUFFER; + + if (memory <= 0) bufSize = PG_BUFFER_SIZE; + else bufSize = memory; + if (foreignBuffer) { + _flags |= PG_FLAG_STATIC_BUFFER; + } +} +#else + +/** + * PGconnection constructor for ESP IDF + * + * @param flags for the connection. See the postgresql libpq documentation for more details + * @param _Buffer points to the external allocated buffer + * @param _bufSize specify the buffer size. + */ +PGconnection::PGconnection(const int flags, const unsigned char *_Buffer, const int _bufSize) { + conn_status = CONNECTION_NEEDED; + _passwd = NULL; + _user = NULL; + Buffer = (char *) _Buffer; + bufSize = _bufSize; + bufPos = 0; + result_status=0; + _available=0; + _nfields=0; + _ntuples=0; + _flags=0; + + conn_status = CONNECTION_NEEDED; + _flags = flags & ~PG_FLAG_STATIC_BUFFER; + bufSize = PG_BUFFER_SIZE; +} +#endif + +#ifndef ESP32_IDF + +/** + * PGconnection setDbLogin for Arduino + * + * @param IPAddress for the connection. + * @param user name, the PostgreSQL user name. + * @param passwd the PostgreSQL password. + * @param db the name of the database. + * @param charset the used char set + * @param port the TCP/IP port, typically 5432 + * @returns the connection status + */ + +int PGconnection::setDbLogin(IPAddress server, + const char *user, + const char *passwd, + const char *db, + const char *charset, + int port) +{ + + char *startpacket; + int packetlen; + int len; + + close(); + if (!db) db = user; + len = strlen(user) + 1; + if (passwd) { + len += strlen(passwd) + 1; + } + _user = (char *)malloc(len); + strcpy(_user, user); + if (passwd) { + _passwd = _user + strlen(user) + 1; + strcpy(_passwd, passwd); + } + else { + _passwd = NULL; + } + if (!Buffer) Buffer = (char *) malloc(bufSize); + byte connected = client -> connect(server, port); + if (!connected) { + setMsg_P(EM_CONN, PG_RSTAT_HAVE_ERROR); + return conn_status = CONNECTION_BAD; + } + packetlen = build_startup_packet(NULL, db, charset); + if (packetlen > bufSize - 10) { + setMsg_P(EM_OOM, PG_RSTAT_HAVE_ERROR); + conn_status = CONNECTION_BAD; + return conn_status; + } + startpacket=Buffer + (bufSize - (packetlen + 1)); + build_startup_packet(startpacket, db, charset); + if (pqPacketSend(0, startpacket, packetlen) < 0) { + setMsg_P(EM_WRITE, PG_RSTAT_HAVE_ERROR); + return conn_status = CONNECTION_BAD; + } + attempts = 0; + return conn_status = CONNECTION_AWAITING_RESPONSE; +} +#else + +/** + * PGconnection setDbLogin for ESP IDF + * + * @param ServerIP as char string. + * @param ServerPort, typically 5432 + * @param dbName the name of the database. + * @param dbUser the user name of the database. + * @param dbPasswd the PostgreSQL password. + * @param charset the used char set + * @returns the connection status + */ + +int PGconnection::setDbLogin(const char *ServerIP, int ServerPort, const char *dbName, const char *dbUser, const char *dbPasswd, const char *dbCharset) { + + char *startpacket; + int packetlen; + int len; + +// close(); + memset(&DestAddr, 0, sizeof(DestAddr)); + AddrFamily = AF_INET; + ipProtocol = IPPROTO_IP; + DestAddr.sin_addr.s_addr = inet_addr(ServerIP); + DestAddr.sin_family = AF_INET; + DestAddr.sin_port = htons(ServerPort); + + if (!dbName) + dbName = dbUser; + len = strlen(dbUser) + 1; + if (dbPasswd) { + len += strlen(dbPasswd) + 1; + } + _user = (char *) malloc(len); + strcpy(_user, dbUser); + if (dbPasswd) { + _passwd = _user + strlen(dbUser) + 1; + strcpy(_passwd, dbPasswd); + } else { + _passwd = NULL; + } + + //int8_t connected = connect(client->connect(server, port); + SockH = socket(AddrFamily, SOCK_STREAM, ipProtocol); + if (SockH < 0) { + ESP_LOGE(PGTAG, "Unable to create socket: errno %d", errno); + setMsg_P(EM_CONN, PG_RSTAT_HAVE_ERROR); + return conn_status = CONNECTION_BAD; + } else { + int err = connect(SockH, (struct sockaddr *) &DestAddr, sizeof(DestAddr)); + if (err != 0) { + ESP_LOGE(PGTAG, "Socket unable to connect: errno %d", errno); + return conn_status = CONNECTION_BAD; + } + NetConnected = 1; + ESP_LOGI(PGTAG, "Successfully connected"); + } + + packetlen = build_startup_packet(NULL, dbName, dbCharset); + if (packetlen > bufSize - 10) { + setMsg_P(EM_OOM, PG_RSTAT_HAVE_ERROR); + conn_status = CONNECTION_BAD; + return conn_status; + } + + startpacket = Buffer + (bufSize - (packetlen + 1)); + build_startup_packet(startpacket, dbName, dbCharset); + if (pqPacketSend(0, startpacket, packetlen) < 0) { + setMsg_P(EM_WRITE, PG_RSTAT_HAVE_ERROR); + return conn_status = CONNECTION_BAD; + } + attempts = 0; + return conn_status = CONNECTION_AWAITING_RESPONSE; +} + +/** + * PGconnection close_socket for ESP IDF + * + * @param _SockH the socket handle. + */ +void close_socket(int _SockH) +{ + close(_SockH); +} +#endif + +/** + * PGconnection close. Free allocated resources + * + */ + +void PGconnection::close(void) +{ +#ifndef ESP32_IDF + if (client->connected()) { +#else + if (NetConnected) { +#endif + pqPacketSend('X', NULL, 0); +#ifndef ESP32_IDF + client->stop(); +#else + shutdown(SockH, 0); + close_socket(SockH); +#endif + } + if (Buffer && !(_flags & PG_FLAG_STATIC_BUFFER)) { + free(Buffer); + Buffer = NULL; + } + if (_user) { + free(_user); + _user = _passwd = NULL; + } + conn_status = CONNECTION_NEEDED; + +#ifndef ESP32_IDF + NetConnected = 0; +#endif +} + +#ifdef ESP32_IDF +/** + * PGconnection dataAvailable. + * + * @returns number of Bytes available + */ +int PGconnection::dataAvailable() { + int res=0; +// if (_available) return _available; + ioctl(SockH,FIONREAD,&res); + return res; +} +#endif + +/** + * PGconnection dataAvailable. + * + * @returns the current status of the connection + */ + +int PGconnection::status(void) +{ + char bereq; + char rc; + int32_t msgLen; + int32_t areq; + char * pwd = _passwd; +#ifdef PG_USE_MD5 + char salt[4]; +#endif + + switch(conn_status) { + case CONNECTION_NEEDED: + case CONNECTION_OK: + case CONNECTION_BAD: + + return conn_status; + + case CONNECTION_AWAITING_RESPONSE: +#ifndef ESP32_IDF + if (!client->available()) return conn_status; +#else + if (dataAvailable() == 0) return conn_status; +#endif + if (attempts++ >= 2) { + setMsg_P(EM_SYNC, PG_RSTAT_HAVE_ERROR); + return conn_status = CONNECTION_BAD; + } + if (pqGetc(&bereq)) { + goto read_error; + } + if (bereq == 'E') { + pqGetInt4(&msgLen); + pqGetNotice(PG_RSTAT_HAVE_ERROR); + return conn_status = CONNECTION_BAD; + } + if (bereq != 'R') { + setMsg_P(EM_SYNC, PG_RSTAT_HAVE_ERROR); + return conn_status = CONNECTION_BAD; + } + if (pqGetInt4(&msgLen)) { + goto read_error; + } + if (pqGetInt4(&areq)) { + goto read_error; + } + if (areq == AUTH_REQ_OK) { + if (_user) { + free(_user); + _user = _passwd=NULL; + } + result_status = PG_RSTAT_READY; + return conn_status = CONNECTION_AUTH_OK; + } + if ( +#ifdef PG_USE_MD5 + areq != AUTH_REQ_MD5 && +#endif + areq != AUTH_REQ_PASSWORD) { + setMsg_P(EM_UAUTH, PG_RSTAT_HAVE_ERROR); + return conn_status = CONNECTION_BAD; + } + if (!_passwd || !*_passwd) { + setMsg_P(EM_PASSWD, PG_RSTAT_HAVE_ERROR); + return conn_status = CONNECTION_BAD; + } + pwd = _passwd; +#ifdef PG_USE_MD5 + if (areq == AUTH_REQ_MD5) { + if (pqGetnchar(salt, 4)) goto read_error; + if (bufSize < 3 * MD5_PASSWD_LEN + 10) { + setMsg_P(EM_OOM, PG_RSTAT_HAVE_ERROR); + return conn_status = CONNECTION_BAD; + } + char *crypt_pwd = Buffer + (bufSize - (2 * (MD5_PASSWD_LEN + 1))); + char *crypt_pwd2 = crypt_pwd + MD5_PASSWD_LEN + 1; + pg_md5_encrypt(pwd, _user, strlen(_user), crypt_pwd2); + pg_md5_encrypt(crypt_pwd2 + 3, salt,4, crypt_pwd); + pwd = crypt_pwd; + } +#endif + rc=pqPacketSend('p', pwd, strlen(pwd) + 1); + if (rc) { + goto write_error; + } + return conn_status; + + case CONNECTION_AUTH_OK: + for (;;) { +#ifndef ESP32_IDF + if (!client -> available()) return conn_status; +#else + if (dataAvailable() == 0) return conn_status; +#endif + if (pqGetc(&bereq)) goto read_error; + if (pqGetInt4(&msgLen)) goto read_error; + msgLen -= 4; + if (bereq == 'A' || bereq == 'N' || bereq == 'S' || bereq == 'K') { + if (pqSkipnchar(msgLen)) goto read_error; + continue; + } + if (bereq == 'E') { + pqGetNotice(PG_RSTAT_HAVE_ERROR); + return conn_status = CONNECTION_BAD; + } + +/* if (bereq == 'K') { + if (pqGetInt4(&be_pid)) goto read_error; + if (pqGetInt4(&be_key)) goto read_error; + continue; + } +*/ + if (bereq == 'Z') { + pqSkipnchar(msgLen); + return conn_status = CONNECTION_OK; + } + return conn_status = CONNECTION_BAD; + } + + default: + setMsg_P(EM_INTR, PG_RSTAT_HAVE_ERROR); + return conn_status = CONNECTION_BAD; + } +read_error: + setMsg_P(EM_READ, PG_RSTAT_HAVE_ERROR); + return conn_status = CONNECTION_BAD; +write_error: + setMsg_P(EM_WRITE, PG_RSTAT_HAVE_ERROR); + return conn_status = CONNECTION_BAD; +} + +/** + * PGconnection execute. + * + * @param query the SQL command. + * @param progmen inicates the usage of progmem on Arduino, not used on ESP IDF + * @returns 0 if ok, otherwise -1 + */ + +int PGconnection::execute(const char *query, int progmem) +{ +#ifndef ESP32 +// for unknown reason, this status check get wrong on ESP32-IDF. +// uncommenting solve the problem for now, but need invesitgation + if (!(result_status & PG_RSTAT_READY)) { + setMsg_P(EM_EXEC, PG_RSTAT_HAVE_ERROR); + return -1; + } +#endif + int len = +#ifndef ESP32 + progmem ? strlen_P(query) : +#endif + strlen(query); + +#ifndef ESP32_IDF + if (pqPacketSend('Q', query, len+1, progmem)) { +#else + if (pqPacketSend('Q', query, len + 1)) { +#endif + setMsg_P(EM_WRITE, PG_RSTAT_HAVE_ERROR); + conn_status = CONNECTION_BAD; + return -1; + } + result_status = PG_RSTAT_COMMAND_SENT; + return 0; + +} +/* +int PGconnection::PGexecute(const char *query) { + int len = strlen(query); + if (pqPacketSend('Q', query, len + 1)) { + setMsg_P(EM_WRITE, PG_RSTAT_HAVE_ERROR); + conn_status = CONNECTION_BAD; + return -1; + } + result_status = PG_RSTAT_COMMAND_SENT; + return 0; +} +*/ + +/** + * PGconnection escapeName. + * + * @param inbuf the SQL command before names are escaped. + * @param outbuf the SQL command after names are escaped. + * @returns the lenght of the outbuf result + */ + +int PGconnection::escapeName(const char *inbuf, char *outbuf) +{ + const char *c; + int l = 2; + for (c=inbuf; *c; c++) { + l++; + if (*c == '\\' || *c == '"') l++; + } + if (outbuf) { + *outbuf++='"'; + for (c=inbuf; *c; c++) { + *outbuf++ = *c; + if (*c == '\\' || *c == '"') *outbuf++ = *c; + } + *outbuf++='"'; + } + return l; +} + +/** + * PGconnection escapeString. + * + * @param inbuf the SQL command before names are escaped. + * @param outbuf the SQL command after names are escaped. + * @returns the lenght of the outbuf result + */ + +int PGconnection::escapeString(const char *inbuf, char *outbuf) +{ + const char *c; + int e = 0, l; + for (c=inbuf; *c; c++) { + if (*c == '\\' || *c == '\'') e++; + } + l = e + (c - inbuf) + (e ? 4 : 2); + if (outbuf) { + if (e) { + *outbuf++=' '; + *outbuf++='E'; + } + *outbuf++='\''; + for (c=inbuf; *c; c++) { + *outbuf++ = *c; + if (*c == '\\' || *c == '\'') *outbuf++ = *c; + } + *outbuf++='\''; + } + return l; +} + +/** + * PGconnection getValue. + * + * @param nr number of the row. + * @returns the value as char pointer. + */ + +char * PGconnection::getValue(int nr) +{ + int i; + if (_null & (1<<nr)) return NULL; + char *c=Buffer; + if (nr < 0 || nr >= _nfields) return NULL; + for (i=0; i < nr; i++) { + if (_null & (1 <<i)) continue; + c += strlen(c) + 1; + } + return c; +} + +/** + * PGconnection getColumn. + * + * @param n the column. + * @returns the char pointer to the column. + */ +char *PGconnection::getColumn(int n) +{ + char *c;int i; + if (!(result_status & PG_RSTAT_HAVE_COLUMNS)) return NULL; + if (n < 0 || n >= _nfields) return NULL; + for (c = Buffer, i = 0; i<n; i++) { + c += strlen(c) + 1; + } + return c; +} + +/** + * PGconnection getMessage. + * + * @returns the last message, if any. + */ + +char *PGconnection::getMessage(void) +{ + if (!(result_status & PG_RSTAT_HAVE_MESSAGE)) return NULL; + return Buffer; +} + +/** + * PGconnection getData. + * + * @returns the availability status of data + */ + +int PGconnection::getData(void) +{ + char id; + int32_t msgLen; + int rc; + char *c; +#ifndef ESP32_IDF + if (!client -> available()) return 0; +#else + if (dataAvailable() == 0) return 0; +#endif + + + if (pqGetc(&id)) goto read_error; + if (pqGetInt4(&msgLen)) goto read_error; + //Serial.printf("ID=%c\n", id); + msgLen -= 4; + switch(id) { + case 'T': + if ((rc=pqGetRowDescriptions())) { + if (rc == -2) setMsg_P(EM_OOM, PG_RSTAT_HAVE_ERROR); + else if (rc == -3) setMsg_P(EM_BIN, PG_RSTAT_HAVE_ERROR); + goto read_error; + } + if (_flags & PG_FLAG_IGNORE_COLUMNS) { + result_status &= ~PG_RSTAT_HAVE_MASK; + return 0; + } + return result_status = (result_status & ~PG_RSTAT_HAVE_MASK) | PG_RSTAT_HAVE_COLUMNS; + + case 'E': + if (pqGetNotice(PG_RSTAT_HAVE_ERROR)) goto read_error; + return result_status; + + case 'N': + if (_flags & PG_FLAG_IGNORE_NOTICES) { + if (pqSkipnchar(msgLen)) goto read_error; + return 0; + } + if(pqGetNotice(PG_RSTAT_HAVE_NOTICE)) goto read_error; + return result_status = (result_status & ~PG_RSTAT_HAVE_MASK) | PG_RSTAT_HAVE_NOTICE; + + case 'A': + if (_flags & PG_FLAG_IGNORE_NOTICES) { + if (pqSkipnchar(msgLen)) goto read_error; + return 0; + } + if (pqGetNotify(msgLen)) goto read_error; + return result_status = (result_status & ~PG_RSTAT_HAVE_MASK) | PG_RSTAT_HAVE_NOTICE; + + case 'Z': + if (pqSkipnchar(msgLen)) goto read_error; + result_status = (result_status & PG_RSTAT_HAVE_SUMMARY) | PG_RSTAT_READY; + return PG_RSTAT_READY; + + case 'S': // parameters setting ignored + case 'K': // should not be here? + if (pqSkipnchar(msgLen)) goto read_error; + return 0; + + case 'C': // summary + if (msgLen > bufSize - 1) goto oom; + if (pqGetnchar(Buffer, msgLen)) goto read_error; + Buffer[msgLen] = 0; + _ntuples = 0; + result_status = (result_status & ~PG_RSTAT_HAVE_MASK) | PG_RSTAT_HAVE_SUMMARY; + for (c = Buffer; *c && !isdigit(*c); c++); + if (!*c) return result_status; + if (strncmp(Buffer,"SELECT ",7)) { + for (; *c && isdigit(*c); c++); + for (; *c && !isdigit(*c); c++); + } + if (*c) _ntuples = strtol(c, NULL, 10); + return result_status; + + case 'D': + if ((rc=pqGetRow())) { + if (rc == -2) setMsg_P(EM_OOM, PG_RSTAT_HAVE_ERROR); + else if (rc == -3) setMsg_P(EM_SYNC, PG_RSTAT_HAVE_ERROR); + goto read_error; + } + if (_flags & PG_FLAG_IGNORE_COLUMNS) { + result_status &= ~PG_RSTAT_HAVE_MASK; + return 0; + } + return result_status = (result_status & ~PG_RSTAT_HAVE_MASK) | PG_RSTAT_HAVE_ROW; + + case 'I': + if (pqSkipnchar(msgLen)) goto read_error; + setMsg_P(EM_EMPTY, PG_RSTAT_HAVE_ERROR); + return result_status; + + default: + setMsg_P(EM_SYNC, PG_RSTAT_HAVE_ERROR); + conn_status = CONNECTION_BAD; + return -1; + } + +oom: + setMsg_P(EM_OOM, PG_RSTAT_HAVE_ERROR); + +read_error: + if (!(result_status & PG_RSTAT_HAVE_ERROR)) { + setMsg_P(EM_READ, PG_RSTAT_HAVE_ERROR); + } + conn_status = CONNECTION_BAD; + return -1; +} + +/** + * PGconnection executeFormat. + * + * @param progmem the SQL command. + * @param format the format string. + * @returns 0 on sucess, otherwise -1. + */ + +int PGconnection::executeFormat(int progmem, const char *format, ...) +{ + int32_t msgLen; + va_list va; + va_start(va, format); + msgLen = writeFormattedQuery(0, progmem, format, va); + va_end(va); + if (msgLen < 0) return -1; + va_start(va, format); + msgLen = writeFormattedQuery(msgLen, progmem, format, va); + va_end(va); + if (msgLen) { + return -1; + } + result_status = PG_RSTAT_COMMAND_SENT; + return 0; +} + +#ifdef ESP8266 + +// there is no strchr_P in ESP8266 ROM :( + +static const char *strchr_P(const char *str, char c) +{ + char z; + for (;;) { + z = pgm_read_byte(str); + if (!z) return NULL; + if (z == c) return str; + str++; + } +} + +#endif + +#ifdef ESP32_IDF +//emulate Arduino PSTR() + +/** + * PSTR. + * + * @param str input string. + * @returns the result string. + */ + +char *PSTR(char *str) +{ + return str; +} +const char *PSTR(const char *str) +{ + return str; +} + +//emulate strcpy_P() +/** + * strcpy_P(). for compatibility with the Arduino version on ESP IDF + * + * @param destination buffer. + * @param source buffer. + * @returns number of bytes copied + */ +char *strcpy_P(char * destination, const char * source ) +{ + return strcpy(destination, source); +} + +int strlen_P(char *str) +{ + return strlen(str); +} +int strlen_P(const char *str) +{ + return strlen(str); +} +#endif + +/** + * PGconnection build_startup_packet. + * + * @param packet buffer for the packet. + * @param db name of the database. + * @param charset name of the character set + * @returns the lenght of the packet + */ + +int PGconnection::build_startup_packet( + char *packet, + const char *db, + const char *charset) +{ + int packet_len = 4; + if (packet) { + memcpy(packet,"\0\003\0\0", 4); + } +#define ADD_STARTUP_OPTION(optname, optval) \ + do { \ + if (packet) \ + strcpy_P(packet + packet_len, (char *)optname); \ + packet_len += strlen_P((char *)optname) + 1; \ + if (packet) \ + strcpy(packet + packet_len, (char *)optval); \ + packet_len += strlen((char *)optval) + 1; \ + } while(0) + +#define ADD_STARTUP_OPTION_P(optname, optval) \ + do { \ + if (packet) \ + strcpy_P(packet + packet_len, (char *)optname); \ + packet_len += strlen_P((char *)optname) + 1; \ + if (packet) \ + strcpy_P(packet + packet_len, (char *)optval); \ + packet_len += strlen_P((char *)optval) + 1; \ + } while(0) + + if (_user && _user[0]) + ADD_STARTUP_OPTION(PSTR("user"), _user); + if (db && db[0]) + ADD_STARTUP_OPTION(PSTR("database"), db); + if (charset && charset[0]) + ADD_STARTUP_OPTION(PSTR("client_encoding"), charset); + ADD_STARTUP_OPTION_P(PSTR("application_name"), PSTR("arduino")); +#undef ADD_STARTUP_OPTION + if (packet) + packet[packet_len] = '\0'; + packet_len++; + + return packet_len; +} + +/** + * PGconnection pqPacketSend. + * + * @param pack_type type of the packet. + * @param buf the result buffer. + * @param buf_len length of the result buffer + * @progmem to be used on Arduino + * @returns 0 if success, network error code otherwise + */ + +int PGconnection::pqPacketSend(char pack_type, const char *buf, int buf_len, int progmem) +{ + char *start = Buffer; + int l = bufSize - 4; +#ifndef ESP32 + int n; +#endif + if (pack_type) { + *start++ = pack_type; + l--; + } +#ifdef __AVR__ + *start++=0; + *start++=0; +#else + *start++ = ((buf_len + 4) >> 24) & 0xff; + *start++ = ((buf_len + 4) >> 16) & 0xff; +#endif + *start++ = ((buf_len + 4) >> 8) & 0xff; + *start++ = (buf_len + 4) & 0xff; +#ifndef ESP32 + if (progmem) { + while (buf_len > 0) { + while (buf_len > 0 && l > 0) { + *start++ = pgm_read_byte(buf++); + buf_len--; + } + n = client->write((const uint8_t *)Buffer, start - Buffer); + if (n != start - Buffer) return -1; + start = Buffer; + l = bufSize; + } + } + else { +#endif + if (buf) { + if (buf_len <= l) { + memcpy(start, buf, buf_len); + start += buf_len; + buf_len = 0; + } + else { + memcpy(start, buf, l); + start += l; + buf_len -= l; + buf += l; + } + } +#ifndef ESP32_IDF + n = client->write((const uint8_t *)Buffer, start - Buffer); + if (n != start - Buffer) return -1; + if (buf && buf_len) { + n = client->write((const uint8_t *)buf, buf_len); + if (n != buf_len) return -1; + } +#else + int err = send(SockH, /*_buffer*/Buffer, start - /*_buffer*/Buffer, 0); + if (err < 0) { + ESP_LOGE(PGTAG, "pqPacketSend: Send Error occurred during sending: errno %d", errno); + return err; + } + if (buf && buf_len) { + err = send(SockH, (const char *) buf, (size_t) buf_len, 0); + if (err < 0) { + ESP_LOGE(PGTAG, "Send2 Error occurred during sending: errno %d", errno); + return err; + + } + } +#endif + +#ifndef ESP32 + } +#endif + return 0; +} + +/** + * PGconnection pqGetc. + * + * @param buf buffer to store received char. + * @returns length of received data + */ + +int PGconnection::pqGetc(char *buf) +{ + int i; +#ifndef ESP32_IDF + for (i=0; !client->available() && i < 10; i++) { + delay (i * 10 + 10); + } + if (!client->available()) { + return -1; + } + *buf = client->read(); + return 0; +#else + i = 0; + while (i<10) { + if (dataAvailable()>0) break; + else { + vTaskDelay(i++); + } + } + if (i==10) return -1; + + int len = read(SockH, (void *) buf, 1); + _available-=len; + return -1+len; +#endif +} + +/** + * PGconnection pqGetInt4. + * + * @param result the converted result to int4. + * @returns 0 for sucess, otherwise -1. + */ + +int PGconnection::pqGetInt4(int32_t *result) +{ + uint32_t tmp4 = 0; + byte tmp,i; + for (i = 0; i < 4; i++) { + if (pqGetc((char *)&tmp)) return -1; + tmp4 = (tmp4 << 8) | tmp; + } + *result = tmp4; + return 0; +} + +/** + * PGconnection pqGetInt2. + * + * @param result the converted result to int2. + * @returns 0 for sucess, otherwise -1. + */ + +int PGconnection::pqGetInt2(int16_t *result) +{ + uint16_t tmp2 = 0; + byte tmp,i; + for (i = 0; i < 2; i++) { + if (pqGetc((char *)&tmp)) return -1; + tmp2 = (tmp2 << 8) | tmp; + } + *result = tmp2; + return 0; +} + +/** + * PGconnection pqGetnchar. + * + * @param s the string to search in + * @param len the lenght of the string + * @returns 0 for sucess, otherwise -1. + */ + +int PGconnection::pqGetnchar(char *s, int len) +{ + while (len-- > 0) { + if (pqGetc(s++)) return -1; + } + return 0; +} + +/** + * PGconnection pqGets. + * + * @param s the string to search in + * @param maxlen the lenght of the string + * @returns the position in the string, or -1 for an error + */ + +int PGconnection::pqGets(char *s, int maxlen) +{ + int len; + char z; + for (len = 0;len < maxlen; len++) { + if (pqGetc(&z)) return -1; + if (s) *s++ = z; + if (!z) return len+1; + } + return - (len + 1); +} + +/** + * PGconnection pqSkipnchar. + * + * @param len the lenght of the string + * @returns 0 for sucess, or -1 for an error + */ + +int PGconnection::pqSkipnchar(int len) +{ + char dummy; + while (len-- > 0) { + if (pqGetc(&dummy)) return -1; + } + return 0; +} + +/** + * PGconnection pqGetRow. + * + * @returns 0 for sucess, or -1 for an error + */ + +int PGconnection::pqGetRow(void) +{ + int i; + int bufpos = 0; + int32_t len; + int16_t cols; + + _null = 0; + if (pqGetInt2(&cols)) return -1; + if (cols != _nfields) { + return -3; + } + for (i=0; i < _nfields; i++) { + if (pqGetInt4(&len)) return -1; + if (len < 0) { + _null |= 1<<i; + continue; + } + if (bufpos + len + 1 > bufSize) { + return -2; + } + if (pqGetnchar(Buffer + bufpos, len)) return -1; + bufpos += len; + Buffer[bufpos++]=0; + } + return 0; +} + +/** + * PGconnection pqGetRowDescriptions. + * + * @returns the row description, or -1 for an error + */ + +int PGconnection::pqGetRowDescriptions(void) +{ + int i; + int16_t format; + int rc; + int bufpos; + if (pqGetInt2(&_nfields)) return -1; + if (_nfields > PG_MAX_FIELDS) return -2; // implementation limit + _formats = 0; + bufpos = 0; + for (i = 0;i < _nfields; i++) { + if (!(_flags & PG_FLAG_IGNORE_COLUMNS)) { + if (bufpos >= bufSize - 1) return -2; + rc = pqGets(Buffer + bufpos, bufSize - bufpos); + if (rc < 0) return -1; + bufpos += rc; + } + else { + if (pqGets(NULL, 8192) < 0) { + return -1; + } + } + if (pqSkipnchar(16)) return -1; + if (pqGetInt2(&format)) return -1; + format = format ? 1 : 0; + _formats |= format << i; + } + if (_formats) return -3; + return 0; +} + +/** + * PGconnection setMsg. + * + * @param s the message + * @param len the lenght of the message + */ +void PGconnection::setMsg(const char *s, int type) +{ + strcpy(Buffer, s); + result_status = (result_status & ~PG_RSTAT_HAVE_MASK) | type; +} + +/** + * PGconnection setMsg_P. + * + * @param s the message + * @param len the lenght of the message + */ +void PGconnection::setMsg_P(const char *s, int type) +{ + strcpy_P(Buffer, s); + result_status = (result_status & ~PG_RSTAT_HAVE_MASK) | type; +} + +/** + * PGconnection pqGetNotice. + * + * @param type the notice type + * @returns 0 on success, or -1 for an error + */ +int PGconnection::pqGetNotice(int type) +{ + int bufpos = 0; + char id; + int rc; + for (;;) { + if (pqGetc(&id)) goto read_error; + if (!id) break; + if (id == 'S' || id == 'M') { + if (bufpos && bufpos < bufSize - 1) Buffer[bufpos++]=':'; + rc = pqGets(Buffer + bufpos, bufSize - bufpos); + if (rc < 0) goto read_error; + bufpos += rc -1; + } + else { + rc = pqGets(NULL, 8192); + if (rc < 0) goto read_error; + } + } + Buffer[bufpos] = 0; + result_status = (result_status & ~PG_RSTAT_HAVE_MASK) | type; + return 0; + +read_error: + if (!bufpos) setMsg_P(EM_READ, PG_RSTAT_HAVE_ERROR); + return -1; +} + +/** +/* PGconnection pqGetNotify. + * + * @param msgLen the length of the message + * @returns 0 on success, or -1 for an error + */ +int PGconnection::pqGetNotify(int32_t msgLen) +{ + int32_t pid; + int bufpos, i; + if (pqGetInt4(&pid)) return -1; + msgLen -= 4; + bufpos = sprintf(Buffer,"%d:",pid); + if (msgLen > bufSize - (bufpos + 1)) { + if (pqGetnchar(Buffer+bufpos, bufSize - (bufpos + 1))) + return -1; + msgLen -= bufSize - (bufpos + 1); + if (pqSkipnchar(msgLen)) return -1; + Buffer[msgLen = bufSize - 1] = 0; + + } + else { + if (pqGetnchar(Buffer+ bufpos, msgLen)) return -1; + Buffer[bufpos + msgLen] = 0; + msgLen += bufpos; + } + for (i=0; i<msgLen; i++) if (!Buffer[i]) Buffer[i] = ':'; + return 0; +} + +#ifndef ESP32 +int PGconnection::writeMsgPart_P(const char *s, int len, int fine) +{ + while (len > 0) { + if (bufPos >= bufSize) { + if (client->write((uint8_t *)Buffer, bufPos) != (size_t)bufPos) { + return -1; + } + bufPos = 0; + } + Buffer[bufPos++] = pgm_read_byte(s++); + len--; + } + if (bufPos && fine) { + if (client->write((uint8_t *)Buffer, bufPos) != (size_t)bufPos) { + return -1; + } + bufPos = 0; + } + return 0; +} +#endif + +/** +/* PGconnection writeMsgPart. + * + * @param s message string + * @param len the length of the message + * @param fine write end + * @returns 0 on success, or -1 for an error + */ +int PGconnection::writeMsgPart(const char *s, int len, int fine) +{ + while (len > 0) { + int n = len; + if (n > bufSize - bufPos) n = bufSize - bufPos; + memcpy(Buffer + bufPos, s, n); + bufPos += n; + s += n; + len -= n; + if (bufPos >= bufSize) { +#ifndef ESP32_IDF + if (client->write((uint8_t *)Buffer, bufPos) != (size_t)bufPos) { + return -1; + } +#else + int err = send(SockH, /*_buffer*/Buffer, bufPos, 0); + if (err < 0) + return -1; +#endif + + bufPos = 0; + } + } + if (bufPos && fine) { +#ifndef ESP32_IDF + if (client->write((uint8_t *)Buffer, bufPos) != (size_t)bufPos) { + return -1; + } +#else + int err = send(SockH, /*_buffer*/Buffer, bufPos, 0); + if (err < 0) + return -1; +#endif + bufPos = 0; + } + + return 0; +} + +/** +/* PGconnection writeFormattedQuery. + * + * @param length the length of the message + * @param progmem the command + * @param format format string + * @param va parameters + * @returns 0 on success, or -1 for an error + */ +int32_t PGconnection::writeFormattedQuery(int32_t length, int progmem, const char *format, va_list va) +{ + int32_t msgLen = 0; + const char *percent; + int blen, rc; + char buf[32], znak; +#ifdef ESP32 + (void) progmem; +#endif + if (length) { + length += 4; + bufPos = 0; + Buffer[bufPos++] = 'Q'; + Buffer[bufPos++] = (length >> 24) & 0xff; + Buffer[bufPos++] = (length >> 16) & 0xff; + Buffer[bufPos++] = (length >> 8) & 0xff; + Buffer[bufPos++] = (length) & 0xff; + } + for (;;) { +#ifndef ESP32 + if (progmem) { + percent = strchr_P(format, '%'); + } + else { +#endif + percent = strchr(format, '%'); +#ifndef ESP32 + } +#endif + if (!percent) break; +#ifndef ESP32 + if (progmem) { + znak = pgm_read_byte(percent+1); + } + else { +#endif + znak = percent[1]; +#ifndef ESP32 + } +#endif + if (!length) { + msgLen += (percent - format); + } + else { +#ifndef ESP32 + if (progmem) { + rc = writeMsgPart_P(format, percent - format, false); + } + else { +#endif + rc = writeMsgPart(format, percent - format, false); +#ifndef ESP32 + } +#endif + if (rc) goto write_error; + } + format = percent + 2; + if (znak == 's' || znak == 'n') { + char *str = va_arg(va, char *); + blen = (znak == 's') ? escapeString(str, NULL) : escapeName(str, NULL); + if (!length) { + msgLen += blen; + } + else { + if (bufPos + blen > bufSize) { + rc = writeMsgPart(NULL, 0, true); + if (rc) goto write_error; + } + } + if (znak == 's') { + escapeString(str, Buffer + bufPos); + } + else { + escapeName(str, Buffer + bufPos); + } + bufPos += blen; + continue; + } + if (znak == 'l' || znak == 'd') { + if (znak == 'l') { + long n = va_arg(va, long); + blen = snprintf(buf, 32, "'%ld'", n); + } + else { + int n = va_arg(va, int); + blen = snprintf(buf, 32, "'%d'", n); + } + if (length) { + rc = writeMsgPart(buf, blen, false); + if (rc) goto write_error; + } + else { + msgLen += blen; + } + } + setMsg_P(EM_FORMAT, PG_RSTAT_HAVE_ERROR); + return -1; + } +#ifndef ESP32 + if (progmem) { + blen = strlen_P(format); + } + else { +#endif + blen = strlen(format); +#ifndef ESP32 + } +#endif + if (length) { +#ifndef ESP32 + if (progmem) { + rc = writeMsgPart_P(format, blen, false); + } + else { +#endif + rc = writeMsgPart(format, blen, false); +#ifndef ESP32 + } +#endif + if (!rc) { + rc = writeMsgPart("\0",1,true); + } + if (rc) goto write_error; + } + else { + msgLen += blen + 1; + } + return msgLen; +write_error: + setMsg_P(EM_WRITE, PG_RSTAT_HAVE_ERROR); + conn_status = CONNECTION_BAD; + return -1; +} diff --git a/software/libs/libpq_esp32.h b/software/libs/libpq_esp32.h new file mode 100644 index 0000000..914b1b3 --- /dev/null +++ b/software/libs/libpq_esp32.h @@ -0,0 +1,270 @@ +/* + * libpq_esp32.h + * + * Created on: 26.02.2022 + * Author: michi + * SPDX-FileCopyrightText: 2022 MDC Service <info@mdc-service.de> + * SPDX-License-Identifier: GPL-3.0-or-later + * Ported from SimplePgSQL (https://github.com/ethanak/SimplePgSQL) to ESP IDF + * original SimplePgSQL: * Copyright (C) Bohdan R. Rau 2016 <ethanak@polip.com> + */ + + +#ifndef _SIMPLEPGSQL +#define _SIMPLEPGSQL 1 + +//Michi begin +#define ESP32 +#define ESP32_IDF +//Michi end + +#ifdef __AVR__ +// You need MD5 library from https://github.com/tzikis/ArduinoMD5 +// for Arduino boards. Uncomment only if you have this library and +// you must use md5 passwords. +// Do not use it on 32 kB ATMega processors! + +// #define PG_USE_MD5 1 + +#else +// ESP8266 has MD5 code in ROM so there is no need to comment +#define PG_USE_MD5 1 +#endif + + + + +#ifndef ESP32_IDF + #include <Arduino.h> + #include <Client.h> +#else + #include "esp_system.h" + #include "esp_netif.h" + #include "lwip/err.h" + #include "lwip/sockets.h" + + #define byte uint8_t +#endif + +typedef enum +{ + CONNECTION_OK, + CONNECTION_BAD, + CONNECTION_NEEDED, /* setDbLogin() needed */ + /* Internal states here */ + CONNECTION_AWAITING_RESPONSE, /* Waiting for a response from the + * postmaster. */ + CONNECTION_AUTH_OK /* Received authentication; waiting for + * backend startup. */ +} ConnStatusType; + +#ifdef ESP8266 +#define PG_BUFFER_SIZE 2048 +#elif defined(ESP32) +#define PG_BUFFER_SIZE 16384 +#else +#define PG_BUFFER_SIZE 256 +#endif + +// maximum number of fields in backend response +// must not exceed number of bits in _formats and _null +#ifdef ESP32 +#define PG_MAX_FIELDS 64 +#else +#define PG_MAX_FIELDS 32 +#endif +// ignore notices and notifications +#define PG_FLAG_IGNORE_NOTICES 1 +// do not store column names +#define PG_FLAG_IGNORE_COLUMNS 2 +// never set this flag manually! +# define PG_FLAG_STATIC_BUFFER 4 + +// ready for next query +#define PG_RSTAT_READY 1 +// command sent +#define PG_RSTAT_COMMAND_SENT 2 +// column names in buffer +#define PG_RSTAT_HAVE_COLUMNS 4 +// row values in buffer +#define PG_RSTAT_HAVE_ROW 8 +// summary (number of tuples/affected rows) received +#define PG_RSTAT_HAVE_SUMMARY 16 +// error message in buffer +#define PG_RSTAT_HAVE_ERROR 32 +// notice/notification in buffer +#define PG_RSTAT_HAVE_NOTICE 64 + +#define PG_RSTAT_HAVE_MASK (PG_RSTAT_HAVE_COLUMNS | \ + PG_RSTAT_HAVE_ROW | \ + PG_RSTAT_HAVE_SUMMARY | \ + PG_RSTAT_HAVE_ERROR | \ + PG_RSTAT_HAVE_NOTICE) + +#define PG_RSTAT_HAVE_MESSAGE (PG_RSTAT_HAVE_ERROR | PG_RSTAT_HAVE_NOTICE) + +// Framework abstraction - Michi +// only here, differences of Arduino, ESP32-Arduino, ESP32-IDF or Platform.io should be made +//class fw_function { +// public: +// send_network_packet(); +//} + + +class PGconnection { + public: +#ifndef ESP32_IDF + PGconnection(Client *c, int flags = 0, int memory = 0, char *foreignBuffer = NULL); +#else + PGconnection(const int flags, const unsigned char *_Buffer, const int bufSize); +#endif + /* + * returns connection status. + * passwd may be null in case of 'trust' authorization. + * only 'trust', 'password' and 'md5' (if compiled in) + * authorization modes are implemented. + * ssl mode is not implemented. + * database name defaults to user name * + */ +#ifndef ESP32_IDF + int setDbLogin(IPAddress server, const char *user, const char *passwd = NULL, const char *db = NULL, const char *charset = NULL, int port = 5432); +#else + int setDbLogin(const char *ServerIP, int ServerPort, const char *dbName, const char *dbUser, const char *dbPasswd, const char *charset); +#endif + /* + * performs authorization tasks if needed + * returns current connection status + * must be called periodically until OK, BAD or NEEDED + */ + int status(void); + /* + * sends termination command if possible + * closes client connection and frees internal buffer + */ + void close(void); + /* + * sends query to backend + * returns negative value on error + * or zero on success + */ + int execute(const char *query, int progmem = 0); + int PGexecute(const char *query); //old version from 17.02.2022 + + /* should be called periodically in idle state + * if notifications are enabled + * returns: + * - negative value on error + * - zero if no interesting data arrived + * - current data status if some data arrived + */ + + int getData(void); + /* + * returns pointer to n-th column name in internal buffer + * if available or null if column number out of range + * will be invalidated on next getData call + */ + char *getColumn(int n); + /* + * returns pointer to n-th column value in internal buffer + * if available or null if column number out of range + * or value is NULL + * will be invalidated on next getData call + */ + char *getValue(int); + /* + * returns pointer to message (error or notice) + * if available or NULL + * will be invalidated on next getData call + */ + char *getMessage(void); + int dataStatus(void) { + return result_status; + }; + int nfields(void) { + return _nfields; + }; + int ntuples(void) { + return _ntuples; + }; + /* + * returns length of escaped string + * single quotes and E prefix (if needed) + * will be added. + */ + int escapeString(const char *inbuf, char *outbuf); + /* + * returns length of escaped string + * double quotes will be added. + */ + int escapeName(const char *inbuf, char *outbuf); + /* + * sends formatted query to backend + * returns negative value on error + * or zero on success + * Formatting sequences: + * %s - string literal (will be escaped with escapeString) + * %n - name (will be escaped with escapeName) + * %d - int (single quotes will be added) + * %l - long int (single quotes will be added) + * %% - % character + */ + int executeFormat(int progmem, const char *format, ...); + + + private: +#ifndef ESP32_IDF + Client *client; +#endif + int pqPacketSend(char pack_type, const char *buf, int buf_len, int progmem = 0); + int pqGetc(char *); + int pqGetInt4(int32_t *result); + int pqGetInt2(int16_t *result); + int pqGetnchar(char *s, int len); + int pqSkipnchar(int len); + int pqGets(char *s, int maxlen); + int pqGetRowDescriptions(void); + int pqGetRow(void); + void setMsg(const char *, int); + void setMsg_P(const char *, int); + int pqGetNotice(int); + int pqGetNotify(int32_t); + char *_user; + char *_passwd; + char *Buffer; + int bufSize; + int bufPos; + int writeMsgPart(const char *s, int len, int fine); + int writeMsgPart_P(const char *s, int len, int fine); + int32_t writeFormattedQuery(int32_t length, int progmem, const char *format, va_list va); + + int build_startup_packet(char *packet, const char *db, const char *charset); + byte conn_status; + byte attempts; +/* + int32_t be_pid; + int32_t be_key; +*/ + int16_t _nfields; + int16_t _ntuples; +#ifdef ESP32_IDF + uint64_t _formats; + uint64_t _null; + uint32_t _available; //michi + struct sockaddr_in DestAddr; + int SockH = -1; + int ipProtocol = 0; + int AddrFamily = 0; + int NetConnected=0; + + int dataAvailable(void); +#else + uint32_t _formats; + uint32_t _null; +#endif + byte _binary; + byte _flags; + int result_status; +}; + +#endif |
