Release v0.6.0: embedded ESP32 support docs and external esp32_send_kind4 example
This commit is contained in:
61
CMakeLists.txt
Normal file
61
CMakeLists.txt
Normal file
@@ -0,0 +1,61 @@
|
||||
cmake_minimum_required(VERSION 3.16)
|
||||
|
||||
if(ESP_PLATFORM)
|
||||
idf_component_register(
|
||||
SRCS
|
||||
"nostr_core/nostr_common.c"
|
||||
"nostr_core/nostr_log.c"
|
||||
"nostr_core/nip001.c"
|
||||
"nostr_core/nip004.c"
|
||||
"nostr_core/nip006.c"
|
||||
"nostr_core/nip019.c"
|
||||
"nostr_core/utils.c"
|
||||
"nostr_core/crypto/nostr_secp256k1.c"
|
||||
"nostr_core/crypto/nostr_aes.c"
|
||||
"nostr_core/crypto/nostr_chacha20.c"
|
||||
"cjson/cJSON.c"
|
||||
"platform/esp32/nostr_platform_esp32.c"
|
||||
"platform/esp32/nostr_http_esp32.c"
|
||||
"platform/esp32/nostr_websocket_esp32.c"
|
||||
INCLUDE_DIRS
|
||||
"."
|
||||
"nostr_core"
|
||||
"nostr_core/crypto"
|
||||
"cjson"
|
||||
"nostr_websocket"
|
||||
REQUIRES
|
||||
secp256k1
|
||||
esp_hw_support
|
||||
esp_http_client
|
||||
esp-tls
|
||||
tcp_transport
|
||||
mbedtls
|
||||
)
|
||||
|
||||
target_compile_definitions(${COMPONENT_LIB} PRIVATE NOSTR_NO_FILESYSTEM=1)
|
||||
else()
|
||||
project(nostr_core_lib C)
|
||||
|
||||
add_library(nostr_core STATIC
|
||||
nostr_core/nostr_common.c
|
||||
nostr_core/nostr_log.c
|
||||
nostr_core/request_validator.c
|
||||
nostr_core/nip001.c
|
||||
nostr_core/nip006.c
|
||||
nostr_core/nip019.c
|
||||
nostr_core/utils.c
|
||||
nostr_core/crypto/nostr_secp256k1.c
|
||||
nostr_core/crypto/nostr_aes.c
|
||||
nostr_core/crypto/nostr_chacha20.c
|
||||
cjson/cJSON.c
|
||||
platform/linux.c
|
||||
)
|
||||
|
||||
target_include_directories(nostr_core PUBLIC
|
||||
.
|
||||
nostr_core
|
||||
nostr_core/crypto
|
||||
cjson
|
||||
nostr_websocket
|
||||
)
|
||||
endif()
|
||||
26
README.md
26
README.md
@@ -2,7 +2,7 @@
|
||||
|
||||
A C library for NOSTR protocol implementation. Work in progress.
|
||||
|
||||
[](VERSION)
|
||||
[](VERSION)
|
||||
[](#license)
|
||||
[](#building)
|
||||
|
||||
@@ -508,6 +508,20 @@ int main(void) {
|
||||
- **Windows** (MinGW, MSYS2)
|
||||
- **Embedded Systems** (resource-constrained environments)
|
||||
|
||||
## 🔌 Embedded (ESP32) Support
|
||||
|
||||
`nostr_core_lib` now includes a platform abstraction layer and ESP32-native implementations for core networking and entropy APIs, while preserving desktop compatibility.
|
||||
|
||||
Implemented embedded capabilities include:
|
||||
- platform random source abstraction via `nostr_platform_random()`
|
||||
- ESP32 random provider using `esp_fill_random`
|
||||
- ESP32 HTTP client implementation via `esp_http_client`
|
||||
- ESP32 WebSocket/WSS transport via `esp_transport_ws` + TLS certificate bundle
|
||||
- NIP-04 encrypted DM flow validated on-device against public relays
|
||||
|
||||
Reference ESP-IDF example project (in this workspace):
|
||||
- `../esp32_send_kind4/` — Wi-Fi connect + `wss://` relay connect + send two kind-4 encrypted events + print relay responses
|
||||
|
||||
## 📄 Documentation
|
||||
|
||||
- **[POOL_API.md](POOL_API.md)** - Relay pool API notes
|
||||
@@ -527,20 +541,22 @@ int main(void) {
|
||||
|
||||
## 📈 Version History
|
||||
|
||||
Current version: **0.5.12**
|
||||
Current version: **0.6.0**
|
||||
|
||||
The library uses automatic semantic versioning based on Git tags. Each build increments the patch version automatically.
|
||||
|
||||
**Recent Developments:**
|
||||
- **OpenSSL Migration**: Transitioned from mbedTLS to OpenSSL for improved compatibility
|
||||
- **ESP32 Embedded Enablement**: Added PAL-based embedded support directly in `nostr_core_lib`
|
||||
- **ESP32 HTTP + WSS**: Added ESP-IDF-backed HTTP and secure websocket transport implementations
|
||||
- **NIP-04 On-Device Validation**: Verified encrypted kind-4 DM publish flow from ESP32 to live relays
|
||||
- **NIP-05 Support**: DNS-based internet identifier verification
|
||||
- **NIP-11 Support**: Relay information document fetching and parsing
|
||||
- **NIP-19 Support**: Bech32-encoded entities (nsec/npub)
|
||||
- **Enhanced WebSocket**: OpenSSL-based TLS WebSocket communication
|
||||
- **Comprehensive Testing**: Extensive test suite and error handling
|
||||
|
||||
**Version Timeline:**
|
||||
- `v0.4.x` - Current development releases with relay pool and expanded NIP support
|
||||
- `v0.6.x` - Embedded-capable releases with ESP32 PAL, HTTP, and WSS support
|
||||
- `v0.4.x` - Development releases with relay pool and expanded NIP support
|
||||
- `v0.2.x` - Earlier OpenSSL-based development releases
|
||||
- `v0.1.x` - Initial development releases
|
||||
- Focus on core protocol implementation and OpenSSL-based crypto
|
||||
|
||||
1
build.sh
1
build.sh
@@ -509,6 +509,7 @@ SOURCES="$SOURCES nostr_core/nostr_log.c"
|
||||
SOURCES="$SOURCES nostr_websocket/nostr_websocket_openssl.c"
|
||||
SOURCES="$SOURCES nostr_core/request_validator.c"
|
||||
SOURCES="$SOURCES nostr_core/nostr_http.c"
|
||||
SOURCES="$SOURCES platform/linux.c"
|
||||
|
||||
NIP_DESCRIPTIONS=""
|
||||
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
#!/bin/bash
|
||||
|
||||
# increment_and_push.sh - Version increment and git automation script
|
||||
# Usage: ./increment_and_push.sh "meaningful git comment"
|
||||
# Usage:
|
||||
# ./increment_and_push.sh "meaningful git comment"
|
||||
# ./increment_and_push.sh --set-version vX.Y.Z "meaningful git comment"
|
||||
|
||||
set -e # Exit on error
|
||||
|
||||
@@ -65,16 +67,30 @@ if ! git rev-parse --git-dir > /dev/null 2>&1; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Check if we have a commit message
|
||||
if [ $# -eq 0 ]; then
|
||||
print_error "Usage: $0 \"meaningful git comment\""
|
||||
echo ""
|
||||
echo "Example: $0 \"Add enhanced subscription functionality\""
|
||||
echo ""
|
||||
exit 1
|
||||
fi
|
||||
TARGET_VERSION=""
|
||||
COMMIT_MESSAGE=""
|
||||
|
||||
COMMIT_MESSAGE="$1"
|
||||
# Parse arguments
|
||||
if [ "$1" = "--set-version" ]; then
|
||||
if [ $# -lt 3 ]; then
|
||||
print_error "Usage: $0 --set-version vX.Y.Z \"meaningful git comment\""
|
||||
echo ""
|
||||
echo "Example: $0 --set-version v0.6.0 \"Release v0.6.0 with ESP32 embedded support\""
|
||||
echo ""
|
||||
exit 1
|
||||
fi
|
||||
TARGET_VERSION="$2"
|
||||
COMMIT_MESSAGE="$3"
|
||||
else
|
||||
if [ $# -eq 0 ]; then
|
||||
print_error "Usage: $0 \"meaningful git comment\""
|
||||
echo ""
|
||||
echo "Example: $0 \"Add enhanced subscription functionality\""
|
||||
echo ""
|
||||
exit 1
|
||||
fi
|
||||
COMMIT_MESSAGE="$1"
|
||||
fi
|
||||
|
||||
# Check if nostr_core.h exists
|
||||
if [ ! -f "nostr_core/nostr_core.h" ]; then
|
||||
@@ -103,21 +119,37 @@ fi
|
||||
|
||||
print_info "Current version: $CURRENT_VERSION (Major: $VERSION_MAJOR, Minor: $VERSION_MINOR, Patch: $VERSION_PATCH)"
|
||||
|
||||
# Increment patch version
|
||||
NEW_PATCH=$((VERSION_PATCH + 1))
|
||||
NEW_VERSION="v$VERSION_MAJOR.$VERSION_MINOR.$NEW_PATCH"
|
||||
if [ -n "$TARGET_VERSION" ]; then
|
||||
if [[ ! "$TARGET_VERSION" =~ ^v([0-9]+)\.([0-9]+)\.([0-9]+)$ ]]; then
|
||||
print_error "Invalid target version format: $TARGET_VERSION (expected vX.Y.Z)"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
NEW_VERSION="$TARGET_VERSION"
|
||||
NEW_MAJOR="${BASH_REMATCH[1]}"
|
||||
NEW_MINOR="${BASH_REMATCH[2]}"
|
||||
NEW_PATCH="${BASH_REMATCH[3]}"
|
||||
else
|
||||
# Increment patch version
|
||||
NEW_MAJOR="$VERSION_MAJOR"
|
||||
NEW_MINOR="$VERSION_MINOR"
|
||||
NEW_PATCH=$((VERSION_PATCH + 1))
|
||||
NEW_VERSION="v$NEW_MAJOR.$NEW_MINOR.$NEW_PATCH"
|
||||
fi
|
||||
|
||||
print_info "New version will be: $NEW_VERSION"
|
||||
|
||||
# Update version in nostr_core.h
|
||||
sed -i "s/#define VERSION .*/#define VERSION \"$NEW_VERSION\"/" nostr_core/nostr_core.h
|
||||
sed -i "s/#define VERSION_MAJOR .*/#define VERSION_MAJOR $NEW_MAJOR/" nostr_core/nostr_core.h
|
||||
sed -i "s/#define VERSION_MINOR .*/#define VERSION_MINOR $NEW_MINOR/" nostr_core/nostr_core.h
|
||||
sed -i "s/#define VERSION_PATCH .*/#define VERSION_PATCH $NEW_PATCH/" nostr_core/nostr_core.h
|
||||
|
||||
print_success "Updated version in nostr_core.h"
|
||||
|
||||
# Check if VERSION file exists and update it
|
||||
if [ -f "VERSION" ]; then
|
||||
echo "$VERSION_MAJOR.$VERSION_MINOR.$NEW_PATCH" > VERSION
|
||||
echo "$NEW_MAJOR.$NEW_MINOR.$NEW_PATCH" > VERSION
|
||||
print_success "Updated VERSION file"
|
||||
fi
|
||||
|
||||
|
||||
@@ -3,9 +3,8 @@
|
||||
#include <secp256k1_ecdh.h>
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
#include <fcntl.h>
|
||||
#include <unistd.h>
|
||||
#include <stddef.h>
|
||||
#include "../nostr_platform.h"
|
||||
|
||||
/*
|
||||
* PRIVATE INTERNAL FUNCTIONS - NOT EXPORTED
|
||||
@@ -313,23 +312,9 @@ int nostr_secp256k1_get_random_bytes(unsigned char *buf, size_t len) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Try to use /dev/urandom for good randomness
|
||||
int fd = open("/dev/urandom", O_RDONLY);
|
||||
if (fd >= 0) {
|
||||
ssize_t result = read(fd, buf, len);
|
||||
close(fd);
|
||||
if (result == (ssize_t)len) {
|
||||
return 1;
|
||||
}
|
||||
if (nostr_platform_random(buf, len) != 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Fallback to a simple PRNG (not cryptographically secure, but better than nothing)
|
||||
// In a real implementation, you'd want to use a proper CSPRNG
|
||||
static unsigned long seed = 1;
|
||||
for (size_t i = 0; i < len; i++) {
|
||||
seed = seed * 1103515245 + 12345;
|
||||
buf[i] = (unsigned char)(seed >> 16);
|
||||
}
|
||||
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
@@ -10,24 +10,17 @@
|
||||
#include <ctype.h>
|
||||
#include <time.h>
|
||||
#include "../nostr_core/nostr_common.h"
|
||||
#include "nostr_platform.h"
|
||||
|
||||
int nostr_generate_keypair(unsigned char* private_key, unsigned char* public_key) {
|
||||
if (!private_key || !public_key) {
|
||||
return NOSTR_ERROR_INVALID_INPUT;
|
||||
}
|
||||
|
||||
// Generate random entropy using /dev/urandom
|
||||
FILE* urandom = fopen("/dev/urandom", "rb");
|
||||
if (!urandom) {
|
||||
if (nostr_platform_random(private_key, 32) != 0) {
|
||||
return NOSTR_ERROR_IO_FAILED;
|
||||
}
|
||||
|
||||
if (fread(private_key, 1, 32, urandom) != 32) {
|
||||
fclose(urandom);
|
||||
return NOSTR_ERROR_IO_FAILED;
|
||||
}
|
||||
fclose(urandom);
|
||||
|
||||
// Validate private key
|
||||
if (nostr_ec_private_key_verify(private_key) != 0) {
|
||||
return NOSTR_ERROR_CRYPTO_FAILED;
|
||||
@@ -50,17 +43,10 @@ int nostr_generate_mnemonic_and_keys(char* mnemonic, size_t mnemonic_size,
|
||||
|
||||
// Generate entropy for 12-word mnemonic
|
||||
unsigned char entropy[16];
|
||||
FILE* urandom = fopen("/dev/urandom", "rb");
|
||||
if (!urandom) {
|
||||
if (nostr_platform_random(entropy, sizeof(entropy)) != 0) {
|
||||
return NOSTR_ERROR_IO_FAILED;
|
||||
}
|
||||
|
||||
if (fread(entropy, 1, sizeof(entropy), urandom) != sizeof(entropy)) {
|
||||
fclose(urandom);
|
||||
return NOSTR_ERROR_IO_FAILED;
|
||||
}
|
||||
fclose(urandom);
|
||||
|
||||
// Generate mnemonic from entropy
|
||||
if (nostr_bip39_mnemonic_from_bytes(entropy, sizeof(entropy), mnemonic) != 0) {
|
||||
return NOSTR_ERROR_CRYPTO_FAILED;
|
||||
|
||||
@@ -180,7 +180,7 @@ nostr_input_type_t nostr_detect_input_type(const char* input) {
|
||||
if (len == 64) {
|
||||
int is_hex = 1;
|
||||
for (size_t i = 0; i < len; i++) {
|
||||
if (!isxdigit(input[i])) {
|
||||
if (!isxdigit((unsigned char)input[i])) {
|
||||
is_hex = 0;
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -2,10 +2,10 @@
|
||||
#define NOSTR_CORE_H
|
||||
|
||||
// Version information (auto-updated by increment_and_push.sh)
|
||||
#define VERSION "v0.5.16"
|
||||
#define VERSION "v0.6.0"
|
||||
#define VERSION_MAJOR 0
|
||||
#define VERSION_MINOR 5
|
||||
#define VERSION_PATCH 16
|
||||
#define VERSION_MINOR 6
|
||||
#define VERSION_PATCH 0
|
||||
|
||||
/*
|
||||
* NOSTR Core Library - Complete API Reference
|
||||
|
||||
20
nostr_core/nostr_platform.h
Normal file
20
nostr_core/nostr_platform.h
Normal file
@@ -0,0 +1,20 @@
|
||||
#ifndef NOSTR_PLATFORM_H
|
||||
#define NOSTR_PLATFORM_H
|
||||
|
||||
#include <stddef.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/*
|
||||
* Fill buffer with cryptographically secure random bytes.
|
||||
* Returns 0 on success, -1 on failure.
|
||||
*/
|
||||
int nostr_platform_random(unsigned char *buf, size_t len);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* NOSTR_PLATFORM_H */
|
||||
@@ -433,32 +433,38 @@ int nostr_sha256_final(nostr_sha256_ctx_t* ctx, unsigned char* hash) {
|
||||
}
|
||||
|
||||
int nostr_sha256_file_stream(const char* filename, unsigned char* hash) {
|
||||
#ifndef NOSTR_NO_FILESYSTEM
|
||||
if (!filename || !hash) return -1;
|
||||
|
||||
|
||||
FILE* file = fopen(filename, "rb");
|
||||
if (!file) return -1;
|
||||
|
||||
|
||||
nostr_sha256_ctx_t ctx;
|
||||
if (nostr_sha256_init(&ctx) != 0) {
|
||||
fclose(file);
|
||||
return -1;
|
||||
}
|
||||
|
||||
|
||||
// Process file in 4KB chunks for memory efficiency
|
||||
unsigned char buffer[4096];
|
||||
size_t bytes_read;
|
||||
|
||||
|
||||
while ((bytes_read = fread(buffer, 1, sizeof(buffer), file)) > 0) {
|
||||
if (nostr_sha256_update(&ctx, buffer, bytes_read) != 0) {
|
||||
fclose(file);
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
fclose(file);
|
||||
|
||||
|
||||
// Finalize and return result
|
||||
return nostr_sha256_final(&ctx, hash);
|
||||
#else
|
||||
(void)filename;
|
||||
(void)hash;
|
||||
return -1;
|
||||
#endif
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
|
||||
312
platform/esp32/nostr_http_esp32.c
Normal file
312
platform/esp32/nostr_http_esp32.c
Normal file
@@ -0,0 +1,312 @@
|
||||
#include "../../nostr_core/nostr_http.h"
|
||||
|
||||
#include "esp_http_client.h"
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <strings.h>
|
||||
|
||||
typedef struct {
|
||||
char* data;
|
||||
size_t len;
|
||||
size_t cap;
|
||||
size_t max_bytes;
|
||||
int truncated;
|
||||
} nostr_http_buffer_t;
|
||||
|
||||
static char g_ca_bundle_path[512] = {0};
|
||||
|
||||
static char* nostr_http_strdup_local(const char* s) {
|
||||
if (!s) return NULL;
|
||||
size_t n = strlen(s);
|
||||
char* out = (char*)malloc(n + 1U);
|
||||
if (!out) return NULL;
|
||||
memcpy(out, s, n + 1U);
|
||||
return out;
|
||||
}
|
||||
|
||||
static int append_bytes(nostr_http_buffer_t* b, const void* src, size_t n) {
|
||||
if (!b || !src || n == 0) return 1;
|
||||
|
||||
if (b->max_bytes > 0 && b->len >= b->max_bytes) {
|
||||
b->truncated = 1;
|
||||
return 1;
|
||||
}
|
||||
|
||||
size_t to_copy = n;
|
||||
if (b->max_bytes > 0) {
|
||||
size_t remaining = b->max_bytes - b->len;
|
||||
if (to_copy > remaining) {
|
||||
to_copy = remaining;
|
||||
b->truncated = 1;
|
||||
}
|
||||
}
|
||||
|
||||
if (b->len + to_copy + 1U > b->cap) {
|
||||
size_t new_cap = b->cap == 0 ? 1024U : b->cap;
|
||||
while (new_cap < b->len + to_copy + 1U) {
|
||||
new_cap *= 2U;
|
||||
}
|
||||
char* p = (char*)realloc(b->data, new_cap);
|
||||
if (!p) return 0;
|
||||
b->data = p;
|
||||
b->cap = new_cap;
|
||||
}
|
||||
|
||||
memcpy(b->data + b->len, src, to_copy);
|
||||
b->len += to_copy;
|
||||
b->data[b->len] = '\0';
|
||||
return 1;
|
||||
}
|
||||
|
||||
typedef struct {
|
||||
nostr_http_buffer_t* body;
|
||||
nostr_http_buffer_t* headers;
|
||||
int capture_headers;
|
||||
} nostr_http_event_ctx_t;
|
||||
|
||||
static esp_err_t http_event_handler(esp_http_client_event_t* evt) {
|
||||
if (!evt || !evt->user_data) return ESP_OK;
|
||||
|
||||
nostr_http_event_ctx_t* ctx = (nostr_http_event_ctx_t*)evt->user_data;
|
||||
|
||||
switch (evt->event_id) {
|
||||
case HTTP_EVENT_ON_DATA:
|
||||
if (evt->data && evt->data_len > 0 && ctx->body) {
|
||||
if (!append_bytes(ctx->body, evt->data, (size_t)evt->data_len)) {
|
||||
return ESP_FAIL;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case HTTP_EVENT_ON_HEADER:
|
||||
if (ctx->capture_headers && ctx->headers && evt->header_key && evt->header_value) {
|
||||
if (!append_bytes(ctx->headers, evt->header_key, strlen(evt->header_key)) ||
|
||||
!append_bytes(ctx->headers, ": ", 2) ||
|
||||
!append_bytes(ctx->headers, evt->header_value, strlen(evt->header_value)) ||
|
||||
!append_bytes(ctx->headers, "\r\n", 2)) {
|
||||
return ESP_FAIL;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
void nostr_http_set_ca_bundle(const char* ca_bundle_path) {
|
||||
if (!ca_bundle_path || ca_bundle_path[0] == '\0') {
|
||||
g_ca_bundle_path[0] = '\0';
|
||||
return;
|
||||
}
|
||||
|
||||
strncpy(g_ca_bundle_path, ca_bundle_path, sizeof(g_ca_bundle_path) - 1);
|
||||
g_ca_bundle_path[sizeof(g_ca_bundle_path) - 1] = '\0';
|
||||
}
|
||||
|
||||
const char* nostr_http_detect_ca_bundle(void) {
|
||||
if (g_ca_bundle_path[0] != '\0') {
|
||||
return g_ca_bundle_path;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
int nostr_http_request(const nostr_http_request_t* req, nostr_http_response_t* resp) {
|
||||
if (!req || !req->url || !resp) return NOSTR_ERROR_INVALID_INPUT;
|
||||
|
||||
memset(resp, 0, sizeof(*resp));
|
||||
|
||||
const char* method = (req->method && req->method[0] != '\0') ? req->method : "GET";
|
||||
int timeout_ms = (req->timeout_seconds > 0 ? req->timeout_seconds : 30) * 1000;
|
||||
|
||||
nostr_http_buffer_t body = {0};
|
||||
body.max_bytes = req->max_response_bytes;
|
||||
|
||||
nostr_http_buffer_t headers = {0};
|
||||
|
||||
nostr_http_event_ctx_t evt_ctx = {
|
||||
.body = &body,
|
||||
.headers = &headers,
|
||||
.capture_headers = req->capture_headers
|
||||
};
|
||||
|
||||
esp_http_client_config_t cfg = {
|
||||
.url = req->url,
|
||||
.timeout_ms = timeout_ms,
|
||||
.event_handler = http_event_handler,
|
||||
.user_data = &evt_ctx,
|
||||
.disable_auto_redirect = req->follow_redirects ? false : true,
|
||||
.max_redirection_count = req->max_redirects > 0 ? req->max_redirects : 3,
|
||||
};
|
||||
|
||||
esp_http_client_handle_t client = esp_http_client_init(&cfg);
|
||||
if (!client) {
|
||||
return NOSTR_ERROR_NETWORK_FAILED;
|
||||
}
|
||||
|
||||
if (req->user_agent && req->user_agent[0] != '\0') {
|
||||
esp_http_client_set_header(client, "User-Agent", req->user_agent);
|
||||
} else {
|
||||
esp_http_client_set_header(client, "User-Agent", "nostr-core/1.0");
|
||||
}
|
||||
|
||||
if (req->headers) {
|
||||
for (const char** h = req->headers; *h; h++) {
|
||||
const char* line = *h;
|
||||
if (!line || line[0] == '\0') continue;
|
||||
const char* colon = strchr(line, ':');
|
||||
if (!colon) continue;
|
||||
|
||||
size_t key_len = (size_t)(colon - line);
|
||||
if (key_len == 0) continue;
|
||||
|
||||
char* key = (char*)malloc(key_len + 1U);
|
||||
if (!key) {
|
||||
esp_http_client_cleanup(client);
|
||||
free(body.data);
|
||||
free(headers.data);
|
||||
return NOSTR_ERROR_MEMORY_FAILED;
|
||||
}
|
||||
|
||||
memcpy(key, line, key_len);
|
||||
key[key_len] = '\0';
|
||||
|
||||
const char* value = colon + 1;
|
||||
while (*value == ' ') value++;
|
||||
|
||||
esp_http_client_set_header(client, key, value);
|
||||
free(key);
|
||||
}
|
||||
}
|
||||
|
||||
if (strcasecmp(method, "GET") == 0) {
|
||||
esp_http_client_set_method(client, HTTP_METHOD_GET);
|
||||
} else if (strcasecmp(method, "POST") == 0) {
|
||||
esp_http_client_set_method(client, HTTP_METHOD_POST);
|
||||
if (req->body && req->body_len > 0) {
|
||||
esp_http_client_set_post_field(client, (const char*)req->body, (int)req->body_len);
|
||||
} else {
|
||||
esp_http_client_set_post_field(client, "", 0);
|
||||
}
|
||||
} else if (strcasecmp(method, "HEAD") == 0) {
|
||||
esp_http_client_set_method(client, HTTP_METHOD_HEAD);
|
||||
} else if (strcasecmp(method, "PUT") == 0) {
|
||||
esp_http_client_set_method(client, HTTP_METHOD_PUT);
|
||||
if (req->body && req->body_len > 0) {
|
||||
esp_http_client_set_post_field(client, (const char*)req->body, (int)req->body_len);
|
||||
}
|
||||
} else if (strcasecmp(method, "DELETE") == 0) {
|
||||
esp_http_client_set_method(client, HTTP_METHOD_DELETE);
|
||||
} else {
|
||||
esp_http_client_cleanup(client);
|
||||
return NOSTR_ERROR_INVALID_INPUT;
|
||||
}
|
||||
|
||||
esp_err_t err = esp_http_client_perform(client);
|
||||
if (err != ESP_OK) {
|
||||
esp_http_client_cleanup(client);
|
||||
free(body.data);
|
||||
free(headers.data);
|
||||
return NOSTR_ERROR_NETWORK_FAILED;
|
||||
}
|
||||
|
||||
resp->status_code = esp_http_client_get_status_code(client);
|
||||
resp->body = body.data ? body.data : nostr_http_strdup_local("");
|
||||
resp->body_len = body.data ? body.len : 0;
|
||||
resp->headers_raw = headers.data;
|
||||
resp->truncated = body.truncated;
|
||||
|
||||
char* ct = NULL;
|
||||
char* ct_raw = NULL;
|
||||
if (esp_http_client_get_header(client, "Content-Type", &ct_raw) == ESP_OK && ct_raw && ct_raw[0] != '\0') {
|
||||
ct = nostr_http_strdup_local(ct_raw);
|
||||
}
|
||||
resp->content_type = ct;
|
||||
|
||||
esp_http_client_cleanup(client);
|
||||
|
||||
if (!resp->body) {
|
||||
free(resp->headers_raw);
|
||||
free(resp->content_type);
|
||||
memset(resp, 0, sizeof(*resp));
|
||||
return NOSTR_ERROR_MEMORY_FAILED;
|
||||
}
|
||||
|
||||
return NOSTR_SUCCESS;
|
||||
}
|
||||
|
||||
void nostr_http_response_free(nostr_http_response_t* resp) {
|
||||
if (!resp) return;
|
||||
free(resp->body);
|
||||
free(resp->content_type);
|
||||
free(resp->headers_raw);
|
||||
memset(resp, 0, sizeof(*resp));
|
||||
}
|
||||
|
||||
int nostr_http_get(const char* url, int timeout_seconds, char** body_out, long* status_out) {
|
||||
if (!url || !body_out) return NOSTR_ERROR_INVALID_INPUT;
|
||||
|
||||
*body_out = NULL;
|
||||
if (status_out) *status_out = 0;
|
||||
|
||||
nostr_http_request_t req;
|
||||
memset(&req, 0, sizeof(req));
|
||||
req.method = "GET";
|
||||
req.url = url;
|
||||
req.timeout_seconds = timeout_seconds;
|
||||
req.follow_redirects = 1;
|
||||
req.max_redirects = 3;
|
||||
|
||||
nostr_http_response_t resp;
|
||||
int rc = nostr_http_request(&req, &resp);
|
||||
if (rc != NOSTR_SUCCESS) return rc;
|
||||
|
||||
if (status_out) *status_out = resp.status_code;
|
||||
*body_out = resp.body;
|
||||
free(resp.content_type);
|
||||
free(resp.headers_raw);
|
||||
return NOSTR_SUCCESS;
|
||||
}
|
||||
|
||||
int nostr_http_post_json(const char* url,
|
||||
const char* json_body,
|
||||
int timeout_seconds,
|
||||
char** body_out,
|
||||
long* status_out) {
|
||||
if (!url || !body_out) return NOSTR_ERROR_INVALID_INPUT;
|
||||
|
||||
*body_out = NULL;
|
||||
if (status_out) *status_out = 0;
|
||||
|
||||
const char* headers[] = {
|
||||
"Accept: application/json",
|
||||
"Content-Type: application/json",
|
||||
NULL
|
||||
};
|
||||
|
||||
const char* payload = json_body ? json_body : "{}";
|
||||
|
||||
nostr_http_request_t req;
|
||||
memset(&req, 0, sizeof(req));
|
||||
req.method = "POST";
|
||||
req.url = url;
|
||||
req.headers = headers;
|
||||
req.body = (const unsigned char*)payload;
|
||||
req.body_len = strlen(payload);
|
||||
req.timeout_seconds = timeout_seconds;
|
||||
req.follow_redirects = 1;
|
||||
req.max_redirects = 3;
|
||||
|
||||
nostr_http_response_t resp;
|
||||
int rc = nostr_http_request(&req, &resp);
|
||||
if (rc != NOSTR_SUCCESS) return rc;
|
||||
|
||||
if (status_out) *status_out = resp.status_code;
|
||||
*body_out = resp.body;
|
||||
free(resp.content_type);
|
||||
free(resp.headers_raw);
|
||||
return NOSTR_SUCCESS;
|
||||
}
|
||||
12
platform/esp32/nostr_platform_esp32.c
Normal file
12
platform/esp32/nostr_platform_esp32.c
Normal file
@@ -0,0 +1,12 @@
|
||||
#include "../../nostr_core/nostr_platform.h"
|
||||
|
||||
#include "esp_random.h"
|
||||
|
||||
int nostr_platform_random(unsigned char *buf, size_t len) {
|
||||
if (buf == NULL || len == 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
esp_fill_random(buf, len);
|
||||
return 0;
|
||||
}
|
||||
457
platform/esp32/nostr_websocket_esp32.c
Normal file
457
platform/esp32/nostr_websocket_esp32.c
Normal file
@@ -0,0 +1,457 @@
|
||||
#include "../../nostr_websocket/nostr_websocket_tls.h"
|
||||
|
||||
#include "esp_crt_bundle.h"
|
||||
#include "esp_err.h"
|
||||
#include "esp_log.h"
|
||||
|
||||
#include <ctype.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <esp_transport.h>
|
||||
#include <esp_transport_ssl.h>
|
||||
#include <esp_transport_tcp.h>
|
||||
#include <esp_transport_ws.h>
|
||||
|
||||
typedef struct {
|
||||
char* host;
|
||||
int port;
|
||||
char* path;
|
||||
int use_tls;
|
||||
} ws_url_parts_t;
|
||||
|
||||
struct nostr_ws_client {
|
||||
esp_transport_list_handle_t list;
|
||||
esp_transport_handle_t transport;
|
||||
nostr_ws_state_t state;
|
||||
int timeout_ms;
|
||||
char* host;
|
||||
int port;
|
||||
char* path;
|
||||
};
|
||||
|
||||
static void ws_free_url_parts(ws_url_parts_t* p) {
|
||||
if (!p) return;
|
||||
free(p->host);
|
||||
free(p->path);
|
||||
p->host = NULL;
|
||||
p->path = NULL;
|
||||
p->port = 0;
|
||||
p->use_tls = 0;
|
||||
}
|
||||
|
||||
static int ws_parse_url(const char* url, ws_url_parts_t* out) {
|
||||
if (!url || !out) return -1;
|
||||
|
||||
memset(out, 0, sizeof(*out));
|
||||
|
||||
const char* p = NULL;
|
||||
if (strncmp(url, "ws://", 5) == 0) {
|
||||
out->use_tls = 0;
|
||||
out->port = 80;
|
||||
p = url + 5;
|
||||
} else if (strncmp(url, "wss://", 6) == 0) {
|
||||
out->use_tls = 1;
|
||||
out->port = 443;
|
||||
p = url + 6;
|
||||
} else {
|
||||
return -1;
|
||||
}
|
||||
|
||||
const char* slash = strchr(p, '/');
|
||||
const char* host_end = slash ? slash : (p + strlen(p));
|
||||
|
||||
const char* colon = NULL;
|
||||
for (const char* it = p; it < host_end; ++it) {
|
||||
if (*it == ':') {
|
||||
colon = it;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
size_t host_len;
|
||||
if (colon) {
|
||||
host_len = (size_t)(colon - p);
|
||||
char portbuf[8] = {0};
|
||||
size_t port_len = (size_t)(host_end - colon - 1);
|
||||
if (port_len == 0 || port_len >= sizeof(portbuf)) return -1;
|
||||
memcpy(portbuf, colon + 1, port_len);
|
||||
for (size_t i = 0; i < port_len; i++) {
|
||||
if (!isdigit((unsigned char)portbuf[i])) return -1;
|
||||
}
|
||||
out->port = atoi(portbuf);
|
||||
if (out->port <= 0 || out->port > 65535) return -1;
|
||||
} else {
|
||||
host_len = (size_t)(host_end - p);
|
||||
}
|
||||
|
||||
if (host_len == 0) return -1;
|
||||
|
||||
out->host = (char*)malloc(host_len + 1U);
|
||||
if (!out->host) return -1;
|
||||
memcpy(out->host, p, host_len);
|
||||
out->host[host_len] = '\0';
|
||||
|
||||
out->path = slash ? strdup(slash) : strdup("/");
|
||||
if (!out->path) {
|
||||
ws_free_url_parts(out);
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void ws_client_destroy(struct nostr_ws_client* c) {
|
||||
if (!c) return;
|
||||
|
||||
if (c->transport) {
|
||||
esp_transport_close(c->transport);
|
||||
c->transport = NULL;
|
||||
}
|
||||
|
||||
if (c->list) {
|
||||
esp_transport_list_destroy(c->list);
|
||||
c->list = NULL;
|
||||
}
|
||||
|
||||
free(c->host);
|
||||
free(c->path);
|
||||
free(c);
|
||||
}
|
||||
|
||||
static esp_transport_handle_t ws_build_transport(const ws_url_parts_t* up,
|
||||
esp_transport_list_handle_t list) {
|
||||
if (!up || !list) return NULL;
|
||||
|
||||
if (up->use_tls) {
|
||||
esp_transport_handle_t ssl = esp_transport_ssl_init();
|
||||
if (!ssl) return NULL;
|
||||
|
||||
esp_transport_ssl_crt_bundle_attach(ssl, esp_crt_bundle_attach);
|
||||
esp_transport_set_default_port(ssl, 443);
|
||||
if (esp_transport_list_add(list, ssl, "ssl") != ESP_OK) {
|
||||
esp_transport_destroy(ssl);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
esp_transport_handle_t wss = esp_transport_ws_init(ssl);
|
||||
if (!wss) return NULL;
|
||||
|
||||
esp_transport_set_default_port(wss, up->port);
|
||||
esp_transport_ws_set_path(wss, up->path);
|
||||
if (esp_transport_ws_set_user_agent(wss, "nostr_core_lib/esp32") != ESP_OK) {
|
||||
esp_transport_destroy(wss);
|
||||
return NULL;
|
||||
}
|
||||
if (esp_transport_list_add(list, wss, "wss") != ESP_OK) {
|
||||
esp_transport_destroy(wss);
|
||||
return NULL;
|
||||
}
|
||||
return wss;
|
||||
}
|
||||
|
||||
esp_transport_handle_t tcp = esp_transport_tcp_init();
|
||||
if (!tcp) return NULL;
|
||||
|
||||
esp_transport_set_default_port(tcp, 80);
|
||||
if (esp_transport_list_add(list, tcp, "tcp") != ESP_OK) {
|
||||
esp_transport_destroy(tcp);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
esp_transport_handle_t ws = esp_transport_ws_init(tcp);
|
||||
if (!ws) return NULL;
|
||||
|
||||
esp_transport_set_default_port(ws, up->port);
|
||||
esp_transport_ws_set_path(ws, up->path);
|
||||
if (esp_transport_ws_set_user_agent(ws, "nostr_core_lib/esp32") != ESP_OK) {
|
||||
esp_transport_destroy(ws);
|
||||
return NULL;
|
||||
}
|
||||
if (esp_transport_list_add(list, ws, "ws") != ESP_OK) {
|
||||
esp_transport_destroy(ws);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return ws;
|
||||
}
|
||||
|
||||
nostr_ws_client_t* nostr_ws_connect(const char* url) {
|
||||
if (!url) return NULL;
|
||||
|
||||
ws_url_parts_t up;
|
||||
if (ws_parse_url(url, &up) != 0) return NULL;
|
||||
|
||||
struct nostr_ws_client* c = (struct nostr_ws_client*)calloc(1, sizeof(struct nostr_ws_client));
|
||||
if (!c) {
|
||||
ws_free_url_parts(&up);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
c->list = esp_transport_list_init();
|
||||
if (!c->list) {
|
||||
ws_free_url_parts(&up);
|
||||
free(c);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
c->transport = ws_build_transport(&up, c->list);
|
||||
if (!c->transport) {
|
||||
ws_free_url_parts(&up);
|
||||
ws_client_destroy(c);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
c->timeout_ms = 30000;
|
||||
c->state = NOSTR_WS_CONNECTING;
|
||||
c->host = up.host;
|
||||
c->port = up.port;
|
||||
c->path = up.path;
|
||||
|
||||
up.host = NULL;
|
||||
up.path = NULL;
|
||||
|
||||
int cr = esp_transport_connect(c->transport, c->host, c->port, c->timeout_ms);
|
||||
if (cr < 0) {
|
||||
c->state = NOSTR_WS_ERROR;
|
||||
ws_client_destroy(c);
|
||||
ws_free_url_parts(&up);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
int hs = esp_transport_ws_get_upgrade_request_status(c->transport);
|
||||
if (hs != 101) {
|
||||
c->state = NOSTR_WS_ERROR;
|
||||
ws_client_destroy(c);
|
||||
ws_free_url_parts(&up);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
c->state = NOSTR_WS_CONNECTED;
|
||||
ws_free_url_parts(&up);
|
||||
return c;
|
||||
}
|
||||
|
||||
int nostr_ws_close(nostr_ws_client_t* client) {
|
||||
if (!client) return NOSTR_WS_ERROR_INVALID;
|
||||
|
||||
if (client->state == NOSTR_WS_CONNECTED) {
|
||||
(void)esp_transport_ws_send_raw(client->transport,
|
||||
(ws_transport_opcodes_t)(WS_TRANSPORT_OPCODES_CLOSE | WS_TRANSPORT_OPCODES_FIN),
|
||||
NULL,
|
||||
0,
|
||||
client->timeout_ms);
|
||||
}
|
||||
|
||||
client->state = NOSTR_WS_CLOSED;
|
||||
ws_client_destroy(client);
|
||||
return NOSTR_WS_SUCCESS;
|
||||
}
|
||||
|
||||
nostr_ws_state_t nostr_ws_get_state(nostr_ws_client_t* client) {
|
||||
if (!client) return NOSTR_WS_ERROR;
|
||||
return client->state;
|
||||
}
|
||||
|
||||
int nostr_ws_send_text(nostr_ws_client_t* client, const char* message) {
|
||||
if (!client || !message || client->state != NOSTR_WS_CONNECTED) {
|
||||
return NOSTR_WS_ERROR_INVALID;
|
||||
}
|
||||
|
||||
int len = (int)strlen(message);
|
||||
int wr = esp_transport_ws_send_raw(client->transport,
|
||||
(ws_transport_opcodes_t)(WS_TRANSPORT_OPCODES_TEXT | WS_TRANSPORT_OPCODES_FIN),
|
||||
message,
|
||||
len,
|
||||
client->timeout_ms);
|
||||
return (wr == len) ? NOSTR_WS_SUCCESS : NOSTR_WS_ERROR_NETWORK;
|
||||
}
|
||||
|
||||
int nostr_ws_receive(nostr_ws_client_t* client, char* buffer, size_t buffer_size, int timeout_ms) {
|
||||
if (!client || !buffer || buffer_size == 0) return NOSTR_WS_ERROR_INVALID;
|
||||
if (client->state != NOSTR_WS_CONNECTED && client->state != NOSTR_WS_CLOSING) {
|
||||
return NOSTR_WS_ERROR_INVALID;
|
||||
}
|
||||
|
||||
int tmo = (timeout_ms > 0) ? timeout_ms : client->timeout_ms;
|
||||
int n = esp_transport_read(client->transport, buffer, (int)buffer_size - 1, tmo);
|
||||
if (n < 0) {
|
||||
return NOSTR_WS_ERROR_NETWORK;
|
||||
}
|
||||
if (n == 0) {
|
||||
/* timeout or internally-handled control frame; not a hard network failure */
|
||||
buffer[0] = '\0';
|
||||
return 0;
|
||||
}
|
||||
|
||||
ws_transport_opcodes_t opcode = esp_transport_ws_get_read_opcode(client->transport);
|
||||
|
||||
if (opcode == WS_TRANSPORT_OPCODES_TEXT || opcode == WS_TRANSPORT_OPCODES_BINARY) {
|
||||
buffer[n] = '\0';
|
||||
return n;
|
||||
}
|
||||
|
||||
if (opcode == WS_TRANSPORT_OPCODES_PING) {
|
||||
(void)esp_transport_ws_send_raw(client->transport,
|
||||
(ws_transport_opcodes_t)(WS_TRANSPORT_OPCODES_PONG | WS_TRANSPORT_OPCODES_FIN),
|
||||
buffer,
|
||||
n,
|
||||
tmo);
|
||||
return nostr_ws_receive(client, buffer, buffer_size, timeout_ms);
|
||||
}
|
||||
|
||||
if (opcode == WS_TRANSPORT_OPCODES_PONG) {
|
||||
buffer[n] = '\0';
|
||||
return n;
|
||||
}
|
||||
|
||||
if (opcode == WS_TRANSPORT_OPCODES_CLOSE) {
|
||||
client->state = NOSTR_WS_CLOSING;
|
||||
buffer[0] = '\0';
|
||||
return 0;
|
||||
}
|
||||
|
||||
return NOSTR_WS_ERROR_PROTOCOL;
|
||||
}
|
||||
|
||||
int nostr_ws_ping(nostr_ws_client_t* client) {
|
||||
if (!client || client->state != NOSTR_WS_CONNECTED) {
|
||||
return NOSTR_WS_ERROR_INVALID;
|
||||
}
|
||||
|
||||
int wr = esp_transport_ws_send_raw(client->transport,
|
||||
(ws_transport_opcodes_t)(WS_TRANSPORT_OPCODES_PING | WS_TRANSPORT_OPCODES_FIN),
|
||||
"ping",
|
||||
4,
|
||||
client->timeout_ms);
|
||||
return (wr == 4) ? NOSTR_WS_SUCCESS : NOSTR_WS_ERROR_NETWORK;
|
||||
}
|
||||
|
||||
int nostr_ws_set_timeout(nostr_ws_client_t* client, int timeout_ms) {
|
||||
if (!client || timeout_ms < 0) return NOSTR_WS_ERROR_INVALID;
|
||||
client->timeout_ms = timeout_ms;
|
||||
return NOSTR_WS_SUCCESS;
|
||||
}
|
||||
|
||||
int nostr_relay_send_req(nostr_ws_client_t* client, const char* subscription_id, cJSON* filters) {
|
||||
if (!client || !subscription_id || !filters) {
|
||||
return NOSTR_WS_ERROR_INVALID;
|
||||
}
|
||||
|
||||
cJSON* req_array = cJSON_CreateArray();
|
||||
cJSON_AddItemToArray(req_array, cJSON_CreateString("REQ"));
|
||||
cJSON_AddItemToArray(req_array, cJSON_CreateString(subscription_id));
|
||||
|
||||
if (cJSON_IsArray(filters)) {
|
||||
cJSON* filter;
|
||||
cJSON_ArrayForEach(filter, filters) {
|
||||
cJSON_AddItemToArray(req_array, cJSON_Duplicate(filter, 1));
|
||||
}
|
||||
} else {
|
||||
cJSON_AddItemToArray(req_array, cJSON_Duplicate(filters, 1));
|
||||
}
|
||||
|
||||
char* req_string = cJSON_PrintUnformatted(req_array);
|
||||
if (!req_string) {
|
||||
cJSON_Delete(req_array);
|
||||
return NOSTR_WS_ERROR_MEMORY;
|
||||
}
|
||||
|
||||
int result = nostr_ws_send_text(client, req_string);
|
||||
|
||||
free(req_string);
|
||||
cJSON_Delete(req_array);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
int nostr_relay_send_event(nostr_ws_client_t* client, cJSON* event) {
|
||||
if (!client || !event) {
|
||||
return NOSTR_WS_ERROR_INVALID;
|
||||
}
|
||||
|
||||
cJSON* event_array = cJSON_CreateArray();
|
||||
cJSON_AddItemToArray(event_array, cJSON_CreateString("EVENT"));
|
||||
cJSON_AddItemToArray(event_array, cJSON_Duplicate(event, 1));
|
||||
|
||||
char* event_string = cJSON_PrintUnformatted(event_array);
|
||||
if (!event_string) {
|
||||
cJSON_Delete(event_array);
|
||||
return NOSTR_WS_ERROR_MEMORY;
|
||||
}
|
||||
|
||||
int result = nostr_ws_send_text(client, event_string);
|
||||
|
||||
free(event_string);
|
||||
cJSON_Delete(event_array);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
int nostr_relay_send_close(nostr_ws_client_t* client, const char* subscription_id) {
|
||||
if (!client || !subscription_id) {
|
||||
return NOSTR_WS_ERROR_INVALID;
|
||||
}
|
||||
|
||||
cJSON* close_array = cJSON_CreateArray();
|
||||
cJSON_AddItemToArray(close_array, cJSON_CreateString("CLOSE"));
|
||||
cJSON_AddItemToArray(close_array, cJSON_CreateString(subscription_id));
|
||||
|
||||
char* close_string = cJSON_PrintUnformatted(close_array);
|
||||
if (!close_string) {
|
||||
cJSON_Delete(close_array);
|
||||
return NOSTR_WS_ERROR_MEMORY;
|
||||
}
|
||||
|
||||
int result = nostr_ws_send_text(client, close_string);
|
||||
|
||||
free(close_string);
|
||||
cJSON_Delete(close_array);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
int nostr_parse_relay_message(const char* message, char** message_type, cJSON** parsed_json) {
|
||||
if (!message || !message_type || !parsed_json) {
|
||||
return NOSTR_WS_ERROR_INVALID;
|
||||
}
|
||||
|
||||
*message_type = NULL;
|
||||
*parsed_json = NULL;
|
||||
|
||||
cJSON* json = cJSON_Parse(message);
|
||||
if (!json || !cJSON_IsArray(json)) {
|
||||
cJSON_Delete(json);
|
||||
return NOSTR_WS_ERROR_PROTOCOL;
|
||||
}
|
||||
|
||||
cJSON* type_item = cJSON_GetArrayItem(json, 0);
|
||||
if (!type_item || !cJSON_IsString(type_item)) {
|
||||
cJSON_Delete(json);
|
||||
return NOSTR_WS_ERROR_PROTOCOL;
|
||||
}
|
||||
|
||||
*message_type = strdup(type_item->valuestring);
|
||||
if (!*message_type) {
|
||||
cJSON_Delete(json);
|
||||
return NOSTR_WS_ERROR_MEMORY;
|
||||
}
|
||||
|
||||
*parsed_json = json;
|
||||
return NOSTR_WS_SUCCESS;
|
||||
}
|
||||
|
||||
const char* nostr_ws_strerror(int error_code) {
|
||||
switch (error_code) {
|
||||
case NOSTR_WS_SUCCESS: return "Success";
|
||||
case NOSTR_WS_ERROR_INVALID: return "Invalid parameter";
|
||||
case NOSTR_WS_ERROR_NETWORK: return "Network error";
|
||||
case NOSTR_WS_ERROR_PROTOCOL: return "Protocol error";
|
||||
case NOSTR_WS_ERROR_MEMORY: return "Memory allocation error";
|
||||
case NOSTR_WS_ERROR_TLS: return "TLS error";
|
||||
default: return "Unknown error";
|
||||
}
|
||||
}
|
||||
28
platform/linux.c
Normal file
28
platform/linux.c
Normal file
@@ -0,0 +1,28 @@
|
||||
#include "../nostr_core/nostr_platform.h"
|
||||
|
||||
#include <fcntl.h>
|
||||
#include <unistd.h>
|
||||
|
||||
int nostr_platform_random(unsigned char *buf, size_t len) {
|
||||
if (buf == NULL || len == 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
int fd = open("/dev/urandom", O_RDONLY);
|
||||
if (fd < 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
size_t total = 0;
|
||||
while (total < len) {
|
||||
ssize_t n = read(fd, buf + total, len - total);
|
||||
if (n <= 0) {
|
||||
close(fd);
|
||||
return -1;
|
||||
}
|
||||
total += (size_t)n;
|
||||
}
|
||||
|
||||
close(fd);
|
||||
return 0;
|
||||
}
|
||||
Reference in New Issue
Block a user