Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
83 changes: 83 additions & 0 deletions src/cbdev/src/device_session.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@
#include "cbdev/clock_sync.h"
#include <cbproto/cbproto.h>
#include <cbproto/config.h>
#include <cbproto/packet_translator.h>
#include <cstdio>
#include <cstring>
#include <mutex>
Expand Down Expand Up @@ -204,6 +205,11 @@ struct DeviceSession::Impl {
// Device configuration (from REQCONFIGALL)
cbproto::DeviceConfig device_config{};

// Wire protocol for OUTBOUND packets. PROTOCOL_CURRENT means send as-is;
// a legacy value makes sendPacket() down-translate. Set by protocol
// wrappers via setSendProtocol() so config helpers reach legacy firmware.
ProtocolVersion send_protocol = ProtocolVersion::PROTOCOL_CURRENT;

// Outbound pacing for configuration packets. A device's UDP receive buffer
// can be as small as ~8 KB (~8 CHANINFO packets); a caller that sends many
// per-channel setChannelConfig() packets back-to-back (e.g. clearing LNC /
Expand Down Expand Up @@ -569,6 +575,12 @@ Result<int> DeviceSession::receivePackets(void* buffer, const size_t buffer_size
return result;
}

void DeviceSession::setSendProtocol(ProtocolVersion version) {
if (m_impl) {
m_impl->send_protocol = version;
}
}

Result<void> DeviceSession::sendPacket(const cbPKT_GENERIC& pkt) {
if (!m_impl || !m_impl->connected) {
return Result<void>::error("Device not connected");
Expand All @@ -589,6 +601,17 @@ Result<void> DeviceSession::sendPacket(const cbPKT_GENERIC& pkt) {
m_impl->last_config_send = std::chrono::steady_clock::now();
}

// Legacy device: down-translate the current-format packet to the device's
// wire protocol. This is the single choke point for outbound translation:
// the high-level config helpers (setChannelConfig, setSystemRunLevelSync,
// …) all funnel through sendPacket()/sendPackets(), so routing translation
// here makes them work even when this session is wrapped by a protocol
// translator that delegates those helpers to us.
if (m_impl->send_protocol != ProtocolVersion::PROTOCOL_CURRENT &&
m_impl->send_protocol != ProtocolVersion::UNKNOWN) {
return sendTranslated(pkt);
}

// Non-Gemini: convert nanosecond timestamp back to device clock ticks.
// This is the inverse of the ticks→ns conversion in receivePackets().
if (!m_impl->timestamps_are_nanoseconds && m_impl->ts_convert_den > 1 &&
Expand All @@ -606,6 +629,66 @@ Result<void> DeviceSession::sendPacket(const cbPKT_GENERIC& pkt) {
return sendRaw(&pkt, packet_size);
}

Result<void> DeviceSession::sendTranslated(const cbPKT_GENERIC& pkt) {
using cbproto::PacketTranslator;
using cbproto::cbPKT_HEADER_311;
using cbproto::cbPKT_HEADER_400;
using cbproto::HEADER_SIZE_311;
using cbproto::HEADER_SIZE_400;
using cbproto::HEADER_SIZE_410;

uint8_t dest[cbPKT_MAX_SIZE];

switch (m_impl->send_protocol) {
case ProtocolVersion::PROTOCOL_311: {
// 3.11 header: 32-bit tick timestamp, 8-bit type, 8-bit dlen.
if (pkt.cbpkt_header.type > 0xFF) {
return Result<void>::error("Packet type too large for protocol 3.11 (max 255)");
}
if (pkt.cbpkt_header.dlen > 0xFF) {
return Result<void>::error("Packet dlen too large for protocol 3.11 (max 255)");
}
auto& dest_header = *reinterpret_cast<cbPKT_HEADER_311*>(&dest[0]);
dest_header.time = static_cast<uint32_t>(pkt.cbpkt_header.time * 30000 / 1000000000);
dest_header.chid = pkt.cbpkt_header.chid;
dest_header.type = static_cast<uint8_t>(pkt.cbpkt_header.type); // Narrowing!
dest_header.dlen = static_cast<uint8_t>(pkt.cbpkt_header.dlen); // Narrowing!
const size_t dest_dlen = PacketTranslator::translatePayload_current_to_311(pkt, dest);
dest_header.dlen = static_cast<uint8_t>(dest_dlen);
return sendRaw(dest, HEADER_SIZE_311 + dest_header.dlen * 4);
}
case ProtocolVersion::PROTOCOL_400: {
// 4.0 header: 64-bit timestamp, 8-bit type, 16-bit dlen, 16-bit reserved.
auto& dest_header = *reinterpret_cast<cbPKT_HEADER_400*>(&dest[0]);
dest_header.time = pkt.cbpkt_header.time;
dest_header.chid = pkt.cbpkt_header.chid;
dest_header.type = static_cast<uint8_t>(pkt.cbpkt_header.type);
dest_header.dlen = pkt.cbpkt_header.dlen;
dest_header.instrument = pkt.cbpkt_header.instrument;
dest_header.reserved = static_cast<uint16_t>(pkt.cbpkt_header.reserved);
const size_t dest_dlen = PacketTranslator::translatePayload_current_to_400(pkt, dest);
dest_header.dlen = static_cast<uint16_t>(dest_dlen);
return sendRaw(dest, HEADER_SIZE_400 + dest_header.dlen * 4);
}
case ProtocolVersion::PROTOCOL_410: {
// 4.1 header is identical to current; only the payload may differ.
cbPKT_GENERIC out;
std::memcpy(&out, &pkt, sizeof(cbPKT_GENERIC));
auto* out_bytes = reinterpret_cast<uint8_t*>(&out);
const size_t dest_dlen = PacketTranslator::translatePayload_current_to_410(pkt, out_bytes);
out.cbpkt_header.dlen = dest_dlen;
return sendRaw(out_bytes, HEADER_SIZE_410 + out.cbpkt_header.dlen * 4);
}
default:
break;
}

// send_protocol is current/unknown — send unchanged (should not happen: the
// caller only routes here for legacy protocols).
const size_t packet_size = cbPKT_HEADER_SIZE + (pkt.cbpkt_header.dlen * 4);
return sendRaw(&pkt, packet_size);
}

Result<void> DeviceSession::sendPackets(const std::vector<cbPKT_GENERIC>& pkts) {
if (pkts.empty()) {
return Result<void>::error("Empty packet vector");
Expand Down
26 changes: 0 additions & 26 deletions src/cbdev/src/device_session_311.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -137,32 +137,6 @@ Result<int> DeviceSession_311::receivePackets(void* buffer, const size_t buffer_
return Result<int>::ok(static_cast<int>(dest_offset));
}

Result<void> DeviceSession_311::sendPacket(const cbPKT_GENERIC& pkt) {
// Translate current format to 3.11 format
uint8_t dest[cbPKT_MAX_SIZE];

if (pkt.cbpkt_header.type > 0xFF) {
return Result<void>::error("Packet type too large for protocol 3.11 (max 255)");
}
if (pkt.cbpkt_header.dlen > 0xFF) {
return Result<void>::error("Packet dlen too large for protocol 3.11 (max 255)");
}

// -- Header --
auto& dest_header = *reinterpret_cast<cbPKT_HEADER_311*>(&dest[0]);
dest_header.time = static_cast<uint32_t>(pkt.cbpkt_header.time * 30000 / 1000000000);
dest_header.chid = pkt.cbpkt_header.chid;
dest_header.type = static_cast<uint8_t>(pkt.cbpkt_header.type); // Narrowing!
dest_header.dlen = static_cast<uint8_t>(pkt.cbpkt_header.dlen); // Narrowing!

// -- Payload --
const size_t dest_dlen = PacketTranslator::translatePayload_current_to_311(pkt, dest);
dest_header.dlen = dest_dlen;

// Send raw bytes directly via the device's sendRaw method
return m_device.sendRaw(dest, HEADER_SIZE_311 + dest_header.dlen * 4);
}

Result<void> DeviceSession_311::sendRaw(const void* buffer, const size_t size) {
// Pass through to underlying device
return m_device.sendRaw(buffer, size);
Expand Down
9 changes: 5 additions & 4 deletions src/cbdev/src/device_session_311.h
Original file line number Diff line number Diff line change
Expand Up @@ -57,9 +57,6 @@ class DeviceSession_311 : public DeviceSessionWrapper {
/// Receive packets from device and translate from 3.11 to current format
Result<int> receivePackets(void* buffer, size_t buffer_size) override;

/// Send packet to device, translating from current to 3.11 format
Result<void> sendPacket(const cbPKT_GENERIC& pkt) override;

/// Send raw bytes (pass-through to underlying device)
Result<void> sendRaw(const void* buffer, size_t size) override;

Expand All @@ -71,7 +68,11 @@ class DeviceSession_311 : public DeviceSessionWrapper {
private:
/// Private constructor taking a DeviceSession
explicit DeviceSession_311(DeviceSession&& device)
: DeviceSessionWrapper(std::move(device)) {}
: DeviceSessionWrapper(std::move(device)) {
// Outbound packets (incl. those from delegated config helpers) are
// down-translated to 3.11 by the wrapped session.
m_device.setSendProtocol(ProtocolVersion::PROTOCOL_311);
}
};

} // namespace cbdev
Expand Down
29 changes: 0 additions & 29 deletions src/cbdev/src/device_session_400.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -133,35 +133,6 @@ Result<int> DeviceSession_400::receivePackets(void* buffer, const size_t buffer_
return Result<int>::ok(static_cast<int>(dest_offset));
}

Result<void> DeviceSession_400::sendPacket(const cbPKT_GENERIC& pkt) {
// Translate current format to 4.0 format
uint8_t temp_buffer[cbPKT_MAX_SIZE];

// -- Header --
// Read current format header fields
/// When going from current to 4.0, we fix the header as follows:
/// 1. Read 16-bit type from bytes 11-12, shift right 8 bits to get 8-bit type, set on byte 11.
/// 2. Read dlen from bytes 13-14, write to bytes 12-13.
/// 3. Read instrument from byte 15, write to byte 14.
/// 4. Read reserved from byte 16, write to bytes 15-16 as 16-bit.
auto& dest_header = *reinterpret_cast<cbPKT_HEADER_400*>(temp_buffer);
dest_header.time = pkt.cbpkt_header.time; // TODO: What if we are using time ticks, not nanoseconds?
dest_header.chid = pkt.cbpkt_header.chid;
dest_header.type = static_cast<uint8_t>(pkt.cbpkt_header.type);
dest_header.dlen = pkt.cbpkt_header.dlen;
dest_header.instrument = pkt.cbpkt_header.instrument;
dest_header.reserved = static_cast<uint16_t>(pkt.cbpkt_header.reserved);

// -- Payload --
auto* dest_payload = &temp_buffer[HEADER_SIZE_400];
const size_t dest_dlen = PacketTranslator::translatePayload_current_to_400(pkt, dest_payload);
dest_header.dlen = dest_dlen;
const size_t packet_size_400 = HEADER_SIZE_400 + dest_header.dlen * 4;

// Send raw bytes directly via the device's sendRaw method
return m_device.sendRaw(temp_buffer, packet_size_400);
}

Result<void> DeviceSession_400::sendRaw(const void* buffer, const size_t size) {
// Pass through to underlying device
return m_device.sendRaw(buffer, size);
Expand Down
9 changes: 5 additions & 4 deletions src/cbdev/src/device_session_400.h
Original file line number Diff line number Diff line change
Expand Up @@ -62,9 +62,6 @@ class DeviceSession_400 : public DeviceSessionWrapper {
/// Receive packets from device and translate from 4.0 to current format
Result<int> receivePackets(void* buffer, size_t buffer_size) override;

/// Send packet to device, translating from current to 4.0 format
Result<void> sendPacket(const cbPKT_GENERIC& pkt) override;

/// Send raw bytes (pass-through to underlying device)
Result<void> sendRaw(const void* buffer, size_t size) override;

Expand All @@ -76,7 +73,11 @@ class DeviceSession_400 : public DeviceSessionWrapper {
private:
/// Private constructor taking a DeviceSession
explicit DeviceSession_400(DeviceSession&& device)
: DeviceSessionWrapper(std::move(device)) {}
: DeviceSessionWrapper(std::move(device)) {
// Outbound packets (incl. those from delegated config helpers) are
// down-translated to 4.0 by the wrapped session.
m_device.setSendProtocol(ProtocolVersion::PROTOCOL_400);
}
};

} // namespace cbdev
Expand Down
14 changes: 0 additions & 14 deletions src/cbdev/src/device_session_410.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -98,20 +98,6 @@ Result<int> DeviceSession_410::receivePackets(void* buffer, const size_t buffer_
return Result<int>::ok(static_cast<int>(offset));
}

Result<void> DeviceSession_410::sendPacket(const cbPKT_GENERIC& pkt) {
// Formats are nearly identical.
// Nevertheless, the src pkt is const so we make a copy to modify.
cbPKT_GENERIC new_pkt;
std::memcpy(&new_pkt, &pkt, sizeof(cbPKT_GENERIC));
auto* pkt_bytes = reinterpret_cast<uint8_t*>(&new_pkt);
const size_t dest_dlen = PacketTranslator::translatePayload_current_to_410(pkt, pkt_bytes);
new_pkt.cbpkt_header.dlen = dest_dlen;
const size_t packet_size_410 = HEADER_SIZE_410 + new_pkt.cbpkt_header.dlen * 4;

// Send raw bytes directly via the device's sendRaw method
return m_device.sendRaw(pkt_bytes, packet_size_410);
}

Result<void> DeviceSession_410::sendRaw(const void* buffer, const size_t size) {
// Pass through to underlying device
return m_device.sendRaw(buffer, size);
Expand Down
9 changes: 5 additions & 4 deletions src/cbdev/src/device_session_410.h
Original file line number Diff line number Diff line change
Expand Up @@ -62,9 +62,6 @@ class DeviceSession_410 : public DeviceSessionWrapper {
/// Receive packets from device and translate from 4.10 to current format
Result<int> receivePackets(void* buffer, size_t buffer_size) override;

/// Send packet to device, translating from current to 4.10 format
Result<void> sendPacket(const cbPKT_GENERIC& pkt) override;

/// Send raw bytes (pass-through to underlying device)
Result<void> sendRaw(const void* buffer, size_t size) override;

Expand All @@ -76,7 +73,11 @@ class DeviceSession_410 : public DeviceSessionWrapper {
private:
/// Private constructor taking a DeviceSession
explicit DeviceSession_410(DeviceSession&& device)
: DeviceSessionWrapper(std::move(device)) {}
: DeviceSessionWrapper(std::move(device)) {
// Outbound packets (incl. those from delegated config helpers) are
// down-translated to 4.10 by the wrapped session.
m_device.setSendProtocol(ProtocolVersion::PROTOCOL_410);
}
};

} // namespace cbdev
Expand Down
16 changes: 16 additions & 0 deletions src/cbdev/src/device_session_impl.h
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,16 @@ class DeviceSession : public IDeviceSession {
/// @return Protocol version (PROTOCOL_CURRENT for this session)
[[nodiscard]] ProtocolVersion getProtocolVersion() const override;

/// Set the wire protocol used when sending packets.
/// Defaults to PROTOCOL_CURRENT (no translation). Protocol wrappers set
/// this to their device's version so that sendPacket()/sendPackets() —
/// and therefore every high-level config helper that funnels through them
/// — down-translate outgoing packets to the legacy wire format. Without
/// this, helpers delegated to the wrapped session would send current-format
/// packets that legacy firmware cannot parse.
/// @param version Target wire protocol for outbound packets
void setSendProtocol(ProtocolVersion version);

/// Get full device configuration
[[nodiscard]] const cbproto::DeviceConfig& getDeviceConfig() const override;

Expand Down Expand Up @@ -340,6 +350,12 @@ class DeviceSession : public IDeviceSession {
size_t count = 1
);

/// Down-translate a current-format packet to m_impl->send_protocol and send
/// it. Only called from sendPacket() when send_protocol != PROTOCOL_CURRENT.
/// @param pkt Packet in current (4.1+) wire format
/// @return Success or error
Result<void> sendTranslated(const cbPKT_GENERIC& pkt);

/// Implementation details (pImpl pattern)
struct Impl;
std::unique_ptr<Impl> m_impl;
Expand Down
20 changes: 16 additions & 4 deletions src/cbdev/src/device_session_wrapper.h
Original file line number Diff line number Diff line change
Expand Up @@ -69,16 +69,28 @@ class DeviceSessionWrapper : public IDeviceSession {
/// Subclasses MUST override to translate from protocol format → current format
Result<int> receivePackets(void* buffer, size_t buffer_size) override = 0;

/// Send packet with protocol translation
/// Subclasses MUST override to translate from current format → protocol format
Result<void> sendPacket(const cbPKT_GENERIC& pkt) override = 0;

/// Get protocol version
/// Subclasses MUST override to return their specific protocol version
[[nodiscard]] ProtocolVersion getProtocolVersion() const override = 0;

/// @}

///////////////////////////////////////////////////////////////////////////////////////////////////
/// @name Auto-Delegated Send Path (Same for All Protocols)
/// @{

/// Send a packet, down-translating to the device's wire protocol.
/// Delegates to the wrapped session, which performs the current→legacy
/// translation (its send protocol was set at construction). Keeping the
/// translation in one place — DeviceSession::sendPacket — ensures the
/// high-level config helpers, which are also delegated to m_device and call
/// sendPacket() internally, translate identically.
Result<void> sendPacket(const cbPKT_GENERIC& pkt) override {
return m_device.sendPacket(pkt);
}

/// @}

///////////////////////////////////////////////////////////////////////////////////////////////////
/// @name Auto-Delegated Methods (Same for All Protocols)
/// @{
Expand Down
12 changes: 11 additions & 1 deletion src/cbproto/include/cbproto/packet_translator.h
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,17 @@

namespace cbproto {

// These describe on-the-wire byte layouts, so they MUST be byte-packed — the
// current-protocol structs in cbproto/types.h are all under #pragma pack(1).
// Without this, natural alignment inflates cbPKT_HEADER_400 to 24 bytes (dlen
// lands at offset 12 instead of 11), corrupting every translated 4.0 packet.
#pragma pack(push, 1)
typedef struct {
uint32_t time; ///< Ticks at 30 kHz
uint16_t chid; ///< Channel identifier
uint8_t type; ///< Packet type
uint8_t dlen; ///< Length of data field in 32-bit chunks
} cbPKT_HEADER_311;
constexpr size_t HEADER_SIZE_311 = sizeof(cbPKT_HEADER_311);

typedef struct {
PROCTIME time; ///< Ticks at 30 kHz on legacy, or nanoseconds on Gemini
Expand All @@ -30,8 +34,14 @@ typedef struct {
uint8_t instrument; ///< Instrument identifier
uint16_t reserved; ///< Reserved byte
} cbPKT_HEADER_400;
#pragma pack(pop)

constexpr size_t HEADER_SIZE_311 = sizeof(cbPKT_HEADER_311);
constexpr size_t HEADER_SIZE_400 = sizeof(cbPKT_HEADER_400);

static_assert(HEADER_SIZE_311 == 8, "cbPKT_HEADER_311 must be 8 bytes on the wire");
static_assert(HEADER_SIZE_400 == 16, "cbPKT_HEADER_400 must be 16 bytes on the wire");

constexpr size_t HEADER_SIZE_410 = cbPKT_HEADER_SIZE; // Header unchanged since 4.1


Expand Down
Loading
Loading