From 07fead823c33b229f4bf9787bb4ba34cf67fd65f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=96mer=20=C5=9Eiar=20Baysal?= Date: Tue, 5 Nov 2024 09:13:36 +0000 Subject: [PATCH 01/57] initial network support --- src/Makefile | 6 +- src/Rules.mk | 8 +- src/config.cpp | 15 + src/config.h | 8 + src/kernel.cpp | 11 +- src/kernel.h | 2 +- src/minidexed.cpp | 141 ++++++- src/minidexed.h | 21 +- src/net/applemidi.cpp | 874 ++++++++++++++++++++++++++++++++++++++++++ src/net/applemidi.h | 111 ++++++ src/net/byteorder.h | 42 ++ src/net/udpmidi.cpp | 89 +++++ src/net/udpmidi.h | 57 +++ src/rtpmididevice.cpp | 80 ++++ src/rtpmididevice.h | 69 ++++ 15 files changed, 1524 insertions(+), 10 deletions(-) create mode 100644 src/net/applemidi.cpp create mode 100644 src/net/applemidi.h create mode 100644 src/net/byteorder.h create mode 100644 src/net/udpmidi.cpp create mode 100644 src/net/udpmidi.h create mode 100644 src/rtpmididevice.cpp create mode 100644 src/rtpmididevice.h diff --git a/src/Makefile b/src/Makefile index 540ae684..f078fad0 100644 --- a/src/Makefile +++ b/src/Makefile @@ -5,11 +5,13 @@ CIRCLE_STDLIB_DIR = ../circle-stdlib SYNTH_DEXED_DIR = ../Synth_Dexed/src CMSIS_DIR = ../CMSIS_5/CMSIS +NET_DIR = ./net OBJS = main.o kernel.o minidexed.o config.o userinterface.o uimenu.o \ - mididevice.o midikeyboard.o serialmididevice.o pckeyboard.o \ + mididevice.o rtpmididevice.o midikeyboard.o serialmididevice.o pckeyboard.o \ sysexfileloader.o performanceconfig.o perftimer.o \ - effect_compressor.o effect_platervbstereo.o uibuttons.o midipin.o + effect_compressor.o effect_platervbstereo.o uibuttons.o midipin.o \ + net/applemidi.o net/udpmidi.o OPTIMIZE = -O3 diff --git a/src/Rules.mk b/src/Rules.mk index 2ebc1323..64618331 100644 --- a/src/Rules.mk +++ b/src/Rules.mk @@ -11,7 +11,8 @@ include $(CIRCLEHOME)/Rules.mk INCLUDE += \ -I $(CIRCLE_STDLIB_DIR)/include \ - -I $(NEWLIBDIR)/include + -I $(NEWLIBDIR)/include \ + -I $(NET_DIR) LIBS += \ $(NEWLIBDIR)/lib/libm.a \ @@ -28,6 +29,9 @@ LIBS += \ $(CIRCLEHOME)/addon/fatfs/libfatfs.a \ $(CIRCLEHOME)/lib/fs/libfs.a \ $(CIRCLEHOME)/lib/sched/libsched.a \ - $(CIRCLEHOME)/lib/libcircle.a + $(CIRCLEHOME)/lib/libcircle.a \ + $(CIRCLEHOME)/addon/wlan/hostap/wpa_supplicant/libwpa_supplicant.a \ + $(CIRCLEHOME)/addon/wlan/libwlan.a \ + $(CIRCLEHOME)/lib/net/libnet.a -include $(DEPS) diff --git a/src/config.cpp b/src/config.cpp index 526d8ad7..ac6a2e49 100644 --- a/src/config.cpp +++ b/src/config.cpp @@ -152,6 +152,10 @@ void CConfig::Load (void) m_bProfileEnabled = m_Properties.GetNumber ("ProfileEnabled", 0) != 0; m_bPerformanceSelectToLoad = m_Properties.GetNumber ("PerformanceSelectToLoad", 1) != 0; m_bPerformanceSelectChannel = m_Properties.GetNumber ("PerformanceSelectChannel", 0); + + // Network + m_bNetworkEnabled = m_Properties.GetNumber ("NetworkEnabled", 0) != 0; + m_NetworkType = m_Properties.GetString ("NetworkType", ""); } bool CConfig::GetUSBGadgetMode (void) const @@ -503,3 +507,14 @@ unsigned CConfig::GetPerformanceSelectChannel (void) const { return m_bPerformanceSelectChannel; } + +// Network +bool CConfig::GetNetworkEnabled (void) const +{ + return m_bNetworkEnabled; +} + +const char *CConfig::GetNetworkType (void) const +{ + return m_NetworkType.c_str(); +} \ No newline at end of file diff --git a/src/config.h b/src/config.h index f83c177b..30ee5ed9 100644 --- a/src/config.h +++ b/src/config.h @@ -168,6 +168,10 @@ class CConfig // Configuration for MiniDexed bool GetPerformanceSelectToLoad (void) const; unsigned GetPerformanceSelectChannel (void) const; + // Network + bool GetNetworkEnabled (void) const; + const char *GetNetworkType (void) const; + private: CPropertiesFatFsFile m_Properties; @@ -252,6 +256,10 @@ class CConfig // Configuration for MiniDexed bool m_bProfileEnabled; bool m_bPerformanceSelectToLoad; unsigned m_bPerformanceSelectChannel; + + // Network + bool m_bNetworkEnabled; + std::string m_NetworkType; }; #endif diff --git a/src/kernel.cpp b/src/kernel.cpp index c06c3860..5c7a7a93 100644 --- a/src/kernel.cpp +++ b/src/kernel.cpp @@ -24,12 +24,17 @@ #include #include "usbminidexedmidigadget.h" +#define NET_DEVICE_TYPE NetDeviceTypeWLAN // or: NetDeviceTypeWLAN + LOGMODULE ("kernel"); CKernel *CKernel::s_pThis = 0; CKernel::CKernel (void) -: CStdlibAppStdio ("minidexed"), +: + //CStdlibAppStdio ("minidexed"), + CStdlibAppNetwork ("minidexed", CSTDLIBAPP_DEFAULT_PARTITION, + 0, 0, 0, 0, NET_DEVICE_TYPE), m_Config (&mFileSystem), m_GPIOManager (&mInterrupt), m_I2CMaster (CMachineInfo::Get ()->GetDevice (DeviceI2CMaster), TRUE), @@ -47,7 +52,7 @@ CKernel::~CKernel(void) bool CKernel::Initialize (void) { - if (!CStdlibAppStdio::Initialize ()) + if (!CStdlibAppNetwork::Initialize ()) { return FALSE; } @@ -109,7 +114,7 @@ CStdlibApp::TShutdownMode CKernel::Run (void) mScreen.Update (); } - m_CPUThrottle.Update (); + m_CPUThrottle.Update (); } return ShutdownHalt; diff --git a/src/kernel.h b/src/kernel.h index 7d2f346a..31757cdc 100644 --- a/src/kernel.h +++ b/src/kernel.h @@ -35,7 +35,7 @@ enum TShutdownMode ShutdownReboot }; -class CKernel : public CStdlibAppStdio +class CKernel : public CStdlibAppNetwork { public: CKernel (void); diff --git a/src/minidexed.cpp b/src/minidexed.cpp index 71b72476..d736ed3e 100644 --- a/src/minidexed.cpp +++ b/src/minidexed.cpp @@ -27,6 +27,19 @@ #include #include #include +#include +#include +#include "circle_stdlib_app.h" +//#include "mididevice.h" + +/* +#define DRIVE "SD:" +#define FIRMWARE_PATH DRIVE "/firmware/" // firmware files must be provided here +#define CONFIG_FILE DRIVE "/wpa_supplicant.conf" + +const char WLANFirmwarePath[] = "SD:firmware/"; +const char WLANConfigFile[] = "SD:wpa_supplicant.conf"; +*/ LOGMODULE ("minidexed"); @@ -54,7 +67,17 @@ CMiniDexed::CMiniDexed (CConfig *pConfig, CInterruptSystem *pInterrupt, m_bSavePerformanceNewFile (false), m_bSetNewPerformance (false), m_bDeletePerformance (false), - m_bLoadPerformanceBusy(false) + m_bLoadPerformanceBusy(false), + /* + m_pNet(nullptr), + m_pNetDevice(nullptr), + m_WLAN(WLANFirmwarePath), + m_WPASupplicant(WLANConfigFile), + m_bNetworkReady(false), + */ + //CNetSubSystem* const pNet = CNetSubSystem::Get(); + m_bNetworkReady(false), + m_RTPMIDI (this, pConfig, &m_UI) { assert (m_pConfig); @@ -267,7 +290,14 @@ bool CMiniDexed::Initialize (void) return false; } #endif + //InitNetwork(); + UpdateNetwork(); + //CMIDIDevice->InitializeRTP(); + if (m_RTPMIDI.Initialize ()) + { + LOGNOTE ("RTP MIDI interface enabled"); + } return true; } @@ -325,6 +355,7 @@ void CMiniDexed::Process (bool bPlugAndPlayUpdated) { m_GetChunkTimer.Dump (); } + } #ifdef ARM_ALLOW_MULTI_CORE @@ -1802,3 +1833,111 @@ unsigned CMiniDexed::getModController (unsigned controller, unsigned parameter, } } + +void CMiniDexed::UpdateNetwork() +/*{ + CNetSubSystem* const pNet = CNetSubSystem::Get(); + if (!m_bNetworkReady){ + + m_bNetworkReady = true; + CString IPString; + pNet->GetConfig()->GetIPAddress()->Format(&IPString); + LOGNOTE("Network up and running at: %s", static_cast(IPString)); + } + if (!pNet) + return; + +}*/ + +{ + CNetSubSystem* const pNet = CNetSubSystem::Get(); + if (!pNet) + return; + + bool bNetIsRunning = pNet->IsRunning(); + //bNetIsRunning &= m_WPASupplicant.IsConnected(); + + if (!m_bNetworkReady) + { + m_bNetworkReady = true; + + CString IPString; + pNet->GetConfig()->GetIPAddress()->Format(&IPString); + + LOGNOTE("Network up and running at: %s", static_cast(IPString)); + } + else if (m_bNetworkReady && !bNetIsRunning) + { + m_bNetworkReady = false; + LOGNOTE("Network disconnected."); + + } +} +/* +void CMiniDexed::UpdateNetwork() +{ + if (!m_pNet) + return; + + bool bNetIsRunning = m_pNet->IsRunning(); + if (strcmp(m_pConfig->GetNetworkType(), "ethernet") == 0) + bNetIsRunning &= m_pNetDevice->IsLinkUp(); + else if (strcmp(m_pConfig->GetNetworkType(), "wifi") == 0) + bNetIsRunning &= m_WPASupplicant.IsConnected(); + + if (!m_bNetworkReady && bNetIsRunning) + { + m_bNetworkReady = true; + + CString IPString; + m_pNet->GetConfig()->GetIPAddress()->Format(&IPString); + + LOGNOTE("Network up and running at: %s", static_cast(IPString)); + } + else if (m_bNetworkReady && !bNetIsRunning) + { + m_bNetworkReady = false; + LOGNOTE("Network disconnected."); + } +} + +bool CMiniDexed::InitNetwork() +{ + assert(m_pNet == nullptr); + + TNetDeviceType NetDeviceType = NetDeviceTypeWLAN; + + if (m_pConfig->GetNetworkEnabled () && (strcmp(m_pConfig->GetNetworkType(), "wifi") == 0)) + { + LOGNOTE("Initializing Wi-Fi"); + + if (m_WLAN.Initialize() && m_WPASupplicant.Initialize()) + { + LOGNOTE("wlan and wpasupplicant initialized"); + //NetDeviceType = NetDeviceTypeWLAN; + + } + else + LOGERR("Failed to initialize Wi-Fi"); + } + else if (m_pConfig->GetNetworkEnabled () && (strcmp(m_pConfig->GetNetworkType(), "ethernet") == 0)) + { + LOGNOTE("Initializing Ethernet"); + //NetDeviceType = NetDeviceTypeEthernet; + } + + + LOGNOTE("creating network with wifi and dhcp"); + m_pNet = new CNetSubSystem(0, 0, 0, 0, "minidexed", NetDeviceType); + if (!m_pNet->Initialize(true)) + { + LOGERR("Failed to initialize network subsystem"); + delete m_pNet; + m_pNet = nullptr; + } + m_pNetDevice = CNetDevice::GetNetDevice(NetDeviceType); + + + return m_pNet != nullptr; +} +*/ \ No newline at end of file diff --git a/src/minidexed.h b/src/minidexed.h index 1aa30966..65d4034d 100644 --- a/src/minidexed.h +++ b/src/minidexed.h @@ -43,6 +43,10 @@ #include "effect_mixer.hpp" #include "effect_platervbstereo.h" #include "effect_compressor.h" +//#include +//#include +//#include +#include "rtpmididevice.h" class CMiniDexed #ifdef ARM_ALLOW_MULTI_CORE @@ -60,7 +64,6 @@ class CMiniDexed #ifdef ARM_ALLOW_MULTI_CORE void Run (unsigned nCore); #endif - CSysExFileLoader *GetSysExFileLoader (void); void BankSelect (unsigned nBank, unsigned nTG); @@ -211,12 +214,15 @@ class CMiniDexed bool DoSavePerformance (void); void setMasterVolume (float32_t vol); + //bool InitNetwork(); + void UpdateNetwork(); private: int16_t ApplyNoteLimits (int16_t pitch, unsigned nTG); // returns < 0 to ignore note uint8_t m_uchOPMask[CConfig::ToneGenerators]; void LoadPerformanceParameters(void); void ProcessSound (void); + const char* GetNetworkDeviceShortName() const; #ifdef ARM_ALLOW_MULTI_CORE enum TCoreStatus @@ -309,6 +315,19 @@ class CMiniDexed unsigned m_nDeletePerformanceID; bool m_bLoadPerformanceBusy; bool m_bSaveAsDeault; + bool m_bNetworkReady; + //CWPASupplicant m_WPASupplicant; + // Networking + //CNetSubSystem &mNet; + /* + CNetSubSystem* m_pNet; + CNetDevice* m_pNetDevice; + CBcm4343Device m_WLAN; + CWPASupplicant m_WPASupplicant; + bool m_bNetworkReady; + CBcmRandomNumberGenerator m_Random; + */ + CRTPMIDIDevice m_RTPMIDI; }; #endif diff --git a/src/net/applemidi.cpp b/src/net/applemidi.cpp new file mode 100644 index 00000000..b484a077 --- /dev/null +++ b/src/net/applemidi.cpp @@ -0,0 +1,874 @@ +// +// applemidi.cpp +// +// mt32-pi - A baremetal MIDI synthesizer for Raspberry Pi +// Copyright (C) 2020-2023 Dale Whinham +// +// This file is part of mt32-pi. +// +// mt32-pi 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 3 of the License, or (at your option) any later +// version. +// +// mt32-pi 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 +// mt32-pi. If not, see . +// + +#include +#include +#include +#include +#include +#include +#include + +#include "applemidi.h" +#include "byteorder.h" + +// #define APPLEMIDI_DEBUG + +LOGMODULE("applemidi"); + +constexpr u16 ControlPort = 5004; +constexpr u16 MIDIPort = ControlPort + 1; + +constexpr u16 AppleMIDISignature = 0xFFFF; +constexpr u8 AppleMIDIVersion = 2; + +constexpr u8 RTPMIDIPayloadType = 0x61; +constexpr u8 RTPMIDIVersion = 2; + +// Arbitrary value +constexpr size_t MaxNameLength = 256; + +// Timeout period for invitation (5 seconds in 100 microsecond units) +constexpr unsigned int InvitationTimeout = 5 * 10000; + +// Timeout period for sync packets (60 seconds in 100 microsecond units) +constexpr unsigned int SyncTimeout = 60 * 10000; + +// Receiver feedback packet frequency (1 second in 100 microsecond units) +constexpr unsigned int ReceiverFeedbackPeriod = 1 * 10000; + +constexpr u16 CommandWord(const char Command[2]) { return Command[0] << 8 | Command[1]; } + +enum TAppleMIDICommand : u16 +{ + Invitation = CommandWord("IN"), + InvitationAccepted = CommandWord("OK"), + InvitationRejected = CommandWord("NO"), + Sync = CommandWord("CK"), + ReceiverFeedback = CommandWord("RS"), + EndSession = CommandWord("BY"), +}; + +struct TAppleMIDISession +{ + u16 nSignature; + u16 nCommand; + u32 nVersion; + u32 nInitiatorToken; + u32 nSSRC; + char Name[MaxNameLength]; +} +PACKED; + +// The Name field is optional +constexpr size_t NamelessSessionPacketSize = sizeof(TAppleMIDISession) - sizeof(TAppleMIDISession::Name); + +struct TAppleMIDISync +{ + u16 nSignature; + u16 nCommand; + u32 nSSRC; + u8 nCount; + u8 Padding[3]; + u64 Timestamps[3]; +} +PACKED; + +struct TAppleMIDIReceiverFeedback +{ + u16 nSignature; + u16 nCommand; + u32 nSSRC; + u32 nSequence; +} +PACKED; + +struct TRTPMIDI +{ + u16 nFlags; + u16 nSequence; + u32 nTimestamp; + u32 nSSRC; +} +PACKED; + +u64 GetSyncClock() +{ + static const u64 nStartTime = CTimer::GetClockTicks(); + const u64 nMicrosSinceEpoch = CTimer::GetClockTicks(); + + // Units of 100 microseconds + return (nMicrosSinceEpoch - nStartTime ) / 100; +} + +bool ParseInvitationPacket(const u8* pBuffer, size_t nSize, TAppleMIDISession* pOutPacket) +{ + const TAppleMIDISession* const pInPacket = reinterpret_cast(pBuffer); + + if (nSize < NamelessSessionPacketSize) + return false; + + const u16 nSignature = ntohs(pInPacket->nSignature); + if (nSignature != AppleMIDISignature) + return false; + + const u16 nCommand = ntohs(pInPacket->nCommand); + if (nCommand != Invitation) + return false; + + const u32 nVersion = ntohl(pInPacket->nVersion); + if (nVersion != AppleMIDIVersion) + return false; + + pOutPacket->nSignature = nSignature; + pOutPacket->nCommand = nCommand; + pOutPacket->nVersion = nVersion; + pOutPacket->nInitiatorToken = ntohl(pInPacket->nInitiatorToken); + pOutPacket->nSSRC = ntohl(pInPacket->nSSRC); + + if (nSize > NamelessSessionPacketSize) + strncpy(pOutPacket->Name, pInPacket->Name, sizeof(pOutPacket->Name)); + else + strncpy(pOutPacket->Name, "", sizeof(pOutPacket->Name)); + + return true; +} + +bool ParseEndSessionPacket(const u8* pBuffer, size_t nSize, TAppleMIDISession* pOutPacket) +{ + const TAppleMIDISession* const pInPacket = reinterpret_cast(pBuffer); + + if (nSize < NamelessSessionPacketSize) + return false; + + const u16 nSignature = ntohs(pInPacket->nSignature); + if (nSignature != AppleMIDISignature) + return false; + + const u16 nCommand = ntohs(pInPacket->nCommand); + if (nCommand != EndSession) + return false; + + const u32 nVersion = ntohl(pInPacket->nVersion); + if (nVersion != AppleMIDIVersion) + return false; + + pOutPacket->nSignature = nSignature; + pOutPacket->nCommand = nCommand; + pOutPacket->nVersion = nVersion; + pOutPacket->nInitiatorToken = ntohl(pInPacket->nInitiatorToken); + pOutPacket->nSSRC = ntohl(pInPacket->nSSRC); + + return true; +} + +bool ParseSyncPacket(const u8* pBuffer, size_t nSize, TAppleMIDISync* pOutPacket) +{ + const TAppleMIDISync* const pInPacket = reinterpret_cast(pBuffer); + + if (nSize < sizeof(TAppleMIDISync)) + return false; + + const u32 nSignature = ntohs(pInPacket->nSignature); + if (nSignature != AppleMIDISignature) + return false; + + const u32 nCommand = ntohs(pInPacket->nCommand); + if (nCommand != Sync) + return false; + + pOutPacket->nSignature = nSignature; + pOutPacket->nCommand = nCommand; + pOutPacket->nSSRC = ntohl(pInPacket->nSSRC); + pOutPacket->nCount = pInPacket->nCount; + pOutPacket->Timestamps[0] = ntohll(pInPacket->Timestamps[0]); + pOutPacket->Timestamps[1] = ntohll(pInPacket->Timestamps[1]); + pOutPacket->Timestamps[2] = ntohll(pInPacket->Timestamps[2]); + + return true; +} + +u8 ParseMIDIDeltaTime(const u8* pBuffer) +{ + u8 nLength = 0; + u32 nDeltaTime = 0; + + while (nLength < 4) + { + nDeltaTime <<= 7; + nDeltaTime |= pBuffer[nLength] & 0x7F; + + // Upper bit not set; end of timestamp + if ((pBuffer[nLength++] & (1 << 7)) == 0) + break; + } + + return nLength; +} + +size_t ParseSysExCommand(const u8* pBuffer, size_t nSize, CAppleMIDIHandler* pHandler) +{ + size_t nBytesParsed = 1; + const u8 nHead = pBuffer[0]; + u8 nTail = 0; + + while (nBytesParsed < nSize && !(nTail == 0xF0 || nTail == 0xF7 || nTail == 0xF4)) + nTail = pBuffer[nBytesParsed++]; + + size_t nReceiveLength = nBytesParsed; + + // First segmented SysEx packet + if (nHead == 0xF0 && nTail == 0xF0) + { +#ifdef APPLEMIDI_DEBUG + LOGNOTE("Received segmented SysEx (first)"); +#endif + --nReceiveLength; + } + + // Middle segmented SysEx packet + else if (nHead == 0xF7 && nTail == 0xF0) + { +#ifdef APPLEMIDI_DEBUG + LOGNOTE("Received segmented SysEx (middle)"); +#endif + ++pBuffer; + nBytesParsed -= 2; + } + + // Last segmented SysEx packet + else if (nHead == 0xF7 && nTail == 0xF7) + { +#ifdef APPLEMIDI_DEBUG + LOGNOTE("Received segmented SysEx (last)"); +#endif + ++pBuffer; + --nReceiveLength; + } + + // Cancelled segmented SysEx packet + else if (nHead == 0xF7 && nTail == 0xF4) + { +#ifdef APPLEMIDI_DEBUG + LOGNOTE("Received cancelled SysEx"); +#endif + nReceiveLength = 1; + } + +#ifdef APPLEMIDI_DEBUG + else + { + LOGNOTE("Received complete SysEx"); + } +#endif + + pHandler->OnAppleMIDIDataReceived(pBuffer, nReceiveLength); + + return nBytesParsed; +} + +size_t ParseMIDICommand(const u8* pBuffer, size_t nSize, u8& nRunningStatus, CAppleMIDIHandler* pHandler) +{ + size_t nBytesParsed = 0; + u8 nByte = pBuffer[0]; + + // System Real-Time message - single byte, handle immediately + // Can appear anywhere in the stream, even in between status/data bytes + if (nByte >= 0xF8) + { + // Ignore undefined System Real-Time + if (nByte != 0xF9 && nByte != 0xFD) + pHandler->OnAppleMIDIDataReceived(&nByte, 1); + + return 1; + } + + // Is it a status byte? + if (nByte & 0x80) + { + // Update running status if non Real-Time System status + if (nByte < 0xF0) + nRunningStatus = nByte; + else + nRunningStatus = 0; + + ++nBytesParsed; + } + else + { + // First byte not a status byte and no running status - invalid + if (!nRunningStatus) + return 0; + + // Use running status + nByte = nRunningStatus; + } + + // Channel messages + if (nByte < 0xF0) + { + // How many data bytes? + switch (nByte & 0xF0) + { + case 0x80: // Note off + case 0x90: // Note on + case 0xA0: // Polyphonic key pressure/aftertouch + case 0xB0: // Control change + case 0xE0: // Pitch bend + nBytesParsed += 2; + break; + + case 0xC0: // Program change + case 0xD0: // Channel pressure/aftertouch + nBytesParsed += 1; + break; + } + + // Handle command + pHandler->OnAppleMIDIDataReceived(pBuffer, nBytesParsed); + return nBytesParsed; + } + + // System common commands + switch (nByte) + { + case 0xF0: // Start of System Exclusive + case 0xF7: // End of Exclusive + return ParseSysExCommand(pBuffer, nSize, pHandler); + + case 0xF1: // MIDI Time Code Quarter Frame + case 0xF3: // Song Select + ++nBytesParsed; + break; + + case 0xF2: // Song Position Pointer + nBytesParsed += 2; + break; + } + + pHandler->OnAppleMIDIDataReceived(pBuffer, nBytesParsed); + return nBytesParsed; +} + +bool ParseMIDICommandSection(const u8* pBuffer, size_t nSize, CAppleMIDIHandler* pHandler) +{ + // Must have at least a header byte and a single status byte + if (nSize < 2) + return false; + + size_t nMIDICommandsProcessed = 0; + size_t nBytesRemaining = nSize - 1; + u8 nRunningStatus = 0; + + const u8 nMIDIHeader = pBuffer[0]; + const u8* pMIDICommands = pBuffer + 1; + + // Lower 4 bits of the header is length + u16 nMIDICommandLength = nMIDIHeader & 0x0F; + + // If B flag is set, length value is 12 bits + if (nMIDIHeader & (1 << 7)) + { + nMIDICommandLength <<= 8; + nMIDICommandLength |= pMIDICommands[0]; + ++pMIDICommands; + --nBytesRemaining; + } + + if (nMIDICommandLength > nBytesRemaining) + { + LOGERR("Invalid MIDI command length"); + return false; + } + + // Begin decoding the command list + while (nMIDICommandLength) + { + // If Z flag is set, first list entry is a delta time + if (nMIDICommandsProcessed || nMIDIHeader & (1 << 5)) + { + const u8 nBytesParsed = ParseMIDIDeltaTime(pMIDICommands); + nMIDICommandLength -= nBytesParsed; + pMIDICommands += nBytesParsed; + } + + if (nMIDICommandLength) + { + const size_t nBytesParsed = ParseMIDICommand(pMIDICommands, nMIDICommandLength, nRunningStatus, pHandler); + nMIDICommandLength -= nBytesParsed; + pMIDICommands += nBytesParsed; + ++nMIDICommandsProcessed; + } + } + + return true; +} + +bool ParseMIDIPacket(const u8* pBuffer, size_t nSize, TRTPMIDI* pOutPacket, CAppleMIDIHandler* pHandler) +{ + assert(pHandler != nullptr); + + const TRTPMIDI* const pInPacket = reinterpret_cast(pBuffer); + const u16 nRTPFlags = ntohs(pInPacket->nFlags); + + // Check size (RTP-MIDI header plus MIDI command section header) + if (nSize < sizeof(TRTPMIDI) + 1) + return false; + + // Check version + if (((nRTPFlags >> 14) & 0x03) != RTPMIDIVersion) + return false; + + // Ensure no CSRC identifiers + if (((nRTPFlags >> 8) & 0x0F) != 0) + return false; + + // Check payload type + if ((nRTPFlags & 0xFF) != RTPMIDIPayloadType) + return false; + + pOutPacket->nFlags = nRTPFlags; + pOutPacket->nSequence = ntohs(pInPacket->nSequence); + pOutPacket->nTimestamp = ntohl(pInPacket->nTimestamp); + pOutPacket->nSSRC = ntohl(pInPacket->nSSRC); + + // RTP-MIDI variable-length header + const u8* const pMIDICommandSection = pBuffer + sizeof(TRTPMIDI); + size_t nRemaining = nSize - sizeof(TRTPMIDI); + return ParseMIDICommandSection(pMIDICommandSection, nRemaining, pHandler); +} + +CAppleMIDIParticipant::CAppleMIDIParticipant(CBcmRandomNumberGenerator* pRandom, CAppleMIDIHandler* pHandler) + : CTask(TASK_STACK_SIZE, true), + + m_pRandom(pRandom), + + m_pControlSocket(nullptr), + m_pMIDISocket(nullptr), + + m_nForeignControlPort(0), + m_nForeignMIDIPort(0), + m_nInitiatorControlPort(0), + m_nInitiatorMIDIPort(0), + m_ControlBuffer{0}, + m_MIDIBuffer{0}, + + m_nControlResult(0), + m_nMIDIResult(0), + + m_pHandler(pHandler), + + m_State(TState::ControlInvitation), + + m_nInitiatorToken(0), + m_nInitiatorSSRC(0), + m_nSSRC(0), + m_nLastMIDISequenceNumber(0), + + m_nOffsetEstimate(0), + m_nLastSyncTime(0), + + m_nSequence(0), + m_nLastFeedbackSequence(0), + m_nLastFeedbackTime(0) +{ +} + +CAppleMIDIParticipant::~CAppleMIDIParticipant() +{ + if (m_pControlSocket) + delete m_pControlSocket; + + if (m_pMIDISocket) + delete m_pMIDISocket; +} + +bool CAppleMIDIParticipant::Initialize() +{ + assert(m_pControlSocket == nullptr); + assert(m_pMIDISocket == nullptr); + + CNetSubSystem* const pNet = CNetSubSystem::Get(); + + if ((m_pControlSocket = new CSocket(pNet, IPPROTO_UDP)) == nullptr) + return false; + + if ((m_pMIDISocket = new CSocket(pNet, IPPROTO_UDP)) == nullptr) + return false; + + if (m_pControlSocket->Bind(ControlPort) != 0) + { + LOGERR("Couldn't bind to port %d", ControlPort); + return false; + } + + if (m_pMIDISocket->Bind(MIDIPort) != 0) + { + LOGERR("Couldn't bind to port %d", MIDIPort); + return false; + } + + // We started as a suspended task; run now that initialization is successful + Start(); + + return true; +} + +void CAppleMIDIParticipant::Run() +{ + assert(m_pControlSocket != nullptr); + assert(m_pMIDISocket != nullptr); + + CScheduler* const pScheduler = CScheduler::Get(); + + while (true) + { + if ((m_nControlResult = m_pControlSocket->ReceiveFrom(m_ControlBuffer, sizeof(m_ControlBuffer), MSG_DONTWAIT, &m_ForeignControlIPAddress, &m_nForeignControlPort)) < 0) + LOGERR("Control socket receive error: %d", m_nControlResult); + + if ((m_nMIDIResult = m_pMIDISocket->ReceiveFrom(m_MIDIBuffer, sizeof(m_MIDIBuffer), MSG_DONTWAIT, &m_ForeignMIDIIPAddress, &m_nForeignMIDIPort)) < 0) + LOGERR("MIDI socket receive error: %d", m_nMIDIResult); + + switch (m_State) + { + case TState::ControlInvitation: + ControlInvitationState(); + break; + + case TState::MIDIInvitation: + MIDIInvitationState(); + break; + + case TState::Connected: + ConnectedState(); + break; + } + + // Allow other tasks to run + pScheduler->Yield(); + } +} + +void CAppleMIDIParticipant::ControlInvitationState() +{ + TAppleMIDISession SessionPacket; + + if (m_nControlResult == 0) + return; + + if (!ParseInvitationPacket(m_ControlBuffer, m_nControlResult, &SessionPacket)) + { + LOGERR("Unexpected packet"); + return; + } + +#ifdef APPLEMIDI_DEBUG + LOGNOTE("<-- Control invitation"); +#endif + + // Store initiator details + m_InitiatorIPAddress.Set(m_ForeignControlIPAddress); + m_nInitiatorControlPort = m_nForeignControlPort; + m_nInitiatorToken = SessionPacket.nInitiatorToken; + m_nInitiatorSSRC = SessionPacket.nSSRC; + + // Generate random SSRC and accept + m_nSSRC = m_pRandom->GetNumber(); + if (!SendAcceptInvitationPacket(m_pControlSocket, &m_InitiatorIPAddress, m_nInitiatorControlPort)) + { + LOGERR("Couldn't accept control invitation"); + return; + } + + m_nLastSyncTime = GetSyncClock(); + m_State = TState::MIDIInvitation; +} + +void CAppleMIDIParticipant::MIDIInvitationState() +{ + TAppleMIDISession SessionPacket; + + if (m_nControlResult > 0) + { + if (ParseInvitationPacket(m_ControlBuffer, m_nControlResult, &SessionPacket)) + { + // Unexpected peer; reject invitation + if (m_ForeignControlIPAddress != m_InitiatorIPAddress || m_nForeignControlPort != m_nInitiatorControlPort) + SendRejectInvitationPacket(m_pControlSocket, &m_ForeignControlIPAddress, m_nForeignControlPort, SessionPacket.nInitiatorToken); + else + LOGERR("Unexpected packet"); + } + } + + if (m_nMIDIResult > 0) + { + if (!ParseInvitationPacket(m_MIDIBuffer, m_nMIDIResult, &SessionPacket)) + { + LOGERR("Unexpected packet"); + return; + } + + // Unexpected peer; reject invitation + if (m_ForeignMIDIIPAddress != m_InitiatorIPAddress) + { + SendRejectInvitationPacket(m_pMIDISocket, &m_ForeignMIDIIPAddress, m_nForeignMIDIPort, SessionPacket.nInitiatorToken); + return; + } + +#ifdef APPLEMIDI_DEBUG + LOGNOTE("<-- MIDI invitation"); +#endif + + m_nInitiatorMIDIPort = m_nForeignMIDIPort; + + if (SendAcceptInvitationPacket(m_pMIDISocket, &m_InitiatorIPAddress, m_nInitiatorMIDIPort)) + { + CString IPAddressString; + m_InitiatorIPAddress.Format(&IPAddressString); + LOGNOTE("Connection to %s (%s) established", SessionPacket.Name, static_cast(IPAddressString)); + m_nLastSyncTime = GetSyncClock(); + m_State = TState::Connected; + m_pHandler->OnAppleMIDIConnect(&m_InitiatorIPAddress, SessionPacket.Name); + } + else + { + LOGERR("Couldn't accept MIDI invitation"); + Reset(); + } + } + + // Timeout + else if ((GetSyncClock() - m_nLastSyncTime) > InvitationTimeout) + { + LOGERR("MIDI port invitation timed out"); + Reset(); + } +} + +void CAppleMIDIParticipant::ConnectedState() +{ + TAppleMIDISession SessionPacket; + TRTPMIDI MIDIPacket; + TAppleMIDISync SyncPacket; + + if (m_nControlResult > 0) + { + if (ParseEndSessionPacket(m_ControlBuffer, m_nControlResult, &SessionPacket)) + { +#ifdef APPLEMIDI_DEBUG + LOGNOTE("<-- End session"); +#endif + + if (m_ForeignControlIPAddress == m_InitiatorIPAddress && + m_nForeignControlPort == m_nInitiatorControlPort && + SessionPacket.nSSRC == m_nInitiatorSSRC) + { + LOGNOTE("Initiator ended session"); + m_pHandler->OnAppleMIDIDisconnect(&m_InitiatorIPAddress, SessionPacket.Name); + Reset(); + return; + } + } + else if (ParseInvitationPacket(m_ControlBuffer, m_nControlResult, &SessionPacket)) + { + // Unexpected peer; reject invitation + if (m_ForeignControlIPAddress != m_InitiatorIPAddress || m_nForeignControlPort != m_nInitiatorControlPort) + SendRejectInvitationPacket(m_pControlSocket, &m_ForeignControlIPAddress, m_nForeignControlPort, SessionPacket.nInitiatorToken); + else + LOGERR("Unexpected packet"); + } + } + + if (m_nMIDIResult > 0) + { + if (m_ForeignMIDIIPAddress != m_InitiatorIPAddress || m_nForeignMIDIPort != m_nInitiatorMIDIPort) + LOGERR("Unexpected packet"); + else if (ParseMIDIPacket(m_MIDIBuffer, m_nMIDIResult, &MIDIPacket, m_pHandler)) + m_nSequence = MIDIPacket.nSequence; + else if (ParseSyncPacket(m_MIDIBuffer, m_nMIDIResult, &SyncPacket)) + { +#ifdef APPLEMIDI_DEBUG + LOGNOTE("<-- Sync %d", SyncPacket.nCount); +#endif + + if (SyncPacket.nSSRC == m_nInitiatorSSRC && (SyncPacket.nCount == 0 || SyncPacket.nCount == 2)) + { + if (SyncPacket.nCount == 0) + SendSyncPacket(SyncPacket.Timestamps[0], GetSyncClock()); + else if (SyncPacket.nCount == 2) + { + m_nOffsetEstimate = ((SyncPacket.Timestamps[2] + SyncPacket.Timestamps[0]) / 2) - SyncPacket.Timestamps[1]; +#ifdef APPLEMIDI_DEBUG + LOGNOTE("Offset estimate: %llu", m_nOffsetEstimate); +#endif + } + + m_nLastSyncTime = GetSyncClock(); + } + else + { + LOGERR("Unexpected sync packet"); + } + } + } + + const u64 nTicks = GetSyncClock(); + + if ((nTicks - m_nLastFeedbackTime) > ReceiverFeedbackPeriod) + { + if (m_nSequence != m_nLastFeedbackSequence) + { + SendFeedbackPacket(); + m_nLastFeedbackSequence = m_nSequence; + } + m_nLastFeedbackTime = nTicks; + } + + if ((nTicks - m_nLastSyncTime) > SyncTimeout) + { + LOGERR("Initiator timed out"); + Reset(); + } +} + +void CAppleMIDIParticipant::Reset() +{ + m_State = TState::ControlInvitation; + + m_nInitiatorToken = 0; + m_nInitiatorSSRC = 0; + m_nSSRC = 0; + m_nLastMIDISequenceNumber = 0; + + m_nOffsetEstimate = 0; + m_nLastSyncTime = 0; + + m_nSequence = 0; + m_nLastFeedbackSequence = 0; + m_nLastFeedbackTime = 0; +} + +bool CAppleMIDIParticipant::SendPacket(CSocket* pSocket, CIPAddress* pIPAddress, u16 nPort, const void* pData, size_t nSize) +{ + const int nResult = pSocket->SendTo(pData, nSize, MSG_DONTWAIT, *pIPAddress, nPort); + + if (nResult < 0) + { + LOGERR("Send failure, error code: %d", nResult); + return false; + } + + if (static_cast(nResult) != nSize) + { + LOGERR("Send failure, only %d/%d bytes sent", nResult, nSize); + return false; + } + +#ifdef APPLEMIDI_DEBUG + LOGNOTE("Sent %d bytes to port %d", nResult, nPort); +#endif + + return true; +} + +bool CAppleMIDIParticipant::SendAcceptInvitationPacket(CSocket* pSocket, CIPAddress* pIPAddress, u16 nPort) +{ + TAppleMIDISession AcceptPacket = + { + htons(AppleMIDISignature), + htons(InvitationAccepted), + htonl(AppleMIDIVersion), + htonl(m_nInitiatorToken), + htonl(m_nSSRC), + {'\0'} + }; + + // TODO: configurable name + strncpy(AcceptPacket.Name, "mt32-pi", sizeof(AcceptPacket.Name)); + +#ifdef APPLEMIDI_DEBUG + LOGNOTE("--> Accept invitation"); +#endif + + const size_t nSendSize = NamelessSessionPacketSize + strlen(AcceptPacket.Name) + 1; + return SendPacket(pSocket, pIPAddress, nPort, &AcceptPacket, nSendSize); +} + +bool CAppleMIDIParticipant::SendRejectInvitationPacket(CSocket* pSocket, CIPAddress* pIPAddress, u16 nPort, u32 nInitiatorToken) +{ + TAppleMIDISession RejectPacket = + { + htons(AppleMIDISignature), + htons(InvitationRejected), + htonl(AppleMIDIVersion), + htonl(nInitiatorToken), + htonl(m_nSSRC), + {'\0'} + }; + +#ifdef APPLEMIDI_DEBUG + LOGNOTE("--> Reject invitation"); +#endif + + // Send without name + return SendPacket(pSocket, pIPAddress, nPort, &RejectPacket, NamelessSessionPacketSize); +} + +bool CAppleMIDIParticipant::SendSyncPacket(u64 nTimestamp1, u64 nTimestamp2) +{ + const TAppleMIDISync SyncPacket = + { + htons(AppleMIDISignature), + htons(Sync), + htonl(m_nSSRC), + 1, + {0}, + { + htonll(nTimestamp1), + htonll(nTimestamp2), + 0 + } + }; + +#ifdef APPLEMIDI_DEBUG + LOGNOTE("--> Sync 1"); +#endif + + return SendPacket(m_pMIDISocket, &m_InitiatorIPAddress, m_nInitiatorMIDIPort, &SyncPacket, sizeof(SyncPacket)); +} + +bool CAppleMIDIParticipant::SendFeedbackPacket() +{ + const TAppleMIDIReceiverFeedback FeedbackPacket = + { + htons(AppleMIDISignature), + htons(ReceiverFeedback), + htonl(m_nSSRC), + htonl(m_nSequence << 16) + }; + +#ifdef APPLEMIDI_DEBUG + LOGNOTE("--> Feedback"); +#endif + + return SendPacket(m_pControlSocket, &m_InitiatorIPAddress, m_nInitiatorControlPort, &FeedbackPacket, sizeof(FeedbackPacket)); +} \ No newline at end of file diff --git a/src/net/applemidi.h b/src/net/applemidi.h new file mode 100644 index 00000000..3df68aef --- /dev/null +++ b/src/net/applemidi.h @@ -0,0 +1,111 @@ +// +// applemidi.h +// +// mt32-pi - A baremetal MIDI synthesizer for Raspberry Pi +// Copyright (C) 2020-2023 Dale Whinham +// +// This file is part of mt32-pi. +// +// mt32-pi 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 3 of the License, or (at your option) any later +// version. +// +// mt32-pi 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 +// mt32-pi. If not, see . +// + +#ifndef _applemidi_h +#define _applemidi_h + +#include +#include +#include +#include + +class CAppleMIDIHandler +{ +public: + virtual void OnAppleMIDIDataReceived(const u8* pData, size_t nSize) = 0; + virtual void OnAppleMIDIConnect(const CIPAddress* pIPAddress, const char* pName) = 0; + virtual void OnAppleMIDIDisconnect(const CIPAddress* pIPAddress, const char* pName) = 0; +}; + +class CAppleMIDIParticipant : protected CTask +{ +public: + CAppleMIDIParticipant(CBcmRandomNumberGenerator* pRandom, CAppleMIDIHandler* pHandler); + virtual ~CAppleMIDIParticipant() override; + + bool Initialize(); + + virtual void Run() override; + +private: + void ControlInvitationState(); + void MIDIInvitationState(); + void ConnectedState(); + void Reset(); + + bool SendPacket(CSocket* pSocket, CIPAddress* pIPAddress, u16 nPort, const void* pData, size_t nSize); + bool SendAcceptInvitationPacket(CSocket* pSocket, CIPAddress* pIPAddress, u16 nPort); + bool SendRejectInvitationPacket(CSocket* pSocket, CIPAddress* pIPAddress, u16 nPort, u32 nInitiatorToken); + bool SendSyncPacket(u64 nTimestamp1, u64 nTimestamp2); + bool SendFeedbackPacket(); + + CBcmRandomNumberGenerator* m_pRandom; + + // UDP sockets + CSocket* m_pControlSocket; + CSocket* m_pMIDISocket; + + // Foreign peers + CIPAddress m_ForeignControlIPAddress; + CIPAddress m_ForeignMIDIIPAddress; + u16 m_nForeignControlPort; + u16 m_nForeignMIDIPort; + + // Connected peer + CIPAddress m_InitiatorIPAddress; + u16 m_nInitiatorControlPort; + u16 m_nInitiatorMIDIPort; + + // Socket receive buffers + u8 m_ControlBuffer[FRAME_BUFFER_SIZE]; + u8 m_MIDIBuffer[FRAME_BUFFER_SIZE]; + + int m_nControlResult; + int m_nMIDIResult; + + // Callback handler + CAppleMIDIHandler* m_pHandler; + + // Participant state machine + enum class TState + { + ControlInvitation, + MIDIInvitation, + Connected + }; + + TState m_State; + + u32 m_nInitiatorToken = 0; + u32 m_nInitiatorSSRC = 0; + u32 m_nSSRC = 0; + u32 m_nLastMIDISequenceNumber = 0; + + u64 m_nOffsetEstimate = 0; + u64 m_nLastSyncTime = 0; + + u16 m_nSequence = 0; + u16 m_nLastFeedbackSequence = 0; + u64 m_nLastFeedbackTime = 0; +}; + +#endif \ No newline at end of file diff --git a/src/net/byteorder.h b/src/net/byteorder.h new file mode 100644 index 00000000..5160119f --- /dev/null +++ b/src/net/byteorder.h @@ -0,0 +1,42 @@ +// +// byteorder.h +// +// mt32-pi - A baremetal MIDI synthesizer for Raspberry Pi +// Copyright (C) 2020-2023 Dale Whinham +// +// This file is part of mt32-pi. +// +// mt32-pi 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 3 of the License, or (at your option) any later +// version. +// +// mt32-pi 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 +// mt32-pi. If not, see . +// + +#ifndef _byteorder_h +#define _byteorder_h + +#if __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__ +#define htons(VALUE) (VALUE) +#define htonl(VALUE) (VALUE) +#define htonll(VALUE) (VALUE) +#define ntohs(VALUE) (VALUE) +#define ntohl(VALUE) (VALUE) +#define ntohll(VALUE) (VALUE) +#else +#define htons(VALUE) __builtin_bswap16(VALUE) +#define htonl(VALUE) __builtin_bswap32(VALUE) +#define htonll(VALUE) __builtin_bswap64(VALUE) +#define ntohs(VALUE) __builtin_bswap16(VALUE) +#define ntohl(VALUE) __builtin_bswap32(VALUE) +#define ntohll(VALUE) __builtin_bswap64(VALUE) +#endif + +#endif \ No newline at end of file diff --git a/src/net/udpmidi.cpp b/src/net/udpmidi.cpp new file mode 100644 index 00000000..2f25edab --- /dev/null +++ b/src/net/udpmidi.cpp @@ -0,0 +1,89 @@ +// +// udpmidi.cpp +// +// mt32-pi - A baremetal MIDI synthesizer for Raspberry Pi +// Copyright (C) 2020-2023 Dale Whinham +// +// This file is part of mt32-pi. +// +// mt32-pi 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 3 of the License, or (at your option) any later +// version. +// +// mt32-pi 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 +// mt32-pi. If not, see . +// + +#include +#include +#include +#include + +#include "udpmidi.h" + +LOGMODULE("udpmidi"); + +constexpr u16 MIDIPort = 1999; + +CUDPMIDIReceiver::CUDPMIDIReceiver(CUDPMIDIHandler* pHandler) + : CTask(TASK_STACK_SIZE, true), + m_pMIDISocket(nullptr), + m_MIDIBuffer{0}, + m_pHandler(pHandler) +{ +} + +CUDPMIDIReceiver::~CUDPMIDIReceiver() +{ + if (m_pMIDISocket) + delete m_pMIDISocket; +} + +bool CUDPMIDIReceiver::Initialize() +{ + assert(m_pMIDISocket == nullptr); + + CNetSubSystem* const pNet = CNetSubSystem::Get(); + + if ((m_pMIDISocket = new CSocket(pNet, IPPROTO_UDP)) == nullptr) + return false; + + if (m_pMIDISocket->Bind(MIDIPort) != 0) + { + LOGERR("Couldn't bind to port %d", MIDIPort); + return false; + } + + // We started as a suspended task; run now that initialization is successful + Start(); + + return true; +} + +void CUDPMIDIReceiver::Run() +{ + assert(m_pHandler != nullptr); + assert(m_pMIDISocket != nullptr); + + CScheduler* const pScheduler = CScheduler::Get(); + + while (true) + { + // Blocking call + const int nMIDIResult = m_pMIDISocket->Receive(m_MIDIBuffer, sizeof(m_MIDIBuffer), 0); + + if (nMIDIResult < 0) + LOGERR("MIDI socket receive error: %d", nMIDIResult); + else if (nMIDIResult > 0) + m_pHandler->OnUDPMIDIDataReceived(m_MIDIBuffer, nMIDIResult); + + // Allow other tasks to run + pScheduler->Yield(); + } +} \ No newline at end of file diff --git a/src/net/udpmidi.h b/src/net/udpmidi.h new file mode 100644 index 00000000..102d3398 --- /dev/null +++ b/src/net/udpmidi.h @@ -0,0 +1,57 @@ +// +// udpmidi.h +// +// mt32-pi - A baremetal MIDI synthesizer for Raspberry Pi +// Copyright (C) 2020-2023 Dale Whinham +// +// This file is part of mt32-pi. +// +// mt32-pi 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 3 of the License, or (at your option) any later +// version. +// +// mt32-pi 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 +// mt32-pi. If not, see . +// + +#ifndef _udpmidi_h +#define _udpmidi_h + +#include +#include +#include + +class CUDPMIDIHandler +{ +public: + virtual void OnUDPMIDIDataReceived(const u8* pData, size_t nSize) = 0; +}; + +class CUDPMIDIReceiver : protected CTask +{ +public: + CUDPMIDIReceiver(CUDPMIDIHandler* pHandler); + virtual ~CUDPMIDIReceiver() override; + + bool Initialize(); + + virtual void Run() override; + +private: + // UDP sockets + CSocket* m_pMIDISocket; + + // Socket receive buffer + u8 m_MIDIBuffer[FRAME_BUFFER_SIZE]; + + // Callback handler + CUDPMIDIHandler* m_pHandler; +}; + +#endif \ No newline at end of file diff --git a/src/rtpmididevice.cpp b/src/rtpmididevice.cpp new file mode 100644 index 00000000..2c29a942 --- /dev/null +++ b/src/rtpmididevice.cpp @@ -0,0 +1,80 @@ +// +// serialmididevice.cpp +// +// MiniDexed - Dexed FM synthesizer for bare metal Raspberry Pi +// Copyright (C) 2022 The MiniDexed Team +// +// Original author of this class: +// R. Stange +// +// 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 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// + +#include +#include +#include "rtpmididevice.h" +#include + +LOGMODULE("rtpmididevice"); + +CRTPMIDIDevice::CRTPMIDIDevice (CMiniDexed *pSynthesizer, + CConfig *pConfig, CUserInterface *pUI) +: CMIDIDevice (pSynthesizer, pConfig, pUI), + m_pConfig (pConfig) + + //m_Serial (pInterrupt, TRUE), + //m_nSerialState (0), + //m_nSysEx (0), + //m_SendBuffer (&m_Serial) +{ + AddDevice ("rtpdummy"); +} + +CRTPMIDIDevice::~CRTPMIDIDevice (void) +{ + //m_nSerialState = 255; +} + +boolean CRTPMIDIDevice::Initialize (void) +{ + m_pAppleMIDIParticipant = new CAppleMIDIParticipant(&m_Random, this); + if (!m_pAppleMIDIParticipant->Initialize()) + { + LOGERR("Failed to init RTP listener"); + return false; //continue without rtp midi + } + else + LOGNOTE("RTP Listener initialized"); + return true; +} + +// Methods to handle MIDI events + +void CRTPMIDIDevice::OnAppleMIDIDataReceived(const u8* pData, size_t nSize) +{ + LOGNOTE("Recieved RTP MIDI Data"); + printf ("MIDI-RTP: %02X %02X\n", + (unsigned) pData[0], (unsigned) pData[1]); + MIDIMessageHandler(pData, nSize); +} + +void CRTPMIDIDevice::OnAppleMIDIConnect(const CIPAddress* pIPAddress, const char* pName) +{ + LOGNOTE("RTP Device connected"); +} + +void CRTPMIDIDevice::OnAppleMIDIDisconnect(const CIPAddress* pIPAddress, const char* pName) +{ + // RemoveRTPDevice +} diff --git a/src/rtpmididevice.h b/src/rtpmididevice.h new file mode 100644 index 00000000..418fcded --- /dev/null +++ b/src/rtpmididevice.h @@ -0,0 +1,69 @@ +// +// .h +// +// MiniDexed - Dexed FM synthesizer for bare metal Raspberry Pi +// Copyright (C) 2022 The MiniDexed Team +// +// Original author of this class: +// R. Stange +// +// 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 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// +#ifndef _rtpmididevice_h +#define _rtpmididevice_h + +#include "mididevice.h" +#include "config.h" +#include "net/applemidi.h" + +#include +#include +#include +#include + +class CMiniDexed; + +class CRTPMIDIDevice : CAppleMIDIHandler, CMIDIDevice +{ +public: + CRTPMIDIDevice (CMiniDexed *pSynthesizer, CConfig *pConfig, CUserInterface *pUI); + ~CRTPMIDIDevice (void); + + boolean Initialize (void); + virtual void OnAppleMIDIDataReceived(const u8* pData, size_t nSize) override; + virtual void OnAppleMIDIConnect(const CIPAddress* pIPAddress, const char* pName) override; + virtual void OnAppleMIDIDisconnect(const CIPAddress* pIPAddress, const char* pName) override; + //void OnAppleMIDIDataReceived(const u8* pData, size_t nSize); + //void OnAppleMIDIConnect(const CIPAddress* pIPAddress, const char* pName); + //void OnAppleMIDIDisconnect(const CIPAddress* pIPAddress, const char* pName); + + //void Process (void); + + //void Send (const u8 *pMessage, size_t nLength, unsigned nCable = 0) override; + +private: + CConfig *m_pConfig; + + //CSerialDevice m_Serial; + //unsigned m_nSerialState; + //unsigned m_nSysEx; + //u8 m_SerialMessage[MAX_MIDI_MESSAGE]; + + //CWriteBufferDevice m_SendBuffer; + CBcmRandomNumberGenerator m_Random; + //CAppleMIDIHandler* m_MIDIHandler; + CAppleMIDIParticipant* m_pAppleMIDIParticipant; // AppleMIDI participant instance +}; + +#endif From 0a9eb7f56581aa3eb9b015cea3de64041bedc398 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=96mer=20=C5=9Eiar=20Baysal?= Date: Tue, 5 Nov 2024 09:14:57 +0000 Subject: [PATCH 02/57] add build.sh --- build.sh | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/build.sh b/build.sh index b69ba6bd..25d9dfc4 100755 --- a/build.sh +++ b/build.sh @@ -15,7 +15,7 @@ else fi # Define system options -OPTIONS="-o USE_PWM_AUDIO_ON_ZERO -o SAVE_VFP_REGS_ON_IRQ -o REALTIME -o SCREEN_DMA_BURST_LENGTH=1" +OPTIONS="-o USE_PWM_AUDIO_ON_ZERO -o SAVE_VFP_REGS_ON_IRQ -o REALTIME -o USE_SDHOST -o SCREEN_DMA_BURST_LENGTH=1" if [ "${RPI}" -gt "1" ]; then OPTIONS="${OPTIONS} -o ARM_ALLOW_MULTI_CORE" fi @@ -39,6 +39,11 @@ make -j cd libs/circle/addon/display/ make clean || true make -j + +cd ../wlan/ +make clean || true +make -j + cd ../sensor/ make clean || true make -j From 893b9f60f11843353cfbe6210097cb4ce13cad85 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=96mer=20=C5=9Eiar=20Baysal?= Date: Tue, 5 Nov 2024 20:13:23 +0000 Subject: [PATCH 03/57] working RTP and UDP poc --- src/Makefile | 4 +- src/kernel.h | 8 +- src/mididevice.cpp | 12 +- src/minidexed.cpp | 35 +- src/minidexed.h | 10 +- src/net/ftpdaemon.cpp | 111 ++++ src/net/ftpdaemon.h | 47 ++ src/net/ftpworker.cpp | 1206 +++++++++++++++++++++++++++++++++++++++++ src/net/ftpworker.h | 157 ++++++ src/net/utility.h | 193 +++++++ src/udpmididevice.cpp | 106 ++++ src/udpmididevice.h | 76 +++ 12 files changed, 1946 insertions(+), 19 deletions(-) create mode 100644 src/net/ftpdaemon.cpp create mode 100644 src/net/ftpdaemon.h create mode 100644 src/net/ftpworker.cpp create mode 100644 src/net/ftpworker.h create mode 100644 src/net/utility.h create mode 100644 src/udpmididevice.cpp create mode 100644 src/udpmididevice.h diff --git a/src/Makefile b/src/Makefile index f078fad0..fa4ab107 100644 --- a/src/Makefile +++ b/src/Makefile @@ -8,10 +8,10 @@ CMSIS_DIR = ../CMSIS_5/CMSIS NET_DIR = ./net OBJS = main.o kernel.o minidexed.o config.o userinterface.o uimenu.o \ - mididevice.o rtpmididevice.o midikeyboard.o serialmididevice.o pckeyboard.o \ + mididevice.o udpmididevice.o midikeyboard.o serialmididevice.o pckeyboard.o \ sysexfileloader.o performanceconfig.o perftimer.o \ effect_compressor.o effect_platervbstereo.o uibuttons.o midipin.o \ - net/applemidi.o net/udpmidi.o + net/ftpdaemon.o net/ftpworker.o net/applemidi.o net/udpmidi.o OPTIMIZE = -O3 diff --git a/src/kernel.h b/src/kernel.h index 31757cdc..000985ba 100644 --- a/src/kernel.h +++ b/src/kernel.h @@ -50,12 +50,12 @@ class CKernel : public CStdlibAppNetwork private: // do not change this order - CConfig m_Config; + CConfig m_Config; CCPUThrottle m_CPUThrottle; CGPIOManager m_GPIOManager; - CI2CMaster m_I2CMaster; - CMiniDexed *m_pDexed; - CUSBController *m_pUSB; + CI2CMaster m_I2CMaster; + CMiniDexed *m_pDexed; + CUSBController *m_pUSB; static CKernel *s_pThis; }; diff --git a/src/mididevice.cpp b/src/mididevice.cpp index 5f231d63..917f674e 100644 --- a/src/mididevice.cpp +++ b/src/mididevice.cpp @@ -169,6 +169,7 @@ void CMIDIDevice::MIDIMessageHandler (const u8 *pMessage, size_t nLength, unsign } m_MIDISpinLock.Acquire (); + printf ("MIDI-DEBUG: SPINLOCK ACQUIRED\n"); u8 ucStatus = pMessage[0]; u8 ucChannel = ucStatus & 0x0F; @@ -213,8 +214,10 @@ void CMIDIDevice::MIDIMessageHandler (const u8 *pMessage, size_t nLength, unsign } // Process MIDI for each Tone Generator + printf ("MIDI-DEBUG: EACH TONEGENERATOR LOOP\n"); for (unsigned nTG = 0; nTG < CConfig::ToneGenerators; nTG++) { + printf ("%u TONE GENERATOR", nTG); if (ucStatus == MIDI_SYSTEM_EXCLUSIVE_BEGIN) { // MIDI SYSEX per MIDI channel @@ -227,12 +230,15 @@ void CMIDIDevice::MIDIMessageHandler (const u8 *pMessage, size_t nLength, unsign } else { + printf ("NOT AN SYSEX"); + if ( m_ChannelMap[nTG] == ucChannel || m_ChannelMap[nTG] == OmniMode) { switch (ucType) { case MIDI_NOTE_ON: + printf ("MIDI-DEBUG: CASE MIDI NOTE ON\n"); if (nLength < 3) { break; @@ -242,12 +248,15 @@ void CMIDIDevice::MIDIMessageHandler (const u8 *pMessage, size_t nLength, unsign { if (pMessage[2] <= 127) { + printf ("MIDI-DEBUG: KEYDOWN EVENT\n"); m_pSynthesizer->keydown (pMessage[1], pMessage[2], nTG); } } else { + printf ("MIDI-DEBUG: KEYUP EVENT\n"); + //printf ("MIDI-RTP: %02X\n", m_pSynthesizer); m_pSynthesizer->keyup (pMessage[1], nTG); } break; @@ -257,7 +266,7 @@ void CMIDIDevice::MIDIMessageHandler (const u8 *pMessage, size_t nLength, unsign { break; } - + printf ("MIDI-DEBUG: MIDI NOTE OFF\n"); m_pSynthesizer->keyup (pMessage[1], nTG); break; @@ -379,6 +388,7 @@ void CMIDIDevice::MIDIMessageHandler (const u8 *pMessage, size_t nLength, unsign } } m_MIDISpinLock.Release (); + printf ("MIDI-DEBUG: SPINLOCK RELEASED\n"); } void CMIDIDevice::AddDevice (const char *pDeviceName) diff --git a/src/minidexed.cpp b/src/minidexed.cpp index d736ed3e..7e0c9302 100644 --- a/src/minidexed.cpp +++ b/src/minidexed.cpp @@ -28,15 +28,17 @@ #include #include #include -#include -#include "circle_stdlib_app.h" +//#include +//#include "circle_stdlib_app.h" //#include "mididevice.h" -/* + #define DRIVE "SD:" #define FIRMWARE_PATH DRIVE "/firmware/" // firmware files must be provided here #define CONFIG_FILE DRIVE "/wpa_supplicant.conf" - +#define FTPUSERNAME "admin" +#define FTPPASSWORD "admin" +/* const char WLANFirmwarePath[] = "SD:firmware/"; const char WLANConfigFile[] = "SD:wpa_supplicant.conf"; */ @@ -77,7 +79,7 @@ CMiniDexed::CMiniDexed (CConfig *pConfig, CInterruptSystem *pInterrupt, */ //CNetSubSystem* const pNet = CNetSubSystem::Get(); m_bNetworkReady(false), - m_RTPMIDI (this, pConfig, &m_UI) + m_UDPMIDI (this, pConfig, &m_UI) { assert (m_pConfig); @@ -291,18 +293,24 @@ bool CMiniDexed::Initialize (void) } #endif //InitNetwork(); - UpdateNetwork(); + //CMIDIDevice->InitializeRTP(); - if (m_RTPMIDI.Initialize ()) + m_pFTPDaemon = new CFTPDaemon(FTPUSERNAME, FTPPASSWORD); + if (!m_pFTPDaemon->Initialize()) { - LOGNOTE ("RTP MIDI interface enabled"); + LOGERR("Failed to init FTP daemon"); + delete m_pFTPDaemon; + m_pFTPDaemon = nullptr; } + else + LOGNOTE("FTP daemon initialized"); return true; } void CMiniDexed::Process (bool bPlugAndPlayUpdated) { + CScheduler* const pScheduler = CScheduler::Get(); #ifndef ARM_ALLOW_MULTI_CORE ProcessSound (); #endif @@ -355,7 +363,9 @@ void CMiniDexed::Process (bool bPlugAndPlayUpdated) { m_GetChunkTimer.Dump (); } - + UpdateNetwork(); + // Allow other tasks to run + pScheduler->Yield(); } #ifdef ARM_ALLOW_MULTI_CORE @@ -629,6 +639,7 @@ void CMiniDexed::SetMIDIChannel (uint8_t uchChannel, unsigned nTG) { m_SerialMIDI.SetChannel (uchChannel, nTG); } + m_UDPMIDI.SetChannel (uchChannel, nTG); #ifdef ARM_ALLOW_MULTI_CORE unsigned nActiveTGs = 0; @@ -1860,11 +1871,15 @@ void CMiniDexed::UpdateNetwork() if (!m_bNetworkReady) { m_bNetworkReady = true; - CString IPString; pNet->GetConfig()->GetIPAddress()->Format(&IPString); LOGNOTE("Network up and running at: %s", static_cast(IPString)); + + if (m_UDPMIDI.Initialize ()) + { + LOGNOTE ("RTP MIDI interface enabled"); + } } else if (m_bNetworkReady && !bNetIsRunning) { diff --git a/src/minidexed.h b/src/minidexed.h index 65d4034d..45c3797c 100644 --- a/src/minidexed.h +++ b/src/minidexed.h @@ -38,6 +38,9 @@ #include #include #include +#include +////#include +//#include #include #include "common.h" #include "effect_mixer.hpp" @@ -46,7 +49,8 @@ //#include //#include //#include -#include "rtpmididevice.h" +#include "udpmididevice.h" +#include "net/ftpdaemon.h" class CMiniDexed #ifdef ARM_ALLOW_MULTI_CORE @@ -316,6 +320,7 @@ class CMiniDexed bool m_bLoadPerformanceBusy; bool m_bSaveAsDeault; bool m_bNetworkReady; + //CNetSubSystem* m_pNet; //CWPASupplicant m_WPASupplicant; // Networking //CNetSubSystem &mNet; @@ -327,7 +332,8 @@ class CMiniDexed bool m_bNetworkReady; CBcmRandomNumberGenerator m_Random; */ - CRTPMIDIDevice m_RTPMIDI; + CUDPMIDIDevice m_UDPMIDI; + CFTPDaemon* m_pFTPDaemon; }; #endif diff --git a/src/net/ftpdaemon.cpp b/src/net/ftpdaemon.cpp new file mode 100644 index 00000000..0cab51c3 --- /dev/null +++ b/src/net/ftpdaemon.cpp @@ -0,0 +1,111 @@ +// +// ftpdaemon.cpp +// +// mt32-pi - A baremetal MIDI synthesizer for Raspberry Pi +// Copyright (C) 2020-2023 Dale Whinham +// +// This file is part of mt32-pi. +// +// mt32-pi 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 3 of the License, or (at your option) any later +// version. +// +// mt32-pi 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 +// mt32-pi. If not, see . +// + +#include +#include +#include +#include +#include + +#include "ftpdaemon.h" +#include "ftpworker.h" + +LOGMODULE("ftpd"); + +constexpr u16 ListenPort = 21; +constexpr u8 MaxConnections = 1; + +CFTPDaemon::CFTPDaemon(const char* pUser, const char* pPassword) + : CTask(TASK_STACK_SIZE, true), + m_pListenSocket(nullptr), + m_pUser(pUser), + m_pPassword(pPassword) +{ +} + +CFTPDaemon::~CFTPDaemon() +{ + if (m_pListenSocket) + delete m_pListenSocket; +} + +bool CFTPDaemon::Initialize() +{ + CNetSubSystem* const pNet = CNetSubSystem::Get(); + + if ((m_pListenSocket = new CSocket(pNet, IPPROTO_TCP)) == nullptr) + return false; + + if (m_pListenSocket->Bind(ListenPort) != 0) + { + LOGERR("Couldn't bind to port %d", ListenPort); + return false; + } + + if (m_pListenSocket->Listen() != 0) + { + LOGERR("Failed to listen on control socket"); + return false; + } + + // We started as a suspended task; run now that initialization is successful + Start(); + + return true; +} + +void CFTPDaemon::Run() +{ + assert(m_pListenSocket != nullptr); + + LOGNOTE("Listener task spawned"); + + while (true) + { + CIPAddress ClientIPAddress; + u16 nClientPort; + + LOGDBG("Listener: waiting for connection"); + CSocket* pConnection = m_pListenSocket->Accept(&ClientIPAddress, &nClientPort); + + if (pConnection == nullptr) + { + LOGERR("Unable to accept connection"); + continue; + } + + CString IPAddressString; + ClientIPAddress.Format(&IPAddressString); + LOGNOTE("Incoming connection from %s:%d", static_cast(IPAddressString), nClientPort); + + if (CFTPWorker::GetInstanceCount() >= MaxConnections) + { + pConnection->Send("421 Maximum number of connections reached.\r\n", 45, 0); + delete pConnection; + LOGWARN("Maximum number of connections reached"); + continue; + } + + // Spawn new worker + new CFTPWorker(pConnection, m_pUser, m_pPassword); + } +} \ No newline at end of file diff --git a/src/net/ftpdaemon.h b/src/net/ftpdaemon.h new file mode 100644 index 00000000..4d757623 --- /dev/null +++ b/src/net/ftpdaemon.h @@ -0,0 +1,47 @@ +// +// ftpdaemon.h +// +// mt32-pi - A baremetal MIDI synthesizer for Raspberry Pi +// Copyright (C) 2020-2023 Dale Whinham +// +// This file is part of mt32-pi. +// +// mt32-pi 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 3 of the License, or (at your option) any later +// version. +// +// mt32-pi 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 +// mt32-pi. If not, see . +// + +#ifndef _ftpdaemon_h +#define _ftpdaemon_h + +#include +#include + +class CFTPDaemon : protected CTask +{ +public: + CFTPDaemon(const char* pUser, const char* pPassword); + virtual ~CFTPDaemon() override; + + bool Initialize(); + + virtual void Run() override; + +private: + // TCP sockets + CSocket* m_pListenSocket; + + const char* m_pUser; + const char* m_pPassword; +}; + +#endif \ No newline at end of file diff --git a/src/net/ftpworker.cpp b/src/net/ftpworker.cpp new file mode 100644 index 00000000..e3fd1f3d --- /dev/null +++ b/src/net/ftpworker.cpp @@ -0,0 +1,1206 @@ +// +// ftpworker.cpp +// +// mt32-pi - A baremetal MIDI synthesizer for Raspberry Pi +// Copyright (C) 2020-2023 Dale Whinham +// +// This file is part of mt32-pi. +// +// mt32-pi 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 3 of the License, or (at your option) any later +// version. +// +// mt32-pi 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 +// mt32-pi. If not, see . +// + +//#define FTPDAEMON_DEBUG + +#include +#include +#include +#include +#include +#include + +#include + +#include "ftpworker.h" +#include "utility.h" + +// Use a per-instance name for the log macros +#define From m_LogName + +constexpr u16 PassivePortBase = 9000; +constexpr size_t TextBufferSize = 512; +constexpr unsigned int SocketTimeout = 20; +constexpr unsigned int NumRetries = 3; + +#ifndef MT32_PI_VERSION +#define MT32_PI_VERSION "(version unknown)" +#endif + +const char MOTDBanner[] = "Welcome to the mt32-pi " MT32_PI_VERSION " embedded FTP server!"; + +enum class TDirectoryListEntryType +{ + File, + Directory, +}; + +struct TDirectoryListEntry +{ + char Name[FF_LFN_BUF + 1]; + TDirectoryListEntryType Type; + u32 nSize; + u16 nLastModifedDate; + u16 nLastModifedTime; +}; + +using TCommandHandler = bool (CFTPWorker::*)(const char* pArgs); + +struct TFTPCommand +{ + const char* pCmdStr; + TCommandHandler pHandler; +}; + +const TFTPCommand CFTPWorker::Commands[] = +{ + { "SYST", &CFTPWorker::System }, + { "USER", &CFTPWorker::Username }, + { "PASS", &CFTPWorker::Password }, + { "TYPE", &CFTPWorker::Type }, + { "PASV", &CFTPWorker::Passive }, + { "PORT", &CFTPWorker::Port }, + { "RETR", &CFTPWorker::Retrieve }, + { "STOR", &CFTPWorker::Store }, + { "DELE", &CFTPWorker::Delete }, + { "RMD", &CFTPWorker::Delete }, + { "MKD", &CFTPWorker::MakeDirectory }, + { "CWD", &CFTPWorker::ChangeWorkingDirectory }, + { "CDUP", &CFTPWorker::ChangeToParentDirectory }, + { "PWD", &CFTPWorker::PrintWorkingDirectory }, + { "LIST", &CFTPWorker::List }, + { "NLST", &CFTPWorker::ListFileNames }, + { "RNFR", &CFTPWorker::RenameFrom }, + { "RNTO", &CFTPWorker::RenameTo }, + { "BYE", &CFTPWorker::Bye }, + { "QUIT", &CFTPWorker::Bye }, + { "NOOP", &CFTPWorker::NoOp }, +}; + +u8 CFTPWorker::s_nInstanceCount = 0; + +// Volume names from ffconf.h +// TODO: Share with soundfontmanager.cpp +const char* const VolumeNames[] = { FF_VOLUME_STRS }; + +bool ValidateVolumeName(const char* pVolumeName) +{ + for (const auto pName : VolumeNames) + { + if (strcasecmp(pName, pVolumeName) == 0) + return true; + } + + return false; +} + +// Comparator for sorting directory listings +inline bool DirectoryCaseInsensitiveAscending(const TDirectoryListEntry& EntryA, const TDirectoryListEntry& EntryB) +{ + // Directories first in ascending order + if (EntryA.Type != EntryB.Type) + return EntryA.Type == TDirectoryListEntryType::Directory; + + return strncasecmp(EntryA.Name, EntryB.Name, sizeof(TDirectoryListEntry::Name)) < 0; +} + + +CFTPWorker::CFTPWorker(CSocket* pControlSocket, const char* pExpectedUser, const char* pExpectedPassword) + : CTask(TASK_STACK_SIZE), + m_LogName(), + m_pExpectedUser(pExpectedUser), + m_pExpectedPassword(pExpectedPassword), + m_pControlSocket(pControlSocket), + m_pDataSocket(nullptr), + m_nDataSocketPort(0), + m_DataSocketIPAddress(), + m_CommandBuffer{'\0'}, + m_DataBuffer{0}, + m_User(), + m_Password(), + m_DataType(TDataType::ASCII), + m_TransferMode(TTransferMode::Active), + m_CurrentPath(), + m_RenameFrom() +{ + ++s_nInstanceCount; + m_LogName.Format("ftpd[%d]", s_nInstanceCount); +} + +CFTPWorker::~CFTPWorker() +{ + if (m_pControlSocket) + delete m_pControlSocket; + + if (m_pDataSocket) + delete m_pDataSocket; + + --s_nInstanceCount; + + LOGNOTE("Instance count is now %d", s_nInstanceCount); +} + +void CFTPWorker::Run() +{ + assert(m_pControlSocket != nullptr); + + const size_t nWorkerNumber = s_nInstanceCount; + CScheduler* const pScheduler = CScheduler::Get(); + + LOGNOTE("Worker task %d spawned", nWorkerNumber); + + if (!SendStatus(TFTPStatus::ReadyForNewUser, MOTDBanner)) + return; + + CTimer* const pTimer = CTimer::Get(); + unsigned int nTimeout = pTimer->GetTicks(); + + while (m_pControlSocket) + { + // Block while waiting to receive +#ifdef FTPDAEMON_DEBUG + LOGDBG("Waiting for command"); +#endif + const int nReceiveBytes = m_pControlSocket->Receive(m_CommandBuffer, sizeof(m_CommandBuffer), MSG_DONTWAIT); + + if (nReceiveBytes == 0) + { + if (pTimer->GetTicks() - nTimeout >= SocketTimeout * HZ) + { + LOGERR("Socket timed out"); + break; + } + + pScheduler->Yield(); + continue; + } + + if (nReceiveBytes < 0) + { + LOGNOTE("Connection closed"); + break; + } + + // FIXME + m_CommandBuffer[nReceiveBytes - 2] = '\0'; + +#ifdef FTPDAEMON_DEBUG + const u8* pIPAddress = m_pControlSocket->GetForeignIP(); + LOGDBG("<-- Received %d bytes from %d.%d.%d.%d: '%s'", nReceiveBytes, pIPAddress[0], pIPAddress[1], pIPAddress[2], pIPAddress[3], m_CommandBuffer); +#endif + + char* pSavePtr; + char* pToken = strtok_r(m_CommandBuffer, " \r\n", &pSavePtr); + + if (!pToken) + { + LOGERR("String tokenization error (received: '%s')", m_CommandBuffer); + continue; + } + + TCommandHandler pHandler = nullptr; + for (size_t i = 0; i < Utility::ArraySize(Commands); ++i) + { + if (strcasecmp(pToken, Commands[i].pCmdStr) == 0) + { + pHandler = Commands[i].pHandler; + break; + } + } + + if (pHandler) + (this->*pHandler)(pSavePtr); + else + SendStatus(TFTPStatus::CommandNotImplemented, "Command not implemented."); + + nTimeout = pTimer->GetTicks(); + } + + LOGNOTE("Worker task %d shutting down", nWorkerNumber); + + delete m_pControlSocket; + m_pControlSocket = nullptr; +} + +CSocket* CFTPWorker::OpenDataConnection() +{ + CSocket* pDataSocket = nullptr; + u8 nRetries = NumRetries; + + while (pDataSocket == nullptr && nRetries > 0) + { + // Active: Create new socket and connect to client + if (m_TransferMode == TTransferMode::Active) + { + CNetSubSystem* const pNet = CNetSubSystem::Get(); + pDataSocket = new CSocket(pNet, IPPROTO_TCP); + + if (pDataSocket == nullptr) + { + SendStatus(TFTPStatus::DataConnectionFailed, "Could not open socket."); + return nullptr; + } + + if (pDataSocket->Connect(m_DataSocketIPAddress, m_nDataSocketPort) < 0) + { + SendStatus(TFTPStatus::DataConnectionFailed, "Could not connect to data port."); + delete pDataSocket; + pDataSocket = nullptr; + } + } + + // Passive: Use previously-created socket and accept connection from client + else if (m_TransferMode == TTransferMode::Passive && m_pDataSocket != nullptr) + { + CIPAddress ClientIPAddress; + u16 nClientPort; + pDataSocket = m_pDataSocket->Accept(&ClientIPAddress, &nClientPort); + } + + --nRetries; + } + + if (pDataSocket == nullptr) + { + LOGERR("Unable to open data socket after %d attempts", NumRetries); + SendStatus(TFTPStatus::DataConnectionFailed, "Couldn't open data connection."); + } + + return pDataSocket; +} + +bool CFTPWorker::SendStatus(TFTPStatus StatusCode, const char* pMessage) +{ + assert(m_pControlSocket != nullptr); + + const int nLength = snprintf(m_CommandBuffer, sizeof(m_CommandBuffer), "%d %s\r\n", StatusCode, pMessage); + if (m_pControlSocket->Send(m_CommandBuffer, nLength, 0) < 0) + { + LOGERR("Failed to send status"); + return false; + } +#ifdef FTPDAEMON_DEBUG + else + { + m_CommandBuffer[nLength - 2] = '\0'; + LOGDBG("--> Sent: '%s'", m_CommandBuffer); + } +#endif + + return true; +} + +bool CFTPWorker::CheckLoggedIn() +{ +#ifdef FTPDAEMON_DEBUG + LOGDBG("Username compare: expected '%s', actual '%s'", static_cast(m_pExpectedUser), static_cast(m_User)); + LOGDBG("Password compare: expected '%s', actual '%s'", static_cast(m_pExpectedPassword), static_cast(m_Password)); +#endif + + if (m_User.Compare(m_pExpectedUser) == 0 && m_Password.Compare(m_pExpectedPassword) == 0) + return true; + + SendStatus(TFTPStatus::NotLoggedIn, "Not logged in."); + return false; +} + +CString CFTPWorker::RealPath(const char* pInBuffer) const +{ + assert(pInBuffer != nullptr); + + CString Path; + const bool bAbsolute = pInBuffer[0] == '/'; + + if (bAbsolute) + { + char Buffer[TextBufferSize]; + FTPPathToFatFsPath(pInBuffer, Buffer, sizeof(Buffer)); + Path = Buffer; + } + else + Path.Format("%s/%s", static_cast(m_CurrentPath), pInBuffer); + + return Path; +} + +const TDirectoryListEntry* CFTPWorker::BuildDirectoryList(size_t& nOutEntries) const +{ + DIR Dir; + FILINFO FileInfo; + FRESULT Result; + + TDirectoryListEntry* pEntries = nullptr; + nOutEntries = 0; + + // Volume list + if (m_CurrentPath.GetLength() == 0) + { + constexpr size_t nVolumes = Utility::ArraySize(VolumeNames); + bool VolumesAvailable[nVolumes] = { false }; + + for (size_t i = 0; i < nVolumes; ++i) + { + char VolumeName[6]; + strncpy(VolumeName, VolumeNames[i], sizeof(VolumeName)); + strcat(VolumeName, ":"); + + // Returns FR_ + if ((Result = f_opendir(&Dir, VolumeName)) == FR_OK) + { + f_closedir(&Dir); + VolumesAvailable[i] = true; + ++nOutEntries; + } + } + + pEntries = new TDirectoryListEntry[nOutEntries]; + + size_t nCurrentEntry = 0; + for (size_t i = 0; i < nVolumes && nCurrentEntry < nOutEntries; ++i) + { + if (VolumesAvailable[i]) + { + TDirectoryListEntry& Entry = pEntries[nCurrentEntry++]; + strncpy(Entry.Name, VolumeNames[i], sizeof(Entry.Name)); + Entry.Type = TDirectoryListEntryType::Directory; + Entry.nSize = 0; + Entry.nLastModifedDate = 0; + Entry.nLastModifedTime = 0; + } + } + + return pEntries; + } + + // Directory list + Result = f_findfirst(&Dir, &FileInfo, m_CurrentPath, "*"); + if (Result == FR_OK && *FileInfo.fname) + { + // Count how many entries we need + do + { + ++nOutEntries; + Result = f_findnext(&Dir, &FileInfo); + } while (Result == FR_OK && *FileInfo.fname); + + f_closedir(&Dir); + + if (nOutEntries && (pEntries = new TDirectoryListEntry[nOutEntries])) + { + size_t nCurrentEntry = 0; + Result = f_findfirst(&Dir, &FileInfo, m_CurrentPath, "*"); + while (Result == FR_OK && *FileInfo.fname) + { + TDirectoryListEntry& Entry = pEntries[nCurrentEntry++]; + strncpy(Entry.Name, FileInfo.fname, sizeof(Entry.Name)); + + if (FileInfo.fattrib & AM_DIR) + { + Entry.Type = TDirectoryListEntryType::Directory; + Entry.nSize = 0; + } + else + { + Entry.Type = TDirectoryListEntryType::File; + Entry.nSize = FileInfo.fsize; + } + + Entry.nLastModifedDate = FileInfo.fdate; + Entry.nLastModifedTime = FileInfo.ftime; + + Result = f_findnext(&Dir, &FileInfo); + } + + f_closedir(&Dir); + + Utility::QSort(pEntries, DirectoryCaseInsensitiveAscending, 0, nOutEntries - 1); + } + } + + return pEntries; +} + +bool CFTPWorker::System(const char* pArgs) +{ + // Some FTP clients (e.g. Directory Opus) will only attempt to parse LIST responses as IIS/DOS-style if we pretend to be Windows NT + SendStatus(TFTPStatus::SystemType, "Windows_NT"); + return true; +} + +bool CFTPWorker::Username(const char* pArgs) +{ + m_User = pArgs; + char Buffer[TextBufferSize]; + snprintf(Buffer, sizeof(Buffer), "Password required for '%s'.", static_cast(m_User)); + SendStatus(TFTPStatus::PasswordRequired, Buffer); + return true; +} + +bool CFTPWorker::Port(const char* pArgs) +{ + if (!CheckLoggedIn()) + return false; + + char Buffer[TextBufferSize]; + strncpy(Buffer, pArgs, sizeof(Buffer)); + + if (m_pDataSocket != nullptr) + { + delete m_pDataSocket; + m_pDataSocket = nullptr; + } + + m_TransferMode = TTransferMode::Active; + + // TODO: PORT IP Address should match original IP address + + u8 PortBytes[6]; + char* pSavePtr; + char* pToken = strtok_r(Buffer, " ,", &pSavePtr); + bool bParseError = (pToken == nullptr); + + if (!bParseError) + { + PortBytes[0] = static_cast(atoi(pToken)); + + for (u8 i = 0; i < 5; ++i) + { + pToken = strtok_r(nullptr, " ,", &pSavePtr); + if (pToken == nullptr) + { + bParseError = true; + break; + } + + PortBytes[i + 1] = static_cast(atoi(pToken)); + } + } + + if (bParseError) + { + SendStatus(TFTPStatus::SyntaxError, "Syntax error."); + return false; + } + + m_DataSocketIPAddress.Set(PortBytes); + m_nDataSocketPort = (PortBytes[4] << 8) + PortBytes[5]; + +#ifdef FTPDAEMON_DEBUG + CString IPAddressString; + m_DataSocketIPAddress.Format(&IPAddressString); + LOGDBG("PORT set to: %s:%d", static_cast(IPAddressString), m_nDataSocketPort); +#endif + + SendStatus(TFTPStatus::Success, "Command OK."); + return true; +} + +bool CFTPWorker::Passive(const char* pArgs) +{ + if (!CheckLoggedIn()) + return false; + + if (m_pDataSocket == nullptr) + { + m_TransferMode = TTransferMode::Passive; + m_nDataSocketPort = PassivePortBase + s_nInstanceCount - 1; + + CNetSubSystem* const pNet = CNetSubSystem::Get(); + m_pDataSocket = new CSocket(pNet, IPPROTO_TCP); + + if (m_pDataSocket == nullptr) + { + SendStatus(TFTPStatus::ServiceNotAvailable, "Failed to open port for passive mode."); + return false; + } + + if (m_pDataSocket->Bind(m_nDataSocketPort) < 0) + { + SendStatus(TFTPStatus::DataConnectionFailed, "Could not bind to data port."); + delete m_pDataSocket; + m_pDataSocket = nullptr; + return false; + } + + if (m_pDataSocket->Listen() < 0) + { + SendStatus(TFTPStatus::DataConnectionFailed, "Could not listen on data port."); + delete m_pDataSocket; + m_pDataSocket = nullptr; + return false; + } + } + + u8 IPAddress[IP_ADDRESS_SIZE]; + CNetSubSystem::Get()->GetConfig()->GetIPAddress()->CopyTo(IPAddress); + + char Buffer[TextBufferSize]; + snprintf(Buffer, sizeof(Buffer), "Entering passive mode (%d,%d,%d,%d,%d,%d).", + IPAddress[0], + IPAddress[1], + IPAddress[2], + IPAddress[3], + (m_nDataSocketPort >> 8) & 0xFF, + m_nDataSocketPort & 0xFF + ); + + SendStatus(TFTPStatus::EnteringPassiveMode, Buffer); + return true; +} + +bool CFTPWorker::Password(const char* pArgs) +{ + if (m_User.GetLength() == 0) + { + SendStatus(TFTPStatus::AccountRequired, "Need account for login."); + return false; + } + + m_Password = pArgs; + + if (!CheckLoggedIn()) + return false; + + SendStatus(TFTPStatus::UserLoggedIn, "User logged in."); + return true; +} + +bool CFTPWorker::Type(const char* pArgs) +{ + if (!CheckLoggedIn()) + return false; + + if (strcasecmp(pArgs, "A") == 0) + { + m_DataType = TDataType::ASCII; + SendStatus(TFTPStatus::Success, "Type set to ASCII."); + return true; + } + + if (strcasecmp(pArgs, "I") == 0) + { + m_DataType = TDataType::Binary; + SendStatus(TFTPStatus::Success, "Type set to binary."); + return true; + } + + SendStatus(TFTPStatus::SyntaxError, "Syntax error."); + return false; +} + +bool CFTPWorker::Retrieve(const char* pArgs) +{ + if (!CheckLoggedIn()) + return false; + + FIL File; + CString Path = RealPath(pArgs); + + if (f_open(&File, Path, FA_READ) != FR_OK) + { + SendStatus(TFTPStatus::FileActionNotTaken, "Could not open file for reading."); + return false; + } + + if (!SendStatus(TFTPStatus::FileStatusOk, "Command OK.")) + return false; + + CSocket* pDataSocket = OpenDataConnection(); + if (pDataSocket == nullptr) + return false; + + size_t nSize = f_size(&File); + size_t nSent = 0; + + while (nSent < nSize) + { + UINT nBytesRead; +#ifdef FTPDAEMON_DEBUG + LOGDBG("Sending data"); +#endif + if (f_read(&File, m_DataBuffer, sizeof(m_DataBuffer), &nBytesRead) != FR_OK || pDataSocket->Send(m_DataBuffer, nBytesRead, 0) < 0) + { + delete pDataSocket; + f_close(&File); + SendStatus(TFTPStatus::ActionAborted, "File action aborted, local error."); + return false; + } + + nSent += nBytesRead; + assert(nSent <= nSize); + } + + delete pDataSocket; + f_close(&File); + SendStatus(TFTPStatus::TransferComplete, "Transfer complete."); + + return false; +} + +bool CFTPWorker::Store(const char* pArgs) +{ + if (!CheckLoggedIn()) + return false; + + FIL File; + CString Path = RealPath(pArgs); + + if (f_open(&File, Path, FA_CREATE_ALWAYS | FA_WRITE) != FR_OK) + { + SendStatus(TFTPStatus::FileActionNotTaken, "Could not open file for writing."); + return false; + } + + f_sync(&File); + + if (!SendStatus(TFTPStatus::FileStatusOk, "Command OK.")) + return false; + + CSocket* pDataSocket = OpenDataConnection(); + if (pDataSocket == nullptr) + return false; + + bool bSuccess = true; + + CTimer* const pTimer = CTimer::Get(); + unsigned int nTimeout = pTimer->GetTicks(); + + while (true) + { +#ifdef FTPDAEMON_DEBUG + LOGDBG("Waiting to receive"); +#endif + int nReceiveResult = pDataSocket->Receive(m_DataBuffer, sizeof(m_DataBuffer), MSG_DONTWAIT); + FRESULT nWriteResult; + UINT nWritten; + + if (nReceiveResult == 0) + { + if (pTimer->GetTicks() - nTimeout >= SocketTimeout * HZ) + { + LOGERR("Socket timed out"); + bSuccess = false; + break; + } + CScheduler::Get()->Yield(); + continue; + } + + // All done + if (nReceiveResult < 0) + { + LOGNOTE("Receive done, no more data"); + break; + } + +#ifdef FTPDAEMON_DEBUG + //LOGDBG("Received %d bytes", nReceiveResult); +#endif + + if ((nWriteResult = f_write(&File, m_DataBuffer, nReceiveResult, &nWritten)) != FR_OK) + { + LOGERR("Write FAILED, return code %d", nWriteResult); + bSuccess = false; + break; + } + + f_sync(&File); + CScheduler::Get()->Yield(); + + nTimeout = pTimer->GetTicks(); + } + + if (bSuccess) + SendStatus(TFTPStatus::TransferComplete, "Transfer complete."); + else + SendStatus(TFTPStatus::ActionAborted, "File action aborted, local error."); + +#ifdef FTPDAEMON_DEBUG + LOGDBG("Closing socket/file"); +#endif + delete pDataSocket; + f_close(&File); + + return true; +} + +bool CFTPWorker::Delete(const char* pArgs) +{ + if (!CheckLoggedIn()) + return false; + + CString Path = RealPath(pArgs); + + if (f_unlink(Path) != FR_OK) + SendStatus(TFTPStatus::FileActionNotTaken, "File was not deleted."); + else + SendStatus(TFTPStatus::FileActionOk, "File deleted."); + + return true; +} + +bool CFTPWorker::MakeDirectory(const char* pArgs) +{ + if (!CheckLoggedIn()) + return false; + + CString Path = RealPath(pArgs); + + if (f_mkdir(Path) != FR_OK) + SendStatus(TFTPStatus::FileActionNotTaken, "Directory creation failed."); + else + { + char Buffer[TextBufferSize]; + FatFsPathToFTPPath(Path, Buffer, sizeof(Buffer)); + strcat(Buffer, " directory created."); + SendStatus(TFTPStatus::PathCreated, Buffer); + } + + return true; +} + +bool CFTPWorker::ChangeWorkingDirectory(const char* pArgs) +{ + if (!CheckLoggedIn()) + return false; + + char Buffer[TextBufferSize]; + bool bSuccess = false; + + const bool bAbsolute = pArgs[0] == '/'; + if (bAbsolute) + { + // Root + if (pArgs[1] == '\0') + { + m_CurrentPath = ""; + bSuccess = true; + } + else + { + DIR Dir; + FTPPathToFatFsPath(pArgs, Buffer, sizeof(Buffer)); + + // f_stat() will fail if we're trying to CWD to the root of a volume, so use f_opendir() + if (f_opendir(&Dir, Buffer) == FR_OK) + { + f_closedir(&Dir); + m_CurrentPath = Buffer; + bSuccess = true; + } + } + } + else + { + const bool bAtRoot = m_CurrentPath.GetLength() == 0; + if (bAtRoot) + { + if (ValidateVolumeName(pArgs)) + { + m_CurrentPath.Format("%s:", pArgs); + bSuccess = true; + } + } + else + { + CString NewPath; + NewPath.Format("%s/%s", static_cast(m_CurrentPath), pArgs); + + if (f_stat(NewPath, nullptr) == FR_OK) + { + m_CurrentPath = NewPath; + bSuccess = true; + } + } + } + + if (bSuccess) + { + const bool bAtRoot = m_CurrentPath.GetLength() == 0; + if (bAtRoot) + strncpy(Buffer, "\"/\"", sizeof(Buffer)); + else + FatFsPathToFTPPath(m_CurrentPath, Buffer, sizeof(Buffer)); + SendStatus(TFTPStatus::FileActionOk, Buffer); + } + else + SendStatus(TFTPStatus::FileNotFound, "Directory unavailable."); + + return bSuccess; +} + +bool CFTPWorker::ChangeToParentDirectory(const char* pArgs) +{ + if (!CheckLoggedIn()) + return false; + + char Buffer[TextBufferSize]; + bool bSuccess = false; + bool bAtRoot = m_CurrentPath.GetLength() == 0; + + if (!bAtRoot) + { + DIR Dir; + FatFsParentPath(m_CurrentPath, Buffer, sizeof(Buffer)); + + bAtRoot = Buffer[0] == '\0'; + if (bAtRoot) + { + m_CurrentPath = Buffer; + bSuccess = true; + } + else if (f_opendir(&Dir, Buffer) == FR_OK) + { + f_closedir(&Dir); + m_CurrentPath = Buffer; + bSuccess = true; + } + } + + if (bSuccess) + { + bAtRoot = m_CurrentPath.GetLength() == 0; + if (bAtRoot) + strncpy(Buffer, "\"/\"", sizeof(Buffer)); + else + FatFsPathToFTPPath(m_CurrentPath, Buffer, sizeof(Buffer)); + SendStatus(TFTPStatus::FileActionOk, Buffer); + } + else + SendStatus(TFTPStatus::FileNotFound, "Directory unavailable."); + + return false; +} + +bool CFTPWorker::PrintWorkingDirectory(const char* pArgs) +{ + if (!CheckLoggedIn()) + return false; + + char Buffer[TextBufferSize]; + + const bool bAtRoot = m_CurrentPath.GetLength() == 0; + if (bAtRoot) + strncpy(Buffer, "\"/\"", sizeof(Buffer)); + else + FatFsPathToFTPPath(m_CurrentPath, Buffer, sizeof(Buffer)); + + SendStatus(TFTPStatus::PathCreated, Buffer); + + return true; +} + +bool CFTPWorker::List(const char* pArgs) +{ + if (!CheckLoggedIn()) + return false; + + if (!SendStatus(TFTPStatus::FileStatusOk, "Command OK.")) + return false; + + CSocket* pDataSocket = OpenDataConnection(); + if (pDataSocket == nullptr) + return false; + + char Buffer[TextBufferSize]; + char Date[9]; + char Time[8]; + + size_t nEntries; + const TDirectoryListEntry* pDirEntries = BuildDirectoryList(nEntries); + + if (pDirEntries) + { + for (size_t i = 0; i < nEntries; ++i) + { + const TDirectoryListEntry& Entry = pDirEntries[i]; + int nLength; + + // Mimic the Microsoft IIS LIST format + FormatLastModifiedDate(Entry.nLastModifedDate, Date, sizeof(Date)); + FormatLastModifiedTime(Entry.nLastModifedTime, Time, sizeof(Time)); + + if (Entry.Type == TDirectoryListEntryType::Directory) + nLength = snprintf(Buffer, sizeof(Buffer), "%-9s %-13s %-14s %s\r\n", Date, Time, "", Entry.Name); + else + nLength = snprintf(Buffer, sizeof(Buffer), "%-9s %-13s %14d %s\r\n", Date, Time, Entry.nSize, Entry.Name); + + if (pDataSocket->Send(Buffer, nLength, 0) < 0) + { + delete[] pDirEntries; + delete pDataSocket; + SendStatus(TFTPStatus::DataConnectionFailed, "Transfer error."); + return false; + } + } + + delete[] pDirEntries; + } + + delete pDataSocket; + SendStatus(TFTPStatus::TransferComplete, "Transfer complete."); + return true; +} + +bool CFTPWorker::ListFileNames(const char* pArgs) +{ + if (!CheckLoggedIn()) + return false; + + if (!SendStatus(TFTPStatus::FileStatusOk, "Command OK.")) + return false; + + CSocket* pDataSocket = OpenDataConnection(); + if (pDataSocket == nullptr) + return false; + + char Buffer[TextBufferSize]; + size_t nEntries; + const TDirectoryListEntry* pDirEntries = BuildDirectoryList(nEntries); + + if (pDirEntries) + { + for (size_t i = 0; i < nEntries; ++i) + { + const TDirectoryListEntry& Entry = pDirEntries[i]; + if (Entry.Type == TDirectoryListEntryType::Directory) + continue; + + const int nLength = snprintf(Buffer, sizeof(Buffer), "%s\r\n", Entry.Name); + if (pDataSocket->Send(Buffer, nLength, 0) < 0) + { + delete[] pDirEntries; + delete pDataSocket; + SendStatus(TFTPStatus::DataConnectionFailed, "Transfer error."); + return false; + } + } + + delete[] pDirEntries; + } + + delete pDataSocket; + SendStatus(TFTPStatus::TransferComplete, "Transfer complete."); + return true; +} + +bool CFTPWorker::RenameFrom(const char* pArgs) +{ + if (!CheckLoggedIn()) + return false; + + m_RenameFrom = pArgs; + SendStatus(TFTPStatus::PendingFurtherInfo, "Requested file action pending further information."); + + return false; +} + +bool CFTPWorker::RenameTo(const char* pArgs) +{ + if (!CheckLoggedIn()) + return false; + + if (m_RenameFrom.GetLength() == 0) + { + SendStatus(TFTPStatus::BadCommandSequence, "Bad sequence of commands."); + return false; + } + + CString SourcePath = RealPath(m_RenameFrom); + CString DestPath = RealPath(pArgs); + + if (f_rename(SourcePath, DestPath) != FR_OK) + SendStatus(TFTPStatus::FileNameNotAllowed, "File name not allowed."); + else + SendStatus(TFTPStatus::FileActionOk, "File renamed."); + + m_RenameFrom = ""; + + return false; +} + +bool CFTPWorker::Bye(const char* pArgs) +{ + SendStatus(TFTPStatus::ClosingControl, "Goodbye."); + delete m_pControlSocket; + m_pControlSocket = nullptr; + return true; +} + +bool CFTPWorker::NoOp(const char* pArgs) +{ + SendStatus(TFTPStatus::Success, "Command OK."); + return true; +} + +void CFTPWorker::FatFsPathToFTPPath(const char* pInBuffer, char* pOutBuffer, size_t nSize) +{ + assert(pOutBuffer && nSize > 2); + const char* pEnd = pOutBuffer + nSize; + const char* pInChar = pInBuffer; + char* pOutChar = pOutBuffer; + + *pOutChar++ = '"'; + *pOutChar++ = '/'; + + while (*pInChar != '\0' && pOutChar < pEnd) + { + // Kill the volume colon + if (*pInChar == ':') + { + *pOutChar++ = '/'; + ++pInChar; + + // Kill any slashes after the colon + while (*pInChar == '/') ++pInChar; + continue; + } + + // Kill duplicate slashes + if (*pInChar == '/') + { + *pOutChar++ = *pInChar++; + while (*pInChar == '/') ++pInChar; + continue; + } + + *pOutChar++ = *pInChar++; + } + + // Kill trailing slash + if (*(pOutChar - 1) == '/') + --pOutChar; + + assert(pOutChar < pEnd - 2); + *pOutChar++ = '"'; + *pOutChar++ = '\0'; +} + +void CFTPWorker::FTPPathToFatFsPath(const char* pInBuffer, char* pOutBuffer, size_t nSize) +{ + assert(pInBuffer && pOutBuffer); + const char* pEnd = pOutBuffer + nSize; + const char* pInChar = pInBuffer; + char* pOutChar = pOutBuffer; + + // Kill leading slashes + while (*pInChar == '/') ++pInChar; + + bool bGotVolume = false; + while (*pInChar != '\0' && pOutChar < pEnd) + { + // Kill the volume colon + if (!bGotVolume && *pInChar == '/') + { + bGotVolume = true; + *pOutChar++ = ':'; + ++pInChar; + + // Kill any slashes after the colon + while (*pInChar == '/') ++pInChar; + continue; + } + + // Kill duplicate slashes + if (*pInChar == '/') + { + *pOutChar++ = *pInChar++; + while (*pInChar == '/') ++pInChar; + continue; + } + + *pOutChar++ = *pInChar++; + } + + assert(pOutChar < pEnd - 2); + + // Kill trailing slash + if (*(pOutChar - 1) == '/') + --pOutChar; + + // Add volume colon + if (!bGotVolume) + *pOutChar++ = ':'; + + *pOutChar++ = '\0'; +} + +void CFTPWorker::FatFsParentPath(const char* pInBuffer, char* pOutBuffer, size_t nSize) +{ + assert(pInBuffer != nullptr && pOutBuffer != nullptr); + + size_t nLength = strlen(pInBuffer); + assert(nLength > 0 && nSize >= nLength); + + const char* pLastChar = pInBuffer + nLength - 1; + const char* pInChar = pLastChar; + + // Kill trailing slashes + while (*pInChar == '/' && pInChar > pInBuffer) --pInChar; + + // Kill subdirectory name + while (*pInChar != '/' && *pInChar != ':' && pInChar > pInBuffer) --pInChar; + + // Kill trailing slashes + while (*pInChar == '/' && pInChar > pInBuffer) --pInChar; + + // Pointer didn't move (we're already at a volume root), or we reached the start of the string (path invalid) + if (pInChar == pLastChar || pInChar == pInBuffer) + { + *pOutBuffer = '\0'; + return; + } + + // Truncate string + nLength = pInChar - pInBuffer + 1; + memcpy(pOutBuffer, pInBuffer, nLength); + pOutBuffer[nLength] = '\0'; +} + +void CFTPWorker::FormatLastModifiedDate(u16 nDate, char* pOutBuffer, size_t nSize) +{ + // 2-digit year + const u16 nYear = (1980 + (nDate >> 9)) % 100; + u16 nMonth = (nDate >> 5) & 0x0F; + u16 nDay = nDate & 0x1F; + + if (nMonth == 0) + nMonth = 1; + if (nDay == 0) + nDay = 1; + + snprintf(pOutBuffer, nSize, "%02d-%02d-%02d", nMonth, nDay, nYear); +} + +void CFTPWorker::FormatLastModifiedTime(u16 nDate, char* pOutBuffer, size_t nSize) +{ + u16 nHour = (nDate >> 11) & 0x1F; + const u16 nMinute = (nDate >> 5) & 0x3F; + const char* pSuffix = nHour < 12 ? "AM" : "PM"; + + if (nHour == 0) + nHour = 12; + else if (nHour >= 12) + nHour -= 12; + + snprintf(pOutBuffer, nSize, "%02d:%02d%s", nHour, nMinute, pSuffix); +} \ No newline at end of file diff --git a/src/net/ftpworker.h b/src/net/ftpworker.h new file mode 100644 index 00000000..62e60ed2 --- /dev/null +++ b/src/net/ftpworker.h @@ -0,0 +1,157 @@ +// +// ftpworker.h +// +// mt32-pi - A baremetal MIDI synthesizer for Raspberry Pi +// Copyright (C) 2020-2023 Dale Whinham +// +// This file is part of mt32-pi. +// +// mt32-pi 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 3 of the License, or (at your option) any later +// version. +// +// mt32-pi 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 +// mt32-pi. If not, see . +// + +#ifndef _ftpworker_h +#define _ftpworker_h + +#include +#include +#include +#include + +// TODO: These may be incomplete/inaccurate +enum TFTPStatus +{ + FileStatusOk = 150, + + Success = 200, + SystemType = 215, + ReadyForNewUser = 220, + ClosingControl = 221, + TransferComplete = 226, + EnteringPassiveMode = 227, + UserLoggedIn = 230, + FileActionOk = 250, + PathCreated = 257, + + PasswordRequired = 331, + AccountRequired = 332, + PendingFurtherInfo = 350, + + ServiceNotAvailable = 421, + DataConnectionFailed = 425, + FileActionNotTaken = 450, + ActionAborted = 451, + + CommandUnrecognized = 500, + SyntaxError = 501, + CommandNotImplemented = 502, + BadCommandSequence = 503, + NotLoggedIn = 530, + FileNotFound = 550, + FileNameNotAllowed = 553, +}; + +enum class TTransferMode +{ + Active, + Passive, +}; + +enum class TDataType +{ + ASCII, + Binary, +}; + +struct TFTPCommand; +struct TDirectoryListEntry; + +class CFTPWorker : protected CTask +{ +public: + CFTPWorker(CSocket* pControlSocket, const char* pExpectedUser, const char* pExpectedPassword); + virtual ~CFTPWorker() override; + + virtual void Run() override; + + static u8 GetInstanceCount() { return s_nInstanceCount; } + +private: + CSocket* OpenDataConnection(); + + bool SendStatus(TFTPStatus StatusCode, const char* pMessage); + + bool CheckLoggedIn(); + + // Directory navigation + CString RealPath(const char* pInBuffer) const; + const TDirectoryListEntry* BuildDirectoryList(size_t& nOutEntries) const; + + // FTP command handlers + bool System(const char* pArgs); + bool Username(const char* pArgs); + bool Port(const char* pArgs); + bool Passive(const char* pArgs); + bool Password(const char* pArgs); + bool Type(const char* pArgs); + bool Retrieve(const char* pArgs); + bool Store(const char* pArgs); + bool Delete(const char* pArgs); + bool MakeDirectory(const char* pArgs); + bool ChangeWorkingDirectory(const char* pArgs); + bool ChangeToParentDirectory(const char* pArgs); + bool PrintWorkingDirectory(const char* pArgs); + bool List(const char* pArgs); + bool ListFileNames(const char* pArgs); + bool RenameFrom(const char* pArgs); + bool RenameTo(const char* pArgs); + bool Bye(const char* pArgs); + bool NoOp(const char* pArgs); + + CString m_LogName; + + // Authentication + const char* m_pExpectedUser; + const char* m_pExpectedPassword; + + // TCP sockets + CSocket* m_pControlSocket; + CSocket* m_pDataSocket; + u16 m_nDataSocketPort; + CIPAddress m_DataSocketIPAddress; + + // Command/data buffers + char m_CommandBuffer[FRAME_BUFFER_SIZE]; + u8 m_DataBuffer[FRAME_BUFFER_SIZE]; + + // Session state + CString m_User; + CString m_Password; + TDataType m_DataType; + TTransferMode m_TransferMode; + CString m_CurrentPath; + CString m_RenameFrom; + + static void FatFsPathToFTPPath(const char* pInBuffer, char* pOutBuffer, size_t nSize); + static void FTPPathToFatFsPath(const char* pInBuffer, char* pOutBuffer, size_t nSize); + + static void FatFsParentPath(const char* pInBuffer, char* pOutBuffer, size_t nSize); + + static void FormatLastModifiedDate(u16 nDate, char* pOutBuffer, size_t nSize); + static void FormatLastModifiedTime(u16 nDate, char* pOutBuffer, size_t nSize); + + static const TFTPCommand Commands[]; + static u8 s_nInstanceCount; +}; + +#endif \ No newline at end of file diff --git a/src/net/utility.h b/src/net/utility.h new file mode 100644 index 00000000..3b643951 --- /dev/null +++ b/src/net/utility.h @@ -0,0 +1,193 @@ + +// +// utility.h +// +// mt32-pi - A baremetal MIDI synthesizer for Raspberry Pi +// Copyright (C) 2020-2023 Dale Whinham +// +// This file is part of mt32-pi. +// +// mt32-pi 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 3 of the License, or (at your option) any later +// version. +// +// mt32-pi 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 +// mt32-pi. If not, see . +// + +#ifndef _utility_h +#define _utility_h + +#include +#include + +// Macro to extract the string representation of an enum +#define CONFIG_ENUM_VALUE(VALUE, STRING) VALUE, + +// Macro to extract the enum value +#define CONFIG_ENUM_STRING(VALUE, STRING) #STRING, + +// Macro to declare the enum itself +#define CONFIG_ENUM(NAME, VALUES) enum class NAME { VALUES(CONFIG_ENUM_VALUE) } + +// Macro to declare an array of string representations for an enum +#define CONFIG_ENUM_STRINGS(NAME, DATA) static const char* NAME##Strings[] = { DATA(CONFIG_ENUM_STRING) } + +namespace Utility +{ + // Templated function for clamping a value between a minimum and a maximum + template + constexpr T Clamp(const T& nValue, const T& nMin, const T& nMax) + { + return (nValue < nMin) ? nMin : (nValue > nMax) ? nMax : nValue; + } + + // Templated function for taking the minimum of two values + template + constexpr T Min(const T& nLHS, const T& nRHS) + { + return nLHS < nRHS ? nLHS : nRHS; + } + + // Templated function for taking the maximum of two values + template + constexpr T Max(const T& nLHS, const T& nRHS) + { + return nLHS > nRHS ? nLHS : nRHS; + } + + // Function for performing a linear interpolation of a value + constexpr float Lerp(float nValue, float nMinA, float nMaxA, float nMinB, float nMaxB) + { + return nMinB + (nValue - nMinA) * ((nMaxB - nMinB) / (nMaxA - nMinA)); + } + + // Return number of elements in an array + template + constexpr size_t ArraySize(const T(&)[N]) { return N; } + + // Returns whether some value is a power of 2 + template + constexpr bool IsPowerOfTwo(const T& nValue) + { + return nValue && ((nValue & (nValue - 1)) == 0); + } + + // Rounds a number to a nearest multiple; only works for integer values/multiples + template + constexpr T RoundToNearestMultiple(const T& nValue, const T& nMultiple) + { + return ((nValue + nMultiple / 2) / nMultiple) * nMultiple; + } + + // Convert between milliseconds and ticks of a 1MHz clock + template + constexpr T MillisToTicks(const T& nMillis) + { + return nMillis * 1000; + } + + template + constexpr T TicksToMillis(const T& nTicks) + { + return nTicks / 1000; + } + + // Computes the Roland checksum + constexpr u8 RolandChecksum(const u8* pData, size_t nSize) + { + u8 nSum = 0; + for (size_t i = 0; i < nSize; ++i) + nSum = (nSum + pData[i]) & 0x7F; + + return 128 - nSum; + } + + // Comparators for sorting + namespace Comparator + { + template + using TComparator = bool (*)(const T&, const T&); + + template + inline bool LessThan(const T& ObjectA, const T& ObjectB) + { + return ObjectA < ObjectB; + } + + template + inline bool GreaterThan(const T& ObjectA, const T& ObjectB) + { + return ObjectA > ObjectB; + } + + inline bool CaseInsensitiveAscending(const CString& StringA, const CString& StringB) + { + return strcasecmp(StringA, StringB) < 0; + } + } + + // Swaps two objects in-place + template + inline void Swap(T& ObjectA, T& ObjectB) + { + u8 Buffer[sizeof(T)]; + memcpy(Buffer, &ObjectA, sizeof(T)); + memcpy(&ObjectA, &ObjectB, sizeof(T)); + memcpy(&ObjectB, Buffer, sizeof(T)); + } + + namespace + { + // Quicksort partition function (private) + template + size_t Partition(T* Items, Comparator::TComparator Comparator, size_t nLow, size_t nHigh) + { + const size_t nPivotIndex = (nHigh + nLow) / 2; + T* Pivot = &Items[nPivotIndex]; + + while (true) + { + while (Comparator(Items[nLow], *Pivot)) + ++nLow; + + while (Comparator(*Pivot, Items[nHigh])) + --nHigh; + + if (nLow >= nHigh) + return nHigh; + + Swap(Items[nLow], Items[nHigh]); + + // Update pointer if pivot was swapped + if (nPivotIndex == nLow) + Pivot = &Items[nHigh]; + else if (nPivotIndex == nHigh) + Pivot = &Items[nLow]; + + ++nLow; + --nHigh; + } + } + } + + // Sorts an array in-place using the Tony Hoare Quicksort algorithm + template + void QSort(T* Items, Comparator::TComparator Comparator, size_t nLow, size_t nHigh) + { + if (nLow < nHigh) + { + size_t p = Partition(Items, Comparator, nLow, nHigh); + QSort(Items, Comparator, nLow, p); + QSort(Items, Comparator, p + 1, nHigh); + } + } +} + +#endif diff --git a/src/udpmididevice.cpp b/src/udpmididevice.cpp new file mode 100644 index 00000000..97355b7b --- /dev/null +++ b/src/udpmididevice.cpp @@ -0,0 +1,106 @@ +// +// udpmididevice.cpp +// +// MiniDexed - Dexed FM synthesizer for bare metal Raspberry Pi +// Copyright (C) 2022 The MiniDexed Team +// +// Original author of this class: +// R. Stange +// +// 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 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// + +#include +#include +#include "udpmididevice.h" +#include + +//#define VIRTUALCABLE 24 + +LOGMODULE("rtpmididevice"); + +CUDPMIDIDevice::CUDPMIDIDevice (CMiniDexed *pSynthesizer, + CConfig *pConfig, CUserInterface *pUI) +: CMIDIDevice (pSynthesizer, pConfig, pUI), + m_pSynthesizer (pSynthesizer), + m_pConfig (pConfig) + + + //m_Serial (pInterrupt, TRUE), + //m_nSerialState (0), + //m_nSysEx (0), + //m_SendBuffer (&m_Serial) +{ + AddDevice ("udp"); + /*for (unsigned nTG = 0; nTG < CConfig::ToneGenerators; nTG++) + { + m_ChannelMap[nTG] = Disabled; + }*/ +} + +CUDPMIDIDevice::~CUDPMIDIDevice (void) +{ + m_pSynthesizer = 0; +} + +boolean CUDPMIDIDevice::Initialize (void) +{ + m_pAppleMIDIParticipant = new CAppleMIDIParticipant(&m_Random, this); + if (!m_pAppleMIDIParticipant->Initialize()) + { + LOGERR("Failed to init RTP listener"); + return false; //continue without rtp midi + } + else + LOGNOTE("RTP Listener initialized"); + return true; + m_pUDPMIDIReceiver = new CUDPMIDIReceiver(this); + if (!m_pUDPMIDIReceiver->Initialize()) + { + LOGERR("Failed to init UDP MIDI receiver"); + delete m_pUDPMIDIReceiver; + m_pUDPMIDIReceiver = nullptr; + } + else + LOGNOTE("UDP MIDI receiver initialized"); +} + +// Methods to handle MIDI events + +void CUDPMIDIDevice::OnAppleMIDIDataReceived(const u8* pData, size_t nSize) +{ + LOGNOTE("Recieved RTPUDP MIDI Data"); + printf ("MIDI-RTP: %02X %02X\n", + (unsigned) pData[0], (unsigned) pData[1]); + MIDIMessageHandler(pData, nSize); +} + +void CUDPMIDIDevice::OnAppleMIDIConnect(const CIPAddress* pIPAddress, const char* pName) +{ + LOGNOTE("RTP Device connected"); + //AddDevice ("udp1"); +} + +void CUDPMIDIDevice::OnAppleMIDIDisconnect(const CIPAddress* pIPAddress, const char* pName) +{ + LOGNOTE("RTP Device disconnected"); +} + +void CUDPMIDIDevice::OnUDPMIDIDataReceived(const u8* pData, size_t nSize) +{ + LOGNOTE("Recieved UDP MIDI Data"); + printf ("MIDI-UDP: %02X %02X\n", + (unsigned) pData[0], (unsigned) pData[1]); + MIDIMessageHandler(pData, nSize); +} \ No newline at end of file diff --git a/src/udpmididevice.h b/src/udpmididevice.h new file mode 100644 index 00000000..e0119e19 --- /dev/null +++ b/src/udpmididevice.h @@ -0,0 +1,76 @@ +// +// udpmididevice.h +// +// Virtual midi device for data recieved on network +// +// MiniDexed - Dexed FM synthesizer for bare metal Raspberry Pi +// Copyright (C) 2022 The MiniDexed Team +// +// Original author of this class: +// R. Stange +// +// 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 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// +#ifndef _rtpmididevice_h +#define _rtpmididevice_h + +#include "mididevice.h" +#include "config.h" +#include "net/applemidi.h" +#include "net/udpmidi.h" + +//#include +//#include +//#include +//#include + +class CMiniDexed; + +class CUDPMIDIDevice : CAppleMIDIHandler, CUDPMIDIHandler, public CMIDIDevice +{ +public: + CUDPMIDIDevice (CMiniDexed *pSynthesizer, CConfig *pConfig, CUserInterface *pUI); + ~CUDPMIDIDevice (void); + + boolean Initialize (void); + virtual void OnAppleMIDIDataReceived(const u8* pData, size_t nSize) override; + virtual void OnAppleMIDIConnect(const CIPAddress* pIPAddress, const char* pName) override; + virtual void OnAppleMIDIDisconnect(const CIPAddress* pIPAddress, const char* pName) override; + virtual void OnUDPMIDIDataReceived(const u8* pData, size_t nSize) override; + //void OnAppleMIDIDataReceived(const u8* pData, size_t nSize); + //void OnAppleMIDIConnect(const CIPAddress* pIPAddress, const char* pName); + //void OnAppleMIDIDisconnect(const CIPAddress* pIPAddress, const char* pName); + + //void Process (void); + + //void Send (const u8 *pMessage, size_t nLength, unsigned nCable = 0) override; + +private: + CMiniDexed *m_pSynthesizer; + CConfig *m_pConfig; + //u8 m_ChannelMap[CConfig::ToneGenerators]; + //CSerialDevice m_Serial; + //unsigned m_nSerialState; + //unsigned m_nSysEx; + //u8 m_SerialMessage[MAX_MIDI_MESSAGE]; + + //CWriteBufferDevice m_SendBuffer; + CBcmRandomNumberGenerator m_Random; + //CAppleMIDIHandler* m_MIDIHandler; + CAppleMIDIParticipant* m_pAppleMIDIParticipant; // AppleMIDI participant instance + CUDPMIDIReceiver* m_pUDPMIDIReceiver; + +}; + +#endif From 2046d81d589d630c5de6936f4e9079e16a9ff73a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=96mer=20=C5=9Eiar=20Baysal?= Date: Wed, 6 Nov 2024 14:49:31 +0100 Subject: [PATCH 04/57] init network after minidexed starts --- build.sh | 9 ++++- src/circle_stdlib_app.h | 1 + src/config.cpp | 38 +++++++++++++++++- src/config.h | 13 ++++++ src/kernel.cpp | 9 +++-- src/kernel.h | 4 +- src/mididevice.cpp | 12 ------ src/minidexed.cpp | 87 +++++++++++++++++++++++------------------ src/minidexed.h | 15 +++++-- src/net/applemidi.cpp | 2 +- src/net/ftpworker.cpp | 4 +- src/udpmididevice.cpp | 14 ++----- 12 files changed, 135 insertions(+), 73 deletions(-) diff --git a/build.sh b/build.sh index 25d9dfc4..c2122be2 100755 --- a/build.sh +++ b/build.sh @@ -14,12 +14,19 @@ else export TOOLCHAIN_PREFIX="arm-none-eabi-" fi +SDHOST=$([ "${RPI}" == 3 ] && echo "" || echo "") + # Define system options -OPTIONS="-o USE_PWM_AUDIO_ON_ZERO -o SAVE_VFP_REGS_ON_IRQ -o REALTIME -o USE_SDHOST -o SCREEN_DMA_BURST_LENGTH=1" +OPTIONS="-o USE_PWM_AUDIO_ON_ZERO -o SAVE_VFP_REGS_ON_IRQ -o REALTIME -o SCREEN_DMA_BURST_LENGTH=1" if [ "${RPI}" -gt "1" ]; then OPTIONS="${OPTIONS} -o ARM_ALLOW_MULTI_CORE" fi +# For wireless access +if [ "${RPI}" == "3" ]; then + OPTIONS="${OPTIONS} -o USE_SDHOST" +fi + # USB Vendor and Device ID for use with USB Gadget Mode source USBID.sh if [ "${USB_VID}" ] ; then diff --git a/src/circle_stdlib_app.h b/src/circle_stdlib_app.h index 8a69c828..bf4534af 100644 --- a/src/circle_stdlib_app.h +++ b/src/circle_stdlib_app.h @@ -223,6 +223,7 @@ class CStdlibAppStdio: public CStdlibAppScreen CEMMCDevice mEMMC; FATFS mFileSystem; CConsole mConsole; + CScheduler mScheduler; }; /** diff --git a/src/config.cpp b/src/config.cpp index ac6a2e49..ca958eff 100644 --- a/src/config.cpp +++ b/src/config.cpp @@ -155,7 +155,13 @@ void CConfig::Load (void) // Network m_bNetworkEnabled = m_Properties.GetNumber ("NetworkEnabled", 0) != 0; - m_NetworkType = m_Properties.GetString ("NetworkType", ""); + m_bNetworkDHCP = m_Properties.GetNumber ("NetworkDHCP", 0) != 0; + m_NetworkType = m_Properties.GetString ("NetworkType", "wifi"); + m_NetworkHostname = m_Properties.GetString ("NetworkHostname", "minidexed"); + m_INetworkIPAddress = m_Properties.GetIPAddress("NetworkIPAddress") != 0; + m_INetworkSubnetMask = m_Properties.GetIPAddress("NetworkSubnetMask") != 0; + m_INetworkDefaultGateway = m_Properties.GetIPAddress("NetworkDefaultGateway") != 0; + m_INetworkDNSServer = m_Properties.GetIPAddress("NetworkDNSServer") != 0; } bool CConfig::GetUSBGadgetMode (void) const @@ -514,7 +520,37 @@ bool CConfig::GetNetworkEnabled (void) const return m_bNetworkEnabled; } +bool CConfig::GetNetworkDHCP (void) const +{ + return m_bNetworkDHCP; +} + const char *CConfig::GetNetworkType (void) const { return m_NetworkType.c_str(); +} + +const char *CConfig::GetNetworkHostname (void) const +{ + return m_NetworkHostname.c_str(); +} + +CIPAddress CConfig::GetNetworkIPAddress (void) const +{ + return m_INetworkIPAddress; +} + +CIPAddress CConfig::GetNetworkSubnetMask (void) const +{ + return m_INetworkSubnetMask; +} + +CIPAddress CConfig::GetNetworkDefaultGateway (void) const +{ + return m_INetworkDefaultGateway; +} + +CIPAddress CConfig::GetNetworkDNSServer (void) const +{ + return m_INetworkDNSServer; } \ No newline at end of file diff --git a/src/config.h b/src/config.h index 30ee5ed9..794b9cec 100644 --- a/src/config.h +++ b/src/config.h @@ -23,6 +23,7 @@ #ifndef _config_h #define _config_h +#include #include #include #include @@ -171,6 +172,12 @@ class CConfig // Configuration for MiniDexed // Network bool GetNetworkEnabled (void) const; const char *GetNetworkType (void) const; + bool GetNetworkDHCP (void) const; + const char *GetNetworkHostname (void) const; + CIPAddress GetNetworkIPAddress (void) const; + CIPAddress GetNetworkSubnetMask (void) const; + CIPAddress GetNetworkDefaultGateway (void) const; + CIPAddress GetNetworkDNSServer (void) const; private: CPropertiesFatFsFile m_Properties; @@ -259,7 +266,13 @@ class CConfig // Configuration for MiniDexed // Network bool m_bNetworkEnabled; + bool m_bNetworkDHCP; std::string m_NetworkType; + std::string m_NetworkHostname; + CIPAddress m_INetworkIPAddress; + CIPAddress m_INetworkSubnetMask; + CIPAddress m_INetworkDefaultGateway; + CIPAddress m_INetworkDNSServer; }; #endif diff --git a/src/kernel.cpp b/src/kernel.cpp index 5c7a7a93..8874cf70 100644 --- a/src/kernel.cpp +++ b/src/kernel.cpp @@ -32,12 +32,13 @@ CKernel *CKernel::s_pThis = 0; CKernel::CKernel (void) : - //CStdlibAppStdio ("minidexed"), - CStdlibAppNetwork ("minidexed", CSTDLIBAPP_DEFAULT_PARTITION, - 0, 0, 0, 0, NET_DEVICE_TYPE), + CStdlibAppStdio ("minidexed"), + //CStdlibAppNetwork ("minidexed", CSTDLIBAPP_DEFAULT_PARTITION, + // 0, 0, 0, 0, NET_DEVICE_TYPE), m_Config (&mFileSystem), m_GPIOManager (&mInterrupt), m_I2CMaster (CMachineInfo::Get ()->GetDevice (DeviceI2CMaster), TRUE), + //m_Scheduler(), m_pDexed (0) { s_pThis = this; @@ -52,7 +53,7 @@ CKernel::~CKernel(void) bool CKernel::Initialize (void) { - if (!CStdlibAppNetwork::Initialize ()) + if (!CStdlibAppStdio::Initialize ()) { return FALSE; } diff --git a/src/kernel.h b/src/kernel.h index 000985ba..f5e080ea 100644 --- a/src/kernel.h +++ b/src/kernel.h @@ -25,6 +25,7 @@ #include #include #include +#include #include "config.h" #include "minidexed.h" @@ -35,7 +36,7 @@ enum TShutdownMode ShutdownReboot }; -class CKernel : public CStdlibAppNetwork +class CKernel : public CStdlibAppStdio { public: CKernel (void); @@ -54,6 +55,7 @@ class CKernel : public CStdlibAppNetwork CCPUThrottle m_CPUThrottle; CGPIOManager m_GPIOManager; CI2CMaster m_I2CMaster; + //CScheduler m_Scheduler; CMiniDexed *m_pDexed; CUSBController *m_pUSB; diff --git a/src/mididevice.cpp b/src/mididevice.cpp index 917f674e..cc5a79ca 100644 --- a/src/mididevice.cpp +++ b/src/mididevice.cpp @@ -169,7 +169,6 @@ void CMIDIDevice::MIDIMessageHandler (const u8 *pMessage, size_t nLength, unsign } m_MIDISpinLock.Acquire (); - printf ("MIDI-DEBUG: SPINLOCK ACQUIRED\n"); u8 ucStatus = pMessage[0]; u8 ucChannel = ucStatus & 0x0F; @@ -205,7 +204,6 @@ void CMIDIDevice::MIDIMessageHandler (const u8 *pMessage, size_t nLength, unsign { if ((ucChannel == nPerfCh) || (nPerfCh == OmniMode)) { - //printf("Performance Select Channel %d\n", nPerfCh); m_pSynthesizer->ProgramChangePerformance (pMessage[1]); } } @@ -214,10 +212,8 @@ void CMIDIDevice::MIDIMessageHandler (const u8 *pMessage, size_t nLength, unsign } // Process MIDI for each Tone Generator - printf ("MIDI-DEBUG: EACH TONEGENERATOR LOOP\n"); for (unsigned nTG = 0; nTG < CConfig::ToneGenerators; nTG++) { - printf ("%u TONE GENERATOR", nTG); if (ucStatus == MIDI_SYSTEM_EXCLUSIVE_BEGIN) { // MIDI SYSEX per MIDI channel @@ -230,15 +226,12 @@ void CMIDIDevice::MIDIMessageHandler (const u8 *pMessage, size_t nLength, unsign } else { - printf ("NOT AN SYSEX"); - if ( m_ChannelMap[nTG] == ucChannel || m_ChannelMap[nTG] == OmniMode) { switch (ucType) { case MIDI_NOTE_ON: - printf ("MIDI-DEBUG: CASE MIDI NOTE ON\n"); if (nLength < 3) { break; @@ -248,15 +241,12 @@ void CMIDIDevice::MIDIMessageHandler (const u8 *pMessage, size_t nLength, unsign { if (pMessage[2] <= 127) { - printf ("MIDI-DEBUG: KEYDOWN EVENT\n"); m_pSynthesizer->keydown (pMessage[1], pMessage[2], nTG); } } else { - printf ("MIDI-DEBUG: KEYUP EVENT\n"); - //printf ("MIDI-RTP: %02X\n", m_pSynthesizer); m_pSynthesizer->keyup (pMessage[1], nTG); } break; @@ -266,7 +256,6 @@ void CMIDIDevice::MIDIMessageHandler (const u8 *pMessage, size_t nLength, unsign { break; } - printf ("MIDI-DEBUG: MIDI NOTE OFF\n"); m_pSynthesizer->keyup (pMessage[1], nTG); break; @@ -388,7 +377,6 @@ void CMIDIDevice::MIDIMessageHandler (const u8 *pMessage, size_t nLength, unsign } } m_MIDISpinLock.Release (); - printf ("MIDI-DEBUG: SPINLOCK RELEASED\n"); } void CMIDIDevice::AddDevice (const char *pDeviceName) diff --git a/src/minidexed.cpp b/src/minidexed.cpp index 7e0c9302..1c0c89fa 100644 --- a/src/minidexed.cpp +++ b/src/minidexed.cpp @@ -27,15 +27,14 @@ #include #include #include -#include +//#include //#include //#include "circle_stdlib_app.h" //#include "mididevice.h" -#define DRIVE "SD:" -#define FIRMWARE_PATH DRIVE "/firmware/" // firmware files must be provided here -#define CONFIG_FILE DRIVE "/wpa_supplicant.conf" +const char WLANFirmwarePath[] = "SD:firmware/"; +const char WLANConfigFile[] = "SD:wpa_supplicant.conf"; #define FTPUSERNAME "admin" #define FTPPASSWORD "admin" /* @@ -78,6 +77,10 @@ CMiniDexed::CMiniDexed (CConfig *pConfig, CInterruptSystem *pInterrupt, m_bNetworkReady(false), */ //CNetSubSystem* const pNet = CNetSubSystem::Get(); + m_pNet(nullptr), + m_pNetDevice(nullptr), + m_WLAN(WLANFirmwarePath), + m_WPASupplicant(WLANConfigFile), m_bNetworkReady(false), m_UDPMIDI (this, pConfig, &m_UI) { @@ -292,19 +295,10 @@ bool CMiniDexed::Initialize (void) return false; } #endif - //InitNetwork(); + InitNetwork(); //CMIDIDevice->InitializeRTP(); - m_pFTPDaemon = new CFTPDaemon(FTPUSERNAME, FTPPASSWORD); - if (!m_pFTPDaemon->Initialize()) - { - LOGERR("Failed to init FTP daemon"); - delete m_pFTPDaemon; - m_pFTPDaemon = nullptr; - } - else - LOGNOTE("FTP daemon initialized"); return true; } @@ -1861,18 +1855,18 @@ void CMiniDexed::UpdateNetwork() }*/ { - CNetSubSystem* const pNet = CNetSubSystem::Get(); - if (!pNet) + //CNetSubSystem* const pNet = CNetSubSystem::Get(); + if (!m_pNet) return; - bool bNetIsRunning = pNet->IsRunning(); - //bNetIsRunning &= m_WPASupplicant.IsConnected(); + bool bNetIsRunning = m_pNet->IsRunning(); + bNetIsRunning &= m_WPASupplicant.IsConnected(); - if (!m_bNetworkReady) + if (!m_bNetworkReady && bNetIsRunning) { m_bNetworkReady = true; CString IPString; - pNet->GetConfig()->GetIPAddress()->Format(&IPString); + m_pNet->GetConfig()->GetIPAddress()->Format(&IPString); LOGNOTE("Network up and running at: %s", static_cast(IPString)); @@ -1880,6 +1874,15 @@ void CMiniDexed::UpdateNetwork() { LOGNOTE ("RTP MIDI interface enabled"); } + m_pFTPDaemon = new CFTPDaemon(FTPUSERNAME, FTPPASSWORD); + if (!m_pFTPDaemon->Initialize()) + { + LOGERR("Failed to init FTP daemon"); + delete m_pFTPDaemon; + m_pFTPDaemon = nullptr; + } + else + LOGNOTE("FTP daemon initialized"); } else if (m_bNetworkReady && !bNetIsRunning) { @@ -1914,13 +1917,13 @@ void CMiniDexed::UpdateNetwork() m_bNetworkReady = false; LOGNOTE("Network disconnected."); } -} +}*/ bool CMiniDexed::InitNetwork() { assert(m_pNet == nullptr); - TNetDeviceType NetDeviceType = NetDeviceTypeWLAN; + TNetDeviceType NetDeviceType = NetDeviceTypeUnknown; if (m_pConfig->GetNetworkEnabled () && (strcmp(m_pConfig->GetNetworkType(), "wifi") == 0)) { @@ -1929,7 +1932,7 @@ bool CMiniDexed::InitNetwork() if (m_WLAN.Initialize() && m_WPASupplicant.Initialize()) { LOGNOTE("wlan and wpasupplicant initialized"); - //NetDeviceType = NetDeviceTypeWLAN; + NetDeviceType = NetDeviceTypeWLAN; } else @@ -1938,21 +1941,31 @@ bool CMiniDexed::InitNetwork() else if (m_pConfig->GetNetworkEnabled () && (strcmp(m_pConfig->GetNetworkType(), "ethernet") == 0)) { LOGNOTE("Initializing Ethernet"); - //NetDeviceType = NetDeviceTypeEthernet; + NetDeviceType = NetDeviceTypeEthernet; } + if (NetDeviceType != NetDeviceTypeUnknown) + { + if (m_pConfig->GetNetworkDHCP()) + m_pNet = new CNetSubSystem(0, 0, 0, 0, m_pConfig->GetNetworkHostname(), NetDeviceType); + else + m_pNet = new CNetSubSystem( + m_pConfig->GetNetworkIPAddress().Get(), + m_pConfig->GetNetworkSubnetMask().Get(), + m_pConfig->GetNetworkDefaultGateway().Get(), + m_pConfig->GetNetworkDNSServer().Get(), + m_pConfig->GetNetworkHostname(), + NetDeviceType + ); + + if (!m_pNet->Initialize()) + { + LOGERR("Failed to initialize network subsystem"); + delete m_pNet; + m_pNet = nullptr; + } - LOGNOTE("creating network with wifi and dhcp"); - m_pNet = new CNetSubSystem(0, 0, 0, 0, "minidexed", NetDeviceType); - if (!m_pNet->Initialize(true)) - { - LOGERR("Failed to initialize network subsystem"); - delete m_pNet; - m_pNet = nullptr; - } - m_pNetDevice = CNetDevice::GetNetDevice(NetDeviceType); - - + m_pNetDevice = CNetDevice::GetNetDevice(NetDeviceType); + } return m_pNet != nullptr; -} -*/ \ No newline at end of file +} \ No newline at end of file diff --git a/src/minidexed.h b/src/minidexed.h index 45c3797c..d566e828 100644 --- a/src/minidexed.h +++ b/src/minidexed.h @@ -39,8 +39,9 @@ #include #include #include -////#include -//#include +#include +#include +#include #include #include "common.h" #include "effect_mixer.hpp" @@ -218,7 +219,7 @@ class CMiniDexed bool DoSavePerformance (void); void setMasterVolume (float32_t vol); - //bool InitNetwork(); + bool InitNetwork(); void UpdateNetwork(); private: @@ -319,7 +320,6 @@ class CMiniDexed unsigned m_nDeletePerformanceID; bool m_bLoadPerformanceBusy; bool m_bSaveAsDeault; - bool m_bNetworkReady; //CNetSubSystem* m_pNet; //CWPASupplicant m_WPASupplicant; // Networking @@ -332,8 +332,15 @@ class CMiniDexed bool m_bNetworkReady; CBcmRandomNumberGenerator m_Random; */ + // Networking + CNetSubSystem* m_pNet; + CNetDevice* m_pNetDevice; + CBcm4343Device m_WLAN; + CWPASupplicant m_WPASupplicant; + bool m_bNetworkReady; CUDPMIDIDevice m_UDPMIDI; CFTPDaemon* m_pFTPDaemon; + }; #endif diff --git a/src/net/applemidi.cpp b/src/net/applemidi.cpp index b484a077..3c96629f 100644 --- a/src/net/applemidi.cpp +++ b/src/net/applemidi.cpp @@ -803,7 +803,7 @@ bool CAppleMIDIParticipant::SendAcceptInvitationPacket(CSocket* pSocket, CIPAddr }; // TODO: configurable name - strncpy(AcceptPacket.Name, "mt32-pi", sizeof(AcceptPacket.Name)); + strncpy(AcceptPacket.Name, "minidexed", sizeof(AcceptPacket.Name)); #ifdef APPLEMIDI_DEBUG LOGNOTE("--> Accept invitation"); diff --git a/src/net/ftpworker.cpp b/src/net/ftpworker.cpp index e3fd1f3d..f61b07b4 100644 --- a/src/net/ftpworker.cpp +++ b/src/net/ftpworker.cpp @@ -43,10 +43,10 @@ constexpr unsigned int SocketTimeout = 20; constexpr unsigned int NumRetries = 3; #ifndef MT32_PI_VERSION -#define MT32_PI_VERSION "(version unknown)" +#define MT32_PI_VERSION "alpha version" #endif -const char MOTDBanner[] = "Welcome to the mt32-pi " MT32_PI_VERSION " embedded FTP server!"; +const char MOTDBanner[] = "Welcome to the minidexed " MT32_PI_VERSION " embedded FTP server!"; enum class TDirectoryListEntryType { diff --git a/src/udpmididevice.cpp b/src/udpmididevice.cpp index 97355b7b..f24d5804 100644 --- a/src/udpmididevice.cpp +++ b/src/udpmididevice.cpp @@ -26,7 +26,7 @@ #include "udpmididevice.h" #include -//#define VIRTUALCABLE 24 +#define VIRTUALCABLE 24 LOGMODULE("rtpmididevice"); @@ -51,7 +51,7 @@ CUDPMIDIDevice::CUDPMIDIDevice (CMiniDexed *pSynthesizer, CUDPMIDIDevice::~CUDPMIDIDevice (void) { - m_pSynthesizer = 0; + //m_pSynthesizer = 0; } boolean CUDPMIDIDevice::Initialize (void) @@ -80,10 +80,7 @@ boolean CUDPMIDIDevice::Initialize (void) void CUDPMIDIDevice::OnAppleMIDIDataReceived(const u8* pData, size_t nSize) { - LOGNOTE("Recieved RTPUDP MIDI Data"); - printf ("MIDI-RTP: %02X %02X\n", - (unsigned) pData[0], (unsigned) pData[1]); - MIDIMessageHandler(pData, nSize); + MIDIMessageHandler(pData, nSize, VIRTUALCABLE); } void CUDPMIDIDevice::OnAppleMIDIConnect(const CIPAddress* pIPAddress, const char* pName) @@ -99,8 +96,5 @@ void CUDPMIDIDevice::OnAppleMIDIDisconnect(const CIPAddress* pIPAddress, const c void CUDPMIDIDevice::OnUDPMIDIDataReceived(const u8* pData, size_t nSize) { - LOGNOTE("Recieved UDP MIDI Data"); - printf ("MIDI-UDP: %02X %02X\n", - (unsigned) pData[0], (unsigned) pData[1]); - MIDIMessageHandler(pData, nSize); + MIDIMessageHandler(pData, nSize, VIRTUALCABLE); } \ No newline at end of file From 2ad3301129d746cf95e20da512005d0cfee40e0a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=96mer=20=C5=9Eiar=20Baysal?= Date: Wed, 6 Nov 2024 15:01:22 +0100 Subject: [PATCH 05/57] some cleanup --- .gitignore | 3 ++- build.sh | 2 -- src/minidexed.cpp | 50 +---------------------------------------------- src/minidexed.h | 17 +--------------- 4 files changed, 4 insertions(+), 68 deletions(-) diff --git a/.gitignore b/.gitignore index 93bd0017..11bef8ba 100644 --- a/.gitignore +++ b/.gitignore @@ -48,4 +48,5 @@ sdcard CMSIS_5/ Synth_Dexed/ -circle-stdlib/ \ No newline at end of file +circle-stdlib/ +.vscode/ \ No newline at end of file diff --git a/build.sh b/build.sh index c2122be2..2db6d59c 100755 --- a/build.sh +++ b/build.sh @@ -14,8 +14,6 @@ else export TOOLCHAIN_PREFIX="arm-none-eabi-" fi -SDHOST=$([ "${RPI}" == 3 ] && echo "" || echo "") - # Define system options OPTIONS="-o USE_PWM_AUDIO_ON_ZERO -o SAVE_VFP_REGS_ON_IRQ -o REALTIME -o SCREEN_DMA_BURST_LENGTH=1" if [ "${RPI}" -gt "1" ]; then diff --git a/src/minidexed.cpp b/src/minidexed.cpp index 1c0c89fa..e4e53870 100644 --- a/src/minidexed.cpp +++ b/src/minidexed.cpp @@ -69,14 +69,7 @@ CMiniDexed::CMiniDexed (CConfig *pConfig, CInterruptSystem *pInterrupt, m_bSetNewPerformance (false), m_bDeletePerformance (false), m_bLoadPerformanceBusy(false), - /* - m_pNet(nullptr), - m_pNetDevice(nullptr), - m_WLAN(WLANFirmwarePath), - m_WPASupplicant(WLANConfigFile), - m_bNetworkReady(false), - */ - //CNetSubSystem* const pNet = CNetSubSystem::Get(); + m_pNet(nullptr), m_pNetDevice(nullptr), m_WLAN(WLANFirmwarePath), @@ -1840,20 +1833,6 @@ unsigned CMiniDexed::getModController (unsigned controller, unsigned parameter, } void CMiniDexed::UpdateNetwork() -/*{ - CNetSubSystem* const pNet = CNetSubSystem::Get(); - if (!m_bNetworkReady){ - - m_bNetworkReady = true; - CString IPString; - pNet->GetConfig()->GetIPAddress()->Format(&IPString); - LOGNOTE("Network up and running at: %s", static_cast(IPString)); - } - if (!pNet) - return; - -}*/ - { //CNetSubSystem* const pNet = CNetSubSystem::Get(); if (!m_pNet) @@ -1891,33 +1870,6 @@ void CMiniDexed::UpdateNetwork() } } -/* -void CMiniDexed::UpdateNetwork() -{ - if (!m_pNet) - return; - - bool bNetIsRunning = m_pNet->IsRunning(); - if (strcmp(m_pConfig->GetNetworkType(), "ethernet") == 0) - bNetIsRunning &= m_pNetDevice->IsLinkUp(); - else if (strcmp(m_pConfig->GetNetworkType(), "wifi") == 0) - bNetIsRunning &= m_WPASupplicant.IsConnected(); - - if (!m_bNetworkReady && bNetIsRunning) - { - m_bNetworkReady = true; - - CString IPString; - m_pNet->GetConfig()->GetIPAddress()->Format(&IPString); - - LOGNOTE("Network up and running at: %s", static_cast(IPString)); - } - else if (m_bNetworkReady && !bNetIsRunning) - { - m_bNetworkReady = false; - LOGNOTE("Network disconnected."); - } -}*/ bool CMiniDexed::InitNetwork() { diff --git a/src/minidexed.h b/src/minidexed.h index d566e828..b28f9b41 100644 --- a/src/minidexed.h +++ b/src/minidexed.h @@ -47,9 +47,6 @@ #include "effect_mixer.hpp" #include "effect_platervbstereo.h" #include "effect_compressor.h" -//#include -//#include -//#include #include "udpmididevice.h" #include "net/ftpdaemon.h" @@ -320,19 +317,7 @@ class CMiniDexed unsigned m_nDeletePerformanceID; bool m_bLoadPerformanceBusy; bool m_bSaveAsDeault; - //CNetSubSystem* m_pNet; - //CWPASupplicant m_WPASupplicant; - // Networking - //CNetSubSystem &mNet; - /* - CNetSubSystem* m_pNet; - CNetDevice* m_pNetDevice; - CBcm4343Device m_WLAN; - CWPASupplicant m_WPASupplicant; - bool m_bNetworkReady; - CBcmRandomNumberGenerator m_Random; - */ - // Networking + CNetSubSystem* m_pNet; CNetDevice* m_pNetDevice; CBcm4343Device m_WLAN; From 8d5eef90b4b6b2adc6165c8346c5af5f346589e2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=96mer=20=C5=9Eiar=20Baysal?= Date: Thu, 7 Nov 2024 16:04:05 +0100 Subject: [PATCH 06/57] write IP address to LCD --- src/minidexed.cpp | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/src/minidexed.cpp b/src/minidexed.cpp index e4e53870..b9fb7524 100644 --- a/src/minidexed.cpp +++ b/src/minidexed.cpp @@ -1853,15 +1853,25 @@ void CMiniDexed::UpdateNetwork() { LOGNOTE ("RTP MIDI interface enabled"); } - m_pFTPDaemon = new CFTPDaemon(FTPUSERNAME, FTPPASSWORD); + + m_pFTPDaemon = new CFTPDaemon(FTPUSERNAME, FTPPASSWORD); + if (!m_pFTPDaemon->Initialize()) { LOGERR("Failed to init FTP daemon"); delete m_pFTPDaemon; m_pFTPDaemon = nullptr; } - else - LOGNOTE("FTP daemon initialized"); + else + { + LOGNOTE("FTP daemon initialized"); + } + + m_UI.DisplayWrite ("IP", + "Network", + IPString, + 0, + 1); } else if (m_bNetworkReady && !bNetIsRunning) { From aeeceae12f0df76b610a46a25ef92e83fccf5172 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=96mer=20=C5=9Eiar=20Baysal?= Date: Fri, 8 Nov 2024 10:28:28 +0100 Subject: [PATCH 07/57] some more cleanup --- .gitignore | 6 +++--- .gitmodules | 3 +++ src/minidexed.cpp | 18 ++---------------- src/udpmididevice.cpp | 11 ----------- src/udpmididevice.h | 20 -------------------- 5 files changed, 8 insertions(+), 50 deletions(-) diff --git a/.gitignore b/.gitignore index 11bef8ba..6ad1469b 100644 --- a/.gitignore +++ b/.gitignore @@ -46,7 +46,7 @@ sdcard *.swp *.swo -CMSIS_5/ -Synth_Dexed/ -circle-stdlib/ +CMSIS_5/** +Synth_Dexed/** +circle-stdlib/** .vscode/ \ No newline at end of file diff --git a/.gitmodules b/.gitmodules index 064ffe47..a70e358c 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,9 +1,12 @@ [submodule "circle-stdlib"] path = circle-stdlib url = https://github.com/smuehlst/circle-stdlib + ignore = all [submodule "Synth_Dexed"] path = Synth_Dexed url = https://codeberg.org/dcoredump/Synth_Dexed.git + ignore = all [submodule "CMSIS_5"] path = CMSIS_5 url = https://github.com/ARM-software/CMSIS_5 + ignore = all diff --git a/src/minidexed.cpp b/src/minidexed.cpp index b9fb7524..ac3b79ae 100644 --- a/src/minidexed.cpp +++ b/src/minidexed.cpp @@ -27,20 +27,11 @@ #include #include #include -//#include -//#include -//#include "circle_stdlib_app.h" -//#include "mididevice.h" - const char WLANFirmwarePath[] = "SD:firmware/"; const char WLANConfigFile[] = "SD:wpa_supplicant.conf"; #define FTPUSERNAME "admin" #define FTPPASSWORD "admin" -/* -const char WLANFirmwarePath[] = "SD:firmware/"; -const char WLANConfigFile[] = "SD:wpa_supplicant.conf"; -*/ LOGMODULE ("minidexed"); @@ -289,9 +280,7 @@ bool CMiniDexed::Initialize (void) } #endif InitNetwork(); - - - //CMIDIDevice->InitializeRTP(); + return true; } @@ -1849,10 +1838,7 @@ void CMiniDexed::UpdateNetwork() LOGNOTE("Network up and running at: %s", static_cast(IPString)); - if (m_UDPMIDI.Initialize ()) - { - LOGNOTE ("RTP MIDI interface enabled"); - } + m_UDPMIDI.Initialize(); m_pFTPDaemon = new CFTPDaemon(FTPUSERNAME, FTPPASSWORD); diff --git a/src/udpmididevice.cpp b/src/udpmididevice.cpp index f24d5804..6080132b 100644 --- a/src/udpmididevice.cpp +++ b/src/udpmididevice.cpp @@ -35,18 +35,8 @@ CUDPMIDIDevice::CUDPMIDIDevice (CMiniDexed *pSynthesizer, : CMIDIDevice (pSynthesizer, pConfig, pUI), m_pSynthesizer (pSynthesizer), m_pConfig (pConfig) - - - //m_Serial (pInterrupt, TRUE), - //m_nSerialState (0), - //m_nSysEx (0), - //m_SendBuffer (&m_Serial) { AddDevice ("udp"); - /*for (unsigned nTG = 0; nTG < CConfig::ToneGenerators; nTG++) - { - m_ChannelMap[nTG] = Disabled; - }*/ } CUDPMIDIDevice::~CUDPMIDIDevice (void) @@ -86,7 +76,6 @@ void CUDPMIDIDevice::OnAppleMIDIDataReceived(const u8* pData, size_t nSize) void CUDPMIDIDevice::OnAppleMIDIConnect(const CIPAddress* pIPAddress, const char* pName) { LOGNOTE("RTP Device connected"); - //AddDevice ("udp1"); } void CUDPMIDIDevice::OnAppleMIDIDisconnect(const CIPAddress* pIPAddress, const char* pName) diff --git a/src/udpmididevice.h b/src/udpmididevice.h index e0119e19..f20cc792 100644 --- a/src/udpmididevice.h +++ b/src/udpmididevice.h @@ -30,11 +30,6 @@ #include "net/applemidi.h" #include "net/udpmidi.h" -//#include -//#include -//#include -//#include - class CMiniDexed; class CUDPMIDIDevice : CAppleMIDIHandler, CUDPMIDIHandler, public CMIDIDevice @@ -48,26 +43,11 @@ class CUDPMIDIDevice : CAppleMIDIHandler, CUDPMIDIHandler, public CMIDIDevice virtual void OnAppleMIDIConnect(const CIPAddress* pIPAddress, const char* pName) override; virtual void OnAppleMIDIDisconnect(const CIPAddress* pIPAddress, const char* pName) override; virtual void OnUDPMIDIDataReceived(const u8* pData, size_t nSize) override; - //void OnAppleMIDIDataReceived(const u8* pData, size_t nSize); - //void OnAppleMIDIConnect(const CIPAddress* pIPAddress, const char* pName); - //void OnAppleMIDIDisconnect(const CIPAddress* pIPAddress, const char* pName); - - //void Process (void); - - //void Send (const u8 *pMessage, size_t nLength, unsigned nCable = 0) override; private: CMiniDexed *m_pSynthesizer; CConfig *m_pConfig; - //u8 m_ChannelMap[CConfig::ToneGenerators]; - //CSerialDevice m_Serial; - //unsigned m_nSerialState; - //unsigned m_nSysEx; - //u8 m_SerialMessage[MAX_MIDI_MESSAGE]; - - //CWriteBufferDevice m_SendBuffer; CBcmRandomNumberGenerator m_Random; - //CAppleMIDIHandler* m_MIDIHandler; CAppleMIDIParticipant* m_pAppleMIDIParticipant; // AppleMIDI participant instance CUDPMIDIReceiver* m_pUDPMIDIReceiver; From df387faa674ca3ea198cb59d791627fa8824286d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=96mer=20=C5=9Eiar=20Baysal?= Date: Fri, 8 Nov 2024 10:31:37 +0100 Subject: [PATCH 08/57] clean up --- src/kernel.cpp | 5 +---- src/kernel.h | 1 - src/udpmididevice.h | 2 +- 3 files changed, 2 insertions(+), 6 deletions(-) diff --git a/src/kernel.cpp b/src/kernel.cpp index 8874cf70..3d80d713 100644 --- a/src/kernel.cpp +++ b/src/kernel.cpp @@ -33,12 +33,9 @@ CKernel *CKernel::s_pThis = 0; CKernel::CKernel (void) : CStdlibAppStdio ("minidexed"), - //CStdlibAppNetwork ("minidexed", CSTDLIBAPP_DEFAULT_PARTITION, - // 0, 0, 0, 0, NET_DEVICE_TYPE), m_Config (&mFileSystem), m_GPIOManager (&mInterrupt), m_I2CMaster (CMachineInfo::Get ()->GetDevice (DeviceI2CMaster), TRUE), - //m_Scheduler(), m_pDexed (0) { s_pThis = this; @@ -115,7 +112,7 @@ CStdlibApp::TShutdownMode CKernel::Run (void) mScreen.Update (); } - m_CPUThrottle.Update (); + m_CPUThrottle.Update (); } return ShutdownHalt; diff --git a/src/kernel.h b/src/kernel.h index f5e080ea..3d9d70b7 100644 --- a/src/kernel.h +++ b/src/kernel.h @@ -55,7 +55,6 @@ class CKernel : public CStdlibAppStdio CCPUThrottle m_CPUThrottle; CGPIOManager m_GPIOManager; CI2CMaster m_I2CMaster; - //CScheduler m_Scheduler; CMiniDexed *m_pDexed; CUSBController *m_pUSB; diff --git a/src/udpmididevice.h b/src/udpmididevice.h index f20cc792..55bc7a23 100644 --- a/src/udpmididevice.h +++ b/src/udpmididevice.h @@ -47,7 +47,7 @@ class CUDPMIDIDevice : CAppleMIDIHandler, CUDPMIDIHandler, public CMIDIDevice private: CMiniDexed *m_pSynthesizer; CConfig *m_pConfig; - CBcmRandomNumberGenerator m_Random; + CBcmRandomNumberGenerator m_Random; CAppleMIDIParticipant* m_pAppleMIDIParticipant; // AppleMIDI participant instance CUDPMIDIReceiver* m_pUDPMIDIReceiver; From b66058b17f8b0ad47605e598d21acd3644258b0c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=96mer=20=C5=9Eiar=20Baysal?= Date: Fri, 8 Nov 2024 11:19:07 +0100 Subject: [PATCH 09/57] try to resolve conflicts --- src/kernel.h | 8 ++++---- src/minidexed.cpp | 1 - 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/src/kernel.h b/src/kernel.h index 3d9d70b7..8167c616 100644 --- a/src/kernel.h +++ b/src/kernel.h @@ -51,12 +51,12 @@ class CKernel : public CStdlibAppStdio private: // do not change this order - CConfig m_Config; + CConfig m_Config; CCPUThrottle m_CPUThrottle; CGPIOManager m_GPIOManager; - CI2CMaster m_I2CMaster; - CMiniDexed *m_pDexed; - CUSBController *m_pUSB; + CI2CMaster m_I2CMaster; + CMiniDexed *m_pDexed; + CUSBController *m_pUSB; static CKernel *s_pThis; }; diff --git a/src/minidexed.cpp b/src/minidexed.cpp index ac3b79ae..aa014df1 100644 --- a/src/minidexed.cpp +++ b/src/minidexed.cpp @@ -60,7 +60,6 @@ CMiniDexed::CMiniDexed (CConfig *pConfig, CInterruptSystem *pInterrupt, m_bSetNewPerformance (false), m_bDeletePerformance (false), m_bLoadPerformanceBusy(false), - m_pNet(nullptr), m_pNetDevice(nullptr), m_WLAN(WLANFirmwarePath), From dc2a8b4a43ac4d353577f95414e5fe864bb7193a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=96mer=20=C5=9Eiar=20Baysal?= Date: Fri, 8 Nov 2024 11:20:30 +0100 Subject: [PATCH 10/57] comma conflict --- src/minidexed.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/minidexed.cpp b/src/minidexed.cpp index aa014df1..6c0b46f4 100644 --- a/src/minidexed.cpp +++ b/src/minidexed.cpp @@ -59,13 +59,13 @@ CMiniDexed::CMiniDexed (CConfig *pConfig, CInterruptSystem *pInterrupt, m_bSavePerformanceNewFile (false), m_bSetNewPerformance (false), m_bDeletePerformance (false), - m_bLoadPerformanceBusy(false), m_pNet(nullptr), m_pNetDevice(nullptr), m_WLAN(WLANFirmwarePath), m_WPASupplicant(WLANConfigFile), m_bNetworkReady(false), - m_UDPMIDI (this, pConfig, &m_UI) + m_UDPMIDI (this, pConfig, &m_UI), + m_bLoadPerformanceBusy(false) { assert (m_pConfig); From db2534d8a61370727b0e36057b963a49a2ea0783 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=96mer=20=C5=9Eiar=20Baysal?= Date: Fri, 8 Nov 2024 11:25:33 +0100 Subject: [PATCH 11/57] try to resolve conflict --- src/minidexed.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/minidexed.cpp b/src/minidexed.cpp index 6c0b46f4..f26c8a1e 100644 --- a/src/minidexed.cpp +++ b/src/minidexed.cpp @@ -55,16 +55,16 @@ CMiniDexed::CMiniDexed (CConfig *pConfig, CInterruptSystem *pInterrupt, m_GetChunkTimer ("GetChunk", 1000000U * pConfig->GetChunkSize ()/2 / pConfig->GetSampleRate ()), m_bProfileEnabled (m_pConfig->GetProfileEnabled ()), - m_bSavePerformance (false), - m_bSavePerformanceNewFile (false), - m_bSetNewPerformance (false), - m_bDeletePerformance (false), m_pNet(nullptr), m_pNetDevice(nullptr), m_WLAN(WLANFirmwarePath), m_WPASupplicant(WLANConfigFile), m_bNetworkReady(false), m_UDPMIDI (this, pConfig, &m_UI), + m_bSavePerformance (false), + m_bSavePerformanceNewFile (false), + m_bSetNewPerformance (false), + m_bDeletePerformance (false), m_bLoadPerformanceBusy(false) { assert (m_pConfig); From d6a2c6a320fd823755d338b55b5d837698b344c4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=96mer=20=C5=9Eiar=20Baysal?= Date: Fri, 8 Nov 2024 11:47:28 +0100 Subject: [PATCH 12/57] fix init order --- src/minidexed.h | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/src/minidexed.h b/src/minidexed.h index b28f9b41..c109fac2 100644 --- a/src/minidexed.h +++ b/src/minidexed.h @@ -309,6 +309,15 @@ class CMiniDexed CSpinLock m_ReverbSpinLock; + // Network + CNetSubSystem* m_pNet; + CNetDevice* m_pNetDevice; + CBcm4343Device m_WLAN; + CWPASupplicant m_WPASupplicant; + bool m_bNetworkReady; + CUDPMIDIDevice m_UDPMIDI; + CFTPDaemon* m_pFTPDaemon; + bool m_bSavePerformance; bool m_bSavePerformanceNewFile; bool m_bSetNewPerformance; @@ -318,13 +327,7 @@ class CMiniDexed bool m_bLoadPerformanceBusy; bool m_bSaveAsDeault; - CNetSubSystem* m_pNet; - CNetDevice* m_pNetDevice; - CBcm4343Device m_WLAN; - CWPASupplicant m_WPASupplicant; - bool m_bNetworkReady; - CUDPMIDIDevice m_UDPMIDI; - CFTPDaemon* m_pFTPDaemon; + }; From ea8f5f7b7ead65468a6d6afa67b06b0eb653b928 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=96mer=20=C5=9Eiar=20Baysal?= Date: Fri, 8 Nov 2024 15:43:28 +0100 Subject: [PATCH 13/57] clean net directory as well --- src/Rules.mk | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Rules.mk b/src/Rules.mk index 64618331..d86f51a9 100644 --- a/src/Rules.mk +++ b/src/Rules.mk @@ -34,4 +34,6 @@ LIBS += \ $(CIRCLEHOME)/addon/wlan/libwlan.a \ $(CIRCLEHOME)/lib/net/libnet.a +EXTRACLEAN += $(NET_DIR)/*.d $(NET_DIR)/*.o + -include $(DEPS) From 3a1f7e5f00703127f8a6372578274b8a5a2ea809 Mon Sep 17 00:00:00 2001 From: probonopd Date: Sat, 9 Nov 2024 09:35:34 +0100 Subject: [PATCH 14/57] Add Network section to minidexed.ini https://github.com/probonopd/MiniDexed/pull/744#issuecomment-2466114284 --- src/minidexed.ini | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/minidexed.ini b/src/minidexed.ini index de9f1500..41c5a12b 100644 --- a/src/minidexed.ini +++ b/src/minidexed.ini @@ -137,5 +137,10 @@ EncoderPinData=9 MIDIDumpEnabled=0 ProfileEnabled=0 +# Network +NetworkEnabled=0 +NetworkDHCP=1 +NetworkType=wifi + # Performance PerformanceSelectToLoad=1 From bb1f352fdb37de252012d28dc8b6d8b3034947d2 Mon Sep 17 00:00:00 2001 From: probonopd Date: Sat, 9 Nov 2024 08:52:20 +0000 Subject: [PATCH 15/57] Add WLAN firmware and wpa_supplicant.conf --- .github/workflows/build.yml | 8 +++++++- Synth_Dexed | 2 +- circle-stdlib | 2 +- 3 files changed, 9 insertions(+), 3 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 8df59112..ff616dde 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -57,7 +57,7 @@ jobs: export PATH=$(readlink -f ./gcc-*arm-none*/bin/):$PATH RPI=1 bash -ex build.sh cp ./src/kernel*.img ./kernels/ - - name: Get Raspberry Pi boot files + - name: Get Raspberry Pi boot files and WLAN firmware run: | set -ex export PATH=$(readlink -f ./gcc-*aarch64-none*/bin/):$PATH @@ -74,6 +74,12 @@ jobs: cd sdcard cp ../kernels/* . || true cd - + # WLAN firmware + cp circle-stdlib/libs/circle/addon/wlan/sample/hello_wlan/wpa_supplicant.conf sdcard/ + mkdir -p sdcard/firmware + cd sdcard/firmware + make -f ../../circle-stdlib/libs/circle/addon/wlan/firmware/Makefile + cd - - name: Get performance files run: | git clone https://github.com/Banana71/Soundplantage --depth 1 # depth 1 means only the latest commit diff --git a/Synth_Dexed b/Synth_Dexed index 8c677ceb..c9f52741 160000 --- a/Synth_Dexed +++ b/Synth_Dexed @@ -1 +1 @@ -Subproject commit 8c677ceb4b3fb73f8643e30ff6cf4158dc8b9e53 +Subproject commit c9f52741a802ad9bb01263823650f7cc3b0b5108 diff --git a/circle-stdlib b/circle-stdlib index 61cf3a47..3bd135d3 160000 --- a/circle-stdlib +++ b/circle-stdlib @@ -1 +1 @@ -Subproject commit 61cf3a47bf93628039078b7c840e44432e52343e +Subproject commit 3bd135d35ac23c7c726924cb85b5890cbf80bbfe From f5447152d40bd49f173f474ea33de0a4d41bd43a Mon Sep 17 00:00:00 2001 From: probonopd Date: Sat, 9 Nov 2024 09:05:01 +0000 Subject: [PATCH 16/57] Use "WLAN" terminology --- src/minidexed.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/minidexed.cpp b/src/minidexed.cpp index ceb34918..a1a12d5a 100644 --- a/src/minidexed.cpp +++ b/src/minidexed.cpp @@ -2236,7 +2236,7 @@ bool CMiniDexed::InitNetwork() if (m_pConfig->GetNetworkEnabled () && (strcmp(m_pConfig->GetNetworkType(), "wifi") == 0)) { - LOGNOTE("Initializing Wi-Fi"); + LOGNOTE("Initializing WLAN"); if (m_WLAN.Initialize() && m_WPASupplicant.Initialize()) { @@ -2245,7 +2245,7 @@ bool CMiniDexed::InitNetwork() } else - LOGERR("Failed to initialize Wi-Fi"); + LOGERR("Failed to initialize WLAN"); } else if (m_pConfig->GetNetworkEnabled () && (strcmp(m_pConfig->GetNetworkType(), "ethernet") == 0)) { From 0b69e35d4db0f76fab049afd8d623137cd454350 Mon Sep 17 00:00:00 2001 From: probonopd Date: Sat, 9 Nov 2024 11:39:54 +0100 Subject: [PATCH 17/57] Update submod.sh --- submod.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/submod.sh b/submod.sh index f9524a32..559aeb0e 100755 --- a/submod.sh +++ b/submod.sh @@ -6,13 +6,13 @@ git submodule update --init --recursive # # Use fixed master branch of circle-stdlib then re-update cd circle-stdlib/ -git checkout 3bd135d +git checkout 695ab4a git submodule update --init --recursive cd - # # Optional update submodules explicitly cd circle-stdlib/libs/circle -git checkout fff3764 +git checkout tags/Step48 cd - cd circle-stdlib/libs/circle-newlib #git checkout develop From 084dca96f6bc1a5bcf89d9a3e2c05acf4d19b6c3 Mon Sep 17 00:00:00 2001 From: probonopd Date: Sat, 9 Nov 2024 11:40:08 +0100 Subject: [PATCH 18/57] Add initial mdns responder for rtp-midi aka applemidi (#749) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Ömer Şiar Baysal --- src/minidexed.cpp | 19 +++++++++++++------ src/minidexed.h | 1 + src/rtpmididevice.cpp | 14 ++++---------- src/rtpmididevice.h | 22 +--------------------- 4 files changed, 19 insertions(+), 37 deletions(-) diff --git a/src/minidexed.cpp b/src/minidexed.cpp index a1a12d5a..c2105368 100644 --- a/src/minidexed.cpp +++ b/src/minidexed.cpp @@ -2197,7 +2197,8 @@ void CMiniDexed::UpdateNetwork() CString IPString; m_pNet->GetConfig()->GetIPAddress()->Format(&IPString); - LOGNOTE("Network up and running at: %s", static_cast(IPString)); + //LOGNOTE("Network up and running at: %s", static_cast(IPString)); + m_UDPMIDI.Initialize(); @@ -2214,11 +2215,17 @@ void CMiniDexed::UpdateNetwork() LOGNOTE("FTP daemon initialized"); } - m_UI.DisplayWrite ("IP", - "Network", - IPString, - 0, - 1); + m_UI.DisplayWrite ("IP", "Network", IPString, 0, 1); + + CmDNSPublisher *pmDNSPublisher = new CmDNSPublisher (m_pNet); + assert (pmDNSPublisher); + static const char ServiceName[] = "minidexed-rtpmidi"; + static const char *ppText[] = {"RTP-MIDI Receiver", nullptr}; // TXT record strings + if (!pmDNSPublisher->PublishService (ServiceName, CmDNSPublisher::ServiceTypeAppleMIDI, + 5004, ppText)) + { + LOGPANIC ("Cannot publish mdns service"); + } } else if (m_bNetworkReady && !bNetIsRunning) { diff --git a/src/minidexed.h b/src/minidexed.h index 2d73e743..8466eb02 100644 --- a/src/minidexed.h +++ b/src/minidexed.h @@ -43,6 +43,7 @@ #include #include #include +#include #include #include "common.h" #include "effect_mixer.hpp" diff --git a/src/rtpmididevice.cpp b/src/rtpmididevice.cpp index 2c29a942..572a5c91 100644 --- a/src/rtpmididevice.cpp +++ b/src/rtpmididevice.cpp @@ -26,24 +26,21 @@ #include "rtpmididevice.h" #include +#define VIRTUALCABLE 24 + LOGMODULE("rtpmididevice"); CRTPMIDIDevice::CRTPMIDIDevice (CMiniDexed *pSynthesizer, CConfig *pConfig, CUserInterface *pUI) : CMIDIDevice (pSynthesizer, pConfig, pUI), m_pConfig (pConfig) - - //m_Serial (pInterrupt, TRUE), - //m_nSerialState (0), - //m_nSysEx (0), - //m_SendBuffer (&m_Serial) { AddDevice ("rtpdummy"); } CRTPMIDIDevice::~CRTPMIDIDevice (void) { - //m_nSerialState = 255; + } boolean CRTPMIDIDevice::Initialize (void) @@ -63,10 +60,7 @@ boolean CRTPMIDIDevice::Initialize (void) void CRTPMIDIDevice::OnAppleMIDIDataReceived(const u8* pData, size_t nSize) { - LOGNOTE("Recieved RTP MIDI Data"); - printf ("MIDI-RTP: %02X %02X\n", - (unsigned) pData[0], (unsigned) pData[1]); - MIDIMessageHandler(pData, nSize); + MIDIMessageHandler(pData, nSize, VIRTUALCABLE); } void CRTPMIDIDevice::OnAppleMIDIConnect(const CIPAddress* pIPAddress, const char* pName) diff --git a/src/rtpmididevice.h b/src/rtpmididevice.h index 418fcded..870301bd 100644 --- a/src/rtpmididevice.h +++ b/src/rtpmididevice.h @@ -27,11 +27,6 @@ #include "config.h" #include "net/applemidi.h" -#include -#include -#include -#include - class CMiniDexed; class CRTPMIDIDevice : CAppleMIDIHandler, CMIDIDevice @@ -44,26 +39,11 @@ class CRTPMIDIDevice : CAppleMIDIHandler, CMIDIDevice virtual void OnAppleMIDIDataReceived(const u8* pData, size_t nSize) override; virtual void OnAppleMIDIConnect(const CIPAddress* pIPAddress, const char* pName) override; virtual void OnAppleMIDIDisconnect(const CIPAddress* pIPAddress, const char* pName) override; - //void OnAppleMIDIDataReceived(const u8* pData, size_t nSize); - //void OnAppleMIDIConnect(const CIPAddress* pIPAddress, const char* pName); - //void OnAppleMIDIDisconnect(const CIPAddress* pIPAddress, const char* pName); - - //void Process (void); - - //void Send (const u8 *pMessage, size_t nLength, unsigned nCable = 0) override; private: CConfig *m_pConfig; - - //CSerialDevice m_Serial; - //unsigned m_nSerialState; - //unsigned m_nSysEx; - //u8 m_SerialMessage[MAX_MIDI_MESSAGE]; - - //CWriteBufferDevice m_SendBuffer; CBcmRandomNumberGenerator m_Random; - //CAppleMIDIHandler* m_MIDIHandler; - CAppleMIDIParticipant* m_pAppleMIDIParticipant; // AppleMIDI participant instance + CAppleMIDIParticipant* m_pAppleMIDIParticipant; }; #endif From 3a673dd48cb37974424d4cde0e01f59388b6b9bf Mon Sep 17 00:00:00 2001 From: probonopd Date: Sat, 9 Nov 2024 11:44:58 +0100 Subject: [PATCH 19/57] circle: git checkout c243194 --- submod.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/submod.sh b/submod.sh index 559aeb0e..b18a43df 100755 --- a/submod.sh +++ b/submod.sh @@ -12,7 +12,7 @@ cd - # # Optional update submodules explicitly cd circle-stdlib/libs/circle -git checkout tags/Step48 +git checkout c243194 cd - cd circle-stdlib/libs/circle-newlib #git checkout develop From d9e32a117a28a10ca03bbab0269a849f4fba6109 Mon Sep 17 00:00:00 2001 From: probonopd Date: Sat, 9 Nov 2024 11:49:49 +0100 Subject: [PATCH 20/57] circle: git checkout 59484ba (develop) --- submod.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/submod.sh b/submod.sh index b18a43df..02a9e364 100755 --- a/submod.sh +++ b/submod.sh @@ -12,7 +12,7 @@ cd - # # Optional update submodules explicitly cd circle-stdlib/libs/circle -git checkout c243194 +git checkout 59484ba cd - cd circle-stdlib/libs/circle-newlib #git checkout develop From c5e338e1fc68597ef784dcd7610f92f847b1b62c Mon Sep 17 00:00:00 2001 From: probonopd Date: Sat, 9 Nov 2024 11:58:45 +0100 Subject: [PATCH 21/57] Update submod.sh --- submod.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/submod.sh b/submod.sh index 02a9e364..0f7a8696 100755 --- a/submod.sh +++ b/submod.sh @@ -6,13 +6,13 @@ git submodule update --init --recursive # # Use fixed master branch of circle-stdlib then re-update cd circle-stdlib/ -git checkout 695ab4a +git checkout 3bd135d git submodule update --init --recursive cd - # # Optional update submodules explicitly cd circle-stdlib/libs/circle -git checkout 59484ba +git checkout 3bd135d cd - cd circle-stdlib/libs/circle-newlib #git checkout develop From 8d26e20441f65e43d95d8acf8b80ae7f872c408a Mon Sep 17 00:00:00 2001 From: probonopd Date: Sat, 9 Nov 2024 11:59:59 +0100 Subject: [PATCH 22/57] circle: git checkout c243194 (master) --- submod.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/submod.sh b/submod.sh index 0f7a8696..78531dcc 100755 --- a/submod.sh +++ b/submod.sh @@ -12,7 +12,7 @@ cd - # # Optional update submodules explicitly cd circle-stdlib/libs/circle -git checkout 3bd135d +git checkout c243194 cd - cd circle-stdlib/libs/circle-newlib #git checkout develop From 2a6d966aa16047420508c35afd863ae67c4ae52e Mon Sep 17 00:00:00 2001 From: probonopd Date: Sat, 9 Nov 2024 12:38:01 +0100 Subject: [PATCH 23/57] Use "MiniDexed" as the mDNS-SD service name --- src/minidexed.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/minidexed.cpp b/src/minidexed.cpp index c2105368..5059ea05 100644 --- a/src/minidexed.cpp +++ b/src/minidexed.cpp @@ -2219,7 +2219,7 @@ void CMiniDexed::UpdateNetwork() CmDNSPublisher *pmDNSPublisher = new CmDNSPublisher (m_pNet); assert (pmDNSPublisher); - static const char ServiceName[] = "minidexed-rtpmidi"; + static const char ServiceName[] = "MiniDexed"; static const char *ppText[] = {"RTP-MIDI Receiver", nullptr}; // TXT record strings if (!pmDNSPublisher->PublishService (ServiceName, CmDNSPublisher::ServiceTypeAppleMIDI, 5004, ppText)) @@ -2284,4 +2284,4 @@ bool CMiniDexed::InitNetwork() m_pNetDevice = CNetDevice::GetNetDevice(NetDeviceType); } return m_pNet != nullptr; -} \ No newline at end of file +} From a2f8b4c5cfeb8dd54bb9c1ffb153b7789d409acc Mon Sep 17 00:00:00 2001 From: probonopd Date: Sat, 9 Nov 2024 12:45:20 +0100 Subject: [PATCH 24/57] Try to also publish _ftp._tcp --- src/minidexed.cpp | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/minidexed.cpp b/src/minidexed.cpp index 5059ea05..2b4127df 100644 --- a/src/minidexed.cpp +++ b/src/minidexed.cpp @@ -2226,6 +2226,13 @@ void CMiniDexed::UpdateNetwork() { LOGPANIC ("Cannot publish mdns service"); } + static const char *ppText2[] = {"FTP Server", nullptr}; // TXT record strings + static constexpr const char *ServiceTypeFTP = "_ftp._tcp"; + if (!pmDNSPublisher->PublishService (ServiceName, CmDNSPublisher::ServiceTypeFTP, + 21, ppText2)) + { + LOGPANIC ("Cannot publish mdns service"); + } } else if (m_bNetworkReady && !bNetIsRunning) { From 20a6ec8cd3f117fbd84cf144eff2db1f0d564946 Mon Sep 17 00:00:00 2001 From: probonopd Date: Sat, 9 Nov 2024 12:51:58 +0100 Subject: [PATCH 25/57] ServiceTypeFTP --- src/minidexed.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/minidexed.cpp b/src/minidexed.cpp index 2b4127df..ddde6841 100644 --- a/src/minidexed.cpp +++ b/src/minidexed.cpp @@ -2228,7 +2228,7 @@ void CMiniDexed::UpdateNetwork() } static const char *ppText2[] = {"FTP Server", nullptr}; // TXT record strings static constexpr const char *ServiceTypeFTP = "_ftp._tcp"; - if (!pmDNSPublisher->PublishService (ServiceName, CmDNSPublisher::ServiceTypeFTP, + if (!pmDNSPublisher->PublishService (ServiceName, ServiceTypeFTP, 21, ppText2)) { LOGPANIC ("Cannot publish mdns service"); From 0e484ac6d131e61a25640892bd2414cfe306c231 Mon Sep 17 00:00:00 2001 From: probonopd Date: Sat, 9 Nov 2024 17:16:46 +0100 Subject: [PATCH 26/57] Shorten mDNS interval to 15 seconds --- .github/workflows/build.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index ff616dde..79de46a3 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -19,6 +19,10 @@ jobs: - name: Get specific commits of git submodules run: | sh -ex ./submod.sh + - name: Apply patches + run: | + # https://github.com/rsta2/circle/discussions/427#discussioncomment-11198505 + sed -i -e 's|TTLShort = 120|TTLShort = 15|g' circle-stdlib/libs/circle/net/mdnspublisher.h - name: Install toolchains run: | set -ex From f7e7556b454718107bc28ae3681cd374afa4054d Mon Sep 17 00:00:00 2001 From: probonopd Date: Sat, 9 Nov 2024 17:26:01 +0100 Subject: [PATCH 27/57] Patch include/circle/net/mdnspublisher.h --- .github/workflows/build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 79de46a3..a960de17 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -22,7 +22,7 @@ jobs: - name: Apply patches run: | # https://github.com/rsta2/circle/discussions/427#discussioncomment-11198505 - sed -i -e 's|TTLShort = 120|TTLShort = 15|g' circle-stdlib/libs/circle/net/mdnspublisher.h + sed -i -e 's|TTLShort = 120|TTLShort = 15|g' circle-stdlib/libs/circle/include/circle/net/mdnspublisher.h - name: Install toolchains run: | set -ex From bf3a93b7730903a859efcc69ac288c41f56c1b6e Mon Sep 17 00:00:00 2001 From: probonopd Date: Sat, 9 Nov 2024 18:00:39 +0100 Subject: [PATCH 28/57] Put git hash in startup message --- .github/workflows/build.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index a960de17..f88831d9 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -21,6 +21,8 @@ jobs: sh -ex ./submod.sh - name: Apply patches run: | + # Put git hash in startup message + sed -i "s/Loading.../$(git rev-parse --short=7 HEAD)/g" src/userinterface.c # https://github.com/rsta2/circle/discussions/427#discussioncomment-11198505 sed -i -e 's|TTLShort = 120|TTLShort = 15|g' circle-stdlib/libs/circle/include/circle/net/mdnspublisher.h - name: Install toolchains From 3e64e44152c2b33690f324fbe6e0f21303fea720 Mon Sep 17 00:00:00 2001 From: probonopd Date: Sat, 9 Nov 2024 18:04:03 +0100 Subject: [PATCH 29/57] Patch src/userinterface.cpp --- .github/workflows/build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index f88831d9..20ca9e87 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -22,7 +22,7 @@ jobs: - name: Apply patches run: | # Put git hash in startup message - sed -i "s/Loading.../$(git rev-parse --short=7 HEAD)/g" src/userinterface.c + sed -i "s/Loading.../$(git rev-parse --short=7 HEAD)/g" src/userinterface.cpp # https://github.com/rsta2/circle/discussions/427#discussioncomment-11198505 sed -i -e 's|TTLShort = 120|TTLShort = 15|g' circle-stdlib/libs/circle/include/circle/net/mdnspublisher.h - name: Install toolchains From 29b5d05bd4452ec2b9beacc5060d85c81492ac6c Mon Sep 17 00:00:00 2001 From: probonopd Date: Sat, 9 Nov 2024 18:34:18 +0100 Subject: [PATCH 30/57] Put build date into startup message --- .github/workflows/build.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 20ca9e87..c8199392 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -22,7 +22,7 @@ jobs: - name: Apply patches run: | # Put git hash in startup message - sed -i "s/Loading.../$(git rev-parse --short=7 HEAD)/g" src/userinterface.cpp + sed -i "s/Loading.../$(date +%Y%m%d)-$(git rev-parse --short HEAD)/g" src/userinterface.cpp # https://github.com/rsta2/circle/discussions/427#discussioncomment-11198505 sed -i -e 's|TTLShort = 120|TTLShort = 15|g' circle-stdlib/libs/circle/include/circle/net/mdnspublisher.h - name: Install toolchains @@ -91,7 +91,7 @@ jobs: git clone https://github.com/Banana71/Soundplantage --depth 1 # depth 1 means only the latest commit cp -r ./Soundplantage/performance ./Soundplantage/*.pdf ./sdcard/ cd sdcard - zip -r ../MiniDexed_$GITHUB_RUN_NUMBER_$(date +%Y-%m-%d)-$(git rev-parse --short HEAD).zip * + zip -r ../MiniDexed_$GITHUB_RUN_NUMBER_$(date +%Y%m%d)-$(git rev-parse --short HEAD).zip * echo "artifactName=MiniDexed_$GITHUB_RUN_NUMBER_$(date +%Y-%m-%d)-$(git rev-parse --short HEAD)" >> $GITHUB_ENV cd - - uses: actions/upload-artifact@v3 From b822a9742ff2686fdb90137ab2db87e7f94f6602 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=96mer=20=C5=9Eiar=20Baysal?= Date: Sat, 9 Nov 2024 21:53:42 +0100 Subject: [PATCH 31/57] Rebased network changes (#751) * add initial mdns responder for rtp-midi aka applemidi * do not reinitialized network * continue publishing mdns after network connection reestablishes --- src/minidexed.cpp | 25 ++++++++++++++++++++----- src/minidexed.h | 1 + 2 files changed, 21 insertions(+), 5 deletions(-) diff --git a/src/minidexed.cpp b/src/minidexed.cpp index ddde6841..3c2a1830 100644 --- a/src/minidexed.cpp +++ b/src/minidexed.cpp @@ -32,6 +32,7 @@ const char WLANFirmwarePath[] = "SD:firmware/"; const char WLANConfigFile[] = "SD:wpa_supplicant.conf"; #define FTPUSERNAME "admin" #define FTPPASSWORD "admin" +#define MDNSSERVICENAME "MiniDexed" LOGMODULE ("minidexed"); @@ -62,6 +63,7 @@ CMiniDexed::CMiniDexed (CConfig *pConfig, CInterruptSystem *pInterrupt, m_WPASupplicant(WLANConfigFile), m_bNetworkReady(false), m_UDPMIDI (this, pConfig, &m_UI), + m_pmDNSPublisher (), m_bSavePerformance (false), m_bSavePerformanceNewFile (false), m_bSetNewPerformance (false), @@ -2188,10 +2190,14 @@ void CMiniDexed::UpdateNetwork() if (!m_pNet) return; + //add wired network check as well bool bNetIsRunning = m_pNet->IsRunning(); - bNetIsRunning &= m_WPASupplicant.IsConnected(); - - if (!m_bNetworkReady && bNetIsRunning) + if ((strcmp(m_pConfig->GetNetworkType(), "ethernet") == 0)) + bNetIsRunning &= m_pNetDevice->IsLinkUp(); + else if ((strcmp(m_pConfig->GetNetworkType(), "wifi") == 0)) + bNetIsRunning &= m_WPASupplicant.IsConnected(); + + if (!m_bNetworkReady && (m_pNet->IsRunning())) { m_bNetworkReady = true; CString IPString; @@ -2199,7 +2205,6 @@ void CMiniDexed::UpdateNetwork() //LOGNOTE("Network up and running at: %s", static_cast(IPString)); - m_UDPMIDI.Initialize(); m_pFTPDaemon = new CFTPDaemon(FTPUSERNAME, FTPPASSWORD); @@ -2236,8 +2241,18 @@ void CMiniDexed::UpdateNetwork() } else if (m_bNetworkReady && !bNetIsRunning) { - m_bNetworkReady = false; + //m_bNetworkReady = false; + m_pmDNSPublisher->UnpublishService (MDNSSERVICENAME); LOGNOTE("Network disconnected."); + } + else if (m_bNetworkReady && bNetIsRunning) + { + if (!m_pmDNSPublisher->PublishService (MDNSSERVICENAME, CmDNSPublisher::ServiceTypeAppleMIDI, + 5004)) + { + LOGPANIC ("Cannot publish mdns service"); + } + LOGNOTE("Network connection reestablished."); } } diff --git a/src/minidexed.h b/src/minidexed.h index 8466eb02..7a691c4b 100644 --- a/src/minidexed.h +++ b/src/minidexed.h @@ -340,6 +340,7 @@ class CMiniDexed bool m_bNetworkReady; CUDPMIDIDevice m_UDPMIDI; CFTPDaemon* m_pFTPDaemon; + CmDNSPublisher *m_pmDNSPublisher; bool m_bSavePerformance; bool m_bSavePerformanceNewFile; From a95d6e0c65b5a2da47cc95707b7cc5d55bf1007b Mon Sep 17 00:00:00 2001 From: probonopd Date: Thu, 14 Nov 2024 18:43:43 +0100 Subject: [PATCH 32/57] Network (#753) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * add initial mdns responder for rtp-midi aka applemidi * do not reinitialized network * continue publishing mdns after network connection reestablishes --------- Co-authored-by: Ömer Şiar Baysal From 024d23a69d2501a0a2db6626629953c5c19f0c1e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=96mer=20=C5=9Eiar=20Baysal?= Date: Fri, 15 Nov 2024 17:29:57 +0100 Subject: [PATCH 33/57] Step47 Network Support without mDNS (#757) * add initial mdns responder for rtp-midi aka applemidi * do not reinitialized network * continue publishing mdns after network connection reestablishes * remote mdnsresponder and revert to step48 * disable mdns TTL patching for now --- .github/workflows/build.yml | 2 +- src/minidexed.cpp | 55 +++++++++++++++------------ src/minidexed.h | 5 ++- src/net/applemidi.cpp | 2 +- src/net/ftpworker.cpp | 7 +++- src/rtpmididevice.cpp | 74 ------------------------------------- src/rtpmididevice.h | 49 ------------------------ src/udpmididevice.cpp | 7 ++-- src/udpmididevice.h | 5 +-- submod.sh | 4 +- 10 files changed, 51 insertions(+), 159 deletions(-) delete mode 100644 src/rtpmididevice.cpp delete mode 100644 src/rtpmididevice.h diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index c8199392..c3a85ab8 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -24,7 +24,7 @@ jobs: # Put git hash in startup message sed -i "s/Loading.../$(date +%Y%m%d)-$(git rev-parse --short HEAD)/g" src/userinterface.cpp # https://github.com/rsta2/circle/discussions/427#discussioncomment-11198505 - sed -i -e 's|TTLShort = 120|TTLShort = 15|g' circle-stdlib/libs/circle/include/circle/net/mdnspublisher.h + # sed -i -e 's|TTLShort = 120|TTLShort = 15|g' circle-stdlib/libs/circle/include/circle/net/mdnspublisher.h - name: Install toolchains run: | set -ex diff --git a/src/minidexed.cpp b/src/minidexed.cpp index 3c2a1830..64ad2cfa 100644 --- a/src/minidexed.cpp +++ b/src/minidexed.cpp @@ -62,8 +62,9 @@ CMiniDexed::CMiniDexed (CConfig *pConfig, CInterruptSystem *pInterrupt, m_WLAN(WLANFirmwarePath), m_WPASupplicant(WLANConfigFile), m_bNetworkReady(false), + m_bNetworkInit(false), m_UDPMIDI (this, pConfig, &m_UI), - m_pmDNSPublisher (), + //m_pmDNSPublisher (nullptr), m_bSavePerformance (false), m_bSavePerformanceNewFile (false), m_bSetNewPerformance (false), @@ -367,12 +368,14 @@ void CMiniDexed::Process (bool bPlugAndPlayUpdated) CScheduler* const pScheduler = CScheduler::Get(); #ifndef ARM_ALLOW_MULTI_CORE ProcessSound (); + pScheduler->Yield(); #endif for (unsigned i = 0; i < CConfig::MaxUSBMIDIDevices; i++) { assert (m_pMIDIKeyboard[i]); m_pMIDIKeyboard[i]->Process (bPlugAndPlayUpdated); + pScheduler->Yield(); } m_PCKeyboard.Process (bPlugAndPlayUpdated); @@ -380,6 +383,7 @@ void CMiniDexed::Process (bool bPlugAndPlayUpdated) if (m_bUseSerial) { m_SerialMIDI.Process (); + pScheduler->Yield(); } m_UI.Process (); @@ -389,12 +393,14 @@ void CMiniDexed::Process (bool bPlugAndPlayUpdated) DoSavePerformance (); m_bSavePerformance = false; + pScheduler->Yield(); } if (m_bSavePerformanceNewFile) { DoSavePerformanceNewFile (); m_bSavePerformanceNewFile = false; + pScheduler->Yield(); } if (m_bSetNewPerformanceBank && !m_bLoadPerformanceBusy && !m_bLoadPerformanceBankBusy) @@ -412,6 +418,7 @@ void CMiniDexed::Process (bool bPlugAndPlayUpdated) { DoSetFirstPerformance(); } + pScheduler->Yield(); } if (m_bSetNewPerformance && !m_bSetNewPerformanceBank && !m_bLoadPerformanceBusy && !m_bLoadPerformanceBankBusy) @@ -421,17 +428,20 @@ void CMiniDexed::Process (bool bPlugAndPlayUpdated) { m_bSetNewPerformance = false; } + pScheduler->Yield(); } if(m_bDeletePerformance) { DoDeletePerformance (); m_bDeletePerformance = false; + pScheduler->Yield(); } if (m_bProfileEnabled) { m_GetChunkTimer.Dump (); + pScheduler->Yield(); } UpdateNetwork(); // Allow other tasks to run @@ -2190,16 +2200,17 @@ void CMiniDexed::UpdateNetwork() if (!m_pNet) return; + //add wired network check as well //add wired network check as well bool bNetIsRunning = m_pNet->IsRunning(); - if ((strcmp(m_pConfig->GetNetworkType(), "ethernet") == 0)) + if (m_pNetDevice->GetType() == NetDeviceTypeEthernet) bNetIsRunning &= m_pNetDevice->IsLinkUp(); - else if ((strcmp(m_pConfig->GetNetworkType(), "wifi") == 0)) + else if (m_pNetDevice->GetType() == NetDeviceTypeWLAN) bNetIsRunning &= m_WPASupplicant.IsConnected(); - if (!m_bNetworkReady && (m_pNet->IsRunning())) + if (!m_bNetworkInit) { - m_bNetworkReady = true; + m_bNetworkInit = true; CString IPString; m_pNet->GetConfig()->GetIPAddress()->Format(&IPString); @@ -2219,39 +2230,37 @@ void CMiniDexed::UpdateNetwork() { LOGNOTE("FTP daemon initialized"); } - m_UI.DisplayWrite ("IP", "Network", IPString, 0, 1); - CmDNSPublisher *pmDNSPublisher = new CmDNSPublisher (m_pNet); - assert (pmDNSPublisher); - static const char ServiceName[] = "MiniDexed"; - static const char *ppText[] = {"RTP-MIDI Receiver", nullptr}; // TXT record strings - if (!pmDNSPublisher->PublishService (ServiceName, CmDNSPublisher::ServiceTypeAppleMIDI, - 5004, ppText)) - { - LOGPANIC ("Cannot publish mdns service"); - } - static const char *ppText2[] = {"FTP Server", nullptr}; // TXT record strings - static constexpr const char *ServiceTypeFTP = "_ftp._tcp"; - if (!pmDNSPublisher->PublishService (ServiceName, ServiceTypeFTP, - 21, ppText2)) + /*m_pmDNSPublisher = new CmDNSPublisher (m_pNet); + assert (m_pmDNSPublisher); + + //static const char *ppText[] = {"RTP-MIDI Receiver", nullptr}; // dont bother adding additional data + if (!m_pmDNSPublisher->PublishService (MDNSSERVICENAME, CmDNSPublisher::ServiceTypeAppleMIDI, + 5004)) { LOGPANIC ("Cannot publish mdns service"); } + */ + m_bNetworkReady = true; } - else if (m_bNetworkReady && !bNetIsRunning) + + if (m_bNetworkReady && !bNetIsRunning) { - //m_bNetworkReady = false; - m_pmDNSPublisher->UnpublishService (MDNSSERVICENAME); + m_bNetworkReady = false; + //m_pmDNSPublisher->UnpublishService (MDNSSERVICENAME); LOGNOTE("Network disconnected."); } - else if (m_bNetworkReady && bNetIsRunning) + else if (!m_bNetworkReady && bNetIsRunning) { + m_bNetworkReady = true; + /* if (!m_pmDNSPublisher->PublishService (MDNSSERVICENAME, CmDNSPublisher::ServiceTypeAppleMIDI, 5004)) { LOGPANIC ("Cannot publish mdns service"); } + */ LOGNOTE("Network connection reestablished."); } diff --git a/src/minidexed.h b/src/minidexed.h index 7a691c4b..0c29978b 100644 --- a/src/minidexed.h +++ b/src/minidexed.h @@ -43,7 +43,7 @@ #include #include #include -#include +//#include #include #include "common.h" #include "effect_mixer.hpp" @@ -338,9 +338,10 @@ class CMiniDexed CBcm4343Device m_WLAN; CWPASupplicant m_WPASupplicant; bool m_bNetworkReady; + bool m_bNetworkInit; CUDPMIDIDevice m_UDPMIDI; CFTPDaemon* m_pFTPDaemon; - CmDNSPublisher *m_pmDNSPublisher; + //CmDNSPublisher *m_pmDNSPublisher; bool m_bSavePerformance; bool m_bSavePerformanceNewFile; diff --git a/src/net/applemidi.cpp b/src/net/applemidi.cpp index 3c96629f..e14b2163 100644 --- a/src/net/applemidi.cpp +++ b/src/net/applemidi.cpp @@ -803,7 +803,7 @@ bool CAppleMIDIParticipant::SendAcceptInvitationPacket(CSocket* pSocket, CIPAddr }; // TODO: configurable name - strncpy(AcceptPacket.Name, "minidexed", sizeof(AcceptPacket.Name)); + strncpy(AcceptPacket.Name, "MiniDexed", sizeof(AcceptPacket.Name)); #ifdef APPLEMIDI_DEBUG LOGNOTE("--> Accept invitation"); diff --git a/src/net/ftpworker.cpp b/src/net/ftpworker.cpp index f61b07b4..f7438ce4 100644 --- a/src/net/ftpworker.cpp +++ b/src/net/ftpworker.cpp @@ -61,6 +61,7 @@ struct TDirectoryListEntry u32 nSize; u16 nLastModifedDate; u16 nLastModifedTime; + //bool bHid; }; using TCommandHandler = bool (CFTPWorker::*)(const char* pArgs); @@ -422,6 +423,7 @@ const TDirectoryListEntry* CFTPWorker::BuildDirectoryList(size_t& nOutEntries) c { Entry.Type = TDirectoryListEntryType::File; Entry.nSize = FileInfo.fsize; + //Entry.bHid = (FileInfo.fattrib & AM_HID) ? true : false; } Entry.nLastModifedDate = FileInfo.fdate; @@ -984,7 +986,10 @@ bool CFTPWorker::ListFileNames(const char* pArgs) const TDirectoryListEntry& Entry = pDirEntries[i]; if (Entry.Type == TDirectoryListEntryType::Directory) continue; - + /*if (Entry.Name == "wpa_supplicant.conf") + { + continue; + }*/ const int nLength = snprintf(Buffer, sizeof(Buffer), "%s\r\n", Entry.Name); if (pDataSocket->Send(Buffer, nLength, 0) < 0) { diff --git a/src/rtpmididevice.cpp b/src/rtpmididevice.cpp deleted file mode 100644 index 572a5c91..00000000 --- a/src/rtpmididevice.cpp +++ /dev/null @@ -1,74 +0,0 @@ -// -// serialmididevice.cpp -// -// MiniDexed - Dexed FM synthesizer for bare metal Raspberry Pi -// Copyright (C) 2022 The MiniDexed Team -// -// Original author of this class: -// R. Stange -// -// 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 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see . -// - -#include -#include -#include "rtpmididevice.h" -#include - -#define VIRTUALCABLE 24 - -LOGMODULE("rtpmididevice"); - -CRTPMIDIDevice::CRTPMIDIDevice (CMiniDexed *pSynthesizer, - CConfig *pConfig, CUserInterface *pUI) -: CMIDIDevice (pSynthesizer, pConfig, pUI), - m_pConfig (pConfig) -{ - AddDevice ("rtpdummy"); -} - -CRTPMIDIDevice::~CRTPMIDIDevice (void) -{ - -} - -boolean CRTPMIDIDevice::Initialize (void) -{ - m_pAppleMIDIParticipant = new CAppleMIDIParticipant(&m_Random, this); - if (!m_pAppleMIDIParticipant->Initialize()) - { - LOGERR("Failed to init RTP listener"); - return false; //continue without rtp midi - } - else - LOGNOTE("RTP Listener initialized"); - return true; -} - -// Methods to handle MIDI events - -void CRTPMIDIDevice::OnAppleMIDIDataReceived(const u8* pData, size_t nSize) -{ - MIDIMessageHandler(pData, nSize, VIRTUALCABLE); -} - -void CRTPMIDIDevice::OnAppleMIDIConnect(const CIPAddress* pIPAddress, const char* pName) -{ - LOGNOTE("RTP Device connected"); -} - -void CRTPMIDIDevice::OnAppleMIDIDisconnect(const CIPAddress* pIPAddress, const char* pName) -{ - // RemoveRTPDevice -} diff --git a/src/rtpmididevice.h b/src/rtpmididevice.h deleted file mode 100644 index 870301bd..00000000 --- a/src/rtpmididevice.h +++ /dev/null @@ -1,49 +0,0 @@ -// -// .h -// -// MiniDexed - Dexed FM synthesizer for bare metal Raspberry Pi -// Copyright (C) 2022 The MiniDexed Team -// -// Original author of this class: -// R. Stange -// -// 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 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see . -// -#ifndef _rtpmididevice_h -#define _rtpmididevice_h - -#include "mididevice.h" -#include "config.h" -#include "net/applemidi.h" - -class CMiniDexed; - -class CRTPMIDIDevice : CAppleMIDIHandler, CMIDIDevice -{ -public: - CRTPMIDIDevice (CMiniDexed *pSynthesizer, CConfig *pConfig, CUserInterface *pUI); - ~CRTPMIDIDevice (void); - - boolean Initialize (void); - virtual void OnAppleMIDIDataReceived(const u8* pData, size_t nSize) override; - virtual void OnAppleMIDIConnect(const CIPAddress* pIPAddress, const char* pName) override; - virtual void OnAppleMIDIDisconnect(const CIPAddress* pIPAddress, const char* pName) override; - -private: - CConfig *m_pConfig; - CBcmRandomNumberGenerator m_Random; - CAppleMIDIParticipant* m_pAppleMIDIParticipant; -}; - -#endif diff --git a/src/udpmididevice.cpp b/src/udpmididevice.cpp index 6080132b..4b0d1c7a 100644 --- a/src/udpmididevice.cpp +++ b/src/udpmididevice.cpp @@ -28,7 +28,7 @@ #define VIRTUALCABLE 24 -LOGMODULE("rtpmididevice"); +LOGMODULE("udpmididevice"); CUDPMIDIDevice::CUDPMIDIDevice (CMiniDexed *pSynthesizer, CConfig *pConfig, CUserInterface *pUI) @@ -50,11 +50,11 @@ boolean CUDPMIDIDevice::Initialize (void) if (!m_pAppleMIDIParticipant->Initialize()) { LOGERR("Failed to init RTP listener"); - return false; //continue without rtp midi + delete m_pAppleMIDIParticipant; + m_pAppleMIDIParticipant = nullptr; } else LOGNOTE("RTP Listener initialized"); - return true; m_pUDPMIDIReceiver = new CUDPMIDIReceiver(this); if (!m_pUDPMIDIReceiver->Initialize()) { @@ -64,6 +64,7 @@ boolean CUDPMIDIDevice::Initialize (void) } else LOGNOTE("UDP MIDI receiver initialized"); + return true; } // Methods to handle MIDI events diff --git a/src/udpmididevice.h b/src/udpmididevice.h index 55bc7a23..de50172b 100644 --- a/src/udpmididevice.h +++ b/src/udpmididevice.h @@ -22,8 +22,8 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . // -#ifndef _rtpmididevice_h -#define _rtpmididevice_h +#ifndef _udpmididevice_h +#define _udpmididevice_h #include "mididevice.h" #include "config.h" @@ -50,7 +50,6 @@ class CUDPMIDIDevice : CAppleMIDIHandler, CUDPMIDIHandler, public CMIDIDevice CBcmRandomNumberGenerator m_Random; CAppleMIDIParticipant* m_pAppleMIDIParticipant; // AppleMIDI participant instance CUDPMIDIReceiver* m_pUDPMIDIReceiver; - }; #endif diff --git a/submod.sh b/submod.sh index 78531dcc..03bd0f0f 100755 --- a/submod.sh +++ b/submod.sh @@ -6,13 +6,13 @@ git submodule update --init --recursive # # Use fixed master branch of circle-stdlib then re-update cd circle-stdlib/ -git checkout 3bd135d +git checkout tags/v16.5 git submodule update --init --recursive cd - # # Optional update submodules explicitly cd circle-stdlib/libs/circle -git checkout c243194 +git checkout tags/Step47 cd - cd circle-stdlib/libs/circle-newlib #git checkout develop From d331dfa03e954a75c8a9abe56543ea5d72abf222 Mon Sep 17 00:00:00 2001 From: probonopd Date: Sat, 16 Nov 2024 10:36:36 +0100 Subject: [PATCH 34/57] Display IP address in upper line of the display --- src/minidexed.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/minidexed.cpp b/src/minidexed.cpp index 64ad2cfa..26c628a7 100644 --- a/src/minidexed.cpp +++ b/src/minidexed.cpp @@ -2230,7 +2230,7 @@ void CMiniDexed::UpdateNetwork() { LOGNOTE("FTP daemon initialized"); } - m_UI.DisplayWrite ("IP", "Network", IPString, 0, 1); + m_UI.DisplayWrite (IPString, "IP", "TG1", 0, 1); // FIXME: Do not hardcode "TG1" here /*m_pmDNSPublisher = new CmDNSPublisher (m_pNet); assert (m_pmDNSPublisher); From 9fcacb24a219d77ae7a3b2c4b3a61585dbbffdc6 Mon Sep 17 00:00:00 2001 From: probonopd Date: Sat, 16 Nov 2024 13:29:32 +0100 Subject: [PATCH 35/57] Don't write "IP" to save space for the IP address --- src/minidexed.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/minidexed.cpp b/src/minidexed.cpp index 26c628a7..c198c6fd 100644 --- a/src/minidexed.cpp +++ b/src/minidexed.cpp @@ -2230,7 +2230,7 @@ void CMiniDexed::UpdateNetwork() { LOGNOTE("FTP daemon initialized"); } - m_UI.DisplayWrite (IPString, "IP", "TG1", 0, 1); // FIXME: Do not hardcode "TG1" here + m_UI.DisplayWrite (IPString, "", "TG1", 0, 1); // FIXME: Do not hardcode "TG1" here /*m_pmDNSPublisher = new CmDNSPublisher (m_pNet); assert (m_pmDNSPublisher); From 3c35244ddbe4dceac4473f3cbb75afd10c4a3a45 Mon Sep 17 00:00:00 2001 From: probonopd Date: Sat, 16 Nov 2024 17:53:29 +0100 Subject: [PATCH 36/57] NetworkType ( wifi ; ethernet ) [ci skip] --- src/minidexed.ini | 1 + 1 file changed, 1 insertion(+) diff --git a/src/minidexed.ini b/src/minidexed.ini index 3c033273..fa25ba11 100644 --- a/src/minidexed.ini +++ b/src/minidexed.ini @@ -146,6 +146,7 @@ ProfileEnabled=0 # Network NetworkEnabled=0 NetworkDHCP=1 +# NetworkType ( wifi ; ethernet ) NetworkType=wifi # Performance From 02b709df31b1cc27422d04d39ca48f75b971136b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=96mer=20=C5=9Eiar=20Baysal?= Date: Sat, 16 Nov 2024 19:20:12 +0100 Subject: [PATCH 37/57] Backport mdnspublisher from Circle (#761) * add initial mdns responder for rtp-midi aka applemidi * do not reinitialized network * continue publishing mdns after network connection reestablishes * remote mdnsresponder and revert to step48 * disable mdns TTL patching for now * backport mdnspublisher * re-enable build and fix TTLShort --- .github/workflows/build.yml | 2 +- src/Makefile | 2 +- src/minidexed.cpp | 12 +- src/minidexed.h | 4 +- src/net/mdnspublisher.cpp | 345 ++++++++++++++++++++++++++++++++++++ src/net/mdnspublisher.h | 90 ++++++++++ 6 files changed, 445 insertions(+), 10 deletions(-) create mode 100644 src/net/mdnspublisher.cpp create mode 100644 src/net/mdnspublisher.h diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index dc5ad014..9a0381e6 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -24,7 +24,7 @@ jobs: # Put git hash in startup message sed -i "s/Loading.../$(date +%Y%m%d)-$(git rev-parse --short HEAD)/g" src/userinterface.cpp # https://github.com/rsta2/circle/discussions/427#discussioncomment-11198505 - # sed -i -e 's|TTLShort = 120|TTLShort = 15|g' circle-stdlib/libs/circle/include/circle/net/mdnspublisher.h + sed -i -e 's|TTLShort = 120|TTLShort = 15|g' circle-stdlib/libs/circle/include/circle/net/mdnspublisher.h - name: Install toolchains run: | set -ex diff --git a/src/Makefile b/src/Makefile index fa4ab107..ed5e0503 100644 --- a/src/Makefile +++ b/src/Makefile @@ -11,7 +11,7 @@ OBJS = main.o kernel.o minidexed.o config.o userinterface.o uimenu.o \ mididevice.o udpmididevice.o midikeyboard.o serialmididevice.o pckeyboard.o \ sysexfileloader.o performanceconfig.o perftimer.o \ effect_compressor.o effect_platervbstereo.o uibuttons.o midipin.o \ - net/ftpdaemon.o net/ftpworker.o net/applemidi.o net/udpmidi.o + net/ftpdaemon.o net/ftpworker.o net/applemidi.o net/udpmidi.o net/mdnspublisher.o OPTIMIZE = -O3 diff --git a/src/minidexed.cpp b/src/minidexed.cpp index c198c6fd..14d0c3f5 100644 --- a/src/minidexed.cpp +++ b/src/minidexed.cpp @@ -64,7 +64,7 @@ CMiniDexed::CMiniDexed (CConfig *pConfig, CInterruptSystem *pInterrupt, m_bNetworkReady(false), m_bNetworkInit(false), m_UDPMIDI (this, pConfig, &m_UI), - //m_pmDNSPublisher (nullptr), + m_pmDNSPublisher (nullptr), m_bSavePerformance (false), m_bSavePerformanceNewFile (false), m_bSetNewPerformance (false), @@ -2232,7 +2232,7 @@ void CMiniDexed::UpdateNetwork() } m_UI.DisplayWrite (IPString, "", "TG1", 0, 1); // FIXME: Do not hardcode "TG1" here - /*m_pmDNSPublisher = new CmDNSPublisher (m_pNet); + m_pmDNSPublisher = new CmDNSPublisher (m_pNet); assert (m_pmDNSPublisher); //static const char *ppText[] = {"RTP-MIDI Receiver", nullptr}; // dont bother adding additional data @@ -2241,26 +2241,26 @@ void CMiniDexed::UpdateNetwork() { LOGPANIC ("Cannot publish mdns service"); } - */ + m_bNetworkReady = true; } if (m_bNetworkReady && !bNetIsRunning) { m_bNetworkReady = false; - //m_pmDNSPublisher->UnpublishService (MDNSSERVICENAME); + m_pmDNSPublisher->UnpublishService (MDNSSERVICENAME); LOGNOTE("Network disconnected."); } else if (!m_bNetworkReady && bNetIsRunning) { m_bNetworkReady = true; - /* + if (!m_pmDNSPublisher->PublishService (MDNSSERVICENAME, CmDNSPublisher::ServiceTypeAppleMIDI, 5004)) { LOGPANIC ("Cannot publish mdns service"); } - */ + LOGNOTE("Network connection reestablished."); } diff --git a/src/minidexed.h b/src/minidexed.h index 0c29978b..79e857df 100644 --- a/src/minidexed.h +++ b/src/minidexed.h @@ -43,7 +43,7 @@ #include #include #include -//#include +#include "net/mdnspublisher.h" #include #include "common.h" #include "effect_mixer.hpp" @@ -341,7 +341,7 @@ class CMiniDexed bool m_bNetworkInit; CUDPMIDIDevice m_UDPMIDI; CFTPDaemon* m_pFTPDaemon; - //CmDNSPublisher *m_pmDNSPublisher; + CmDNSPublisher *m_pmDNSPublisher; bool m_bSavePerformance; bool m_bSavePerformanceNewFile; diff --git a/src/net/mdnspublisher.cpp b/src/net/mdnspublisher.cpp new file mode 100644 index 00000000..23052db7 --- /dev/null +++ b/src/net/mdnspublisher.cpp @@ -0,0 +1,345 @@ +// +// mdnspublisher.cpp +// +// Circle - A C++ bare metal environment for Raspberry Pi +// Copyright (C) 2024 R. Stange +// +// 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 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// +#include "mdnspublisher.h" +#include +#include +#include +#include +#include +#define MDNS_HOST_GROUP {224, 0, 0, 251} +#define MDNS_PORT 5353 +#define MDNS_DOMAIN "local" +#define RR_TYPE_A 1 +#define RR_TYPE_PTR 12 +#define RR_TYPE_TXT 16 +#define RR_TYPE_SRV 33 +#define RR_CLASS_IN 1 +#define RR_CACHE_FLUSH 0x8000 +LOGMODULE ("mdnspub"); +CmDNSPublisher::CmDNSPublisher (CNetSubSystem *pNet) +: m_pNet (pNet), + m_pSocket (nullptr), + m_bRunning (FALSE), + m_pWritePtr (nullptr), + m_pDataLen (nullptr) +{ + SetName ("mdnspub"); +} +CmDNSPublisher::~CmDNSPublisher (void) +{ + assert (!m_pSocket); + m_bRunning = FALSE; +} +boolean CmDNSPublisher::PublishService (const char *pServiceName, const char *pServiceType, + u16 usServicePort, const char *ppText[]) +{ + if (!m_bRunning) + { + // Let task can run once to initialize + CScheduler::Get ()->Yield (); + if (!m_bRunning) + { + return FALSE; + } + } + assert (pServiceName); + assert (pServiceType); + TService *pService = new TService {pServiceName, pServiceType, usServicePort, 0}; + assert (pService); + if (ppText) + { + for (unsigned i = 0; i < MaxTextRecords && ppText[i]; i++) + { + pService->ppText[i] = new CString (ppText[i]); + assert (pService->ppText[i]); + pService->nTextRecords++; + } + } + m_Mutex.Acquire (); + // Insert as first element into list + TPtrListElement *pElement = m_ServiceList.GetFirst (); + if (pElement) + { + m_ServiceList.InsertBefore (pElement, pService); + } + else + { + m_ServiceList.InsertAfter (nullptr, pService); + } + m_Mutex.Release (); + LOGDBG ("Publish service %s", (const char *) pService->ServiceName); + m_Event.Set (); // Trigger resent for everything + return TRUE; +} +boolean CmDNSPublisher::UnpublishService (const char *pServiceName) +{ + if (!m_bRunning) + { + return FALSE; + } + assert (pServiceName); + m_Mutex.Acquire (); + // Find service in the list and remove it + TService *pService = nullptr; + TPtrListElement *pElement = m_ServiceList.GetFirst (); + while (pElement) + { + pService = static_cast (CPtrList::GetPtr (pElement)); + assert (pService); + if (pService->ServiceName.Compare (pServiceName) == 0) + { + m_ServiceList.Remove (pElement); + break; + } + pService = nullptr; + pElement = m_ServiceList.GetNext (pElement); + } + m_Mutex.Release (); + if (!pService) + { + return FALSE; + } + LOGDBG ("Unpublish service %s", (const char *) pService->ServiceName); + if (!SendResponse (pService, TRUE)) + { + LOGWARN ("Send failed"); + } + for (unsigned i = 0; i < pService->nTextRecords; i++) + { + delete pService->ppText[i]; + } + delete pService; + return TRUE; +} +void CmDNSPublisher::Run (void) +{ + assert (m_pNet); + assert (!m_pSocket); + m_pSocket = new CSocket (m_pNet, IPPROTO_UDP); + assert (m_pSocket); + if (m_pSocket->Bind (MDNS_PORT) < 0) + { + LOGERR ("Cannot bind to port %u", MDNS_PORT); + delete m_pSocket; + m_pSocket = nullptr; + while (1) + { + m_Event.Clear (); + m_Event.Wait (); + } + } + static const u8 mDNSIPAddress[] = MDNS_HOST_GROUP; + CIPAddress mDNSIP (mDNSIPAddress); + if (m_pSocket->Connect (mDNSIP, MDNS_PORT) < 0) + { + LOGERR ("Cannot connect to mDNS host group"); + delete m_pSocket; + m_pSocket = nullptr; + while (1) + { + m_Event.Clear (); + m_Event.Wait (); + } + } + m_bRunning = TRUE; + while (1) + { + m_Event.Clear (); + m_Event.WaitWithTimeout ((TTLShort - 10) * 1000000); + for (unsigned i = 1; i <= 3; i++) + { + m_Mutex.Acquire (); + TPtrListElement *pElement = m_ServiceList.GetFirst (); + while (pElement) + { + TService *pService = + static_cast (CPtrList::GetPtr (pElement)); + assert (pService); + if (!SendResponse (pService, FALSE)) + { + LOGWARN ("Send failed"); + } + pElement = m_ServiceList.GetNext (pElement); + } + m_Mutex.Release (); + CScheduler::Get ()->Sleep (1); + } + } +} +boolean CmDNSPublisher::SendResponse (TService *pService, boolean bDelete) +{ + assert (pService); + assert (m_pNet); + // Collect data + static const char Domain[] = "." MDNS_DOMAIN; + CString ServiceType (pService->ServiceType); + ServiceType.Append (Domain); + CString ServiceName (pService->ServiceName); + ServiceName.Append ("."); + ServiceName.Append (ServiceType); + CString Hostname (m_pNet->GetHostname ()); + Hostname.Append (Domain); + // Start writing buffer + assert (!m_pWritePtr); + m_pWritePtr = m_Buffer; + // mDNS Header + PutWord (0); // Transaction ID + PutWord (0x8400); // Message is a response, Server is an authority for the domain + PutWord (0); // Questions + PutWord (5); // Answer RRs + PutWord (0); // Authority RRs + PutWord (0); // Additional RRs + // Answer RRs + // PTR + PutDNSName ("_services._dns-sd._udp.local"); + PutWord (RR_TYPE_PTR); + PutWord (RR_CLASS_IN); + PutDWord (bDelete ? TTLDelete : TTLLong); + ReserveDataLength (); + u8 *pServiceTypePtr = m_pWritePtr; + PutDNSName (ServiceType); + SetDataLength (); + // PTR + PutCompressedString (pServiceTypePtr); + PutWord (RR_TYPE_PTR); + PutWord (RR_CLASS_IN); + PutDWord (bDelete ? TTLDelete : TTLLong); + ReserveDataLength (); + u8 *pServiceNamePtr = m_pWritePtr; + PutDNSName (ServiceName); + SetDataLength (); + // SRV + PutCompressedString (pServiceNamePtr); + PutWord (RR_TYPE_SRV); + PutWord (RR_CLASS_IN | RR_CACHE_FLUSH); + PutDWord (bDelete ? TTLDelete : TTLShort); + ReserveDataLength (); + PutWord (0); // Priority + PutWord (0); // Weight + PutWord (pService->usServicePort); + u8 *pHostnamePtr = m_pWritePtr; + PutDNSName (Hostname); + SetDataLength (); + // A + PutCompressedString (pHostnamePtr); + PutWord (RR_TYPE_A); + PutWord (RR_CLASS_IN | RR_CACHE_FLUSH); + PutDWord (TTLShort); + ReserveDataLength (); + PutIPAddress (*m_pNet->GetConfig ()->GetIPAddress ()); + SetDataLength (); + // TXT + PutCompressedString (pServiceNamePtr); + PutWord (RR_TYPE_TXT); + PutWord (RR_CLASS_IN | RR_CACHE_FLUSH); + PutDWord (bDelete ? TTLDelete : TTLLong); + ReserveDataLength (); + for (int i = pService->nTextRecords-1; i >= 0; i--) // In reverse order + { + assert (pService->ppText[i]); + PutString (*pService->ppText[i]); + } + SetDataLength (); + unsigned nMsgSize = m_pWritePtr - m_Buffer; + m_pWritePtr = nullptr; + if (nMsgSize >= MaxMessageSize) + { + return FALSE; + } + assert (m_pSocket); + return m_pSocket->Send (m_Buffer, nMsgSize, MSG_DONTWAIT) == (int) nMsgSize; +} +void CmDNSPublisher::PutByte (u8 uchValue) +{ + assert (m_pWritePtr); + if ((unsigned) (m_pWritePtr - m_Buffer) < MaxMessageSize) + { + *m_pWritePtr++ = uchValue; + } +} +void CmDNSPublisher::PutWord (u16 usValue) +{ + PutByte (usValue >> 8); + PutByte (usValue & 0xFF); +} +void CmDNSPublisher::PutDWord (u32 nValue) +{ + PutWord (nValue >> 16); + PutWord (nValue & 0xFFFF); +} +void CmDNSPublisher::PutString (const char *pValue) +{ + assert (pValue); + size_t nLen = strlen (pValue); + assert (nLen <= 255); + PutByte (nLen); + while (*pValue) + { + PutByte (static_cast (*pValue++)); + } +} +void CmDNSPublisher::PutCompressedString (const u8 *pWritePtr) +{ + assert (m_pWritePtr); + assert (pWritePtr < m_pWritePtr); + unsigned nOffset = pWritePtr - m_Buffer; + assert (nOffset < MaxMessageSize); + nOffset |= 0xC000; + PutWord (static_cast (nOffset)); +} +void CmDNSPublisher::PutDNSName (const char *pValue) +{ + char Buffer[256]; + assert (pValue); + strncpy (Buffer, pValue, sizeof Buffer); + Buffer[sizeof Buffer-1] = '\0'; + char *pSavePtr = nullptr; + char *pToken = strtok_r (Buffer, ".", &pSavePtr); + while (pToken) + { + PutString (pToken); + pToken = strtok_r (nullptr, ".", &pSavePtr); + } + PutByte (0); +} +void CmDNSPublisher::PutIPAddress (const CIPAddress &rValue) +{ + u8 Buffer[IP_ADDRESS_SIZE]; + rValue.CopyTo (Buffer); + for (unsigned i = 0; i < IP_ADDRESS_SIZE; i++) + { + PutByte (Buffer[i]); + } +} +void CmDNSPublisher::ReserveDataLength (void) +{ + assert (!m_pDataLen); + m_pDataLen = m_pWritePtr; + assert (m_pDataLen); + PutWord (0); +} +void CmDNSPublisher::SetDataLength (void) +{ + assert (m_pDataLen); + assert (m_pWritePtr); + assert (m_pWritePtr > m_pDataLen); + *reinterpret_cast (m_pDataLen) = le2be16 (m_pWritePtr - m_pDataLen - sizeof (u16)); + m_pDataLen = nullptr; +} \ No newline at end of file diff --git a/src/net/mdnspublisher.h b/src/net/mdnspublisher.h new file mode 100644 index 00000000..6b132a75 --- /dev/null +++ b/src/net/mdnspublisher.h @@ -0,0 +1,90 @@ +// +// mdnspublisher.h +// +// Circle - A C++ bare metal environment for Raspberry Pi +// Copyright (C) 2024 R. Stange +// +// 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 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// +#ifndef _circle_net_mdnspublisher_h +#define _circle_net_mdnspublisher_h +#include +#include +#include +#include +#include +#include +#include +#include +#include +class CmDNSPublisher : public CTask /// mDNS / Bonjour client task +{ +public: + static constexpr const char *ServiceTypeAppleMIDI = "_apple-midi._udp"; +public: + /// \param pNet Pointer to the network subsystem object + CmDNSPublisher (CNetSubSystem *pNet); + ~CmDNSPublisher (void); + /// \brief Start publishing a service + /// \param pServiceName Name of the service to be published + /// \param pServiceType Type of the service to be published (e.g. ServiceTypeAppleMIDI) + /// \param usServicePort Port number of the service to be published (in host byte order) + /// \param ppText Descriptions of the service (terminated with a nullptr, or nullptr itself) + /// \return Operation successful? + boolean PublishService (const char *pServiceName, + const char *pServiceType, + u16 usServicePort, + const char *ppText[] = nullptr); + /// \brief Stop publishing a service + /// \param pServiceName Name of the service to be unpublished (same as when published) + /// \return Operation successful? + boolean UnpublishService (const char *pServiceName); + void Run (void) override; +private: + static const unsigned MaxTextRecords = 10; + static const unsigned MaxMessageSize = 1400; // safe UDP payload in an Ethernet frame + static const unsigned TTLShort = 15; // seconds + static const unsigned TTLLong = 4500; + static const unsigned TTLDelete = 0; + struct TService + { + CString ServiceName; + CString ServiceType; + u16 usServicePort; + unsigned nTextRecords; + CString *ppText[MaxTextRecords]; + }; + boolean SendResponse (TService *pService, boolean bDelete); + // Helpers for writing to buffer + void PutByte (u8 uchValue); + void PutWord (u16 usValue); + void PutDWord (u32 nValue); + void PutString (const char *pValue); + void PutCompressedString (const u8 *pWritePtr); + void PutDNSName (const char *pValue); + void PutIPAddress (const CIPAddress &rValue); + void ReserveDataLength (void); + void SetDataLength (void); +private: + CNetSubSystem *m_pNet; + CPtrList m_ServiceList; + CMutex m_Mutex; + CSocket *m_pSocket; + boolean m_bRunning; + CSynchronizationEvent m_Event; + u8 m_Buffer[MaxMessageSize]; + u8 *m_pWritePtr; + u8 *m_pDataLen; +}; +#endif \ No newline at end of file From 41be649a3f967ae23f41fdd6b5f2c1dd10c147b1 Mon Sep 17 00:00:00 2001 From: probonopd Date: Sat, 16 Nov 2024 19:26:02 +0100 Subject: [PATCH 38/57] Remove TTL patching https://github.com/rsta2/circle/discussions/427#discussioncomment-11198505 --- .github/workflows/build.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 9a0381e6..8fffb61b 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -23,8 +23,6 @@ jobs: run: | # Put git hash in startup message sed -i "s/Loading.../$(date +%Y%m%d)-$(git rev-parse --short HEAD)/g" src/userinterface.cpp - # https://github.com/rsta2/circle/discussions/427#discussioncomment-11198505 - sed -i -e 's|TTLShort = 120|TTLShort = 15|g' circle-stdlib/libs/circle/include/circle/net/mdnspublisher.h - name: Install toolchains run: | set -ex From 7ca6e296828f36d4e4285c7b97b73fffaa198454 Mon Sep 17 00:00:00 2001 From: probonopd Date: Sat, 16 Nov 2024 21:19:30 +0100 Subject: [PATCH 39/57] Add all network configuration keys [ci skip] --- src/minidexed.ini | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/minidexed.ini b/src/minidexed.ini index fa25ba11..2864cfa4 100644 --- a/src/minidexed.ini +++ b/src/minidexed.ini @@ -148,6 +148,11 @@ NetworkEnabled=0 NetworkDHCP=1 # NetworkType ( wifi ; ethernet ) NetworkType=wifi +NetworkHostname=MiniDexed +NetworkIPAddress=0 +NetworkSubnetMask=0 +NetworkDefaultGateway=0 +NetworkDefaultGateway=0 # Performance PerformanceSelectToLoad=1 From 0171b877d14ee44594930023fe6e88ebe6f0973a Mon Sep 17 00:00:00 2001 From: probonopd Date: Sat, 16 Nov 2024 21:20:26 +0100 Subject: [PATCH 40/57] Default hostname to "MiniDexed" [ci skip] --- src/config.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/config.cpp b/src/config.cpp index 21c24737..10a43558 100644 --- a/src/config.cpp +++ b/src/config.cpp @@ -204,7 +204,7 @@ void CConfig::Load (void) m_bNetworkEnabled = m_Properties.GetNumber ("NetworkEnabled", 0) != 0; m_bNetworkDHCP = m_Properties.GetNumber ("NetworkDHCP", 0) != 0; m_NetworkType = m_Properties.GetString ("NetworkType", "wifi"); - m_NetworkHostname = m_Properties.GetString ("NetworkHostname", "minidexed"); + m_NetworkHostname = m_Properties.GetString ("NetworkHostname", "MiniDexed"); m_INetworkIPAddress = m_Properties.GetIPAddress("NetworkIPAddress") != 0; m_INetworkSubnetMask = m_Properties.GetIPAddress("NetworkSubnetMask") != 0; m_INetworkDefaultGateway = m_Properties.GetIPAddress("NetworkDefaultGateway") != 0; @@ -766,4 +766,4 @@ CIPAddress CConfig::GetNetworkDefaultGateway (void) const CIPAddress CConfig::GetNetworkDNSServer (void) const { return m_INetworkDNSServer; -} \ No newline at end of file +} From ad5140a1f6355c85380c20b0d9f29c35a9177417 Mon Sep 17 00:00:00 2001 From: probonopd Date: Sat, 16 Nov 2024 21:23:15 +0100 Subject: [PATCH 41/57] Do not hardcode service name --- src/minidexed.cpp | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/minidexed.cpp b/src/minidexed.cpp index 14d0c3f5..db4aa0a2 100644 --- a/src/minidexed.cpp +++ b/src/minidexed.cpp @@ -32,7 +32,6 @@ const char WLANFirmwarePath[] = "SD:firmware/"; const char WLANConfigFile[] = "SD:wpa_supplicant.conf"; #define FTPUSERNAME "admin" #define FTPPASSWORD "admin" -#define MDNSSERVICENAME "MiniDexed" LOGMODULE ("minidexed"); @@ -2236,7 +2235,7 @@ void CMiniDexed::UpdateNetwork() assert (m_pmDNSPublisher); //static const char *ppText[] = {"RTP-MIDI Receiver", nullptr}; // dont bother adding additional data - if (!m_pmDNSPublisher->PublishService (MDNSSERVICENAME, CmDNSPublisher::ServiceTypeAppleMIDI, + if (!m_pmDNSPublisher->PublishService (m_pConfig->GetNetworkHostname(), CmDNSPublisher::ServiceTypeAppleMIDI, 5004)) { LOGPANIC ("Cannot publish mdns service"); @@ -2248,14 +2247,14 @@ void CMiniDexed::UpdateNetwork() if (m_bNetworkReady && !bNetIsRunning) { m_bNetworkReady = false; - m_pmDNSPublisher->UnpublishService (MDNSSERVICENAME); + m_pmDNSPublisher->UnpublishService (m_pConfig->GetNetworkHostname()); LOGNOTE("Network disconnected."); } else if (!m_bNetworkReady && bNetIsRunning) { m_bNetworkReady = true; - if (!m_pmDNSPublisher->PublishService (MDNSSERVICENAME, CmDNSPublisher::ServiceTypeAppleMIDI, + if (!m_pmDNSPublisher->PublishService (m_pConfig->GetNetworkHostname(), CmDNSPublisher::ServiceTypeAppleMIDI, 5004)) { LOGPANIC ("Cannot publish mdns service"); From 3d9a339c12c965bdd225d379b53843ccee5e7f57 Mon Sep 17 00:00:00 2001 From: probonopd Date: Sat, 16 Nov 2024 22:34:38 +0100 Subject: [PATCH 42/57] Also publish _ftp._tcp For, e..g., Cyberduck --- src/minidexed.cpp | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/minidexed.cpp b/src/minidexed.cpp index db4aa0a2..6593c2fb 100644 --- a/src/minidexed.cpp +++ b/src/minidexed.cpp @@ -2240,6 +2240,12 @@ void CMiniDexed::UpdateNetwork() { LOGPANIC ("Cannot publish mdns service"); } + + static constexpr const char *ServiceTypeFTP = "_ftp._tcp"; + if (!pmDNSPublisher->PublishService (m_pConfig->GetNetworkHostname(), ServiceTypeFTP, 21)) + { + LOGPANIC ("Cannot publish mdns service"); + } m_bNetworkReady = true; } @@ -2259,6 +2265,14 @@ void CMiniDexed::UpdateNetwork() { LOGPANIC ("Cannot publish mdns service"); } + + static constexpr const char *ServiceTypeFTP = "_ftp._tcp"; + if (!pmDNSPublisher->PublishService (m_pConfig->GetNetworkHostname(), ServiceTypeFTP, 21)) + { + LOGPANIC ("Cannot publish mdns service"); + } + + m_bNetworkReady = true; LOGNOTE("Network connection reestablished."); From 99319d484068a455fe31f3e489bf88428a364888 Mon Sep 17 00:00:00 2001 From: probonopd Date: Sat, 16 Nov 2024 22:45:01 +0100 Subject: [PATCH 43/57] m_pmDNSPublisher --- src/minidexed.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/minidexed.cpp b/src/minidexed.cpp index 6593c2fb..f4b30a6e 100644 --- a/src/minidexed.cpp +++ b/src/minidexed.cpp @@ -2242,7 +2242,7 @@ void CMiniDexed::UpdateNetwork() } static constexpr const char *ServiceTypeFTP = "_ftp._tcp"; - if (!pmDNSPublisher->PublishService (m_pConfig->GetNetworkHostname(), ServiceTypeFTP, 21)) + if (!m_pmDNSPublisher->PublishService (m_pConfig->GetNetworkHostname(), ServiceTypeFTP, 21)) { LOGPANIC ("Cannot publish mdns service"); } @@ -2267,7 +2267,7 @@ void CMiniDexed::UpdateNetwork() } static constexpr const char *ServiceTypeFTP = "_ftp._tcp"; - if (!pmDNSPublisher->PublishService (m_pConfig->GetNetworkHostname(), ServiceTypeFTP, 21)) + if (!m_pmDNSPublisher->PublishService (m_pConfig->GetNetworkHostname(), ServiceTypeFTP, 21)) { LOGPANIC ("Cannot publish mdns service"); } From f8a5ee590f9ce07b4e7ac90454c26c0383d3d9f2 Mon Sep 17 00:00:00 2001 From: probonopd Date: Sat, 16 Nov 2024 22:49:51 +0100 Subject: [PATCH 44/57] Add NetworkDNSServer (fix typo) --- src/minidexed.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/minidexed.ini b/src/minidexed.ini index 2864cfa4..644ef4af 100644 --- a/src/minidexed.ini +++ b/src/minidexed.ini @@ -152,7 +152,7 @@ NetworkHostname=MiniDexed NetworkIPAddress=0 NetworkSubnetMask=0 NetworkDefaultGateway=0 -NetworkDefaultGateway=0 +NetworkDNSServer=0 # Performance PerformanceSelectToLoad=1 From decd7375dafc290ada3fa2c09b1c91ae11c737b3 Mon Sep 17 00:00:00 2001 From: probonopd Date: Sat, 16 Nov 2024 22:37:36 +0000 Subject: [PATCH 45/57] Prevent wpa_supplicant.conf from being downloaded To be tested --- circle-stdlib | 2 +- src/net/ftpworker.cpp | 11 +++++++++-- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/circle-stdlib b/circle-stdlib index 3bd135d3..db053a32 160000 --- a/circle-stdlib +++ b/circle-stdlib @@ -1 +1 @@ -Subproject commit 3bd135d35ac23c7c726924cb85b5890cbf80bbfe +Subproject commit db053a32c165c1b22423a47ed6cb5bddc72b51f2 diff --git a/src/net/ftpworker.cpp b/src/net/ftpworker.cpp index f7438ce4..fb57254c 100644 --- a/src/net/ftpworker.cpp +++ b/src/net/ftpworker.cpp @@ -617,6 +617,13 @@ bool CFTPWorker::Retrieve(const char* pArgs) FIL File; CString Path = RealPath(pArgs); + // If the filename is "wpa_supplicant.conf", don't allow it to be retrieved + if (strcmp(Path, "/wpa_supplicant.conf") == 0) + { + SendStatus(TFTPStatus::FileActionNotTaken, "File action not taken."); + return false; + } + if (f_open(&File, Path, FA_READ) != FR_OK) { SendStatus(TFTPStatus::FileActionNotTaken, "Could not open file for reading."); @@ -986,10 +993,10 @@ bool CFTPWorker::ListFileNames(const char* pArgs) const TDirectoryListEntry& Entry = pDirEntries[i]; if (Entry.Type == TDirectoryListEntryType::Directory) continue; - /*if (Entry.Name == "wpa_supplicant.conf") + if (strcmp(Entry.Name, "wpa_supplicant.conf") == 0) { continue; - }*/ + } const int nLength = snprintf(Buffer, sizeof(Buffer), "%s\r\n", Entry.Name); if (pDataSocket->Send(Buffer, nLength, 0) < 0) { From 2b619b00851a0cceb3c616209af0a6e78d351071 Mon Sep 17 00:00:00 2001 From: probonopd Date: Sat, 16 Nov 2024 23:09:47 +0000 Subject: [PATCH 46/57] /SD/wpa_supplicant.conf --- src/net/ftpworker.cpp | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/net/ftpworker.cpp b/src/net/ftpworker.cpp index fb57254c..0dcaa044 100644 --- a/src/net/ftpworker.cpp +++ b/src/net/ftpworker.cpp @@ -27,6 +27,7 @@ #include #include #include +#include #include #include @@ -618,7 +619,7 @@ bool CFTPWorker::Retrieve(const char* pArgs) CString Path = RealPath(pArgs); // If the filename is "wpa_supplicant.conf", don't allow it to be retrieved - if (strcmp(Path, "/wpa_supplicant.conf") == 0) + if (strcmp(Path, "/SD/wpa_supplicant.conf") == 0) { SendStatus(TFTPStatus::FileActionNotTaken, "File action not taken."); return false; @@ -993,7 +994,7 @@ bool CFTPWorker::ListFileNames(const char* pArgs) const TDirectoryListEntry& Entry = pDirEntries[i]; if (Entry.Type == TDirectoryListEntryType::Directory) continue; - if (strcmp(Entry.Name, "wpa_supplicant.conf") == 0) + if (strcmp(Entry.Name, "/SD/wpa_supplicant.conf") == 0) { continue; } @@ -1055,6 +1056,9 @@ bool CFTPWorker::Bye(const char* pArgs) SendStatus(TFTPStatus::ClosingControl, "Goodbye."); delete m_pControlSocket; m_pControlSocket = nullptr; + + // Reboot the system if the user disconnects in order to apply any changes made + reboot (); return true; } From 9a6c782e3faf6225cd39f801c9cde53befe0222c Mon Sep 17 00:00:00 2001 From: probonopd Date: Sat, 16 Nov 2024 23:35:50 +0000 Subject: [PATCH 47/57] "wpa_supplicant.conf" --- src/net/ftpworker.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/net/ftpworker.cpp b/src/net/ftpworker.cpp index 0dcaa044..8856d755 100644 --- a/src/net/ftpworker.cpp +++ b/src/net/ftpworker.cpp @@ -619,7 +619,7 @@ bool CFTPWorker::Retrieve(const char* pArgs) CString Path = RealPath(pArgs); // If the filename is "wpa_supplicant.conf", don't allow it to be retrieved - if (strcmp(Path, "/SD/wpa_supplicant.conf") == 0) + if (strcmp(Path, "wpa_supplicant.conf") == 0) { SendStatus(TFTPStatus::FileActionNotTaken, "File action not taken."); return false; @@ -994,7 +994,7 @@ bool CFTPWorker::ListFileNames(const char* pArgs) const TDirectoryListEntry& Entry = pDirEntries[i]; if (Entry.Type == TDirectoryListEntryType::Directory) continue; - if (strcmp(Entry.Name, "/SD/wpa_supplicant.conf") == 0) + if (strcmp(Entry.Name, "wpa_supplicant.conf") == 0) { continue; } From a9921e47b22aefc2a15a9d69601699901c648a47 Mon Sep 17 00:00:00 2001 From: probonopd Date: Sun, 17 Nov 2024 11:23:31 +0000 Subject: [PATCH 48/57] Add logging over the network to a syslog server This has been tested successfully. FIXME: Don't hardcode IP address, use m_INetworkSyslogServerIPAddress instead Thanks @rsta https://github.com/rsta2/circle/tree/master/sample/33-syslog --- src/config.cpp | 6 ++++++ src/config.h | 2 ++ src/minidexed.cpp | 14 ++++++++++++++ src/minidexed.ini | 1 + 4 files changed, 23 insertions(+) diff --git a/src/config.cpp b/src/config.cpp index 10a43558..40ad095f 100644 --- a/src/config.cpp +++ b/src/config.cpp @@ -209,6 +209,7 @@ void CConfig::Load (void) m_INetworkSubnetMask = m_Properties.GetIPAddress("NetworkSubnetMask") != 0; m_INetworkDefaultGateway = m_Properties.GetIPAddress("NetworkDefaultGateway") != 0; m_INetworkDNSServer = m_Properties.GetIPAddress("NetworkDNSServer") != 0; + m_INetworkSyslogServerIPAddress = m_Properties.GetIPAddress("NetworkSyslogServerIPAddress") != 0; } unsigned CConfig::GetToneGenerators (void) const @@ -767,3 +768,8 @@ CIPAddress CConfig::GetNetworkDNSServer (void) const { return m_INetworkDNSServer; } + +CIPAddress CConfig::GetNetworkSyslogServerIPAddress (void) const +{ + return m_INetworkSyslogServerIPAddress; +} diff --git a/src/config.h b/src/config.h index b87a94c8..7af0bfc0 100644 --- a/src/config.h +++ b/src/config.h @@ -248,6 +248,7 @@ class CConfig // Configuration for MiniDexed CIPAddress GetNetworkSubnetMask (void) const; CIPAddress GetNetworkDefaultGateway (void) const; CIPAddress GetNetworkDNSServer (void) const; + CIPAddress GetNetworkSyslogServerIPAddress (void) const; private: CPropertiesFatFsFile m_Properties; @@ -372,6 +373,7 @@ class CConfig // Configuration for MiniDexed CIPAddress m_INetworkSubnetMask; CIPAddress m_INetworkDefaultGateway; CIPAddress m_INetworkDNSServer; + CIPAddress m_INetworkSyslogServerIPAddress; }; #endif diff --git a/src/minidexed.cpp b/src/minidexed.cpp index f4b30a6e..f61c8359 100644 --- a/src/minidexed.cpp +++ b/src/minidexed.cpp @@ -23,6 +23,8 @@ #include #include #include +#include +#include #include #include #include @@ -2326,6 +2328,18 @@ bool CMiniDexed::InitNetwork() } m_pNetDevice = CNetDevice::GetNetDevice(NetDeviceType); + + // Syslog configuration + static const u8 SysLogServer[] = {192, 168, 0, 143}; // FIXME: Don't hardcode this, use m_INetworkSyslogServerIPAddress instead + static const u16 usServerPort = 8514; // standard port is 514 + CIPAddress ServerIP (SysLogServer); + CString IPString; + ServerIP.Format (&IPString); + LOGNOTE ( "Sending log messages to %s:%u", + (const char *) IPString, (unsigned) usServerPort); + + new CSysLogDaemon (m_pNet, ServerIP, usServerPort); + } return m_pNet != nullptr; } diff --git a/src/minidexed.ini b/src/minidexed.ini index 644ef4af..ac24026f 100644 --- a/src/minidexed.ini +++ b/src/minidexed.ini @@ -153,6 +153,7 @@ NetworkIPAddress=0 NetworkSubnetMask=0 NetworkDefaultGateway=0 NetworkDNSServer=0 +NetworkSyslogServerIPAddress=0 # Performance PerformanceSelectToLoad=1 From 2fbb9d238fc86254f2245edad9c6241d252e2522 Mon Sep 17 00:00:00 2001 From: probonopd Date: Sun, 17 Nov 2024 18:27:39 +0000 Subject: [PATCH 49/57] Apply patch to prevent wpa_supplicant.conf from being downloaded --- src/net/ftpworker.cpp | 20 ++++++++------------ 1 file changed, 8 insertions(+), 12 deletions(-) diff --git a/src/net/ftpworker.cpp b/src/net/ftpworker.cpp index 8856d755..e912b335 100644 --- a/src/net/ftpworker.cpp +++ b/src/net/ftpworker.cpp @@ -44,10 +44,11 @@ constexpr unsigned int SocketTimeout = 20; constexpr unsigned int NumRetries = 3; #ifndef MT32_PI_VERSION -#define MT32_PI_VERSION "alpha version" +#define MT32_PI_VERSION "(version unknown)" #endif -const char MOTDBanner[] = "Welcome to the minidexed " MT32_PI_VERSION " embedded FTP server!"; +const char MOTDBanner[] = "Welcome to the MiniDexed " MT32_PI_VERSION " embedded FTP server!"; +const char* exclude_filename = "SD:/wpa_supplicant.conf"; enum class TDirectoryListEntryType { @@ -62,7 +63,6 @@ struct TDirectoryListEntry u32 nSize; u16 nLastModifedDate; u16 nLastModifedTime; - //bool bHid; }; using TCommandHandler = bool (CFTPWorker::*)(const char* pArgs); @@ -424,7 +424,6 @@ const TDirectoryListEntry* CFTPWorker::BuildDirectoryList(size_t& nOutEntries) c { Entry.Type = TDirectoryListEntryType::File; Entry.nSize = FileInfo.fsize; - //Entry.bHid = (FileInfo.fattrib & AM_HID) ? true : false; } Entry.nLastModifedDate = FileInfo.fdate; @@ -617,11 +616,12 @@ bool CFTPWorker::Retrieve(const char* pArgs) FIL File; CString Path = RealPath(pArgs); - - // If the filename is "wpa_supplicant.conf", don't allow it to be retrieved - if (strcmp(Path, "wpa_supplicant.conf") == 0) + typedef const char* LPCTSTR; + //printf("%s\n", (LPCTSTR)Path); + //printf("%s\n", exclude_filename ); + if (strcmp((LPCTSTR)Path, exclude_filename) == 0) { - SendStatus(TFTPStatus::FileActionNotTaken, "File action not taken."); + SendStatus(TFTPStatus::FileNameNotAllowed, "Reading this file is not allowed"); return false; } @@ -994,10 +994,6 @@ bool CFTPWorker::ListFileNames(const char* pArgs) const TDirectoryListEntry& Entry = pDirEntries[i]; if (Entry.Type == TDirectoryListEntryType::Directory) continue; - if (strcmp(Entry.Name, "wpa_supplicant.conf") == 0) - { - continue; - } const int nLength = snprintf(Buffer, sizeof(Buffer), "%s\r\n", Entry.Name); if (pDataSocket->Send(Buffer, nLength, 0) < 0) { From b59af1451471c96be06c42284867c0e5c5bc5e65 Mon Sep 17 00:00:00 2001 From: probonopd Date: Sun, 17 Nov 2024 18:30:39 +0000 Subject: [PATCH 50/57] Remove compiler warnings net/ftpworker.cpp: In member function 'bool CFTPWorker::_ZN10CFTPWorker4PortEPKc.part.0(const char*)': net/ftpworker.cpp:466:9: warning: 'char* strncpy(char*, const char*, size_t)' specified bound 512 equals destination size [-Wstringop-truncation] 466 | strncpy(Buffer, pArgs, sizeof(Buffer)); | ~~~~~~~^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ net/ftpworker.cpp: In member function 'const TDirectoryListEntry* CFTPWorker::BuildDirectoryList(size_t&) const': net/ftpworker.cpp:365:11: warning: 'char* strncpy(char*, const char*, size_t)' specified bound 6 equals destination size [-Wstringop-truncation] 365 | strncpy(VolumeName, VolumeNames[i], sizeof(VolumeName)); | ~~~~~~~^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ../Synth_Dexed/src/dexed.cpp: In member function 'void Dexed::setName(char*)': ../Synth_Dexed/src/dexed.cpp:1699:10: warning: 'char* strncpy(char*, const char*, size_t)' output may be truncated copying 10 bytes from a string of length 155 [-Wstringop-truncation] 1699 | strncpy(name, (char*)&data[DEXED_VOICE_OFFSET + DEXED_NAME], 10); | ~~~~~~~^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ --- src/net/ftpworker.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/net/ftpworker.cpp b/src/net/ftpworker.cpp index e912b335..6f19f8af 100644 --- a/src/net/ftpworker.cpp +++ b/src/net/ftpworker.cpp @@ -362,7 +362,7 @@ const TDirectoryListEntry* CFTPWorker::BuildDirectoryList(size_t& nOutEntries) c for (size_t i = 0; i < nVolumes; ++i) { char VolumeName[6]; - strncpy(VolumeName, VolumeNames[i], sizeof(VolumeName)); + strncpy(VolumeName, VolumeNames[i], sizeof(VolumeName) - 1); strcat(VolumeName, ":"); // Returns FR_ @@ -463,7 +463,7 @@ bool CFTPWorker::Port(const char* pArgs) return false; char Buffer[TextBufferSize]; - strncpy(Buffer, pArgs, sizeof(Buffer)); + strncpy(Buffer, pArgs, sizeof(Buffer) - 1); if (m_pDataSocket != nullptr) { From 46ccb9ca12ae45bf60a93e0cfe6d4eb063fdfe28 Mon Sep 17 00:00:00 2001 From: probonopd Date: Sun, 17 Nov 2024 19:01:29 +0000 Subject: [PATCH 51/57] Do not hardcode syslog server https://github.com/rsta2/circle/discussions/506#discussioncomment-11284425 --- src/config.cpp | 7 ++++++- src/minidexed.cpp | 22 ++++++++++++---------- 2 files changed, 18 insertions(+), 11 deletions(-) diff --git a/src/config.cpp b/src/config.cpp index 40ad095f..cc689f8b 100644 --- a/src/config.cpp +++ b/src/config.cpp @@ -209,7 +209,12 @@ void CConfig::Load (void) m_INetworkSubnetMask = m_Properties.GetIPAddress("NetworkSubnetMask") != 0; m_INetworkDefaultGateway = m_Properties.GetIPAddress("NetworkDefaultGateway") != 0; m_INetworkDNSServer = m_Properties.GetIPAddress("NetworkDNSServer") != 0; - m_INetworkSyslogServerIPAddress = m_Properties.GetIPAddress("NetworkSyslogServerIPAddress") != 0; + + const u8 *pSyslogServerIP = m_Properties.GetIPAddress ("NetworkSyslogServerIPAddress"); + if (pSyslogServerIP) + { + m_INetworkSyslogServerIPAddress.Set (pSyslogServerIP); + } } unsigned CConfig::GetToneGenerators (void) const diff --git a/src/minidexed.cpp b/src/minidexed.cpp index f61c8359..57c9c86a 100644 --- a/src/minidexed.cpp +++ b/src/minidexed.cpp @@ -2329,16 +2329,18 @@ bool CMiniDexed::InitNetwork() m_pNetDevice = CNetDevice::GetNetDevice(NetDeviceType); - // Syslog configuration - static const u8 SysLogServer[] = {192, 168, 0, 143}; // FIXME: Don't hardcode this, use m_INetworkSyslogServerIPAddress instead - static const u16 usServerPort = 8514; // standard port is 514 - CIPAddress ServerIP (SysLogServer); - CString IPString; - ServerIP.Format (&IPString); - LOGNOTE ( "Sending log messages to %s:%u", - (const char *) IPString, (unsigned) usServerPort); - - new CSysLogDaemon (m_pNet, ServerIP, usServerPort); + // syslog configuration + CIPAddress ServerIP = m_pConfig->GetNetworkSyslogServerIPAddress(); + if (ServerIP.IsSet () && !ServerIP.IsNull ()) + { + static const u16 usServerPort = 8514; // standard port is 514 + CString IPString; + ServerIP.Format (&IPString); + LOGNOTE ("Sending log messages to syslog server %s:%u", + (const char *) IPString, (unsigned) usServerPort); + + new CSysLogDaemon (m_pNet, ServerIP, usServerPort); + } } return m_pNet != nullptr; From 84525eb9e4c6873d98add509ff473f91705ab35a Mon Sep 17 00:00:00 2001 From: probonopd Date: Sun, 17 Nov 2024 20:39:28 +0000 Subject: [PATCH 52/57] Support MIDIDumpEnabled=1 to syslog --- src/mididevice.cpp | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/mididevice.cpp b/src/mididevice.cpp index 8ea5600b..6cc82a4f 100644 --- a/src/mididevice.cpp +++ b/src/mididevice.cpp @@ -144,17 +144,17 @@ void CMIDIDevice::MIDIMessageHandler (const u8 *pMessage, size_t nLength, unsign if ( pMessage[0] != MIDI_TIMING_CLOCK && pMessage[0] != MIDI_ACTIVE_SENSING) { - printf ("MIDI%u: %02X\n", nCable, (unsigned) pMessage[0]); + LOGNOTE ("MIDI%u: %02X\n", nCable, (unsigned) pMessage[0]); } break; case 2: - printf ("MIDI%u: %02X %02X\n", nCable, + LOGNOTE ("MIDI%u: %02X %02X\n", nCable, (unsigned) pMessage[0], (unsigned) pMessage[1]); break; case 3: - printf ("MIDI%u: %02X %02X %02X\n", nCable, + LOGNOTE ("MIDI%u: %02X %02X %02X\n", nCable, (unsigned) pMessage[0], (unsigned) pMessage[1], (unsigned) pMessage[2]); break; @@ -162,17 +162,17 @@ void CMIDIDevice::MIDIMessageHandler (const u8 *pMessage, size_t nLength, unsign switch(pMessage[0]) { case MIDI_SYSTEM_EXCLUSIVE_BEGIN: - printf("MIDI%u: SysEx data length: [%d]:",nCable, uint16_t(nLength)); + LOGNOTE("MIDI%u: SysEx data length: [%d]:",nCable, uint16_t(nLength)); for (uint16_t i = 0; i < nLength; i++) { if((i % 16) == 0) printf("\n%04d:",i); printf(" 0x%02x",pMessage[i]); } - printf("\n"); + LOGNOTE("\n"); break; default: - printf("MIDI%u: Unhandled MIDI event type %0x02x\n",nCable,pMessage[0]); + LOGNOTE("MIDI%u: Unhandled MIDI event type %0x02x\n",nCable,pMessage[0]); } break; } From 9142a8ea1a95e2f08e5448fd400b0fa6a49a664c Mon Sep 17 00:00:00 2001 From: probonopd Date: Sun, 17 Nov 2024 21:07:23 +0000 Subject: [PATCH 53/57] Use custom Dockerfile for GitHub Codespaces --- src/.devcontainer/Dockerfile | 12 ++++++++++++ src/.devcontainer/devcontainer.json | 3 +++ 2 files changed, 15 insertions(+) create mode 100644 src/.devcontainer/Dockerfile create mode 100644 src/.devcontainer/devcontainer.json diff --git a/src/.devcontainer/Dockerfile b/src/.devcontainer/Dockerfile new file mode 100644 index 00000000..c2d5b61b --- /dev/null +++ b/src/.devcontainer/Dockerfile @@ -0,0 +1,12 @@ +FROM mcr.microsoft.com/devcontainers/universal:linux + +RUN mkdir -p /opt/gcc && \ + cd /opt/gcc && \ + wget -q https://developer.arm.com/-/media/Files/downloads/gnu-a/10.3-2021.07/binrel/gcc-arm-10.3-2021.07-x86_64-aarch64-none-elf.tar.xz && \ + tar xf gcc-arm-10.3-2021.07-x86_64-aarch64-none-elf.tar.xz && \ + rm gcc-arm-10.3-2021.07-x86_64-aarch64-none-elf.tar.xz && \ + wget -q https://developer.arm.com/-/media/Files/downloads/gnu-a/10.3-2021.07/binrel/gcc-arm-10.3-2021.07-x86_64-arm-none-eabi.tar.xz && \ + tar xf gcc-arm-10.3-2021.07-x86_64-arm-none-eabi.tar.xz && \ + rm gcc-arm-10.3-2021.07-x86_64-arm-none-eabi.tar.xz + +ENV PATH=/opt/gcc/gcc-arm-10.3-2021.07-x86_64-aarch64-none-elf/bin:/opt/gcc/gcc-arm-10.3-2021.07-x86_64-arm-none-eabi/bin:$PATH diff --git a/src/.devcontainer/devcontainer.json b/src/.devcontainer/devcontainer.json new file mode 100644 index 00000000..dae163bf --- /dev/null +++ b/src/.devcontainer/devcontainer.json @@ -0,0 +1,3 @@ +{ + "build": { "dockerfile": "Dockerfile" }, +} From 77cf10676cda75a882017479e07931e307e26485 Mon Sep 17 00:00:00 2001 From: probonopd Date: Sun, 17 Nov 2024 21:18:50 +0000 Subject: [PATCH 54/57] Move .devcontainer in place --- {src/.devcontainer => .devcontainer}/Dockerfile | 0 {src/.devcontainer => .devcontainer}/devcontainer.json | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename {src/.devcontainer => .devcontainer}/Dockerfile (100%) rename {src/.devcontainer => .devcontainer}/devcontainer.json (100%) diff --git a/src/.devcontainer/Dockerfile b/.devcontainer/Dockerfile similarity index 100% rename from src/.devcontainer/Dockerfile rename to .devcontainer/Dockerfile diff --git a/src/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json similarity index 100% rename from src/.devcontainer/devcontainer.json rename to .devcontainer/devcontainer.json From cd93cac064744a76574e8da262480c87d677887d Mon Sep 17 00:00:00 2001 From: probonopd Date: Sun, 17 Nov 2024 21:42:09 +0000 Subject: [PATCH 55/57] Start build when Codespace starts --- .devcontainer/devcontainer.json | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index dae163bf..ea6ad11a 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -1,3 +1,9 @@ { - "build": { "dockerfile": "Dockerfile" }, + "build": { + "dockerfile": "Dockerfile" + }, + "containerEnv": { + "PATH": "/opt/gcc/gcc-arm-10.3-2021.07-x86_64-aarch64-none-elf/bin:/opt/gcc/gcc-arm-10.3-2021.07-x86_64-arm-none-eabi/bin:${PATH}" + }, + "postCreateCommand": "export RPI=3 && ./build.sh" } From 5cabb8cb9dbf2f2e03a5d44a95ce1bbca141a404 Mon Sep 17 00:00:00 2001 From: probonopd Date: Sun, 17 Nov 2024 21:54:34 +0000 Subject: [PATCH 56/57] Revert Dockerfile --- .devcontainer/Dockerfile | 12 ------------ .devcontainer/devcontainer.json | 8 +------- 2 files changed, 1 insertion(+), 19 deletions(-) delete mode 100644 .devcontainer/Dockerfile diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile deleted file mode 100644 index c2d5b61b..00000000 --- a/.devcontainer/Dockerfile +++ /dev/null @@ -1,12 +0,0 @@ -FROM mcr.microsoft.com/devcontainers/universal:linux - -RUN mkdir -p /opt/gcc && \ - cd /opt/gcc && \ - wget -q https://developer.arm.com/-/media/Files/downloads/gnu-a/10.3-2021.07/binrel/gcc-arm-10.3-2021.07-x86_64-aarch64-none-elf.tar.xz && \ - tar xf gcc-arm-10.3-2021.07-x86_64-aarch64-none-elf.tar.xz && \ - rm gcc-arm-10.3-2021.07-x86_64-aarch64-none-elf.tar.xz && \ - wget -q https://developer.arm.com/-/media/Files/downloads/gnu-a/10.3-2021.07/binrel/gcc-arm-10.3-2021.07-x86_64-arm-none-eabi.tar.xz && \ - tar xf gcc-arm-10.3-2021.07-x86_64-arm-none-eabi.tar.xz && \ - rm gcc-arm-10.3-2021.07-x86_64-arm-none-eabi.tar.xz - -ENV PATH=/opt/gcc/gcc-arm-10.3-2021.07-x86_64-aarch64-none-elf/bin:/opt/gcc/gcc-arm-10.3-2021.07-x86_64-arm-none-eabi/bin:$PATH diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index ea6ad11a..8537eb73 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -1,9 +1,3 @@ { - "build": { - "dockerfile": "Dockerfile" - }, - "containerEnv": { - "PATH": "/opt/gcc/gcc-arm-10.3-2021.07-x86_64-aarch64-none-elf/bin:/opt/gcc/gcc-arm-10.3-2021.07-x86_64-arm-none-eabi/bin:${PATH}" - }, - "postCreateCommand": "export RPI=3 && ./build.sh" + "postCreateCommand": "echo 'Hello, Dev Container!'" } From 7eda0d123747d6cd510c5d37d0262969be1e3af8 Mon Sep 17 00:00:00 2001 From: probonopd Date: Tue, 19 Nov 2024 07:04:31 +0000 Subject: [PATCH 57/57] Revert unrelated change --- .devcontainer/devcontainer.json | 3 --- 1 file changed, 3 deletions(-) delete mode 100644 .devcontainer/devcontainer.json diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json deleted file mode 100644 index 8537eb73..00000000 --- a/.devcontainer/devcontainer.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "postCreateCommand": "echo 'Hello, Dev Container!'" -}