65db38d9f2
tests / build-and-test (push) Failing after 31s
Closes the S9 stream. Every documented protocol-error condition is now
auto-emitted by Connection (with the assist of one Router predicate),
without involving the application.
Router (include/secsgem/gem/router.hpp)
Adds two predicates: has_handler(stream, function) and
has_handler_for_stream(stream). Lets the wrapping message handler
decide whether an unhandled message is "unknown stream" (S9F3) or
"unknown function in a known stream" (S9F5).
Connection (include/secsgem/hsms/{connection.hpp, connection.cpp})
- emit_s9() goes public so the message_handler can call it.
- New current_header() accessor returns the HSMS header of the
primary currently being dispatched. Non-null only inside the
on_message_ call; cleared on the way out.
- handle_data sets current_header_ before invoking on_message_.
- on_length on oversized frame: synthesizes a 10-byte MHEAD whose
first 4 bytes are the offending length prefix, emits S9F11, and
sets close_after_flush so the S9F11 goes out before the socket
closes.
Server (apps/secs_server.cpp)
The conn->set_message_handler lambda now wraps router.dispatch. For
any inbound primary without a registered handler, it captures the
MHEAD via current_header() and emits either S9F3 (stream unknown) or
S9F5 (function unknown). The wrapper still returns the Router's
reply (SxF0 for primaries with W) so transactional semantics are
preserved.
COMPLIANCE.md
Error Messages row flips from 🟡 to ✅. S9F3/F5/F11 rows in the
coverage matrix flip from 🟡 to ✅. Each row in the matrix now
states its trigger condition explicitly. Drops the
"Finish S9 wiring" bullet from the "what would 100% take" list.
Verified
- Tests: 78 cases / 454 assertions still pass (no behavioural change
on the happy path; new emission paths fire only on protocol errors
that the demo doesn't induce).
- Build clean.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
68 lines
2.0 KiB
C++
68 lines
2.0 KiB
C++
#pragma once
|
|
|
|
#include <cstdint>
|
|
#include <functional>
|
|
#include <map>
|
|
#include <optional>
|
|
#include <utility>
|
|
|
|
#include "secsgem/secs2/message.hpp"
|
|
|
|
namespace secsgem::gem {
|
|
|
|
namespace s2 = secsgem::secs2;
|
|
|
|
// A small (stream, function) dispatch table. The Server registers one
|
|
// handler per primary SxFy and calls `dispatch` from the Connection's
|
|
// message handler. Replaces the imperative if-ladder; behaviour stays in
|
|
// the handlers (since each SxFy reply shape is unique), but routing is
|
|
// data.
|
|
//
|
|
// Default behaviour for unregistered primaries:
|
|
// - If a `fallback` is installed, it runs.
|
|
// - Otherwise, if the inbound message has W set, reply with SxF0
|
|
// (Abort) per E5 convention.
|
|
// - Otherwise, do nothing.
|
|
class Router {
|
|
public:
|
|
using Handler = std::function<std::optional<s2::Message>(const s2::Message&)>;
|
|
|
|
Router& on(uint8_t stream, uint8_t function, Handler h) {
|
|
handlers_[{stream, function}] = std::move(h);
|
|
return *this;
|
|
}
|
|
|
|
Router& fallback(Handler h) {
|
|
fallback_ = std::move(h);
|
|
return *this;
|
|
}
|
|
|
|
std::optional<s2::Message> dispatch(const s2::Message& msg) const {
|
|
auto it = handlers_.find({msg.stream, msg.function});
|
|
if (it != handlers_.end()) return it->second(msg);
|
|
if (fallback_) return fallback_(msg);
|
|
if (msg.reply_expected) return s2::Message(msg.stream, 0, false);
|
|
return std::nullopt;
|
|
}
|
|
|
|
// Introspection: lets the message_handler wrapper decide whether an
|
|
// unhandled message is "unknown stream" (S9F3) or "unknown function in
|
|
// a known stream" (S9F5).
|
|
bool has_handler(uint8_t stream, uint8_t function) const {
|
|
return handlers_.find({stream, function}) != handlers_.end();
|
|
}
|
|
bool has_handler_for_stream(uint8_t stream) const {
|
|
for (const auto& [key, _] : handlers_)
|
|
if (key.first == stream) return true;
|
|
return false;
|
|
}
|
|
|
|
std::size_t size() const { return handlers_.size(); }
|
|
|
|
private:
|
|
std::map<std::pair<uint8_t, uint8_t>, Handler> handlers_;
|
|
Handler fallback_;
|
|
};
|
|
|
|
} // namespace secsgem::gem
|