@@ -72,6 +72,33 @@ 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 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+
75102class 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+
110415void registerCommand (CLI::App& app)
111416{
112417 auto mctpRaw = app.add_subcommand (
0 commit comments