Skip to content

Commit f24a061

Browse files
committed
pldmtool: File I/O & checksum options for mctpRaw
Enhance the `mctpRaw` subcommand with options to read request data from a file (`--file-in`), write response data to a file (`--file-out`), and append or validate checksum (`--checksum`). These additions improve usability for large payloads and ensure data integrity during raw MCTP transactions. Contents of the file specified in option --file-in are appended to the contents of the option --data. Signed-off-by: Shirish Pargaonkar <[email protected]>
1 parent c7449e7 commit f24a061

File tree

2 files changed

+316
-11
lines changed

2 files changed

+316
-11
lines changed

pldmtool/pldm_cmd_helper.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -162,7 +162,7 @@ int mctpSockSendRecv(const uint8_t mctpNetworkId, const uint8_t eid,
162162
// wait for for the response from the MCTP Endpoint
163163
// Instance ID expiration interval (MT4) - after which the instance ID
164164
// will be reused. For PCIe binding this timeout is 5 seconds.
165-
const int MCTP_INST_ID_EXPIRATION_INTERVAL_MT4 = 5;
165+
const int MCTP_INST_ID_EXPIRATION_INTERVAL_MT4 = 15;
166166
struct pollfd pollfd;
167167
pollfd.fd = sd;
168168
pollfd.events = POLLIN;
@@ -177,7 +177,7 @@ int mctpSockSendRecv(const uint8_t mctpNetworkId, const uint8_t eid,
177177
else if (rc == 0)
178178
{
179179
// poll() timed out
180-
std::cerr << "Timeout(5s): No response from the endpoint\n";
180+
std::cerr << "Timeout(15s): No response from the endpoint\n";
181181
close(sd);
182182
return rc;
183183
}

pldmtool/pldmtool.cpp

Lines changed: 314 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,33 @@ namespace
7272
std::vector<std::unique_ptr<CommandInterface>> commands;
7373
}
7474

75+
/*
76+
* • The file size and CRC32 fields are always stored in **big-endian**
77+
* format regardless of host endianness.
78+
*
79+
* • If checksum is disabled, the IC bit is cleared and the CRC32 field
80+
* is omitted.
81+
*
82+
* • rawData[] is assembled in this exact order:
83+
*
84+
* rawData = [Header] + [File Size] + [File Data] + [Optional CRC]
85+
*
86+
* • The offsets DevHdrSize and FsField refer to the start of the file-size
87+
* field and help determine where payload sections begin.
88+
*
89+
* • Header include AMD IANA number in big-endian format
90+
*
91+
*/
92+
93+
constexpr unsigned int SubTypeSFS = 0;
94+
constexpr unsigned int DevHdrSize = 7;
95+
constexpr unsigned int FsField = 4;
96+
constexpr unsigned int CsField = 4;
97+
constexpr unsigned int SubTypeByte = 4;
98+
constexpr unsigned int CompCodeByte = 6;
99+
constexpr unsigned int MaxFileSize = 0x4000;
100+
constexpr unsigned int AMD_Iana_Numg = 0xe78;
101+
75102
class MctpRawOp : public CommandInterface
76103
{
77104
public:
@@ -90,23 +117,301 @@ class MctpRawOp : public CommandInterface
90117
app->add_option("-d,--data", rawData, "raw MCTP data")
91118
->required()
92119
->expected(-3);
120+
app->add_option("-i,--file-in", inFileName, "Read in the file to be sent");
121+
app->add_option("-o,--file-out", outFileName, "Write out the received file");
122+
app->add_flag("-c,--checksum", checksum, "Append/Validate checksum");
93123
app->add_flag("-p,--prealloc-tag", mctpPreAllocTag,
94124
"use pre-allocated MCTP tag for this request");
125+
app->footer(R"(Example: pldmtool mctpRaw -m 21 -e 1 \
126+
--data 0x7F 0x00 0x00 0x0E 0x78 0x80 0x02 \
127+
--file-in req.bin --file-out resp.bin --checksum)");
95128
}
96-
std::pair<int, std::vector<uint8_t>> createRequestMsg() override
97-
98-
{
99-
return {PLDM_SUCCESS, rawData};
100-
}
101-
102-
void parseResponseMsg(pldm_msg* /* responsePtr */,
103-
size_t /* payloadLength */) override
104-
{}
105129

130+
const std::string getInFileName() const {return inFileName;}
131+
const std::string getOutFileName() const {return outFileName;}
132+
std::pair<int, std::vector<uint8_t>> createRequestMsg() override;
133+
void parseResponseMsg(pldm_msg *, size_t) override;
106134
private:
107135
std::vector<uint8_t> rawData;
136+
std::string outFileName{};
137+
std::string inFileName{};
138+
bool checksum{false};
139+
140+
void appendInfile();
141+
void handleSFSResponse(const uint8_t*, size_t);
108142
};
109143

144+
/**
145+
* @brief Convert a native-endian value to big-endian.
146+
*
147+
* This function performs a byte swap on little-endian systems so the returned
148+
* value is always in big-endian format. On big-endian systems, the value is
149+
* returned unchanged.
150+
*
151+
* @tparam T An integral type (e.g., uint16_t, uint32_t, uint64_t).
152+
* @param value The value to convert.
153+
* @return The value represented in big-endian byte order.
154+
*/
155+
156+
template <typename T>
157+
constexpr T convToBigEndian(T value) {
158+
if constexpr (std::endian::native == std::endian::little)
159+
return std::byteswap(value);
160+
else
161+
return value;
162+
}
163+
164+
/**
165+
* @brief Append the contents of the input file into the raw MCTP payload.
166+
*
167+
* This function performs the following steps:
168+
* - Retrieves the file size using std::filesystem.
169+
* - Reads the full file into memory.
170+
* - Appends the 4-byte big-endian file size field to @ref rawData.
171+
* - Appends the file contents to @ref rawData.
172+
* - Clears or sets the IC (Integrity Check) bit in the first byte.
173+
* - If checksum support is enabled, computes a CRC32 over the file data and
174+
* appends it in big-endian format.
175+
*
176+
* Exceptions during file I/O (open/read) are handled via std::ios_base::failure.
177+
* Filesystem errors (e.g. file not found) are caught via std::filesystem::filesystem_error.
178+
*
179+
* @return void
180+
*
181+
* @note Files larger than 16 MiB are not expected by design.
182+
* @note The function reserves enough space in @ref rawData to avoid repeated reallocations.
183+
*/
184+
185+
void MctpRawOp::appendInfile()
186+
{
187+
std::uintmax_t fSize = 0;
188+
std::ifstream toDevice;
189+
std::vector<uint8_t> fData;
190+
191+
try {
192+
fSize = std::filesystem::file_size(getInFileName());
193+
}
194+
catch (const std::filesystem::filesystem_error& fse) {
195+
std::cerr << "File size error: " << fse.what() << std::endl;
196+
return;
197+
}
198+
199+
if (!fSize) {
200+
std::cerr << "Empty file: " << getInFileName() << std::endl;
201+
return;
202+
}
203+
204+
try {
205+
toDevice.exceptions(std::ios::failbit | std::ios::badbit);
206+
207+
toDevice.open(getInFileName(), std::ios::binary);
208+
209+
fData.resize(fSize);
210+
211+
toDevice.read(reinterpret_cast<char*>(fData.data()), static_cast<std::streamsize>(fSize));
212+
}
213+
catch (const std::ios_base::failure& iose) {
214+
std::cerr << "In file I/O error: " << iose.what() << getInFileName() << std::endl;
215+
return;
216+
}
217+
218+
// Reserve expected additional (in) payload size
219+
std::size_t reserveSize = FsField + fSize + (checksum ? CsField : 0);
220+
rawData.reserve(rawData.size() + reserveSize);
221+
222+
uint32_t fsField = convToBigEndian(static_cast<uint32_t>(fSize));
223+
auto fsValue = std::bit_cast<std::array<uint8_t, sizeof(fsField)>>(fsField);
224+
rawData.insert(rawData.end(), fsValue.begin(), fsValue.end());
225+
226+
rawData.insert(rawData.end(), fData.begin(), fData.end());
227+
228+
if (checksum) {
229+
uint32_t csField = convToBigEndian(static_cast<uint32_t>(pldm_edac_crc32(fData.data(), fData.size())));
230+
auto csValue = std::bit_cast<std::array<uint8_t, sizeof(csField)>>(csField);
231+
rawData.insert(rawData.end(), csValue.begin(), csValue.end());
232+
}
233+
}
234+
235+
/** @brief Creates a raw MCTP request message.
236+
*
237+
* This function processes raw data to prepare an MCTP request. It specifically
238+
* handles subtype SFS messages by adjusting the Integrity Check (IC) bit
239+
* based on the checksum requirement and optionally appending an input file.
240+
*
241+
* @return A pair containing the PLDM status code and the modified raw data.
242+
*/
243+
244+
std::pair<int, std::vector<uint8_t>> MctpRawOp::createRequestMsg()
245+
{
246+
if ((rawData[0] & 0x7F) == 0x7F) {
247+
if ((rawData[SubTypeByte] & 0xF) == SubTypeSFS) {
248+
rawData[0] &= 0x7F; // clear the IC bit (bit 7 [0:7]) e.g. in case 0xFF, to 0x7F
249+
if (checksum)
250+
rawData[0] |= 0x80; // set the IC bit (bit 7 [0:7]) e.g. 0x7F to 0xFF
251+
252+
if (!getInFileName().empty()) // only for the -i option
253+
appendInfile();
254+
}
255+
}
256+
257+
return {PLDM_SUCCESS, rawData};
258+
}
259+
260+
/*
261+
* @brief Parse and process a SFS response message received over MCTP.
262+
*
263+
* This function validates and extracts payload information from a SFS
264+
* response message. The following operations are performed:
265+
*
266+
* 1. Check the PLDM completion code and abort on non-zero values.
267+
* 2. Extract and validate the reported firmware file size.
268+
* 3. If an output filename is specified, extract the firmware payload data.
269+
* 4. If checksum validation is enabled, compute CRC32 of the payload and
270+
* validate it against the received checksum.
271+
* 5. Write the received firmware data to the specified output file.
272+
*
273+
* Errors and validation failures are reported to stderr and result in an
274+
* immediate return without modifying any output file.
275+
*
276+
* @param msg Pointer to the SFS message buffer.
277+
* @param size Total size of the SFS message in bytes.
278+
*
279+
* @note The function expects the message to contain AMD-specific vendor data
280+
* with fields in big-endian format as defined by the device protocol.
281+
*/
282+
283+
void MctpRawOp::handleSFSResponse(const uint8_t* msg, size_t size)
284+
{
285+
// Check Completion Code (byte)
286+
if (msg[CompCodeByte] != 0) {
287+
std::cerr << "Command failed with completion code: " << msg[CompCodeByte] << std::endl;
288+
return;
289+
}
290+
291+
if (size < (DevHdrSize + FsField))
292+
return;
293+
294+
// Extract four bytes to obtain the received file size
295+
uint32_t fSize;
296+
297+
std::memcpy(&fSize, msg + DevHdrSize, sizeof(fSize)); // File size is in Big endian format as per spec
298+
fSize = convToBigEndian(fSize);
299+
300+
try {
301+
if (fSize > MaxFileSize)
302+
throw std::runtime_error("Invalid file size in bytes: " + std::to_string(fSize));
303+
}
304+
catch(const std::runtime_error & e) {
305+
std::cerr << e.what() << std::endl;
306+
return;
307+
}
308+
309+
// If -o option is not specified, nothing to write to
310+
if (getOutFileName().empty())
311+
return;
312+
313+
if (size < (DevHdrSize + FsField + fSize)) {
314+
std::cerr << "Received response of " << size << " bytes shorter than expected size of " << ((DevHdrSize + FsField + fSize)) << std::endl;
315+
return;
316+
}
317+
318+
std::vector<uint8_t> fData{msg + DevHdrSize + FsField, msg + DevHdrSize + FsField + fSize};
319+
320+
if (checksum) {
321+
if (size < (DevHdrSize + FsField + fSize + CsField)) {
322+
std::cerr << "Received response of " << size << " bytes does not contain checksum" << std::endl;
323+
return;
324+
}
325+
326+
// Calculate checksum
327+
const uint32_t crc = pldm_edac_crc32(fData.data(), fData.size());
328+
329+
// Extract four bytes of checksum
330+
uint32_t checkSum;
331+
332+
std::memcpy(&checkSum, msg + DevHdrSize + FsField + fSize, sizeof(checkSum)); // Checksum is in Big endian format as per spec
333+
checkSum = convToBigEndian(checkSum);
334+
335+
if (crc != checkSum) {
336+
std::cerr << std::showbase << std::hex << "CRC: " << crc << " does not match the received CRC: " << checkSum << std::endl;
337+
return;
338+
}
339+
}
340+
341+
std::ofstream fromDevice;
342+
343+
try {
344+
fromDevice.exceptions(std::ios::failbit | std::ios::badbit);
345+
346+
fromDevice.open(getOutFileName(), std::ios::binary);
347+
348+
fromDevice.write(reinterpret_cast<const char*>(fData.data()), fData.size());
349+
350+
std::cerr << "Created file: " << getOutFileName() << " of size (in bytes): " << fData.size() << std::endl;
351+
}
352+
catch (const std::ios_base::failure& iose) {
353+
std::cerr << "Out file I/O error: " << iose.what() << getOutFileName() << std::endl;
354+
return;
355+
}
356+
}
357+
358+
/*
359+
* @brief Parse and process a PLDM response message received over MCTP.
360+
*
361+
* This function validates and extracts payload information from a raw PLDM
362+
* response message. The following operations are performed:
363+
*
364+
* 1. Check whether the response is for a non-SFS request.
365+
* 2. Validate input pointer and minimum message size.
366+
* 3. Verify the 4-byte IANA number (big-endian) against the expected AMD
367+
* identifier.
368+
* 3. Check whether the response for SFS sub type SFS and if it is, call
369+
* SFS response handling code.
370+
*
371+
* Errors and validation failures are reported to stderr and result in an
372+
* immediate return without modifying any output file.
373+
*
374+
* @param pmsg Pointer to the raw PLDM message buffer.
375+
* @param size Total size of the PLDM message in bytes.
376+
*
377+
* @note The function expects the message to contain AMD-specific vendor data
378+
* with fields in big-endian format as defined by the device protocol.
379+
*/
380+
381+
void MctpRawOp::parseResponseMsg(pldm_msg *pmsg, size_t size)
382+
{
383+
// If this response is not for one of the file sharing protocols (e.g. SFS), return
384+
if ((rawData[0] & 0x7F) != 0x7F)
385+
return;
386+
387+
if (!pmsg)
388+
return;
389+
390+
// Add back the size, that response receiver deducts for PLDM message header
391+
// does not apply in this case.
392+
size += sizeof(pldm_msg_hdr);
393+
394+
if (size < DevHdrSize) {
395+
std::cerr << "Invalid respose size (in bytes): " << size << ", minimum expected response size: " << DevHdrSize << std::endl;
396+
return;
397+
}
398+
399+
const uint8_t * msg{reinterpret_cast<uint8_t *>(pmsg)};
400+
401+
// Extract four bytes of IANA number to verify
402+
uint32_t iana;
403+
memcpy(&iana, msg, sizeof(iana)); // IANA number in big endian format
404+
iana = convToBigEndian(iana);
405+
if (iana != AMD_Iana_Numg) {
406+
std::cerr << std::showbase << std::hex << "Received IANA number: " << iana << " does not match with: " << AMD_Iana_Numg << std::endl;
407+
return;
408+
}
409+
410+
// Check SubType
411+
if ((msg[SubTypeByte] & 0xF) == SubTypeSFS)
412+
handleSFSResponse(msg, size);
413+
}
414+
110415
void registerCommand(CLI::App& app)
111416
{
112417
auto mctpRaw = app.add_subcommand(

0 commit comments

Comments
 (0)