Use data return pattern on Uplink and standardize port names (#3546)

* First pass at Svc + TcpClient implementation

* Revert FileUplink changes

* Add copy (with allocation/deallocation) to FprimeRouter to simplify buffer management

* Update FprimeRouter UTs

* Update FprimeDeframer UTs

* Update FrameAccumulator UTs

* Update ComStub UTs

* Update missing Drv and UTs

* Update ComInterface to use ComDataWithContext on output

* Update Ref/RPI topology

* Fix spelling

* Fix test typo

* Update Udp component and UTs

* Rename data ports and standardize "Return" naming pattern

* Fix variable name

* Adapt UTs

* Update Communication Adapter Interface docs

* Full SDD updates

* Spelling &  nits and details

* Put formatting back to original

* Update Deframer interface to include bufferReturn

* Address review comments
This commit is contained in:
Thomas Boyer-Chammard 2025-05-08 10:54:54 -07:00 committed by GitHub
parent 53320cd6a5
commit c3b2e04880
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
65 changed files with 685 additions and 368 deletions

View File

@ -8,7 +8,7 @@ The outgoing stream is represented by the input `send` port; other components ca
### Send ### Send
The manager component (for example a radio manager) initiates the transfer of send data by calling the "send" port. The manager component (for example a radio manager) initiates the transfer of send data by calling the "send" port.
The caller will provide a `Fw::Buffer` containing the data to send. The driver component **must** perform a callback on its `dataReturnOut` port to return the status of that send as well as returning ownership of the `Fw::Buffer` to the caller. The caller will provide a `Fw::Buffer` containing the data to send. The driver component **must** perform a callback on its `sendReturnOut` port to return the status of that send as well as returning ownership of the `Fw::Buffer` to the caller.
These responses are an enumeration whose values are described in the following table: These responses are an enumeration whose values are described in the following table:
| Value | Description | Buffer Ownership | | Value | Description | Buffer Ownership |

View File

@ -7,5 +7,8 @@
@ Invoke this port to send data out the driver @ Invoke this port to send data out the driver
guarded input port $send: Fw.BufferSend guarded input port $send: Fw.BufferSend
@ Port invoked to return ownership of sent data back to the sender @ Port returning ownership of data received on $send port
output port dataReturnOut: Drv.ByteStreamData output port sendReturnOut: Drv.ByteStreamData
@ Port receiving back ownership of data sent out on $recv port
guarded input port recvReturnIn: Fw.BufferSend

View File

@ -311,7 +311,12 @@ void LinuxUartDriver ::send_handler(const FwIndexType portNum, Fw::Buffer& serBu
} }
} }
// Return the buffer back to the caller // Return the buffer back to the caller
dataReturnOut_out(0, serBuffer, status); sendReturnOut_out(0, serBuffer, status);
}
void LinuxUartDriver::recvReturnIn_handler(FwIndexType portNum, Fw::Buffer& fwBuffer) {
this->deallocate_out(0, fwBuffer);
} }
void LinuxUartDriver ::serialReadTaskEntry(void* ptr) { void LinuxUartDriver ::serialReadTaskEntry(void* ptr) {

View File

@ -11,6 +11,9 @@ module Drv {
@ Allocation port used for allocating memory in the receive task @ Allocation port used for allocating memory in the receive task
output port allocate: Fw.BufferGet output port allocate: Fw.BufferGet
@ Deallocation of allocated buffers
output port deallocate: Fw.BufferSend
# ---------------------------------------------------------------------- # ----------------------------------------------------------------------
# Special ports # Special ports
# ---------------------------------------------------------------------- # ----------------------------------------------------------------------

View File

@ -94,8 +94,14 @@ class LinuxUartDriver final : public LinuxUartDriverComponentBase {
//! Handler implementation for serialSend //! Handler implementation for serialSend
//! //!
void send_handler(FwIndexType portNum, /*!< The port number*/ void send_handler(FwIndexType portNum, /*!< The port number*/
Fw::Buffer& serBuffer); Fw::Buffer& serBuffer) override;
//! Handler implementation for recvReturnIn
//!
//! Port receiving back ownership of data sent out on $recv port
void recvReturnIn_handler(FwIndexType portNum, //!< The port number
Fw::Buffer& fwBuffer //!< The buffer
) override;
PlatformIntType m_fd; //!< file descriptor returned for I/O device PlatformIntType m_fd; //!< file descriptor returned for I/O device
U32 m_allocationSize; //!< size of allocation request to memory manager U32 m_allocationSize; //!< size of allocation request to memory manager

View File

@ -6,5 +6,8 @@ module Drv {
@ Allocation for received data @ Allocation for received data
output port allocate: Fw.BufferGet output port allocate: Fw.BufferGet
@ Deallocation of allocated buffers
output port deallocate: Fw.BufferSend
} }
} }

View File

@ -91,7 +91,11 @@ void TcpClientComponentImpl::send_handler(const FwIndexType portNum, Fw::Buffer&
break; break;
} }
// Return the buffer and status to the caller // Return the buffer and status to the caller
this->dataReturnOut_out(0, fwBuffer, returnStatus); this->sendReturnOut_out(0, fwBuffer, returnStatus);
}
void TcpClientComponentImpl::recvReturnIn_handler(FwIndexType portNum, Fw::Buffer& fwBuffer) {
this->deallocate_out(0, fwBuffer);
} }
} // end namespace Drv } // end namespace Drv

View File

@ -126,6 +126,14 @@ class TcpClientComponentImpl final : public TcpClientComponentBase, public Socke
*/ */
void send_handler(const FwIndexType portNum, Fw::Buffer& fwBuffer) override; void send_handler(const FwIndexType portNum, Fw::Buffer& fwBuffer) override;
//! Handler implementation for recvReturnIn
//!
//! Port receiving back ownership of data sent out on $recv port
void recvReturnIn_handler(FwIndexType portNum, //!< The port number
Fw::Buffer& fwBuffer //!< The buffer
) override;
Drv::TcpClientSocket m_socket; //!< Socket implementation Drv::TcpClientSocket m_socket; //!< Socket implementation
// Member variable to store the buffer size // Member variable to store the buffer size

View File

@ -15,6 +15,11 @@ TEST(Nominal, TcpClientBasicReceiveThread) {
tester.test_receive_thread(); tester.test_receive_thread();
} }
TEST(Nominal, TcpClientBufferDeallocation) {
Drv::TcpClientTester tester;
tester.test_buffer_deallocation();
}
TEST(Reconnect, TcpClientMultiMessaging) { TEST(Reconnect, TcpClientMultiMessaging) {
Drv::TcpClientTester tester; Drv::TcpClientTester tester;
tester.test_multiple_messaging(); tester.test_multiple_messaging();

View File

@ -80,8 +80,8 @@ void TcpClientTester ::test_with_loop(U32 iterations, bool recv_thread) {
m_data_buffer.setSize(sizeof(m_data_storage)); m_data_buffer.setSize(sizeof(m_data_storage));
size = Drv::Test::fill_random_buffer(m_data_buffer); size = Drv::Test::fill_random_buffer(m_data_buffer);
invoke_to_send(0, m_data_buffer); invoke_to_send(0, m_data_buffer);
ASSERT_from_dataReturnOut_SIZE(i + 1); ASSERT_from_sendReturnOut_SIZE(i + 1);
Drv::ByteStreamStatus status = this->fromPortHistory_dataReturnOut->at(i).status; Drv::ByteStreamStatus status = this->fromPortHistory_sendReturnOut->at(i).status;
EXPECT_EQ(status, ByteStreamStatus::OP_OK); EXPECT_EQ(status, ByteStreamStatus::OP_OK);
Drv::Test::receive_all(server, server_fd, buffer, size); Drv::Test::receive_all(server, server_fd, buffer, size);
Drv::Test::validate_random_buffer(m_data_buffer, buffer); Drv::Test::validate_random_buffer(m_data_buffer, buffer);
@ -180,6 +180,15 @@ void TcpClientTester ::test_no_automatic_recv_connection() {
server.terminate(server_fd); server.terminate(server_fd);
} }
void TcpClientTester ::test_buffer_deallocation() {
U8 data[1];
Fw::Buffer buffer(data, sizeof(data));
this->invoke_to_recvReturnIn(0, buffer);
ASSERT_from_deallocate_SIZE(1); // incoming buffer should be deallocated
ASSERT_EQ(this->fromPortHistory_deallocate->at(0).fwBuffer.getData(), data);
ASSERT_EQ(this->fromPortHistory_deallocate->at(0).fwBuffer.getSize(), sizeof(data));
}
// ---------------------------------------------------------------------- // ----------------------------------------------------------------------
// Handler overrides for typed from ports // Handler overrides for typed from ports
// ---------------------------------------------------------------------- // ----------------------------------------------------------------------

View File

@ -67,6 +67,8 @@ namespace Drv {
void test_with_loop(U32 iterations, bool recv_thread=false); void test_with_loop(U32 iterations, bool recv_thread=false);
void test_buffer_deallocation();
private: private:
// ---------------------------------------------------------------------- // ----------------------------------------------------------------------

View File

@ -6,5 +6,8 @@ module Drv {
@ Allocation for received data @ Allocation for received data
output port allocate: Fw.BufferGet output port allocate: Fw.BufferGet
@ Deallocation of allocated buffers
output port deallocate: Fw.BufferSend
} }
} }

View File

@ -139,7 +139,11 @@ void TcpServerComponentImpl::send_handler(const FwIndexType portNum, Fw::Buffer&
break; break;
} }
// Return the buffer and status to the caller // Return the buffer and status to the caller
this->dataReturnOut_out(0, fwBuffer, returnStatus); this->sendReturnOut_out(0, fwBuffer, returnStatus);
}
void TcpServerComponentImpl::recvReturnIn_handler(FwIndexType portNum, Fw::Buffer& fwBuffer) {
this->deallocate_out(0, fwBuffer);
} }
} // end namespace Drv } // end namespace Drv

View File

@ -161,6 +161,13 @@ class TcpServerComponentImpl final : public TcpServerComponentBase, public Socke
*/ */
void send_handler(const FwIndexType portNum, Fw::Buffer& fwBuffer) override; void send_handler(const FwIndexType portNum, Fw::Buffer& fwBuffer) override;
//! Handler implementation for recvReturnIn
//!
//! Port receiving back ownership of data sent out on $recv port
void recvReturnIn_handler(FwIndexType portNum, //!< The port number
Fw::Buffer& fwBuffer //!< The buffer
) override;
Drv::TcpServerSocket m_socket; //!< Socket implementation Drv::TcpServerSocket m_socket; //!< Socket implementation
FwSizeType m_allocation_size; //!< Member variable to store the buffer size FwSizeType m_allocation_size; //!< Member variable to store the buffer size

View File

@ -4,6 +4,11 @@
#include "TcpServerTester.hpp" #include "TcpServerTester.hpp"
TEST(Nominal, TcpServerBufferDeallocation) {
Drv::TcpServerTester tester;
tester.test_buffer_deallocation();
}
TEST(Nominal, TcpServerBasicMessaging) { TEST(Nominal, TcpServerBasicMessaging) {
Drv::TcpServerTester tester; Drv::TcpServerTester tester;
tester.test_basic_messaging(); tester.test_basic_messaging();

View File

@ -81,8 +81,8 @@ void TcpServerTester ::test_with_loop(U32 iterations, bool recv_thread) {
m_data_buffer.setSize(sizeof(m_data_storage)); m_data_buffer.setSize(sizeof(m_data_storage));
size = Drv::Test::fill_random_buffer(m_data_buffer); size = Drv::Test::fill_random_buffer(m_data_buffer);
invoke_to_send(0, m_data_buffer); invoke_to_send(0, m_data_buffer);
ASSERT_from_dataReturnOut_SIZE(i + 1); ASSERT_from_sendReturnOut_SIZE(i + 1);
Drv::ByteStreamStatus status = this->fromPortHistory_dataReturnOut->at(i).status; Drv::ByteStreamStatus status = this->fromPortHistory_sendReturnOut->at(i).status;
EXPECT_EQ(status, ByteStreamStatus::OP_OK) << EXPECT_EQ(status, ByteStreamStatus::OP_OK) <<
"On iteration: " << i << " and receive thread: " << recv_thread; "On iteration: " << i << " and receive thread: " << recv_thread;
Drv::Test::receive_all(client, client_fd, buffer, size); Drv::Test::receive_all(client, client_fd, buffer, size);
@ -217,6 +217,15 @@ void TcpServerTester ::test_no_automatic_recv_connection() {
this->component.terminate(); this->component.terminate();
} }
void TcpServerTester ::test_buffer_deallocation() {
U8 data[1];
Fw::Buffer buffer(data, sizeof(data));
this->invoke_to_recvReturnIn(0, buffer);
ASSERT_from_deallocate_SIZE(1); // incoming buffer should be deallocated
ASSERT_EQ(this->fromPortHistory_deallocate->at(0).fwBuffer.getData(), data);
ASSERT_EQ(this->fromPortHistory_deallocate->at(0).fwBuffer.getSize(), sizeof(data));
}
// ---------------------------------------------------------------------- // ----------------------------------------------------------------------
// Handlers for typed from ports // Handlers for typed from ports
// ---------------------------------------------------------------------- // ----------------------------------------------------------------------

View File

@ -75,6 +75,8 @@ namespace Drv {
void test_no_automatic_send_connection(); void test_no_automatic_send_connection();
void test_no_automatic_recv_connection(); void test_no_automatic_recv_connection();
void test_buffer_deallocation();
bool wait_on_change(bool open, U32 iterations); bool wait_on_change(bool open, U32 iterations);
bool wait_on_started(bool open, U32 iterations); bool wait_on_started(bool open, U32 iterations);

View File

@ -5,5 +5,7 @@ module Drv {
output port allocate: Fw.BufferGet output port allocate: Fw.BufferGet
output port deallocate: Fw.BufferSend
} }
} }

View File

@ -100,7 +100,11 @@ void UdpComponentImpl::send_handler(const FwIndexType portNum, Fw::Buffer& fwBuf
break; break;
} }
// Return the buffer and status to the caller // Return the buffer and status to the caller
this->dataReturnOut_out(0, fwBuffer, returnStatus); this->sendReturnOut_out(0, fwBuffer, returnStatus);
}
void UdpComponentImpl::recvReturnIn_handler(FwIndexType portNum, Fw::Buffer& fwBuffer) {
this->deallocate_out(0, fwBuffer);
} }
} // end namespace Drv } // end namespace Drv

View File

@ -99,7 +99,7 @@ PROTECTED:
* *
* \return IpSocket reference * \return IpSocket reference
*/ */
IpSocket& getSocketHandler(); IpSocket& getSocketHandler() override;
/** /**
* \brief returns a buffer to fill with data * \brief returns a buffer to fill with data
@ -109,7 +109,7 @@ PROTECTED:
* *
* \return Fw::Buffer to fill with data * \return Fw::Buffer to fill with data
*/ */
Fw::Buffer getBuffer(); Fw::Buffer getBuffer() override;
/** /**
* \brief sends a buffer to be filled with data * \brief sends a buffer to be filled with data
@ -119,12 +119,12 @@ PROTECTED:
* *
* \return Fw::Buffer filled with data to send out * \return Fw::Buffer filled with data to send out
*/ */
void sendBuffer(Fw::Buffer buffer, SocketIpStatus status); void sendBuffer(Fw::Buffer buffer, SocketIpStatus status) override;
/** /**
* \brief called when the IPv4 system has been connected * \brief called when the IPv4 system has been connected
*/ */
void connected(); void connected() override;
PRIVATE: PRIVATE:
@ -146,7 +146,15 @@ PROTECTED:
* \param portNum: fprime port number of the incoming port call * \param portNum: fprime port number of the incoming port call
* \param fwBuffer: buffer containing data to be sent * \param fwBuffer: buffer containing data to be sent
*/ */
void send_handler(const FwIndexType portNum, Fw::Buffer& fwBuffer); void send_handler(const FwIndexType portNum, Fw::Buffer& fwBuffer) override;
//! Handler implementation for recvReturnIn
//!
//! Port receiving back ownership of data sent out on $recv port
void recvReturnIn_handler(FwIndexType portNum, //!< The port number
Fw::Buffer& fwBuffer //!< The buffer
) override;
Drv::UdpSocket m_socket; //!< Socket implementation Drv::UdpSocket m_socket; //!< Socket implementation

View File

@ -24,6 +24,11 @@ TEST(Reconnect, UdpReceiveThreadReconnect) {
tester.test_advanced_reconnect(); tester.test_advanced_reconnect();
} }
TEST(Reconnect, UdpBufferDeallocation) {
Drv::UdpTester tester;
tester.test_buffer_deallocation();
}
int main(int argc, char** argv) { int main(int argc, char** argv) {
::testing::InitGoogleTest(&argc, argv); ::testing::InitGoogleTest(&argc, argv);
return RUN_ALL_TESTS(); return RUN_ALL_TESTS();

View File

@ -92,8 +92,8 @@ void UdpTester::test_with_loop(U32 iterations, bool recv_thread) {
m_data_buffer.setSize(sizeof(m_data_storage)); m_data_buffer.setSize(sizeof(m_data_storage));
size = Drv::Test::fill_random_buffer(m_data_buffer); size = Drv::Test::fill_random_buffer(m_data_buffer);
invoke_to_send(0, m_data_buffer); invoke_to_send(0, m_data_buffer);
ASSERT_from_dataReturnOut_SIZE(i + 1); ASSERT_from_sendReturnOut_SIZE(i + 1);
Drv::ByteStreamStatus status = this->fromPortHistory_dataReturnOut->at(i).status; Drv::ByteStreamStatus status = this->fromPortHistory_sendReturnOut->at(i).status;
EXPECT_EQ(status, ByteStreamStatus::OP_OK); EXPECT_EQ(status, ByteStreamStatus::OP_OK);
Drv::Test::receive_all(udp2, udp2_fd, buffer, size); Drv::Test::receive_all(udp2, udp2_fd, buffer, size);
Drv::Test::validate_random_buffer(m_data_buffer, buffer); Drv::Test::validate_random_buffer(m_data_buffer, buffer);
@ -158,6 +158,15 @@ void UdpTester ::test_advanced_reconnect() {
test_with_loop(10, true); // Up to 10 * RECONNECT_MS test_with_loop(10, true); // Up to 10 * RECONNECT_MS
} }
void UdpTester ::test_buffer_deallocation() {
U8 data[1];
Fw::Buffer buffer(data, sizeof(data));
this->invoke_to_recvReturnIn(0, buffer);
ASSERT_from_deallocate_SIZE(1); // incoming buffer should be deallocated
ASSERT_EQ(this->fromPortHistory_deallocate->at(0).fwBuffer.getData(), data);
ASSERT_EQ(this->fromPortHistory_deallocate->at(0).fwBuffer.getSize(), sizeof(data));
}
// ---------------------------------------------------------------------- // ----------------------------------------------------------------------
// Handlers for typed from ports // Handlers for typed from ports
// ---------------------------------------------------------------------- // ----------------------------------------------------------------------

View File

@ -69,6 +69,9 @@ namespace Drv {
//! //!
void test_advanced_reconnect(); void test_advanced_reconnect();
//! Test buffer deallocation
void test_buffer_deallocation();
// Helpers // Helpers
void test_with_loop(U32 iterations, bool recv_thread=false); void test_with_loop(U32 iterations, bool recv_thread=false);

View File

@ -63,26 +63,26 @@ module RPI {
# ---------------------------------------------------------------------- # ----------------------------------------------------------------------
connections Downlink { connections Downlink {
eventLogger.PktSend -> comQueue.comPacketQueueIn[0] eventLogger.PktSend -> comQueue.comPacketQueueIn[0]
chanTlm.PktSend -> comQueue.comPacketQueueIn[1] chanTlm.PktSend -> comQueue.comPacketQueueIn[1]
fileDownlink.bufferSendOut -> comQueue.bufferQueueIn[0] fileDownlink.bufferSendOut -> comQueue.bufferQueueIn[0]
comQueue.queueSend -> framer.dataIn
comQueue.bufferReturnOut[0] -> fileDownlink.bufferReturn comQueue.bufferReturnOut[0] -> fileDownlink.bufferReturn
framer.dataReturnOut -> comQueue.bufferReturnIn
framer.bufferAllocate -> commsBufferManager.bufferGetCallee comQueue.dataOut -> framer.dataIn
framer.dataReturnOut -> comQueue.dataReturnIn
framer.bufferAllocate -> commsBufferManager.bufferGetCallee
framer.bufferDeallocate -> commsBufferManager.bufferSendIn framer.bufferDeallocate -> commsBufferManager.bufferSendIn
framer.dataOut -> comStub.comDataIn framer.dataOut -> comStub.dataIn
comStub.dataReturnOut -> framer.dataReturnIn comStub.dataReturnOut -> framer.dataReturnIn
comDriver.dataReturnOut -> comStub.dataReturnIn
comDriver.ready -> comStub.drvConnected comStub.drvSendOut -> comDriver.$send
comStub.drvDataOut -> comDriver.$send comDriver.sendReturnOut -> comStub.drvSendReturnIn
comDriver.ready -> comStub.drvConnected
comStub.comStatusOut -> framer.comStatusIn comStub.comStatusOut -> framer.comStatusIn
framer.comStatusOut -> comQueue.comStatusIn framer.comStatusOut -> comQueue.comStatusIn
} }
connections FaultProtection { connections FaultProtection {
@ -125,34 +125,41 @@ module RPI {
rpiDemo.SpiReadWrite -> spiDrv.SpiReadWrite rpiDemo.SpiReadWrite -> spiDrv.SpiReadWrite
} }
connections MemoryAllocations {
comDriver.allocate -> commsBufferManager.bufferGetCallee
fileUplink.bufferSendOut -> commsBufferManager.bufferSendIn
frameAccumulator.bufferAllocate -> commsBufferManager.bufferGetCallee
frameAccumulator.bufferDeallocate -> commsBufferManager.bufferSendIn
fprimeRouter.bufferDeallocate -> commsBufferManager.bufferSendIn
deframer.bufferDeallocate -> commsBufferManager.bufferSendIn
}
connections UART { connections UART {
rpiDemo.UartBuffers -> uartBufferManager.bufferSendIn rpiDemo.UartBuffers -> uartBufferManager.bufferSendIn
rpiDemo.UartWrite -> uartDrv.$send rpiDemo.UartWrite -> uartDrv.$send
uartDrv.$recv -> rpiDemo.UartRead uartDrv.$recv -> rpiDemo.UartRead
uartDrv.allocate -> uartBufferManager.bufferGetCallee uartDrv.allocate -> uartBufferManager.bufferGetCallee
uartDrv.dataReturnOut -> rpiDemo.UartWriteReturn uartDrv.sendReturnOut -> rpiDemo.UartWriteReturn
} }
connections Uplink { connections Uplink {
comDriver.$recv -> comStub.drvDataIn # ComDriver buffer allocations
comStub.comDataOut -> frameAccumulator.dataIn comDriver.allocate -> commsBufferManager.bufferGetCallee
comDriver.deallocate -> commsBufferManager.bufferSendIn
frameAccumulator.frameOut -> deframer.framedIn # ComDriver <-> ComStub
deframer.deframedOut -> fprimeRouter.dataIn comDriver.$recv -> comStub.drvReceiveIn
comStub.drvReceiveReturnOut -> comDriver.recvReturnIn
fprimeRouter.commandOut -> cmdDisp.seqCmdBuff # ComStub <-> FrameAccumulator
fprimeRouter.fileOut -> fileUplink.bufferSendIn comStub.dataOut -> frameAccumulator.dataIn
frameAccumulator.dataReturnOut -> comStub.dataReturnIn
cmdDisp.seqCmdStatus -> fprimeRouter.cmdResponseIn # FrameAccumulator buffer allocations
frameAccumulator.bufferDeallocate -> commsBufferManager.bufferSendIn
frameAccumulator.bufferAllocate -> commsBufferManager.bufferGetCallee
# FrameAccumulator <-> Deframer
frameAccumulator.dataOut -> deframer.dataIn
deframer.dataReturnOut -> frameAccumulator.dataReturnIn
# Deframer <-> Router
deframer.dataOut -> fprimeRouter.dataIn
fprimeRouter.dataReturnOut -> deframer.dataReturnIn
# Router buffer allocations
fprimeRouter.bufferAllocate -> commsBufferManager.bufferGetCallee
fprimeRouter.bufferDeallocate -> commsBufferManager.bufferSendIn
# Router <-> CmdDispatcher/FileUplink
fprimeRouter.commandOut -> cmdDisp.seqCmdBuff
cmdDisp.seqCmdStatus -> fprimeRouter.cmdResponseIn
fprimeRouter.fileOut -> fileUplink.bufferSendIn
fileUplink.bufferSendOut -> fprimeRouter.fileBufferReturnIn
} }
} }

View File

@ -15,6 +15,10 @@ module Ref {
TELEMETRY TELEMETRY
} }
enum Ports_ComBufferQueue {
FILE_DOWNLINK
}
topology Ref { topology Ref {
# ---------------------------------------------------------------------- # ----------------------------------------------------------------------
@ -92,28 +96,30 @@ module Ref {
# ---------------------------------------------------------------------- # ----------------------------------------------------------------------
connections Downlink { connections Downlink {
dpCat.fileOut -> fileDownlink.SendFile # Data Products
dpCat.fileOut -> fileDownlink.SendFile
fileDownlink.FileComplete -> dpCat.fileDone fileDownlink.FileComplete -> dpCat.fileDone
# Inputs to ComQueue (events, telemetry, file)
eventLogger.PktSend -> comQueue.comPacketQueueIn[Ports_ComPacketQueue.EVENTS] eventLogger.PktSend -> comQueue.comPacketQueueIn[Ports_ComPacketQueue.EVENTS]
tlmSend.PktSend -> comQueue.comPacketQueueIn[Ports_ComPacketQueue.TELEMETRY] tlmSend.PktSend -> comQueue.comPacketQueueIn[Ports_ComPacketQueue.TELEMETRY]
fileDownlink.bufferSendOut -> comQueue.bufferQueueIn[0] fileDownlink.bufferSendOut -> comQueue.bufferQueueIn[Ports_ComBufferQueue.FILE_DOWNLINK]
comQueue.bufferReturnOut[0] -> fileDownlink.bufferReturn comQueue.bufferReturnOut[Ports_ComBufferQueue.FILE_DOWNLINK] -> fileDownlink.bufferReturn
# ComQueue <-> Framer
comQueue.queueSend -> fprimeFramer.dataIn comQueue.dataOut -> fprimeFramer.dataIn
fprimeFramer.dataReturnOut -> comQueue.bufferReturnIn fprimeFramer.dataReturnOut -> comQueue.dataReturnIn
fprimeFramer.comStatusOut -> comQueue.comStatusIn # Buffer Management for Framer
fprimeFramer.bufferAllocate -> commsBufferManager.bufferGetCallee
fprimeFramer.bufferAllocate -> commsBufferManager.bufferGetCallee
fprimeFramer.bufferDeallocate -> commsBufferManager.bufferSendIn fprimeFramer.bufferDeallocate -> commsBufferManager.bufferSendIn
# Framer <-> ComStub
fprimeFramer.dataOut -> comStub.comDataIn fprimeFramer.dataOut -> comStub.dataIn
comStub.dataReturnOut -> fprimeFramer.dataReturnIn comStub.dataReturnOut -> fprimeFramer.dataReturnIn
comStub.comStatusOut -> fprimeFramer.comStatusIn # ComStub <-> ComDriver
comStub.drvSendOut -> comDriver.$send
comStub.drvDataOut -> comDriver.$send comDriver.sendReturnOut -> comStub.drvSendReturnIn
comDriver.dataReturnOut -> comStub.dataReturnIn comDriver.ready -> comStub.drvConnected
comDriver.ready -> comStub.drvConnected # ComStatus
comStub.comStatusOut -> fprimeFramer.comStatusIn
fprimeFramer.comStatusOut -> comQueue.comStatusIn
} }
connections FaultProtection { connections FaultProtection {
@ -163,25 +169,32 @@ module Ref {
} }
connections Uplink { connections Uplink {
# ComDriver buffer allocations
comDriver.allocate -> commsBufferManager.bufferGetCallee comDriver.allocate -> commsBufferManager.bufferGetCallee
comDriver.$recv -> comStub.drvDataIn comDriver.deallocate -> commsBufferManager.bufferSendIn
comStub.comDataOut -> frameAccumulator.dataIn # ComDriver <-> ComStub
comDriver.$recv -> comStub.drvReceiveIn
frameAccumulator.frameOut -> deframer.framedIn comStub.drvReceiveReturnOut -> comDriver.recvReturnIn
frameAccumulator.bufferAllocate -> commsBufferManager.bufferGetCallee # ComStub <-> FrameAccumulator
comStub.dataOut -> frameAccumulator.dataIn
frameAccumulator.dataReturnOut -> comStub.dataReturnIn
# FrameAccumulator buffer allocations
frameAccumulator.bufferDeallocate -> commsBufferManager.bufferSendIn frameAccumulator.bufferDeallocate -> commsBufferManager.bufferSendIn
deframer.bufferDeallocate -> commsBufferManager.bufferSendIn frameAccumulator.bufferAllocate -> commsBufferManager.bufferGetCallee
deframer.deframedOut -> fprimeRouter.dataIn # FrameAccumulator <-> Deframer
frameAccumulator.dataOut -> deframer.dataIn
fprimeRouter.commandOut -> cmdDisp.seqCmdBuff deframer.dataReturnOut -> frameAccumulator.dataReturnIn
fprimeRouter.fileOut -> fileUplink.bufferSendIn # Deframer <-> Router
deframer.dataOut -> fprimeRouter.dataIn
fprimeRouter.dataReturnOut -> deframer.dataReturnIn
# Router buffer allocations
fprimeRouter.bufferAllocate -> commsBufferManager.bufferGetCallee
fprimeRouter.bufferDeallocate -> commsBufferManager.bufferSendIn fprimeRouter.bufferDeallocate -> commsBufferManager.bufferSendIn
# Router <-> CmdDispatcher/FileUplink
cmdDisp.seqCmdStatus -> fprimeRouter.cmdResponseIn fprimeRouter.commandOut -> cmdDisp.seqCmdBuff
cmdDisp.seqCmdStatus -> fprimeRouter.cmdResponseIn
fileUplink.bufferSendOut -> commsBufferManager.bufferSendIn fprimeRouter.fileOut -> fileUplink.bufferSendIn
fileUplink.bufferSendOut -> fprimeRouter.fileBufferReturnIn
} }
connections DataProducts { connections DataProducts {

View File

@ -193,7 +193,7 @@ void ComQueue::run_handler(const FwIndexType portNum, U32 context) {
this->tlmWrite_buffQueueDepth(buffQueueDepth); this->tlmWrite_buffQueueDepth(buffQueueDepth);
} }
void ComQueue ::bufferReturnIn_handler(FwIndexType portNum, void ComQueue ::dataReturnIn_handler(FwIndexType portNum,
Fw::Buffer& data, Fw::Buffer& data,
const ComCfg::FrameContext& context) { const ComCfg::FrameContext& context) {
static_assert(std::numeric_limits<FwIndexType>::is_signed, "FwIndexType must be signed"); static_assert(std::numeric_limits<FwIndexType>::is_signed, "FwIndexType must be signed");
@ -205,7 +205,7 @@ void ComQueue ::bufferReturnIn_handler(FwIndexType portNum,
// Failing this assert means that context.apid was modified since ComQueue set it, which should not happen // Failing this assert means that context.apid was modified since ComQueue set it, which should not happen
FW_ASSERT(bufferReturnPortNum < BUFFER_PORT_COUNT, static_cast<FwAssertArgType>(bufferReturnPortNum)); FW_ASSERT(bufferReturnPortNum < BUFFER_PORT_COUNT, static_cast<FwAssertArgType>(bufferReturnPortNum));
if (bufferReturnPortNum >= 0) { if (bufferReturnPortNum >= 0) {
// It is a coding error not to connect the associated bufferReturnOut port for each bufferReturnIn port // It is a coding error not to connect the associated bufferReturnOut port for each dataReturnIn port
FW_ASSERT(this->isConnected_bufferReturnOut_OutputPort(bufferReturnPortNum), static_cast<FwAssertArgType>(bufferReturnPortNum)); FW_ASSERT(this->isConnected_bufferReturnOut_OutputPort(bufferReturnPortNum), static_cast<FwAssertArgType>(bufferReturnPortNum));
// If this is a buffer port, return the buffer to the BufferDownlink // If this is a buffer port, return the buffer to the BufferDownlink
this->bufferReturnOut_out(bufferReturnPortNum, data); this->bufferReturnOut_out(bufferReturnPortNum, data);
@ -263,7 +263,7 @@ void ComQueue::sendComBuffer(Fw::ComBuffer& comBuffer, FwIndexType queueIndex) {
// Context APID is set to the queue index for now. A future implementation may want this to be configurable // Context APID is set to the queue index for now. A future implementation may want this to be configurable
ComCfg::FrameContext context; ComCfg::FrameContext context;
context.setcomQueueIndex(queueIndex); context.setcomQueueIndex(queueIndex);
this->queueSend_out(0, outBuffer, context); this->dataOut_out(0, outBuffer, context);
// Set state to WAITING for the status to come back // Set state to WAITING for the status to come back
this->m_state = WAITING; this->m_state = WAITING;
} }
@ -275,7 +275,7 @@ void ComQueue::sendBuffer(Fw::Buffer& buffer, FwIndexType queueIndex) {
// Context APID is set to the queue index for now. A future implementation may want this to be configurable // Context APID is set to the queue index for now. A future implementation may want this to be configurable
ComCfg::FrameContext context; ComCfg::FrameContext context;
context.setcomQueueIndex(queueIndex); context.setcomQueueIndex(queueIndex);
this->queueSend_out(0, buffer, context); this->dataOut_out(0, buffer, context);
// Set state to WAITING for the status to come back // Set state to WAITING for the status to come back
this->m_state = WAITING; this->m_state = WAITING;

View File

@ -17,7 +17,7 @@ module Svc {
# ---------------------------------------------------------------------- # ----------------------------------------------------------------------
@ Port for emitting data ready to be sent @ Port for emitting data ready to be sent
output port queueSend: Svc.ComDataWithContext output port dataOut: Svc.ComDataWithContext
@ Port for receiving the status signal @ Port for receiving the status signal
async input port comStatusIn: Fw.SuccessCondition async input port comStatusIn: Fw.SuccessCondition
@ -31,9 +31,8 @@ module Svc {
@ Port array for returning ownership of Fw::Buffer to its original sender @ Port array for returning ownership of Fw::Buffer to its original sender
output port bufferReturnOut: [ComQueueBufferPorts] Fw.BufferSend output port bufferReturnOut: [ComQueueBufferPorts] Fw.BufferSend
# It is appropriate for this port to be sync since it is just a passthrough
@ Port for receiving Fw::Buffer whose ownership needs to be handed back @ Port for receiving Fw::Buffer whose ownership needs to be handed back
sync input port bufferReturnIn: Svc.ComDataWithContext sync input port dataReturnIn: Svc.ComDataWithContext
@ Port for scheduling telemetry output @ Port for scheduling telemetry output
async input port run: Svc.Sched drop async input port run: Svc.Sched drop

View File

@ -148,10 +148,10 @@ class ComQueue final : public ComQueueComponentBase {
U32 context /*!<The call order*/ U32 context /*!<The call order*/
) override; ) override;
//! Handler implementation for bufferReturnIn //! Handler implementation for dataReturnIn
//! //!
//! Port for returning ownership of Fw::Buffer to its sender //! Port for returning ownership of Fw::Buffer to its sender
void bufferReturnIn_handler(FwIndexType portNum, //!< The port number void dataReturnIn_handler(FwIndexType portNum, //!< The port number
Fw::Buffer& data, Fw::Buffer& data,
const ComCfg::FrameContext& context) override; const ComCfg::FrameContext& context) override;

View File

@ -24,7 +24,7 @@ Queued messages from the highest priority source port are serviced first and a r
| Requirement | Description | Rationale | Verification Method | | Requirement | Description | Rationale | Verification Method |
|------------------|-----------------------------------------------------------------------------------------------------------------------------------------|-------------------------------------------------------------------------|---------------------| |------------------|-----------------------------------------------------------------------------------------------------------------------------------------|-------------------------------------------------------------------------|---------------------|
| SVC-COMQUEUE-001 | `Svc::ComQueue` shall queue `Fw::Buffer` and `Fw::ComBuffer` received on incoming ports. | The purpose of the queue is to store messages. | Unit Test | | SVC-COMQUEUE-001 | `Svc::ComQueue` shall queue `Fw::Buffer` and `Fw::ComBuffer` received on incoming ports. | The purpose of the queue is to store messages. | Unit Test |
| SVC-COMQUEUE-002 | `Svc::ComQueue` shall output exactly one `Fw::Buffer` (wrapping the queued data units) on a received `Fw::Success::SUCCESS` signal. | `Svc::ComQueue` obeys the communication adapter interface protocol. | Unit Test | | SVC-COMQUEUE-002 | `Svc::ComQueue` shall output exactly one `Fw::Buffer` (wrapping the queued data units) on a received `Fw::Success::SUCCESS` signal. | `Svc::ComQueue` obeys the communication adapter interface protocol. | Unit Test |
| SVC-COMQUEUE-003 | `Svc::ComQueue` shall pause sending on the `Fw::Success::FAILURE` and restart on the next `Fw::Success::SUCCESS` signal. | `Svc::ComQueue` should not sent to a failing communication adapter. | Unit Test | | SVC-COMQUEUE-003 | `Svc::ComQueue` shall pause sending on the `Fw::Success::FAILURE` and restart on the next `Fw::Success::SUCCESS` signal. | `Svc::ComQueue` should not sent to a failing communication adapter. | Unit Test |
| SVC-COMQUEUE-004 | `Svc::ComQueue` shall have a configurable number of `Fw::Com` and `Fw::Buffer` input ports. | `Svc::ComQueue` should be adaptable for a number of projects. | Inspection | | SVC-COMQUEUE-004 | `Svc::ComQueue` shall have a configurable number of `Fw::Com` and `Fw::Buffer` input ports. | `Svc::ComQueue` should be adaptable for a number of projects. | Inspection |
| SVC-COMQUEUE-005 | `Svc::ComQueue` shall select and send the next priority `Fw::Buffer` and `Fw::ComBuffer` message in response to `Fw::Success::SUCCESS`. | `Svc::ComQueue` obeys the communication adapter interface protocol. | Unit test | | SVC-COMQUEUE-005 | `Svc::ComQueue` shall select and send the next priority `Fw::Buffer` and `Fw::ComBuffer` message in response to `Fw::Success::SUCCESS`. | `Svc::ComQueue` obeys the communication adapter interface protocol. | Unit test |
@ -32,7 +32,8 @@ Queued messages from the highest priority source port are serviced first and a r
| SVC-COMQUEUE-007 | `Svc::ComQueue` shall emit a queue overflow event for a given port when the configured depth is exceeded. Messages shall be discarded. | `Svc::ComQueue` needs to indicate off-nominal events. | Unit Test | | SVC-COMQUEUE-007 | `Svc::ComQueue` shall emit a queue overflow event for a given port when the configured depth is exceeded. Messages shall be discarded. | `Svc::ComQueue` needs to indicate off-nominal events. | Unit Test |
| SVC-COMQUEUE-008 | `Svc::ComQueue` shall implement a round robin approach to balance between ports of the same priority. | Allows projects to balance between a set of queues of similar priority. | Unit Test | | SVC-COMQUEUE-008 | `Svc::ComQueue` shall implement a round robin approach to balance between ports of the same priority. | Allows projects to balance between a set of queues of similar priority. | Unit Test |
| SVC-COMQUEUE-009 | `Svc::ComQueue` shall keep track and throttle queue overflow events per port. | Prevents a flood of queue overflow events. | Unit test | | SVC-COMQUEUE-009 | `Svc::ComQueue` shall keep track and throttle queue overflow events per port. | Prevents a flood of queue overflow events. | Unit test |
comPacketQueueIn | SVC-COMQUEUE-010 | `Svc::ComQueue` shall return ownership of incoming buffers once they have been enqueued. | Memory management | Unit test |
## 4. Design ## 4. Design
The diagram below shows the `Svc::ComQueue` component. The diagram below shows the `Svc::ComQueue` component.
@ -43,12 +44,12 @@ The diagram below shows the `Svc::ComQueue` component.
| Kind | Name | Port Type | Usage | | Kind | Name | Port Type | Usage |
|---------------|-------------------|---------------------------------------|----------------------------------------------------------| |---------------|-------------------|---------------------------------------|----------------------------------------------------------|
| `output` | `queueSend` | `Svc.ComDataWithContext` | Port emitting queued messages | | `output` | `dataOut` | `Svc.ComDataWithContext` | Port emitting queued messages |
| `sync input` | `dataReturnIn` | `Svc.ComDataWithContext` | Port retrieving back ownership buffers sent on dataOut |
| `async input` | `comStatusIn` | `Fw.SuccessCondition` | Port for receiving the status signal | | `async input` | `comStatusIn` | `Fw.SuccessCondition` | Port for receiving the status signal |
| `async input` | `comPacketQueueIn`| `[ComQueueComPorts] Fw.Com` | Port array for receiving Fw::ComBuffers | | `async input` | `comPacketQueueIn`| `[ComQueueComPorts] Fw.Com` | Port array for receiving Fw::ComBuffers |
| `async input` | `bufferQueueIn` | `[ComQueueBufferPorts] Fw.BufferSend`| Port array for receiving Fw::Buffers | | `async input` | `bufferQueueIn` | `[ComQueueBufferPorts] Fw.BufferSend` | Port array for receiving Fw::Buffers |
| `sync input` | `bufferReturnIn` | `Svc.ComDataWithContext` | Port for deallocating Fw::Buffer on queue overflow | | `output` | `bufferReturnOut` | `[ComQueueBufferPorts] Fw.BufferSend` | Port array returning ownership of buffers received on bufferQueueIn |
| `output` | `bufferReturnOut` | `Svc.ComDataWithContext` | Port for deallocating Fw::Buffer on queue overflow |
> [!NOTE] > [!NOTE]
> ComQueue also has the port instances for autocoded functionality for events, telemetry and time. > ComQueue also has the port instances for autocoded functionality for events, telemetry and time.

View File

@ -68,7 +68,7 @@ void ComQueueTester ::emitOneAndCheck(FwIndexType expectedIndex,
FwSizeType expectedSize) { FwSizeType expectedSize) {
emitOne(); emitOne();
// Check that the data buffers are identical (size + data) // Check that the data buffers are identical (size + data)
Fw::Buffer emittedBuffer = this->fromPortHistory_queueSend->at(expectedIndex).data; Fw::Buffer emittedBuffer = this->fromPortHistory_dataOut->at(expectedIndex).data;
ASSERT_EQ(expectedSize, emittedBuffer.getSize()); ASSERT_EQ(expectedSize, emittedBuffer.getSize());
for (FwSizeType i = 0; i < expectedSize; i++) { for (FwSizeType i = 0; i < expectedSize; i++) {
ASSERT_EQ(emittedBuffer.getData()[i], expectedData[i]); ASSERT_EQ(emittedBuffer.getData()[i], expectedData[i]);
@ -158,17 +158,17 @@ void ComQueueTester ::testPrioritySend() {
} }
// Check that nothing has yet been sent // Check that nothing has yet been sent
ASSERT_from_queueSend_SIZE(0); ASSERT_from_dataOut_SIZE(0);
for (FwIndexType index = 0; index < ComQueue::TOTAL_PORT_COUNT; index++) { for (FwIndexType index = 0; index < ComQueue::TOTAL_PORT_COUNT; index++) {
U8 orderKey; U8 orderKey;
U32 previousSize = fromPortHistory_queueSend->size(); U32 previousSize = fromPortHistory_dataOut->size();
emitOne(); emitOne();
ASSERT_EQ(fromPortHistory_queueSend->size(), (index + 1)); ASSERT_EQ(fromPortHistory_dataOut->size(), (index + 1));
// Check that the size changed by exactly one // Check that the size changed by exactly one
ASSERT_EQ(fromPortHistory_queueSend->size(), (previousSize + 1)); ASSERT_EQ(fromPortHistory_dataOut->size(), (previousSize + 1));
orderKey = fromPortHistory_queueSend->at(index).data.getData()[0]; orderKey = fromPortHistory_dataOut->at(index).data.getData()[0];
ASSERT_EQ(orderKey, index); ASSERT_EQ(orderKey, index);
} }
clearFromPortHistory(); clearFromPortHistory();
@ -295,7 +295,7 @@ void ComQueueTester ::testReadyFirst() {
invoke_to_comPacketQueueIn(portNum, comBuffer, 0); invoke_to_comPacketQueueIn(portNum, comBuffer, 0);
dispatchAll(); dispatchAll();
Fw::Buffer emittedBuffer = this->fromPortHistory_queueSend->at(portNum).data; Fw::Buffer emittedBuffer = this->fromPortHistory_dataOut->at(portNum).data;
ASSERT_EQ(emittedBuffer.getSize(), comBuffer.getBuffLength()); ASSERT_EQ(emittedBuffer.getSize(), comBuffer.getBuffLength());
for (FwSizeType i = 0; i < emittedBuffer.getSize(); i++) { for (FwSizeType i = 0; i < emittedBuffer.getSize(); i++) {
ASSERT_EQ(emittedBuffer.getData()[i], comBuffer.getBuffAddr()[i]); ASSERT_EQ(emittedBuffer.getData()[i], comBuffer.getBuffAddr()[i]);
@ -307,7 +307,7 @@ void ComQueueTester ::testReadyFirst() {
emitOne(); emitOne();
invoke_to_bufferQueueIn(portNum, buffer); invoke_to_bufferQueueIn(portNum, buffer);
dispatchAll(); dispatchAll();
Fw::Buffer emittedBuffer = this->fromPortHistory_queueSend->at(portNum).data; Fw::Buffer emittedBuffer = this->fromPortHistory_dataOut->at(portNum).data;
ASSERT_EQ(emittedBuffer.getSize(), buffer.getSize()); ASSERT_EQ(emittedBuffer.getSize(), buffer.getSize());
for (FwSizeType i = 0; i < buffer.getSize(); i++) { for (FwSizeType i = 0; i < buffer.getSize(); i++) {
ASSERT_EQ(buffer.getData()[i], emittedBuffer.getData()[i]); ASSERT_EQ(buffer.getData()[i], emittedBuffer.getData()[i]);
@ -328,7 +328,7 @@ void ComQueueTester ::testContextData() {
emitOne(); emitOne();
// Currently, the APID is set to the queue index, which is the same as the port number for COM ports // Currently, the APID is set to the queue index, which is the same as the port number for COM ports
FwIndexType expectedApid = portNum; FwIndexType expectedApid = portNum;
auto emittedContext = this->fromPortHistory_queueSend->at(portNum).context; auto emittedContext = this->fromPortHistory_dataOut->at(portNum).context;
ASSERT_EQ(expectedApid, emittedContext.getcomQueueIndex()); ASSERT_EQ(expectedApid, emittedContext.getcomQueueIndex());
} }
clearFromPortHistory(); clearFromPortHistory();
@ -338,7 +338,7 @@ void ComQueueTester ::testContextData() {
emitOne(); emitOne();
// APID is queue index, which is COM_PORT_COUNT + portNum for BUFFER ports // APID is queue index, which is COM_PORT_COUNT + portNum for BUFFER ports
FwIndexType expectedApid = portNum + ComQueue::COM_PORT_COUNT; FwIndexType expectedApid = portNum + ComQueue::COM_PORT_COUNT;
auto emittedContext = this->fromPortHistory_queueSend->at(portNum).context; auto emittedContext = this->fromPortHistory_dataOut->at(portNum).context;
ASSERT_EQ(expectedApid, emittedContext.getcomQueueIndex()); ASSERT_EQ(expectedApid, emittedContext.getcomQueueIndex());
} }
clearFromPortHistory(); clearFromPortHistory();
@ -354,7 +354,7 @@ void ComQueueTester ::testBufferQueueReturn() {
for(FwIndexType portNum = 0; portNum < ComQueue::TOTAL_PORT_COUNT; portNum++){ for(FwIndexType portNum = 0; portNum < ComQueue::TOTAL_PORT_COUNT; portNum++){
clearFromPortHistory(); clearFromPortHistory();
context.setcomQueueIndex(portNum); context.setcomQueueIndex(portNum);
invoke_to_bufferReturnIn(0, buffer, context); invoke_to_dataReturnIn(0, buffer, context);
// APIDs that correspond to an buffer originating from a Fw.Com port // APIDs that correspond to an buffer originating from a Fw.Com port
// do no get deallocated APIDs that correspond to a Fw.Buffer do // do no get deallocated APIDs that correspond to a Fw.Buffer do
if (portNum < ComQueue::COM_PORT_COUNT) { if (portNum < ComQueue::COM_PORT_COUNT) {

View File

@ -23,10 +23,10 @@ ComStub::~ComStub() {}
// Handler implementations for user-defined typed input ports // Handler implementations for user-defined typed input ports
// ---------------------------------------------------------------------- // ----------------------------------------------------------------------
void ComStub::comDataIn_handler(const FwIndexType portNum, Fw::Buffer& sendBuffer, const ComCfg::FrameContext& context) { void ComStub::dataIn_handler(const FwIndexType portNum, Fw::Buffer& sendBuffer, const ComCfg::FrameContext& context) {
FW_ASSERT(!this->m_reinitialize || !this->isConnected_comStatusOut_OutputPort(0)); // A message should never get here if we need to reinitialize is needed FW_ASSERT(!this->m_reinitialize || !this->isConnected_comStatusOut_OutputPort(0)); // A message should never get here if we need to reinitialize is needed
this->m_storedContext = context; // Store the context of the current message this->m_storedContext = context; // Store the context of the current message
this->drvDataOut_out(0, sendBuffer); this->drvSendOut_out(0, sendBuffer);
} }
void ComStub::drvConnected_handler(const FwIndexType portNum) { void ComStub::drvConnected_handler(const FwIndexType portNum) {
@ -37,15 +37,16 @@ void ComStub::drvConnected_handler(const FwIndexType portNum) {
} }
} }
void ComStub::drvDataIn_handler(const FwIndexType portNum, void ComStub::drvReceiveIn_handler(const FwIndexType portNum,
Fw::Buffer& recvBuffer, Fw::Buffer& recvBuffer,
const Drv::ByteStreamStatus& recvStatus) { const Drv::ByteStreamStatus& recvStatus) {
if (recvStatus.e == Drv::ByteStreamStatus::OP_OK) { if (recvStatus.e == Drv::ByteStreamStatus::OP_OK) {
this->comDataOut_out(0, recvBuffer); ComCfg::FrameContext emptyContext; // ComStub knows nothing about the received bytes, so use an empty context
this->dataOut_out(0, recvBuffer, emptyContext);
} }
} }
void ComStub ::dataReturnIn_handler(FwIndexType portNum, //!< The port number void ComStub ::drvSendReturnIn_handler(FwIndexType portNum, //!< The port number
Fw::Buffer& fwBuffer, //!< The buffer Fw::Buffer& fwBuffer, //!< The buffer
const Drv::ByteStreamStatus& sendStatus) { const Drv::ByteStreamStatus& sendStatus) {
if (sendStatus != Drv::ByteStreamStatus::SEND_RETRY) { if (sendStatus != Drv::ByteStreamStatus::SEND_RETRY) {
@ -60,7 +61,7 @@ void ComStub ::dataReturnIn_handler(FwIndexType portNum, //!< The port number
if (this->m_retry_count < this->RETRY_LIMIT) { if (this->m_retry_count < this->RETRY_LIMIT) {
// If we have not yet retried more than the retry limit, attempt to retry // If we have not yet retried more than the retry limit, attempt to retry
this->m_retry_count++; this->m_retry_count++;
this->drvDataOut_out(0, fwBuffer); this->drvSendOut_out(0, fwBuffer);
} else { } else {
// If retried too many times, return buffer and log failure // If retried too many times, return buffer and log failure
this->dataReturnOut_out(0, fwBuffer, this->m_storedContext); this->dataReturnOut_out(0, fwBuffer, this->m_storedContext);
@ -70,4 +71,8 @@ void ComStub ::dataReturnIn_handler(FwIndexType portNum, //!< The port number
} }
} }
void ComStub ::dataReturnIn_handler(FwIndexType portNum, Fw::Buffer& fwBuffer, const ComCfg::FrameContext& context) {
this->drvReceiveReturnOut_out(0, fwBuffer);
}
} // end namespace Svc } // end namespace Svc

View File

@ -10,14 +10,17 @@ module Svc {
@ Ready signal when driver is connected @ Ready signal when driver is connected
sync input port drvConnected: Drv.ByteStreamReady sync input port drvConnected: Drv.ByteStreamReady
@ Receive (read) data from driver. This gets forwarded to comDataOut @ Receive (read) data from driver. This gets forwarded to dataOut
sync input port drvDataIn: Drv.ByteStreamData sync input port drvReceiveIn: Drv.ByteStreamData
@ Send (write) data to the driver. This gets invoked on comDataIn invocation @ Send (write) data to the driver. This gets invoked on dataIn invocation
output port drvDataOut: Fw.BufferSend output port drvSendOut: Fw.BufferSend
@ Callback from drvDataOut (retrieving status and ownership of sent buffer) @ Callback from drvSendOut (retrieving status and ownership of sent buffer)
sync input port dataReturnIn: Drv.ByteStreamData sync input port drvSendReturnIn: Drv.ByteStreamData
@ Returning ownership of buffer that came in on drvReceiveIn
output port drvReceiveReturnOut: Fw.BufferSend
} }
} }

View File

@ -35,11 +35,11 @@ class ComStub final : public ComStubComponentBase {
// Handler implementations for user-defined typed input ports // Handler implementations for user-defined typed input ports
// ---------------------------------------------------------------------- // ----------------------------------------------------------------------
//! Handler implementation for comDataIn //! Handler implementation for dataIn
//! //!
//! Comms data is coming in meaning there is a request for ComStub to send data on the wire //! Comms data is coming in meaning there is a request for ComStub to send data on the wire
//! For ComStub, this means we send the data to the underlying driver (e.g. TCP/UDP/UART) //! For ComStub, this means we send the data to the underlying driver (e.g. TCP/UDP/UART)
void comDataIn_handler(const FwIndexType portNum, /*!< The port number*/ void dataIn_handler(const FwIndexType portNum, /*!< The port number*/
Fw::Buffer& sendBuffer, Fw::Buffer& sendBuffer,
const ComCfg::FrameContext& context) override; const ComCfg::FrameContext& context) override;
@ -47,18 +47,25 @@ class ComStub final : public ComStubComponentBase {
//! //!
void drvConnected_handler(const FwIndexType portNum) override; void drvConnected_handler(const FwIndexType portNum) override;
//! Handler implementation for drvDataIn //! Handler implementation for drvReceiveIn
//! //!
//! Data is coming in from the driver (meaning it has been read from the wire). //! Data is coming in from the driver (meaning it has been read from the wire).
//! ComStub forwards this to the comDataOut port //! ComStub forwards this to the dataOut port
void drvDataIn_handler(const FwIndexType portNum, void drvReceiveIn_handler(const FwIndexType portNum,
/*!< The port number*/ Fw::Buffer& recvBuffer, /*!< The port number*/ Fw::Buffer& recvBuffer,
const Drv::ByteStreamStatus& recvStatus) override; const Drv::ByteStreamStatus& recvStatus) override;
//! Handler implementation for dataReturnIn //! Handler implementation for dataReturnIn
//! //!
//! Port receiving back ownership of buffer sent out on dataOut
void dataReturnIn_handler(FwIndexType portNum, //!< The port number
Fw::Buffer& fwBuffer, //!< The buffer
const ComCfg::FrameContext& context) override;
//! Handler implementation for drvSendReturnIn
//!
//! Buffer ownership and status returning from a Driver "send" operation //! Buffer ownership and status returning from a Driver "send" operation
void dataReturnIn_handler(FwIndexType portNum, //!< The port number void drvSendReturnIn_handler(FwIndexType portNum, //!< The port number
Fw::Buffer& fwBuffer, //!< The buffer Fw::Buffer& fwBuffer, //!< The buffer
const Drv::ByteStreamStatus& recvStatus) override; const Drv::ByteStreamStatus& recvStatus) override;

View File

@ -55,32 +55,37 @@ be useful
| Kind | Name | Port Type | Usage | | Kind | Name | Port Type | Usage |
|--------------|----------------|-----------------------|-----------------------------------------------------------------------------------| |--------------|----------------|-----------------------|-----------------------------------------------------------------------------------|
| `sync input` | `comDataIn` | `Svc.ComDataWithContext` | Port receiving `Fw::Buffer`s for transmission out `drvDataOut` | | `sync input` | `dataIn` | `Svc.ComDataWithContext` | Port receiving `Fw::Buffer`s for transmission out `drvSendOut` |
| `output` | `comStatusOut` | `Svc.ComStatus` | Port indicating success or failure to attached `Svc::ComQueue` | | `output` | `comStatusOut` | `Svc.ComStatus` | Port indicating success or failure to attached `Svc::ComQueue` |
| `output` | `comDataOut` | `Drv.ByteStreamRecv` | Port providing received `Fw::Buffers` to a potential `Svc::Deframer` | | `output` | `dataOut` | `Svc.ComDataWithContext` | Port providing received `Fw::Buffers` to the broader application (typically a Deframer) |
| `output` | `dataReturnOut` | `Svc.ComDataWithContext` | Port returning ownership of data that came in on `dataIn` |
| `sync input` | `dataReturnIn` | `Svc.ComDataWithContext` | Port receiving back ownership of buffer sent out on `dataOut` |
**Byte Stream Driver Model Ports** **Byte Stream Driver Model Ports**
| Kind | Name | Port Type | Usage | | Kind | Name | Port Type | Usage |
|--------------|----------------|-----------------------|-----------------------------------------------------------------------------------| |--------------|----------------|-----------------------|-----------------------------------------------------------------------------------|
| `sync input` | `drvConnected` | `Drv.ByteStreamReady` | Port called when the underlying driver has connected | | `sync input` | `drvConnected` | `Drv.ByteStreamReady` | Port called when the underlying driver has connected |
| `sync input` | `drvDataIn` | `Drv.ByteStreamRecv` | Port receiving `Fw::Buffers` from underlying communications bus driver | | `sync input` | `drvReceiveIn` | `Drv.ByteStreamRecv` | Port receiving `Fw::Buffers` from underlying communications bus driver |
| `output` | `drvDataOut` | `Drv.ByteStreamSend` | Port providing received `Fw::Buffers` to the underlying communications bus driver | | `output` | `drvSendOut` | `Drv.ByteStreamSend` | Port providing received `Fw::Buffers` to the underlying communications bus driver |
| `sync input` | `drvSendReturnIn` | `Drv.ByteStreamData` | Port receiving status and ownership of buffer sent out on `drvSendOut` |
| `output` | `drvReceiveReturnOut` | `Fw.BufferSend` | Port returning ownership of buffer that came in on `drvReceiveIn` |
### 4.2. State, Configuration, and Runtime Setup ### 4.2. State, Configuration, and Runtime Setup
`Svc::ComStub` has only stores a boolean `m_reinitialize` indicating when it should send `Fw::Success::SUCCESS` in `Svc::ComStub` stores a boolean `m_reinitialize` indicating when it should send `Fw::Success::SUCCESS` in
response to a driver reconnection event. This is to implement the Communication Adapter Protocol of a response to a driver reconnection event. This is to implement the Communication Adapter Protocol of a
[communication adapter interface](../../../docs/reference/communication-adapter-interface.md). [communication adapter interface](../../../docs/reference/communication-adapter-interface.md). It also keeps
track of a `m_retry_count` to limit the number of retries on an attempt to send data.
### 4.3. Port Handlers ### 4.3. Port Handlers
#### 4.3.1 comDataIn #### 4.3.1 dataIn
The `comDataIn` port handler receives an `Fw::Buffer` from the F´ system for transmission to the ground. Typically, it The `dataIn` port handler receives an `Fw::Buffer` from the F´ system for transmission to the ground. Typically, it
is connected to the output of the `Svc::Framer` component. In this `Svc::ComStub` implementation, it passes this is connected to the output of the `Svc::Framer` component. In this `Svc::ComStub` implementation, it passes this
`Fw::Buffer` directly to the `drvDataOut` port. It will retry when that port responds with a `RETRY` request. Otherwise, `Fw::Buffer` directly to the `drvSendOut` port. It will retry when that port responds with a `RETRY` request. Otherwise,
the `comStatusOut` port will be invoked to indicate success or failure. Retries attempts are limited before the port the `comStatusOut` port will be invoked to indicate success or failure. Retries attempts are limited before the port
asserts. asserts.
@ -89,7 +94,7 @@ asserts.
This port receives the connected signal from the driver and responds with exactly one `READY` invocation to the This port receives the connected signal from the driver and responds with exactly one `READY` invocation to the
`comStatusOut` port. This starts downlink. This occurs each time the driver reconnects. `comStatusOut` port. This starts downlink. This occurs each time the driver reconnects.
#### 4.3.1 drvDataIn #### 4.3.1 drvReceiveIn
The `drvDataIn` handler receives data read from the driver and supplies it out the `comDataOut` port. It is usually The `drvReceiveIn` handler receives data read from the driver and supplies it out the `dataOut` port. It is usually
connected to the `Svc::Deframer` component connected to the `Svc::Deframer` component

View File

@ -20,6 +20,11 @@ TEST(Nominal, Fail) {
tester.test_fail(); tester.test_fail();
} }
TEST(Nominal, BufferReturn) {
Svc::ComStubTester tester;
tester.test_buffer_return();
}
TEST(OffNominal, Retry) { TEST(OffNominal, Retry) {
Svc::ComStubTester tester; Svc::ComStubTester tester;
tester.test_retry(); tester.test_retry();

View File

@ -55,14 +55,15 @@ void ComStubTester ::test_basic() {
this->fill(buffer); this->fill(buffer);
// Downlink // Downlink
invoke_to_comDataIn(0, buffer, context); invoke_to_dataIn(0, buffer, context);
ASSERT_from_drvDataOut_SIZE(1); ASSERT_from_drvSendOut_SIZE(1);
ASSERT_from_drvDataOut(0, buffer); ASSERT_from_drvSendOut(0, buffer);
// Uplink // Uplink
invoke_to_drvDataIn(0, buffer, Drv::ByteStreamStatus::OP_OK); ComCfg::FrameContext emptyContext;
ASSERT_from_comDataOut_SIZE(1); invoke_to_drvReceiveIn(0, buffer, Drv::ByteStreamStatus::OP_OK);
ASSERT_from_comDataOut(0, buffer); ASSERT_from_dataOut_SIZE(1);
ASSERT_from_dataOut(0, buffer, emptyContext);
} }
void ComStubTester ::test_fail() { void ComStubTester ::test_fail() {
@ -75,13 +76,13 @@ void ComStubTester ::test_fail() {
ComCfg::FrameContext context; ComCfg::FrameContext context;
// Downlink // Downlink
invoke_to_comDataIn(0, buffer, context); invoke_to_dataIn(0, buffer, context);
ASSERT_from_drvDataOut_SIZE(1); ASSERT_from_drvSendOut_SIZE(1);
ASSERT_from_drvDataOut(0, buffer); ASSERT_from_drvSendOut(0, buffer);
// Uplink // Uplink
invoke_to_drvDataIn(0, buffer, Drv::ByteStreamStatus::OTHER_ERROR); invoke_to_drvReceiveIn(0, buffer, Drv::ByteStreamStatus::OTHER_ERROR);
ASSERT_from_comDataOut_SIZE(0); // receiving failure should not send anything ASSERT_from_dataOut_SIZE(0); // receiving failure should not send anything
} }
void ComStubTester ::test_retry() { void ComStubTester ::test_retry() {
@ -99,16 +100,16 @@ void ComStubTester ::test_retry() {
} }
// Retrying for as many times as the RETRY_LIMIT should be ok // Retrying for as many times as the RETRY_LIMIT should be ok
for (FwIndexType i = 0; i < this->component.RETRY_LIMIT; i++) { for (FwIndexType i = 0; i < this->component.RETRY_LIMIT; i++) {
invoke_to_dataReturnIn(0, buffers[i], Drv::ByteStreamStatus::SEND_RETRY); invoke_to_drvSendReturnIn(0, buffers[i], Drv::ByteStreamStatus::SEND_RETRY);
// Test we have indeed retried (data sent on drvDataOut) // Test we have indeed retried (data sent on drvSendOut)
ASSERT_from_drvDataOut_SIZE(static_cast<U32>(i + 1)); ASSERT_from_drvSendOut_SIZE(static_cast<U32>(i + 1));
ASSERT_from_drvDataOut(static_cast<U32>(i), buffers[i]); ASSERT_from_drvSendOut(static_cast<U32>(i), buffers[i]);
} }
ASSERT_from_drvDataOut_SIZE(static_cast<U32>(this->component.RETRY_LIMIT)); ASSERT_from_drvSendOut_SIZE(static_cast<U32>(this->component.RETRY_LIMIT));
ASSERT_EQ(this->component.m_retry_count, this->component.RETRY_LIMIT); ASSERT_EQ(this->component.m_retry_count, this->component.RETRY_LIMIT);
// Retry one more time should block from retrying and reset retry count // Retry one more time should block from retrying and reset retry count
invoke_to_dataReturnIn(0, buffers[MAX_ITERS - 1], Drv::ByteStreamStatus::SEND_RETRY); invoke_to_drvSendReturnIn(0, buffers[MAX_ITERS - 1], Drv::ByteStreamStatus::SEND_RETRY);
ASSERT_from_drvDataOut_SIZE(static_cast<U32>(this->component.RETRY_LIMIT)); // no drvDataOut sent when SEND_RETRY ASSERT_from_drvSendOut_SIZE(static_cast<U32>(this->component.RETRY_LIMIT)); // no drvSendOut sent when SEND_RETRY
ASSERT_from_dataReturnOut_SIZE(1); // buffer ownership was returned ASSERT_from_dataReturnOut_SIZE(1); // buffer ownership was returned
ASSERT_EQ(this->component.m_retry_count, 0); ASSERT_EQ(this->component.m_retry_count, 0);
} }
@ -116,7 +117,7 @@ void ComStubTester ::test_retry() {
void ComStubTester ::test_retry_reset() { void ComStubTester ::test_retry_reset() {
this->test_initial(); this->test_initial();
FwIndexType MAX_ITERS = static_cast<FwIndexType>(this->component.RETRY_LIMIT + 1); FwIndexType MAX_ITERS = static_cast<FwIndexType>(this->component.RETRY_LIMIT + 1);
U32 expected_drvDataOut_count = 0; U32 expected_drvSendOut_count = 0;
// Make small individual buffers for testing // Make small individual buffers for testing
U8 storage[MAX_ITERS][8]; U8 storage[MAX_ITERS][8];
@ -130,40 +131,51 @@ void ComStubTester ::test_retry_reset() {
// Retrying for as many times as the RETRY_LIMIT should be ok // Retrying for as many times as the RETRY_LIMIT should be ok
for (FwIndexType i = 0; i < this->component.RETRY_LIMIT; i++) { for (FwIndexType i = 0; i < this->component.RETRY_LIMIT; i++) {
invoke_to_dataReturnIn(0, buffers[i], Drv::ByteStreamStatus::SEND_RETRY); invoke_to_drvSendReturnIn(0, buffers[i], Drv::ByteStreamStatus::SEND_RETRY);
ASSERT_from_drvDataOut(expected_drvDataOut_count, buffers[i]); ASSERT_from_drvSendOut(expected_drvSendOut_count, buffers[i]);
expected_drvDataOut_count++; // trick: increment now to use as index prior and size after expected_drvSendOut_count++; // trick: increment now to use as index prior and size after
ASSERT_from_drvDataOut_SIZE(expected_drvDataOut_count); ASSERT_from_drvSendOut_SIZE(expected_drvSendOut_count);
} }
// Now, we receive a OP_OK, which should not retry (drvDataOut should not be called) and reset the retry count // Now, we receive a OP_OK, which should not retry (drvSendOut should not be called) and reset the retry count
ASSERT_from_drvDataOut_SIZE(expected_drvDataOut_count); // no drvDataOut sent when OP_OK ASSERT_from_drvSendOut_SIZE(expected_drvSendOut_count); // no drvSendOut sent when OP_OK
invoke_to_dataReturnIn(0, buffers[0], Drv::ByteStreamStatus::OP_OK); invoke_to_drvSendReturnIn(0, buffers[0], Drv::ByteStreamStatus::OP_OK);
ASSERT_from_drvDataOut_SIZE(expected_drvDataOut_count); // no drvDataOut sent when OP_OK ASSERT_from_drvSendOut_SIZE(expected_drvSendOut_count); // no drvSendOut sent when OP_OK
// Now that retry count is reset, we can retry again without a problem // Now that retry count is reset, we can retry again without a problem
for (FwIndexType i = 0; i < this->component.RETRY_LIMIT; i++) { for (FwIndexType i = 0; i < this->component.RETRY_LIMIT; i++) {
invoke_to_dataReturnIn(0, buffers[i], Drv::ByteStreamStatus::SEND_RETRY); invoke_to_drvSendReturnIn(0, buffers[i], Drv::ByteStreamStatus::SEND_RETRY);
ASSERT_from_drvDataOut(expected_drvDataOut_count, buffers[i]); ASSERT_from_drvSendOut(expected_drvSendOut_count, buffers[i]);
expected_drvDataOut_count++; // trick: increment now to use as index prior and size after expected_drvSendOut_count++; // trick: increment now to use as index prior and size after
ASSERT_from_drvDataOut_SIZE(expected_drvDataOut_count); ASSERT_from_drvSendOut_SIZE(expected_drvSendOut_count);
} }
ASSERT_from_drvDataOut_SIZE(expected_drvDataOut_count); // no drvDataOut sent when OP_OK ASSERT_from_drvSendOut_SIZE(expected_drvSendOut_count); // no drvSendOut sent when OP_OK
}
void ComStubTester ::test_buffer_return() {
U8 data[1];
Fw::Buffer buffer(data, sizeof(data));
ComCfg::FrameContext context;
this->invoke_to_dataReturnIn(0, buffer, context);
ASSERT_from_drvReceiveReturnOut_SIZE(1); // incoming buffer should be returned
ASSERT_EQ(this->fromPortHistory_drvReceiveReturnOut->at(0).fwBuffer.getData(), data);
ASSERT_EQ(this->fromPortHistory_drvReceiveReturnOut->at(0).fwBuffer.getSize(), sizeof(data));
} }
// ---------------------------------------------------------------------- // ----------------------------------------------------------------------
// Handlers for typed from ports // Handlers for typed from ports
// ---------------------------------------------------------------------- // ----------------------------------------------------------------------
void ComStubTester ::from_comDataOut_handler(const FwIndexType portNum, void ComStubTester ::from_dataOut_handler(const FwIndexType portNum,
Fw::Buffer& recvBuffer) { Fw::Buffer& recvBuffer,
this->pushFromPortEntry_comDataOut(recvBuffer); const ComCfg::FrameContext& context) {
this->pushFromPortEntry_dataOut(recvBuffer, context);
} }
void ComStubTester ::from_comStatusOut_handler(const FwIndexType portNum, Fw::Success& condition) { void ComStubTester ::from_comStatusOut_handler(const FwIndexType portNum, Fw::Success& condition) {
this->pushFromPortEntry_comStatusOut(condition); this->pushFromPortEntry_comStatusOut(condition);
} }
void ComStubTester ::from_drvDataOut_handler(const FwIndexType portNum, Fw::Buffer& sendBuffer) { void ComStubTester ::from_drvSendOut_handler(const FwIndexType portNum, Fw::Buffer& sendBuffer) {
this->pushFromPortEntry_drvDataOut(sendBuffer); this->pushFromPortEntry_drvSendOut(sendBuffer);
} }

View File

@ -60,15 +60,20 @@ class ComStubTester : public ComStubGTestBase {
//! //!
void test_retry_reset(); void test_retry_reset();
//! Tests buffer is returned
//!
void test_buffer_return();
private: private:
// ---------------------------------------------------------------------- // ----------------------------------------------------------------------
// Handlers for typed from ports // Handlers for typed from ports
// ---------------------------------------------------------------------- // ----------------------------------------------------------------------
//! Handler for from_comDataOut //! Handler for from_dataOut
//! //!
void from_comDataOut_handler(const FwIndexType portNum, //!< The port number void from_dataOut_handler(const FwIndexType portNum, //!< The port number
Fw::Buffer& recvBuffer); Fw::Buffer& recvBuffer,
const ComCfg::FrameContext& context //!< The context
);
//! Handler for from_comStatusOut //! Handler for from_comStatusOut
//! //!
@ -76,9 +81,9 @@ class ComStubTester : public ComStubGTestBase {
Fw::Success& condition //!< Status of communication state Fw::Success& condition //!< Status of communication state
); );
//! Handler for from_drvDataOut //! Handler for from_drvSendOut
//! //!
void from_drvDataOut_handler(const FwIndexType portNum, //!< The port number void from_drvSendOut_handler(const FwIndexType portNum, //!< The port number
Fw::Buffer& sendBuffer); Fw::Buffer& sendBuffer);
private: private:

View File

@ -25,11 +25,11 @@ FprimeDeframer ::~FprimeDeframer() {}
// Handler implementations for user-defined typed input ports // Handler implementations for user-defined typed input ports
// ---------------------------------------------------------------------- // ----------------------------------------------------------------------
void FprimeDeframer ::framedIn_handler(FwIndexType portNum, Fw::Buffer& data, const ComCfg::FrameContext& context) { void FprimeDeframer ::dataIn_handler(FwIndexType portNum, Fw::Buffer& data, const ComCfg::FrameContext& context) {
if (data.getSize() < FprimeProtocol::FrameHeader::SERIALIZED_SIZE + FprimeProtocol::FrameTrailer::SERIALIZED_SIZE) { if (data.getSize() < FprimeProtocol::FrameHeader::SERIALIZED_SIZE + FprimeProtocol::FrameTrailer::SERIALIZED_SIZE) {
// Incoming buffer is not long enough to contain a valid frame (header+trailer) // Incoming buffer is not long enough to contain a valid frame (header+trailer)
this->log_WARNING_HI_InvalidBufferReceived(); this->log_WARNING_HI_InvalidBufferReceived();
this->bufferDeallocate_out(0, data); // drop the frame this->dataReturnOut_out(0, data, context); // drop the frame
return; return;
} }
@ -46,7 +46,7 @@ void FprimeDeframer ::framedIn_handler(FwIndexType portNum, Fw::Buffer& data, co
const FprimeProtocol::FrameHeader defaultValue; const FprimeProtocol::FrameHeader defaultValue;
if (header.getstartWord() != defaultValue.getstartWord()) { if (header.getstartWord() != defaultValue.getstartWord()) {
this->log_WARNING_HI_InvalidStartWord(); this->log_WARNING_HI_InvalidStartWord();
this->bufferDeallocate_out(0, data); this->dataReturnOut_out(0, data, context); // drop the frame
return; return;
} }
// We expect the frame size to be size of header + body (of size specified in header) + trailer // We expect the frame size to be size of header + body (of size specified in header) + trailer
@ -54,7 +54,7 @@ void FprimeDeframer ::framedIn_handler(FwIndexType portNum, Fw::Buffer& data, co
FprimeProtocol::FrameTrailer::SERIALIZED_SIZE; FprimeProtocol::FrameTrailer::SERIALIZED_SIZE;
if (data.getSize() < expectedFrameSize) { if (data.getSize() < expectedFrameSize) {
this->log_WARNING_HI_InvalidLengthReceived(); this->log_WARNING_HI_InvalidLengthReceived();
this->bufferDeallocate_out(0, data); this->dataReturnOut_out(0, data, context); // drop the frame
return; return;
} }
@ -77,7 +77,7 @@ void FprimeDeframer ::framedIn_handler(FwIndexType portNum, Fw::Buffer& data, co
// Check that the CRC in the trailer of the frame matches the computed CRC // Check that the CRC in the trailer of the frame matches the computed CRC
if (trailer.getcrcField() != computedCrc.asBigEndianU32()) { if (trailer.getcrcField() != computedCrc.asBigEndianU32()) {
this->log_WARNING_HI_InvalidChecksum(); this->log_WARNING_HI_InvalidChecksum();
this->bufferDeallocate_out(0, data); this->dataReturnOut_out(0, data, context); // drop the frame
return; return;
} }
@ -88,7 +88,12 @@ void FprimeDeframer ::framedIn_handler(FwIndexType portNum, Fw::Buffer& data, co
data.setSize(data.getSize() - FprimeProtocol::FrameHeader::SERIALIZED_SIZE - data.setSize(data.getSize() - FprimeProtocol::FrameHeader::SERIALIZED_SIZE -
FprimeProtocol::FrameTrailer::SERIALIZED_SIZE); FprimeProtocol::FrameTrailer::SERIALIZED_SIZE);
// Emit the deframed data // Emit the deframed data
this->deframedOut_out(0, data, context); this->dataOut_out(0, data, context);
} }
void FprimeDeframer ::dataReturnIn_handler(FwIndexType portNum, Fw::Buffer& fwBuffer, const ComCfg::FrameContext& context) {
this->dataReturnOut_out(0, fwBuffer, context);
}
} // namespace Svc } // namespace Svc

View File

@ -10,9 +10,6 @@ module Svc {
include "../Interfaces/DeframerInterface.fppi" include "../Interfaces/DeframerInterface.fppi"
@ Port for deallocating dropped frames
output port bufferDeallocate: Fw.BufferSend
@ An invalid frame was received (too short to be a frame) @ An invalid frame was received (too short to be a frame)
event InvalidBufferReceived \ event InvalidBufferReceived \
severity warning high \ severity warning high \

View File

@ -36,9 +36,18 @@ class FprimeDeframer final : public FprimeDeframerComponentBase {
//! //!
//! Port to receive framed data. The handler will strip the header and trailer from the frame //! Port to receive framed data. The handler will strip the header and trailer from the frame
//! and pass the deframed data to the deframed output port. //! and pass the deframed data to the deframed output port.
void framedIn_handler(FwIndexType portNum, //!< The port number void dataIn_handler(FwIndexType portNum, //!< The port number
Fw::Buffer& data, Fw::Buffer& data,
const ComCfg::FrameContext& context) override; const ComCfg::FrameContext& context) override;
//! Handler implementation for dataReturnIn
//!
//! Port receiving back ownership of sent frame buffers
void dataReturnIn_handler(FwIndexType portNum, //!< The port number
Fw::Buffer& data, //!< The buffer
const ComCfg::FrameContext& context) override;
}; };
} // namespace Svc } // namespace Svc

View File

@ -2,15 +2,13 @@
The `Svc::FprimeDeframer` component receives F´ frames on its input port, takes off the header and trailer (sometimes referred to as "footer"), and passes the encapsulated payload to a downstream component (usually the [`Svc.FprimeRouter`](../../FprimeRouter/docs/sdd.md)). The `Svc::FprimeDeframer` component receives F´ frames on its input port, takes off the header and trailer (sometimes referred to as "footer"), and passes the encapsulated payload to a downstream component (usually the [`Svc.FprimeRouter`](../../FprimeRouter/docs/sdd.md)).
Following the [F Prime Protocol frame specification](../../FprimeProtocol/docs/sdd.md), the `Svc::FprimeDeframer` validates the passed in `Fw.Buffer` to ensure it represents a valid frame (see [Frame validation](#frame-validation)), extract the payload from the frame, and outputs the payload on the `deframedOut` output port. Following the [F Prime Protocol frame specification](../../FprimeProtocol/docs/sdd.md), the `Svc::FprimeDeframer` validates the passed in `Fw.Buffer` to ensure it represents a valid frame (see [Frame validation](#frame-validation)), extract the payload from the frame, and outputs the payload on the `dataOut` output port.
## Internals ## Internals
The `Svc::FprimeDeframer` 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. The `Svc::FprimeDeframer` 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 `dataIn` input port, modifies the input buffer to remove the header and trailer, and sends it out through its `dataOut` output port.
Ownership of the buffer is transferred to the component connected to the `deframedOut` output port. The input buffer is modified by subtracting the header and trailer size from the buffer's length, and offsetting the buffer's data pointer to point to the start of the packet data. Ownership of the buffer is transferred to the component connected to the `dataOut` output port. The input buffer is modified by subtracting the header and trailer size from the buffer's length, and offsetting the buffer's data pointer to point to the start of the packet data.
The `Svc::FprimeDeframer` 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).
The `Svc::FprimeDeframer` does not support deframing multiple packets in a single frame (i.e. concatenated packets) as this is not supported by the F´ communications protocol. The `Svc::FprimeDeframer` does not support deframing multiple packets in a single frame (i.e. concatenated packets) as this is not supported by the F´ communications protocol.
@ -28,35 +26,25 @@ If any of these conditions are not met, the frame is dropped meaning no payload
The `Svc::FprimeDeframer` component is used in the uplink stack of many reference F´ application such as [the tutorials source code](https://github.com/fprime-community#tutorials). The `Svc::FprimeDeframer` 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 ## Diagrams
The below diagram shows a typical configuration in which the `Svc::FprimeDeframer` 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::FprimeRouter](../../FprimeRouter/docs/sdd.md) for routing to other components. The below diagram shows a typical configuration in which the `Svc::FprimeDeframer` 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::FprimeRouter](../../FprimeRouter/docs/sdd.md) for routing to other components.
![./img/deframer_uplink_stack.png](./img/deframer_uplink_stack.png) ![./img/deframer_uplink_stack.png](./img/deframer_uplink_stack.png)
## Class Diagram
```mermaid
classDiagram
class FprimeDeframer~PassiveComponent~ {
+ void framedIn_handler(FwIndexType portNum, Fw::Buffer& data, const ComCfg::FrameContext& context)
}
```
## Requirements ## Requirements
Requirement | Description | Rationale | Verification Method Requirement | Description | Rationale | Verification Method
----------- | ----------- | ----------| ------------------- ----------- | ----------- | ----------| -------------------
SVC-DEFRAMER-001 | `Svc::FprimeDeframer` shall extract the payload field from input buffers that represent a valid F Prime frame as specified by the [F Prime Protocol](../../FprimeProtocol/docs/sdd.md) | Deframe valid frames and extract payload | Unit test | SVC-DEFRAMER-001 | `Svc::FprimeDeframer` shall extract the payload field from input buffers that represent a valid F Prime frame as specified by the [F Prime Protocol](../../FprimeProtocol/docs/sdd.md) | Deframe valid frames and extract payload | Unit test |
SVC-DEFRAMER-002 | `Svc::FprimeDeframer` shall deallocate input buffers that are not a valid F Prime frame as specified by the [F Prime Protocol](../../FprimeProtocol/docs/sdd.md) | Drop invalid frames | Unit test | SVC-DEFRAMER-002 | `Svc::FprimeDeframer` shall return ownership of input buffers that are not a valid F Prime frame as specified by the [F Prime Protocol](../../FprimeProtocol/docs/sdd.md) | Drop invalid frames | Unit test |
## Port Descriptions ## Port Descriptions
| Kind | Name | Type | Description | | Kind | Name | Type | Description |
|---|---|---|---| |---|---|---|---|
| `guarded input` | framedIn | `Svc.ComDataWithContext` | Receives a frame with optional context data | | `guarded input` | `dataIn` | `Svc.ComDataWithContext` | Receives a frame for deframing |
| `output` | deframedOut | `Svc.ComDataWithContext` | Receives a frame with optional context data | | `output` | `dataOut` | `Svc.ComDataWithContext` | Emits deframed data (F´ packets) |
| `output` | bufferDeallocate | `Fw.BufferSend` | Port for deallocating dropped frames | | `sync input` | `dataReturnIn` | `Svc.ComDataWithContext` | Receives ownership of the emitted data back |
| `output` | `dataReturnOut` | `Svc.ComDataWithContext` | Returns ownership of the input buffer back to the sender |

View File

@ -37,6 +37,11 @@ TEST(FprimeDeframer, testIncorrectCrc) {
tester.testIncorrectCrc(); tester.testIncorrectCrc();
} }
TEST(FprimeDeframer, testDataReturn) {
Svc::FprimeDeframerTester tester;
tester.testDataReturn();
}
int main(int argc, char** argv) { int main(int argc, char** argv) {
STest::Random::seed(); STest::Random::seed();
::testing::InitGoogleTest(&argc, argv); ::testing::InitGoogleTest(&argc, argv);

View File

@ -34,10 +34,10 @@ void FprimeDeframerTester ::testNominalFrame() {
this->injectChecksum(data, sizeof(data)); this->injectChecksum(data, sizeof(data));
this->mockReceiveData(data, sizeof(data)); this->mockReceiveData(data, sizeof(data));
ASSERT_from_deframedOut_SIZE(1); // something emitted on deframedOut ASSERT_from_dataOut_SIZE(1); // something emitted on dataOut
ASSERT_from_bufferDeallocate_SIZE(0); // nothing emitted on bufferDeallocate ASSERT_from_dataReturnOut_SIZE(0); // nothing emitted on dataReturnOut
// Assert that the data that was emitted on deframedOut is equal to Data field above (randomByte) // Assert that the data that was emitted on dataOut is equal to Data field above (randomByte)
ASSERT_EQ(this->fromPortHistory_deframedOut->at(0).data.getData()[0], randomByte); ASSERT_EQ(this->fromPortHistory_dataOut->at(0).data.getData()[0], randomByte);
ASSERT_EVENTS_SIZE(0); // no events emitted ASSERT_EVENTS_SIZE(0); // no events emitted
} }
@ -48,8 +48,8 @@ void FprimeDeframerTester ::testIncorrectLengthToken() {
this->injectChecksum(data, sizeof(data)); this->injectChecksum(data, sizeof(data));
this->mockReceiveData(data, sizeof(data)); this->mockReceiveData(data, sizeof(data));
ASSERT_from_deframedOut_SIZE(0); // nothing emitted on deframedOut ASSERT_from_dataOut_SIZE(0); // nothing emitted on dataOut
ASSERT_from_bufferDeallocate_SIZE(1); // invalid buffer was deallocated ASSERT_from_dataReturnOut_SIZE(1); // invalid buffer was deallocated
// Check which event was emitted // Check which event was emitted
ASSERT_EVENTS_SIZE(1); // exactly 1 event emitted ASSERT_EVENTS_SIZE(1); // exactly 1 event emitted
ASSERT_EVENTS_InvalidLengthReceived_SIZE(1); // event was emitted for invalid length ASSERT_EVENTS_InvalidLengthReceived_SIZE(1); // event was emitted for invalid length
@ -62,8 +62,8 @@ void FprimeDeframerTester ::testIncorrectStartWord() {
this->injectChecksum(data, sizeof(data)); this->injectChecksum(data, sizeof(data));
this->mockReceiveData(data, sizeof(data)); this->mockReceiveData(data, sizeof(data));
ASSERT_from_deframedOut_SIZE(0); // nothing emitted on deframedOut ASSERT_from_dataOut_SIZE(0); // nothing emitted on dataOut
ASSERT_from_bufferDeallocate_SIZE(1); // invalid buffer was deallocated ASSERT_from_dataReturnOut_SIZE(1); // invalid buffer was deallocated
// Check which event was emitted // Check which event was emitted
ASSERT_EVENTS_SIZE(1); // exactly 1 event emitted ASSERT_EVENTS_SIZE(1); // exactly 1 event emitted
ASSERT_EVENTS_InvalidStartWord_SIZE(1); // event was emitted for invalid start word ASSERT_EVENTS_InvalidStartWord_SIZE(1); // event was emitted for invalid start word
@ -73,8 +73,8 @@ void FprimeDeframerTester ::testIncorrectCrc() {
// Frame: | F´ start word | Length = 1 | Data | INCORRECT Checksum | // Frame: | F´ start word | Length = 1 | Data | INCORRECT Checksum |
U8 data[13] = {0xDE, 0xAD, 0xBE, 0xEF, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00}; U8 data[13] = {0xDE, 0xAD, 0xBE, 0xEF, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00};
this->mockReceiveData(data, sizeof(data)); this->mockReceiveData(data, sizeof(data));
ASSERT_from_deframedOut_SIZE(0); // nothing emitted on deframedOut ASSERT_from_dataOut_SIZE(0); // nothing emitted on dataOut
ASSERT_from_bufferDeallocate_SIZE(1); // invalid buffer was deallocated ASSERT_from_dataReturnOut_SIZE(1); // invalid buffer was deallocated
// Check which event was emitted // Check which event was emitted
ASSERT_EVENTS_SIZE(1); // exactly 1 event emitted ASSERT_EVENTS_SIZE(1); // exactly 1 event emitted
ASSERT_EVENTS_InvalidChecksum_SIZE(1); // event was emitted for invalid checksum ASSERT_EVENTS_InvalidChecksum_SIZE(1); // event was emitted for invalid checksum
@ -84,8 +84,8 @@ void FprimeDeframerTester::testTruncatedFrame() {
// Send a truncated frame, too short to be valid // Send a truncated frame, too short to be valid
U8 data[11] = {0xDE, 0xAD, 0xBE, 0xEF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; U8 data[11] = {0xDE, 0xAD, 0xBE, 0xEF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
this->mockReceiveData(data, sizeof(data)); this->mockReceiveData(data, sizeof(data));
ASSERT_from_deframedOut_SIZE(0); // nothing emitted on deframedOut ASSERT_from_dataOut_SIZE(0); // nothing emitted on dataOut
ASSERT_from_bufferDeallocate_SIZE(1); // invalid buffer was deallocated ASSERT_from_dataReturnOut_SIZE(1); // invalid buffer was deallocated
// Check which event was emitted // Check which event was emitted
ASSERT_EVENTS_SIZE(1); // exactly 1 event emitted ASSERT_EVENTS_SIZE(1); // exactly 1 event emitted
ASSERT_EVENTS_InvalidBufferReceived_SIZE(1); // event was emitted for invalid buffer ASSERT_EVENTS_InvalidBufferReceived_SIZE(1); // event was emitted for invalid buffer
@ -94,13 +94,23 @@ void FprimeDeframerTester::testTruncatedFrame() {
void FprimeDeframerTester::testZeroSizeFrame() { void FprimeDeframerTester::testZeroSizeFrame() {
// Send an empty frame, too short to be valid // Send an empty frame, too short to be valid
this->mockReceiveData(nullptr, 0); this->mockReceiveData(nullptr, 0);
ASSERT_from_deframedOut_SIZE(0); // nothing emitted on deframedOut ASSERT_from_dataOut_SIZE(0); // nothing emitted on dataOut
ASSERT_from_bufferDeallocate_SIZE(1); // invalid buffer was deallocated ASSERT_from_dataReturnOut_SIZE(1); // invalid buffer was deallocated
// Check which event was emitted // Check which event was emitted
ASSERT_EVENTS_SIZE(1); // exactly 1 event emitted ASSERT_EVENTS_SIZE(1); // exactly 1 event emitted
ASSERT_EVENTS_InvalidBufferReceived_SIZE(1); // event was emitted for invalid buffer ASSERT_EVENTS_InvalidBufferReceived_SIZE(1); // event was emitted for invalid buffer
} }
void FprimeDeframerTester::testDataReturn() {
U8 data[1];
Fw::Buffer buffer(data, sizeof(data));
ComCfg::FrameContext nullContext;
this->invoke_to_dataReturnIn(0, buffer, nullContext);
ASSERT_from_dataReturnOut_SIZE(1); // incoming buffer should be deallocated
ASSERT_EQ(this->fromPortHistory_dataReturnOut->at(0).data.getData(), data);
ASSERT_EQ(this->fromPortHistory_dataReturnOut->at(0).data.getSize(), sizeof(data));
}
// ---------------------------------------------------------------------- // ----------------------------------------------------------------------
// Test Helpers // Test Helpers
// ---------------------------------------------------------------------- // ----------------------------------------------------------------------
@ -124,7 +134,7 @@ void FprimeDeframerTester::injectChecksum(U8* data, FwSizeType size) {
void FprimeDeframerTester::mockReceiveData(U8* data, FwSizeType size) { void FprimeDeframerTester::mockReceiveData(U8* data, FwSizeType size) {
ComCfg::FrameContext nullContext; ComCfg::FrameContext nullContext;
Fw::Buffer buffer(data, static_cast<Fw::Buffer::SizeType>(size)); Fw::Buffer buffer(data, static_cast<Fw::Buffer::SizeType>(size));
this->invoke_to_framedIn(0, buffer, nullContext); this->invoke_to_dataIn(0, buffer, nullContext);
} }
} // namespace Svc } // namespace Svc

View File

@ -58,6 +58,9 @@ class FprimeDeframerTester : public FprimeDeframerGTestBase {
//! Test receiving a frame with an incorrect Crc field //! Test receiving a frame with an incorrect Crc field
void testIncorrectCrc(); void testIncorrectCrc();
//! Test bufferReturn passthrough
void testDataReturn();
private: private:
// ---------------------------------------------------------------------- // ----------------------------------------------------------------------
// Helper functions // Helper functions

View File

@ -4,11 +4,11 @@ The `Svc::FprimeFramer` is an implementation of the [FramerInterface](../../Inte
It receives data (an F´ packet) on input and produces an [F´ frame](../../FprimeProtocol/docs/sdd.md) on its output port as a result. Please refer to the [F Prime frame specification](../../FprimeProtocol/docs/sdd.md) for details on the frame format. It receives data (an F´ packet) on input and produces an [F´ frame](../../FprimeProtocol/docs/sdd.md) on its output port as a result. Please refer to the [F Prime frame specification](../../FprimeProtocol/docs/sdd.md) for details on the frame format.
### Diagrams It is designed to receive packets from a [`Svc::ComQueue`](../../ComQueue/docs/sdd.md) and passes frames to a [Communications Adapter](../../Interfaces/docs/sdd.md), such as a Radio manager component or [`Svc::ComStub`](../../ComStub/docs/sdd.md), for transmission on the wire.
Below is the common configuration in which the `Svc::FprimeFramer` can be used. It is receiving packets from a [`Svc::ComQueue`](../../ComQueue/docs/sdd.md) and passes frames to a [Communications Adapter](../../Interfaces/docs/sdd.md), such as a Radio manager component (or a [`Svc::ComStub`](../../ComStub/docs/sdd.md)), for transmission. ## Usage Examples
![./img/framer-topology.png](./img/framer-topology.png) The `Svc::FprimeFramer` component is used in the uplink stack of many reference F´ application such as [the tutorials source code](https://github.com/fprime-community#tutorials).
## Internals ## Internals
@ -25,12 +25,14 @@ On receiving a data packet, the `Svc::FprimeFramer` performs the following actio
## Port Descriptions ## Port Descriptions
| Kind | Name | Port Type | Usage | | Kind | Name | Port Type | Usage |
|---|---|---|---| |-----------------|-----------------|--------------------------|--------------------------------------------------------------------------|
| `guarded input` | `dataIn` | `Svc.ComDataWithContext` | Port to receive data to frame, in a Fw::Buffer with optional context| | `guarded input` | `dataIn` | `Svc.ComDataWithContext` | Port to receive data to frame, in a Fw::Buffer with optional context |
| `output` | `dataOut` | `Svc.ComDataWithContext` | Port to output framed data, with optional context, for follow-up framing| | `output` | `dataOut` | `Svc.ComDataWithContext` | Port to output framed data, with optional context, for follow-up framing |
| `sync input` | `comStatusIn` | `Fw.SuccessCondition` | Port receiving the general status from the downstream component| | `sync input` | `dataReturnIn` | `Svc.ComDataWithContext` | Port to receive back ownership of buffer sent out of `dataOut` |
| `output` | `comStatusOut` | `Fw.SuccessCondition` | Port receiving indicating the status of framer for receiving more data| | `output` | `dataReturnOut` | `Svc.ComDataWithContext` | Port to return ownership of buffer received on `dataIn` |
| `sync input` | `comStatusIn` | `Fw.SuccessCondition` | Port receiving the general status from the downstream component |
| `output` | `comStatusOut` | `Fw.SuccessCondition` | Port receiving indicating the status of framer for receiving more data |
## Requirements ## Requirements
@ -39,4 +41,5 @@ On receiving a data packet, the `Svc::FprimeFramer` performs the following actio
| SVC-FPRIME_FRAMER-001 | `Svc::FprimeFramer` shall accept data buffers (packets) stored in `Fw::Buffer` through its `dataIn` input port | Unit Test | | SVC-FPRIME_FRAMER-001 | `Svc::FprimeFramer` shall accept data buffers (packets) stored in `Fw::Buffer` through its `dataIn` input port | Unit Test |
| SVC-FPRIME_FRAMER-002 | `Svc::FprimeFramer` shall emit one F Prime frame on its `framedOut` output port for each packet received on `dataIn` input port | Unit Test | | SVC-FPRIME_FRAMER-002 | `Svc::FprimeFramer` shall emit one F Prime frame on its `framedOut` output port for each packet received on `dataIn` input port | Unit Test |
| SVC-FPRIME_FRAMER-003 | `Svc::FprimeFramer` shall emit F Prime frames that conforms to the [F´ frame specification](../../FprimeProtocol/docs/sdd.md) | Unit Test | | SVC-FPRIME_FRAMER-003 | `Svc::FprimeFramer` shall emit F Prime frames that conforms to the [F´ frame specification](../../FprimeProtocol/docs/sdd.md) | Unit Test |
| SVC-FPRIME_FRAMER-004 | `Svc::FprimeFramer` shall pass through all `Fw.SuccessCondition` received on `comStatusIn` to `comStatusOut` | Unit Test |

View File

@ -32,9 +32,6 @@ void FprimeRouter ::dataIn_handler(FwIndexType portNum, Fw::Buffer& packetBuffer
status = esb.deserialize(packetType); status = esb.deserialize(packetType);
} }
// Whether to deallocate the packet buffer
bool deallocate = true;
// Process the packet // Process the packet
if (status == Fw::FW_SERIALIZE_OK) { if (status == Fw::FW_SERIALIZE_OK) {
U8* const packetData = packetBuffer.getData(); U8* const packetData = packetBuffer.getData();
@ -57,13 +54,16 @@ void FprimeRouter ::dataIn_handler(FwIndexType portNum, Fw::Buffer& packetBuffer
} }
// Handle a file packet // Handle a file packet
case Fw::ComPacket::FW_PACKET_FILE: { case Fw::ComPacket::FW_PACKET_FILE: {
// If the file uplink output port is connected, // If the file uplink output port is connected, send the file packet. Otherwise take no action.
// send the file packet. Otherwise take no action.
if (this->isConnected_fileOut_OutputPort(0)) { if (this->isConnected_fileOut_OutputPort(0)) {
// Send the packet buffer // Copy buffer into a new allocated buffer. This lets us return the original buffer with dataReturnOut,
this->fileOut_out(0, packetBuffer); // and FprimeRouter can handle the deallocation of the file buffer when it returns on fileBufferReturnIn
// Transfer ownership of the packetBuffer to the receiver Fw::Buffer packetBufferCopy = this->bufferAllocate_out(0, packetBuffer.getSize());
deallocate = false; auto copySerializer = packetBufferCopy.getSerializer();
status = copySerializer.serialize(packetBuffer.getData(), packetBuffer.getSize(), Fw::Serialization::OMIT_LENGTH);
FW_ASSERT(status == Fw::FW_SERIALIZE_OK, status);
// Send the copied buffer out. It will come back on fileBufferReturnIn once the receiver is done with it
this->fileOut_out(0, packetBufferCopy);
} }
break; break;
} }
@ -71,9 +71,14 @@ void FprimeRouter ::dataIn_handler(FwIndexType portNum, Fw::Buffer& packetBuffer
// Packet type is not known to the F Prime protocol. If the unknownDataOut port is // Packet type is not known to the F Prime protocol. If the unknownDataOut port is
// connected, forward packet and context for further processing // connected, forward packet and context for further processing
if (this->isConnected_unknownDataOut_OutputPort(0)) { if (this->isConnected_unknownDataOut_OutputPort(0)) {
this->unknownDataOut_out(0, packetBuffer, context); // Copy buffer into a new allocated buffer. This lets us return the original buffer with dataReturnOut,
// Transfer ownership of the packetBuffer to the receiver // and FprimeRouter can handle the deallocation of the unknown buffer when it returns on bufferReturnIn
deallocate = false; Fw::Buffer packetBufferCopy = this->bufferAllocate_out(0, packetBuffer.getSize());
auto copySerializer = packetBufferCopy.getSerializer();
status = copySerializer.serialize(packetBuffer.getData(), packetBuffer.getSize(), Fw::Serialization::OMIT_LENGTH);
FW_ASSERT(status == Fw::FW_SERIALIZE_OK, status);
// Send the copied buffer out. It will come back on fileBufferReturnIn once the receiver is done with it
this->unknownDataOut_out(0, packetBufferCopy, context);
} }
} }
} }
@ -81,10 +86,8 @@ void FprimeRouter ::dataIn_handler(FwIndexType portNum, Fw::Buffer& packetBuffer
this->log_WARNING_HI_DeserializationError(status); this->log_WARNING_HI_DeserializationError(status);
} }
if (deallocate) { // Return ownership of the incoming packetBuffer
// Deallocate the packet buffer this->dataReturnOut_out(0, packetBuffer, context);
this->bufferDeallocate_out(0, packetBuffer);
}
} }
void FprimeRouter ::cmdResponseIn_handler(FwIndexType portNum, void FprimeRouter ::cmdResponseIn_handler(FwIndexType portNum,
@ -93,4 +96,9 @@ void FprimeRouter ::cmdResponseIn_handler(FwIndexType portNum,
const Fw::CmdResponse& response) { const Fw::CmdResponse& response) {
// Nothing to do // Nothing to do
} }
void FprimeRouter ::fileBufferReturnIn_handler(FwIndexType portNum, Fw::Buffer& fwBuffer) {
this->bufferDeallocate_out(0, fwBuffer);
}
} // namespace Svc } // namespace Svc

View File

@ -10,6 +10,9 @@ module Svc {
@ Port for forwarding non-recognized packet types @ Port for forwarding non-recognized packet types
output port unknownDataOut: Svc.ComDataWithContext output port unknownDataOut: Svc.ComDataWithContext
@ Port for allocating buffers
output port bufferAllocate: Fw.BufferGet
@ Port for deallocating buffers @ Port for deallocating buffers
output port bufferDeallocate: Fw.BufferSend output port bufferDeallocate: Fw.BufferSend

View File

@ -44,6 +44,14 @@ class FprimeRouter final : public FprimeRouterComponentBase {
U32 cmdSeq, //!< The command sequence number U32 cmdSeq, //!< The command sequence number
const Fw::CmdResponse& response //!< The command response const Fw::CmdResponse& response //!< The command response
) override; ) override;
//! Handler implementation for fileBufferReturnIn
//!
//! Port for receiving ownership back of buffers sent on fileOut
void fileBufferReturnIn_handler(FwIndexType portNum, //!< The port number
Fw::Buffer& fwBuffer //!< The buffer
) override;
}; };
} // namespace Svc } // namespace Svc

View File

@ -4,7 +4,13 @@ The `Svc::FprimeRouter` component routes F´ packets (such as command or file pa
The `Svc::FprimeRouter` component receives F´ packets (as [Fw::Buffer](../../../Fw/Buffer/docs/sdd.md) objects) and routes them to other components through synchronous port calls. The input port of type `Svc.ComDataWithContext` passes this Fw.Buffer object along with optional context data which can help for routing. The current F Prime protocol does not use this context data, but is nevertheless present in the interface for compatibility with other protocols which may for example pass APIDs in the frame headers. The `Svc::FprimeRouter` component receives F´ packets (as [Fw::Buffer](../../../Fw/Buffer/docs/sdd.md) objects) and routes them to other components through synchronous port calls. The input port of type `Svc.ComDataWithContext` passes this Fw.Buffer object along with optional context data which can help for routing. The current F Prime protocol does not use this context data, but is nevertheless present in the interface for compatibility with other protocols which may for example pass APIDs in the frame headers.
The `Svc::FprimeRouter` component supports `Fw::ComPacket::FW_PACKET_COMMAND` and `Fw::ComPacket::FW_PACKET_FILE` packet types. Unknown packet types are forwarded on the `unknownDataOut` port, which a project-specific component can connect to for custom routing. In the case of unknown data being forwarded, the ownership of the packet data `Fw::Buffer` object is passed to the receiver. The `Svc::FprimeRouter` component supports `Fw::ComPacket::FW_PACKET_COMMAND` and `Fw::ComPacket::FW_PACKET_FILE` packet types. Unknown packet types are forwarded on the `unknownDataOut` port, which a project-specific component can connect to for custom routing.
About memory management, all buffers sent by `Svc::FprimeRouter` on the `fileOut` and `unknownDataOut` ports are expected to be returned to the router through the `fileBufferReturnIn` port for deallocation.
## Custom Routing
The `Svc::FprimeRouter` component is designed to be extensible through the use of a project-specific router. The `unknownDataOut` port can be connected to a project-specific component that can receive all unknown packet types. This component can then implement custom handling of these unknown packets. After processing, the project-specific component shall return the received buffer to the `Svc::FprimeRouter` component through the `fileBufferReturnIn` port (named this way as it only receives file packets in the common use-case), which will deallocate the buffer.
## Usage Examples ## Usage Examples
@ -16,26 +22,18 @@ In the canonical uplink communications stack, `Svc::FprimeRouter` is connected t
![uplink_stack](../../FprimeDeframer/docs/img/deframer_uplink_stack.png) ![uplink_stack](../../FprimeDeframer/docs/img/deframer_uplink_stack.png)
## Class Diagram
```mermaid
classDiagram
class FprimeRouter~PassiveComponent~ {
+ void dataIn_handler(portNum, packetBuffer, contextBuffer)
+ void cmdResponseIn_handler(portNum, opcode, cmdSeq, response)
}
```
## Port Descriptions ## Port Descriptions
| Name | Description | Type | | Kind | Name | Type | Description |
|---|---|---| |---|---|---|---|
| `dataIn: Svc.ComDataWithContext` | Receiving Fw::Buffer with context buffer from Deframer | `guarded input` | | `guarded input` | `dataIn` | `Svc.ComDataWithContext` | Receiving Fw::Buffer with context buffer from Deframer
| `commandOut: Fw.Com` | Port for sending command packets as Fw::ComBuffers | `output` | | `guarded input` | `dataReturnOut` | `Svc.ComDataWithContext` | Returning ownership of buffer received on `dataIn`
| `fileOut: Fw.BufferSend` | Port for sending file packets as Fw::Buffer (ownership passed to receiver) | `output` | | `output` | `commandOut` | `Fw.Com` | Port for sending command packets as Fw::ComBuffers |
| `unknownDataOut: Svc.ComDataWithContext` | Port forwarding unknown data (useful for adding custom routing rules with a project-defined router) | `output` | | `output` | `fileOut` | `Fw.BufferSend` | Port for sending file packets as Fw::Buffer (ownership passed to receiver) |
| `output`| bufferDeallocate | `Fw.BufferSend` | Port for deallocating buffers once routed | | `sync input` | `fileBufferReturnIn` | `Fw.BufferSend` | Receiving back ownership of buffer sent on `fileOut` and `unknownDataOut` |
| `output` | `unknownDataOut` | `Svc.ComDataWithContext` | Port forwarding unknown data (useful for adding custom routing rules with a project-defined router) |
| `output`| `bufferAllocate` | `Fw.BufferGet` | Port for allocating buffers, allowing copy of received data |
| `output`| `bufferDeallocate` | `Fw.BufferSend` | Port for deallocating buffers |
## Requirements ## Requirements
@ -46,3 +44,5 @@ SVC-ROUTER-002 | `Svc::FprimeRouter` shall route packets of type `Fw::ComPacket:
SVC-ROUTER-003 | `Svc::FprimeRouter` shall route packets of type `Fw::ComPacket::FW_PACKET_FILE` to the `fileOut` output port. | Routing file packets | Unit test | SVC-ROUTER-003 | `Svc::FprimeRouter` shall route packets of type `Fw::ComPacket::FW_PACKET_FILE` to the `fileOut` output port. | Routing file packets | Unit test |
SVC-ROUTER-004 | `Svc::FprimeRouter` shall route data that is neither `Fw::ComPacket::FW_PACKET_COMMAND` nor `Fw::ComPacket::FW_PACKET_FILE` to the `unknownDataOut` output port. | Allows for projects to provide custom routing for additional (project-specific) uplink data types | Unit test | SVC-ROUTER-004 | `Svc::FprimeRouter` shall route data that is neither `Fw::ComPacket::FW_PACKET_COMMAND` nor `Fw::ComPacket::FW_PACKET_FILE` to the `unknownDataOut` output port. | Allows for projects to provide custom routing for additional (project-specific) uplink data types | Unit test |
SVC-ROUTER-005 | `Svc::FprimeRouter` shall emit warning events if serialization errors occur during processing of incoming packets | Aid in diagnosing uplink issues | Unit test | SVC-ROUTER-005 | `Svc::FprimeRouter` shall emit warning events if serialization errors occur during processing of incoming packets | Aid in diagnosing uplink issues | Unit test |
SVC-ROUTER-005 | `Svc::FprimeRouter` shall make a copy of buffers that represent a `FW_PACKET_FILE` | Aid in memory management of file buffers | Unit test |
SVC-ROUTER-005 | `Svc::FprimeRouter` shall return ownership of all buffers received on `dataIn` through `dataReturnOut` | Memory management | Unit test |

View File

@ -28,6 +28,11 @@ TEST(FprimeRouter, TestRouteUnknownPacketUnconnected) {
Svc::FprimeRouterTester tester(true); Svc::FprimeRouterTester tester(true);
tester.testRouteUnknownPacketUnconnected(); tester.testRouteUnknownPacketUnconnected();
} }
TEST(FprimeRouter, TestBufferReturn) {
COMMENT("Deallocate a returning buffer");
Svc::FprimeRouterTester tester;
tester.testBufferReturn();
}
TEST(FprimeRouter, TestCommandResponse) { TEST(FprimeRouter, TestCommandResponse) {
COMMENT("Handle a command response (no-op)"); COMMENT("Handle a command response (no-op)");
Svc::FprimeRouterTester tester; Svc::FprimeRouterTester tester;

View File

@ -33,7 +33,8 @@ void FprimeRouterTester ::testRouteComInterface() {
ASSERT_from_commandOut_SIZE(1); // one command packet emitted ASSERT_from_commandOut_SIZE(1); // one command packet emitted
ASSERT_from_fileOut_SIZE(0); // no file packet emitted ASSERT_from_fileOut_SIZE(0); // no file packet emitted
ASSERT_from_unknownDataOut_SIZE(0); // no unknown data emitted ASSERT_from_unknownDataOut_SIZE(0); // no unknown data emitted
ASSERT_from_bufferDeallocate_SIZE(1); // command packets are deallocated by the router ASSERT_from_dataReturnOut_SIZE(1); // data ownership should always be returned
ASSERT_from_bufferAllocate_SIZE(0); // no buffer allocation for Com packets
} }
void FprimeRouterTester ::testRouteFileInterface() { void FprimeRouterTester ::testRouteFileInterface() {
@ -41,7 +42,8 @@ void FprimeRouterTester ::testRouteFileInterface() {
ASSERT_from_commandOut_SIZE(0); // no command packet emitted ASSERT_from_commandOut_SIZE(0); // no command packet emitted
ASSERT_from_fileOut_SIZE(1); // one file packet emitted ASSERT_from_fileOut_SIZE(1); // one file packet emitted
ASSERT_from_unknownDataOut_SIZE(0); // no unknown data emitted ASSERT_from_unknownDataOut_SIZE(0); // no unknown data emitted
ASSERT_from_bufferDeallocate_SIZE(0); // no deallocation (file packets' ownership is transferred to the receiver) ASSERT_from_dataReturnOut_SIZE(1); // data ownership should always be returned
ASSERT_from_bufferAllocate_SIZE(1); // file packet was copied into a new allocated buffer
} }
void FprimeRouterTester ::testRouteUnknownPacket() { void FprimeRouterTester ::testRouteUnknownPacket() {
@ -49,15 +51,26 @@ void FprimeRouterTester ::testRouteUnknownPacket() {
ASSERT_from_commandOut_SIZE(0); // no command packet emitted ASSERT_from_commandOut_SIZE(0); // no command packet emitted
ASSERT_from_fileOut_SIZE(0); // no file packet emitted ASSERT_from_fileOut_SIZE(0); // no file packet emitted
ASSERT_from_unknownDataOut_SIZE(1); // one unknown data emitted ASSERT_from_unknownDataOut_SIZE(1); // one unknown data emitted
ASSERT_from_bufferDeallocate_SIZE(0); // no deallocation (unknown data ownership is transferred to the receiver) ASSERT_from_dataReturnOut_SIZE(1); // data ownership should always be returned
ASSERT_from_bufferAllocate_SIZE(1); // unknown packet was copied into a new allocated buffer
} }
void FprimeRouterTester ::testRouteUnknownPacketUnconnected() { void FprimeRouterTester ::testRouteUnknownPacketUnconnected() {
this->mockReceivePacketType(Fw::ComPacket::FW_PACKET_UNKNOWN); this->mockReceivePacketType(Fw::ComPacket::FW_PACKET_UNKNOWN);
ASSERT_from_commandOut_SIZE(0); // no command packet emitted ASSERT_from_commandOut_SIZE(0); // no command packet emitted
ASSERT_from_fileOut_SIZE(0); // no file packet emitted ASSERT_from_fileOut_SIZE(0); // no file packet emitted
ASSERT_from_unknownDataOut_SIZE(0); // zero unknown data emitted ASSERT_from_unknownDataOut_SIZE(0); // zero unknown data emitted when port is unconnected
ASSERT_from_bufferDeallocate_SIZE(1); // test that buffer is deallocated when output port is not connected ASSERT_from_dataReturnOut_SIZE(1); // data ownership should always be returned
ASSERT_from_bufferAllocate_SIZE(0); // no buffer allocation when port is unconnected
}
void FprimeRouterTester ::testBufferReturn() {
U8 data[1];
Fw::Buffer buffer(data, sizeof(data));
this->invoke_to_fileBufferReturnIn(0, buffer);
ASSERT_from_bufferDeallocate_SIZE(1); // incoming buffer should be deallocated
ASSERT_EQ(this->fromPortHistory_bufferDeallocate->at(0).fwBuffer.getData(), data);
ASSERT_EQ(this->fromPortHistory_bufferDeallocate->at(0).fwBuffer.getSize(), sizeof(data));
} }
void FprimeRouterTester ::testCommandResponse() { void FprimeRouterTester ::testCommandResponse() {
@ -87,15 +100,27 @@ void FprimeRouterTester::connectPortsExceptUnknownData() {
this->component.set_logOut_OutputPort(0, this->get_from_logOut(0)); this->component.set_logOut_OutputPort(0, this->get_from_logOut(0));
this->component.set_logTextOut_OutputPort(0, this->get_from_logTextOut(0)); this->component.set_logTextOut_OutputPort(0, this->get_from_logTextOut(0));
this->component.set_timeCaller_OutputPort(0, this->get_from_timeCaller(0)); this->component.set_timeCaller_OutputPort(0, this->get_from_timeCaller(0));
// Connect typed input ports // Connect typed input ports
this->connect_to_cmdResponseIn(0, this->component.get_cmdResponseIn_InputPort(0)); this->connect_to_cmdResponseIn(0, this->component.get_cmdResponseIn_InputPort(0));
this->connect_to_dataIn(0, this->component.get_dataIn_InputPort(0)); this->connect_to_dataIn(0, this->component.get_dataIn_InputPort(0));
this->connect_to_fileBufferReturnIn(0, this->component.get_fileBufferReturnIn_InputPort(0));
// Connect typed output ports // Connect typed output ports
this->component.set_bufferAllocate_OutputPort(0, this->get_from_bufferAllocate(0));
this->component.set_bufferDeallocate_OutputPort(0, this->get_from_bufferDeallocate(0)); this->component.set_bufferDeallocate_OutputPort(0, this->get_from_bufferDeallocate(0));
this->component.set_commandOut_OutputPort(0, this->get_from_commandOut(0)); this->component.set_commandOut_OutputPort(0, this->get_from_commandOut(0));
this->component.set_dataReturnOut_OutputPort(0, this->get_from_dataReturnOut(0));
this->component.set_fileOut_OutputPort(0, this->get_from_fileOut(0)); this->component.set_fileOut_OutputPort(0, this->get_from_fileOut(0));
} }
// ----------------------------------------------------------------------
// Port handler overrides
// ----------------------------------------------------------------------
Fw::Buffer FprimeRouterTester::from_bufferAllocate_handler(FwIndexType portNum, U32 size) {
this->pushFromPortEntry_bufferAllocate(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 } // namespace Svc

View File

@ -56,6 +56,9 @@ class FprimeRouterTester : public FprimeRouterGTestBase {
//! Route a packet of unknown type //! Route a packet of unknown type
void testRouteUnknownPacketUnconnected(); void testRouteUnknownPacketUnconnected();
//! Deallocate a returning buffer
void testBufferReturn();
//! Invoke the command response input port //! Invoke the command response input port
void testCommandResponse(); void testCommandResponse();
@ -76,6 +79,12 @@ class FprimeRouterTester : public FprimeRouterGTestBase {
//! Mock the reception of a packet of a specific type //! Mock the reception of a packet of a specific type
void mockReceivePacketType(Fw::ComPacket::ComPacketType packetType); void mockReceivePacketType(Fw::ComPacket::ComPacketType packetType);
// ----------------------------------------------------------------------
// Port handler overrides
// ----------------------------------------------------------------------
//! Overriding bufferAllocate handler to be able to request a buffer in component tests
Fw::Buffer from_bufferAllocate_handler(FwIndexType portNum, U32 size) override;
private: private:
// ---------------------------------------------------------------------- // ----------------------------------------------------------------------
// Member variables // Member variables
@ -83,6 +92,9 @@ class FprimeRouterTester : public FprimeRouterGTestBase {
//! The component under test //! The component under test
FprimeRouter component; FprimeRouter component;
Fw::Buffer m_buffer; // buffer to be returned by mocked bufferAllocate call
U8 m_buffer_slot[64];
}; };
} // namespace Svc } // namespace Svc

View File

@ -50,14 +50,14 @@ void FrameAccumulator ::cleanup() {
// Handler implementations for user-defined typed input ports // Handler implementations for user-defined typed input ports
// ---------------------------------------------------------------------- // ----------------------------------------------------------------------
void FrameAccumulator ::dataIn_handler(FwIndexType portNum, Fw::Buffer& buffer) { void FrameAccumulator ::dataIn_handler(FwIndexType portNum, Fw::Buffer& buffer, const ComCfg::FrameContext& context) {
// Check whether there is data to process // Check whether there is data to process
if (buffer.isValid()) { if (buffer.isValid()) {
// The buffer is not necessarily a full frame, so the attached context has no meaning and we ignore it
this->processBuffer(buffer); this->processBuffer(buffer);
} }
// TODO: rework the uplink deallocation logic to use the bufferReturn chaining pattern // Return ownership of the incoming data
// Deallocate the buffer this->dataReturnOut_out(0, buffer, context);
this->bufferDeallocate_out(0, buffer);
} }
void FrameAccumulator ::processBuffer(Fw::Buffer& buffer) { void FrameAccumulator ::processBuffer(Fw::Buffer& buffer) {
@ -136,7 +136,7 @@ void FrameAccumulator ::processRing() {
static_cast<FwAssertArgType>(m_inRing.get_allocated_size()), static_cast<FwAssertArgType>(m_inRing.get_allocated_size()),
static_cast<FwAssertArgType>(remaining), static_cast<FwAssertArgType>(size_out)); static_cast<FwAssertArgType>(remaining), static_cast<FwAssertArgType>(size_out));
ComCfg::FrameContext context; ComCfg::FrameContext context;
this->frameOut_out(0, buffer, context); this->dataOut_out(0, buffer, context);
} else { } else {
// No buffer is available, we need to exit and try again later // No buffer is available, we need to exit and try again later
this->log_WARNING_HI_NoBufferAvailable(); this->log_WARNING_HI_NoBufferAvailable();
@ -163,4 +163,11 @@ void FrameAccumulator ::processRing() {
} }
} }
} }
void FrameAccumulator ::dataReturnIn_handler(FwIndexType portNum, Fw::Buffer& fwBuffer, const ComCfg::FrameContext& context) {
// Frame buffer ownership is returned to the component. Component had allocated with a buffer manager,
// so we return it to the buffer manager for deallocation
this->bufferDeallocate_out(0, fwBuffer);
}
} // namespace Svc } // namespace Svc

View File

@ -7,8 +7,7 @@ module Svc {
# ---------------------------------------------------------------------- # ----------------------------------------------------------------------
include "../Interfaces/FrameAccumulatorInterface.fppi" include "../Interfaces/FrameAccumulatorInterface.fppi"
@ Port for deallocating buffers holding extracted frames
@ Port for deallocating buffers received on dataIn.
output port bufferDeallocate: Fw.BufferSend output port bufferDeallocate: Fw.BufferSend
@ Port for allocating buffer to hold extracted frame @ Port for allocating buffer to hold extracted frame

View File

@ -48,9 +48,18 @@ class FrameAccumulator final : public FrameAccumulatorComponentBase {
//! Handler implementation for dataIn //! Handler implementation for dataIn
//! //!
//! Receives raw data from a ByteStreamDriver, ComStub, or other buffer producing component //! Receive stream of bytes from a ComInterface component
void dataIn_handler(FwIndexType portNum, //!< The port number void dataIn_handler(FwIndexType portNum, //!< The port number
Fw::Buffer& recvBuffer) override; Fw::Buffer& recvBuffer,
const ComCfg::FrameContext& context) override;
//! Handler implementation for bufferReturnIn
//!
//! Port receiving ownership back of buffers sent on dataOut
void dataReturnIn_handler(FwIndexType portNum, //!< The port number
Fw::Buffer& fwBuffer, //!< The buffer
const ComCfg::FrameContext& context //!< The context object
) override;
PRIVATE: PRIVATE:
//! \brief process raw buffer //! \brief process raw buffer

View File

@ -2,7 +2,7 @@
The `Svc::FrameAccumulator` component accumulates a stream of data (sequence of [Fw::Buffer](../../../Fw/Buffer/docs/sdd.md) objects) to extract full frames from. The `Svc::FrameAccumulator` component accumulates a stream of data (sequence of [Fw::Buffer](../../../Fw/Buffer/docs/sdd.md) objects) to extract full frames from.
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. 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 `dataOut` output port.
## Internals ## Internals
@ -19,7 +19,7 @@ The uplink frames need not be aligned on the buffer boundaries, and each frame m
The `Svc::FrameAccumulator` receives `Fw::Buffer` objects on its `dataIn` input port. These buffers are accumulated in a `Utils::CircularBuffer`. Every time a new buffer is accumulated into the circular buffer, the `Svc::FrameAccumulator` enters a loop to `detect()` a frame within the circular buffer, starting at the current head of the circular buffer. The `Svc::FrameDetector` returns one of three results: The `Svc::FrameAccumulator` receives `Fw::Buffer` objects on its `dataIn` input port. These buffers are accumulated in a `Utils::CircularBuffer`. Every time a new buffer is accumulated into the circular buffer, the `Svc::FrameAccumulator` enters a loop to `detect()` a frame within the circular buffer, starting at the current head of the circular buffer. The `Svc::FrameDetector` returns one of three results:
- `NO_FRAME_DETECTED`: indicates no valid frame is present at the head of the circular buffer (for example, start word does not match the current head of the circular buffer). The `Svc::FrameAccumulator` rotates the circular buffer one byte and loops over to `detect()` again, or break the loop if the circular buffer is exhausted. - `NO_FRAME_DETECTED`: indicates no valid frame is present at the head of the circular buffer (for example, start word does not match the current head of the circular buffer). The `Svc::FrameAccumulator` rotates the circular buffer one byte and loops over to `detect()` again, or break the loop if the circular buffer is exhausted.
- `FRAME_DETECTED`: indicates there is a frame at the current head of the circular buffer. The `Svc::FrameAccumulator` allocates a new `Fw::Buffer` object to hold the frame, copies the detected frame from the circular buffer into the new `Fw::Buffer` object, and emits the new `Fw::Buffer` object (containing the frame) on its `frameOut` output port. The `Svc::FrameAccumulator` then rotates the circular buffer to remove the data that was just extracted, and deallocates the original `Fw::Buffer` that was received on the `dataIn` input port. - `FRAME_DETECTED`: indicates there is a frame at the current head of the circular buffer. The `Svc::FrameAccumulator` allocates a new `Fw::Buffer` object to hold the frame, copies the detected frame from the circular buffer into the new `Fw::Buffer` object, and emits the new `Fw::Buffer` object (containing the frame) on its `dataOut` output port. The `Svc::FrameAccumulator` then rotates the circular buffer to remove the data that was just extracted, and deallocates the original `Fw::Buffer` that was received on the `dataIn` input port.
- `MORE_DATA_NEEDED`: indicates that more data is needed to determine whether there is a valid frame. The `Svc::FrameAccumulator` deallocates the original `Fw::Buffer` that was received on the `dataIn` input port and halts execution, effectively waiting for the next `Fw::Buffer` to be received on the `dataIn` input port. - `MORE_DATA_NEEDED`: indicates that more data is needed to determine whether there is a valid frame. The `Svc::FrameAccumulator` deallocates the original `Fw::Buffer` that was received on the `dataIn` input port and halts execution, effectively waiting for the next `Fw::Buffer` to be received on the `dataIn` input port.
@ -85,7 +85,9 @@ SVC-FRAME-ACCUMULATOR-004 | `Svc::FrameAccumulator` shall accept byte buffers co
| Kind | Name | Type | Description | | Kind | Name | Type | Description |
|---|---|---|---| |---|---|---|---|
| `guarded input` | dataIn | `Drv.ByteStreamRecv` | Receives raw data from a ByteStreamDriver, ComStub, or other buffer producing component | | `guarded input` | dataIn | `Svc.ComDataWithContext` | Receives a stream of byte buffers from a [Communication Adapter](../../Interfaces/docs/sdd.md) |
| `output` | frameOut | `Svc.ComDataWithContext` | Port for sending an extracted frame out | | `output` | dataOut | `Svc.ComDataWithContext` | Port for sending an extracted frame out |
| `output` | bufferAllocate | `Fw.BufferGet` | Port for allocating buffer to hold extracted frame | | `output` | bufferAllocate | `Fw.BufferGet` | Port for allocating buffer to hold extracted frame |
| `output`| bufferDeallocate | `Fw.BufferSend` | Port for deallocating buffers received on dataIn. | | `output`| bufferDeallocate | `Fw.BufferSend` | Port for deallocating buffers received on dataIn. |
| `output` | dataReturnOut | `Svc.ComDataWithContext` | Port for returning ownership of buffers received on dataIn |
| `sync input` | dataReturnIn | `Svc.ComDataWithContext` | Receiving back ownership of buffers sent on dataOut |

View File

@ -42,6 +42,10 @@ TEST(FrameAccumulator, testAccumulateBuffersEmitManyFrames) {
tester.testAccumulateBuffersEmitManyFrames(); tester.testAccumulateBuffersEmitManyFrames();
} }
TEST(FrameAccumulator, testBufferReturnDeallocation) {
Svc::FrameAccumulatorTester tester;
tester.testBufferReturnDeallocation();
}
int main(int argc, char** argv) { int main(int argc, char** argv) {
STest::Random::seed(); STest::Random::seed();

View File

@ -37,15 +37,16 @@ void FrameAccumulatorTester ::testFrameDetected() {
U32 buffer_size = STest::Random::lowerUpper(1, 1024); U32 buffer_size = STest::Random::lowerUpper(1, 1024);
U8 data[buffer_size]; U8 data[buffer_size];
Fw::Buffer buffer(data, buffer_size); Fw::Buffer buffer(data, buffer_size);
ComCfg::FrameContext context;
// Set the mock detector to report success of size_out = 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); this->mockDetector.set_next_result(FrameDetector::Status::FRAME_DETECTED, buffer_size);
// Receive the buffer on dataIn // Receive the buffer on dataIn
this->invoke_to_dataIn(0, buffer); this->invoke_to_dataIn(0, buffer, context);
// Checks // Checks
ASSERT_from_bufferDeallocate_SIZE(1); // input buffer was deallocated ASSERT_from_dataReturnOut_SIZE(1); // input buffer ownership was returned
ASSERT_from_frameOut_SIZE(1); // frame was sent ASSERT_from_dataOut_SIZE(1); // frame was sent
ASSERT_EQ(this->component.m_inRing.get_allocated_size(), 0); // no data left in ring buffer 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 ASSERT_EQ(this->fromPortHistory_dataOut->at(0).data.getSize(), buffer_size); // all data was sent out
} }
void FrameAccumulatorTester ::testMoreDataNeeded() { void FrameAccumulatorTester ::testMoreDataNeeded() {
@ -53,13 +54,14 @@ void FrameAccumulatorTester ::testMoreDataNeeded() {
U32 buffer_size = STest::Random::lowerUpper(1, 1024); U32 buffer_size = STest::Random::lowerUpper(1, 1024);
U8 data[buffer_size]; U8 data[buffer_size];
Fw::Buffer buffer(data, buffer_size); Fw::Buffer buffer(data, buffer_size);
ComCfg::FrameContext context;
// Set the mock detector to report more data needed // Set the mock detector to report more data needed
this->mockDetector.set_next_result(FrameDetector::Status::MORE_DATA_NEEDED, buffer_size + 1); this->mockDetector.set_next_result(FrameDetector::Status::MORE_DATA_NEEDED, buffer_size + 1);
// Receive the buffer on dataIn // Receive the buffer on dataIn
this->invoke_to_dataIn(0, buffer); this->invoke_to_dataIn(0, buffer, context);
// Checks // Checks
ASSERT_from_bufferDeallocate_SIZE(1); // input buffer was deallocated ASSERT_from_dataReturnOut_SIZE(1); // input buffer ownership was returned
ASSERT_from_frameOut_SIZE(0); // frame was not sent (waiting on more data) ASSERT_from_dataOut_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 ASSERT_EQ(this->component.m_inRing.get_allocated_size(), buffer_size); // data left in ring buffer
} }
@ -68,13 +70,14 @@ void FrameAccumulatorTester ::testNoFrameDetected() {
U32 buffer_size = STest::Random::lowerUpper(1, 1024); U32 buffer_size = STest::Random::lowerUpper(1, 1024);
U8 data[buffer_size]; U8 data[buffer_size];
Fw::Buffer buffer(data, buffer_size); Fw::Buffer buffer(data, buffer_size);
ComCfg::FrameContext context;
// Set the mock detector // Set the mock detector
this->mockDetector.set_next_result(FrameDetector::Status::NO_FRAME_DETECTED, 0); this->mockDetector.set_next_result(FrameDetector::Status::NO_FRAME_DETECTED, 0);
// Receive the buffer on dataIn // Receive the buffer on dataIn
this->invoke_to_dataIn(0, buffer); this->invoke_to_dataIn(0, buffer, context);
// Checks // Checks
ASSERT_from_bufferDeallocate_SIZE(1); // input buffer was deallocated ASSERT_from_dataReturnOut_SIZE(1); // input buffer ownership was returned
ASSERT_from_frameOut_SIZE(0); // No frame was sent out ASSERT_from_dataOut_SIZE(0); // No frame was sent out
ASSERT_EQ(this->component.m_inRing.get_allocated_size(), 0); // all data was consumed and discarded ASSERT_EQ(this->component.m_inRing.get_allocated_size(), 0); // all data was consumed and discarded
} }
@ -83,11 +86,12 @@ void FrameAccumulatorTester ::testReceiveZeroSizeBuffer() {
// Prepare a zero size buffer // Prepare a zero size buffer
U8 data[1] = {0}; U8 data[1] = {0};
Fw::Buffer buffer(data, 0); Fw::Buffer buffer(data, 0);
ComCfg::FrameContext context;
// Receive the buffer on dataIn // Receive the buffer on dataIn
this->invoke_to_dataIn(0, buffer); this->invoke_to_dataIn(0, buffer, context);
// Checks // Checks
ASSERT_from_bufferDeallocate_SIZE(1); // input buffer was deallocated ASSERT_from_dataReturnOut_SIZE(1); // input buffer ownership was returned
ASSERT_from_frameOut_SIZE(0); // No frame was sent out ASSERT_from_dataOut_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.get_allocated_size(), 0); // No data in ring buffer
ASSERT_EQ(this->component.m_inRing.m_head_idx, 0); ASSERT_EQ(this->component.m_inRing.m_head_idx, 0);
} }
@ -99,18 +103,19 @@ void FrameAccumulatorTester ::testAccumulateTwoBuffers() {
U8 data2[buffer2_size]; U8 data2[buffer2_size];
Fw::Buffer buffer1(data1, buffer1_size); Fw::Buffer buffer1(data1, buffer1_size);
Fw::Buffer buffer2(data2, buffer2_size); Fw::Buffer buffer2(data2, buffer2_size);
ComCfg::FrameContext context;
this->mockDetector.set_next_result(FrameDetector::Status::MORE_DATA_NEEDED, buffer2_size); this->mockDetector.set_next_result(FrameDetector::Status::MORE_DATA_NEEDED, buffer2_size);
// Receive the buffer on dataIn // Receive the buffer on dataIn
this->invoke_to_dataIn(0, buffer1); this->invoke_to_dataIn(0, buffer1, context);
// Next result is detection of a full frame, size = buffer1_size + buffer2_size // 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 ); this->mockDetector.set_next_result(FrameDetector::Status::FRAME_DETECTED, buffer1_size + buffer2_size );
// Receive the buffer on dataIn // Receive the buffer on dataIn
this->invoke_to_dataIn(0, buffer2); this->invoke_to_dataIn(0, buffer2, context);
// Checks // Checks
ASSERT_from_bufferDeallocate_SIZE(2); // both input buffers deallocated ASSERT_from_dataReturnOut_SIZE(2); // both input buffers ownership were returned
ASSERT_from_frameOut_SIZE(1); // Exactly one frame was sent out ASSERT_from_dataOut_SIZE(1); // Exactly one 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.get_allocated_size(), 0); // No data in ring buffer
} }
@ -119,10 +124,10 @@ void FrameAccumulatorTester ::testAccumulateBuffersEmitFrame() {
U32 buffer_count = 0; U32 buffer_count = 0;
this->mockAccumulateFullFrame(frame_size, buffer_count); this->mockAccumulateFullFrame(frame_size, buffer_count);
// Checks // Checks
ASSERT_from_bufferDeallocate_SIZE(buffer_count); // all input buffers deallocated ASSERT_from_dataReturnOut_SIZE(buffer_count); // all input buffers ownership were returned
ASSERT_from_frameOut_SIZE(1); // Exactly one frame was sent out ASSERT_from_dataOut_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->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 ASSERT_EQ(this->fromPortHistory_dataOut->at(0).data.getSize(), frame_size); // accumulated buffer size
} }
void FrameAccumulatorTester ::testAccumulateBuffersEmitManyFrames() { void FrameAccumulatorTester ::testAccumulateBuffersEmitManyFrames() {
@ -137,17 +142,27 @@ void FrameAccumulatorTester ::testAccumulateBuffersEmitManyFrames() {
this->mockAccumulateFullFrame(frame_size, buffer_count); this->mockAccumulateFullFrame(frame_size, buffer_count);
total_buffer_received += buffer_count; total_buffer_received += buffer_count;
ASSERT_from_bufferDeallocate_SIZE(total_buffer_received); // all input buffers deallocated ASSERT_from_dataReturnOut_SIZE(total_buffer_received); // all input buffers returned
ASSERT_from_frameOut_SIZE(i+1); // Exactly one frame was sent out ASSERT_from_dataOut_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->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 ASSERT_EQ(this->fromPortHistory_dataOut->at(i).data.getSize(), frame_size); // accumulated buffer size
} }
// Final checks // Final checks
ASSERT_from_bufferDeallocate_SIZE(total_buffer_received); // all input buffers deallocated ASSERT_from_dataReturnOut_SIZE(total_buffer_received); // all input buffers returned
ASSERT_from_frameOut_SIZE(max_iters); // Exactly max_iters frames were sent out ASSERT_from_dataOut_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 ASSERT_EQ(this->component.m_inRing.get_allocated_size(), 0); // No data left in ring buffer
} }
void FrameAccumulatorTester ::testBufferReturnDeallocation() {
U8 data[1];
Fw::Buffer buffer(data, sizeof(data));
ComCfg::FrameContext context;
this->invoke_to_dataReturnIn(0, buffer, context);
ASSERT_from_bufferDeallocate_SIZE(1); // incoming buffer should be deallocated
ASSERT_EQ(this->fromPortHistory_bufferDeallocate->at(0).fwBuffer.getData(), data);
ASSERT_EQ(this->fromPortHistory_bufferDeallocate->at(0).fwBuffer.getSize(), sizeof(data));
}
// ---------------------------------------------------------------------- // ----------------------------------------------------------------------
// Helper functions // Helper functions
// ---------------------------------------------------------------------- // ----------------------------------------------------------------------
@ -163,6 +178,7 @@ void FrameAccumulatorTester ::mockAccumulateFullFrame(U32& frame_size, U32& buff
U32 buffer_size; U32 buffer_size;
Fw::Buffer buffer(data, 0); Fw::Buffer buffer(data, 0);
U32 accumulated_size = 0; U32 accumulated_size = 0;
ComCfg::FrameContext context;
// Send multiple buffers with MORE_DATA_NEEDED // Send multiple buffers with MORE_DATA_NEEDED
for (U32 i = 0; i < iters; i++) { for (U32 i = 0; i < iters; i++) {
@ -171,7 +187,7 @@ void FrameAccumulatorTester ::mockAccumulateFullFrame(U32& frame_size, U32& buff
buffer.setSize(buffer_size); buffer.setSize(buffer_size);
// Detector reports MORE_DATA_NEEDED and size needed bigger than accumulated size so far // 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->mockDetector.set_next_result(FrameDetector::Status::MORE_DATA_NEEDED, accumulated_size + 1);
this->invoke_to_dataIn(0, buffer); this->invoke_to_dataIn(0, buffer, context);
} }
// Send last buffer with FRAME_DETECTED // Send last buffer with FRAME_DETECTED
@ -181,7 +197,7 @@ void FrameAccumulatorTester ::mockAccumulateFullFrame(U32& frame_size, U32& buff
// Send last buffer with finally FRAME_DETECTED and total accumulated + last buffer // Send last buffer with finally FRAME_DETECTED and total accumulated + last buffer
this->mockDetector.set_next_result(FrameDetector::Status::FRAME_DETECTED, accumulated_size); this->mockDetector.set_next_result(FrameDetector::Status::FRAME_DETECTED, accumulated_size);
// Receive the last buffer on dataIn // Receive the last buffer on dataIn
this->invoke_to_dataIn(0, buffer); this->invoke_to_dataIn(0, buffer, context);
frame_size = accumulated_size; frame_size = accumulated_size;
buffer_count = iters + 1; buffer_count = iters + 1;
} }

View File

@ -63,6 +63,9 @@ class FrameAccumulatorTester : public FrameAccumulatorGTestBase {
//! Test accumulation of multiple random-size buffer into frames successively //! Test accumulation of multiple random-size buffer into frames successively
void testAccumulateBuffersEmitManyFrames(); void testAccumulateBuffersEmitManyFrames();
//! Test returning ownership of a buffer
void testBufferReturnDeallocation();
private: private:
// ---------------------------------------------------------------------- // ----------------------------------------------------------------------
// Helper functions // Helper functions

View File

@ -3,10 +3,10 @@
# ---------------------------------------------------------------------- # ----------------------------------------------------------------------
@ Data to be sent on the wire (coming in to the component) @ Data to be sent on the wire (coming in to the component)
sync input port comDataIn: Svc.ComDataWithContext sync input port dataIn: Svc.ComDataWithContext
@ Data received from the wire (coming out of the component) @ Data received from the wire (going out of the component)
output port comDataOut: Fw.BufferSend output port dataOut: Svc.ComDataWithContext
@ Status of the last transmission @ Status of the last transmission
output port comStatusOut: Fw.SuccessCondition output port comStatusOut: Fw.SuccessCondition
@ -15,5 +15,8 @@ output port comStatusOut: Fw.SuccessCondition
# Memory management # Memory management
# ---------------------------------------------------------------------- # ----------------------------------------------------------------------
@ Returning ownership of data that came in on comDataIn @ Port returning ownership of data that came in on dataIn
output port dataReturnOut: Svc.ComDataWithContext output port dataReturnOut: Svc.ComDataWithContext
@ Port receiving back ownership of buffer sent out on dataOut
sync input port dataReturnIn: Svc.ComDataWithContext

View File

@ -1,5 +1,11 @@
@ Port to receive framed data, with optional context @ Port to receive framed data, with optional context
guarded input port framedIn: Svc.ComDataWithContext guarded input port dataIn: Svc.ComDataWithContext
@ Port to output deframed data, with optional context @ Port to output deframed data, with optional context
output port deframedOut: Svc.ComDataWithContext output port dataOut: Svc.ComDataWithContext
@ Port for returning ownership of received buffers to deframe
output port dataReturnOut: Svc.ComDataWithContext
@ Port receiving back ownership of sent buffers
sync input port dataReturnIn: Svc.ComDataWithContext

View File

@ -1,5 +1,11 @@
@ Receive raw bytes from a ComInterface (e.g. ComStub) @ Receive raw bytes from a ComInterface (e.g. ComStub)
guarded input port dataIn: Fw.BufferSend guarded input port dataIn: Svc.ComDataWithContext
@ Port for sending an extracted frame out @ Port for sending an extracted frame out
output port frameOut: Svc.ComDataWithContext output port dataOut: Svc.ComDataWithContext
@ Port for returning ownership of buffers received on dataIn
output port dataReturnOut: Svc.ComDataWithContext
@ Port receiving back ownership of buffers sent on frameOut
sync input port dataReturnIn: Svc.ComDataWithContext

View File

@ -1,9 +1,21 @@
# ---------------------------------------------
# Router <-> Deframers
# ---------------------------------------------
@ Receiving data (Fw::Buffer) to be routed with optional context to help with routing @ Receiving data (Fw::Buffer) to be routed with optional context to help with routing
sync input port dataIn: Svc.ComDataWithContext sync input port dataIn: Svc.ComDataWithContext
@ Port for returning ownership of data (includes Fw.Buffer) received on dataIn
output port dataReturnOut: Svc.ComDataWithContext
# ---------------------------------------------
# Router <-> CmdDispatch/FileUplink
# ---------------------------------------------
@ Port for sending file packets as Fw::Buffer (ownership passed to receiver) @ Port for sending file packets as Fw::Buffer (ownership passed to receiver)
output port fileOut: Fw.BufferSend output port fileOut: Fw.BufferSend
@ Port for receiving ownership back of buffers sent on fileOut
sync input port fileBufferReturnIn: Fw.BufferSend
@ Port for sending command packets as Fw::ComBuffers @ Port for sending command packets as Fw::ComBuffers
output port commandOut: Fw.Com output port commandOut: Fw.Com

View File

@ -4,7 +4,7 @@ The Svc interfaces are a set of `.fppi` files that define FPP interfaces for com
## Svc/ComInterface ## Svc/ComInterface
The `Svc/ComInterface` is an interface for implementing the [Communications Adapter Interface](../../../docs/reference/communication-adapter-interface.md). The `Svc/ComInterface` is an interface for implementing the [Communications Adapter Interface](../../../docs/reference/communication-adapter-interface.md). [`Svc::ComStub`](../../ComStub/docs/sdd.md) implements this interface and uses a ByteStream driver to send and receive data on a TCP/UDP/UART link, and is often used in development and early testing.
## Svc/DeframerInterface ## Svc/DeframerInterface

View File

@ -13,50 +13,51 @@ protocol to ensure that data messages do not overload a communication interface.
## Ports ## Ports
The communication adapter interface is composed of three ports. These ports are used to transmit outgoing data through The `Svc.ComDataWithContext` port type is used to transmit comms data between components of the F´ system. This port type is composed of a `Fw::Buffer` object and a `ComCfg::FrameContext` object. The `Fw::Buffer` object is used to transmit the data, while the `ComCfg::FrameContext` object is used to provide context for the data being transmitted, such as header information once the header has been consumed.
The communication adapter interface is composed of five ports. These ports are used to transmit outgoing data through
some communication hardware and receive incoming data from that same hardware. These ports share types with the byte some communication hardware and receive incoming data from that same hardware. These ports share types with the byte
stream driver model for backwards compatibility. stream driver model for backwards compatibility.
| Kind | Suggested Name | Port Type | Usage | | Kind | Suggested Name | Port Type | Description |
|----------|----------------|-----------------------|----------------------------------------------------------------| |----------|----------------|--------------------------|----------------------------------------------------------------|
| `input` | `comDataIn` | `Svc.ComDataWithContext` | Port receiving `Fw::Buffer` objects for outgoing transmission (to be sent on the wire) | | `input` | `dataIn` | `Svc.ComDataWithContext` | Port receiving data for outgoing transmission (to be sent on the wire) |
| `output` | `comDataOut` | `Fw.BufferSend` | Port producing incoming `Fw::Buffer` objects (received on the wire) | | `output` | `dataOut` | `Svc.ComDataWithContext` | Port producing data from incoming transmissions (received on the wire) |
| `output` | `dataReturnOut` | `Svc.ComDataWithContext` | Port returning ownership of the `Fw::Buffer` sent on the `comDataIn` port | | `output` | `dataReturnOut`| `Svc.ComDataWithContext` | Port for sending back ownership of the `Fw::Buffer` received on the `dataIn` port |
| `output` | `comStatusOut` | `Fw.SuccessCondition` | Port indicating status of outgoing transmission. See protocol. | | `input` | `dataReturnIn` | `Svc.ComDataWithContext` | Port for receiving back ownership of the `Fw::Buffer` sent on the `dataOut` port |
| `output` | `comStatusOut` | `Fw.SuccessCondition` | Port indicating status of outgoing transmission. See protocol. |
> [!NOTE] > [!NOTE]
> About buffer management: after receiving a buffer on the `comDataIn` port, the communication component must return ownership of said buffer through the `dataReturnOut` port. The common scenario is to connect `comDataIn` and `dataReturnOut` to the same component, so that the sender can handle deallocation. > About buffer management: after receiving a buffer on the `dataIn` port, the communication component must return ownership of said buffer through the `dataReturnOut` port. The common scenario is to connect `dataIn` and `dataReturnOut` to the same component, so that the sender can handle deallocation. This is done with a callback so that `dataIn` can be an asynchronous port if needed.
> This is done with a callback so that `comDataIn` can be an asynchronous port if needed. > Similarly, the buffer sent out on `dataOut` is expected to be returned through the `dataReturnIn` port.
### comDataIn Description ### dataIn Description
This port receives data from an F´ application in the form of an argument of `Fw::Buffer` type. This data is intended to This port receives data from an F´ application in the form of an argument of `Fw::Buffer` type and a `ComCfg::FrameContext` context. This data is intended to be sent out the communications interface managed by this component (often referred to as _the wire_). From the perspective of the application this is the _outgoing data_ port.
be sent out the communications interface managed by this component (often referred to as _the wire_). From the perspective of the application this is
the outgoing data port.
### comDataOut Description ### dataOut Description
This port receives data from the communication interface managed by this component and provides it to the F´ application This port produces data to the F´ application once it is received on the wire. From the perspective of the application this is the _incoming data_ port.
in the form of an argument of `Fw::Buffer` type. From the perspective of the application this is the incoming data port.
### dataReturnOut Description ### dataReturnOut Description
This port is used to receive a callback returning ownership of the `Fw::Buffer` object that was sent on the `comDataIn` port. Ownership is typically returned to the sender. A callback is used so that `comDataIn` may be an asynchronous port if needed. This port is used to send a callback returning ownership of the `Fw::Buffer` object that was received on the `dataIn` port. Ownership is typically returned to the sender. A callback is used so that `dataIn` may be an asynchronous port if needed.
### dataReturnIn Description
This port is used to receive a callback returning ownership of the `Fw::Buffer` object that was sent on the `dataOut` port. Ownership is typically returned from the receiver of dataOut. A callback is used so that `dataOut` may be an asynchronous call if needed.
### comStatusOut Description ### comStatusOut Description
This port carries a status of `Fw::Success::SUCCESS` or `Fw::Success::FAILURE` typically in response to a call to the This port carries a status of `Fw::Success::SUCCESS` or `Fw::Success::FAILURE` typically in response to a call to the `dataIn` port described above.
`comDataIn` port described above.
> [!NOTE] > [!NOTE]
> it is critical to obey the protocol as described in the protocol section below. > it is critical to obey the protocol as described in the protocol section below.
## Communication Queue Protocol ## Communication Queue Protocol
`Svc::ComQueue` queues messages until the communication adapter is ready to receive these messages. For each `Svc::ComQueue` queues messages until the communication adapter is ready to receive these messages. For each `Fw::Success::SUCCESS` message received, `Svc::ComQueue` will emit one message. `Svc::ComQueue` will not emit messages at any other time. This implies several things:
Fw::Success::SUCCESS message received, `Svc::ComQueue` will emit one message. `Svc::ComQueue` will not emit messages
at any other time. This implies several things:
1. An initial `Fw::Success::SUCCESS` message must be sent to `Svc::ComQueue` to initiate data flow 1. An initial `Fw::Success::SUCCESS` message must be sent to `Svc::ComQueue` to initiate data flow
2. A `Fw::Success::SUCCESS` must be eventually received in response to each message for data to continue to flow 2. A `Fw::Success::SUCCESS` must be eventually received in response to each message for data to continue to flow
@ -65,22 +66,19 @@ These implications are discussed in more depth in the appropriate protocol secti
## Framer Status Protocol ## Framer Status Protocol
Framing typically happens between `Svc::ComQueue` and a communications adapter. The `Svc::Framer` component implements Framing typically happens between `Svc::ComQueue` and a communications adapter. The action taken by this protocol is dependent on the number of framed messages (frames) sent to the communication adapter in response to each message received from `Svc::ComQueue`.
this protocol in order to interoperate with these other components. The action taken by this protocol is dependent on
the number of framed messages (frames) sent to the communication adapter in response to each message received from The `Svc::FprimeFramer` implements the Framer status protocol by emitting a frame for each message received from `Svc::ComQueue`. Therefore, the `Svc::FprimeFramer` receives a status from the communication adapter on each sent frame and forwards it back to `Svc::ComQueue`. This allows the data flow to continue, or pause if the communication adapter is unable to receive more data.
`Svc::ComQueue`.
Framer implementations may choose to implement a different behavior. For example, a framer may choose to accept multiple messages from `Svc::ComQueue` and concatenate them in a single frame. In this case, the framer implementation must manage the flow of statuses accordingly. This is summarized in the table below.
| Produced Frames | Action | Rationale | | Produced Frames | Action | Rationale |
|-----------------|----------------------------------------|----------------------------------------------------------| |-----------------|----------------------------------------|----------------------------------------------------------|
| 0 | Send one `Fw::Success::SUCCESS` status | Data must continue to flow to produce a frame | | 0 | Send one `Fw::Success::SUCCESS` status | Data must continue to flow to produce a frame |
| 1 | Pass through received statuses | Frame produced and communication adapter produces status | | 1 | Pass through received status from ComQueue | Frame produced and communication adapter produces status |
When a frame is produced the communication adapter is responsible for determining the status of the message and its > [!IMPORTANT]
statuses must be passed through. When no frame is produced, the communication adapter does not receive a frame and is > All framer implementations must pass through the initiation message from the communication adapter to start data flow.
incapable of producing a status. `Svc::Framer` must act appropriately in this case.
> [!NOTE]
> `Svc::Framer` must also pass through the initiation message from the communication adapter to start data flow.
## Communication Adapter Protocol ## Communication Adapter Protocol
@ -89,16 +87,16 @@ transmissions. This is done with the `comStatus` port. A communication status is
| Status | Description | | Status | Description |
|----------------------|-----------------------------------------------------------------------------------| |----------------------|-----------------------------------------------------------------------------------|
| Fw::Success::SUCCESS | *Communication adapter* transmission succeeded and is ready for more data. | | Fw::Success::SUCCESS | *Communication adapter* transmission succeeded and is ready for more data. |
| Fw::Success::FAILURE | Last transmission failed; *communication adapter* is unable to receive more data. | | Fw::Success::FAILURE | Last transmission failed; *communication adapter* is unable to receive more data. |
* Fw::Success::SUCCESS may also indicate a connection/reconnection success when data flow must be initiated. * Fw::Success::SUCCESS may also indicate a connection/reconnection success when data flow must be initiated.
A *Communication Adapter* shall emit either Fw::Success::SUCCESS or Fw::Success::FAILURE via the `comStatus` port once A *Communication Adapter* shall emit either Fw::Success::SUCCESS or Fw::Success::FAILURE via the `comStatusOut` port once
for each call received on `comDataIn`. Additionally, a *Communication Adapter* shall emit Fw::Success::SUCCESS once at for each call received on `dataIn`. Additionally, a *Communication Adapter* shall emit Fw::Success::SUCCESS once at
startup to indicate communication is initially ready and once after each Fw::Success::FAILURE event to indicate that startup to indicate communication is initially ready and once after each Fw::Success::FAILURE event to indicate that
communication has been restored. By emitting Fw::Success::SUCCESS after any failure, the communication adapter ensures communication has been restored. By emitting Fw::Success::SUCCESS after any failure, the communication adapter ensures
that each received message eventually results in a Fw::Success::SUCCESS. that each received message eventually results in a Fw::Success::SUCCESS.
> [!NOTE] > [!NOTE]
> It is imperative that *Communication Adapters* implement the `comStatus` protocol correctly. > It is imperative that *Communication Adapters* implement the `comStatusOut` protocol correctly.