diff --git a/CMakeLists.txt b/CMakeLists.txt index 16724b1b..69272129 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -30,6 +30,10 @@ option(BUILD_AVDECC_INTERFACE_PCAP_DYNAMIC_LINKING "Pcap protocol interface uses option(BUILD_AVDECC_INTERFACE_MAC "Build the macOS native protocol interface (macOS only)." TRUE) option(BUILD_AVDECC_INTERFACE_PROXY "Build the proxy protocol interface." FALSE) option(BUILD_AVDECC_INTERFACE_VIRTUAL "Build the virtual protocol interface (for unit tests)." TRUE) +if(NOT WIN32) + option(BUILD_AVDECC_INTERFACE_SERIAL "Build the serial protocol interface." TRUE) + option(BUILD_AVDECC_INTERFACE_LOCAL "Build the local domain socket protocol interface." TRUE) +endif() # Install options option(INSTALL_AVDECC_EXAMPLES "Install examples." FALSE) option(INSTALL_AVDECC_TESTS "Install unit tests." FALSE) diff --git a/Docker/sonar.properties b/Docker/sonar.properties index 15bba791..aff8f39f 100644 --- a/Docker/sonar.properties +++ b/Docker/sonar.properties @@ -24,4 +24,4 @@ sonar.issue.ignore.multicriteria.TempIgnoreNestedNamespaces.resourceKey=**/* sonar.issue.ignore.multicriteria.IgnoreVirtualSpecifierWarning.ruleKey=cpp:S3471 sonar.issue.ignore.multicriteria.IgnoreVirtualSpecifierWarning.resourceKey=**/* -sonar.issue.ignore.multicriteria=IgnoreDefaultObserverHandlers,TempIgnoreNestedNamespaces,IgnoreVirtualSpecifierWarning \ No newline at end of file +sonar.issue.ignore.multicriteria=IgnoreDefaultObserverHandlers,TempIgnoreNestedNamespaces,IgnoreVirtualSpecifierWarning diff --git a/include/la/avdecc/internals/protocolInterface.hpp b/include/la/avdecc/internals/protocolInterface.hpp index 160da943..9648a2d4 100644 --- a/include/la/avdecc/internals/protocolInterface.hpp +++ b/include/la/avdecc/internals/protocolInterface.hpp @@ -66,6 +66,8 @@ class ProtocolInterface : public la::avdecc::utils::Subject + +#include "cobsSerialization.hpp" + +namespace la +{ +namespace avdecc +{ +namespace protocol +{ +namespace cobs +{ + +/** + * COBS encodes a message + * @param input [in] pointer to the raw message + * @param input_length [in] the length of the raw message + * @param output [out] pointer to the output encode buffer + * @return the number of bytes written to "output". + */ +std::size_t encode(const std::uint8_t* input, std::size_t input_length, std::uint8_t* output) noexcept +{ + std::size_t read_index = 0, write_index = 1; + std::size_t code_index = 0; + std::uint8_t code = 1; + + while (read_index < input_length) + { + if (input[read_index] == 0) + { + output[code_index] = code; + code = 1; + code_index = write_index++; + read_index++; + } + else + { + output[write_index++] = input[read_index++]; + code++; + if (code == 0xFF) + { + output[code_index] = code; + code = 1; + code_index = write_index++; + } + } + } + + output[code_index] = code; + + return write_index; +} + +/** + * Decodes a COBS encoded message + * @param input [in] pointer to the COBS encoded message + * @param input_length [in] the length of the COBS encoded message + * @param output [in] pointer to the decode buffer + * @param output_length [in] length of the decode buffer + * @return the number of bytes written to "output" if "input" was successfully + * unstuffed, and 0 if there was an error unstuffing "input". + */ +std::size_t decode(const std::uint8_t* input, std::size_t input_length, std::uint8_t* output, std::size_t output_length) +{ + std::size_t read_index = 0, write_index = 0; + std::uint8_t code, i; + + while (read_index < input_length) + { + code = input[read_index]; + + if (read_index + code > input_length && code != 1) + { + return 0; + } + + read_index++; + + for (i = 1; i < code; i++) + { + if (write_index == output_length) + { + throw std::invalid_argument("Not enough room to decode"); + } + output[write_index++] = input[read_index++]; + } + if (code != 0xFF && read_index != input_length) + { + if (write_index == output_length) + { + throw std::invalid_argument("Not enough room to decode"); + } + output[write_index++] = '\0'; + } + } + + return write_index; +} + +} // namespace cobs +} // namespace protocol +} // namespace avdecc +} // namespace la diff --git a/src/protocolInterface/cobsSerialization.hpp b/src/protocolInterface/cobsSerialization.hpp new file mode 100644 index 00000000..bdf01e66 --- /dev/null +++ b/src/protocolInterface/cobsSerialization.hpp @@ -0,0 +1,70 @@ +/* + * Copyright 2011, Jacques Fortier + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the “Software”), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + * of the Software, and to permit persons to whom the Software is furnished to do + * so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#pragma once + +#include +#include + +namespace la +{ +namespace avdecc +{ +namespace protocol +{ +namespace cobs +{ +/** + * Delimiter byte to frame COBS encoded data + */ +static constexpr std::uint8_t DelimiterByte = 0; + +/** + * Macro to calculate the maximum number of COBS pad bytes with a given payload size 'n' + * @note Do not use this macro to determine the overhead resulting from a COBS encoding. Use the return value from \ref cobs_encode instead + */ +#define COBS_BUFFER_PAD(n) ((((n) + 254 - 1) & ~(254 - 1)) / 254) + +/** + * COBS encodes a message + * @param input [in] pointer to the raw message + * @param input_length [in] the length of the raw message + * @param output [out] pointer to the output encode buffer + * @return the number of bytes written to "output". + */ +std::size_t encode(const std::uint8_t* input, std::size_t input_length, std::uint8_t* output) noexcept; + +/** + * Decodes a COBS encoded message + * @param input [in] pointer to the COBS encoded message + * @param input_length [in] the length of the COBS encoded message + * @param output [in] pointer to the decode buffer + * @param output_length [in] length of the decode buffer + * @return the number of bytes written to "output" if "input" was successfully + * unstuffed, and 0 if there was an error unstuffing "input". + */ +std::size_t decode(const std::uint8_t* input, std::size_t input_length, std::uint8_t* output, std::size_t output_length); + +} // namespace cobs +} // namespace protocol +} // namespace avdecc +} // namespace la diff --git a/src/protocolInterface/protocolInterface.cpp b/src/protocolInterface/protocolInterface.cpp index e03380e2..51e9468a 100644 --- a/src/protocolInterface/protocolInterface.cpp +++ b/src/protocolInterface/protocolInterface.cpp @@ -39,6 +39,12 @@ #ifdef HAVE_PROTOCOL_INTERFACE_VIRTUAL # include "protocolInterface/protocolInterface_virtual.hpp" #endif // HAVE_PROTOCOL_INTERFACE_VIRTUAL +#ifdef HAVE_PROTOCOL_INTERFACE_SERIAL +# include "protocolInterface/protocolInterface_serial.hpp" +#endif // HAVE_PROTOCOL_INTERFACE_SERIAL +#ifdef HAVE_PROTOCOL_INTERFACE_LOCAL +# include "protocolInterface/protocolInterface_local.hpp" +#endif // HAVE_PROTOCOL_INTERFACE_LOCAL namespace la { @@ -46,6 +52,18 @@ namespace avdecc { namespace protocol { +// constructor for non-network protocol interfaces +ProtocolInterface::ProtocolInterface(std::string const& executorName) + : _executorName{ executorName } +{ + // Check if the executor exists + if (!ExecutorManager::getInstance().isExecutorRegistered(_executorName)) + { + throw Exception(Error::ExecutorNotInitialized, "The receive executor '" + std::string{ _executorName } + "' is not registered"); + } + _networkInterfaceMacAddress = { 0, 0, 0, 0, 0, 0 }; +} + // Throws an Exception if networkInterfaceName is not usable ProtocolInterface::ProtocolInterface(std::string const& networkInterfaceName, std::string const& executorName) : _networkInterfaceName(networkInterfaceName) @@ -196,6 +214,14 @@ ProtocolInterface* LA_AVDECC_CALL_CONVENTION ProtocolInterface::createRawProtoco case Type::Virtual: return ProtocolInterfaceVirtual::createRawProtocolInterfaceVirtual(networkInterfaceName, { { 0x00, 0x01, 0x02, 0x03, 0x04, 0x05 } }, executorName); #endif // HAVE_PROTOCOL_INTERFACE_VIRTUAL +#if defined(HAVE_PROTOCOL_INTERFACE_SERIAL) + case Type::Serial: + return ProtocolInterfaceSerial::createRawProtocolInterfaceSerial(networkInterfaceName, executorName); +#endif // HAVE_PROTOCOL_INTERFACE_SERIAL +#if defined(HAVE_PROTOCOL_INTERFACE_LOCAL) + case Type::Local: + return ProtocolInterfaceLocal::createRawProtocolInterfaceLocal(networkInterfaceName, executorName); +#endif // HAVE_PROTOCOL_INTERFACE_LOCAL default: break; } @@ -222,6 +248,10 @@ std::string LA_AVDECC_CALL_CONVENTION ProtocolInterface::typeToString(Type const return "IEEE Std 1722.1 proxy"; case Type::Virtual: return "Virtual interface"; + case Type::Serial: + return "Serial port interface"; + case Type::Local: + return "Local domain socket interface"; default: return "Unknown protocol interface type"; } @@ -264,6 +294,22 @@ ProtocolInterface::SupportedProtocolInterfaceTypes LA_AVDECC_CALL_CONVENTION Pro s_supportedProtocolInterfaceTypes.set(Type::Virtual); } #endif // HAVE_PROTOCOL_INTERFACE_VIRTUAL + + // Serial +#if defined(HAVE_PROTOCOL_INTERFACE_SERIAL) + if (protocol::ProtocolInterfaceSerial::isSupported()) + { + s_supportedProtocolInterfaceTypes.set(Type::Serial); + } +#endif // HAVE_PROTOCOL_INTERFACE_SERIAL + + // Local domain socket +#if defined(HAVE_PROTOCOL_INTERFACE_LOCAL) + if (protocol::ProtocolInterfaceLocal::isSupported()) + { + s_supportedProtocolInterfaceTypes.set(Type::Local); + } +#endif // HAVE_PROTOCOL_INTERFACE_LOCAL } return s_supportedProtocolInterfaceTypes; diff --git a/src/protocolInterface/protocolInterface_local.cpp b/src/protocolInterface/protocolInterface_local.cpp new file mode 100644 index 00000000..e69f991c --- /dev/null +++ b/src/protocolInterface/protocolInterface_local.cpp @@ -0,0 +1,672 @@ +/* +* Copyright (C) 2024, L-Acoustics and its contributors + +* This file is part of LA_avdecc. + +* LA_avdecc is free software: you can redistribute it and/or modify +* it under the terms of the GNU Lesser General Public License as published by +* the Free Software Foundation, either version 3 of the License, or +* (at your option) any later version. + +* LA_avdecc 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 Lesser General Public License for more details. + +* You should have received a copy of the GNU Lesser General Public License +* along with LA_avdecc. If not, see . +*/ + +/** +* @file protocolInterface_local.cpp +* @author Luke Howard +*/ + +#include "la/avdecc/internals/serialization.hpp" +#include "la/avdecc/internals/protocolAemAecpdu.hpp" +#include "la/avdecc/internals/protocolAaAecpdu.hpp" +#include "la/avdecc/watchDog.hpp" +#include "la/avdecc/utils.hpp" +#include "la/avdecc/executor.hpp" + +#include "stateMachine/stateMachineManager.hpp" +#include "ethernetPacketDispatch.hpp" +#include "protocolInterface_local.hpp" +#include "logHelper.hpp" + + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +namespace la +{ +namespace avdecc +{ +namespace protocol +{ + +static constexpr int SocketReceiveLoopTimeout = 250u; + +class ProtocolInterfaceLocalImpl final : public ProtocolInterfaceLocal, private stateMachine::ProtocolInterfaceDelegate, private stateMachine::AdvertiseStateMachine::Delegate, private stateMachine::DiscoveryStateMachine::Delegate, private stateMachine::CommandStateMachine::Delegate +{ +public: + /* ************************************************************ */ + /* Public APIs */ + /* ************************************************************ */ + /** Constructor */ + ProtocolInterfaceLocalImpl(std::string const& networkInterfaceName, std::string const& executorName) + : ProtocolInterfaceLocal(networkInterfaceName, executorName) + { + // open socket + sockaddr_un sun{}; + + _fd = socket(AF_LOCAL, SOCK_DGRAM, 0); + if (_fd < 0) + { + throw Exception(Error::TransportError, "Failed to create local domain socket"); + } + + sun.sun_family = AF_LOCAL; + if (networkInterfaceName.size() >= sizeof(sun.sun_path)) + { + throw Exception(Error::InvalidParameters, "Local domain socket path too long"); + } + strncpy(sun.sun_path, networkInterfaceName.c_str(), sizeof(sun.sun_path) - 1); +#if defined(__APPLE__) + sun.sun_len = SUN_LEN(&sun); +#endif + + auto err = connect(_fd, reinterpret_cast(&sun), sizeof(sun)); + if (err < 0) + { + throw Exception(Error::TransportError, "Faild to connect local domain socket to peer"); + } + + // Start the capture thread + _captureThread = std::thread( + [this] + { + utils::setCurrentThreadName("avdecc::LocalInterface::Capture"); + socketReceiveLoop(); + if (!_shouldTerminate) + { + notifyObserversMethod(&ProtocolInterface::Observer::onTransportError, this); + } + }); + + // Start the state machines + _stateMachineManager.startStateMachines(); + } + + /** Destructor */ + virtual ~ProtocolInterfaceLocalImpl() noexcept + { + shutdown(); + } + + /** Destroy method for COM-like interface */ + virtual void destroy() noexcept override + { + delete this; + } + + // Deleted compiler auto-generated methods + ProtocolInterfaceLocalImpl(ProtocolInterfaceLocalImpl&&) = delete; + ProtocolInterfaceLocalImpl(ProtocolInterfaceLocalImpl const&) = delete; + ProtocolInterfaceLocalImpl& operator=(ProtocolInterfaceLocalImpl const&) = delete; + ProtocolInterfaceLocalImpl& operator=(ProtocolInterfaceLocalImpl&&) = delete; + +private: + /* ************************************************************ */ + /* ProtocolInterface overrides */ + /* ************************************************************ */ + virtual void shutdown() noexcept override + { + // Stop the state machines + _stateMachineManager.stopStateMachines(); + + // Notify the thread we are shutting down + _shouldTerminate = true; + + // Wait for the thread to complete its pending tasks + if (_captureThread.joinable()) + { + _captureThread.join(); + } + + // Flush executor jobs + la::avdecc::ExecutorManager::getInstance().flush(getExecutorName()); + + // Close underlying file descriptor. + if (_fd != -1) + { + close(_fd); + } + } + + virtual UniqueIdentifier getDynamicEID() const noexcept override + { + UniqueIdentifier::value_type eid{ 0u }; + auto const& macAddress = getMacAddress(); + + eid += macAddress[0]; + eid <<= 8; + eid += macAddress[1]; + eid <<= 8; + eid += macAddress[2]; + eid <<= 16; + std::srand(static_cast(std::time(0))); + eid += static_cast((std::rand() % 0xFFFD) + 1); + eid <<= 8; + eid += macAddress[3]; + eid <<= 8; + eid += macAddress[4]; + eid <<= 8; + eid += macAddress[5]; + + return UniqueIdentifier{ eid }; + } + + virtual void releaseDynamicEID(UniqueIdentifier const /*entityID*/) const noexcept override + { + // Nothing to do + } + + virtual Error registerLocalEntity(entity::LocalEntity& entity) noexcept override + { + // Checks if entity has declared an InterfaceInformation matching this ProtocolInterface + auto const index = _stateMachineManager.getMatchingInterfaceIndex(entity); + + if (index) + { + return _stateMachineManager.registerLocalEntity(entity); + } + + return Error::InvalidParameters; + } + + virtual Error unregisterLocalEntity(entity::LocalEntity& entity) noexcept override + { + return _stateMachineManager.unregisterLocalEntity(entity); + } + + virtual Error injectRawPacket(la::avdecc::MemoryBuffer&& packet) const noexcept override + { + processRawPacket(std::move(packet)); + return Error::NoError; + } + + virtual Error setEntityNeedsAdvertise(entity::LocalEntity const& entity, entity::LocalEntity::AdvertiseFlags const /*flags*/) noexcept override + { + return _stateMachineManager.setEntityNeedsAdvertise(entity); + } + + virtual Error enableEntityAdvertising(entity::LocalEntity& entity) noexcept override + { + return _stateMachineManager.enableEntityAdvertising(entity); + } + + virtual Error disableEntityAdvertising(entity::LocalEntity const& entity) noexcept override + { + return _stateMachineManager.disableEntityAdvertising(entity); + } + + virtual Error discoverRemoteEntities() const noexcept override + { + return discoverRemoteEntity(UniqueIdentifier::getNullUniqueIdentifier()); + } + + virtual Error discoverRemoteEntity(UniqueIdentifier const entityID) const noexcept override + { + auto const frame = stateMachine::Manager::makeDiscoveryMessage(getMacAddress(), entityID); + auto const err = sendMessage(frame); + if (!err) + { + _stateMachineManager.discoverMessageSent(); // Notify we are sending a discover message + } + return err; + } + + virtual Error setAutomaticDiscoveryDelay(std::chrono::milliseconds const delay) const noexcept override + { + return _stateMachineManager.setAutomaticDiscoveryDelay(delay); + } + + virtual bool isDirectMessageSupported() const noexcept override + { + return true; + } + + virtual Error sendAdpMessage(Adpdu const& adpdu) const noexcept override + { + return sendMessage(adpdu); + } + + virtual Error sendAecpMessage(Aecpdu const& aecpdu) const noexcept override + { + return sendMessage(aecpdu); + } + + virtual Error sendAcmpMessage(Acmpdu const& acmpdu) const noexcept override + { + return sendMessage(acmpdu); + } + + virtual Error sendAecpCommand(Aecpdu::UniquePointer&& aecpdu, AecpCommandResultHandler const& onResult) const noexcept override + { + auto const messageType = aecpdu->getMessageType(); + + if (!AVDECC_ASSERT_WITH_RET(!isAecpResponseMessageType(messageType), "Calling sendAecpCommand with a Response MessageType")) + { + return Error::MessageNotSupported; + } + + // Special check for VendorUnique messages + if (messageType == AecpMessageType::VendorUniqueCommand) + { + auto& vuAecp = static_cast(*aecpdu); + + auto const vuProtocolID = vuAecp.getProtocolIdentifier(); + auto* vuDelegate = getVendorUniqueDelegate(vuProtocolID); + + // No delegate, or the messages are not handled by the ControllerStateMachine + if (!vuDelegate || !vuDelegate->areHandledByControllerStateMachine(vuProtocolID)) + { + return Error::MessageNotSupported; + } + } + + // Command goes through the state machine to handle timeout, retry and response + return _stateMachineManager.sendAecpCommand(std::move(aecpdu), onResult); + } + + virtual Error sendAecpResponse(Aecpdu::UniquePointer&& aecpdu) const noexcept override + { + auto const messageType = aecpdu->getMessageType(); + + if (!AVDECC_ASSERT_WITH_RET(isAecpResponseMessageType(messageType), "Calling sendAecpResponse with a Command MessageType")) + { + return Error::MessageNotSupported; + } + + // Special check for VendorUnique messages + if (messageType == AecpMessageType::VendorUniqueResponse) + { + auto& vuAecp = static_cast(*aecpdu); + + auto const vuProtocolID = vuAecp.getProtocolIdentifier(); + auto* vuDelegate = getVendorUniqueDelegate(vuProtocolID); + + // No delegate, or the messages are not handled by the ControllerStateMachine + if (!vuDelegate || !vuDelegate->areHandledByControllerStateMachine(vuProtocolID)) + { + return Error::MessageNotSupported; + } + } + + // Response can be directly sent + return sendMessage(static_cast(*aecpdu)); + } + + virtual Error sendAcmpCommand(Acmpdu::UniquePointer&& acmpdu, AcmpCommandResultHandler const& onResult) const noexcept override + { + // Command goes through the state machine to handle timeout, retry and response + return _stateMachineManager.sendAcmpCommand(std::move(acmpdu), onResult); + } + + virtual Error sendAcmpResponse(Acmpdu::UniquePointer&& acmpdu) const noexcept override + { + // Response can be directly sent + return sendMessage(static_cast(*acmpdu)); + } + + virtual void lock() const noexcept override + { + _stateMachineManager.lock(); + } + + virtual void unlock() const noexcept override + { + _stateMachineManager.unlock(); + } + + virtual bool isSelfLocked() const noexcept override + { + return _stateMachineManager.isSelfLocked(); + } + + /* ************************************************************ */ + /* stateMachine::ProtocolInterfaceDelegate overrides */ + /* ************************************************************ */ + /* **** AECP notifications **** */ + virtual void onAecpCommand(Aecpdu const& aecpdu) noexcept override + { + // Notify observers + notifyObserversMethod(&ProtocolInterface::Observer::onAecpCommand, this, aecpdu); + } + + /* **** ACMP notifications **** */ + virtual void onAcmpCommand(Acmpdu const& acmpdu) noexcept override + { + // Notify observers + notifyObserversMethod(&ProtocolInterface::Observer::onAcmpCommand, this, acmpdu); + } + + virtual void onAcmpResponse(Acmpdu const& acmpdu) noexcept override + { + // Notify observers + notifyObserversMethod(&ProtocolInterface::Observer::onAcmpResponse, this, acmpdu); + } + + /* **** Sending methods **** */ + virtual Error sendMessage(Adpdu const& adpdu) const noexcept override + { + try + { + SerializationBuffer buffer; + + // Then Avtp control + serialize(adpdu, buffer); + // Then with Adp + serialize(adpdu, buffer); + + // Send the message + return sendPacket(buffer); + } + catch ([[maybe_unused]] std::exception const& e) + { + LOG_PROTOCOL_INTERFACE_DEBUG(adpdu.getSrcAddress(), adpdu.getDestAddress(), std::string("Failed to serialize ADPDU: ") + e.what()); + return Error::InternalError; + } + } + + virtual Error sendMessage(Aecpdu const& aecpdu) const noexcept override + { + try + { + SerializationBuffer buffer; + + // Then Avtp control + serialize(aecpdu, buffer); + // Then with Aecp + serialize(aecpdu, buffer); + + // Send the message + return sendPacket(buffer); + } + catch ([[maybe_unused]] std::exception const& e) + { + LOG_PROTOCOL_INTERFACE_DEBUG(aecpdu.getSrcAddress(), aecpdu.getDestAddress(), std::string("Failed to serialize AECPDU: ") + e.what()); + return Error::InternalError; + } + } + + virtual Error sendMessage(Acmpdu const& acmpdu) const noexcept override + { + try + { + SerializationBuffer buffer; + + // Then Avtp control + serialize(acmpdu, buffer); + // Then with Acmp + serialize(acmpdu, buffer); + + // Send the message + return sendPacket(buffer); + } + catch ([[maybe_unused]] std::exception const& e) + { + LOG_PROTOCOL_INTERFACE_DEBUG(acmpdu.getSrcAddress(), Acmpdu::Multicast_Mac_Address, "Failed to serialize ACMPDU: {}", e.what()); + return Error::InternalError; + } + } + + /* *** Other methods **** */ + virtual std::uint32_t getVuAecpCommandTimeoutMsec(VuAecpdu::ProtocolIdentifier const& protocolIdentifier, VuAecpdu const& aecpdu) const noexcept override + { + return getVuAecpCommandTimeout(protocolIdentifier, aecpdu); + } + + /* ************************************************************ */ + /* stateMachine::AdvertiseStateMachine::Delegate overrides */ + /* ************************************************************ */ + + /* ************************************************************ */ + /* stateMachine::DiscoveryStateMachine::Delegate overrides */ + /* ************************************************************ */ + virtual void onLocalEntityOnline(entity::Entity const& entity) noexcept override + { + // Notify observers + notifyObserversMethod(&ProtocolInterface::Observer::onLocalEntityOnline, this, entity); + } + + virtual void onLocalEntityOffline(UniqueIdentifier const entityID) noexcept override + { + // Notify observers + notifyObserversMethod(&ProtocolInterface::Observer::onLocalEntityOffline, this, entityID); + } + + virtual void onLocalEntityUpdated(entity::Entity const& entity) noexcept override + { + // Notify observers + notifyObserversMethod(&ProtocolInterface::Observer::onLocalEntityUpdated, this, entity); + } + + virtual void onRemoteEntityOnline(entity::Entity const& entity) noexcept override + { + // Notify observers + notifyObserversMethod(&ProtocolInterface::Observer::onRemoteEntityOnline, this, entity); + } + + virtual void onRemoteEntityOffline(UniqueIdentifier const entityID) noexcept override + { + // Notify observers + notifyObserversMethod(&ProtocolInterface::Observer::onRemoteEntityOffline, this, entityID); + + // Notify the StateMachineManager + _stateMachineManager.onRemoteEntityOffline(entityID); + } + + virtual void onRemoteEntityUpdated(entity::Entity const& entity) noexcept override + { + // Notify observers + notifyObserversMethod(&ProtocolInterface::Observer::onRemoteEntityUpdated, this, entity); + } + + /* ************************************************************ */ + /* stateMachine::CommandStateMachine::Delegate overrides */ + /* ************************************************************ */ + virtual void onAecpAemUnsolicitedResponse(AemAecpdu const& aecpdu) noexcept override + { + // Notify observers + notifyObserversMethod(&ProtocolInterface::Observer::onAecpAemUnsolicitedResponse, this, aecpdu); + } + + virtual void onAecpAemIdentifyNotification(AemAecpdu const& aecpdu) noexcept override + { + // Notify observers + notifyObserversMethod(&ProtocolInterface::Observer::onAecpAemIdentifyNotification, this, aecpdu); + } + virtual void onAecpRetry(UniqueIdentifier const& entityID) noexcept override + { + // Notify observers + notifyObserversMethod(&ProtocolInterface::Observer::onAecpRetry, this, entityID); + } + virtual void onAecpTimeout(UniqueIdentifier const& entityID) noexcept override + { + // Notify observers + notifyObserversMethod(&ProtocolInterface::Observer::onAecpTimeout, this, entityID); + } + virtual void onAecpUnexpectedResponse(UniqueIdentifier const& entityID) noexcept override + { + // Notify observers + notifyObserversMethod(&ProtocolInterface::Observer::onAecpUnexpectedResponse, this, entityID); + } + virtual void onAecpResponseTime(UniqueIdentifier const& entityID, std::chrono::milliseconds const& responseTime) noexcept override + { + // Notify observers + notifyObserversMethod(&ProtocolInterface::Observer::onAecpResponseTime, this, entityID, responseTime); + } + + /* ************************************************************ */ + /* la::avdecc::utils::Subject overrides */ + /* ************************************************************ */ + virtual void onObserverRegistered(observer_type* const observer) noexcept override + { + if (observer) + { + class DiscoveryDelegate final : public stateMachine::DiscoveryStateMachine::Delegate + { + public: + DiscoveryDelegate(ProtocolInterface& pi, ProtocolInterface::Observer& obs) + : _pi{ pi } + , _obs{ obs } + { + } + + private: + virtual void onLocalEntityOnline(la::avdecc::entity::Entity const& entity) noexcept override + { + utils::invokeProtectedMethod(&ProtocolInterface::Observer::onLocalEntityOnline, &_obs, &_pi, entity); + } + virtual void onLocalEntityOffline(la::avdecc::UniqueIdentifier const /*entityID*/) noexcept override {} + virtual void onLocalEntityUpdated(la::avdecc::entity::Entity const& /*entity*/) noexcept override {} + virtual void onRemoteEntityOnline(la::avdecc::entity::Entity const& entity) noexcept override + { + utils::invokeProtectedMethod(&ProtocolInterface::Observer::onRemoteEntityOnline, &_obs, &_pi, entity); + } + virtual void onRemoteEntityOffline(la::avdecc::UniqueIdentifier const /*entityID*/) noexcept override {} + virtual void onRemoteEntityUpdated(la::avdecc::entity::Entity const& /*entity*/) noexcept override {} + + ProtocolInterface& _pi; + ProtocolInterface::Observer& _obs; + }; + auto discoveryDelegate = DiscoveryDelegate{ *this, static_cast(*observer) }; + + _stateMachineManager.notifyDiscoveredEntities(discoveryDelegate); + } + } + + /* ************************************************************ */ + /* Private methods */ + /* ************************************************************ */ + void processRawPacket(la::avdecc::MemoryBuffer&& packet) const noexcept + { + la::avdecc::ExecutorManager::getInstance().pushJob(getExecutorName(), + [this, msg = std::move(packet)]() + { + std::uint8_t const* avtpdu = msg.data(); // Start of AVB Transport Protocol + auto avtpdu_size = msg.size(); + + auto etherLayer2 = EtherLayer2{}; + etherLayer2.setEtherType(AvtpEtherType); + etherLayer2.setSrcAddress(getMacAddress()); // zero + etherLayer2.setDestAddress(getMacAddress()); // zero + + // Try to detect possible deadlock + { + _watchDog.registerWatch("avdecc::LocalInterface::dispatchAvdeccMessage::" + utils::toHexString(reinterpret_cast(this)), std::chrono::milliseconds{ 1000u }, true); + _ethernetPacketDispatcher.dispatchAvdeccMessage(avtpdu, avtpdu_size, etherLayer2); + _watchDog.unregisterWatch("avdecc::LocalInterface::dispatchAvdeccMessage::" + utils::toHexString(reinterpret_cast(this)), true); + } + }); + } + + void socketReceiveLoop(void) noexcept + { + struct ::pollfd pollfd; + std::uint8_t payloadBuffer[AvtpMaxPayloadLength]; + + pollfd.fd = _fd; + + while (!_shouldTerminate) + { + iovec iov = { .iov_base = payloadBuffer, .iov_len = sizeof(payloadBuffer) }; + msghdr msg{}; + + pollfd.events = POLLIN; + pollfd.revents = 0; + + auto const err = poll(&pollfd, 1, SocketReceiveLoopTimeout); // timeout so we can check _shouldTerminate + if (err < 0) + { + break; + } + else if (err == 0 || pollfd.events != POLLIN) + { + continue; // timed out or no input events + } + + msg.msg_iov = &iov; + msg.msg_iovlen = 1; + + auto const bytesReceived = recvmsg(_fd, &msg, 0); + if (bytesReceived > 0) + { + auto message = la::avdecc::MemoryBuffer{ payloadBuffer, static_cast(bytesReceived) }; + processRawPacket(std::move(message)); + } + } + } + + Error sendPacket(SerializationBuffer const& buffer) const noexcept + { + iovec iov = { .iov_base = const_cast(reinterpret_cast(buffer.data())), .iov_len = buffer.size() }; + msghdr msg{}; + + msg.msg_iov = &iov; + msg.msg_iovlen = 1; + + auto const bytesSent = sendmsg(_fd, &msg, 0); + if (bytesSent != buffer.size()) + { + return Error::TransportError; + } + + return Error::NoError; + } + + // Private variables + watchDog::WatchDog::SharedPointer _watchDogSharedPointer{ watchDog::WatchDog::getInstance() }; + watchDog::WatchDog& _watchDog{ *_watchDogSharedPointer }; + int _fd{ -1 }; + bool _shouldTerminate{ false }; + mutable stateMachine::Manager _stateMachineManager{ this, this, this, this, this }; + std::thread _captureThread{}; + friend class EthernetPacketDispatcher; + EthernetPacketDispatcher _ethernetPacketDispatcher{ this, _stateMachineManager }; +}; + +ProtocolInterfaceLocal::ProtocolInterfaceLocal(std::string const& networkInterfaceName, std::string const& executorName) + : ProtocolInterface(executorName) +{ +} + +bool ProtocolInterfaceLocal::isSupported() noexcept +{ + return true; +} + +ProtocolInterfaceLocal* ProtocolInterfaceLocal::createRawProtocolInterfaceLocal(std::string const& networkInterfaceName, std::string const& executorName) +{ + return new ProtocolInterfaceLocalImpl(networkInterfaceName, executorName); +} + +} // namespace protocol +} // namespace avdecc +} // namespace la diff --git a/src/protocolInterface/protocolInterface_local.hpp b/src/protocolInterface/protocolInterface_local.hpp new file mode 100644 index 00000000..e2c06987 --- /dev/null +++ b/src/protocolInterface/protocolInterface_local.hpp @@ -0,0 +1,66 @@ +/* +* Copyright (C) 2016-2024, L-Acoustics and its contributors + +* This file is part of LA_avdecc. + +* LA_avdecc is free software: you can redistribute it and/or modify +* it under the terms of the GNU Lesser General Public License as published by +* the Free Software Foundation, either version 3 of the License, or +* (at your option) any later version. + +* LA_avdecc 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 Lesser General Public License for more details. + +* You should have received a copy of the GNU Lesser General Public License +* along with LA_avdecc. If not, see . +*/ + +/** +* @file protocolInterface_local.hpp +* @author Luke Howard +*/ + +#pragma once + +#include "la/avdecc/internals/protocolInterface.hpp" + +namespace la +{ +namespace avdecc +{ +namespace protocol +{ +class ProtocolInterfaceLocal : public ProtocolInterface +{ +public: + /** + * @brief Factory method to create a new ProtocolInterfaceLocal. + * @details Creates a new ProtocolInterfaceLocal as a raw pointer. + * @param[in] networkInterfaceName A path to the local domain socket. + * @param[in] executorName The name of the executor to use to dispatch incoming messages. + * @return A new ProtocolInterfaceLocal as a raw pointer. + * @note Throws Exception if #interfaceName is invalid or inaccessible. + */ + static ProtocolInterfaceLocal* createRawProtocolInterfaceLocal(std::string const& networkInterfaceName, std::string const& executorName); + + /** Returns true if this ProtocolInterface is supported (runtime check) */ + static bool isSupported() noexcept; + + /** Destructor */ + virtual ~ProtocolInterfaceLocal() noexcept = default; + + // Deleted compiler auto-generated methods + ProtocolInterfaceLocal(ProtocolInterfaceLocal&&) = delete; + ProtocolInterfaceLocal(ProtocolInterfaceLocal const&) = delete; + ProtocolInterfaceLocal& operator=(ProtocolInterfaceLocal const&) = delete; + ProtocolInterfaceLocal& operator=(ProtocolInterfaceLocal&&) = delete; + +protected: + ProtocolInterfaceLocal(std::string const& networkInterfaceName, std::string const& executorName); +}; + +} // namespace protocol +} // namespace avdecc +} // namespace la diff --git a/src/protocolInterface/protocolInterface_serial.cpp b/src/protocolInterface/protocolInterface_serial.cpp new file mode 100644 index 00000000..0ac03cbf --- /dev/null +++ b/src/protocolInterface/protocolInterface_serial.cpp @@ -0,0 +1,848 @@ +/* +* Copyright (C) 2024, L-Acoustics and its contributors + +* This file is part of LA_avdecc. + +* LA_avdecc is free software: you can redistribute it and/or modify +* it under the terms of the GNU Lesser General Public License as published by +* the Free Software Foundation, either version 3 of the License, or +* (at your option) any later version. + +* LA_avdecc 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 Lesser General Public License for more details. + +* You should have received a copy of the GNU Lesser General Public License +* along with LA_avdecc. If not, see . +*/ + +/** +* @file protocolInterface_serial.cpp +* @author Luke Howard +*/ + +#include "la/avdecc/internals/serialization.hpp" +#include "la/avdecc/internals/protocolAemAecpdu.hpp" +#include "la/avdecc/internals/protocolAaAecpdu.hpp" +#include "la/avdecc/watchDog.hpp" +#include "la/avdecc/utils.hpp" +#include "la/avdecc/executor.hpp" + +#include "stateMachine/stateMachineManager.hpp" +#include "ethernetPacketDispatch.hpp" +#include "protocolInterface_serial.hpp" +#include "logHelper.hpp" + +#include "cobsSerialization.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#ifdef __linux__ +# include +# include +# include +#else +# include +#endif + +namespace la +{ +namespace avdecc +{ +namespace protocol +{ + +#ifndef __linux__ +static const std::map SpeedMap = { { 9600, B9600 }, { 19200, B19200 }, { 38400, B38400 }, { 57600, B57600 }, { 115200, B115200 }, { 230400, B230400 } }; +#endif + +static constexpr int SerialReceiveLoopTimeout = 250u; + +// we need a valid non-zero MAC address to represent the serial port +// use a locally assigned address assigned by the author (PADL CID) +static constexpr networkInterface::MacAddress Serial_Mac_Address = { 0x0A, 0xE9, 0x1B, 0xFF, 0xFF, 0xFF }; + +class ProtocolInterfaceSerialImpl final : public ProtocolInterfaceSerial, private stateMachine::ProtocolInterfaceDelegate, private stateMachine::AdvertiseStateMachine::Delegate, private stateMachine::DiscoveryStateMachine::Delegate, private stateMachine::CommandStateMachine::Delegate +{ +public: + /* ************************************************************ */ + /* Public APIs */ + /* ************************************************************ */ + /** Constructor */ + ProtocolInterfaceSerialImpl(std::string const& networkInterfaceName, std::string const& executorName) + : ProtocolInterfaceSerial(networkInterfaceName, executorName) + { + auto deviceNameParameters = utils::tokenizeString(networkInterfaceName, '@', false); + + if (deviceNameParameters.size() < 1 || deviceNameParameters.size() > 2) + { + throw Exception(Error::InvalidParameters, "Expected serial port device name format path[@speed]"); + } + + // Get file descriptor + _fd = ::open(deviceNameParameters[0].c_str(), O_RDWR); + if (_fd < 0) + { + throw Exception(Error::TransportError, "Failed to open serial port"); + } + + auto error = configureNonBlockingIO(); + if (error == Error::NoError) + { + std::size_t speed = (deviceNameParameters.size() > 1) ? std::stoi(deviceNameParameters[1]) : 0; + error = configureTty(speed); + } + if (error != Error::NoError) + { + throw Exception(error, "Failed to set serial port parameters"); + } + + // Start the capture thread + _captureThread = std::thread( + [this] + { + utils::setCurrentThreadName("avdecc::SerialInterface::Capture"); + serialReceiveLoop(); + if (!_shouldTerminate) + { + notifyObserversMethod(&ProtocolInterface::Observer::onTransportError, this); + } + }); + + // Start the state machines + _stateMachineManager.startStateMachines(); + } + + /** Destructor */ + virtual ~ProtocolInterfaceSerialImpl() noexcept + { + shutdown(); + } + + /** Destroy method for COM-like interface */ + virtual void destroy() noexcept override + { + delete this; + } + + // Deleted compiler auto-generated methods + ProtocolInterfaceSerialImpl(ProtocolInterfaceSerialImpl&&) = delete; + ProtocolInterfaceSerialImpl(ProtocolInterfaceSerialImpl const&) = delete; + ProtocolInterfaceSerialImpl& operator=(ProtocolInterfaceSerialImpl const&) = delete; + ProtocolInterfaceSerialImpl& operator=(ProtocolInterfaceSerialImpl&&) = delete; + +private: + /* ************************************************************ */ + /* ProtocolInterface overrides */ + /* ************************************************************ */ + virtual void shutdown() noexcept override + { + // Stop the state machines + _stateMachineManager.stopStateMachines(); + + // Notify the thread we are shutting down + _shouldTerminate = true; + + // Wait for the thread to complete its pending tasks + if (_captureThread.joinable()) + { + _captureThread.join(); + } + + // Flush executor jobs + la::avdecc::ExecutorManager::getInstance().flush(getExecutorName()); + + // Close underlying file descriptor. + if (_fd != -1) + { + close(_fd); + } + } + + virtual UniqueIdentifier getDynamicEID() const noexcept override + { + UniqueIdentifier::value_type eid{ 0u }; + auto const& macAddress = getMacAddress(); + + eid += macAddress[0]; + eid <<= 8; + eid += macAddress[1]; + eid <<= 8; + eid += macAddress[2]; + eid <<= 16; + std::srand(static_cast(std::time(0))); + eid += static_cast((std::rand() % 0xFFFD) + 1); + eid <<= 8; + eid += macAddress[3]; + eid <<= 8; + eid += macAddress[4]; + eid <<= 8; + eid += macAddress[5]; + + return UniqueIdentifier{ eid }; + } + + virtual void releaseDynamicEID(UniqueIdentifier const /*entityID*/) const noexcept override + { + // Nothing to do + } + + virtual Error registerLocalEntity(entity::LocalEntity& entity) noexcept override + { + // Checks if entity has declared an InterfaceInformation matching this ProtocolInterface + auto const index = _stateMachineManager.getMatchingInterfaceIndex(entity); + + if (index) + { + return _stateMachineManager.registerLocalEntity(entity); + } + + return Error::InvalidParameters; + } + + virtual Error unregisterLocalEntity(entity::LocalEntity& entity) noexcept override + { + return _stateMachineManager.unregisterLocalEntity(entity); + } + + virtual Error injectRawPacket(la::avdecc::MemoryBuffer&& packet) const noexcept override + { + processRawPacket(std::move(packet)); + return Error::NoError; + } + + virtual Error setEntityNeedsAdvertise(entity::LocalEntity const& entity, entity::LocalEntity::AdvertiseFlags const /*flags*/) noexcept override + { + return _stateMachineManager.setEntityNeedsAdvertise(entity); + } + + virtual Error enableEntityAdvertising(entity::LocalEntity& entity) noexcept override + { + return _stateMachineManager.enableEntityAdvertising(entity); + } + + virtual Error disableEntityAdvertising(entity::LocalEntity const& entity) noexcept override + { + return _stateMachineManager.disableEntityAdvertising(entity); + } + + virtual Error discoverRemoteEntities() const noexcept override + { + return discoverRemoteEntity(UniqueIdentifier::getNullUniqueIdentifier()); + } + + virtual Error discoverRemoteEntity(UniqueIdentifier const entityID) const noexcept override + { + auto const frame = stateMachine::Manager::makeDiscoveryMessage(getMacAddress(), entityID); + auto const err = sendMessage(frame); + if (!err) + { + _stateMachineManager.discoverMessageSent(); // Notify we are sending a discover message + } + return err; + } + + virtual Error setAutomaticDiscoveryDelay(std::chrono::milliseconds const delay) const noexcept override + { + return _stateMachineManager.setAutomaticDiscoveryDelay(delay); + } + + virtual bool isDirectMessageSupported() const noexcept override + { + return true; + } + + virtual Error sendAdpMessage(Adpdu const& adpdu) const noexcept override + { + return sendMessage(adpdu); + } + + virtual Error sendAecpMessage(Aecpdu const& aecpdu) const noexcept override + { + return sendMessage(aecpdu); + } + + virtual Error sendAcmpMessage(Acmpdu const& acmpdu) const noexcept override + { + return sendMessage(acmpdu); + } + + virtual Error sendAecpCommand(Aecpdu::UniquePointer&& aecpdu, AecpCommandResultHandler const& onResult) const noexcept override + { + auto const messageType = aecpdu->getMessageType(); + + if (!AVDECC_ASSERT_WITH_RET(!isAecpResponseMessageType(messageType), "Calling sendAecpCommand with a Response MessageType")) + { + return Error::MessageNotSupported; + } + + // Special check for VendorUnique messages + if (messageType == AecpMessageType::VendorUniqueCommand) + { + auto& vuAecp = static_cast(*aecpdu); + + auto const vuProtocolID = vuAecp.getProtocolIdentifier(); + auto* vuDelegate = getVendorUniqueDelegate(vuProtocolID); + + // No delegate, or the messages are not handled by the ControllerStateMachine + if (!vuDelegate || !vuDelegate->areHandledByControllerStateMachine(vuProtocolID)) + { + return Error::MessageNotSupported; + } + } + + // Command goes through the state machine to handle timeout, retry and response + return _stateMachineManager.sendAecpCommand(std::move(aecpdu), onResult); + } + + virtual Error sendAecpResponse(Aecpdu::UniquePointer&& aecpdu) const noexcept override + { + auto const messageType = aecpdu->getMessageType(); + + if (!AVDECC_ASSERT_WITH_RET(isAecpResponseMessageType(messageType), "Calling sendAecpResponse with a Command MessageType")) + { + return Error::MessageNotSupported; + } + + // Special check for VendorUnique messages + if (messageType == AecpMessageType::VendorUniqueResponse) + { + auto& vuAecp = static_cast(*aecpdu); + + auto const vuProtocolID = vuAecp.getProtocolIdentifier(); + auto* vuDelegate = getVendorUniqueDelegate(vuProtocolID); + + // No delegate, or the messages are not handled by the ControllerStateMachine + if (!vuDelegate || !vuDelegate->areHandledByControllerStateMachine(vuProtocolID)) + { + return Error::MessageNotSupported; + } + } + + // Response can be directly sent + return sendMessage(static_cast(*aecpdu)); + } + + virtual Error sendAcmpCommand(Acmpdu::UniquePointer&& acmpdu, AcmpCommandResultHandler const& onResult) const noexcept override + { + // Command goes through the state machine to handle timeout, retry and response + return _stateMachineManager.sendAcmpCommand(std::move(acmpdu), onResult); + } + + virtual Error sendAcmpResponse(Acmpdu::UniquePointer&& acmpdu) const noexcept override + { + // Response can be directly sent + return sendMessage(static_cast(*acmpdu)); + } + + virtual void lock() const noexcept override + { + _stateMachineManager.lock(); + } + + virtual void unlock() const noexcept override + { + _stateMachineManager.unlock(); + } + + virtual bool isSelfLocked() const noexcept override + { + return _stateMachineManager.isSelfLocked(); + } + + /* ************************************************************ */ + /* stateMachine::ProtocolInterfaceDelegate overrides */ + /* ************************************************************ */ + /* **** AECP notifications **** */ + virtual void onAecpCommand(Aecpdu const& aecpdu) noexcept override + { + // Notify observers + notifyObserversMethod(&ProtocolInterface::Observer::onAecpCommand, this, aecpdu); + } + + /* **** ACMP notifications **** */ + virtual void onAcmpCommand(Acmpdu const& acmpdu) noexcept override + { + // Notify observers + notifyObserversMethod(&ProtocolInterface::Observer::onAcmpCommand, this, acmpdu); + } + + virtual void onAcmpResponse(Acmpdu const& acmpdu) noexcept override + { + // Notify observers + notifyObserversMethod(&ProtocolInterface::Observer::onAcmpResponse, this, acmpdu); + } + + /* **** Sending methods **** */ + virtual Error sendMessage(Adpdu const& adpdu) const noexcept override + { + try + { + SerializationBuffer buffer; + + // Then Avtp control + serialize(adpdu, buffer); + // Then with Adp + serialize(adpdu, buffer); + + // Send the message + return sendPacket(buffer); + } + catch ([[maybe_unused]] std::exception const& e) + { + LOG_PROTOCOL_INTERFACE_DEBUG(adpdu.getSrcAddress(), adpdu.getDestAddress(), std::string("Failed to serialize ADPDU: ") + e.what()); + return Error::InternalError; + } + } + + virtual Error sendMessage(Aecpdu const& aecpdu) const noexcept override + { + try + { + SerializationBuffer buffer; + + // Then Avtp control + serialize(aecpdu, buffer); + // Then with Aecp + serialize(aecpdu, buffer); + + // Send the message + return sendPacket(buffer); + } + catch ([[maybe_unused]] std::exception const& e) + { + LOG_PROTOCOL_INTERFACE_DEBUG(aecpdu.getSrcAddress(), aecpdu.getDestAddress(), std::string("Failed to serialize AECPDU: ") + e.what()); + return Error::InternalError; + } + } + + virtual Error sendMessage(Acmpdu const& acmpdu) const noexcept override + { + try + { + SerializationBuffer buffer; + + // Then Avtp control + serialize(acmpdu, buffer); + // Then with Acmp + serialize(acmpdu, buffer); + + // Send the message + return sendPacket(buffer); + } + catch ([[maybe_unused]] std::exception const& e) + { + LOG_PROTOCOL_INTERFACE_DEBUG(acmpdu.getSrcAddress(), Acmpdu::Multicast_Mac_Address, "Failed to serialize ACMPDU: {}", e.what()); + return Error::InternalError; + } + } + + /* *** Other methods **** */ + virtual std::uint32_t getVuAecpCommandTimeoutMsec(VuAecpdu::ProtocolIdentifier const& protocolIdentifier, VuAecpdu const& aecpdu) const noexcept override + { + return getVuAecpCommandTimeout(protocolIdentifier, aecpdu); + } + + /* ************************************************************ */ + /* stateMachine::AdvertiseStateMachine::Delegate overrides */ + /* ************************************************************ */ + + /* ************************************************************ */ + /* stateMachine::DiscoveryStateMachine::Delegate overrides */ + /* ************************************************************ */ + virtual void onLocalEntityOnline(entity::Entity const& entity) noexcept override + { + // Notify observers + notifyObserversMethod(&ProtocolInterface::Observer::onLocalEntityOnline, this, entity); + } + + virtual void onLocalEntityOffline(UniqueIdentifier const entityID) noexcept override + { + // Notify observers + notifyObserversMethod(&ProtocolInterface::Observer::onLocalEntityOffline, this, entityID); + } + + virtual void onLocalEntityUpdated(entity::Entity const& entity) noexcept override + { + // Notify observers + notifyObserversMethod(&ProtocolInterface::Observer::onLocalEntityUpdated, this, entity); + } + + virtual void onRemoteEntityOnline(entity::Entity const& entity) noexcept override + { + // Notify observers + notifyObserversMethod(&ProtocolInterface::Observer::onRemoteEntityOnline, this, entity); + } + + virtual void onRemoteEntityOffline(UniqueIdentifier const entityID) noexcept override + { + // Notify observers + notifyObserversMethod(&ProtocolInterface::Observer::onRemoteEntityOffline, this, entityID); + + // Notify the StateMachineManager + _stateMachineManager.onRemoteEntityOffline(entityID); + } + + virtual void onRemoteEntityUpdated(entity::Entity const& entity) noexcept override + { + // Notify observers + notifyObserversMethod(&ProtocolInterface::Observer::onRemoteEntityUpdated, this, entity); + } + + /* ************************************************************ */ + /* stateMachine::CommandStateMachine::Delegate overrides */ + /* ************************************************************ */ + virtual void onAecpAemUnsolicitedResponse(AemAecpdu const& aecpdu) noexcept override + { + // Notify observers + notifyObserversMethod(&ProtocolInterface::Observer::onAecpAemUnsolicitedResponse, this, aecpdu); + } + + virtual void onAecpAemIdentifyNotification(AemAecpdu const& aecpdu) noexcept override + { + // Notify observers + notifyObserversMethod(&ProtocolInterface::Observer::onAecpAemIdentifyNotification, this, aecpdu); + } + virtual void onAecpRetry(UniqueIdentifier const& entityID) noexcept override + { + // Notify observers + notifyObserversMethod(&ProtocolInterface::Observer::onAecpRetry, this, entityID); + } + virtual void onAecpTimeout(UniqueIdentifier const& entityID) noexcept override + { + // Notify observers + notifyObserversMethod(&ProtocolInterface::Observer::onAecpTimeout, this, entityID); + } + virtual void onAecpUnexpectedResponse(UniqueIdentifier const& entityID) noexcept override + { + // Notify observers + notifyObserversMethod(&ProtocolInterface::Observer::onAecpUnexpectedResponse, this, entityID); + } + virtual void onAecpResponseTime(UniqueIdentifier const& entityID, std::chrono::milliseconds const& responseTime) noexcept override + { + // Notify observers + notifyObserversMethod(&ProtocolInterface::Observer::onAecpResponseTime, this, entityID, responseTime); + } + + /* ************************************************************ */ + /* la::avdecc::utils::Subject overrides */ + /* ************************************************************ */ + virtual void onObserverRegistered(observer_type* const observer) noexcept override + { + if (observer) + { + class DiscoveryDelegate final : public stateMachine::DiscoveryStateMachine::Delegate + { + public: + DiscoveryDelegate(ProtocolInterface& pi, ProtocolInterface::Observer& obs) + : _pi{ pi } + , _obs{ obs } + { + } + + private: + virtual void onLocalEntityOnline(la::avdecc::entity::Entity const& entity) noexcept override + { + utils::invokeProtectedMethod(&ProtocolInterface::Observer::onLocalEntityOnline, &_obs, &_pi, entity); + } + virtual void onLocalEntityOffline(la::avdecc::UniqueIdentifier const /*entityID*/) noexcept override {} + virtual void onLocalEntityUpdated(la::avdecc::entity::Entity const& /*entity*/) noexcept override {} + virtual void onRemoteEntityOnline(la::avdecc::entity::Entity const& entity) noexcept override + { + utils::invokeProtectedMethod(&ProtocolInterface::Observer::onRemoteEntityOnline, &_obs, &_pi, entity); + } + virtual void onRemoteEntityOffline(la::avdecc::UniqueIdentifier const /*entityID*/) noexcept override {} + virtual void onRemoteEntityUpdated(la::avdecc::entity::Entity const& /*entity*/) noexcept override {} + + ProtocolInterface& _pi; + ProtocolInterface::Observer& _obs; + }; + auto discoveryDelegate = DiscoveryDelegate{ *this, static_cast(*observer) }; + + _stateMachineManager.notifyDiscoveredEntities(discoveryDelegate); + } + } + + /* ************************************************************ */ + /* Private methods */ + /* ************************************************************ */ + void processRawPacket(la::avdecc::MemoryBuffer&& packet) const noexcept + { + la::avdecc::ExecutorManager::getInstance().pushJob(getExecutorName(), + [this, msg = std::move(packet)]() + { + std::uint8_t const* avtpdu = msg.data(); // Start of AVB Transport Protocol + auto avtpdu_size = msg.size(); + + auto etherLayer2 = EtherLayer2{}; + etherLayer2.setEtherType(AvtpEtherType); + etherLayer2.setSrcAddress(Serial_Mac_Address); + etherLayer2.setDestAddress(getMacAddress()); // initialized to zero by super class + + // Try to detect possible deadlock + { + _watchDog.registerWatch("avdecc::SerialInterface::dispatchAvdeccMessage::" + utils::toHexString(reinterpret_cast(this)), std::chrono::milliseconds{ 1000u }, true); + _ethernetPacketDispatcher.dispatchAvdeccMessage(avtpdu, avtpdu_size, etherLayer2); + _watchDog.unregisterWatch("avdecc::SerialInterface::dispatchAvdeccMessage::" + utils::toHexString(reinterpret_cast(this)), true); + } + }); + } + + static constexpr std::size_t AvtpMaxCobsEncodedPayloadLength = (1 + AvtpMaxPayloadLength + COBS_BUFFER_PAD(AvtpMaxPayloadLength) + 1); + + enum class SerialState : std::uint8_t + { + Synchronizing, + Reading, + Finished + }; + + void serialReceiveLoop(void) noexcept + { + struct ::pollfd pollfd; + std::uint8_t cobsEncodedBuffer[AvtpMaxCobsEncodedPayloadLength]; + SerialState state = SerialState::Synchronizing; + size_t cobsBytesRead; + + pollfd.fd = _fd; + + while (!_shouldTerminate) + { + std::uint8_t readBuffer[AvtpMaxCobsEncodedPayloadLength]; + + pollfd.events = POLLIN; + pollfd.revents = 0; + + if (state == SerialState::Synchronizing) + { + cobsBytesRead = 0; + } + + auto const err = poll(&pollfd, 1, SerialReceiveLoopTimeout); // timeout so we can check _shouldTerminate + if (err < 0) + { + break; + } + else if (err == 0 || pollfd.events != POLLIN) + { + continue; // timed out or no input events + } + + auto const bytesRead = read(_fd, readBuffer, sizeof(readBuffer)); + if (bytesRead == 0 || (bytesRead < 0 && errno == EAGAIN)) + { + continue; + } + else if (bytesRead < 0) + { + break; + } + + for (auto i = 0; i < bytesRead; i++) + { + if (cobsBytesRead >= AvtpMaxCobsEncodedPayloadLength) + { + state = SerialState::Synchronizing; + break; + } + + switch (state) + { + case SerialState::Synchronizing: + if (readBuffer[i] == cobs::DelimiterByte) + { + state = SerialState::Reading; + } + break; + case SerialState::Reading: + if (readBuffer[i] != cobs::DelimiterByte) + { + cobsEncodedBuffer[cobsBytesRead++] = readBuffer[i]; + break; + } + else + { + state = SerialState::Finished; + [[fallthrough]]; // end of frame marker + } + case SerialState::Finished: { + std::uint8_t payloadBuffer[AvtpMaxPayloadLength]; + try + { + auto payloadLength = cobs::decode(cobsEncodedBuffer, cobsBytesRead, payloadBuffer, sizeof(payloadBuffer)); + if (payloadLength != 0) + { + auto message = la::avdecc::MemoryBuffer{ payloadBuffer, payloadLength }; + processRawPacket(std::move(message)); + } + } + catch (...) + { + } + state = SerialState::Synchronizing; + break; + } + } + } + } + } + + Error sendPacket(SerializationBuffer const& buffer) const noexcept + { + std::uint8_t cobsEncodedBuffer[AvtpMaxCobsEncodedPayloadLength] = { cobs::DelimiterByte }; + auto cobsBytesEncoded = 1 + cobs::encode(buffer.data(), buffer.size(), &cobsEncodedBuffer[1]); + cobsEncodedBuffer[cobsBytesEncoded++] = cobs::DelimiterByte; + auto cobsBytesRemaining = cobsBytesEncoded; + + struct pollfd pollfd; + pollfd.fd = _fd; + + while (cobsBytesRemaining > 0) + { + pollfd.events = POLLOUT; + pollfd.revents = 0; + + auto const err = poll(&pollfd, 1, -1); + if (err < 0) + { + return Error::TransportError; + } + + if (pollfd.revents != POLLOUT) + { + continue; + } + + auto bytesWritten = write(_fd, &cobsEncodedBuffer[cobsBytesEncoded - cobsBytesRemaining], cobsBytesRemaining); + if (bytesWritten < 0) + { + if (errno == EAGAIN) + { + continue; + } + else + { + return Error::TransportError; + } + } + + cobsBytesRemaining -= bytesWritten; + } + + return Error::NoError; + } + + Error configureNonBlockingIO() + { + auto flags = ::fcntl(_fd, F_GETFL, 0); + if ((flags & O_NONBLOCK) == 0) + { + if (::fcntl(_fd, F_SETFL, flags | O_NONBLOCK) < 0) + { + return Error::TransportError; + } + } + + return Error::NoError; + } + + Error configureTty(std::size_t speed) + { +#ifdef __linux__ + struct termios2 tty; + + if (ioctl(_fd, TCGETS2, &tty) < 0) +#else + struct termios tty; + + if (tcgetattr(_fd, &tty) < 0) +#endif + { + return Error::TransportError; + } + + if (speed != 0) + { +#ifdef __linux__ + tty.c_cflag &= ~(CBAUD); + tty.c_cflag |= BOTHER; + tty.c_ospeed = speed; + + tty.c_cflag &= ~(CBAUD << IBSHIFT); + tty.c_cflag |= BOTHER << IBSHIFT; + tty.c_ispeed = speed; +#else + if (SpeedMap.find(speed) == SpeedMap.end()) + { + return Error::InvalidParameters; + } + + auto mapped = SpeedMap.at(speed); + cfsetispeed(&tty, mapped); + cfsetospeed(&tty, mapped); +#endif + } + + // set no parity, 1 stop bit + tty.c_cflag &= ~(PARENB | CSTOPB | CSIZE); + // set 8 data bits, local mode, receiver enabled + tty.c_cflag |= (CS8 | CLOCAL | CREAD); + // disable canonical mode and signals + tty.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG); + +#ifdef __linux__ + if (ioctl(_fd, TCSETS2, &tty) < 0) +#else + if (tcsetattr(_fd, TCSANOW, &tty) < 0) +#endif + { + return Error::TransportError; + } + + return Error::NoError; + } + + // Private variables + watchDog::WatchDog::SharedPointer _watchDogSharedPointer{ watchDog::WatchDog::getInstance() }; + watchDog::WatchDog& _watchDog{ *_watchDogSharedPointer }; + int _fd{ -1 }; + bool _shouldTerminate{ false }; + mutable stateMachine::Manager _stateMachineManager{ this, this, this, this, this }; + std::thread _captureThread{}; + friend class EthernetPacketDispatcher; + EthernetPacketDispatcher _ethernetPacketDispatcher{ this, _stateMachineManager }; +}; + +ProtocolInterfaceSerial::ProtocolInterfaceSerial(std::string const& networkInterfaceName, std::string const& executorName) + : ProtocolInterface(executorName) +{ +} + +bool ProtocolInterfaceSerial::isSupported() noexcept +{ + return true; +} + +ProtocolInterfaceSerial* ProtocolInterfaceSerial::createRawProtocolInterfaceSerial(std::string const& networkInterfaceName, std::string const& executorName) +{ + return new ProtocolInterfaceSerialImpl(networkInterfaceName, executorName); +} + +} // namespace protocol +} // namespace avdecc +} // namespace la diff --git a/src/protocolInterface/protocolInterface_serial.hpp b/src/protocolInterface/protocolInterface_serial.hpp new file mode 100644 index 00000000..2cf81268 --- /dev/null +++ b/src/protocolInterface/protocolInterface_serial.hpp @@ -0,0 +1,66 @@ +/* +* Copyright (C) 2016-2024, L-Acoustics and its contributors + +* This file is part of LA_avdecc. + +* LA_avdecc is free software: you can redistribute it and/or modify +* it under the terms of the GNU Lesser General Public License as published by +* the Free Software Foundation, either version 3 of the License, or +* (at your option) any later version. + +* LA_avdecc 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 Lesser General Public License for more details. + +* You should have received a copy of the GNU Lesser General Public License +* along with LA_avdecc. If not, see . +*/ + +/** +* @file protocolInterface_serial.hpp +* @author Luke Howard +*/ + +#pragma once + +#include "la/avdecc/internals/protocolInterface.hpp" + +namespace la +{ +namespace avdecc +{ +namespace protocol +{ +class ProtocolInterfaceSerial : public ProtocolInterface +{ +public: + /** + * @brief Factory method to create a new ProtocolInterfaceSerial. + * @details Creates a new ProtocolInterfaceSerial as a raw pointer. + * @param[in] networkInterfaceName The TTY device name to use, with the baudrate as an optional suffix (e.g. `/dev/ttyAMA0@115200`). + * @param[in] executorName The name of the executor to use to dispatch incoming messages. + * @return A new ProtocolInterfaceSerial as a raw pointer. + * @note Throws Exception if #interfaceName is invalid or inaccessible. + */ + static ProtocolInterfaceSerial* createRawProtocolInterfaceSerial(std::string const& networkInterfaceName, std::string const& executorName); + + /** Returns true if this ProtocolInterface is supported (runtime check) */ + static bool isSupported() noexcept; + + /** Destructor */ + virtual ~ProtocolInterfaceSerial() noexcept = default; + + // Deleted compiler auto-generated methods + ProtocolInterfaceSerial(ProtocolInterfaceSerial&&) = delete; + ProtocolInterfaceSerial(ProtocolInterfaceSerial const&) = delete; + ProtocolInterfaceSerial& operator=(ProtocolInterfaceSerial const&) = delete; + ProtocolInterfaceSerial& operator=(ProtocolInterfaceSerial&&) = delete; + +protected: + ProtocolInterfaceSerial(std::string const& networkInterfaceName, std::string const& executorName); +}; + +} // namespace protocol +} // namespace avdecc +} // namespace la