diff --git a/src/vscp/common/vscp.h b/src/vscp/common/vscp.h index d3a28c480..ce01194f4 100644 --- a/src/vscp/common/vscp.h +++ b/src/vscp/common/vscp.h @@ -648,6 +648,7 @@ struct vscpMyNode { #define VSCP_ERROR_PARSING 62 /* Failed to parse input */ #define VSCP_ERROR_INVALID_FRAME 63 /* A protocol has wrong format */ #define VSCP_ERROR_SIZE 64 /* The size is wring */ +#define VSCP_ERROR_NACK 65 /* NACK received */ /*! HLO (High Level Object) type (bits 7,6,5,4) diff --git a/src/vscp/common/vscp_bootdevice.h b/src/vscp/common/vscp_bootdevice.h index efca6a63f..6d162d302 100644 --- a/src/vscp/common/vscp_bootdevice.h +++ b/src/vscp/common/vscp_bootdevice.h @@ -53,12 +53,12 @@ An abstract memory model is used for the firmware. In this model memory contents of different types is placed into - different memory regions. When a block of datra from that region - is sent to the remote device it is up to that device to - place the data at the correct location. This model can be used - for code, ram, eeprom, fuses, config data etc. The pic1 bootloader + different memory regions. When a block of datra from that region + is sent to the remote device it is up to that device to + place the data at the correct location. This model can be used + for code, ram, eeprom, fuses, config data etc. The pic1 bootloader use the Microchip abstract memory model. The VSCP bootloader let - the receiving end take the decision on where a block should be written + the receiving end take the decision on where a block should be written Sequency of use ---------------- @@ -125,8 +125,8 @@ class CBootDevice { */ CBootDevice(CVscpClient *pclient, uint16_t nodeid, - std::function statusCallback = nullptr, - uint32_t timeout = REGISTER_DEFAULT_TIMEOUT); + std::function statusCallback = nullptr, + uint32_t timeout = REGISTER_DEFAULT_TIMEOUT); /*! Constructor @@ -140,8 +140,8 @@ class CBootDevice { CBootDevice(CVscpClient *pclient, uint16_t nodeid, cguid &guidif, - std::function statusCallback = nullptr, - uint32_t timeout = REGISTER_DEFAULT_TIMEOUT); + std::function statusCallback = nullptr, + uint32_t timeout = REGISTER_DEFAULT_TIMEOUT); /*! Constructor @@ -153,8 +153,8 @@ class CBootDevice { */ CBootDevice(CVscpClient *pclient, cguid &guid, - std::function statusCallback = nullptr, - uint32_t timeout = REGISTER_DEFAULT_TIMEOUT); + std::function statusCallback = nullptr, + uint32_t timeout = REGISTER_DEFAULT_TIMEOUT); /*! Destructor @@ -162,10 +162,10 @@ class CBootDevice { virtual ~CBootDevice(void); /// Timeout for response - static const uint16_t BOOT_COMMAND_DEFAULT_RESPONSE_TIMEOUT = 5000; + static const uint16_t BOOT_COMMAND_DEFAULT_RESPONSE_TIMEOUT = 5; /// Max size for a programming block - static const uint32_t BOOT_MAX_BLOCK_SIZE = (1024*1024*2); + static const uint32_t BOOT_MAX_BLOCK_SIZE = (1024 * 1024 * 2); /// Hexfiles type enum type_hex_file { @@ -201,10 +201,10 @@ class CBootDevice { @param end Stop address for memory range (inclusive - end is part of address space) @param pmin Pointer to variable that will get min address @param pmax Pointer to variable that will get max address - @return VSCP_ERROR_SUCCESS on success. + @return VSCP_ERROR_SUCCESS on success. VSCP_ERROR_INVALID_POINTER if ponters are invalid. VSCP_ERROR_INVALID_PARAMETER if end <= start - VSCP_ERROR_ERROR if no memory defined in the selected range. value of + VSCP_ERROR_ERROR if no memory defined in the selected range. value of pmin/pmax is zero in this case. */ virtual int getMinMaxForRange(uint32_t start, uint32_t end, uint32_t *pmin, uint32_t *pmax); @@ -227,11 +227,13 @@ class CBootDevice { /*! Set a device in bootmode + @param ourguid GUID for local device + @param devicecode This is the device code expected by the user @param bAbortOnFirmwareCodeFail Set to true to fail if firmware code fetched from MDF is not the same as the one read from the remote device. @return VSCP_ERROR_SUCCESS on success. */ - virtual int deviceInit(bool bAbortOnFirmwareCodeFail = false) = 0; + virtual int deviceInit(cguid &ourguid, uint8_t devicecode, bool bAbortOnFirmwareCodeFail = false) = 0; /*! Perform the actual firmware load process @@ -264,7 +266,7 @@ class CBootDevice { int is percentage, str is real text description */ - std::function m_statusCallback; + std::function m_statusCallback; /*! The device code tell the type of hardware of the remote device @@ -275,6 +277,9 @@ class CBootDevice { */ uint8_t m_firmwaredeviceCode; + /// Our local GUID + cguid m_ourguid; + /// node id for device uint8_t m_nodeid; @@ -294,9 +299,6 @@ class CBootDevice { // This is the minimum code address (startvector) uint32_t m_startAddr; - /// # data bytes in file - uint32_t m_totalCntData; - /// Standard registers CStandardRegisters m_stdRegs; diff --git a/src/vscp/common/vscp_bootdevice_pic1.cpp b/src/vscp/common/vscp_bootdevice_pic1.cpp index 46c501ff5..19e8a6dc7 100644 --- a/src/vscp/common/vscp_bootdevice_pic1.cpp +++ b/src/vscp/common/vscp_bootdevice_pic1.cpp @@ -224,10 +224,13 @@ CBootDevice_PIC1::deviceInfo(void) // int -CBootDevice_PIC1::deviceInit(bool bAbortOnFirmwareCodeFail) +CBootDevice_PIC1::deviceInit(cguid& ourguid, uint8_t devicecode, bool bAbortOnFirmwareCodeFail) { int rv; + // Save our local GUID + m_ourguid = ourguid; + /* First do a test to see if the device is already in boot mode if it is 0x14nn/0x15nn should be returned (nn == nodeid). @@ -339,7 +342,7 @@ CBootDevice_PIC1::deviceInit(bool bAbortOnFirmwareCodeFail) m_firmwaredeviceCode, m_stdRegs.getFirmwareDeviceCode()); if (nullptr != m_statusCallback) { - m_statusCallback(-1, "Firware device code is not equal the one on the device local: {0} device: {1}"); + m_statusCallback(-1, "Firmware device code is not equal the one on the device local: {0} device: {1}"); } if (bAbortOnFirmwareCodeFail) { return VSCP_ERROR_PARAMETER; @@ -378,7 +381,7 @@ CBootDevice_PIC1::deviceInit(bool bAbortOnFirmwareCodeFail) while (bRun) { time(&tnow); - if ((unsigned long) (tnow - tstart) > PIC_BOOTLOADER_RESPONSE_TIMEOUT) { + if ((unsigned long) (tnow - tstart) > BOOT_COMMAND_DEFAULT_RESPONSE_TIMEOUT) { bRun = false; } @@ -639,7 +642,7 @@ CBootDevice_PIC1::checkResponseLevel1(uint32_t response_id) // check for timeout time(&tnow); - if ((unsigned long) (tnow - tstart) > PIC_BOOTLOADER_RESPONSE_TIMEOUT) { + if ((unsigned long) (tnow - tstart) > BOOT_COMMAND_DEFAULT_RESPONSE_TIMEOUT) { bRun = false; // End } } @@ -685,7 +688,7 @@ CBootDevice_PIC1::checkResponseLevel2(uint32_t id) // check for timeout time(&tnow); - if ((tnow - tstart) > PIC_BOOTLOADER_RESPONSE_TIMEOUT) { + if ((tnow - tstart) > BOOT_COMMAND_DEFAULT_RESPONSE_TIMEOUT) { bRun = false; // abort timeout } } diff --git a/src/vscp/common/vscp_bootdevice_pic1.h b/src/vscp/common/vscp_bootdevice_pic1.h index 3ed3c9f0b..2c1975f4e 100644 --- a/src/vscp/common/vscp_bootdevice_pic1.h +++ b/src/vscp/common/vscp_bootdevice_pic1.h @@ -94,8 +94,6 @@ class CBootDevice_PIC1 : public CBootDevice { static const uint8_t VSCP_PIC1_WRITE_REGISTER = 0x0B; static const uint8_t VSCP_PIC1_ENTER_BOOTLODER_MODE = 0x0C; - static const uint8_t PIC_BOOTLOADER_RESPONSE_TIMEOUT = 5; - // Flash memory static const uint32_t MEM_CODE_START = 0x000000; static const uint32_t MEM_CODE_END = 0x1fffff; @@ -129,8 +127,7 @@ class CBootDevice_PIC1 : public CBootDevice { MEM_TYPE_USERID // 0x200000 }; - // Default timeout for response - static const uint8_t BOOT_COMMAND_RESPONSE_TIMEOUT = 5; + // flags // CONTROL is defined as follows @@ -183,11 +180,13 @@ class CBootDevice_PIC1 : public CBootDevice { /*! Initialize boot mode, ie set device in bootmode + @param outguid Our local GUID + @param devicecode Device code user expect device tro have @param bAbortOnFirmwareCodeFail Set to true to fail if firmware code fetched from MDF is not the same as the one read from the remote device. @return VSCP_ERROR_SUCCESS on success. */ - int deviceInit(bool bAbortOnFirmwareCodeFail = false); + int deviceInit(cguid& ourguid, uint8_t devicecode, bool bAbortOnFirmwareCodeFail = false); /*! Write a boot block to the device diff --git a/src/vscp/common/vscp_bootdevice_vscp.cpp b/src/vscp/common/vscp_bootdevice_vscp.cpp index d9447b976..3a3990a03 100644 --- a/src/vscp/common/vscp_bootdevice_vscp.cpp +++ b/src/vscp/common/vscp_bootdevice_vscp.cpp @@ -1,14 +1,6 @@ ///////////////////////////////////////////////////////////////////////////// -// Name: bootdevice_vscp.cpp -// Purpose: -// Author: Ake Hedman -// Modified by: -// Created: 16/12/2009 22:26:09 -// RCS-ID: -// Copyright: (C) 2007-2024 -// Ake Hedman, the VSCP project, -// (C) 2012 Dinesh Guleria -// Licence: +// bootdevice_vscp.cpp +// // 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 @@ -16,29 +8,20 @@ // // This file is part of the VSCP (https://www.vscp.org) // +// Copyright: (C) 2000-2024 +// Ake Hedman, the VSCP project, +// // This file 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. +// 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 file see the file COPYING. If not, write to // the Free Software Foundation, 59 Temple Place - Suite 330, -// Boston, MA 02111-1307, USA. +// Boston, MA 02111-1307, USA. // -// As a special exception, if other files instantiate templates or use macros -// or inline functions from this file, or you compile this file and link it -// with other works to produce a work based on this file, this file does not -// by itself cause the resulting work to be covered by the GNU General Public -// License. However the source code for this file must still be made available -// in accordance with section (3) of the GNU General Public License. // -// This exception does not invalidate any other reasons why a work based on -// this file might be covered by the GNU General Public License. -// -// Alternative licenses for VSCP & Friends may be arranged by contacting -// the VSCP project at info@vscp.org, https://www.vscp.org -/// #if defined(__GNUG__) && !defined(NO_GCC_PRAGMA) #pragma implementation "vscp_bootdevice_vscp.h" @@ -49,1120 +32,553 @@ #endif #include "vscp_bootdevice_vscp.h" -#include "vscp_bootdevice_vscp_defs.h" + #include #define CRC16 #include -CBootDevice_vscp::CBootDevice_vscp(CDllWrapper *pdll, +#include +#include +#include +#include + +CBootDevice_VSCP::CBootDevice_VSCP(CVscpClient *pclient, uint8_t nodeid, - bool bDeviceFound) - : CBootDevice(pdll, nodeid, bDeviceFound) + std::function statusCallback, + uint32_t timeout) + : CBootDevice(pclient, nodeid, statusCallback, timeout) { - init(); + m_chunkSize = 8; + init(); } -CBootDevice_vscp::CBootDevice_vscp(VscpRemoteTcpIf *ptcpip, - cguid &guid, - cguid &ifguid, - bool bDeviceFound) - : CBootDevice(ptcpip, guid, ifguid, bDeviceFound) +CBootDevice_VSCP::CBootDevice_VSCP(CVscpClient *pclient, + uint8_t nodeid, + cguid &guidif, + std::function statusCallback, + uint32_t timeout) + : CBootDevice(pclient, nodeid, guidif, statusCallback, timeout) { - init(); + m_chunkSize = 8; + init(); } -CBootDevice_vscp::~CBootDevice_vscp(void) +CBootDevice_VSCP::CBootDevice_VSCP(CVscpClient *pclient, + cguid &guid, + std::function statusCallback, + uint32_t timeout) + : CBootDevice(pclient, guid, statusCallback, timeout) { - ; + m_chunkSize = 512; + init(); } /////////////////////////////////////////////////////////////////////////////// -// init +// DTOR // -void -CBootDevice_vscp::init(void) +CBootDevice_VSCP::~CBootDevice_VSCP(void) { - // Create buffers - m_pbufPrg = new unsigned char[BUFFER_SIZE_PROGRAM_COMMON]; - m_pAddr = 0; - crcInit(); + ; } /////////////////////////////////////////////////////////////////////////////// -// loadBinaryFile +// init // -bool -CBootDevice_vscp::loadBinaryFile(const wxString &path, uint16_t type) +void +CBootDevice_VSCP::init(void) { - unsigned long i; - bool rv = false; -#ifdef WIN32 - errno_t err; -#else - long err; -#endif - unsigned long fullAddr = 0; - unsigned long highAddr = 0; - unsigned long lowAddr = 0; - unsigned long cntData = 0; - unsigned long recType = 0; - - FILE *fs = NULL; - - // Init. program memory pointers - m_minFlashAddr = 0xffffffff; - m_maxFlashAddr = 0; - m_totalCntData = 0; - m_bPrgData = false; - m_bFlashMemory = true; // Program memory should be programmed - - // Init program memory buffer - if (NULL == m_pbufPrg) return false; - - memset(m_pbufPrg, 0xff, BUFFER_SIZE_PROGRAM_COMMON); - -#ifdef WIN32 - if (0 != (err = fopen_s(&fs, path.char_str(), "r"))) { - return false; - } -#else - if (NULL == (fs = fopen(path.char_str(), "r"))) { - return false; - } -#endif - - char szLine[MAX_PATH]; - char szData[16]; - char *endptr; - - bool bRun = true; - - while ((true == bRun) && (NULL != fgets(szLine, MAX_PATH, fs))) { - - if (':' == szLine[0]) { - - // Get data count - memset(szData, 0, 16); -#ifdef WIN32 - strncpy_s(szData, 16, (szLine + 1), 2); -#else - strncpy(szData, (szLine + 1), 2); -#endif - cntData = strtoul(szData, &endptr, 16); - m_totalCntData += cntData; - - // Get address - memset(szData, 0, 16); -#ifdef WIN32 - strncpy_s(szData, 16, (szLine + 3), 4); -#else - strncpy(szData, (szLine + 3), 4); -#endif - lowAddr = strtoul(szData, &endptr, 16); - - // Get record type - memset(szData, 0, 16); -#ifdef WIN32 - strncpy_s(szData, 16, (szLine + 7), 2); -#else - strncpy(szData, (szLine + 7), 2); -#endif - - recType = strtoul(szData, &endptr, 16); - - fullAddr = (highAddr * 0xffff) + lowAddr; - - // Decode the record type - switch (recType) { - - case INTEL_LINETYPE_DATA: - - for (i = 0; i < cntData; i++) { - - memset(szData, 0, 16); -#ifdef WIN32 - strncpy_s(szData, 16, (szLine + ((i * 2) + 9)), 2); -#else - strncpy(szData, (szLine + ((i * 2) + 9)), 2); -#endif - unsigned char val = - (unsigned char)(strtoul(szData, &endptr, 16) & 0xff); - - /* In program memory address space? */ - if ((fullAddr >= MEMREG_PRG_START_COMMON) && - (fullAddr <= MEMREG_PRG_END_COMMON)) { - - /* Avoid program memory buffer overflow. */ - if ((fullAddr - MEMREG_PRG_START_COMMON) < - BUFFER_SIZE_PROGRAM_COMMON) { - // Write into program memory buffer - m_pbufPrg[fullAddr - MEMREG_PRG_START_COMMON] = - val; - m_bPrgData = true; - - // Set min flash address - if (fullAddr < m_minFlashAddr) - m_minFlashAddr = fullAddr; - - // Set max flash address - if (fullAddr > m_maxFlashAddr) - m_maxFlashAddr = fullAddr; - } - } - - ++fullAddr; - } - break; - - case INTEL_LINETYPE_EOF: - bRun = false; // We are done - rv = true; - break; - - case INTEL_LINETYPE_EXTENDED_SEGMENT: - // We don't handle this - break; - - case INTEL_LINETYPE_EXTENDED_LINEAR: - memset(szData, 0, 16); -#ifdef WIN32 - strncpy_s(szData, 16, (szLine + 9), 4); -#else - strncpy(szData, (szLine + 9), 4); -#endif - highAddr = strtoul(szData, &endptr, 16); - break; - } - } - } - - // Flash to program if none read - if (!m_bPrgData) { - m_bFlashMemory = false; - m_minFlashAddr = 0x00000000; - } - - fclose(fs); - - return rv; + m_pAddr = 0; + m_numBlocks = 0; + m_blockSize = 0; + crcInit(); } /////////////////////////////////////////////////////////////////////////////// -// showInfo +// deviceInfo // -void -CBootDevice_vscp::showInfo(wxHtmlWindow *phtmlWnd) +std::string +CBootDevice_VSCP::deviceInfo(void) { - wxString strInfo; - - // Check pointer - if (NULL == phtmlWnd) return; - - // Clear HTML - phtmlWnd->SetPage(_("")); - - // * * * Flash Memory * * * - - phtmlWnd->AppendToPage(_("Flash Memory
")); - - phtmlWnd->AppendToPage(_("Start :")); - - strInfo.Printf(_("0x%08X"), m_minFlashAddr); - phtmlWnd->AppendToPage(strInfo); - - phtmlWnd->AppendToPage(_(" End :")); - - strInfo.Printf(_("0x%08X
"), m_maxFlashAddr); - phtmlWnd->AppendToPage(strInfo); - - if (m_bFlashMemory) { - phtmlWnd->AppendToPage( - _("Will be programmed
")); - } else { - phtmlWnd->AppendToPage( - _("Will not be programmed
")); - } - - phtmlWnd->AppendToPage(_("

")); + std::string str; + std::string strInfo; + std::ostringstream oss; + + uint32_t min; + uint32_t max; + + // * * * Device * * * + oss << "Device
"; + oss << "nodeid :"; + oss << m_nodeid; + oss << "
"; + oss << "GUID :"; + oss << m_guid.toString(); + oss << "
"; + oss << "Interface :"; + oss << m_guidif.toString(); + oss << "
"; + + // * * * Flash Memory * * * + getMinMaxForRange(MEM_CODE_START, MEM_CODE_END, &min, &max); + oss << "Flash Memory
"; + oss << "Start :"; + oss << std::hex << std::setw(8) << std::setfill('0') << min; + oss << " End :"; + oss << std::hex << std::setw(8) << std::setfill('0') << max; + if (max > min) { + oss << "Will be programmed
"; + } + else { + oss << "Will not be programmed
"; + } + oss << "

"; + + // * * * UserID Memory * * * + getMinMaxForRange(MEM_USERID_START, MEM_CODE_END, &min, &max); + oss << "UserID Memory
"; + oss << "Start :"; + + oss << ""; + oss << std::hex << std::setw(8) << std::setfill('0') << min << ""; + + oss << " End :"; + + oss << ""; + oss << std::hex << std::setw(8) << std::setfill('0') << max << ""; + + if (max > min) { + oss << "Will be programmed
"; + } + else { + oss << "Will not be programmed
"; + } + oss << "

"; + + // * * * Config Memory * * * + getMinMaxForRange(MEM_CONFIG_START, MEM_CONFIG_END, &min, &max); + oss << "Config Memory
"; + oss << "Start :"; + + oss << ""; + oss << std::hex << std::setw(8) << std::setfill('0') << min << ""; + + oss << " End :"; + + oss << ""; + oss << std::hex << std::setw(8) << std::setfill('0') << max << ""; + + if (max > min) { + oss << "Will be programmed
"; + } + else { + oss << "Will not be programmed
"; + } + oss << "

"; + + // * * * EEPROM * * * + getMinMaxForRange(MEM_EEPROM_START, MEM_EEPROM_END, &min, &max); + oss << "EEPROM Memory
"; + oss << "Start :"; + + oss << ""; + oss << std::hex << std::setw(8) << std::setfill('0') << min << ""; + oss << " End :"; + + oss << ""; + oss << std::hex << std::setw(8) << std::setfill('0') << PRIdMAX << ""; + + if (max > min) { + oss << "Will be programmed
"; + } + else { + oss << "Will not be programmed
"; + } + oss << "

"; + + return oss.str(); } /////////////////////////////////////////////////////////////////////////////// -// setDeviceInBootMode +// deviceInit // -bool -CBootDevice_vscp::setDeviceInBootMode(void) +int +CBootDevice_VSCP::deviceInit(cguid &ourguid, uint8_t devicecode, bool bAbortOnFirmwareCodeFail) { - bool bRun; - - uint8_t pageSelectMsb = 0; - uint8_t pageSelectLsb = 0; - uint8_t guid0 = 0; - uint8_t guid3 = 0; - uint8_t guid5 = 0; - uint8_t guid7 = 0; - - uint16_t vscpclass; - uint8_t vscptype; - uint8_t priority = 0; - - wxBusyCursor busy; - - if (USE_DLL_INTERFACE == m_type) { - - canalMsg msg, rcvmsg; - time_t tstart, tnow; - - memset(msg.data, 0x00, 8); - - // Read page register Page select MSB - if (CANAL_ERROR_SUCCESS != - m_pdll->readLevel1Register( - m_nodeid, 0, VSCP_REG_PAGE_SELECT_MSB, &pageSelectMsb)) { - return false; - } - - // Read page register page select lsb - if (CANAL_ERROR_SUCCESS != - m_pdll->readLevel1Register( - m_nodeid, 0, VSCP_REG_PAGE_SELECT_LSB, &pageSelectLsb)) { - return false; - } - - // Read page register GUID0 - if (CANAL_ERROR_SUCCESS != - m_pdll->readLevel1Register(m_nodeid, 0, VSCP_REG_GUID0, &guid0)) { - return false; - } - - // Read page register GUID3 - if (CANAL_ERROR_SUCCESS != - m_pdll->readLevel1Register(m_nodeid, 0, VSCP_REG_GUID3, &guid3)) { - return false; - } - - // Read page register GUID5 - if (CANAL_ERROR_SUCCESS != - m_pdll->readLevel1Register(m_nodeid, 0, VSCP_REG_GUID5, &guid5)) { - return false; - } - - // Read page register GUID7 - if (CANAL_ERROR_SUCCESS != - m_pdll->readLevel1Register(m_nodeid, 0, VSCP_REG_GUID7, &guid7)) { - return false; - } - - vscpclass = VSCP_CLASS1_PROTOCOL; - vscptype = VSCP_ENTER_BOOTLODER_MODE; - priority = VSCP_PRIORITY_LOW_COMMON; - - // Set device in boot mode - msg.data[0] = m_nodeid; // Nickname to read register from - msg.data[1] = - VSCP_BOOTLOADER_VSCP; // VSCP standard bootloader algorithm - msg.data[2] = guid0; - msg.data[3] = guid3; - msg.data[4] = guid5; - msg.data[5] = guid7; - msg.data[6] = pageSelectMsb; - msg.data[7] = pageSelectLsb; - - // Send message - msg.id = ((uint32_t)priority << 26) | ((uint32_t)vscpclass << 16) | - ((uint32_t)vscptype << 8) | - m_nodeid; // nodeaddress (our address) - - msg.flags = CANAL_IDFLAG_EXTENDED; - msg.sizeData = 8; - if (CANAL_ERROR_SUCCESS == m_pdll->doCmdSend(&msg)) { - - bRun = true; - - // Get start time - time(&tstart); - - while (bRun) { - - time(&tnow); - if ((unsigned long)(tnow - tstart) > - BOOT_COMMAND_RESPONSE_TIMEOUT) { - bRun = false; - } - - if (m_pdll->doCmdDataAvailable()) { - - m_pdll->doCmdReceive(&rcvmsg); - - vscpclass = VSCP_CLASS1_PROTOCOL; - vscptype = VSCP_TYPE_PROTOCOL_ACK_BOOT_LOADER; - - if ((uint32_t)(rcvmsg.id & 0x01ffffff) == - (uint32_t)(((uint32_t)vscpclass << 16) | - ((uint32_t)vscptype << 8) | m_nodeid)) { - - // OK in bootmode - return - m_blockSize = ((uint32_t)rcvmsg.data[0] << 24) | - ((uint32_t)rcvmsg.data[1] << 16) | - ((uint32_t)rcvmsg.data[2] << 8) | - ((uint32_t)rcvmsg.data[3] << 0); - - m_numBlocks = ((uint32_t)rcvmsg.data[4] << 24) | - ((uint32_t)rcvmsg.data[5] << 16) | - ((uint32_t)rcvmsg.data[6] << 8) | - ((uint32_t)rcvmsg.data[7] << 0); - - return true; - } - } - } - } - - } else if (USE_TCPIP_INTERFACE == m_type) { - - vscpEventEx event; - time_t tstart, tnow; - - // Read page register MSB - if (VSCP_ERROR_SUCCESS != - m_ptcpip->readLevel2Register( - VSCP_REG_PAGE_SELECT_MSB, 0, &pageSelectMsb, m_ifguid, &m_guid)) { - return false; - } - - // Read page register LSB - if (VSCP_ERROR_SUCCESS != - m_ptcpip->readLevel2Register( - VSCP_REG_PAGE_SELECT_LSB, 0, &pageSelectLsb, m_ifguid, &m_guid)) { - return false; - } - - // Read GUID0 - if (VSCP_ERROR_SUCCESS != - m_ptcpip->readLevel2Register( - VSCP_REG_GUID0, 0, &guid0, m_ifguid, &m_guid)) { - return false; - } - - // Read GUID3 - if (VSCP_ERROR_SUCCESS != - m_ptcpip->readLevel2Register( - VSCP_REG_GUID3, 0, &guid3, m_ifguid, &m_guid)) { - return false; - } - - // Read GUID5 - if (VSCP_ERROR_SUCCESS != - m_ptcpip->readLevel2Register( - VSCP_REG_GUID5, 0, &guid5, m_ifguid, &m_guid)) { - return false; - } - - // Read GUID7 - if (VSCP_ERROR_SUCCESS != - m_ptcpip->readLevel2Register( - VSCP_REG_GUID7, 0, &guid7, m_ifguid, &m_guid)) { - return false; - } - - // Set device in boot mode - - // Send message - event.head = 0; - event.vscp_class = 512; // CLASS2.PROTOCOL1 - event.vscp_type = - VSCP_ENTER_BOOTLODER_MODE; // We want to enter bootloader mode - memset(event.GUID, 0, 16); // We use interface GUID - event.sizeData = 16 + 8; // Interface GUID - memset(event.data, 0, sizeof(event.data)); - memcpy(event.data, m_ifguid.m_id, 16); // Address node i/f - event.data[16] = m_guid.getLSB(); // Nickname for device - event.data[17] = - VSCP_BOOTLOADER_VSCP; // VSCP standard bootloader algorithm - event.data[18] = guid0; - event.data[19] = guid3; - event.data[20] = guid5; - event.data[21] = guid7; - event.data[22] = pageSelectMsb; - event.data[23] = pageSelectLsb; - - if (VSCP_ERROR_SUCCESS == m_ptcpip->doCmdSendEx(&event)) { - - bRun = true; - - // Get start time - time(&tstart); - - while (bRun) { - - time(&tnow); - if ((unsigned long)(tnow - tstart) > - BOOT_COMMAND_RESPONSE_TIMEOUT) { - bRun = false; - } - - // vscpEventEx rcvmsg; - if (m_ptcpip->doCmdDataAvailable()) { - - m_ptcpip->doCmdReceiveEx(&event); - - // Check for response --- Type = 13 (0x0D) ACK boot loader - // mode. - if (VSCP_TYPE_PROTOCOL_ACK_BOOT_LOADER == event.vscp_type) { - // OK in bootmode - return - - m_blockSize = ((uint32_t)event.data[0] << 24) | - ((uint32_t)event.data[1] << 16) | - ((uint32_t)event.data[2] << 8) | - ((uint32_t)event.data[3] << 0); - - m_numBlocks = ((uint32_t)event.data[4] << 24) | - ((uint32_t)event.data[5] << 16) | - ((uint32_t)event.data[6] << 8) | - ((uint32_t)event.data[7] << 0); - - return true; - } - } - } - } + int rv; + vscpEventEx ex; + + // Read standard registers + if (VSCP_ERROR_SUCCESS != m_stdRegs.init(*m_pclient, m_guid, m_guidif, nullptr, REGISTER_DEFAULT_TIMEOUT)) { + spdlog::error("VSCP bootloader: Failed to read standard registers"); + return VSCP_ERROR_COMMUNICATION; + } + + /* + Check that the remote device expect the firmware load type + we offer + */ + if (VSCP_BOOTLOADER_VSCP != m_stdRegs.getBootloaderAlgorithm()) { + spdlog::error("VSCP bootloader: Bootloader algorithm is not Microchip PIC1"); + if (nullptr != m_statusCallback) { + m_statusCallback(-1, "Bootloader algorithm is not Microchip PIC1"); } + return VSCP_ERROR_NOT_SUPPORTED; + } + + /* + The device code tell the type of hardware of the remote device + Must be the same as the firmware we try to load is intended for + */ + if (m_firmwaredeviceCode != m_stdRegs.getFirmwareDeviceCode()) { + spdlog::warn("Firware device code is not equal the one on the device local: {0} device: {1}", + m_firmwaredeviceCode, + m_stdRegs.getFirmwareDeviceCode()); + if (nullptr != m_statusCallback) { + m_statusCallback(-1, "Firware device code is not equal the one on the device local: {0} device: {1}"); + } + if (bAbortOnFirmwareCodeFail) { + return VSCP_ERROR_PARAMETER; + } + } + + // Set device in boot mode + ex.vscp_class = VSCP_CLASS1_PROTOCOL; + ex.vscp_type = VSCP_TYPE_PROTOCOL_ENTER_BOOT_LOADER; + ex.timestamp = vscp_makeTimeStamp(); + vscp_setEventExDateTimeBlockToNow(&ex); + + cguid node_guid; + m_stdRegs.getGUID(node_guid); + + ex.sizeData = 8; + ex.data[0] = m_nodeid; // Nickname to read register from + ex.data[1] = VSCP_BOOTLOADER_VSCP; // VSCP bootloader algorithm + ex.data[2] = node_guid.getAt(0); + ex.data[3] = node_guid.getAt(3); + ex.data[4] = node_guid.getAt(5); + ex.data[5] = node_guid.getAt(7); + ex.data[6] = m_stdRegs.getRegisterPage() >> 8; + ex.data[7] = m_stdRegs.getRegisterPage() & 0xff; + + if (VSCP_ERROR_SUCCESS != (rv = m_pclient->send(ex))) { + spdlog::error("VSCP bootloader: Failed to send enter bootloader event {0}", rv); + if (nullptr != m_statusCallback) { + m_statusCallback(-1, "VSCP bootloader: Failed to send enter bootloader event"); + } + return rv; + } + + if (VSCP_ERROR_SUCCESS != + (rv = checkResponse(ex, node_guid, VSCP_TYPE_PROTOCOL_ACK_BOOT_LOADER, VSCP_TYPE_PROTOCOL_NACK_BOOT_LOADER))) { + // Negative response on bootmode request- return + spdlog::debug("VSCP bootloader: NACK recived from set bootloader request."); + if (nullptr != m_statusCallback) { + m_statusCallback(-1, "VSCP bootloader: NACK received from set bootloader request."); + } + return rv; + } + + // OK now in bootmode - return + m_blockSize = construct_unsigned32(ex.data[0], ex.data[1], ex.data[2], ex.data[3]); + m_numBlocks = construct_unsigned32(ex.data[4], ex.data[5], ex.data[6], ex.data[7]); + spdlog::debug("VSCP bootloader: Confirmed, device is in boot mood."); + if (nullptr != m_statusCallback) { + m_statusCallback(-1, "VSCP bootloader: Confirmed, device is in boot mood."); + } + + // chunk must be <= blocksize + if (m_chunkSize > m_blockSize) { + spdlog::debug("VSCP bootloader: chunk size is larger than block size. chunksz={0} blocksz={1}", + m_chunkSize, + m_blockSize); + if (nullptr != m_statusCallback) { + m_statusCallback(-1, "VSCP bootloader: Chunk size is larger than block size."); + } + return VSCP_ERROR_SIZE; + } - return false; + return VSCP_ERROR_SUCCESS; } +// ---------------------------------------------------------------------------- + /////////////////////////////////////////////////////////////////////////////// -// doFirmwareLoad +// writeFirmwareBlockDataChunk // -bool -CBootDevice_vscp::doFirmwareLoad(void) +int +CBootDevice_VSCP::writeFirmwareBlockDataChunk(const uint8_t *paddr, uint16_t size) { - bool bRun = true; - bool rv = true; - bool flag_crc = true; - - wxBusyCursor busy; - - m_checksum = 0; - uint32_t progress = 0; - uint32_t addr; - wxString wxStatusStr; - - uint32_t nFlashPackets = 0; - - // Packet size is always eight byte due to CAN frame limitation - - // Flash memory - if (m_bPrgData) { - nFlashPackets = (m_maxFlashAddr - m_minFlashAddr) / 8; - - if (0 != ((m_maxFlashAddr - m_minFlashAddr) % 8)) { - nFlashPackets++; - } + int rv; + vscpEventEx ex; + + // Send the start block + ex.vscp_class = VSCP_CLASS1_PROTOCOL; + ex.vscp_type = VSCP_TYPE_PROTOCOL_BLOCK_DATA; + ex.timestamp = vscp_makeTimeStamp(); + vscp_setEventExDateTimeBlockToNow(&ex); + + cguid node_guid; + m_stdRegs.getGUID(node_guid); + + // Check size + if (size > m_chunkSize) { + return VSCP_ERROR_SIZE; + } + + ex.sizeData = size; + memcpy(ex.data, paddr, size); + + if (VSCP_ERROR_SUCCESS != (rv = m_pclient->send(ex))) { + spdlog::error("VSCP bootloader: Failed to send start block chunk transfer event {0}", rv); + if (nullptr != m_statusCallback) { + m_statusCallback(-1, "VSCP bootloader: Failed to send start block chunk transfer event"); } - - long nTotalPackets = nFlashPackets; - wxProgressDialog *pDlg = - new wxProgressDialog(_T("Boot loading in progress..."), - _T("---"), - nTotalPackets, - NULL, - wxPD_AUTO_HIDE | wxPD_APP_MODAL | wxPD_CAN_ABORT | - wxPD_ELAPSED_TIME | wxPD_REMAINING_TIME); - - // Initialize checksum - addr = m_minFlashAddr; - - // * * * flash memory * * * - - if (rv && m_bPrgData) { - - addr = m_minFlashAddr; - - // nFlashPackets = number of 8 byte packets - m_blockNumber = 0; - for (uint32_t blk = 0; ((blk < nFlashPackets) && (true == bRun)); - blk++) { - - // Start block data transfer - if (0 == (blk % (m_blockSize / 8))) { - if (true != sendVSCPCommandStartBlock(blk * 8 / m_blockSize)) - wxMessageBox(_T("start Block error")); - bRun = false; - } - - wxStatusStr.Printf(_("Loading flash... %0X"), addr); - if (false == (bRun = pDlg->Update(progress, wxStatusStr))) { - wxMessageBox(_T("Aborted by user.")); - rv = false; - bRun = false; - } - - if (false == writeFirmwareSector()) { - wxMessageBox(_T("Failed to write flash data to node(s).")); - rv = false; - bRun = false; - } - - /* After a complete block, wait for the block data acknowledge. */ - if (0 == ((blk + 1) % (m_blockSize / 8))) { - - if (USE_DLL_INTERFACE == m_type) { - - flag_crc = sendVSCPCommandSeqenceLevel1(); - } else if (USE_TCPIP_INTERFACE == m_type) { - - flag_crc = sendVSCPCommandSeqenceLevel2(); - } - - m_blockNumber++; - } - - wxMilliSleep(1); - progress++; - addr += 8; - } - } - - /* - ** All blocks loaded -- now reset the device - */ - - if (!sendVSCPBootCommand( - VSCP_TYPE_PROTOCOL_ACTIVATE_NEW_IMAGE)) { // send as Zero as at -- - // present AVR bode for - // microcontroller -- does - // not implement this - // method. - // Failure - - wxMessageBox(_T(" ACTIVATE_NEW_IMAGE TX fails")); - } else { + return rv; + } + + // Wait for response on start block transfer event + if (VSCP_ERROR_SUCCESS != + (rv = checkResponse(ex, node_guid, VSCP_TYPE_PROTOCOL_START_BLOCK_ACK, VSCP_TYPE_PROTOCOL_START_BLOCK_NACK))) { + spdlog::error("VSCP bootloader: Negative response from block start request {0}", rv); + if (nullptr != m_statusCallback) { + m_statusCallback(-1, "VSCP bootloader: Negative response from block start request"); } - - // Done - progress = nTotalPackets; - pDlg->Update(progress, wxStatusStr); - - pDlg->Destroy(); - return rv; + } + + return VSCP_ERROR_SUCCESS; } /////////////////////////////////////////////////////////////////////////////// -// writeFirmwareSector +// writeFirmwareBlock // -bool -CBootDevice_vscp::writeFirmwareSector(void) +int +CBootDevice_VSCP::writeFirmwareBlock(const uint8_t *paddr) { - canalMsg msg; - vscpEventEx event; - bool rv = true; - - uint16_t vscpclass = VSCP_CLASS1_PROTOCOL; - uint8_t vscptype = VSCP_TYPE_PROTOCOL_BLOCK_DATA; - uint8_t priority = VSCP_PRIORITY_LOW_COMMON; - - // Send event - if (USE_DLL_INTERFACE == m_type) { - // Send message - msg.id = ((uint32_t)priority << 26) | ((uint32_t)vscpclass << 16) | - ((uint32_t)vscptype << 8) | - m_nodeid; // nodeaddress (our address) - msg.flags = CANAL_IDFLAG_EXTENDED; - msg.sizeData = 8; - } else if (USE_TCPIP_INTERFACE == m_type) { - event.head = 0; - event.vscp_class = 512; // CLASS2.PROTOCOL1 - event.vscp_type = vscptype; - memset(event.GUID, 0, 16); // We use interface GUID - event.sizeData = 16 + 8; // Interface GUID - memcpy(event.data, m_guid.m_id, 16); // Address node - } else { - return false; + int rv; + vscpEventEx ex; + cguid guid; + uint32_t nChunks; + + if (m_blockSize > m_chunkSize) { + nChunks = m_blockSize / m_chunkSize; + // A not completely full packet also count + if (0 != (m_blockSize % m_chunkSize)) { + nChunks++; } - - uint8_t b; - for (int i = 0; i < 8; i++) { - - b = m_pbufPrg[m_pAddr]; - m_checksum += m_pbufPrg[m_pAddr]; - - // Write data into frame - if (USE_DLL_INTERFACE == m_type) { - msg.data[i] = b; - } else if (USE_TCPIP_INTERFACE == m_type) { - event.data[16 + i] = b; - } else { - return false; - } - - // Update address - m_pAddr++; + } + else { + // There is just one chunk + // We have taken care of chunk > block in init + nChunks = 1; + } + + for (uint32_t chunk = 0; chunk < nChunks; chunk++) { + //uint32_t addr = memory_range[8].beginning; + spdlog::debug("Loading flash on remote device... block={0} {1:X}", chunk, chunk * m_chunkSize); + if (VSCP_ERROR_SUCCESS != writeFirmwareBlockDataChunk(paddr + (m_chunkSize*nChunks), m_chunkSize)) { + spdlog::error("Failed to write flash data to node(s)."); + break; } - if (USE_DLL_INTERFACE == m_type) { - m_pdll->doCmdSend(&msg); - } else if (USE_TCPIP_INTERFACE == m_type) { - m_ptcpip->doCmdSendEx(&event); - } else { - rv = false; + paddr += m_chunkSize; + if (nullptr != m_statusCallback) { + m_statusCallback((100 * chunk) / nChunks, "" /*vscp_str_format("blk %d.", blk).c_str()*/); } - return rv; -} - -/////////////////////////////////////////////////////////////////////////////// -// sendVSCPCommandStartBlock -// PageAddress : Page to be programmed -// This command have no ACK - -bool -CBootDevice_vscp::sendVSCPCommandStartBlock(uint16_t PageAddress) -{ - uint16_t vscpclass = 0; - uint8_t vscptype = 0; - uint8_t priority = 0; - - wxBusyCursor busy; - - if (USE_DLL_INTERFACE == m_type) { - - canalMsg msg; - - memset(msg.data, 0x00, 8); + } // for - vscpclass = VSCP_CLASS1_PROTOCOL; // Class - vscptype = VSCP_TYPE_PROTOCOL_START_BLOCK; // Start block data transfer. - priority = VSCP_PRIORITY_LOW_COMMON; - // block data transfer - msg.data[0] = 0x00; // Block number MSB - msg.data[1] = 0x00; // Block number - msg.data[2] = (PageAddress & 0xFF00) >> 8; // Block number - msg.data[3] = (PageAddress & 0x00FF); // Block number LSB - - // Send message - msg.id = ((uint32_t)priority << 26) | ((uint32_t)vscpclass << 16) | - ((uint32_t)vscptype << 8) | - m_nodeid; // nodeaddress (our address) - - msg.flags = CANAL_IDFLAG_EXTENDED; - msg.sizeData = 4; - if (CANAL_ERROR_SUCCESS == m_pdll->doCmdSend(&msg)) { - wxMilliSleep(1); - return true; - } - - } else if (USE_TCPIP_INTERFACE == m_type) { - - // Start block data transfer. - - vscpEventEx event; - - // Send message - event.head = 0; - event.vscp_class = 512; // CLASS2.PROTOCOL1 - event.vscp_type = - VSCP_TYPE_PROTOCOL_START_BLOCK; // We want to Start block data - // transfer. - memset(event.GUID, 0, 16); // We use interface GUID - event.sizeData = 16 + 4; // Interface GUID - memcpy(event.data, m_guid.m_id, 16); // Address node - event.data[16] = 0x00; // Block number MSB - event.data[17] = 0x00; // Block number - event.data[18] = (PageAddress & 0xFF00) >> 8; // Block number - event.data[19] = (PageAddress & 0x00FF); // Block number LSB - - if (CANAL_ERROR_SUCCESS == m_ptcpip->doCmdSendEx(&event)) { - wxMilliSleep(1); - return true; - } - } - - return false; + return VSCP_ERROR_SUCCESS; } /////////////////////////////////////////////////////////////////////////////// -// sendVSCPBootCommand -// This routine is used to send command from nodes under boot. -// Index tells which Type & class to send. +// deviceLoad // -bool -CBootDevice_vscp::sendVSCPBootCommand(uint8_t index) +int +CBootDevice_VSCP::deviceLoad(std::function statusCallback, bool bAbortOnFirmwareCodeFail) { - uint16_t vscpclass; - uint8_t vscptype; - // uint8_t nodeid; - uint8_t priority = 0; - - if (USE_DLL_INTERFACE == m_type) { + int rv; + vscpEventEx ex; - canalMsg msg; // rcvmsg; - // time_t tstart, tnow; + uint32_t progress = 0; + uint32_t addr; + std::string strStatus; - memset(msg.data, 0x00, 8); + uint32_t minAddr; + uint32_t maxAddr; - if (index == VSCP_TYPE_PROTOCOL_ACTIVATE_NEW_IMAGE) { + uint32_t nBlocks; - uint16_t crc16 = crcFast(&m_pbufPrg[0], m_numBlocks * m_blockSize); + m_checksum = 0; - vscpclass = VSCP_CLASS1_PROTOCOL; // Class - vscptype = VSCP_TYPE_PROTOCOL_ACTIVATE_NEW_IMAGE; - priority = VSCP_PRIORITY_LOW_COMMON; + if (nullptr != m_statusCallback) { + m_statusCallback(0, "Starting firmware download"); + } - msg.data[0] = (uint8_t)(crc16 >> 8) & 0xff; - msg.data[1] = (uint8_t)(crc16 >> 0) & 0xff; + // Clear checksum + m_checksum = 0; - msg.sizeData = 2; - } else if (index == VSCP_TYPE_PROTOCOL_PROGRAM_BLOCK_DATA) { - vscpclass = VSCP_CLASS1_PROTOCOL; // Class - vscptype = VSCP_TYPE_PROTOCOL_PROGRAM_BLOCK_DATA; - priority = VSCP_PRIORITY_LOW_COMMON; + // Iterate over memory types + int pos = 0; + do { - // block data transfer - msg.data[0] = ((uint8_t)(m_blockNumber >> 24)) & 0xFF; - msg.data[1] = ((uint8_t)(m_blockNumber >> 16)) & 0xFF; - msg.data[2] = ((uint8_t)(m_blockNumber >> 8)) & 0xFF; - msg.data[3] = ((uint8_t)(m_blockNumber >> 0)) & 0xFF; - - msg.sizeData = 4; - } else { - return false; - } + if (VSCP_ERROR_SUCCESS != + (rv = getMinMaxForRange(memory_range[pos++].beginning, memory_range[pos++].end, &minAddr, &maxAddr))) { + spdlog::error("writeFirmwareBlock: Failed to get min max range for block {0:X}-{1:X}", minAddr, maxAddr); + if (nullptr != m_statusCallback) { + m_statusCallback(-1, vscp_str_format("writeFirmwareBlock: Failed to get min max range for block", rv).c_str()); + } + return rv; + } - // Send message - msg.id = ((uint32_t)priority << 26) | ((uint32_t)vscpclass << 16) | - ((uint32_t)vscptype << 8) | - m_nodeid; // nodeaddress (our address) + // If there is a memory range to work with + if (maxAddr > minAddr) { - msg.flags = CANAL_IDFLAG_EXTENDED; + nBlocks = (maxAddr - minAddr) / m_blockSize; + // A not completely full packet also count + if (0 != ((maxAddr - minAddr) % m_blockSize)) { + nBlocks++; + } - if (CANAL_ERROR_SUCCESS == m_pdll->doCmdSend(&msg)) { + uint8_t *pbuf = new uint8_t[m_blockSize]; - // bRun = true; - wxMilliSleep(1); - return true; + // Init the block + if (VSCP_ERROR_SUCCESS != (rv = fillBlock(pbuf, m_blockSize, minAddr))) { + spdlog::error("writeFirmwareBlock: Failed to fill code block with data."); + if (nullptr != m_statusCallback) { + m_statusCallback( + -1, + vscp_str_format("writeFirmwareBlock: Failed to fill code block with data rv=%d", rv).c_str()); } - - } else if (USE_TCPIP_INTERFACE == m_type) { - - vscpEventEx event; - - // Send message - - if (index == VSCP_TYPE_PROTOCOL_ACTIVATE_NEW_IMAGE) { - - uint16_t crc16 = crcFast(&m_pbufPrg[0], m_numBlocks * m_blockSize); - - event.head = 0; - event.vscp_class = 512; // CLASS2.PROTOCOL1 - event.vscp_type = - VSCP_TYPE_PROTOCOL_ACTIVATE_NEW_IMAGE; // Activate new Image - memset(event.GUID, 0, 16); // We use interface GUID - event.sizeData = 16 + 2; // Interface GUID - memcpy(event.data, m_guid.m_id, 16); // Address node - event.data[16] = (uint8_t)(crc16 >> 8) & 0xff; - event.data[17] = (uint8_t)(crc16 >> 0) & 0xff; - } else if (index == VSCP_TYPE_PROTOCOL_PROGRAM_BLOCK_DATA) { - event.head = 0; - event.vscp_class = 512; // CLASS2.PROTOCOL1 - event.vscp_type = - VSCP_TYPE_PROTOCOL_PROGRAM_BLOCK_DATA; // Activate new Image - memset(event.GUID, 0, 16); // We use interface GUID - event.sizeData = 16 + 4; // Interface GUID - memcpy(event.data, m_guid.m_id, 16); // Address node - event.data[16] = ((uint8_t)(m_blockNumber >> 24)) & 0xFF; - event.data[17] = ((uint8_t)(m_blockNumber >> 16)) & 0xFF; - event.data[18] = ((uint8_t)(m_blockNumber >> 8)) & 0xFF; - event.data[19] = ((uint8_t)(m_blockNumber >> 0)) & 0xFF; - - } else { - return false; + delete[] pbuf; + return rv; + } + + // * * * start block * * * + + ex.vscp_class = VSCP_CLASS1_PROTOCOL; + ex.vscp_type = VSCP_TYPE_PROTOCOL_START_BLOCK; + ex.timestamp = vscp_makeTimeStamp(); + vscp_setEventExDateTimeBlockToNow(&ex); + + cguid node_guid; + m_stdRegs.getGUID(node_guid); + + ex.sizeData = 8; + // ex.data[0] = (nblock >> 24) & 0xff; + // ex.data[1] = (nblock >> 16) & 0xff; + // ex.data[2] = (nblock >> 8) & 0xff; + // ex.data[3] = nblock & 0xff; + ex.data[4] = memory_range[pos].type; + + if (VSCP_ERROR_SUCCESS != (rv = m_pclient->send(ex))) { + spdlog::error("VSCP bootloader: Failed to send start block transfer event {0}", rv); + if (nullptr != m_statusCallback) { + m_statusCallback(-1, "VSCP bootloader: Failed to send start block transfer event"); } - - if (CANAL_ERROR_SUCCESS == m_ptcpip->doCmdSendEx(&event)) { - - wxMilliSleep(1); - - return true; + return rv; + } + + // Wait for response on start block transfer event + if (VSCP_ERROR_SUCCESS != + (rv = + checkResponse(ex, node_guid, VSCP_TYPE_PROTOCOL_START_BLOCK_ACK, VSCP_TYPE_PROTOCOL_START_BLOCK_NACK))) { + spdlog::error("VSCP bootloader: Negative response from block start request {0}", rv); + if (nullptr != m_statusCallback) { + m_statusCallback(-1, "VSCP bootloader: Negative response from block start request"); } - } + return rv; + } - return false; -} - -/////////////////////////////////////////////////////////////////////////////// -// sendVSCPCommandSeqenceLevel1 -// This routine is used to check ack & send command from nodes under boot. -// check response VSCP_TYPE_PROTOCOL_BLOCK_DATA_ACK --- Check CRC -// send VSCP_TYPE_PROTOCOL_PROGRAM_BLOCK_DATA -// check response VSCP_TYPE_PROTOCOL_PROGRAM_BLOCK_DATA_ACK -// - -bool -CBootDevice_vscp::sendVSCPCommandSeqenceLevel1(void) -{ - - if (!checkResponseLevel1(VSCP_TYPE_PROTOCOL_BLOCK_DATA_ACK)) { - - wxMessageBox(_T(" Response PROTOCOL_BLOCK_DATA_ACK fails")); - - } else { - - if (crc_16_host != crc_16_remote) { - m_pAddr -= m_blockSize; - return false; + while (true) { + if (VSCP_ERROR_SUCCESS != (rv = writeFirmwareBlock(pbuf))) { + return rv; } + } } + } while (0xff == memory_range[pos++].type); - wxMilliSleep(1); - - /* - ** Send command - */ - - if (!sendVSCPBootCommand(VSCP_TYPE_PROTOCOL_PROGRAM_BLOCK_DATA)) { - - wxMessageBox(_T(" PROGRAM_BLOCK_DATA TX fails")); - - } else { - } - - wxMilliSleep(1); - - if (!checkResponseLevel1(VSCP_TYPE_PROTOCOL_PROGRAM_BLOCK_DATA_ACK)) { - - wxMessageBox(_T(" Response PROGRAM_BLOCK_DATA_ACK fails")); - - } else { - } - - wxMilliSleep(1); - - return true; + return VSCP_ERROR_TIMEOUT; } /////////////////////////////////////////////////////////////////////////////// -// sendVSCPCommandSeqenceLevel2 -// This routine is used to check ack & send command from nodes under boot. -// check response VSCP_TYPE_PROTOCOL_BLOCK_DATA_ACK -// send VSCP_TYPE_PROTOCOL_PROGRAM_BLOCK_DATA -// check response VSCP_TYPE_PROTOCOL_PROGRAM_BLOCK_DATA_ACK +// deviceReboot // -bool -CBootDevice_vscp::sendVSCPCommandSeqenceLevel2(void) +int +CBootDevice_VSCP::deviceReboot(void) { + int rv; - // Check response - if (!checkResponseLevel2(VSCP_TYPE_PROTOCOL_BLOCK_DATA_ACK)) { - - wxMessageBox(_T(" Response PROTOCOL_BLOCK_DATA_ACK fails")); - - } else { - - if (crc_16_host != crc_16_remote) { - m_pAddr -= m_blockSize; - return false; - } - } - - wxMilliSleep(1); - - /* - ** Send command - */ - - if (!sendVSCPBootCommand(VSCP_TYPE_PROTOCOL_PROGRAM_BLOCK_DATA)) { - // Failure - - wxMessageBox(_T(" PROGRAM_BLOCK_DATA TX fails")); - - } else { - ; - } - - wxMilliSleep(1); - - // Check response - if (!checkResponseLevel2( - VSCP_TYPE_PROTOCOL_PROGRAM_BLOCK_DATA_ACK)) { // send as Zero as at -- - // present AVR bode for - // microcontroller -- - // does not implement - // this method. - // Failure - // rv = false; - // TODO Resend the block - wxMessageBox(_T(" Response PROGRAM_BLOCK_DATA_ACK fails")); - } else { - } - - return true; + return VSCP_ERROR_SUCCESS; } /////////////////////////////////////////////////////////////////////////////// -// checkResponseLevel1 -// -// Type = 20 (0x14) ACK program data block -- for 8 byte packet received -// correctly by AVR bootloader +// checkResponse // -bool -CBootDevice_vscp::checkResponseLevel1(uint8_t index) +int +CBootDevice_VSCP::checkResponse(vscpEventEx &ex, + cguid &guid, + uint16_t response_event_ack, + uint16_t response_event_nack, + uint32_t timeout) { - // canalMsg msg; - // time_t tstart, tnow; - bool rv = false; - - uint16_t vscpclass; - uint8_t vscptype; - uint8_t priority = 0; - - canalMsg rcvmsg; // msg, - - if (NULL == m_pdll) return false; - - // Get system time - // time( &tstart ); - - bool bRun = true; - while (bRun) { - - if (m_pdll->doCmdDataAvailable()) { - - // if(m_type==0xff) - // wxMessageBox( _T("123456") ); - m_pdll->doCmdReceive(&rcvmsg); - - if ((int)(rcvmsg.id & 0xff) == m_nodeid) { - - // Case -- index = 0 --- not implemented always return true - if (index == 0) { + int rv; + time_t tstart, tnow; - // Response received from all - return success - rv = true; - bRun = false; + // Get start time + time(&tstart); - } - // Case -- index = 1 - else if (index == VSCP_TYPE_PROTOCOL_BLOCK_DATA_ACK) { + while (true) { - vscpclass = VSCP_CLASS1_PROTOCOL; - vscptype = VSCP_TYPE_PROTOCOL_BLOCK_DATA_ACK; - - if ((uint32_t)(rcvmsg.id & 0x01ffffff) == - (uint32_t)(((uint32_t)vscpclass << 16) | - ((uint32_t)vscptype << 8) | m_nodeid)) { - - // Calculate CRC in host - crc_16_host = crcFast(&m_pbufPrg[m_pAddr - m_blockSize], - m_blockSize); - // GET CRC in remote node - crc_16_remote = (((uint16_t)rcvmsg.data[0]) << 8) | - (((uint16_t)rcvmsg.data[1]) << 0); - - // Response received from all - return success - rv = true; - bRun = false; - } - - } else if (index == VSCP_TYPE_PROTOCOL_PROGRAM_BLOCK_DATA_ACK) { - - vscpclass = VSCP_CLASS1_PROTOCOL; - vscptype = VSCP_TYPE_PROTOCOL_PROGRAM_BLOCK_DATA_ACK; - - if ((uint32_t)(rcvmsg.id & 0x01ffffff) == - (uint32_t)(((uint32_t)vscpclass << 16) | - ((uint32_t)vscptype << 8) | m_nodeid)) { - - // Response received from all - return success - rv = true; - bRun = false; - } - } - - } // id found - - } // received message + // Test for timeout + time(&tnow); + if ((unsigned long) (tnow - tstart) > timeout) { + spdlog::debug("VSCP Bootloader: Timeout."); + break; } - return rv; -} - -/////////////////////////////////////////////////////////////////////////////// -// checkResponseLevel2 -// Type = 20 (0x14) ACK program data block -- -// for 8 byte packet received correctly by bootloader -// -bool -CBootDevice_vscp::checkResponseLevel2(uint8_t index) -{ - vscpEventEx event; - // time_t tstart, tnow; - bool rv = false; - - if (NULL == m_ptcpip) return false; + uint16_t cnt; + if ((VSCP_ERROR_SUCCESS == m_pclient->getcount(&cnt)) && cnt) { - bool bRun = true; - while (bRun) { + rv = m_pclient->receive(ex); - if (m_ptcpip->doCmdDataAvailable()) { + spdlog::debug("VSCP Bootloader: Received event: class={0} type={1} size={2}", + ex.vscp_class, + ex.vscp_type, + ex.sizeData); - m_ptcpip->doCmdReceiveEx(&event); - - if ((VSCP_CLASS1_PROTOCOL == event.vscp_class) && - (m_guid.getLSB() == event.GUID[15])) { // correct id - - // Case -- index = 0 --- not implemented always return true - if (index == 0) { - - // Response received from all - return success - rv = true; - bRun = false; - - } - // Case -- index = VSCP_TYPE_PROTOCOL_BLOCK_DATA_ACK - else if (index == VSCP_TYPE_PROTOCOL_BLOCK_DATA_ACK) { - - if (event.vscp_type == VSCP_TYPE_PROTOCOL_BLOCK_DATA_ACK) { - - // Calculate CRC in host - crc_16_host = crcFast(&m_pbufPrg[m_pAddr - m_blockSize], - m_blockSize); - // GET CRC in remote node - crc_16_remote = (((uint16_t)event.data[0]) << 8) | - (((uint16_t)event.data[1]) << 0); - - // Response received from all - return success - rv = true; - bRun = false; - } - - } - // Case -- index = VSCP_TYPE_PROTOCOL_PROGRAM_BLOCK_DATA_ACK - else if (index == VSCP_TYPE_PROTOCOL_PROGRAM_BLOCK_DATA_ACK) { - - if (event.vscp_type == - VSCP_TYPE_PROTOCOL_PROGRAM_BLOCK_DATA_ACK) { - - // Response received from all - return success - rv = true; - bRun = false; - } - } - - } // id - - } // received message + // Is this a read/write reply from the node? + if ((VSCP_CLASS1_PROTOCOL == ex.vscp_class) && (response_event_ack == ex.vscp_type) && guid.isSameGUID(ex.GUID)) { + // ACK response + spdlog::debug("VSCP Bootloader: ACK recived."); + if (nullptr != m_statusCallback) { + m_statusCallback(-1, "VSCP Bootloader:ACK recived"); + } + return VSCP_ERROR_SUCCESS; + } + + // Is this a read/write reply from the node? + if ((VSCP_CLASS1_PROTOCOL == ex.vscp_class) && (response_event_nack == ex.vscp_type) && + guid.isSameGUID(ex.GUID)) { + // NACK response + spdlog::debug("VSCP Bootloader: NACK recived."); + if (nullptr != m_statusCallback) { + m_statusCallback(-1, "VSCP Bootloader: NACK recived."); + } + return VSCP_ERROR_NACK; + } } + } // while - return rv; + return VSCP_ERROR_TIMEOUT; } diff --git a/src/vscp/common/vscp_bootdevice_vscp.h b/src/vscp/common/vscp_bootdevice_vscp.h index de8e949b8..a499a1feb 100644 --- a/src/vscp/common/vscp_bootdevice_vscp.h +++ b/src/vscp/common/vscp_bootdevice_vscp.h @@ -1,14 +1,6 @@ ///////////////////////////////////////////////////////////////////////////// -// Name: bootdevice_vscp.cpp -// Purpose: -// Author: Ake Hedman -// Modified by: -// Created: 16/12/2009 22:26:09 -// RCS-ID: -// Copyright: (C) 2007-2024 -// Ake Hedman, the VSCP project, -// (C) 2012 Dinesh Guleria -// Licence: +// bootdevice_vscp.cpp +// // 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 @@ -16,28 +8,19 @@ // // This file is part of the VSCP (https://www.vscp.org) // +// Copyright: (C) 2000-2024 +// Ake Hedman, the VSCP project, +// // This file 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. +// 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 file see the file COPYING. If not, write to // the Free Software Foundation, 59 Temple Place - Suite 330, -// Boston, MA 02111-1307, USA. -// -// As a special exception, if other files instantiate templates or use macros -// or inline functions from this file, or you compile this file and link it -// with other works to produce a work based on this file, this file does not -// by itself cause the resulting work to be covered by the GNU General Public -// License. However the source code for this file must still be made available -// in accordance with section (3) of the GNU General Public License. -// -// This exception does not invalidate any other reasons why a work based on -// this file might be covered by the GNU General Public License. +// Boston, MA 02111-1307, USA. // -// Alternative licenses for VSCP & Friends may be arranged by contacting -// the VSCP project at info@vscp.org, https://www.vscp.org // #pragma once @@ -50,144 +33,299 @@ #include -class CBootDevice_vscp : public CBootDevice -{ - public: - /*! - Constructor - - @param pdll Pointer to opended CANAL object. - @param nodeid Nickname/nodeid for node that should be loaded - with new code. - */ - CBootDevice_vscp(CDllWrapper *pdll, - uint8_t nodeid, - bool bDeviceFound = true); - - /*! - Constructor - - @param ptcpip Pointer to opened TCP/IP interface object. - @param guid GUID for node to bootload. - @param ifguid GUID for interface node is located on - */ - CBootDevice_vscp(VscpRemoteTcpIf *ptcpip, - cguid &guid, - cguid &ifguid, - bool bDeviceFound = true); - - /// Dtor - ~CBootDevice_vscp(void); - - // Initialise data - void init(void); - - /*! - Load a binary file to the image - - This is typically an Intel HEX file that contains the memory - image of the device. - - @param path Path to file - @return true on success - */ - bool loadBinaryFile(const std::string &path, uint16_t typeHexfile); - - /*! - Show info for hex file - @param Pointer to HTML window that will receive information. - */ - void showInfo(std::string *phtmlWnd); - - /*! - Set a device in bootmode - @return true on success. - */ - bool setDeviceInBootMode(void); - - /*! - Perform the actual boot process - @return true on success. - */ - bool doFirmwareLoad(void); - - /*! - Write a sector - @return true on success. - */ - bool writeFirmwareSector(void); - - /*! - // sendVSCPCommandStartBlock - // PageAddress : Page to be programmed - // This command have no ACK - */ - bool sendVSCPCommandStartBlock(uint16_t PageAddress); - - /*! - Send command to node under test nodes (Level I). - - This routine is used to send command from nodes under boot. - Index tells which Type & class to send. - - @return true on success. - */ - bool sendVSCPBootCommand(uint8_t index); - - /*! - sendVSCPCommandSeqenceLevel1 - This routine is used to check ack & send command from nodes under boot. - check response VSCP_TYPE_PROTOCOL_BLOCK_DATA_ACK --- Check CRC - send VSCP_TYPE_PROTOCOL_PROGRAM_BLOCK_DATA - check response VSCP_TYPE_PROTOCOL_PROGRAM_BLOCK_DATA_ACK - */ - bool sendVSCPCommandSeqenceLevel1(void); - - /*! - sendVSCPCommandSeqenceLevel1 - This routine is used to check ack & send command from nodes under boot. - check response VSCP_TYPE_PROTOCOL_BLOCK_DATA_ACK --- Check CRC - send VSCP_TYPE_PROTOCOL_PROGRAM_BLOCK_DATA - check response VSCP_TYPE_PROTOCOL_PROGRAM_BLOCK_DATA_ACK - */ - bool sendVSCPCommandSeqenceLevel2(void); - - /*! - Check for response from nodes (Level I). - - This routine is used as a check for response from nodes under boot. - Index tells which Type & class to check - - @return true on success. - */ - bool checkResponseLevel1(uint8_t index); - - /*! - Check for response from nodes over server (Level II). - - This routine is used as a check for response from nodes under boot. - Index tells which Type & class to check - - @return true on success. - */ - bool checkResponseLevel2(uint8_t index); - - private: - /// Internal address pointer - uint32_t m_pAddr; - - /// Current page - uint32_t m_page; - - /// Current block number - uint32_t m_blockNumber; - - /// Block size in byte - uint32_t m_blockSize; - - /// Number of blocks - uint32_t m_numBlocks; - - uint16_t crc_16_remote; - uint16_t crc_16_host; +// This macro construct a signed integer from two unsigned chars in a safe way +#define construct_signed16(msb, lsb) ((int16_t) ((((uint16_t) msb) << 8) + (uint16_t) lsb)) + +// This macro construct a unsigned integer from two unsigned chars in a safe way +#define construct_unsigned16(msb, lsb) ((uint16_t) ((((uint16_t) msb) << 8) + (uint16_t) lsb)) + +// This macro construct a signed long from four unsigned chars in a safe way +#define construct_signed32(b0, b1, b2, b3) \ + ((int32_t) ((((uint32_t) b0) << 24) + (((uint32_t) b0) << 16) + (((uint32_t) b0) << 8) + (uint32_t) b0)) + +// This macro construct a unsigned long from four unsigned chars in a safe way +#define construct_unsigned32(b0, b1, b2, b3) \ + ((uint32_t) ((((uint32_t) b0) << 24) + (((uint32_t) b0) << 16) + (((uint32_t) b0) << 8) + (uint32_t) b0)) + +class CBootDevice_VSCP : public CBootDevice { +public: + /*! + Constructor + + @param pdll Pointer to opended CANAL object. + @param nodeid Nickname/nodeid for node that should be loaded + with new code. + */ + CBootDevice_VSCP(CVscpClient *pclient, + uint8_t nodeid, + std::function statusCallback = nullptr, + uint32_t timeout = REGISTER_DEFAULT_TIMEOUT); + + /*! + Constructor + + This is Level I over Level II that uses an interface on + a remote device to communicate with Level I nodes. + + @param pdll Pointer to opended CANAL object. + @param nodeid Nickname/nodeid for node that should be loaded + @param guidif GUID for interface. + with new code. +*/ + CBootDevice_VSCP(CVscpClient *pclient, + uint8_t nodeid, + cguid &guidif, + std::function statusCallback = nullptr, + uint32_t timeout = REGISTER_DEFAULT_TIMEOUT); + + /*! + Constructor + + This is a full level II device. No interface, full GUID. + + @param ptcpip Pointer to opened TCP/IP interface object. + @param guid GUID for node to bootload. + */ + CBootDevice_VSCP(CVscpClient *pclient, + cguid &guid, + std::function statusCallback = nullptr, + uint32_t timeout = REGISTER_DEFAULT_TIMEOUT); + + /// Dtor + ~CBootDevice_VSCP(void); + + // Memory Types + enum mem_type { + MEM_TYPE_FLASH = 0x00, // 0x00000000 - 0x3FFFFFFF 1G + MEM_TYPE_RAM = 0x03, // 0x40000000 - 0xBFFFFFFF 2G + MEM_TYPE_USERID = 0x04, // 0xC1000000 - 0xC1FFFFFF 16M + MEM_TYPE_CONFIG = 0x02, // 0xC2000000 - 0xC2FFFFFF 16M + MEM_TYPE_EEPROM = 0x01, // 0xC3000000 - 0xC3FFFFFF 16M + MEM_TYPE_USER1 = 0x05, // 0xD0000000 - 0xDFFFFFFF 256M + MEM_TYPE_USER2 = 0x06, // 0xE0000000 - 0xEFFFFFFF 256M + MEM_TYPE_USER3 = 0x07, // 0xF0000000 - 0xFFFFFFFF 256M + }; + + // Flash memory + static const uint32_t MEM_CODE_START = 0x00000000; + static const uint32_t MEM_CODE_END = 0x3FFFFFFF; + + // Ram memory + static const uint32_t MEM_RAM_START = 0x40000000; + static const uint32_t MEM_RAM_END = 0xBFFFFFFF; + + // User id memory + static const uint32_t MEM_USERID_START = 0xC1000000; + static const uint32_t MEM_USERID_END = 0xC1FFFFFF; + + // Config memory + static const uint32_t MEM_CONFIG_START = 0xC2000000; + static const uint32_t MEM_CONFIG_END = 0xC2FFFFFF; + + // EEPROM memory + static const uint32_t MEM_EEPROM_START = 0xC3000000; + static const uint32_t MEM_EEPROM_END = 0xC3FFFFFF; + + // User defined memory 0 + static const uint32_t MEM_USER0_START = 0xD0000000; + static const uint32_t MEM_USER0_END = 0xDFFFFFFF; + + // User defined memory 1 + static const uint32_t MEM_USER1_START = 0xE0000000; + static const uint32_t MEM_USER1_END = 0xEFFFFFFF; + + // User defined memory 2 + static const uint32_t MEM_USER2_START = 0xF0000000; + static const uint32_t MEM_USER2_END = 0xFFFFFFFF; + + // Default timeout for response + static const uint8_t BOOT_COMMAND_RESPONSE_TIMEOUT = 5; + + // Initialize data + void init(void); + + /*! + Show info for hex file + @return String that will receive information. + */ + std::string deviceInfo(void); + + /*! + Initialize boot mode, ie set device in bootmode + @param ourguid Our local GUID + @param devicecode This is the devicecode expected by the user + @param bAbortOnFirmwareCodeFail Set to true to fail if firmware code fetched from + MDF is not the same as the one read from the remote device. + @return VSCP_ERROR_SUCCESS on success. + */ + int deviceInit(cguid& ourguid, uint8_t devicecode, bool bAbortOnFirmwareCodeFail = false); + + /*! + Perform the actual firmware load process + @param statusCallback Callback that receive status info as presentage (int) + and status message (const char *) + @param bAbortOnFirmwareCodeFail If firmware device code stored in standard registers + is different then the one we try to load, abort if true. + @return VSCP_ERROR_SUCCESS on success. + */ + int deviceLoad(std::function statusCallback = nullptr,bool bAbortOnFirmwareCodeFail = true); + + /*! + Restart remote device + @return VSCP_ERROR_SUCCESS is returned on success, ortherwise error code + */ + int deviceRestart(void); + + /*! + Reboot remote device + @return VSCP_ERROR_SUCCESS is returned on success, ortherwise error code + */ + int deviceReboot(void); + + /*! + Write a sector + @param paddr Pointer to firts byte of 8-byte block to write + to remote device + @param size Number of data bytes to send. Must be less than the + max event data (8/512) + @return VSCP_ERROR_SUCCESS on success. + */ + int writeFirmwareBlockDataChunk(const uint8_t *paddr, uint16_t size); + + /*! + Write a firmware flock to the device + @param paddr Address to beginning of data to write + @return VSCP_ERROR_SUCCESS on success. + */ + int writeFirmwareBlock(const uint8_t *paddr); + + /*! + Write to device control registry + + @param addr Address to set as start address + @param flags Control Flags + @param cmd Boot command + @param cmdData0 Boot command data byte 0 + @param cmdData1 Boot command data byte 1 + @return VSCP_ERROR_SUCCESS on success. + + */ + // int writeDeviceControlRegs(uint32_t addr, + // uint8_t flags = (MODE_WRT_UNLCK | MODE_AUTO_ERASE | MODE_AUTO_INC | MODE_ACK), + // uint8_t cmd = CMD_NOP, + // uint8_t cmdData0 = 0, + // uint8_t cmdData1 = 0); + + /*! + Check response event + @param ex Event that is the result of the response + @param guid GUID for our device the expected event should originate from. + @param response_event_ack ACK event type. + @param response_event_nack NACK event type. + @return VSCP_ERROR_SUCCESS if ack is received within the timoeut time. VSCP_ERROR_NACK if NACK + event is received. VSCP_ERROR_TIMEOUT if timeout occured. + */ + int checkResponse(vscpEventEx &ex, + cguid &guid, + uint16_t response_event_ack, + uint16_t response_event_nack, + uint32_t timeout = BOOT_COMMAND_DEFAULT_RESPONSE_TIMEOUT); + + /*! + Check for response from nodes (Level I). + + This routine is used as a check for response from nodes under boot. + + The supplied id is valid from bit 8 and upwards. The lower eight bits + are the id. + Only extended messages are accepted as a valid response. + + @param id Response code to look for. + @return VSCP_ERROR_SUCCESS on success. + */ + int checkResponseLevel1(uint32_t id); + + /*! + Check for response from nodes over server (Level II). + + This routine is used as a check for response from nodes under boot. + + The supplied id is valid from bit 8 and upwards. The lower eight bits + are the id. + Only extended messages are accepted as a valid response. + + @param id Response code to look for. + @return VSCP_ERROR_SUCCESS on success. + */ + int checkResponseLevel2(uint32_t id); + +private: + + /// Current set page in register space on remote device + uint32_t m_page; + + /// Our GUID + cguid m_ourguid; + + /*! + Current block number + This is the current block we are writing + */ + //uint32_t m_blockNumber; + + /*! + Internal address pointer + This is the next address to write + */ + uint32_t m_pAddr; + + /*! + Chunk size in bytes + ------------------- + This is the (max) size for each part sent over the wire + to transfer a block from a host and a master. It can have + two values 8 (Level I) or 512 (Level II). + + Special cases + ------------- + The last chunk may have a size less than 8/512 or + it holds 8/512 bytes. It's up to the remote device to handle both + cases. + + block size can be less then chunk size. This is escpecially true + for level II + */ + uint16_t m_chunkSize; + + /*! + Block size in bytes + ------------------- + This is the block size the remote device expect us to send before + programing a block. + Programming is done in chunks of 8 or 512 bytes or less until a full + block has been written. + */ + uint32_t m_blockSize; + + /*! + Number of blocks + ---------------- + This is the number of blocks available at the remote device. We can not program more than + this amount of blocks but it's OK to program less. + */ + uint32_t m_numBlocks; + + /*! + CRC for full programming data. + */ + uint16_t m_crc16; + + struct memory_range { + uint8_t type; + uint32_t beginning; + uint32_t end; + } memory_range[8] = { { 0x00, MEM_CODE_START, MEM_CODE_START }, { 0x01, MEM_EEPROM_START, MEM_EEPROM_END }, + { 0x02, MEM_CONFIG_START, MEM_CONFIG_END }, { 0x03, MEM_RAM_START, MEM_RAM_END }, + { 0x04, MEM_USERID_START, MEM_USERID_END }, { 0xfd, MEM_USER0_START, MEM_USER0_END }, + { 0xfe, MEM_USER1_START, MEM_USER1_END }, { 0xff, MEM_USER2_START, MEM_USER2_END } }; }; diff --git a/src/vscp/common/vscp_bootloader.cpp b/src/vscp/common/vscp_bootloader.cpp deleted file mode 100644 index 53fcebe8f..000000000 --- a/src/vscp/common/vscp_bootloader.cpp +++ /dev/null @@ -1,78 +0,0 @@ -///////////////////////////////////////////////////////////////////////////// -// Name: vscpbootloader.cpp -// Purpose: -// Author: Ake Hedman -// Modified by: -// Created: Thu 25 Oct 2007 22:32:58 CEST -// RCS-ID: -// Copyright: (C) 2007-2024 -// Ake Hedman, the VSCP project, -// (C) 2012 Dinesh Guleria -// Licence: -// 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 file is part of the VSCP (https://www.vscp.org) -// -// This file 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 file see the file COPYING. If not, write to -// the Free Software Foundation, 59 Temple Place - Suite 330, -// Boston, MA 02111-1307, USA. -// -// As a special exception, if other files instantiate templates or use macros -// or inline functions from this file, or you compile this file and link it -// with other works to produce a work based on this file, this file does not -// by itself cause the resulting work to be covered by the GNU General Public -// License. However the source code for this file must still be made available -// in accordance with section (3) of the GNU General Public License. -// -// This exception does not invalidate any other reasons why a work based on -// this file might be covered by the GNU General Public License. -// -// TODO: Delete !!!!!!!!!!!!!!!!!!!!! - -#if defined(__GNUG__) && !defined(NO_GCC_PRAGMA) -#pragma implementation "vscpboot_loader.h" -#endif - -#include "vscp_bootloader.h" - -///////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -// VSCPBootloader constructors -// - -VSCPBootloader::VSCPBootloader() -{ - init(); -} - -///////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -// VSCPBootloader destructor -// - -VSCPBootloader::~VSCPBootloader() {} - -///////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -// init -// - -void -VSCPBootloader::init() -{} - -///////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -// Runs the wizard. -// - -bool -VSCPBootloader::run() -{ - return false; -} diff --git a/src/vscp/common/vscp_bootloader.h b/src/vscp/common/vscp_bootloader.h deleted file mode 100644 index 687b2993e..000000000 --- a/src/vscp/common/vscp_bootloader.h +++ /dev/null @@ -1,74 +0,0 @@ -///////////////////////////////////////////////////////////////////////////// -// Name: vscpbootloader.h -// Purpose: -// Author: Ake Hedman -// Modified by: -// Created: Thu 25 Oct 2007 22:32:58 CEST -// RCS-ID: -// Copyright: (C) 2007-2024 -// Ake Hedman, the VSCP project, -// (C) 2012 Dinesh Guleria -// Licence: -// 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 file is part of the VSCP (https://www.vscp.org) -// -// This file 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 file see the file COPYING. If not, write to -// the Free Software Foundation, 59 Temple Place - Suite 330, -// Boston, MA 02111-1307, USA. -// -// As a special exception, if other files instantiate templates or use macros -// or inline functions from this file, or you compile this file and link it -// with other works to produce a work based on this file, this file does not -// by itself cause the resulting work to be covered by the GNU General Public -// License. However the source code for this file must still be made available -// in accordance with section (3) of the GNU General Public License. -// -// This exception does not invalidate any other reasons why a work based on -// this file might be covered by the GNU General Public License. -// -// TODO: Delete !!!!!!!!!!!!!!!!!!!!! - -#ifndef _VSCPBOOTLOADER_H_ -#define _VSCPBOOTLOADER_H_ - -#if defined(__GNUG__) && !defined(NO_GCC_PRAGMA) -#pragma interface "vscpboot_loader.h" -#endif - -/*! - * Control identifiers - */ - -/*! - * VSCPBootloader class declaration - */ - -class VSCPBootloader -{ - - public: - /// Constructors - VSCPBootloader(); - - /// Destructor - ~VSCPBootloader(); - - /// Initialises member variables - void init(); - - /// Runs the wizard - bool run(); -}; - -#endif -// _VSCPBOOTLOADER_H_ diff --git a/src/vscp/vscphubd/Makefile.in b/src/vscp/vscphubd/Makefile.in new file mode 100644 index 000000000..0682e4619 --- /dev/null +++ b/src/vscp/vscphubd/Makefile.in @@ -0,0 +1,377 @@ +# ========================================================================= +# Autogenerated from Makefile.in +# ========================================================================= + +# Package version +MAJOR_VERSION=@MAJOR_VERSION@ +MINOR_VERSION=@MINOR_VERSION@ +RELEASE_VERSION=@RELEASE_VERSION@ +BUILD_VERSION=@BUILD_VERSION@ +PACKAGE_VERSION=@PACKAGE_VERSION@ + +# STATIC = yes if a static build is requested. +STATIC=@STATIC@ +VSCP_PROJ_BASE_DIR=@VSCPDIR@ + +prefix = @prefix@ +exec_prefix = @exec_prefix@ +INSTALL = @INSTALL@ +top_srcdir = @top_srcdir@ +top_builddir = @top_builddir@ + +CC = @CC@ +CXX = @CXX@ + +WARNINGS_HARD = -Werror -Wall -Wextra -pedantic -Wcast-align -Wcast-qual \ + -Wctor-dtor-privacy -Wdisabled-optimization -Wformat=2 -Winit-self \ + -Wlogical-op -Wmissing-include-dirs -Wnoexcept -Wold-style-cast \ + -Woverloaded-virtual -Wredundant-decls -Wshadow -Wsign-promo \ + -Wstrict-null-sentinel -Wstrict-overflow=5 -Wundef -Wno-unused \ + -Wno-variadic-macros -Wno-parentheses -fdiagnostics-show-option +WARNINGS_MEDIUM = -Wformat-security -Wformat=2 -Wmissing-include-dirs \ + -Wno-parentheses -Wformat=2 -Wlogical-op +WARNINGS = -Wall + +VERSION_DEFS = -DMAJOR_VERSION=$(MAJOR_VERSION) -DMINOR_VERSION=$(MINOR_VERSION) \ + -DRELEASE_VERSION=$(RELEASE_VERSION) -DBUILD_VERSION=$(BUILD_VERSION) + +CIVETWEB_FLAGS = -DUSE_IPV6 -DUSE_WEBSOCKET \ + -DUSE_LUA -DUSE_DUKTAPE -DUSE_ZLIB -DUSE_SERVER_STATS +CFLAGS = -std=c99 @CFLAGS@ ${WARNINGS} -DCBC -DLUA_USE_POSIX -D_POSIX_SOURCE -I@top_srcdir@ \ + -I. \ + -I../../common \ + -I../../../common \ + -I../../../common/third_party/nlohmann \ + -I../../../common/third_party -I../../../common/third_party/lua-5.3.5/src/ \ + -I../../../common/third_party/duktape-2.5.0/src/ \ + -I../../../common/third_party/sqlite3-3.30.1/ +CFLAGS += $(CIVETWEB_FLAGS) +CXXFLAGS = -std=c++11 @CXXFLAGS@ ${WARNINGS} +CPPFLAGS = @CPPFLAGS@ ${WARNINGS} -fno-var-tracking-assignments -I@top_srcdir@ \ + -I. \ + -I../../common \ + -I../../../common \ + -I../../../common/third_party/nlohmann \ + -I../../../common/third_party -I../../../common/third_party/lua-5.3.5/src/ \ + -I../../../common/third_party/duktape-2.5.0/src/ \ + -I../../../common/third_party/sqlite3-3.30.1/ +CPPFLAGS += $(CIVETWEB_FLAGS) + +LDFLAGS = @LDFLAGS@ +EXTRALIBS = @EXTRALIBS@ +DESTDIR=@DESTDIR@ + +prefix = @prefix@ +exec_prefix = @exec_prefix@ +STRIP = strip +INSTALL_PROGRAM = ${INSTALL} +INSTALL_DATA = ${INSTALL} -m 644 +INSTALL_DIR = /usr/bin/install -c -d +srcdir = .< +top_srcdir = . +top_builddir = +bindir = ${prefix}/sbin +libdir = ${prefix}/lib +datadir = ${prefix}/share +includedir = ${prefix}/include +DLLPREFIX = lib + +### Variables: ### + +DESTDIR = + +VSCPD_OBJECTS = vscpd.o \ + clientlist.o \ + controlobject.o \ + tcpipsrv.o \ + interfacelist.o \ + userlist.o \ + devicethread.o \ + websrv.o \ + websocketsrv.o \ + restsrv.o\ + civetweb.o \ + vscphelper.o \ + vscpremotetcpif.o \ + automation.o \ + devicelist.o \ + mdf.o \ + sockettcp.o \ + guid.o \ + register.o \ + dllist.o \ + configfile.o \ + crc.o \ + crc8.o \ + randpassword.o \ + duktape.o \ + duktape_vscp_func.o \ + duktape_vscp_wrkthread.o \ + LuaXML_lib.o \ + lua_all.o\ + lua_vscp_wrkthread.o\ + lua_vscp_func.o\ + vscp_aes.o \ + sqlite3.o \ + sqlite3math.o \ + fastpbkdf2.o \ + vscpmd5.o \ + vscpbase64.o \ + vscpdatetime.o + +### Targets: ### + +all: vscpd + +vscpd.o: vscpd.cpp vscpd.h + $(CXX) $(CXXFLAGS) $(CPPFLAGS) -c vscpd.cpp -o $@ $(EXTRALIBS) + +clientlist.o: ../../common/clientlist.cpp ../../common/clientlist.h + $(CXX) $(CXXFLAGS) $(CPPFLAGS) -c ../../common/clientlist.cpp -o $@ + +controlobject.o: ../../common/controlobject.cpp ../../common/controlobject.h + $(CXX) $(CXXFLAGS) $(CPPFLAGS) -c ../../common/controlobject.cpp -o $@ + +tcpipsrv.o: ../../common/tcpipsrv.cpp ../../common/tcpipsrv.h + $(CXX) $(CXXFLAGS) $(CPPFLAGS) -c ../../common/tcpipsrv.cpp -o $@ + +interfacelist.o: ../../common/interfacelist.cpp ../../common/interfacelist.h + $(CXX) $(CXXFLAGS) $(CPPFLAGS) -c ../../common/interfacelist.cpp -o $@ + +userlist.o: ../../common/userlist.cpp ../../common/userlist.h + $(CXX) $(CXXFLAGS) $(CPPFLAGS) -c ../../common/userlist.cpp -o $@ + +devicethread.o: ../../common/devicethread.cpp ../../common/devicethread.h + $(CXX) $(CXXFLAGS) $(CPPFLAGS) -c ../../common/devicethread.cpp -o $@ + +websrv.o: ../../common/websrv.cpp ../../common/websrv.h + $(CXX) $(CXXFLAGS) $(CPPFLAGS) -c ../../common/websrv.cpp -o $@ + +websocketsrv.o: ../../common/websocketsrv.cpp ../../common/websocket.h + $(CXX) $(CXXFLAGS) $(CPPFLAGS) -c ../../common/websocketsrv.cpp -o $@ + +restsrv.o: ../../common/restsrv.cpp ../../common/restsrv.h + $(CXX) $(CXXFLAGS) $(CPPFLAGS) -c ../../common/restsrv.cpp -o $@ + +vscphelper.o: ../../common/vscphelper.cpp ../../common/vscphelper.h + $(CXX) $(CXXFLAGS) $(CPPFLAGS) -c ../../common/vscphelper.cpp -o $@ + +vscpremotetcpif.o: ../../common/vscpremotetcpif.cpp ../../common/vscpremotetcpif.h + $(CXX) $(CXXFLAGS) $(CPPFLAGS) -c ../../common/vscpremotetcpif.cpp -o $@ + +automation.o: ../../common/automation.cpp ../../common/automation.h + $(CXX) $(CXXFLAGS) $(CPPFLAGS) -c ../../common/automation.cpp -o $@ + +devicelist.o: ../../common/devicelist.cpp ../../common/devicelist.h + $(CXX) $(CXXFLAGS) $(CPPFLAGS) -c ../../common/devicelist.cpp -o $@ + +sockettcp.o: ../../../common/sockettcp.c ../../../common/sockettcp.h + $(CC) $(CFLAGS) -c ../../../common/sockettcp.c -o $@ + +guid.o: ../../common/guid.cpp ../../common/guid.h + $(CXX) $(CXXFLAGS) $(CPPFLAGS) -c ../../common/guid.cpp -o $@ + +mdf.o: ../../common/mdf.cpp ../../common/mdf.h + $(CXX) $(CXXFLAGS) $(CPPFLAGS) -c ../../common/mdf.cpp -o $@ + +register.o: ../../common/register.cpp ../../common/register.h + $(CXX) $(CXXFLAGS) $(CPPFLAGS) -c ../../common/register.cpp -o $@ + +configfile.o: ../../../common/configfile.cpp ../../../common/configfile.h + $(CXX) $(CXXFLAGS) $(CPPFLAGS) -c ../../../common/configfile.cpp -o $@ + +vscpdatetime.o: ../../common/vscpdatetime.cpp ../../common/vscpdatetime.h + $(CXX) $(CXXFLAGS) $(CPPFLAGS) -c ../../common/vscpdatetime.cpp -o $@ + +dllist.o: ../../../common/dllist.c ../../../common/dllist.h + $(CC) $(CFLAGS) -c ../../../common/dllist.c -o $@ + +vscp_aes.o: ../../../common/vscp_aes.c ../../../common/vscp_aes.h + $(CC) $(CFLAGS) -c ../../../common/vscp_aes.c -o $@ + +crc.o: ../../../common/crc.c ../../../common/crc.h + $(CC) $(CFLAGS) -c ../../../common/crc.c -o $@ + +vscpbase64.o: ../../../common/vscpbase64.c ../../../common/vscpbase64.h + $(CC) $(CFLAGS) -c ../../../common/vscpbase64.c -o $@ + +crc8.o: ../../../common/crc8.c ../../../common/crc8.h + $(CC) $(CFLAGS) -c ../../../common/crc8.c -o $@ + +randpassword.o: ../../../common/randpassword.cpp ../../../common/randpassword.h + $(CXX) $(CXXFLAGS) $(CPPFLAGS) -c ../../../common/randpassword.cpp -o $@ + +fastpbkdf2.o: ../../../common/fastpbkdf2.c ../../../common/fastpbkdf2.h + $(CC) $(CFLAGS) -c ../../../common/fastpbkdf2.c -o $@ + +vscpmd5.o: ../../../common/vscpmd5.c ../../../common/vscpmd5.h + $(CC) $(CFLAGS) -c ../../../common/vscpmd5.c -o $@ + +civetweb.o: ../../../common/civetweb.c ../../../common/civetweb.h + $(CC) $(CFLAGS) $(CIVETWEB_FLAGS) -c ../../../common/civetweb.c -o $@ + +duktape.o: ../../../common/third_party/duktape-2.5.0/src/duktape.c ../../../common/third_party/duktape-2.5.0/src/duktape.h + $(CC) $(CFLAGS) -c ../../../common/third_party/duktape-2.5.0/src/duktape.c -o $@ + +duktape_vscp_func.o: ../../common/duktape_vscp_func.cpp ../../common/duktape_vscp_func.h + $(CXX) $(CXXFLAGS) $(CPPFLAGS) -c ../../common/duktape_vscp_func.cpp -o $@ + +duktape_vscp_wrkthread.o: ../../common/duktape_vscp_wrkthread.cpp ../../common/duktape_vscp_wrkthread.h + $(CXX) $(CXXFLAGS) $(CPPFLAGS) -c ../../common/duktape_vscp_wrkthread.cpp -o $@ + +lsqlite3.o: ../../../common/third_party/lsqlite3.c + $(CC) $(CFLAGS) -c ../../../common/third_party/lsqlite3.c -o $@ + +lfs.o: ../../../common/third_party/lfs.c + $(CC) $(CFLAGS) -c ../../../common/third_party/lfs.c -o $@ + +LuaXML_lib.o: ../../../common/third_party/LuaXML_lib.c + $(CC) $(CFLAGS) -c ../../../common/third_party/LuaXML_lib.c -o $@ + +lua_all.o: ../../../common/third_party/lua_all.c + $(CC) $(CFLAGS) -c ../../../common/third_party/lua_all.c -o $@ + +lua_vscp_func.o: ../../common/lua_vscp_func.cpp ../../common/lua_vscp_func.h + $(CXX) $(CXXFLAGS) $(CPPFLAGS) -c ../../common/lua_vscp_func.cpp -o $@ + +lua_vscp_wrkthread.o: ../../common/lua_vscp_wrkthread.cpp ../../common/lua_vscp_wrkthread.h + $(CXX) $(CXXFLAGS) $(CPPFLAGS) -c ../../common/lua_vscp_wrkthread.cpp -o $@ + +sqlite3.o: ../../../common/third_party/sqlite3-3.30.1/sqlite3.c + $(CC) $(CFLAGS) $(CPPFLAGS) -c ../../../common/third_party/sqlite3-3.30.1/sqlite3.c -o $@ + +sqlite3math.o: ../../../common/third_party/sqlite3math.c + $(CC) $(CFLAGS) $(CPPFLAGS) -c ../../../common/third_party/sqlite3math.c -o $@ + +vscpd: $(VSCPD_OBJECTS) + $(CXX) -o $@ $(VSCPD_OBJECTS) $(LDFLAGS) $(EXTRALIBS) + +install: all install-folders install-startup-script install-configfile install-sample-certs + $(STRIP) vscpd + $(INSTALL_PROGRAM) -d $(DESTDIR)$(bindir) + $(INSTALL_PROGRAM) vscpd $(DESTDIR)$(bindir) + $(INSTALL_PROGRAM) -m 600 ../../../../debian_orig/vscp.key $(DESTDIR)etc/vscp/certs + $(INSTALL_PROGRAM) -m 644 debian/README-ca_certificates $(DESTDIR)/etc/vscp/ca_certificates/README + $(INSTALL_PROGRAM) -m 600 debian/README-certs $(DESTDIR)/etc/vscp/certs/README + $(INSTALL_PROGRAM) -m 600 debian/vscp.key $(DESTDIR)/etc/vscp/certs/vscp.key + $(INSTALL_PROGRAM) -m 644 debian/vscpd.conf $(DESTDIR)/etc/vscp/vscpd.conf + $(INSTALL_PROGRAM) -m 644 ./service/systemd/vscpd.service.notify $(DESTDIR)/$(libdir)/systemd/system/vscpd.service + +install-folders: + @echo "- Create folder structure." + $(INSTALL_PROGRAM) -d $(DESTDIR)/var/logs/vscp + $(INSTALL_PROGRAM) -d $(DESTDIR)/etc/vscp/certs + $(INSTALL_PROGRAM) -d $(DESTDIR)/etc/vscp/ca-certificates + $(INSTALL_PROGRAM) -d $(DESTDIR)/usr/share/vscp + $(INSTALL_PROGRAM) -d $(DESTDIR)/usr/share/vscp/vscpd + $(INSTALL_PROGRAM) -d $(DESTDIR)/usr/share/vscp/vscpd/certs + $(INSTALL_PROGRAM) -d $(DESTDIR)/var/log/vscp + $(INSTALL_PROGRAM) -d $(DESTDIR)/var/lib/vscp + $(INSTALL_PROGRAM) -d $(DESTDIR)/var/lib/vscp/vscpd + $(INSTALL_PROGRAM) -d $(DESTDIR)/var/lib/vscp/drivers + $(INSTALL_PROGRAM) -d $(DESTDIR)/var/lib/vscp/drivers/level1 + $(INSTALL_PROGRAM) -d $(DESTDIR)/var/lib/vscp/drivers/level2 + $(INSTALL_PROGRAM) -d $(DESTDIR)/var/lib/vscp/web + $(INSTALL_PROGRAM) -d $(DESTDIR)/var/lib/vscp/web/html + $(INSTALL_PROGRAM) -d $(DESTDIR)/var/lib/vscp/web/html/images + $(INSTALL_PROGRAM) -d $(DESTDIR)/var/lib/vscp/web/html/css + $(INSTALL_PROGRAM) -d $(DESTDIR)/var/lib/vscp/web/html/js + $(INSTALL_PROGRAM) -d $(DESTDIR)/usr/lib/systemd/system/ + +install-startup-script: + @echo "- Installing startup script." + @$(INSTALL_PROGRAM) -d $(DESTDIR)/etc + $(INSTALL_PROGRAM) -d $(DESTDIR)/etc/init.d + @if [ ! -e $(DESTDIR)/etc/init.d/vscpd ]; then\ + echo "- Copying startup script";\ + $(INSTALL_PROGRAM) -b -m755 ../../../../debian_orig/vscpd.init $(DESTDIR)/etc/init.d/vscpd;\ + echo "- Installing startup script";\ + update-rc.d vscpd defaults;\ + fi + +install-configfile: + @echo "- Installing main configuration file." + @mkdir -p $(DESTDIR)/etc/vscp + @if [ ! -e $(DESTDIR)/etc/vscp/vscpd.conf ]; then\ + $(INSTALL_PROGRAM) -b -m744 ../../../../install_files/unix/vscpd.conf $(DESTDIR)/etc/vscp/vscpd.conf;\ + else\ + $(INSTALL_PROGRAM) -b -m744 ../../../../install_files/unix/vscpd.conf $(DESTDIR)/etc/vscp/vscpd.conf.`date +'%Y%m%d'`;\ + fi + +install-sample-certs: + @echo "- Installing server sample certificat." + @if [ ! -e @VSCPDIR@/certs/server.pem ]; then\ + $(INSTALL_PROGRAM) -b -m744 ../../../../install_files/certs/server.pem $(DESTDIR)/usr/share/vscp/vscpd/certs;\ + fi + + @echo "- Installing client sample certificat." + @if [ ! -e @VSCPDIR@/certs/client.pem ]; then\ + $(INSTALL_PROGRAM) -b -m744 ../../../../install_files/certs/client.pem $(DESTDIR)/etc/vscp/certs;\ + fi + +uninstall: + @echo "- Uninstall vscpd." + rm -rf $(DESTDIR)/usr/share/vscpd + rm -rf $(DESTDIR)/var/lib/vscp + rm -rf $(DESTDIR)/var/log/vscp + rm -rf $(DESTDIR)/var/web-vscp + rm -rf $(DESTDIR)/etc/vscp/certs + rm -rf $(DESTDIR)/etc/vscp/ca-certificats + rm -f $(DESTDIR)/etc/init.d/vscpd + rm -rf $(DESTDIR)/var/logs/vscp + em -rf $(DESTDIR)/var/web-vscp + + rm -f $(DESTDIR)/etc/vscp/vscpd.conf + rm -rf $(DESTDIR)/etc/vscp/vscpd.conf.* + # Remove /etc/vscp if empty + if [ ! "$(ls -A /etc/vscp)" ]; then\ + rm -rf $(DESTDIR)/etc/vscp;\ + fi + + rm -rf $(DESTDIR)/usr/share/vscp/vscpd + # Remove /usr/share/vscp if empty + if [ ! "$(ls -A /usr/share/vscp)" ]; then\ + rm -rf $(DESTDIR)/usr/share/vscp;\ + fi + + rm -rf $(DESTDIR)/var/lib/vscp/vscpd + # Remove /var/lib/vscp if empty + if [ ! "$(ls -A /var/lib/vscp)" ]; then\ + rm -rf $(DESTDIR)/var/lib/vscp;\ + fi + + rm -rf $(DESTDIR)/usr/share/vscp/vscpd + # Remove /usr/share/vscp if empty + if [ ! "$(ls -A /usr/share/vscp)" ]; then\ + rm -rf $(DESTDIR)/usr/share/vscp;\ + fi + +install-strip: install + +deb: all + echo "\n" | ./builddist.sh + @mkdir -p ../../../../dist + @mv *.deb ../../../../dist + +clean: + rm -rf ./.deps ./.pch + rm -f *.o + rm -f ./*.o + rm -f ../../common/*.o + rm -f ../../../common/*.o + rm -f ../../../vscp/common/*.o + rm -f *.deb + rm -f *.gz + rm -f vscpd + +distclean: clean + rm -f config.cache config.log config.status bk-deps bk-make-pch shared-ld-sh Makefile + +$(ALWAYS_BUILD): .FORCE + +.FORCE: + +# Include dependency info, if present: +-include .deps/*.d + +.PHONY: all install uninstall clean distclean data .FORCE diff --git a/src/vscp/vscphubd/controlobject.cpp b/src/vscp/vscphubd/controlobject.cpp new file mode 100644 index 000000000..c58aef785 --- /dev/null +++ b/src/vscp/vscphubd/controlobject.cpp @@ -0,0 +1,2781 @@ +// ControlObject.cpp: m_path_db_vscp_logimplementation of the CControlObject +// class. +// +// This file is part of the VSCP (https://www.vscp.org) +// +// The MIT License (MIT) +// +// Copyright © 2000-2024 Ake Hedman, Grodans Paradis AB +// +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +// +// + +#define _POSIX + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#ifdef WITH_SYSTEMD +#include +#endif + +#include + +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define UNUSED(x) (void)(x) +void +foo(const int i) +{ + UNUSED(i); +} + +#ifndef _CRT_SECURE_NO_WARNINGS +#define _CRT_SECURE_NO_WARNINGS +#endif + +#define XML_BUFF_SIZE 0xffff + +// Prototypes +void +createFolderStuct(std::string& rootFolder); // from vscpd.cpp + +void* +clientMsgWorkerThread(void* userdata); // this +void* +tcpipListenThread(void* pData); // tcpipsev.cpp +void* +UDPThread(void* pData); // udpsrv.cpp + +////////////////////////////////////////////////////////////////////// +// Construction/Destruction +////////////////////////////////////////////////////////////////////// + +CControlObject::CControlObject() +{ + // Open syslog + openlog("vscpd", LOG_CONS, LOG_DAEMON); + + if (__VSCP_DEBUG_EXTRA) { + syslog(LOG_DEBUG, "Starting the vscpd daemon"); + } + + m_bQuit = false; // true for app termination + m_bQuit_clientMsgWorkerThread = + false; // true for clientWorkerThread termination + + if (-1 == sem_init(&m_semClientOutputQueue, 0, 0)) { + syslog(LOG_ERR, "Unable to init m_semClientOutputQueue"); + return; + } + + if (-1 == sem_init(&m_semSentToAllClients, 0, 0)) { + syslog(LOG_ERR, "Unable to init m_semSentToAllClients"); + return; + } + + if (0 != pthread_mutex_init(&m_mutex_ClientOutputQueue, NULL)) { + syslog(LOG_ERR, "Unable to init m_mutex_ClientOutputQueue"); + return; + } + + if (0 != pthread_mutex_init(&m_mutex_websrvSession, NULL)) { + syslog(LOG_ERR, "Unable to init m_mutex_websrvSession"); + return; + } + + if (0 != pthread_mutex_init(&m_mutex_restSession, NULL)) { + syslog(LOG_ERR, "Unable to init m_mutex_restSession"); + return; + } + + if (0 != pthread_mutex_init(&m_mutex_websocketSession, NULL)) { + syslog(LOG_ERR, "Unable to init m_mutex_websocketSession"); + return; + } + + if (0 != pthread_mutex_init(&m_mutex_DeviceList, NULL)) { + syslog(LOG_ERR, "Unable to init m_mutex_DeviceList"); + return; + } + + if (0 != pthread_mutex_init(&m_mutex_clientList, NULL)) { + syslog(LOG_ERR, "Unable to init m_mutex_clientList"); + return; + } + + if (0 != pthread_mutex_init(&m_mutex_UserList, NULL)) { + syslog(LOG_ERR, "Unable to init m_mutex_UserList"); + return; + } + + m_rootFolder = "/var/lib/vscp/vscpd/"; + + // Default admin user credentials + m_admin_user = "admin"; + m_admin_password = "450ADCE88F2FDBB20F3318B65E53CA4A;" + "06D3311CC2195E80BE4F8EB12931BFEB5C" + "630F6B154B2D644ABE29CEBDBFB545"; + m_admin_allowfrom = ""; // All access + m_vscptoken = "Carpe diem quam minimum credula postero"; + vscp_hexStr2ByteArray(m_systemKey, + 32, + "A4A86F7D7E119BA3F0CD06881E371B989B" + "33B6D606A863B633EF529D64544F8E"); + + m_automation.setControlObject(this); + m_maxItemsInClientReceiveQueue = MAX_ITEMS_CLIENT_RECEIVE_QUEUE; + + // Nill the GUID + m_guid.clear(); + + // web admin interface + m_enableWebAdminIf = true; + + // Local domain + m_web_authentication_domain = "mydomain.com"; + + // Default TCP/IP interface settings + m_enableTcpip = true; + m_strTcpInterfaceAddress = "9598"; + m_encryptionTcpip = 0; + m_tcpip_ssl_certificate.clear(); + m_tcpip_ssl_certificate_chain.clear(); + m_tcpip_ssl_verify_peer = 0; // no=0, optional=1, yes=2 + m_tcpip_ssl_ca_path.clear(); + m_tcpip_ssl_ca_file.clear(); + m_tcpip_ssl_verify_depth = 9; + m_tcpip_ssl_default_verify_paths = false; + m_tcpip_ssl_cipher_list.clear(); + m_tcpip_ssl_protocol_version = 0; + m_tcpip_ssl_short_trust = false; + + // Web server SSL settings + m_web_ssl_certificate = "/etc/vscp/certs/server.pem"; + m_web_ssl_certificate_chain = ""; + m_web_ssl_verify_peer = false; + m_web_ssl_ca_path = ""; + m_web_ssl_ca_file = ""; + m_web_ssl_verify_depth = 9; + m_web_ssl_default_verify_paths = true; + m_web_ssl_cipher_list = "DES-CBC3-SHA:AES128-SHA:AES128-GCM-SHA256"; + m_web_ssl_protocol_version = 3; + m_web_ssl_short_trust = false; + + // Webserver interface + m_web_bEnable = true; + m_web_listening_ports = "[::]:9999r,[::]:8843s,8884"; + + m_web_index_files = "index.xhtml,index.html,index.htm," + "index.lp,index.lsp,index.lua,index.cgi," + "index.shtml,index.php"; + + m_web_document_root = m_rootFolder + "www/html"; + + // Directory listings on by default + m_web_enable_directory_listing = true; + m_web_enable_keep_alive = false; + m_web_keep_alive_timeout_ms = 0; + m_web_access_control_list = ""; + m_web_extra_mime_types = ""; + m_web_num_threads = 50; + m_web_url_rewrite_patterns = ""; + m_web_hide_file_patterns = ""; + m_web_global_auth_file = ""; + m_web_per_directory_auth_file = ""; + m_web_ssi_patterns = ""; + m_web_url_rewrite_patterns = ""; + m_web_request_timeout_ms = 10000; + m_web_linger_timeout_ms = -1; // Do not set + m_web_decode_url = true; + m_web_ssi_patterns = ""; + m_web_access_control_allow_origin = "*"; + m_web_access_control_allow_methods = "*"; + m_web_access_control_allow_headers = "*"; + m_web_error_pages = ""; + m_web_tcp_nodelay = 0; + m_web_static_file_cache_control = ""; + m_web_static_file_max_age = 3600; + m_web_strict_transport_security_max_age = -1; + m_web_allow_sendfile_call = true; + m_web_additional_header = ""; + m_web_max_request_size = 16384; + m_web_allow_index_script_resource = false; + m_web_duktape_script_patterns = "**.ssjs$"; + m_web_lua_preload_file = ""; + m_web_lua_script_patterns = "**.lua$"; + m_web_lua_server_page_patterns = "**.lp$|**.lsp$"; + m_web_lua_websocket_patterns = "**.lua$"; + m_web_lua_background_script = ""; + m_web_lua_background_script_params = ""; + + m_bWebsocketsEnable = true; + m_websocket_document_root = ""; + m_websocket_timeout_ms = atoi(VSCPDB_CONFIG_DEFAULT_WEBSOCKET_TIMEOUT_MS); + bEnable_websocket_ping_pong = false; + lua_websocket_pattern = + std::string(VSCPDB_CONFIG_DEFAULT_WEB_LUA_WEBSOCKET_PATTERN); + + m_bEnableRestApi = true; + + // Init. web server subsystem - All features enabled + // ssl mt locks will we initiated here for openssl 1.0 + if (0 == mg_init_library(MG_FEATURES_IPV6 | MG_FEATURES_WEBSOCKET | + MG_FEATURES_LUA | MG_FEATURES_SSJS | + MG_FEATURES_COMPRESSION)) { + syslog(LOG_ERR, "Failed to initialize webserver subsystem."); + } + + // Initialize the CRC + crcInit(); +} + +/////////////////////////////////////////////////////////////////////////////// +// Destructor +// + +CControlObject::~CControlObject() +{ + if (__VSCP_DEBUG_EXTRA) { + syslog(LOG_DEBUG, "Cleaning up"); + } + + // Remove objects in Client send queue + std::list::iterator iterVSCP; + pthread_mutex_lock(&m_mutex_ClientOutputQueue); + for (iterVSCP = m_clientOutputQueue.begin(); + iterVSCP != m_clientOutputQueue.end(); + ++iterVSCP) { + vscpEvent* pEvent = *iterVSCP; + vscp_deleteEvent_v2(&pEvent); + } + m_clientOutputQueue.clear(); + pthread_mutex_unlock(&m_mutex_ClientOutputQueue); + + // Remove all clients + m_clientList.removeAllClients(); + + // Clean up civetweb + mg_exit_library(); + + if (0 != sem_destroy(&m_semClientOutputQueue)) { + syslog(LOG_ERR, "Unable to destroy m_semClientOutputQueue"); + } + + if (0 != sem_destroy(&m_semSentToAllClients)) { + syslog(LOG_ERR, "Unable to destroy m_semSentToAllClients"); + } + + if (0 != pthread_mutex_destroy(&m_mutex_ClientOutputQueue)) { + syslog(LOG_ERR, "Unable to destroy m_mutex_ClientOutputQueue"); + return; + } + + if (0 != pthread_mutex_destroy(&m_mutex_websrvSession)) { + syslog(LOG_ERR, "Unable to destroy m_mutex_websrvSession"); + return; + } + + if (0 != pthread_mutex_destroy(&m_mutex_restSession)) { + syslog(LOG_ERR, "Unable to destroy m_mutex_restSession"); + return; + } + + if (0 != pthread_mutex_destroy(&m_mutex_websocketSession)) { + syslog(LOG_ERR, "Unable to destroy m_mutex_websocketSession"); + return; + } + + if (0 != pthread_mutex_destroy(&m_mutex_DeviceList)) { + syslog(LOG_ERR, "Unable to destroy m_mutex_DeviceList"); + return; + } + + if (0 != pthread_mutex_destroy(&m_mutex_clientList)) { + syslog(LOG_ERR, "Unable to destroy m_mutex_clientList"); + return; + } + + if (0 != pthread_mutex_destroy(&m_mutex_UserList)) { + syslog(LOG_ERR, "Unable to destroy m_mutex_UserList"); + return; + } + + if (__VSCP_DEBUG_EXTRA) { + syslog(LOG_DEBUG, "Terminating the vscpd daemon"); + } + + // Close syslog + closelog(); +} + +///////////////////////////////////////////////////////////////////////////// +// init +// + +bool +CControlObject::init(std::string& strcfgfile, std::string& rootFolder) +{ + std::string str; + + // Save root folder for later use. + m_rootFolder = rootFolder; + + // Root folder must exist + if (!vscp_fileExists(m_rootFolder.c_str())) { + syslog(LOG_ERR, + "The specified rootfolder does not exist (%s).", + (const char*)m_rootFolder.c_str()); + return false; + } + + std::string strRootwww = m_rootFolder + "www/html"; + m_web_document_root = strRootwww; + + // Change locale to get the correct decimal point "." + setlocale(LC_NUMERIC, "C"); + + // A configuration file must be available + if (!vscp_fileExists(strcfgfile.c_str())) { + printf("No configuration file. Can't initialize!."); + syslog(LOG_ERR, + "No configuration file. Can't initialize!. Path=%s", + strcfgfile.c_str()); + return false; + } + + //////////////////////////////////////////////////////////////////////////// + // Read XML configuration + //////////////////////////////////////////////////////////////////////////// + + // Read XML configuration + if (__VSCP_DEBUG_EXTRA) { + syslog(LOG_DEBUG, "Reading configuration file"); + } + + // Read XML configuration + try { + if (!readConfiguration(strcfgfile)) { + syslog(LOG_ERR, + "Unable to open/parse configuration file. Can't initialize! " + "Path =%s", + strcfgfile.c_str()); + return FALSE; + } + } + catch (...) { + syslog(LOG_ERR, "Exception when reading configuration file"); + return FALSE; + } + +#ifndef WIN32 + if (m_runAsUser.length()) { + struct passwd* pw; + if (NULL == (pw = getpwnam(m_runAsUser.c_str()))) { + syslog(LOG_ERR, "Unknown user."); + } + else if (setgid(pw->pw_gid) != 0) { + syslog(LOG_ERR, "setgid() failed. [%s]", strerror(errno)); + } + else if (setuid(pw->pw_uid) != 0) { + syslog(LOG_ERR, "setuid() failed. [%s]", strerror(errno)); + } + } +#endif + + if (__VSCP_DEBUG_EXTRA) { + syslog(LOG_DEBUG, "Using configuration file: %s", strcfgfile.c_str()); + } + + //========================================================================== + // Add admin user + //========================================================================== + + m_userList.addSuperUser(m_admin_user, + m_admin_password, + m_web_authentication_domain, + m_admin_allowfrom); // Remotes allows to connect + + //========================================================================== + // Add driver user + //========================================================================== + + // Generate username and password for drivers + char buf[128]; + randPassword pw(4); + + // Level III Driver Username + memset(buf, 0, sizeof(buf)); + pw.generatePassword(32, buf); + m_driverUsername = "drv_"; + m_driverUsername += std::string(buf); + + // Level III Driver Password (can't contain ";" character) + memset(buf, 0, sizeof(buf)); + pw.generatePassword(32, buf); + m_driverPassword = buf; + + std::string drvhash; + vscp_makePasswordHash(drvhash, std::string(buf)); + + m_userList.addUser(m_driverUsername, + drvhash, // salt;hash + "System added driver user.", // full name + "System added driver user.", // note + m_web_authentication_domain, + NULL, + "driver", + "+127.0.0.0/24", // Only local + "*:*", // All events + 0); + + // Get GUID + if (m_guid.isNULL()) { + if (!getMacAddress(m_guid)) { + // We failed to create GUID from MAC address use + // 'localhost' IP instead as the base. + getIPAddress(m_guid); + } + } + + // If no server name set construct one + if (0 == m_strServerName.length()) { + m_strServerName = "VSCP Server @ "; + std::string strguid; + m_guid.toString(strguid); + m_strServerName += std::string(strguid); + } + + str = "VSCP Server started - "; + str += "Version: "; + str += VSCPD_DISPLAY_VERSION; + str += " - "; + str += VSCPD_COPYRIGHT; + syslog(LOG_INFO, "%s", str.c_str()); + + // Start daemon internal client worker thread + try { + startClientMsgWorkerThread(); + } + catch (...) { + syslog(LOG_ERR, "Exception when starting message worker thread"); + return FALSE; + } + + // Start webserver and websockets + // IMPORTANT!!!!!!!! + // Must be started before the tcp/ip server as + // ssl initializarion is done here + try { + start_webserver(); + } + catch (...) { + syslog(LOG_ERR, "Exception when starting web server"); + return FALSE; + } + + // Start TCP/IP interface + try { + startTcpipSrvThread(); + } + catch (...) { + syslog(LOG_ERR, "Exception when starting tcp/ip server"); + return FALSE; + } + + // Load drivers + try { + startDeviceWorkerThreads(); + } + catch (...) { + syslog(LOG_ERR, "Exception when loading drivers"); + return FALSE; + } + + return true; +} + +///////////////////////////////////////////////////////////////////////////// +// run - Program main loop +// +// Most work is done in the threads at the moment +// + +bool +CControlObject::run(void) +{ + std::deque::iterator nodeClient; + + // We need to create a clientItem and add this object to the list + CClientItem* pClientItem = new CClientItem; + if (NULL == pClientItem) { + syslog(LOG_ERR, "Unable to allocate Client item, Ending."); + return false; + } + + // This is an active client + pClientItem->m_bOpen = true; + pClientItem->m_type = CLIENT_ITEM_INTERFACE_TYPE_CLIENT_INTERNAL; + pClientItem->m_strDeviceName = "Internal Server Client.|Started at "; + pClientItem->m_strDeviceName += vscpdatetime::Now().getISODateTime(); + + // Add the client to the Client List + pthread_mutex_lock(&m_clientList.m_mutexItemList); + if (!addClient(pClientItem, CLIENT_ID_INTERNAL)) { + // Failed to add client + delete pClientItem; + syslog(LOG_ERR, "ControlObject: Failed to add internal client."); + pthread_mutex_unlock(&m_clientList.m_mutexItemList); + delete pClientItem; + return false; + } + pthread_mutex_unlock(&m_clientList.m_mutexItemList); + + if (__VSCP_DEBUG_EXTRA) { + syslog(LOG_DEBUG, "Mainloop starting"); + } + +#ifdef WITH_SYSTEMD + sd_notify(0, "READY=1"); +#endif + + //------------------------------------------------------------------------- + // MAIN - LOOP + //------------------------------------------------------------------------- + + struct timespec now, old_now; + clock_gettime(CLOCK_REALTIME, &old_now); + old_now.tv_sec -= 60; // Do firts send right away + + while (!m_bQuit) { + + clock_gettime(CLOCK_REALTIME, &now); + + // We send heartbeat every minute + if ((now.tv_sec-old_now.tv_sec) > 60) { + + // Save time + clock_gettime(CLOCK_REALTIME, &old_now); + + if (!automation(pClientItem)) { + syslog(LOG_ERR, "Failed to send automation events!"); + } + } + + // Wait for event + if ((-1 == vscp_sem_wait(&m_semSentToAllClients, 10)) && + errno == ETIMEDOUT) { + continue; + } + + // Send events to websocket clients + websock_post_incomingEvents(); + + //---------------------------------------------------------------------- + // Event received here + // from one of the incoming source + //---------------------------------------------------------------------- + + if (pClientItem->m_clientInputQueue.size()) { + + vscpEvent* pEvent; + + pthread_mutex_lock(&pClientItem->m_mutexClientInputQueue); + pEvent = pClientItem->m_clientInputQueue.front(); + pClientItem->m_clientInputQueue.pop_front(); + pthread_mutex_unlock(&pClientItem->m_mutexClientInputQueue); + + if (NULL != pEvent) { + } + + vscp_deleteEvent_v2(&pEvent); + + } // Event in queue + + } // while + + // Remove messages in the client queues + pthread_mutex_lock(&m_clientList.m_mutexItemList); + removeClient(pClientItem); + pthread_mutex_unlock(&m_clientList.m_mutexItemList); + + // Clean up is called in main file + + if (__VSCP_DEBUG_EXTRA) { + syslog(LOG_DEBUG, "Mainloop ending"); + } + + return true; +} + +///////////////////////////////////////////////////////////////////////////// +// automation + +bool +CControlObject::automation(CClientItem* pClientItem) +{ + vscpEventEx ex; + + // Send VSCP_CLASS1_INFORMATION, + // Type=9/VSCP_TYPE_INFORMATION_NODE_HEARTBEAT + ex.obid = 0; // IMPORTANT Must be set by caller before event is sent + ex.head = 0; + ex.timestamp = vscp_makeTimeStamp(); + vscp_setEventExToNow(&ex); // Set time to current time + ex.vscp_class = VSCP_CLASS1_INFORMATION; + ex.vscp_type = VSCP_TYPE_INFORMATION_NODE_HEARTBEAT; + ex.sizeData = 3; + + // GUID + memcpy(ex.data + VSCP_CAPABILITY_OFFSET_GUID, m_guid.getGUID(), 16); + + ex.data[0] = 0; // index + ex.data[1] = 0; // zone + ex.data[2] = 0; // subzone + + if (!sendEvent(pClientItem, &ex)) { + syslog(LOG_ERR, "Failed to send Class1 heartbeat"); + } + + // Send VSCP_CLASS2_INFORMATION, + // Type=2/VSCP2_TYPE_INFORMATION_HEART_BEAT + ex.obid = 0; // IMPORTANT Must be set by caller before event is sent + ex.head = 0; + ex.timestamp = vscp_makeTimeStamp(); + vscp_setEventExToNow(&ex); // Set time to current time + ex.vscp_class = VSCP_CLASS2_INFORMATION; + ex.vscp_type = VSCP2_TYPE_INFORMATION_HEART_BEAT; + ex.sizeData = 64; + + // GUID + memcpy(ex.data + VSCP_CAPABILITY_OFFSET_GUID, m_guid.getGUID(), 16); + + memset(ex.data, 0, sizeof(ex.data)); + memcpy(ex.data, + m_strServerName.c_str(), + std::min((int)strlen(m_strServerName.c_str()), 64)); + + if (!sendEvent(pClientItem, &ex)) { + syslog(LOG_ERR, "Failed to send Class2 heartbeat"); + } + + // Send VSCP_CLASS1_PROTOCOL, + // Type=1/VSCP_TYPE_PROTOCOL_SEGCTRL_HEARTBEAT + ex.obid = 0; // IMPORTANT Must be set by caller before event is sent + ex.head = 0; + ex.timestamp = vscp_makeTimeStamp(); + vscp_setEventExToNow(&ex); // Set time to current time + ex.vscp_class = VSCP_CLASS1_PROTOCOL; + ex.vscp_type = VSCP_TYPE_PROTOCOL_SEGCTRL_HEARTBEAT; + ex.sizeData = 5; + + // GUID + memcpy(ex.data + VSCP_CAPABILITY_OFFSET_GUID, m_guid.getGUID(), 16); + + time_t tnow; + time(&tnow); + uint32_t time32 = (uint32_t)tnow; + + ex.data[0] = 0; // 8 - bit crc for VSCP daemon GUID + ex.data[1] = (uint8_t)((time32 >> 24) & 0xff); // Time since epoch MSB + ex.data[2] = (uint8_t)((time32 >> 16) & 0xff); + ex.data[3] = (uint8_t)((time32 >> 8) & 0xff); + ex.data[4] = (uint8_t)((time32)&0xff); // Time since epoch LSB + + if (!sendEvent(pClientItem, &ex)) { + syslog(LOG_ERR, "Failed to send segment controller heartbeat"); + } + + // Send VSCP_CLASS2_PROTOCOL, + // Type=20/VSCP2_TYPE_PROTOCOL_HIGH_END_SERVER_CAPS + ex.obid = 0; // IMPORTANT Must be set by caller before event is sent + ex.head = 0; + ex.timestamp = vscp_makeTimeStamp(); + vscp_setEventExToNow(&ex); // Set time to current time + ex.vscp_class = VSCP_CLASS2_PROTOCOL; + ex.vscp_type = VSCP2_TYPE_PROTOCOL_HIGH_END_SERVER_CAPS; + + // Fill in data + memset(ex.data, 0, sizeof(ex.data)); + + // GUID + memcpy(ex.data + VSCP_CAPABILITY_OFFSET_GUID, m_guid.getGUID(), 16); + + // Server ip address + cguid guid; + if (getIPAddress(guid)) { + ex.data[VSCP_CAPABILITY_OFFSET_IP_ADDR] = guid.getAt(8); + ex.data[VSCP_CAPABILITY_OFFSET_IP_ADDR + 1] = guid.getAt(9); + ex.data[VSCP_CAPABILITY_OFFSET_IP_ADDR + 2] = guid.getAt(10); + ex.data[VSCP_CAPABILITY_OFFSET_IP_ADDR + 3] = guid.getAt(11); + } + + // Server name + memcpy(ex.data + VSCP_CAPABILITY_OFFSET_SRV_NAME, + (const char*)m_strServerName.c_str(), + std::min((int)strlen((const char*)m_strServerName.c_str()), 64)); + + // Capabilities array + getVscpCapabilities(ex.data); + + // non-standard ports + // TODO + + ex.sizeData = 104; + + if (!sendEvent(pClientItem, &ex)) { + syslog(LOG_ERR, "Failed to send high end server capabilities."); + } + + return true; +} + +///////////////////////////////////////////////////////////////////////////// +// cleanup + +bool +CControlObject::cleanup(void) +{ + if (__VSCP_DEBUG_EXTRA) { + syslog(LOG_DEBUG, + "ControlObject: cleanup - Giving worker threads time to stop " + "operations..."); + } + + if (__VSCP_DEBUG_EXTRA) { + syslog(LOG_DEBUG, + "ControlObject: cleanup - Stopping device worker thread..."); + } + + try { + stopDeviceWorkerThreads(); + } + catch (...) { + syslog(LOG_ERR, + "REST: Exception occurred when stoping device worker threads"); + } + + if (__VSCP_DEBUG_EXTRA) { + syslog( + LOG_DEBUG, + "ControlObject: cleanup - Stopping VSCP Server worker thread..."); + } + + // stopDaemonWorkerThread(); ***** + + if (__VSCP_DEBUG_EXTRA) { + syslog(LOG_DEBUG, + "ControlObject: cleanup - Stopping client worker thread..."); + } + + try { + stopClientMsgWorkerThread(); + } + catch (...) { + syslog(LOG_ERR, "Exception occurred when stoping client worker thread"); + } + + if (__VSCP_DEBUG_EXTRA) { + syslog(LOG_DEBUG, + "ControlObject: cleanup - Stopping Web Server worker thread..."); + } + + try { + stop_webserver(); + } + catch (...) { + syslog(LOG_ERR, "REST: Exception occurred when stoping web server"); + } + + if (__VSCP_DEBUG_EXTRA) { + syslog(LOG_DEBUG, + "ControlObject: cleanup - Stopping TCP/IP worker thread..."); + } + + try { + stopTcpipSrvThread(); + } + catch (...) { + syslog(LOG_ERR, "REST: Exception occurred when stoping tcp/ip server"); + } + + if (__VSCP_DEBUG_EXTRA) { + syslog(LOG_DEBUG, "Controlobject: ControlObject: Cleanup done."); + } + + return true; +} + +///////////////////////////////////////////////////////////////////////////// +// startClientMsgWorkerThread +// + +bool +CControlObject::startClientMsgWorkerThread(void) +{ + if (__VSCP_DEBUG_EXTRA) { + syslog(LOG_DEBUG, "Controlobject: Starting client worker thread..."); + } + + if (pthread_create(&m_clientMsgWorkerThread, + NULL, + clientMsgWorkerThread, + this)) { + + syslog(LOG_ERR, "Controlobject: Unable to start client thread."); + return false; + } + + return true; +} + +///////////////////////////////////////////////////////////////////////////// +// stopClientMsgWorkerThread +// + +bool +CControlObject::stopClientMsgWorkerThread(void) +{ + // Request therad to terminate + m_bQuit_clientMsgWorkerThread = true; + pthread_join(m_clientMsgWorkerThread, NULL); + + return true; +} + +///////////////////////////////////////////////////////////////////////////// +// startTcpWorkerThread +// + +bool +CControlObject::startTcpipSrvThread(void) +{ + if (!m_enableTcpip) { + if (__VSCP_DEBUG_TCP) { + syslog(LOG_DEBUG, "Controlobject: TCP/IP interface disabled."); + } + return true; + } + + if (__VSCP_DEBUG_TCP) { + syslog(LOG_DEBUG, "Controlobject: Starting TCP/IP interface..."); + } + + // Create the tcp/ip server data object + m_ptcpipSrvObject = (tcpipListenThreadObj*)new tcpipListenThreadObj(this); + if (NULL == m_ptcpipSrvObject) { + syslog(LOG_ERR, + "Controlobject: Failed to allocate storage for tcp/ip."); + } + + // Set the port to listen for connections on + m_ptcpipSrvObject->setListeningPort(m_strTcpInterfaceAddress); + + if (pthread_create(&m_tcpipListenThread, + NULL, + tcpipListenThread, + m_ptcpipSrvObject)) { + delete m_ptcpipSrvObject; + m_ptcpipSrvObject = NULL; + syslog(LOG_ERR, + "Controlobject: Unable to start the tcp/ip listen thread."); + return false; + } + + return true; +} + +///////////////////////////////////////////////////////////////////////////// +// stopTcpWorkerThread +// + +bool +CControlObject::stopTcpipSrvThread(void) +{ + // Tell the thread it's time to quit + m_ptcpipSrvObject->m_nStopTcpIpSrv = VSCP_TCPIP_SRV_STOP; + + if (__VSCP_DEBUG_TCP) { + syslog(LOG_DEBUG, "Controlobject: Terminating TCP thread."); + } + + pthread_join(m_tcpipListenThread, NULL); + delete m_ptcpipSrvObject; + m_ptcpipSrvObject = NULL; + + if (__VSCP_DEBUG_TCP) { + syslog(LOG_DEBUG, "Controlobject: Terminated TCP thread."); + } + + return true; +} + +//////////////////////////////////////////////////////////////////////////////// +// startDeviceWorkerThreads +// + +bool +CControlObject::startDeviceWorkerThreads(void) +{ + CDeviceItem* pDeviceItem; + if (__VSCP_DEBUG_EXTRA) { + syslog(LOG_DEBUG, "[Controlobject][Driver] - Starting drivers..."); + } + + std::deque::iterator it; + for (it = m_deviceList.m_devItemList.begin(); + it != m_deviceList.m_devItemList.end(); + ++it) { + + pDeviceItem = *it; + if (NULL != pDeviceItem) { + + if (__VSCP_DEBUG_EXTRA) { + syslog(LOG_DEBUG, + "Controlobject: [Driver] - Preparing: %s ", + pDeviceItem->m_strName.c_str()); + } + + // Just start if enabled + if (!pDeviceItem->m_bEnable) + continue; + + if (__VSCP_DEBUG_EXTRA) { + syslog(LOG_DEBUG, + "Controlobject: [Driver] - Starting: %s ", + pDeviceItem->m_strName.c_str()); + } + + // Start the driver logic + pDeviceItem->startDriver(this); + + } // Valid device item + } + + return true; +} + +//////////////////////////////////////////////////////////////////////////////// +// stopDeviceWorkerThreads +// + +bool +CControlObject::stopDeviceWorkerThreads(void) +{ + CDeviceItem* pDeviceItem; + + if (__VSCP_DEBUG_EXTRA) { + syslog(LOG_DEBUG, "[Controlobject][Driver] - Stopping drivers..."); + } + std::deque::iterator iter; + for (iter = m_deviceList.m_devItemList.begin(); + iter != m_deviceList.m_devItemList.end(); + ++iter) { + + pDeviceItem = *iter; + if (NULL != pDeviceItem) { + if (__VSCP_DEBUG_EXTRA) { + syslog(LOG_DEBUG, + "Controlobject: [Driver] - Stopping: %s ", + pDeviceItem->m_strName.c_str()); + } + pDeviceItem->stopDriver(); + } + } + + return true; +} + +///////////////////////////////////////////////////////////////////////////// +// generateSessionId +// + +bool +CControlObject::generateSessionId(const char* pKey, char* psid) +{ + char buf[8193]; + + // Check pointers + if (NULL == pKey) + return false; + if (NULL == psid) + return false; + + if (strlen(pKey) > 256) + return false; + + // Generate a random session ID + time_t t; + t = time(NULL); + sprintf(buf, + "__%s_%X%X%X%X_be_hungry_stay_foolish_%X%X", + pKey, + (unsigned int)rand(), + (unsigned int)rand(), + (unsigned int)rand(), + (unsigned int)t, + (unsigned int)rand(), + 1337); + + vscp_md5(psid, (const unsigned char*)buf, strlen(buf)); + + return true; +} + +///////////////////////////////////////////////////////////////////////////// +// getVscpCapabilities +// + +bool +CControlObject::getVscpCapabilities(uint8_t* pCapability) +{ + // Check pointer + if (NULL == pCapability) + return false; + + uint64_t caps = 0; + memset(pCapability, 0, 8); + + // VSCP Multicast interface + // if (m_bEnableMulticast) { + // caps |= VSCP_SERVER_CAPABILITY_MULTICAST_CHANNEL; + // } + + // VSCP TCP/IP interface + if (m_enableTcpip) { + caps |= VSCP_SERVER_CAPABILITY_TCPIP; + } + + // VSCP UDP interface + // if (m_udpSrvObj.m_bEnable) { + // caps |= VSCP_SERVER_CAPABILITY_UDP; + // } + + // VSCP Multicast announce interface + // if (m_bEnableMulticastAnnounce) { + // caps |= VSCP_SERVER_CAPABILITY_MULTICAST_ANNOUNCE; + // } + + // VSCP raw Ethernet interface + if (1) { + caps |= VSCP_SERVER_CAPABILITY_RAWETH; + } + + // VSCP web server + if (m_web_bEnable) { + caps |= VSCP_SERVER_CAPABILITY_WEB; + } + + // VSCP websocket interface + if (m_web_bEnable) { + caps |= VSCP_SERVER_CAPABILITY_WEBSOCKET; + } + + // VSCP websocket interface + if (m_web_bEnable) { + caps |= VSCP_SERVER_CAPABILITY_REST; + } + + // IPv6 support + if (0) { + caps |= VSCP_SERVER_CAPABILITY_IP6; + } + + // IPv4 support + if (0) { + caps |= VSCP_SERVER_CAPABILITY_IP4; + } + + // SSL support + if (1) { + caps |= VSCP_SERVER_CAPABILITY_SSL; + } + + // +2 tcp/ip connections support + if (m_enableTcpip) { + caps |= VSCP_SERVER_CAPABILITY_TWO_CONNECTIONS; + } + + // AES256 + caps |= VSCP_SERVER_CAPABILITY_AES256; + + // AES192 + caps |= VSCP_SERVER_CAPABILITY_AES192; + + // AES128 + caps |= VSCP_SERVER_CAPABILITY_AES128; + + for (int i = 0; i < 8; i++) { + pCapability[i] = caps & 0xff; + caps = caps >> 8; + } + + return true; +} + +/////////////////////////////////////////////////////////////////////////////// +// sendEventToClient +// + +bool +CControlObject::sendEventToClient(CClientItem* pClientItem, vscpEvent* pEvent) +{ + // Must be valid pointers + if (NULL == pClientItem) { + syslog(LOG_ERR, "sendEventToClient - Pointer to clientitem is null"); + return false; + } + if (NULL == pEvent) { + syslog(LOG_ERR, "sendEventToClient - Pointer to event is null"); + return false; + } + + // Check if filtered out - if so do nothing here + if (!vscp_doLevel2Filter(pEvent, &pClientItem->m_filter)) { + if (__VSCP_DEBUG_EXTRA) { + syslog(LOG_DEBUG, "sendEventToClient - Filtered out"); + } + return false; + } + + // If the client queue is full for this client then the + // client will not receive the message + if (pClientItem->m_clientInputQueue.size() > + m_maxItemsInClientReceiveQueue) { + if (__VSCP_DEBUG_EXTRA) { + syslog(LOG_DEBUG, "sendEventToClient - overrun"); + } + // Overrun + pClientItem->m_statistics.cntOverruns++; + return false; + } + + // Create a new event + vscpEvent* pnewvscpEvent = new vscpEvent; + if (NULL != pnewvscpEvent) { + + // Copy in the new event + if (!vscp_copyEvent(pnewvscpEvent, pEvent)) { + vscp_deleteEvent_v2(&pnewvscpEvent); + return false; + } + + // Add the new event to the input queue + pthread_mutex_lock(&pClientItem->m_mutexClientInputQueue); + pClientItem->m_clientInputQueue.push_back(pnewvscpEvent); + pthread_mutex_unlock(&pClientItem->m_mutexClientInputQueue); + sem_post(&pClientItem->m_semClientInputQueue); + } + + return true; +} + +/////////////////////////////////////////////////////////////////////////////// +// sendEventAllClients +// + +bool +CControlObject::sendEventAllClients(vscpEvent* pEvent, uint32_t excludeID) +{ + CClientItem* pClientItem; + std::deque::iterator it; + + if (NULL == pEvent) { + syslog(LOG_ERR, "sendEventAllClients - null event"); + return false; + } + + pthread_mutex_lock(&m_clientList.m_mutexItemList); + for (it = m_clientList.m_itemList.begin(); + it != m_clientList.m_itemList.end(); + ++it) { + pClientItem = *it; + + if ((NULL != pClientItem) && (excludeID != pClientItem->m_clientID)) { + if (__VSCP_DEBUG_EXTRA) { + syslog(LOG_DEBUG, + "Send event to client [%s]", + pClientItem->m_strDeviceName.c_str()); + } + if (!sendEventToClient(pClientItem, pEvent)) { + syslog(LOG_ERR, "sendEventAllClients - Failed to send event"); + } + } + } + + pthread_mutex_unlock(&m_clientList.m_mutexItemList); + + return true; +} + +/////////////////////////////////////////////////////////////////////////////// +// sendEvent +// +// !!! pEventToSend must be deallocated by sender !!! +// + +bool +CControlObject::sendEvent(CClientItem* pClientItem, vscpEvent* peventToSend) +{ + bool bSent = false; + + // Check pointers + if (NULL == pClientItem) { + syslog(LOG_ERR, "sendEvent - null clientItem"); + return false; + } + if (NULL == peventToSend) { + syslog(LOG_ERR, "sendEvent - null event"); + return false; + } + + // If timestamp is nulled make one + if (0 == peventToSend->timestamp) { + peventToSend->timestamp = vscp_makeTimeStamp(); + } + + // If obid is nulled set client interface id + if (0 == peventToSend->obid) { + peventToSend->obid = pClientItem->m_clientID; + } + + // If GUID is all nilled set interface GUID + if (vscp_isGUIDEmpty(peventToSend->GUID)) { + memcpy(peventToSend->GUID, pClientItem->m_guid.getGUID(), 16); + } + + vscpEvent* pEvent = new vscpEvent; // Create new VSCP Event + if (NULL == pEvent) { + syslog(LOG_ERR, "sendEvent - Allocation of event failed"); + return false; + } + + pEvent->pdata = NULL; + + // Copy event + if (!vscp_copyEvent(pEvent, peventToSend)) { + vscp_deleteEvent_v2(&pEvent); + syslog(LOG_ERR, "sendEvent - Event copy failed"); + return false; + } + + // Save the originating clients id so + // this client don't get the message back + if ( 0 == pEvent->obid) { + pEvent->obid = pClientItem->m_clientID; + } + + // Level II events between 512-1023 is recognised by the daemon and + // sent to the correct interface as Level I events if the interface + // is addressed by the client. + if ((pEvent->vscp_class <= 1023) && (pEvent->vscp_class >= 512) && + (pEvent->sizeData >= 16)) { + + // This event should be sent to the correct interface if it is + // available on this machine. If not it should be sent to + // the rest of the network as normal + + cguid destguid; + destguid.getFromArray(pEvent->pdata); + + destguid.setAt(14, 0); // Interface GUID's have LSB bytes nilled + destguid.setAt(15, 0); + + if (__VSCP_DEBUG_EXTRA) { + syslog(LOG_DEBUG, + "Level I event over Level II " + "dest = %02X:%02X:%02X:%02X:%02X:%02X:%02X:%02X:" + "%02X:%02X:%02X:%02X:%02X:%02X:%02X:%02X:", + destguid.getAt(0), + destguid.getAt(1), + destguid.getAt(2), + destguid.getAt(3), + destguid.getAt(4), + destguid.getAt(5), + destguid.getAt(6), + destguid.getAt(7), + destguid.getAt(8), + destguid.getAt(9), + destguid.getAt(10), + destguid.getAt(11), + destguid.getAt(12), + destguid.getAt(13), + destguid.getAt(14), + destguid.getAt(15)); + } + + // Find client + pthread_mutex_lock(&m_clientList.m_mutexItemList); + + std::deque::iterator it; + for (it = m_clientList.m_itemList.begin(); + it != m_clientList.m_itemList.end(); + ++it) { + + CClientItem* pItem = *it; + if (__VSCP_DEBUG_EXTRA) { + syslog(LOG_DEBUG, + "Test if = " + "%02X:%02X:%02X:%02X:%02X:%02X:%02X:%02X:%02X:%02X:%02X:%02X:%02X:%02X:%02X:%02X: - %s", + pItem->m_guid.getAt(0), + pItem->m_guid.getAt(1), + pItem->m_guid.getAt(2), + pItem->m_guid.getAt(3), + pItem->m_guid.getAt(4), + pItem->m_guid.getAt(5), + pItem->m_guid.getAt(6), + pItem->m_guid.getAt(7), + pItem->m_guid.getAt(8), + pItem->m_guid.getAt(9), + pItem->m_guid.getAt(10), + pItem->m_guid.getAt(11), + pItem->m_guid.getAt(12), + pItem->m_guid.getAt(13), + pItem->m_guid.getAt(14), + pItem->m_guid.getAt(15), + pItem->m_strDeviceName.c_str()); + syslog(LOG_DEBUG,"Match = %s", + (pItem->m_guid == destguid) ? "true" : "false" ); + } + + if (pItem->m_guid == destguid) { + // Found + // pDestClientItem = pItem; + bSent = true; + if (!sendEventToClient(pItem, pEvent)) { + syslog(LOG_DEBUG,"sendEventToClient: Failed!"); + } + break; + } + } + + pthread_mutex_unlock(&m_clientList.m_mutexItemList); + } + + if (!bSent) { + + // There must be room in the send queue + if (m_maxItemsInClientReceiveQueue > m_clientOutputQueue.size()) { + + pthread_mutex_lock(&m_mutex_ClientOutputQueue); + m_clientOutputQueue.push_back(pEvent); + + // TX Statistics + pClientItem->m_statistics.cntTransmitData += pEvent->sizeData; + pClientItem->m_statistics.cntTransmitFrames++; + + pthread_mutex_unlock(&m_mutex_ClientOutputQueue); + + sem_post(&m_semClientOutputQueue); + } + else { + if (__VSCP_DEBUG_EXTRA) { + syslog(LOG_DEBUG, "sendEvent - overrun"); + } + pClientItem->m_statistics.cntOverruns++; + vscp_deleteEvent_v2(&pEvent); + return false; + } + } + + return true; +} + +/////////////////////////////////////////////////////////////////////////////// +// sendEvent +// + +bool +CControlObject::sendEvent(CClientItem* pClientItem, vscpEventEx* pex) +{ + bool rv; + vscpEvent ev; + + if (!vscp_convertEventExToEvent(&ev, pex)) { + syslog(LOG_ERR, "sendEvent: Failed in vscp_convertEventExToEvent"); + return false; + } + + if (!(rv = sendEvent(pClientItem, &ev))) { + syslog(LOG_ERR, "sendEvent: Failed to send event"); + } + + vscp_deleteEvent(&ev); + + return rv; +} + +////////////////////////////////////////////////////////////////////////////// +// addClient +// + +bool +CControlObject::addClient(CClientItem* pClientItem, uint32_t id) +{ + // Check pointer + if ( NULL == pClientItem ) { + return false; + } + + // Add client to client list + if (!m_clientList.addClient(pClientItem, id)) { + return false; + } + + // Set GUID for interface + pClientItem->m_guid = m_guid; + + // Fill in client id + pClientItem->m_guid.setNicknameID(0); + pClientItem->m_guid.setClientID(pClientItem->m_clientID); + + return true; +} + +////////////////////////////////////////////////////////////////////////////// +// addClient - GUID (for drivers with set GUID) +// + +bool +CControlObject::addClient(CClientItem* pClientItem, cguid& guid) +{ + // Check pointer + if ( NULL == pClientItem ) { + return false; + } + + // Add client to client list + if (!m_clientList.addClient(pClientItem, guid)) { + return false; + } + + return true; +} + +////////////////////////////////////////////////////////////////////////////// +// removeClient +// + +void +CControlObject::removeClient(CClientItem* pClientItem) +{ + // Do not try to handle invalid clients + if (NULL == pClientItem) + return; + + // Remove the client + m_clientList.removeClient(pClientItem); +} + +////////////////////////////////////////////////////////////////////////////// +// addKnowNode +// + +void +CControlObject::addKnownNode(cguid& guid, cguid& ifguid, std::string& name) +{ + ; // TODO +} + +/////////////////////////////////////////////////////////////////////////////// +// getMacAddress +// + +bool +CControlObject::getMacAddress(cguid& guid) +{ +#ifdef WIN32 + + bool rv = false; + NCB Ncb; + UCHAR uRetCode; + LANA_ENUM lenum; + int i; + + // Clear the GUID + guid.clear(); + + memset(&Ncb, 0, sizeof(Ncb)); + Ncb.ncb_command = NCBENUM; + Ncb.ncb_buffer = (UCHAR*)&lenum; + Ncb.ncb_length = sizeof(lenum); + uRetCode = Netbios(&Ncb); + // printf( "The NCBENUM return code is: 0x%x ", uRetCode ); + + for (i = 0; i < lenum.length; i++) { + memset(&Ncb, 0, sizeof(Ncb)); + Ncb.ncb_command = NCBRESET; + Ncb.ncb_lana_num = lenum.lana[i]; + + uRetCode = Netbios(&Ncb); + + memset(&Ncb, 0, sizeof(Ncb)); + Ncb.ncb_command = NCBASTAT; + Ncb.ncb_lana_num = lenum.lana[i]; + + strcpy((char*)Ncb.ncb_callname, "* "); + Ncb.ncb_buffer = (unsigned char*)&Adapter; + Ncb.ncb_length = sizeof(Adapter); + + uRetCode = Netbios(&Ncb); + + if (uRetCode == 0) { + guid.setAt(0, 0xff); + guid.setAt(1, 0xff); + guid.setAt(2, 0xff); + guid.setAt(3, 0xff); + guid.setAt(4, 0xff); + guid.setAt(5, 0xff); + guid.setAt(6, 0xff); + guid.setAt(7, 0xfe); + guid.setAt(8, Adapter.adapt.adapter_address[0]); + guid.setAt(9, Adapter.adapt.adapter_address[1]); + guid.setAt(10, Adapter.adapt.adapter_address[2]); + guid.setAt(11, Adapter.adapt.adapter_address[3]); + guid.setAt(12, Adapter.adapt.adapter_address[4]); + guid.setAt(13, Adapter.adapt.adapter_address[5]); + guid.setAt(14, 0); + guid.setAt(15, 0); +#ifdef DEBUG__ + char buf[256]; + sprintf(buf, + "The Ethernet MAC Address: %02x:%02x:%02x:%02x:%02x:%02x", + guid.getAt(2), + guid.getAt(3), + guid.getAt(4), + guid.getAt(5), + guid.getAt(6), + guid.getAt(7)); + + std::string str = std::string(buf); +#endif + + rv = true; + } + } + + return rv; + +#else + // cat /sys/class/net/eth0/address + bool rv = true; + struct ifreq s; + int fd; + + // Clear the GUID + guid.clear(); + + fd = socket(PF_INET, SOCK_DGRAM, IPPROTO_IP); + if (-1 == fd) + return false; + + memset(&s, 0, sizeof(s)); + strcpy(s.ifr_name, "eth0"); + + if (0 == ioctl(fd, SIOCGIFHWADDR, &s)) { + + // ptr = (unsigned char *)&s.ifr_ifru.ifru_hwaddr.sa_data[0]; + if (__VSCP_DEBUG_EXTRA) { + syslog(LOG_DEBUG, + "Ethernet MAC address: %02X:%02X:%02X:%02X:%02X:%02X", + (uint8_t)s.ifr_addr.sa_data[0], + (uint8_t)s.ifr_addr.sa_data[1], + (uint8_t)s.ifr_addr.sa_data[2], + (uint8_t)s.ifr_addr.sa_data[3], + (uint8_t)s.ifr_addr.sa_data[4], + (uint8_t)s.ifr_addr.sa_data[5]); + } + + guid.setAt(0, 0xff); + guid.setAt(1, 0xff); + guid.setAt(2, 0xff); + guid.setAt(3, 0xff); + guid.setAt(4, 0xff); + guid.setAt(5, 0xff); + guid.setAt(6, 0xff); + guid.setAt(7, 0xfe); + guid.setAt(8, s.ifr_addr.sa_data[0]); + guid.setAt(9, s.ifr_addr.sa_data[1]); + guid.setAt(10, s.ifr_addr.sa_data[2]); + guid.setAt(11, s.ifr_addr.sa_data[3]); + guid.setAt(12, s.ifr_addr.sa_data[4]); + guid.setAt(13, s.ifr_addr.sa_data[5]); + guid.setAt(14, 0); + guid.setAt(15, 0); + } + else { + syslog(LOG_ERR, "Failed to get hardware address (must be root?)."); + rv = false; + } + + return rv; + +#endif +} + +/////////////////////////////////////////////////////////////////////////////// +// getIPAddress +// + +bool +CControlObject::getIPAddress(cguid& guid) +{ + // Clear the GUID + guid.clear(); + + guid.setAt(0, 0xff); + guid.setAt(1, 0xff); + guid.setAt(2, 0xff); + guid.setAt(3, 0xff); + guid.setAt(4, 0xff); + guid.setAt(5, 0xff); + guid.setAt(6, 0xff); + guid.setAt(7, 0xfd); + + char szName[128]; + gethostname(szName, sizeof(szName)); +#if defined(_WIN32) + LPHOSTENT lpLocalHostEntry; +#else + struct hostent* lpLocalHostEntry; +#endif + lpLocalHostEntry = gethostbyname(szName); + if (NULL == lpLocalHostEntry) { + return false; + } + + // Get all local addresses + int idx = -1; + void* pAddr; + unsigned long localaddr[16]; // max 16 local addresses + do { + idx++; + localaddr[idx] = 0; + pAddr = lpLocalHostEntry->h_addr_list[idx]; + if (NULL != pAddr) + localaddr[idx] = *((unsigned long*)pAddr); + } while ((NULL != pAddr) && (idx < 16)); + + guid.setAt(8, (localaddr[0] >> 24) & 0xff); + guid.setAt(9, (localaddr[0] >> 16) & 0xff); + guid.setAt(10, (localaddr[0] >> 8) & 0xff); + guid.setAt(11, localaddr[0] & 0xff); + + guid.setAt(12, 0); + guid.setAt(13, 0); + guid.setAt(14, 0); + guid.setAt(15, 0); + + return true; +} + +/////////////////////////////////////////////////////////////////////////////// +// getSystemKey +// + +uint8_t* +CControlObject::getSystemKey(uint8_t* pKey) +{ + if (NULL != pKey) { + memcpy(pKey, m_systemKey, 32); + } + + return m_systemKey; +} + +/////////////////////////////////////////////////////////////////////////////// +// getSystemKeyMD5 +// + +void +CControlObject::getSystemKeyMD5(std::string& strKey) +{ + char digest[33]; + vscp_md5(digest, m_systemKey, 32); + strKey = digest; +} + +// ---------------------------------------------------------------------------- +// FULL XML configuration callbacks +// ---------------------------------------------------------------------------- + +static int depth_full_config_parser = 0; +static int bVscpConfigFound = 0; +static int bGeneralConfigFound = 0; +static int bRemoteUserConfigFound = 0; +static int bLevel1DriverConfigFound = 0; +static int bLevel2DriverConfigFound = 0; +static int bLevel3DriverConfigFound = 0; + +static void +startFullConfigParser(void* data, const char* name, const char** attr) +{ + CControlObject* pObj = (CControlObject*)data; + if (NULL == data) + return; + + // fprintf(stderr, "%s\n", name); + + if ((0 == depth_full_config_parser) && + (0 == vscp_strcasecmp(name, "vscpconfig"))) { + bVscpConfigFound = TRUE; + } + else if (bVscpConfigFound && (1 == depth_full_config_parser) && + (0 == vscp_strcasecmp(name, "general"))) { + bGeneralConfigFound = TRUE; + + for (int i = 0; attr[i]; i += 2) { + + std::string attribute = attr[i + 1]; + vscp_trim(attribute); + + if (0 == vscp_strcasecmp(attr[i], "clientbuffersize")) { + pObj->m_maxItemsInClientReceiveQueue = + vscp_readStringValue(attribute); + } + else if (0 == vscp_strcasecmp(attr[i], "runasuser")) { + vscp_trim(attribute); + pObj->m_runAsUser = attribute; + } + else if (0 == vscp_strcasecmp(attr[i], "guid")) { + pObj->m_guid.getFromString(attribute); + } + else if (0 == vscp_strcasecmp(attr[i], "servername")) { + pObj->m_strServerName = attribute; + } + else if (0 == vscp_strcasecmp(attr[i], "webadminif")) { + if (0 == vscp_strcasecmp(attribute.c_str(), "true")) { + pObj->m_enableWebAdminIf = true; + } + else { + pObj->m_enableWebAdminIf = false; + } + } + } + } + else if (bVscpConfigFound && bGeneralConfigFound && + (2 == depth_full_config_parser) && + (0 == vscp_strcasecmp(name, "debug"))) { + + for (int i = 0; attr[i]; i += 2) { + + std::string attribute = attr[i + 1]; + vscp_trim(attribute); + + if (0 == vscp_strcasecmp(attr[i], "byte1")) { + if (attribute.length()) { + pObj->m_debugFlags[0] = vscp_readStringValue(attribute); + } + } + else if (0 == vscp_strcasecmp(attr[i], "byte2")) { + if (attribute.length()) { + pObj->m_debugFlags[1] = vscp_readStringValue(attribute); + } + } + else if (0 == vscp_strcasecmp(attr[i], "byte3")) { + if (attribute.length()) { + pObj->m_debugFlags[2] = vscp_readStringValue(attribute); + } + } + else if (0 == vscp_strcasecmp(attr[i], "byte4")) { + if (attribute.length()) { + pObj->m_debugFlags[3] = vscp_readStringValue(attribute); + } + } + else if (0 == vscp_strcasecmp(attr[i], "byte5")) { + if (attribute.length()) { + pObj->m_debugFlags[4] = vscp_readStringValue(attribute); + } + } + else if (0 == vscp_strcasecmp(attr[i], "byte6")) { + if (attribute.length()) { + pObj->m_debugFlags[5] = vscp_readStringValue(attribute); + } + } + else if (0 == vscp_strcasecmp(attr[i], "byte7")) { + if (attribute.length()) { + pObj->m_debugFlags[6] = vscp_readStringValue(attribute); + } + } + else if (0 == vscp_strcasecmp(attr[i], "byte8")) { + if (attribute.length()) { + pObj->m_debugFlags[7] = vscp_readStringValue(attribute); + } + } + } + } + else if (bVscpConfigFound && bGeneralConfigFound && + (2 == depth_full_config_parser) && + (0 == vscp_strcasecmp(name, "security"))) { + + for (int i = 0; attr[i]; i += 2) { + + std::string attribute = attr[i + 1]; + vscp_trim(attribute); + + if (0 == vscp_strcasecmp(attr[i], "admin")) { + pObj->m_admin_user = attribute; + } + else if (0 == vscp_strcasecmp(attr[i], "password")) { + pObj->m_admin_password = attribute; + } + else if (0 == vscp_strcasecmp(attr[i], "allowfrom")) { + pObj->m_admin_allowfrom = attribute; + } + else if (0 == vscp_strcasecmp(attr[i], "vscptoken")) { + pObj->m_vscptoken = attribute; + } + else if (0 == vscp_strcasecmp(attr[i], "vscpkey")) { + if (attribute.length()) { + vscp_hexStr2ByteArray(pObj->m_systemKey, + 32, + attribute.c_str()); + } + } + else if (0 == vscp_strcasecmp(attr[i], "authentication_domain")) { + if (attribute.length()) { + pObj->m_web_authentication_domain = attribute; + } + } + } + } + else if (bVscpConfigFound && (1 == depth_full_config_parser) && + (0 == vscp_strcasecmp(name, "tcpip"))) { + + for (int i = 0; attr[i]; i += 2) { + + std::string attribute = attr[i + 1]; + vscp_trim(attribute); + + if (0 == vscp_strcasecmp(attr[i], "enable")) { + if (0 == vscp_strcasecmp(attribute.c_str(), "true")) { + pObj->m_enableTcpip = true; + } + else { + pObj->m_enableTcpip = false; + } + } + else if (0 == vscp_strcasecmp(attr[i], "interface")) { + vscp_startsWith(attribute, "tcp://", &attribute); + vscp_trim(attribute); + pObj->m_strTcpInterfaceAddress = attribute; + } + else if (0 == vscp_strcasecmp(attr[i], "ssl_certificate")) { + pObj->m_tcpip_ssl_certificate = attribute; + } + else if (0 == vscp_strcasecmp(attr[i], "ssl_verify_peer")) { + pObj->m_tcpip_ssl_verify_peer = vscp_readStringValue(attribute); + } + else if (0 == vscp_strcasecmp(attr[i], "ssl_certificate_chain")) { + pObj->m_tcpip_ssl_certificate_chain = attribute; + } + else if (0 == vscp_strcasecmp(attr[i], "ssl_ca_path")) { + pObj->m_tcpip_ssl_ca_path = attribute; + } + else if (0 == vscp_strcasecmp(attr[i], "ssl_ca_file")) { + pObj->m_tcpip_ssl_ca_file = attribute; + } + else if (0 == vscp_strcasecmp(attr[i], "ssl_verify_depth")) { + pObj->m_tcpip_ssl_verify_depth = + vscp_readStringValue(attribute); + } + else if (0 == + vscp_strcasecmp(attr[i], "ssl_default_verify_paths")) { + if (0 == vscp_strcasecmp(attribute.c_str(), "true")) { + pObj->m_tcpip_ssl_default_verify_paths = true; + } + else { + pObj->m_tcpip_ssl_default_verify_paths = false; + } + } + else if (0 == vscp_strcasecmp(attr[i], "ssl_cipher_list")) { + pObj->m_tcpip_ssl_cipher_list = attribute; + } + else if (0 == vscp_strcasecmp(attr[i], "ssl_protocol_version")) { + pObj->m_tcpip_ssl_verify_depth = + vscp_readStringValue(attribute); + } + else if (0 == vscp_strcasecmp(attr[i], "ssl_short_trust")) { + if (0 == vscp_strcasecmp(attribute.c_str(), "true")) { + pObj->m_tcpip_ssl_short_trust = true; + } + else { + pObj->m_tcpip_ssl_short_trust = false; + } + } + } + } + + else if (bVscpConfigFound && (1 == depth_full_config_parser) && + (0 == vscp_strcasecmp(name, "webserver"))) { + + for (int i = 0; attr[i]; i += 2) { + + std::string attribute = attr[i + 1]; + vscp_trim(attribute); + + if (0 == vscp_strcasecmp(attr[i], "enable")) { + if (0 == vscp_strcasecmp(attribute.c_str(), "true")) { + pObj->m_web_bEnable = true; + } + else { + pObj->m_web_bEnable = false; + } + } + else if (0 == vscp_strcasecmp(attr[i], "document_root")) { + if (attribute.length()) { + pObj->m_web_document_root = attribute; + } + } + else if (0 == vscp_strcasecmp(attr[i], "listening_ports")) { + if (attribute.length()) { + pObj->m_web_listening_ports = attribute; + } + } + else if (0 == vscp_strcasecmp(attr[i], "index_files")) { + if (attribute.length()) { + pObj->m_web_index_files = vscp_trim_copy(attribute); + ; + } + } + else if (0 == vscp_strcasecmp(attr[i], "authentication_domain")) { + if (attribute.length()) { + pObj->m_web_authentication_domain = attribute; + } + } + else if (0 == + vscp_strcasecmp(attr[i], "enable_auth_domain_check")) { + if (0 == vscp_strcasecmp(attribute.c_str(), "true")) { + pObj->m_enable_auth_domain_check = true; + } + else { + pObj->m_enable_auth_domain_check = false; + } + } + else if (0 == vscp_strcasecmp(attr[i], "ssl_certificat")) { + if (attribute.length()) { + pObj->m_web_ssl_certificate = attribute; + } + } + else if (0 == vscp_strcasecmp(attr[i], "ssl_certificat_chain")) { + if (attribute.length()) { + pObj->m_web_ssl_certificate_chain = attribute; + } + } + else if (0 == vscp_strcasecmp(attr[i], "ssl_verify_peer")) { + if (0 == vscp_strcasecmp(attribute.c_str(), "true")) { + pObj->m_web_ssl_verify_peer = true; + } + else { + pObj->m_web_ssl_verify_peer = false; + } + } + else if (0 == vscp_strcasecmp(attr[i], "ssl_ca_path")) { + if (attribute.length()) { + pObj->m_web_ssl_ca_path = attribute; + } + } + else if (0 == vscp_strcasecmp(attr[i], "ssl_ca_file")) { + if (attribute.length()) { + pObj->m_web_ssl_ca_file = attribute; + } + } + else if (0 == vscp_strcasecmp(attr[i], "ssl_verify_depth")) { + if (attribute.length()) { + pObj->m_web_ssl_verify_depth = + vscp_readStringValue(attribute); + } + } + else if (0 == + vscp_strcasecmp(attr[i], "ssl_default_verify_paths")) { + if (0 == vscp_strcasecmp(attribute.c_str(), "true")) { + pObj->m_web_ssl_default_verify_paths = true; + } + else { + pObj->m_web_ssl_default_verify_paths = false; + } + } + else if (0 == vscp_strcasecmp(attr[i], "ssl_cipher_list")) { + if (attribute.length()) { + pObj->m_web_ssl_cipher_list = attribute; + } + } + else if (0 == vscp_strcasecmp(attr[i], "ssl_protcol_version")) { + if (attribute.length()) { + pObj->m_web_ssl_protocol_version = + vscp_readStringValue(attribute); + } + } + else if (0 == vscp_strcasecmp(attr[i], "ssl_short_trust")) { + if (0 == vscp_strcasecmp(attribute.c_str(), "true")) { + pObj->m_web_ssl_short_trust = true; + } + else { + pObj->m_web_ssl_short_trust = false; + } + } + else if (0 == vscp_strcasecmp(attr[i], "cgi_interpreter")) { + if (attribute.length()) { + pObj->m_web_cgi_interpreter = attribute; + } + } + else if (0 == vscp_strcasecmp(attr[i], "cgi_pattern")) { + if (attribute.length()) { + pObj->m_web_cgi_patterns = attribute; + } + } + else if (0 == vscp_strcasecmp(attr[i], "cgi_environment")) { + if (attribute.length()) { + pObj->m_web_cgi_environment = attribute; + } + } + else if (0 == vscp_strcasecmp(attr[i], "protect_uri")) { + if (attribute.length()) { + pObj->m_web_protect_uri = attribute; + } + } + else if (0 == vscp_strcasecmp(attr[i], "trottle")) { + if (attribute.length()) { + pObj->m_web_trottle = attribute; + } + } + else if (0 == + vscp_strcasecmp(attr[i], "enable_directory_listing")) { + if (0 == vscp_strcasecmp(attribute.c_str(), "true")) { + pObj->m_web_enable_directory_listing = true; + } + else { + pObj->m_web_enable_directory_listing = false; + } + } + else if (0 == vscp_strcasecmp(attr[i], "enable_keep_alive")) { + if (0 == vscp_strcasecmp(attribute.c_str(), "true")) { + pObj->m_web_enable_keep_alive = true; + } + else { + pObj->m_web_enable_keep_alive = false; + } + } + else if (0 == vscp_strcasecmp(attr[i], "keep_alive_timeout_ms")) { + if (attribute.length()) { + pObj->m_web_keep_alive_timeout_ms = + vscp_readStringValue(attribute); + } + } + else if (0 == vscp_strcasecmp(attr[i], "access_control_list")) { + if (attribute.length()) { + pObj->m_web_access_control_list = attribute; + } + } + else if (0 == vscp_strcasecmp(attr[i], "extra_mime_types")) { + if (attribute.length()) { + pObj->m_web_extra_mime_types = attribute; + } + } + else if (0 == vscp_strcasecmp(attr[i], "num_threads")) { + if (attribute.length()) { + pObj->m_web_num_threads = vscp_readStringValue(attribute); + } + } + else if (0 == vscp_strcasecmp(attr[i], "hide_file_pattern")) { + if (attribute.length()) { + pObj->m_web_hide_file_patterns = attribute; + } + } + else if (0 == vscp_strcasecmp(attr[i], "url_rewrite_patterns")) { + if (attribute.length()) { + pObj->m_web_url_rewrite_patterns = attribute; + } + } + else if (0 == vscp_strcasecmp(attr[i], "hide_file_patterns")) { + if (attribute.length()) { + pObj->m_web_hide_file_patterns = attribute; + } + } + else if (0 == vscp_strcasecmp(attr[i], "request_timeout_ms")) { + if (attribute.length()) { + pObj->m_web_request_timeout_ms = + vscp_readStringValue(attribute); + } + } + else if (0 == vscp_strcasecmp(attr[i], "linger_timeout_ms")) { + if (attribute.length()) { + pObj->m_web_linger_timeout_ms = + vscp_readStringValue(attribute); + } + } + else if (0 == vscp_strcasecmp(attr[i], "decode_url")) { + if (0 == vscp_strcasecmp(attribute.c_str(), "true")) { + pObj->m_web_decode_url = true; + } + else { + pObj->m_web_decode_url = false; + } + } + else if (0 == vscp_strcasecmp(attr[i], "global_auth_file")) { + if (attribute.length()) { + pObj->m_web_global_auth_file = attribute; + } + } + else if (0 == + vscp_strcasecmp(attr[i], "web_per_directory_auth_file")) { + if (attribute.length()) { + pObj->m_web_per_directory_auth_file = attribute; + } + } + else if (0 == + vscp_strcasecmp(attr[i], "access_control_allow_origin")) { + if (attribute.length()) { + pObj->m_web_access_control_allow_methods = attribute; + } + } + else if (0 == + vscp_strcasecmp(attr[i], "access_control_allow_methods")) { + if (attribute.length()) { + pObj->m_web_access_control_allow_methods = attribute; + } + } + else if (0 == + vscp_strcasecmp(attr[i], "access_control_allow_headers")) { + if (attribute.length()) { + pObj->m_web_access_control_allow_headers = attribute; + } + } + else if (0 == vscp_strcasecmp(attr[i], "error_pages")) { + if (attribute.length()) { + pObj->m_web_error_pages = attribute; + } + } + else if (0 == vscp_strcasecmp(attr[i], "tcp_nodelay")) { + if (attribute.length()) { + pObj->m_web_linger_timeout_ms = + vscp_readStringValue(attribute); + } + } + else if (0 == + vscp_strcasecmp(attr[i], "static_file_cache_control")) { + if (attribute.length()) { + pObj->m_web_static_file_cache_control = attribute; + } + } + else if (0 == vscp_strcasecmp(attr[i], "static_file_max_age")) { + if (attribute.length()) { + pObj->m_web_static_file_max_age = + vscp_readStringValue(attribute); + } + } + else if (0 == + vscp_strcasecmp(attr[i], + "strict_transport_security_max_age")) { + if (attribute.length()) { + pObj->m_web_strict_transport_security_max_age = + vscp_readStringValue(attribute); + } + } + else if (0 == vscp_strcasecmp(attr[i], "sendfile_call")) { + if (0 == vscp_strcasecmp(attribute.c_str(), "true")) { + pObj->m_web_allow_sendfile_call = true; + } + else { + pObj->m_web_allow_sendfile_call = false; + } + } + else if (0 == vscp_strcasecmp(attr[i], "additional_headers")) { + if (attribute.length()) { + pObj->m_web_additional_header = attribute; + } + } + else if (0 == vscp_strcasecmp(attr[i], "max_request_size")) { + if (attribute.length()) { + pObj->m_web_max_request_size = + vscp_readStringValue(attribute); + } + } + else if (0 == vscp_strcasecmp(attr[i], + "web_allow_index_script_resource")) { + if (0 == vscp_strcasecmp(attribute.c_str(), "true")) { + pObj->m_web_allow_index_script_resource = true; + } + else { + pObj->m_web_allow_index_script_resource = false; + } + } + else if (0 == vscp_strcasecmp(attr[i], "duktape_script_patterns")) { + if (attribute.length()) { + pObj->m_web_duktape_script_patterns = attribute; + } + } + else if (0 == vscp_strcasecmp(attr[i], "lua_preload_file")) { + if (attribute.length()) { + pObj->m_web_lua_preload_file = attribute; + } + } + else if (0 == vscp_strcasecmp(attr[i], "lua_script_patterns")) { + if (attribute.length()) { + pObj->m_web_lua_script_patterns = attribute; + } + } + else if (0 == + vscp_strcasecmp(attr[i], "lua_server_page_patterns")) { + if (attribute.length()) { + pObj->m_web_lua_server_page_patterns = attribute; + } + } + else if (0 == vscp_strcasecmp(attr[i], "lua_websockets_patterns")) { + if (attribute.length()) { + pObj->m_web_lua_websocket_patterns = attribute; + } + } + else if (0 == vscp_strcasecmp(attr[i], "lua_background_script")) { + if (attribute.length()) { + pObj->m_web_lua_background_script = attribute; + } + } + else if (0 == + vscp_strcasecmp(attr[i], "lua_background_script_params")) { + if (attribute.length()) { + pObj->m_web_lua_background_script_params = attribute; + } + } + } + } + else if (bVscpConfigFound && (1 == depth_full_config_parser) && + (0 == vscp_strcasecmp(name, "restapi"))) { + + for (int i = 0; attr[i]; i += 2) { + + std::string attribute = attr[i + 1]; + vscp_trim(attribute); + + if (0 == vscp_strcasecmp(attr[i], "enable")) { + if (0 == vscp_strcasecmp(attribute.c_str(), "true")) { + pObj->m_bEnableRestApi = true; + } + else { + pObj->m_bEnableRestApi = false; + } + } + } + } + else if (bVscpConfigFound && (1 == depth_full_config_parser) && + (0 == vscp_strcasecmp(name, "websockets"))) { + + for (int i = 0; attr[i]; i += 2) { + + std::string attribute = attr[i + 1]; + vscp_trim(attribute); + + if (0 == vscp_strcasecmp(attr[i], "enable")) { + if (0 == vscp_strcasecmp(attribute.c_str(), "true")) { + pObj->m_bWebsocketsEnable = true; + } + else { + pObj->m_bWebsocketsEnable = false; + } + } + else if (0 == vscp_strcasecmp(attr[i], "document_root")) { + if (attribute.length()) { + pObj->m_websocket_document_root = attribute; + } + } + else if (0 == vscp_strcasecmp(attr[i], "timeout_ms")) { + if (attribute.length()) { + pObj->m_websocket_timeout_ms = + vscp_readStringValue(attribute); + } + } + else if (0 == + vscp_strcasecmp(attr[i], "enable_websocket_ping_pong")) { + if (0 == vscp_strcasecmp(attribute.c_str(), "true")) { + pObj->bEnable_websocket_ping_pong = true; + } + else { + pObj->bEnable_websocket_ping_pong = false; + } + } + else if (0 == + vscp_strcasecmp(attr[i], "web-lua_websocket_pattern")) { + if (attribute.length()) { + pObj->lua_websocket_pattern = attribute; + } + } + } + } + else if (bVscpConfigFound && (1 == depth_full_config_parser) && + (0 == vscp_strcasecmp(name, "remoteuser"))) { + bRemoteUserConfigFound = TRUE; + } + else if (bVscpConfigFound && bRemoteUserConfigFound && + (2 == depth_full_config_parser) && + (0 == vscp_strcasecmp(name, "user"))) { + + vscpEventFilter VSCPFilter; + bool bFilterPresent = false; + bool bMaskPresent = false; + std::string name; + std::string md5; + std::string privilege; + std::string allowfrom; + std::string allowevent; + std::string fullname; + std::string note; + + vscp_clearVSCPFilter(&VSCPFilter); // Allow all frames + + for (int i = 0; attr[i]; i += 2) { + + std::string attribute = attr[i + 1]; + vscp_trim(attribute); + + if (0 == vscp_strcasecmp(attr[i], "name")) { + name = attribute; + } + else if (0 == vscp_strcasecmp(attr[i], "password")) { + md5 = attribute; + } + else if (0 == vscp_strcasecmp(attr[i], "fullname")) { + fullname = attribute; + } + else if (0 == vscp_strcasecmp(attr[i], "note")) { + note = attribute; + } + else if (0 == vscp_strcasecmp(attr[i], "privilege")) { + privilege = attribute; + } + else if (0 == vscp_strcasecmp(attr[i], "allowfrom")) { + allowfrom = attribute; + } + else if (0 == vscp_strcasecmp(attr[i], "allowevent")) { + allowevent = attribute; + } + else if (0 == vscp_strcasecmp(attr[i], "filter")) { + if (attribute.length()) { + if (vscp_readFilterFromString(&VSCPFilter, attribute)) { + bFilterPresent = true; + } + } + } + else if (0 == vscp_strcasecmp(attr[i], "mask")) { + if (attribute.length()) { + if (vscp_readMaskFromString(&VSCPFilter, attribute)) { + bMaskPresent = true; + } + } + } + + if (bFilterPresent && bMaskPresent) { + pObj->m_userList.addUser(name, + md5, + fullname, + note, + pObj->m_web_authentication_domain, + &VSCPFilter, + privilege, + allowfrom, + allowevent, + 0); + } + else { + pObj->m_userList.addUser(name, + md5, + fullname, + note, + pObj->m_web_authentication_domain, + NULL, + privilege, + allowfrom, + allowevent, + 0); + } + } + } + else if (bVscpConfigFound && (1 == depth_full_config_parser) && + ((0 == vscp_strcasecmp(name, "level1driver")) || + (0 == vscp_strcasecmp(name, "canal1driver")))) { + bLevel1DriverConfigFound = TRUE; + } + else if (bVscpConfigFound && bLevel1DriverConfigFound && + (2 == depth_full_config_parser) && + (0 == vscp_strcasecmp(name, "driver"))) { + + std::string strName; + std::string strConfig; + std::string strPath; + unsigned long flags = 0; + uint32_t translation = 0; + cguid guid; + bool bEnabled = false; + + for (int i = 0; attr[i]; i += 2) { + + std::string attribute = attr[i + 1]; + vscp_trim(attribute); + + if (0 == vscp_strcasecmp(attr[i], "enable")) { + if (0 == vscp_strcasecmp(attribute.c_str(), "true")) { + bEnabled = true; + } + else { + bEnabled = false; + } + } + else if (0 == vscp_strcasecmp(attr[i], "name")) { + strName = attribute; + // Replace spaces in name with underscore + std::string::size_type found; + while (std::string::npos != + (found = strName.find_first_of(" "))) { + strName[found] = '_'; + } + } + else if (0 == vscp_strcasecmp(attr[i], "config")) { + strConfig = attribute; + } + else if (0 == vscp_strcasecmp(attr[i], + "parameter")) { // deprecated + strConfig = attribute; + } + else if (0 == vscp_strcasecmp(attr[i], "path")) { + strPath = attribute; + } + else if (0 == vscp_strcasecmp(attr[i], "flags")) { + flags = vscp_readStringValue(attribute); + } + else if (0 == vscp_strcasecmp(attr[i], "guid")) { + guid.getFromString(attribute); + } + else if (0 == vscp_strcasecmp(attr[i], "translation")) { + translation = vscp_readStringValue(attribute); + } + } // for + + if (bEnabled) { + // Add the level I device + if (!pObj->m_deviceList.addItem(strName, + strConfig, + strPath, + flags, + guid, + VSCP_DRIVER_LEVEL1, + bEnabled, + translation)) { + syslog(LOG_ERR, + "Level I driver not added name=%s. " + "Path does not exist. - [%s]", + strName.c_str(), + strPath.c_str()); + } + else { + if (__VSCP_DEBUG_DRIVER1) { + syslog(LOG_DEBUG, + "Level I driver added. name = %s - [%s]", + strName.c_str(), + strPath.c_str()); + } + } + } + } + else if (bVscpConfigFound && (1 == depth_full_config_parser) && + ((0 == vscp_strcasecmp(name, "level2driver")))) { + bLevel2DriverConfigFound = TRUE; + } + else if (bVscpConfigFound && bLevel2DriverConfigFound && + (2 == depth_full_config_parser) && + (0 == vscp_strcasecmp(name, "driver"))) { + + std::string strName; + std::string strConfig; + std::string strPath; + cguid guid; + bool bEnabled = false; + + for (int i = 0; attr[i]; i += 2) { + + std::string attribute = attr[i + 1]; + vscp_trim(attribute); + + if (0 == vscp_strcasecmp(attr[i], "enable")) { + if (0 == vscp_strcasecmp(attribute.c_str(), "true")) { + bEnabled = true; + } + else { + bEnabled = false; + } + } + else if (0 == vscp_strcasecmp(attr[i], "name")) { + strName = attribute; + // Replace spaces in name with underscore + std::string::size_type found; + while (std::string::npos != + (found = strName.find_first_of(" "))) { + strName[found] = '_'; + } + } + else if (0 == vscp_strcasecmp(attr[i], "path-config")) { + strConfig = attribute; + } + else if (0 == vscp_strcasecmp(attr[i], + "parameter")) { // deprecated + strConfig = attribute; + } + else if (0 == vscp_strcasecmp(attr[i], "path-driver")) { + strPath = attribute; + } + else if (0 == vscp_strcasecmp(attr[i], "guid")) { + guid.getFromString(attribute); + } + } // for + + // Add the level II device + if (bEnabled) { + if (!pObj->m_deviceList.addItem(strName, + strConfig, + strPath, + 0, + guid, + VSCP_DRIVER_LEVEL2, + bEnabled)) { + if (__VSCP_DEBUG_DRIVER2) { + syslog(LOG_ERR, + "Level II driver was not added. name = %s" + "Path does not exist. - [%s]", + strName.c_str(), + strPath.c_str()); + } + } + else { + if (__VSCP_DEBUG_DRIVER2) { + syslog(LOG_DEBUG, + "Level II driver added. name = %s- [%s]", + strName.c_str(), + strPath.c_str()); + } + } + } + } + + depth_full_config_parser++; +} + +static void +handleFullConfigData(void* data, const char* content, int length) +{ +} + +static void +endFullConfigParser(void* data, const char* name) +{ + depth_full_config_parser--; + + if (1 == depth_full_config_parser && + (0 == vscp_strcasecmp(name, "vscpconfig"))) { + bVscpConfigFound = FALSE; + } + if (bVscpConfigFound && (1 == depth_full_config_parser) && + ((0 == vscp_strcasecmp(name, "level1driver")) || + (0 == vscp_strcasecmp(name, "canal1driver")))) { + bLevel1DriverConfigFound = FALSE; + } + else if (bVscpConfigFound && (1 == depth_full_config_parser) && + (0 == vscp_strcasecmp(name, "level2driver"))) { + bLevel2DriverConfigFound = FALSE; + } + else if (bVscpConfigFound && (1 == depth_full_config_parser) && + (0 == vscp_strcasecmp(name, "level3driver"))) { + bLevel3DriverConfigFound = FALSE; + } +} + +// ---------------------------------------------------------------------------- + +/////////////////////////////////////////////////////////////////////////////// +// readConfiguration +// +// Read the configuration XML file +// + +bool +CControlObject::readConfiguration(const std::string& strcfgfile) +{ + FILE* fp; + + if (__VSCP_DEBUG_EXTRA) { + syslog(LOG_DEBUG, + "Reading full XML configuration from [%s]", + (const char*)strcfgfile.c_str()); + } + + fp = fopen(strcfgfile.c_str(), "r"); + if (NULL == fp) { + syslog(LOG_ERR, + "Failed to open configuration file [%s]", + strcfgfile.c_str()); + return false; + } + + XML_Parser xmlParser = XML_ParserCreate("UTF-8"); + XML_SetUserData(xmlParser, this); + XML_SetElementHandler(xmlParser, + startFullConfigParser, + endFullConfigParser); + XML_SetCharacterDataHandler(xmlParser, handleFullConfigData); + + void* buf = XML_GetBuffer(xmlParser, XML_BUFF_SIZE); + if (NULL == buf) { + XML_ParserFree(xmlParser); + fclose(fp); + syslog(LOG_ERR, + "Failed to allocate buffer for configuration file [%s]", + strcfgfile.c_str()); + return false; + } + + size_t file_size = 0; + file_size = fread(buf, sizeof(char), XML_BUFF_SIZE, fp); + + if (!XML_ParseBuffer(xmlParser, file_size, file_size == 0)) { + syslog(LOG_ERR, "Failed parse XML configuration file."); + fclose(fp); + XML_ParserFree(xmlParser); + return false; + } + + fclose(fp); + XML_ParserFree(xmlParser); + + return true; +} // XML config + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// + +/////////////////////////////////////////////////////////////////////////////// +// clientMsgWorkerThread +// +// Is there any messages to send from Level II clients. Send it/them to all +// devices/clients except for itself. +// + +void* +clientMsgWorkerThread(void* userdata) +{ + std::list::iterator it; + vscpEvent* pvscpEvent = NULL; + + // Must be a valid control object pointer + CControlObject* pObj = (CControlObject*)userdata; + if (NULL == pObj) + return NULL; + + while (!pObj->m_bQuit_clientMsgWorkerThread) { + + // Wait for event + if ((-1 == vscp_sem_wait(&pObj->m_semClientOutputQueue, 10)) && + errno == ETIMEDOUT) { + continue; + } + + if (pObj->m_clientOutputQueue.size()) { + + pthread_mutex_lock(&pObj->m_mutex_ClientOutputQueue); + pvscpEvent = pObj->m_clientOutputQueue.front(); + pObj->m_clientOutputQueue.pop_front(); + pthread_mutex_unlock(&pObj->m_mutex_ClientOutputQueue); + + if (NULL != pvscpEvent) { + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + // + // Send event to all Level II clients (not to + // ourself ) + // + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + + pObj->sendEventAllClients(pvscpEvent, pvscpEvent->obid); + // Tell main thread that there are work to do + sem_post(&pObj->m_semSentToAllClients); + + } // Valid event + + // Delete the event - we are done with it + vscp_deleteEvent_v2(&pvscpEvent); + + } // Events in queue + + } // while + + return NULL; +} diff --git a/src/vscp/vscphubd/controlobject.h b/src/vscp/vscphubd/controlobject.h new file mode 100644 index 000000000..213c5afde --- /dev/null +++ b/src/vscp/vscphubd/controlobject.h @@ -0,0 +1,619 @@ +// ControlObject.h: interface for the CControlObject class. +// +// This file is part of the VSCP (https://www.vscp.org) +// +// The MIT License (MIT) +// +// Copyright © 2000-2024 Ake Hedman, Grodans Paradis AB +// +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +// + +#if !defined(CONTROLOBJECT_H__INCLUDED_) +#define CONTROLOBJECT_H__INCLUDED_ + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// Forward declarations +class TCPListenThread; + +// TTL Scope +// ---------------------------------------------------------------------- +// 0 Restricted to the same host.Won't be output by any interface. +// 1 Restricted to the same subnet.Won't be forwarded by a router. +// <32 Restricted to the same site, organization or department. +// <64 Restricted to the same region. +// <128 Restricted to the same continent. +// <255 Unrestricted in scope.Global. +#define IP_MULTICAST_DEFAULT_TTL 1 + +// Needed on Linux +#ifndef VSCPMIN +#define VSCPMIN(X, Y) ((X) < (Y) ? (X) : (Y)) +#endif + +#ifndef VSCPMAX +#define VSCPMAX(a, b) \ + ({ \ + __typeof__(a) _a = (a); \ + __typeof__(b) _b = (b); \ + _a > _b ? _a : _b; \ + }) +#endif + +#define MAX_ITEMS_RECEIVE_QUEUE 1021 +#define MAX_ITEMS_SEND_QUEUE 1021 +#define MAX_ITEMS_CLIENT_RECEIVE_QUEUE 8192 + +// VSCP daemon defines from vscp.h +#define VSCP_MAX_CLIENTS 4096 // abs. max. is 0xffff +#define VSCP_MAX_DEVICES 1024 // abs. max. is 0xffff + +/*! + This is the class that does the main work in the daemon. +*/ + +class CControlObject { + public: + /*! + Constructor + */ + CControlObject(void); + + /*! + Destructor + */ + virtual ~CControlObject(void); + + /*! + Generate a random session key from a string key + @param pKey Null terminated string key (max 255 characters) + @param pSid Pointer to 33 byte sid that will receive sid + */ + bool generateSessionId(const char* pKey, char* pSid); + + /*! + Get server capabilities (64-bit array) + @param pCapability Pointer to 64 bit capabilities array + @return True on success. + */ + bool getVscpCapabilities(uint8_t* pCapability); + + /*! + General initialisation + */ + bool init(std::string& strcfgfile, std::string& rootFolder); + + /*! + Clean up used resources + */ + bool cleanup(void); + + /*! + The main worker thread + */ + bool run(void); + + /*! + Send automation events + @param pClientItem Pointer to client item that want to send + automation events + @return true on success + */ + bool automation(CClientItem* pClientItem); + + /*! + Start worker threads for devices + @return true on success + */ + bool startDeviceWorkerThreads(void); + + /*! + Stop worker threads for devices + @return true on success + */ + bool stopDeviceWorkerThreads(void); + + /*! + Starting daemon worker thread + @return true on success + */ + bool startDaemonWorkerThread(void); + + /*! + Stop daemon worker thread + @return true on success + */ + bool stopDaemonWorkerThread(void); + + /*! + Starting TCP/IP worker thread + @return true on success + */ + bool startTcpipSrvThread(void); + + /*! + Stop the TCP/IP worker thread + @return true on success + */ + bool stopTcpipSrvThread(void); + + /*! + Start the UDP worker thread + */ + // bool startUDPSrvThread(void); + + /*! + Stop the UDP Workerthread + */ + // bool stopUDPSrvThread(void); + + /*! + Start the Multicast worker threads + */ + bool startMulticastWorkerThreads(void); + + /*! + Stop the Multicast Workerthreads + */ + bool stopMulticastWorkerThreads(void); + + /*! + Starting Client worker thread + @return true on success + */ + bool startClientMsgWorkerThread(void); + + /*! + Stop Client worker thread + @return true on success + */ + bool stopClientMsgWorkerThread(void); + + /*! + Add a new client to the client list + + @param Pointer to client that should be added. + @param Normally not used but can be used to set a special + client id. + @return True on success. + */ + bool addClient(CClientItem* pClientItem, uint32_t id = 0); + + /*! + Add a new client to the client list using GUID. + + This add client method is for drivers that specify a + full GUID (two lsb nilled). + + @param Pointer to client that should be added. + @param guid The guid that is used for the client. Two least + significant bytes will be set to zero. + @return True on success. + */ + bool addClient(CClientItem* pClientItem, cguid& guid); + + /*! + Add a known node + @param guid Real GUID for node + @param name Symbolic name for node. + */ + void addKnownNode(cguid& guid, cguid& ifguid, std::string& name); + + /*! + Remove a new client from the client list + + @param pClientItem Pointer to client that should be added. + */ + void removeClient(CClientItem* pClientItem); + + /*! + Get device address for primary ehernet adapter + + @param guid class + */ + bool getMacAddress(cguid& guid); + + /*! + Get the first IP address computer is known under + + @param pGUID Pointer to GUID class + */ + bool getIPAddress(cguid& guid); + + /*! + Read configuration data + @param strcfgfile path to configuration file. + @return Returns true on success false on failure. + */ + bool readConfiguration(const std::string& strcfgfile); + + /*! + send level II message to all clients + @param pClientItem Pointer to client object for client that should + receive the event + @param pEvent Pointer to event that should be sent to client. Caller + must deallocate if needed. + @return true on success + */ + bool sendEventToClient(CClientItem* pClientItem, vscpEvent* pEvent); + + /*! + Send Level II event to all clients with exception + @param pEvent Pointer to event that should be sent. Caller must + must take care of deallocation. + @param excludeID Client with this obid should not receive event. + @return True on success + */ + bool sendEventAllClients(vscpEvent* pEvent, uint32_t excludeID = 0); + + /*! + * Send event + * @param pClientItem Client that send the event. + * @param pEvent Event to send. !!! pEventToSend must be + * deallocated by sender !!! + * @return True on success false on failure. + */ + bool sendEvent(CClientItem* pClientItem, vscpEvent* peventToSend); + + /*! + * Send event + * @param pClientItem Client that send the event. + * @param pex Eventex to send. + * @return True on success false on failure. + */ + bool sendEvent(CClientItem* pClientItem, vscpEventEx* pex); + + /*! + * Check if a driver name is free to us + * + * @param drvname Name of driver to check. + * @return true if 'drvname' is not used + */ + bool checkIfDriverNameFreeToUse(std::string& drvname) + { + return (m_driverNameSet.find("drvname") == m_driverNameSet.end()); + } + + /*! + * Get the system key + * + * @param pKey Buffer that will get 32-byte key. Can be NULL in which + * case the key is not copied to the param. + * @return Pointer to the 32 byte key + */ + uint8_t* getSystemKey(uint8_t* pKey); + + /*! + * Get MD5 of system key (vscptoken) + * + * @param Reference to string that will receive the MD5 of the key. + */ + void getSystemKeyMD5(std::string& strKey); + + /*! + * Create the folder structure that the VSCP daemon is expecting + * https://www.vscp.org/docs/vscpd/doku.php?id=files_and_directory_structure + */ + bool createFolderStructure(void); + + public: + // This is the root folder for the VSCP daemon, it will look for + // the configuration database here + std::string m_rootFolder; + + // Set to true if we should quit application + bool m_bQuit; + + // Set to true of the clientWorkerThread should terminate + bool m_bQuit_clientMsgWorkerThread; + + /*! + * Debug flags + * See vscp_debug.h for possible flags. + * Set to point to m_gdebugArray in startup + */ + uint32_t* m_debugFlags; + + //************************************************************************** + // Security + //************************************************************************** + + // Password is MD5 hash over "username:domain:password" + std::string m_admin_user; // Defaults to "admin" + std::string m_admin_password; + // Default password salt;key + // E2D453EF99FB3FCD19E67876554A8C27;A4A86F7D7E119BA3F0CD06881E371B989B33B6D606A863B633EF529D64544F8E + std::string m_admin_allowfrom; // Remotes allowed to connect from as admin. + // Defaults to "" + std::string m_vscptoken; + // A4A86F7D7E119BA3F0CD06881E371B989B33B6D606A863B633EF529D64544F8E + // { + // 0xA4,0xA8,0x6F,0x7D,0x7E,0x11,0x9B,0xA3,0xF0,0xCD,0x06,0x88,0x1E,0x37,0x1B,0x98, + // 0x9B,0x33,0xB6,0xD6,0x06,0xA8,0x63,0xB6,0x33,0xEF,0x52,0x9D,0x64,0x54,0x4F,0x8E + // }; + uint8_t m_systemKey[32]; + + /*! + User to run as for Unix + if not "" + */ + std::string m_runAsUser; + + /*! + Maximum number of items in receive queue for clients (ClientBufferSize) + */ + uint32_t m_maxItemsInClientReceiveQueue; + + /*! + Name of this server + */ + std::string m_strServerName; + + /*! + Server GUID + This is the GUID for the server + */ + cguid m_guid; + + //************************************************************************** + // Communication + //************************************************************************** + + ///////////////////////////////////////////////////////// + // TCP/IP server + ///////////////////////////////////////////////////////// + + // Server will be started if set to true (by configuration (db/xml) + bool m_enableTcpip; + + // Enable encryption on tcp/ip interface if enabled. + // 0 = Disabled + // 1 = AES-128 + // 2 = AES-192 + // 3 = AES-256 + uint8_t m_encryptionTcpip; + + // Interface used for TCP/IP connection (only one) + std::string m_strTcpInterfaceAddress; + + // Data object for the tcp/ip Listen thread + tcpipListenThreadObj* m_ptcpipSrvObject; + + // Listen thread for tcp/ip connections + pthread_t m_tcpipListenThread; + + // tcp/ip SSL settings + std::string m_tcpip_ssl_certificate; + std::string m_tcpip_ssl_certificate_chain; + uint8_t m_tcpip_ssl_verify_peer; // no=0, optional=1, yes=2 + std::string m_tcpip_ssl_ca_path; + std::string m_tcpip_ssl_ca_file; + uint8_t m_tcpip_ssl_verify_depth; + bool m_tcpip_ssl_default_verify_paths; + std::string m_tcpip_ssl_cipher_list; + uint8_t m_tcpip_ssl_protocol_version; + bool m_tcpip_ssl_short_trust; + + //***************************************************** + // webserver interface + //***************************************************** + + // Context for web server + struct mg_context* m_web_ctx; + + // Enable webserver + bool m_web_bEnable; + + // Enable web admin interface (general section in conf) + bool m_enableWebAdminIf; + + // See + // https://www.vscp.org/docs/vscpd/doku.php?id=configuring_the_vscp_daemon#webserver + std::string m_web_document_root; + std::string m_web_listening_ports; + std::string m_web_index_files; + std::string m_web_authentication_domain; + bool m_enable_auth_domain_check; + std::string m_web_ssl_certificate; + std::string m_web_ssl_certificate_chain; + bool m_web_ssl_verify_peer; + std::string m_web_ssl_ca_path; + std::string m_web_ssl_ca_file; + uint16_t m_web_ssl_verify_depth; + bool m_web_ssl_default_verify_paths; + std::string m_web_ssl_cipher_list; + uint8_t m_web_ssl_protocol_version; + bool m_web_ssl_short_trust; + std::string m_web_cgi_interpreter; + std::string m_web_cgi_patterns; + std::string m_web_cgi_environment; + std::string m_web_protect_uri; + std::string m_web_trottle; + bool m_web_enable_directory_listing; + bool m_web_enable_keep_alive; + long m_web_keep_alive_timeout_ms; + std::string m_web_access_control_list; + std::string m_web_extra_mime_types; + int m_web_num_threads; + std::string m_web_url_rewrite_patterns; + std::string m_web_hide_file_patterns; + long m_web_request_timeout_ms; + long m_web_linger_timeout_ms; // Set negative to not set + bool m_web_decode_url; + std::string m_web_global_auth_file; + std::string m_web_per_directory_auth_file; + std::string m_web_ssi_patterns; + std::string m_web_access_control_allow_origin; + std::string m_web_access_control_allow_methods; + std::string m_web_access_control_allow_headers; + std::string m_web_error_pages; + long m_web_tcp_nodelay; + std::string m_web_static_file_cache_control; + long m_web_static_file_max_age; + long m_web_strict_transport_security_max_age; + bool m_web_allow_sendfile_call; + std::string m_web_additional_header; + long m_web_max_request_size; + bool m_web_allow_index_script_resource; + std::string m_web_duktape_script_patterns; + std::string m_web_lua_preload_file; + std::string m_web_lua_script_patterns; + std::string m_web_lua_server_page_patterns; + std::string m_web_lua_websocket_patterns; + std::string m_web_lua_background_script; + std::string m_web_lua_background_script_params; + + // Protects the web session object + pthread_mutex_t m_mutex_websrvSession; + + // Linked list of all active sessions. (websrv.h) + std::list m_web_sessions; + + //************************************************************************** + // REST + //************************************************************************** + + // Protects the REST session object + pthread_mutex_t m_mutex_restSession; + + // Session structure for REST API + std::list m_rest_sessions; + + // Enable REST API + bool m_bEnableRestApi; + + //************************************************************************** + // WEBSOCKETS + //************************************************************************** + + bool m_bWebsocketsEnable; // Enable web socket functionality + std::string m_websocket_document_root; + long m_websocket_timeout_ms; + bool bEnable_websocket_ping_pong; + std::string lua_websocket_pattern; + + // * * Websockets * * + + // Protects the websocket session object + pthread_mutex_t m_mutex_websocketSession; + + // List of active websocket sessions + std::list m_websocketSessions; + + //************************************************************************** + // DRIVERS + //************************************************************************* + + // The list with available devices. + CDeviceList m_deviceList; + pthread_mutex_t m_mutex_DeviceList; + + // This set holds driver names. + // Returns true for an active driver + // A driver can only be loaded if it have an unique name. + + std::set m_driverNameSet; + + std::map m_driverNameDeviceMap; + + // Mutex for device queue + pthread_mutex_t m_mutex_deviceList; + + // Automation Object + CAutomation m_automation; + + // Username for level III drivers + std::string m_driverUsername; // TODO remove + + // Password for Level III drivers + std::string m_driverPassword; // TODO remove + + //************************************************************************** + // CLIENTS + //************************************************************************** + + // The list with active clients. (protecting mutex in object) + CClientList m_clientList; + + // Mutex for client queue + pthread_mutex_t m_mutex_clientList; + + // The list of users + CUserList m_userList; // deque + pthread_mutex_t m_mutex_UserList; + + // ************************************************************************* + + /*! + Send queue + + This is the send queue for all clients attached to the system. A client + place events here and the system distribute it to all other clients. + */ + std::list m_clientOutputQueue; + + /*! + Event object to indicate that there is an event in the client output + queue. + */ + sem_t m_semClientOutputQueue; + + /*! + Mutex for Level II message send queue + */ + pthread_mutex_t m_mutex_ClientOutputQueue; + + /*! + Semaphore that is signaled when workerthread + have send an incoming event to all clients + */ + sem_t m_semSentToAllClients; + + // ************************************************************************* + + private: + //************************************************************************** + // Threads + //************************************************************************** + + /*! + controlobject device thread + */ + pthread_t m_clientMsgWorkerThread; + + /*! + The server thread for the VSCP daemon + */ + // daemonWorkerObj *m_pdaemonWorkerObj; + // pthread_t m_pdaemonWorkerThread; +}; + +#endif // !defined(CONTROLOBJECT_H__7D80016B_5EFD_40D5_94E3_6FD9C324CC7B__INCLUDED_) diff --git a/src/vscp/vscphubd/version.h b/src/vscp/vscphubd/version.h new file mode 100644 index 000000000..1fabbf823 --- /dev/null +++ b/src/vscp/vscphubd/version.h @@ -0,0 +1,53 @@ +// version.h +// +// The MIT License (MIT) +// +// Copyright © 2000-2024 Ake Hedman, Grodans Paradis AB +// +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +#ifndef _____VSCPHUBD_VERSION_h_____ +#define _____VSCPHUBD_VERSION_h_____ +/* + MAJOR version with incompatible API changes, + MINOR version with add functionality in a backwards-compatible manner, and + RELEASE version with backwards-compatible bug fixes. + BUILD Just a new build. +*/ +// I M P O T A N T ! ! ! Lines below must be located at line +// 35/36/37/38/40/42/43 I M P O T A N T ! ! ! +#define VSCPD_MAJOR_VERSION 14 +#define VSCPD_MINOR_VERSION 0 +#define VSCPD_RELEASE_VERSION 5 +#define VSCPD_BUILD_VERSION 4 + +#define VSCPD_DISPLAY_VERSION "14.0.5-4 Silicon" + +#define VSCPD_COPYRIGHT \ + "Copyright © 2000-2024 Ake Hedman, Grodans Paradis AB, " \ + "https://www.grodansparadis.com" +#define VSCPD_COPYRIGHT_HTML \ + "Copyright Ⓒ 2000-2024 Ake Hedman, Grodans Paradis AB, https://" \ + "www.grodansparadis.com" + +#define VSCP_VERSION(major, minor, release) \ + (((major) << 16) | ((minor) << 8) | (release)) +#endif diff --git a/src/vscp/vscphubd/vscphubd.cpp b/src/vscp/vscphubd/vscphubd.cpp new file mode 100644 index 000000000..bf05a6d48 --- /dev/null +++ b/src/vscp/vscphubd/vscphubd.cpp @@ -0,0 +1,536 @@ +// vscphubd.cpp : Defines the class behaviour for the application. +// +// This file is part of the VSCP (https://www.vscp.org) +// +// The MIT License (MIT) +// +// Copyright © 2000-2024 Ake Hedman, Grodans Paradis AB +// +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +// + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "canal_macro.h" +#include "vscphubd.h" +#include +#include +#include +#include + +//#define DEBUG +uint32_t m_gdebugArray[8]; + +// Globals for the daemon +int gbStopDaemon; +int gnDebugLevel = 0; +bool gbDontRunAsDaemon = false; +bool gbRestart = false; +std::string systemKey; + +// Control object +CControlObject* gpobj; + +// Forward declarations +int +init(std::string& strcfgfile, std::string& rootFolder); +void +copyleft(void); +void +help(char* szPrgname); +bool +createFolderStuct(std::string& rootFolder); + +void +_sighandlerStop(int sig) +{ + fprintf(stderr, "vscphubd: signal received, forced to stop.\n"); + syslog(LOG_ERR, "vscphubd: signal received, forced to stop.: %m"); + gpobj->m_bQuit = true; + gbStopDaemon = true; + gbRestart = false; +} + +void +_sighandlerRestart(int sig) +{ + fprintf(stderr, "vscphubd: signal received, restart. %m\n"); + syslog(LOG_ERR, "vscphubd: signal received, restart.: %m"); + gpobj->m_bQuit = true; + gbStopDaemon = false; + gbRestart = true; +} + +void +getDebugValues(const char* optarg) +{ + std::string attribute = optarg; + std::deque tokens; + vscp_split(tokens, attribute, ","); + size_t cnt = tokens.size(); + for (size_t idx = 0; idx < MIN(8, cnt); idx++) { + if (tokens.size()) { + uint32_t val = vscp_readStringValue(tokens.front()); + m_gdebugArray[idx] = val; + tokens.pop_front(); + } + } +} + +///////////////////////////////////////////////////////////////////////////// +// The one and only app. object +// + +int +main(int argc, char** argv) +{ + int opt = 0; + std::string rootFolder; // Folder where VSCP files & folders will be located + std::string strcfgfile; // Points to XML configuration file + + // Clear debug settings + for (int i = 0; i < 8; i++) { + m_gdebugArray[i] = 0; + } + + fprintf(stderr, "Prepare to start vscphubd...\n"); + + openlog("vscphubd", LOG_PERROR | LOG_PID | LOG_CONS, LOG_DAEMON); + syslog(LOG_INFO, "Starting the VSCP daemon..."); + + // Ignore return value from defunct processes d + signal(SIGCHLD, SIG_IGN); + + crcInit(); + + rootFolder = "/var/lib/vscp/"; + strcfgfile = "/etc/vscp/vscphubd.conf"; + gbStopDaemon = false; + + while ((opt = getopt(argc, argv, "d:c:r:k:hgs")) != -1) { + + switch (opt) { + + case 's': + fprintf(stderr, + "I will ***NOT*** run as daemon! " + "(ctrl+c to terminate)\n"); + gbDontRunAsDaemon = true; + break; + + case 'c': + strcfgfile = optarg; + break; + + case 'r': + rootFolder = optarg; + fprintf(stderr, "Will use rootfolder = %s", rootFolder.c_str()); + syslog(LOG_INFO, + "Will use rootfolder = %s", + rootFolder.c_str()); + break; + + case 'k': + systemKey = optarg; + break; + + case 'd': + gnDebugLevel = atoi(optarg); + fprintf(stderr, "Debug flags=%s\n", optarg); + syslog(LOG_INFO, "Debug flags=%s\n", optarg); + getDebugValues(optarg); + break; + + case 'g': + copyleft(); + exit(0); + break; + + default: + case 'h': + help(argv[0]); + exit(-1); + } + } + + fprintf(stderr, + "[vscphubd] Configfile = %s\n", + (const char*)strcfgfile.c_str()); + + if (!init(strcfgfile, rootFolder)) { + syslog(LOG_ERR, "[vscphubd] Failed to configure. Terminating.\n"); + fprintf(stderr, "vscphubd: Failed to configure. Terminating.\n"); + exit(-1); + } + + closelog(); // Close syslog + + fprintf(stderr, "vscphubd: Bye, bye.\n"); + exit(EXIT_SUCCESS); +} + +///////////////////////////////////////////////////////////////////////////// +// initialisation + +int +init(std::string& strcfgfile, std::string& rootFolder) +{ + pid_t pid, sid; + + if (!gbDontRunAsDaemon) { + + // Fork child + if (0 > (pid = fork())) { + // Failure + syslog(LOG_ERR, "Failed to fork.\n"); + return -1; + } else if (0 != pid) { + exit(0); // Parent goes by by. + } + + sid = setsid(); // Become session leader + if (sid < 0) { + // Failure + syslog(LOG_ERR, "Failed to become session leader.\n"); + return -1; + } + + umask(0); // Clear out file mode creation mask + + // Close out the standard file descriptors + close(STDIN_FILENO); + close(STDOUT_FILENO); + close(STDERR_FILENO); + + if (open("/", 0)) { + syslog(LOG_ERR, "vscphubd: open / not 0: %m"); + } + + dup2(0, 1); + dup2(0, 2); + } + + signal(SIGHUP, _sighandlerStop); + signal(SIGUSR1, _sighandlerStop); + signal(SIGUSR2, _sighandlerRestart); + + // Write pid to file + FILE* pFile; + pFile = fopen("/var/run/vscphubd.pid", "w"); + if (NULL == pFile) { + syslog(LOG_ERR, "Writing pid file failed.\n"); + fprintf(stderr, "Writing pid file failed.\n"); + } + else { + syslog(LOG_ERR, "Writing pid file [/var/run/vscphubd.pid] sid=%u\n", sid); + fprintf(pFile, "%u\n", sid); + fclose(pFile); + } + + // Create folder structure + if (!createFolderStuct(rootFolder)) { + syslog(LOG_ERR, + "vscphubd: Folder structure is not in place (You may need to run " + "as root)."); + fprintf(stderr, + "vscphubd: Folder structure is not in place (You may need to run " + "as root)."); + unlink("/var/run/vscphubd.pid"); + return -1; + } + + // Change working directory to root folder + if (chdir((const char*)rootFolder.c_str())) { + syslog(LOG_ERR, "vscphubd: Failed to change dir to rootdir"); + fprintf(stderr, "vscphubd: Failed to change dir to rootdir"); + unlink("/var/run/vscphubd.pid"); + if (-1 == chdir("/var/lib/vscp/vscphubd")) { + syslog( + LOG_ERR, + "Unable to chdir to home folder [/var/lib/vscp/vscphubd] errno=%d", + errno); + } + + return -1; + } + + struct sigaction my_action; + + // Ignore SIGPIPE + my_action.sa_handler = SIG_IGN; + my_action.sa_flags = SA_RESTART; + sigaction(SIGPIPE, &my_action, NULL); + + // Redirect SIGQUIT + my_action.sa_handler = _sighandlerStop; + my_action.sa_flags = SA_RESTART; + sigaction(SIGQUIT, &my_action, NULL); + + // Redirect SIGABRT + my_action.sa_handler = _sighandlerStop; + my_action.sa_flags = SA_RESTART; + sigaction(SIGABRT, &my_action, NULL); + + // Redirect SIGINT + my_action.sa_handler = _sighandlerStop; + my_action.sa_flags = SA_RESTART; + sigaction(SIGINT, &my_action, NULL); + + // Redirect SIGTERM + my_action.sa_handler = _sighandlerStop; + my_action.sa_flags = SA_RESTART; + sigaction(SIGTERM, &my_action, NULL); + + // Redirect SIGHUP + my_action.sa_handler = _sighandlerStop; + my_action.sa_flags = SA_RESTART; + sigaction(SIGHUP, &my_action, NULL); + + do { + + gbRestart = false; + + // Create the control object + gpobj = new CControlObject(); + + // Set system key + vscp_hexStr2ByteArray(gpobj->m_systemKey, + 32, + (const char*)systemKey.c_str()); + + fprintf(stderr, "vscphubd: init.\n"); + if (!gpobj->init(strcfgfile, rootFolder)) { + fprintf(stderr, "Can't initialize daemon. Exiting.\n"); + syslog(LOG_ERR, "Can't initialize daemon. Exiting."); + unlink("/var/run/vscphubd.pid"); + return FALSE; + } + + // Tansfer read debug parameters if set + gpobj->m_debugFlags = m_gdebugArray; + + + // ******************************* + // Main loop is entered here + // ******************************* + + fprintf(stderr, "vscphubd: run.\n"); + if (!gpobj->run()) { + fprintf(stderr, + "Unable to start the vscphubd application. Exiting.\n"); + syslog(LOG_ERR, "Unable to start the vscphubd application. Exiting."); + unlink("/var/run/vscphubd.pid"); + return FALSE; + } + + fprintf(stderr, "vscphubd: cleanup.\n"); + + if (!gpobj->cleanup()) { + fprintf(stderr, "Unable to clean up the vscphubd application.\n"); + syslog(LOG_ERR, "Unable to clean up the vscphubd application."); + return FALSE; + } + + fprintf(stderr, "vscphubd: cleanup done.\n"); + + if (gbRestart) { + syslog(LOG_ERR, "vscphubd: Will try to restart.\n"); + fprintf(stderr, "vscphubd: Will try to restart.\n"); + } else { + syslog(LOG_ERR, "vscphubd: Will end things.\n"); + fprintf(stderr, "vscphubd: Will end things.\n"); + } + + fprintf(stderr, "vscphubd: Deleting the control object.\n"); + delete gpobj; + + } while (gbRestart); + + // Remove the pid file + unlink("/var/run/vscp/vscphubd.pid"); + + fprintf(stderr, "vscphubd: ending...\n"); + + gpobj = NULL; + + return TRUE; +} + +/////////////////////////////////////////////////////////////////////////////// +// copyleft + +void +copyleft(void) +{ + fprintf(stderr, "\n\n"); + fprintf(stderr, "vscphubd - "); + fprintf(stderr, VSCPHUBD_DISPLAY_VERSION); + fprintf(stderr, "\n"); + fprintf(stderr, VSCPHUBD_COPYRIGHT); + fprintf(stderr, "\n"); + fprintf(stderr, "\n"); + fprintf( + stderr, + "The MIT License (MIT)" + "\n" + "Copyright © 2000-2020 Ake Hedman, Grodans Paradis AB\n" + "\n" + "\n" + "Permission is hereby granted, free of charge, to any person obtaining a " + "copy\n" + "of this software and associated documentation files (the 'Software'), " + "to deal\n" + "in the Software without restriction, including without limitation the " + "rights\n" + "to use, copy, modify, merge, publish, distribute, sublicense, and/or " + "sell\n" + "copies of the Software, and to permit persons to whom the Software is\n" + "furnished to do so, subject to the following conditions:\n" + "\n" + "The above copyright notice and this permission notice shall be included " + "in\n" + "all copies or substantial portions of the Software.\n" + "\n" + "THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS " + "OR\n" + "IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF " + "MERCHANTABILITY,\n" + "FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL " + "THE\n" + "AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n" + "LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING " + "FROM,\n" + "OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS " + "IN THE\n" + "SOFTWARE.\n"); + fprintf(stderr, "\n"); +} + +/////////////////////////////////////////////////////////////////////////////// +// help + +void +help(char* szPrgname) +{ + fprintf(stderr, + "Usage: %s [-hg] [-r rootfolder] [-c config-file] [-k key] " + "-dd0,d1,d2...\n", + szPrgname); + fprintf(stderr, "\t-h\tThis help message.\n"); + fprintf(stderr, "\t-s\tStandalone (don't run as daemon). \n"); + fprintf(stderr, "\t-r\tSpecify VSCP root folder. \n"); + fprintf(stderr, "\t-c\tSpecify a configuration file. \n"); + fprintf(stderr, "\t-k\t32 byte encryption key string in hex format. \n"); + fprintf(stderr, + "\t-d\tDebug flags as comma separated list (d0,d1,d2,d3,,,)."); + fprintf(stderr, "that should be used (default: /etc/vscphubd.conf).\n"); + fprintf(stderr, "\t-g\tPrint MIT license info.\n"); +} + +/////////////////////////////////////////////////////////////////////////////// +// createFolder +// + +bool +createFolder(const char* folder) +{ + if (0 == vscp_dirExists(folder)) { + if (-1 == mkdir(folder, S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH)) { + fprintf(stderr, "Failed to create folder %s\n", folder); + syslog(LOG_ERR, "Failed to create folder %s\n", folder); + return false; + } + } + + return true; +} + +/////////////////////////////////////////////////////////////////////////////// +// createFolderStuct +// + +bool +createFolderStuct(std::string& rootFolder) +{ + std::string path; + + if (!createFolder(rootFolder.c_str())) { + return false; + } + + if (!createFolder("/etc/vscp/certs")) { + return false; + } + + if (!createFolder("/etc/vscp/ca_certificats")) { + return false; + } + + path = rootFolder + "/web"; + if (!createFolder(path.c_str())) { + return false; + } + + path = rootFolder + "/web/html"; + if (!createFolder(path.c_str())) { + return false; + } + + path = rootFolder + "/web/html/images"; + if (!createFolder(path.c_str())) { + return false; + } + + path = rootFolder + "/web/html/js"; + if (!createFolder(path.c_str())) { + return false; + } + + path = rootFolder + "/web/html/css"; + if (!createFolder(path.c_str())) { + return false; + } + + return true; +} diff --git a/src/vscp/vscphubd/vscphubd.h b/src/vscp/vscphubd/vscphubd.h new file mode 100644 index 000000000..306dbe42f --- /dev/null +++ b/src/vscp/vscphubd/vscphubd.h @@ -0,0 +1,73 @@ +// vscpd.h : main header file for the VSCPD application +// +// This file is part of the VSCP (https://www.vscp.org) +// +// The MIT License (MIT) +// +// Copyright © 2000-2024 Ake Hedman, Grodans Paradis AB +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +// + +#if !defined(_VSCPD_H__INCLUDED_) +#define _VSCPD_H__INCLUDED_ + +#include + +// Include the CANAL definitions +#include + +/// Names of mutex's + +#define CANALD_CLIENT_OBJ_MUTEX "____CANAL_CLIENT_OBJ_MUTEX____" +#define CANALD_DEVICE_OBJ_MUTEX "____CANAL_DEVICE_OBJ_MUTEX____" +#define CANALD_SEND_OBJ_MUTEX "____CANAL_SEND_OBJ_MUTEX____" +#define CANALD_RECEIVE_OBJ_MUTEX "____CANAL_RECEIVE_OBJ_MUTEX____" +#define CANALD_CLIENT_MUTEX "__CANAL_CLIENT_MUTEX__" + +///////////////////////////////////////////////////////////////////////////// +// VSCPApp: +// See vscpd.cpp for the implementation of this class +// + +class VSCPHUBApp { + +public: + VSCPHUBApp(); + +public: + + + /*! + Initialize the system + */ + int init( std::string& strcfgfile, std::string& rootFolder ); + + + // Implementation + +private: + +}; + + +///////////////////////////////////////////////////////////////////////////// + + +#endif // !defined(VSCPD_H__3D1CDB8C_8027_46D5_9284_67750BEA4B7E__INCLUDED_) diff --git a/src/vscp/vscphubd/websocket.h b/src/vscp/vscphubd/websocket.h new file mode 100644 index 000000000..f74408bd3 --- /dev/null +++ b/src/vscp/vscphubd/websocket.h @@ -0,0 +1,206 @@ +// websocket.h: +// +// This file is part of the VSCP (https://www.vscp.org) +// +// The MIT License (MIT) +// +// Copyright © 2000-2024 Ake Hedman, Grodans Paradis AB +// +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +// + +#if !defined(WEBSOCKET_H__INCLUDED_) +#define WEBSOCKET_H__INCLUDED_ + +#include +//#include + +//****************************************************************************** +// WEBSOCKETS +//****************************************************************************** + +// websocket types +#define WEBSOCKET_SUBYPE_STANDARD "vscp-std" // Original form +#define WEBSOCKET_SUBTYPE_JSON "vscp-json" // JSON format + +#define MAX_VSCPWS_MESSAGE_QUEUE (512) + +// This is the time it takes for an expired websocket session to be +// removed by the system. +#define WEBSOCKET_EXPIRE_TIME (2 * 60) + +// Authentication states +enum +{ + WEBSOCK_CONN_STATE_NULL = 0, + WEBSOCK_CONN_STATE_CONNECTED, + WEBSOCK_CONN_STATE_DATA +}; + +enum +{ + WEBSOCK_ERROR_NO_ERROR = 0, // Everything is OK. + WEBSOCK_ERROR_SYNTAX_ERROR = 1, // Syntax error. + WEBSOCK_ERROR_UNKNOWN_COMMAND = 2, // Unknown command. + WEBSOCK_ERROR_TX_BUFFER_FULL = 3, // Transmit buffer full. + WEBSOCK_ERROR_MEMORY_ALLOCATION = 4, // Problem allocating memory. + WEBSOCK_ERROR_NOT_AUTHORISED = 5, // Not authorised- + WEBSOCK_ERROR_NOT_ALLOWED_TO_SEND_EVENT = + 6, // Not authorized to send events. + WEBSOCK_ERROR_NOT_ALLOWED_TO_DO_THAT = 7, // Not allowed to do that. + WEBSOCK_ERROR_PARSE_FORMAT = 8, // Parse error, invalid format. + WEBSOCK_ERROR_UNKNOWN_TYPE = 9, // Unkown object type + WEBSOCK_ERROR_GENERAL = 10, // General errors and exceptions +}; + +#define WEBSOCK_STR_ERROR_NO_ERROR "Everything is OK." +#define WEBSOCK_STR_ERROR_SYNTAX_ERROR "Syntax error." +#define WEBSOCK_STR_ERROR_UNKNOWN_COMMAND "Unknown command." +#define WEBSOCK_STR_ERROR_TX_BUFFER_FULL "Transmit buffer full." +#define WEBSOCK_STR_ERROR_MEMORY_ALLOCATION \ + "Having problems to allocate memory." +#define WEBSOCK_STR_ERROR_NOT_AUTHORISED "Not authorised." +#define WEBSOCK_STR_ERROR_NOT_ALLOWED_TO_SEND_EVENT "Not allowed to send event." +#define WEBSOCK_STR_ERROR_NOT_ALLOWED_TO_DO_THAT \ + "Not allowed to do that (check privileges)" +#define WEBSOCK_STR_ERROR_PARSE_FORMAT "Parse error, invalid format." +#define WEBSOCK_STR_ERROR_UNKNOWN_TYPE "Unknown type, only know 'COMMAND' and 'EVENT'." +#define WEBSOCK_STR_ERROR_GENERAL "Exception or other general error." + +#define WEBSOCKET_MAINCODE_POSITIVE "+" +#define WEBSOCKET_MAINCODE_NEGATIVE "-" + +#define WEBSOCKET_MAINCODE_COMMAND "C" +#define WEBSOCKET_MAINCODE_EVENT "E" +#define WEBSOCKET_MAINCODE_VARIABLE "V" + +#define WEBSOCKET_SUBCODE_VARIABLE_CHANGED "C" +#define WEBSOCKET_SUBCODE_VARIABLE_CREATED "N" +#define WEBSOCKET_SUBCODE_VARIABLE_DELETED "D" + +#define WS_TYPE_1 1 +#define WS_TYPE_2 2 + +class websock_session +{ + + public: + websock_session(void); + ~websock_session(void); + + // ws type 1/2 + uint8_t m_wstypes; + + // Connection object + struct mg_connection* m_conn; + + // Connection state (see enums above) + int m_conn_state; + + // Unique ID for this session. + char m_websocket_key[33]; // Sec-WebSocket-Key + + // 16 byte iv (SID) for this session + char m_sid[33]; + + // Protocol version + int m_version; // Sec-WebSocket-Version + + // Time when this session was last active. + time_t lastActiveTime; + + // Concatenated message receive + std::string m_strConcatenated; + + // Client structure for websocket + CClientItem* m_pClientItem; +}; + +#define WS2_COMMAND \ + "{" \ + " \"type\" : \"CMD\", " \ + " \"command\" : \"%s\", " \ + " \"args\" : %s" \ + "}" + +#define WS2_EVENT \ + "{" \ + " \"type\" : \"EVENT\", " \ + " \"event\" : " \ + " %s " \ + "}" + +#define WS2_POSITIVE_RESPONSE \ + "{" \ + " \"type\" : \"+\", " \ + " \"command\" : \"%s\", " \ + " \"args\" : %s" \ + "}" + +#define WS2_NEGATIVE_RESPONSE \ + "{" \ + " \"type\" : \"-\", " \ + " \"command\" : \"%s\", " \ + " \"errcode\" : %d, " \ + " \"errstr\" : \"%s\" " \ + "}" + +#define WS2_VARIABLE \ + "{" \ + " \"type\" : \"VARIABLE\", " \ + " \"variable\" : " \ + " %s " \ + "}" + +const int MSG_TYPE_COMMAND = 0; // Built in command +const int MSG_TYPE_XCOMMAND = 1; // Add on command +const int MSG_TYPE_EVENT = 2; // Event +const int MSG_TYPE_RESPONSE_POSITIVE = 3; // Positive reply +const int MSG_TYPE_RESPONSE_NEGATIVE = 4; // Negative reply +const int MSG_TYPE_VARIABLE = 5; // Changed variable + +class w2msg +{ + public: + w2msg(void); + ~w2msg(void); + + /* + Event (E)/Command (C)/Response (+)/Variable (V) + */ + int m_type; + + /* + Command/Response/Variable arguments + */ + std::map m_arguments; + + /* + Holder for Event + */ + vscpEventEx m_ex; +}; + +// Public functions + +void +websock_post_incomingEvents(void); + +#endif \ No newline at end of file diff --git a/src/vscp/vscphubd/websocketsrv.cpp b/src/vscp/vscphubd/websocketsrv.cpp new file mode 100644 index 000000000..134e4f107 --- /dev/null +++ b/src/vscp/vscphubd/websocketsrv.cpp @@ -0,0 +1,2535 @@ +// websocketserver.cpp +// +// This file is part of the VSCP (https://www.vscp.org) +// +// The MIT License (MIT) +// +// Copyright © 2000-2024 Ake Hedman, Grodans Paradis AB +// +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +// + +#ifdef __GNUG__ +//#pragma implementation +#endif + +#define _POSIX + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "web_css.h" +#include "web_js.h" +#include "web_template.h" + +#include +#include +#include // Needs C++11 -std=c++11 + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "websocket.h" + +#ifndef _CRT_SECURE_NO_WARNINGS +#define _CRT_SECURE_NO_WARNINGS +#endif + +#define XML_BUFF_SIZE 0xffff + +// for convenience +using json = nlohmann::json; + +/////////////////////////////////////////////////// +// GLOBALS +/////////////////////////////////////////////////// + +extern CControlObject* gpobj; + +// Webserver +extern struct mg_mgr gmgr; + +// Linked list of all active sessions. (webserv.h) +extern struct websrv_Session* gp_websrv_sessions; + +// Session structure for REST API +extern struct websrv_rest_session* gp_websrv_rest_sessions; + +// Prototypes +int +webserv_url_decode(const char* src, + int src_len, + char* dst, + int dst_len, + int is_form_url_encoded); + +void +webserv_util_sendheader(struct mg_connection* nc, + const int returncode, + const char* content); + +//////////////////////////////////////////////////// +// Forward declarations +//////////////////////////////////////////////////// + +void +ws1_command(struct mg_connection* conn, + struct websock_session* pSession, + std::string& strCmd); + +bool +ws1_message(struct mg_connection* conn, + websock_session* pSession, + std::string& strWsPkt); + +bool +ws2_command(struct mg_connection* conn, + struct websock_session* pSession, + std::string& strCmd, + json& obj); + +bool +ws2_message(struct mg_connection* conn, + websock_session* pSession, + std::string& strWsPkt); + +/////////////////////////////////////////////////// +// WEBSOCKETS +/////////////////////////////////////////////////// + +// Linked list of websocket sessions +// Protected by the websocketSexxionMutex +// static struct websock_session *gp_websock_sessions; + +websock_session::websock_session(void) +{ + m_wstypes = WS_TYPE_1; // ws1 + m_conn = NULL; + m_conn_state = WEBSOCK_CONN_STATE_NULL; + memset(m_websocket_key, 0, 33); + memset(m_sid, 0, 33); + m_version = 0; + lastActiveTime = 0; + m_pClientItem = NULL; +}; + +websock_session::~websock_session(void) +{ + m_pClientItem = NULL; +}; + +// w2msg - Message holder for W2 + +w2msg::w2msg(void) +{ + m_type = MSG_TYPE_COMMAND; + memset(&m_ex, 0., sizeof(vscpEventEx)); +}; + +w2msg::~w2msg(void) {} + +/////////////////////////////////////////////////////////////////////////////// +// websock_authentication +// +// client sends +// "AUTH;iv;AES128("username:password) +// + +bool +websock_authentication(struct mg_connection* conn, + struct websock_session* pSession, + std::string& strIV, + std::string& strCrypto) +{ + uint8_t buf[2048], secret[2048]; + uint8_t iv[16]; + std::string strUser, strPassword; + + struct mg_context* ctx; + const struct mg_request_info* reqinfo; + bool bValidHost = false; + + // Check pointers + if ((NULL == conn) || (NULL == pSession) || !(ctx = mg_get_context(conn)) || + !(reqinfo = mg_get_request_info(conn))) { + syslog(LOG_ERR, + "[Websocket Client] Authentication: Invalid " + "pointers. "); + return false; + } + + if (0 == vscp_hexStr2ByteArray(iv, 16, (const char*)strIV.c_str())) { + syslog(LOG_ERR, + "[Websocket Client] Authentication: No room " + "for iv block. "); + return false; // Not enough room in buffer + } + + size_t len; + if (0 == (len = vscp_hexStr2ByteArray(secret, + strCrypto.length(), + (const char*)strCrypto.c_str()))) { + syslog(LOG_ERR, + "[Websocket Client] Authentication: No room " + "for crypto block. "); + return false; // Not enough room in buffer + } + + memset(buf, 0, sizeof(buf)); + AES_CBC_decrypt_buffer(AES128, buf, secret, len, gpobj->m_systemKey, iv); + + std::string str = std::string((const char*)buf); + std::deque tokens; + vscp_split(tokens, str, ":"); + + // Get username + if (tokens.empty()) { + syslog(LOG_ERR, + "[Websocket Client] Authentication: Missing " + "username from client. "); + return false; // No username + } + + strUser = tokens.front(); + tokens.pop_front(); + vscp_trim(strUser); + + // Get password + if (tokens.empty()) { + syslog(LOG_ERR, + "[Websocket Client] Authentication: Missing " + "password from client. "); + return false; // No username + } + + strPassword = tokens.front(); + tokens.pop_front(); + vscp_trim(strPassword); + + // Check if user is valid + CUserItem* pUserItem = gpobj->m_userList.getUser(strUser); + if (NULL == pUserItem) { + syslog(LOG_ERR, + "[Websocket Client] Authentication: CUserItem " + "allocation problem "); + return false; + } + + // Check if remote ip is valid + bValidHost = pUserItem->isAllowedToConnect(inet_addr(reqinfo->remote_addr)); + + if (!bValidHost) { + // Log valid login + syslog(LOG_ERR, + "[Websocket Client] Authentication: Host " + "[%s] NOT allowed to connect.", + reqinfo->remote_addr); + return false; + } + + if (!vscp_isPasswordValid(pUserItem->getPassword(), strPassword)) { + syslog(LOG_ERR, + "[Websocket Client] Authentication: User %s at host " + "[%s] gave wrong password.", + (const char*)strUser.c_str(), + reqinfo->remote_addr); + return false; + } + + pSession->m_pClientItem->bAuthenticated = true; + + // Add user to client + pSession->m_pClientItem->m_pUserItem = pUserItem; + + // Copy in the user filter + memcpy(&pSession->m_pClientItem->m_filter, + pUserItem->getUserFilter(), + sizeof(vscpEventFilter)); + + // Log valid login + syslog(LOG_ERR, + "[Websocket Client] Authentication: Host [%s] " + "User [%s] allowed to connect.", + reqinfo->remote_addr, + (const char*)strUser.c_str()); + + return true; +} + +/////////////////////////////////////////////////////////////////////////////// +// websock_new_session +// + +websock_session* +websock_new_session(const struct mg_connection* conn) +{ + const char* pHeader; + char ws_version[10]; + char ws_key[33]; + websock_session* pSession = NULL; + + // Check pointer + if (NULL == conn) + return NULL; + + // user + memset(ws_version, 0, sizeof(ws_version)); + if (NULL != (pHeader = mg_get_header(conn, "Sec-WebSocket-Version"))) { + strncpy(ws_version, + pHeader, + std::min(strlen(pHeader) + 1, sizeof(ws_version))); + } + memset(ws_key, 0, sizeof(ws_key)); + if (NULL != (pHeader = mg_get_header(conn, "Sec-WebSocket-Key"))) { + strncpy(ws_key, pHeader, std::min(strlen(pHeader) + 1, sizeof(ws_key))); + } + + // create fresh session + pSession = new websock_session; + if (NULL == pSession) { + syslog(LOG_ERR, + "[Websockets] New session: Unable to create session object."); + return NULL; + } + + // Generate the sid + unsigned char iv[16]; + char hexiv[33]; + getRandomIV(iv, 16); // Generate 16 random bytes + memset(hexiv, 0, sizeof(hexiv)); + vscp_byteArray2HexStr(hexiv, iv, 16); + + memset(pSession->m_sid, 0, sizeof(pSession->m_sid)); + memcpy(pSession->m_sid, hexiv, 32); + memset(pSession->m_websocket_key, 0, sizeof(pSession->m_websocket_key)); + + // Init. + strcpy(pSession->m_websocket_key, ws_key); // Save key + pSession->m_conn = (struct mg_connection*)conn; + pSession->m_conn_state = WEBSOCK_CONN_STATE_CONNECTED; + pSession->m_version = atoi(ws_version); // Store protocol version + + pSession->m_pClientItem = new CClientItem(); // Create client + if (NULL == pSession->m_pClientItem) { + syslog(LOG_ERR, + "[Websockets] New session: Unable to create client object."); + delete pSession; + return NULL; + } + + pSession->m_pClientItem->bAuthenticated = false; // Not authenticated in yet + vscp_clearVSCPFilter(&pSession->m_pClientItem->m_filter); // Clear filter + + // This is an active client + pSession->m_pClientItem->m_bOpen = false; + pSession->m_pClientItem->m_dtutc = vscpdatetime::Now(); + pSession->m_pClientItem->m_type = + CLIENT_ITEM_INTERFACE_TYPE_CLIENT_WEBSOCKET; + pSession->m_pClientItem->m_strDeviceName = ("Internal websocket client."); + + // Add the client to the Client List + pthread_mutex_lock(&gpobj->m_clientList.m_mutexItemList); + if (!gpobj->addClient(pSession->m_pClientItem)) { + // Failed to add client + delete pSession->m_pClientItem; + pSession->m_pClientItem = NULL; + pthread_mutex_unlock(&gpobj->m_clientList.m_mutexItemList); + syslog(LOG_ERR, + ("Websocket server: Failed to add client. Terminating thread.")); + return NULL; + } + pthread_mutex_unlock(&gpobj->m_clientList.m_mutexItemList); + + pthread_mutex_lock(&gpobj->m_mutex_websocketSession); + gpobj->m_websocketSessions.push_back(pSession); + pthread_mutex_unlock(&gpobj->m_mutex_websocketSession); + + // Use the session object as user data + mg_set_user_connection_data(pSession->m_conn, (void*)pSession); + + return pSession; +} + +/////////////////////////////////////////////////////////////////////////////// +// websock_sendevent +// +// Send event to all other clients. +// + +bool +websock_sendevent(struct mg_connection* conn, + websock_session* pSession, + vscpEventEx* pex) +{ + // Check pointer + if (NULL == conn) { + syslog(LOG_ERR,"Internal error: websock_sendevent - conn == NULL"); + return false; + } + + if (NULL == pSession) { + syslog(LOG_ERR,"Internal error: websock_sendevent - pSession == NULL"); + return false; + } + + if (NULL == pex) { + syslog(LOG_ERR,"Internal error: websock_sendevent - pEvent == NULL"); + return false; + } + + return gpobj->sendEvent(pSession->m_pClientItem, pex ); +} + +/////////////////////////////////////////////////////////////////////////////// +// websocket_post_incomingEvent +// + +void +websock_post_incomingEvents(void) +{ + pthread_mutex_lock(&gpobj->m_mutex_websocketSession); + + std::list::iterator iter; + for (iter = gpobj->m_websocketSessions.begin(); + iter != gpobj->m_websocketSessions.end(); + ++iter) { + + websock_session* pSession = *iter; + if (NULL == pSession) { + continue; + } + + // Should be a client item... hmm.... client disconnected + if (NULL == pSession->m_pClientItem) { + continue; + } + + if (pSession->m_conn_state < WEBSOCK_CONN_STATE_CONNECTED) + continue; + + if (NULL == pSession->m_conn) + continue; + + if (pSession->m_pClientItem->m_bOpen && + pSession->m_pClientItem->m_clientInputQueue.size()) { + + vscpEvent* pEvent; + pthread_mutex_lock( + &pSession->m_pClientItem->m_mutexClientInputQueue); + pEvent = pSession->m_pClientItem->m_clientInputQueue.front(); + pSession->m_pClientItem->m_clientInputQueue.pop_front(); + pthread_mutex_unlock( + &pSession->m_pClientItem->m_mutexClientInputQueue); + if (NULL != pEvent) { + + // Run event through filter + if (vscp_doLevel2Filter(pEvent, + &pSession->m_pClientItem->m_filter)) { + + // User must be authorized to receive events + if (!(pSession->m_pClientItem->m_pUserItem + ->getUserRights() & + VSCP_USER_RIGHT_ALLOW_RCV_EVENT)) { + continue; + } + + std::string str; + if (vscp_convertEventToString(str, pEvent)) { + + if (__VSCP_DEBUG_WEBSOCKET_RX) { + syslog(LOG_DEBUG, + "Received ws event %s", + str.c_str()); + } + + // Write it out + if (WS_TYPE_1 == pSession->m_wstypes) { + str = ("E;") + str; + mg_websocket_write(pSession->m_conn, + MG_WEBSOCKET_OPCODE_TEXT, + (const char*)str.c_str(), + str.length()); + } + else if (WS_TYPE_2 == pSession->m_wstypes) { + std::string strEvent; + vscp_convertEventToJSON(strEvent, pEvent); + std::string str = + vscp_str_format(WS2_EVENT, strEvent.c_str()); + mg_websocket_write(pSession->m_conn, + MG_WEBSOCKET_OPCODE_TEXT, + (const char*)str.c_str(), + str.length()); + } + } + } + + // Remove the event + vscp_deleteEvent_v2(&pEvent); + + } // Valid pEvent pointer + + } // events available + + } // for + + pthread_mutex_unlock(&gpobj->m_mutex_websocketSession); +} + +//////////////////////////////////////////////////////////////////////////////// +// ws1_connectHandler +// + +int +ws1_connectHandler(const struct mg_connection* conn, void* cbdata) +{ + struct mg_context* ctx = mg_get_context(conn); + int reject = 1; + + // Check pointers + if (NULL == conn) + return 1; + if (NULL == ctx) + return 1; + + mg_lock_context(ctx); + websock_session* pSession = websock_new_session(conn); + + if (NULL != pSession) { + reject = 0; + } + + // This is a WS1 type connection + pSession->m_wstypes = WS_TYPE_1; + + mg_unlock_context(ctx); + + if (__VSCP_DEBUG_WEBSOCKET) { + syslog(LOG_ERR, + "[Websocket ws1] WS1 Connection: client %s", + (reject ? "rejected" : "accepted")); + } + + return reject; +} + +//////////////////////////////////////////////////////////////////////////////// +// ws1_closeHandler +// + +void +ws1_closeHandler(const struct mg_connection* conn, void* cbdata) +{ + struct mg_context* ctx = mg_get_context(conn); + websock_session* pSession = + (websock_session*)mg_get_user_connection_data(conn); + + if (NULL == conn) + return; + if (NULL == pSession) + return; + if (pSession->m_conn != conn) + return; + if (pSession->m_conn_state < WEBSOCK_CONN_STATE_CONNECTED) + return; + + mg_lock_context(ctx); + + // Record activity + pSession->lastActiveTime = time(NULL); + + pSession->m_conn_state = WEBSOCK_CONN_STATE_NULL; + pSession->m_conn = NULL; + gpobj->m_clientList.removeClient(pSession->m_pClientItem); + pSession->m_pClientItem = NULL; + + pthread_mutex_lock(&gpobj->m_mutex_websocketSession); + // Remove session + gpobj->m_websocketSessions.remove(pSession); + delete pSession; + pthread_mutex_unlock(&gpobj->m_mutex_websocketSession); + + mg_unlock_context(ctx); +} + +//////////////////////////////////////////////////////////////////////////////// +// ws1_readyHandler +// + +void +ws1_readyHandler(struct mg_connection* conn, void* cbdata) +{ + websock_session* pSession = + (websock_session*)mg_get_user_connection_data(conn); + + // Check pointers + if (NULL == conn) + return; + if (NULL == pSession) + return; + if (pSession->m_conn != conn) + return; + if (pSession->m_conn_state < WEBSOCK_CONN_STATE_CONNECTED) + return; + + // Record activity + pSession->lastActiveTime = time(NULL); + + // Start authentication + std::string str = vscp_str_format(("+;AUTH0;%s"), pSession->m_sid); + mg_websocket_write(conn, + MG_WEBSOCKET_OPCODE_TEXT, + (const char*)str.c_str(), + str.length()); + + pSession->m_conn_state = WEBSOCK_CONN_STATE_DATA; +} + +//////////////////////////////////////////////////////////////////////////////// +// ws1_dataHandler +// + +int +ws1_dataHandler(struct mg_connection* conn, + int bits, + char* data, + size_t len, + void* cbdata) +{ + std::string strWsPkt; + websock_session* pSession = + (websock_session*)mg_get_user_connection_data(conn); + + // Check pointers + if (NULL == conn) + return WEB_ERROR; + if (NULL == pSession) + return WEB_ERROR; + if (pSession->m_conn != conn) + return WEB_ERROR; + if (pSession->m_conn_state < WEBSOCK_CONN_STATE_CONNECTED) + return WEB_ERROR; + + // Record activity + pSession->lastActiveTime = time(NULL); + + switch (((unsigned char)bits) & 0x0F) { + + case MG_WEBSOCKET_OPCODE_CONTINUATION: + + if (__VSCP_DEBUG_WEBSOCKET_RX) { + syslog(LOG_DEBUG, "Websocket WS1 - opcode = Continuation"); + } + + // Save and concatenate mesage + pSession->m_strConcatenated += std::string(data, len); + + // if last process is + if (1 & bits) { + try { + if (!ws1_message(conn, + pSession, + pSession->m_strConcatenated)) { + return WEB_ERROR; + } + } + catch (...) { + syslog(LOG_ERR, + "ws1: Exception occurred ws1_message concat"); + } + } + break; + + // https://developer.mozilla.org/en-US/docs/Web/API/WebSockets_API/Writing_WebSocket_servers + case MG_WEBSOCKET_OPCODE_TEXT: + if (__VSCP_DEBUG_WEBSOCKET_RX) { + syslog(LOG_DEBUG, + "Websocket WS1 - opcode = text[%s]", + strWsPkt.c_str()); + } + if (1 & bits) { + try { + strWsPkt = std::string(data, len); + if (!ws1_message(conn, pSession, strWsPkt)) { + return WEB_ERROR; + } + } + catch (...) { + syslog(LOG_ERR, "ws1: Exception occurred ws1_message"); + } + } + else { + // Store first part + pSession->m_strConcatenated = std::string(data, len); + } + break; + + case MG_WEBSOCKET_OPCODE_BINARY: + if (__VSCP_DEBUG_WEBSOCKET_RX) { + syslog(LOG_DEBUG, "Websocket WS1 - opcode = BINARY"); + } + break; + + case MG_WEBSOCKET_OPCODE_CONNECTION_CLOSE: + if (__VSCP_DEBUG_WEBSOCKET) { + syslog(LOG_DEBUG, "Websocket WS1 - opcode = Connection close"); + } + break; + + case MG_WEBSOCKET_OPCODE_PING: + if (__VSCP_DEBUG_WEBSOCKET_PING) { + syslog(LOG_DEBUG, "Websocket WS1 - Ping received/Pong sent,"); + } + mg_websocket_write(conn, MG_WEBSOCKET_OPCODE_PONG, NULL, 0); + break; + + case MG_WEBSOCKET_OPCODE_PONG: + if (__VSCP_DEBUG_WEBSOCKET_PING) { + syslog(LOG_DEBUG, "Websocket WS2 - Pong received/Pung sent,"); + } + mg_websocket_write(conn, MG_WEBSOCKET_OPCODE_PING, NULL, 0); + break; + + default: + break; + } + + return WEB_OK; +} + +/////////////////////////////////////////////////////////////////////////////// +// ws1_message +// + +bool +ws1_message(struct mg_connection* conn, + websock_session* pSession, + std::string& strWsPkt) +{ + std::string str; + + // Check pointer + if (NULL == conn) + return false; + if (NULL == pSession) + return false; + + vscp_trim(strWsPkt); + + switch (strWsPkt[0]) { + + // Command - | 'C' | command type (byte) | data | + case 'C': + // Point beyond initial info "C;" + strWsPkt = vscp_str_right(strWsPkt, strWsPkt.length() - 2); + try { + ws1_command(conn, pSession, strWsPkt); + } + catch (...) { + syslog(LOG_ERR, "ws1: Exception occurred ws1_command"); + str = vscp_str_format(("-;C;%d;%s"), + (int)WEBSOCK_ERROR_GENERAL, + WEBSOCK_STR_ERROR_GENERAL); + mg_websocket_write(conn, + MG_WEBSOCKET_OPCODE_TEXT, + (const char*)str.c_str(), + str.length()); + } + break; + + // Event | 'E' ; head(byte) , vscp_class(unsigned short) , + // vscp_type(unsigned + // short) , GUID(16*byte), data(0-487 bytes) | + case 'E': { + + // Must be authorised to do this + if ((NULL == pSession->m_pClientItem) || + !pSession->m_pClientItem->bAuthenticated) { + + str = vscp_str_format(("-;%d;%s"), + (int)WEBSOCK_ERROR_NOT_AUTHORISED, + WEBSOCK_STR_ERROR_NOT_AUTHORISED); + mg_websocket_write(conn, + MG_WEBSOCKET_OPCODE_TEXT, + (const char*)str.c_str(), + str.length()); + + syslog( + LOG_ERR, + "[Websocket ws1] User [%s] is not " + "authorised.\n", + pSession->m_pClientItem->m_pUserItem->getUserName().c_str()); + + return true; + } + + // User must be allowed to send events + if (!(pSession->m_pClientItem->m_pUserItem->getUserRights() & + VSCP_USER_RIGHT_ALLOW_SEND_EVENT)) { + + str = vscp_str_format(("-;%d;%s"), + (int)WEBSOCK_ERROR_NOT_ALLOWED_TO_DO_THAT, + WEBSOCK_STR_ERROR_NOT_ALLOWED_TO_DO_THAT); + + mg_websocket_write(conn, + MG_WEBSOCKET_OPCODE_TEXT, + (const char*)str.c_str(), + str.length()); + + syslog( + LOG_ERR, + "[Websocket ws1] User [%s] is not " + "allowed to send events.\n", + pSession->m_pClientItem->m_pUserItem->getUserName().c_str()); + + return true; // We still leave channel open + } + + // Point beyond initial info "E;" + strWsPkt = vscp_str_right(strWsPkt, strWsPkt.length() - 2); + vscpEventEx ex; + + try { + if (vscp_convertStringToEventEx(&ex, strWsPkt)) { + + // If GUID is all null give it GUID of interface + if (vscp_isGUIDEmpty(ex.GUID)) { + pSession->m_pClientItem->m_guid.writeGUID( + ex.GUID); + } + + // Is this user allowed to send events + if (!(pSession->m_pClientItem->m_pUserItem + ->getUserRights() & + VSCP_USER_RIGHT_ALLOW_SEND_EVENT)) { + + str = vscp_str_format( + ("-;%d;%s"), + (int)WEBSOCK_ERROR_NOT_ALLOWED_TO_SEND_EVENT, + WEBSOCK_ERROR_NOT_ALLOWED_TO_SEND_EVENT); + + mg_websocket_write(conn, + MG_WEBSOCKET_OPCODE_TEXT, + (const char*)str.c_str(), + str.length()); + + syslog( + LOG_ERR, + "[Websocket ws1] User [%s] is not " + "allowed to send events.\n", + pSession->m_pClientItem->m_pUserItem->getUserName() + .c_str()); + + return true; // We still leave channel open + } + + // Is user allowed to send CLASS1.PROTOCOL events + if ((VSCP_CLASS1_PROTOCOL == ex.vscp_class) && + (VSCP_CLASS2_LEVEL1_PROTOCOL == + ex.vscp_class) && + !(pSession->m_pClientItem->m_pUserItem + ->getUserRights() & + VSCP_USER_RIGHT_ALLOW_SEND_L1CTRL_EVENT)) { + + str = vscp_str_format( + ("-;%d;%s"), + (int)WEBSOCK_ERROR_NOT_ALLOWED_TO_SEND_EVENT, + WEBSOCK_ERROR_NOT_ALLOWED_TO_SEND_EVENT); + mg_websocket_write(conn, + MG_WEBSOCKET_OPCODE_TEXT, + (const char*)str.c_str(), + str.length()); + + syslog( + LOG_ERR, + "[Websocket ws1] User [%s] is not " + "authorised to send CLASS1.PROTOCOL events.\n", + pSession->m_pClientItem->m_pUserItem->getUserName() + .c_str()); + + return true; + } + + // Is user allowed to send CLASS2.PROTOCOL events + if ((VSCP_CLASS2_PROTOCOL == ex.vscp_class) && + !(pSession->m_pClientItem->m_pUserItem + ->getUserRights() & + VSCP_USER_RIGHT_ALLOW_SEND_L2CTRL_EVENT)) { + + str = vscp_str_format( + ("-;%d;%s"), + (int)WEBSOCK_ERROR_NOT_ALLOWED_TO_SEND_EVENT, + WEBSOCK_ERROR_NOT_ALLOWED_TO_SEND_EVENT); + mg_websocket_write(conn, + MG_WEBSOCKET_OPCODE_TEXT, + (const char*)str.c_str(), + str.length()); + + syslog( + LOG_ERR, + "[Websocket ws1] User [%s] is not " + "authorised to send CLASS2.PROTOCOL events.\n", + pSession->m_pClientItem->m_pUserItem->getUserName() + .c_str()); + + return true; + } + + // Is user allowed to send CLASS2.HLO events + if ((VSCP_CLASS2_HLO == ex.vscp_class) && + !(pSession->m_pClientItem->m_pUserItem + ->getUserRights() & + VSCP_USER_RIGHT_ALLOW_SEND_HLO_EVENT)) { + + str = vscp_str_format( + ("-;%d;%s"), + (int)WEBSOCK_ERROR_NOT_ALLOWED_TO_SEND_EVENT, + WEBSOCK_ERROR_NOT_ALLOWED_TO_SEND_EVENT); + mg_websocket_write(conn, + MG_WEBSOCKET_OPCODE_TEXT, + (const char*)str.c_str(), + str.length()); + + syslog( + LOG_ERR, + "[Websocket ws1] User [%s] is not " + "authorised to send CLASS2.HLO events.\n", + pSession->m_pClientItem->m_pUserItem->getUserName() + .c_str()); + + return true; + } + + // Check if this user is allowed to send this event + if (!pSession->m_pClientItem->m_pUserItem + ->isUserAllowedToSendEvent(ex.vscp_class, + ex.vscp_type)) { + + str = vscp_str_format( + ("-;%d;%s"), + (int)WEBSOCK_ERROR_NOT_ALLOWED_TO_SEND_EVENT, + WEBSOCK_ERROR_NOT_ALLOWED_TO_SEND_EVENT); + + mg_websocket_write(conn, + MG_WEBSOCKET_OPCODE_TEXT, + (const char*)str.c_str(), + str.length()); + + syslog( + LOG_ERR, + "[websocket ws1] User [%s] is not allowed to " + "send event class=%d type=%d.", + pSession->m_pClientItem->m_pUserItem->getUserName() + .c_str(), + ex.vscp_class, + ex.vscp_type); + + return true; // Keep connection open + } + + ex.obid = pSession->m_pClientItem->m_clientID; + if (websock_sendevent(conn, pSession, &ex)) { + mg_websocket_write(conn, + MG_WEBSOCKET_OPCODE_TEXT, + "+;EVENT", + 7); + if (__VSCP_DEBUG_WEBSOCKET_TX) { + syslog(LOG_ERR, + "[websocket ws1] Sent ws1 event %s", + strWsPkt.c_str()); + } + } + else { + str = vscp_str_format(("-;%d;%s"), + (int)WEBSOCK_ERROR_TX_BUFFER_FULL, + WEBSOCK_STR_ERROR_TX_BUFFER_FULL); + mg_websocket_write(conn, + MG_WEBSOCKET_OPCODE_TEXT, + (const char*)str.c_str(), + str.length()); + } + } + } + catch (...) { + syslog(LOG_ERR, "ws1: Exception occurred send event"); + str = vscp_str_format(("-;E;%d;%s"), + (int)WEBSOCK_ERROR_GENERAL, + WEBSOCK_STR_ERROR_GENERAL); + mg_websocket_write(conn, + MG_WEBSOCKET_OPCODE_TEXT, + (const char*)str.c_str(), + str.length()); + } + + } break; + + // Unknown command + default: + break; + } + + return true; +} + +//////////////////////////////////////////////////////////////////////////////// +// ws1_command +// + +void +ws1_command(struct mg_connection* conn, + struct websock_session* pSession, + std::string& strCmd) +{ + std::string str; // Worker string + std::string strTok; + + // Check pointer + if (NULL == conn) + return; + if (NULL == pSession) + return; + + if (__VSCP_DEBUG_WEBSOCKET) { + syslog(LOG_ERR, "[Websocket ws1] Command = %s", strCmd.c_str()); + } + + std::deque tokens; + vscp_split(tokens, strCmd, ";"); + + // Get command + if (!tokens.empty()) { + strTok = tokens.front(); + tokens.pop_front(); + vscp_trim(strTok); + vscp_makeUpper(strTok); + } + else { + std::string str = vscp_str_format(("-;%d;%s"), + (int)WEBSOCK_ERROR_SYNTAX_ERROR, + WEBSOCK_STR_ERROR_SYNTAX_ERROR); + mg_websocket_write(conn, + MG_WEBSOCKET_OPCODE_TEXT, + (const char*)str.c_str(), + str.length()); + return; + } + + // ------------------------------------------------------------------------ + // NOOP + //------------------------------------------------------------------------- + + if (vscp_startsWith(strTok, "NOOP")) { + mg_websocket_write(conn, MG_WEBSOCKET_OPCODE_TEXT, "+;NOOP", 6); + } + + // ------------------------------------------------------------------------ + // CHALLENGE + //------------------------------------------------------------------------- + + else if (vscp_startsWith(strTok, "CHALLENGE")) { + + // Send authentication challenge + if ((NULL == pSession->m_pClientItem) || + !pSession->m_pClientItem->bAuthenticated) { + + // Start authentication + str = vscp_str_format(("+;AUTH0;%s"), pSession->m_sid); + mg_websocket_write(conn, + MG_WEBSOCKET_OPCODE_TEXT, + (const char*)str.c_str(), + str.length()); + } + } + + // ------------------------------------------------------------------------ + // AUTH + //------------------------------------------------------------------------- + + // AUTH;iv;aes128 + else if (vscp_startsWith(strTok, "AUTH")) { + + try { + std::string str; + std::string strUser; + std::string strIV = tokens.front(); + tokens.pop_front(); + std::string strCrypto = tokens.front(); + tokens.pop_front(); + if (websock_authentication(conn, pSession, strIV, strCrypto)) { + std::string userSettings; + pSession->m_pClientItem->m_pUserItem->getAsString(userSettings); + str = vscp_str_format(("+;AUTH1;%s"), + (const char*)userSettings.c_str()); + mg_websocket_write(conn, + MG_WEBSOCKET_OPCODE_TEXT, + (const char*)str.c_str(), + str.length()); + } + else { + + str = vscp_str_format(("-;AUTH;%d;%s"), + (int)WEBSOCK_ERROR_NOT_AUTHORISED, + WEBSOCK_STR_ERROR_NOT_AUTHORISED); + mg_websocket_write(conn, + MG_WEBSOCKET_OPCODE_TEXT, + (const char*)str.c_str(), + str.length()); + pSession->m_pClientItem->bAuthenticated = + false; // Authenticated + } + } + catch (...) { + syslog(LOG_ERR, "WS1: AUTH failed (syntax)"); + str = vscp_str_format(("-;AUTH;%d;%s"), + (int)WEBSOCK_ERROR_SYNTAX_ERROR, + WEBSOCK_STR_ERROR_SYNTAX_ERROR); + mg_websocket_write(conn, + MG_WEBSOCKET_OPCODE_TEXT, + (const char*)str.c_str(), + str.length()); + } + } + + // ------------------------------------------------------------------------ + // OPEN + //------------------------------------------------------------------------- + + else if (vscp_startsWith(strTok, "OPEN")) { + + // Must be authorised to do this + if ((NULL == pSession->m_pClientItem) || + !pSession->m_pClientItem->bAuthenticated) { + + str = vscp_str_format(("-;OPEN;%d;%s"), + (int)WEBSOCK_ERROR_NOT_AUTHORISED, + WEBSOCK_STR_ERROR_NOT_AUTHORISED); + + mg_websocket_write(conn, + MG_WEBSOCKET_OPCODE_TEXT, + (const char*)str.c_str(), + str.length()); + + return; // We still leave channel open + } + + pSession->m_pClientItem->m_bOpen = true; + mg_websocket_write(conn, MG_WEBSOCKET_OPCODE_TEXT, "+;OPEN", 6); + } + + // ------------------------------------------------------------------------ + // CLOSE + //------------------------------------------------------------------------- + + else if (vscp_startsWith(strTok, "CLOSE")) { + pSession->m_pClientItem->m_bOpen = false; + mg_websocket_write(conn, MG_WEBSOCKET_OPCODE_TEXT, "+;CLOSE", 7); + } + + // ------------------------------------------------------------------------ + // SETFILTER/SF + //------------------------------------------------------------------------- + + else if (vscp_startsWith(strTok, "SETFILTER") || + vscp_startsWith(strTok, "SF")) { + + unsigned char ifGUID[16]; + memset(ifGUID, 0, 16); + + // Must be authorized to do this + if ((NULL == pSession->m_pClientItem) || + !pSession->m_pClientItem->bAuthenticated) { + + str = vscp_str_format(("-;SF;%d;%s"), + (int)WEBSOCK_ERROR_NOT_AUTHORISED, + WEBSOCK_STR_ERROR_NOT_AUTHORISED); + + mg_websocket_write(conn, + MG_WEBSOCKET_OPCODE_TEXT, + (const char*)str.c_str(), + str.length()); + + syslog(LOG_ERR, + "[Websocket ws1] User/host not authorised to set a filter."); + + return; // We still leave channel open + } + + // Check privilege + if (!(pSession->m_pClientItem->m_pUserItem->getUserRights() & + VSCP_USER_RIGHT_ALLOW_SETFILTER)) { + + str = vscp_str_format(("-;SF;%d;%s"), + (int)WEBSOCK_ERROR_NOT_ALLOWED_TO_DO_THAT, + WEBSOCK_STR_ERROR_NOT_ALLOWED_TO_DO_THAT); + + mg_websocket_write(conn, + MG_WEBSOCKET_OPCODE_TEXT, + (const char*)str.c_str(), + str.length()); + + syslog(LOG_ERR, + "[Websocket ws1] User [%s] not " + "allowed to set a filter.\n", + pSession->m_pClientItem->m_pUserItem->getUserName().c_str()); + return; // We still leave channel open + } + + // Get filter + if (!tokens.empty()) { + + strTok = tokens.front(); + tokens.pop_front(); + + pthread_mutex_lock( + &pSession->m_pClientItem->m_mutexClientInputQueue); + if (!vscp_readFilterFromString(&pSession->m_pClientItem->m_filter, + strTok)) { + + str = vscp_str_format(("-;SF;%d;%s"), + (int)WEBSOCK_ERROR_SYNTAX_ERROR, + WEBSOCK_STR_ERROR_SYNTAX_ERROR); + + mg_websocket_write(conn, + MG_WEBSOCKET_OPCODE_TEXT, + (const char*)str.c_str(), + str.length()); + + pthread_mutex_unlock( + &pSession->m_pClientItem->m_mutexClientInputQueue); + return; + } + + pthread_mutex_unlock( + &pSession->m_pClientItem->m_mutexClientInputQueue); + } + else { + + str = vscp_str_format(("-;SF;%d;%s"), + (int)WEBSOCK_ERROR_SYNTAX_ERROR, + WEBSOCK_STR_ERROR_SYNTAX_ERROR); + + mg_websocket_write(conn, + MG_WEBSOCKET_OPCODE_TEXT, + (const char*)str.c_str(), + str.length()); + + return; + } + + // Get mask + if (!tokens.empty()) { + + strTok = tokens.front(); + tokens.pop_front(); + + pthread_mutex_lock( + &pSession->m_pClientItem->m_mutexClientInputQueue); + if (!vscp_readMaskFromString(&pSession->m_pClientItem->m_filter, + strTok)) { + + str = vscp_str_format(("-;SF;%d;%s"), + (int)WEBSOCK_ERROR_SYNTAX_ERROR, + WEBSOCK_STR_ERROR_SYNTAX_ERROR); + + mg_websocket_write(conn, + MG_WEBSOCKET_OPCODE_TEXT, + (const char*)str.c_str(), + str.length()); + + pthread_mutex_unlock( + &pSession->m_pClientItem->m_mutexClientInputQueue); + return; + } + + pthread_mutex_unlock( + &pSession->m_pClientItem->m_mutexClientInputQueue); + } + else { + str = vscp_str_format(("-;SF;%d;%s"), + (int)WEBSOCK_ERROR_SYNTAX_ERROR, + WEBSOCK_STR_ERROR_SYNTAX_ERROR); + + mg_websocket_write(conn, + MG_WEBSOCKET_OPCODE_TEXT, + (const char*)str.c_str(), + str.length()); + return; + } + + // Positive response + mg_websocket_write(conn, MG_WEBSOCKET_OPCODE_TEXT, "+;SF", 4); + } + + // ------------------------------------------------------------------------ + // CLRQ/CLRQUEUE + //------------------------------------------------------------------------- + + // Clear the event queue + else if (vscp_startsWith(strTok, "CLRQUEUE") || + vscp_startsWith(strTok, "CLRQ")) { + + // Must be authorised to do this + if ((NULL == pSession->m_pClientItem) || + !pSession->m_pClientItem->bAuthenticated) { + + str = vscp_str_format(("-;CLRQ;%d;%s"), + (int)WEBSOCK_ERROR_NOT_AUTHORISED, + WEBSOCK_STR_ERROR_NOT_AUTHORISED); + + mg_websocket_write(conn, + MG_WEBSOCKET_OPCODE_TEXT, + (const char*)str.c_str(), + str.length()); + + syslog( + LOG_ERR, + "[Websocket ws1] User/host not authorised to clear the queue."); + + return; // We still leave channel open + } + + std::deque::iterator it; + pthread_mutex_lock(&pSession->m_pClientItem->m_mutexClientInputQueue); + + for (it = pSession->m_pClientItem->m_clientInputQueue.begin(); + it != pSession->m_pClientItem->m_clientInputQueue.end(); + ++it) { + vscpEvent* pEvent = + pSession->m_pClientItem->m_clientInputQueue.front(); + pSession->m_pClientItem->m_clientInputQueue.pop_front(); + vscp_deleteEvent_v2(&pEvent); + } + + pSession->m_pClientItem->m_clientInputQueue.clear(); + pthread_mutex_unlock(&pSession->m_pClientItem->m_mutexClientInputQueue); + + mg_websocket_write(conn, MG_WEBSOCKET_OPCODE_TEXT, "+;CLRQ", 6); + } + + // ------------------------------------------------------------------------ + // VERSION + //------------------------------------------------------------------------- + + else if (vscp_startsWith(strTok, "VERSION")) { + + std::string strvalue; + + std::string strResult = ("+;VERSION;"); + strResult += VSCPD_DISPLAY_VERSION; + strResult += (";"); + strResult += vscp_str_format(("%d.%d.%d.%d"), + VSCPD_MAJOR_VERSION, + VSCPD_MINOR_VERSION, + VSCPD_RELEASE_VERSION, + VSCPD_BUILD_VERSION); + // Positive reply + mg_websocket_write(conn, + MG_WEBSOCKET_OPCODE_TEXT, + (const char*)strResult.c_str(), + strResult.length()); + } + + // ------------------------------------------------------------------------ + // COPYRIGHT + //------------------------------------------------------------------------- + + else if (vscp_startsWith(strTok, "COPYRIGHT")) { + + std::string strvalue; + + std::string strResult = ("+;COPYRIGHT;"); + strResult += VSCPD_COPYRIGHT; + + // Positive reply + mg_websocket_write(conn, + MG_WEBSOCKET_OPCODE_TEXT, + (const char*)strResult.c_str(), + strResult.length()); + } +} + +// ---------------------------------------------------------------------------- +// WS2 +// ---------------------------------------------------------------------------- + +//////////////////////////////////////////////////////////////////////////////// +// ws2_connectHandler +// + +int +ws2_connectHandler(const struct mg_connection* conn, void* cbdata) +{ + struct mg_context* ctx = mg_get_context(conn); + int reject = 1; + + // Check pointers + if (NULL == conn) + return 1; + if (NULL == ctx) + return 1; + + mg_lock_context(ctx); + websock_session* pSession = websock_new_session(conn); + + if (NULL != pSession) { + reject = 0; + } + + // This is a WS2 type connection + pSession->m_wstypes = WS_TYPE_2; + + mg_unlock_context(ctx); + + if (__VSCP_DEBUG_WEBSOCKET) { + syslog(LOG_ERR, + "[Websocket ws2] WS2 Connection: client %s", + (reject ? "rejected" : "accepted")); + } + + return reject; +} + +//////////////////////////////////////////////////////////////////////////////// +// ws2_closeHandler +// + +void +ws2_closeHandler(const struct mg_connection* conn, void* cbdata) + +{ + struct mg_context* ctx = mg_get_context(conn); + websock_session* pSession = + (websock_session*)mg_get_user_connection_data(conn); + + if (NULL == conn) + return; + if (NULL == pSession) + return; + if (pSession->m_conn != conn) + return; + if (pSession->m_conn_state < WEBSOCK_CONN_STATE_CONNECTED) + return; + + mg_lock_context(ctx); + + // Record activity + pSession->lastActiveTime = time(NULL); + + pSession->m_conn_state = WEBSOCK_CONN_STATE_NULL; + pSession->m_conn = NULL; + gpobj->m_clientList.removeClient(pSession->m_pClientItem); + pSession->m_pClientItem = NULL; + + pthread_mutex_lock(&gpobj->m_mutex_websocketSession); + gpobj->m_websocketSessions.remove(pSession); + delete pSession; + pthread_mutex_unlock(&gpobj->m_mutex_websocketSession); + + mg_unlock_context(ctx); +} + +#define WS2_AUTH0_TEMPLATE \ + "{" \ + " \"type\" : \"+\", " \ + " \"args\" : [\"AUTH0\",\"%s\"]" \ + "}" + +//////////////////////////////////////////////////////////////////////////////// +// ws2_readyHandler +// + +void +ws2_readyHandler(struct mg_connection* conn, void* cbdata) +{ + websock_session* pSession = + (websock_session*)mg_get_user_connection_data(conn); + + // Check pointers + if (NULL == conn) + return; + if (NULL == pSession) + return; + if (pSession->m_conn != conn) + return; + if (pSession->m_conn_state < WEBSOCK_CONN_STATE_CONNECTED) + return; + + // Record activity + pSession->lastActiveTime = time(NULL); + + // Start authentication + /* Auth0 response + { + "type" : "+" + "args" : ["AUTH0","%s"] + } + */ + std::string str = vscp_str_format(WS2_AUTH0_TEMPLATE, pSession->m_sid); + mg_websocket_write(conn, + MG_WEBSOCKET_OPCODE_TEXT, + (const char*)str.c_str(), + str.length()); + + pSession->m_conn_state = WEBSOCK_CONN_STATE_DATA; +} + +//////////////////////////////////////////////////////////////////////////////// +// ws2_dataHandler +// + +int +ws2_dataHandler(struct mg_connection* conn, + int bits, + char* data, + size_t len, + void* cbdata) +{ + std::string strWsPkt; + websock_session* pSession = + (websock_session*)mg_get_user_connection_data(conn); + + // Check pointers + if (NULL == conn) + return WEB_ERROR; + if (NULL == pSession) + return WEB_ERROR; + if (pSession->m_conn != conn) + return WEB_ERROR; + if (pSession->m_conn_state < WEBSOCK_CONN_STATE_CONNECTED) + return WEB_ERROR; + + // Record activity + pSession->lastActiveTime = time(NULL); + + switch (((unsigned char)bits) & 0x0F) { + + case MG_WEBSOCKET_OPCODE_CONTINUATION: + + if (__VSCP_DEBUG_WEBSOCKET) { + syslog(LOG_DEBUG, "Websocket WS2 - opcode = Continuation"); + } + + // Save and concatenate mesage + pSession->m_strConcatenated += std::string(data, len); + + // if last process is + if (1 & bits) { + try { + if (!ws2_message(conn, + pSession, + pSession->m_strConcatenated)) { + return WEB_ERROR; + } + } + catch (...) { + syslog(LOG_ERR, + "ws1: Exception occurred ws2_message concat"); + } + } + break; + + // https://developer.mozilla.org/en-US/docs/Web/API/WebSockets_API/Writing_WebSocket_servers + case MG_WEBSOCKET_OPCODE_TEXT: + + if (__VSCP_DEBUG_WEBSOCKET) { + syslog(LOG_DEBUG, + "Websocket WS2 - opcode = Text [%s]", + strWsPkt.c_str()); + } + + if (1 & bits) { + try { + strWsPkt = std::string(data, len); + if (!ws2_message(conn, pSession, strWsPkt)) { + return WEB_ERROR; + } + } + catch (...) { + syslog(LOG_ERR, "ws1: Exception occurred ws2_message"); + } + } + else { + // Store first part + pSession->m_strConcatenated = std::string(data, len); + } + break; + + case MG_WEBSOCKET_OPCODE_BINARY: + if (__VSCP_DEBUG_WEBSOCKET) { + syslog(LOG_DEBUG, "Websocket WS2 - opcode = BINARY"); + } + break; + + case MG_WEBSOCKET_OPCODE_CONNECTION_CLOSE: + if (__VSCP_DEBUG_WEBSOCKET) { + syslog(LOG_DEBUG, "Websocket WS2 - Connection close"); + } + break; + + case MG_WEBSOCKET_OPCODE_PING: + if (__VSCP_DEBUG_WEBSOCKET_PING) { + syslog(LOG_DEBUG, "Websocket WS2 - Ping received/Pong sent,"); + } + mg_websocket_write(conn, MG_WEBSOCKET_OPCODE_PONG, data, len); + break; + + case MG_WEBSOCKET_OPCODE_PONG: + if (__VSCP_DEBUG_WEBSOCKET_PING) { + syslog(LOG_DEBUG, "Websocket WS2 - Pong received/Ping sent,"); + } + mg_websocket_write(conn, MG_WEBSOCKET_OPCODE_PING, data, len); + break; + + default: + break; + } + + return WEB_OK; +} + +/////////////////////////////////////////////////////////////////////////////// +// ws2_message +// + +bool +ws2_message(struct mg_connection* conn, + websock_session* pSession, + std::string& strWsPkt) +{ + w2msg msg; + std::string str; + json json_obj; // Command obj, event obj etc + + // Check pointer + if (NULL == conn) { + return false; + } + if (NULL == pSession) { + return false; + } + + /* + { + "type": "event(E)|command(C)|response(+)|variable(V), + } + */ + try { + json json_pkg = json::parse(strWsPkt.c_str()); + + // "type": "event(E)|command(C)|response(+)|variable(V) + if (json_pkg.find("type") != json_pkg.end()) { + + std::string str = json_pkg.at("type").get(); + vscp_trim(str); + vscp_makeUpper(str); + + // Command + if (("COMMAND" == str) || ("CMD" == str) || ("C" == str)) { + + msg.m_type = MSG_TYPE_COMMAND; + + // Get command + std::string strCmd = json_pkg.at("command").get(); + vscp_trim(strCmd); + vscp_makeUpper(strCmd); + + // Find args + try { + + for (auto it = json_pkg.begin(); it != json_pkg.end(); + ++it) { + if ("args" == it.key()) { + // str = it.value(); + // json_obj = json::parse(str); + return ws2_command(conn, + pSession, + strCmd, + it.value()); + } + } + + std::string str = + vscp_str_format(WS2_NEGATIVE_RESPONSE, + strCmd.c_str(), + WEBSOCK_ERROR_PARSE_FORMAT, + WEBSOCK_STR_ERROR_PARSE_FORMAT); + mg_websocket_write(conn, + MG_WEBSOCKET_OPCODE_TEXT, + str.c_str(), + str.length()); + + // No arg found + syslog(LOG_ERR, + "Failed to parse ws2 websocket command object %s", + strWsPkt.c_str()); + return false; + } + catch (...) { + std::string str = + vscp_str_format(WS2_NEGATIVE_RESPONSE, + strCmd.c_str(), + WEBSOCK_ERROR_PARSE_FORMAT, + WEBSOCK_STR_ERROR_PARSE_FORMAT); + mg_websocket_write(conn, + MG_WEBSOCKET_OPCODE_TEXT, + str.c_str(), + str.length()); + + syslog(LOG_ERR, + "Failed to parse ws2 websocket command object %s", + strWsPkt.c_str()); + + return false; + } + } + // Event + else if (("EVENT" == str) || ("E" == str)) { + msg.m_type = MSG_TYPE_EVENT; + try { + for (auto it = json_pkg.begin(); it != json_pkg.end(); + ++it) { + if ("event" == it.key()) { + + str = it.value().dump(); + + // Client must be authorised to send events + if ((NULL == pSession->m_pClientItem) || + !pSession->m_pClientItem->bAuthenticated) { + + str = vscp_str_format( + WS2_NEGATIVE_RESPONSE, + "EVENT", + (int)WEBSOCK_ERROR_NOT_AUTHORISED, + WEBSOCK_STR_ERROR_NOT_AUTHORISED); + + mg_websocket_write(conn, + MG_WEBSOCKET_OPCODE_TEXT, + (const char*)str.c_str(), + str.length()); + + syslog(LOG_ERR, + "[Websocket ws2] User [%s] is not " + "allowed to login.\n", + pSession->m_pClientItem->m_pUserItem + ->getUserName() + .c_str()); + + return false; // 'false' - Drop connection + } + + vscpEventEx ex; + if (vscp_convertJSONToEventEx(&ex, str)) { + + // If GUID is all null give it GUID of interface + if (vscp_isGUIDEmpty(ex.GUID)) { + pSession->m_pClientItem->m_guid.writeGUID( + ex.GUID); + } + + // Is this user allowed to send events + if (!(pSession->m_pClientItem->m_pUserItem + ->getUserRights() & + VSCP_USER_RIGHT_ALLOW_SEND_EVENT)) { + + std::string str = vscp_str_format( + WS2_NEGATIVE_RESPONSE, + "EVENT", + WEBSOCK_ERROR_NOT_ALLOWED_TO_DO_THAT, + WEBSOCK_STR_ERROR_NOT_ALLOWED_TO_DO_THAT); + mg_websocket_write(conn, + MG_WEBSOCKET_OPCODE_TEXT, + str.c_str(), + str.length()); + + syslog(LOG_ERR, + "[Websocket ws2] User [%s] is not " + "allowed to send events.\n", + pSession->m_pClientItem->m_pUserItem + ->getUserName() + .c_str()); + + return true; // 'true' leave connection open + } + + // Is user allowed to send CLASS1.PROTOCOL + // events + if ( + (VSCP_CLASS1_PROTOCOL == ex.vscp_class) && + (VSCP_CLASS2_LEVEL1_PROTOCOL == + ex.vscp_class) && + !(pSession->m_pClientItem->m_pUserItem + ->getUserRights() & + VSCP_USER_RIGHT_ALLOW_SEND_L1CTRL_EVENT)) { + + std::string str = vscp_str_format( + WS2_NEGATIVE_RESPONSE, + "EVENT", + WEBSOCK_ERROR_NOT_ALLOWED_TO_DO_THAT, + WEBSOCK_STR_ERROR_NOT_ALLOWED_TO_DO_THAT); + mg_websocket_write(conn, + MG_WEBSOCKET_OPCODE_TEXT, + str.c_str(), + str.length()); + + syslog(LOG_ERR, + "[Websocket ws2] User [%s] is not " + "authorised to send CLASS1.PROTOCOL " + "events.\n", + pSession->m_pClientItem->m_pUserItem + ->getUserName() + .c_str()); + + return true; // 'true' leave connection open + } + + // Is user allowed to send CLASS2.PROTOCOL + // events + if ( + (VSCP_CLASS2_PROTOCOL == ex.vscp_class) && + !(pSession->m_pClientItem->m_pUserItem + ->getUserRights() & + VSCP_USER_RIGHT_ALLOW_SEND_L2CTRL_EVENT)) { + + std::string str = vscp_str_format( + WS2_NEGATIVE_RESPONSE, + "EVENT", + WEBSOCK_ERROR_NOT_ALLOWED_TO_DO_THAT, + WEBSOCK_STR_ERROR_NOT_ALLOWED_TO_DO_THAT); + mg_websocket_write(conn, + MG_WEBSOCKET_OPCODE_TEXT, + str.c_str(), + str.length()); + + syslog(LOG_ERR, + "[Websocket ws2] User [%s] is not " + "authorised to send CLASS2.PROTOCOL " + "events.\n", + pSession->m_pClientItem->m_pUserItem + ->getUserName() + .c_str()); + + return true; // 'true' leave connection open + } + + // Is user allowed to send CLASS2.HLO events + if ((VSCP_CLASS2_HLO == ex.vscp_class) && + !(pSession->m_pClientItem->m_pUserItem + ->getUserRights() & + VSCP_USER_RIGHT_ALLOW_SEND_HLO_EVENT)) { + + std::string str = vscp_str_format( + WS2_NEGATIVE_RESPONSE, + "EVENT", + WEBSOCK_ERROR_NOT_ALLOWED_TO_DO_THAT, + WEBSOCK_STR_ERROR_NOT_ALLOWED_TO_DO_THAT); + mg_websocket_write(conn, + MG_WEBSOCKET_OPCODE_TEXT, + str.c_str(), + str.length()); + + syslog(LOG_ERR, + "[Websocket ws2] User [%s] is not " + "authorised to send CLASS2.HLO " + "events.\n", + pSession->m_pClientItem->m_pUserItem + ->getUserName() + .c_str()); + + return true; // 'true' leave connection open + } + + // Check if this user is allowed to send this + // event + if (!pSession->m_pClientItem->m_pUserItem + ->isUserAllowedToSendEvent( + ex.vscp_class, + ex.vscp_type)) { + + std::string str = vscp_str_format( + WS2_NEGATIVE_RESPONSE, + "EVENT", + WEBSOCK_ERROR_NOT_ALLOWED_TO_DO_THAT, + WEBSOCK_STR_ERROR_NOT_ALLOWED_TO_DO_THAT); + mg_websocket_write(conn, + MG_WEBSOCKET_OPCODE_TEXT, + str.c_str(), + str.length()); + + syslog( + LOG_ERR, + "websocket] User [%s] is not allowed to " + "send event class=%d type=%d.", + pSession->m_pClientItem->m_pUserItem + ->getUserName() + .c_str(), + ex.vscp_class, + ex.vscp_type); + + return true; // 'true' leave connection open + } + + ex.obid = pSession->m_pClientItem->m_clientID; + if (websock_sendevent(conn, pSession, &ex)) { + + str = vscp_str_format(WS2_POSITIVE_RESPONSE, + "EVENT", + "null"); + mg_websocket_write(conn, + MG_WEBSOCKET_OPCODE_TEXT, + str.c_str(), + str.length()); + + if (__VSCP_DEBUG_WEBSOCKET_TX) { + syslog(LOG_ERR, + "Sent ws2 event %s", + strWsPkt.c_str()); + } + } + else { + + str = vscp_str_format( + WS2_NEGATIVE_RESPONSE, + "EVENT", + (int)WEBSOCK_ERROR_TX_BUFFER_FULL, + WEBSOCK_STR_ERROR_TX_BUFFER_FULL); + mg_websocket_write(conn, + MG_WEBSOCKET_OPCODE_TEXT, + (const char*)str.c_str(), + str.length()); + syslog(LOG_ERR, + "Transmission buffer is full %s", + strWsPkt.c_str()); + + return true; // 'true' leave connection open + } + } + } + } + } + catch (...) { + std::string str = + vscp_str_format(WS2_NEGATIVE_RESPONSE, + "EVENT", + WEBSOCK_ERROR_PARSE_FORMAT, + WEBSOCK_STR_ERROR_PARSE_FORMAT); + mg_websocket_write(conn, + MG_WEBSOCKET_OPCODE_TEXT, + str.c_str(), + str.length()); + + syslog(LOG_ERR, + "Failed to parse ws2 websocket event object %s", + strWsPkt.c_str()); + + return true; // 'true' leave connection open + } + } + // Positive response + else if ("+" == str) { + msg.m_type = MSG_TYPE_RESPONSE_POSITIVE; + try { + for (auto it = json_pkg.begin(); it != json_pkg.end(); + ++it) { + if ("response" == it.key()) { + str = it.value(); + json_obj = json::parse(str); + break; + } + } + } + catch (...) { + std::string str = + vscp_str_format(WS2_NEGATIVE_RESPONSE, + "EVENT", + WEBSOCK_ERROR_PARSE_FORMAT, + WEBSOCK_STR_ERROR_PARSE_FORMAT); + mg_websocket_write(conn, + MG_WEBSOCKET_OPCODE_TEXT, + str.c_str(), + str.length()); + + syslog(LOG_ERR, + "Failed to parse ws2 websocket + response object %s", + strWsPkt.c_str()); + return true; // 'true' leave connection open + } + } + // Negative response + else if ("-" == str) { + msg.m_type = MSG_TYPE_RESPONSE_NEGATIVE; + try { + for (auto it = json_pkg.begin(); it != json_pkg.end(); + ++it) { + if ("response" == it.key()) { + str = it.value(); + json_obj = json::parse(str); + break; + } + } + } + catch (...) { + std::string str = + vscp_str_format(WS2_NEGATIVE_RESPONSE, + "EVENT", + WEBSOCK_ERROR_PARSE_FORMAT, + WEBSOCK_STR_ERROR_PARSE_FORMAT); + mg_websocket_write(conn, + MG_WEBSOCKET_OPCODE_TEXT, + str.c_str(), + str.length()); + + syslog(LOG_ERR, + "Failed to parse ws2 websocket - response object %s", + strWsPkt.c_str()); + return true; // 'true' leave connection open + } + } + // Changed variable + else if ("VARIABLE" == str) { + msg.m_type = MSG_TYPE_VARIABLE; + try { + for (auto it = json_pkg.begin(); it != json_pkg.end(); + ++it) { + if ("variable" == it.key()) { + str = it.value(); + json_obj = json::parse(str); + break; + } + } + } + catch (...) { + std::string str = + vscp_str_format(WS2_NEGATIVE_RESPONSE, + "EVENT", + WEBSOCK_ERROR_PARSE_FORMAT, + WEBSOCK_STR_ERROR_PARSE_FORMAT); + mg_websocket_write(conn, + MG_WEBSOCKET_OPCODE_TEXT, + str.c_str(), + str.length()); + + syslog(LOG_ERR, + "Failed to parse ws2 websocket variable object %s", + strWsPkt.c_str()); + return true; // 'true' leave connection open + } + } + else { + + std::string str = + vscp_str_format(WS2_NEGATIVE_RESPONSE, + "EVENT", + WEBSOCK_ERROR_UNKNOWN_TYPE, + WEBSOCK_STR_ERROR_UNKNOWN_TYPE); + mg_websocket_write(conn, + MG_WEBSOCKET_OPCODE_TEXT, + str.c_str(), + str.length()); + + // This is a type we do not recognize + syslog(LOG_ERR, + "Unknown ws2 websocket type %s", + strWsPkt.c_str()); + return true; // 'true' leave connection open + } + } + } + catch (...) { + std::string str = vscp_str_format(WS2_NEGATIVE_RESPONSE, + "EVENT", + WEBSOCK_ERROR_UNKNOWN_TYPE, + WEBSOCK_STR_ERROR_UNKNOWN_TYPE); + mg_websocket_write(conn, + MG_WEBSOCKET_OPCODE_TEXT, + str.c_str(), + str.length()); + + syslog(LOG_ERR, + "Failed to parse ws2 websocket command %s", + strWsPkt.c_str()); + return true; // 'true' leave connection open + } + + return true; +} + +//////////////////////////////////////////////////////////////////////////////// +// ws2_command +// + +bool +ws2_command(struct mg_connection* conn, + struct websock_session* pSession, + std::string& strCmd, + json& jsonObj) +{ + // Check pointer + if (NULL == conn) { + return false; + } + if (NULL == pSession) { + return false; + } + + if (__VSCP_DEBUG_WEBSOCKET) { + syslog(LOG_DEBUG, "[Websocket ws2] Command = %s", strCmd.c_str()); + } + + // Get arguments + std::map argmap; + try { + for (auto it = jsonObj.begin(); it != jsonObj.end(); ++it) { + if (it.value().is_string()) { + argmap[it.key()] = it.value(); + } + } + } + catch (...) { + std::string str = vscp_str_format(WS2_NEGATIVE_RESPONSE, + "SETFILTER", + (int)WEBSOCK_ERROR_PARSE_FORMAT, + WEBSOCK_STR_ERROR_PARSE_FORMAT); + mg_websocket_write(conn, + MG_WEBSOCKET_OPCODE_TEXT, + (const char*)str.c_str(), + str.length()); + + syslog(LOG_ERR, + "[Websocket ws2] SETFILTER parse error = %s", + jsonObj.dump().c_str()); + + return false; + } + + // ------------------------------------------------------------------------ + // NOOP + //------------------------------------------------------------------------- + + if ("NOOP" == strCmd) { + + std::string str = + vscp_str_format(WS2_POSITIVE_RESPONSE, "NOOP", "null"); + mg_websocket_write(conn, + MG_WEBSOCKET_OPCODE_TEXT, + str.c_str(), + str.length()); + } + + // ------------------------------------------------------------------------ + // CHALLENGE + //------------------------------------------------------------------------- + + else if ("CHALLENGE" == strCmd) { + + // Send authentication challenge + if ((NULL == pSession->m_pClientItem) || + !pSession->m_pClientItem->bAuthenticated) { + + // Start authentication + std::string strSessionId = + vscp_str_format("{\"sid\": \"%s\"}", pSession->m_sid); + std::string str = + vscp_str_format(WS2_POSITIVE_RESPONSE, "CHALLENGE", strSessionId); + mg_websocket_write(conn, + MG_WEBSOCKET_OPCODE_TEXT, + (const char*)str.c_str(), + str.length()); + } + } + + // ------------------------------------------------------------------------ + // AUTH + //------------------------------------------------------------------------- + + // AUTH;iv;aes128 + else if ("AUTH" == strCmd) { + + std::string str; + std::string strUser; + std::string strIV = argmap["iv"]; + std::string strCrypto = argmap["crypto"]; + if (websock_authentication(conn, pSession, strIV, strCrypto)) { + std::string userSettings; + pSession->m_pClientItem->m_pUserItem->getAsString(userSettings); + str = vscp_str_format(WS2_POSITIVE_RESPONSE, "AUTH", "null"); + mg_websocket_write(conn, + MG_WEBSOCKET_OPCODE_TEXT, + (const char*)str.c_str(), + str.length()); + } + else { + + str = vscp_str_format(WS2_NEGATIVE_RESPONSE, + "AUTH", + (int)WEBSOCK_ERROR_NOT_AUTHORISED, + WEBSOCK_STR_ERROR_NOT_AUTHORISED); + mg_websocket_write(conn, + MG_WEBSOCKET_OPCODE_TEXT, + (const char*)str.c_str(), + str.length()); + pSession->m_pClientItem->bAuthenticated = false; // Authenticated + } + } + + // ------------------------------------------------------------------------ + // OPEN + //------------------------------------------------------------------------- + + else if ("OPEN" == strCmd) { + + // Must be authorised to do this + if ((NULL == pSession->m_pClientItem) || + !pSession->m_pClientItem->bAuthenticated) { + + std::string str = vscp_str_format(WS2_NEGATIVE_RESPONSE, + "OPEN", + (int)WEBSOCK_ERROR_NOT_AUTHORISED, + WEBSOCK_STR_ERROR_NOT_AUTHORISED); + mg_websocket_write(conn, + MG_WEBSOCKET_OPCODE_TEXT, + (const char*)str.c_str(), + str.length()); + + return false; // We still leave channel open + } + + pSession->m_pClientItem->m_bOpen = true; + std::string str = + vscp_str_format(WS2_POSITIVE_RESPONSE, "OPEN", "null"); + mg_websocket_write(conn, + MG_WEBSOCKET_OPCODE_TEXT, + (const char*)str.c_str(), + str.length()); + } + + // ------------------------------------------------------------------------ + // CLOSE + //------------------------------------------------------------------------- + + else if ("CLOSE" == strCmd) { + pSession->m_pClientItem->m_bOpen = false; + std::string str = + vscp_str_format(WS2_POSITIVE_RESPONSE, "CLOSE", "null"); + mg_websocket_write(conn, + MG_WEBSOCKET_OPCODE_TEXT, + (const char*)str.c_str(), + str.length()); + } + + // ------------------------------------------------------------------------ + // SETFILTER/SF + //------------------------------------------------------------------------- + + else if (("SETFILTER" == strCmd) || ("SF" == strCmd)) { + + std::string strFilter; + unsigned char ifGUID[16]; + memset(ifGUID, 0, 16); + + // Must be authorized to do this + if ((NULL == pSession->m_pClientItem) || + !pSession->m_pClientItem->bAuthenticated) { + + std::string str = vscp_str_format(WS2_NEGATIVE_RESPONSE, + strCmd.c_str(), + (int)WEBSOCK_ERROR_NOT_AUTHORISED, + WEBSOCK_STR_ERROR_NOT_AUTHORISED); + mg_websocket_write(conn, + MG_WEBSOCKET_OPCODE_TEXT, + (const char*)str.c_str(), + str.length()); + + syslog( + LOG_ERR, + "[Websocket w2] User/host is not authorised to set a filter."); + + return false; // We still leave channel open + } + + // Check privilege + if (!(pSession->m_pClientItem->m_pUserItem->getUserRights() & + VSCP_USER_RIGHT_ALLOW_SETFILTER)) { + + std::string str = + vscp_str_format(WS2_NEGATIVE_RESPONSE, + strCmd.c_str(), + (int)WEBSOCK_ERROR_NOT_ALLOWED_TO_DO_THAT, + WEBSOCK_STR_ERROR_NOT_ALLOWED_TO_DO_THAT); + mg_websocket_write(conn, + MG_WEBSOCKET_OPCODE_TEXT, + (const char*)str.c_str(), + str.length()); + + syslog(LOG_ERR, + "[Websocket w2] User [%s] is not " + "allowed to set a filter.\n", + pSession->m_pClientItem->m_pUserItem->getUserName().c_str()); + return false; // We still leave channel open + } + + // Get filter + if (!argmap.empty()) { + + strFilter = jsonObj.dump(); + + pthread_mutex_lock( + &pSession->m_pClientItem->m_mutexClientInputQueue); + if (!vscp_readFilterMaskFromJSON(&pSession->m_pClientItem->m_filter, + strFilter)) { + + std::string str = + vscp_str_format(WS2_NEGATIVE_RESPONSE, + strCmd.c_str(), + (int)WEBSOCK_ERROR_SYNTAX_ERROR, + WEBSOCK_STR_ERROR_SYNTAX_ERROR); + mg_websocket_write(conn, + MG_WEBSOCKET_OPCODE_TEXT, + (const char*)str.c_str(), + str.length()); + + syslog(LOG_ERR, + "[Websocket w2] Set filter syntax error. [%s]", + strFilter.c_str()); + + pthread_mutex_unlock( + &pSession->m_pClientItem->m_mutexClientInputQueue); + return false; + } + + pthread_mutex_unlock( + &pSession->m_pClientItem->m_mutexClientInputQueue); + } + else { + + std::string str = vscp_str_format(WS2_NEGATIVE_RESPONSE, + strCmd.c_str(), + (int)WEBSOCK_ERROR_SYNTAX_ERROR, + WEBSOCK_STR_ERROR_SYNTAX_ERROR); + mg_websocket_write(conn, + MG_WEBSOCKET_OPCODE_TEXT, + (const char*)str.c_str(), + str.length()); + + syslog(LOG_ERR, + "[Websocket w2] Set filter syntax error. [%s]", + strFilter.c_str()); + + return false; + } + + // Positive response + std::string str = + vscp_str_format(WS2_POSITIVE_RESPONSE, strCmd.c_str(), "null"); + mg_websocket_write(conn, + MG_WEBSOCKET_OPCODE_TEXT, + (const char*)str.c_str(), + str.length()); + } + + // ------------------------------------------------------------------------ + // CLRQ/CLRQUEUE + //------------------------------------------------------------------------- + + // Clear the event queue + else if (("CLRQUEUE" == strCmd) || ("CLRQ" == strCmd)) { + + // Must be authorised to do this + if ((NULL == pSession->m_pClientItem) || + !pSession->m_pClientItem->bAuthenticated) { + + std::string str = vscp_str_format(WS2_NEGATIVE_RESPONSE, + strCmd.c_str(), + (int)WEBSOCK_ERROR_NOT_AUTHORISED, + WEBSOCK_STR_ERROR_NOT_AUTHORISED); + mg_websocket_write(conn, + MG_WEBSOCKET_OPCODE_TEXT, + (const char*)str.c_str(), + str.length()); + + syslog( + LOG_ERR, + "[Websocket w2] User/host is not authorised to clear the queue."); + + return false; // We still leave channel open + } + + std::deque::iterator it; + pthread_mutex_lock(&pSession->m_pClientItem->m_mutexClientInputQueue); + + for (it = pSession->m_pClientItem->m_clientInputQueue.begin(); + it != pSession->m_pClientItem->m_clientInputQueue.end(); + ++it) { + vscpEvent* pEvent = + pSession->m_pClientItem->m_clientInputQueue.front(); + pSession->m_pClientItem->m_clientInputQueue.pop_front(); + vscp_deleteEvent_v2(&pEvent); + } + + pSession->m_pClientItem->m_clientInputQueue.clear(); + pthread_mutex_unlock(&pSession->m_pClientItem->m_mutexClientInputQueue); + + std::string str = + vscp_str_format(WS2_POSITIVE_RESPONSE, strCmd.c_str(), "null"); + mg_websocket_write(conn, + MG_WEBSOCKET_OPCODE_TEXT, + (const char*)str.c_str(), + str.length()); + } + + // ------------------------------------------------------------------------ + // VERSION + //------------------------------------------------------------------------- + + else if (("VERSION" == strCmd) || ("VER" == strCmd)) { + + // std::string strvalue; + std::string strResult; + strResult = vscp_str_format("{ \"version\" : \"%d.%d.%d-%d\" }", + VSCPD_MAJOR_VERSION, + VSCPD_MINOR_VERSION, + VSCPD_RELEASE_VERSION, + VSCPD_BUILD_VERSION); + // Positive reply + std::string str = vscp_str_format(WS2_POSITIVE_RESPONSE, + strCmd.c_str(), + strResult.c_str()); + mg_websocket_write(conn, + MG_WEBSOCKET_OPCODE_TEXT, + (const char*)str.c_str(), + str.length()); + } + + // ------------------------------------------------------------------------ + // COPYRIGHT + //------------------------------------------------------------------------- + + else if ("COPYRIGHT" == strCmd) { + + std::string strvalue; + + std::string strResult = ("{ \"copyright\" : \""); + strResult += VSCPD_COPYRIGHT; + strResult += "\" }"; + + // Positive reply + std::string str = vscp_str_format(WS2_POSITIVE_RESPONSE, + strCmd.c_str(), + strResult.c_str()); + mg_websocket_write(conn, + MG_WEBSOCKET_OPCODE_TEXT, + (const char*)str.c_str(), + str.length()); + } + else { + std::string str = vscp_str_format(WS2_NEGATIVE_RESPONSE, + strCmd, + (int)WEBSOCK_ERROR_UNKNOWN_COMMAND, + WEBSOCK_STR_ERROR_UNKNOWN_COMMAND); + syslog(LOG_ERR, "[Websocket w2] Unknown command [%s].", strCmd.c_str()); + + return false; + } + + return true; +} + +//////////////////////////////////////////////////////////////////////////////// +// ws2_xcommand +// + +void +ws2_xcommand(struct mg_connection* conn, + struct websock_session* pSession, + std::string& strCmd) +{ + std::string str; // Worker string + std::string strTok; + + // Check pointer + if (NULL == conn) + return; + if (NULL == pSession) + return; + + if (__VSCP_DEBUG_WEBSOCKET) { + syslog(LOG_ERR, "[Websocket ws2] Command = %s", strCmd.c_str()); + } + + std::deque tokens; + vscp_split(tokens, strCmd, ";"); + + // Get command + if (!tokens.empty()) { + strTok = tokens.front(); + tokens.pop_front(); + vscp_trim(strTok); + vscp_makeUpper(strTok); + } + else { + std::string str = vscp_str_format(("-;%d;%s"), + (int)WEBSOCK_ERROR_SYNTAX_ERROR, + WEBSOCK_STR_ERROR_SYNTAX_ERROR); + mg_websocket_write(conn, + MG_WEBSOCKET_OPCODE_TEXT, + (const char*)str.c_str(), + str.length()); + return; + } + + // ------------------------------------------------------------------------ + // NOOP + //------------------------------------------------------------------------- + + if (vscp_startsWith(strTok, "NOOP")) { + mg_websocket_write(conn, MG_WEBSOCKET_OPCODE_TEXT, "+;NOOP", 6); + } +} \ No newline at end of file diff --git a/src/vscp/vscphubd/win32/vscphubd.cpp b/src/vscp/vscphubd/win32/vscphubd.cpp new file mode 100644 index 000000000..a87976f6e --- /dev/null +++ b/src/vscp/vscphubd/win32/vscphubd.cpp @@ -0,0 +1,262 @@ +// vscpd.cpp : Defines the class behaviors for the application. +// +// 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. +// canald.cpp +// +// This file is part of the CANAL (https://www.vscp.org) +// +// Copyright (C) 2000-2017 Ake Hedman, the VSCP project, +// +// This file 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 file see the file COPYING. If not, write to +// the Free Software Foundation, 59 Temple Place - Suite 330, +// Boston, MA 02111-1307, USA. +// + +#ifdef WIN32 +#include +#endif + +#include +#include + +#define XSTR(x) STR(x) +//#define STR(x) #x + +#ifdef WIN32 +#include +#include +#endif + +#include + +#include +#include +#include + +// The global control object +CControlObject *gpobj; + +// static const wxCmdLineEntryDesc cmdLineDesc[] = { +// { +// wxCMD_LINE_OPTION, +// _("c"), +// _("configpath"), +// _("Path to configuration file"), +// wxCMD_LINE_VAL_STRING, +// wxCMD_LINE_PARAM_OPTIONAL +// }, +// { +// wxCMD_LINE_OPTION, +// _("d"), +// _("debuglevel"), +// _("Debug Level."), +// wxCMD_LINE_VAL_NUMBER, +// wxCMD_LINE_PARAM_OPTIONAL +// }, +// { wxCMD_LINE_SWITCH, _("h"), _("help"), _("Shows this message"), wxCMD_LINE_VAL_NONE, wxCMD_LINE_PARAM_OPTIONAL | wxCMD_LINE_OPTION_HELP }, +// { wxCMD_LINE_SWITCH, _("v"), _("verbose"), _("Vebose mode"), wxCMD_LINE_VAL_NONE, wxCMD_LINE_PARAM_OPTIONAL }, +// { wxCMD_LINE_SWITCH, _("w"), _("hide"), _("Hide debug window"), wxCMD_LINE_VAL_NONE, wxCMD_LINE_PARAM_OPTIONAL }, +// { wxCMD_LINE_SWITCH, _("g"), _("gnu"), _("Copyleft"), wxCMD_LINE_VAL_NONE, wxCMD_LINE_PARAM_OPTIONAL }, +// { wxCMD_LINE_NONE } +// }; + + +void doabort(int sig) { + printf("Wake up call ... !!! - Catched signal: %d ... !!\n", sig); + (void) signal(SIGINT, SIG_DFL); +} + +int main(int argc, char **argv) +{ + (void) signal( SIGABRT, doabort ); + (void) signal( SIGTERM, doabort ); + (void) signal( SIGINT, doabort ); + +#if defined( _MSC_VER ) && _MSC_VER < 1600 +#pragma message ( "Visual studio version = " STR(_MSC_VER) ) +#endif + + // wxChar **wxArgv = new wxChar *[argc + 1]; + + // { + // int n; + + // for (n = 0; n < argc; n++ ) + // { + // wxMB2WXbuf warg = wxConvertMB2WX( argv[n] ); + // wxArgv[ n ] = wxStrdup( warg ); + // } + + // wxArgv[n] = NULL; + // } + + + // /* Clean up */ + // { + // for ( int n = 0; n < argc; n++ ) { + // free(wxArgv[n]); + // } + + // delete [] wxArgv; + // } + + + // return -1; + // } + + bool bHideWindow = false; + std::string strCfgFile; + std::string rootFolder; + CControlObject ctrlobj; + + rootFolder = ""; // wxStandardPaths::Get().GetUserDataDir(); + strCfgFile = ""; // wxStandardPaths::Get().GetConfigDir(); + +#ifdef WIN32 + // The following needed because wx 3.1 add "vscpd" to standardpath + size_t pos; + if (std::string::npos != (pos = strCfgFile.find("vscpd"))) { + strCfgFile = strCfgFile.substr( strCfgFile.length() - 5 ); + strCfgFile += "vscp"; + } + else if (std::string::npos != (pos = strCfgFile.find("vscpd32"))) { + strCfgFile = strCfgFile.substr( strCfgFile.length() - 7 ); + strCfgFile += "vscp"; + } + strCfgFile += "\\vscpd.json"; +#else + strCfgFile += "/vscp/vscpd.json"; +#endif + + // wxCmdLineParser *pparser = new wxCmdLineParser( cmdLineDesc, argc, argv ); + + // if ( NULL != pparser ) { + + // std::string wxstr; + // if ( pparser->Parse(false) > 0 ) { + // printf("\n\nUsage for vscpd.exe\n"); + // printf("-------------------------\n"); + // char idx = 0; + // while( cmdLineDesc[idx].kind != wxCMD_LINE_NONE ) { + // wxPrintf( _("%s or %s \t %s \n"), + // cmdLineDesc[idx].shortName, + // cmdLineDesc[idx].longName, + // cmdLineDesc[idx].description ); + // idx++; + // } + // } + + + // // * * * Verbose * * * + // if ( pparser->Found( wxT("verbose") ) ) { + // std::string wxstr; + // wxstr.Printf(wxT("Verbose mode set\n")); + // } + + // // * * * Hide debug window * * * + // if ( pparser->Found( wxT("hide") ) ) { + // bHideWindow = true; + // { + // std::string wxstr; + // wxstr.Printf(wxT("Debug window hidden\n")); + // } + // } + + // // * * * Copyleft * * * + // if ( pparser->Found( wxT("gnu") ) ) { + // std::string wxstr; + // wxstr.Printf(wxT("Verbose mode set\n")); + // } + + // // * * * Path to configuration file * * * + // if ( pparser->Found( _T("configpath"), &wxstr ) ) { + // strCfgFile = wxstr; + // } + + // delete pparser; + + // } + + if ( bHideWindow ) { + + TCHAR savetitle[1024]; + + // Save the title + GetConsoleTitle( savetitle, 1024 ); + + // Set dummy title + SetConsoleTitle("_____VSCP Daemon console window____" ); + + // Find the window + HWND hwnd = FindWindow( NULL, "_____VSCP Daemon console window____"); + + if ( NULL != hwnd ) { + ShowWindow( hwnd, SW_HIDE ); + } + + // Restore old title + SetConsoleTitle( savetitle ); + } + + if ( !ctrlobj.init( strCfgFile, rootFolder ) ) { + // ctrlobj.logMsg("Unable to initialize the vscpd application.", + // DAEMON_LOGMSG_NORMAL ); + +#if wxUSE_UNICODE + /* Clean up */ + { + for ( int n = 0; n < argc; n++ ) { + free(wxArgv[n]); + } + + delete [] wxArgv; + } +#endif // wxUSE_UNICODE + + //::wxUninitialize(); + return FALSE; + } + + if ( !ctrlobj.run() ) { + // ctrlobj.logMsg("Unable to start the vscpd application.", + // DAEMON_LOGMSG_NORMAL ); + } + + + if ( !ctrlobj.cleanup() ) { + // ctrlobj.logMsg( _("Unable to clean up the vscpd application."), + // DAEMON_LOGMSG_NORMAL ); + } + +#if wxUSE_UNICODE + { + for ( int n = 0; n < argc; n++ ) { + free(wxArgv[n]); + } + + delete [] wxArgv; + } +#endif // wxUSE_UNICODE + + //::wxUninitialize(); + return TRUE; + +} + + + + + + + + + diff --git a/src/vscp/vscphubd/win32/vscphubd.h b/src/vscp/vscphubd/win32/vscphubd.h new file mode 100644 index 000000000..af57cca29 --- /dev/null +++ b/src/vscp/vscphubd/win32/vscphubd.h @@ -0,0 +1,59 @@ +/////////////////////////////////////////////////////////////////////////////// +// +// Define a new application type +// +// Copyright (C) 2002-2018 Ake Hedman info@vscp.org +// +// This software is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; either +// version 2 of the License, or (at your option) any later version. +// +// This library 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 +// Library General Public License for more details. +// +// You should have received a copy of the GNU Library General Public +// License along with this library; if not, write to the +// Free Software Foundation, Inc., 59 Temple Place - Suite 330, +// Boston, MA 02111-1307, USA. +// + +#if !defined(VSCPDAEMON_H__D7E6521D_8D1A_49E9_9F38_36244F430B38__INCLUDED_) +#define VSCPDAEMON_H__D7E6521D_8D1A_49E9_9F38_36244F430B38__INCLUDED_ + + + +#include + + + +/* + +class CanalApp: public wxApp +{ + +public: + + + bool OnInit( void ); + int OnRun( void ); + int OnExit( void ); + + /// The object encapsulating control functionality + //CControlObject m_ctrlObj; + +public: + + CControlObject m_obj; + + bool m_bIsRunning; + +private: + +}; + +*/ + +#endif // VSCPDAEMON_H__D7E6521D_8D1A_49E9_9F38_36244F430B38__INCLUDED_ \ No newline at end of file diff --git a/tests/vscpremotetcpip/alltest/CMakeLists.txt b/tests/vscpremotetcpip/alltest/CMakeLists.txt index aaf5294ee..6c746b72d 100644 --- a/tests/vscpremotetcpip/alltest/CMakeLists.txt +++ b/tests/vscpremotetcpip/alltest/CMakeLists.txt @@ -23,7 +23,12 @@ set(CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/cmake/modules) message(STATUS "Build dir: " ${PROJECT_SOURCE_DIR}) -find_package(GTest CONFIG REQUIRED) +# Locate GTest +find_package(GTest REQUIRED) +include_directories(${GTEST_INCLUDE_DIRS}) + + find_package(spdlog REQUIRED) + find_package(OpenSSL REQUIRED) set(CMAKE_INCLUDE_CURRENT_DIR ON) @@ -64,9 +69,9 @@ add_executable(unittest ${PROJECT_SOURCE_DIR}/../../../src/common/crc8.c ${PROJECT_SOURCE_DIR}/../../../src/common/vscp_aes.h ${PROJECT_SOURCE_DIR}/../../../src/common/vscp_aes.c - ${PROJECT_SOURCE_DIR}/../../../src/common/fastpbkdf2.h - ${PROJECT_SOURCE_DIR}/../../../src/common/fastpbkdf2.c - ${PROJECT_SOURCE_DIR}/../../../src/common/third_party/nlohmann/json.hpp + ${PROJECT_SOURCE_DIR}/../../../third_party/fastpbkdf2/fastpbkdf2.h + ${PROJECT_SOURCE_DIR}/../../../third_party/fastpbkdf2/fastpbkdf2.c + ${PROJECT_SOURCE_DIR}/../../../third_party/nlohmann/include/nlohmann/json.hpp ) @@ -77,8 +82,8 @@ if (WIN32) target_include_directories(unittest PUBLIC ${PROJECT_SOURCE_DIR}/../../../src/vscp/common/ ${PROJECT_SOURCE_DIR}/../../../src/common/ - ${PROJECT_SOURCE_DIR}/../../../src/common/third_party - ${PROJECT_SOURCE_DIR}/../../../src/common/third_party/nlohmann + ${PROJECT_SOURCE_DIR}/../../../third_party/fastpbkdf2/ + ${PROJECT_SOURCE_DIR}/../../../third_party/nlohmann/include/ ${PROJECT_SOURCE_DIR}/../../../src/common/third_party/spdlog-1.9.2/include/ ${OPENSSL_INCLUDE_DIR} ${EXPAT_INCLUDE_DIRS} @@ -87,8 +92,8 @@ else() target_include_directories(unittest PUBLIC ${PROJECT_SOURCE_DIR}/../../../src/vscp/common/ ${PROJECT_SOURCE_DIR}/../../../src/common/ - ${PROJECT_SOURCE_DIR}/../../../src/common/third_party - ${PROJECT_SOURCE_DIR}/../../../src/common/third_party/nlohmann + ${PROJECT_SOURCE_DIR}/../../../third_party/fastpbkdf2/ + ${PROJECT_SOURCE_DIR}/../../../third_party/nlohmann/include/ ${PROJECT_SOURCE_DIR}/../../../src/common/third_party/spdlog-1.9.2/include/ ${OPENSSL_INCLUDE_DIR} ${EXPAT_INCLUDE_DIRS} @@ -101,8 +106,8 @@ if (WIN32) OpenSSL::SSL OpenSSL::Crypto ${OPENSSL_LIBRARIES} ${EXPAT_LIBRARIES} - GTest::gtest - GTest::gmock + ${GTEST_LIBRARIES} + spdlog::spdlog ) else() target_link_libraries(unittest PRIVATE @@ -110,8 +115,8 @@ else() OpenSSL::SSL OpenSSL::Crypto ${OPENSSL_LIBRARIES} ${EXPAT_LIBRARIES} - GTest::gtest - GTest::gmock + ${GTEST_LIBRARIES} + spdlog::spdlog ) endif()