#include "link_protocol.hpp" namespace microbu::link { bool encode(const Message& message, Bytes& out) { if (header_size + message.body.size() > maximum_message) return false; out.clear(); out.reserve(header_size + message.body.size()); out.push_back(static_cast(message.header.opcode)); out.push_back(message.header.flags); out.push_back(message.header.sequence & 0xFF); out.push_back(message.header.sequence >> 8); out.insert(out.end(), message.body.begin(), message.body.end()); return true; } bool decode(const Bytes& octets, Message& out) { if (octets.size() < header_size || octets.size() > maximum_message) return false; out.header.opcode = static_cast(octets[0]); out.header.flags = octets[1]; out.header.sequence = octets[2] | (octets[3] << 8); out.body.assign(octets.begin() + header_size, octets.end()); return true; } namespace { void put_area(Writer& w, const DestinationArea& a) { w.u8(a.shape); w.i32(a.latitude); w.i32(a.longitude); w.u16(a.distance_a); w.u16(a.distance_b); w.u16(a.angle); } DestinationArea get_area(Reader& r) { DestinationArea a; a.shape = r.u8(); a.latitude = r.i32(); a.longitude = r.i32(); a.distance_a = r.u16(); a.distance_b = r.u16(); a.angle = r.u16(); return a; } } // STATION_CONFIGURE Bytes encode(const StationConfigure& c) { Writer w; w.u8(c.station_type); w.u8(c.security); w.u8(c.address_configuration); w.bytes(c.mid, 6); w.u8(c.beaconing); w.u16(c.channel_number); w.u8(c.transmit_power_dbm); w.u8(c.radio); w.u8(c.default_traffic_class); w.u8(c.default_lifetime); return w.out; } bool decode(const Bytes& body, StationConfigure& c) { Reader r(body); c.station_type = r.u8(); c.security = r.u8(); c.address_configuration = r.u8(); r.bytes(c.mid, 6); c.beaconing = r.u8(); c.channel_number = r.u16(); c.transmit_power_dbm = r.u8(); c.radio = r.u8(); c.default_traffic_class = r.u8(); c.default_lifetime = r.u8(); return r.done() && c.security <= 1 && c.address_configuration <= 1 && c.beaconing <= 1 && c.radio <= 2; } // POTI_UPDATE Bytes encode(const PotiUpdate& p) { Writer w; w.u64(p.timestamp_ms); w.i32(p.latitude); w.i32(p.longitude); w.u16(p.semi_major_cm); w.u16(p.semi_minor_cm); w.u16(p.orientation_deci_degree); w.u8(p.flags); w.i32(p.altitude_cm); w.u16(p.speed_cm_s); w.u16(p.heading_deci_degree); return w.out; } bool decode(const Bytes& body, PotiUpdate& p) { Reader r(body); p.timestamp_ms = r.u64(); p.latitude = r.i32(); p.longitude = r.i32(); p.semi_major_cm = r.u16(); p.semi_minor_cm = r.u16(); p.orientation_deci_degree = r.u16(); p.flags = r.u8(); p.altitude_cm = r.i32(); p.speed_cm_s = r.u16(); p.heading_deci_degree = r.u16(); return r.done(); } // BTP_DATA_REQUEST Bytes encode(const BtpDataRequest& q) { Writer w; w.u8(q.btp_type); w.u16(q.destination_port); w.u16(q.destination_port_info); w.u8(q.gn_packet_transport_type); w.u8(q.gn_communication_profile); w.u8(q.gn_security_profile); w.u8(q.gn_traffic_class); w.u8(q.gn_maximum_packet_lifetime); w.u8(q.gn_maximum_hop_limit); w.u16(q.gn_repetition_interval_ms); w.u16(q.gn_repetition_maximum_ms); w.u32(q.its_aid); w.u8(static_cast(q.permissions.size())); w.bytes(q.permissions); w.u8(static_cast(q.context.size())); w.bytes(q.context); if (q.gn_packet_transport_type == 3) put_area(w, q.area.value_or(DestinationArea {})); w.u16(static_cast(q.fl_sdu.size())); w.bytes(q.fl_sdu); return w.out; } bool decode(const Bytes& body, BtpDataRequest& q) { Reader r(body); q.btp_type = r.u8(); q.destination_port = r.u16(); q.destination_port_info = r.u16(); q.gn_packet_transport_type = r.u8(); q.gn_communication_profile = r.u8(); q.gn_security_profile = r.u8(); q.gn_traffic_class = r.u8(); q.gn_maximum_packet_lifetime = r.u8(); q.gn_maximum_hop_limit = r.u8(); q.gn_repetition_interval_ms = r.u16(); q.gn_repetition_maximum_ms = r.u16(); q.its_aid = r.u32(); q.permissions = r.bytes(r.u8()); q.context = r.bytes(r.u8()); q.area.reset(); if (q.gn_packet_transport_type == 3) q.area = get_area(r); q.fl_sdu = r.bytes(r.u16()); return r.done() && q.btp_type <= 1 && q.permissions.size() <= 31; } // BTP_DATA_INDICATION Bytes encode(const BtpDataIndication& i) { Writer w; w.u8(i.btp_type); w.u16(i.destination_port); w.u16(i.destination_port_info); w.u8(i.gn_packet_transport_type); w.u8(i.gn_traffic_class); w.u8(i.gn_remaining_packet_lifetime); w.u8(i.gn_remaining_hop_limit); w.bytes(i.source_gn_address, 8); w.u32(i.source_timestamp); w.i32(i.source_latitude); w.i32(i.source_longitude); w.u8(i.security_report); w.u32(i.its_aid); w.u8(static_cast(i.permissions.size())); w.bytes(i.permissions); w.u8(i.certificate_present ? 1 : 0); w.bytes(i.certificate_id, 8); w.u8(i.area ? 1 : 0); if (i.area) put_area(w, *i.area); w.u16(static_cast(i.received_fl_sdu.size())); w.bytes(i.received_fl_sdu); return w.out; } bool decode(const Bytes& body, BtpDataIndication& i) { Reader r(body); i.btp_type = r.u8(); i.destination_port = r.u16(); i.destination_port_info = r.u16(); i.gn_packet_transport_type = r.u8(); i.gn_traffic_class = r.u8(); i.gn_remaining_packet_lifetime = r.u8(); i.gn_remaining_hop_limit = r.u8(); r.bytes(i.source_gn_address, 8); i.source_timestamp = r.u32(); i.source_latitude = r.i32(); i.source_longitude = r.i32(); i.security_report = r.u8(); i.its_aid = r.u32(); i.permissions = r.bytes(r.u8()); i.certificate_present = r.u8() != 0; r.bytes(i.certificate_id, 8); i.area.reset(); if (r.u8()) i.area = get_area(r); i.received_fl_sdu = r.bytes(r.u16()); return r.done(); } // CREDENTIALS_PROVISION Bytes encode(const CredentialsProvision& c) { Writer w; w.u16(c.total_length); w.u16(c.offset); w.u8(static_cast(c.segment.size())); w.bytes(c.segment); return w.out; } bool decode(const Bytes& body, CredentialsProvision& c) { Reader r(body); c.total_length = r.u16(); c.offset = r.u16(); c.segment = r.bytes(r.u8()); return r.done() && !c.segment.empty() && c.offset + c.segment.size() <= c.total_length; } // SF_IDCHANGE_EVENT Bytes encode(const IdChangeEvent& e) { Writer w; w.u64(e.subscription); w.u8(e.command); w.bytes(e.id, 8); w.u8(static_cast(e.subscriber_data.size())); w.bytes(e.subscriber_data); return w.out; } bool decode(const Bytes& body, IdChangeEvent& e) { Reader r(body); e.subscription = r.u64(); e.command = r.u8(); r.bytes(e.id, 8); e.subscriber_data = r.bytes(r.u8()); return r.done() && e.command <= 3; } Bytes encode(const IdChangeEventResponse& e) { Writer w; w.u64(e.subscription); w.u8(e.return_code); return w.out; } bool decode(const Bytes& body, IdChangeEventResponse& e) { Reader r(body); e.subscription = r.u64(); e.return_code = r.u8(); return r.done() && e.return_code <= 1; } // STATUS Bytes encode(const Status& s) { Writer w; w.u32(s.uptime_ms); w.u8(s.configured); w.bytes(s.gn_address, 8); w.bytes(s.identifier, 8); w.u8(s.change_pending); w.u8(s.tickets); w.u32(s.signed_messages); w.u32(s.refused_no_ticket); w.u32(s.refused_change_pending); w.u32(s.refused_permission); w.u32(s.sign_failed); w.u32(s.verified); w.u32(s.rejected); w.u32(s.requests_accepted); w.u32(s.requests_refused); w.u32(s.indications); w.u32(s.radio_submitted); w.u32(s.radio_failed); w.u32(s.radio_received); w.u32(s.radio_dropped); w.u32(s.link_rx_frames); w.u32(s.link_crc_errors); w.u32(s.link_malformed); w.u32(s.poti_updates); w.u64(s.its_time_ms); return w.out; } bool decode(const Bytes& body, Status& s) { Reader r(body); s.uptime_ms = r.u32(); s.configured = r.u8(); r.bytes(s.gn_address, 8); r.bytes(s.identifier, 8); s.change_pending = r.u8(); s.tickets = r.u8(); s.signed_messages = r.u32(); s.refused_no_ticket = r.u32(); s.refused_change_pending = r.u32(); s.refused_permission = r.u32(); s.sign_failed = r.u32(); s.verified = r.u32(); s.rejected = r.u32(); s.requests_accepted = r.u32(); s.requests_refused = r.u32(); s.indications = r.u32(); s.radio_submitted = r.u32(); s.radio_failed = r.u32(); s.radio_received = r.u32(); s.radio_dropped = r.u32(); s.link_rx_frames = r.u32(); s.link_crc_errors = r.u32(); s.link_malformed = r.u32(); s.poti_updates = r.u32(); s.its_time_ms = r.u64(); return r.done(); } // RESULT Bytes encode(const Result& res) { Writer w; w.u8(static_cast(res.code)); w.u8(static_cast(res.detail.size())); w.bytes(res.detail); return w.out; } bool decode(const Bytes& body, Result& res) { Reader r(body); res.code = static_cast(r.u8()); res.detail = r.bytes(r.u8()); return r.done(); } } // namespace microbu::link