kopia lustrzana https://github.com/meshtastic/firmware
				
				
				
			Native Webserver (#3343)
* Added WebServer/WebServices for Native Linux Meshtastic and web gui * Fix bug in login functionality * Added customized config of portdunio.ini with LovyannGFX from marelab repro * Compile Problem resolved with developer version of LovyanGFX.git * Compile against dev version * Fixes to fit into main branch * Update variant.h, main.cpp, .gitignore, WebServer.cpp, esp32s2.ini, WebServer.h, ContentHandler.cpp, rp2040.ini, nrf52.ini, ContentHelper.cpp, Dockerfile, ContentHandler.h, esp32.ini, stm32wl5e.ini * Added linux pi std /usr/include dir * Adding /usr/innclude for Linux compile against native libs that are not hadled by platformio * Review log level changes & translation * Update Dockerfile * Fix Typo & VFS ref. Part1 * Fix Typo & VFS ref. * Dev Version for ulfius web lib * Update platformio.ini * Free VFS path string * Remove unintended changes * More unintentional changes * Make the HTTP server optional on native * Tune-up for Native web defaults * Don't modify build system yet * Remove more unneeded changes --------- Co-authored-by: marc hammermann <marchammermann@googlemail.com> Co-authored-by: Ben Meadors <benmmeadors@gmail.com> Co-authored-by: Thomas Göttgens <tgoettgens@gmail.com>pull/3344/head
							rodzic
							
								
									9d37a8d17f
								
							
						
					
					
						commit
						e174328de3
					
				| 
						 | 
				
			
			@ -31,3 +31,5 @@ venv/
 | 
			
		|||
release/
 | 
			
		||||
.vscode/extensions.json
 | 
			
		||||
/compile_commands.json
 | 
			
		||||
src/mesh/raspihttp/certificate.pem
 | 
			
		||||
src/mesh/raspihttp/private_key.pem
 | 
			
		||||
| 
						 | 
				
			
			@ -4,7 +4,7 @@ extends = arduino_base
 | 
			
		|||
platform = platformio/espressif32@6.3.2 # This is a temporary fix to the S3-based devices bluetooth issues until we can determine what within ESP-IDF changed and can develop a suitable patch. 
 | 
			
		||||
 | 
			
		||||
build_src_filter = 
 | 
			
		||||
  ${arduino_base.build_src_filter} -<platform/nrf52/> -<platform/stm32wl> -<platform/rp2040> -<mesh/eth/>
 | 
			
		||||
  ${arduino_base.build_src_filter} -<platform/nrf52/> -<platform/stm32wl> -<platform/rp2040> -<mesh/eth/> -<mesh/raspihttp>
 | 
			
		||||
 | 
			
		||||
upload_speed = 921600
 | 
			
		||||
debug_init_break = tbreak setup
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -2,7 +2,7 @@
 | 
			
		|||
extends = esp32_base
 | 
			
		||||
 | 
			
		||||
build_src_filter = 
 | 
			
		||||
  ${esp32_base.build_src_filter} -<nimble/>
 | 
			
		||||
  ${esp32_base.build_src_filter} -<nimble/> -<mesh/raspihttp>
 | 
			
		||||
 | 
			
		||||
monitor_speed = 115200
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -12,5 +12,4 @@ build_flags =
 | 
			
		|||
  
 | 
			
		||||
lib_ignore = 
 | 
			
		||||
  ${esp32_base.lib_ignore} 
 | 
			
		||||
  NimBLE-Arduino
 | 
			
		||||
 | 
			
		||||
  NimBLE-Arduino
 | 
			
		||||
| 
						 | 
				
			
			@ -11,7 +11,7 @@ build_flags =
 | 
			
		|||
  -Isrc/platform/nrf52
 | 
			
		||||
 | 
			
		||||
build_src_filter = 
 | 
			
		||||
  ${arduino_base.build_src_filter} -<platform/esp32/> -<platform/stm32wl> -<nimble/> -<mesh/wifi/> -<mesh/api/> -<mesh/http/> -<modules/esp32> -<platform/rp2040> -<mesh/eth/>
 | 
			
		||||
  ${arduino_base.build_src_filter} -<platform/esp32/> -<platform/stm32wl> -<nimble/> -<mesh/wifi/> -<mesh/api/> -<mesh/http/> -<modules/esp32> -<platform/rp2040> -<mesh/eth/> -<mesh/raspihttp>
 | 
			
		||||
 | 
			
		||||
lib_deps=
 | 
			
		||||
  ${arduino_base.lib_deps}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -12,6 +12,7 @@ build_src_filter =
 | 
			
		|||
  -<platform/rp2040>
 | 
			
		||||
  -<mesh/wifi/>
 | 
			
		||||
  -<mesh/http/>
 | 
			
		||||
  +<mesh/raspihttp/>
 | 
			
		||||
  -<mesh/eth/>
 | 
			
		||||
  -<modules/esp32>
 | 
			
		||||
  -<modules/Telemetry/EnvironmentTelemetry.cpp>
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -12,7 +12,7 @@ build_flags =
 | 
			
		|||
  -D__PLAT_RP2040__
 | 
			
		||||
#  -D _POSIX_THREADS
 | 
			
		||||
build_src_filter = 
 | 
			
		||||
  ${arduino_base.build_src_filter} -<platform/esp32/> -<nimble/> -<modules/esp32> -<platform/nrf52/> -<platform/stm32wl> -<mesh/eth/> -<mesh/wifi/> -<mesh/http/>
 | 
			
		||||
  ${arduino_base.build_src_filter} -<platform/esp32/> -<nimble/> -<modules/esp32> -<platform/nrf52/> -<platform/stm32wl> -<mesh/eth/> -<mesh/wifi/> -<mesh/http/> -<mesh/raspihttp>
 | 
			
		||||
 | 
			
		||||
lib_ignore =
 | 
			
		||||
  BluetoothOTA
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -13,7 +13,7 @@ build_flags =
 | 
			
		|||
  -DVECT_TAB_OFFSET=0x08000000
 | 
			
		||||
  
 | 
			
		||||
build_src_filter = 
 | 
			
		||||
  ${arduino_base.build_src_filter} -<platform/esp32/> -<nimble/> -<mesh/api/> -<mesh/wifi/> -<mesh/http/> -<modules/esp32> -<mesh/eth/> -<input> -<buzz> -<modules/Telemetry> -<platform/nrf52> -<platform/portduino> -<platform/rp2040>
 | 
			
		||||
  ${arduino_base.build_src_filter} -<platform/esp32/> -<nimble/> -<mesh/api/> -<mesh/wifi/> -<mesh/http/> -<modules/esp32> -<mesh/eth/> -<input> -<buzz> -<modules/Telemetry> -<platform/nrf52> -<platform/portduino> -<platform/rp2040> -<mesh/raspihttp>
 | 
			
		||||
 | 
			
		||||
board_upload.offset_address = 0x08000000
 | 
			
		||||
upload_protocol = stlink
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -117,3 +117,7 @@ Input:
 | 
			
		|||
 | 
			
		||||
Logging:
 | 
			
		||||
  LogLevel: info # debug, info, warn, error
 | 
			
		||||
 | 
			
		||||
Webserver:
 | 
			
		||||
#  Port: 443 # Port for Webserver & Webservices
 | 
			
		||||
#  RootPath: /usr/share/doc/meshtasticd/web # Root Dir of WebServer
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -68,6 +68,7 @@ NRF52Bluetooth *nrf52Bluetooth;
 | 
			
		|||
 | 
			
		||||
#ifdef ARCH_PORTDUINO
 | 
			
		||||
#include "linux/LinuxHardwareI2C.h"
 | 
			
		||||
#include "mesh/raspihttp/PiWebServer.h"
 | 
			
		||||
#include "platform/portduino/PortduinoGlue.h"
 | 
			
		||||
#include <fstream>
 | 
			
		||||
#include <iostream>
 | 
			
		||||
| 
						 | 
				
			
			@ -857,6 +858,11 @@ void setup()
 | 
			
		|||
#endif
 | 
			
		||||
 | 
			
		||||
#ifdef ARCH_PORTDUINO
 | 
			
		||||
#if __has_include(<ulfius.h>)
 | 
			
		||||
    if (settingsMap[webserverport] != -1) {
 | 
			
		||||
        piwebServerThread = new PiWebServerThread();
 | 
			
		||||
    }
 | 
			
		||||
#endif
 | 
			
		||||
    initApiServer(TCPPort);
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -0,0 +1,530 @@
 | 
			
		|||
/*
 | 
			
		||||
Adds a WebServer and WebService callbacks to meshtastic as Linux Version. The WebServer & Webservices
 | 
			
		||||
runs in a real linux thread beside the portdunio threading emulation. It replaces the complete ESP32
 | 
			
		||||
Webserver libs including generation of SSL certifcicates,  because the use ESP specific details in
 | 
			
		||||
the lib that can't be emulated.
 | 
			
		||||
 | 
			
		||||
The WebServices adapt to the two major phoneapi functions "handleAPIv1FromRadio,handleAPIv1ToRadio"
 | 
			
		||||
The WebServer just adds basaic support to deliver WebContent, so it can be used to
 | 
			
		||||
deliver the WebGui definded by the WebClient Project.
 | 
			
		||||
 | 
			
		||||
Steps to get it running:
 | 
			
		||||
1.) Add these Linux Libs to the compile and target machine:
 | 
			
		||||
 | 
			
		||||
    sudo apt update && \
 | 
			
		||||
        apt -y install openssl libssl-dev libopenssl libsdl2-dev \
 | 
			
		||||
                     libulfius-dev liborcania-dev
 | 
			
		||||
 | 
			
		||||
2.) Configure the root directory of the web Content in the config.yaml file.
 | 
			
		||||
    The followinng tags should be included and set at your needs
 | 
			
		||||
 | 
			
		||||
    Example entry in the config.yaml
 | 
			
		||||
    Webserver:
 | 
			
		||||
        Port: 9001 # Port for Webserver & Webservices
 | 
			
		||||
        RootPath: /home/marc/web # Root Dir of WebServer
 | 
			
		||||
 | 
			
		||||
3.) Checkout the web project
 | 
			
		||||
    https://github.com/meshtastic/web.git
 | 
			
		||||
 | 
			
		||||
    Build it and copy the content of the folder web/dist/* to the folder you did set as "RootPath"
 | 
			
		||||
 | 
			
		||||
!!!The WebServer should not be used as production system or exposed to the Internet. Its a raw basic version!!!
 | 
			
		||||
 | 
			
		||||
Author: Marc Philipp Hammermann
 | 
			
		||||
mail:   marchammermann@googlemail.com
 | 
			
		||||
 | 
			
		||||
*/
 | 
			
		||||
#ifdef PORTDUINO_LINUX_HARDWARE
 | 
			
		||||
#if __has_include(<ulfius.h>)
 | 
			
		||||
#include "PiWebServer.h"
 | 
			
		||||
#include "NodeDB.h"
 | 
			
		||||
#include "PhoneAPI.h"
 | 
			
		||||
#include "PowerFSM.h"
 | 
			
		||||
#include "RadioLibInterface.h"
 | 
			
		||||
#include "airtime.h"
 | 
			
		||||
#include "graphics/Screen.h"
 | 
			
		||||
#include "main.h"
 | 
			
		||||
#include "mesh/wifi/WiFiAPClient.h"
 | 
			
		||||
#include "sleep.h"
 | 
			
		||||
#include <openssl/bn.h>
 | 
			
		||||
#include <openssl/evp.h>
 | 
			
		||||
#include <openssl/pem.h>
 | 
			
		||||
#include <openssl/rsa.h>
 | 
			
		||||
#include <openssl/x509.h>
 | 
			
		||||
#include <orcania.h>
 | 
			
		||||
#include <string.h>
 | 
			
		||||
#include <ulfius.h>
 | 
			
		||||
#include <yder.h>
 | 
			
		||||
 | 
			
		||||
#include <cstring>
 | 
			
		||||
#include <string>
 | 
			
		||||
 | 
			
		||||
#include "PortduinoFS.h"
 | 
			
		||||
#include "platform/portduino/PortduinoGlue.h"
 | 
			
		||||
 | 
			
		||||
#define DEFAULT_REALM "default_realm"
 | 
			
		||||
#define PREFIX ""
 | 
			
		||||
 | 
			
		||||
struct _file_config configWeb;
 | 
			
		||||
 | 
			
		||||
// We need to specify some content-type mapping, so the resources get delivered with the
 | 
			
		||||
// right content type and are displayed correctly in the browser
 | 
			
		||||
char contentTypes[][2][32] = {{".txt", "text/plain"},      {".html", "text/html"},
 | 
			
		||||
                              {".js", "text/javascript"},  {".png", "image/png"},
 | 
			
		||||
                              {".jpg", "image/jpg"},       {".gz", "application/gzip"},
 | 
			
		||||
                              {".gif", "image/gif"},       {".json", "application/json"},
 | 
			
		||||
                              {".css", "text/css"},        {".ico", "image/vnd.microsoft.icon"},
 | 
			
		||||
                              {".svg", "image/svg+xml"},   {".ts", "text/javascript"},
 | 
			
		||||
                              {".tsx", "text/javascript"}, {"", ""}};
 | 
			
		||||
 | 
			
		||||
#undef str
 | 
			
		||||
 | 
			
		||||
volatile bool isWebServerReady;
 | 
			
		||||
volatile bool isCertReady;
 | 
			
		||||
 | 
			
		||||
HttpAPI webAPI;
 | 
			
		||||
 | 
			
		||||
PiWebServerThread *piwebServerThread;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Return the filename extension
 | 
			
		||||
 */
 | 
			
		||||
const char *get_filename_ext(const char *path)
 | 
			
		||||
{
 | 
			
		||||
    const char *dot = strrchr(path, '.');
 | 
			
		||||
    if (!dot || dot == path)
 | 
			
		||||
        return "*";
 | 
			
		||||
    if (strchr(dot, '?') != NULL) {
 | 
			
		||||
        //*strchr(dot, '?') = '\0';
 | 
			
		||||
        const char *empty = "\0";
 | 
			
		||||
        return empty;
 | 
			
		||||
    }
 | 
			
		||||
    return dot;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Streaming callback function to ease sending large files
 | 
			
		||||
 */
 | 
			
		||||
static ssize_t callback_static_file_stream(void *cls, uint64_t pos, char *buf, size_t max)
 | 
			
		||||
{
 | 
			
		||||
    (void)(pos);
 | 
			
		||||
    if (cls != NULL) {
 | 
			
		||||
        return fread(buf, 1, max, (FILE *)cls);
 | 
			
		||||
    } else {
 | 
			
		||||
        return U_STREAM_END;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Cleanup FILE* structure when streaming is complete
 | 
			
		||||
 */
 | 
			
		||||
static void callback_static_file_stream_free(void *cls)
 | 
			
		||||
{
 | 
			
		||||
    if (cls != NULL) {
 | 
			
		||||
        fclose((FILE *)cls);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * static file callback endpoint that delivers the content for WebServer calls
 | 
			
		||||
 */
 | 
			
		||||
int callback_static_file(const struct _u_request *request, struct _u_response *response, void *user_data)
 | 
			
		||||
{
 | 
			
		||||
    size_t length;
 | 
			
		||||
    FILE *f;
 | 
			
		||||
    char *file_requested, *file_path, *url_dup_save, *real_path = NULL;
 | 
			
		||||
    const char *content_type;
 | 
			
		||||
 | 
			
		||||
    /*
 | 
			
		||||
     * Comment this if statement if you don't access static files url from root dir, like /app
 | 
			
		||||
     */
 | 
			
		||||
    if (request->callback_position > 0) {
 | 
			
		||||
        return U_CALLBACK_CONTINUE;
 | 
			
		||||
    } else if (user_data != NULL && (configWeb.files_path != NULL)) {
 | 
			
		||||
        file_requested = o_strdup(request->http_url);
 | 
			
		||||
        url_dup_save = file_requested;
 | 
			
		||||
 | 
			
		||||
        while (file_requested[0] == '/') {
 | 
			
		||||
            file_requested++;
 | 
			
		||||
        }
 | 
			
		||||
        file_requested += o_strlen(configWeb.url_prefix);
 | 
			
		||||
        while (file_requested[0] == '/') {
 | 
			
		||||
            file_requested++;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (strchr(file_requested, '#') != NULL) {
 | 
			
		||||
            *strchr(file_requested, '#') = '\0';
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (strchr(file_requested, '?') != NULL) {
 | 
			
		||||
            *strchr(file_requested, '?') = '\0';
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (file_requested == NULL || o_strlen(file_requested) == 0 || 0 == o_strcmp("/", file_requested)) {
 | 
			
		||||
            o_free(url_dup_save);
 | 
			
		||||
            url_dup_save = file_requested = o_strdup("index.html");
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        file_path = msprintf("%s/%s", configWeb.files_path, file_requested);
 | 
			
		||||
        real_path = realpath(file_path, NULL);
 | 
			
		||||
        if (0 == o_strncmp(configWeb.files_path, real_path, o_strlen(configWeb.files_path))) {
 | 
			
		||||
            if (access(file_path, F_OK) != -1) {
 | 
			
		||||
                f = fopen(file_path, "rb");
 | 
			
		||||
                if (f) {
 | 
			
		||||
                    fseek(f, 0, SEEK_END);
 | 
			
		||||
                    length = ftell(f);
 | 
			
		||||
                    fseek(f, 0, SEEK_SET);
 | 
			
		||||
 | 
			
		||||
                    content_type = u_map_get_case(&configWeb.mime_types, get_filename_ext(file_requested));
 | 
			
		||||
                    if (content_type == NULL) {
 | 
			
		||||
                        content_type = u_map_get(&configWeb.mime_types, "*");
 | 
			
		||||
                        LOG_DEBUG("Static File Server - Unknown mime type for extension %s \n", get_filename_ext(file_requested));
 | 
			
		||||
                    }
 | 
			
		||||
                    u_map_put(response->map_header, "Content-Type", content_type);
 | 
			
		||||
                    u_map_copy_into(response->map_header, &configWeb.map_header);
 | 
			
		||||
 | 
			
		||||
                    if (ulfius_set_stream_response(response, 200, callback_static_file_stream, callback_static_file_stream_free,
 | 
			
		||||
                                                   length, STATIC_FILE_CHUNK, f) != U_OK) {
 | 
			
		||||
                        LOG_DEBUG("callback_static_file - Error ulfius_set_stream_response\n	");
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            } else {
 | 
			
		||||
                if (configWeb.redirect_on_404 == NULL) {
 | 
			
		||||
                    ulfius_set_string_body_response(response, 404, "File not found");
 | 
			
		||||
                } else {
 | 
			
		||||
                    ulfius_add_header_to_response(response, "Location", configWeb.redirect_on_404);
 | 
			
		||||
                    response->status = 302;
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        } else {
 | 
			
		||||
            if (configWeb.redirect_on_404 == NULL) {
 | 
			
		||||
                ulfius_set_string_body_response(response, 404, "File not found");
 | 
			
		||||
            } else {
 | 
			
		||||
                ulfius_add_header_to_response(response, "Location", configWeb.redirect_on_404);
 | 
			
		||||
                response->status = 302;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        o_free(file_path);
 | 
			
		||||
        o_free(url_dup_save);
 | 
			
		||||
        free(real_path); // realpath uses malloc
 | 
			
		||||
        return U_CALLBACK_CONTINUE;
 | 
			
		||||
    } else {
 | 
			
		||||
        LOG_DEBUG("Static File Server - Error, user_data is NULL or inconsistent\n");
 | 
			
		||||
        return U_CALLBACK_ERROR;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static void handleWebResponse() {}
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
 * Adapt the radioapi to the Webservice handleAPIv1ToRadio
 | 
			
		||||
 * Trigger : WebGui(SAVE)->WebServcice->phoneApi
 | 
			
		||||
 */
 | 
			
		||||
int handleAPIv1ToRadio(const struct _u_request *req, struct _u_response *res, void *user_data)
 | 
			
		||||
{
 | 
			
		||||
    LOG_DEBUG("handleAPIv1ToRadio web -> radio  \n");
 | 
			
		||||
 | 
			
		||||
    ulfius_add_header_to_response(res, "Content-Type", "application/x-protobuf");
 | 
			
		||||
    ulfius_add_header_to_response(res, "Access-Control-Allow-Headers", "Content-Type");
 | 
			
		||||
    ulfius_add_header_to_response(res, "Access-Control-Allow-Origin", "*");
 | 
			
		||||
    ulfius_add_header_to_response(res, "Access-Control-Allow-Methods", "PUT, OPTIONS");
 | 
			
		||||
    ulfius_add_header_to_response(res, "X-Protobuf-Schema",
 | 
			
		||||
                                  "https://raw.githubusercontent.com/meshtastic/protobufs/master/mesh.proto");
 | 
			
		||||
 | 
			
		||||
    if (req->http_verb == "OPTIONS") {
 | 
			
		||||
        ulfius_set_response_properties(res, U_OPT_STATUS, 204);
 | 
			
		||||
        return U_CALLBACK_CONTINUE;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    byte buffer[MAX_TO_FROM_RADIO_SIZE];
 | 
			
		||||
    size_t s = req->binary_body_length;
 | 
			
		||||
 | 
			
		||||
    memcpy(buffer, req->binary_body, MAX_TO_FROM_RADIO_SIZE);
 | 
			
		||||
 | 
			
		||||
    // FIXME* Problem with portdunio loosing mountpoint maybe because of running in a real sep. thread
 | 
			
		||||
 | 
			
		||||
    portduinoVFS->mountpoint("/home/marc/.portduino/default");
 | 
			
		||||
 | 
			
		||||
    LOG_DEBUG("Received %d bytes from PUT request\n", s);
 | 
			
		||||
    webAPI.handleToRadio(buffer, s);
 | 
			
		||||
    LOG_DEBUG("end web->radio  \n");
 | 
			
		||||
    return U_CALLBACK_COMPLETE;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
 * Adapt the radioapi to the Webservice handleAPIv1FromRadio
 | 
			
		||||
 * Trigger : WebGui(POLL)->handleAPIv1FromRadio->phoneapi->Meshtastic(Radio) events
 | 
			
		||||
 */
 | 
			
		||||
int handleAPIv1FromRadio(const struct _u_request *req, struct _u_response *res, void *user_data)
 | 
			
		||||
{
 | 
			
		||||
 | 
			
		||||
    // LOG_DEBUG("handleAPIv1FromRadio radio -> web\n");
 | 
			
		||||
    std::string valueAll;
 | 
			
		||||
 | 
			
		||||
    // Status code is 200 OK by default.
 | 
			
		||||
    ulfius_add_header_to_response(res, "Content-Type", "application/x-protobuf");
 | 
			
		||||
    ulfius_add_header_to_response(res, "Access-Control-Allow-Origin", "*");
 | 
			
		||||
    ulfius_add_header_to_response(res, "Access-Control-Allow-Methods", "GET");
 | 
			
		||||
    ulfius_add_header_to_response(res, "X-Protobuf-Schema",
 | 
			
		||||
                                  "https://raw.githubusercontent.com/meshtastic/protobufs/master/mesh.proto");
 | 
			
		||||
 | 
			
		||||
    uint8_t txBuf[MAX_STREAM_BUF_SIZE];
 | 
			
		||||
    uint32_t len = 1;
 | 
			
		||||
 | 
			
		||||
    if (valueAll == "true") {
 | 
			
		||||
        while (len) {
 | 
			
		||||
            len = webAPI.getFromRadio(txBuf);
 | 
			
		||||
            ulfius_set_response_properties(res, U_OPT_STATUS, 200, U_OPT_BINARY_BODY, txBuf, len);
 | 
			
		||||
            const char *tmpa = (const char *)txBuf;
 | 
			
		||||
            ulfius_set_string_body_response(res, 200, tmpa);
 | 
			
		||||
            // LOG_DEBUG("\n----webAPI response all:----\n");
 | 
			
		||||
            LOG_DEBUG(tmpa);
 | 
			
		||||
            LOG_DEBUG("\n");
 | 
			
		||||
        }
 | 
			
		||||
        // Otherwise, just return one protobuf
 | 
			
		||||
    } else {
 | 
			
		||||
        len = webAPI.getFromRadio(txBuf);
 | 
			
		||||
        const char *tmpa = (const char *)txBuf;
 | 
			
		||||
        ulfius_set_binary_body_response(res, 200, tmpa, len);
 | 
			
		||||
        // LOG_DEBUG("\n----webAPI response:\n");
 | 
			
		||||
        LOG_DEBUG(tmpa);
 | 
			
		||||
        LOG_DEBUG("\n");
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // LOG_DEBUG("end radio->web\n", len);
 | 
			
		||||
    return U_CALLBACK_COMPLETE;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
OpenSSL RSA Key Gen
 | 
			
		||||
*/
 | 
			
		||||
int generate_rsa_key(EVP_PKEY **pkey)
 | 
			
		||||
{
 | 
			
		||||
    EVP_PKEY_CTX *pkey_ctx = EVP_PKEY_CTX_new_id(EVP_PKEY_RSA, NULL);
 | 
			
		||||
    if (!pkey_ctx)
 | 
			
		||||
        return -1;
 | 
			
		||||
    if (EVP_PKEY_keygen_init(pkey_ctx) <= 0)
 | 
			
		||||
        return -1;
 | 
			
		||||
    if (EVP_PKEY_CTX_set_rsa_keygen_bits(pkey_ctx, 2048) <= 0)
 | 
			
		||||
        return -1;
 | 
			
		||||
    if (EVP_PKEY_keygen(pkey_ctx, pkey) <= 0)
 | 
			
		||||
        return -1;
 | 
			
		||||
    EVP_PKEY_CTX_free(pkey_ctx);
 | 
			
		||||
    return 0; // SUCCESS
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
int generate_self_signed_x509(EVP_PKEY *pkey, X509 **x509)
 | 
			
		||||
{
 | 
			
		||||
    *x509 = X509_new();
 | 
			
		||||
    if (!*x509)
 | 
			
		||||
        return -1;
 | 
			
		||||
    if (X509_set_version(*x509, 2) != 1)
 | 
			
		||||
        return -1;
 | 
			
		||||
    ASN1_INTEGER_set(X509_get_serialNumber(*x509), 1);
 | 
			
		||||
    X509_gmtime_adj(X509_get_notBefore(*x509), 0);
 | 
			
		||||
    X509_gmtime_adj(X509_get_notAfter(*x509), 31536000L); // 1 YEAR ACCESS
 | 
			
		||||
 | 
			
		||||
    X509_set_pubkey(*x509, pkey);
 | 
			
		||||
 | 
			
		||||
    // SET Subject Name
 | 
			
		||||
    X509_NAME *name = X509_get_subject_name(*x509);
 | 
			
		||||
    X509_NAME_add_entry_by_txt(name, "C", MBSTRING_ASC, (unsigned char *)"DE", -1, -1, 0);
 | 
			
		||||
    X509_NAME_add_entry_by_txt(name, "O", MBSTRING_ASC, (unsigned char *)"Meshtastic", -1, -1, 0);
 | 
			
		||||
    X509_NAME_add_entry_by_txt(name, "CN", MBSTRING_ASC, (unsigned char *)"meshtastic.local", -1, -1, 0);
 | 
			
		||||
    // Selfsigned,  Issuer = Subject
 | 
			
		||||
    X509_set_issuer_name(*x509, name);
 | 
			
		||||
 | 
			
		||||
    // Certificate signed with our privte key
 | 
			
		||||
    if (X509_sign(*x509, pkey, EVP_sha256()) <= 0)
 | 
			
		||||
        return -1;
 | 
			
		||||
 | 
			
		||||
    return 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
char *read_file_into_string(const char *filename)
 | 
			
		||||
{
 | 
			
		||||
    FILE *file = fopen(filename, "rb");
 | 
			
		||||
    if (file == NULL) {
 | 
			
		||||
        LOG_ERROR("Error reading File : %s \n", filename);
 | 
			
		||||
        return NULL;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Size of file
 | 
			
		||||
    fseek(file, 0, SEEK_END);
 | 
			
		||||
    long filesize = ftell(file);
 | 
			
		||||
    rewind(file);
 | 
			
		||||
 | 
			
		||||
    // reserve mem for file + 1 byte
 | 
			
		||||
    char *buffer = (char *)malloc(filesize + 1);
 | 
			
		||||
    if (buffer == NULL) {
 | 
			
		||||
        LOG_ERROR("Malloc of mem failed for file : %s \n", filename);
 | 
			
		||||
        fclose(file);
 | 
			
		||||
        return NULL;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // read content
 | 
			
		||||
    size_t readSize = fread(buffer, 1, filesize, file);
 | 
			
		||||
    if (readSize != filesize) {
 | 
			
		||||
        LOG_ERROR("Error reading file into buffer\n");
 | 
			
		||||
        free(buffer);
 | 
			
		||||
        fclose(file);
 | 
			
		||||
        return NULL;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // add terminator sign at the end
 | 
			
		||||
    buffer[filesize] = '\0';
 | 
			
		||||
    fclose(file);
 | 
			
		||||
    return buffer; // return pointer
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
int PiWebServerThread::CheckSSLandLoad()
 | 
			
		||||
{
 | 
			
		||||
    // read certificate
 | 
			
		||||
    cert_pem = read_file_into_string("certificate.pem");
 | 
			
		||||
    if (cert_pem == NULL) {
 | 
			
		||||
        LOG_ERROR("ERROR SSL Certificate File can't be loaded or is missing\n");
 | 
			
		||||
        return 1;
 | 
			
		||||
    }
 | 
			
		||||
    // read private key
 | 
			
		||||
    key_pem = read_file_into_string("private_key.pem");
 | 
			
		||||
    if (key_pem == NULL) {
 | 
			
		||||
        LOG_ERROR("ERROR file private_key can't be loaded or is missing\n");
 | 
			
		||||
        return 2;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
int PiWebServerThread::CreateSSLCertificate()
 | 
			
		||||
{
 | 
			
		||||
 | 
			
		||||
    EVP_PKEY *pkey = NULL;
 | 
			
		||||
    X509 *x509 = NULL;
 | 
			
		||||
 | 
			
		||||
    if (generate_rsa_key(&pkey) != 0) {
 | 
			
		||||
        LOG_ERROR("Error generating RSA-Key.\n");
 | 
			
		||||
        return 1;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (generate_self_signed_x509(pkey, &x509) != 0) {
 | 
			
		||||
        LOG_ERROR("Error generating of X509-Certificat.\n");
 | 
			
		||||
        return 2;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Ope file to write private key file
 | 
			
		||||
    FILE *pkey_file = fopen("private_key.pem", "wb");
 | 
			
		||||
    if (!pkey_file) {
 | 
			
		||||
        LOG_ERROR("Error opening private key file.\n");
 | 
			
		||||
        return 3;
 | 
			
		||||
    }
 | 
			
		||||
    // write private key file
 | 
			
		||||
    PEM_write_PrivateKey(pkey_file, pkey, NULL, NULL, 0, NULL, NULL);
 | 
			
		||||
    fclose(pkey_file);
 | 
			
		||||
 | 
			
		||||
    // open Certificate file
 | 
			
		||||
    FILE *x509_file = fopen("certificate.pem", "wb");
 | 
			
		||||
    if (!x509_file) {
 | 
			
		||||
        LOG_ERROR("Error opening certificate.\n");
 | 
			
		||||
        return 4;
 | 
			
		||||
    }
 | 
			
		||||
    // write cirtificate
 | 
			
		||||
    PEM_write_X509(x509_file, x509);
 | 
			
		||||
    fclose(x509_file);
 | 
			
		||||
 | 
			
		||||
    EVP_PKEY_free(pkey);
 | 
			
		||||
    X509_free(x509);
 | 
			
		||||
    LOG_INFO("Create SSL Certifictate -certificate.pem- succesfull \n");
 | 
			
		||||
    return 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void initWebServer() {}
 | 
			
		||||
 | 
			
		||||
PiWebServerThread::PiWebServerThread()
 | 
			
		||||
{
 | 
			
		||||
    int ret, retssl, webservport;
 | 
			
		||||
 | 
			
		||||
    if (CheckSSLandLoad() != 0) {
 | 
			
		||||
        CreateSSLCertificate();
 | 
			
		||||
        if (CheckSSLandLoad() != 0) {
 | 
			
		||||
            LOG_ERROR("Major Error Gen & Read SSL Certificate\n");
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (settingsMap[webserverport] != 0) {
 | 
			
		||||
        webservport = settingsMap[webserverport];
 | 
			
		||||
        LOG_INFO("Using webserver port from yaml config. %i \n", webservport);
 | 
			
		||||
    } else {
 | 
			
		||||
        LOG_INFO("Webserver port in yaml config set to 0, so defaulting to port 443.\n");
 | 
			
		||||
        webservport = 443;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Web Content Service Instance
 | 
			
		||||
    if (ulfius_init_instance(&instanceWeb, webservport, NULL, DEFAULT_REALM) != U_OK) {
 | 
			
		||||
        LOG_ERROR("Webserver couldn't be started, abort execution\n");
 | 
			
		||||
    } else {
 | 
			
		||||
 | 
			
		||||
        LOG_INFO("Webserver started ....\n");
 | 
			
		||||
        u_map_init(&configWeb.mime_types);
 | 
			
		||||
        u_map_put(&configWeb.mime_types, "*", "application/octet-stream");
 | 
			
		||||
        u_map_put(&configWeb.mime_types, ".html", "text/html");
 | 
			
		||||
        u_map_put(&configWeb.mime_types, ".htm", "text/html");
 | 
			
		||||
        u_map_put(&configWeb.mime_types, ".tsx", "application/javascript");
 | 
			
		||||
        u_map_put(&configWeb.mime_types, ".ts", "application/javascript");
 | 
			
		||||
        u_map_put(&configWeb.mime_types, ".css", "text/css");
 | 
			
		||||
        u_map_put(&configWeb.mime_types, ".js", "application/javascript");
 | 
			
		||||
        u_map_put(&configWeb.mime_types, ".json", "application/json");
 | 
			
		||||
        u_map_put(&configWeb.mime_types, ".png", "image/png");
 | 
			
		||||
        u_map_put(&configWeb.mime_types, ".gif", "image/gif");
 | 
			
		||||
        u_map_put(&configWeb.mime_types, ".jpeg", "image/jpeg");
 | 
			
		||||
        u_map_put(&configWeb.mime_types, ".jpg", "image/jpeg");
 | 
			
		||||
        u_map_put(&configWeb.mime_types, ".ttf", "font/ttf");
 | 
			
		||||
        u_map_put(&configWeb.mime_types, ".woff", "font/woff");
 | 
			
		||||
        u_map_put(&configWeb.mime_types, ".ico", "image/x-icon");
 | 
			
		||||
        u_map_put(&configWeb.mime_types, ".svg", "image/svg+xml");
 | 
			
		||||
 | 
			
		||||
        webrootpath = settingsStrings[webserverrootpath];
 | 
			
		||||
 | 
			
		||||
        configWeb.files_path = (char *)webrootpath.c_str();
 | 
			
		||||
        configWeb.url_prefix = "";
 | 
			
		||||
        configWeb.rootPath = strdup(portduinoVFS->mountpoint());
 | 
			
		||||
 | 
			
		||||
        u_map_put(instanceWeb.default_headers, "Access-Control-Allow-Origin", "*");
 | 
			
		||||
        // Maximum body size sent by the client is 1 Kb
 | 
			
		||||
        instanceWeb.max_post_body_size = 1024;
 | 
			
		||||
        ulfius_add_endpoint_by_val(&instanceWeb, "GET", PREFIX, "/api/v1/fromradio/*", 1, &handleAPIv1FromRadio, NULL);
 | 
			
		||||
        ulfius_add_endpoint_by_val(&instanceWeb, "PUT", PREFIX, "/api/v1/toradio/*", 1, &handleAPIv1ToRadio, configWeb.rootPath);
 | 
			
		||||
 | 
			
		||||
        // Add callback function to all endpoints for the Web Server
 | 
			
		||||
        ulfius_add_endpoint_by_val(&instanceWeb, "GET", NULL, "/*", 2, &callback_static_file, &configWeb);
 | 
			
		||||
 | 
			
		||||
        // thats for serving without SSL
 | 
			
		||||
        // retssl = ulfius_start_framework(&instanceWeb);
 | 
			
		||||
 | 
			
		||||
        // thats for serving with SSL
 | 
			
		||||
        retssl = ulfius_start_secure_framework(&instanceWeb, key_pem, cert_pem);
 | 
			
		||||
 | 
			
		||||
        if (retssl == U_OK) {
 | 
			
		||||
            LOG_INFO("Web Server framework started on port: %i \n", webservport);
 | 
			
		||||
            LOG_INFO("Web Server root %s\n", (char *)webrootpath.c_str());
 | 
			
		||||
        } else {
 | 
			
		||||
            LOG_ERROR("Error starting Web Server framework\n");
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
PiWebServerThread::~PiWebServerThread()
 | 
			
		||||
{
 | 
			
		||||
    u_map_clean(&configWeb.mime_types);
 | 
			
		||||
 | 
			
		||||
    ulfius_stop_framework(&instanceWeb);
 | 
			
		||||
    ulfius_stop_framework(&instanceWeb);
 | 
			
		||||
    free(configWeb.rootPath);
 | 
			
		||||
    ulfius_clean_instance(&instanceService);
 | 
			
		||||
    ulfius_clean_instance(&instanceService);
 | 
			
		||||
    free(cert_pem);
 | 
			
		||||
    LOG_INFO("End framework");
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#endif
 | 
			
		||||
#endif
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,61 @@
 | 
			
		|||
#pragma once
 | 
			
		||||
#ifdef PORTDUINO_LINUX_HARDWARE
 | 
			
		||||
#if __has_include(<ulfius.h>)
 | 
			
		||||
#include "PhoneAPI.h"
 | 
			
		||||
#include "ulfius-cfg.h"
 | 
			
		||||
#include "ulfius.h"
 | 
			
		||||
#include <Arduino.h>
 | 
			
		||||
#include <functional>
 | 
			
		||||
 | 
			
		||||
#define STATIC_FILE_CHUNK 256
 | 
			
		||||
 | 
			
		||||
void initWebServer();
 | 
			
		||||
void createSSLCert();
 | 
			
		||||
int callback_static_file(const struct _u_request *request, struct _u_response *response, void *user_data);
 | 
			
		||||
const char *get_filename_ext(const char *path);
 | 
			
		||||
 | 
			
		||||
struct _file_config {
 | 
			
		||||
    char *files_path;
 | 
			
		||||
    char *url_prefix;
 | 
			
		||||
    struct _u_map mime_types;
 | 
			
		||||
    struct _u_map map_header;
 | 
			
		||||
    char *redirect_on_404;
 | 
			
		||||
    char *rootPath;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
class PiWebServerThread
 | 
			
		||||
{
 | 
			
		||||
  private:
 | 
			
		||||
    char *key_pem = NULL;
 | 
			
		||||
    char *cert_pem = NULL;
 | 
			
		||||
    // struct _u_map mime_types;
 | 
			
		||||
    std::string webrootpath;
 | 
			
		||||
 | 
			
		||||
  public:
 | 
			
		||||
    PiWebServerThread();
 | 
			
		||||
    ~PiWebServerThread();
 | 
			
		||||
    int CreateSSLCertificate();
 | 
			
		||||
    int CheckSSLandLoad();
 | 
			
		||||
    uint32_t requestRestart = 0;
 | 
			
		||||
    struct _u_instance instanceWeb;
 | 
			
		||||
    struct _u_instance instanceService;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
class HttpAPI : public PhoneAPI
 | 
			
		||||
{
 | 
			
		||||
 | 
			
		||||
  public:
 | 
			
		||||
    // Nothing here yet
 | 
			
		||||
 | 
			
		||||
  private:
 | 
			
		||||
    // Nothing here yet
 | 
			
		||||
 | 
			
		||||
  protected:
 | 
			
		||||
    /// Check the current underlying physical link to see if the client is currently connected
 | 
			
		||||
    virtual bool checkIsConnected() override { return true; } // FIXME, be smarter about this
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
extern PiWebServerThread *piwebServerThread;
 | 
			
		||||
 | 
			
		||||
#endif
 | 
			
		||||
#endif
 | 
			
		||||
| 
						 | 
				
			
			@ -195,6 +195,11 @@ void portduinoSetup()
 | 
			
		|||
            settingsStrings[keyboardDevice] = (yamlConfig["Input"]["KeyboardDevice"]).as<std::string>("");
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (yamlConfig["Webserver"]) {
 | 
			
		||||
            settingsMap[webserverport] = (yamlConfig["Webserver"]["Port"]).as<int>(-1);
 | 
			
		||||
            settingsStrings[webserverrootpath] = (yamlConfig["Webserver"]["RootPath"]).as<std::string>("");
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
    } catch (YAML::Exception e) {
 | 
			
		||||
        std::cout << "*** Exception " << e.what() << std::endl;
 | 
			
		||||
        exit(EXIT_FAILURE);
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -33,7 +33,10 @@ enum configNames {
 | 
			
		|||
    displayOffsetY,
 | 
			
		||||
    displayInvert,
 | 
			
		||||
    keyboardDevice,
 | 
			
		||||
    logoutputlevel
 | 
			
		||||
    logoutputlevel,
 | 
			
		||||
    webserver,
 | 
			
		||||
    webserverport,
 | 
			
		||||
    webserverrootpath
 | 
			
		||||
};
 | 
			
		||||
enum { no_screen, st7789, st7735, st7735s, ili9341 };
 | 
			
		||||
enum { no_touchscreen, xpt2046, stmpe610 };
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,6 +1,10 @@
 | 
			
		|||
[env:native]
 | 
			
		||||
extends = portduino_base
 | 
			
		||||
build_flags = ${portduino_base.build_flags} -O0 -I variants/portduino
 | 
			
		||||
; The pkg-config commands below optionally add link flags.
 | 
			
		||||
; the || : is just a "or run the null command" to avoid returning an error code
 | 
			
		||||
build_flags = ${portduino_base.build_flags} -O0 -I variants/portduino -I /usr/include
 | 
			
		||||
  !pkg-config --libs libulfius --silence-errors || :
 | 
			
		||||
  !pkg-config --libs openssl --silence-errors || :
 | 
			
		||||
board = cross_platform
 | 
			
		||||
lib_deps = ${portduino_base.lib_deps}
 | 
			
		||||
build_src_filter = ${portduino_base.build_src_filter}
 | 
			
		||||
build_src_filter = ${portduino_base.build_src_filter}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Ładowanie…
	
		Reference in New Issue