FruDevice: implement FRU field decoding
Currently, FruDevice's parser assumes that the field data types are
binary, so we get corrupted FRU data if other types (6-bit ASCII and
BCD plus) are present.
This change implements a decoder for the encoding types specified in the
Platform Management FRU Information Storage Definition.
Signed-off-by: Jeremy Kerr <jk@ozlabs.org>
Change-Id: I2a23c04884cbe50fe0e5e4fe407c93f2fd15be10
diff --git a/src/FruDevice.cpp b/src/FruDevice.cpp
index 874bbb1..22ad48b 100644
--- a/src/FruDevice.cpp
+++ b/src/FruDevice.cpp
@@ -42,6 +42,7 @@
#include <sstream>
#include <string>
#include <thread>
+#include <utility>
#include <variant>
#include <vector>
@@ -785,6 +786,121 @@
return val;
}
+static char sixBitToChar(uint8_t val)
+{
+ return static_cast<char>((val & 0x3f) + ' ');
+}
+
+/* 0xd - 0xf are reserved values, but not fatal; use a placeholder char. */
+static const char bcdHighChars[] = {
+ ' ', '-', '.', 'X', 'X', 'X',
+};
+
+static char bcdPlusToChar(uint8_t val)
+{
+ val &= 0xf;
+ return (val < 10) ? static_cast<char>(val + '0') : bcdHighChars[val - 10];
+}
+
+enum class DecodeState
+{
+ ok,
+ end,
+ err,
+};
+
+enum FRUDataEncoding
+{
+ binary = 0x0,
+ bcdPlus = 0x1,
+ sixBitASCII = 0x2,
+ languageDependent = 0x3,
+};
+
+/* Decode FRU data into a std::string, given an input iterator and end. If the
+ * state returned is fruDataOk, then the resulting string is the decoded FRU
+ * data. The input iterator is advanced past the data consumed.
+ *
+ * On fruDataErr, we have lost synchronisation with the length bytes, so the
+ * iterator is no longer usable.
+ */
+static std::pair<DecodeState, std::string>
+ decodeFruData(std::vector<char>::const_iterator& iter,
+ const std::vector<char>::const_iterator& end)
+{
+ std::string value;
+ unsigned int i;
+
+ /* we need at least one byte to decode the type/len header */
+ if (iter == end)
+ {
+ std::cerr << "Truncated FRU data\n";
+ return make_pair(DecodeState::err, value);
+ }
+
+ uint8_t c = *(iter++);
+
+ /* 0xc1 is the end marker */
+ if (c == 0xc1)
+ {
+ return make_pair(DecodeState::end, value);
+ }
+
+ /* decode type/len byte */
+ uint8_t type = static_cast<uint8_t>(c >> 6);
+ uint8_t len = static_cast<uint8_t>(c & 0x3f);
+
+ /* we should have at least len bytes of data available overall */
+ if (iter + len > end)
+ {
+ std::cerr << "FRU data field extends past end of FRU data\n";
+ return make_pair(DecodeState::err, value);
+ }
+
+ switch (type)
+ {
+ case FRUDataEncoding::binary:
+ case FRUDataEncoding::languageDependent:
+ /* For language-code dependent encodings, assume 8-bit ASCII */
+ value = std::string(iter, iter + len);
+ iter += len;
+ break;
+
+ case FRUDataEncoding::bcdPlus:
+ value = std::string();
+ for (i = 0; i < len; i++, iter++)
+ {
+ uint8_t val = static_cast<uint8_t>(*iter);
+ value.push_back(bcdPlusToChar(val >> 4));
+ value.push_back(bcdPlusToChar(val & 0xf));
+ }
+ break;
+
+ case FRUDataEncoding::sixBitASCII:
+ {
+ unsigned int accum;
+ unsigned int accumBitLen = 0;
+ value = std::string();
+ for (i = 0; i < len; i++, iter++)
+ {
+ /* we need to cast to unsigned before the promotion to
+ * int, so we don't sign-extend. */
+ accum |= static_cast<unsigned char>(*iter) << accumBitLen;
+ accumBitLen += 8;
+ while (accumBitLen >= 6)
+ {
+ value.push_back(sixBitToChar(accum & 0x3f));
+ accum >>= 6;
+ accumBitLen -= 6;
+ }
+ }
+ }
+ break;
+ }
+
+ return make_pair(DecodeState::ok, value);
+}
+
bool formatFru(const std::vector<char>& fruBytes,
boost::container::flat_map<std::string, std::string>& result)
{
@@ -882,26 +998,20 @@
}
for (auto& field : *fieldData)
{
- if (fruBytesIter >= fruBytes.end())
- {
- return false;
- }
+ auto res = decodeFruData(fruBytesIter, fruBytes.end());
+ std::string name = area + "_" + field;
- /* Checking for last byte C1 to indicate that no more
- * field to be read */
- if (static_cast<uint8_t>(*fruBytesIter) == 0xC1)
+ if (res.first == DecodeState::end)
{
break;
}
-
- size_t length = *fruBytesIter & 0x3f;
- fruBytesIter += 1;
-
- if (fruBytesIter >= fruBytes.end())
+ else if (res.first == DecodeState::err)
{
+ std::cerr << "Error while parsing " << name << "\n";
return false;
}
- std::string value(fruBytesIter, fruBytesIter + length);
+
+ std::string value = res.second;
// Strip non null characters from the end
value.erase(std::find_if(value.rbegin(), value.rend(),
@@ -909,27 +1019,7 @@
.base(),
value.end());
- result[area + "_" + field] = std::move(value);
-
- fruBytesIter += length;
- if (fruBytesIter >= fruBytes.end())
- {
- std::cerr << "Warning Fru Length Mismatch:\n ";
- for (auto& c : fruBytes)
- {
- std::cerr << c;
- }
- std::cerr << "\n";
- if (DEBUG)
- {
- for (auto& keyPair : result)
- {
- std::cerr << keyPair.first << " : "
- << keyPair.second << "\n";
- }
- }
- return false;
- }
+ result[name] = std::move(value);
}
}
}