Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .github/actions/spelling/expect.txt
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,7 @@ COMMANDDISPATCHERIMPLCFG
COMPACKET
COMPACKETQUEUEIN
COMQUEUE
COMRETRY
COMSPLITTER
COMSTUB
constexpr
Expand Down Expand Up @@ -716,6 +717,7 @@ UNITTESTASSERT
unittests
useconds
usecs
valdaarhun
valgrind
vbai
VCA
Expand Down
1 change: 1 addition & 0 deletions Svc/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -72,3 +72,4 @@ add_fprime_subdirectory("${CMAKE_CURRENT_LIST_DIR}/Version/")
add_fprime_subdirectory("${CMAKE_CURRENT_LIST_DIR}/FpySequencer/")
add_fprime_subdirectory("${CMAKE_CURRENT_LIST_DIR}/Ccsds/")
add_fprime_subdirectory("${CMAKE_CURRENT_LIST_DIR}/ComAggregator/")
add_fprime_subdirectory("${CMAKE_CURRENT_LIST_DIR}/ComRetry/")
36 changes: 36 additions & 0 deletions Svc/ComRetry/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
####
# F Prime CMakeLists.txt:
#
# SOURCES: list of source files (to be compiled)
# AUTOCODER_INPUTS: list of files to be passed to the autocoders
# DEPENDS: list of libraries that this module depends on
#
# More information in the F´ CMake API documentation:
# https://fprime.jpl.nasa.gov/latest/docs/reference/api/cmake/API/
#
####

register_fprime_library(
AUTOCODER_INPUTS
"${CMAKE_CURRENT_LIST_DIR}/ComRetry.fpp"
SOURCES
"${CMAKE_CURRENT_LIST_DIR}/ComRetry.cpp"
DEPENDS
Fw_Types
Fw_Buffer
)

### UTs ###
register_fprime_ut(
SOURCES
"${CMAKE_CURRENT_LIST_DIR}/test/ut/ComRetryTestMain.cpp"
"${CMAKE_CURRENT_LIST_DIR}/test/ut/ComRetryTester.cpp"
AUTOCODER_INPUTS
"${CMAKE_CURRENT_LIST_DIR}/ComRetry.fpp"
UT_AUTO_HELPERS
)

set (UT_TARGET_NAME "${FPRIME_CURRENT_MODULE}_ut_exe")
if (TARGET "${UT_TARGET_NAME}")
target_compile_options("${UT_TARGET_NAME}" PRIVATE -Wno-conversion)
endif()
67 changes: 67 additions & 0 deletions Svc/ComRetry/ComRetry.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
// ======================================================================
// \title ComRetry.cpp
// \author valdaarhun
// \brief cpp file for ComRetry component implementation class
// ======================================================================

#include "Svc/ComRetry/ComRetry.hpp"
#include "ComRetry.hpp"

namespace Svc {

// ----------------------------------------------------------------------
// Component construction and destruction
// ----------------------------------------------------------------------

ComRetry ::ComRetry(const char* const compName)
: ComRetryComponentBase(compName),
m_num_retries(1),
m_retry_count(0),
m_bufferState(Fw::Buffer::OwnershipState::OWNED) {}

ComRetry ::~ComRetry() {}

void ComRetry::configure(U32 num_retries) {
this->m_num_retries = num_retries;
}

// ----------------------------------------------------------------------
// Handler implementations for typed input ports
// ----------------------------------------------------------------------

void ComRetry ::comStatusIn_handler(FwIndexType portNum, Fw::Success& condition) {
if (condition == Fw::Success::SUCCESS) {
this->m_retry_count = 0;
this->dataReturnOut_out(0, this->m_buffer, this->m_context);
this->comStatusOut_out(0, condition);
}
// Delivery of last message failed
else if (this->m_retry_count < this->m_num_retries) {
FW_ASSERT(this->m_bufferState == Fw::Buffer::OwnershipState::OWNED);
this->m_retry_count++;
this->m_bufferState = Fw::Buffer::OwnershipState::NOT_OWNED;
this->dataOut_out(0, this->m_buffer, this->m_context);
}
// All retries failed, send FAILURE to upstream component
else {
this->m_retry_count = 0;
condition = Fw::Success::FAILURE;
this->dataReturnOut_out(0, this->m_buffer, this->m_context);
this->comStatusOut_out(0, condition);
}
}

void ComRetry ::dataIn_handler(FwIndexType portNum, Fw::Buffer& buffer, const ComCfg::FrameContext& context) {
FW_ASSERT(this->m_bufferState == Fw::Buffer::OwnershipState::OWNED);
this->m_bufferState = Fw::Buffer::OwnershipState::NOT_OWNED;
this->dataOut_out(0, buffer, context);
}

void ComRetry ::dataReturnIn_handler(FwIndexType portNum, Fw::Buffer& buffer, const ComCfg::FrameContext& context) {
FW_ASSERT(this->m_bufferState == Fw::Buffer::OwnershipState::NOT_OWNED);
this->m_bufferState = Fw::Buffer::OwnershipState::OWNED;
this->m_buffer = buffer;
this->m_context = context;
}

} // namespace Svc
6 changes: 6 additions & 0 deletions Svc/ComRetry/ComRetry.fpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
module Svc {
@ A component for retrying message delivery on failure
passive component ComRetry {
import Svc.Framer
}
}
70 changes: 70 additions & 0 deletions Svc/ComRetry/ComRetry.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
// ======================================================================
// \title ComRetry.hpp
// \author valdaarhun
// \brief hpp file for ComRetry component implementation class
// ======================================================================

#ifndef Svc_ComRetry_HPP
#define Svc_ComRetry_HPP

#include "Svc/ComRetry/ComRetryComponentAc.hpp"

namespace Svc {

class ComRetry final : public ComRetryComponentBase {
public:
// ----------------------------------------------------------------------
// Component construction and destruction
// ----------------------------------------------------------------------

//! Construct ComRetry object
ComRetry(const char* const compName //!< The component name
);

//! Destroy ComRetry object
~ComRetry();

//! Configure the number of retries
void configure(U32 num_retries //!< Number of retries allowed
);

private:
// ----------------------------------------------------------------------
// Handler implementations for typed input ports
// ----------------------------------------------------------------------

//! Handler implementation for comStatusIn
//!
//! Resend last delivered message on failure
void comStatusIn_handler(FwIndexType portNum, //!< The port number
Fw::Success& condition //!< Condition success/failure
) override;

//! Handler implementation for dataIn
//!
//! Port to receive data in a Fw::Buffer with optional context
void dataIn_handler(FwIndexType portNum, //!< The port number
Fw::Buffer& data,
const ComCfg::FrameContext& context) override;

//! Handler implementation for dataReturnIn
//!
//! Buffer coming from a deallocate call in a ComDriver component
void dataReturnIn_handler(FwIndexType portNum, //!< The port number
Fw::Buffer& data,
const ComCfg::FrameContext& context) override;

private:
// ----------------------------------------------------------------------
// Member variables
// ----------------------------------------------------------------------
U32 m_num_retries; //!< Maximum number of retries
U32 m_retry_count; //!< Track number of attempted retries
ComCfg::FrameContext m_context; //!< Context for the current frame
Fw::Buffer m_buffer; //!< Store incoming buffer
Fw::Buffer::OwnershipState m_bufferState; //!< Track ownership of stored buffer
};

} // namespace Svc

#endif
22 changes: 22 additions & 0 deletions Svc/ComRetry/docs/sdd.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# Svc::ComRetry

## 1. Introduction

The `Svc::ComRetry` component forwards messages from upstream to downstream components, resending messages on failure. Any topology requiring retry capabilities must place this component in the pipeline before a `ComStub` or `Radio` component. This component expects a `ComStatus` response. It acts as a pass-through component in case of a successful delivery, i.e. when it receives `Fw::Success::SUCCESS`. On receiving `Fw::Success::FAILURE`, it resends the message until it exceeds the maximum number of retries.

`Svc::ComRetry` can be used alongside the other F´ communication components (`Svc::Framer`, `Svc::Deframer`, `Svc::ComQueue`).

## 2. Requirements

| Requirement | Description | Rationale | Verification Method |
|-----------------|-----------------------------------------------------------------------------------------------------------------------------|-------------------------------------------------------------|---------------------|
| SVC-COMRETRY-001 | `Svc::ComRetry` shall accept incoming downlink data as `Fw::Buffer` and pass them to an `Svc.ComDataWithContext` port | The component must forward messages without modifying them | Unit Test |
| SVC-COMRETRY-002 | `Svc::ComRetry` shall store `Fw::Buffer` and its context on receiving buffer ownership through `dataReturnIn` | Store the buffer in case a retry is required | Unit test |
| SVC-COMRETRY-003 | `Svc::ComRetry` shall resend the stored `Fw::Buffer` on receiving `Fw::Success::FAILURE` | Retry delivery of message | Unit test |
| SVC-COMRETRY-004 | The maximum number of retries shall be configurable | The number of retries should be adaptable for projects | Inspection |
| SVC-COMRETRY-005 | `Svc::ComRetry` shall return buffer ownership to the upstream component on receiving `Fw::Success::SUCCESS` or after all retry attempts fail | Memory management | Unit Test |
| SVC-COMRETRY-006 | `Svc::ComRetry` shall send `ComStatus` upstream on successful delivery or after all retry attempts fail | Upstream component must receive status of message delivery from downstream | Unit Test |

## 3. Design

`Svc::ComRetry` implements `Svc.Framer`.
27 changes: 27 additions & 0 deletions Svc/ComRetry/test/ut/ComRetryTestMain.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
// ======================================================================
// \title ComRetryTestMain.cpp
// \author valdaarhun
// \brief cpp file for ComRetry test main function
// ======================================================================

#include "ComRetryTester.hpp"

TEST(Nominal, Send) {
Svc::ComRetryTester tester;
tester.testBufferSend();
}

TEST(Nominal, Retry) {
Svc::ComRetryTester tester;
tester.testBufferRetry();
}

TEST(Nominal, RetryTillFailure) {
Svc::ComRetryTester tester;
tester.testBufferRetryTillFailure();
}

int main(int argc, char** argv) {
::testing::InitGoogleTest(&argc, argv);
return RUN_ALL_TESTS();
}
127 changes: 127 additions & 0 deletions Svc/ComRetry/test/ut/ComRetryTester.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
// ======================================================================
// \title ComRetryTester.cpp
// \author valdaarhun
// \brief cpp file for ComRetry test harness implementation class
// ======================================================================

#include "ComRetryTester.hpp"

namespace Svc {

// ----------------------------------------------------------------------
// Construction and destruction
// ----------------------------------------------------------------------

ComRetryTester ::ComRetryTester()
: ComRetryGTestBase("ComRetryTester", ComRetryTester::MAX_HISTORY_SIZE), component("ComRetry") {
this->initComponents();
this->connectPorts();
}

ComRetryTester ::~ComRetryTester() {}

void ComRetryTester ::configure(U32 num_retries=1) {
component.configure(num_retries);
}

void ComRetryTester ::receiveBuffer(Fw::Buffer &buffer, ComCfg::FrameContext &context) {
invoke_to_dataIn(0, buffer, context);
invoke_to_dataReturnIn(0, buffer, context);
}

void ComRetryTester ::checkDataOut(FwIndexType expectedIndex, U8* expectedData, FwSizeType expectedDataSize) {
Fw::Buffer emittedBuffer = this->fromPortHistory_dataOut->at(expectedIndex).data;
ASSERT_EQ(expectedDataSize, emittedBuffer.getSize());
for (FwSizeType i = 0; i < expectedDataSize; i++) {
ASSERT_EQ(emittedBuffer.getData()[i], expectedData[i]);
}
}

// ----------------------------------------------------------------------
// Tests
// ----------------------------------------------------------------------

void ComRetryTester ::testBufferSend() {
U8 data_a[BUFFER_LENGTH] = DATA_A;
U8 data_b[BUFFER_LENGTH] = DATA_B;
Fw::Buffer buffer_a(&data_a[0], sizeof(data_a));
Fw::Buffer buffer_b(&data_b[0], sizeof(data_b));
ComCfg::FrameContext nullContext;
Fw::Success state = Fw::Success::SUCCESS;
configure();

receiveBuffer(buffer_a, nullContext);
invoke_to_comStatusIn(0, state);
ASSERT_from_dataReturnOut(0, buffer_a, nullContext);
ASSERT_from_comStatusOut(0, state);

receiveBuffer(buffer_b, nullContext);
invoke_to_comStatusIn(0, state);
ASSERT_from_dataReturnOut(1, buffer_b, nullContext);
ASSERT_from_comStatusOut(1, state);

checkDataOut(0, buffer_a.getData(), buffer_a.getSize());
checkDataOut(1, buffer_b.getData(), buffer_b.getSize());
}

void ComRetryTester ::testBufferRetry() {
U8 data_a[BUFFER_LENGTH] = DATA_A;
U8 data_b[BUFFER_LENGTH] = DATA_B;
Fw::Buffer buffer_a(&data_a[0], sizeof(data_a));
Fw::Buffer buffer_b(&data_b[0], sizeof(data_b));
ComCfg::FrameContext nullContext;
Fw::Success state = Fw::Success::FAILURE;
configure();

receiveBuffer(buffer_a, nullContext);
invoke_to_comStatusIn(0, state);
invoke_to_dataReturnIn(0, buffer_a, nullContext);

state = Fw::Success::SUCCESS;
invoke_to_comStatusIn(0, state);
ASSERT_from_dataReturnOut(0, buffer_a, nullContext);
ASSERT_from_comStatusOut(0, state);

receiveBuffer(buffer_b, nullContext);
invoke_to_comStatusIn(0, state);
ASSERT_from_dataReturnOut(1, buffer_b, nullContext);
ASSERT_from_comStatusOut(1, state);

checkDataOut(0, buffer_a.getData(), buffer_a.getSize());
checkDataOut(1, buffer_a.getData(), buffer_a.getSize());
checkDataOut(2, buffer_b.getData(), buffer_b.getSize());
}

void ComRetryTester ::testBufferRetryTillFailure() {
U8 data_a[BUFFER_LENGTH] = DATA_A;
U8 data_b[BUFFER_LENGTH] = DATA_B;
Fw::Buffer buffer_a(&data_a[0], sizeof(data_a));
Fw::Buffer buffer_b(&data_b[0], sizeof(data_b));
ComCfg::FrameContext nullContext;
Fw::Success state = Fw::Success::FAILURE;

FwIndexType num_retries = 3;
configure(num_retries);

receiveBuffer(buffer_a, nullContext);
invoke_to_comStatusIn(0, state);
checkDataOut(0, buffer_a.getData(), buffer_a.getSize());

for (FwIndexType i = 1; i <= num_retries; i++) {
invoke_to_dataReturnIn(0, buffer_a, nullContext);
invoke_to_comStatusIn(0, state);
checkDataOut(i, buffer_a.getData(), buffer_a.getSize());
}

ASSERT_from_dataReturnOut(0, buffer_a, nullContext);
ASSERT_from_comStatusOut(0, state);

state = Fw::Success::SUCCESS;
receiveBuffer(buffer_b, nullContext);
invoke_to_comStatusIn(0, state);
ASSERT_from_dataReturnOut(1, buffer_b, nullContext);
ASSERT_from_comStatusOut(1, state);
checkDataOut(num_retries + 1, buffer_b.getData(), buffer_b.getSize());
}

} // namespace Svc
Loading
Loading