diff --git a/Fw/Buffer/Buffer.fpp b/Fw/Buffer/Buffer.fpp index 48dd549caf..fab0b95895 100644 --- a/Fw/Buffer/Buffer.fpp +++ b/Fw/Buffer/Buffer.fpp @@ -16,4 +16,8 @@ module Fw { $size: U32 ) -> Fw.Buffer + + @ Port for sending data buffer along with context buffer + @ This is useful for passing data that needs context to be interpreted + port DataWithContext(ref data: Fw.Buffer, ref context: Fw.Buffer) } diff --git a/RPI/Top/RPITopologyDefs.hpp b/RPI/Top/RPITopologyDefs.hpp index 50ec1d1c84..5040f33ff6 100644 --- a/RPI/Top/RPITopologyDefs.hpp +++ b/RPI/Top/RPITopologyDefs.hpp @@ -6,6 +6,7 @@ #include "RPI/Top/FppConstantsAc.hpp" #include "Svc/FramingProtocol/FprimeProtocol.hpp" #include "Svc/LinuxTimer/LinuxTimer.hpp" +#include namespace RPI { diff --git a/RPI/Top/instances.fpp b/RPI/Top/instances.fpp index 047a61ad5b..ba37959136 100644 --- a/RPI/Top/instances.fpp +++ b/RPI/Top/instances.fpp @@ -178,13 +178,13 @@ module RPI { instance fatalHandler: Svc.FatalHandler base id 100 - instance fileUplinkBufferManager: Svc.BufferManager base id 900 \ + instance commsBufferManager: Svc.BufferManager base id 900 \ { phase Fpp.ToCpp.Phases.configConstants """ enum { STORE_SIZE = 3000, - QUEUE_SIZE = 30, + STORE_COUNT = 30, MGR_ID = 200 }; """ @@ -193,10 +193,10 @@ module RPI { { Svc::BufferManager::BufferBins bufferBins; memset(&bufferBins, 0, sizeof(bufferBins)); - using namespace ConfigConstants::RPI_fileUplinkBufferManager; + using namespace ConfigConstants::RPI_commsBufferManager; bufferBins.bins[0].bufferSize = STORE_SIZE; - bufferBins.bins[0].numBuffers = QUEUE_SIZE; - RPI::fileUplinkBufferManager.setup( + bufferBins.bins[0].numBuffers = STORE_COUNT; + RPI::commsBufferManager.setup( MGR_ID, 0, Allocation::mallocator, @@ -207,15 +207,13 @@ module RPI { """ phase Fpp.ToCpp.Phases.tearDownComponents """ - RPI::fileUplinkBufferManager.cleanup(); + RPI::commsBufferManager.cleanup(); """ } instance fatalAdapter: Svc.AssertFatalAdapter base id 1000 - instance staticMemory: Svc.StaticMemory base id 1200 - instance downlink: Svc.Framer base id 1220 \ { @@ -229,18 +227,7 @@ module RPI { } - instance uplink: Svc.Deframer base id 1240 \ - { - - phase Fpp.ToCpp.Phases.configObjects """ - Svc::FprimeDeframing deframing; - """ - - phase Fpp.ToCpp.Phases.configComponents """ - RPI::uplink.setup(ConfigObjects::RPI_uplink::deframing); - """ - - } + instance deframer: Svc.Deframer base id 1240 instance comm: Drv.TcpClient base id 1260 \ { @@ -465,5 +452,20 @@ module RPI { """ } + instance frameAccumulator: Svc.FrameAccumulator base id 2900 \ + { + phase Fpp.ToCpp.Phases.configObjects """ + Svc::FrameDetectors::FprimeFrameDetector fprimeFrameDetector; + """ + + phase Fpp.ToCpp.Phases.configComponents """ + { + frameAccumulator.configure(ConfigObjects::RPI_frameAccumulator::fprimeFrameDetector, 1, Allocation::mallocator, 2048); + } + + """ + } + + instance uplinkRouter: Svc.Router base id 3000 } diff --git a/RPI/Top/topology.fpp b/RPI/Top/topology.fpp index 3d842a1828..a81ac6c481 100644 --- a/RPI/Top/topology.fpp +++ b/RPI/Top/topology.fpp @@ -11,13 +11,15 @@ module RPI { instance cmdDisp instance cmdSeq instance comm + instance deframer instance downlink instance eventLogger instance fatalAdapter instance fatalHandler instance fileDownlink instance fileUplink - instance fileUplinkBufferManager + instance frameAccumulator + instance commsBufferManager instance gpio17Drv instance gpio23Drv instance gpio24Drv @@ -31,11 +33,10 @@ module RPI { instance rateGroupDriverComp instance rpiDemo instance spiDrv - instance staticMemory instance textLogger instance uartDrv - instance uplink instance uartBufferManager + instance uplinkRouter # ---------------------------------------------------------------------- # Pattern graph specifiers @@ -71,13 +72,6 @@ module RPI { eventLogger.FatalAnnounce -> fatalHandler.FatalReceive } - connections FileUplinkBuffers { - fileUplink.bufferSendOut -> fileUplinkBufferManager.bufferSendIn - uplink.bufferAllocate -> fileUplinkBufferManager.bufferGetCallee - uplink.bufferDeallocate -> fileUplinkBufferManager.bufferSendIn - uplink.bufferOut -> fileUplink.bufferSendIn - } - connections GPIO { rpiDemo.GpioRead -> gpio25Drv.gpioRead rpiDemo.GpioRead -> gpio17Drv.gpioRead @@ -114,11 +108,14 @@ module RPI { rpiDemo.SpiReadWrite -> spiDrv.SpiReadWrite } - connections StaticMemory { - comm.allocate -> staticMemory.bufferAllocate[0] - comm.deallocate -> staticMemory.bufferDeallocate[1] - downlink.framedAllocate -> staticMemory.bufferAllocate[1] - uplink.framedDeallocate -> staticMemory.bufferDeallocate[0] + connections MemoryAllocations { + comm.allocate -> commsBufferManager.bufferGetCallee + comm.deallocate -> commsBufferManager.bufferSendIn + downlink.framedAllocate -> commsBufferManager.bufferGetCallee + fileUplink.bufferSendOut -> commsBufferManager.bufferSendIn + frameAccumulator.frameAllocate -> commsBufferManager.bufferGetCallee + frameAccumulator.dataDeallocate -> commsBufferManager.bufferSendIn + uplinkRouter.bufferDeallocate -> commsBufferManager.bufferSendIn } connections UART { @@ -129,9 +126,15 @@ module RPI { } connections Uplink { - cmdDisp.seqCmdStatus -> uplink.cmdResponseIn - comm.$recv -> uplink.framedIn - uplink.comOut -> cmdDisp.seqCmdBuff + comm.$recv -> frameAccumulator.dataIn + + frameAccumulator.frameOut -> deframer.framedIn + deframer.deframedOut -> uplinkRouter.dataIn + + uplinkRouter.commandOut -> cmdDisp.seqCmdBuff + uplinkRouter.fileOut -> fileUplink.bufferSendIn + + cmdDisp.seqCmdStatus -> uplinkRouter.cmdResponseIn } } diff --git a/Ref/Top/RefPackets.xml b/Ref/Top/RefPackets.xml index e212af7524..a29fe30edf 100644 --- a/Ref/Top/RefPackets.xml +++ b/Ref/Top/RefPackets.xml @@ -13,9 +13,9 @@ - - - + + + @@ -32,8 +32,8 @@ - - + + diff --git a/Ref/Top/RefTopology.cpp b/Ref/Top/RefTopology.cpp index 8c6694c560..9d989a5d64 100644 --- a/Ref/Top/RefTopology.cpp +++ b/Ref/Top/RefTopology.cpp @@ -16,6 +16,7 @@ #include #include #include +#include // Used for 1Hz synthetic cycling #include @@ -33,7 +34,8 @@ Fw::MallocAllocator mallocator; // The reference topology uses the F´ packet protocol when communicating with the ground and therefore uses the F´ // framing and deframing implementations. Svc::FprimeFraming framing; -Svc::FprimeDeframing deframing; +Svc::FrameDetectors::FprimeFrameDetector frameDetector; + // The reference topology divides the incoming clock signal (1Hz) into sub-signals: 1Hz, 1/2Hz, and 1/4Hz and // zero offset for all the dividers @@ -54,12 +56,16 @@ enum TopologyConstants { FILE_DOWNLINK_FILE_QUEUE_DEPTH = 10, HEALTH_WATCHDOG_CODE = 0x123, COMM_PRIORITY = 100, - UPLINK_BUFFER_MANAGER_STORE_SIZE = 3000, - UPLINK_BUFFER_MANAGER_QUEUE_SIZE = 30, - UPLINK_BUFFER_MANAGER_ID = 200, + // Buffer manager for Uplink/Downlink + COMMS_BUFFER_MANAGER_STORE_SIZE = 2048, + COMMS_BUFFER_MANAGER_STORE_COUNT = 20, + COMMS_BUFFER_MANAGER_FILE_STORE_SIZE = 3000, + COMMS_BUFFER_MANAGER_FILE_QUEUE_SIZE = 30, + COMMS_BUFFER_MANAGER_ID = 200, + // Buffer manager for Data Products DP_BUFFER_MANAGER_STORE_SIZE = 10000, - DP_BUFFER_MANAGER_QUEUE_SIZE = 10, - DP_BUFFER_MANAGER_ID = 300 + DP_BUFFER_MANAGER_STORE_COUNT = 10, + DP_BUFFER_MANAGER_ID = 300, }; /** @@ -94,21 +100,23 @@ void configureTopology() { FW_NUM_ARRAY_ELEMENTS(ConfigObjects::Ref_health::pingEntries), HEALTH_WATCHDOG_CODE); // Buffer managers need a configured set of buckets and an allocator used to allocate memory for those buckets. - Svc::BufferManager::BufferBins upBuffMgrBins; - memset(&upBuffMgrBins, 0, sizeof(upBuffMgrBins)); - upBuffMgrBins.bins[0].bufferSize = UPLINK_BUFFER_MANAGER_STORE_SIZE; - upBuffMgrBins.bins[0].numBuffers = UPLINK_BUFFER_MANAGER_QUEUE_SIZE; - fileUplinkBufferManager.setup(UPLINK_BUFFER_MANAGER_ID, 0, mallocator, upBuffMgrBins); + Svc::BufferManager::BufferBins commsBuffMgrBins; + memset(&commsBuffMgrBins, 0, sizeof(commsBuffMgrBins)); + commsBuffMgrBins.bins[0].bufferSize = COMMS_BUFFER_MANAGER_STORE_SIZE; + commsBuffMgrBins.bins[0].numBuffers = COMMS_BUFFER_MANAGER_STORE_COUNT; + commsBuffMgrBins.bins[1].bufferSize = COMMS_BUFFER_MANAGER_FILE_STORE_SIZE; + commsBuffMgrBins.bins[1].numBuffers = COMMS_BUFFER_MANAGER_FILE_QUEUE_SIZE; + commsBufferManager.setup(COMMS_BUFFER_MANAGER_ID, 0, mallocator, commsBuffMgrBins); Svc::BufferManager::BufferBins dpBuffMgrBins; memset(&dpBuffMgrBins, 0, sizeof(dpBuffMgrBins)); dpBuffMgrBins.bins[0].bufferSize = DP_BUFFER_MANAGER_STORE_SIZE; - dpBuffMgrBins.bins[0].numBuffers = DP_BUFFER_MANAGER_QUEUE_SIZE; + dpBuffMgrBins.bins[0].numBuffers = DP_BUFFER_MANAGER_STORE_COUNT; dpBufferManager.setup(DP_BUFFER_MANAGER_ID, 0, mallocator, dpBuffMgrBins); // Framer and Deframer components need to be passed a protocol handler - downlink.setup(framing); - uplink.setup(deframing); + framer.setup(framing); + frameAccumulator.configure(frameDetector, 1, mallocator, 2048); Fw::FileNameString dpDir("./DpCat"); Fw::FileNameString dpState("./DpCat/DpState.dat"); @@ -192,6 +200,6 @@ void teardownTopology(const TopologyState& state) { // Resource deallocation cmdSeq.deallocateBuffer(mallocator); - fileUplinkBufferManager.cleanup(); + commsBufferManager.cleanup(); } }; // namespace Ref diff --git a/Ref/Top/instances.fpp b/Ref/Top/instances.fpp index 33399937b9..1bce26e869 100644 --- a/Ref/Top/instances.fpp +++ b/Ref/Top/instances.fpp @@ -137,16 +137,15 @@ module Ref { # ---------------------------------------------------------------------- @ Communications driver. May be swapped with other comm drivers like UART - @ Note: Here we have TCP reliable uplink and UDP (low latency) downlink instance comm: Drv.TcpClient base id 0x4000 - instance downlink: Svc.Framer base id 0x4100 + instance framer: Svc.Framer base id 0x4100 instance fatalAdapter: Svc.AssertFatalAdapter base id 0x4200 instance fatalHandler: Svc.FatalHandler base id 0x4300 - instance fileUplinkBufferManager: Svc.BufferManager base id 0x4400 + instance commsBufferManager: Svc.BufferManager base id 0x4400 instance posixTime: Svc.PosixTime base id 0x4500 @@ -154,16 +153,19 @@ module Ref { instance recvBuffComp: Ref.RecvBuff base id 0x4700 - instance staticMemory: Svc.StaticMemory base id 0x4800 + instance version: Svc.Version base id 0x4800 instance textLogger: Svc.PassiveTextLogger base id 0x4900 - instance uplink: Svc.Deframer base id 0x4A00 + instance systemResources: Svc.SystemResources base id 0x4A00 - instance systemResources: Svc.SystemResources base id 0x4B00 + instance dpBufferManager: Svc.BufferManager base id 0x4B00 - instance dpBufferManager: Svc.BufferManager base id 0x4C00 - - instance version: Svc.Version base id 0x4D00 + instance frameAccumulator: Svc.FrameAccumulator base id 0x4C00 + + instance deframer: Svc.Deframer base id 0x4D00 + + instance uplinkRouter: Svc.Router base id 0x4E00 } + diff --git a/Ref/Top/topology.fpp b/Ref/Top/topology.fpp index d890604f4a..076f3880e2 100644 --- a/Ref/Top/topology.fpp +++ b/Ref/Top/topology.fpp @@ -13,6 +13,7 @@ module Ref { enum Ports_StaticMemory { downlink uplink + accumulator } topology Ref { @@ -32,14 +33,16 @@ module Ref { instance cmdDisp instance cmdSeq instance comm - instance downlink + instance deframer instance eventLogger instance fatalAdapter instance fatalHandler instance fileDownlink instance fileManager instance fileUplink - instance fileUplinkBufferManager + instance commsBufferManager + instance frameAccumulator + instance framer instance posixTime instance pingRcvr instance prmDb @@ -48,11 +51,10 @@ module Ref { instance rateGroup3Comp instance rateGroupDriverComp instance recvBuffComp + instance uplinkRouter instance sendBuffComp - instance staticMemory instance textLogger instance typeDemo - instance uplink instance systemResources instance dpCat instance dpMgr @@ -84,15 +86,15 @@ module Ref { connections Downlink { - tlmSend.PktSend -> downlink.comIn - eventLogger.PktSend -> downlink.comIn - fileDownlink.bufferSendOut -> downlink.bufferIn + tlmSend.PktSend -> framer.comIn + eventLogger.PktSend -> framer.comIn + fileDownlink.bufferSendOut -> framer.bufferIn - downlink.framedAllocate -> staticMemory.bufferAllocate[Ports_StaticMemory.downlink] - downlink.framedOut -> comm.$send - downlink.bufferDeallocate -> fileDownlink.bufferReturn + framer.framedAllocate -> commsBufferManager.bufferGetCallee + framer.framedOut -> comm.$send + framer.bufferDeallocate -> fileDownlink.bufferReturn - comm.deallocate -> staticMemory.bufferDeallocate[Ports_StaticMemory.downlink] + comm.deallocate -> commsBufferManager.bufferSendIn dpCat.fileOut -> fileDownlink.SendFile fileDownlink.FileComplete -> dpCat.fileDone @@ -128,7 +130,7 @@ module Ref { rateGroup3Comp.RateGroupMemberOut[0] -> $health.Run rateGroup3Comp.RateGroupMemberOut[1] -> SG5.schedIn rateGroup3Comp.RateGroupMemberOut[2] -> blockDrv.Sched - rateGroup3Comp.RateGroupMemberOut[3] -> fileUplinkBufferManager.schedIn + rateGroup3Comp.RateGroupMemberOut[3] -> commsBufferManager.schedIn rateGroup3Comp.RateGroupMemberOut[4] -> dpBufferManager.schedIn rateGroup3Comp.RateGroupMemberOut[5] -> dpWriter.schedIn rateGroup3Comp.RateGroupMemberOut[6] -> dpMgr.schedIn @@ -146,17 +148,21 @@ module Ref { connections Uplink { - comm.allocate -> staticMemory.bufferAllocate[Ports_StaticMemory.uplink] - comm.$recv -> uplink.framedIn - uplink.framedDeallocate -> staticMemory.bufferDeallocate[Ports_StaticMemory.uplink] + comm.allocate -> commsBufferManager.bufferGetCallee + comm.$recv -> frameAccumulator.dataIn - uplink.comOut -> cmdDisp.seqCmdBuff - cmdDisp.seqCmdStatus -> uplink.cmdResponseIn + frameAccumulator.frameOut -> deframer.framedIn + frameAccumulator.frameAllocate -> commsBufferManager.bufferGetCallee + frameAccumulator.dataDeallocate -> commsBufferManager.bufferSendIn + deframer.deframedOut -> uplinkRouter.dataIn - uplink.bufferAllocate -> fileUplinkBufferManager.bufferGetCallee - uplink.bufferOut -> fileUplink.bufferSendIn - uplink.bufferDeallocate -> fileUplinkBufferManager.bufferSendIn - fileUplink.bufferSendOut -> fileUplinkBufferManager.bufferSendIn + uplinkRouter.commandOut -> cmdDisp.seqCmdBuff + uplinkRouter.fileOut -> fileUplink.bufferSendIn + uplinkRouter.bufferDeallocate -> commsBufferManager.bufferSendIn + + cmdDisp.seqCmdStatus -> uplinkRouter.cmdResponseIn + + fileUplink.bufferSendOut -> commsBufferManager.bufferSendIn } diff --git a/Svc/CMakeLists.txt b/Svc/CMakeLists.txt index 49de3f43aa..8663ccf942 100644 --- a/Svc/CMakeLists.txt +++ b/Svc/CMakeLists.txt @@ -36,6 +36,7 @@ add_fprime_subdirectory("${CMAKE_CURRENT_LIST_DIR}/FileDownlink/") add_fprime_subdirectory("${CMAKE_CURRENT_LIST_DIR}/FileManager/") add_fprime_subdirectory("${CMAKE_CURRENT_LIST_DIR}/FileUplink/") add_fprime_subdirectory("${CMAKE_CURRENT_LIST_DIR}/GenericHub/") +add_fprime_subdirectory("${CMAKE_CURRENT_LIST_DIR}/FrameAccumulator/") add_fprime_subdirectory("${CMAKE_CURRENT_LIST_DIR}/Framer/") add_fprime_subdirectory("${CMAKE_CURRENT_LIST_DIR}/FramingProtocol/") add_fprime_subdirectory("${CMAKE_CURRENT_LIST_DIR}/Health/") @@ -43,6 +44,7 @@ add_fprime_subdirectory("${CMAKE_CURRENT_LIST_DIR}/PassiveRateGroup") add_fprime_subdirectory("${CMAKE_CURRENT_LIST_DIR}/PolyDb/") add_fprime_subdirectory("${CMAKE_CURRENT_LIST_DIR}/PrmDb/") add_fprime_subdirectory("${CMAKE_CURRENT_LIST_DIR}/RateGroupDriver/") +add_fprime_subdirectory("${CMAKE_CURRENT_LIST_DIR}/Router/") add_fprime_subdirectory("${CMAKE_CURRENT_LIST_DIR}/SeqDispatcher/") add_fprime_subdirectory("${CMAKE_CURRENT_LIST_DIR}/StaticMemory/") add_fprime_subdirectory("${CMAKE_CURRENT_LIST_DIR}/TlmChan/") diff --git a/Svc/Deframer/CMakeLists.txt b/Svc/Deframer/CMakeLists.txt index 6df9e8c344..43b667f25e 100644 --- a/Svc/Deframer/CMakeLists.txt +++ b/Svc/Deframer/CMakeLists.txt @@ -18,28 +18,16 @@ set(MOD_DEPS register_fprime_module() -#### UTS ### -# These tests verify the Deframer component against a mock -# deframing protocol +#### UTs #### set(UT_SOURCE_FILES "${CMAKE_CURRENT_LIST_DIR}/Deframer.fpp" "${CMAKE_CURRENT_LIST_DIR}/test/ut/DeframerTester.cpp" "${CMAKE_CURRENT_LIST_DIR}/test/ut/DeframerTestMain.cpp" ) -register_fprime_ut() - -# These tests verify the Deframer component against the -# F Prime deframing protocol located at Svc/FramingProtocol. -# They also verify the deframing part of the F Prime protocol -# implementation. -set(UT_SOURCE_FILES - "${CMAKE_CURRENT_LIST_DIR}/Deframer.fpp" - "${CMAKE_CURRENT_LIST_DIR}/test/ut-fprime-protocol/GenerateFrames.cpp" - "${CMAKE_CURRENT_LIST_DIR}/test/ut-fprime-protocol/SendBuffer.cpp" - "${CMAKE_CURRENT_LIST_DIR}/test/ut-fprime-protocol/DeframerTester.cpp" - "${CMAKE_CURRENT_LIST_DIR}/test/ut-fprime-protocol/DeframerTestMain.cpp" - "${CMAKE_CURRENT_LIST_DIR}/test/ut-fprime-protocol/UplinkFrame.cpp" +set(UT_MOD_DEPS + STest ) -set(UT_MOD_DEPS STest) -register_fprime_ut(Svc_Deframer_fprime_protocol) +set(UT_AUTO_HELPERS ON) + +register_fprime_ut() diff --git a/Svc/Deframer/Deframer.cpp b/Svc/Deframer/Deframer.cpp index 2f697da8b6..79b4f91aad 100644 --- a/Svc/Deframer/Deframer.cpp +++ b/Svc/Deframer/Deframer.cpp @@ -1,312 +1,38 @@ // ====================================================================== // \title Deframer.cpp -// \author mstarch, bocchino +// \author thomas-bc // \brief cpp file for Deframer component implementation class -// -// \copyright -// Copyright 2009-2022, by the California Institute of Technology. -// ALL RIGHTS RESERVED. United States Government Sponsorship -// acknowledged. -// // ====================================================================== -#include - -#include "Fw/Com/ComPacket.hpp" -#include "Fw/Logger/Logger.hpp" -#include #include "Svc/Deframer/Deframer.hpp" +#include "FpConfig.hpp" +#include "Fw/Types/Assert.hpp" namespace Svc { // ---------------------------------------------------------------------- -// Static assertions -// ---------------------------------------------------------------------- - -static_assert( - DeframerCfg::POLL_BUFFER_SIZE > 0, - "poll buffer size must be greater than zero" -); -static_assert( - DeframerCfg::RING_BUFFER_SIZE > 0, - "ring buffer size must be greater than zero" -); - -// ---------------------------------------------------------------------- -// Construction, initialization, and destruction +// Component construction and destruction // ---------------------------------------------------------------------- -Deframer ::Deframer(const char* const compName) : - DeframerComponentBase(compName), - DeframingProtocolInterface(), - m_protocol(nullptr), - m_inRing(m_ringBuffer, sizeof m_ringBuffer) -{ - (void) memset(m_pollBuffer, 0, sizeof m_pollBuffer); -} +Deframer ::Deframer(const char* const compName) : DeframerComponentBase(compName) {} Deframer ::~Deframer() {} -void Deframer ::setup(DeframingProtocol& protocol) { - // Check that this is the first time we are calling setup - FW_ASSERT(m_protocol == nullptr); - // Assign the protocol passed in to m_protocol - m_protocol = &protocol; - // Pass *this as the DeframingProtocolInstance to protocol setup - // Deframer is derived from and implements DeframingProtocolInterface - protocol.setup(*this); -} - // ---------------------------------------------------------------------- // Handler implementations for user-defined typed input ports // ---------------------------------------------------------------------- -void Deframer ::cmdResponseIn_handler( - NATIVE_INT_TYPE portNum, - FwOpcodeType opcode, - U32 cmdSeq, - const Fw::CmdResponse& response -) { - // Nothing to do -} - -void Deframer ::framedIn_handler( - const NATIVE_INT_TYPE portNum, - Fw::Buffer& recvBuffer, - const Drv::RecvStatus& recvStatus -) { - // Check whether there is data to process - if (recvStatus.e == Drv::RecvStatus::RECV_OK) { - // There is: process the data - processBuffer(recvBuffer); - } - // Deallocate the buffer - framedDeallocate_out(0, recvBuffer); -} - -void Deframer ::schedIn_handler( - const NATIVE_INT_TYPE portNum, - U32 context -) { - // Check for data - Fw::Buffer buffer(m_pollBuffer, sizeof(m_pollBuffer)); - const Drv::PollStatus status = framedPoll_out(0, buffer); - if (status.e == Drv::PollStatus::POLL_OK) { - // Data exists: process it - processBuffer(buffer); - } -} - -// ---------------------------------------------------------------------- -// Implementation of DeframingProtocolInterface -// ---------------------------------------------------------------------- - -Fw::Buffer Deframer ::allocate(const U32 size) { - return bufferAllocate_out(0, size); -} - -void Deframer ::route(Fw::Buffer& packetBuffer) { - - // Read the packet type from the packet buffer - FwPacketDescriptorType packetType = Fw::ComPacket::FW_PACKET_UNKNOWN; - Fw::SerializeStatus status = Fw::FW_SERIALIZE_OK; - { - Fw::SerializeBufferBase& serial = packetBuffer.getSerializeRepr(); - status = serial.setBuffLen(packetBuffer.getSize()); - FW_ASSERT(status == Fw::FW_SERIALIZE_OK); - status = serial.deserialize(packetType); - } - - // Whether to deallocate the packet buffer - bool deallocate = true; - - // Process the packet - if (status == Fw::FW_SERIALIZE_OK) { - U8 *const packetData = packetBuffer.getData(); - const U32 packetSize = packetBuffer.getSize(); - switch (packetType) { - // Handle a command packet - case Fw::ComPacket::FW_PACKET_COMMAND: { - // Allocate a com buffer on the stack - Fw::ComBuffer com; - // Copy the contents of the packet buffer into the com buffer - status = com.setBuff(packetData, packetSize); - if (status == Fw::FW_SERIALIZE_OK) { - // Send the com buffer - comOut_out(0, com, 0); - } - else { - Fw::Logger::log( - "[ERROR] Serializing com buffer failed with status %d\n", - status - ); - } - break; - } - // Handle a file packet - case Fw::ComPacket::FW_PACKET_FILE: { - // If the file uplink output port is connected, - // send the file packet. Otherwise take no action. - if (isConnected_bufferOut_OutputPort(0)) { - // Shift the packet buffer to skip the packet type - // The FileUplink component does not expect the packet - // type to be there. - packetBuffer.setData(packetData + sizeof(packetType)); - packetBuffer.setSize(static_cast(packetSize - sizeof(packetType))); - // Send the packet buffer - bufferOut_out(0, packetBuffer); - // Transfer ownership of the buffer to the receiver - deallocate = false; - } - break; - } - // Take no action for other packet types - default: - break; - } - } - else { - Fw::Logger::log( - "[ERROR] Deserializing packet type failed with status %d\n", - status - ); - } - - if (deallocate) { - // Deallocate the packet buffer - bufferDeallocate_out(0, packetBuffer); - } - -} - -// ---------------------------------------------------------------------- -// Helper methods -// ---------------------------------------------------------------------- - -void Deframer ::processBuffer(Fw::Buffer& buffer) { - - const U32 bufferSize = buffer.getSize(); - U8 *const bufferData = buffer.getData(); - // Current offset into buffer - U32 offset = 0; - // Remaining data in buffer - U32 remaining = bufferSize; - - for (U32 i = 0; i < bufferSize; ++i) { - // If there is no data left, exit the loop - if (remaining == 0) { - break; - } - // Compute the size of data to serialize - const NATIVE_UINT_TYPE ringFreeSize = m_inRing.get_free_size(); - const NATIVE_UINT_TYPE serSize = (ringFreeSize <= remaining) ? - ringFreeSize : static_cast(remaining); - // Serialize data into the ring buffer - const Fw::SerializeStatus status = - m_inRing.serialize(&bufferData[offset], serSize); - // If data does not fit, there is a coding error - FW_ASSERT( - status == Fw::FW_SERIALIZE_OK, - static_cast(status), - static_cast(offset), - static_cast(serSize)); - // Process the data - processRing(); - // Update buffer offset and remaining - offset += serSize; - remaining -= serSize; - } - - // In every iteration, either remaining == 0 and we break out - // of the loop, or we consume at least one byte from the buffer. - // So there should be no data left in the buffer. - FW_ASSERT(remaining == 0, static_cast(remaining)); - -} - -void Deframer ::processRing() { - - FW_ASSERT(m_protocol != nullptr); - - // The number of remaining bytes in the ring buffer - U32 remaining = 0; - // The protocol status - DeframingProtocol::DeframingStatus status = - DeframingProtocol::DEFRAMING_STATUS_SUCCESS; - // The ring buffer capacity - const NATIVE_UINT_TYPE ringCapacity = m_inRing.get_capacity(); +void Deframer ::framedIn_handler(FwIndexType portNum, Fw::Buffer& data, Fw::Buffer& context) { - // Process the ring buffer looking for at least the header - for (U32 i = 0; i < ringCapacity; i++) { - // Get the number of bytes remaining in the ring buffer - remaining = m_inRing.get_allocated_size(); - // If there are none, we are done - if (remaining == 0) { - break; - } - // Needed is an out-only variable - // Initialize it to zero - U32 needed = 0; - // Call the deframe method of the protocol, getting - // needed and status - status = m_protocol->deframe(m_inRing, needed); - // Deframing protocol must not consume data in the ring buffer - FW_ASSERT( - m_inRing.get_allocated_size() == remaining, - static_cast(m_inRing.get_allocated_size()), - static_cast(remaining) - ); - // On successful deframing, consume data from the ring buffer now - if (status == DeframingProtocol::DEFRAMING_STATUS_SUCCESS) { - // If deframing succeeded, protocol should set needed - // to a non-zero value - FW_ASSERT(needed != 0); - FW_ASSERT( - needed <= remaining, - static_cast(needed), - static_cast(remaining)); - m_inRing.rotate(needed); - FW_ASSERT( - m_inRing.get_allocated_size() == remaining - needed, - static_cast(m_inRing.get_allocated_size()), - static_cast(remaining), - static_cast(needed) - ); - } - // More data needed - else if (status == DeframingProtocol::DEFRAMING_MORE_NEEDED) { - // Deframing protocol should not report "more is needed" - // unless more is needed - FW_ASSERT( - needed > remaining, - static_cast(needed), - static_cast(remaining)); - // Break out of loop: suspend deframing until we receive - // another buffer - break; - } - // Error occurred - else { - // Skip one byte of bad data - m_inRing.rotate(1); - FW_ASSERT( - m_inRing.get_allocated_size() == remaining - 1, - static_cast(m_inRing.get_allocated_size()), - static_cast(remaining) - ); - // Log checksum errors - // This is likely a real error, not an artifact of other data corruption - if (status == DeframingProtocol::DEFRAMING_INVALID_CHECKSUM) { - Fw::Logger::log("[ERROR] Deframing checksum validation failed\n"); - } - } - } + // Add checks to ensure that the data is a valid F' frame ?? + // Seems like this component should be able to do that on its own without relying + // on an upstream component (the F´ accumulator) + FW_ASSERT(data.getSize() >= FrameConfig::HEADER_SIZE + FrameConfig::CHECKSUM_SIZE); - // If more not needed, circular buffer should be empty - if (status != DeframingProtocol::DEFRAMING_MORE_NEEDED) { - FW_ASSERT(remaining == 0, static_cast(remaining)); - } + data.setData(data.getData() + FrameConfig::HEADER_SIZE); + data.setSize(data.getSize() - FrameConfig::HEADER_SIZE - FrameConfig::CHECKSUM_SIZE); + this->deframedOut_out(0, data, context); } -} // end namespace Svc +} // namespace Svc diff --git a/Svc/Deframer/Deframer.fpp b/Svc/Deframer/Deframer.fpp index 3d7323a77c..7a9653f473 100644 --- a/Svc/Deframer/Deframer.fpp +++ b/Svc/Deframer/Deframer.fpp @@ -1,74 +1,14 @@ module Svc { @ A component for deframing input received from the ground - @ via a byte stream driver, which may be active or passive + @ via a FrameAccumulator passive component Deframer { # ---------------------------------------------------------------------- - # Receiving framed data via push + # Deframer interface # ---------------------------------------------------------------------- - @ Port for receiving frame buffers FB pushed from the byte stream driver. - @ After using a buffer FB received on this port, Deframer deallocates it - @ by invoking framedDeallocate. - guarded input port framedIn: Drv.ByteStreamRecv - - @ Port for deallocating buffers received on framedIn. - output port framedDeallocate: Fw.BufferSend - - # ---------------------------------------------------------------------- - # Receiving framed data via poll - # ---------------------------------------------------------------------- - - @ Schedule in port, driven by a rate group. - guarded input port schedIn: Svc.Sched - - @ Port that polls for data from the byte stream driver. - @ Deframer invokes this port on its schedIn cycle, if it is connected. - @ No allocation or occurs when invoking this port. - @ The data transfer uses a pre-allocated frame buffer - @ owned by Deframer. - output port framedPoll: Drv.ByteStreamPoll - - # ---------------------------------------------------------------------- - # Memory management for deframing and for sending file packets - # ---------------------------------------------------------------------- - - @ Port for allocating Fw::Buffer objects from a buffer manager. - @ When Deframer invokes this port, it receives a packet buffer PB and - @ takes ownership of it. It uses PB internally for deframing. - @ Then one of two things happens: - @ - @ 1. PB contains a file packet, which Deframer sends on bufferOut. - @ In this case ownership of PB passes to the receiver. - @ - @ 2. PB does not contain a file packet, or bufferOut is unconnected. - @ In this case Deframer deallocates PB on bufferDeallocate. - output port bufferAllocate: Fw.BufferGet - - @ Port for sending file packets (case 1 above). - @ The file packets are wrapped in Fw::Buffer objects allocated with - @ bufferAllocate. - @ Ownership of the Fw::Buffer passes to the receiver, which is - @ responsible for the deallocation. - output port bufferOut: Fw.BufferSend - - @ Port for deallocating temporary buffers allocated with - @ bufferAllocate (case 2 above). Deallocation occurs here - @ when there is nothing to send on bufferOut. - output port bufferDeallocate: Fw.BufferSend - - # ---------------------------------------------------------------------- - # Sending command packets and receiving command responses - # ---------------------------------------------------------------------- - - @ Port for sending command packets as Com buffers. - output port comOut: Fw.Com - - @ Port for receiving command responses from a command dispatcher. - @ Invoking this port does nothing. The port exists to allow the matching - @ connection in the topology. - sync input port cmdResponseIn: Fw.CmdResponse + include "../Interfaces/DeframerInterface.fppi" } diff --git a/Svc/Deframer/Deframer.hpp b/Svc/Deframer/Deframer.hpp index b0d3a78653..8326a4a741 100644 --- a/Svc/Deframer/Deframer.hpp +++ b/Svc/Deframer/Deframer.hpp @@ -1,138 +1,53 @@ // ====================================================================== // \title Deframer.hpp -// \author mstarch, bocchino +// \author thomas-bc // \brief hpp file for Deframer component implementation class -// -// \copyright -// Copyright 2009-2022, by the California Institute of Technology. -// ALL RIGHTS RESERVED. United States Government Sponsorship -// acknowledged. -// // ====================================================================== #ifndef Svc_Deframer_HPP #define Svc_Deframer_HPP -#include - #include "Svc/Deframer/DeframerComponentAc.hpp" -#include "Svc/FramingProtocol/DeframingProtocol.hpp" -#include "Svc/FramingProtocol/DeframingProtocolInterface.hpp" -#include "Utils/Types/CircularBuffer.hpp" +#include "Utils/Hash/Hash.hpp" namespace Svc { -/** - * \brief Generic deframing component using DeframingProtocol implementation for actual deframing - * - * Deframing component used to take byte streams and expand them into Com/File buffers. This is - * done using a deframing protocol specified in a DeframingProtocol instance. The instance must be - * supplied using the `setup` method. - * - * Using this component, projects can implement and supply a fresh DeframingProtocol implementation - * without changing the reference topology. - * - * Implementation uses a circular buffer to store incoming data, which is drained one framed packet - * at a time into buffers dispatched to the rest of the system. - */ -class Deframer : - public DeframerComponentBase, - public DeframingProtocolInterface -{ - public: - - // ---------------------------------------------------------------------- - // Construction, initialization, and destruction - // ---------------------------------------------------------------------- - - //! Construct Deframer instance - Deframer( - const char* const compName //!< The component name - ); - - //! Destroy Deframer instance - ~Deframer(); - - //! Set up the instance - void setup( - DeframingProtocol& protocol //!< Deframing protocol instance - ); - PRIVATE: +namespace FrameConfig { + //! Token type for F Prime frame header + typedef U32 TokenType; + static const U8 HEADER_SIZE = sizeof(TokenType) * 2; + static const U32 CHECKSUM_SIZE = HASH_DIGEST_LENGTH; +} - // ---------------------------------------------------------------------- - // Handler implementations for user-defined typed input ports - // ---------------------------------------------------------------------- - - //! Handler for input port cmdResponseIn - void cmdResponseIn_handler( - NATIVE_INT_TYPE portNum, //!< The port number - FwOpcodeType opcode, //!< The command opcode - U32 cmdSeq, //!< The command sequence number - const Fw::CmdResponse& response //!< The command response - ); +class Deframer : public DeframerComponentBase { - //! Handler implementation for framedIn - void framedIn_handler( - const NATIVE_INT_TYPE portNum, //!< The port number - Fw::Buffer& recvBuffer, //!< Buffer containing framed data - const Drv::RecvStatus& recvStatus //!< Status of the bytes - ); - - //! Handler implementation for schedIn - void schedIn_handler( - const NATIVE_INT_TYPE portNum, //!< The port number - U32 context //!< The call order - ); - - // ---------------------------------------------------------------------- - // Implementation of DeframingProtocolInterface - // ---------------------------------------------------------------------- - - //! The implementation of DeframingProtocolInterface::route - //! Send a data packet - void route( - Fw::Buffer& packetBuffer //!< The packet buffer - ); - - //! The implementation of DeframingProtocolInterface::allocate - //! Allocate a packet buffer - //! \return The packet buffer - Fw::Buffer allocate( - const U32 size //!< The number of bytes to request - ); + public: // ---------------------------------------------------------------------- - // Helper methods + // Component construction and destruction // ---------------------------------------------------------------------- - //! Copy data from an incoming frame buffer into the internal - //! circular buffer - void processBuffer( - Fw::Buffer& buffer //!< The frame buffer + //! Construct Deframer object + Deframer(const char* const compName //!< The component name ); - //! Process data in the circular buffer - void processRing(); + //! Destroy Deframer object + ~Deframer(); + PRIVATE: // ---------------------------------------------------------------------- - // Member variables + // Handler implementations for user-defined typed input ports // ---------------------------------------------------------------------- - //! The DeframingProtocol implementation - DeframingProtocol* m_protocol; - - //! The circular buffer - Types::CircularBuffer m_inRing; - - //! Memory for the circular buffer - U8 m_ringBuffer[DeframerCfg::RING_BUFFER_SIZE]; - - //! Memory for the polling buffer - U8 m_pollBuffer[DeframerCfg::POLL_BUFFER_SIZE]; - + //! Handler implementation for frame + //! + //! Port to receive framed data + void framedIn_handler(FwIndexType portNum, //!< The port number + Fw::Buffer& data, + Fw::Buffer& context) override; }; -} // end namespace Svc +} // namespace Svc #endif diff --git a/Svc/Deframer/docs/img/.fpv-env b/Svc/Deframer/docs/img/.fpv-env deleted file mode 100644 index ff60caacf4..0000000000 --- a/Svc/Deframer/docs/img/.fpv-env +++ /dev/null @@ -1 +0,0 @@ -DATA_FOLDER=top/ diff --git a/Svc/Deframer/docs/img/Deframer.png b/Svc/Deframer/docs/img/Deframer.png deleted file mode 100644 index b6d54cdc32..0000000000 Binary files a/Svc/Deframer/docs/img/Deframer.png and /dev/null differ diff --git a/Svc/Deframer/docs/img/deframer_example_1.png b/Svc/Deframer/docs/img/deframer_example_1.png deleted file mode 100644 index 30a83a4863..0000000000 Binary files a/Svc/Deframer/docs/img/deframer_example_1.png and /dev/null differ diff --git a/Svc/Deframer/docs/img/deframer_example_2.png b/Svc/Deframer/docs/img/deframer_example_2.png deleted file mode 100644 index aae87e6941..0000000000 Binary files a/Svc/Deframer/docs/img/deframer_example_2.png and /dev/null differ diff --git a/Svc/Deframer/docs/img/deframer_uplink_stack.png b/Svc/Deframer/docs/img/deframer_uplink_stack.png new file mode 100644 index 0000000000..fb55878b15 Binary files /dev/null and b/Svc/Deframer/docs/img/deframer_uplink_stack.png differ diff --git a/Svc/Deframer/docs/img/top/cmd.json b/Svc/Deframer/docs/img/top/cmd.json deleted file mode 100644 index e079481d12..0000000000 --- a/Svc/Deframer/docs/img/top/cmd.json +++ /dev/null @@ -1,76 +0,0 @@ -{ - "columns" : [ - [ - { - "instanceName" : "deframer", - "inputPorts" : [ - { - "name" : "cmdResponseIn", - "portNumbers" : [ - 0 - ] - } - ], - "outputPorts" : [ - { - "name" : "comOut", - "portNumbers" : [ - 0 - ] - } - ] - } - ], - [ - { - "instanceName" : "cmdDisp", - "inputPorts" : [ - { - "name" : "seqCmdBuff", - "portNumbers" : [ - 0 - ] - } - ], - "outputPorts" : [ - { - "name" : "compCmdStat", - "portNumbers" : [ - 0 - ] - } - ] - } - ] - ], - "connections" : [ - [ - [ - 0, - 0, - 0, - 0 - ], - [ - 1, - 0, - 0, - 0 - ] - ], - [ - [ - 1, - 0, - 0, - 0 - ], - [ - 0, - 0, - 0, - 0 - ] - ] - ] -} diff --git a/Svc/Deframer/docs/img/top/cmd.png b/Svc/Deframer/docs/img/top/cmd.png deleted file mode 100644 index 39b36b9710..0000000000 Binary files a/Svc/Deframer/docs/img/top/cmd.png and /dev/null differ diff --git a/Svc/Deframer/docs/img/top/cmd.txt b/Svc/Deframer/docs/img/top/cmd.txt deleted file mode 100644 index ea505a43cc..0000000000 --- a/Svc/Deframer/docs/img/top/cmd.txt +++ /dev/null @@ -1,13 +0,0 @@ -deframer -comOut -0 -cmdDisp -seqCmdBuff -0 - -cmdDisp -compCmdStat -0 -deframer -cmdResponseIn -0 diff --git a/Svc/Deframer/docs/img/top/deframer-file.json b/Svc/Deframer/docs/img/top/deframer-file.json deleted file mode 100644 index 2b1ef18172..0000000000 --- a/Svc/Deframer/docs/img/top/deframer-file.json +++ /dev/null @@ -1,129 +0,0 @@ -{ - "columns" : [ - [ - { - "instanceName" : "deframer", - "inputPorts" : [], - "outputPorts" : [ - { - "name" : "bufferOut", - "portNumbers" : [ - 0 - ] - }, - { - "name" : "bufferDeallocate", - "portNumbers" : [ - 0 - ] - }, - { - "name" : "bufferAllocate", - "portNumbers" : [ - 0 - ] - } - ] - } - ], - [ - { - "instanceName" : "fileUplink", - "inputPorts" : [ - { - "name" : "bufferSendIn", - "portNumbers" : [ - 0 - ] - } - ], - "outputPorts" : [ - { - "name" : "bufferSendOut", - "portNumbers" : [ - 0 - ] - } - ] - } - ], - [ - { - "instanceName" : "buffMgr", - "inputPorts" : [ - { - "name" : "bufferSendIn", - "portNumbers" : [ - 0 - ] - }, - { - "name" : "bufferGetCallee", - "portNumbers" : [ - 0 - ] - } - ], - "outputPorts" : [] - } - ] - ], - "connections" : [ - [ - [ - 0, - 0, - 0, - 0 - ], - [ - 1, - 0, - 0, - 0 - ] - ], - [ - [ - 1, - 0, - 0, - 0 - ], - [ - 2, - 0, - 0, - 0 - ] - ], - [ - [ - 0, - 0, - 2, - 0 - ], - [ - 2, - 0, - 1, - 0 - ] - ], - [ - [ - 0, - 0, - 1, - 0 - ], - [ - 2, - 0, - 0, - 0 - ] - ] - ] -} diff --git a/Svc/Deframer/docs/img/top/deframer-file.png b/Svc/Deframer/docs/img/top/deframer-file.png deleted file mode 100644 index 1c4713605f..0000000000 Binary files a/Svc/Deframer/docs/img/top/deframer-file.png and /dev/null differ diff --git a/Svc/Deframer/docs/img/top/deframer-file.txt b/Svc/Deframer/docs/img/top/deframer-file.txt deleted file mode 100644 index 40f6369e00..0000000000 --- a/Svc/Deframer/docs/img/top/deframer-file.txt +++ /dev/null @@ -1,28 +0,0 @@ -deframer -bufferOut -0 -fileUplink -bufferSendIn -0 - -fileUplink -bufferSendOut -0 -buffMgr -bufferSendIn -0 - -deframer -bufferAllocate -0 -buffMgr -bufferGetCallee -0 - -deframer -bufferDeallocate -0 -buffMgr -bufferSendIn -0 - diff --git a/Svc/Deframer/docs/img/top/framed-active.json b/Svc/Deframer/docs/img/top/framed-active.json deleted file mode 100644 index 965f9cc8d5..0000000000 --- a/Svc/Deframer/docs/img/top/framed-active.json +++ /dev/null @@ -1,109 +0,0 @@ -{ - "columns" : [ - [ - { - "instanceName" : "activeComm", - "inputPorts" : [], - "outputPorts" : [ - { - "name" : "recv", - "portNumbers" : [ - 0 - ] - }, - { - "name" : "allocate", - "portNumbers" : [ - 0 - ] - } - ] - } - ], - [ - { - "instanceName" : "deframer", - "inputPorts" : [ - { - "name" : "framedIn", - "portNumbers" : [ - 0 - ] - } - ], - "outputPorts" : [ - { - "name" : "framedDeallocate", - "portNumbers" : [ - 0 - ] - } - ] - } - ], - [ - { - "instanceName" : "buffMgr", - "inputPorts" : [ - { - "name" : "bufferSendIn", - "portNumbers" : [ - 0 - ] - }, - { - "name" : "bufferGetCallee", - "portNumbers" : [ - 0 - ] - } - ], - "outputPorts" : [] - } - ] - ], - "connections" : [ - [ - [ - 0, - 0, - 1, - 0 - ], - [ - 2, - 0, - 1, - 0 - ] - ], - [ - [ - 0, - 0, - 0, - 0 - ], - [ - 1, - 0, - 0, - 0 - ] - ], - [ - [ - 1, - 0, - 0, - 0 - ], - [ - 2, - 0, - 0, - 0 - ] - ] - ] -} diff --git a/Svc/Deframer/docs/img/top/framed-active.png b/Svc/Deframer/docs/img/top/framed-active.png deleted file mode 100644 index d941a77174..0000000000 Binary files a/Svc/Deframer/docs/img/top/framed-active.png and /dev/null differ diff --git a/Svc/Deframer/docs/img/top/framed-active.txt b/Svc/Deframer/docs/img/top/framed-active.txt deleted file mode 100644 index ec3f5c3779..0000000000 --- a/Svc/Deframer/docs/img/top/framed-active.txt +++ /dev/null @@ -1,20 +0,0 @@ -activeComm -allocate -0 -buffMgr -bufferGetCallee -0 - -activeComm -recv -0 -deframer -framedIn -0 - -deframer -framedDeallocate -0 -buffMgr -bufferSendIn -0 diff --git a/Svc/Deframer/docs/img/top/framed-passive.json b/Svc/Deframer/docs/img/top/framed-passive.json deleted file mode 100644 index 428a67f0a4..0000000000 --- a/Svc/Deframer/docs/img/top/framed-passive.json +++ /dev/null @@ -1,83 +0,0 @@ -{ - "columns" : [ - [ - { - "instanceName" : "rateGroup", - "inputPorts" : [], - "outputPorts" : [ - { - "name" : "RateGroupMemberOut", - "portNumbers" : [ - 0 - ] - } - ] - } - ], - [ - { - "instanceName" : "deframer", - "inputPorts" : [ - { - "name" : "schedIn", - "portNumbers" : [ - 0 - ] - } - ], - "outputPorts" : [ - { - "name" : "framedPoll", - "portNumbers" : [ - 0 - ] - } - ] - } - ], - [ - { - "instanceName" : "passiveComm", - "inputPorts" : [ - { - "name" : "poll", - "portNumbers" : [ - 0 - ] - } - ], - "outputPorts" : [] - } - ] - ], - "connections" : [ - [ - [ - 0, - 0, - 0, - 0 - ], - [ - 1, - 0, - 0, - 0 - ] - ], - [ - [ - 1, - 0, - 0, - 0 - ], - [ - 2, - 0, - 0, - 0 - ] - ] - ] -} diff --git a/Svc/Deframer/docs/img/top/framed-passive.png b/Svc/Deframer/docs/img/top/framed-passive.png deleted file mode 100644 index 6254b39090..0000000000 Binary files a/Svc/Deframer/docs/img/top/framed-passive.png and /dev/null differ diff --git a/Svc/Deframer/docs/img/top/framed-passive.txt b/Svc/Deframer/docs/img/top/framed-passive.txt deleted file mode 100644 index 8dbdf5c0ec..0000000000 --- a/Svc/Deframer/docs/img/top/framed-passive.txt +++ /dev/null @@ -1,13 +0,0 @@ -rateGroup -RateGroupMemberOut -0 -deframer -schedIn -0 - -deframer -framedPoll -0 -passiveComm -poll -0 diff --git a/Svc/Deframer/docs/img/top/hub-cmd.json b/Svc/Deframer/docs/img/top/hub-cmd.json deleted file mode 100644 index 173ba2411f..0000000000 --- a/Svc/Deframer/docs/img/top/hub-cmd.json +++ /dev/null @@ -1,48 +0,0 @@ -{ - "columns" : [ - [ - { - "instanceName" : "deframer", - "inputPorts" : [], - "outputPorts" : [ - { - "name" : "comOut", - "portNumbers" : [ - 0 - ] - } - ] - } - ], - [ - { - "instanceName" : "hub", - "inputPorts" : [ - { - "name" : "portIn", - "portNumbers" : [ - 0 - ] - } - ], - "outputPorts" : [] - } - ] - ], - "connections" : [ - [ - [ - 0, - 0, - 0, - 0 - ], - [ - 1, - 0, - 0, - 0 - ] - ] - ] -} diff --git a/Svc/Deframer/docs/img/top/hub-cmd.png b/Svc/Deframer/docs/img/top/hub-cmd.png deleted file mode 100644 index ed34b1c7de..0000000000 Binary files a/Svc/Deframer/docs/img/top/hub-cmd.png and /dev/null differ diff --git a/Svc/Deframer/docs/img/top/hub-cmd.txt b/Svc/Deframer/docs/img/top/hub-cmd.txt deleted file mode 100644 index 55302ba996..0000000000 --- a/Svc/Deframer/docs/img/top/hub-cmd.txt +++ /dev/null @@ -1,6 +0,0 @@ -deframer -comOut -0 -hub -portIn -0 diff --git a/Svc/Deframer/docs/img/top/hub-file.json b/Svc/Deframer/docs/img/top/hub-file.json deleted file mode 100644 index e3a14136c5..0000000000 --- a/Svc/Deframer/docs/img/top/hub-file.json +++ /dev/null @@ -1,129 +0,0 @@ -{ - "columns" : [ - [ - { - "instanceName" : "deframer", - "inputPorts" : [], - "outputPorts" : [ - { - "name" : "bufferOut", - "portNumbers" : [ - 0 - ] - }, - { - "name" : "bufferDeallocate", - "portNumbers" : [ - 0 - ] - }, - { - "name" : "bufferAllocate", - "portNumbers" : [ - 0 - ] - } - ] - } - ], - [ - { - "instanceName" : "hub", - "inputPorts" : [ - { - "name" : "buffersIn", - "portNumbers" : [ - 0 - ] - } - ], - "outputPorts" : [ - { - "name" : "bufferDeallocate", - "portNumbers" : [ - 0 - ] - } - ] - } - ], - [ - { - "instanceName" : "buffMgr", - "inputPorts" : [ - { - "name" : "bufferSendIn", - "portNumbers" : [ - 0 - ] - }, - { - "name" : "bufferGetCallee", - "portNumbers" : [ - 0 - ] - } - ], - "outputPorts" : [] - } - ] - ], - "connections" : [ - [ - [ - 0, - 0, - 0, - 0 - ], - [ - 1, - 0, - 0, - 0 - ] - ], - [ - [ - 1, - 0, - 0, - 0 - ], - [ - 2, - 0, - 0, - 0 - ] - ], - [ - [ - 0, - 0, - 2, - 0 - ], - [ - 2, - 0, - 1, - 0 - ] - ], - [ - [ - 0, - 0, - 1, - 0 - ], - [ - 2, - 0, - 0, - 0 - ] - ] - ] -} diff --git a/Svc/Deframer/docs/img/top/hub-file.png b/Svc/Deframer/docs/img/top/hub-file.png deleted file mode 100644 index c73171779b..0000000000 Binary files a/Svc/Deframer/docs/img/top/hub-file.png and /dev/null differ diff --git a/Svc/Deframer/docs/img/top/hub-file.txt b/Svc/Deframer/docs/img/top/hub-file.txt deleted file mode 100644 index d2d83484b9..0000000000 --- a/Svc/Deframer/docs/img/top/hub-file.txt +++ /dev/null @@ -1,28 +0,0 @@ -deframer -bufferOut -0 -hub -buffersIn -0 - -hub -bufferDeallocate -0 -buffMgr -bufferSendIn -0 - -deframer -bufferAllocate -0 -buffMgr -bufferGetCallee -0 - -deframer -bufferDeallocate -0 -buffMgr -bufferSendIn -0 - diff --git a/Svc/Deframer/docs/sdd.md b/Svc/Deframer/docs/sdd.md index 5c4acf2719..223fc6f178 100644 --- a/Svc/Deframer/docs/sdd.md +++ b/Svc/Deframer/docs/sdd.md @@ -1,519 +1,45 @@ -# Svc::Deframer (Passive Component) +# Svc::Deframer -## 1. Introduction +The `Svc::Deframer` component receives F´ frames, takes off the header and trailer, and passes the payload to other components of the system. -`Svc::Deframer` is a passive component. -It accepts as input a sequence of byte buffers, which -typically come from a ground data system via a -[byte stream driver](../../../Drv/ByteStreamDriverModel/docs/sdd.md). -It interprets the concatenated data of the buffers -as a sequence of uplink frames. -The uplink frames need not be aligned on the -buffer boundaries, and each frame may span one or more buffers. -`Deframer` extracts the frames from the sequence of buffers. -For each complete frame _F_ received, `Deframer` -validates _F_ and extracts a data packet from _F_. -It sends the data packet to another component in the service layer, e.g., -an instance of [`Svc::CommandDispatcher`](../../CmdDispatcher/docs/sdd.md), -[`Svc::FileUplink`](../../FileUplink/docs/sdd.md), -or [`Svc::GenericHub`](../../GenericHub/docs/sdd.md). +## Internals -When instantiating Deframer, you must provide an implementation -of [`Svc::DeframingProtocol`](../../FramingProtocol/docs/sdd.md). -This implementation specifies exactly what is -in each frame; typically it is a frame header, a data packet, and a hash value. +The `Svc::Deframer` component is an implementation of the [DeframerInterface](../../Interfaces/DeframerInterface.fppi) for the F´ communications protocol. It receives an F´ frame (in a [Fw::Buffer](../../../Fw/Buffer/docs/sdd.md) object) on its `framedIn` input port, modifies the input buffer to remove the header and trailer, and sends it out through its `deframedOut` output port. -On receiving a buffer _FB_ containing framed data, `Deframer` -(1) copies the data from _FB_ into a circular buffer _CB_ owned by `Deframer` and (2) -calls the `deframe` method of the `Svc::DeframingProtocol` implementation, -passing a reference to _CB_ as input. -If _FB_ holds more data than will fit in _CB_, -then `Deframer` repeats this process until _FB_ is empty. -If the protocol implementation reports that the data in _CB_ -represents an incomplete frame, then `Deframer` postpones deframing -until the next buffer _FB_ becomes available. +The `Svc::Deframer` component does not perform any validation of the frame. It is expected that the frame is valid and well-formed. The validation should be performed by an upstream component, such as [`Svc::FrameAccumulator`](../../FrameAccumulator/docs/sdd.md). -Deframer supports two configurations for streaming data: +The `Svc::Deframer` does not support deframing multiple packets in a single frame (i.e. concatenated packets) as this is not supported by the F´ communications protocol. -1. **Poll:** This configuration works with a passive byte stream driver. - In this configuration, `Deframer` polls the driver for buffers - on its `schedIn` cycle. - No buffer allocation occurs when polling. - _FB_ is a buffer owned by `Deframer`. +## Usage Examples -2. **Push:** This configuration works with an active byte stream driver. - In this configuration the driver invokes a guarded port of `Deframer` to - send a buffer _FB_ to `Deframer`. - The invocation transfers ownership of _FB_ from the driver to `Deframer`. - Deframing occurs on the thread of the byte stream driver. - `Deframer` deallocates _FB_ before it returns from - the guarded port call. +The `Svc::Deframer` component is used in the uplink stack of many reference F´ application such as [the tutorials source code](https://github.com/fprime-community#tutorials). -## 2. Assumptions -1. For any deployment _D_ that uses an instance _I_ of `Deframer`, the - deframing protocol used with _I_ matches the uplink protocol of - any ground system that sends frames to _I_. +## Diagrams -1. In any topology _T_, for any instance _I_ of `Deframer` in _T_, - at any one time, framed data arrives on the poll interface of _I_ or on - the push interface of _I_, but not on both concurrently. - The push and poll interfaces are guarded by a mutual exclusion lock, - so there is no concurrency safety issue. - However, ordinarily it does not make sense to interleave framed data - concurrently on two different interfaces. +The below diagram shows a typical configuration in which the `Svc::Deframer` can be used. This is the configuration used in the [the tutorials source code](https://github.com/fprime-community#tutorials). It is receiving accumulated frames from a [Svc::FrameAccumulator](../../FrameAccumulator/docs/sdd.md) and passes packets to a [Svc::Router](../../Router/docs/sdd.md) for routing to other components. -1. Each frame received by `Deframer` contains an F Prime command packet - or file packet _P_. - The first _n_ bytes of the packet hold the packet descriptor value - `Fw::ComPacket::FW_PACKET_COMMAND` (for a command packet) or - `Fw::ComPacket::FW_PACKET_FILE` (for a file packet), - serialized as an unsigned integer in big-endian byte order. - The number of bytes _n_ matches the size of the type defined by the - C preprocessor symbol `FwPacketDescriptorType` in the F Prime FSW. +![./img/deframer_uplink_stack.png](./img/deframer_uplink_stack.png) -## 3. Requirements -Requirement | Description | Rationale | Verification Method ------------ | ----------- | ----------| ------------------- -SVC-DEFRAMER-001 | `Svc::Deframer` shall accept a sequence of byte buffers and interpret their concatenated data as a sequence of uplink frames. | The purpose of the component is to do uplink deframing. | Unit test -SVC-DEFRAMER-002 | `Svc::Deframer` shall accept byte buffers containing uplink frames that are not aligned on a buffer boundary. | For flexibility, we do not require that the frames be aligned on a buffer boundary. | Unit test -SVC-DEFRAMER-003 | `Svc::Deframer` shall accept byte buffers containing uplink frames that span one or more buffers. | For flexibility, we do not require each frame to fit in a single buffer. | Unit test -SVC-DEFRAMER-004 | `Svc::Deframer` shall provide a port interface that a threaded driver can use to push byte buffers to be deframed. | This interface supports applications in which the byte stream driver has its own thread. | Unit test -SVC-DEFRAMER-005 | `Svc::Deframer` shall provide a port interface that Deframer can use to poll for byte buffers to be deframed. | This interface supports applications in which byte stream driver does not have its own thread. | Unit test -SVC-DEFRAMER-006 | If the polling interface is connected, then `Svc::Deframer` shall poll for byte buffers on its `schedIn` port. | This requirement allows the system scheduler to drive the periodic polling. | Unit test -SVC-DEFRAMER-007 | `Svc::Deframer` shall use an instance of `Svc::DeframingProtocol`, supplied when the component is instantiated, to validate the frames and extract their packet data. | Using the `Svc::DeframingProtocol` interface allows the same Deframer component to operate with different protocols. | Unit test -SVC-DEFRAMER-008 | `Svc::Deframer` shall interpret the initial bytes of the packet data as a value of type `FwPacketDescriptorType`. | `FwPacketDescriptorType` is the type of an F Prime packet descriptor. The size of the type is configurable in the F Prime framework. | Test -SVC-DEFRAMER-009 | `Svc::Deframer` shall extract and send packets with the following types: `Fw::ComPacket::FW_PACKET_COMMAND`, `Fw::ComPacket::FW_PACKET_FILE`. | These are the packet types used for uplink. | Unit test -SVC-DEFRAMER-010 | `Svc::Deframer` shall send command packets and file packets on separate ports. | Command packets and file packets are typically handled by different components. | Unit test -SVC-DEFRAMER-011 | `Svc::Deframer` shall operate nominally when its port for sending file packets is unconnected, even if it receives a frame containing a file packet. | Some applications do not use file uplink. Sending a file uplink packet to `Deframer` should not crash the application because of an unconnected port. | Unit test - -## 4. Design - -### 4.1. Component Diagram - -The diagram below shows the `Deframer` component. - -![Deframer](img/Deframer.png) - -### 4.2. Ports - -`Deframer` has the following ports: - -| Kind | Name | Port Type | Usage | -|------|------|-----------|-------| -| `guarded input` | `framedIn` | `Drv.ByteStreamRecv` | Port for receiving frame buffers FB pushed from the byte stream driver. After using a buffer FB received on this port, Deframer deallocates it by invoking framedDeallocate.| -| `output` | `framedDeallocate` | `Fw.BufferSend` | Port for deallocating buffers received on framedIn. | -| `guarded input` | `schedIn` | `Svc.Sched` | Schedule in port, driven by a rate group.| -| `output` | `framedPoll` | `Drv.ByteStreamPoll` | Port that polls for data from the byte stream driver. Deframer invokes this port on its schedIn cycle, if it is connected. No allocation or occurs when invoking this port. The data transfer uses a pre-allocated frame buffer owned by Deframer. | -| `output` | `bufferAllocate` | `Fw.BufferGet` | Port for allocating Fw::Buffer objects from a buffer manager. When Deframer invokes this port, it receives a packet buffer PB and takes ownership of it. It uses PB internally for deframing. Then one of two things happens: 1. PB contains a file packet, which Deframer sends on bufferOut. In this case ownership of PB passes to the receiver. 2. PB does not contain a file packet, or bufferOut is unconnected. In this case Deframer deallocates PB on bufferDeallocate. | -| `output` | `bufferOut` | `Fw.BufferSend` | Port for sending file packets (case 1 above). The file packets are wrapped in Fw::Buffer objects allocated with bufferAllocate. Ownership of the Fw::Buffer passes to the receiver, which is responsible for the deallocation. | -| `output` | `bufferDeallocate` | `Fw.BufferSend` | Port for deallocating temporary buffers allocated with bufferAllocate (case 2 above). Deallocation occurs here when there is nothing to send on bufferOut. | -| `output` | `comOut` | `Fw.Com` | Port for sending command packets as Com buffers. | -| `sync input` | `cmdResponseIn` | `Fw.CmdResponse` | Port for receiving command responses from a command dispatcher. Invoking this port does nothing. The port exists to allow the matching connection in the topology. | - - -### 4.3. Derived Classes - -`Deframer` is derived from `DeframerComponentBase` as usual. -It is also derived (via C++ multiple inheritance) from -[`Svc::DeframingProtocolInterface`](../../FramingProtocol/docs/sdd.md). -The multiple inheritance makes the `Deframer` instance into the -instance of `Svc::DeframingProtocolInterface` that is required -to use `Svc::DeframingProtocol`. -See below for a description of how `Deframer` implements -`DeframingProtocolInterface`. - -Here is a class diagram for `Deframer`: +## Class Diagram ```mermaid classDiagram - ObjBase <|-- PassiveComponentBase - PassiveComponentBase <|-- DeframerComponentBase - DeframerComponentBase <|-- Deframer - DeframingProtocolInterface <|-- Deframer -``` - -### 4.4. State - -`Deframer` maintains the following state: - -1. `m_protocol`: A pointer to the implementation of `DeframingProtocol` - used for deframing. - -1. `m_inRing`: An instance of `Types::CircularBuffer` for storing data to be deframed. - -1. `m_ringBuffer`: The storage backing the circular buffer: an array of `RING_BUFFER_SIZE` -`U8` values. - -1. `m_pollBuffer`: The buffer used for polling input: an array of 1024 `POLL_BUFFER_SIZE` -values. - -### 4.5. Header File Configuration - -The `Deframer` header file provides the following configurable constants: - -1. `Svc::Deframer::RING_BUFFER_SIZE`: The size of the circular buffer. -The capacity of the circular buffer must be large enough to hold a -complete frame. - -1. `Svc::Deframer::POLL_BUFFER_SIZE`: The size of the buffer used for polling data. - -### 4.6. Runtime Setup - -To set up an instance of `Deframer`, you do the following: - -1. Call the constructor and the `init` method in the usual way -for an F Prime passive component. - -1. Call the `setup` method, passing in an instance _P_ of `Svc::DeframingProtocol`. -The `setup` method does the following: - - 1. Store a pointer to _P_ in `m_protocol`. - - 1. Pass `*this` into the setup method for _P_. - As noted above, `*this` - is the instance of `Svc::DeframingProtocolInterface` - used by _P_. - -For an example of setting up a `Deframer` instance, see the -`uplink` instance in [`Ref/Top/instances.fpp`](../../../Ref/Top/instances.fpp). - -### 4.7. Port Handlers - -#### 4.7.1. framedIn - -The `framedIn` port handler receives an `Fw::Buffer` _FB_ and a receive status _S_. -It does the following: - -1. If _S_ = `RECV_OK`, then call - `processBuffer`, passing in _FB_. - -2. Deallocate _FB_ by invoking `framedDeallocate`. - -#### 4.7.2. schedIn - -The `schedIn` port handler does the following: - -1. Construct an `Fw::Buffer` _FB_ that wraps `m_pollBuffer`. - -1. If `framedPoll` is connected, then - - 1. Invoke `framedPollOut`, passing in _FB_, to poll for new data. - - 1. If new data is available, then call - `processBuffer`, passing in _FB_. - -#### 4.7.3. cmdResponseIn - -The `cmdResponseIn` handler does nothing. -It exists to provide the necessary symmetry in the topology -(every component that sends a command to the dispatcher should -accept a matching response). - - -### 4.8. Implementation of Svc::DeframingProtocolInterface - - -#### 4.8.1. allocate - -The implementation of `allocate` invokes `bufferAllocate`. - - -#### 4.8.2. route - -The implementation of `route` takes a reference to an -`Fw::Buffer` _PB_ (a packet buffer) and does the following: - -1. Set `deallocate = true`. - -1. Let _N_ = `sizeof(FwPacketDescriptorType)`. -Deserialize the first _N_ bytes of _PB_ as a value of type -`FwPacketDescriptorType`. - -1. If the deserialization succeeds, then switch on the packet type _T_. - - 1. If _T_ = `FW_PACKET_COMMAND`, then send the contents - of _PB_ as a Com buffer on `comOut`. - - 1. Otherwise if _T_ = `FW_PACKET_FILE` and `bufferOut` is connected, - then - - 1. Shift the pointer of _PB_ _N_ bytes forward and - reduce the size of _PB_ by _N_ to skip the packet type. - This step is necessary to accommodate the `FileUplink` component. - - 1. Send _B_ on `bufferOut`. - - 1. Set `deallocate = false`. This step causes ownership - of the buffer to pass to the receiver. - -1. If `deallocate = true`, then invoke `bufferDeallocate` - to deallocate _PB_. - -### 4.9. Helper Functions - - -#### 4.9.1. processBuffer - -`processBuffer` accepts a reference to an `Fw::Buffer` _FB_ -(a frame buffer). -It does the following: - -1. Set `buffer_offset` = 0. - -1. Set _S_ = `buffer.getSize()`. - -1. In a bounded loop, while `buffer_offset` < _S_, do: - - 1. Compute the amount of remaining data in _FB_. - This is _R_ = _S_ - `buffer_offset`. - - 1. Compute _C_, the number of bytes to copy from _FB_ into the - circular buffer `m_inRing`. - - 1. Let _F_ be the number of free bytes in `m_inRing`. - - 1. If _R_ < _F_, then _C_ = _R_. - - 1. Otherwise _C_ = _F_. - - 1. Copy _C_ bytes from _FB_ starting at `buffer_offset` - into `m_inRing`. - - 1. Advance `buffer_offset` by _C_. - - 1. Call `processRing` - to process the data stored in `m_inRing`. - - -#### 4.9.2. processRing - -In a bounded loop, while there is data remaining in `m_inRing`, do: - -1. Call the `deframe` method of `m_protocol` on `m_inRing`. - The `deframe` method calls `allocate` and - `route` as necessary. - It returns a status value _S_ and the number _N_ of bytes - needed for successful deframing. - -1. If _S_ = `SUCCESS`, then _N_ represents the number of bytes - used in a successful deframing. Rotate `m_inRing` by _N_ bytes (i.e., - deallocate _N_ bytes from the head of `m_inRing`). - -1. Otherwise if _S_ = `MORE_NEEDED`, then do nothing. - Further processing will occur on the next call, after more - data goes into `m_inRing`. - -1. Otherwise something is wrong. - Rotate `m_inRing` by one byte, to skip byte by byte over - bad data until we find a valid frame. - -## 5. Ground Interface - -None. - -## 6. Example Uses - - -### 6.1. Topology Diagrams - -The following topology diagrams show how to connect `Svc::Deframer` -to a byte stream driver, a command dispatcher, and a file uplink component. -The diagrams use the following instances: - -* `activeComm`: An active instance of -[`Drv::ByteStreamDriverModel`](../../../Drv/ByteStreamDriverModel/docs/sdd.md), for example, -[`Drv::TcpClient`](../../../Drv/TcpClient/docs/sdd.md). - -* `buffMgr`: An instance of [`Svc::BufferManager`](../../BufferManager/docs/sdd.md) - -* `cmdDisp`: An instance of [`Svc::CommandDispatcher`](../../CmdDispatcher/docs/sdd.md) - -* `deframer`: An instance of `Svc::Deframer`. - -* `fileUplink`: An instance of [`Svc::FileUplink`](../../FileUplink/docs/sdd.md). - -* `passiveComm`: A passive instance of -[`Drv::ByteStreamDriverModel`](../../../Drv/ByteStreamDriverModel/docs/sdd.md). - -* `rateGroup`: An instance of [`Svc::ActiveRateGroup`](../../ActiveRateGroup/docs/sdd.md). - -Topologies 1a and 1b are alternate topologies. -You should use one or the other. -In topology 3, the `fileUplink` instance and its connections are -optional. - -**Topology 1a: Buffers containing framed data (active byte stream driver):** - -![active](img/top/framed-active.png) - -**Topology 1b: Buffers containing framed data (passive byte stream driver):** - -![passive](img/top/framed-passive.png) - -Revise the port number of `rateGroup.RateGroupMemberOut` as -appropriate for your application. - -**Topology 2: Command packets and command responses:** - -![cmd](img/top/cmd.png) - -Revise the port numbers of `cmdDisp.seqCmdBuff` and -`cmdDisp.compCmdStat` as appropriate for your application. -If you model your topology in FPP, then FPP can automatically -assign these numbers. - -**Topology 3: Buffers containing packet data:** - -![file](img/top/deframer-file.png) - -### 6.2. Sequence Diagrams - -#### 6.2.1. Active Byte Stream Driver - -**Sending a command packet:** -The following sequence diagram shows what happens when `activeComm` -sends data to `deframer`, and `deframer` -decodes the data into a command packet. -Open vertical rectangles represent threads. -Vertical dashed lines represent component code. -Solid horizontal arrows represent synchronous port invocations, and open -horizontal arrows represent asynchronous port invocations. - -```mermaid -sequenceDiagram - activate activeComm - activeComm->>buffMgr: Allocate frame buffer FB - buffMgr-->>activeComm: Return FB - activeComm->>activeComm: Fill FB with framed data - activeComm->>deframer: Send FB[framedIn] - deframer->>buffMgr: Allocate packet buffer PB [bufferAllocate] - buffMgr-->>deframer: Return PB - deframer->>deframer: Deframe FB into PB - deframer->>deframer: Copy PB into a command packet C - deframer-)cmdDisp: Send C [comOut] - deframer->>buffMgr: Deallocate PB [bufferDeallocate] - buffMgr-->>deframer: - deframer->>buffMgr: Deallocate FB [framedDeallocate] - buffMgr-->>deframer: - deframer-->>activeComm: - deactivate activeComm - activate cmdDisp - cmdDisp->>deframer: Send cmd response [cmdResponseIn] - deframer-->>cmdDisp: - deactivate cmdDisp -``` - -**Sending a file packet:** -The following sequence diagram shows what happens when `activeComm` -sends data to `deframer`, and `deframer` decodes the data into a file packet. - -```mermaid -sequenceDiagram - activate activeComm - activeComm->>buffMgr: Allocate frame buffer FB - buffMgr-->>activeComm: Return FB - activeComm->>activeComm: Fill FB with framed data - activeComm->>deframer: Send FB [framedIn] - deframer->>buffMgr: Allocate packet buffer PB [bufferAllocate] - buffMgr-->>deframer: Return PB - deframer->>deframer: Deframe FB into PB - deframer-)fileUplink: Send PB [bufferOut] - deframer->>buffMgr: Deallocate FB [framedDeallocate] - buffMgr-->>deframer: - deframer-->>activeComm: - deactivate activeComm - activate fileUplink - fileUplink->>buffMgr: Deallocate PB - buffMgr-->>fileUplink: - deactivate fileUplink -``` - -#### 6.2.2. Passive Byte Stream Driver - -**Sending a command packet:** The following sequence diagram shows what -happens when `passiveComm` sends data to `deframer`, and -`deframer` decodes the data into a command packet. - -```mermaid -sequenceDiagram - activate rateGroup - rateGroup->>deframer: Send schedule tick [schedIn] - deframer->>passiveComm: Poll for data [framedPoll] - passiveComm-->>deframer: Return status - deframer->>buffMgr: Allocate packet buffer PB [bufferAllocate] - buffMgr-->>deframer: Return PB - deframer->>deframer: Deframe data into PB - deframer->>deframer: Copy PB into a command packet C - deframer-)cmdDisp: Send C [comOut] - deframer->>buffMgr: Deallocate PB [bufferDeallocate] - buffMgr-->>deframer: - deframer-->>rateGroup: - deactivate rateGroup - activate cmdDisp - cmdDisp->>deframer: Send cmd response [cmdResponseIn] - deframer-->>cmdDisp: - deactivate cmdDisp + class FrameAccumulator~PassiveComponent~ { + + void framedIn_handler(FwIndexType portNum, Fw::Buffer& data, Fw::Buffer& context) + } ``` -**Sending a file packet:** The following sequence diagram shows what -happens when `passiveComm` sends data to `deframer`, and -`Deframer` decodes the data into a file packet. +## Requirements -```mermaid -sequenceDiagram - activate rateGroup - rateGroup->>deframer: Send schedule tick [schedIn] - deframer->>passiveComm: Poll for data [framedPoll] - passiveComm-->>deframer: Return status - deframer->>buffMgr: Allocate packet buffer PB [bufferAllocate] - buffMgr-->>deframer: Return PB - deframer->>deframer: Deframe data into PB - deframer-)fileUplink: Send PB [bufferOut] - deframer-->>rateGroup: - deactivate rateGroup - activate fileUplink - fileUplink->>buffMgr: Deallocate PB - buffMgr-->>fileUplink: - deactivate fileUplink -``` - - -### 6.3. Using Svc::GenericHub - -You can use `Deframer` with an instance of -[`Svc::GenericHub`](../../GenericHub/docs/sdd.md) to send deframed -command packets and file packets across a network connection, instead of -directly to a command dispatcher or file uplink component. -To send deframed packets this way, do the following: - -1. In the topology described above, -instead of the `cmdDisp` and `fileUplink` instances, use an -instance `hub` of type `Svc::GenericHub`. - -1. Revise topologies 2 and 3 as shown below. - -**Topology 2: Command packets** - -![hub-cmd](img/top/hub-cmd.png) - -Revise the port number of `hub.portIn` as appropriate for your application. - -**Topology 3: Buffers containing packet data** - -![file](img/top/hub-file.png) - -Revise the port number of `hub.buffersIn` as appropriate for your application. -When `hub` receives a buffer on `buffersIn`, it copies the data across -the connection to the other hub and deallocates the buffer. - -If you don't need to transmit file packets across the hub, then you can -omit the `hub` connections shown in this topology. +Requirement | Description | Rationale | Verification Method +----------- | ----------- | ----------| ------------------- +SVC-DEFRAMER-001 | `Svc::Deframer` shall remove the header and trailer from and F´ frame | Purpose of the component | Unit test | -## 7. Change Log +## Port Descriptions -| Date | Description | -|---|---| -| 2021-01-30 | Initial Draft | -| 2022-04-04 | Revised | +| Kind | Name | Type | Description | +|---|---|---|---| +| `guarded input` | framedIn | `Fw.DataWithContext` | Receives a frame with optional context data | +| `output` | deframedOut | `Fw.DataWithContext` | Receives a frame with optional context data | diff --git a/Svc/Deframer/test/ut-fprime-protocol/DeframerTestMain.cpp b/Svc/Deframer/test/ut-fprime-protocol/DeframerTestMain.cpp deleted file mode 100644 index da7091a50e..0000000000 --- a/Svc/Deframer/test/ut-fprime-protocol/DeframerTestMain.cpp +++ /dev/null @@ -1,132 +0,0 @@ -// ---------------------------------------------------------------------- -// TestMain.cpp -// ---------------------------------------------------------------------- - -#include - -#include "Fw/Test/UnitTest.hpp" -#include "GenerateFrames.hpp" -#include "Os/Console.hpp" -#include "STest/Scenario/BoundedScenario.hpp" -#include "STest/Scenario/RandomScenario.hpp" -#include "STest/Scenario/Scenario.hpp" -#include "SendBuffer.hpp" -#include "DeframerTester.hpp" - -#define STEP_COUNT 10000 - -// Uncomment the following line to turn on OS logging -//Os::Log logger; - -// ---------------------------------------------------------------------- -// Static helper functions -// ---------------------------------------------------------------------- - -//! Run a random scenario -static void runRandomScenario(Svc::DeframerTester::InputMode::t inputMode) { - Svc::DeframerTester tester(Svc::DeframerTester::InputMode::PUSH); - - // Create rules, and assign them into the array - Svc::GenerateFrames generateFrames; - Svc::SendBuffer sendBuffer; - - // Setup a list of rules to choose from - STest::Rule* rules[] = { - &generateFrames, - &sendBuffer - }; - // Construct the random scenario and run it with the defined bounds - STest::RandomScenario random( - "Random Rules", - rules, - FW_NUM_ARRAY_ELEMENTS(rules) - ); - - // Setup a bounded scenario to run rules a set number of times - STest::BoundedScenario bounded( - "Bounded Random Rules Scenario", - random, - STEP_COUNT - ); - // Run! - const U32 numSteps = bounded.run(tester); - printf("Ran %u steps.\n", numSteps); -} - -// ---------------------------------------------------------------------- -// Tests -// ---------------------------------------------------------------------- - -TEST(Nominal, BasicPush) { - COMMENT("Send one buffer to the deframer, simulating an active driver (push)"); - REQUIREMENT("SVC-DEFRAMER-001"); - REQUIREMENT("SVC-DEFRAMER-002"); - REQUIREMENT("SVC-DEFRAMER-003"); - REQUIREMENT("SVC-DEFRAMER-004"); - REQUIREMENT("SVC-DEFRAMER-007"); - REQUIREMENT("SVC-DEFRAMER-008"); - REQUIREMENT("SVC-DEFRAMER-009"); - REQUIREMENT("SVC-DEFRAMER-010"); - Svc::DeframerTester tester(Svc::DeframerTester::InputMode::PUSH); - Svc::GenerateFrames().apply(tester); - Svc::SendBuffer().apply(tester); -} - -TEST(Nominal, BasicPoll) { - COMMENT("Send one buffer to the deframer, simulating a passive driver (poll)"); - REQUIREMENT("SVC-DEFRAMER-001"); - REQUIREMENT("SVC-DEFRAMER-002"); - REQUIREMENT("SVC-DEFRAMER-003"); - REQUIREMENT("SVC-DEFRAMER-005"); - REQUIREMENT("SVC-DEFRAMER-006"); - REQUIREMENT("SVC-DEFRAMER-007"); - REQUIREMENT("SVC-DEFRAMER-008"); - REQUIREMENT("SVC-DEFRAMER-009"); - REQUIREMENT("SVC-DEFRAMER-010"); - Svc::DeframerTester tester(Svc::DeframerTester::InputMode::POLL); - Svc::GenerateFrames().apply(tester); - Svc::SendBuffer().apply(tester); -} - -TEST(Nominal, RandomPush) { - COMMENT("Send random buffers to the deframer, simulating an active driver (push)"); - REQUIREMENT("SVC-DEFRAMER-001"); - REQUIREMENT("SVC-DEFRAMER-002"); - REQUIREMENT("SVC-DEFRAMER-003"); - REQUIREMENT("SVC-DEFRAMER-004"); - REQUIREMENT("SVC-DEFRAMER-007"); - REQUIREMENT("SVC-DEFRAMER-008"); - REQUIREMENT("SVC-DEFRAMER-009"); - REQUIREMENT("SVC-DEFRAMER-010"); - runRandomScenario(Svc::DeframerTester::InputMode::PUSH); -} - -TEST(Nominal, RandomPoll) { - COMMENT("Send random buffers to the deframer, simulating a passive driver (poll)"); - REQUIREMENT("SVC-DEFRAMER-001"); - REQUIREMENT("SVC-DEFRAMER-002"); - REQUIREMENT("SVC-DEFRAMER-003"); - REQUIREMENT("SVC-DEFRAMER-005"); - REQUIREMENT("SVC-DEFRAMER-006"); - REQUIREMENT("SVC-DEFRAMER-007"); - REQUIREMENT("SVC-DEFRAMER-008"); - REQUIREMENT("SVC-DEFRAMER-009"); - REQUIREMENT("SVC-DEFRAMER-010"); - runRandomScenario(Svc::DeframerTester::InputMode::POLL); -} - -TEST(Error, SizeOverflow) { - COMMENT("Test handling of size overflow in F Prime deframing protocol"); - Svc::DeframerTester tester(Svc::DeframerTester::InputMode::PUSH); - tester.sizeOverflow(); -} - -// ---------------------------------------------------------------------- -// Main function -// ---------------------------------------------------------------------- - -int main(int argc, char **argv) { - ::testing::InitGoogleTest(&argc, argv); - STest::Random::seed(); - return RUN_ALL_TESTS(); -} diff --git a/Svc/Deframer/test/ut-fprime-protocol/DeframerTester.cpp b/Svc/Deframer/test/ut-fprime-protocol/DeframerTester.cpp deleted file mode 100644 index 1cf494ccd3..0000000000 --- a/Svc/Deframer/test/ut-fprime-protocol/DeframerTester.cpp +++ /dev/null @@ -1,261 +0,0 @@ -// ====================================================================== -// \title DeframerTester.cpp -// \brief Implementation file for Deframer test with F Prime protocol -// \author mstarch, bocchino -// -// \copyright -// Copyright 2009-2022, by the California Institute of Technology. -// ALL RIGHTS RESERVED. United States Government Sponsorship -// acknowledged. -// ====================================================================== - -#include -#include - -#include "Fw/Types/Assert.hpp" -#include "DeframerTester.hpp" -#include "Utils/Hash/Hash.hpp" -#include "Utils/Hash/HashBuffer.hpp" - -#define INSTANCE 0 -#define MAX_HISTORY_SIZE 10000 - -namespace Svc { - - // ---------------------------------------------------------------------- - // Constructor - // ---------------------------------------------------------------------- - - DeframerTester ::DeframerTester(InputMode::t inputMode) - : DeframerGTestBase("Tester", MAX_HISTORY_SIZE), - component("Deframer"), - m_inputMode(inputMode) - { - this->initComponents(); - this->connectPorts(); - component.setup(protocol); - memset(m_incomingBufferBytes, 0, sizeof m_incomingBufferBytes); - } - - // ---------------------------------------------------------------------- - // Tests - // ---------------------------------------------------------------------- - - void DeframerTester ::sizeOverflow() { - U8 data[FpFrameHeader::SIZE]; - Fw::Buffer buffer(data, sizeof data); - Fw::SerializeBufferBase& serialRepr = buffer.getSerializeRepr(); - Fw::SerializeStatus status = serialRepr.serialize(FpFrameHeader::START_WORD); - ASSERT_EQ(status, Fw::FW_SERIALIZE_OK); - FpFrameHeader::TokenType size = std::numeric_limits::max(); - status = serialRepr.serialize(size); - ASSERT_EQ(status, Fw::FW_SERIALIZE_OK); - this->component.processBuffer(buffer); - // Assert no output - ASSERT_FROM_PORT_HISTORY_SIZE(0); - } - - // ---------------------------------------------------------------------- - // Public instance methods - // ---------------------------------------------------------------------- - - void DeframerTester ::setUpIncomingBuffer() { - const U32 bufferSize = STest::Pick::lowerUpper( - 1, - sizeof m_incomingBufferBytes - ); - ASSERT_LE(bufferSize, sizeof m_incomingBufferBytes); - m_incomingBuffer = Fw::Buffer( - m_incomingBufferBytes, - bufferSize - ); - } - - void DeframerTester ::sendIncomingBuffer() { - switch (m_inputMode) { - case InputMode::PUSH: - // Push buffer to framedIn - invoke_to_framedIn( - 0, - m_incomingBuffer, - Drv::RecvStatus::RECV_OK - ); - break; - case InputMode::POLL: - // Call schedIn handler, which polls for buffer - invoke_to_schedIn(0, 0); - break; - default: - FW_ASSERT(0); - break; - } - } - - // ---------------------------------------------------------------------- - // Handlers for typed from ports - // ---------------------------------------------------------------------- - - void DeframerTester ::from_comOut_handler( - const NATIVE_INT_TYPE portNum, - Fw::ComBuffer& data, - U32 context - ) { - // Check that a received frame is expected - ASSERT_GT(m_framesToReceive.size(), 0) << - "Queue of frames to receive is empty" << std::endl; - // Get the frame at the front - UplinkFrame frame = m_framesToReceive.front(); - m_framesToReceive.pop_front(); - // Check the packet type - ASSERT_EQ(frame.packetType, Fw::ComPacket::FW_PACKET_COMMAND); - // Check the packet data - for (U32 i = 0; i < data.getBuffLength(); i++) { - EXPECT_EQ( - (data.getBuffAddr())[i], - (frame.getData())[FpFrameHeader::SIZE + i] - ); - } - // Push the history entry - this->pushFromPortEntry_comOut(data, context); - } - - void DeframerTester ::from_bufferOut_handler( - const NATIVE_INT_TYPE portNum, - Fw::Buffer& fwBuffer - ) { - // Check that a received frame is expected - ASSERT_GT(m_framesToReceive.size(), 0) << - "Queue of frames to receive is empty" << std::endl; - // Get the frame at the front - UplinkFrame frame = m_framesToReceive.front(); - m_framesToReceive.pop_front(); - // Check the packet type - ASSERT_EQ(frame.packetType, Fw::ComPacket::FW_PACKET_FILE); - // Check the packet data - for (U32 i = 0; i < fwBuffer.getSize(); i++) { - // Deframer strips type before sending to FileUplink - const U32 frameOffset = - FpFrameHeader::SIZE + sizeof(FwPacketDescriptorType) + i; - ASSERT_EQ( - (fwBuffer.getData())[i], - (frame.getData())[frameOffset] - ); - } - // Buffers received on this port are owned by the receiver - // So delete the allocation now - // Before deallocating, undo the deframer's adjustment of the pointer - // by the size of the packet type - delete[](fwBuffer.getData() - sizeof(FwPacketDescriptorType)); - // Push the history entry - this->pushFromPortEntry_bufferOut(fwBuffer); - } - - Fw::Buffer DeframerTester ::from_bufferAllocate_handler( - const NATIVE_INT_TYPE portNum, - U32 size - ) { - this->pushFromPortEntry_bufferAllocate(size); - U8 *const data = new U8[size]; - memset(data, 0, size); - Fw::Buffer buffer(data, size); - return buffer; - } - - void DeframerTester ::from_bufferDeallocate_handler( - const NATIVE_INT_TYPE portNum, - Fw::Buffer& fwBuffer - ) { - delete[] fwBuffer.getData(); - this->pushFromPortEntry_bufferDeallocate(fwBuffer); - } - - void DeframerTester ::from_framedDeallocate_handler( - const NATIVE_INT_TYPE portNum, - Fw::Buffer& fwBuffer - ) { - this->pushFromPortEntry_framedDeallocate(fwBuffer); - } - - Drv::PollStatus DeframerTester ::from_framedPoll_handler( - const NATIVE_INT_TYPE portNum, - Fw::Buffer& pollBuffer - ) { - this->pushFromPortEntry_framedPoll(pollBuffer); - U8* incoming = m_incomingBuffer.getData(); - const U32 size = m_incomingBuffer.getSize(); - U8* outgoing = pollBuffer.getData(); - const U32 maxSize = pollBuffer.getSize(); - FW_ASSERT(size <= maxSize, size, maxSize); - memcpy(outgoing, incoming, size); - pollBuffer.setSize(size); - return Drv::PollStatus::POLL_OK; - } - - // ---------------------------------------------------------------------- - // Helper methods - // ---------------------------------------------------------------------- - - void DeframerTester ::connectPorts() { - - // bufferAllocate - this->component.set_bufferAllocate_OutputPort( - 0, - this->get_from_bufferAllocate(0) - ); - - // bufferDeallocate - this->component.set_bufferDeallocate_OutputPort( - 0, - this->get_from_bufferDeallocate(0) - ); - - // bufferOut - this->component.set_bufferOut_OutputPort( - 0, - this->get_from_bufferOut(0) - ); - - // cmdResponseIn - this->connect_to_cmdResponseIn( - 0, - this->component.get_cmdResponseIn_InputPort(0) - ); - - // comOut - this->component.set_comOut_OutputPort( - 0, - this->get_from_comOut(0) - ); - - // framedDeallocate - this->component.set_framedDeallocate_OutputPort( - 0, - this->get_from_framedDeallocate(0) - ); - - // framedIn - this->connect_to_framedIn( - 0, - this->component.get_framedIn_InputPort(0) - ); - - // framedPoll - this->component.set_framedPoll_OutputPort( - 0, - this->get_from_framedPoll(0) - ); - - // schedIn - this->connect_to_schedIn( - 0, - this->component.get_schedIn_InputPort(0) - ); - - } - - void DeframerTester ::initComponents() { - this->init(); - this->component.init(INSTANCE); - } - -} diff --git a/Svc/Deframer/test/ut-fprime-protocol/DeframerTester.hpp b/Svc/Deframer/test/ut-fprime-protocol/DeframerTester.hpp deleted file mode 100644 index 1ea922556d..0000000000 --- a/Svc/Deframer/test/ut-fprime-protocol/DeframerTester.hpp +++ /dev/null @@ -1,325 +0,0 @@ -// ====================================================================== -// \title DeframerTester.hpp -// \brief Header file for Deframer test with F Prime protocol -// \author mstarch, bocchino -// -// \copyright -// Copyright 2009-2022, by the California Institute of Technology. -// ALL RIGHTS RESERVED. United States Government Sponsorship -// acknowledged. -// ====================================================================== - -#ifndef SVC_TESTER_HPP -#define SVC_TESTER_HPP - -#include -#include - -#include "DeframerGTestBase.hpp" -#include "Fw/Com/ComPacket.hpp" -#include "Fw/Types/SerialBuffer.hpp" -#include "STest/STest/Pick/Pick.hpp" -#include "Svc/Deframer/Deframer.hpp" -#include "Svc/FramingProtocol/FprimeProtocol.hpp" -#include "Utils/Hash/Hash.hpp" - -namespace Svc { - - class DeframerTester : public DeframerGTestBase { - - // ---------------------------------------------------------------------- - // Friend classes - // ---------------------------------------------------------------------- - - friend struct GenerateFrames; - friend class SendBuffer; - - // ---------------------------------------------------------------------- - // Types - // ---------------------------------------------------------------------- - - public: - - //! Enumerated constants - enum Constants { - //! The maximum valid frame size - //! Every valid frame must fit in the ring buffer - MAX_VALID_FRAME_SIZE = DeframerCfg::RING_BUFFER_SIZE, - //! The max frame size that will fit in the test buffer - //! Larger than max valid size, to test bad sizes - MAX_FRAME_SIZE = MAX_VALID_FRAME_SIZE + 1, - //! The size of the part of the frame that is outside the packet - NON_PACKET_SIZE = FpFrameHeader::SIZE + HASH_DIGEST_LENGTH, - //! The offset of the start word in an F Prime protocol frame - START_WORD_OFFSET = 0, - //! The offset of the packet size in an F Prime protocol frame - PACKET_SIZE_OFFSET = START_WORD_OFFSET + - sizeof FpFrameHeader::START_WORD, - //! The offset of the packet type in an F Prime protocol frame - PACKET_TYPE_OFFSET = FpFrameHeader::SIZE, - }; - - //! The type of the input mode - struct InputMode { - typedef enum { - //! Push data from another thread - PUSH, - //! Poll for data on the schedIn thread - POLL - } t; - }; - - //! An uplink frame for testing - class UplinkFrame { - - // ---------------------------------------------------------------------- - // Types - // ---------------------------------------------------------------------- - - //! The type of frame data - typedef U8 FrameData[MAX_FRAME_SIZE]; - - public: - - // ---------------------------------------------------------------------- - // Constructor - // ---------------------------------------------------------------------- - - //! Construct an uplink frame - UplinkFrame( - Fw::ComPacket::ComPacketType packetType, //!< The packet type - U32 packetSize //!< The packet size - ); - - public: - - // ---------------------------------------------------------------------- - // Public instance methods - // ---------------------------------------------------------------------- - - //! Copy data from the frame, advancing the copy offset - void copyDataOut( - Fw::SerialBuffer& serialBuffer, //!< The serial buffer to copy to - U32 size //!< The number of bytes to copy - ); - - //! Get a constant reference to the frame data - const FrameData& getData() const; - - //! Get the frame size - U32 getSize() const; - - //! Get the size of data that remains for copying - U32 getRemainingCopySize() const; - - //! Report whether the frame is valid - bool isValid() const; - - public: - - // ---------------------------------------------------------------------- - // Public static methods - // ---------------------------------------------------------------------- - - //! Construct a random frame - static UplinkFrame random(); - - //! Get the max packet size that will fit in the test buffer - //! This is an invalid size for the deframer - static U32 getInvalidPacketSize(); - - //! Get the max valid command packet size - static U32 getMaxValidCommandPacketSize(); - - //! Get the max valid file packet size - static U32 getMaxValidFilePacketSize(); - - //! Get the min packet size - static U32 getMinPacketSize(); - - private: - - // ---------------------------------------------------------------------- - // Private instance methods - // ---------------------------------------------------------------------- - - //! Randomly invalidate a valid frame, or leave it alone - //! If the frame is already invalid, leave it alone - void randomlyInvalidate(); - - //! Update the frame header - void updateHeader(); - - //! Update the hash value - void updateHash(); - - //! Write an arbitrary packet size - void writePacketSize( - FpFrameHeader::TokenType ps //!< The packet size - ); - - //! Write an arbitrary packet type - void writePacketType( - FwPacketDescriptorType pt //!< The packet type - ); - - //! Write an arbitrary start word - void writeStartWord( - FpFrameHeader::TokenType sw //!< The start word - ); - - public: - - // ---------------------------------------------------------------------- - // Public member variables - // ---------------------------------------------------------------------- - - //! The packet type - const Fw::ComPacket::ComPacketType packetType; - - //! The packet size - const U32 packetSize; - - private: - - // ---------------------------------------------------------------------- - // Private member variables - // ---------------------------------------------------------------------- - - //! The frame data, including header, packet data, and hash. - //! The array is big enough to hold a frame larger than the - //! max valid frame size for the deframer. - U8 data[MAX_FRAME_SIZE]; - - //! The amount of frame data already copied out into a buffer - U32 copyOffset; - - //! Whether the frame is valid - bool valid; - - }; - - public: - - // ---------------------------------------------------------------------- - // Constructor - // ---------------------------------------------------------------------- - - //! Construct a DeframerTester - DeframerTester(InputMode::t inputMode); - - public: - - // ---------------------------------------------------------------------- - // Tests - // ---------------------------------------------------------------------- - - //! Size would cause integer overflow - void sizeOverflow(); - - public: - - // ---------------------------------------------------------------------- - // Public instance methods - // ---------------------------------------------------------------------- - - //! Set up the incoming buffer - void setUpIncomingBuffer(); - - //! Send the incoming buffer - void sendIncomingBuffer(); - - private: - - // ---------------------------------------------------------------------- - // Handlers for typed from ports - // ---------------------------------------------------------------------- - - //! Handler for from_comOut - void from_comOut_handler( - const NATIVE_INT_TYPE portNum, //!< The port number - Fw::ComBuffer& data, //!< Buffer containing packet data - U32 context //!< Call context value; meaning chosen by user - ); - - //! Handler for from_bufferOut - void from_bufferOut_handler( - const NATIVE_INT_TYPE portNum, //!< The port number - Fw::Buffer& fwBuffer //!< The buffer - ); - - //! Handler for from_bufferAllocate - Fw::Buffer from_bufferAllocate_handler( - const NATIVE_INT_TYPE portNum, //!< The port number - U32 size //!< The size - ); - - //! Handler for from_bufferDeallocate - void from_bufferDeallocate_handler( - const NATIVE_INT_TYPE portNum, //!< The port number - Fw::Buffer& fwBuffer //!< The buffer - ); - - //! Handler for from_framedDeallocate - //! - void from_framedDeallocate_handler( - const NATIVE_INT_TYPE portNum, //!< The port number - Fw::Buffer& fwBuffer //!< The buffer - ); - - //! Handler for from_framedPoll - Drv::PollStatus from_framedPoll_handler( - const NATIVE_INT_TYPE portNum, //!< The port number - Fw::Buffer& pollBuffer //!< The poll buffer - ); - - private: - - // ---------------------------------------------------------------------- - // Private helper methods - // ---------------------------------------------------------------------- - - //! Connect ports - void connectPorts(); - - //! Initialize components - void initComponents(); - - //! Allocate a packet buffer - Fw::Buffer allocatePacketBuffer( - U32 size //!< The buffer size - ); - - private: - - // ---------------------------------------------------------------------- - // Private member variables - // ---------------------------------------------------------------------- - - //! The component under test - Deframer component; - - //! The deframing protocol - Svc::FprimeDeframing protocol; - - //! Frames that the DeframerTester should send to the Deframer - std::deque m_framesToSend; - - //! Frames that the DeframerTester should receive from the Deframer - std::deque m_framesToReceive; - - //! Byte store for the incoming buffer - //! In polling mode, the incoming buffer must fit in the poll buffer - U8 m_incomingBufferBytes[DeframerCfg::POLL_BUFFER_SIZE]; - - //! Serialized frame data to send to the Deframer - Fw::Buffer m_incomingBuffer; - - //! The input mode - InputMode::t m_inputMode; - - }; - -} - -#endif diff --git a/Svc/Deframer/test/ut-fprime-protocol/GenerateFrames.cpp b/Svc/Deframer/test/ut-fprime-protocol/GenerateFrames.cpp deleted file mode 100644 index 76d9710ed8..0000000000 --- a/Svc/Deframer/test/ut-fprime-protocol/GenerateFrames.cpp +++ /dev/null @@ -1,40 +0,0 @@ -//! ====================================================================== -//! \title GenerateFrames.cpp -//! \brief Implementation file for GenerateFrames rule -//! \author mstarch, bocchino -//! ====================================================================== - -#include "GenerateFrames.hpp" -#include "Printing.hpp" -#include "STest/Pick/Pick.hpp" -#include "Utils/Hash/Hash.hpp" - -namespace Svc { - - GenerateFrames :: GenerateFrames() : - STest::Rule("GenerateFrames") - { - - } - - bool GenerateFrames :: precondition(const Svc::DeframerTester &state) { - return state.m_framesToSend.size() == 0; - } - - void GenerateFrames :: action(Svc::DeframerTester &state) { - PRINT("----------------------------------------------------------------------"); - PRINT("GenerateFrames action"); - PRINT("----------------------------------------------------------------------"); - // Generate 1-100 frames - const U32 numFrames = STest::Pick::lowerUpper(1, 100); - PRINT_ARGS("Generating %d frames", numFrames) - for (U32 i = 0; i < numFrames; i++) { - // Generate a random frame - DeframerTester::UplinkFrame frame = DeframerTester::UplinkFrame::random(); - // Push it on the sending list - state.m_framesToSend.push_back(frame); - } - PRINT_ARGS("frameToSend.size()=%lu", state.m_framesToSend.size()) - } - -} diff --git a/Svc/Deframer/test/ut-fprime-protocol/GenerateFrames.hpp b/Svc/Deframer/test/ut-fprime-protocol/GenerateFrames.hpp deleted file mode 100644 index 170a11a883..0000000000 --- a/Svc/Deframer/test/ut-fprime-protocol/GenerateFrames.hpp +++ /dev/null @@ -1,46 +0,0 @@ -//! ====================================================================== -//! \title DeframerRules.hpp -//! \brief Header file for GenerateFrames rule -//! \author lestarch, bocchino -//! ====================================================================== - -#ifndef SVC_GENERATE_FRAMES_HPP -#define SVC_GENERATE_FRAMES_HPP - -#include -#include "Fw/Types/StringType.hpp" -#include "STest/STest/Pick/Pick.hpp" -#include "STest/STest/Rule/Rule.hpp" -#include "DeframerTester.hpp" - -namespace Svc { - - //! Generate frames to send - struct GenerateFrames : public STest::Rule { - - // ---------------------------------------------------------------------- - // Construction - // ---------------------------------------------------------------------- - - //! Constructor - GenerateFrames(); - - // ---------------------------------------------------------------------- - // Public member functions - // ---------------------------------------------------------------------- - - //! Precondition - bool precondition( - const DeframerTester& state //!< The test state - ); - - //! Action - void action( - DeframerTester& state //!< The test state - ); - - }; - -} - -#endif diff --git a/Svc/Deframer/test/ut-fprime-protocol/Printing.hpp b/Svc/Deframer/test/ut-fprime-protocol/Printing.hpp deleted file mode 100644 index 7dd24e5039..0000000000 --- a/Svc/Deframer/test/ut-fprime-protocol/Printing.hpp +++ /dev/null @@ -1,18 +0,0 @@ -//! ====================================================================== -//! \title Printing.hpp -//! \brief Print macros for deframer unit tests -//! \author bocchino -//! ====================================================================== - -// Uncomment the following line to turn on printing -//#define PRINTING - -#ifdef PRINTING -#include - -#define PRINT(S) printf("[Deframer Tests] " S "\n"); -#define PRINT_ARGS(S, ...) printf("[Deframer Tests] " S "\n", __VA_ARGS__); -#else -#define PRINT(S) -#define PRINT_ARGS(S, ...) -#endif diff --git a/Svc/Deframer/test/ut-fprime-protocol/SendBuffer.cpp b/Svc/Deframer/test/ut-fprime-protocol/SendBuffer.cpp deleted file mode 100644 index 4fc2b6ee50..0000000000 --- a/Svc/Deframer/test/ut-fprime-protocol/SendBuffer.cpp +++ /dev/null @@ -1,158 +0,0 @@ -//! ====================================================================== -//! \title SendBuffer.cpp -//! \brief Implementation file for SendBuffer rule -//! \author mstarch, bocchino -//! ====================================================================== - -#include "Printing.hpp" -#include "STest/Pick/Pick.hpp" -#include "SendBuffer.hpp" -#include "Utils/Hash/Hash.hpp" - -namespace Svc { - - SendBuffer :: SendBuffer() : - STest::Rule("SendBuffer"), - expectedComCount(0), - expectedBuffCount(0) - { - - } - - bool SendBuffer :: precondition(const Svc::DeframerTester &state) { - return state.m_framesToSend.size() > 0; - } - - void SendBuffer :: action(Svc::DeframerTester &state) { - - PRINT("----------------------------------------------------------------------"); - PRINT("SendBuffer action"); - PRINT("----------------------------------------------------------------------"); - - // Clear the test history - state.clearHistory(); - - // Set up the incoming buffer - state.setUpIncomingBuffer(); - - // Fill the incoming buffer with frame data - fillIncomingBuffer(state); - - // Send the buffer - state.sendIncomingBuffer(); - - // Check the counts - state.assert_from_comOut_size(__FILE__, __LINE__, expectedComCount); - PRINT_ARGS("expectedComCount=%d", expectedComCount) - state.assert_from_bufferOut_size(__FILE__, __LINE__, expectedBuffCount); - PRINT_ARGS("expectedBuffCount=%d", expectedBuffCount) - - } - - void SendBuffer :: fillIncomingBuffer(Svc::DeframerTester &state) { - - // Get the size of the incoming buffer - const U32 incomingBufferSize = state.m_incomingBuffer.getSize(); - - // Set up a serial buffer for data transfer - Fw::SerialBuffer serialBuffer( - state.m_incomingBufferBytes, - incomingBufferSize - ); - - // Reset the expected com count - expectedComCount = 0; - // Reset the expected buff count - expectedBuffCount = 0; - - // The number of bytes copied into the buffer - U32 copiedSize = 0; - // The size of available data in the buffer - U32 buffAvailable = incomingBufferSize; - - // Fill the incoming buffer as much as possible with available frames - for (U32 i = 0; i < incomingBufferSize; ++i) { - - // Check if there is any room left in the buffer - if (buffAvailable == 0) { - break; - } - - // Check if there are any frames to send - if (state.m_framesToSend.size() == 0) { - break; - } - - // Get the frame from the head of the sending queue - DeframerTester::UplinkFrame& frame = state.m_framesToSend.front(); - - // Compute the amount to copy - const U32 frameAvailable = frame.getRemainingCopySize(); - const U32 copyAmt = std::min(frameAvailable, buffAvailable); - - // Copy the frame data into the serial buffer - frame.copyDataOut(serialBuffer, copyAmt); - - // Update buffAvailable - ASSERT_GE(buffAvailable, copyAmt); - buffAvailable -= copyAmt; - - // Update copiedSize - copiedSize += copyAmt; - ASSERT_LE(copiedSize, incomingBufferSize); - - // If we have copied a complete frame F, then (1) remove F from - // the send queue; and (2) if F is valid, then record F as - // received - if (frame.getRemainingCopySize() == 0) { - // If F is valid, then record it as received - recordReceivedFrame(state, frame); - // Remove F from the send queue - state.m_framesToSend.pop_front(); - PRINT_ARGS( - "frameToSend.size()=%lu", - state.m_framesToSend.size() - ) - } - - } - - // Update the buffer - ASSERT_LE(copiedSize, incomingBufferSize); - state.m_incomingBuffer.setSize(copiedSize); - - } - - void SendBuffer :: recordReceivedFrame( - Svc::DeframerTester& state, - Svc::DeframerTester::UplinkFrame& frame - ) { - if (frame.isValid()) { - // Push frame F on the received queue - state.m_framesToReceive.push_back(frame); - // Update the count of expected frames - switch (frame.packetType) { - case Fw::ComPacket::FW_PACKET_COMMAND: - PRINT("popped valid command frame") - // If F contains a command packet, then increment - // the expected com count - ++expectedComCount; - break; - case Fw::ComPacket::FW_PACKET_FILE: - PRINT("popped valid file frame") - // If F contains a file packet, then increment - // the expected buffer count - ++expectedBuffCount; - break; - default: - // This should not happen for a valid frame - FW_ASSERT(0, frame.packetType); - break; - } - } - else { - PRINT("popped invalid frame") - } - } - -}; diff --git a/Svc/Deframer/test/ut-fprime-protocol/SendBuffer.hpp b/Svc/Deframer/test/ut-fprime-protocol/SendBuffer.hpp deleted file mode 100644 index fc91d89364..0000000000 --- a/Svc/Deframer/test/ut-fprime-protocol/SendBuffer.hpp +++ /dev/null @@ -1,80 +0,0 @@ -//! ====================================================================== -//! \title SendBuffer.hpp -//! \brief Header file for SendBuffer rule -//! \author lestarch, bocchino -//! ====================================================================== - -#ifndef SVC_SEND_BUFFER_HPP -#define SVC_SEND_BUFFER_HPP - -#include -#include "Fw/Types/StringType.hpp" -#include "STest/STest/Pick/Pick.hpp" -#include "STest/STest/Rule/Rule.hpp" -#include "DeframerTester.hpp" - -namespace Svc { - - //! Pack generated frames into a buffer - //! Send the buffer - class SendBuffer : public STest::Rule { - - public: - - // ---------------------------------------------------------------------- - // Construction - // ---------------------------------------------------------------------- - - //! Constructor - SendBuffer(); - - public: - - // ---------------------------------------------------------------------- - // Public functions - // ---------------------------------------------------------------------- - - //! Precondition - bool precondition( - const DeframerTester& state //!< The test state - ); - - //! Action - void action( - Svc::DeframerTester &state //!< The test state - ); - - private: - - // ---------------------------------------------------------------------- - // Private helper functions - // ---------------------------------------------------------------------- - - //! Fill the incoming buffer with frame data - void fillIncomingBuffer( - Svc::DeframerTester &state //!< The test state - ); - - //! Record a received frame - void recordReceivedFrame( - Svc::DeframerTester& state, //!< The test state - Svc::DeframerTester::UplinkFrame& frame //!< The frame - ); - - private: - - // ---------------------------------------------------------------------- - // Private member variables - // ---------------------------------------------------------------------- - - //! The expected number of com buffers emitted - U32 expectedComCount; - - //! The expected number of file packet buffers emitted - U32 expectedBuffCount; - - }; - -}; - -#endif diff --git a/Svc/Deframer/test/ut-fprime-protocol/UplinkFrame.cpp b/Svc/Deframer/test/ut-fprime-protocol/UplinkFrame.cpp deleted file mode 100644 index 8110ea42a5..0000000000 --- a/Svc/Deframer/test/ut-fprime-protocol/UplinkFrame.cpp +++ /dev/null @@ -1,259 +0,0 @@ -// ====================================================================== -// \title UplinkFrame.cpp -// \author mstarch, bocchino -// \brief Implementation file for DeframerTester::UplinkFrame -// -// \copyright -// Copyright 2009-2022, by the California Institute of Technology. -// ALL RIGHTS RESERVED. United States Government Sponsorship -// acknowledged. -// ====================================================================== - -#include "DeframerTester.hpp" - -namespace Svc { - - // ---------------------------------------------------------------------- - // Constructor - // ---------------------------------------------------------------------- - - DeframerTester::UplinkFrame::UplinkFrame( - Fw::ComPacket::ComPacketType packetType, - U32 packetSize - ) : - packetType(packetType), - packetSize(packetSize), - copyOffset(0), - valid(false) - { - // Fill in random data - for (U32 i = 0; i < sizeof data; ++i) { - data[i] = STest::Pick::lowerUpper(0, 0xFF); - } - // Update the frame header - this->updateHeader(); - // Update the hash value - this->updateHash(); - // Check validity of packet type and size - if ( - (packetType == Fw::ComPacket::FW_PACKET_COMMAND) && - (packetSize <= getMaxValidCommandPacketSize()) - ) { - valid = true; - } - if ( - (packetType == Fw::ComPacket::FW_PACKET_FILE) && - (packetSize <= getMaxValidFilePacketSize()) - ) { - valid = true; - } - } - - // ---------------------------------------------------------------------- - // Public instance methods - // ---------------------------------------------------------------------- - - U32 DeframerTester::UplinkFrame::getRemainingCopySize() const { - const U32 frameSize = getSize(); - FW_ASSERT(frameSize >= copyOffset, frameSize, copyOffset); - return frameSize - copyOffset; - } - - U32 DeframerTester::UplinkFrame::getSize() const { - return NON_PACKET_SIZE + packetSize; - } - - bool DeframerTester::UplinkFrame::isValid() const { - return valid; - } - - const DeframerTester::UplinkFrame::FrameData& - DeframerTester::UplinkFrame::getData() const - { - return data; - } - - void DeframerTester::UplinkFrame::copyDataOut( - Fw::SerialBuffer& serialBuffer, - U32 size - ) { - ASSERT_LE(copyOffset + size, getSize()); - const Fw::SerializeStatus status = serialBuffer.pushBytes( - &data[copyOffset], - size - ); - ASSERT_EQ(status, Fw::FW_SERIALIZE_OK); - copyOffset += size; - } - - // ---------------------------------------------------------------------- - // Public static methods - // ---------------------------------------------------------------------- - - DeframerTester::UplinkFrame DeframerTester::UplinkFrame::random() { - // Randomly set the packet type - Fw::ComPacket::ComPacketType packetType = - Fw::ComPacket::FW_PACKET_UNKNOWN; - const U32 packetSelector = STest::Pick::lowerUpper(0,1); - U32 maxValidPacketSize = 0; - switch (packetSelector) { - case 0: - packetType = Fw::ComPacket::FW_PACKET_COMMAND; - maxValidPacketSize = getMaxValidCommandPacketSize(); - break; - case 1: - packetType = Fw::ComPacket::FW_PACKET_FILE; - maxValidPacketSize = getMaxValidFilePacketSize(); - break; - default: - FW_ASSERT(0); - break; - } - // Randomly set the packet size - U32 packetSize = 0; - // Invalidate 1 in 100 packet sizes - const U32 invalidSizeIndex = STest::Pick::startLength(0, 100); - if (invalidSizeIndex == 0) { - // This packet size fits in the test buffer, - // but is invalid for the deframer - packetSize = getInvalidPacketSize(); - } - else { - // Choose a valid packet size - // This packet size fits in the test buffer and is - // valid for the deframer - packetSize = STest::Pick::lowerUpper( - getMinPacketSize(), - maxValidPacketSize - ); - } - // Construct the frame - UplinkFrame frame = UplinkFrame(packetType, packetSize); - // Randomly invalidate the frame, or leave it alone - frame.randomlyInvalidate(); - // Return the frame - return frame; - } - - U32 DeframerTester::UplinkFrame::getInvalidPacketSize() { - FW_ASSERT( - MAX_FRAME_SIZE >= NON_PACKET_SIZE, - MAX_FRAME_SIZE, - NON_PACKET_SIZE - ); - const U32 result = MAX_FRAME_SIZE - NON_PACKET_SIZE; - // Make sure the size is invalid - FW_ASSERT( - result > getMaxValidCommandPacketSize(), - result, - getMaxValidCommandPacketSize() - ); - FW_ASSERT( - result > getMaxValidFilePacketSize(), - result, - getMaxValidFilePacketSize() - ); - return result; - } - - U32 DeframerTester::UplinkFrame::getMaxValidCommandPacketSize() { - return std::min( - // Packet must fit into a com buffer - static_cast(FW_COM_BUFFER_MAX_SIZE), - // Frame must fit into the ring buffer - getMaxValidFilePacketSize() - ); - } - - U32 DeframerTester::UplinkFrame::getMaxValidFilePacketSize() { - FW_ASSERT( - MAX_VALID_FRAME_SIZE >= NON_PACKET_SIZE, - MAX_VALID_FRAME_SIZE, - NON_PACKET_SIZE - ); - return MAX_VALID_FRAME_SIZE - NON_PACKET_SIZE; - } - - U32 DeframerTester::UplinkFrame::getMinPacketSize() { - // Packet must hold the packet type - return sizeof(FwPacketDescriptorType); - } - - // ---------------------------------------------------------------------- - // Private instance methods - // ---------------------------------------------------------------------- - - void DeframerTester::UplinkFrame::randomlyInvalidate() { - if (valid) { - // Invalidation cases occur out of 100 samples - const U32 invalidateIndex = STest::Pick::startLength(0, 100); - switch (invalidateIndex) { - case 0: { - // Invalidate the start word - const FpFrameHeader::TokenType badStartWord = - FpFrameHeader::START_WORD + 1; - writeStartWord(badStartWord); - valid = false; - break; - } - case 1: - // Invalidate the packet type - writePacketType(Fw::ComPacket::FW_PACKET_UNKNOWN); - valid = false; - break; - case 2: - // Invalidate the hash value - ++data[getSize() - 1]; - valid = false; - break; - default: - // Stay valid - break; - } - } - } - - void DeframerTester::UplinkFrame::updateHash() { - Utils::Hash hash; - Utils::HashBuffer hashBuffer; - const U32 dataSize = FpFrameHeader::SIZE + packetSize; - hash.update(data, dataSize); - hash.final(hashBuffer); - const U8 *const hashAddr = hashBuffer.getBuffAddr(); - memcpy(&data[dataSize], hashAddr, HASH_DIGEST_LENGTH); - } - - void DeframerTester::UplinkFrame::updateHeader() { - // Write the correct start word - writeStartWord(FpFrameHeader::START_WORD); - // Write the correct packet size - writePacketSize(packetSize); - // Write the correct packet type - writePacketType(packetType); - } - - void DeframerTester::UplinkFrame::writePacketSize( - FpFrameHeader::TokenType ps - ) { - Fw::SerialBuffer sb(&data[PACKET_SIZE_OFFSET], sizeof ps); - const Fw::SerializeStatus status = sb.serialize(packetSize); - ASSERT_EQ(status, Fw::FW_SERIALIZE_OK); - } - - void DeframerTester::UplinkFrame::writePacketType( - FwPacketDescriptorType pt - ) { - Fw::SerialBuffer sb(&data[PACKET_TYPE_OFFSET], sizeof pt); - const Fw::SerializeStatus status = sb.serialize(pt); - ASSERT_EQ(status, Fw::FW_SERIALIZE_OK); - } - - void DeframerTester::UplinkFrame::writeStartWord( - FpFrameHeader::TokenType sw - ) { - Fw::SerialBuffer sb(data, sizeof sw); - const Fw::SerializeStatus status = sb.serialize(sw); - ASSERT_EQ(status, Fw::FW_SERIALIZE_OK); - } - -} diff --git a/Svc/Deframer/test/ut/DeframerTestMain.cpp b/Svc/Deframer/test/ut/DeframerTestMain.cpp index 26ebf1a619..de6c39f8f5 100644 --- a/Svc/Deframer/test/ut/DeframerTestMain.cpp +++ b/Svc/Deframer/test/ut/DeframerTestMain.cpp @@ -1,107 +1,29 @@ -// ---------------------------------------------------------------------- -// TestMain.cpp -// ---------------------------------------------------------------------- +// ====================================================================== +// \title DeframerTestMain.cpp +// \author thomas-bc +// \brief cpp file for Deframer component test main function +// ====================================================================== #include "DeframerTester.hpp" -#include -#include +#include "STest/Random/Random.hpp" -TEST(Deframer, TestMoreNeeded) { - COMMENT("Send a frame buffer to the mock protocol, expecting more needed"); - REQUIREMENT("SVC-DEFRAMER-001"); - REQUIREMENT("SVC-DEFRAMER-002"); - REQUIREMENT("SVC-DEFRAMER-003"); - REQUIREMENT("SVC-DEFRAMER-007"); +TEST(Deframer, NominalFrame) { Svc::DeframerTester tester; - tester.test_incoming_frame(Svc::DeframingProtocol::DEFRAMING_MORE_NEEDED); + tester.testNominalFrame(); } -TEST(Deframer, TestSuccess) { - COMMENT("Send a frame buffer to the mock protocol, expecting success"); - REQUIREMENT("SVC-DEFRAMER-001"); - REQUIREMENT("SVC-DEFRAMER-002"); - REQUIREMENT("SVC-DEFRAMER-003"); - REQUIREMENT("SVC-DEFRAMER-007"); - Svc::DeframerTester tester; - tester.test_incoming_frame(Svc::DeframingProtocol::DEFRAMING_STATUS_SUCCESS); -} -TEST(Deframer, TestBadChecksum) { - COMMENT("Send a frame buffer to the mock protocol, expecting bad checksum"); - REQUIREMENT("SVC-DEFRAMER-001"); - REQUIREMENT("SVC-DEFRAMER-002"); - REQUIREMENT("SVC-DEFRAMER-003"); - REQUIREMENT("SVC-DEFRAMER-007"); - Svc::DeframerTester tester; - tester.test_incoming_frame(Svc::DeframingProtocol::DEFRAMING_INVALID_CHECKSUM); -} -TEST(Deframer, TestBadSize) { - COMMENT("Send a frame buffer to the mock protocol, expecting bad size"); - REQUIREMENT("SVC-DEFRAMER-001"); - REQUIREMENT("SVC-DEFRAMER-002"); - REQUIREMENT("SVC-DEFRAMER-003"); - REQUIREMENT("SVC-DEFRAMER-007"); - Svc::DeframerTester tester; - tester.test_incoming_frame(Svc::DeframingProtocol::DEFRAMING_INVALID_SIZE); -} -TEST(Deframer, TestBadFormat) { - COMMENT("Send a frame buffer to the mock protocol, expecting bad format"); - REQUIREMENT("SVC-DEFRAMER-001"); - REQUIREMENT("SVC-DEFRAMER-002"); - REQUIREMENT("SVC-DEFRAMER-003"); - REQUIREMENT("SVC-DEFRAMER-007"); - Svc::DeframerTester tester; - tester.test_incoming_frame(Svc::DeframingProtocol::DEFRAMING_INVALID_FORMAT); -} -TEST(Deframer, TestComInterface) { - COMMENT("Route a com packet"); - REQUIREMENT("SVC-DEFRAMER-008"); - REQUIREMENT("SVC-DEFRAMER-009"); - REQUIREMENT("SVC-DEFRAMER-010"); - Svc::DeframerTester tester; - tester.test_com_interface(); -} -TEST(Deframer, TestFileInterface) { - COMMENT("Route a file packet"); - REQUIREMENT("SVC-DEFRAMER-008"); - REQUIREMENT("SVC-DEFRAMER-009"); - REQUIREMENT("SVC-DEFRAMER-010"); - Svc::DeframerTester tester; - tester.test_file_interface(); -} -TEST(Deframer, TestUnknownInterface) { - COMMENT("Attempt to route a packet of unknown type"); - REQUIREMENT("SVC-DEFRAMER-008"); - REQUIREMENT("SVC-DEFRAMER-009"); - Svc::DeframerTester tester; - tester.test_unknown_interface(); -} -TEST(Deframer, TestCommandResponse) { - COMMENT("Handle a command response (no-op)"); - Svc::DeframerTester tester; - tester.testCommandResponse(); -} -TEST(Deframer, TestCommandPacketTooLarge) { - COMMENT("Attempt to route a command packet that is too large"); - REQUIREMENT("SVC-DEFRAMER-008"); - REQUIREMENT("SVC-DEFRAMER-009"); - Svc::DeframerTester tester; - tester.testCommandPacketTooLarge(); -} -TEST(Deframer, TestPacketBufferTooSmall) { - COMMENT("Attempt to route a packet that is too small"); - REQUIREMENT("SVC-DEFRAMER-008"); - REQUIREMENT("SVC-DEFRAMER-009"); + +TEST(Deframer, TruncatedFrame) { Svc::DeframerTester tester; - tester.testPacketBufferTooSmall(); + tester.testTruncatedFrame(); } -TEST(Deframer, TestBufferOutUnconnected) { - COMMENT("Test routing a file packet when bufferOut is unconnected"); - REQUIREMENT("SVC-DEFRAMER-011"); - Svc::DeframerTester tester(Svc::DeframerTester::ConnectStatus::UNCONNECTED); - tester.testBufferOutUnconnected(); +TEST(Deframer, ZeroSizeFrame) { + Svc::DeframerTester tester; + tester.testZeroSizeFrame(); } -int main(int argc, char **argv) { +int main(int argc, char** argv) { + STest::Random::seed(); ::testing::InitGoogleTest(&argc, argv); return RUN_ALL_TESTS(); } diff --git a/Svc/Deframer/test/ut/DeframerTester.cpp b/Svc/Deframer/test/ut/DeframerTester.cpp index bfcf575381..3783fc2d79 100644 --- a/Svc/Deframer/test/ut/DeframerTester.cpp +++ b/Svc/Deframer/test/ut/DeframerTester.cpp @@ -1,55 +1,22 @@ // ====================================================================== -// \title Deframer.hpp -// \author janamian, bocchino -// \brief cpp file for Deframer test harness implementation class -// -// \copyright -// Copyright 2009-2021, by the California Institute of Technology. -// ALL RIGHTS RESERVED. United States Government Sponsorship -// acknowledged. -// +// \title DeframerTester.cpp +// \author thomas-bc +// \brief cpp file for Deframer component test harness implementation class // ====================================================================== -#include - #include "DeframerTester.hpp" - -#define INSTANCE 0 -#define MAX_HISTORY_SIZE 1000 +#include "STest/Random/Random.hpp" namespace Svc { // ---------------------------------------------------------------------- // Construction and destruction // ---------------------------------------------------------------------- -DeframerTester::MockDeframer::MockDeframer(DeframerTester& parent) : m_status(DeframingProtocol::DEFRAMING_STATUS_SUCCESS) {} - -DeframerTester::MockDeframer::DeframingStatus DeframerTester::MockDeframer::deframe(Types::CircularBuffer& ring_buffer, U32& needed) { - needed = ring_buffer.get_allocated_size(); - if (m_status == DeframingProtocol::DEFRAMING_MORE_NEEDED) { - needed = ring_buffer.get_allocated_size() + 1; // Obey the rules - } - return m_status; -} - -void DeframerTester::MockDeframer::test_interface(Fw::ComPacket::ComPacketType com_packet_type) { - const FwPacketDescriptorType descriptorType = com_packet_type; - U8 chars[sizeof descriptorType]; - m_interface->allocate(3042); - Fw::Buffer buffer(chars, sizeof(chars)); - buffer.getSerializeRepr().serialize(descriptorType); - m_interface->route(buffer); -} - -DeframerTester ::DeframerTester(ConnectStatus::t bufferOutStatus) - : DeframerGTestBase("Tester", MAX_HISTORY_SIZE), - component("Deframer"), - m_mock(*this), - bufferOutStatus(bufferOutStatus) { +DeframerTester ::DeframerTester() + : DeframerGTestBase("DeframerTester", DeframerTester::MAX_HISTORY_SIZE), component("Deframer") { this->initComponents(); this->connectPorts(); - component.setup(this->m_mock); } DeframerTester ::~DeframerTester() {} @@ -57,182 +24,46 @@ DeframerTester ::~DeframerTester() {} // ---------------------------------------------------------------------- // Tests // ---------------------------------------------------------------------- -void DeframerTester ::test_incoming_frame(DeframerTester::MockDeframer::DeframingStatus status) { - U32 buffer_size = 512; - U8 data[buffer_size]; - ::memset(data, 0, buffer_size); - Fw::Buffer recvBuffer(data, buffer_size); - m_mock.m_status = status; - Drv::RecvStatus recvStatus = Drv::RecvStatus::RECV_OK; - invoke_to_framedIn(0, recvBuffer, recvStatus); - // Check remaining size - if (status == DeframingProtocol::DEFRAMING_MORE_NEEDED) { - ASSERT_EQ(component.m_inRing.get_allocated_size(), buffer_size); - } else if (status == DeframingProtocol::DEFRAMING_STATUS_SUCCESS) { - ASSERT_EQ(component.m_inRing.get_allocated_size(), 0); - } else { - ASSERT_EQ(component.m_inRing.get_allocated_size(), 0); - } - ASSERT_from_framedDeallocate(0, recvBuffer); -} - -void DeframerTester ::test_com_interface() { - m_mock.test_interface(Fw::ComPacket::FW_PACKET_COMMAND); - ASSERT_from_comOut_SIZE(1); - ASSERT_from_bufferAllocate(0, 3042); - ASSERT_from_bufferOut_SIZE(0); - ASSERT_from_bufferDeallocate_SIZE(1); -} - -void DeframerTester ::test_file_interface() { - m_mock.test_interface(Fw::ComPacket::FW_PACKET_FILE); - ASSERT_from_comOut_SIZE(0); - ASSERT_from_bufferAllocate(0, 3042); - ASSERT_from_bufferOut_SIZE(1); - ASSERT_from_bufferDeallocate_SIZE(0); -} - -void DeframerTester ::test_unknown_interface() { - m_mock.test_interface(Fw::ComPacket::FW_PACKET_UNKNOWN); - ASSERT_from_comOut_SIZE(0); - ASSERT_from_bufferAllocate(0, 3042); - ASSERT_from_bufferOut_SIZE(0); - ASSERT_from_bufferDeallocate_SIZE(1); -} - -void DeframerTester ::testCommandResponse() { - const U32 portNum = 0; - const U32 opcode = 0; - const U32 cmdSeq = 0; - const Fw::CmdResponse cmdResp(Fw::CmdResponse::OK); - this->invoke_to_cmdResponseIn(portNum, opcode, cmdSeq, cmdResp); - ASSERT_FROM_PORT_HISTORY_SIZE(0); -} - -void DeframerTester ::testCommandPacketTooLarge() { - // Allocate a large packet buffer - enum { - BUFFER_SIZE = 2 * FW_COM_BUFFER_MAX_SIZE - }; - U8 bytes[BUFFER_SIZE]; - ::memset(bytes, 0, sizeof bytes); - Fw::Buffer buffer(bytes, sizeof bytes); - // Serialize the packet type - Fw::SerializeBufferBase& serialRepr = buffer.getSerializeRepr(); - const FwPacketDescriptorType descriptorType = - Fw::ComPacket::FW_PACKET_COMMAND; - const Fw::SerializeStatus status = - serialRepr.serialize(descriptorType); - ASSERT_EQ(status, Fw::FW_SERIALIZE_OK); - // Call the route method - this->component.route(buffer); - // Assert buffer deallocated, no packet output - ASSERT_FROM_PORT_HISTORY_SIZE(1); - ASSERT_from_bufferDeallocate_SIZE(1); -} - -void DeframerTester ::testPacketBufferTooSmall() { - // Allocate a small packet buffer - U8 byte = 0; - Fw::Buffer buffer(&byte, sizeof byte); - // Call the route method - this->component.route(buffer); - // Assert buffer deallocated, no packet output - ASSERT_FROM_PORT_HISTORY_SIZE(1); - ASSERT_from_bufferDeallocate_SIZE(1); -} - -void DeframerTester ::testBufferOutUnconnected() { - ASSERT_EQ(this->bufferOutStatus, ConnectStatus::UNCONNECTED); - enum { - BUFFER_SIZE = 256 - }; - U8 bytes[BUFFER_SIZE]; - ::memset(bytes, 0, sizeof bytes); - Fw::Buffer buffer(bytes, sizeof bytes); - // Serialize the packet type - Fw::SerializeBufferBase& serialRepr = buffer.getSerializeRepr(); - const FwPacketDescriptorType descriptorType = - Fw::ComPacket::FW_PACKET_FILE; - const Fw::SerializeStatus status = - serialRepr.serialize(descriptorType); - ASSERT_EQ(status, Fw::FW_SERIALIZE_OK); - // Call the route method - this->component.route(buffer); - // Assert buffer deallocated, no packet output - ASSERT_FROM_PORT_HISTORY_SIZE(1); - ASSERT_from_bufferDeallocate_SIZE(1); -} - -// ---------------------------------------------------------------------- -// Handlers for typed from ports -// ---------------------------------------------------------------------- -void DeframerTester ::from_comOut_handler(const NATIVE_INT_TYPE portNum, Fw::ComBuffer& data, U32 context) { - this->pushFromPortEntry_comOut(data, context); -} - -void DeframerTester ::from_bufferOut_handler(const NATIVE_INT_TYPE portNum, Fw::Buffer& fwBuffer) { - this->pushFromPortEntry_bufferOut(fwBuffer); -} - -Fw::Buffer DeframerTester ::from_bufferAllocate_handler(const NATIVE_INT_TYPE portNum, U32 size) { - this->pushFromPortEntry_bufferAllocate(size); - Fw::Buffer buffer(nullptr, size); - return buffer; -} +void DeframerTester ::testNominalFrame() { + // TODO: make this test multiple times with different random bytes and lengths + // Get random byte of data + U8 randomByte = STest::Random::lowerUpper(0, 255); + // Note: the content of the frame header/footer doesn't actually matter in these tests + // | F´ start word | Length (= 1) | Data | Checksum (4 bytes) | + U8 data[13] = {0xDE, 0xAD, 0xBE, 0xEF, 0x00, 0x00, 0x00, 0x01, randomByte, 0x00, 0x00, 0x00, 0x00}; + this->mockReceiveData(data, sizeof(data)); -void DeframerTester ::from_bufferDeallocate_handler(const NATIVE_INT_TYPE portNum, Fw::Buffer& fwBuffer) { - this->pushFromPortEntry_bufferDeallocate(fwBuffer); + // Assert that something was emitted on the deframedOut port + ASSERT_from_deframedOut_SIZE(1); + // Assert that the data that was emitted on deframedOut is equal to Data field above (randomByte) + ASSERT_EQ(this->fromPortHistory_deframedOut->at(0).data.getData()[0], randomByte); } -void DeframerTester ::from_framedDeallocate_handler(const NATIVE_INT_TYPE portNum, Fw::Buffer& fwBuffer) { - this->pushFromPortEntry_framedDeallocate(fwBuffer); +void DeframerTester::testTruncatedFrame() { + // Send a truncated frame, too short to be valid + U8 data[11] = {0xDE, 0xAD, 0xBE, 0xEF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; + ASSERT_DEATH(this->mockReceiveData(data, sizeof(data)), "Deframer.cpp"); + ASSERT_from_deframedOut_SIZE(0); } -Drv::PollStatus DeframerTester ::from_framedPoll_handler(const NATIVE_INT_TYPE portNum, Fw::Buffer& pollBuffer) { - this->pushFromPortEntry_framedPoll(pollBuffer); - return Drv::PollStatus::POLL_OK; +void DeframerTester::testZeroSizeFrame() { + // Send a null frame, too short to be valid + U8 data[0] = {}; + ASSERT_DEATH(this->mockReceiveData(data, sizeof(data)), "Deframer.cpp"); + ASSERT_from_deframedOut_SIZE(0); } // ---------------------------------------------------------------------- -// Helper methods +// Test Helpers // ---------------------------------------------------------------------- -void DeframerTester ::connectPorts() { - // bufferAllocate - this->component.set_bufferAllocate_OutputPort(0, this->get_from_bufferAllocate(0)); - - // bufferDeallocate - this->component.set_bufferDeallocate_OutputPort(0, this->get_from_bufferDeallocate(0)); - - // bufferOut - if (this->bufferOutStatus == ConnectStatus::CONNECTED) { - this->component.set_bufferOut_OutputPort(0, this->get_from_bufferOut(0)); - } - - // cmdResponseIn - this->connect_to_cmdResponseIn(0, this->component.get_cmdResponseIn_InputPort(0)); - - // comOut - this->component.set_comOut_OutputPort(0, this->get_from_comOut(0)); - - // framedDeallocate - this->component.set_framedDeallocate_OutputPort(0, this->get_from_framedDeallocate(0)); - - // framedIn - this->connect_to_framedIn(0, this->component.get_framedIn_InputPort(0)); - - // framedPoll - this->component.set_framedPoll_OutputPort(0, this->get_from_framedPoll(0)); - - // schedIn - this->connect_to_schedIn(0, this->component.get_schedIn_InputPort(0)); -} - -void DeframerTester ::initComponents() { - this->init(); - this->component.init(INSTANCE); +void DeframerTester::mockReceiveData(U8* data, FwSizeType size) { + Fw::Buffer nullContext; + Fw::Buffer buffer; + buffer.setData(data); + buffer.setSize(size); + this->invoke_to_framedIn(0, buffer, nullContext); } -} // end namespace Svc +} // namespace Svc diff --git a/Svc/Deframer/test/ut/DeframerTester.hpp b/Svc/Deframer/test/ut/DeframerTester.hpp index 1031ea2672..b08099ef21 100644 --- a/Svc/Deframer/test/ut/DeframerTester.hpp +++ b/Svc/Deframer/test/ut/DeframerTester.hpp @@ -1,57 +1,38 @@ // ====================================================================== -// \title Deframer/test/ut/Tester.hpp -// \author janamian, bocchino -// \brief hpp file for Deframer test harness implementation class -// -// \copyright -// Copyright 2009-2021, by the California Institute of Technology. -// ALL RIGHTS RESERVED. United States Government Sponsorship -// acknowledged. -// +// \title DeframerTester.hpp +// \author thomas-bc +// \brief hpp file for Deframer component test harness implementation class // ====================================================================== -#ifndef TESTER_HPP -#define TESTER_HPP +#ifndef Svc_DeframerTester_HPP +#define Svc_DeframerTester_HPP -#include "DeframerGTestBase.hpp" #include "Svc/Deframer/Deframer.hpp" +#include "Svc/Deframer/DeframerGTestBase.hpp" namespace Svc { class DeframerTester : public DeframerGTestBase { public: // ---------------------------------------------------------------------- - // Types + // Constants // ---------------------------------------------------------------------- - struct ConnectStatus { - //! Whether a port is connected - typedef enum { - CONNECTED, - UNCONNECTED - } t; - }; + // Maximum size of histories storing events, telemetry, and port outputs + static const FwSizeType MAX_HISTORY_SIZE = 10; + // Instance ID supplied to the component instance under test + static const FwEnumStoreType TEST_INSTANCE_ID = 0; + + public: // ---------------------------------------------------------------------- // Construction and destruction // ---------------------------------------------------------------------- - class MockDeframer : public DeframingProtocol { - public: - MockDeframer(DeframerTester& parent); - DeframingStatus deframe(Types::CircularBuffer& ring_buffer, U32& needed); - //! Test the implementation of DeframingProtocolInterface provided - //! by the Deframer component - void test_interface(Fw::ComPacket::ComPacketType com_type); - DeframingStatus m_status; - }; - public: //! Construct object DeframerTester - //! - DeframerTester(ConnectStatus::t bufferOutStatus = ConnectStatus::CONNECTED); + DeframerTester(); //! Destroy object DeframerTester - //! ~DeframerTester(); public: @@ -59,97 +40,39 @@ class DeframerTester : public DeframerGTestBase { // Tests // ---------------------------------------------------------------------- - //! Send a frame to framedIn - void test_incoming_frame( - DeframingProtocol::DeframingStatus status //!< The status that the mock deframer will return - ); - - //! Route a com packet - void test_com_interface(); - - //! Route a file packet - void test_file_interface(); - - //! Route a packet of unknown type - void test_unknown_interface(); - - //! Invoke the command response input port - void testCommandResponse(); - - //! Attempt to route a command packet that is too large - void testCommandPacketTooLarge(); + //! Test receiving a nominal frame + void testNominalFrame(); - //! Attempt to route a packet buffer that is too small - void testPacketBufferTooSmall(); + //! Test receiving a truncated frame + void testTruncatedFrame(); - //! Route a file packet with bufferOutUnconnected - void testBufferOutUnconnected(); + //! Test receiving a zero size frame + void testZeroSizeFrame(); private: // ---------------------------------------------------------------------- - // Handlers for typed from ports - // ---------------------------------------------------------------------- - - //! Handler for from_comOut - //! - void from_comOut_handler(const NATIVE_INT_TYPE portNum, /*!< The port number*/ - Fw::ComBuffer& data, /*!< Buffer containing packet data*/ - U32 context /*!< Call context value; meaning chosen by user*/ - ); - - //! Handler for from_bufferOut - //! - void from_bufferOut_handler(const NATIVE_INT_TYPE portNum, /*!< The port number*/ - Fw::Buffer& fwBuffer); - - //! Handler for from_bufferAllocate - //! - Fw::Buffer from_bufferAllocate_handler(const NATIVE_INT_TYPE portNum, /*!< The port number*/ - U32 size); - - //! Handler for from_bufferDeallocate - //! - void from_bufferDeallocate_handler(const NATIVE_INT_TYPE portNum, /*!< The port number*/ - Fw::Buffer& fwBuffer); - - //! Handler for from_framedDeallocate - //! - void from_framedDeallocate_handler(const NATIVE_INT_TYPE portNum, /*!< The port number*/ - Fw::Buffer& fwBuffer); - - //! Handler for from_framedPoll - //! - Drv::PollStatus from_framedPoll_handler(const NATIVE_INT_TYPE portNum, /*!< The port number*/ - Fw::Buffer& pollBuffer); - - private: - // ---------------------------------------------------------------------- - // Helper methods + // Helper functions // ---------------------------------------------------------------------- //! Connect ports - //! void connectPorts(); //! Initialize components - //! void initComponents(); + //! Sends a buffer of supplied data and size on the component input port + void mockReceiveData(U8* data, FwSizeType size); + + private: // ---------------------------------------------------------------------- - // Variables + // Member variables // ---------------------------------------------------------------------- //! The component under test - //! Deframer component; - - Fw::Buffer m_buffer; - MockDeframer m_mock; - // Whether the bufferOut port is connected - ConnectStatus::t bufferOutStatus; }; -} // end namespace Svc +} // namespace Svc #endif diff --git a/Svc/FrameAccumulator/CMakeLists.txt b/Svc/FrameAccumulator/CMakeLists.txt new file mode 100644 index 0000000000..5c48666cba --- /dev/null +++ b/Svc/FrameAccumulator/CMakeLists.txt @@ -0,0 +1,40 @@ +#### +# F prime CMakeLists.txt: +# +# SOURCE_FILES: combined list of source and autocoding files +# MOD_DEPS: (optional) module dependencies +# UT_SOURCE_FILES: list of source files for unit tests +# +#### + +set(SOURCE_FILES + "${CMAKE_CURRENT_LIST_DIR}/FrameAccumulator.fpp" + "${CMAKE_CURRENT_LIST_DIR}/FrameAccumulator.cpp" +) + +register_fprime_module() + +#### UTs #### +set(UT_SOURCE_FILES + "${CMAKE_CURRENT_LIST_DIR}/FrameAccumulator.fpp" + "${CMAKE_CURRENT_LIST_DIR}/test/ut/FrameAccumulatorTester.cpp" + "${CMAKE_CURRENT_LIST_DIR}/test/ut/FrameAccumulatorTestMain.cpp" +) +set(UT_MOD_DEPS + Utils/Types + STest +) +set(UT_AUTO_HELPERS ON) + +register_fprime_ut() + +#### FprimeFrameDetector tests #### + +set(UT_HEADER_FILES + "${CMAKE_CURRENT_LIST_DIR}/FrameDetector/FprimeFrameDetector.hpp" +) +set(UT_SOURCE_FILES + "${CMAKE_CURRENT_LIST_DIR}/test/ut/detectors/FprimeFrameDetectorTestMain.cpp" +) + +register_fprime_ut("Svc_FrameAccumulator_FprimeFrameDetector_test") diff --git a/Svc/FrameAccumulator/FrameAccumulator.cpp b/Svc/FrameAccumulator/FrameAccumulator.cpp new file mode 100644 index 0000000000..73ebd6f035 --- /dev/null +++ b/Svc/FrameAccumulator/FrameAccumulator.cpp @@ -0,0 +1,177 @@ +// ====================================================================== +// \title FrameAccumulator.cpp +// \author mstarch +// \brief cpp file for FrameAccumulator component implementation class +// ====================================================================== + +#include "Svc/FrameAccumulator/FrameAccumulator.hpp" +#include "Fw/Types/Assert.hpp" +#include "FpConfig.hpp" + +namespace Svc { + +// ---------------------------------------------------------------------- +// Component construction and destruction +// ---------------------------------------------------------------------- + +FrameAccumulator ::FrameAccumulator(const char* const compName) : FrameAccumulatorComponentBase(compName), + m_detector(nullptr), m_memoryAllocator(nullptr), m_memory(nullptr), m_allocatorId(0) {} + +FrameAccumulator ::~FrameAccumulator() { + // If configuration happened, we must deallocate + if (this->m_memoryAllocator != nullptr) { + //TODO: after this line we have ownership of a deallocated buffer + this->m_memoryAllocator->deallocate(this->m_allocatorId, this->m_memory); + } +} + +void FrameAccumulator ::configure(const FrameDetector& detector, NATIVE_UINT_TYPE allocationId, + Fw::MemAllocator& allocator, + FwSizeType store_size +) { + bool recoverable = false; + FW_ASSERT(std::numeric_limits::max() >= store_size, static_cast(store_size)); + NATIVE_UINT_TYPE store_size_int = static_cast(store_size); + U8* data = reinterpret_cast(allocator.allocate(allocationId, store_size_int, recoverable)); + FW_ASSERT(data != nullptr); + FW_ASSERT(store_size_int >= store_size); + m_inRing.setup(data, store_size_int); + + this->m_detector = &detector; + this->m_allocatorId = allocationId; + this->m_memoryAllocator = &allocator; + this->m_memory = data; +} + +// ---------------------------------------------------------------------- +// Handler implementations for user-defined typed input ports +// ---------------------------------------------------------------------- + +void FrameAccumulator ::dataIn_handler(FwIndexType portNum, Fw::Buffer& buffer, const Drv::RecvStatus& status) { + // Check whether there is data to process + if (status.e == Drv::RecvStatus::RECV_OK) { + // There is: process the data + this->processBuffer(buffer); + } + // Deallocate the buffer + this->dataDeallocate_out(0, buffer); +} + +void FrameAccumulator ::processBuffer(Fw::Buffer& buffer) { + const U32 bufferSize = buffer.getSize(); + U8 *const bufferData = buffer.getData(); + // Current offset into buffer + U32 offset = 0; + // Remaining data in buffer + U32 remaining = bufferSize; + + for (U32 i = 0; i < bufferSize; ++i) { + // If there is no data left or no space, exit the loop + if (remaining == 0 || this->m_inRing.get_free_size() == 0) { + break; + } + // Compute the size of data to serialize + const NATIVE_UINT_TYPE ringFreeSize = this->m_inRing.get_free_size(); + const NATIVE_UINT_TYPE serSize = (ringFreeSize <= remaining) ? + ringFreeSize : static_cast(remaining); + // Serialize data into the ring buffer + const Fw::SerializeStatus status = + this->m_inRing.serialize(&bufferData[offset], serSize); + // If data does not fit, there is a coding error + FW_ASSERT( + status == Fw::FW_SERIALIZE_OK, + static_cast(status), + static_cast(offset), + static_cast(serSize)); + // Process the data + this->processRing(); + // Update buffer offset and remaining + offset += serSize; + remaining -= serSize; + } + // Either all the bytes from the data buffer must be processed, or the ring must be full + FW_ASSERT(remaining == 0 || this->m_inRing.get_free_size() == 0, static_cast(remaining)); +} + + void FrameAccumulator ::processRing() { + FW_ASSERT(this->m_detector != nullptr); + + // The number of remaining bytes in the ring buffer + U32 remaining = 0; + // The protocol status + FrameDetector::Status status = FrameDetector::Status::FRAME_DETECTED; + // The ring buffer capacity + const NATIVE_UINT_TYPE ringCapacity = this->m_inRing.get_capacity(); + + // Process the ring buffer looking for at least the header + for (U32 i = 0; i < ringCapacity; i++) { + // Get the number of bytes remaining in the ring buffer + remaining = this->m_inRing.get_allocated_size(); + // If there are none, we are done + if (remaining == 0) { + break; + } + // size_out is a return variable we initialize to zero, but it should be overwritten + FwSizeType size_out = 0; + // Attempt to detect the frame without changing the circular buffer + status = this->m_detector->detect(this->m_inRing, size_out); + // Detect must not consume data in the ring buffer + FW_ASSERT( + m_inRing.get_allocated_size() == remaining, + static_cast(m_inRing.get_allocated_size()), + static_cast(remaining) + ); + // On successful detection, consume data from the ring buffer and place it into an allocated frame + if (status == FrameDetector::FRAME_DETECTED) { + // size_out must be set to the size of the buffer and must fit within the existing data + FW_ASSERT(size_out != 0); + FW_ASSERT( + size_out <= remaining, + static_cast(size_out), + static_cast(remaining)); + // REVIEW NOTE: size_out needs to be cast down in multiple places below - is this ok? + Fw::Buffer buffer = this->frameAllocate_out(0, static_cast(size_out)); + if (buffer.isValid()) { + // Copy out data and rotate + FW_ASSERT(this->m_inRing.peek(buffer.getData(), static_cast(size_out)) == Fw::SerializeStatus::FW_SERIALIZE_OK); + buffer.setSize(static_cast(size_out)); + m_inRing.rotate(static_cast(size_out)); + FW_ASSERT( + m_inRing.get_allocated_size() == static_cast(remaining - size_out), + static_cast(m_inRing.get_allocated_size()), + static_cast(remaining), + static_cast(size_out) + ); + Fw::Buffer nullContext; + this->frameOut_out(0, buffer, nullContext); + } + else { + // No buffer is available, we need to exit and try again later + break; + } + } + // More data needed + else if (status == FrameDetector::MORE_DATA_NEEDED) { + // size_out can never be larger than the capacity of the ring. Otherwise all uplink will fail. + FW_ASSERT(size_out < m_inRing.get_capacity(), static_cast(size_out)); + // Detection should report "more is needed" and set size_out to something larger than available data + FW_ASSERT( + size_out > remaining, + static_cast(size_out), + static_cast(remaining)); + // Break out of loop: suspend detection until we receive another buffer + break; + } + // No frame was detected or an unknown status was received + else { + // Discard a single byte of data and start again + m_inRing.rotate(1); + FW_ASSERT( + m_inRing.get_allocated_size() == remaining - 1, + static_cast(m_inRing.get_allocated_size()), + static_cast(remaining) + ); + } + } + } +} // namespace Svc diff --git a/Svc/FrameAccumulator/FrameAccumulator.fpp b/Svc/FrameAccumulator/FrameAccumulator.fpp new file mode 100644 index 0000000000..71455d0e66 --- /dev/null +++ b/Svc/FrameAccumulator/FrameAccumulator.fpp @@ -0,0 +1,17 @@ +module Svc { + @ Accumulates data into frames + passive component FrameAccumulator { + + @ Receives raw data from a ByteStreamDriver, ComStub, or other buffer producing component + guarded input port dataIn: Drv.ByteStreamRecv + + @ Port for deallocating buffers received on dataIn. + output port dataDeallocate: Fw.BufferSend + + @ Port for allocating buffer to hold extracted frame + output port frameAllocate: Fw.BufferGet + + @ Port for sending an extracted frame out + output port frameOut: Fw.DataWithContext + } +} diff --git a/Svc/FrameAccumulator/FrameAccumulator.hpp b/Svc/FrameAccumulator/FrameAccumulator.hpp new file mode 100644 index 0000000000..cf1d81a4e2 --- /dev/null +++ b/Svc/FrameAccumulator/FrameAccumulator.hpp @@ -0,0 +1,77 @@ +// ====================================================================== +// \title FrameAccumulator.hpp +// \author mstarch +// \brief hpp file for FrameAccumulator component implementation class +// ====================================================================== + +#ifndef Svc_FrameAccumulator_HPP +#define Svc_FrameAccumulator_HPP + +#include "Fw/Types/MemAllocator.hpp" +#include "Utils/Types/CircularBuffer.hpp" +#include "Svc/FrameAccumulator/FrameAccumulatorComponentAc.hpp" +#include "Svc/FrameAccumulator/FrameDetector.hpp" + +namespace Svc { + +class FrameAccumulator : public FrameAccumulatorComponentBase { + public: + // ---------------------------------------------------------------------- + // Component construction and destruction + // ---------------------------------------------------------------------- + + //! \brief Construct FrameAccumulator object + FrameAccumulator(const char* const compName //!< The component name + ); + + //! \brief Destroy FrameAccumulator object + ~FrameAccumulator(); + + //! \brief configure memory allocation for the circular buffer + //! + //! Takes in parameters used in the Fw::MemAllocator pattern and configures a memory allocation for storing the + //! circular buffer. + void configure(const FrameDetector& detector, //!< Frame detector helper instance + NATIVE_UINT_TYPE allocationId, //!< Identifier used when dealing with the Fw::MemAllocator + Fw::MemAllocator& allocator, //!< Fw::MemAllocator used to acquire memory + FwSizeType store_size //!< Size to request for circular buffer + ); + PRIVATE: + // ---------------------------------------------------------------------- + // Handler implementations for user-defined typed input ports + // ---------------------------------------------------------------------- + + //! Handler implementation for dataIn + //! + //! Receives raw data from a ByteStreamDriver, ComStub, or other buffer producing component + void dataIn_handler(FwIndexType portNum, //!< The port number + Fw::Buffer& recvBuffer, + const Drv::RecvStatus& recvStatus) override; + PRIVATE: + //! \brief process raw buffer + //! \return raw data buffer + void processBuffer(Fw::Buffer& buffer); + + //! \brief process circular buffer + void processRing(); + + //! Circular buffer for storing data + Types::CircularBuffer m_inRing; + + //! Pointer to helper class that detects frames + FrameDetector const* m_detector; + + //! Memory allocator instance used with deallocating + Fw::MemAllocator* m_memoryAllocator; + + //! Memory pointer for allocated memory + U8* m_memory; + + //! Identification used with the memory allocator + NATIVE_UINT_TYPE m_allocatorId; + +}; + +} // namespace Svc + +#endif diff --git a/Svc/FrameAccumulator/FrameDetector.hpp b/Svc/FrameAccumulator/FrameDetector.hpp new file mode 100644 index 0000000000..69a4914e30 --- /dev/null +++ b/Svc/FrameAccumulator/FrameDetector.hpp @@ -0,0 +1,50 @@ +// ====================================================================== +// \title FrameDetector.hpp +// \author mstarch +// \brief hpp interface specification for FrameDetector +// ====================================================================== +#ifndef FPRIME_FRAMEDETECTOR_HPP +#define FPRIME_FRAMEDETECTOR_HPP +#include +#include + +namespace Svc { + + //! \brief interface class used to codify what must be supported to allow frame detection + class FrameDetector { + public: + //! \brief status returned from the detection step + enum Status { + FRAME_DETECTED, //!< Frame detected. Extract frame and return with new data. + NO_FRAME_DETECTED, //!< No frame detected. Discard data and return with new data. + MORE_DATA_NEEDED //!< More data is needed to detect a frame. Keep current data and return with more. + }; + + //! \brief detect if a frame is available within the circular buffer + //! + //! Function implemented by sub classes used to determine if a frame is available at the current position of the + //! circular buffer. Implementors should detect if a frame is available, set size_out, and return a status while + //! following these expectations: + //! + //! 1. FRAME_DETECTED status implies a frame is available at the current offset of the circular buffer. + //! size_out must be set to the size of the frame from that location. + //! + //! 2. NO_FRAME_DETECTED status implies no frame is possible at the current offset of the circular buffer. + //! e.g. no start word is found at the current offset. size_out is ignored. + //! + //! 3. MORE_DATA_NEEDED status implies that a frame might be possible but more data is needed before a + //! determination is possible. size_out must be set to the total amount of data needed. + //! + //! For example, if a frame start word is 4 bytes, and 3 bytes are available in the circular buffer then the + //! return status would be NO_FRAME_DETECTED and size_out must be set to 4 to ensure that at least the start + //! word is available. + //! + //! \param data: circular buffer with read-only access + //! \param size_out: set as output to caller indicating size when appropriate + //! \return status of the detection to be paired with size_out + virtual Status detect(const Types::CircularBuffer& data, FwSizeType& size_out) const = 0; + }; + +} // Svc + +#endif //FPRIME_FRAMEDETECTOR_HPP diff --git a/Svc/FrameAccumulator/FrameDetector/CCSDSFrameDetector.hpp b/Svc/FrameAccumulator/FrameDetector/CCSDSFrameDetector.hpp new file mode 100644 index 0000000000..183be062cc --- /dev/null +++ b/Svc/FrameAccumulator/FrameDetector/CCSDSFrameDetector.hpp @@ -0,0 +1,57 @@ +// ====================================================================== +// \title CCSDSFrameDetector.hpp +// \author mstarch +// \brief hpp file for CCSDS frame detector definitions +// ====================================================================== +#ifndef SVC_FRAME_ACCUMULATOR_FRAME_DETECTOR_CCSDS_FRAME_DETECTOR +#define SVC_FRAME_ACCUMULATOR_FRAME_DETECTOR_CCSDS_FRAME_DETECTOR +#include "FpConfig.h" +#include "Svc/FrameAccumulator/FrameDetector/StartLengthCrcDetector.hpp" +namespace Svc { +namespace FrameDetectors { + +//! \brief CRC16 CCITT implementation +//! +//! CCSDS uses a CRC16 (CCITT) implementation with polynomial 0x11021, initial value of 0xFFFF, and XOR of 0x0000. +//! +class CRC16_CCITT : public CRCWrapper { + public: + // Initial value is 0xFFFF + CRC16_CCITT() : CRCWrapper(std::numeric_limits::max()) {} + + //! \brief update CRC with one new byte + //! + //! Update function for CRC taking previous value from member variable and updating it. + //! + //! \param new_byte: new byte to add to calculation + void update(U8 new_byte) override { + this->m_crc = static_cast(update_crc_ccitt(m_crc, new_byte)); + }; + + //! \brief finalize and return CRC value + U16 finalize() override { + // Specified XOR value is 0x0000 + return this->m_crc ^ static_cast(0); + }; +}; +constexpr U16 TEN_BIT_MASK = 0x03FF; +constexpr U16 CCSDS_SDLP_TC_MAX_FRAME_LENGTH = 1017; +static_assert(CCSDS_SCID <= (std::numeric_limits::max() & TEN_BIT_MASK), "SCID must fit in 10bits"); + +//! CCSDS framing start word is: +//! - 2 bits of version number "00" +//! - 1 bit of bypass flag "0" +//! - 1 bit of control command flag "0" +//! - 2 bits of reserved "00" +//! - 10 bits of configurable SCID +using CCSDSStartWord = StartToken(0 | (CCSDS_SCID & TEN_BIT_MASK))>; +//! CCSDS length is the last 10 bits of the 3rd and 4th octet +using CCSDSLength = LengthToken; +//! CCSDS checksum is a 16bit CRC with data starting at the 6th octet and the crc following directly +using CCSDSChecksum = CRC; + +//! CCSDS frame detector is a start/length/crc detector using the configured fprime tokens +using CCSDSFrameDetector = StartLengthCrcDetector; +} // namespace FrameDetectors +} // namespace Svc +#endif // SVC_FRAME_ACCUMULATOR_FRAME_DETECTOR_CCSDS_FRAME_DETECTOR diff --git a/Svc/FrameAccumulator/FrameDetector/FprimeFrameDetector.hpp b/Svc/FrameAccumulator/FrameDetector/FprimeFrameDetector.hpp new file mode 100644 index 0000000000..a515212c07 --- /dev/null +++ b/Svc/FrameAccumulator/FrameDetector/FprimeFrameDetector.hpp @@ -0,0 +1,24 @@ +// ====================================================================== +// \title FprimeFrameDetector.hpp +// \author mstarch +// \brief hpp file for fprime frame detector definitions +// ====================================================================== +#ifndef SVC_FRAME_ACCUMULATOR_FRAME_DETECTOR_FPRIME_FRAME_DETECTOR +#define SVC_FRAME_ACCUMULATOR_FRAME_DETECTOR_FPRIME_FRAME_DETECTOR +#include "FpConfig.h" +#include "Svc/FrameAccumulator/FrameDetector/StartLengthCrcDetector.hpp" +namespace Svc { +namespace FrameDetectors { + +//! fprime framing start word is a configurable type and matched against 0xdeadbeef as cast into the appropriate type +using FprimeStartWord = StartToken(0xdeadbeef)>; +//! fprime framing length is a configurable type +using FprimeLength = LengthToken; +//! fprime uses a CRC32 checksum anchored at the end of the data +using FprimeChecksum = CRC; + +//! fprime frame detector is a start/length/crc detector using the configured fprime tokens +using FprimeFrameDetector = StartLengthCrcDetector; +} // namespace FrameDetectors +} // namespace Svc +#endif // SVC_FRAME_ACCUMULATOR_FRAME_DETECTOR_FPRIME_FRAME_DETECTOR diff --git a/Svc/FrameAccumulator/FrameDetector/StartLengthCrcDetector.hpp b/Svc/FrameAccumulator/FrameDetector/StartLengthCrcDetector.hpp new file mode 100644 index 0000000000..4da20b068a --- /dev/null +++ b/Svc/FrameAccumulator/FrameDetector/StartLengthCrcDetector.hpp @@ -0,0 +1,344 @@ +// ====================================================================== +// \title StartLengthCrcDetector.hpp +// \author mstarch +// \brief hpp file for start/length/crc frame detector definitions +// ====================================================================== +#ifndef SVC_FRAMEACCUCULATOR_FRAME_DETECTOR_STARTLENGTHCHECKSUMDETECTOR_HPP +#define SVC_FRAMEACCUCULATOR_FRAME_DETECTOR_STARTLENGTHCHECKSUMDETECTOR_HPP +#include "Fw/Types/PolyType.hpp" +#include "Fw/Types/Assert.hpp" +#include "Svc/FrameAccumulator/FrameDetector.hpp" +#include + +// Include the lic crc c library: +extern "C" { + #include +} + +namespace Svc { +namespace FrameDetectors { + +//! \brief endianness of the token word +enum Endianness { + BIG, //!< Token word is big-endian + LITTLE //!< Token word is little-endian +}; + +// CRC uses library defined with non fixed size types. Check for compliance with correct fixed size types. +static_assert(sizeof(unsigned long) >= sizeof(U32), "CRC32 cannot fit in CRC32 library chosen types"); +static_assert(sizeof(unsigned short) >= sizeof(U16), "CRC16 cannot fit in CRC16 library chosen types"); + +//! \brief base template definition of a "token" +//! +//! A token is a field that can be read from the circular buffer in order to detect a frame. These tokens could be start +//! words, lengths, CRCs, etc. Tokens are static with respect to a given framing protocol, but are parameterized across +//! several concepts: +//! 1. Token type: type of the token's containing word (e.g. U32, U16, U8) +//! 2. Token mask: mask used to pull the token from the containing word. Used for bit fields. +//! 3. Token endianness: endianness of the stored token word +//! \tparam TokenType: template parameter setting token word's type. Must be unsigned. +//! \tparam TokenMask: template parameter storing mask for token word. Expressed in type "TokenType" +//! \tparam TokenEndianness: template parameter setting endianness of token word. Endianness::BIG or Endianness::LITTLE. +template ::max(), + Endianness TokenEndianness = Endianness::BIG> +class Token { + // Checks to ensure token parameters are well-formed + static_assert(!std::numeric_limits::is_signed, "Tokens must be unsigned"); + static_assert(sizeof(TokenType) < std::numeric_limits::max(), "Token sizes must fit in a unsigned byte"); + static_assert(TokenEndianness == Endianness::BIG || TokenEndianness == Endianness::LITTLE, "Invalid endianness"); + + public: + // \brief zero out m_value + Token(): m_value(0) {} + + //! \brief read token from circular buffer without consuming data + //! + //! Reads the token from the circular buffer by reading the token's containing word, correcting for endianness, and + //! applying the token mask. The read data is set in the member variable: m_value. `size_out` parameter is updated + //! to add in the size of the token read regardless of any errors. Will return "MORE_DATA_NEEDED" on error or + //! "FRAME_DETECTED" on success. + //! \param data: circular buffer to peek into + //! \param offset: offset into the circular buffer to read + //! \param size_out: updated to add in token size + //! \return: status of the success/failure of the read + FrameDetector::Status read(const Types::CircularBuffer& data, FwSizeType offset, FwSizeType& size_out) { + // Update size_out + constexpr FwSizeType token_size = sizeof(TokenType); + size_out += token_size; + + // Read token byte by byte correcting for endianness + TokenType token = 0; + for (U8 i = 0; i < static_cast(token_size); i++) { + // Read most significant remaining byte regardless of endianness + FwSizeType byte_offset = + (TokenEndianness == Endianness::BIG) ? (offset + i) : (offset + (token_size - i - 1)); + U8 byte = 0; + Fw::SerializeStatus status = data.peek(byte, byte_offset); + if (status != Fw::SerializeStatus::FW_SERIALIZE_OK) { + this->m_value = 0; + return FrameDetector::Status::MORE_DATA_NEEDED; + } + // Shift in most significant remaining byte + token = token << 8 | byte; + } + // Mask and set token value + token &= TokenMask; + this->m_value = token; + return FrameDetector::FRAME_DETECTED; + } + + //! \brief get value of token after read, or 0 before + const TokenType& getValue() { return m_value; } + + protected: + TokenType m_value; +}; + +//! \brief subclass of Token representing the start token/start word of the frame +//! +//! Most frames have a start word or start token indicating the beginning of a frame. This token subclass represents +//! this concept in a parameterizable way. Start tokens are parameterized across the following: +//! 1. TokenType: same as token type from parent "Token" class. +//! 2. StartExpected: value of token indicating start of frame as TokenType. e.g. 0xdeadbeef(U32) for fprime +//! 3. TokenMask: same as token mask from parent class. Also applied to StartExpected +//! 4. TokenEndianness: same as token endianness from parent class. +//! +//! Start words read the token from the circular buffer, and then compare against the expected value after each is +//! masked. If the comparison passes, frame detection continues with "FRAME_DETECTED" otherwise "NO_FRAME_DETECTED" is +//! returned to indicate the current position in the buffer is *not* a frame. +//! \tparam TokenType: template parameter setting token word's type. Must be unsigned. +//! \tparam StartExpected: template parameter setting the expected start word pre-mask. +//! \tparam TokenMask: template parameter storing mask for token word. Expressed in type "TokenType" +//! \tparam TokenEndianness: template parameter setting endianness of token word. Endianness::BIG or Endianness::LITTLE. +template ::max(), + Endianness TokenEndianness = Endianness::BIG> +class StartToken : public Token { + public: + //! \brief read start token and determine if match with expected start token + //! + //! This will read the start token from the circular buffer. It then compares to expected to determine if there is + //! a frame start or no. This will return FRAME_DETECTED if the start word is detected. It will return + //! NO_FRAME_DETECTED if there was no match, and MORE_DATA_NEEDED if there is not enough data in underlying read. + //! \param data: circular buffer to read + //! \param size_out: size returned updated to include size of start token. + //! \return: FRAME_DETECTED, NO_FRAME_DETECTED, or MORE_DATA_NEEDED. + FrameDetector::Status read(const Types::CircularBuffer& data, FwSizeType& size_out) { + constexpr TokenType EXPECTED_VALUE = (StartExpected & TokenMask); + + FrameDetector::Status status = this->Token::read(data, 0, size_out); + if ((status == FrameDetector::FRAME_DETECTED) && (this->m_value != EXPECTED_VALUE)) { + status = FrameDetector::NO_FRAME_DETECTED; + } + return status; + } +}; + +//! \brief token representing data length +//! +//! Length of the data field is found somewhere in the data payload at a specified offset. This template has the +//! standard Token functions and an additional template parameter "Offset" used to show the offset of the length token. +//! \tparam TokenType: template parameter setting token word's type. Must be unsigned. +//! \tparam Offset: template parameter setting offset of length word +//! \tparam StartExpected: template parameter setting the expected start word pre-mask. +//! \tparam TokenMask: template parameter storing mask for token word. Expressed in type "TokenType" +//! \tparam TokenEndianness: template parameter setting endianness of token word. Endianness::BIG or Endianness::LITTLE. +template ::max(), + Endianness TokenEndianness = Endianness::BIG> +class LengthToken : public Token { + public: + //! \brief read length token and return total size of packet through length token + //! + //! This reads the length token from the buffer and returns the total needed size up through the length token. If + //! the length exceeds the maximum, then NO_FRAME_DETECTED is returned. size_out does not include the length of the + //! data as that is up to the caller. MORE_DATA_NEEDED is returned when insufficient data is available to read the + //! length token. FRAME_DETECTED is return when a valid size token was read + //! \param data: buffer to read + //! \param size_out: total packet length up through end of length token + //! \return: FRAME_DETECTED, or MORE_DATA_NEEDED. + FrameDetector::Status read(const Types::CircularBuffer& data, FwSizeType& size_out) { + size_out = Offset; + FrameDetector::Status status = this->Token::read(data, size_out, size_out); + if (this->m_value > MaximumLength) { + return FrameDetector::Status::NO_FRAME_DETECTED; + } + return status; + } +}; +template +class CRCWrapper { + protected: + //! \brief constructor setting initial value + CRCWrapper(TokenType initial) : m_crc(initial) {} + public: + //! \brief update CRC with one new byte + //! + //! Update function for CRC taking previous value from member variable and updating it. + //! + //! \param new_byte: new byte to add to calculation + virtual void update(U8 new_byte) = 0; + + //! \brief finalize and return CRC value + virtual TokenType finalize() = 0; + + protected: + TokenType m_crc; +}; +//! \brief standard CRC32 implementation +class CRC32 : public CRCWrapper { + public: + CRC32() : CRCWrapper(std::numeric_limits::max()) {} + + //! \brief update CRC with one new byte + //! + //! Update function for CRC taking previous value from member variable and updating it. + //! + //! \param new_byte: new byte to add to calculation + void update(U8 new_byte) override { + this->m_crc = static_cast(update_crc_32(static_cast(m_crc), new_byte)); + }; + + //! \brief finalize and return CRC value + U32 finalize() override { + // Bitwise not also works, but the CRC standard requests XOR calculation instead + return this->m_crc ^ std::numeric_limits::max(); + }; +}; + +//! \brief token representing the CRC +//! +//! CRC checksum template used to calculate the CRC of the *entire* packet except the stored CRC value itself. This is +//! parameterized on the following: +//! 1. TokenType (same as Token class) +//! 2. DataOffset: offset of data block. Used to calculate the full packet length including the data. +//! 3. RelativeTokenOffset: offset from end of data (length token) to where the CRC stored value +//! 4 CRCFn: function used to update CRC for each byte. Must match the TokenType. +//! This token will update size_out to include the whole packet (data block and CRC). It allows calculating the CRC on +//! the packet data and reading the sent CRC from the buffer. +//! \tparam TokenType: template parameter setting token word's type. Must be unsigned. +//! \tparam DataOffset: offset of variable data block in packet +//! \tparam RelativeTokenOffset: offset relative to end of data to where CRC is stored +//! \tparam CRCFn: function for updating CRC with new byte +template +class CRC : public Token::max(), BIG> { + public: + // Check CRC token and CRC handler match + static_assert(std::is_base_of, CRCHandler>::value, "Invalid CRC wrapper supplied"); + + CRC() : m_stored_offset(0), m_expected(0) {} + //! \brief calculate CRC across whole packet + //! + //! This will read in the whole packet (start word through but not including stored CRC) and calculate the CRC using + //! the parameterized function. It then negates (1's complements) the result per the CRC guidance. The initial + //! value is 0xffffffff converted to the appropriate size for the CRC. This will return FRAME_DETECTED + //! if there is data or MORE_DATA_NEEDED if the whole packet is not available. This will set m_stored_offset and + //! m_expected in preparation for a read call. m_stored_offset will be 0 if not called, or error occurred. + //! \param data: buffer to read + //! \param length: variable data length read from LengthToken + //! \param size_out: updated to include full length of packet (less stored CRC) + //! \return: FRAME_DETECTED, or MORE_DATA_NEEDED. + FrameDetector::Status calculate(const Types::CircularBuffer& data, FwSizeType length, FwSizeType& size_out) { + const FwSizeType checksum_length = DataOffset + length + RelativeTokenOffset; + size_out = checksum_length; + CRCHandler crc; + // Loop byte by byte + for (FwSizeType i = 0; i < checksum_length; i++) { + U8 byte = 0; + Fw::SerializeStatus status = data.peek(byte, i); + if (status != Fw::SerializeStatus::FW_SERIALIZE_OK) { + this->m_stored_offset = 0; + return FrameDetector::Status::MORE_DATA_NEEDED; + } + crc.update(byte); + } + // CRC finalization ends in bit-wise negation + this->m_stored_offset = checksum_length; + this->m_expected = crc.finalize(); + return FrameDetector::FRAME_DETECTED; + } + //! \brief read stored CRC and check against recalculation + //! + //! `calculate` must be called first. This will read the stored CRC and check against the value calculated during + //! the `calculate` step. size_out will be updated to the full packet size (including CRC at the end). This will + //! return FRAME_DETECTED if the CRC is valid (as is the whole frame) or MORE_DATA_NEEDED if the whole packet + //! is not including the trailing CRC, and NO_FRAME_DETECTED if the CRC fails. + //! \param data: buffer to read for data + //! \param size_out: size of the whole packet + //! \return: FRAME_DETECTED, NO_FRAME_DETECTED, or MORE_DATA_NEEDED. + FrameDetector::Status read(const Types::CircularBuffer& data, FwSizeType& size_out) { + FW_ASSERT(this->m_stored_offset != 0); // Must have called calculate before calling read + size_out = this->m_stored_offset; + FrameDetector::Status status = + this->Token::max(), BIG>::read(data, size_out, size_out); + if ((status == FrameDetector::FRAME_DETECTED) && (this->m_value != this->m_expected)) { + status = FrameDetector::NO_FRAME_DETECTED; + } + return status; + } + + protected: + FwSizeType m_stored_offset; + TokenType m_expected; +}; +//! \brief start/length/crc detector template +//! +//! Most packets are of the form start token, length token, data, and a trailing CRC. This template is used to quickly +//! implement detectors that follow the above pattern by supplying configured tokens for each concept. In shor, a dev +//! should create a protocol specific StartToken, LengthToken, and Checksum and then template this class using those +//! specified tokens. That fully specified class can be supplied to FrameAccumulator for detection. +//! +//! For example see: FprimeFrameDetector.hpp +//! \tparam StartToken: start token specification +//! \tparam LengthToken: length token specification +//! \tparam Checksum: checksum token specification +template +class StartLengthCrcDetector : public FrameDetector { + public: + //! \brief detect if a frame is available in circular buffer + //! + //! Detects if the frame is available by checking start word, length, and checksum in order. Returns FRAME_DETECTED + //! when the whole frame is available, NO_FRAME_DETECTED when there is no frame at the head of the buffer, and + //! MORE_DATA_NEEDED if the frame is valid (so far) but incomplete. size_out specifies the size needed to perform + //! the next read of the next token. + //! \param data: circular buffer + //! \param size_out: set to needed size. Note: may not be full frame size, just to read the next token. + //! \return: FRAME_DETECTED, NO_FRAME_DETECTED, or MORE_DATA_NEEDED. + Status detect(const Types::CircularBuffer& data, FwSizeType& size_out) const override { + // Read start word + StartToken startWord; + Status status = startWord.read(data, size_out); + if (status != Status::FRAME_DETECTED) { + return status; + } + + // Read length + LengthToken lengthWord; + status = lengthWord.read(data, size_out); + if (status != Status::FRAME_DETECTED) { + return status; + } + FwSizeType length = lengthWord.getValue(); + + // Recalculate CRC + Checksum crc; + status = crc.calculate(data, length, size_out); + if (status != Status::FRAME_DETECTED) { + return status; + } + // Read CRC + status = crc.read(data, size_out); + return status; + }; +}; +} // namespace FrameDetectors +} // namespace Svc + +#endif // SVC_FRAMEACCUCULATOR_FRAME_DETECTOR_STARTLENGTHCHECKSUMDETECTOR_HPP diff --git a/Svc/FrameAccumulator/docs/sdd.md b/Svc/FrameAccumulator/docs/sdd.md new file mode 100644 index 0000000000..882173cfca --- /dev/null +++ b/Svc/FrameAccumulator/docs/sdd.md @@ -0,0 +1,77 @@ +# Svc::FrameAccumulator + +The `Svc::FrameAccumulator` component accumulates a stream of data (sequence of [Fw::Buffer](../../../Fw/Buffer/docs/sdd.md) objects) to extract full frames. + +The `Svc::FrameAccumulator` accepts as input a sequence of byte buffers, which typically come from a ground data system via a [ByteStreamDriver](../../../Drv/ByteStreamDriverModel/docs/sdd.md). It extracts the frames from the sequence of buffers and emits them on the `frameOut` output port. + +## Internals + +The `Svc::FrameAccumulator` accumulates the [Fw::Buffer](../../../Fw/Buffer/docs/sdd.md) objects into a circular buffer ([Utils::CircularBuffer](../../../Utils/Types/CircularBuffer.hpp)). + +The component must be configured with with a [`Svc::FrameDetector`](../FrameDetector.hpp) which is responsible for detecting frames in the circular buffer. An implementation of this for the F´ communications protocol is provided by `Svc::FrameDetectors::FprimeFrameDetector`. When the configured `Svc::FrameDetector` detects a frame in the circular buffer, it emits the frame on its output port. + +The uplink frames need not be aligned on the buffer boundaries, and each frame may span one or more buffers. + +## Usage Examples + +The `Svc::FrameAccumulator` component is used in the uplink stack of many reference F´ application such as [the tutorials source code](https://github.com/fprime-community#tutorials). + +## Diagrams + +```mermaid +sequenceDiagram + participant I as Input + box Grey FrameAccumulator + participant A as Accumulator + participant D as Detector + end + + I-->>A: Fw::Buffer + activate A + A-->A: Serialize into RingBuffer + loop + A-->>D: detect() + alt MORE_DATA_NEEDED + A-->A: break + else NO_FRAME_DETECTED + A-->>A: ring.rotate() + else FRAME_DETECTED + create participant Z as Output + A-->>Z: Frame + end + end + deactivate A + destroy O + +``` + + +## Class Diagram + +```mermaid +classDiagram + class FrameAccumulator~PassiveComponent~ { + + void configure(FrameDetector& detector, NATIVE_UINT_TYPE allocationId, Fw::MemAllocator& allocator, FwSizeType store_size) + + void dataIn_handler(FwIndexType portNum, Fw::Buffer& recvBuffer, const Drv::RecvStatus& recvStatus) + + void processBuffer(Fw::Buffer& buffer) + + void processRing() + } +``` + +## Requirements + +Requirement | Description | Rationale | Verification Method +----------- | ----------- | ----------| ------------------- +SVC-FRAMEACCUMULATOR-001 | `Svc::FrameAccumulator` shall accumulate a sequence of byte buffers until a full frame is received | FrameAccumulator is designed to re-assemble frames from sequence of bytes | Unit test | +SVC-FRAMEACCUMULATOR-002 | `Svc::FrameAccumulator` shall detect once the accumulated buffers form a full frame and emit said frame | Pass frames to other parts of the system | Unit test | +SVC-FRAMEACCUMULATOR-003 | `Svc::FrameAccumulator` shall accept byte buffers containing frames that are not aligned on a buffer boundary. | For flexibility, we do not require that the frames be aligned on a buffer boundary. | Unit test | +SVC-FRAMEACCUMULATOR-004 | `Svc::FrameAccumulator` shall accept byte buffers containing frames that span one or more buffers. | For flexibility, we do not require each frame to fit in a single buffer. | Unit test | + +## Port Descriptions + +| Kind | Name | Type | Description | +|---|---|---|---| +| `guarded input` | dataIn | `Drv.ByteStreamRecv` | Receives raw data from a ByteStreamDriver, ComStub, or other buffer producing component | +| `output` | frameOut | `Fw.DataWithContext` | Port for sending an extracted frame out | +| `output` | frameAllocate | `Fw.BufferGet` | Port for allocating buffer to hold extracted frame | +| `output`| dataDeallocate | `Fw.BufferSend` | Port for deallocating buffers received on dataIn. | diff --git a/Svc/FrameAccumulator/test/ut/FrameAccumulatorTestMain.cpp b/Svc/FrameAccumulator/test/ut/FrameAccumulatorTestMain.cpp new file mode 100644 index 0000000000..c84b984d42 --- /dev/null +++ b/Svc/FrameAccumulator/test/ut/FrameAccumulatorTestMain.cpp @@ -0,0 +1,50 @@ +// ====================================================================== +// \title FrameAccumulatorTestMain.cpp +// \author thomas-bc +// \brief cpp file for FrameAccumulator component test main function +// ====================================================================== + +#include "FrameAccumulatorTester.hpp" +#include "STest/Random/Random.hpp" + +TEST(FrameAccumulator, TestFrameDetected) { + Svc::FrameAccumulatorTester tester; + tester.testFrameDetected(); +} + +TEST(FrameAccumulator, TestMoreDataNeeded) { + Svc::FrameAccumulatorTester tester; + tester.testMoreDataNeeded(); +} + +TEST(FrameAccumulator, TestNoFrameDetected) { + Svc::FrameAccumulatorTester tester; + tester.testNoFrameDetected(); +} + +TEST(FrameAccumulator, TestReceiveZeroSizeBuffer) { + Svc::FrameAccumulatorTester tester; + tester.testReceiveZeroSizeBuffer(); +} + +TEST(FrameAccumulator, TestAccumulateTwoBuffers) { + Svc::FrameAccumulatorTester tester; + tester.testAccumulateTwoBuffers(); +} + +TEST(FrameAccumulator, testAccumulateBuffersEmitFrame) { + Svc::FrameAccumulatorTester tester; + tester.testAccumulateBuffersEmitFrame(); +} + +TEST(FrameAccumulator, testAccumulateBuffersEmitManyFrames) { + Svc::FrameAccumulatorTester tester; + tester.testAccumulateBuffersEmitManyFrames(); +} + + +int main(int argc, char** argv) { + STest::Random::seed(); + ::testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); +} diff --git a/Svc/FrameAccumulator/test/ut/FrameAccumulatorTester.cpp b/Svc/FrameAccumulator/test/ut/FrameAccumulatorTester.cpp new file mode 100644 index 0000000000..7483ebac8c --- /dev/null +++ b/Svc/FrameAccumulator/test/ut/FrameAccumulatorTester.cpp @@ -0,0 +1,199 @@ +// ====================================================================== +// \title FrameAccumulatorTester.cpp +// \author thomas-bc +// \brief cpp file for FrameAccumulator component test harness implementation class +// ====================================================================== + +#include "FrameAccumulatorTester.hpp" +#include "STest/Random/Random.hpp" + + + +namespace Svc { + +// ---------------------------------------------------------------------- +// Construction and destruction +// ---------------------------------------------------------------------- + +FrameAccumulatorTester ::FrameAccumulatorTester() + : FrameAccumulatorGTestBase("FrameAccumulatorTester", FrameAccumulatorTester::MAX_HISTORY_SIZE), + component("FrameAccumulator") { + component.configure(this->mockDetector, 1, this->mallocator, 2048); + this->initComponents(); + this->connectPorts(); +} + +// ---------------------------------------------------------------------- +// Tests +// ---------------------------------------------------------------------- + +void FrameAccumulatorTester ::testFrameDetected() { + // Prepare a random size buffer + U32 buffer_size = STest::Random::lowerUpper(1, 1024); + U8 data[buffer_size]; + Fw::Buffer buffer(data, buffer_size); + // Set the mock detector to report success of size_out = buffer_size + this->mockDetector.set_next_result(FrameDetector::Status::FRAME_DETECTED, buffer_size); + // Receive the buffer on dataIn + this->invoke_to_dataIn(0, buffer, Drv::RecvStatus::RECV_OK); + // Checks + ASSERT_from_dataDeallocate_SIZE(1); // input buffer was deallocated + ASSERT_from_frameOut_SIZE(1); // frame was sent + ASSERT_EQ(this->component.m_inRing.get_allocated_size(), 0); // no data left in ring buffer + ASSERT_EQ(this->fromPortHistory_frameOut->at(0).data.getSize(), buffer_size); // all data was sent out +} + +void FrameAccumulatorTester ::testMoreDataNeeded() { + // Prepare a random size buffer + U32 buffer_size = STest::Random::lowerUpper(1, 1024); + U8 data[buffer_size]; + Fw::Buffer buffer(data, buffer_size); + // Set the mock detector to report more data needed + this->mockDetector.set_next_result(FrameDetector::Status::MORE_DATA_NEEDED, buffer_size + 1); + // Receive the buffer on dataIn + this->invoke_to_dataIn(0, buffer, Drv::RecvStatus::RECV_OK); + // Checks + ASSERT_from_dataDeallocate_SIZE(1); // input buffer was deallocated + ASSERT_from_frameOut_SIZE(0); // frame was not sent (waiting on more data) + ASSERT_EQ(this->component.m_inRing.get_allocated_size(), buffer_size); // data left in ring buffer +} + +void FrameAccumulatorTester ::testNoFrameDetected() { + // Prepare a random size buffer + U32 buffer_size = STest::Random::lowerUpper(1, 1024); + U8 data[buffer_size]; + Fw::Buffer buffer(data, buffer_size); + // Set the mock detector + this->mockDetector.set_next_result(FrameDetector::Status::NO_FRAME_DETECTED, 0); + // Receive the buffer on dataIn + this->invoke_to_dataIn(0, buffer, Drv::RecvStatus::RECV_OK); + // Checks + ASSERT_from_dataDeallocate_SIZE(1); // input buffer was deallocated + ASSERT_from_frameOut_SIZE(0); // No frame was sent out + ASSERT_EQ(this->component.m_inRing.get_allocated_size(), 0); // all data was consumed and discarded +} + +void FrameAccumulatorTester ::testReceiveZeroSizeBuffer() { + + // Prepare a zero size buffer + U8 data[1] = {0}; + Fw::Buffer buffer(data, 0); + // Receive the buffer on dataIn + this->invoke_to_dataIn(0, buffer, Drv::RecvStatus::RECV_OK); + // Checks + ASSERT_from_dataDeallocate_SIZE(1); // input buffer was deallocated + ASSERT_from_frameOut_SIZE(0); // No frame was sent out + ASSERT_EQ(this->component.m_inRing.get_allocated_size(), 0); // No data in ring buffer + ASSERT_EQ(this->component.m_inRing.m_head_idx, 0); +} + +void FrameAccumulatorTester ::testAccumulateTwoBuffers() { + FwSizeType buffer1_size = 10; + FwSizeType buffer2_size = 20; + U8 data1[buffer1_size]; + U8 data2[buffer2_size]; + Fw::Buffer buffer1(data1, buffer1_size); + Fw::Buffer buffer2(data2, buffer2_size); + + this->mockDetector.set_next_result(FrameDetector::Status::MORE_DATA_NEEDED, buffer2_size); + // Receive the buffer on dataIn + this->invoke_to_dataIn(0, buffer1, Drv::RecvStatus::RECV_OK); + // Next result is detection of a full frame, size = buffer1_size + buffer2_size + this->mockDetector.set_next_result(FrameDetector::Status::FRAME_DETECTED, buffer1_size + buffer2_size ); + // Receive the buffer on dataIn + this->invoke_to_dataIn(0, buffer2, Drv::RecvStatus::RECV_OK); + + // Checks + ASSERT_from_dataDeallocate_SIZE(2); // both input buffers deallocated + ASSERT_from_frameOut_SIZE(1); // Exactly one frame was sent out + ASSERT_EQ(this->component.m_inRing.get_allocated_size(), 0); // No data in ring buffer +} + +void FrameAccumulatorTester ::testAccumulateBuffersEmitFrame() { + U32 frame_size = 0; + U32 buffer_count = 0; + this->mockAccumulateFullFrame(frame_size, buffer_count); + // Checks + ASSERT_from_dataDeallocate_SIZE(buffer_count); // all input buffers deallocated + ASSERT_from_frameOut_SIZE(1); // Exactly one frame was sent out + ASSERT_EQ(this->component.m_inRing.get_allocated_size(), 0); // No data left in ring buffer + ASSERT_EQ(this->fromPortHistory_frameOut->at(0).data.getSize(), frame_size); // accumulated buffer size +} + +void FrameAccumulatorTester ::testAccumulateBuffersEmitManyFrames() { + U32 max_iters = STest::Random::lowerUpper(1, 10); + U32 total_buffer_received = 0; + + U32 frame_size = 0; + U32 buffer_count = 0; + + // Send frames successively, perform some checks after each frame + for (U32 i = 0; i < max_iters; i++) { + this->mockAccumulateFullFrame(frame_size, buffer_count); + total_buffer_received += buffer_count; + + ASSERT_from_dataDeallocate_SIZE(total_buffer_received); // all input buffers deallocated + ASSERT_from_frameOut_SIZE(i+1); // Exactly one frame was sent out + ASSERT_EQ(this->component.m_inRing.get_allocated_size(), 0); // No data left in ring buffer + ASSERT_EQ(this->fromPortHistory_frameOut->at(i).data.getSize(), frame_size); // accumulated buffer size + } + // Final checks + ASSERT_from_dataDeallocate_SIZE(total_buffer_received); // all input buffers deallocated + ASSERT_from_frameOut_SIZE(max_iters); // Exactly max_iters frames were sent out + ASSERT_EQ(this->component.m_inRing.get_allocated_size(), 0); // No data left in ring buffer +} + +// ---------------------------------------------------------------------- +// Helper functions +// ---------------------------------------------------------------------- + +void FrameAccumulatorTester ::mockAccumulateFullFrame(U32& frame_size, U32& buffer_count) { + // Constants need to be picked so that: + // - the worst case doesn't overflow the ring buffer size (max_size * iters < 2048) + // - iters < MAX_HISTORY_SIZE + const U32 buffer_max_size = 64; + const U32 iters = STest::Random::lowerUpper(0, 10); + + U8 data[buffer_max_size]; + U32 buffer_size; + Fw::Buffer buffer(data, 0); + U32 accumulated_size = 0; + + // Send multiple buffers with MORE_DATA_NEEDED + for (U32 i = 0; i < iters; i++) { + buffer_size = STest::Random::lowerUpper(1, buffer_max_size); + accumulated_size += buffer_size; + buffer.setSize(buffer_size); + // Detector reports MORE_DATA_NEEDED and size needed bigger than accumulated size so far + this->mockDetector.set_next_result(FrameDetector::Status::MORE_DATA_NEEDED, accumulated_size + 1); + this->invoke_to_dataIn(0, buffer, Drv::RecvStatus::RECV_OK); + } + + // Send last buffer with FRAME_DETECTED + buffer_size = STest::Random::lowerUpper(1, buffer_max_size); + buffer.setSize(buffer_size); + accumulated_size += buffer_size; // accumulate once more (sending last buffer below) + // Send last buffer with finally FRAME_DETECTED and total accumulated + last buffer + this->mockDetector.set_next_result(FrameDetector::Status::FRAME_DETECTED, accumulated_size); + // Receive the last buffer on dataIn + this->invoke_to_dataIn(0, buffer, Drv::RecvStatus::RECV_OK); + frame_size = accumulated_size; + buffer_count = iters + 1; +} + +// ---------------------------------------------------------------------- +// Port handler overrides +// ---------------------------------------------------------------------- +Fw::Buffer FrameAccumulatorTester ::from_frameAllocate_handler( + FwIndexType portNum, + U32 size + ) + { + this->pushFromPortEntry_frameAllocate(size); + this->m_buffer.setData(this->m_buffer_slot); + this->m_buffer.setSize(size); + ::memset(this->m_buffer.getData(), 0, size); + return this->m_buffer; + } + +} // namespace Svc diff --git a/Svc/FrameAccumulator/test/ut/FrameAccumulatorTester.hpp b/Svc/FrameAccumulator/test/ut/FrameAccumulatorTester.hpp new file mode 100644 index 0000000000..14eb135769 --- /dev/null +++ b/Svc/FrameAccumulator/test/ut/FrameAccumulatorTester.hpp @@ -0,0 +1,126 @@ +// ====================================================================== +// \title FrameAccumulatorTester.hpp +// \author thomas-bc +// \brief hpp file for FrameAccumulator component test harness implementation class +// ====================================================================== + +#ifndef Svc_FrameAccumulatorTester_HPP +#define Svc_FrameAccumulatorTester_HPP + +#include "Fw/Types/MallocAllocator.hpp" +#include "Svc/FrameAccumulator/FrameAccumulator.hpp" +#include "Svc/FrameAccumulator/FrameAccumulatorGTestBase.hpp" +#include "Svc/FrameAccumulator/FrameDetector/FprimeFrameDetector.hpp" + +namespace Svc { + +class FrameAccumulatorTester : public FrameAccumulatorGTestBase { + public: + // ---------------------------------------------------------------------- + // Constants + // ---------------------------------------------------------------------- + + // Maximum size of histories storing events, telemetry, and port outputs + static const FwSizeType MAX_HISTORY_SIZE = 200; + + // Instance ID supplied to the component instance under test + static const FwEnumStoreType TEST_INSTANCE_ID = 0; + + public: + // ---------------------------------------------------------------------- + // Construction and destruction + // ---------------------------------------------------------------------- + + //! Construct object FrameAccumulatorTester + FrameAccumulatorTester(); + + //! Destroy object FrameAccumulatorTester + ~FrameAccumulatorTester() = default; + + public: + // ---------------------------------------------------------------------- + // Tests + // ---------------------------------------------------------------------- + + //! Test that frame is detected + void testFrameDetected(); + + //! More data needed + void testMoreDataNeeded(); + + //! No frame detected + void testNoFrameDetected(); + + //! Receive a zero size buffer + void testReceiveZeroSizeBuffer(); + + // Send two buffers successively and check that the frame is correctly accumulated + void testAccumulateTwoBuffers(); + + //! Test accumulation of multiple random-size buffer into a frame + void testAccumulateBuffersEmitFrame(); + + //! Test accumulation of multiple random-size buffer into frames successively + void testAccumulateBuffersEmitManyFrames(); + + private: + // ---------------------------------------------------------------------- + // Helper functions + // ---------------------------------------------------------------------- + + //! Send a series of random-size buffers, terminated by a buffer that + //! will be detected as a full frame by the MockDetector + //! (output) frame_size and buffer_count are updated with the size of the frame and the number of buffers sent + void mockAccumulateFullFrame(U32& frame_size, U32& buffer_count); + + //! Connect ports + void connectPorts(); + + //! Initialize components + void initComponents(); + + private: + // ---------------------------------------------------------------------- + // Port handler overrides + // ---------------------------------------------------------------------- + //! Overriding frameAllocate handler to be able to request a buffer in component tests + Fw::Buffer from_frameAllocate_handler(FwIndexType portNum, U32 size) override; + + private: + // ---------------------------------------------------------------------- + // Member variables + // ---------------------------------------------------------------------- + + //! The component under test + Svc::FrameAccumulator component; + + //! MockDetector is used to control the behavior of the component under test + //! by controlling what the FrameDetector will report without needing to craft + //! legitimate frames from specific to a protocol + class MockDetector : public FrameDetector { + public: + Status detect(const Types::CircularBuffer& data, FwSizeType& size_out) const override { + size_out = this->next_size_out; + return next_status; + } + + void set_next_result(Status next_status, FwSizeType next_size_out) { + this->next_size_out = next_size_out; + this->next_status = next_status; + } + + Status next_status = Status::FRAME_DETECTED; + U32 next_size_out = 0; + }; + + //! Instances required by the component under test + MockDetector mockDetector; + Fw::MallocAllocator mallocator; + + Fw::Buffer m_buffer; // buffer to be returned by mocked frameAllocate call + U8 m_buffer_slot[2048]; +}; + +} // namespace Svc + +#endif diff --git a/Svc/FrameAccumulator/test/ut/detectors/FprimeFrameDetectorTestMain.cpp b/Svc/FrameAccumulator/test/ut/detectors/FprimeFrameDetectorTestMain.cpp new file mode 100644 index 0000000000..cdda2475d4 --- /dev/null +++ b/Svc/FrameAccumulator/test/ut/detectors/FprimeFrameDetectorTestMain.cpp @@ -0,0 +1,128 @@ +// ====================================================================== +// \title FprimeFrameDetectorTestMain.cpp +// \author thomas-bc +// \brief cpp file for FrameAccumulator component test main function +// ====================================================================== + +#include "gtest/gtest.h" +#include "Svc/FrameAccumulator/FrameDetector/StartLengthCrcDetector.hpp" +#include "Svc/FrameAccumulator/FrameDetector/FprimeFrameDetector.hpp" +#include "STest/Random/Random.hpp" + +U32 CIRCULAR_BUFFER_TEST_SIZE = 2048; + +//! \brief Create an F´ frame and serialize it into the supplied circular buffer +//! \param circular_buffer The circular buffer to serialize the frame into +//! \note The frame is generated with random data of random size +//! \return The size of the generated frame +FwSizeType generate_random_fprime_frame(Types::CircularBuffer& circular_buffer) { + constexpr FwSizeType FRAME_HEADER_SIZE = 8; + constexpr FwSizeType FRAME_FOOTER_SIZE = 4; + + // Generate random packet size (1-1024 bytes; because 0 would trigger undefined behavior warnings) + // 1024 is max length as per FrameAccumulator/FrameDetector/FprimeFrameDetector @ LengthToken::MaximumLength + U32 packet_size = STest::Random::lowerUpper(1, 1024); + U8 data[packet_size]; + // Generate random data of random size + for (FwSizeType i = 0; i < packet_size; i++) { + data[i] = STest::Random::lowerUpper(0, 255); + } + // Frame header | Start Word 4 bytes | Length (4 bytes) | + U8 frame_header[FRAME_HEADER_SIZE] = {0xDE, 0xAD, 0xBE, 0xEF, 0x00, 0x00, 0x00, 0x00}; + // Serialize actual packet size into header + for (FwSizeType i = 0; i < 4; i++) { + frame_header[i + 4] = static_cast(packet_size >> (8 * (3 - i))); + } + + // Calculate CRC on header + data + Svc::FrameDetectors::CRC32 crc_calculator; + for (FwSizeType i = 0; i < FRAME_HEADER_SIZE; i++) { + crc_calculator.update(frame_header[i]); + } + for (FwSizeType i = 0; i < packet_size; i++) { + crc_calculator.update(data[i]); + } + U32 crc = crc_calculator.finalize(); + // printf("crc: %08X\n", crc); + + // Concatenate all data to create the full frame (byte array) + FwSizeType fprime_frame_size = FRAME_HEADER_SIZE + packet_size + FRAME_FOOTER_SIZE; + U8 fprime_frame[fprime_frame_size]; + // Copy header, data, and CRC into the full frame + for (FwIndexType i = 0; i < static_cast(FRAME_HEADER_SIZE); i++) { + fprime_frame[i] = frame_header[i]; + } + for (FwIndexType i = 0; i < static_cast(packet_size); i++) { + fprime_frame[i + FRAME_HEADER_SIZE] = data[i]; + } + for (FwIndexType i = 0; i < static_cast(FRAME_FOOTER_SIZE); i++) { + // crc is a U32; unpack into 4 bytes (shift by 24->-16->8->0 bits, mask with 0xFF) + fprime_frame[i + FRAME_HEADER_SIZE + packet_size] = (crc >> (8 * (3 - i))) & 0xFF; + } + // Serialize frame into circular buffer + circular_buffer.serialize(fprime_frame, fprime_frame_size); + + // Uncomment for debugging + // printf("Serialized %llu bytes:\n", fprime_frame_size); + // for (FwIndexType i = 0; i < static_cast(fprime_frame_size); i++) { + // printf("%02X ", fprime_frame[i]); + // } + return fprime_frame_size; +} + +TEST(FprimeFrameDetector, TestFrameDetected) { + Svc::FrameDetectors::FprimeFrameDetector fprime_detector; + U8 buffer[CIRCULAR_BUFFER_TEST_SIZE]; + ::memset(buffer, 0, CIRCULAR_BUFFER_TEST_SIZE); + Types::CircularBuffer circular_buffer(buffer, CIRCULAR_BUFFER_TEST_SIZE); + + FwSizeType frame_size = generate_random_fprime_frame(circular_buffer); + + Svc::FrameDetector::Status status; + FwSizeType size_out = 0; + status = fprime_detector.detect(circular_buffer, size_out); + + EXPECT_EQ(status, Svc::FrameDetector::Status::FRAME_DETECTED); + EXPECT_EQ(size_out, frame_size); +} + + +TEST(FprimeFrameDetector, TestNoFrameDetected) { + Svc::FrameDetectors::FprimeFrameDetector fprime_detector; + U8 buffer[CIRCULAR_BUFFER_TEST_SIZE]; + ::memset(buffer, 0, CIRCULAR_BUFFER_TEST_SIZE); + Types::CircularBuffer circular_buffer(buffer, CIRCULAR_BUFFER_TEST_SIZE); + + (void) generate_random_fprime_frame(circular_buffer); + // Remove 1 byte from the beginning of the frame, making it invalid + circular_buffer.rotate(1); + + Svc::FrameDetector::Status status; + FwSizeType unused = 0; + status = fprime_detector.detect(circular_buffer, unused); + + EXPECT_EQ(status, Svc::FrameDetector::Status::NO_FRAME_DETECTED); +} + +TEST(FprimeFrameDetector, TestMoreDataNeeded) { + Svc::FrameDetectors::FprimeFrameDetector fprime_detector; + U8 buffer[CIRCULAR_BUFFER_TEST_SIZE]; + ::memset(buffer, 0, CIRCULAR_BUFFER_TEST_SIZE); + Types::CircularBuffer circular_buffer(buffer, CIRCULAR_BUFFER_TEST_SIZE); + + (void) generate_random_fprime_frame(circular_buffer); + circular_buffer.m_allocated_size--; // Remove 1 byte from the end of the frame to trigger "more data needed" + + Svc::FrameDetector::Status status; + FwSizeType unused = 0; + status = fprime_detector.detect(circular_buffer, unused); + + EXPECT_EQ(status, Svc::FrameDetector::Status::MORE_DATA_NEEDED); +} + + +int main(int argc, char** argv) { + STest::Random::seed(); + ::testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); +} diff --git a/Svc/Interfaces/DeframerInterface.fppi b/Svc/Interfaces/DeframerInterface.fppi new file mode 100644 index 0000000000..9ab8561750 --- /dev/null +++ b/Svc/Interfaces/DeframerInterface.fppi @@ -0,0 +1,5 @@ +@ Port to receive framed data, with optional context +guarded input port framedIn: Fw.DataWithContext + +@ Port to output deframed data, with optional context +output port deframedOut: Fw.DataWithContext \ No newline at end of file diff --git a/Svc/Router/CMakeLists.txt b/Svc/Router/CMakeLists.txt new file mode 100644 index 0000000000..fad11ded5d --- /dev/null +++ b/Svc/Router/CMakeLists.txt @@ -0,0 +1,25 @@ +#### +# F prime CMakeLists.txt: +# +# SOURCE_FILES: combined list of source and autocoding files +# MOD_DEPS: (optional) module dependencies +# UT_SOURCE_FILES: list of source files for unit tests +# +#### + +set(SOURCE_FILES + "${CMAKE_CURRENT_LIST_DIR}/Router.fpp" + "${CMAKE_CURRENT_LIST_DIR}/Router.cpp" +) +register_fprime_module() + + +#### UTS #### +set(UT_SOURCE_FILES + "${CMAKE_CURRENT_LIST_DIR}/Router.fpp" + "${CMAKE_CURRENT_LIST_DIR}/test/ut/RouterTester.cpp" + "${CMAKE_CURRENT_LIST_DIR}/test/ut/RouterTestMain.cpp" +) +set(UT_AUTO_HELPERS ON) + +register_fprime_ut() diff --git a/Svc/Router/Router.cpp b/Svc/Router/Router.cpp new file mode 100644 index 0000000000..cc83945f69 --- /dev/null +++ b/Svc/Router/Router.cpp @@ -0,0 +1,97 @@ +// ====================================================================== +// \title Router.cpp +// \author thomas-bc +// \brief cpp file for Router component implementation class +// ====================================================================== + +#include "Svc/Router/Router.hpp" +#include "FpConfig.hpp" +#include "Fw/Com/ComPacket.hpp" +#include "Fw/Logger/Logger.hpp" + +namespace Svc { + +// ---------------------------------------------------------------------- +// Component construction and destruction +// ---------------------------------------------------------------------- + +Router ::Router(const char* const compName) : RouterComponentBase(compName) {} + +Router ::~Router() {} + +// ---------------------------------------------------------------------- +// Handler implementations for user-defined typed input ports +// ---------------------------------------------------------------------- + +void Router ::dataIn_handler(NATIVE_INT_TYPE portNum, Fw::Buffer& packetBuffer, Fw::Buffer& contextBuffer) { + // Read the packet type from the packet buffer + FwPacketDescriptorType packetType = Fw::ComPacket::FW_PACKET_UNKNOWN; + Fw::SerializeStatus status = Fw::FW_SERIALIZE_OK; + { + Fw::SerializeBufferBase& serial = packetBuffer.getSerializeRepr(); + status = serial.setBuffLen(packetBuffer.getSize()); + FW_ASSERT(status == Fw::FW_SERIALIZE_OK); + status = serial.deserialize(packetType); + } + + // Whether to deallocate the packet buffer + bool deallocate = true; + + // Process the packet + if (status == Fw::FW_SERIALIZE_OK) { + U8* const packetData = packetBuffer.getData(); + const U32 packetSize = packetBuffer.getSize(); + switch (packetType) { + // Handle a command packet + case Fw::ComPacket::FW_PACKET_COMMAND: { + // Allocate a com buffer on the stack + Fw::ComBuffer com; + // Copy the contents of the packet buffer into the com buffer + status = com.setBuff(packetData, packetSize); + if (status == Fw::FW_SERIALIZE_OK) { + // Send the com buffer + commandOut_out(0, com, 0); + // REVIEW NOTE: Deframer did not check if the output port was connected, should it? + } else { + Fw::Logger::log("[ERROR] Serializing com buffer failed with status %d\n", status); + } + break; + } + // Handle a file packet + case Fw::ComPacket::FW_PACKET_FILE: { + // If the file uplink output port is connected, + // send the file packet. Otherwise take no action. + if (isConnected_fileOut_OutputPort(0)) { + // Shift the packet buffer to skip the packet type + // The FileUplink component does not expect the packet + // type to be there. + packetBuffer.setData(packetData + sizeof(packetType)); + packetBuffer.setSize(static_cast(packetSize - sizeof(packetType))); + // Send the packet buffer + fileOut_out(0, packetBuffer); + // Transfer ownership of the buffer to the receiver + deallocate = false; + } + break; + } + // Take no action for other packet types + default: + break; + } + } else { + Fw::Logger::log("[ERROR] Deserializing packet type failed with status %d\n", status); + } + + if (deallocate) { + // Deallocate the packet buffer + bufferDeallocate_out(0, packetBuffer); + } +} + +void Router ::cmdResponseIn_handler(NATIVE_INT_TYPE portNum, + FwOpcodeType opcode, + U32 cmdSeq, + const Fw::CmdResponse& response) { + // Nothing to do +} +} // namespace Svc diff --git a/Svc/Router/Router.fpp b/Svc/Router/Router.fpp new file mode 100644 index 0000000000..0a2ba55b4a --- /dev/null +++ b/Svc/Router/Router.fpp @@ -0,0 +1,38 @@ +module Svc { + @ Routes packets deframed by the Deframer to the rest of the system + passive component Router { + + @ Receiving Fw::Buffer with context buffer from Deframer + guarded input port dataIn: Fw.DataWithContext + + @ Port for sending file packets as Fw::Buffer (ownership passed to receiver) + output port fileOut: Fw.BufferSend + + @ Port for sending command packets as Fw::ComBuffers + output port commandOut: Fw.Com + + @ Port for deallocating buffers + output port bufferDeallocate: Fw.BufferSend + + @ Port for receiving command responses from a command dispatcher. + @ Invoking this port does nothing. The port exists to allow the matching + @ connection in the topology. + sync input port cmdResponseIn: Fw.CmdResponse + + ############################################################################### + # Standard AC Ports: Required for Channels, Events, Commands, and Parameters # + ############################################################################### + @ Port for requesting the current time + time get port timeCaller + + @ Port for sending textual representation of events + text event port logTextOut + + @ Port for sending events to downlink + event port logOut + + @ Port for sending telemetry channels to downlink + telemetry port tlmOut + + } +} diff --git a/Svc/Router/Router.hpp b/Svc/Router/Router.hpp new file mode 100644 index 0000000000..1b2aaa2342 --- /dev/null +++ b/Svc/Router/Router.hpp @@ -0,0 +1,50 @@ +// ====================================================================== +// \title Router.hpp +// \author thomas-bc +// \brief hpp file for Router component implementation class +// ====================================================================== + +#ifndef Svc_Router_HPP +#define Svc_Router_HPP + +#include "Svc/Router/RouterComponentAc.hpp" + +namespace Svc { + +class Router : public RouterComponentBase { + public: + // ---------------------------------------------------------------------- + // Component construction and destruction + // ---------------------------------------------------------------------- + + //! Construct Router object + Router(const char* const compName //!< The component name + ); + + //! Destroy Router object + ~Router(); + + PRIVATE: + // ---------------------------------------------------------------------- + // Handler implementations for user-defined typed input ports + // ---------------------------------------------------------------------- + + //! Handler implementation for bufferIn + //! Receiving Fw::Buffer from Deframer + void dataIn_handler(NATIVE_INT_TYPE portNum, //!< The port number + Fw::Buffer& packetBuffer, //!< The packet buffer + Fw::Buffer& contextBuffer //!< The context buffer + ) override; + + // ! Handler for input port cmdResponseIn + // ! This is a no-op because Router does not need to handle command responses + // ! but the port must be connected + void cmdResponseIn_handler(NATIVE_INT_TYPE portNum, //!< The port number + FwOpcodeType opcode, //!< The command opcode + U32 cmdSeq, //!< The command sequence number + const Fw::CmdResponse& response //!< The command response + ) override; +}; +} // namespace Svc + +#endif diff --git a/Svc/Router/docs/sdd.md b/Svc/Router/docs/sdd.md new file mode 100644 index 0000000000..09dfbe6805 --- /dev/null +++ b/Svc/Router/docs/sdd.md @@ -0,0 +1,43 @@ +# Svc::Router + +The `Svc::Router` component routes F´ packets (such as command or file packets) to other components. + +The `Svc::Router` component receives F´ packets (as [Fw::Buffer](../../../Fw/Buffer/docs/sdd.md) objects) and routes them to other components through synchronous port calls. The `Svc::Router` component supports `Fw::ComPacket::FW_PACKET_COMMAND` and `Fw::ComPacket::FW_PACKET_FILE` packet types. + +## Usage Examples + +The `Svc::Router` component is used in the uplink stack of many reference F´ application such as [the tutorials source code](https://github.com/fprime-community#tutorials). + +### Typical Usage + +In the canonical uplink communications stack, `Svc::Router` is connected to a [Svc::CmdDispatcher](../../CmdDispatcher/docs/sdd.md) and a [Svc::FileUplink](../../FileUplink/docs/sdd.md) component, to receive Command and File packets respectively. + +![uplink_stack](../../Deframer/docs/img/deframer_uplink_stack.png) + +## Class Diagram + + +```mermaid +classDiagram + class Router~PassiveComponent~ { + + void dataIn_handler(portNum, packetBuffer, contextBuffer) + + void cmdResponseIn_handler(portNum, opcode, cmdSeq, response) + } +``` + +## Port Descriptions + +| Name | Description | Type | +|---|---|---| +| `dataIn: Fw.DataWithContext` | Receiving Fw::Buffer with context buffer from Deframer | `guarded input` | +| `commandOut: Fw.Com` | Port for sending command packets as Fw::ComBuffers | `output` | +| `fileOut: Fw.BufferSend` | Port for sending file packets as Fw::Buffer (ownership passed to receiver) | `output` | + + +## Requirements + +| Name | Description | Rationale | Validation | +|---|---|---|---| +SVC-ROUTER-001 | `Svc::Router` shall route packets based on their packet type as indicated by the packet header | Routing mechanism of the F´ comms protocol | Unit test | +SVC-ROUTER-002 | `Svc::Router` shall route packets with the following types: `Fw::ComPacket::FW_PACKET_COMMAND`, `Fw::ComPacket::FW_PACKET_FILE`. | These are the packet types used for uplink. | Unit test | +SVC-ROUTER-003 | `Svc::Router` shall route command and file packets to the `commandOut` and `fileOut` ports, respectively | Routing mechanism as dictated by the F´ point-to-point architecture. | Unit test | diff --git a/Svc/Router/test/ut/RouterTestMain.cpp b/Svc/Router/test/ut/RouterTestMain.cpp new file mode 100644 index 0000000000..875e6f09b1 --- /dev/null +++ b/Svc/Router/test/ut/RouterTestMain.cpp @@ -0,0 +1,35 @@ +// ====================================================================== +// \title RouterTestMain.cpp +// \author thomas-bc +// \brief cpp file for Router component test main function +// ====================================================================== + +#include "RouterTester.hpp" + +#include + +TEST(Router, TestComInterface) { + COMMENT("Route a com packet"); + Svc::RouterTester tester; + tester.testRouteComInterface(); +} +TEST(Router, TestFileInterface) { + COMMENT("Route a file packet"); + Svc::RouterTester tester; + tester.testRouteFileInterface(); +} +TEST(Router, TestUnknownInterface) { + COMMENT("Attempt to route a packet of unknown type"); + Svc::RouterTester tester; + tester.testRouteUnknownPacket(); +} +TEST(Router, TestCommandResponse) { + COMMENT("Handle a command response (no-op)"); + Svc::RouterTester tester; + tester.testCommandResponse(); +} + +int main(int argc, char** argv) { + ::testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); +} diff --git a/Svc/Router/test/ut/RouterTester.cpp b/Svc/Router/test/ut/RouterTester.cpp new file mode 100644 index 0000000000..9657d4b274 --- /dev/null +++ b/Svc/Router/test/ut/RouterTester.cpp @@ -0,0 +1,68 @@ +// ====================================================================== +// \title RouterTester.cpp +// \author thomas-bc +// \brief cpp file for Router component test harness implementation class +// ====================================================================== + +#include "RouterTester.hpp" + +namespace Svc { + +// ---------------------------------------------------------------------- +// Construction and destruction +// ---------------------------------------------------------------------- + +RouterTester ::RouterTester() : RouterGTestBase("RouterTester", RouterTester::MAX_HISTORY_SIZE), component("Router") { + this->initComponents(); + this->connectPorts(); +} + +RouterTester ::~RouterTester() {} + +// ---------------------------------------------------------------------- +// Test Cases +// ---------------------------------------------------------------------- + +void RouterTester ::testRouteComInterface() { + this->mockReceivePacketType(Fw::ComPacket::FW_PACKET_COMMAND); + ASSERT_from_commandOut_SIZE(1); // one command packet emitted + ASSERT_from_fileOut_SIZE(0); // no file packet emitted + ASSERT_from_bufferDeallocate_SIZE(1); // command packets are deallocated by the router +} + +void RouterTester ::testRouteFileInterface() { + this->mockReceivePacketType(Fw::ComPacket::FW_PACKET_FILE); + ASSERT_from_commandOut_SIZE(0); // no command packet emitted + ASSERT_from_fileOut_SIZE(1); // one file packet emitted + ASSERT_from_bufferDeallocate_SIZE(0); // no deallocation (file packets' ownership is transferred to the receiver) +} + +void RouterTester ::testRouteUnknownPacket() { + this->mockReceivePacketType(Fw::ComPacket::FW_PACKET_UNKNOWN); + ASSERT_from_commandOut_SIZE(0); // no command packet emitted + ASSERT_from_fileOut_SIZE(0); // no file packet emitted + ASSERT_from_bufferDeallocate_SIZE(1); // unknown packets are deallocated and dropped +} + +void RouterTester ::testCommandResponse() { + const U32 opcode = 0; + const U32 cmdSeq = 0; + const Fw::CmdResponse cmdResp(Fw::CmdResponse::OK); + this->invoke_to_cmdResponseIn(0, opcode, cmdSeq, cmdResp); + ASSERT_FROM_PORT_HISTORY_SIZE(0); +} + +// ---------------------------------------------------------------------- +// Test Helper +// ---------------------------------------------------------------------- + +void RouterTester::mockReceivePacketType(Fw::ComPacket::ComPacketType packetType) { + const FwPacketDescriptorType descriptorType = packetType; + U8 data[sizeof descriptorType]; + Fw::Buffer buffer(data, sizeof(data)); + buffer.getSerializeRepr().serialize(descriptorType); + Fw::Buffer nullContext; + this->invoke_to_dataIn(0, buffer, nullContext); +} + +} // namespace Svc diff --git a/Svc/Router/test/ut/RouterTester.hpp b/Svc/Router/test/ut/RouterTester.hpp new file mode 100644 index 0000000000..b7b5e3175d --- /dev/null +++ b/Svc/Router/test/ut/RouterTester.hpp @@ -0,0 +1,82 @@ +// ====================================================================== +// \title RouterTester.hpp +// \author thomas-bc +// \brief hpp file for Router component test harness implementation class +// ====================================================================== + +#ifndef Svc_RouterTester_HPP +#define Svc_RouterTester_HPP + +#include "Svc/Router/Router.hpp" +#include "Svc/Router/RouterGTestBase.hpp" + +#include + +namespace Svc { + +class RouterTester : public RouterGTestBase { + public: + // ---------------------------------------------------------------------- + // Constants + // ---------------------------------------------------------------------- + + // Maximum size of histories storing events, telemetry, and port outputs + static const FwSizeType MAX_HISTORY_SIZE = 10; + + // Instance ID supplied to the component instance under test + static const FwEnumStoreType TEST_INSTANCE_ID = 0; + + public: + // ---------------------------------------------------------------------- + // Construction and destruction + // ---------------------------------------------------------------------- + + //! Construct object RouterTester + RouterTester(); + + //! Destroy object RouterTester + ~RouterTester(); + + public: + // ---------------------------------------------------------------------- + // Tests + // ---------------------------------------------------------------------- + + //! Route a com packet + void testRouteComInterface(); + + //! Route a file packet + void testRouteFileInterface(); + + //! Route a packet of unknown type + void testRouteUnknownPacket(); + + //! Invoke the command response input port + void testCommandResponse(); + + private: + // ---------------------------------------------------------------------- + // Helper functions + // ---------------------------------------------------------------------- + + //! Connect ports + void connectPorts(); + + //! Initialize components + void initComponents(); + + //! Mock the reception of a packet of a specific type + void mockReceivePacketType(Fw::ComPacket::ComPacketType packetType); + + private: + // ---------------------------------------------------------------------- + // Member variables + // ---------------------------------------------------------------------- + + //! The component under test + Router component; +}; + +} // namespace Svc + +#endif diff --git a/Utils/Hash/libcrc/CRC32.cpp b/Utils/Hash/libcrc/CRC32.cpp index 81e94ada5d..0c49f9b390 100644 --- a/Utils/Hash/libcrc/CRC32.cpp +++ b/Utils/Hash/libcrc/CRC32.cpp @@ -12,6 +12,9 @@ #include +static_assert(sizeof(unsigned long) >= sizeof(U32), "CRC32 cannot fit in CRC32 library chosen types"); + + namespace Utils { Hash :: diff --git a/config/FpConfig.h b/config/FpConfig.h index ca65ff2226..ffc4b5666d 100644 --- a/config/FpConfig.h +++ b/config/FpConfig.h @@ -119,6 +119,11 @@ typedef U32 FwDpIdType; typedef U32 FwDpPriorityType; #define PRI_FwDpPriorityType PRIu32 +// Type for start word and length word used in fprime framing +typedef U32 FwFramingTokenType; +#define PRI_FwFramingTokenType PRIu32 + + // ---------------------------------------------------------------------- // Derived type aliases // By default, these types are aliases of types defined above