Skip to content

Commit

Permalink
Adds implementation for railcom messages related to logon (#562)
Browse files Browse the repository at this point in the history
- Adds constants for the automatic logon railcom messages
- Adds helper functions in the RailcomDefs to generate the S-9.2.1.1 / RCN-218 feedback messages.
- Adds a processor for a railcom hub that recognizes these feedback messages and checks for correctness.

===

* Adds code for parsing 254 railcom feedback messages.

* Adds skeleton for state flow handling the logon sequence.

* Adds declarations for track interface.

* Adds implementation of the most important feedback messages.

* Adds support for decoder ID replies to logon enable.

* Adds implementation for shortinfo reply.

* Adds implementation for Logon Assign feedback.

* Fix whitespace

* Fixes comments.
  • Loading branch information
balazsracz authored Aug 13, 2021
1 parent 87be9a8 commit bd2f9ec
Show file tree
Hide file tree
Showing 8 changed files with 814 additions and 1 deletion.
88 changes: 88 additions & 0 deletions src/dcc/Logon.hxx
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
/** \copyright
* Copyright (c) 2021, Balazs Racz
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* - Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* - Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*
* \file Logon.hxx
* Control flows for supporting DCC Automatic Logon.
*
* @author Balazs Racz
* @date 12 Aug 2021
*/

#ifndef _DCC_LOGON_HXX_
#define _DCC_LOGON_HXX_

#include "dcc/PacketSource.hxx"
#include "dcc/TrackIf.hxx"
#include "dcc/UpdateLoop.hxx"
#include "dcc/LogonFeedback.hxx"
#include "executor/StateFlow.hxx"

namespace dcc
{

/// This class needs to be a base class for the template argument of the Logon
/// Handler.
class LogonHandlerModule
{

}; // LogonHandlerModule

/// Handles the automatic logon flow for DCC decoders.
template <class Module>
class LogonHandler : public NonTrainPacketSource,
public StateFlowBase,
public RailcomHubPortInterface
{
public:
/// Constructor
///
/// @param service points to the executor to use.
/// @param track pointer to the track interface to send DCC packets to.
/// @param rcom_hub will register to this railcom hub to get feedback.
LogonHandler(Service *service, TrackIf *track, RailcomHubFlow *rcom_hub)
: StateFlowBase(service)
, trackIf_(track)
{
}

/// Initiates a logon sequence at startup.
void startup_logon()
{
start_flow(STATE(startup_logon));
}

private:
Action startup_logon()
{
}

/// If we need to send packets to the track, we can do it here directly.
TrackIf *trackIf_;
}; // LogonHandler

} // namespace dcc

#endif // _DCC_LOGON_HXX_
220 changes: 220 additions & 0 deletions src/dcc/LogonFeedback.cxxtest
Original file line number Diff line number Diff line change
@@ -0,0 +1,220 @@
#include "dcc/LogonFeedback.hxx"

#include "utils/test_main.hxx"

using ::testing::Return;

namespace dcc
{

class MockFeedbackCallbacks : public LogonFeedbackCallbacks
{
public:
MOCK_METHOD1(
classify_packet, LogonFeedbackCallbacks::PacketType(uintptr_t));
MOCK_METHOD3(process_select_shortinfo,
void(uintptr_t feedback_key, bool error, uint64_t data));

MOCK_METHOD3(process_logon_assign,
void(uintptr_t feedback_key, bool error, uint64_t data));

MOCK_METHOD3(process_decoder_id,
void(uintptr_t feedback_key, bool error, uint64_t data));
};

class ParseFeedbackTest : public ::testing::Test
{
protected:
/// Creates a valid 8-byte railcom feedback in fb_.
void create_valid_code()
{
RailcomDefs::append12(15, 0x44, fb_.ch1Data);
fb_.ch1Size = 2;

RailcomDefs::append36(0xa, 0x11223344, fb_.ch2Data);
fb_.ch2Size = 6;
}

/// Sends the current value in fb_ to the railcom hub.
void send()
{
auto *b = hub_.alloc();
b->data()->value() = fb_;
hub_.send(b);
wait_for_main_executor();
}

::testing::StrictMock<MockFeedbackCallbacks> cb_;
dcc::Feedback fb_;
RailcomHubFlow hub_ {&g_service};
LogonFeedbackParser parser_ {&cb_, &hub_};
};

TEST_F(ParseFeedbackTest, valid_code)
{
create_valid_code();
uint64_t d = LogonFeedbackParser::parse_code(&fb_);
EXPECT_EQ(0u, d >> LogonFeedbackParser::ERROR_SHIFT);
EXPECT_EQ(8u, ((d >> LogonFeedbackParser::LENGTH_SHIFT) & 0xff));
EXPECT_EQ(0xf44a11223344u, (d & LogonFeedbackParser::PAYLOAD_MASK));
}

TEST_F(ParseFeedbackTest, conflict_code)
{
create_valid_code();
fb_.ch1Data[1] |= 0xFF;
uint64_t d = LogonFeedbackParser::parse_code(&fb_);
EXPECT_EQ(LogonFeedbackParser::ERROR_GARBAGE |
LogonFeedbackParser::ERROR_OUT_OF_ORDER,
d & LogonFeedbackParser::ERROR_MASK);
EXPECT_EQ(7u, ((d >> LogonFeedbackParser::LENGTH_SHIFT) & 0xff));
// Some bits are blanked out.
EXPECT_EQ(0xf40a11223344u, (d & LogonFeedbackParser::PAYLOAD_MASK));
}

TEST_F(ParseFeedbackTest, short_code)
{
create_valid_code();
fb_.ch2Size = 4;
uint64_t d = LogonFeedbackParser::parse_code(&fb_);
EXPECT_EQ(LogonFeedbackParser::ERROR_MISSING_DATA,
d & LogonFeedbackParser::ERROR_MASK);
EXPECT_EQ(6u, ((d >> LogonFeedbackParser::LENGTH_SHIFT) & 0xff));
// Some bits are blanked out.
EXPECT_EQ(0xf44a11223000u, (d & LogonFeedbackParser::PAYLOAD_MASK));
}

TEST_F(ParseFeedbackTest, short_code_ack)
{
create_valid_code();
RailcomDefs::append36(0xa, 0x11220000, fb_.ch2Data);
fb_.ch2Data[5] = RailcomDefs::CODE_ACK;
fb_.ch2Data[4] = RailcomDefs::CODE_ACK2;
uint64_t d = LogonFeedbackParser::parse_code(&fb_);
EXPECT_EQ(
LogonFeedbackParser::ERROR_ACK, d & LogonFeedbackParser::ERROR_MASK);
EXPECT_EQ(6u, ((d >> LogonFeedbackParser::LENGTH_SHIFT) & 0xff));
// Some bits are blanked out.
EXPECT_EQ(0xf44a11220000u, (d & LogonFeedbackParser::PAYLOAD_MASK));
}

TEST_F(ParseFeedbackTest, out_of_order_ack)
{
create_valid_code();
fb_.ch1Data[1] = RailcomDefs::CODE_ACK;
uint64_t d = LogonFeedbackParser::parse_code(&fb_);
EXPECT_EQ(LogonFeedbackParser::ERROR_ACK |
LogonFeedbackParser::ERROR_OUT_OF_ORDER,
d & LogonFeedbackParser::ERROR_MASK);
EXPECT_EQ(7u, ((d >> LogonFeedbackParser::LENGTH_SHIFT) & 0xff));
// Some bits are blanked out.
EXPECT_EQ(0xf40a11223344u, (d & LogonFeedbackParser::PAYLOAD_MASK));
}

TEST_F(ParseFeedbackTest, one_ack)
{
fb_.ch1Data[0] = RailcomDefs::CODE_ACK;
fb_.ch1Size = 1;
fb_.ch2Size = 0;
uint64_t d = LogonFeedbackParser::parse_code(&fb_);
EXPECT_EQ(LogonFeedbackParser::ERROR_ACK |
LogonFeedbackParser::ERROR_MISSING_DATA,
d & LogonFeedbackParser::ERROR_MASK);
EXPECT_EQ(0u, ((d >> LogonFeedbackParser::LENGTH_SHIFT) & 0xff));
EXPECT_EQ(0u, (d & LogonFeedbackParser::PAYLOAD_MASK));
}

TEST_F(ParseFeedbackTest, unknown)
{
create_valid_code();
fb_.ch1Data[1] = RailcomDefs::CODE_BUSY;
uint64_t d = LogonFeedbackParser::parse_code(&fb_);
EXPECT_EQ(LogonFeedbackParser::ERROR_UNKNOWN |
LogonFeedbackParser::ERROR_OUT_OF_ORDER,
d & LogonFeedbackParser::ERROR_MASK);
EXPECT_EQ(7u, ((d >> LogonFeedbackParser::LENGTH_SHIFT) & 0xff));
// Some bits are blanked out.
EXPECT_EQ(0xf40a11223344u, (d & LogonFeedbackParser::PAYLOAD_MASK));
}

TEST_F(ParseFeedbackTest, decoder_id_reply)
{
constexpr uint64_t decoder_id = 0x79944332211ull;
constexpr uintptr_t key = 0x5a5a5a5a;
RailcomDefs::add_did_feedback(decoder_id, &fb_);
fb_.feedbackKey = key;
::testing::InSequence sq;
EXPECT_CALL(cb_, classify_packet(key))
.WillOnce(Return(LogonFeedbackCallbacks::LOGON_ENABLE));
EXPECT_CALL(
cb_, process_decoder_id(key, false, decoder_id | (15ull << 44)));
send();
}

TEST_F(ParseFeedbackTest, short_info_reply)
{
constexpr uintptr_t key = 0x5a5a5a5a;
RailcomDefs::add_shortinfo_feedback(0x1382, 0x55, 0xa7, 0x03, &fb_);
fb_.feedbackKey = key;
::testing::InSequence sq;
EXPECT_CALL(cb_, classify_packet(key))
.WillOnce(Return(LogonFeedbackCallbacks::SELECT_SHORTINFO));
EXPECT_CALL(cb_, process_select_shortinfo(key, false, 0x938255a7030bull));
send();
}

TEST_F(ParseFeedbackTest, short_info_crc_error)
{
constexpr uintptr_t key = 0x5a5a5a5a;
RailcomDefs::add_shortinfo_feedback(0x1382, 0x55, 0xa7, 0x03, &fb_);
fb_.feedbackKey = key;
fb_.ch2Data[5] = railcom_encode[0x3f];
::testing::InSequence sq;
EXPECT_CALL(cb_, classify_packet(key))
.WillOnce(Return(LogonFeedbackCallbacks::SELECT_SHORTINFO));
// Reports error
EXPECT_CALL(cb_, process_select_shortinfo(key, true, 0x938255a7033full));
send();
}

TEST_F(ParseFeedbackTest, short_info_garbage)
{
constexpr uintptr_t key = 0x5a5a5a5a;
RailcomDefs::add_shortinfo_feedback(0x1382, 0x55, 0xa7, 0x03, &fb_);
fb_.feedbackKey = key;
fb_.ch2Data[5] = 0xF3; // invalid 4/8 code
::testing::InSequence sq;
EXPECT_CALL(cb_, classify_packet(key))
.WillOnce(Return(LogonFeedbackCallbacks::SELECT_SHORTINFO));
// Reports error
EXPECT_CALL(cb_, process_select_shortinfo(key, true, 0x938255a70300ull));
send();
}

TEST_F(ParseFeedbackTest, assign_response)
{
constexpr uintptr_t key = 0x5a5a5a5a;
RailcomDefs::add_assign_feedback(0x5a, 0x327, 0x18, 0x22, &fb_);
fb_.feedbackKey = key;
::testing::InSequence sq;
EXPECT_CALL(cb_, classify_packet(key))
.WillOnce(Return(LogonFeedbackCallbacks::LOGON_ASSIGN));
EXPECT_CALL(cb_, process_logon_assign(key, false, 0xd5a327182246ull));
send();
}

TEST_F(ParseFeedbackTest, assign_response_crc_error)
{
constexpr uintptr_t key = 0x5a5a5a5a;
RailcomDefs::add_assign_feedback(0x5a, 0x327, 0x18, 0x22, &fb_);
fb_.feedbackKey = key;
fb_.ch2Data[5] = railcom_encode[0x3f];
::testing::InSequence sq;
EXPECT_CALL(cb_, classify_packet(key))
.WillOnce(Return(LogonFeedbackCallbacks::LOGON_ASSIGN));
// Reports error
EXPECT_CALL(cb_, process_logon_assign(key, true, 0xd5a32718227full));
send();
}

} // namespace dcc
Loading

0 comments on commit bd2f9ec

Please sign in to comment.