@@ -72,6 +72,31 @@ namespace
7272std::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+
75100class MctpRawOp : public CommandInterface
76101{
77102 public:
@@ -90,23 +115,252 @@ 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+ // Add back the size, that response receiver deducts for PLDM message header
276+ // does not apply in this case.
277+ size += sizeof (pldm_msg_hdr);
278+
279+ if (size < DevHdrSize)
280+ return ;
281+
282+ const uint8_t * msg{reinterpret_cast <uint8_t *>(pmsg)};
283+
284+ // Extract four bytes of IANA number to verify
285+ uint32_t iana;
286+ memcpy (&iana, msg, sizeof (iana)); // IANA number in big endian format
287+ iana = convToBigEndian (iana);
288+ if (iana != AMD_Iana_Numg) {
289+ std::cerr << std::showbase << std::hex << " Received IANA number: " << iana << " does not match with: " << AMD_Iana_Numg << std::endl;
290+ return ;
291+ }
292+
293+ // Check Completion Code (byte)
294+ if (msg[CompCodeByte] != 0 ) {
295+ std::cerr << " Non-zero completion code: " << msg[CompCodeByte] << std::endl;
296+ return ;
297+ }
298+
299+ if (size < (DevHdrSize + FsField))
300+ return ;
301+
302+ // Extract four bytes to obtain the received file size
303+ uint32_t fSize ;
304+
305+ std::memcpy (&fSize , msg + DevHdrSize, sizeof (fSize )); // File size is in Big endian format as per spec
306+ fSize = convToBigEndian (fSize );
307+
308+ try {
309+ if (fSize > MaxFileSize)
310+ throw std::runtime_error (" Invalid file size in bytes: " + std::to_string (fSize ));
311+ }
312+ catch (const std::runtime_error & e) {
313+ std::cout << e.what () << std::endl;
314+ return ;
315+ }
316+
317+ // If -o option is not specified, nothing to write to
318+ if (getOutFileName ().empty ())
319+ return ;
320+
321+ if (size < (DevHdrSize + FsField + fSize )) {
322+ std::cerr << " Received response of " << size << " bytes shorter than expected size of " << ((DevHdrSize + FsField + fSize )) << std::endl;
323+ return ;
324+ }
325+
326+ std::vector<uint8_t > fData {msg + DevHdrSize + FsField, msg + DevHdrSize + FsField + fSize };
327+
328+ if (checksum) {
329+ if (size < (DevHdrSize + FsField + fSize + CsField)) {
330+ std::cerr << " Received response of " << size << " bytes does not contain checksum" << std::endl;
331+ return ;
332+ }
333+
334+ // Calculate checksum
335+ const uint32_t crc = pldm_edac_crc32 (fData .data (), fData .size ());
336+
337+ // Extract four bytes of checksum
338+ uint32_t checkSum;
339+
340+ std::memcpy (&checkSum, msg + DevHdrSize + FsField + fSize , sizeof (checkSum)); // Checksum is in Big endian format as per spec
341+ checkSum = convToBigEndian (checkSum);
342+
343+ if (crc != checkSum) {
344+ std::cerr << std::showbase << std::hex << " CRC: " << crc << " does not match the received CRC: " << checkSum << std::endl;
345+ // return;
346+ }
347+ }
348+
349+ std::ofstream fromDevice;
350+
351+ try {
352+ fromDevice.exceptions (std::ios::failbit | std::ios::badbit);
353+
354+ fromDevice.open (getOutFileName (), std::ios::binary);
355+
356+ fromDevice.write (reinterpret_cast <const char *>(fData .data ()), fData .size ());
357+ }
358+ catch (const std::ios_base::failure& iose) {
359+ std::cout << " Out file I/O error: " << iose.what () << getOutFileName () << std::endl;
360+ return ;
361+ }
362+ }
363+
110364void registerCommand (CLI::App& app)
111365{
112366 auto mctpRaw = app.add_subcommand (
0 commit comments