Skip to content

Commit 57fdeb2

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 <Shirish.Pargaonkar@amd.com>
1 parent 92694a1 commit 57fdeb2

File tree

1 file changed

+261
-9
lines changed

1 file changed

+261
-9
lines changed

pldmtool/pldmtool.cpp

Lines changed: 261 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,31 @@ 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 DevHdrSize = 7;
94+
constexpr unsigned int FsField = 4;
95+
constexpr unsigned int CsField = 4;
96+
constexpr unsigned int CompCodeByte = 6;
97+
constexpr unsigned int MaxFileSize = 0x4000;
98+
constexpr unsigned int AMD_Iana_Numg = 0xe78;
99+
75100
class MctpRawOp : public CommandInterface
76101
{
77102
public:
@@ -90,23 +115,250 @@ class MctpRawOp : public CommandInterface
90115
app->add_option("-d,--data", rawData, "raw MCTP data")
91116
->required()
92117
->expected(-3);
118+
app->add_option("-i,--file-in", inFileName, "Read in the file to be sent");
119+
app->add_option("-o,--file-out", outFileName, "Write out the received file");
120+
app->add_flag("-c,--checksum", checksum, "Append/Validate checksum");
93121
app->add_flag("-p,--prealloc-tag", mctpPreAllocTag,
94122
"use pre-allocated MCTP tag for this request");
95-
}
96-
std::pair<int, std::vector<uint8_t>> createRequestMsg() override
97-
98-
{
99-
return {PLDM_SUCCESS, rawData};
123+
app->footer(R"(Example: pldmtool mctpRaw -m 21 -e 1 \
124+
--data 0x7F 0x00 0x00 0x0E 0x78 0x80 0x02 \
125+
--file-in req.bin --file-out resp.bin --checksum)");
100126
}
101127

102-
void parseResponseMsg(pldm_msg* /* responsePtr */,
103-
size_t /* payloadLength */) override
104-
{}
105-
128+
const std::string getInFileName() const {return inFileName;}
129+
const std::string getOutFileName() const {return outFileName;}
130+
std::pair<int, std::vector<uint8_t>> createRequestMsg() override;
131+
void parseResponseMsg(pldm_msg *, size_t) override;
106132
private:
107133
std::vector<uint8_t> rawData;
134+
std::string outFileName{};
135+
std::string inFileName{};
136+
bool checksum{false};
137+
138+
void appendInfile();
108139
};
109140

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

0 commit comments

Comments
 (0)