156 lines
6.7 KiB
C++
156 lines
6.7 KiB
C++
#include "link_service.hpp"
|
|||
|
|
#include "simple_ble.hpp"
|
||
|
|
#include "serial_link.hpp"
|
||
|
|
#include <esp_log.h>
|
||
|
|
#include <esp_timer.h>
|
||
|
|
|
||
|
|
namespace microbu {
|
||
|
|
namespace {
|
||
|
|
const char* TAG = "link";
|
||
|
|
}
|
||
|
|
|
||
|
|
LinkService::LinkService(Station& station) : station_(station) {
|
||
|
|
station_.on_indication([this](const link::BtpDataIndication& indication) {
|
||
|
|
broadcast(link::Opcode::BTP_DATA_INDICATION, ++own_sequence_, link::encode(indication));
|
||
|
|
});
|
||
|
|
station_.on_id_event([this](const link::IdChangeEvent& event) {
|
||
|
|
// PREPARE/COMMIT expect SF_IDCHANGE_EVENT_RESPONSE with this sequence
|
||
|
|
broadcast(link::Opcode::SF_IDCHANGE_EVENT, ++own_sequence_, link::encode(event));
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
void LinkService::send(LinkTransport transport, link::Opcode opcode, std::uint16_t sequence, const link::Bytes& body) {
|
||
|
|
link::Bytes octets;
|
||
|
|
link::Message message {{opcode, 0, sequence}, body};
|
||
|
|
if (!link::encode(message, octets)) { ESP_LOGE(TAG, "message 0x%02x too long (%u)", unsigned(opcode), unsigned(body.size())); return; }
|
||
|
|
if (transport == LinkTransport::serial) serial::write(serial::FrameType::LINK, octets);
|
||
|
|
else ble::write(octets);
|
||
|
|
}
|
||
|
|
|
||
|
|
void LinkService::broadcast(link::Opcode opcode, std::uint16_t sequence, const link::Bytes& body) {
|
||
|
|
send(LinkTransport::serial, opcode, sequence, body);
|
||
|
|
if (ble::connected()) send(LinkTransport::ble, opcode, sequence, body);
|
||
|
|
}
|
||
|
|
|
||
|
|
void LinkService::reply(LinkTransport transport, std::uint16_t sequence, link::Code code, const link::Bytes& detail) {
|
||
|
|
send(transport, link::Opcode::RESULT, sequence, link::encode(link::Result {code, detail}));
|
||
|
|
}
|
||
|
|
|
||
|
|
link::Status LinkService::status() {
|
||
|
|
auto value = station_.status();
|
||
|
|
const auto serial_counters = serial::counters();
|
||
|
|
const auto ble_counters = ble::counters();
|
||
|
|
value.link_rx_frames = serial_counters.frames + ble_counters.rx_messages;
|
||
|
|
value.link_crc_errors = serial_counters.crc_errors;
|
||
|
|
value.link_malformed += ble_counters.malformed;
|
||
|
|
return value;
|
||
|
|
}
|
||
|
|
|
||
|
|
void LinkService::handle(const link::Bytes& octets, LinkTransport origin) {
|
||
|
|
link::Message m;
|
||
|
|
if (!link::decode(octets, m)) return;
|
||
|
|
const auto seq = m.header.sequence;
|
||
|
|
using link::Opcode; using link::Code;
|
||
|
|
switch (m.header.opcode) {
|
||
|
|
case Opcode::STATION_CONFIGURE: {
|
||
|
|
link::StationConfigure c;
|
||
|
|
if (!link::decode(m.body, c)) return reply(origin, seq, Code::malformed);
|
||
|
|
link::Bytes detail;
|
||
|
|
const auto result = station_.configure(c, detail);
|
||
|
|
ESP_LOGI(TAG, "station configured: security %u, radio %u, result %d", unsigned(c.security), unsigned(c.radio), int(result));
|
||
|
|
return reply(origin, seq, result, detail);
|
||
|
|
}
|
||
|
|
case Opcode::POTI_UPDATE: {
|
||
|
|
link::PotiUpdate p;
|
||
|
|
if (!link::decode(m.body, p)) return reply(origin, seq, Code::malformed);
|
||
|
|
const auto result = station_.poti(p);
|
||
|
|
if (result != Code::accepted) reply(origin, seq, result); // accepted updates are silent
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
case Opcode::BTP_DATA_REQUEST: {
|
||
|
|
link::BtpDataRequest q;
|
||
|
|
if (!link::decode(m.body, q)) return reply(origin, seq, Code::malformed);
|
||
|
|
return reply(origin, seq, station_.btp_request(q));
|
||
|
|
}
|
||
|
|
case Opcode::CREDENTIALS_PROVISION: {
|
||
|
|
link::CredentialsProvision c;
|
||
|
|
if (!link::decode(m.body, c)) return reply(origin, seq, Code::malformed);
|
||
|
|
if (c.offset == 0) { bundle_.clear(); bundle_total_ = c.total_length; }
|
||
|
|
if (c.total_length != bundle_total_ || c.offset != bundle_.size() || c.total_length > 8192) { bundle_.clear(); return reply(origin, seq, Code::malformed); }
|
||
|
|
bundle_.insert(bundle_.end(), c.segment.begin(), c.segment.end());
|
||
|
|
if (bundle_.size() < bundle_total_) return reply(origin, seq, Code::accepted);
|
||
|
|
link::ApplyReport report;
|
||
|
|
link::Code result = Code::accepted;
|
||
|
|
try {
|
||
|
|
result = station_.provision(bundle_, report);
|
||
|
|
} catch (const std::exception& e) {
|
||
|
|
ESP_LOGE(TAG, "provision exception: %s", e.what());
|
||
|
|
result = Code::invalid_argument;
|
||
|
|
}
|
||
|
|
bundle_.clear();
|
||
|
|
link::Bytes detail;
|
||
|
|
detail.push_back(static_cast<std::uint8_t>(report.roots));
|
||
|
|
detail.push_back(static_cast<std::uint8_t>(report.authorities));
|
||
|
|
detail.push_back(static_cast<std::uint8_t>(report.tickets));
|
||
|
|
return reply(origin, seq, result, detail);
|
||
|
|
}
|
||
|
|
case Opcode::CREDENTIALS_ERASE:
|
||
|
|
return reply(origin, seq, station_.erase_credentials());
|
||
|
|
case Opcode::SF_IDCHANGE_SUBSCRIBE: {
|
||
|
|
link::Reader r(m.body);
|
||
|
|
const auto data = r.bytes(r.u8());
|
||
|
|
if (!r.done()) {
|
||
|
|
ESP_LOGW(TAG, "SF_IDCHANGE_SUBSCRIBE malformed (len=%u)", (unsigned)m.body.size());
|
||
|
|
return reply(origin, seq, Code::malformed);
|
||
|
|
}
|
||
|
|
std::uint64_t handle = 0;
|
||
|
|
const auto result = station_.subscribe(data, handle);
|
||
|
|
ESP_LOGI(TAG, "SF_IDCHANGE_SUBSCRIBE handle=%llu result=%d", (unsigned long long)handle, int(result));
|
||
|
|
link::Writer w; w.u64(handle);
|
||
|
|
return reply(origin, seq, result, result == Code::accepted ? w.out : link::Bytes {});
|
||
|
|
}
|
||
|
|
case Opcode::SF_IDCHANGE_UNSUBSCRIBE: {
|
||
|
|
link::Reader r(m.body);
|
||
|
|
const auto handle = r.u64();
|
||
|
|
if (!r.done()) return reply(origin, seq, Code::malformed);
|
||
|
|
return reply(origin, seq, station_.unsubscribe(handle));
|
||
|
|
}
|
||
|
|
case Opcode::SF_IDCHANGE_EVENT_RESPONSE: {
|
||
|
|
link::IdChangeEventResponse e;
|
||
|
|
if (!link::decode(m.body, e)) return; // no reply defined for a response
|
||
|
|
station_.event_response(e.subscription, e.return_code != 0);
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
case Opcode::SF_IDCHANGE_TRIGGER:
|
||
|
|
return reply(origin, seq, station_.trigger());
|
||
|
|
case Opcode::SF_ID_LOCK: {
|
||
|
|
link::Reader r(m.body);
|
||
|
|
const auto seconds = r.u8();
|
||
|
|
if (!r.done()) return reply(origin, seq, Code::malformed);
|
||
|
|
std::uint64_t handle = 0;
|
||
|
|
const auto result = station_.lock(seconds, handle);
|
||
|
|
link::Writer w; w.u64(handle);
|
||
|
|
return reply(origin, seq, result, result == Code::accepted ? w.out : link::Bytes {});
|
||
|
|
}
|
||
|
|
case Opcode::SF_ID_UNLOCK: {
|
||
|
|
link::Reader r(m.body);
|
||
|
|
const auto handle = r.u64();
|
||
|
|
if (!r.done()) return reply(origin, seq, Code::malformed);
|
||
|
|
return reply(origin, seq, station_.unlock(handle));
|
||
|
|
}
|
||
|
|
case Opcode::STATUS_REQUEST:
|
||
|
|
return send(origin, link::Opcode::STATUS, seq, link::encode(status()));
|
||
|
|
default:
|
||
|
|
return reply(origin, seq, Code::unknown_opcode);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
void LinkService::tick() {
|
||
|
|
const auto now = esp_timer_get_time();
|
||
|
|
if (now - last_status_us_ < 1000000) return;
|
||
|
|
last_status_us_ = now;
|
||
|
|
broadcast(link::Opcode::STATUS, ++own_sequence_, link::encode(status()));
|
||
|
|
}
|
||
|
|
|
||
|
|
} // namespace microbu
|