microfmt
Zero-allocation C++ formatting for constrained environments
Loading...
Searching...
No Matches
can.hpp
Go to the documentation of this file.
1// SPDX-FileCopyrightText: 2026 Jarosław Pelczar <jarek@jpelczar.com>
2//
3// SPDX-License-Identifier: BSD-2-Clause
4
5#pragma once
6
7#include "../microfmt.hpp"
8#include <cstddef>
9#include <cstdint>
10#include <string_view>
11
21namespace microfmt {
22
23// ============================================================================
24// CAN Frame Flags
25// ============================================================================
26
27enum class can_flags : uint8_t {
28 none = 0,
29 extended = 1 << 0, // 29-bit Extended ID (vs 11-bit Standard ID)
30 rtr = 1 << 1, // Remote Transmission Request
31 fd = 1 << 2, // CAN-FD Frame (payload > 8 bytes, up to 64 bytes)
32 brs = 1 << 3, // CAN-FD Bit Rate Switch
33 esi = 1 << 4 // CAN-FD Error State Indicator
34};
35
36// low-level formatting: raw character pointers and integer width conversions bounded by the buffer sizes checked here.
37RELOCO_BEGIN_UNSAFE_CODE
38[[nodiscard]] constexpr can_flags operator|(can_flags a, can_flags b) noexcept {
39 return static_cast<can_flags>(static_cast<uint8_t>(a) | static_cast<uint8_t>(b));
40}
42
43[[nodiscard]] constexpr bool operator&(can_flags a, can_flags b) noexcept {
44 return (static_cast<uint8_t>(a) & static_cast<uint8_t>(b)) != 0;
45}
46
47// ============================================================================
48// CAN Frame View Descriptor
49// ============================================================================
50
52struct MICROFMT_API_CLASS RELOCO_POINTER can_frame_view {
53 uint32_t id{0};
54 span<const uint8_t> payload{};
55 can_flags flags{can_flags::none};
56};
57
58// ============================================================================
59// Factory Helpers
60// ============================================================================
61
62// Standard CAN 2.0 Frame (11-bit ID, max 8 bytes)
64[[nodiscard]] constexpr can_frame_view can_frame(uint16_t id, span<const uint8_t> payload, bool rtr = false) noexcept {
65 return {static_cast<uint32_t>(id & 0x7FF), payload, rtr ? can_flags::rtr : can_flags::none};
66}
67
68template <size_t N>
69[[nodiscard]] constexpr can_frame_view can_frame(uint16_t id, const uint8_t (&arr)[N], bool rtr = false) noexcept {
70 return {static_cast<uint32_t>(id & 0x7FF), span<const uint8_t>(arr, N), rtr ? can_flags::rtr : can_flags::none};
71}
72
73// Extended CAN Frame (29-bit ID)
75[[nodiscard]] constexpr can_frame_view can_extended(uint32_t id, span<const uint8_t> payload,
76 bool rtr = false) noexcept {
77 return {id & 0x1FFFFFFF, payload, can_flags::extended | (rtr ? can_flags::rtr : can_flags::none)};
78}
79
80// CAN-FD Frame (up to 64 bytes, optional BRS/ESI)
82[[nodiscard]] constexpr can_frame_view can_fd(uint32_t id, span<const uint8_t> payload, bool is_extended = false,
83 bool brs = false, bool esi = false) noexcept {
85 if (is_extended)
86 f = f | can_flags::extended;
87 if (brs)
88 f = f | can_flags::brs;
89 if (esi)
90 f = f | can_flags::esi;
91 return {is_extended ? (id & 0x1FFFFFFF) : (id & 0x7FF), payload, f};
92}
93
94// ============================================================================
95// Formatter Specialization for can_frame_view
96// ============================================================================
97
98template <> struct formatter<can_frame_view> {
99 bool candump_style{false}; // Format like Linux socketcan candump: can0 123#DEADBEEF
100 bool uppercase_hex{true};
101
102 constexpr void parse(format_parse_context &ctx) noexcept {
103 for (char c : ctx.spec()) {
104 if (c == 'c' || c == 'C')
105 candump_style = true; // {:c} -> Linux candump log style
106 if (c == 'x')
107 uppercase_hex = false;
108 }
109 }
110
111 void format(const can_frame_view &f, const sink &out) const noexcept {
112
113 const auto &hex_digits = uppercase_hex ? detail::hex_digits_upper : detail::hex_digits_lower;
114 const bool is_ext = (f.flags & can_flags::extended);
115 const bool is_rtr = (f.flags & can_flags::rtr);
116 const bool is_fd = (f.flags & can_flags::fd);
117
118 if (candump_style) {
119 // Candump compact format: [ID]#[DATA] or [ID]##[FLAGS][DATA] for FD
120 detail::format_unsigned<detail::radix::hex>(out, f.id, uppercase_hex, is_ext ? 8 : 3);
121 if (is_fd) {
122 out.write("##");
123 uint8_t fd_flags = 0;
124 if (f.flags & can_flags::brs) {
125 fd_flags |= 0x1;
126 }
127 if (f.flags & can_flags::esi) {
128 fd_flags |= 0x2;
129 }
130 RELOCO_BEGIN_UNSAFE_BUFFER_USAGE;
131 out.put(hex_digits[fd_flags]);
132 RELOCO_END_UNSAFE_BUFFER_USAGE;
133 } else if (is_rtr) {
134 out.put('#');
135 out.put('R');
136 detail::format_unsigned<detail::radix::decimal>(out, f.payload.size(), false, 0);
137 return;
138 } else {
139 out.put('#');
140 }
141
142 for (uint8_t b : f.payload) {
143 RELOCO_BEGIN_UNSAFE_BUFFER_USAGE;
144 out.put(hex_digits[(b >> 4) & 0x0F]);
145 out.put(hex_digits[b & 0x0F]);
146 RELOCO_END_UNSAFE_BUFFER_USAGE;
147 }
148 return;
149 }
150
151 // Diagnostic human-readable format:
152 // CAN [0x18DAF110] EXT DLC=8 DATA: 02 01 0C 00 00 00 00 00
153 if (is_fd) {
154 out.write("CAN-FD [0x");
155 } else {
156 out.write("CAN [0x");
157 }
158
159 detail::format_unsigned<detail::radix::hex>(out, f.id, uppercase_hex, is_ext ? 8 : 3);
160 out.put(']');
161
162 if (is_ext)
163 out.write(" EXT");
164 if (is_fd) {
165 if (f.flags & can_flags::brs)
166 out.write(" BRS");
167 if (f.flags & can_flags::esi)
168 out.write(" ESI");
169 }
170
171 if (is_rtr) {
172 out.write(" RTR (DLC=");
173 detail::format_unsigned<detail::radix::decimal>(out, f.payload.size(), false, 0);
174 out.put(')');
175 return;
176 }
177
178 out.write(" DLC=");
179 detail::format_unsigned<detail::radix::decimal>(out, f.payload.size(), false, 0);
180
181 if (!f.payload.empty()) {
182 out.write(" DATA: ");
183 for (size_t i = 0; i < f.payload.size(); ++i) {
184 if (i > 0)
185 out.put(' ');
186 const uint8_t b = f.payload[i];
187 out.put(hex_digits[(b >> 4) & 0x0F]);
188 out.put(hex_digits[b & 0x0F]);
189 }
190 }
191 }
192};
193
194} // namespace microfmt
Definition microfmt.hpp:881
#define MICROFMT_API_CLASS
Definition compat.hpp:181
Definition assert.hpp:19
RELOCO_BEGIN_UNSAFE_CODE constexpr can_flags operator|(can_flags a, can_flags b) noexcept
Definition can.hpp:38
reloco::sink sink
Type-erased, zero-allocation output sink.
Definition microfmt.hpp:46
constexpr can_frame_view can_frame(uint16_t id, span< const uint8_t > payload, bool rtr=false) noexcept
Creates a standard 11-bit CAN frame view.
Definition can.hpp:64
constexpr can_frame_view can_extended(uint32_t id, span< const uint8_t > payload, bool rtr=false) noexcept
Creates an extended 29-bit CAN frame view.
Definition can.hpp:75
constexpr can_frame_view can_fd(uint32_t id, span< const uint8_t > payload, bool is_extended=false, bool brs=false, bool esi=false) noexcept
Creates a CAN-FD frame view with optional extended, BRS, and ESI flags.
Definition can.hpp:82
can_flags
Definition can.hpp:27
RELOCO_END_UNSAFE_CODE constexpr bool operator&(can_flags a, can_flags b) noexcept
Definition can.hpp:43
RELOCO_END_UNSAFE_CODE
Definition remote_object.hpp:132
Non-owning CAN or CAN-FD frame descriptor for formatting.
Definition can.hpp:52
void format(const can_frame_view &f, const sink &out) const noexcept
Definition can.hpp:111
constexpr void parse(format_parse_context &ctx) noexcept
Definition can.hpp:102
Definition microfmt.hpp:985