Skip to content
This repository was archived by the owner on Dec 17, 2025. It is now read-only.

Commit 2424077

Browse files
authored
Feature/integrate downstream 2 (#7)
* feat: Implement first downstream handling * fix: Fix review comments * feat: Better rendering of some messages * client: Add some remarks * feat: Continue impl of downstream handling * test: Use default diag session * feat: Begin impl of UDS services * fix: DoIPAddress now accepts single uint16 * feat: Improve message output * feat: Implement UDS services 0x10, 0x2E and 0x3E * feat: Implemented remaining UDS services so that script finishes successfully * feat: Rename UdsHelpers to UdsMock * feat: Enhance UdsMock and UdsResponseCode with improved handling and output formatting * fix: Check for nullptr in stream operator
1 parent 228d121 commit 2424077

28 files changed

Lines changed: 1028 additions & 444 deletions

CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,7 @@ set(SOURCES
8383
src/Logger.cpp
8484
src/MacAddress.cpp
8585
src/DoIPDefaultConnection.cpp
86+
src/uds/UdsMock.cpp
8687
)
8788

8889

examples/ExampleDoIPServerModel.h

Lines changed: 134 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -7,35 +7,159 @@
77
#define EXAMPLEDOIPSERVERMODEL_H
88

99
#include "DoIPServerModel.h"
10+
#include "ThreadSafeQueue.h"
11+
#include "uds/UdsMock.h"
12+
#include "uds/UdsResponseCode.h"
1013

1114
using namespace doip;
1215

1316
class ExampleDoIPServerModel : public DoIPServerModel {
1417
public:
1518
ExampleDoIPServerModel() {
16-
onCloseConnection = [](IConnectionContext &ctx, DoIPCloseReason reason) noexcept {
19+
onOpenConnection = [this](IConnectionContext &ctx) noexcept {
1720
(void)ctx;
21+
startWorker();
22+
};
23+
onCloseConnection = [this](IConnectionContext &ctx, DoIPCloseReason reason) noexcept {
24+
(void)ctx;
25+
stopWorker();
1826
DOIP_LOG_WARN("Connection closed ({})", fmt::streamed(reason));
1927
};
2028

21-
onDiagnosticMessage = [](IConnectionContext &ctx, const DoIPMessage &msg) noexcept -> DoIPDiagnosticAck {
29+
onDiagnosticMessage = [this](IConnectionContext &ctx, const DoIPMessage &msg) noexcept -> DoIPDiagnosticAck {
2230
(void)ctx;
23-
DOIP_LOG_INFO("Received Diagnostic message (from ExampleDoIPServerModel)", fmt::streamed(msg));
31+
m_log->info("Received Diagnostic message (from ExampleDoIPServerModel)", fmt::streamed(msg));
2432

2533
// Example: Access payload using getPayload()
26-
auto payload = msg.getDiagnosticMessagePayload();
27-
if (payload.second >= 3 && payload.first[0] == 0x22 && payload.first[1] == 0xF1 && payload.first[2] == 0x90) {
28-
DOIP_LOG_INFO(" - Detected Read Data by Identifier for VIN (0xF190) -> send NACK");
29-
return DoIPNegativeDiagnosticAck::UnknownTargetAddress;
30-
}
34+
// auto payload = msg.getDiagnosticMessagePayload();
35+
// if (payload.second >= 3 && payload.first[0] == 0x22 && payload.first[1] == 0xF1 && payload.first[2] == 0x90) {
36+
// m_log->info(" - Detected Read Data by Identifier for VIN (0xF190) -> send NACK");
37+
// return DoIPNegativeDiagnosticAck::UnknownTargetAddress;
38+
// }
3139

3240
return std::nullopt;
3341
};
3442

35-
onDiagnosticNotification = [](IConnectionContext &ctx, DoIPDiagnosticAck ack) noexcept {
43+
onDiagnosticNotification = [this](IConnectionContext &ctx, DoIPDiagnosticAck ack) noexcept {
3644
(void)ctx;
37-
DOIP_LOG_INFO("Diagnostic ACK/NACK sent (from ExampleDoIPServerModel)", fmt::streamed(ack));
45+
m_log->info("Diagnostic ACK/NACK sent (from ExampleDoIPServerModel)", fmt::streamed(ack));
46+
};
47+
48+
onDownstreamRequest = [this](IConnectionContext &ctx, const DoIPMessage &msg, ServerModelDownstreamResponseHandler callback) noexcept {
49+
(void)ctx;
50+
(void)msg;
51+
52+
m_log->info("Received downstream request (from ExampleDoIPServerModel)", fmt::streamed(msg));
53+
m_downstreamCallback = callback;
54+
if (!m_downstreamCallback) {
55+
m_log->error("onDownstreamRequest: No callback function passed");
56+
return DoIPDownstreamResult::Error;
57+
}
58+
59+
// Store message in send queue
60+
auto [data, size] = msg.getDiagnosticMessagePayload();
61+
m_tx.push(ByteArray(data, size));
62+
m_log->info("Enqueued msg");
63+
return DoIPDownstreamResult::Pending;
3864
};
65+
66+
m_uds.registerDefaultServices();
67+
68+
m_uds.registerDiagnosticSessionControlHandler([this](uint8_t sessionType) {
69+
m_loguds->info("Diagnostic Session Control requested, sessionType={:02X}", sessionType);
70+
auto response = ByteArray{sessionType}; // Positive response SID = 0x50
71+
response.writeU16BE(m_p2_ms);
72+
response.writeU16BE(m_p2star_10ms);
73+
return std::make_pair(uds::UdsResponseCode::PositiveResponse, response); // Positive response
74+
});
75+
76+
m_uds.registerECUResetHandler([this](uint8_t resetType) {
77+
m_loguds->info("ECU Reset requested, resetType={:02X}", resetType);
78+
return std::make_pair(uds::UdsResponseCode::PositiveResponse, ByteArray{resetType}); // Positive response SID = 0x61
79+
});
80+
81+
m_uds.registerReadDataByIdentifierHandler([this](uint16_t did) {
82+
m_loguds->info("Read Data By Identifier requested, DID={:04X}", did);
83+
if (did == 0xF190) {
84+
// Return example VIN
85+
ByteArray vinPayload = {'1', 'H', 'G', 'C', 'M',
86+
'8', '2', '6', '3', '3',
87+
'A', '0', '0', '0', '0', '1', 'Z'};
88+
ByteArray response = {static_cast<uint8_t>((did >> 8) & 0xFF), static_cast<uint8_t>(did & 0xFF)};
89+
response.insert(response.end(), vinPayload.begin(), vinPayload.end());
90+
return std::make_pair(uds::UdsResponseCode::PositiveResponse, response); // Positive response
91+
}
92+
return std::make_pair(uds::UdsResponseCode::RequestOutOfRange, ByteArray{0x22}); // Positive response
93+
});
94+
95+
m_uds.registerWriteDataByIdentifierHandler([this](uint16_t did, ByteArray value) {
96+
m_loguds->info("Write Data By Identifier requested, DID={:04X}, value={}", did, fmt::streamed(value));
97+
if (did == 0xF190) {
98+
// Accept VIN write
99+
return std::make_pair(uds::UdsResponseCode::PositiveResponse, ByteArray{static_cast<uint8_t>((did >> 8) & 0xFF), static_cast<uint8_t>(did & 0xFF)}); // Positive response
100+
}
101+
return std::make_pair(uds::UdsResponseCode::RequestOutOfRange, ByteArray{0x2E}); // NRC for WriteDataByIdentifier
102+
});
103+
104+
m_uds.registerTesterPresentHandler([this](uint8_t subFunction) {
105+
m_loguds->info("Tester Present requested, subFunction={:02X}", subFunction);
106+
return std::make_pair(uds::UdsResponseCode::PositiveResponse, ByteArray{0x00}); // Positive response SID = 0x7E
107+
});
108+
}
109+
110+
private:
111+
std::shared_ptr<spdlog::logger> m_log = Logger::get("smodel");
112+
std::shared_ptr<spdlog::logger> m_loguds = Logger::get("uds");
113+
ServerModelDownstreamResponseHandler m_downstreamCallback = nullptr;
114+
ThreadSafeQueue<ByteArray> m_rx;
115+
ThreadSafeQueue<ByteArray> m_tx;
116+
uds::UdsMock m_uds;
117+
std::thread m_worker;
118+
bool m_running = true;
119+
uint16_t m_p2_ms = 1000;
120+
uint16_t m_p2star_10ms = 200;
121+
122+
123+
void startWorker() {
124+
m_worker = std::thread([this] {
125+
while (m_running) {
126+
downstream_thread();
127+
}
128+
});
129+
m_log->info("Started worker thread");
130+
}
131+
void stopWorker() {
132+
m_running = false;
133+
if (m_worker.joinable())
134+
m_worker.join();
135+
m_log->info("Stopped worker thread");
136+
}
137+
138+
/**
139+
* @brief Thread simulating downstream communication (e. g. CAN).
140+
*/
141+
void downstream_thread() {
142+
if (m_tx.size()) {
143+
// simulate send. In a real environment we could send a CAN message
144+
ByteArray req;
145+
m_tx.pop(req);
146+
m_log->info("Simulate send {}", fmt::streamed(req));
147+
// simulate some latency
148+
std::this_thread::sleep_for(50ms);
149+
// simulate receive
150+
m_rx.push(m_uds.handleDiagnosticRequest(req));
151+
}
152+
153+
if (m_rx.size()) {
154+
ByteArray rsp;
155+
m_rx.pop(rsp);
156+
m_log->info("Simulate receive {}", fmt::streamed(rsp));
157+
if (m_downstreamCallback) {
158+
m_downstreamCallback(rsp, DoIPDownstreamResult::Handled);
159+
m_downstreamCallback = nullptr;
160+
}
161+
}
162+
std::this_thread::sleep_for(10ms);
39163
}
40164
};
41165

examples/exampleDoIPClient.cpp

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,12 +67,19 @@ int main(int argc, char *argv[]) {
6767
exit(EXIT_FAILURE);
6868
}
6969

70+
// NEEDS REWORK! State handling required!
7071
client.receiveMessage();
7172

7273
client.sendDiagnosticMessage({0x22, 0xF1, 0x90}); // Example: Read Data by Identifier (0xF190 = VIN)
73-
client.receiveMessage();
74+
client.receiveMessage(); // ack
75+
std::this_thread::sleep_for(2s);
7476
client.sendDiagnosticMessage({0x22, 0xF2, 0x90}); // Example: Read Data by Identifier (0xF190 = VIN)
7577
client.receiveMessage();
78+
std::this_thread::sleep_for(2s);
79+
client.sendDiagnosticMessage({0x22, 0xF1, 0x90}); // Example: Read Data by Identifier (0xF190 = VIN)
80+
client.receiveMessage(); // ack
81+
82+
std::this_thread::sleep_for(2s);
7683

7784
client.closeTcpConnection();
7885
client.closeUdpConnection();

examples/exampleDoIPServer.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
using namespace doip;
1515
using namespace std;
1616

17-
static const DoIPAddress LOGICAL_ADDRESS(static_cast<uint8_t>(0x0), static_cast<uint8_t>(0x28));
17+
static const DoIPAddress LOGICAL_ADDRESS(0x0028);
1818

1919
DoIPServer server;
2020
std::vector<std::thread> doipReceiver;

examples/exampleDoIPServerSimple.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
using namespace doip;
1212
using namespace std;
1313

14-
static const DoIPAddress LOGICAL_ADDRESS(static_cast<uint8_t>(0x00), static_cast<uint8_t>(0x28));
14+
static const DoIPAddress LOGICAL_ADDRESS(0x0028);
1515
static std::atomic<bool> g_shutdownRequested{false};
1616

1717
// Forward declarations

inc/ByteArray.h

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,7 @@ struct ByteArray : std::vector<uint8_t> {
107107
*
108108
* @param value The 16-bit value to append
109109
*/
110-
void writeU16(uint16_t value) {
110+
void writeU16BE(uint16_t value) {
111111
emplace_back(static_cast<uint8_t>((value >> 8) & 0xFF));
112112
emplace_back(static_cast<uint8_t>(value & 0xFF));
113113
}
@@ -140,7 +140,7 @@ struct ByteArray : std::vector<uint8_t> {
140140
*
141141
* @param value The 32-bit value to append
142142
*/
143-
void writeU32(uint32_t value) {
143+
void writeU32BE(uint32_t value) {
144144
emplace_back(static_cast<uint8_t>((value >> 24) & 0xFF));
145145
emplace_back(static_cast<uint8_t>((value >> 16) & 0xFF));
146146
emplace_back(static_cast<uint8_t>((value >> 8) & 0xFF));
@@ -171,9 +171,9 @@ struct ByteArray : std::vector<uint8_t> {
171171
if constexpr (sizeof(UnderlyingType) == 1) {
172172
emplace_back(static_cast<uint8_t>(integral_value));
173173
} else if constexpr (sizeof(UnderlyingType) == 2) {
174-
writeU16(static_cast<uint16_t>(integral_value));
174+
writeU16BE(static_cast<uint16_t>(integral_value));
175175
} else if constexpr (sizeof(UnderlyingType) == 4) {
176-
writeU32(static_cast<uint32_t>(integral_value));
176+
writeU32BE(static_cast<uint32_t>(integral_value));
177177
} else {
178178
static_assert(sizeof(UnderlyingType) <= 4, "Enum underlying type too large (max 32-bit supported)");
179179
}

inc/DoIPAddress.h

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,10 +28,9 @@ struct DoIPAddress {
2828
/**
2929
* @brief Constructs an DoIPAddress with specified high and low significant bytes.
3030
*
31-
* @param hsb High significant byte (default: 0)
32-
* @param lsb Low significant byte (default: 0)
31+
* @param address the address
3332
*/
34-
constexpr explicit DoIPAddress(uint8_t hsb = 0, uint8_t lsb = 0) : m_bytes{{hsb, lsb}} {}
33+
constexpr explicit DoIPAddress(uint16_t address = 0x0000) : m_bytes{static_cast<uint8_t>(address >> 8), static_cast<uint8_t>(address & 0xff)} {}
3534

3635
/**
3736
* @brief Constructs an DoIPAddress from a byte array starting at the specified offset.

inc/DoIPClient.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ class DoIPClient {
5959
int _sockFd{-1}, _sockFd_udp{-1}, _sockFd_announcement{-1}, _connected{-1};
6060
int m_broadcast = 1;
6161
struct sockaddr_in _serverAddr, _clientAddr, _announcementAddr;
62-
DoIPAddress m_sourceAddress = DoIPAddress(0xE0, 0x00); // HACK
62+
DoIPAddress m_sourceAddress = DoIPAddress(0xE000);
6363

6464
uint8_t VINResult[17] = {0};
6565
DoIPAddress m_logicalAddress = DoIPAddress::ZeroAddress;

inc/DoIPConnection.h

Lines changed: 0 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -99,39 +99,6 @@ class DoIPConnection : public DoIPDefaultConnection {
9999
*/
100100
bool hasDownstreamHandler() const override;
101101

102-
/**
103-
* @brief Forward a diagnostic message to downstream
104-
*
105-
* Delegates to DoIPServerModel::onDownstreamRequest callback.
106-
* The callback is responsible for sending the message via
107-
* the appropriate transport (CAN, LIN, etc.).
108-
*
109-
* @param msg The diagnostic message to forward
110-
* @return Result indicating if the request was initiated successfully
111-
*/
112-
DoIPDownstreamResult notifyDownstreamRequest(const DoIPMessage &msg) override;
113-
114-
/**
115-
* @brief Receive a response from downstream device
116-
*
117-
* Called by the application when a response arrives from downstream.
118-
* This injects the response into the state machine via
119-
* processEvent(DiagnosticMessageReceivedDownstream, response).
120-
*
121-
* @param response The diagnostic response from downstream
122-
*/
123-
void receiveDownstreamResponse(const DoIPMessage &response) override;
124-
125-
/**
126-
* @brief Notify application that downstream response was received
127-
*
128-
* Delegates to DoIPServerModel::onDownstreamResponse callback if set.
129-
*
130-
* @param request The original request that was sent downstream
131-
* @param response The response received from downstream
132-
*/
133-
void notifyDownstreamResponseReceived(const DoIPMessage &request, const DoIPMessage &response) override;
134-
135102
private:
136103
DoIPAddress m_gatewayAddress;
137104

inc/DoIPDefaultConnection.h

Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -248,15 +248,9 @@ class DoIPDefaultConnection : public IConnectionContext {
248248
/**
249249
* @brief Receives a downstream response
250250
* @param response The downstream response message
251+
* @param result the downstream result
251252
*/
252-
void receiveDownstreamResponse(const DoIPMessage &response) override;
253-
254-
/**
255-
* @brief Notifies application that a downstream response was received
256-
* @param request The original request
257-
* @param response The response message
258-
*/
259-
void notifyDownstreamResponseReceived(const DoIPMessage &request, const DoIPMessage &response) override;
253+
void receiveDownstreamResponse(const ByteArray &response, DoIPDownstreamResult result) override;
260254

261255
/**
262256
* @brief Gets the current state of the connection
@@ -327,6 +321,7 @@ class DoIPDefaultConnection : public IConnectionContext {
327321
ssize_t sendRoutingActivationResponse(const DoIPAddress &source_address, DoIPRoutingActivationResult response_code);
328322
ssize_t sendAliveCheckRequest();
329323
ssize_t sendDiagnosticMessageResponse(const DoIPAddress &sourceAddress, DoIPDiagnosticAck ack);
324+
ssize_t sendDownstreamResponse(const DoIPAddress &sourceAddress, const ByteArray& payload);
330325
};
331326

332327
} // namespace doip

0 commit comments

Comments
 (0)