diff --git a/Makefile b/Makefile index 9386a36d..ff0f995b 100644 --- a/Makefile +++ b/Makefile @@ -329,6 +329,7 @@ CYCLONE_SOURCES = \ # remove cyclone sources for which modifications exist CYCLONE_SOURCES := $(filter-out \ cyclone/common/debug.c \ + cyclone/common/error.c \ cyclone/cyclone_tcp/http/http_server.c \ cyclone/cyclone_tcp/http/http_server_misc.c \ cyclone/cyclone_ssl/tls_certificate.c \ @@ -338,6 +339,7 @@ CYCLONE_SOURCES := $(filter-out \ # and add modified ones CYCLONE_SOURCES += \ src/cyclone/common/debug.c \ + src/cyclone/common/error.c \ src/cyclone/cyclone_crypto/mpi.c \ src/cyclone/cyclone_tcp/http/http_server.c \ src/cyclone/cyclone_tcp/http/http_server_misc.c \ diff --git a/include/fs_ext.h b/include/fs_ext.h index 70819507..508b027d 100644 --- a/include/fs_ext.h +++ b/include/fs_ext.h @@ -17,4 +17,6 @@ void fsFixPath(char_t *path); FsFile *fsOpenFileEx(const char_t *path, char *mode); +error_t fsCompareFiles(const char_t *source_path, const char_t *target_path, size_t *diff_position); error_t fsCopyFile(const char_t *source_path, const char_t *target_path, bool_t overwrite); +error_t fsMoveFile(const char_t *source_path, const char_t *target_path, bool_t overwrite); \ No newline at end of file diff --git a/include/settings.h b/include/settings.h index e7dfd0cd..02b3e1b9 100644 --- a/include/settings.h +++ b/include/settings.h @@ -24,7 +24,7 @@ #define TONIESV2_CUSTOM_JSON_FILE "tonies.custom.json" #define CONFIG_FILE "config.ini" #define CONFIG_OVERLAY_FILE "config.overlay.ini" -#define CONFIG_VERSION 6 +#define CONFIG_VERSION 8 #define MAX_OVERLAYS 16 + 1 typedef enum @@ -71,9 +71,16 @@ typedef struct bool prioCustomContent; bool updateOnLowerAudioId; bool dumpRuidAuthContentJson; - uint32_t ffmpeg_stream_buffer_ms; } settings_cloud_t; +typedef struct +{ + uint32_t bitrate; + uint32_t ffmpeg_stream_buffer_ms; + bool ffmpeg_stream_restart; + +} settings_encode_t; + typedef struct { bool enabled; @@ -248,6 +255,7 @@ typedef struct char *boxName; settings_core_t core; settings_cloud_t cloud; + settings_encode_t encode; settings_mqtt_t mqtt; settings_hass_t hass; settings_toniebox_t toniebox; diff --git a/include/toniebox_state.h b/include/toniebox_state.h index 889ec26e..c8cb2f9b 100644 --- a/include/toniebox_state.h +++ b/include/toniebox_state.h @@ -2,11 +2,13 @@ #include #include +#include "toniefile.h" typedef struct { const char *id; const char *name; + ffmpeg_stream_ctx_t ffmpeg_ctx; } toniebox_state_box_t; typedef struct diff --git a/include/toniefile.h b/include/toniefile.h index dc6a1c85..dafd8d5e 100644 --- a/include/toniefile.h +++ b/include/toniefile.h @@ -1,12 +1,12 @@ +#pragma once #include - #include "fs_ext.h" #define OPUS_FRAME_SIZE_MS OPUS_FRAMESIZE_60_MS #define OPUS_SAMPLING_RATE 48000 -#define OPUS_BIT_RATE 96000 -#define OPUS_FRAME_SIZE 2880 /* samples: 60ms at 48kHz */ +// #define OPUS_BIT_RATE 96000 +#define OPUS_FRAME_SIZE OPUS_SAMPLING_RATE * 60 / 1000 /* samples: 60ms at 48kHz */ #define OPUS_CHANNELS 2 #define OPUS_PACKET_PAD 64 #define OPUS_PACKET_MINSIZE 64 @@ -15,6 +15,18 @@ #define TONIEFILE_MAX_CHAPTERS 100 #define TONIEFILE_PAD_END 64 +#define OGG_HEADER_LENGTH 27 +/* + quint32 Signature; + quint8 Version; + quint8 Flags; + quint64 GranulePosition; + quint32 SerialNumber; + quint32 SequenceNumber; + quint32 Checksum; + quint8 TotalSegments; +*/ + typedef struct toniefile_s toniefile_t; typedef struct @@ -23,13 +35,13 @@ typedef struct char *source; size_t skip_seconds; char *targetFile; + bool_t append; OsTaskId taskId; error_t error; bool_t quit; - } ffmpeg_stream_ctx_t; -toniefile_t *toniefile_create(const char *fullPath, uint32_t audio_id); +toniefile_t *toniefile_create(const char *fullPath, uint32_t audio_id, bool append); error_t toniefile_close(toniefile_t *ctx); error_t toniefile_encode(toniefile_t *ctx, int16_t *sample_buffer, size_t samples_available); error_t toniefile_write_header(toniefile_t *ctx); @@ -39,6 +51,6 @@ FILE *ffmpeg_decode_audio_start(const char *input_source); FILE *ffmpeg_decode_audio_start_skip(const char *input_source, size_t skip_seconds); error_t ffmpeg_decode_audio_end(FILE *ffmpeg_pipe, error_t error); error_t ffmpeg_decode_audio(FILE *ffmpeg_pipe, int16_t *buffer, size_t size, size_t *bytes_read); -error_t ffmpeg_stream(char source[99][PATH_LEN], size_t source_len, const char *target_taf, size_t skip_seconds, bool_t *active); +error_t ffmpeg_stream(char source[99][PATH_LEN], size_t source_len, const char *target_taf, size_t skip_seconds, bool_t *active, bool_t append); error_t ffmpeg_convert(char source[99][PATH_LEN], size_t source_len, const char *target_taf, size_t skip_seconds); void ffmpeg_stream_task(void *param); \ No newline at end of file diff --git a/proto/toniebox.pb.taf-header.proto b/proto/toniebox.pb.taf-header.proto index 08eadb19..fa88a44e 100644 --- a/proto/toniebox.pb.taf-header.proto +++ b/proto/toniebox.pb.taf-header.proto @@ -6,4 +6,10 @@ message TonieboxAudioFileHeader { required uint32 audio_id = 3; repeated uint32 track_page_nums = 4 [packed=true]; required bytes _fill = 5; + //custom_fields_start + optional uint64 ogg_granule_position = 6; + optional uint64 ogg_packet_count = 7; + optional uint64 taf_block_num = 8; + optional uint64 pageno = 9; + //custom_fields_end } \ No newline at end of file diff --git a/src/cert.c b/src/cert.c index fd2ba9ea..56342900 100644 --- a/src/cert.c +++ b/src/cert.c @@ -196,7 +196,7 @@ error_t cert_generate_signed(const char *subject, const uint8_t *serial_number, error_t error = x509CreateCertificate(rand_get_algo(), rand_get_context(), &cert_req, &cert_pubkey, self_sign ? NULL : &issuer_cert, &serial, &validity, &algo, self_sign ? &cert_privkey : &issuer_priv, cert_der_data, &cert_der_size); if (error != NO_ERROR) { - TRACE_ERROR("x509CreateCertificate failed: %d\r\n", error); + TRACE_ERROR("x509CreateCertificate failed: %s\r\n", error2text(error)); return ERROR_FAILURE; } diff --git a/src/cloud_request.c b/src/cloud_request.c index ba6e521a..37c5db42 100644 --- a/src/cloud_request.c +++ b/src/cloud_request.c @@ -218,7 +218,7 @@ error_t web_request(const char *server, int port, bool https, const char *uri, c if (error) { // Debug message - TRACE_ERROR("Failed to connect to HTTP server! Error=%u\r\n", error); + TRACE_ERROR("Failed to connect to HTTP server! Error=%s\r\n", error2text(error)); if (isCloud) stats_update("cloud_failed", 1); break; @@ -235,7 +235,7 @@ error_t web_request(const char *server, int port, bool https, const char *uri, c if (error) { // Debug message - TRACE_ERROR("Failed to set content length! Error=%u\r\n", error); + TRACE_ERROR("Failed to set content length! Error=%s\r\n", error2text(error)); if (isCloud) stats_update("cloud_failed", 1); break; @@ -268,7 +268,7 @@ error_t web_request(const char *server, int port, bool https, const char *uri, c if (error) { // Debug message - TRACE_ERROR("Failed to write HTTP request header, error=%u!\r\n", error); + TRACE_ERROR("Failed to write HTTP request header, error=%s!\r\n", error2text(error)); if (isCloud) stats_update("cloud_failed", 1); break; @@ -282,7 +282,7 @@ error_t web_request(const char *server, int port, bool https, const char *uri, c if (error) { // Debug message - TRACE_ERROR("Failed to write HTTP request body, error=%u!\r\n", error); + TRACE_ERROR("Failed to write HTTP request body, error=%s!\r\n", error2text(error)); if (isCloud) stats_update("cloud_failed", 1); break; diff --git a/src/cyclone/common/error.c b/src/cyclone/common/error.c new file mode 100644 index 00000000..05ff5671 --- /dev/null +++ b/src/cyclone/common/error.c @@ -0,0 +1,258 @@ +#include "error.h" + +#define ERROR_COUNT 597 + 1 + +static char *error_text[ERROR_COUNT]; + +char *error2text(error_t error) +{ + return error_text[error]; +} + +void error_text_init() +{ + for (int i = 0; i < ERROR_COUNT; i++) + { + error_text[i] = ""; + } + + // (\d*)\t[A-Z_]*\t([A-Za-z0-9 \-]*) + // error_text[$1] = "$2 [$1]"; + error_text[0] = "Success [0]"; + error_text[1] = "Generic error code [1]"; + error_text[2] = "Invalid parameter [2]"; + error_text[3] = "Specified parameter is out of range [3]"; + error_text[4] = "Bad CRC detected [4]"; + error_text[5] = "Bad block detected [5]"; + error_text[6] = "Invalid recipient [6]"; + error_text[7] = "Invalid interface [7]"; + error_text[8] = "Invalid endpoint [8]"; + error_text[9] = "Alternate setting does not exist [9]"; + error_text[10] = "Unsupported request [10]"; + error_text[11] = "Unsupported configuration [11]"; + error_text[12] = "Unsupported feature [12]"; + error_text[13] = "Endpoint already in use [13]"; + error_text[14] = "USB reset [14]"; + error_text[15] = "Operation aborted [15]"; + error_text[100] = "Out of memory [100]"; + error_text[101] = "Out of resources [101]"; + error_text[102] = "Invalid request [102]"; + error_text[103] = "Not implemented [103]"; + error_text[104] = "Version not supported [104]"; + error_text[105] = "Invalid syntax [105]"; + error_text[106] = "Authentication failed [106]"; + error_text[107] = "Unexpected response [107]"; + error_text[108] = "Invalid response [108]"; + error_text[109] = "Unexpected value [109]"; + error_text[110] = "Wait canceled [110]"; + error_text[200] = "Open failed [200]"; + error_text[201] = "Connection failed [201]"; + error_text[202] = "Connection refused [202]"; + error_text[203] = "Connection closing [203]"; + error_text[204] = "Connection reset [204]"; + error_text[205] = "Not connected [205]"; + error_text[206] = "Already closed [206]"; + error_text[207] = "Already connected [207]"; + error_text[208] = "Invalid socket [208]"; + error_text[209] = "Protocol unreachable [209]"; + error_text[210] = "Port unreachable [210]"; + error_text[211] = "Invalid frame [211]"; + error_text[212] = "Invalid header [212]"; + error_text[213] = "Wrong checksum [213]"; + error_text[214] = "Wrong identifier [214]"; + error_text[215] = "Wrong client ID [215]"; + error_text[216] = "Wrong server ID [216]"; + error_text[217] = "Wrong cookie [217]"; + error_text[218] = "No response received [218]"; + error_text[219] = "Receive queue full [219]"; + error_text[220] = "Timeout [220]"; + error_text[221] = "Operation would block [221]"; + error_text[222] = "Invalid name [222]"; + error_text[223] = "Invalid option [223]"; + error_text[224] = "Unexpected state [224]"; + error_text[225] = "Invalid command [225]"; + error_text[226] = "Invalid protocol [226]"; + error_text[227] = "Invalid status [227]"; + error_text[228] = "Invalid address [228]"; + error_text[229] = "Invalid port [229]"; + error_text[230] = "Invalid message [230]"; + error_text[231] = "Invalid key [231]"; + error_text[232] = "Invalid key length [232]"; + error_text[233] = "Invalid epoch [233]"; + error_text[234] = "Invalid sequence number [234]"; + error_text[235] = "Invalid character [235]"; + error_text[236] = "Invalid length [236]"; + error_text[237] = "Invalid padding [237]"; + error_text[238] = "Invalid MAC [238]"; + error_text[239] = "Invalid tag [239]"; + error_text[240] = "Invalid type [240]"; + error_text[241] = "Invalid value [241]"; + error_text[242] = "Invalid class [242]"; + error_text[243] = "Invalid version [243]"; + error_text[244] = "Invalid PIN code [244]"; + error_text[245] = "Wrong length [245]"; + error_text[246] = "Wrong type [246]"; + error_text[247] = "Wrong encoding [247]"; + error_text[248] = "Wrong value [248]"; + error_text[249] = "Inconsistent value [249]"; + error_text[250] = "Unsupported type [250]"; + error_text[251] = "Unsupported algorithm [251]"; + error_text[252] = "Unsupported cipher suite [252]"; + error_text[253] = "Unsupported cipher mode [253]"; + error_text[254] = "Unsupported cipher algorithm [254]"; + error_text[255] = "Unsupported hash algorithm [255]"; + error_text[256] = "Unsupported key exchange algorithm [256]"; + error_text[257] = "Unsupported signature algorithm [257]"; + error_text[258] = "Unsupported elliptic curve [258]"; + error_text[259] = "Invalid signature algorithm [259]"; + error_text[260] = "Certificate required [260]"; + error_text[261] = "Message too long [261]"; + error_text[262] = "Out of range [262]"; + error_text[263] = "Message discarded [263]"; + error_text[264] = "Invalid packet [264]"; + error_text[265] = "Buffer is empty [265]"; + error_text[266] = "Buffer overflow [266]"; + error_text[267] = "Buffer underflow [267]"; + error_text[268] = "Invalid resource [268]"; + error_text[269] = "Invalid path [269]"; + error_text[270] = "Not found [270]"; + error_text[271] = "Access denied [271]"; + error_text[272] = "Not writable [272]"; + error_text[273] = "Authentication required [273]"; + error_text[274] = "Transmitter is busy [274]"; + error_text[275] = "No running operation [275]"; + error_text[300] = "Invalid file [300]"; + error_text[301] = "File not found [301]"; + error_text[302] = "File opening failed [302]"; + error_text[303] = "File reading failed [303]"; + error_text[304] = "End of file reached [304]"; + error_text[305] = "Unexpected end of file [305]"; + error_text[306] = "Unknown file format [306]"; + error_text[307] = "Invalid directory [307]"; + error_text[308] = "Directory not found [308]"; + error_text[400] = "File system not supported [400]"; + error_text[401] = "Unknown file system [401]"; + error_text[402] = "Invalid file system [402]"; + error_text[403] = "Invalid boot sector signature [403]"; + error_text[404] = "Invalid sector size [404]"; + error_text[405] = "Invalid cluster size [405]"; + error_text[406] = "Invalid file record size [406]"; + error_text[407] = "Invalid index buffer size [407]"; + error_text[408] = "Invalid volume descriptor signature [408]"; + error_text[409] = "Invalid volume descriptor [409]"; + error_text[410] = "Invalid file record [410]"; + error_text[411] = "Invalid index buffer [411]"; + error_text[412] = "Invalid data runs [412]"; + error_text[413] = "Wrong tag identifier [413]"; + error_text[414] = "Wrong tag checksum [414]"; + error_text[415] = "Wrong magic number [415]"; + error_text[416] = "Wrong sequence number [416]"; + error_text[417] = "Descriptor not found [417]"; + error_text[418] = "Attribute not found [418]"; + error_text[419] = "Resident attribute [419]"; + error_text[420] = "Not resident attribute [420]"; + error_text[421] = "Invalid super block [421]"; + error_text[422] = "Invalid super block signature [422]"; + error_text[423] = "Invalid block size [423]"; + error_text[424] = "Unsupported revision level [424]"; + error_text[425] = "Invalid inode size [425]"; + error_text[426] = "Inode not found [426]"; + error_text[500] = "Unexpected message [500]"; + error_text[501] = "URL is too long [501]"; + error_text[502] = "Query string is too long [502]"; + error_text[503] = "No address [503]"; + error_text[504] = "No binding [504]"; + error_text[505] = "Not on link [505]"; + error_text[506] = "Use multicast [506]"; + error_text[507] = "NAK received [507]"; + error_text[508] = "Exception received [508]"; + error_text[509] = "No carrier [509]"; + error_text[510] = "Invalid level [510]"; + error_text[511] = "Wrong state [511]"; + error_text[512] = "End of stream [512]"; + error_text[513] = "Link down [513]"; + error_text[514] = "Invalid option length [514]"; + error_text[515] = "Operation in progress [515]"; + error_text[516] = "No acknowledgment received [516]"; + error_text[517] = "Invalid metadata [517]"; + error_text[518] = "Not configured [518]"; + error_text[519] = "Name resolution failed [519]"; + error_text[520] = "No route to destination [520]"; + error_text[521] = "Write failed [521]"; + error_text[522] = "Read failed [522]"; + error_text[523] = "Upload failed [523]"; + error_text[524] = "Read-only access [524]"; + error_text[525] = "Invalid signature [525]"; + error_text[526] = "Invalid ticket [526]"; + error_text[527] = "No ticket [527]"; + error_text[528] = "Bad record MAC [528]"; + error_text[529] = "Record overflow [529]"; + error_text[530] = "Handshake failed [530]"; + error_text[531] = "No certificate [531]"; + error_text[532] = "Bad certificate [532]"; + error_text[533] = "Unsupported certificate [533]"; + error_text[534] = "Unknown certificate [534]"; + error_text[535] = "Certificate expired [535]"; + error_text[536] = "Certificate revoked [536]"; + error_text[537] = "Unknown certificate authority [537]"; + error_text[538] = "Decoding failed [538]"; + error_text[539] = "Decryption failed [539]"; + error_text[540] = "Illegal parameter [540]"; + error_text[541] = "Missing extension [541]"; + error_text[542] = "Unsupported extension [542]"; + error_text[543] = "Inappropriate fallback [543]"; + error_text[544] = "No application protocol [544]"; + error_text[545] = "More data required [545]"; + error_text[546] = "TLS not supported [546]"; + error_text[547] = "PRNG not ready [547]"; + error_text[548] = "Service closing [548]"; + error_text[549] = "Invalid timestamp [549]"; + error_text[550] = "No DNS server [550]"; + error_text[551] = "Object not found [551]"; + error_text[552] = "Instance not found [552]"; + error_text[553] = "Address not found [553]"; + error_text[554] = "Unknown identity [554]"; + error_text[555] = "Unknown engine ID [555]"; + error_text[556] = "Unknown user name [556]"; + error_text[557] = "Unknown identity [557]"; + error_text[558] = "Unknown engine ID [558]"; + error_text[559] = "Unknown user name [559]"; + error_text[560] = "Unknown context [560]"; + error_text[561] = "Unavailable context [561]"; + error_text[562] = "Unsupported security level [562]"; + error_text[563] = "Not in time window [563]"; + error_text[564] = "Authorization failed [564]"; + error_text[565] = "Invalid function code [565]"; + error_text[566] = "Device busy [566]"; + error_text[567] = "Request rejected [567]"; + error_text[568] = "Invalid channel [568]"; + error_text[569] = "Invalid group [569]"; + error_text[570] = "Unknown service [570]"; + error_text[571] = "Unknown request [571]"; + error_text[572] = "Flow control [572]"; + error_text[573] = "Invalid password [573]"; + error_text[574] = "Invalid handle [574]"; + error_text[575] = "Bad nonce [575]"; + error_text[576] = "Unexpected status [576]"; + error_text[577] = "Response too large [577]"; + error_text[578] = "Invalid session [578]"; + error_text[579] = "Ticket expired [579]"; + error_text[580] = "Invalid entry [580]"; + error_text[581] = "Table full [581]"; + error_text[582] = "End of table [582]"; + error_text[583] = "Already running [583]"; + error_text[584] = "Unknown key [584]"; + error_text[585] = "Unknown type [585]"; + error_text[586] = "Unsupported option [586]"; + error_text[587] = "Invalid SPI [587]"; + error_text[588] = "Retry [588]"; + error_text[589] = "Policy failure [589]"; + error_text[590] = "Invalid proposal [590]"; + error_text[591] = "Invalid selector [591]"; + error_text[592] = "Wrong nonce [592]"; + error_text[593] = "Wrong issuer [593]"; + error_text[594] = "Response expired [594]"; + error_text[595] = "CRL expired [595]"; + error_text[596] = "No match [596]"; + error_text[597] = "Partial match [597]"; +} \ No newline at end of file diff --git a/src/cyclone/common/error.h b/src/cyclone/common/error.h new file mode 100644 index 00000000..e98a6d13 --- /dev/null +++ b/src/cyclone/common/error.h @@ -0,0 +1,319 @@ +/** + * @file error.h + * @brief Error codes description + * + * @section License + * + * SPDX-License-Identifier: GPL-2.0-or-later + * + * Copyright (C) 2010-2023 Oryx Embedded SARL. All rights reserved. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * @author Oryx Embedded SARL (www.oryx-embedded.com) + * @version 2.3.2 + **/ + +#ifndef _ERROR_H +#define _ERROR_H + +//C++ guard +#ifdef __cplusplus +extern "C" { +#endif + + +/** + * @brief Error codes + **/ + +typedef enum +{ + NO_ERROR = 0, /// #include "core/net.h" #include "http/http_server.h" @@ -52,10 +52,9 @@ #include "str.h" #include "debug.h" -//Check TCP/IP stack configuration +// Check TCP/IP stack configuration #if (HTTP_SERVER_SUPPORT == ENABLED) - /** * @brief Initialize settings with default values * @param[out] settings Structure that contains HTTP server settings @@ -63,46 +62,45 @@ void httpServerGetDefaultSettings(HttpServerSettings *settings) { - //The HTTP server is not bound to any interface + // The HTTP server is not bound to any interface settings->interface = NULL; - //Listen to port 80 + // Listen to port 80 settings->port = HTTP_PORT; - //HTTP server IP address + // HTTP server IP address settings->ipAddr = IP_ADDR_ANY; - //Maximum length of the pending connection queue + // Maximum length of the pending connection queue settings->backlog = HTTP_SERVER_BACKLOG; - //Client connections + // Client connections settings->maxConnections = 0; settings->connections = NULL; - //Specify the server's root directory + // Specify the server's root directory osStrcpy(settings->rootDirectory, "/"); - //Set default home page + // Set default home page osStrcpy(settings->defaultDocument, "index.htm"); #if (HTTP_SERVER_TLS_SUPPORT == ENABLED) - //TLS initialization callback function + // TLS initialization callback function settings->tlsInitCallback = NULL; #endif #if (HTTP_SERVER_BASIC_AUTH_SUPPORT == ENABLED || HTTP_SERVER_DIGEST_AUTH_SUPPORT == ENABLED) - //Random data generation callback function + // Random data generation callback function settings->randCallback = NULL; - //HTTP authentication callback function + // HTTP authentication callback function settings->authCallback = NULL; #endif - //CGI callback function + // CGI callback function settings->cgiCallback = NULL; - //HTTP request callback function + // HTTP request callback function settings->requestCallback = NULL; - //URI not found callback function + // URI not found callback function settings->uriNotFoundCallback = NULL; } - /** * @brief HTTP server initialization * @param[in] context Pointer to the HTTP server context @@ -116,92 +114,91 @@ error_t httpServerInit(HttpServerContext *context, const HttpServerSettings *set uint_t i; HttpConnection *connection; - //Debug message + // Debug message TRACE_INFO("Initializing HTTP server...\r\n"); - //Ensure the parameters are valid - if(context == NULL || settings == NULL) + // Ensure the parameters are valid + if (context == NULL || settings == NULL) return ERROR_INVALID_PARAMETER; - //Check settings - if(settings->maxConnections == 0 || settings->connections == NULL) + // Check settings + if (settings->maxConnections == 0 || settings->connections == NULL) return ERROR_INVALID_PARAMETER; - //Clear the HTTP server context + // Clear the HTTP server context osMemset(context, 0, sizeof(HttpServerContext)); - //Save user settings + // Save user settings context->settings = *settings; - //Client connections + // Client connections context->connections = settings->connections; - //Create a semaphore to limit the number of simultaneous connections - if(!osCreateSemaphore(&context->semaphore, context->settings.maxConnections)) + // Create a semaphore to limit the number of simultaneous connections + if (!osCreateSemaphore(&context->semaphore, context->settings.maxConnections)) return ERROR_OUT_OF_RESOURCES; - //Loop through client connections - for(i = 0; i < context->settings.maxConnections; i++) + // Loop through client connections + for (i = 0; i < context->settings.maxConnections; i++) { - //Point to the structure representing the client connection + // Point to the structure representing the client connection connection = &context->connections[i]; - //Initialize the structure + // Initialize the structure osMemset(connection, 0, sizeof(HttpConnection)); - //Create an event object to manage connection lifetime - if(!osCreateEvent(&connection->startEvent)) + // Create an event object to manage connection lifetime + if (!osCreateEvent(&connection->startEvent)) return ERROR_OUT_OF_RESOURCES; } #if (HTTP_SERVER_TLS_SUPPORT == ENABLED && TLS_TICKET_SUPPORT == ENABLED) - //Initialize ticket encryption context + // Initialize ticket encryption context error = tlsInitTicketContext(&context->tlsTicketContext); - //Any error to report? - if(error) + // Any error to report? + if (error) return error; #endif #if (HTTP_SERVER_DIGEST_AUTH_SUPPORT == ENABLED) - //Create a mutex to prevent simultaneous access to the nonce cache - if(!osCreateMutex(&context->nonceCacheMutex)) + // Create a mutex to prevent simultaneous access to the nonce cache + if (!osCreateMutex(&context->nonceCacheMutex)) return ERROR_OUT_OF_RESOURCES; #endif - //Open a TCP socket + // Open a TCP socket context->socket = socketOpen(SOCKET_TYPE_STREAM, SOCKET_IP_PROTO_TCP); - //Failed to open socket? - if(context->socket == NULL) + // Failed to open socket? + if (context->socket == NULL) return ERROR_OPEN_FAILED; - //Set timeout for blocking functions + // Set timeout for blocking functions error = socketSetTimeout(context->socket, INFINITE_DELAY); - //Any error to report? - if(error) + // Any error to report? + if (error) return error; - //Associate the socket with the relevant interface + // Associate the socket with the relevant interface error = socketBindToInterface(context->socket, settings->interface); - //Unable to bind the socket to the desired interface? - if(error) + // Unable to bind the socket to the desired interface? + if (error) return error; - //Bind newly created socket to port 80 + // Bind newly created socket to port 80 error = socketBind(context->socket, &settings->ipAddr, settings->port); - //Failed to bind socket to port 80? - if(error) + // Failed to bind socket to port 80? + if (error) return error; - //Place socket in listening state + // Place socket in listening state error = socketListen(context->socket, settings->backlog); - //Any failure to report? - if(error) + // Any failure to report? + if (error) return error; - //Successful initialization + // Successful initialization return NO_ERROR; } - /** * @brief Start HTTP server * @param[in] context Pointer to the HTTP server context @@ -213,55 +210,54 @@ error_t httpServerStart(HttpServerContext *context) uint_t i; HttpConnection *connection; - //Make sure the HTTP server context is valid - if(context == NULL) + // Make sure the HTTP server context is valid + if (context == NULL) return ERROR_INVALID_PARAMETER; - //Debug message + // Debug message TRACE_INFO("Starting HTTP server...\r\n"); - //Loop through client connections - for(i = 0; i < context->settings.maxConnections; i++) + // Loop through client connections + for (i = 0; i < context->settings.maxConnections; i++) { - //Point to the current session + // Point to the current session connection = &context->connections[i]; #if (OS_STATIC_TASK_SUPPORT == ENABLED) - //Create a task using statically allocated memory + // Create a task using statically allocated memory connection->taskId = osCreateStaticTask("HTTP Connection", - (OsTaskCode) httpConnectionTask, connection, &connection->taskTcb, - connection->taskStack, HTTP_SERVER_STACK_SIZE, HTTP_SERVER_PRIORITY); + (OsTaskCode)httpConnectionTask, connection, &connection->taskTcb, + connection->taskStack, HTTP_SERVER_STACK_SIZE, HTTP_SERVER_PRIORITY); #else - //Create a task + // Create a task connection->taskId = osCreateTask("HTTP Connection", httpConnectionTask, - &context->connections[i], HTTP_SERVER_STACK_SIZE, HTTP_SERVER_PRIORITY); + &context->connections[i], HTTP_SERVER_STACK_SIZE, HTTP_SERVER_PRIORITY); #endif - //Unable to create the task? - if(connection->taskId == OS_INVALID_TASK_ID) + // Unable to create the task? + if (connection->taskId == OS_INVALID_TASK_ID) return ERROR_OUT_OF_RESOURCES; } #if (OS_STATIC_TASK_SUPPORT == ENABLED) - //Create a task using statically allocated memory + // Create a task using statically allocated memory context->taskId = osCreateStaticTask("HTTP Listener", - (OsTaskCode) httpListenerTask, context, &context->taskTcb, - context->taskStack, HTTP_SERVER_STACK_SIZE, HTTP_SERVER_PRIORITY); + (OsTaskCode)httpListenerTask, context, &context->taskTcb, + context->taskStack, HTTP_SERVER_STACK_SIZE, HTTP_SERVER_PRIORITY); #else - //Create a task + // Create a task context->taskId = osCreateTask("HTTP Listener", httpListenerTask, - context, HTTP_SERVER_STACK_SIZE, HTTP_SERVER_PRIORITY); + context, HTTP_SERVER_STACK_SIZE, HTTP_SERVER_PRIORITY); #endif - //Unable to create the task? - if(context->taskId == OS_INVALID_TASK_ID) + // Unable to create the task? + if (context->taskId == OS_INVALID_TASK_ID) return ERROR_OUT_OF_RESOURCES; - //The HTTP server has successfully started + // The HTTP server has successfully started return NO_ERROR; } - /** * @brief HTTP server listener task * @param[in] param Pointer to the HTTP server context @@ -277,71 +273,70 @@ void httpListenerTask(void *param) HttpConnection *connection; Socket *socket; - //Task prologue + // Task prologue osEnterTask(); - //Retrieve the HTTP server context - context = (HttpServerContext *) param; + // Retrieve the HTTP server context + context = (HttpServerContext *)param; - //Process incoming connections to the server - for(counter = 1; ; counter++) + // Process incoming connections to the server + for (counter = 1;; counter++) { - //Debug message + // Debug message TRACE_INFO("Ready to accept a new connection...\r\n"); - //Limit the number of simultaneous connections to the HTTP server + // Limit the number of simultaneous connections to the HTTP server osWaitForSemaphore(&context->semaphore, INFINITE_DELAY); - //Loop through the connection table - for(i = 0; i < context->settings.maxConnections; i++) + // Loop through the connection table + for (i = 0; i < context->settings.maxConnections; i++) { - //Point to the current connection + // Point to the current connection connection = &context->connections[i]; - //Ready to service the client request? - if(!connection->running) + // Ready to service the client request? + if (!connection->running) { - //Accept an incoming connection + // Accept an incoming connection socket = socketAccept(context->socket, &clientIpAddr, &clientPort); - //Make sure the socket handle is valid - if(socket != NULL) + // Make sure the socket handle is valid + if (socket != NULL) { - //Debug message + // Debug message TRACE_INFO("Connection #%u established with client %s port %" PRIu16 "...\r\n", - counter, ipAddrToString(&clientIpAddr, NULL), clientPort); + counter, ipAddrToString(&clientIpAddr, NULL), clientPort); - //Reference to the HTTP server settings + // Reference to the HTTP server settings connection->settings = &context->settings; - //Reference to the HTTP server context + // Reference to the HTTP server context connection->serverContext = context; - //Reference to the new socket + // Reference to the new socket connection->socket = socket; - //Set timeout for blocking functions + // Set timeout for blocking functions socketSetTimeout(connection->socket, HTTP_SERVER_TIMEOUT); - //The client connection task is now running... + // The client connection task is now running... connection->running = TRUE; - //Service the current connection request + // Service the current connection request osSetEvent(&connection->startEvent); } else { - //Just for sanity + // Just for sanity osReleaseSemaphore(&context->semaphore); /* original code releases connection->serverContext, which is not set yet */ - //osReleaseSemaphore(&connection->serverContext->semaphore); + // osReleaseSemaphore(&connection->serverContext->semaphore); } - //We are done + // We are done break; } } } } - /** * @brief Task that services requests from an active connection * @param[in] param Structure representing an HTTP connection with a client @@ -353,111 +348,111 @@ void httpConnectionTask(void *param) uint_t counter; HttpConnection *connection; - //Task prologue + // Task prologue osEnterTask(); - //Point to the structure representing the HTTP connection - connection = (HttpConnection *) param; + // Point to the structure representing the HTTP connection + connection = (HttpConnection *)param; - //Endless loop - while(1) + // Endless loop + while (1) { - //Wait for an incoming connection attempt + // Wait for an incoming connection attempt osWaitForEvent(&connection->startEvent, INFINITE_DELAY); - //Initialize status code + // Initialize status code error = NO_ERROR; #if (HTTP_SERVER_TLS_SUPPORT == ENABLED) - //TLS-secured connection? - if(connection->settings->tlsInitCallback != NULL) + // TLS-secured connection? + if (connection->settings->tlsInitCallback != NULL) { - //Debug message + // Debug message TRACE_INFO("Initializing TLS session...\r\n"); - //Start of exception handling block + // Start of exception handling block do { - //Allocate TLS context + // Allocate TLS context connection->tlsContext = tlsInit(); - //Initialization failed? - if(connection->tlsContext == NULL) + // Initialization failed? + if (connection->tlsContext == NULL) { - //Report an error + // Report an error error = ERROR_OUT_OF_MEMORY; - //Exit immediately + // Exit immediately break; } - //Select server operation mode + // Select server operation mode error = tlsSetConnectionEnd(connection->tlsContext, - TLS_CONNECTION_END_SERVER); - //Any error to report? - if(error) + TLS_CONNECTION_END_SERVER); + // Any error to report? + if (error) break; - //Bind TLS to the relevant socket + // Bind TLS to the relevant socket error = tlsSetSocket(connection->tlsContext, connection->socket); - //Any error to report? - if(error) + // Any error to report? + if (error) break; #if (TLS_TICKET_SUPPORT == ENABLED) - //Enable session ticket mechanism + // Enable session ticket mechanism error = tlsEnableSessionTickets(connection->tlsContext, TRUE); - //Any error to report? - if(error) + // Any error to report? + if (error) break; - //Register ticket encryption/decryption callbacks + // Register ticket encryption/decryption callbacks error = tlsSetTicketCallbacks(connection->tlsContext, tlsEncryptTicket, - tlsDecryptTicket, &connection->serverContext->tlsTicketContext); - //Any error to report? - if(error) + tlsDecryptTicket, &connection->serverContext->tlsTicketContext); + // Any error to report? + if (error) break; #endif - //Invoke user-defined callback, if any - if(connection->settings->tlsInitCallback != NULL) + // Invoke user-defined callback, if any + if (connection->settings->tlsInitCallback != NULL) { - //Perform TLS related initialization + // Perform TLS related initialization error = connection->settings->tlsInitCallback(connection, - connection->tlsContext); - //Any error to report? - if(error) + connection->tlsContext); + // Any error to report? + if (error) break; } - //Establish a secure session + // Establish a secure session error = tlsConnect(connection->tlsContext); - //Any error to report? - if(error) + // Any error to report? + if (error) break; - //End of exception handling block - } while(0); + // End of exception handling block + } while (0); } else { - //Do not use TLS + // Do not use TLS connection->tlsContext = NULL; } #endif - //Check status code - if(!error) + // Check status code + if (!error) { - //Process incoming requests - for(counter = 0; counter < HTTP_SERVER_MAX_REQUESTS; counter++) + // Process incoming requests + for (counter = 0; counter < HTTP_SERVER_MAX_REQUESTS; counter++) { - //Debug message + // Debug message TRACE_INFO("Waiting for request...\r\n"); - //Clear request header + // Clear request header osMemset(&connection->request, 0, sizeof(HttpRequest)); - //Clear response header + // Clear response header osMemset(&connection->response, 0, sizeof(HttpResponse)); - //Read the HTTP request header and parse its contents + // Read the HTTP request header and parse its contents error = httpReadRequestHeader(connection); if (error == ERROR_INVALID_REQUEST && connection->response.contentLength > 4 && connection->buffer[0] == 0 && connection->buffer[1] == 0) { @@ -477,200 +472,199 @@ void httpConnectionTask(void *param) if (length == 0) osDelayTask(100); } - continue; + continue; } - //Any error to report? - if(error) + // Any error to report? + if (error) { - //Debug message - TRACE_WARNING("No HTTP request received or parsing error=%u...\r\n", error); + // Debug message + TRACE_WARNING("No HTTP request received or parsing error=%s...\r\n", error2text(error)); break; } #if (HTTP_SERVER_BASIC_AUTH_SUPPORT == ENABLED || HTTP_SERVER_DIGEST_AUTH_SUPPORT == ENABLED) - //No Authorization header found? - if(!connection->request.auth.found) + // No Authorization header found? + if (!connection->request.auth.found) { - //Invoke user-defined callback, if any - if(connection->settings->authCallback != NULL) + // Invoke user-defined callback, if any + if (connection->settings->authCallback != NULL) { - //Check whether the access to the specified URI is authorized + // Check whether the access to the specified URI is authorized connection->status = connection->settings->authCallback(connection, - connection->request.auth.user, connection->request.uri); + connection->request.auth.user, connection->request.uri); } else { - //Access to the specified URI is allowed + // Access to the specified URI is allowed connection->status = HTTP_ACCESS_ALLOWED; } } - //Check access status - if(connection->status == HTTP_ACCESS_ALLOWED) + // Check access status + if (connection->status == HTTP_ACCESS_ALLOWED) { - //Access to the specified URI is allowed + // Access to the specified URI is allowed error = NO_ERROR; } - else if(connection->status == HTTP_ACCESS_BASIC_AUTH_REQUIRED) + else if (connection->status == HTTP_ACCESS_BASIC_AUTH_REQUIRED) { - //Basic access authentication is required + // Basic access authentication is required connection->response.auth.mode = HTTP_AUTH_MODE_BASIC; - //Report an error + // Report an error error = ERROR_AUTH_REQUIRED; } - else if(connection->status == HTTP_ACCESS_DIGEST_AUTH_REQUIRED) + else if (connection->status == HTTP_ACCESS_DIGEST_AUTH_REQUIRED) { - //Digest access authentication is required + // Digest access authentication is required connection->response.auth.mode = HTTP_AUTH_MODE_DIGEST; - //Report an error + // Report an error error = ERROR_AUTH_REQUIRED; } else { - //Access to the specified URI is denied + // Access to the specified URI is denied error = ERROR_NOT_FOUND; } #endif - //Debug message + // Debug message TRACE_INFO("Sending HTTP response to the client...\r\n"); - //Check status code - if(!error) + // Check status code + if (!error) { - //Default HTTP header fields + // Default HTTP header fields httpInitResponseHeader(connection); - //Invoke user-defined callback, if any - if(connection->settings->requestCallback != NULL) + // Invoke user-defined callback, if any + if (connection->settings->requestCallback != NULL) { error = connection->settings->requestCallback(connection, - connection->request.uri); + connection->request.uri); } else { - //Keep processing... + // Keep processing... error = ERROR_NOT_FOUND; } - //Check status code - if(error == ERROR_NOT_FOUND) + // Check status code + if (error == ERROR_NOT_FOUND) { #if (HTTP_SERVER_SSI_SUPPORT == ENABLED) - //Use server-side scripting to dynamically generate HTML code? - if(httpCompExtension(connection->request.uri, ".stm") || - httpCompExtension(connection->request.uri, ".shtm") || - httpCompExtension(connection->request.uri, ".shtml")) + // Use server-side scripting to dynamically generate HTML code? + if (httpCompExtension(connection->request.uri, ".stm") || + httpCompExtension(connection->request.uri, ".shtm") || + httpCompExtension(connection->request.uri, ".shtml")) { - //SSI processing (Server Side Includes) + // SSI processing (Server Side Includes) error = ssiExecuteScript(connection, connection->request.uri, 0); } else #endif { - //Set the maximum age for static resources + // Set the maximum age for static resources connection->response.maxAge = HTTP_SERVER_MAX_AGE; - //Send the contents of the requested page + // Send the contents of the requested page error = httpSendResponse(connection, connection->request.uri); } } - //The requested resource is not available? - if(error == ERROR_NOT_FOUND) + // The requested resource is not available? + if (error == ERROR_NOT_FOUND) { - //Default HTTP header fields + // Default HTTP header fields httpInitResponseHeader(connection); - //Invoke user-defined callback, if any - if(connection->settings->uriNotFoundCallback != NULL) + // Invoke user-defined callback, if any + if (connection->settings->uriNotFoundCallback != NULL) { error = connection->settings->uriNotFoundCallback(connection, - connection->request.uri); + connection->request.uri); } } } - //Check status code - if(error) + // Check status code + if (error) { - //Default HTTP header fields + // Default HTTP header fields httpInitResponseHeader(connection); - //Bad request? - if(error == ERROR_INVALID_REQUEST) + // Bad request? + if (error == ERROR_INVALID_REQUEST) { - //Send an error 400 and close the connection immediately + // Send an error 400 and close the connection immediately httpSendErrorResponse(connection, 400, - "The request is badly formed"); + "The request is badly formed"); } - //Authorization required? - else if(error == ERROR_AUTH_REQUIRED) + // Authorization required? + else if (error == ERROR_AUTH_REQUIRED) { - //Send an error 401 and keep the connection alive + // Send an error 401 and keep the connection alive error = httpSendErrorResponse(connection, 401, - "Authorization required"); + "Authorization required"); } - //Page not found? - else if(error == ERROR_NOT_FOUND) + // Page not found? + else if (error == ERROR_NOT_FOUND) { - //Send an error 404 and keep the connection alive + // Send an error 404 and keep the connection alive error = httpSendErrorResponse(connection, 404, - "The requested page could not be found"); + "The requested page could not be found"); } } - //Internal error? - if(error) + // Internal error? + if (error) { - //Close the connection immediately + // Close the connection immediately break; } - //Check whether the connection is persistent or not - if(!connection->request.keepAlive || !connection->response.keepAlive) + // Check whether the connection is persistent or not + if (!connection->request.keepAlive || !connection->response.keepAlive) { - //Close the connection immediately + // Close the connection immediately break; } } } #if (HTTP_SERVER_TLS_SUPPORT == ENABLED) - //Valid TLS context? - if(connection->tlsContext != NULL) + // Valid TLS context? + if (connection->tlsContext != NULL) { - //Debug message + // Debug message TRACE_INFO("Closing TLS session...\r\n"); - //Gracefully close TLS session + // Gracefully close TLS session tlsShutdown(connection->tlsContext); - //Release context + // Release context tlsFree(connection->tlsContext); } #endif - //Valid socket handle? - if(connection->socket != NULL) + // Valid socket handle? + if (connection->socket != NULL) { - //Debug message + // Debug message TRACE_INFO("Graceful shutdown...\r\n"); - //Graceful shutdown + // Graceful shutdown socketShutdown(connection->socket, SOCKET_SD_BOTH); - //Debug message + // Debug message TRACE_INFO("Closing socket...\r\n"); - //Close socket + // Close socket socketClose(connection->socket); } - //Ready to serve the next connection request... + // Ready to serve the next connection request... connection->running = FALSE; - //Release semaphore + // Release semaphore osReleaseSemaphore(&connection->serverContext->semaphore); } } - /** * @brief Send HTTP response header * @param[in] connection Structure representing an HTTP connection @@ -682,30 +676,29 @@ error_t httpWriteHeader(HttpConnection *connection) error_t error; #if (NET_RTOS_SUPPORT == DISABLED) - //Flush buffer + // Flush buffer connection->bufferPos = 0; connection->bufferLen = 0; #endif - //Format HTTP response header + // Format HTTP response header error = httpFormatResponseHeader(connection, connection->buffer); - //Check status code - if(!error) + // Check status code + if (!error) { - //Debug message + // Debug message TRACE_DEBUG("HTTP response header:\r\n%s", connection->buffer); - //Send HTTP response header to the client + // Send HTTP response header to the client error = httpSend(connection, connection->buffer, - osStrlen(connection->buffer), HTTP_FLAG_DELAY); + osStrlen(connection->buffer), HTTP_FLAG_DELAY); } - //Return status code + // Return status code return error; } - /** * @brief Read data from client request * @param[in] connection Structure representing an HTTP connection @@ -717,103 +710,102 @@ error_t httpWriteHeader(HttpConnection *connection) **/ error_t httpReadStream(HttpConnection *connection, - void *data, size_t size, size_t *received, uint_t flags) + void *data, size_t size, size_t *received, uint_t flags) { error_t error; size_t n; - //No data has been read yet + // No data has been read yet *received = 0; - //Chunked encoding transfer is used? - if(connection->request.chunkedEncoding) + // Chunked encoding transfer is used? + if (connection->request.chunkedEncoding) { - //Point to the output buffer + // Point to the output buffer char_t *p = data; - //Read as much data as possible - while(*received < size) + // Read as much data as possible + while (*received < size) { - //End of HTTP request body? - if(connection->request.lastChunk) + // End of HTTP request body? + if (connection->request.lastChunk) return ERROR_END_OF_STREAM; - //Acquire a new chunk when the current chunk - //has been completely consumed - if(connection->request.byteCount == 0) + // Acquire a new chunk when the current chunk + // has been completely consumed + if (connection->request.byteCount == 0) { - //The size of each chunk is sent right before the chunk itself + // The size of each chunk is sent right before the chunk itself error = httpReadChunkSize(connection); - //Failed to decode the chunk-size field? - if(error) + // Failed to decode the chunk-size field? + if (error) return error; - //Any chunk whose size is zero terminates the data transfer - if(!connection->request.byteCount) + // Any chunk whose size is zero terminates the data transfer + if (!connection->request.byteCount) { - //The user must be satisfied with data already on hand + // The user must be satisfied with data already on hand return (*received > 0) ? NO_ERROR : ERROR_END_OF_STREAM; } } - //Limit the number of bytes to read at a time + // Limit the number of bytes to read at a time n = MIN(size - *received, connection->request.byteCount); - //Read data + // Read data error = httpReceive(connection, p, n, &n, flags); - //Any error to report? - if(error) + // Any error to report? + if (error) return error; - //Total number of data that have been read + // Total number of data that have been read *received += n; - //Number of bytes left to process in the current chunk + // Number of bytes left to process in the current chunk connection->request.byteCount -= n; - //The HTTP_FLAG_BREAK_CHAR flag causes the function to stop reading - //data as soon as the specified break character is encountered - if((flags & HTTP_FLAG_BREAK_CRLF) != 0) + // The HTTP_FLAG_BREAK_CHAR flag causes the function to stop reading + // data as soon as the specified break character is encountered + if ((flags & HTTP_FLAG_BREAK_CRLF) != 0) { - //Check whether a break character has been received - if(p[n - 1] == LSB(flags)) + // Check whether a break character has been received + if (p[n - 1] == LSB(flags)) break; } - //The HTTP_FLAG_WAIT_ALL flag causes the function to return - //only when the requested number of bytes have been read - else if(!(flags & HTTP_FLAG_WAIT_ALL)) + // The HTTP_FLAG_WAIT_ALL flag causes the function to return + // only when the requested number of bytes have been read + else if (!(flags & HTTP_FLAG_WAIT_ALL)) { break; } - //Advance data pointer + // Advance data pointer p += n; } } - //Default encoding? + // Default encoding? else { - //Return immediately if the end of the request body has been reached - if(!connection->request.byteCount) + // Return immediately if the end of the request body has been reached + if (!connection->request.byteCount) return ERROR_END_OF_STREAM; - //Limit the number of bytes to read + // Limit the number of bytes to read n = MIN(size, connection->request.byteCount); - //Read data + // Read data error = httpReceive(connection, data, n, received, flags); - //Any error to report? - if(error) + // Any error to report? + if (error) return error; - //Decrement the count of remaining bytes to read + // Decrement the count of remaining bytes to read connection->request.byteCount -= *received; } - //Successful read operation + // Successful read operation return NO_ERROR; } - /** * @brief Write data to the client * @param[in] connection Structure representing an HTTP connection @@ -823,64 +815,63 @@ error_t httpReadStream(HttpConnection *connection, **/ error_t httpWriteStream(HttpConnection *connection, - const void *data, size_t length) + const void *data, size_t length) { error_t error; uint_t n; - //Use chunked encoding transfer? - if(connection->response.chunkedEncoding) + // Use chunked encoding transfer? + if (connection->response.chunkedEncoding) { - //Any data to send? - if(length > 0) + // Any data to send? + if (length > 0) { char_t s[20]; - //The chunk-size field is a string of hex digits indicating the size - //of the chunk + // The chunk-size field is a string of hex digits indicating the size + // of the chunk n = osSprintf(s, "%" PRIXSIZE "\r\n", length); - //Send the chunk-size field + // Send the chunk-size field error = httpSend(connection, s, n, HTTP_FLAG_DELAY); - //Failed to send data? - if(error) + // Failed to send data? + if (error) return error; - //Send the chunk-data + // Send the chunk-data error = httpSend(connection, data, length, HTTP_FLAG_DELAY); - //Failed to send data? - if(error) + // Failed to send data? + if (error) return error; - //Terminate the chunk-data by CRLF + // Terminate the chunk-data by CRLF error = httpSend(connection, "\r\n", 2, HTTP_FLAG_DELAY); } else { - //Any chunk whose size is zero may terminate the data - //transfer and must be discarded + // Any chunk whose size is zero may terminate the data + // transfer and must be discarded error = NO_ERROR; } } - //Default encoding? + // Default encoding? else { - //The length of the body shall not exceed the value - //specified in the Content-Length field + // The length of the body shall not exceed the value + // specified in the Content-Length field length = MIN(length, connection->response.byteCount); - //Send user data + // Send user data error = httpSend(connection, data, length, HTTP_FLAG_DELAY); - //Decrement the count of remaining bytes to be transferred + // Decrement the count of remaining bytes to be transferred connection->response.byteCount -= length; } - //Return status code + // Return status code return error; } - /** * @brief Close output stream * @param[in] connection Structure representing an HTTP connection @@ -891,23 +882,22 @@ error_t httpCloseStream(HttpConnection *connection) { error_t error; - //Use chunked encoding transfer? - if(connection->response.chunkedEncoding) + // Use chunked encoding transfer? + if (connection->response.chunkedEncoding) { - //The chunked encoding is ended by any chunk whose size is zero + // The chunked encoding is ended by any chunk whose size is zero error = httpSend(connection, "0\r\n\r\n", 5, HTTP_FLAG_NO_DELAY); } else { - //Flush the send buffer + // Flush the send buffer error = httpSend(connection, "", 0, HTTP_FLAG_NO_DELAY); } - //Return status code + // Return status code return error; } - /** * @brief Send HTTP response * @param[in] connection Structure representing an HTTP connection @@ -915,84 +905,95 @@ error_t httpCloseStream(HttpConnection *connection) * @return Error code **/ -error_t httpSendResponse(HttpConnection *connection, const char_t *uri) { +error_t httpSendResponse(HttpConnection *connection, const char_t *uri) +{ return httpSendResponseStream(connection, uri, false); } -error_t httpSendResponseUnsafe(HttpConnection *connection, const char_t *uri, const char_t *absolutePath) { +error_t httpSendResponseUnsafe(HttpConnection *connection, const char_t *uri, const char_t *absolutePath) +{ return httpSendResponseStreamUnsafe(connection, uri, absolutePath, false); } -error_t httpSendResponseStream(HttpConnection *connection, const char_t *uri, bool_t isStream) { - //Retrieve the full pathname +error_t httpSendResponseStream(HttpConnection *connection, const char_t *uri, bool_t isStream) +{ + // Retrieve the full pathname httpGetAbsolutePath(connection, uri, connection->buffer, HTTP_SERVER_BUFFER_SIZE); return httpSendResponseStreamUnsafe(connection, uri, connection->buffer, isStream); } -error_t httpSendResponseStreamUnsafe(HttpConnection *connection, const char_t *uri, const char_t *absolutePath, bool_t isStream) +error_t httpSendResponseStreamUnsafe(HttpConnection *connection, const char_t *uri, const char_t *absolutePath, bool_t isStream) { - if (connection->buffer != absolutePath) { + if (connection->buffer != absolutePath) + { osStrcpy(connection->buffer, absolutePath); } #if (HTTP_SERVER_FS_SUPPORT == ENABLED) error_t error; size_t n; + uint32_t file_length; uint32_t length; FsFile *file; #if (HTTP_SERVER_GZIP_TYPE_SUPPORT == ENABLED) - //Check whether gzip compression is supported by the client - if(connection->request.acceptGzipEncoding) + // Check whether gzip compression is supported by the client + if (connection->request.acceptGzipEncoding) { - //Calculate the length of the pathname + // Calculate the length of the pathname n = osStrlen(connection->buffer); - //Sanity check - if(n < (HTTP_SERVER_BUFFER_SIZE - 4)) + // Sanity check + if (n < (HTTP_SERVER_BUFFER_SIZE - 4)) { - //Append gzip extension + // Append gzip extension osStrcpy(connection->buffer + n, ".gz"); - //Retrieve the size of the compressed resource, if any + // Retrieve the size of the compressed resource, if any error = fsGetFileSize(connection->buffer, &length); } else { - //Report an error + // Report an error error = ERROR_NOT_FOUND; } - //Check whether the gzip-compressed file exists - if(!error) + // Check whether the gzip-compressed file exists + if (!error) { - //Use gzip format + // Use gzip format connection->response.gzipEncoding = TRUE; } else { - //Strip the gzip extension + // Strip the gzip extension connection->buffer[n] = '\0'; - //Retrieve the size of the non-compressed resource + // Retrieve the size of the non-compressed resource error = fsGetFileSize(connection->buffer, &length); - //The specified URI cannot be found? - if(error) + // The specified URI cannot be found? + if (error) return ERROR_NOT_FOUND; } } else #endif { - //Retrieve the size of the specified file + // Retrieve the size of the specified file error = fsGetFileSize(connection->buffer, &length); - //The specified URI cannot be found? - if(error) + // The specified URI cannot be found? + if (error) return ERROR_NOT_FOUND; } - if (isStream) { + file_length = length; + if (isStream) + { length = CONTENT_LENGTH_MAX; + if (!connection->private.client_ctx.settings->encode.ffmpeg_stream_restart) + { + file_length = length; + } } - //Open the file for reading + // Open the file for reading file = fsOpenFile(connection->buffer, FS_FILE_MODE_READ); - //Failed to open the file? - if(file == NULL) + // Failed to open the file? + if (file == NULL) return ERROR_NOT_FOUND; #else error_t error; @@ -1000,82 +1001,77 @@ error_t httpSendResponseStreamUnsafe(HttpConnection *connection, const char_t *u const uint8_t *data; #if (HTTP_SERVER_GZIP_TYPE_SUPPORT == ENABLED) - //Check whether gzip compression is supported by the client - if(connection->request.acceptGzipEncoding) + // Check whether gzip compression is supported by the client + if (connection->request.acceptGzipEncoding) { size_t n; - //Calculate the length of the pathname + // Calculate the length of the pathname n = osStrlen(connection->buffer); - //Sanity check - if(n < (HTTP_SERVER_BUFFER_SIZE - 4)) + // Sanity check + if (n < (HTTP_SERVER_BUFFER_SIZE - 4)) { - //Append gzip extension + // Append gzip extension osStrcpy(connection->buffer + n, ".gz"); - //Get the compressed resource data associated with the URI, if any + // Get the compressed resource data associated with the URI, if any error = resGetData(connection->buffer, &data, &length); } else { - //Report an error + // Report an error error = ERROR_NOT_FOUND; } - //Check whether the gzip-compressed resource exists - if(!error) + // Check whether the gzip-compressed resource exists + if (!error) { - //Use gzip format + // Use gzip format connection->response.gzipEncoding = TRUE; } else { - //Strip the gzip extension + // Strip the gzip extension connection->buffer[n] = '\0'; - //Get the non-compressed resource data associated with the URI + // Get the non-compressed resource data associated with the URI error = resGetData(connection->buffer, &data, &length); - //The specified URI cannot be found? - if(error) + // The specified URI cannot be found? + if (error) return error; } } else #endif { - //Get the resource data associated with the URI + // Get the resource data associated with the URI error = resGetData(connection->buffer, &data, &length); - //The specified URI cannot be found? - if(error) + // The specified URI cannot be found? + if (error) return error; } #endif - if (connection->private.client_ctx.skip_taf_header) { + if (connection->private.client_ctx.skip_taf_header) + { length -= 4096; } - //Format HTTP response header - // TODO add status 416 on invalid ranges + // Format HTTP response header + // TODO add status 416 on invalid ranges if (connection->request.Range.start > 0) { - if (isStream) { - TRACE_WARNING("Seeking file to %" PRIu32 " but streaming\r\n", connection->request.Range.start); - connection->response.contentLength = 0; - connection->response.statusCode = 404; // TODO find a way to enforce the box to read from the beginning. - } else { - connection->request.Range.size = length; - if (connection->request.Range.end >= connection->request.Range.size || connection->request.Range.end == 0) - connection->request.Range.end = connection->request.Range.size - 1; - - if (connection->response.contentRange == NULL) - connection->response.contentRange = osAllocMem(255); - - osSprintf((char *)connection->response.contentRange, "bytes %" PRIu32 "-%" PRIu32 "/%" PRIu32, connection->request.Range.start, connection->request.Range.end, connection->request.Range.size); - connection->response.statusCode = 206; - connection->response.contentLength = connection->request.Range.end - connection->request.Range.start + 1; - TRACE_DEBUG("Added response range %s\r\n", connection->response.contentRange); - } + connection->request.Range.size = file_length; + if (connection->request.Range.end >= connection->request.Range.size || connection->request.Range.end == 0) + connection->request.Range.end = connection->request.Range.size - 1; + + if (connection->response.contentRange == NULL) + connection->response.contentRange = osAllocMem(255); + + osSprintf((char *)connection->response.contentRange, "bytes %" PRIu32 "-%" PRIu32 "/%" PRIu32, connection->request.Range.start, connection->request.Range.end, connection->request.Range.size); + connection->response.statusCode = 206; + connection->response.contentLength = connection->request.Range.end - connection->request.Range.start + 1; + TRACE_DEBUG("Added response range %s\r\n", connection->response.contentRange); } else { @@ -1096,23 +1092,27 @@ error_t httpSendResponseStreamUnsafe(HttpConnection *connection, const char_t *u connection->response.chunkedEncoding = FALSE; length = connection->response.contentLength; - //Send the header to the client + // Send the header to the client error = httpWriteHeader(connection); - //Any error to report? - if(error) + // Any error to report? + if (error) { #if (HTTP_SERVER_FS_SUPPORT == ENABLED) - //Close the file + // Close the file fsCloseFile(file); #endif - //Return status code + // Return status code return error; } - if (connection->private.client_ctx.skip_taf_header) { - if (connection->request.Range.start > 0) { + if (connection->private.client_ctx.skip_taf_header) + { + if (connection->request.Range.start > 0) + { connection->request.Range.start += 4096; - } else { + } + else + { fsSeekFile(file, 4096, FS_SEEK_SET); } } @@ -1127,64 +1127,63 @@ error_t httpSendResponseStreamUnsafe(HttpConnection *connection, const char_t *u } #if (HTTP_SERVER_FS_SUPPORT == ENABLED) - //Send response body - while(length > 0) + // Send response body + while (length > 0) { - //Limit the number of bytes to read at a time + // Limit the number of bytes to read at a time n = MIN(length, HTTP_SERVER_BUFFER_SIZE); - //Read data from the specified file + // Read data from the specified file error = fsReadFile(file, connection->buffer, n, &n); - //End of input stream? - if (isStream && error == ERROR_END_OF_FILE) + // End of input stream? + if (isStream && error == ERROR_END_OF_FILE && connection->private.client_ctx.state->box.ffmpeg_ctx.active) { - osDelayTask(500); - error = httpCloseStream(connection); //Test connection??? - if(error) + osDelayTask(100); + error = httpCloseStream(connection); // Test connection??? won't work TODO: exit after some seconds + if (error) break; continue; } - if(error) + if (error) break; - //Send data to the client + // Send data to the client error = httpWriteStream(connection, connection->buffer, n); - //Any error to report? - if(error) + // Any error to report? + if (error) break; - //Decrement the count of remaining bytes to be transferred + // Decrement the count of remaining bytes to be transferred length -= n; } - //Close the file + // Close the file fsCloseFile(file); - //Successful file transfer? - if(error == NO_ERROR || error == ERROR_END_OF_FILE) + // Successful file transfer? + if (error == NO_ERROR || error == ERROR_END_OF_FILE) { - if(length == 0) + if (length == 0) { - //Properly close the output stream + // Properly close the output stream error = httpCloseStream(connection); } } #else - //Send response body + // Send response body error = httpWriteStream(connection, data, length); - //Any error to report? - if(error) + // Any error to report? + if (error) return error; - //Properly close output stream + // Properly close output stream error = httpCloseStream(connection); #endif - //Return status code + // Return status code return error; } - /** * @brief Send error response to the client * @param[in] connection Structure representing an HTTP connection @@ -1194,63 +1193,62 @@ error_t httpSendResponseStreamUnsafe(HttpConnection *connection, const char_t *u **/ error_t httpSendErrorResponse(HttpConnection *connection, - uint_t statusCode, const char_t *message) + uint_t statusCode, const char_t *message) { error_t error; size_t length; - //HTML response template + // HTML response template static const char_t template[] = - "\r\n" - "\r\n" - "Error %03d\r\n" - "\r\n" - "

Error %03d

\r\n" - "

%s

\r\n" - "\r\n" - "\r\n"; - - //Compute the length of the response + "\r\n" + "\r\n" + "Error %03d\r\n" + "\r\n" + "

Error %03d

\r\n" + "

%s

\r\n" + "\r\n" + "\r\n"; + + // Compute the length of the response length = osStrlen(template) + osStrlen(message) - 4; - //Check whether the HTTP request has a body - if(osStrcasecmp(connection->request.method, "GET") && - osStrcasecmp(connection->request.method, "HEAD") && - osStrcasecmp(connection->request.method, "DELETE")) + // Check whether the HTTP request has a body + if (osStrcasecmp(connection->request.method, "GET") && + osStrcasecmp(connection->request.method, "HEAD") && + osStrcasecmp(connection->request.method, "DELETE")) { - //Drop the HTTP request body and close the connection after sending - //the HTTP response + // Drop the HTTP request body and close the connection after sending + // the HTTP response connection->response.keepAlive = FALSE; } - //Format HTTP response header + // Format HTTP response header connection->response.statusCode = statusCode; connection->response.contentType = mimeGetType(".htm"); connection->response.chunkedEncoding = FALSE; connection->response.contentLength = length; - //Send the header to the client + // Send the header to the client error = httpWriteHeader(connection); - //Any error to report? - if(error) + // Any error to report? + if (error) return error; - //Format HTML response + // Format HTML response osSprintf(connection->buffer, template, statusCode, statusCode, message); - //Send response body + // Send response body error = httpWriteStream(connection, connection->buffer, length); - //Any error to report? - if(error) + // Any error to report? + if (error) return error; - //Properly close output stream + // Properly close output stream error = httpCloseStream(connection); - //Return status code + // Return status code return error; } - /** * @brief Send redirect response to the client * @param[in] connection Structure representing an HTTP connection @@ -1260,64 +1258,63 @@ error_t httpSendErrorResponse(HttpConnection *connection, **/ error_t httpSendRedirectResponse(HttpConnection *connection, - uint_t statusCode, const char_t *uri) + uint_t statusCode, const char_t *uri) { error_t error; size_t length; - //HTML response template + // HTML response template static const char_t template[] = - "\r\n" - "\r\n" - "Moved\r\n" - "\r\n" - "

Moved

\r\n" - "

This page has moved to %s.

" - "\r\n" - "\r\n"; - - //Compute the length of the response + "\r\n" + "\r\n" + "Moved\r\n" + "\r\n" + "

Moved

\r\n" + "

This page has moved to %s.

" + "\r\n" + "\r\n"; + + // Compute the length of the response length = osStrlen(template) + 2 * osStrlen(uri) - 4; - //Check whether the HTTP request has a body - if(osStrcasecmp(connection->request.method, "GET") && - osStrcasecmp(connection->request.method, "HEAD") && - osStrcasecmp(connection->request.method, "DELETE")) + // Check whether the HTTP request has a body + if (osStrcasecmp(connection->request.method, "GET") && + osStrcasecmp(connection->request.method, "HEAD") && + osStrcasecmp(connection->request.method, "DELETE")) { - //Drop the HTTP request body and close the connection after sending - //the HTTP response + // Drop the HTTP request body and close the connection after sending + // the HTTP response connection->response.keepAlive = FALSE; } - //Format HTTP response header + // Format HTTP response header connection->response.statusCode = statusCode; connection->response.location = uri; connection->response.contentType = mimeGetType(".htm"); connection->response.chunkedEncoding = FALSE; connection->response.contentLength = length; - //Send the header to the client + // Send the header to the client error = httpWriteHeader(connection); - //Any error to report? - if(error) + // Any error to report? + if (error) return error; - //Format HTML response + // Format HTML response osSprintf(connection->buffer, template, uri, uri); - //Send response body + // Send response body error = httpWriteStream(connection, connection->buffer, length); - //Any error to report? - if(error) + // Any error to report? + if (error) return error; - //Properly close output stream + // Properly close output stream error = httpCloseStream(connection); - //Return status code + // Return status code return error; } - /** * @brief Check whether the client's handshake is valid * @param[in] connection Structure representing an HTTP connection @@ -1330,43 +1327,42 @@ bool_t httpCheckWebSocketHandshake(HttpConnection *connection) error_t error; size_t n; - //The request must contain an Upgrade header field whose value - //must include the "websocket" keyword - if(!connection->request.upgradeWebSocket) + // The request must contain an Upgrade header field whose value + // must include the "websocket" keyword + if (!connection->request.upgradeWebSocket) return FALSE; - //The request must contain a Connection header field whose value - //must include the "Upgrade" token - if(!connection->request.connectionUpgrade) + // The request must contain a Connection header field whose value + // must include the "Upgrade" token + if (!connection->request.connectionUpgrade) return FALSE; - //Retrieve the length of the client's key + // Retrieve the length of the client's key n = osStrlen(connection->request.clientKey); - //The request must include a header field with the name Sec-WebSocket-Key - if(n == 0) + // The request must include a header field with the name Sec-WebSocket-Key + if (n == 0) return FALSE; - //The value of the Sec-WebSocket-Key header field must be a 16-byte - //value that has been Base64-encoded + // The value of the Sec-WebSocket-Key header field must be a 16-byte + // value that has been Base64-encoded error = base64Decode(connection->request.clientKey, n, connection->buffer, &n); - //Decoding failed? - if(error) + // Decoding failed? + if (error) return FALSE; - //Check the length of the resulting value - if(n != 16) + // Check the length of the resulting value + if (n != 16) return FALSE; - //The client's handshake is valid + // The client's handshake is valid return TRUE; #else - //WebSocket are not supported + // WebSocket are not supported return FALSE; #endif } - /** * @brief Upgrade an existing HTTP connection to a WebSocket * @param[in] connection Structure representing an HTTP connection @@ -1379,53 +1375,52 @@ WebSocket *httpUpgradeToWebSocket(HttpConnection *connection) #if (HTTP_SERVER_WEB_SOCKET_SUPPORT == ENABLED) #if (HTTP_SERVER_TLS_SUPPORT == ENABLED) - //Check whether a secure connection is being used - if(connection->tlsContext != NULL) + // Check whether a secure connection is being used + if (connection->tlsContext != NULL) { - //Upgrade the secure connection to a WebSocket + // Upgrade the secure connection to a WebSocket webSocket = webSocketUpgradeSecureSocket(connection->socket, - connection->tlsContext); + connection->tlsContext); } else #endif { - //Upgrade the connection to a WebSocket + // Upgrade the connection to a WebSocket webSocket = webSocketUpgradeSocket(connection->socket); } - //Succesful upgrade? - if(webSocket != NULL) + // Succesful upgrade? + if (webSocket != NULL) { error_t error; - //Copy client's key + // Copy client's key error = webSocketSetClientKey(webSocket, connection->request.clientKey); - //Check status code - if(!error) + // Check status code + if (!error) { #if (HTTP_SERVER_TLS_SUPPORT == ENABLED) - //Detach the TLS context from the HTTP connection + // Detach the TLS context from the HTTP connection connection->tlsContext = NULL; #endif - //Detach the socket from the HTTP connection + // Detach the socket from the HTTP connection connection->socket = NULL; } else { - //Clean up side effects + // Clean up side effects webSocketClose(webSocket); webSocket = NULL; } } #else - //WebSockets are not supported + // WebSockets are not supported webSocket = NULL; #endif - //Return a handle to the freshly created WebSocket + // Return a handle to the freshly created WebSocket return webSocket; } - #endif diff --git a/src/fs_ext.c b/src/fs_ext.c index 9fc38072..d7585e0c 100644 --- a/src/fs_ext.c +++ b/src/fs_ext.c @@ -33,6 +33,70 @@ FsFile *fsOpenFileEx(const char_t *path, char *mode) return fp; } +error_t fsCompareFiles(const char_t *source_path, const char_t *target_path, size_t *diff_position) +{ + if (!fsFileExists(source_path)) + { + return ERROR_FILE_NOT_FOUND; + } + if (!fsFileExists(target_path)) + { + return ERROR_FILE_NOT_FOUND; + } + + FsFile *source_file = fsOpenFileEx(source_path, "rb"); + if (source_file == NULL) + return ERROR_FILE_OPENING_FAILED; + + FsFile *target_file = fsOpenFileEx(target_path, "rb"); + if (target_file == NULL) + { + fsCloseFile(source_file); + return ERROR_FILE_OPENING_FAILED; + } + + uint8_t buffer_source[FILE_COPY_BUFFER_SIZE]; + uint8_t buffer_target[FILE_COPY_BUFFER_SIZE]; + size_t bytes_read_source = 0; + size_t bytes_read_target = 0; + error_t error = NO_ERROR; + *diff_position = 0; + while (error == NO_ERROR) + { + error = fsReadFile(source_file, buffer_source, sizeof(buffer_source), &bytes_read_source); + if (error != NO_ERROR && error != ERROR_END_OF_FILE) + { + break; + } + error = fsReadFile(source_file, buffer_source, sizeof(buffer_source), &bytes_read_source); + if (error != NO_ERROR && error != ERROR_END_OF_FILE) + { + break; + } + if (bytes_read_source != bytes_read_target) + { + error = ERROR_ABORTED; + break; + } + for (size_t i = 0; i < bytes_read_source; i++) + { + if (buffer_source[i] != buffer_target[i]) + { + error = ERROR_ABORTED; + break; + } + *diff_position = *diff_position + 1; + } + } + fsCloseFile(source_file); + fsCloseFile(target_file); + if (error == ERROR_END_OF_FILE) + { + return NO_ERROR; + } + return error; +} + error_t fsCopyFile(const char_t *source_path, const char_t *target_path, bool_t overwrite) { // Check if source_path and target_path are not NULL @@ -78,3 +142,22 @@ error_t fsCopyFile(const char_t *source_path, const char_t *target_path, bool_t return error; } +error_t fsMoveFile(const char_t *source_path, const char_t *target_path, bool_t overwrite) +{ + error_t error = fsRenameFile(source_path, target_path); + if (error == NO_ERROR && !fsFileExists(source_path) && fsFileExists(target_path)) + { + return error; + } + + error = fsCopyFile(source_path, target_path, overwrite); + if (error == NO_ERROR) + { + error = fsCompareFiles(source_path, target_path, NULL); + if (error == NO_ERROR) + { + error = fsDeleteFile(source_path); + } + } + return error; +} diff --git a/src/handler.c b/src/handler.c index b0509b32..8bd62132 100644 --- a/src/handler.c +++ b/src/handler.c @@ -1,5 +1,6 @@ #include "handler.h" #include "server_helpers.h" +#include "fs_ext.h" void fillBaseCtx(HttpConnection *connection, const char_t *uri, const char_t *queryString, cloudapi_t api, cbr_ctx_t *ctx, client_ctx_t *client_ctx) { @@ -135,7 +136,7 @@ void cbrCloudBodyPassthrough(void *src_ctx, HttpClientContext *cloud_ctx, const { error_t error = fsWriteFile(ctx->file, (void *)payload, length); if (error) - TRACE_ERROR(">> fsWriteFile Error: %u\r\n", error); + TRACE_ERROR(">> fsWriteFile Error: %s\r\n", error2text(error)); } if (error == ERROR_END_OF_STREAM) { @@ -184,8 +185,8 @@ void cbrCloudBodyPassthrough(void *src_ctx, HttpClientContext *cloud_ctx, const } if (moveToLibrary) { - fsRenameFile(ctx->tonieInfo->contentPath, libraryPath); - if (fsFileExists(libraryPath)) + error_t error = fsMoveFile(ctx->tonieInfo->contentPath, libraryPath, false); + if (error == NO_ERROR) { char *libraryShortPath = custom_asprintf("lib://by/audioID/%" PRIu32 ".taf", audioId); @@ -197,7 +198,7 @@ void cbrCloudBodyPassthrough(void *src_ctx, HttpClientContext *cloud_ctx, const } else { - TRACE_ERROR(">> Failed to move %s to library %s\r\n", ctx->tonieInfo->contentPath, libraryPath); + TRACE_ERROR(">> Failed to move %s to library %s, error=%s\r\n", ctx->tonieInfo->contentPath, libraryPath, error2text(error)); } } @@ -539,7 +540,7 @@ error_t httpWriteResponse(HttpConnection *connection, void *data, size_t size, b osFreeMem(data); if (error != NO_ERROR) { - TRACE_ERROR("Failed to send payload: %d\r\n", error); + TRACE_ERROR("Failed to send payload: %s\r\n", error2text(error)); return error; } @@ -548,7 +549,7 @@ error_t httpWriteResponse(HttpConnection *connection, void *data, size_t size, b /* if (error != NO_ERROR) { - TRACE_ERROR("Failed to close: %d\r\n", error); + TRACE_ERROR("Failed to close: %s\r\n", error2text(error)); return error; } */ diff --git a/src/handler_api.c b/src/handler_api.c index 1431f36e..ea19c0f5 100644 --- a/src/handler_api.c +++ b/src/handler_api.c @@ -1582,7 +1582,7 @@ error_t taf_encode_start(void *in_ctx, const char *name, const char *filename) TRACE_INFO("[TAF] Start encoding to %s\r\n", ctx->file_path); TRACE_INFO("[TAF] first file: %s\r\n", name); - ctx->taf = toniefile_create(ctx->file_path, ctx->audio_id); + ctx->taf = toniefile_create(ctx->file_path, ctx->audio_id, false); if (ctx->taf == NULL) { diff --git a/src/handler_cloud.c b/src/handler_cloud.c index 35c9223e..6b1cff7a 100644 --- a/src/handler_cloud.c +++ b/src/handler_cloud.c @@ -410,7 +410,7 @@ error_t handleCloudContent(HttpConnection *connection, const char_t *uri, const if (error != NO_ERROR) { freeTonieInfo(tonieInfoAssign); - TRACE_ERROR("Could not copy %s to %s, error=%" PRIu32 "\r\n", assignFile, tonieInfo->contentPath, error); + TRACE_ERROR("Could not copy %s to %s, error=%s\r\n", assignFile, tonieInfo->contentPath, error2text(error)); break; } @@ -439,43 +439,52 @@ error_t handleCloudContent(HttpConnection *connection, const char_t *uri, const error = NO_ERROR; } + bool can_use_cloud = !(!client_ctx->settings->cloud.enabled || !client_ctx->settings->cloud.enableV2Content || (tonieInfo->json.nocloud && !tonieInfo->json.cloud_override)); if (tonieInfo->json._stream) { char *streamFileRel = &tonieInfo->json._streamFile[osStrlen(client_ctx->settings->internal.datadirfull)]; TRACE_INFO("Serve streaming content from %s\r\n", tonieInfo->json.source); connection->response.keepAlive = true; - ffmpeg_stream_ctx_t ffmpeg_ctx; - ffmpeg_ctx.active = false; - ffmpeg_ctx.quit = false; - ffmpeg_ctx.source = tonieInfo->json.source; - ffmpeg_ctx.skip_seconds = tonieInfo->json.skip_seconds; - ffmpeg_ctx.targetFile = tonieInfo->json._streamFile; - ffmpeg_ctx.error = NO_ERROR; - ffmpeg_ctx.taskId = osCreateTask(streamFileRel, &ffmpeg_stream_task, &ffmpeg_ctx, 10 * 1024, 0); - - while (!ffmpeg_ctx.active && ffmpeg_ctx.error == NO_ERROR) + ffmpeg_stream_ctx_t *ffmpeg_ctx = &client_ctx->state->box.ffmpeg_ctx; + ffmpeg_ctx->active = false; + ffmpeg_ctx->quit = false; + ffmpeg_ctx->append = (connection->request.Range.start != 0); + ffmpeg_ctx->source = tonieInfo->json.source; + ffmpeg_ctx->skip_seconds = tonieInfo->json.skip_seconds; + ffmpeg_ctx->targetFile = tonieInfo->json._streamFile; + ffmpeg_ctx->error = NO_ERROR; + ffmpeg_ctx->taskId = osCreateTask(streamFileRel, &ffmpeg_stream_task, ffmpeg_ctx, 10 * 1024, 0); + + while (!ffmpeg_ctx->active && ffmpeg_ctx->error == NO_ERROR) { osDelayTask(100); } - if (ffmpeg_ctx.error == NO_ERROR) + if (ffmpeg_ctx->error == NO_ERROR) { - uint32_t delay = client_ctx->settings->cloud.ffmpeg_stream_buffer_ms; + uint32_t delay = client_ctx->settings->encode.ffmpeg_stream_buffer_ms; TRACE_INFO("Serve streaming content from %s, delay %" PRIu32 "ms\r\n", tonieInfo->json.source, delay); - osDelayTask(delay); + if (!ffmpeg_ctx->append) + { + osDelayTask(delay); + } + else + { + osDelayTask(delay); + } error_t error = httpSendResponseStream(connection, streamFileRel, tonieInfo->json._stream); if (error) { - TRACE_ERROR(" >> file %s not available or not send, error=%u...\r\n", tonieInfo->contentPath, error); + TRACE_ERROR(" >> file %s not available or not send, error=%s...\r\n", tonieInfo->contentPath, error2text(error)); } } - ffmpeg_ctx.active = false; - while (!ffmpeg_ctx.quit) + ffmpeg_ctx->active = false; + while (!ffmpeg_ctx->quit) { osDelayTask(100); } } - else if (tonieInfo->exists && tonieInfo->valid && !tonie_marked) + else if (tonieInfo->exists && tonieInfo->valid && (!tonie_marked || !can_use_cloud)) { TRACE_INFO("Serve local content from %s\r\n", tonieInfo->contentPath); connection->response.keepAlive = true; @@ -491,7 +500,7 @@ error_t handleCloudContent(HttpConnection *connection, const char_t *uri, const error_t error = httpSendResponseStream(connection, &tonieInfo->contentPath[dataPathLen], tonieInfo->stream); if (error) { - TRACE_ERROR(" >> file %s not available or not send, error=%u...\r\n", tonieInfo->contentPath, error); + TRACE_ERROR(" >> file %s not available or not send, error=%s...\r\n", tonieInfo->contentPath, error2text(error)); } } else @@ -501,7 +510,7 @@ error_t handleCloudContent(HttpConnection *connection, const char_t *uri, const } else { - if (!client_ctx->settings->cloud.enabled || !client_ctx->settings->cloud.enableV2Content || (tonieInfo->json.nocloud && !tonieInfo->json.cloud_override)) + if (!can_use_cloud) { if (tonieInfo->json.nocloud && !tonieInfo->json.cloud_override) { diff --git a/src/handler_rtnl.c b/src/handler_rtnl.c index 38b1674c..d95822f3 100644 --- a/src/handler_rtnl.c +++ b/src/handler_rtnl.c @@ -282,6 +282,10 @@ void rtnlEvent(HttpConnection *connection, TonieRtnlRPC *rpc, client_ctx_t *clie mqtt_sendBoxEvent("TagInvalid", "", client_ctx); break; case RTNL3_TYPE_PLAYBACK_STOPPED: + if (!client_ctx->state->box.ffmpeg_ctx.quit && client_ctx->state->tag.valid) + { + client_ctx->state->box.ffmpeg_ctx.active = false; + } client_ctx->state->tag.audio_id = 0; client_ctx->state->tag.valid = false; client_ctx->state->tag.uid = 0; diff --git a/src/handler_sse.c b/src/handler_sse.c index 0f10ebc0..9273b7e7 100644 --- a/src/handler_sse.c +++ b/src/handler_sse.c @@ -70,7 +70,7 @@ error_t handleApiSse(HttpConnection *connection, const char_t *uri, const char_t TRACE_INFO("SSE Client disconnected from slot %" PRIu8 ", %" PRIu8 " clients left\r\n", sseCtx->channel, sseSubscriptionCount); if (error != NO_ERROR) { - TRACE_ERROR("SSE Client with error %" PRIu32 "\r\n", error); + TRACE_ERROR("SSE Client with error %s\r\n", error2text(error)); } mutex_unlock(MUTEX_SSE_CTX); break; diff --git a/src/main.c b/src/main.c index 2ea89803..5c0aac3b 100644 --- a/src/main.c +++ b/src/main.c @@ -180,7 +180,7 @@ void main_init_settings(const char *cwd, const char *base_path) } else { - TRACE_ERROR("ERROR: settings_init() failed with error code %d\r\n", error); + TRACE_ERROR("ERROR: settings_init() failed with error %s\r\n", error2text(error)); TRACE_ERROR("ERROR: Make sure the config path exists and is writable\r\n"); } exit(-1); @@ -209,6 +209,7 @@ void cbr_header(void *ctx, HttpClientContext *cloud_ctx, const char *header, con int_t main(int argc, char *argv[]) { char cwd[PATH_LEN] = {0}; + error_text_init(); get_settings()->log.level = TRACE_LEVEL_WARNING; @@ -525,7 +526,7 @@ int_t main(int argc, char *argv[]) TRACE_WARNING("**********************************\r\n"); TRACE_WARNING("File: %s\r\n", options.encode_test); - toniefile_t *taf = toniefile_create(options.encode_test, 0xDEAFBEEF); + toniefile_t *taf = toniefile_create(options.encode_test, 0xDEAFBEEF, false); if (!taf) { diff --git a/src/mqtt.c b/src/mqtt.c index 57fdd7a8..f19b838c 100644 --- a/src/mqtt.c +++ b/src/mqtt.c @@ -337,7 +337,7 @@ error_t mqttConnect(MqttClientContext *mqtt_context) if (error) { - TRACE_ERROR("Failed to connect to MQTT: %d\r\n", error); + TRACE_ERROR("Failed to connect to MQTT: %s\r\n", error2text(error)); break; } diff --git a/src/proto/proto/toniebox.pb.taf-header.pb-c.c b/src/proto/proto/toniebox.pb.taf-header.pb-c.c index 4d6824c6..5f57db5c 100644 --- a/src/proto/proto/toniebox.pb.taf-header.pb-c.c +++ b/src/proto/proto/toniebox.pb.taf-header.pb-c.c @@ -52,7 +52,7 @@ void toniebox_audio_file_header__free_unpacked assert(message->base.descriptor == &toniebox_audio_file_header__descriptor); protobuf_c_message_free_unpacked ((ProtobufCMessage*)message, allocator); } -static const ProtobufCFieldDescriptor toniebox_audio_file_header__field_descriptors[5] = +static const ProtobufCFieldDescriptor toniebox_audio_file_header__field_descriptors[9] = { { "sha1_hash", @@ -114,18 +114,70 @@ static const ProtobufCFieldDescriptor toniebox_audio_file_header__field_descript 0, /* flags */ 0,NULL,NULL /* reserved1,reserved2, etc */ }, + { + "ogg_granule_position", + 6, + PROTOBUF_C_LABEL_OPTIONAL, + PROTOBUF_C_TYPE_UINT64, + offsetof(TonieboxAudioFileHeader, has_ogg_granule_position), + offsetof(TonieboxAudioFileHeader, ogg_granule_position), + NULL, + NULL, + 0, /* flags */ + 0,NULL,NULL /* reserved1,reserved2, etc */ + }, + { + "ogg_packet_count", + 7, + PROTOBUF_C_LABEL_OPTIONAL, + PROTOBUF_C_TYPE_UINT64, + offsetof(TonieboxAudioFileHeader, has_ogg_packet_count), + offsetof(TonieboxAudioFileHeader, ogg_packet_count), + NULL, + NULL, + 0, /* flags */ + 0,NULL,NULL /* reserved1,reserved2, etc */ + }, + { + "taf_block_num", + 8, + PROTOBUF_C_LABEL_OPTIONAL, + PROTOBUF_C_TYPE_UINT64, + offsetof(TonieboxAudioFileHeader, has_taf_block_num), + offsetof(TonieboxAudioFileHeader, taf_block_num), + NULL, + NULL, + 0, /* flags */ + 0,NULL,NULL /* reserved1,reserved2, etc */ + }, + { + "pageno", + 9, + PROTOBUF_C_LABEL_OPTIONAL, + PROTOBUF_C_TYPE_UINT64, + offsetof(TonieboxAudioFileHeader, has_pageno), + offsetof(TonieboxAudioFileHeader, pageno), + NULL, + NULL, + 0, /* flags */ + 0,NULL,NULL /* reserved1,reserved2, etc */ + }, }; static const unsigned toniebox_audio_file_header__field_indices_by_name[] = { 4, /* field[4] = _fill */ 2, /* field[2] = audio_id */ 1, /* field[1] = num_bytes */ + 5, /* field[5] = ogg_granule_position */ + 6, /* field[6] = ogg_packet_count */ + 8, /* field[8] = pageno */ 0, /* field[0] = sha1_hash */ + 7, /* field[7] = taf_block_num */ 3, /* field[3] = track_page_nums */ }; static const ProtobufCIntRange toniebox_audio_file_header__number_ranges[1 + 1] = { { 1, 0 }, - { 0, 5 } + { 0, 9 } }; const ProtobufCMessageDescriptor toniebox_audio_file_header__descriptor = { @@ -135,7 +187,7 @@ const ProtobufCMessageDescriptor toniebox_audio_file_header__descriptor = "TonieboxAudioFileHeader", "", sizeof(TonieboxAudioFileHeader), - 5, + 9, toniebox_audio_file_header__field_descriptors, toniebox_audio_file_header__field_indices_by_name, 1, toniebox_audio_file_header__number_ranges, diff --git a/src/proto/proto/toniebox.pb.taf-header.pb-c.h b/src/proto/proto/toniebox.pb.taf-header.pb-c.h index ad50e315..5e459bac 100644 --- a/src/proto/proto/toniebox.pb.taf-header.pb-c.h +++ b/src/proto/proto/toniebox.pb.taf-header.pb-c.h @@ -32,10 +32,24 @@ struct _TonieboxAudioFileHeader size_t n_track_page_nums; uint32_t *track_page_nums; ProtobufCBinaryData _fill; + /* + *custom_fields_start + */ + protobuf_c_boolean has_ogg_granule_position; + uint64_t ogg_granule_position; + protobuf_c_boolean has_ogg_packet_count; + uint64_t ogg_packet_count; + protobuf_c_boolean has_taf_block_num; + uint64_t taf_block_num; + /* + *custom_fields_end + */ + protobuf_c_boolean has_pageno; + uint64_t pageno; }; #define TONIEBOX_AUDIO_FILE_HEADER__INIT \ { PROTOBUF_C_MESSAGE_INIT (&toniebox_audio_file_header__descriptor) \ - , {0,NULL}, 0, 0, 0,NULL, {0,NULL} } + , {0,NULL}, 0, 0, 0,NULL, {0,NULL}, 0, 0, 0, 0, 0, 0, 0, 0 } /* TonieboxAudioFileHeader methods */ diff --git a/src/rand.c b/src/rand.c index 0f01cc76..f16275d7 100644 --- a/src/rand.c +++ b/src/rand.c @@ -82,14 +82,14 @@ error_t rand_init() error_t error = yarrowInit(&yarrowContext); if (error) { - TRACE_ERROR("Error: PRNG initialization failed (%d)\r\n", error); + TRACE_ERROR("Error: PRNG initialization failed (%s)\r\n", error2text(error)); return ERROR_FAILURE; } error = yarrowSeed(&yarrowContext, seed, sizeof(seed)); if (error) { - TRACE_ERROR("Error: Failed to seed PRNG (%d)\r\n", error); + TRACE_ERROR("Error: Failed to seed PRNG (%s)\r\n", error2text(error)); return error; } return NO_ERROR; diff --git a/src/server.c b/src/server.c index 160617f2..fefdf3d7 100644 --- a/src/server.c +++ b/src/server.c @@ -41,6 +41,8 @@ HttpConnection httpConnections[APP_HTTP_MAX_CONNECTIONS]; HttpConnection httpsConnections[APP_HTTP_MAX_CONNECTIONS]; +size_t openRequestsLast = 0; + enum eRequestMethod { REQ_ANY, @@ -113,21 +115,26 @@ error_t resGetData(const char_t *path, const uint8_t **data, size_t *length) error_t httpServerRequestCallback(HttpConnection *connection, const char_t *uri) { + size_t openRequests = ++openRequestsLast; error_t error = NO_ERROR; stats_update("connections", 1); + char *request_source; if (connection->tlsContext != NULL && osStrlen(connection->tlsContext->client_cert_issuer)) { TRACE_DEBUG("Certificate authentication:\r\n"); TRACE_DEBUG(" Issuer: '%s'\r\n", connection->tlsContext->client_cert_issuer); TRACE_DEBUG(" Subject: '%s'\r\n", connection->tlsContext->client_cert_subject); TRACE_DEBUG(" Serial: '%s'\r\n", connection->tlsContext->client_cert_serial); + request_source = connection->tlsContext->client_cert_subject; } else { TRACE_DEBUG("No certificate authentication\r\n"); + request_source = "unknown/web"; } + TRACE_DEBUG("Started server request to %s, request %" PRIuSIZE ", by %s\r\n", uri, openRequests, request_source); TRACE_DEBUG(" >> client requested '%s' via %s \n", uri, connection->request.method); @@ -273,38 +280,49 @@ error_t httpServerRequestCallback(HttpConnection *connection, const char_t *uri) connection->response.keepAlive = connection->request.keepAlive; - for (size_t i = 0; i < sizeof(request_paths) / sizeof(request_paths[0]); i++) + do { - size_t pathLen = osStrlen(request_paths[i].path); - if (!osStrncmp(request_paths[i].path, uri, pathLen) && ((request_paths[i].method == REQ_ANY) || (request_paths[i].method == REQ_GET && !osStrcasecmp(connection->request.method, "GET")) || (request_paths[i].method == REQ_POST && !osStrcasecmp(connection->request.method, "POST")))) + bool handled = false; + for (size_t i = 0; i < sizeof(request_paths) / sizeof(request_paths[0]); i++) { - error = (*request_paths[i].handler)(connection, uri, connection->request.queryString, client_ctx); - if (error == ERROR_NOT_FOUND || error == ERROR_FILE_NOT_FOUND) - { - return httpServerUriNotFoundCallback(connection, uri); - } - else if (error != NO_ERROR) + size_t pathLen = osStrlen(request_paths[i].path); + if (!osStrncmp(request_paths[i].path, uri, pathLen) && ((request_paths[i].method == REQ_ANY) || (request_paths[i].method == REQ_GET && !osStrcasecmp(connection->request.method, "GET")) || (request_paths[i].method == REQ_POST && !osStrcasecmp(connection->request.method, "POST")))) { - // return httpServerUriErrorCallback(connection, uri, error); + error = (*request_paths[i].handler)(connection, uri, connection->request.queryString, client_ctx); + if (error == ERROR_NOT_FOUND || error == ERROR_FILE_NOT_FOUND) + { + error = httpServerUriNotFoundCallback(connection, uri); + } + else if (error != NO_ERROR) + { + // return httpServerUriErrorCallback(connection, uri, error); + } + handled = true; + break; } - return error; } - } + if (handled) + break; - if (!strcmp(uri, "/") || !strcmp(uri, "index.shtm")) - { - uri = "/index.html"; - } + if (!strcmp(uri, "/") || !strcmp(uri, "index.shtm")) + { + uri = "/index.html"; + } - if (!strncmp(uri, "/web", 4) && (uri[4] == '\0' || uri[strlen(uri) - 1] == '/' || !strchr(uri, '.'))) - { - uri = "/web/index.html"; - } + if (!strncmp(uri, "/web", 4) && (uri[4] == '\0' || uri[strlen(uri) - 1] == '/' || !strchr(uri, '.'))) + { + uri = "/web/index.html"; + } + + char_t *newUri = custom_asprintf("%s%s", client_ctx->settings->core.wwwdir, uri); - char_t *newUri = custom_asprintf("%s%s", client_ctx->settings->core.wwwdir, uri); + error = httpSendResponse(connection, newUri); + free(newUri); + /* code */ + } while (0); - error = httpSendResponse(connection, newUri); - free(newUri); + TRACE_DEBUG("Stopped server request to %s, request %" PRIuSIZE "\r\n", uri, openRequests); + openRequestsLast--; return error; } @@ -405,7 +423,7 @@ error_t httpServerTlsInitCallback(HttpConnection *connection, TlsContext *tlsCon if (error) { - TRACE_ERROR(" Failed to add cert: %d\r\n", error); + TRACE_ERROR(" Failed to add cert: %s\r\n", error2text(error)); return error; } diff --git a/src/server_helpers.c b/src/server_helpers.c index 343e982f..26b7c55a 100644 --- a/src/server_helpers.c +++ b/src/server_helpers.c @@ -342,7 +342,7 @@ error_t multipart_handle(HttpConnection *connection, multipart_cbr_t *cbr, void error_t error = httpReceive(connection, &buffer[leftover], DATA_SIZE - leftover, &packet_size, SOCKET_FLAG_DONT_WAIT); if (error != NO_ERROR) { - TRACE_ERROR("httpReceive failed with error %d\r\n", error); + TRACE_ERROR("httpReceive failed with error %s\r\n", error2text(error)); return error; } @@ -805,7 +805,7 @@ error_t httpServerUriErrorCallback(HttpConnection *connection, const char_t *uri new_error = httpSendErrorResponse(connection, 500, "Internal Server Error"); - TRACE_WARNING(" >> 500 with error code %" PRIu32 " on %s\r\n", error, uri); + TRACE_WARNING(" >> 500 with error code %s on %s\r\n", error2text(error), uri); return new_error; } diff --git a/src/settings.c b/src/settings.c index af4ce34f..d234b02a 100644 --- a/src/settings.c +++ b/src/settings.c @@ -183,7 +183,11 @@ static void option_map_init(uint8_t settingsId) OPTION_BOOL("cloud.prioCustomContent", &settings->cloud.prioCustomContent, TRUE, "Prioritize custom content", "Prioritize custom content over tonies content (force update)") OPTION_BOOL("cloud.updateOnLowerAudioId", &settings->cloud.updateOnLowerAudioId, TRUE, "Update content on lower audio id", "Update content on a lower audio id") OPTION_BOOL("cloud.dumpRuidAuthContentJson", &settings->cloud.dumpRuidAuthContentJson, TRUE, "Dump rUID/auth", "Dump the rUID and authentication into the content JSON.") - OPTION_UNSIGNED("cloud.ffmpeg_stream_buffer_ms", &settings->cloud.ffmpeg_stream_buffer_ms, 5000, 0, 60000, "Stream buffer ms", "Stream buffer for ffmpeg based streaming.") + + OPTION_TREE_DESC("encode", "TAF encoding") + OPTION_UNSIGNED("encode.bitrate", &settings->encode.bitrate, 96, 0, 256, "Opus bitrate", "Opus bitrate, tested 64, 96(default), 128, 192, 256 - be aware that this increases the TAF size!") + OPTION_UNSIGNED("encode.ffmpeg_stream_buffer_ms", &settings->encode.ffmpeg_stream_buffer_ms, 1000, 0, 60000, "Stream buffer ms", "Stream buffer for ffmpeg based streaming.") + OPTION_BOOL("encode.ffmpeg_stream_restart", &settings->encode.ffmpeg_stream_restart, TRUE, "Stream force restart", "If a stream is continued by the box, a new file is forced. This has the cost of a slower restart, but does not play the old buffered content and deletes the previous stream data on the box.") OPTION_TREE_DESC("toniebox", "Toniebox") OPTION_BOOL("toniebox.overrideCloud", &settings->toniebox.overrideCloud, TRUE, "Override cloud settings", "Override tonies cloud settings for the toniebox with those set here") @@ -1260,7 +1264,11 @@ bool settings_set_u64_array_id(const char *item, const uint64_t *value, size_t l uint64_t **ptr = (uint64_t **)opt->ptr; if (*ptr) { - osFreeMem(*ptr); + if (opt->size > 0) + { + opt->size = 0; + osFreeMem(*ptr); + } } *ptr = osAllocMem(sizeof(uint64_t) * len); diff --git a/src/tls_adapter.c b/src/tls_adapter.c index eab40448..4cdfb442 100644 --- a/src/tls_adapter.c +++ b/src/tls_adapter.c @@ -290,7 +290,7 @@ error_t read_certificate(const char_t *filename, char_t **buffer, size_t *length if (error != NO_ERROR) { - TRACE_ERROR("Error: pemEncodeFile failed for %s with code %d\r\n", filename, error); + TRACE_ERROR("Error: pemEncodeFile failed for %s with error %s\r\n", filename, error2text(error)); return error; } @@ -395,7 +395,7 @@ error_t tls_adapter_init() error_t error = settings_load_certs_id(0); if (error) { - TRACE_ERROR("Error: Failed to load certs (%d)\r\n", error); + TRACE_ERROR("Error: Failed to load certs (%s)\r\n", error2text(error)); return error; } diff --git a/src/toniefile.c b/src/toniefile.c index 3579a8f6..805f5b13 100644 --- a/src/toniefile.c +++ b/src/toniefile.c @@ -12,6 +12,7 @@ #include "error.h" #include "path.h" #include "fs_port.h" +#include "fs_ext.h" #include "os_port.h" #include "os_ext.h" #include "debug.h" @@ -90,9 +91,10 @@ static size_t toniefile_header(uint8_t *buffer, size_t length, TonieboxAudioFile return size; } -toniefile_t *toniefile_create(const char *fullPath, uint32_t audio_id) +toniefile_t *toniefile_create(const char *fullPath, uint32_t audio_id, bool append) { int err; + TonieboxAudioFileHeader *tafHeader = NULL; toniefile_t *ctx = osAllocMem(sizeof(toniefile_t)); osMemset(ctx, 0x00, sizeof(toniefile_t)); @@ -104,15 +106,36 @@ toniefile_t *toniefile_create(const char *fullPath, uint32_t audio_id) ctx->taf.n_track_page_nums = 0; ctx->taf.track_page_nums = osAllocMem(sizeof(uint32_t) * TONIEFILE_MAX_CHAPTERS); sha1Init(&ctx->sha1); - toniefile_new_chapter(ctx); /* open file */ ctx->fullPath = fullPath; - ctx->file = fsOpenFile(fullPath, FS_FILE_MODE_WRITE | FS_FILE_MODE_CREATE | FS_FILE_MODE_TRUNC); + if (!fsFileExists(fullPath)) + { + append = false; + } + if (append) + { + ctx->file = fsOpenFileEx(fullPath, "r+"); + TRACE_INFO("Append to TAF: %s\n", fullPath); + + char buffer[TONIEFILE_FRAME_SIZE]; + size_t read_length = 0; + fsSeekFile(ctx->file, 4, SEEK_SET); + fsReadFile(ctx->file, buffer, TONIEFILE_FRAME_SIZE - 4, &read_length); + tafHeader = toniebox_audio_file_header__unpack(NULL, read_length, (uint8_t *)buffer); + audio_id = tafHeader->audio_id; + ctx->taf.audio_id = audio_id; + } + else + { + ctx->file = fsOpenFile(fullPath, FS_FILE_MODE_WRITE | FS_FILE_MODE_CREATE | FS_FILE_MODE_TRUNC); + TRACE_INFO("Create TAF: %s\n", fullPath) + } if (ctx->file == NULL) { - TRACE_ERROR("Cannot create file: %s\n", fullPath); + TRACE_ERROR("Cannot create / open file: %s\n", fullPath); + fsCloseFile(ctx->file); osFreeMem(ctx->taf.track_page_nums); osFreeMem(ctx); return NULL; @@ -125,18 +148,20 @@ toniefile_t *toniefile_create(const char *fullPath, uint32_t audio_id) if (err != OPUS_OK) { TRACE_ERROR("Cannot create opus encoder: %s\n", opus_strerror(err)); + fsCloseFile(ctx->file); osFreeMem(ctx->taf.track_page_nums); osFreeMem(ctx); return NULL; } - opus_encoder_ctl(ctx->enc, OPUS_SET_BITRATE(OPUS_BIT_RATE)); + opus_encoder_ctl(ctx->enc, OPUS_SET_BITRATE(get_settings()->encode.bitrate * 1000)); opus_encoder_ctl(ctx->enc, OPUS_SET_VBR(1)); opus_encoder_ctl(ctx->enc, OPUS_SET_EXPERT_FRAME_DURATION(OPUS_FRAME_SIZE_MS)); /* init OGG */ ogg_stream_init(&ctx->os, audio_id); + // TODO: read header data to check if the same header channel / sampling rate is used unsigned char header_data[] = { 'O', 'p', 'u', 's', 'H', 'e', 'a', 'd', // "OpusHead" string 1, // Version @@ -206,26 +231,73 @@ toniefile_t *toniefile_create(const char *fullPath, uint32_t audio_id) ctx->file_pos = 0; ogg_page og; + if (!append) + { + while (ogg_stream_flush(&ctx->os, &og)) + { + /* write the freshly padded block of frames*/ + if (fsWriteFile(ctx->file, og.header, og.header_len) != NO_ERROR) + { + return NULL; + } + /* write the freshly padded block of frames*/ + if (fsWriteFile(ctx->file, og.body, og.body_len) != NO_ERROR) + { + return NULL; + } + ctx->file_pos += og.header_len + og.body_len; + ctx->audio_length += og.header_len + og.body_len; - while (ogg_stream_flush(&ctx->os, &og)) + sha1Update(&ctx->sha1, og.header, og.header_len); + sha1Update(&ctx->sha1, og.body, og.body_len); + } + } + else { - /* write the freshly padded block of frames*/ - if (fsWriteFile(ctx->file, og.header, og.header_len) != NO_ERROR) + while (ogg_stream_flush(&ctx->os, &og)) { - return NULL; } - /* write the freshly padded block of frames*/ - if (fsWriteFile(ctx->file, og.body, og.body_len) != NO_ERROR) + + char buffer[TONIEFILE_FRAME_SIZE]; + ctx->file_pos = TONIEFILE_FRAME_SIZE; + fsSeekFile(ctx->file, ctx->file_pos, SEEK_SET); + size_t read_length = 0; + while (true) { - return NULL; + error_t error = fsReadFile(ctx->file, buffer, TONIEFILE_FRAME_SIZE, &read_length); + if (error != NO_ERROR && error != ERROR_END_OF_FILE) + { + TRACE_ERROR("Cannot read file, error=%" PRIu16 "\n", error); + break; + } + if (read_length == 0) + { + break; + } + ctx->file_pos += read_length; + ctx->audio_length += read_length; + sha1Update(&ctx->sha1, buffer, read_length); } - ctx->file_pos += og.header_len + og.body_len; - ctx->audio_length += og.header_len + og.body_len; - sha1Update(&ctx->sha1, og.header, og.header_len); - sha1Update(&ctx->sha1, og.body, og.body_len); + size_t block_rest = (ctx->file_pos % TONIEFILE_FRAME_SIZE); + if (block_rest != 0) + { + TRACE_WARNING("Seeking back paddings to block size %" PRIuSIZE "\r\n", block_rest); + } + ctx->file_pos -= block_rest; + ctx->audio_length -= block_rest; + fsSeekFile(ctx->file, ctx->file_pos, SEEK_SET); + + ctx->ogg_granule_position = tafHeader->ogg_granule_position; + ctx->ogg_packet_count = tafHeader->ogg_packet_count; + ctx->taf_block_num = tafHeader->taf_block_num; + ctx->os.pageno = tafHeader->pageno; + toniebox_audio_file_header__free_unpacked(tafHeader, NULL); + // fsSeekFile(ctx->file, ctx->file_pos, SEEK_SET); + // TRACE_WARNING("Seek file to %" PRIuSIZE ", blockrest=%" PRIuSIZE "\r\n", ctx->file_pos, block_rest); } + toniefile_new_chapter(ctx); return ctx; } @@ -241,6 +313,15 @@ error_t toniefile_write_header(toniefile_t *ctx) ctx->taf.sha1_hash.len = SHA1_DIGEST_SIZE; } + ctx->taf.ogg_granule_position = ctx->ogg_granule_position; + ctx->taf.ogg_packet_count = ctx->ogg_packet_count; + ctx->taf.taf_block_num = ctx->taf_block_num; + ctx->taf.pageno = ctx->os.pageno; + ctx->taf.has_ogg_granule_position = true; + ctx->taf.has_ogg_packet_count = true; + ctx->taf.has_taf_block_num = true; + ctx->taf.has_pageno = true; + osMemset(buffer, 0x00, sizeof(buffer)); uint32_t proto_size = (uint32_t)toniefile_header(buffer, sizeof(buffer), &ctx->taf); @@ -308,7 +389,7 @@ error_t toniefile_encode(toniefile_t *ctx, int16_t *sample_buffer, size_t sample int samples_processed = 0; uint8_t output_frame[TONIEFILE_FRAME_SIZE]; - // TRACE_INFO("samples_available: %lu\n", samples_available); + // TRACE_INFO("samples_available: %" PRIuSIZE "\n", samples_available); while (samples_processed < samples_available) { /* get the maximum copyable number of samples */ @@ -318,14 +399,14 @@ error_t toniefile_encode(toniefile_t *ctx, int16_t *sample_buffer, size_t sample { samples = samples_remaining; } - // TRACE_INFO(" samples: %lu (%lu/%lu)\n", samples, samples_processed, samples_available); + // TRACE_INFO(" samples: %lu (%u/%" PRIuSIZE ")\n", samples, samples_processed, samples_available); toniefile_samples_copy(ctx->audio_frame, &ctx->audio_frame_used, sample_buffer, &samples_processed, samples); /* buffer full? */ if (ctx->audio_frame_used >= OPUS_FRAME_SIZE) { - int page_used = (ctx->file_pos % TONIEFILE_FRAME_SIZE) + 27 + ctx->os.lacing_fill - ctx->os.lacing_returned + ctx->os.body_fill - ctx->os.body_returned; + int page_used = (ctx->file_pos % TONIEFILE_FRAME_SIZE) + OGG_HEADER_LENGTH + ctx->os.lacing_fill - ctx->os.lacing_returned + ctx->os.body_fill - ctx->os.body_returned; int page_remain = TONIEFILE_FRAME_SIZE - page_used; int frame_payload = (page_remain / 256) * 255 + (page_remain % 256) - 1; @@ -335,18 +416,20 @@ error_t toniefile_encode(toniefile_t *ctx, int16_t *sample_buffer, size_t sample * reason why this could happen is that "adding one byte" would require one segment more and thus occupies two byte more. * if this would happen, just reduce the calculated free space such that there is room for another segment. */ + bool frame_payload_minified = false; if (page_remain != reconstructed && frame_payload > OPUS_PACKET_MINSIZE) { frame_payload -= OPUS_PACKET_MINSIZE; + frame_payload_minified = true; } - if (frame_payload < OPUS_PACKET_MINSIZE) + if (frame_payload < OPUS_PACKET_MINSIZE - 1) { - TRACE_ERROR("Not enough space in this block\r\n"); + TRACE_ERROR("Not enough space in this block, mini=%X, frame_payload=%i, page_remain=%i, reconstructed=%i\r\n", frame_payload_minified, frame_payload, page_remain, reconstructed); return ERROR_FAILURE; } int frame_len = opus_encode(ctx->enc, ctx->audio_frame, OPUS_FRAME_SIZE, output_frame, frame_payload); - // TRACE_INFO("opus_encode: %d/%d\r\n", frame_len, frame_dest); + // TRACE_INFO("opus_encode: %d/%d\r\n", frame_len, frame_payload); if (frame_len <= 0) { @@ -390,9 +473,11 @@ error_t toniefile_encode(toniefile_t *ctx, int16_t *sample_buffer, size_t sample ogg_stream_packetin(&ctx->os, &op); - page_used = (ctx->file_pos % TONIEFILE_FRAME_SIZE) + 27 + ctx->os.lacing_fill + ctx->os.body_fill; + page_used = (ctx->file_pos % TONIEFILE_FRAME_SIZE) + OGG_HEADER_LENGTH + ctx->os.lacing_fill + ctx->os.body_fill; page_remain = TONIEFILE_FRAME_SIZE - page_used; + // TRACE_INFO("(%" PRIuSIZE " MOD 4096) + 27 + %li + %li;\r\n", ctx->file_pos, ctx->os.lacing_fill, ctx->os.body_fill) + if (page_remain < TONIEFILE_PAD_END) { if (page_remain) @@ -415,6 +500,7 @@ error_t toniefile_encode(toniefile_t *ctx, int16_t *sample_buffer, size_t sample size_t prev = ctx->file_pos; ctx->file_pos += og.header_len + og.body_len; ctx->audio_length += og.header_len + og.body_len; + // TRACE_INFO("Header_len %" PRIuSIZE " Body_len %" PRIuSIZE " prev %" PRIuSIZE " File_pos %" PRIuSIZE "\r\n", og.header_len, og.body_len, prev, ctx->file_pos); sha1Update(&ctx->sha1, og.header, og.header_len); sha1Update(&ctx->sha1, og.body, og.body_len); @@ -548,10 +634,10 @@ error_t ffmpeg_decode_audio(FILE *ffmpeg_pipe, int16_t *buffer, size_t size, siz error_t ffmpeg_convert(char source[99][PATH_LEN], size_t source_len, const char *target_taf, size_t skip_seconds) { bool_t active = true; - return ffmpeg_stream(source, source_len, target_taf, skip_seconds, &active); + return ffmpeg_stream(source, source_len, target_taf, skip_seconds, &active, false); } -error_t ffmpeg_stream(char source[99][PATH_LEN], size_t source_len, const char *target_taf, size_t skip_seconds, bool_t *active) +error_t ffmpeg_stream(char source[99][PATH_LEN], size_t source_len, const char *target_taf, size_t skip_seconds, bool_t *active, bool_t append) { TRACE_INFO("Encode %" PRIuSIZE " sources: \r\n", source_len); for (size_t i = 0; i < source_len; i++) @@ -573,7 +659,7 @@ error_t ffmpeg_stream(char source[99][PATH_LEN], size_t source_len, const char * return ERROR_ABORTED; } - toniefile_t *taf = toniefile_create(target_taf, time(NULL)); + toniefile_t *taf = toniefile_create(target_taf, time(NULL), append); if (!taf) { TRACE_ERROR("toniefile_create() failed, aborting\r\n"); @@ -591,7 +677,7 @@ error_t ffmpeg_stream(char source[99][PATH_LEN], size_t source_len, const char * error = ffmpeg_decode_audio(ffmpeg_pipe, sample_buffer, samples, &blocks_read); if (error != NO_ERROR && error != ERROR_END_OF_STREAM) { - TRACE_ERROR("Could not decode sample error=%" PRIu16 " read=%" PRIuSIZE "\r\n", error, blocks_read); + TRACE_ERROR("Could not decode sample error=%s read=%" PRIuSIZE "\r\n", error2text(error), blocks_read); break; } else if (error == ERROR_END_OF_STREAM) @@ -616,13 +702,21 @@ error_t ffmpeg_stream(char source[99][PATH_LEN], size_t source_len, const char * } break; } - error = toniefile_encode(taf, sample_buffer, blocks_read / 2); + error = toniefile_encode(taf, sample_buffer, blocks_read / OPUS_CHANNELS); if (error != NO_ERROR && error != ERROR_END_OF_STREAM) { - TRACE_ERROR("Could not encode toniesample error=%" PRIu16 "\r\n", error); + TRACE_ERROR("Could not encode toniesample error=%s\r\n", error2text(error)); break; } } + if (!(*active)) + { + TRACE_INFO("Encoding aborted, active flag set to false\r\n"); + } + else + { + *active = false; + } ffmpeg_decode_audio_end(ffmpeg_pipe, error); toniefile_close(taf); @@ -637,7 +731,7 @@ void ffmpeg_stream_task(void *param) ffmpeg_stream_ctx_t *ctx = (ffmpeg_stream_ctx_t *)param; char source[99][PATH_LEN]; // waste memory, but warning otherwise strncpy(source[0], ctx->source, PATH_LEN - 1); - ctx->error = ffmpeg_stream(source, 1, ctx->targetFile, ctx->skip_seconds, &ctx->active); + ctx->error = ffmpeg_stream(source, 1, ctx->targetFile, ctx->skip_seconds, &ctx->active, ctx->append); ctx->quit = true; osDeleteTask(OS_SELF_TASK_ID); } \ No newline at end of file diff --git a/src/toniesJson.c b/src/toniesJson.c index d160bf95..3c7816e7 100644 --- a/src/toniesJson.c +++ b/src/toniesJson.c @@ -79,12 +79,12 @@ void tonies_downloadBody(void *src_ctx, HttpClientContext *cloud_ctx, const char else if (error != NO_ERROR) { fsCloseFile(ctx->file); - TRACE_ERROR("tonies.json download body error=%" PRIu32 "\r\n", error); + TRACE_ERROR("tonies.json download body error=%s\r\n", error2text(error)); } if (errorWrite != NO_ERROR) { fsCloseFile(ctx->file); - TRACE_ERROR("tonies.json (%s) write error=%" PRIu32 "\r\n", tonies_json_tmp_path, errorWrite); + TRACE_ERROR("tonies.json (%s) write error=%s\r\n", tonies_json_tmp_path, error2text(error)); } } } @@ -121,7 +121,7 @@ error_t tonies_update() } else { - TRACE_ERROR("... failed updating tonies.json error=%" PRIu32 "\r\n", error); + TRACE_ERROR("... failed updating tonies.json error=%s\r\n", error2text(error)); } return error; } @@ -158,7 +158,7 @@ error_t toniesV2_update() } else { - TRACE_ERROR("... failed updating tonies.json error=%" PRIu32 "\r\n", error); + TRACE_ERROR("... failed updating tonies.json error=%s\r\n", error2text(error)); } return error; }