summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.gitignore3
-rw-r--r--MAINTAINERS1
-rw-r--r--Makefile.am11
-rw-r--r--README.md322
-rw-r--r--configure.ac33
-rw-r--r--elog_entry.hpp6
-rw-r--r--elog_meta.hpp17
-rw-r--r--extensions.cpp16
-rw-r--r--extensions.hpp246
-rw-r--r--extensions/extensions.mk3
-rw-r--r--extensions/openpower-pels/README.md101
-rw-r--r--extensions/openpower-pels/additional_data.hpp132
-rw-r--r--extensions/openpower-pels/ascii_string.cpp107
-rw-r--r--extensions/openpower-pels/ascii_string.hpp103
-rw-r--r--extensions/openpower-pels/bcd_time.cpp84
-rw-r--r--extensions/openpower-pels/bcd_time.hpp111
-rw-r--r--extensions/openpower-pels/callout.cpp117
-rw-r--r--extensions/openpower-pels/callout.hpp183
-rw-r--r--extensions/openpower-pels/callouts.cpp51
-rw-r--r--extensions/openpower-pels/callouts.hpp96
-rw-r--r--extensions/openpower-pels/data_interface.cpp290
-rw-r--r--extensions/openpower-pels/data_interface.hpp382
-rw-r--r--extensions/openpower-pels/entry_points.cpp80
-rw-r--r--extensions/openpower-pels/event_logger.hpp187
-rw-r--r--extensions/openpower-pels/extended_user_header.cpp179
-rw-r--r--extensions/openpower-pels/extended_user_header.hpp254
-rw-r--r--extensions/openpower-pels/failing_mtms.cpp104
-rw-r--r--extensions/openpower-pels/failing_mtms.hpp114
-rw-r--r--extensions/openpower-pels/fru_identity.cpp113
-rw-r--r--extensions/openpower-pels/fru_identity.hpp222
-rw-r--r--extensions/openpower-pels/generic.cpp70
-rw-r--r--extensions/openpower-pels/generic.hpp87
-rw-r--r--extensions/openpower-pels/host_interface.hpp211
-rw-r--r--extensions/openpower-pels/host_notifier.cpp435
-rw-r--r--extensions/openpower-pels/host_notifier.hpp319
-rw-r--r--extensions/openpower-pels/json_utils.cpp207
-rw-r--r--extensions/openpower-pels/json_utils.hpp87
-rw-r--r--extensions/openpower-pels/log_id.cpp112
-rw-r--r--extensions/openpower-pels/log_id.hpp47
-rw-r--r--extensions/openpower-pels/manager.cpp356
-rw-r--r--extensions/openpower-pels/manager.hpp281
-rw-r--r--extensions/openpower-pels/mru.cpp68
-rw-r--r--extensions/openpower-pels/mru.hpp122
-rw-r--r--extensions/openpower-pels/mtms.cpp102
-rw-r--r--extensions/openpower-pels/mtms.hpp183
-rw-r--r--extensions/openpower-pels/openpower-pels.mk72
-rw-r--r--extensions/openpower-pels/paths.cpp49
-rw-r--r--extensions/openpower-pels/paths.hpp24
-rw-r--r--extensions/openpower-pels/pce_identity.cpp48
-rw-r--r--extensions/openpower-pels/pce_identity.hpp125
-rw-r--r--extensions/openpower-pels/pel.cpp395
-rw-r--r--extensions/openpower-pels/pel.hpp362
-rw-r--r--extensions/openpower-pels/pel_rules.cpp90
-rw-r--r--extensions/openpower-pels/pel_rules.hpp32
-rw-r--r--extensions/openpower-pels/pel_types.hpp134
-rw-r--r--extensions/openpower-pels/pel_values.cpp329
-rw-r--r--extensions/openpower-pels/pel_values.hpp132
-rw-r--r--extensions/openpower-pels/pldm_interface.cpp279
-rw-r--r--extensions/openpower-pels/pldm_interface.hpp160
-rw-r--r--extensions/openpower-pels/private_header.cpp194
-rw-r--r--extensions/openpower-pels/private_header.hpp312
-rw-r--r--extensions/openpower-pels/registry.cpp424
-rw-r--r--extensions/openpower-pels/registry.hpp358
-rw-r--r--extensions/openpower-pels/registry/ComponentIDs.md9
-rw-r--r--extensions/openpower-pels/registry/README.md240
-rw-r--r--extensions/openpower-pels/registry/message_registry.json202
-rw-r--r--extensions/openpower-pels/registry/run-ci.sh3
-rw-r--r--extensions/openpower-pels/registry/schema/registry_example.json39
-rw-r--r--extensions/openpower-pels/registry/schema/schema.json354
-rwxr-xr-xextensions/openpower-pels/registry/tools/process_registry.py167
-rw-r--r--extensions/openpower-pels/repository.cpp382
-rw-r--r--extensions/openpower-pels/repository.hpp376
-rw-r--r--extensions/openpower-pels/section.hpp92
-rw-r--r--extensions/openpower-pels/section_factory.cpp80
-rw-r--r--extensions/openpower-pels/section_factory.hpp30
-rw-r--r--extensions/openpower-pels/section_header.hpp109
-rw-r--r--extensions/openpower-pels/severity.cpp56
-rw-r--r--extensions/openpower-pels/severity.hpp20
-rw-r--r--extensions/openpower-pels/src.cpp444
-rw-r--r--extensions/openpower-pels/src.hpp385
-rw-r--r--extensions/openpower-pels/stream.hpp373
-rw-r--r--extensions/openpower-pels/tools/peltool.cpp431
-rw-r--r--extensions/openpower-pels/user_data.cpp108
-rw-r--r--extensions/openpower-pels/user_data.hpp113
-rw-r--r--extensions/openpower-pels/user_data_formats.hpp19
-rw-r--r--extensions/openpower-pels/user_data_json.cpp153
-rw-r--r--extensions/openpower-pels/user_data_json.hpp25
-rw-r--r--extensions/openpower-pels/user_header.cpp177
-rw-r--r--extensions/openpower-pels/user_header.hpp288
-rw-r--r--log_manager.cpp133
-rw-r--r--log_manager.hpp76
-rw-r--r--log_manager_main.cpp20
-rw-r--r--org.openbmc.Associations.cpp160
-rw-r--r--org/openbmc/Associations.interface.yaml13
-rw-r--r--org/openbmc/Associations/server.hpp97
-rw-r--r--phosphor-logging/elog.hpp2
-rw-r--r--phosphor-logging/log.hpp112
-rw-r--r--phosphor-rsyslog-config/Makefile.am3
-rw-r--r--phosphor-rsyslog-config/server-conf.cpp23
-rw-r--r--test/Makefile.am44
-rw-r--r--test/extensions_test.cpp98
-rw-r--r--test/openpower-pels/Makefile.include364
-rw-r--r--test/openpower-pels/additional_data_test.cpp65
-rw-r--r--test/openpower-pels/ascii_string_test.cpp86
-rw-r--r--test/openpower-pels/bcd_time_test.cpp105
-rw-r--r--test/openpower-pels/event_logger_test.cpp126
-rw-r--r--test/openpower-pels/extended_user_header_test.cpp301
-rw-r--r--test/openpower-pels/failing_mtms_test.cpp133
-rw-r--r--test/openpower-pels/fru_identity_test.cpp89
-rw-r--r--test/openpower-pels/generic_section_test.cpp64
-rw-r--r--test/openpower-pels/host_notifier_test.cpp680
-rw-r--r--test/openpower-pels/json_utils_test.cpp50
-rw-r--r--test/openpower-pels/log_id_test.cpp57
-rw-r--r--test/openpower-pels/mocks.hpp239
-rw-r--r--test/openpower-pels/mru_test.cpp74
-rw-r--r--test/openpower-pels/mtms_test.cpp122
-rw-r--r--test/openpower-pels/paths.cpp67
-rw-r--r--test/openpower-pels/pce_identity_test.cpp57
-rw-r--r--test/openpower-pels/pel_manager_test.cpp514
-rw-r--r--test/openpower-pels/pel_rules_test.cpp72
-rw-r--r--test/openpower-pels/pel_test.cpp387
-rw-r--r--test/openpower-pels/pel_utils.cpp297
-rw-r--r--test/openpower-pels/pel_utils.hpp106
-rw-r--r--test/openpower-pels/pel_values_test.cpp40
-rw-r--r--test/openpower-pels/private_header_test.cpp194
-rw-r--r--test/openpower-pels/real_pel_test.cpp570
-rw-r--r--test/openpower-pels/registry_test.cpp336
-rw-r--r--test/openpower-pels/repository_test.cpp436
-rw-r--r--test/openpower-pels/section_header_test.cpp55
-rw-r--r--test/openpower-pels/severity_test.cpp33
-rw-r--r--test/openpower-pels/src_callout_test.cpp161
-rw-r--r--test/openpower-pels/src_callouts_test.cpp91
-rw-r--r--test/openpower-pels/src_test.cpp261
-rw-r--r--test/openpower-pels/stream_test.cpp197
-rw-r--r--test/openpower-pels/user_data_test.cpp106
-rw-r--r--test/openpower-pels/user_header_test.cpp163
-rw-r--r--test/remote_logging_test_config.cpp21
-rw-r--r--tools/phosphor-logging/templates/elog-lookup-template.mako.cpp20
138 files changed, 21636 insertions, 433 deletions
diff --git a/.gitignore b/.gitignore
index a992ca3..be5d9e2 100644
--- a/.gitignore
+++ b/.gitignore
@@ -2,7 +2,7 @@ Makefile
Makefile.in
aclocal.m4
ar-lib
-arm-openbmc-linux-gnueabi-libtool
+*-libtool
autom4te.cache*
compile
config.*
@@ -43,3 +43,4 @@ test/test-suite.log
test/*_test_*
test/*_test
phosphor-rsyslog-config/phosphor-rsyslog-conf
+peltool
diff --git a/MAINTAINERS b/MAINTAINERS
index edd0c08..5bea752 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -43,5 +43,6 @@ START OF MAINTAINERS LIST
-------------------------
M: Deepak Kodihalli <dkodihal@linux.vnet.ibm.com> <dkodihal!>
+M: Matt Spinler <spinler@us.ibm.com> <mspinler!>
R: Adriana Kobylak <anoo@us.ibm.com> <anoo!>
R: Andrew Geissler <geissonator@yahoo.com> <andrewg!>
diff --git a/Makefile.am b/Makefile.am
index 82d6c86..961aecf 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -36,11 +36,11 @@ phosphor_log_manager_SOURCES = \
log_manager_main.cpp \
elog-lookup.cpp \
elog_entry.cpp \
- org.openbmc.Associations.cpp \
elog-process-metadata.cpp \
elog_meta.cpp \
elog_serialize.cpp \
- sdjournal.cpp
+ sdjournal.cpp \
+ extensions.cpp
# Be sure to build needed files before compiling
BUILT_SOURCES = \
@@ -69,12 +69,18 @@ phosphor_log_manager_LDFLAGS = \
$(SYSTEMD_LIBS) \
$(SDBUSPLUS_LIBS) \
$(PHOSPHOR_DBUS_INTERFACES_LIBS) \
+ $(SDEVENTPLUS_LIBS) \
-lstdc++fs
phosphor_log_manager_CXXFLAGS = \
$(SYSTEMD_CFLAGS) \
$(SDBUSPLUS_CFLAGS) \
+ $(SDEVENTPLUS_CFLAGS) \
$(PHOSPHOR_DBUS_INTERFACES_CFLAGS)
+
+include extensions/extensions.mk
+
+
xyz/openbmc_project/Logging/Internal/Manager/server.cpp: xyz/openbmc_project/Logging/Internal/Manager.interface.yaml xyz/openbmc_project/Logging/Internal/Manager/server.hpp
@mkdir -p `dirname $@`
$(SDBUSPLUSPLUS) -r $(srcdir) interface server-cpp xyz.openbmc_project.Logging.Internal.Manager > $@
@@ -90,6 +96,7 @@ CALLOUTS_MAKO ?= callouts-gen.mako.hpp
ELOG_TEMPLATE_DIR ?= ${abs_srcdir}/tools/phosphor-logging/templates/
REQ_FILES_TO_GEN ?= ${abs_srcdir}/tools/elog-gen.py\
${ELOG_TEMPLATE_DIR}/$(ELOG_MAKO)\
+ ${ELOG_TEMPLATE_DIR}/$(LOOKUP_MAKO)\
${ELOG_TEMPLATE_DIR}/$(META_MAKO)\
${abs_srcdir}/callouts/callouts.py\
${abs_srcdir}/callouts/$(CALLOUTS_MAKO)
diff --git a/README.md b/README.md
index f8ab729..bb5c649 100644
--- a/README.md
+++ b/README.md
@@ -1,6 +1,13 @@
# phosphor-logging
-phosphor logging provides mechanism for common event and logging creation based
-on information from the journal log.
+The phosphor logging repository provides mechanisms for event and journal
+logging.
+
+## Table Of Contents
+* [Building](#to-build)
+* [Remote Logging](#remote-logging-via-rsyslog)
+* [Event Logs](#event-logs)
+* [Application Specific Error YAML](#adding-application-specific-error-yaml)
+* [Event Log Extensions](#event-log-extensions)
## To Build
```
@@ -70,6 +77,212 @@ to the IP.
When switching to a new server from an existing one (i.e the address, or port,
or both change), it is recommended to disable the existing configuration first.
+## Event Logs
+OpenBMC event logs are a collection of D-Bus interfaces owned by
+phosphor-log-manager that reside at `/xyz/openbmc_project/logging/entry/X`,
+where X starts at 1 and is incremented for each new log.
+
+The interfaces are:
+* [xyz.openbmc_project.Logging.Entry]
+ * The main event log interface.
+* [xyz.openbmc_project.Association.Definitions]
+ * Used for specifying inventory items as the cause of the event.
+ * For more information on associations, see [here][associations-doc].
+* [xyz.openbmc_project.Object.Delete]
+ * Provides a Delete method to delete the event.
+* [xyz.openbmc_project.Software.Version]
+ * Stores the code version that the error occurred on.
+
+On platforms that make use of these event logs, the intent is that they are
+the common event log representation that other types of event logs can be
+created from. For example, there is code to convert these into both Redfish
+and IPMI event logs, in addition to the event log extensions mentioned
+[below](#event-log-extensions).
+
+The logging daemon has the ability to add `callout` associations to an event
+log based on text in the AdditionalData property. A callout is a link to the
+inventory item(s) that were the cause of the event log. See [here][callout-doc]
+for details.
+
+### Creating Event Logs In Code
+There are two approaches to creating event logs in OpenBMC code. The first
+makes use of the systemd journal to store metadata needed for the log, and the
+second is a plain D-Bus method call.
+
+#### Journal Based Event Log Creation
+Event logs can be created by using phosphor-logging APIs to commit sdbusplus
+exceptions. These APIs write to the journal, and then call a `Commit`
+D-Bus method on the logging daemon to create the event log using the information
+it put in the journal.
+
+The APIs are found in `<phosphor-logging/elog.hpp>`:
+* `elog()`: Throw an sdbusplus error.
+* `commit()`: Catch an error thrown by elog(), and commit it to create the
+ event log.
+* `report()`: Create an event log from an sdbusplus error without throwing the
+ exception first.
+
+Any errors passed into these APIs must be known to phosphor-logging, usually
+by being defined in `<phosphor-logging/elog-errors.hpp>`. The errors must
+also be known by sdbusplus, and be defined in their corresponding error.hpp.
+See below for details on how get errors into these headers.
+
+Example:
+```
+#include <phosphor-logging/elog-errors.hpp>
+#include <phosphor-logging/elog.hpp>
+#include <xyz/openbmc_project/Common/error.hpp>
+...
+using InternalFailure =
+ sdbusplus::xyz::openbmc_project::Common::Error::InternalFailure;
+...
+if (somethingBadHappened)
+{
+ phosphor::logging::report<InternalFailure>();
+}
+
+```
+Alternatively, to throw, catch, and then commit the error:
+```
+try
+{
+ phosphor::logging::elog<InternalFailure>();
+}
+catch (InternalFailure& e)
+{
+ phosphor::logging::commit<InternalFailure>();
+}
+```
+
+Metadata can be added to event logs to add debug data captured at the time of
+the event. It shows up in the AdditionalData property in the
+`xyz.openbmc_project.Logging.Entry` interface. Metadata is passed in via the
+`elog()` or `report()` functions, which write it to the journal. The metadata
+must be predefined for the error in the [metadata YAML](#event-log-definition)
+so that the daemon knows to look for it in the journal when it creates the
+event log.
+
+Example:
+```
+#include <phosphor-logging/elog-errors.hpp>
+#include <phosphor-logging/elog.hpp>
+#include <xyz/openbmc_project/Control/Device/error.hpp>
+...
+using WriteFailure =
+ sdbusplus::xyz::openbmc_project::Control::Device::Error::WriteFailure;
+using metadata =
+ xyz::openbmc_project::Control::Device::WriteFailure;
+...
+if (somethingBadHappened)
+{
+ phosphor::logging::report<WriteFailure>(metadata::CALLOUT_ERRNO(5),
+ metadata::CALLOUT_DEVICE_PATH("some path"));
+}
+```
+In the above example, the AdditionalData property would look like:
+```
+["CALLOUT_ERRNO=5", "CALLOUT_DEVICE_PATH=some path"]
+```
+Note that the metadata fields must be all uppercase.
+
+##### Event Log Definition
+As mentioned above, both sdbusplus and phosphor-logging must know about the
+event logs in their header files, or the code that uses them will not even
+compile. The standard way to do this to define the event in the appropriate
+`<error-category>.errors.yaml` file, and define any metadata in the
+`<error-category>.metadata.yaml` file in the appropriate `*-dbus-interfaces`
+repository. During the build, phosphor-logging generates the elog-errors.hpp
+file for use by the calling code.
+
+In much the same way, sdbusplus uses the event log definitions to generate an
+error.hpp file that contains the specific exception. The path of the error.hpp
+matches the path of the YAML file.
+
+For example, if in phosphor-dbus-interfaces there is
+`xyz/openbmc_project/Control/Device.errors.yaml`, the errors that come from
+that file will be in the include:
+`xyz/openbmc_project/Control/Device/error.hpp`.
+
+In rare cases, one may want one to define their errors in the same repository
+that uses them. To do that, one must:
+
+1. Add the error and metadata YAML files to the repository.
+2. Run the sdbus++ script within the makefile to create the error.hpp and .cpp
+ files from the local YAML, and include the error.cpp file in the application
+ that uses it. See [openpower-occ-control] for an example.
+3. Tell phosphor-logging about the error. This is done by either:
+ * Following the [directions](#adding-application-specific-error-yaml)
+ defined in this README, or
+ * Running the script yourself:
+ 1. Run phosphor-logging\'s `elog-gen.py` script on the local yaml to
+ generate an elog-errors.hpp file that just contains the local errors,
+ and check that into the repository and include it where the errors are
+ needed.
+ 2. Create a recipe that copies the local YAML files to a place that
+ phosphor-logging can find it during the build. See [here][led-link]
+ for an example.
+
+#### D-Bus Event Log Creation
+There is also a [D-Bus method][log-create-link] to create event logs:
+* Service: xyz.openbmc_project.Logging
+* Object Path: /xyz/openbmc_project/logging
+* Interface: xyz.openbmc_project.Logging.Create
+* Method: Create
+ * Method Arguments:
+ * Message: The `Message` string property for the
+ `xyz.openbmc_project.Logging.Entry` interface.
+ * Severity: The `severity` property for the
+ `xyz.openbmc_project.Logging.Entry` interface.
+ An `xyz.openbmc_project.Logging.Entry.Level` enum value.
+ * AdditionalData: The `AdditionalData` property for the
+ `xyz.openbmc_project.Logging.Entry` interface, but in a map
+ instead of in a vector of "KEY=VALUE" strings.
+ Example:
+```
+ std::map<std::string, std::string> additionalData;
+ additionalData["KEY"] = "VALUE";
+```
+
+
+Unlike the previous APIs where errors could also act as exceptions that could
+be thrown across D-Bus, this API does not require that the error be defined in
+the error YAML in the D-Bus interfaces repository so that sdbusplus knows about
+it. Additionally, as this method passes in everything needed to create the
+event log, the logging daemon doesn't have to know about it ahead of time
+either.
+
+That being said, it is recommended that users of this API still follow some
+guidelines for the message field, which is normally generated from a
+combination of the path to the error YAML file and the error name itself. For
+example, the `Timeout` error in `xyz/openbmc_project/Common.errors.yaml` will
+have a Message property of `xyz.openbmc_project.Common.Error.Timeout`.
+
+The guidelines are:
+1. When it makes sense, one can still use an existing error that has already
+ been defined in an error YAML file, and use the same severity and metadata
+ (AdditionalData) as in the corresponding metadata YAML file.
+
+2. If creating a new error, use the same naming scheme as other errors, which
+ starts with the domain, `xyz.openbmc_project`, `org.open_power`, etc,
+ followed by the capitalized category values, followed by `Error`, followed
+ by the capitalized error name itself, with everything separated by "."s.
+ For example: `xyz.openbmc_project.Some.Category.Error.Name`.
+
+3. If creating a new common error, still add it to the appropriate error and
+ metadata YAML files in the appropriate D-Bus interfaces repository so that
+ others can know about it and use it in the future. This can be done after
+ the fact.
+
+[xyz.openbmc_project.Logging.Entry]: https://github.com/openbmc/phosphor-dbus-interfaces/blob/master/xyz/openbmc_project/Logging/Entry.interface.yaml
+[xyz.openbmc_project.Association.Definitions]: https://github.com/openbmc/phosphor-dbus-interfaces/blob/master/xyz/openbmc_project/Association/Definitions.interface.yaml
+[associations-doc]: https://github.com/openbmc/docs/blob/master/architecture/object-mapper.md#associations
+[callout-doc]: https://github.com/openbmc/phosphor-dbus-interfaces/blob/master/xyz/openbmc_project/Common/Callout/README.md
+[xyz.openbmc_project.Object.Delete]: https://github.com/openbmc/phosphor-dbus-interfaces/blob/master/xyz/openbmc_project/Object/Delete.interface.yaml
+[xyz.openbmc_project.Software.Version]: https://github.com/openbmc/phosphor-dbus-interfaces/blob/master/xyz/openbmc_project/Software/Version.errors.yaml
+[elog-errors.hpp]: https://github.com/openbmc/phosphor-logging/blob/master/phosphor-logging/elog.hpp
+[openpower-occ-control]: https://github.com/openbmc/openpower-occ-control
+[led-link]: https://github.com/openbmc/openbmc/tree/master/meta-phosphor/recipes-phosphor/leds
+[log-create-link]: https://github.com/openbmc/phosphor-dbus-interfaces/blob/master/xyz/openbmc_project/Logging/Create.interface.yaml
## Adding application specific error YAML
* This document captures steps for adding application specific error YAML files
@@ -244,3 +457,108 @@ PACKAGECONFIG_remove_class-target = "install_error_yaml"
**Reference**
* https://github.com/openbmc/openpower-debug-collector/blob/master/README.md
+
+## Event Log Extensions
+
+The extension concept is a way to allow code that creates other formats of
+error logs besides phosphor-logging's event logs to still reside in the
+phosphor-log-manager application.
+
+The extension code lives in the `extensions/<extension>` subdirectories,
+and is enabled with a `--enable-<extension>` configure flag. The
+extension code won't compile unless enabled with this flag.
+
+Extensions can register themselves to have functions called at the following
+points using the REGISTER_EXTENSION_FUNCTION macro.
+* On startup
+ * Function type void(internal::Manager&)
+* After an event log is created
+ * Function type void(args)
+ * The args are:
+ * const std::string& - The Message property
+ * uin32_t - The event log ID
+ * uint64_t - The event log timestamp
+ * Level - The event level
+ * const AdditionalDataArg& - the additional data
+ * const AssociationEndpointsArg& - Association endpoints (callouts)
+* Before an event log is deleted, to check if it is allowed.
+ * Function type void(std::uint32_t, bool&) that takes the event ID
+* After an event log is deleted
+ * Function type void(std::uint32_t) that takes the event ID
+
+Using these callback points, they can create their own event log for each
+OpenBMC event log that is created, and delete these logs when the corresponding
+OpenBMC event log is deleted.
+
+In addition, an extension has the option of disabling phosphor-logging's
+default error log capping policy so that it can use its own. The macro
+DISABLE_LOG_ENTRY_CAPS() is used for that.
+
+### Motivation
+
+The reason for adding support for extensions inside the phosphor-log-manager
+daemon as opposed to just creating new daemons that listen for D-Bus signals is
+to allow interactions that would be complicated or expensive if just done over
+D-Bus, such as:
+* Allowing for custom old log retention algorithms.
+* Prohibiting manual deleting of certain logs based on an extension's
+ requirements.
+
+### Creating extensions
+
+1. Add a new flag to configure.ac to enable the extension:
+```
+AC_ARG_ENABLE([foo-extension],
+ AS_HELP_STRING([--enable-foo-extension],
+ [Create Foo logs]))
+AM_CONDITIONAL([ENABLE_FOO_EXTENSION],
+ [test "x$enable_foo_extension" == "xyes"])
+```
+2. Add the code in `extensions/<extension>/`.
+3. Create a makefile include to add the new code to phosphor-log-manager:
+```
+phosphor_log_manager_SOURCES += \
+ extensions/foo/foo.cpp
+```
+3. In `extensions/extensions.mk`, add the makefile include:
+```
+if ENABLE_FOO_EXTENSION
+include extensions/foo/foo.mk
+endif
+```
+4. In the extension code, register the functions to call and optionally disable
+ log capping using the provided macros:
+```
+DISABLE_LOG_ENTRY_CAPS();
+
+void fooStartup(internal::Manager& manager)
+{
+ // Initialize
+}
+
+REGISTER_EXTENSION_FUNCTION(fooStartup);
+
+void fooCreate(const std::string& message, uint32_t id, uint64_t timestamp,
+ Entry::Level severity, const AdditionalDataArg& additionalData,
+ const AssociationEndpointsArg& assocs)
+{
+ // Create a different type of error log based on 'entry'.
+}
+
+REGISTER_EXTENSION_FUNCTION(fooCreate);
+
+void fooRemove(uint32_t id)
+{
+ // Delete the extension error log that corresponds to 'id'.
+}
+
+REGISTER_EXTENSION_FUNCTION(fooRemove);
+```
+### Extension List
+
+The supported extensions are:
+
+* OpenPower PELs
+ * Enabled with --enable-openpower-pel-extension
+ * Detailed information can be found
+ [here](extensions/openpower-pels/README.md)
diff --git a/configure.ac b/configure.ac
index f918ba6..ae9d6e8 100644
--- a/configure.ac
+++ b/configure.ac
@@ -38,6 +38,7 @@ AS_IF([test "x$enable_install_scripts" != "xyes"], [
# If we ever have a library, move this to private.
AX_PKG_CHECK_MODULES([SDBUSPLUS], [sdbusplus])
AX_PKG_CHECK_MODULES([PHOSPHOR_DBUS_INTERFACES], [phosphor-dbus-interfaces])
+ PKG_CHECK_MODULES([SDEVENTPLUS], [sdeventplus])
# Check for sdbus++
AC_PATH_PROG([SDBUSPLUSPLUS], [sdbus++])
@@ -146,6 +147,38 @@ AC_DEFINE(SYSTEMD_BUSNAME, "org.freedesktop.systemd1", [systemd busname.])
AC_DEFINE(SYSTEMD_PATH, "/org/freedesktop/systemd1", [systemd path.])
AC_DEFINE(SYSTEMD_INTERFACE, "org.freedesktop.systemd1.Manager", [systemd interface.])
+AC_ARG_ENABLE([openpower-pel-extension],
+ AS_HELP_STRING([--enable-openpower-pel-extension], [Create PELs])
+)
+
+AC_ARG_VAR(EXTENSION_PERSIST_DIR, [Base directory for extension persistent data])
+AS_IF([test "x$EXTENSION_PERSIST_DIR" == "x"], \
+ [EXTENSION_PERSIST_DIR="/var/lib/phosphor-logging/extensions"])
+AC_DEFINE_UNQUOTED([EXTENSION_PERSIST_DIR], ["$EXTENSION_PERSIST_DIR"], \
+ [Base directory for extension persistent data])
+
+AM_CONDITIONAL([ENABLE_PEL_EXTENSION], [test "x$enable_openpower_pel_extension" == "xyes"])
+
+AS_IF([test "x$enable_openpower_pel_extension" == "xyes"],
+ [AC_CHECK_HEADER(
+ nlohmann/json.hpp,
+ [],
+ [AC_MSG_ERROR([Could not find nlohmann/json.hpp])])
+ AC_CHECK_HEADER(
+ fifo_map.hpp,
+ [],
+ [AC_MSG_ERROR([Could not find fifo_map.hpp])])
+
+ AX_PKG_CHECK_MODULES([LIBPLDM], [libpldm])]
+)
+
+AC_ARG_ENABLE([dont-send-pels-to-host],
+ AS_HELP_STRING([--enable-dont-send-pels-to-host],
+ [Do not send PELs to the host. \
+ Only applies when PELs are enabled.]),
+ [AX_APPEND_COMPILE_FLAGS([-DDONT_SEND_PELS_TO_HOST])]
+)
+
AC_CONFIG_HEADERS([config.h])
AC_CONFIG_FILES([Makefile test/Makefile phosphor-rsyslog-config/Makefile])
AC_CONFIG_FILES([phosphor-logging.pc])
diff --git a/elog_entry.hpp b/elog_entry.hpp
index abcfe50..9c93afe 100644
--- a/elog_entry.hpp
+++ b/elog_entry.hpp
@@ -1,12 +1,12 @@
#pragma once
-#include "org/openbmc/Associations/server.hpp"
#include "xyz/openbmc_project/Logging/Entry/server.hpp"
#include "xyz/openbmc_project/Object/Delete/server.hpp"
#include "xyz/openbmc_project/Software/Version/server.hpp"
#include <sdbusplus/bus.hpp>
#include <sdbusplus/server/object.hpp>
+#include <xyz/openbmc_project/Association/Definitions/server.hpp>
namespace phosphor
{
@@ -16,7 +16,7 @@ namespace logging
using EntryIfaces = sdbusplus::server::object::object<
sdbusplus::xyz::openbmc_project::Logging::server::Entry,
sdbusplus::xyz::openbmc_project::Object::server::Delete,
- sdbusplus::org::openbmc::server::Associations,
+ sdbusplus::xyz::openbmc_project::Association::server::Definitions,
sdbusplus::xyz::openbmc_project::Software::server::Version>;
using AssociationList =
@@ -31,7 +31,7 @@ class Manager;
* @brief OpenBMC logging entry implementation.
* @details A concrete implementation for the
* xyz.openbmc_project.Logging.Entry and
- * org.openbmc.Associations DBus APIs.
+ * xyz.openbmc_project.Associations.Definitions DBus APIs.
*/
class Entry : public EntryIfaces
{
diff --git a/elog_meta.hpp b/elog_meta.hpp
index fe14a11..6783a32 100644
--- a/elog_meta.hpp
+++ b/elog_meta.hpp
@@ -46,6 +46,23 @@ inline void parse(const std::vector<std::string>& data,
}
};
+/** @brief Combine the metadata keys and values from the map
+ * into a vector of strings that look like:
+ * "<metadata name>=<metadata value>"
+ * @param [in] data - metadata key:value map
+ * @param [out] metadata - vector of "key=value" strings
+ */
+inline void combine(const std::map<std::string, std::string>& data,
+ std::vector<std::string>& metadata)
+{
+ for (const auto& [key, value] : data)
+ {
+ std::string line{key};
+ line += "=" + value;
+ metadata.push_back(std::move(line));
+ }
+}
+
/** @brief Build error associations specific to metadata. Specialize this
* template for handling a specific type of metadata.
* @tparam M - type of metadata
diff --git a/extensions.cpp b/extensions.cpp
new file mode 100644
index 0000000..ffeb059
--- /dev/null
+++ b/extensions.cpp
@@ -0,0 +1,16 @@
+#include "extensions.hpp"
+
+namespace phosphor
+{
+namespace logging
+{
+
+StartupFunctions Extensions::startupFunctions{};
+CreateFunctions Extensions::createFunctions{};
+DeleteFunctions Extensions::deleteFunctions{};
+DeleteProhibitedFunctions Extensions::deleteProhibitedFunctions{};
+Extensions::DefaultErrorCaps Extensions::defaultErrorCaps =
+ Extensions::DefaultErrorCaps::enable;
+
+} // namespace logging
+} // namespace phosphor
diff --git a/extensions.hpp b/extensions.hpp
new file mode 100644
index 0000000..3897894
--- /dev/null
+++ b/extensions.hpp
@@ -0,0 +1,246 @@
+#pragma once
+
+#include "elog_entry.hpp"
+#include "log_manager.hpp"
+
+#include <functional>
+#include <vector>
+
+namespace phosphor
+{
+namespace logging
+{
+
+/**
+ * @brief The function type that will be called on start up.
+ * @param[in] internal::Manager& - A reference to the Manager class.
+ */
+using StartupFunction = std::function<void(internal::Manager&)>;
+
+using AdditionalDataArg = std::vector<std::string>;
+using AssociationEndpointsArg = std::vector<std::string>;
+/**
+ * @brief The function type that will be called after an event log
+ * is created.
+ * @param[in] const std::string& - The Message property
+ * @param[in] uin32_t - The event log ID
+ * @param[in] uint64_t - The event log timestamp
+ * @param[in] Level - The event level
+ * @param[in] const AdditionalDataArg&) - the additional data
+ * @param[in] const AssociationEndpoints& - Association endpoints (callouts)
+ */
+using CreateFunction = std::function<void(
+ const std::string&, uint32_t, uint64_t, Entry::Level,
+ const AdditionalDataArg&, const AssociationEndpointsArg&)>;
+
+/**
+ * @brief The function type that will be called after an event log is deleted.
+ * @param[in] uint32_t - The event log ID
+ */
+using DeleteFunction = std::function<void(uint32_t)>;
+
+/**
+ * @brief The function type that will to check if an event log is prohibited
+ * from being deleted.
+ * @param[in] uint32_t - The event log ID
+ * @param[out] bool - set to true if the delete is prohibited
+ */
+using DeleteProhibitedFunction = std::function<void(uint32_t, bool&)>;
+
+using StartupFunctions = std::vector<StartupFunction>;
+using CreateFunctions = std::vector<CreateFunction>;
+using DeleteFunctions = std::vector<DeleteFunction>;
+using DeleteProhibitedFunctions = std::vector<DeleteProhibitedFunction>;
+
+/**
+ * @brief Register an extension hook function
+ *
+ * Call this macro at global scope to register a hook to call.
+ * Each hook point has a unique function prototype.
+ */
+#define REGISTER_EXTENSION_FUNCTION(func) \
+ namespace func##_ns \
+ { \
+ Extensions e{func}; \
+ }
+
+/**
+ * @brief Disable default error log capping
+ *
+ * Call this macro at global scope to tell phosphor-logging to disable its
+ * default error log capping algorithm, so that an extension can use its own
+ * instead.
+ */
+#define DISABLE_LOG_ENTRY_CAPS() \
+ namespace disable_caps##_ns \
+ { \
+ Extensions e{Extensions::DefaultErrorCaps::disable}; \
+ }
+
+/**
+ * @class Extensions
+ *
+ * This class manages any error log extensions. Extensions can register
+ * their hook functions with this class with the provided macros so that they
+ * are then able to create their own types of logs based on the native logs.
+ *
+ * The class should only be constructed at a global scope via the macros.
+ */
+class Extensions
+{
+ public:
+ Extensions() = delete;
+ ~Extensions() = default;
+ Extensions(const Extensions&) = delete;
+ Extensions& operator=(const Extensions&) = delete;
+ Extensions(Extensions&&) = delete;
+ Extensions& operator=(Extensions&&) = delete;
+
+ enum class DefaultErrorCaps
+ {
+ disable,
+ enable
+ };
+
+ /**
+ * @brief Constructor to register a startup function
+ *
+ * Functions registered with this contructor will be called
+ * when phosphor-log-manager starts up.
+ *
+ * @param[in] func - The startup function to register
+ */
+ explicit Extensions(StartupFunction func)
+ {
+ startupFunctions.push_back(func);
+ }
+
+ /**
+ * @brief Constructor to register a create function
+ *
+ * Functions registered with this contructor will be called
+ * after phosphor-log-manager creates an event log.
+ *
+ * @param[in] func - The create function to register
+ */
+ explicit Extensions(CreateFunction func)
+ {
+ createFunctions.push_back(func);
+ }
+
+ /**
+ * @brief Constructor to register a delete function
+ *
+ * Functions registered with this contructor will be called
+ * after phosphor-log-manager deletes an event log.
+ *
+ * @param[in] func - The delete function to register
+ */
+ explicit Extensions(DeleteFunction func)
+ {
+ deleteFunctions.push_back(func);
+ }
+
+ /**
+ * @brief Constructor to register a delete prohibition function
+ *
+ * Functions registered with this contructor will be called
+ * before phosphor-log-manager deletes an event log to ensure
+ * deleting the log is allowed.
+ *
+ * @param[in] func - The function to register
+ */
+ explicit Extensions(DeleteProhibitedFunction func)
+ {
+ deleteProhibitedFunctions.push_back(func);
+ }
+
+ /**
+ * @brief Constructor to disable event log capping
+ *
+ * This constructor should only be called by the
+ * DISABLE_LOG_ENTRY_CAPS macro to disable the default
+ * event log capping so that the extension can use their own.
+ *
+ * @param[in] defaultCaps - Enable or disable default capping.
+ */
+ explicit Extensions(DefaultErrorCaps defaultCaps)
+ {
+ defaultErrorCaps = defaultCaps;
+ }
+
+ /**
+ * @brief Returns the Startup functions
+ * @return StartupFunctions - the Startup functions
+ */
+ static StartupFunctions& getStartupFunctions()
+ {
+ return startupFunctions;
+ }
+
+ /**
+ * @brief Returns the Create functions
+ * @return CreateFunctions - the Create functions
+ */
+ static CreateFunctions& getCreateFunctions()
+ {
+ return createFunctions;
+ }
+
+ /**
+ * @brief Returns the Delete functions
+ * @return DeleteFunctions - the Delete functions
+ */
+ static DeleteFunctions& getDeleteFunctions()
+ {
+ return deleteFunctions;
+ }
+
+ /**
+ * @brief Returns the DeleteProhibited functions
+ * @return DeleteProhibitedFunctions - the DeleteProhibited functions
+ */
+ static DeleteProhibitedFunctions& getDeleteProhibitedFunctions()
+ {
+ return deleteProhibitedFunctions;
+ }
+
+ /**
+ * @brief Say if the default log capping policy should be disabled
+ * @return bool - true if it should be disabled
+ */
+ static bool disableDefaultLogCaps()
+ {
+ return defaultErrorCaps == DefaultErrorCaps::disable;
+ }
+
+ private:
+ /**
+ * @brief Vector of functions to call on app startup.
+ */
+ static StartupFunctions startupFunctions;
+
+ /**
+ * @brief Vector of functions to call after creating an event log.
+ */
+ static CreateFunctions createFunctions;
+
+ /**
+ * @brief Vector of functions to call after deleting an event log.
+ */
+ static DeleteFunctions deleteFunctions;
+
+ /**
+ * @brief Vector of functions to call to check if deleting a
+ * particular event log is prohibited.
+ */
+ static DeleteProhibitedFunctions deleteProhibitedFunctions;
+
+ /**
+ * @brief If default log capping should be disabled.
+ */
+ static DefaultErrorCaps defaultErrorCaps;
+};
+
+} // namespace logging
+} // namespace phosphor
diff --git a/extensions/extensions.mk b/extensions/extensions.mk
new file mode 100644
index 0000000..e7648a8
--- /dev/null
+++ b/extensions/extensions.mk
@@ -0,0 +1,3 @@
+if ENABLE_PEL_EXTENSION
+include extensions/openpower-pels/openpower-pels.mk
+endif
diff --git a/extensions/openpower-pels/README.md b/extensions/openpower-pels/README.md
new file mode 100644
index 0000000..bf3e260
--- /dev/null
+++ b/extensions/openpower-pels/README.md
@@ -0,0 +1,101 @@
+# OpenPower Platform Event Log (PEL) extension
+
+This extension will create PELs for every OpenBMC event log. It is also
+possible to point to the raw PEL to use in the OpenBMC event, and then that
+will be used instead of creating one.
+
+## Passing PEL related data within an OpenBMC event log
+
+An error log creator can pass in data that is relevant to a PEL by using
+certain keywords in the AdditionalData property of the event log.
+
+### AdditionalData keywords
+
+#### RAWPEL
+
+This keyword is used to point to an existing PEL in a binary file that should
+be associated with this event log. The syntax is:
+```
+RAWPEL=<path to PEL File>
+e.g.
+RAWPEL="/tmp/pels/pel.5"
+```
+The code will assign its own error log ID to this PEL, and also update the
+commit timestamp field to the current time.
+
+#### ESEL
+
+This keyword's data contains a full PEL in string format. This is how hostboot
+sends down PELs when it is configured in IPMI communication mode. The PEL is
+handled just like the PEL obtained using the RAWPEL keyword.
+
+The syntax is:
+
+```
+ESEL=
+"00 00 df 00 00 00 00 20 00 04 12 01 6f aa 00 00 50 48 00 30 01 00 33 00 00..."
+```
+
+Note that there are 16 bytes of IPMI SEL data before the PEL data starts.
+
+#### _PID
+
+This keyword that contains the application's PID is added automatically by the
+phosphor-logging daemon when the `commit` or `report` APIs are used to create
+an event log, but not when the `Create` D-Bus method is used. If a caller of
+the `Create` API wishes to have their PID captured in the PEL this should be
+used.
+
+This will be added to the PEL in a section of type User Data (UD), along with
+the application name it corresponds to.
+
+The syntax is:
+```
+_PID=<PID of application>
+e.g.
+_PID="12345"
+```
+
+## Default UserData sections for BMC created PELs
+
+The extension code that creates PELs will add these UserData sections to every
+PEL:
+
+- The AdditionalData property contents
+ - If the AdditionalData property in the OpenBMC event log has anything in it,
+ it will be saved in a UserData section as a JSON string.
+
+## The PEL Message Registry
+
+The PEL message registry is used to create PELs from OpenBMC event logs.
+Documentation can be found [here](registry/README.md).
+
+## `Action Flags` and `Event Type` Rules
+
+The `Action Flags` and `Event Type` PEL fields are optional in the message
+registry, and if not present the code will set them based on certain rules
+layed out in the PEL spec. In fact, even if they were specified, the checks
+are still done to ensure consistency across all the logs.
+
+These rules are:
+1. Always set the `Report` flag, unless the `Do Not Report` flag is already on.
+2. Always clear the `SP Call Home` flag, as that feature isn't supported.
+3. If the severity is `Non-error Event`:
+ - Clear the `Service Action` flag.
+ - Clear the `Call Home` flag.
+ - If the `Event Type` field is `Not Applicable`, change it to `Information
+ Only`.
+ - If the `Event Type` field is `Information Only` or `Tracing`, set the
+ `Hidden` flag.
+4. If the severity is `Recovered`:
+ - Set the `Hidden` flag.
+ - Clear the `Service Action` flag.
+ - Clear the `Call Home` flag.
+5. For all other severities:
+ - Clear the `Hidden` flag.
+ - Set the `Service Action` flag.
+ - Set the `Call Home` flag.
+
+Additional rules may be added in the future if necessary.
+
+## D-Bus Interfaces
diff --git a/extensions/openpower-pels/additional_data.hpp b/extensions/openpower-pels/additional_data.hpp
new file mode 100644
index 0000000..ca4a47d
--- /dev/null
+++ b/extensions/openpower-pels/additional_data.hpp
@@ -0,0 +1,132 @@
+#pragma once
+#include <map>
+#include <nlohmann/json.hpp>
+#include <optional>
+#include <string>
+#include <vector>
+
+namespace openpower
+{
+namespace pels
+{
+
+/**
+ * @class AdditionalData
+ *
+ * This class takes in the contents of the AdditionalData OpenBMC
+ * event log property, and provides access to its values based on
+ * their keys.
+ *
+ * The property is a vector of strings of the form: "KEY=VALUE",
+ * and this class provides a getValue("KEY") API that would return
+ * "VALUE".
+ */
+class AdditionalData
+{
+ public:
+ AdditionalData() = default;
+ ~AdditionalData() = default;
+ AdditionalData(const AdditionalData&) = default;
+ AdditionalData& operator=(const AdditionalData&) = default;
+ AdditionalData(AdditionalData&&) = default;
+ AdditionalData& operator=(AdditionalData&&) = default;
+
+ /**
+ * @brief constructor
+ *
+ * @param[in] ad - the AdditionalData property vector with
+ * entries of "KEY=VALUE"
+ */
+ explicit AdditionalData(const std::vector<std::string>& ad)
+ {
+ for (auto& item : ad)
+ {
+ auto pos = item.find_first_of('=');
+ if (pos == std::string::npos || pos == 0)
+ {
+ continue;
+ }
+
+ _data[item.substr(0, pos)] = std::move(item.substr(pos + 1));
+ }
+ }
+
+ /**
+ * @brief Returns the value of the AdditionalData item for the
+ * key passed in.
+ * @param[in] key - the key to search for
+ *
+ * @return optional<string> - the value, if found
+ */
+ std::optional<std::string> getValue(const std::string& key) const
+ {
+ auto entry = _data.find(key);
+ if (entry != _data.end())
+ {
+ return entry->second;
+ }
+ return std::nullopt;
+ }
+
+ /**
+ * @brief Remove a key/value pair from the contained data
+ *
+ * @param[in] key - The key of the entry to remove
+ */
+ void remove(const std::string& key)
+ {
+ _data.erase(key);
+ }
+
+ /**
+ * @brief Says if the object has no data
+ *
+ * @return bool true if the object is empty
+ */
+ inline bool empty() const
+ {
+ return _data.empty();
+ }
+
+ /**
+ * @brief Returns the contained data as a JSON object
+ *
+ * Looks like: {"key1":"value1","key2":"value2"}
+ *
+ * @return json - The JSON object
+ */
+ nlohmann::json toJSON() const
+ {
+ nlohmann::json j = _data;
+ return j;
+ }
+
+ /**
+ * @brief Returns the underlying map of data
+ *
+ * @return const std::map<std::string, std::string>& - The data
+ */
+ const std::map<std::string, std::string>& getData() const
+ {
+ return _data;
+ }
+
+ /**
+ * @brief Adds a key/value pair to the object
+ *
+ * @param[in] key - The key
+ * @param[in] value - The value
+ */
+ void add(const std::string& key, const std::string& value)
+ {
+ _data.emplace(key, value);
+ }
+
+ private:
+ /**
+ * @brief a map of keys to values
+ */
+ std::map<std::string, std::string> _data;
+};
+} // namespace pels
+} // namespace openpower
diff --git a/extensions/openpower-pels/ascii_string.cpp b/extensions/openpower-pels/ascii_string.cpp
new file mode 100644
index 0000000..7d4d4f3
--- /dev/null
+++ b/extensions/openpower-pels/ascii_string.cpp
@@ -0,0 +1,107 @@
+/**
+ * Copyright © 2019 IBM Corporation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#include "ascii_string.hpp"
+
+#include "pel_types.hpp"
+
+#include <phosphor-logging/log.hpp>
+
+namespace openpower
+{
+namespace pels
+{
+namespace src
+{
+
+using namespace phosphor::logging;
+
+AsciiString::AsciiString(Stream& stream)
+{
+ unflatten(stream);
+}
+
+AsciiString::AsciiString(const message::Entry& entry)
+{
+ // Power Error: 1100RRRR
+ // BMC Error: BDSSRRRR
+ // where:
+ // RRRR = reason code
+ // SS = subsystem ID
+
+ // First is type, like 'BD'
+ setByte(0, entry.src.type);
+
+ // Next is '00', or subsystem ID
+ if (entry.src.type == static_cast<uint8_t>(SRCType::powerError))
+ {
+ setByte(2, 0x00);
+ }
+ else // BMC Error
+ {
+ setByte(2, entry.subsystem);
+ }
+
+ // Then the reason code
+ setByte(4, entry.src.reasonCode >> 8);
+ setByte(6, entry.src.reasonCode & 0xFF);
+
+ // Padded with spaces
+ for (size_t offset = 8; offset < asciiStringSize; offset++)
+ {
+ _string[offset] = ' ';
+ }
+}
+
+void AsciiString::flatten(Stream& stream) const
+{
+ stream.write(_string.data(), _string.size());
+}
+
+void AsciiString::unflatten(Stream& stream)
+{
+ stream.read(_string.data(), _string.size());
+
+ // Only allow certain ASCII characters as other entities will
+ // eventually want to display this.
+ std::for_each(_string.begin(), _string.end(), [](auto& c) {
+ if (!isalnum(c) && (c != ' ') && (c != '.') && (c != ':') && (c != '/'))
+ {
+ c = ' ';
+ }
+ });
+}
+
+std::string AsciiString::get() const
+{
+ std::string string{_string.begin(), _string.begin() + _string.size()};
+ return string;
+}
+
+void AsciiString::setByte(size_t byteOffset, uint8_t value)
+{
+ assert(byteOffset < asciiStringSize);
+
+ char characters[3];
+ sprintf(characters, "%02X", value);
+
+ auto writeOffset = byteOffset;
+ _string[writeOffset++] = characters[0];
+ _string[writeOffset] = characters[1];
+}
+
+} // namespace src
+} // namespace pels
+} // namespace openpower
diff --git a/extensions/openpower-pels/ascii_string.hpp b/extensions/openpower-pels/ascii_string.hpp
new file mode 100644
index 0000000..0970df9
--- /dev/null
+++ b/extensions/openpower-pels/ascii_string.hpp
@@ -0,0 +1,103 @@
+#pragma once
+
+#include "registry.hpp"
+#include "stream.hpp"
+
+#include <string>
+
+namespace openpower
+{
+namespace pels
+{
+namespace src
+{
+
+const size_t asciiStringSize = 32;
+
+/**
+ * @class AsciiString
+ *
+ * This represents the ASCII string field in the SRC PEL section.
+ * This 32 character string shows up on the panel on a function 11.
+ *
+ * The first 2 characters are the SRC type, like 'BD' or '11'.
+ * Next is the subsystem, like '8D', if a BD SRC, otherwise '00'.
+ * Next is the reason code, like 'AAAA'.
+ * The rest is filled in with spaces.
+ */
+class AsciiString
+{
+ public:
+ AsciiString() = delete;
+ ~AsciiString() = default;
+ AsciiString(const AsciiString&) = default;
+ AsciiString& operator=(const AsciiString&) = default;
+ AsciiString(AsciiString&&) = default;
+ AsciiString& operator=(AsciiString&&) = default;
+
+ /**
+ * @brief Constructor
+ *
+ * Fills in this class's data fields from the stream.
+ *
+ * @param[in] pel - the PEL data stream
+ */
+ explicit AsciiString(Stream& stream);
+
+ /**
+ * @brief Constructor
+ *
+ * Fills in the class from the registry entry
+ */
+ explicit AsciiString(const message::Entry& entry);
+
+ /**
+ * @brief Flatten the object into the stream
+ *
+ * @param[in] stream - The stream to write to
+ */
+ void flatten(Stream& stream) const;
+
+ /**
+ * @brief Fills in the object from the stream data
+ *
+ * @param[in] stream - The stream to read from
+ */
+ void unflatten(Stream& stream);
+
+ /**
+ * @brief Return the 32 character ASCII string data
+ *
+ * @return std::string - The data
+ */
+ std::string get() const;
+
+ private:
+ /**
+ * @brief Converts a byte of raw data to 2 characters
+ * and writes it to the offset.
+ *
+ * For example, if string is: "AABBCCDD"
+ *
+ * setByte(0, 0x11);
+ * setByte(1, 0x22);
+ * setByte(2, 0x33);
+ * setByte(3, 0x44);
+ *
+ * results in "11223344"
+ *
+ * @param[in] offset - The offset into the ascii string
+ * @param[in] value - The value to write (0x55 -> "55")
+ */
+ void setByte(size_t offset, uint8_t value);
+
+ /**
+ * @brief The ASCII string itself
+ */
+ std::array<char, asciiStringSize> _string;
+};
+
+} // namespace src
+
+} // namespace pels
+} // namespace openpower
diff --git a/extensions/openpower-pels/bcd_time.cpp b/extensions/openpower-pels/bcd_time.cpp
new file mode 100644
index 0000000..c5dd0d8
--- /dev/null
+++ b/extensions/openpower-pels/bcd_time.cpp
@@ -0,0 +1,84 @@
+/**
+ * Copyright © 2019 IBM Corporation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#include "bcd_time.hpp"
+
+namespace openpower
+{
+namespace pels
+{
+
+bool BCDTime::operator==(const BCDTime& right) const
+{
+ return (yearMSB == right.yearMSB) && (yearLSB == right.yearLSB) &&
+ (month == right.month) && (day == right.day) &&
+ (hour == right.hour) && (minutes == right.minutes) &&
+ (seconds == right.seconds) && (hundredths == right.hundredths);
+}
+
+bool BCDTime::operator!=(const BCDTime& right) const
+{
+ return !(*this == right);
+}
+
+BCDTime getBCDTime(std::chrono::time_point<std::chrono::system_clock>& time)
+{
+ BCDTime bcd;
+
+ using namespace std::chrono;
+ time_t t = system_clock::to_time_t(time);
+ tm* localTime = localtime(&t);
+ assert(localTime != nullptr);
+
+ int year = 1900 + localTime->tm_year;
+ bcd.yearMSB = toBCD(year / 100);
+ bcd.yearLSB = toBCD(year % 100);
+ bcd.month = toBCD(localTime->tm_mon + 1);
+ bcd.day = toBCD(localTime->tm_mday);
+ bcd.hour = toBCD(localTime->tm_hour);
+ bcd.minutes = toBCD(localTime->tm_min);
+ bcd.seconds = toBCD(localTime->tm_sec);
+
+ auto ms = duration_cast<milliseconds>(time.time_since_epoch()).count();
+ int hundredths = (ms % 1000) / 10;
+ bcd.hundredths = toBCD(hundredths);
+
+ return bcd;
+}
+
+BCDTime getBCDTime(uint64_t epochMS)
+{
+ std::chrono::milliseconds ms{epochMS};
+ std::chrono::time_point<std::chrono::system_clock> time{ms};
+
+ return getBCDTime(time);
+}
+
+Stream& operator>>(Stream& s, BCDTime& time)
+{
+ s >> time.yearMSB >> time.yearLSB >> time.month >> time.day >> time.hour;
+ s >> time.minutes >> time.seconds >> time.hundredths;
+ return s;
+}
+
+Stream& operator<<(Stream& s, const BCDTime& time)
+{
+ s << time.yearMSB << time.yearLSB << time.month << time.day << time.hour;
+ s << time.minutes << time.seconds << time.hundredths;
+ return s;
+}
+
+} // namespace pels
+} // namespace openpower
diff --git a/extensions/openpower-pels/bcd_time.hpp b/extensions/openpower-pels/bcd_time.hpp
new file mode 100644
index 0000000..3a805a5
--- /dev/null
+++ b/extensions/openpower-pels/bcd_time.hpp
@@ -0,0 +1,111 @@
+#pragma once
+#include "stream.hpp"
+
+#include <chrono>
+
+namespace openpower
+{
+namespace pels
+{
+
+/**
+ * @brief A structure that contains a PEL timestamp in BCD.
+ */
+struct BCDTime
+{
+ uint8_t yearMSB;
+ uint8_t yearLSB;
+ uint8_t month;
+ uint8_t day;
+ uint8_t hour;
+ uint8_t minutes;
+ uint8_t seconds;
+ uint8_t hundredths;
+
+ BCDTime() :
+ yearMSB(0), yearLSB(0), month(0), day(0), hour(0), minutes(0),
+ seconds(0), hundredths(0)
+ {
+ }
+
+ BCDTime(uint8_t yearMSB, uint8_t yearLSB, uint8_t month, uint8_t day,
+ uint8_t hour, uint8_t minutes, uint8_t seconds,
+ uint8_t hundredths) :
+ yearMSB(yearMSB),
+ yearLSB(yearLSB), month(month), day(day), hour(hour), minutes(minutes),
+ seconds(seconds), hundredths(hundredths)
+ {
+ }
+
+ bool operator==(const BCDTime& right) const;
+ bool operator!=(const BCDTime& right) const;
+
+} __attribute__((packed));
+
+/**
+ * @brief Converts a time_point into a BCD time
+ *
+ * @param[in] time - the time_point to convert
+ * @return BCDTime - the BCD time
+ */
+BCDTime getBCDTime(std::chrono::time_point<std::chrono::system_clock>& time);
+
+/**
+ * @brief Converts the number of milliseconds since the epoch into BCD time
+ *
+ * @param[in] milliseconds - Number of milliseconds since the epoch
+ * @return BCDTime - the BCD time
+ */
+BCDTime getBCDTime(uint64_t milliseconds);
+
+/**
+ * @brief Converts a number to a BCD.
+ *
+ * For example 32 -> 0x32.
+ *
+ * Source: PLDM repository
+ *
+ * @param[in] value - the value to convert.
+ *
+ * @return T - the BCD value
+ */
+template <typename T>
+T toBCD(T decimal)
+{
+ T bcd = 0;
+ T remainder = 0;
+ auto count = 0;
+
+ while (decimal)
+ {
+ remainder = decimal % 10;
+ bcd = bcd + (remainder << count);
+ decimal = decimal / 10;
+ count += 4;
+ }
+
+ return bcd;
+}
+
+/**
+ * @brief Stream extraction operator for BCDTime
+ *
+ * @param[in] s - the Stream
+ * @param[out] time - the BCD time
+ *
+ * @return Stream&
+ */
+Stream& operator>>(Stream& s, BCDTime& time);
+
+/**
+ * @brief Stream insertion operator for BCDTime
+ *
+ * @param[in/out] s - the Stream
+ * @param[in] time - the BCD time
+ *
+ * @return Stream&
+ */
+Stream& operator<<(Stream& s, const BCDTime& time);
+
+} // namespace pels
+} // namespace openpower
diff --git a/extensions/openpower-pels/callout.cpp b/extensions/openpower-pels/callout.cpp
new file mode 100644
index 0000000..95c6408
--- /dev/null
+++ b/extensions/openpower-pels/callout.cpp
@@ -0,0 +1,117 @@
+/**
+ * Copyright © 2019 IBM Corporation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#include "callout.hpp"
+
+#include <phosphor-logging/log.hpp>
+
+namespace openpower
+{
+namespace pels
+{
+namespace src
+{
+
+using namespace phosphor::logging;
+
+Callout::Callout(Stream& pel)
+{
+ pel >> _size >> _flags >> _priority >> _locationCodeSize;
+
+ if (_locationCodeSize)
+ {
+ _locationCode.resize(_locationCodeSize);
+ pel >> _locationCode;
+ }
+
+ size_t currentSize = 4 + _locationCodeSize;
+
+ // Read in the substructures until the end of this structure.
+ // Any stream overflows will throw an exception up to the SRC constructor
+ while (_size > currentSize)
+ {
+ // Peek the type
+ uint16_t type = 0;
+ pel >> type;
+ pel.offset(pel.offset() - 2);
+
+ switch (type)
+ {
+ case FRUIdentity::substructureType:
+ {
+ _fruIdentity = std::make_unique<FRUIdentity>(pel);
+ currentSize += _fruIdentity->flattenedSize();
+ break;
+ }
+ case PCEIdentity::substructureType:
+ {
+ _pceIdentity = std::make_unique<PCEIdentity>(pel);
+ currentSize += _pceIdentity->flattenedSize();
+ break;
+ }
+ case MRU::substructureType:
+ {
+ _mru = std::make_unique<MRU>(pel);
+ currentSize += _mru->flattenedSize();
+ break;
+ }
+ default:
+ log<level::ERR>("Invalid Callout subsection type",
+ entry("CALLOUT_TYPE=0x%X", type));
+ throw std::runtime_error("Invalid Callout subsection type");
+ break;
+ }
+ }
+}
+
+size_t Callout::flattenedSize()
+{
+ size_t size = sizeof(_size) + sizeof(_flags) + sizeof(_priority) +
+ sizeof(_locationCodeSize) + _locationCodeSize;
+
+ size += _fruIdentity ? _fruIdentity->flattenedSize() : 0;
+ size += _pceIdentity ? _pceIdentity->flattenedSize() : 0;
+ size += _mru ? _mru->flattenedSize() : 0;
+
+ return size;
+}
+
+void Callout::flatten(Stream& pel) const
+{
+ pel << _size << _flags << _priority << _locationCodeSize;
+
+ if (_locationCodeSize)
+ {
+ pel << _locationCode;
+ }
+
+ if (_fruIdentity)
+ {
+ _fruIdentity->flatten(pel);
+ }
+
+ if (_pceIdentity)
+ {
+ _pceIdentity->flatten(pel);
+ }
+ if (_mru)
+ {
+ _mru->flatten(pel);
+ }
+}
+
+} // namespace src
+} // namespace pels
+} // namespace openpower
diff --git a/extensions/openpower-pels/callout.hpp b/extensions/openpower-pels/callout.hpp
new file mode 100644
index 0000000..caf3aec
--- /dev/null
+++ b/extensions/openpower-pels/callout.hpp
@@ -0,0 +1,183 @@
+#pragma once
+
+#include "fru_identity.hpp"
+#include "mru.hpp"
+#include "pce_identity.hpp"
+#include "stream.hpp"
+
+namespace openpower
+{
+namespace pels
+{
+namespace src
+{
+
+/**
+ * @class Callout
+ *
+ * Represents a single FRU callout in the SRC's FRU callout
+ * subsection.
+ *
+ * The 'Callouts' class holds a list of these objects.
+ *
+ * The callout priority and location code are in this structure.
+ *
+ * There can also be up to one each of three types of substructures
+ * in a single callout:
+ * * FRU Identity (must be first if present)
+ * * Power Controlling Enclosure (PCE)
+ * * Manufacturing Replaceable Unit (MRU)
+ *
+ * These substructures have their own objects managed by unique_ptrs
+ * which will only be allocated if those substructures exist.
+ */
+class Callout
+{
+ public:
+ Callout() = delete;
+ ~Callout() = default;
+ Callout(const Callout&) = delete;
+ Callout& operator=(const Callout&) = delete;
+ Callout(Callout&&) = delete;
+ Callout& operator=(Callout&&) = delete;
+
+ /**
+ * @brief Constructor
+ *
+ * Fills in this class's data fields from the stream.
+ *
+ * @param[in] pel - the PEL data stream
+ */
+ explicit Callout(Stream& pel);
+
+ /**
+ * @brief Returns the size of this object when flattened into a PEL
+ *
+ * @return size_t - The size of the section
+ */
+ size_t flattenedSize();
+
+ /**
+ * @brief Flatten the object into the stream
+ *
+ * @param[in] stream - The stream to write to
+ */
+ void flatten(Stream& pel) const;
+
+ /**
+ * @brief Returns the flags field of a callout
+ *
+ * @return uint8_t - The flags
+ */
+ uint8_t flags() const
+ {
+ return _flags;
+ }
+
+ /**
+ * @brief Returns the priority field of a callout
+ *
+ * @return uint8_t - The priority
+ */
+ uint8_t priority() const
+ {
+ return _priority;
+ }
+
+ /**
+ * @brief Returns the location code of the callout
+ *
+ * @return std::string - The location code
+ */
+ std::string locationCode() const
+ {
+ std::string lc;
+ if (!_locationCode.empty())
+ {
+ // NULL terminated
+ lc = static_cast<const char*>(_locationCode.data());
+ }
+ return lc;
+ }
+
+ /**
+ * @brief Returns the FRU identity substructure
+ *
+ * @return const std::unique_ptr<FRUIdentity>&
+ */
+ const std::unique_ptr<FRUIdentity>& fruIdentity() const
+ {
+ return _fruIdentity;
+ }
+
+ /**
+ * @brief Returns the PCE identity substructure
+ *
+ * @return const std::unique_ptr<PCEIdentity>&
+ */
+ const std::unique_ptr<PCEIdentity>& pceIdentity() const
+ {
+ return _pceIdentity;
+ }
+
+ /**
+ * @brief Returns the MRU identity substructure
+ *
+ * @return const std::unique_ptr<FRUIdentity>&
+ */
+ const std::unique_ptr<MRU>& mru() const
+ {
+ return _mru;
+ }
+
+ private:
+ /**
+ * @brief The size of this structure in the PEL
+ */
+ uint8_t _size;
+
+ /**
+ * @brief The flags byte of this structure
+ */
+ uint8_t _flags;
+
+ /**
+ * @brief The replacement priority
+ */
+ uint8_t _priority;
+
+ /**
+ * @brief The length of the location code field.
+ *
+ * Includes the NULL termination, and must be a
+ * multiple of 4 (padded with zeros)
+ */
+ uint8_t _locationCodeSize;
+
+ /**
+ * @brief NULL terminated location code
+ *
+ * Includes the NULL termination, and must be a
+ * multiple of 4 (padded with zeros)
+ */
+ std::vector<char> _locationCode;
+
+ /**
+ * @brief FRU (Field Replaceable Unit) Identity substructure
+ */
+ std::unique_ptr<FRUIdentity> _fruIdentity;
+
+ /**
+ * @brief PCE (Power Controlling Enclosure) Identity substructure
+ */
+ std::unique_ptr<PCEIdentity> _pceIdentity;
+
+ /**
+ * @brief MRU (Manufacturing Replaceable Unit) substructure
+ */
+ std::unique_ptr<MRU> _mru;
+};
+
+} // namespace src
+} // namespace pels
+} // namespace openpower
diff --git a/extensions/openpower-pels/callouts.cpp b/extensions/openpower-pels/callouts.cpp
new file mode 100644
index 0000000..17f5bdd
--- /dev/null
+++ b/extensions/openpower-pels/callouts.cpp
@@ -0,0 +1,51 @@
+/**
+ * Copyright © 2019 IBM Corporation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#include "callouts.hpp"
+
+namespace openpower
+{
+namespace pels
+{
+namespace src
+{
+
+Callouts::Callouts(Stream& pel)
+{
+ pel >> _subsectionID >> _subsectionFlags >> _subsectionWordLength;
+
+ size_t currentLength = sizeof(_subsectionID) + sizeof(_subsectionFlags) +
+ sizeof(_subsectionWordLength);
+
+ while ((_subsectionWordLength * 4) > currentLength)
+ {
+ _callouts.emplace_back(new Callout(pel));
+ currentLength += _callouts.back()->flattenedSize();
+ }
+}
+
+void Callouts::flatten(Stream& pel) const
+{
+ pel << _subsectionID << _subsectionFlags << _subsectionWordLength;
+
+ for (auto& callout : _callouts)
+ {
+ callout->flatten(pel);
+ }
+}
+
+} // namespace src
+} // namespace pels
+} // namespace openpower
diff --git a/extensions/openpower-pels/callouts.hpp b/extensions/openpower-pels/callouts.hpp
new file mode 100644
index 0000000..9226a1b
--- /dev/null
+++ b/extensions/openpower-pels/callouts.hpp
@@ -0,0 +1,96 @@
+#pragma once
+
+#include "callout.hpp"
+#include "stream.hpp"
+
+namespace openpower
+{
+namespace pels
+{
+namespace src
+{
+
+/**
+ * @class Callouts
+ *
+ * This is an optional subsection of the SRC section in a PEL
+ * that holds callouts (represented as Callout objects).
+ * It is at the end of the SRC section, and there can only be one
+ * of these present in the SRC.
+ *
+ * If an SRC doesn't have any callouts, this object won't be created.
+ */
+class Callouts
+{
+ public:
+ Callouts() = default;
+ ~Callouts() = default;
+ Callouts(const Callouts&) = delete;
+ Callouts& operator=(const Callouts&) = delete;
+ Callouts(Callouts&&) = delete;
+ Callouts& operator=(Callouts&&) = delete;
+
+ /**
+ * @brief Constructor
+ *
+ * Fills in this class's data fields from the stream.
+ *
+ * @param[in] pel - the PEL data stream
+ */
+ explicit Callouts(Stream& pel);
+
+ /**
+ * @brief Flatten the object into the stream
+ *
+ * @param[in] stream - The stream to write to
+ */
+ void flatten(Stream& pel) const;
+
+ /**
+ * @brief Returns the size of this object when flattened into a PEL
+ *
+ * @return size_t - The size of the section
+ */
+ size_t flattenedSize()
+ {
+ return _subsectionWordLength * 4;
+ }
+
+ /**
+ * @brief Returns the contained callouts
+ *
+ * @return const std::vector<std::unique_ptr<Callout>>&
+ */
+ const std::vector<std::unique_ptr<Callout>>& callouts() const
+ {
+ return _callouts;
+ }
+
+ private:
+ /**
+ * @brief The ID of this subsection, which is 0xC0.
+ */
+ uint8_t _subsectionID;
+
+ /**
+ * @brief Subsection flags. Always 0.
+ */
+ uint8_t _subsectionFlags;
+
+ /**
+ * @brief Subsection length in 4B words.
+ *
+ * (Subsection is always a multiple of 4B)
+ */
+ uint16_t _subsectionWordLength;
+
+ /**
+ * @brief The contained callouts
+ */
+ std::vector<std::unique_ptr<Callout>> _callouts;
+};
+
+} // namespace src
+
+} // namespace pels
+} // namespace openpower
diff --git a/extensions/openpower-pels/data_interface.cpp b/extensions/openpower-pels/data_interface.cpp
new file mode 100644
index 0000000..3342569
--- /dev/null
+++ b/extensions/openpower-pels/data_interface.cpp
@@ -0,0 +1,290 @@
+/**
+ * Copyright © 2019 IBM Corporation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#include "config.h"
+
+#include "data_interface.hpp"
+
+#include <fstream>
+#include <xyz/openbmc_project/State/OperatingSystem/Status/server.hpp>
+
+namespace openpower
+{
+namespace pels
+{
+
+namespace service_name
+{
+constexpr auto objectMapper = "xyz.openbmc_project.ObjectMapper";
+} // namespace service_name
+
+namespace object_path
+{
+constexpr auto objectMapper = "/xyz/openbmc_project/object_mapper";
+constexpr auto systemInv = "/xyz/openbmc_project/inventory/system";
+constexpr auto hostState = "/xyz/openbmc_project/state/host0";
+constexpr auto pldm = "/xyz/openbmc_project/pldm";
+} // namespace object_path
+
+namespace interface
+{
+constexpr auto dbusProperty = "org.freedesktop.DBus.Properties";
+constexpr auto objectMapper = "xyz.openbmc_project.ObjectMapper";
+constexpr auto invAsset = "xyz.openbmc_project.Inventory.Decorator.Asset";
+constexpr auto osStatus = "xyz.openbmc_project.State.OperatingSystem.Status";
+constexpr auto pldmRequester = "xyz.openbmc_project.PLDM.Requester";
+} // namespace interface
+
+using namespace sdbusplus::xyz::openbmc_project::State::OperatingSystem::server;
+
+DataInterface::DataInterface(sdbusplus::bus::bus& bus) : _bus(bus)
+{
+ readMTMS();
+ readHostState();
+ readBMCFWVersion();
+ readServerFWVersion();
+ readBMCFWVersionID();
+}
+
+void DataInterface::readMTMS()
+{
+ // If this runs when the inventory service isn't running, it will get the
+ // value whenever it starts via the propertiesChanged callback.
+ try
+ {
+ auto inventoryService =
+ getService(object_path::systemInv, interface::invAsset);
+
+ if (!inventoryService.empty())
+ {
+ auto properties = getAllProperties(
+ inventoryService, object_path::systemInv, interface::invAsset);
+
+ _machineTypeModel = std::get<std::string>(properties["Model"]);
+
+ _machineSerialNumber =
+ std::get<std::string>(properties["SerialNumber"]);
+ }
+ }
+ catch (std::exception& e)
+ {
+ // Inventory must not be running at this moment.
+ }
+
+ // Keep up to date by watching for the propertiesChanged signal.
+ _sysInventoryPropMatch = std::make_unique<sdbusplus::bus::match_t>(
+ _bus,
+ sdbusplus::bus::match::rules::propertiesChanged(object_path::systemInv,
+ interface::invAsset),
+ std::bind(std::mem_fn(&DataInterface::sysAssetPropChanged), this,
+ std::placeholders::_1));
+}
+
+void DataInterface::sysAssetPropChanged(sdbusplus::message::message& msg)
+{
+ DBusInterface interface;
+ DBusPropertyMap properties;
+
+ msg.read(interface, properties);
+
+ auto model = properties.find("Model");
+ if (model != properties.end())
+ {
+ _machineTypeModel = std::get<std::string>(model->second);
+ }
+
+ auto sn = properties.find("SerialNumber");
+ if (sn != properties.end())
+ {
+ _machineSerialNumber = std::get<std::string>(sn->second);
+ }
+}
+
+DBusPropertyMap DataInterface::getAllProperties(const std::string& service,
+ const std::string& objectPath,
+ const std::string& interface)
+{
+ DBusPropertyMap properties;
+
+ auto method = _bus.new_method_call(service.c_str(), objectPath.c_str(),
+ interface::dbusProperty, "GetAll");
+ method.append(interface);
+ auto reply = _bus.call(method);
+
+ reply.read(properties);
+
+ return properties;
+}
+
+void DataInterface::getProperty(const std::string& service,
+ const std::string& objectPath,
+ const std::string& interface,
+ const std::string& property, DBusValue& value)
+{
+
+ auto method = _bus.new_method_call(service.c_str(), objectPath.c_str(),
+ interface::dbusProperty, "Get");
+ method.append(interface, property);
+ auto reply = _bus.call(method);
+
+ reply.read(value);
+}
+
+DBusService DataInterface::getService(const std::string& objectPath,
+ const std::string& interface) const
+{
+ auto method = _bus.new_method_call(service_name::objectMapper,
+ object_path::objectMapper,
+ interface::objectMapper, "GetObject");
+
+ method.append(objectPath, std::vector<std::string>({interface}));
+
+ auto reply = _bus.call(method);
+
+ std::map<DBusService, DBusInterfaceList> response;
+ reply.read(response);
+
+ if (!response.empty())
+ {
+ return response.begin()->first;
+ }
+
+ return std::string{};
+}
+
+void DataInterface::readHostState()
+{
+ _hostUp = false;
+
+ try
+ {
+ auto service = getService(object_path::hostState, interface::osStatus);
+ if (!service.empty())
+ {
+ DBusValue value;
+ getProperty(service, object_path::hostState, interface::osStatus,
+ "OperatingSystemState", value);
+
+ auto status =
+ Status::convertOSStatusFromString(std::get<std::string>(value));
+
+ if ((status == Status::OSStatus::BootComplete) ||
+ (status == Status::OSStatus::Standby))
+ {
+ _hostUp = true;
+ }
+ }
+ }
+ catch (std::exception& e)
+ {
+ // Not available yet.
+ }
+
+ // Keep up to date by watching for the propertiesChanged signal.
+ _osStateMatch = std::make_unique<sdbusplus::bus::match_t>(
+ _bus,
+ sdbusplus::bus::match::rules::propertiesChanged(object_path::hostState,
+ interface::osStatus),
+ std::bind(std::mem_fn(&DataInterface::osStatePropChanged), this,
+ std::placeholders::_1));
+}
+
+void DataInterface::osStatePropChanged(sdbusplus::message::message& msg)
+{
+ DBusInterface interface;
+ DBusPropertyMap properties;
+
+ msg.read(interface, properties);
+
+ auto state = properties.find("OperatingSystemState");
+ if (state != properties.end())
+ {
+ auto status = Status::convertOSStatusFromString(
+ std::get<std::string>(state->second));
+
+ bool newHostState = false;
+ if ((status == Status::OSStatus::BootComplete) ||
+ (status == Status::OSStatus::Standby))
+ {
+ newHostState = true;
+ }
+
+ setHostState(newHostState);
+ }
+}
+
+uint8_t DataInterface::getPLDMInstanceID(uint8_t eid) const
+{
+ return 0;
+// Don't use until PLDM switches to async D-Bus.
+#if 0
+ auto service = getService(object_path::pldm, interface::pldmRequester);
+
+ auto method =
+ _bus.new_method_call(service.c_str(), object_path::pldm,
+ interface::pldmRequester, "GetInstanceId");
+ method.append(eid);
+ auto reply = _bus.call(method);
+
+ uint8_t instanceID = 0;
+ reply.read(instanceID);
+
+ return instanceID;
+#endif
+}
+
+/**
+ * @brief Return a value found in the /etc/os-release file
+ *
+ * @param[in] key - The key name, like "VERSION"
+ *
+ * @return std::optional<std::string> - The value
+ */
+std::optional<std::string> getOSReleaseValue(const std::string& key)
+{
+ std::ifstream versionFile{BMC_VERSION_FILE};
+ std::string line;
+ std::string keyPattern{key + '='};
+
+ while (std::getline(versionFile, line))
+ {
+ if (line.find(keyPattern) != std::string::npos)
+ {
+ auto pos = line.find_first_of('"') + 1;
+ auto value = line.substr(pos, line.find_last_of('"') - pos);
+ return value;
+ }
+ }
+
+ return std::nullopt;
+}
+
+void DataInterface::readBMCFWVersion()
+{
+ _bmcFWVersion = getOSReleaseValue("VERSION").value_or("");
+}
+
+void DataInterface::readServerFWVersion()
+{
+ // Not available yet
+}
+
+void DataInterface::readBMCFWVersionID()
+{
+ _bmcFWVersionID = getOSReleaseValue("VERSION_ID").value_or("");
+}
+
+} // namespace pels
+} // namespace openpower
diff --git a/extensions/openpower-pels/data_interface.hpp b/extensions/openpower-pels/data_interface.hpp
new file mode 100644
index 0000000..e139c07
--- /dev/null
+++ b/extensions/openpower-pels/data_interface.hpp
@@ -0,0 +1,382 @@
+#pragma once
+
+#include <filesystem>
+#include <phosphor-logging/log.hpp>
+#include <sdbusplus/bus.hpp>
+#include <sdbusplus/bus/match.hpp>
+
+namespace openpower
+{
+namespace pels
+{
+
+using DBusValue = sdbusplus::message::variant<std::string>;
+using DBusProperty = std::string;
+using DBusInterface = std::string;
+using DBusService = std::string;
+using DBusPath = std::string;
+using DBusInterfaceList = std::vector<DBusInterface>;
+using DBusPropertyMap = std::map<DBusProperty, DBusValue>;
+
+/**
+ * @class DataInterface
+ *
+ * A base class for gathering data about the system for use
+ * in PELs. Implemented this way to facilitate mocking.
+ */
+class DataInterfaceBase
+{
+ public:
+ DataInterfaceBase() = default;
+ virtual ~DataInterfaceBase() = default;
+ DataInterfaceBase(const DataInterfaceBase&) = default;
+ DataInterfaceBase& operator=(const DataInterfaceBase&) = default;
+ DataInterfaceBase(DataInterfaceBase&&) = default;
+ DataInterfaceBase& operator=(DataInterfaceBase&&) = default;
+
+ /**
+ * @brief Returns the machine Type/Model
+ *
+ * @return string - The machine Type/Model string
+ */
+ virtual std::string getMachineTypeModel() const
+ {
+ return _machineTypeModel;
+ }
+
+ /**
+ * @brief Returns the machine serial number
+ *
+ * @return string - The machine serial number
+ */
+ virtual std::string getMachineSerialNumber() const
+ {
+ return _machineSerialNumber;
+ }
+
+ /**
+ * @brief Says if the system is managed by a hardware
+ * management console.
+ * @return bool - If the system is HMC managed
+ */
+ virtual bool isHMCManaged() const
+ {
+ return _hmcManaged;
+ }
+
+ /**
+ * @brief Says if the host is up and running
+ *
+ * @return bool - If the host is running
+ */
+ virtual bool isHostUp() const
+ {
+ return _hostUp;
+ }
+
+ /**
+ * @brief Returns the PLDM instance ID to use for PLDM commands
+ *
+ * The base class implementation just returns zero.
+ *
+ * @param[in] eid - The PLDM EID
+ *
+ * @return uint8_t - The instance ID
+ */
+ virtual uint8_t getPLDMInstanceID(uint8_t eid) const
+ {
+ return 0;
+ }
+
+ using HostStateChangeFunc = std::function<void(bool)>;
+
+ /**
+ * @brief Register a callback function that will get
+ * called on all host on/off transitions.
+ *
+ * The void(bool) function will get passed the new
+ * value of the host state.
+ *
+ * @param[in] name - The subscription name
+ * @param[in] func - The function to run
+ */
+ void subscribeToHostStateChange(const std::string& name,
+ HostStateChangeFunc func)
+ {
+ _hostChangeCallbacks[name] = func;
+ }
+
+ /**
+ * @brief Unsubscribe from host state changes.
+ *
+ * @param[in] name - The subscription name
+ */
+ void unsubscribeFromHostStateChange(const std::string& name)
+ {
+ _hostChangeCallbacks.erase(name);
+ }
+
+ /**
+ * @brief Returns the BMC firmware version
+ *
+ * @return std::string - The BMC version
+ */
+ virtual std::string getBMCFWVersion() const
+ {
+ return _bmcFWVersion;
+ }
+
+ /**
+ * @brief Returns the server firmware version
+ *
+ * @return std::string - The server firmware version
+ */
+ virtual std::string getServerFWVersion() const
+ {
+ return _serverFWVersion;
+ }
+
+ /**
+ * @brief Returns the BMC FW version ID
+ *
+ * @return std::string - The BMC FW version ID
+ */
+ virtual std::string getBMCFWVersionID() const
+ {
+ return _bmcFWVersionID;
+ }
+
+ /**
+ * @brief Returns the process name given its PID.
+ *
+ * @param[in] pid - The PID value as a string
+ *
+ * @return std::optional<std::string> - The name, or std::nullopt
+ */
+ std::optional<std::string> getProcessName(const std::string& pid) const
+ {
+ namespace fs = std::filesystem;
+
+ fs::path path{"/proc"};
+ path /= fs::path{pid} / "exe";
+
+ if (fs::exists(path))
+ {
+ return fs::read_symlink(path);
+ }
+
+ return std::nullopt;
+ }
+
+ protected:
+ /**
+ * @brief Sets the host on/off state and runs any
+ * callback functions (if there was a change).
+ */
+ void setHostState(bool newState)
+ {
+ if (_hostUp != newState)
+ {
+ _hostUp = newState;
+
+ for (auto& [name, func] : _hostChangeCallbacks)
+ {
+ try
+ {
+ func(_hostUp);
+ }
+ catch (std::exception& e)
+ {
+ using namespace phosphor::logging;
+ log<level::ERR>("A host state change callback threw "
+ "an exception");
+ }
+ }
+ }
+ }
+
+ /**
+ * @brief The machine type-model. Always kept up to date
+ */
+ std::string _machineTypeModel;
+
+ /**
+ * @brief The machine serial number. Always kept up to date
+ */
+ std::string _machineSerialNumber;
+
+ /**
+ * @brief The hardware management console status. Always kept
+ * up to date.
+ */
+ bool _hmcManaged = false;
+
+ /**
+ * @brief The host up status. Always kept up to date.
+ */
+ bool _hostUp = false;
+
+ /**
+ * @brief The map of host state change subscriber
+ * names to callback functions.
+ */
+ std::map<std::string, HostStateChangeFunc> _hostChangeCallbacks;
+
+ /**
+ * @brief The BMC firmware version string
+ */
+ std::string _bmcFWVersion;
+
+ /**
+ * @brief The server firmware version string
+ */
+ std::string _serverFWVersion;
+
+ /**
+ * @brief The BMC firmware version ID string
+ */
+ std::string _bmcFWVersionID;
+};
+
+/**
+ * @class DataInterface
+ *
+ * Concrete implementation of DataInterfaceBase.
+ */
+class DataInterface : public DataInterfaceBase
+{
+ public:
+ DataInterface() = delete;
+ ~DataInterface() = default;
+ DataInterface(const DataInterface&) = default;
+ DataInterface& operator=(const DataInterface&) = default;
+ DataInterface(DataInterface&&) = default;
+ DataInterface& operator=(DataInterface&&) = default;
+
+ /**
+ * @brief Constructor
+ *
+ * @param[in] bus - The sdbusplus bus object
+ */
+ explicit DataInterface(sdbusplus::bus::bus& bus);
+
+ /**
+ * @brief Returns the PLDM instance ID to use for PLDM commands
+ *
+ * @param[in] eid - The PLDM EID
+ *
+ * @return uint8_t - The instance ID
+ */
+ uint8_t getPLDMInstanceID(uint8_t eid) const override;
+
+ private:
+ /**
+ * @brief Reads the machine type/model and SN from D-Bus.
+ *
+ * Looks for them on the 'system' inventory object, and also
+ * places a properties changed watch on them to obtain any changes
+ * (or read them for the first time if the inventory isn't ready
+ * when this function runs.)
+ */
+ void readMTMS();
+
+ /**
+ * @brief Reads the host state from D-Bus.
+ *
+ * For host on, looks for the values of 'BootComplete' or 'Standby'
+ * in the OperatingSystemState property on the
+ * 'xyz.openbmc_project.State.OperatingSystem.Status' interface
+ * on the '/xyz/openbmc_project/state/host0' path.
+ *
+ * Also adds a properties changed watch on it so the code can be
+ * kept up to date on changes.
+ */
+ void readHostState();
+
+ /**
+ * @brief Reads the BMC firmware version string and puts it into
+ * _bmcFWVersion.
+ */
+ void readBMCFWVersion();
+
+ /**
+ * @brief Reads the server firmware version string and puts it into
+ * _serverFWVersion.
+ */
+ void readServerFWVersion();
+
+ /**
+ * @brief Reads the BMC firmware version ID and puts it into
+ * _bmcFWVersionID.
+ */
+ void readBMCFWVersionID();
+
+ /**
+ * @brief Finds the D-Bus service name that hosts the
+ * passed in path and interface.
+ *
+ * @param[in] objectPath - The D-Bus object path
+ * @param[in] interface - The D-Bus interface
+ */
+ DBusService getService(const std::string& objectPath,
+ const std::string& interface) const;
+ /**
+ * @brief Wrapper for the 'GetAll' properties method call
+ *
+ * @param[in] service - The D-Bus service to call it on
+ * @param[in] objectPath - The D-Bus object path
+ * @param[in] interface - The interface to get the props on
+ *
+ * @return DBusPropertyMap - The property results
+ */
+ DBusPropertyMap getAllProperties(const std::string& service,
+ const std::string& objectPath,
+ const std::string& interface);
+
+ /**
+ * @brief Wrapper for the 'Get' properties method call
+ *
+ * @param[in] service - The D-Bus service to call it on
+ * @param[in] objectPath - The D-Bus object path
+ * @param[in] interface - The interface to get the property on
+ * @param[in] property - The property name
+ * @param[out] value - Filled in with the property value.
+ */
+ void getProperty(const std::string& service, const std::string& objectPath,
+ const std::string& interface, const std::string& property,
+ DBusValue& value);
+
+ /**
+ * @brief The properties changed callback for the Asset iface
+ * on the system inventory object.
+ *
+ * @param[in] msg - The sdbusplus message of the signal
+ */
+ void sysAssetPropChanged(sdbusplus::message::message& msg);
+
+ /**
+ * @brief The properties changed callback for the OperatingSystemStatus
+ * interface on the host state object.
+ *
+ * @param[in] msg - The sdbusplus message of the signal
+ */
+ void osStatePropChanged(sdbusplus::message::message& msg);
+
+ /**
+ * @brief The match object for the system path's properties
+ */
+ std::unique_ptr<sdbusplus::bus::match_t> _sysInventoryPropMatch;
+
+ /**
+ * @brief The match object for the operating system status.
+ */
+ std::unique_ptr<sdbusplus::bus::match_t> _osStateMatch;
+
+ /**
+ * @brief The sdbusplus bus object for making D-Bus calls.
+ */
+ sdbusplus::bus::bus& _bus;
+};
+
+} // namespace pels
+} // namespace openpower
diff --git a/extensions/openpower-pels/entry_points.cpp b/extensions/openpower-pels/entry_points.cpp
new file mode 100644
index 0000000..f5ef40c
--- /dev/null
+++ b/extensions/openpower-pels/entry_points.cpp
@@ -0,0 +1,80 @@
+/**
+ * Copyright © 2019 IBM Corporation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#include "data_interface.hpp"
+#include "elog_entry.hpp"
+#include "event_logger.hpp"
+#include "extensions.hpp"
+#include "manager.hpp"
+#include "pldm_interface.hpp"
+
+namespace openpower
+{
+namespace pels
+{
+
+using namespace phosphor::logging;
+
+std::unique_ptr<Manager> manager;
+
+void pelStartup(internal::Manager& logManager)
+{
+ EventLogger::LogFunction logger = std::bind(
+ std::mem_fn(&internal::Manager::create), &logManager,
+ std::placeholders::_1, std::placeholders::_2, std::placeholders::_3);
+
+ std::unique_ptr<DataInterfaceBase> dataIface =
+ std::make_unique<DataInterface>(logManager.getBus());
+
+#ifndef DONT_SEND_PELS_TO_HOST
+ std::unique_ptr<HostInterface> hostIface = std::make_unique<PLDMInterface>(
+ logManager.getBus().get_event(), *(dataIface.get()));
+
+ manager =
+ std::make_unique<Manager>(logManager, std::move(dataIface),
+ std::move(logger), std::move(hostIface));
+#else
+ manager = std::make_unique<Manager>(logManager, std::move(dataIface),
+ std::move(logger));
+#endif
+}
+
+REGISTER_EXTENSION_FUNCTION(pelStartup);
+
+void pelCreate(const std::string& message, uint32_t id, uint64_t timestamp,
+ Entry::Level severity, const AdditionalDataArg& additionalData,
+ const AssociationEndpointsArg& assocs)
+{
+ manager->create(message, id, timestamp, severity, additionalData, assocs);
+}
+
+REGISTER_EXTENSION_FUNCTION(pelCreate);
+
+void pelDelete(uint32_t id)
+{
+ return manager->erase(id);
+}
+
+REGISTER_EXTENSION_FUNCTION(pelDelete);
+
+void pelDeleteProhibited(uint32_t id, bool& prohibited)
+{
+ prohibited = manager->isDeleteProhibited(id);
+}
+
+REGISTER_EXTENSION_FUNCTION(pelDeleteProhibited);
+
+} // namespace pels
+} // namespace openpower
diff --git a/extensions/openpower-pels/event_logger.hpp b/extensions/openpower-pels/event_logger.hpp
new file mode 100644
index 0000000..b4df0d4
--- /dev/null
+++ b/extensions/openpower-pels/event_logger.hpp
@@ -0,0 +1,187 @@
+#pragma once
+
+#include "additional_data.hpp"
+#include "elog_entry.hpp"
+
+#include <phosphor-logging/log.hpp>
+#include <queue>
+#include <sdeventplus/event.hpp>
+#include <sdeventplus/source/event.hpp>
+#include <tuple>
+
+namespace openpower::pels
+{
+
+/**
+ * @class EventLogger
+ *
+ * This class handles creating OpenBMC event logs (and thus PELs) from
+ * within the PEL extension code.
+ *
+ * The function to actually create the event log is passed in via the
+ * constructor so that different functions can be used when testing.
+ *
+ * To create the event log, call log() with the appropriate arguments
+ * and the log will be created as soon as the flow gets back to the event
+ * loop. If the queue isn't empty after a log is created, the next
+ * one will be scheduled to be created from the event loop again.
+ *
+ * This class does not allow new events to be added while inside the
+ * creation function, because if the code added an event log every time
+ * it tried to create one, it would do so infinitely.
+ */
+class EventLogger
+{
+ public:
+ using ADMap = std::map<std::string, std::string>;
+ using LogFunction = std::function<void(
+ const std::string&, phosphor::logging::Entry::Level, const ADMap&)>;
+
+ static constexpr size_t msgPos = 0;
+ static constexpr size_t levelPos = 1;
+ static constexpr size_t adPos = 2;
+ using EventEntry = std::tuple<std::string, phosphor::logging::Entry::Level,
+ AdditionalData>;
+
+ EventLogger() = delete;
+ ~EventLogger() = default;
+ EventLogger(const EventLogger&) = delete;
+ EventLogger& operator=(const EventLogger&) = delete;
+ EventLogger(EventLogger&&) = delete;
+ EventLogger& operator=(EventLogger&&) = delete;
+
+ /**
+ * @brief Constructor
+ *
+ * @param[in] event - The sd_event object
+ * @param[in] creator - The function to use to create the event log
+ */
+ EventLogger(sd_event* event, LogFunction creator) :
+ _event(event), _creator(creator)
+ {
+ }
+
+ /**
+ * @brief Adds an event to the queue so that it will be created
+ * as soon as the code makes it back to the event loop.
+ *
+ * Won't add it to the queue if already inside the create()
+ * callback.
+ *
+ * @param[in] message - The message property of the event log
+ * @param[in] severity - The severity level of the event log
+ * @param[in] ad - The additional data property of the event log
+ */
+ void log(const std::string& message,
+ phosphor::logging::Entry::Level severity, const AdditionalData& ad)
+ {
+ if (!_inEventCreation)
+ {
+ _eventsToCreate.emplace(message, severity, ad);
+
+ if (!_eventSource)
+ {
+ scheduleCreate();
+ }
+ }
+ else
+ {
+ phosphor::logging::log<phosphor::logging::level::INFO>(
+ "Already in event create callback, skipping new create",
+ phosphor::logging::entry("ERROR_NAME=%s", message.c_str()));
+ }
+ }
+
+ /**
+ * @brief Returns the event log queue size.
+ *
+ * @return size_t - The queue size
+ */
+ size_t queueSize() const
+ {
+ return _eventsToCreate.size();
+ }
+
+ /**
+ * @brief Schedules the create() function to run using the
+ * 'defer' sd_event source.
+ */
+ void scheduleCreate()
+ {
+ _eventSource = std::make_unique<sdeventplus::source::Defer>(
+ _event, std::bind(std::mem_fn(&EventLogger::create), this,
+ std::placeholders::_1));
+ }
+
+ private:
+ /**
+ * @brief Creates an event log and schedules the next one if
+ * there is one.
+ *
+ * This gets called from the event loop by the sd_event code.
+ *
+ * @param[in] source - The event source object used
+ */
+ void create(sdeventplus::source::EventBase& source)
+ {
+ _eventSource.reset();
+
+ if (_eventsToCreate.empty())
+ {
+ return;
+ }
+
+ auto event = _eventsToCreate.front();
+ _eventsToCreate.pop();
+
+ _inEventCreation = true;
+
+ try
+ {
+ _creator(std::get<msgPos>(event), std::get<levelPos>(event),
+ std::get<adPos>(event).getData());
+ }
+ catch (std::exception& e)
+ {
+ phosphor::logging::log<phosphor::logging::level::ERR>(
+ "EventLogger's create function threw an exception",
+ phosphor::logging::entry("ERROR=%s", e.what()));
+ }
+
+ _inEventCreation = false;
+
+ if (!_eventsToCreate.empty())
+ {
+ scheduleCreate();
+ }
+ }
+
+ /**
+ * @brief The sd_event object.
+ */
+ sdeventplus::Event _event;
+
+ /**
+ * @brief The user supplied function to create the event log.
+ */
+ LogFunction _creator;
+
+ /**
+ * @brief Keeps track of if an event is currently being created.
+ *
+ * Guards against creating new events while creating events.
+ */
+ bool _inEventCreation = false;
+
+ /**
+ * @brief The event source object used for scheduling.
+ */
+ std::unique_ptr<sdeventplus::source::Defer> _eventSource;
+
+ /**
+ * @brief The queue of event logs to create.
+ */
+ std::queue<EventEntry> _eventsToCreate;
+};
+
+} // namespace openpower::pels
diff --git a/extensions/openpower-pels/extended_user_header.cpp b/extensions/openpower-pels/extended_user_header.cpp
new file mode 100644
index 0000000..c5c1938
--- /dev/null
+++ b/extensions/openpower-pels/extended_user_header.cpp
@@ -0,0 +1,179 @@
+/**
+ * Copyright © 2019 IBM Corporation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#include "extended_user_header.hpp"
+
+#include "pel_types.hpp"
+
+#include <phosphor-logging/log.hpp>
+
+namespace openpower
+{
+namespace pels
+{
+
+using namespace phosphor::logging;
+const size_t defaultSymptomIDWord = 3;
+const size_t symptomIDMaxSize = 80;
+
+ExtendedUserHeader::ExtendedUserHeader(Stream& pel)
+{
+ try
+ {
+ unflatten(pel);
+ validate();
+ }
+ catch (const std::exception& e)
+ {
+ log<level::ERR>("Cannot unflatten extended user header",
+ entry("ERROR=%s", e.what()));
+ _valid = false;
+ }
+}
+
+ExtendedUserHeader::ExtendedUserHeader(const DataInterfaceBase& dataIface,
+ const message::Entry& regEntry,
+ const SRC& src) :
+ _mtms(dataIface.getMachineTypeModel(), dataIface.getMachineSerialNumber())
+{
+ _header.id = static_cast<uint16_t>(SectionID::extendedUserHeader);
+ _header.version = extendedUserHeaderVersion;
+ _header.subType = 0;
+ _header.componentID = static_cast<uint16_t>(ComponentID::phosphorLogging);
+
+ memset(_serverFWVersion.data(), 0, _serverFWVersion.size());
+ auto version = dataIface.getServerFWVersion();
+
+ // The last byte must always be the NULL terminator
+ for (size_t i = 0; i < version.size() && i < firmwareVersionSize - 1; i++)
+ {
+ _serverFWVersion[i] = version[i];
+ }
+
+ memset(_subsystemFWVersion.data(), 0, _subsystemFWVersion.size());
+ version = dataIface.getBMCFWVersion();
+
+ // The last byte must always be the NULL terminator
+ for (size_t i = 0; i < version.size() && i < firmwareVersionSize - 1; i++)
+ {
+ _subsystemFWVersion[i] = version[i];
+ }
+
+ createSymptomID(regEntry, src);
+
+ _header.size = flattenedSize();
+ _valid = true;
+}
+
+void ExtendedUserHeader::flatten(Stream& pel) const
+{
+ pel << _header << _mtms;
+ pel.write(_serverFWVersion.data(), _serverFWVersion.size());
+ pel.write(_subsystemFWVersion.data(), _subsystemFWVersion.size());
+ pel << _reserved4B << _refTime << _reserved1B1 << _reserved1B2
+ << _reserved1B3 << _symptomIDSize << _symptomID;
+}
+
+void ExtendedUserHeader::unflatten(Stream& pel)
+{
+ pel >> _header >> _mtms;
+ pel.read(_serverFWVersion.data(), _serverFWVersion.size());
+ pel.read(_subsystemFWVersion.data(), _subsystemFWVersion.size());
+ pel >> _reserved4B >> _refTime >> _reserved1B1 >> _reserved1B2 >>
+ _reserved1B3 >> _symptomIDSize;
+
+ _symptomID.resize(_symptomIDSize);
+ pel >> _symptomID;
+}
+
+void ExtendedUserHeader::validate()
+{
+ bool failed = false;
+
+ if (header().id != static_cast<uint16_t>(SectionID::extendedUserHeader))
+ {
+ log<level::ERR>("Invalid failing Extended User Header section ID",
+ entry("ID=0x%X", header().id));
+ failed = true;
+ }
+
+ if (header().version != extendedUserHeaderVersion)
+ {
+ log<level::ERR>("Invalid Extended User Header version",
+ entry("VERSION=0x%X", header().version));
+ failed = true;
+ }
+
+ _valid = (failed) ? false : true;
+}
+
+void ExtendedUserHeader::createSymptomID(const message::Entry& regEntry,
+ const SRC& src)
+{
+ // Contains the first 8 characters of the ASCII string plus additional
+ // words from the SRC, separated by underscores. The message registry
+ // says which words to use, though that's optional and if not present
+ // then use a default word.
+ std::vector<size_t> idWords;
+
+ if (regEntry.src.symptomID)
+ {
+ idWords = regEntry.src.symptomID.value();
+ }
+ else
+ {
+ idWords.push_back(defaultSymptomIDWord);
+ }
+
+ auto symptomID = src.asciiString().substr(0, 8);
+
+ const auto& hexWords = src.hexwordData();
+
+ for (auto wordNum : idWords)
+ {
+ symptomID.push_back('_');
+
+ // Get the hexword array index for this SRC word
+ auto index = src.getWordIndexFromWordNum(wordNum);
+
+ // Convert to ASCII
+ char word[20];
+ sprintf(word, "%08X", hexWords[index]);
+ symptomID += word;
+ }
+
+ std::copy(symptomID.begin(), symptomID.end(),
+ std::back_inserter(_symptomID));
+
+ // Max total size is 80, including the upcoming NULL
+ if (_symptomID.size() > (symptomIDMaxSize - 1))
+ {
+ _symptomID.resize(symptomIDMaxSize - 1);
+ }
+
+ // NULL terminated
+ _symptomID.push_back(0);
+
+ // PAD with NULLs to a 4 byte boundary
+ while ((_symptomID.size() % 4) != 0)
+ {
+ _symptomID.push_back(0);
+ }
+
+ _symptomIDSize = _symptomID.size();
+}
+
+} // namespace pels
+} // namespace openpower
diff --git a/extensions/openpower-pels/extended_user_header.hpp b/extensions/openpower-pels/extended_user_header.hpp
new file mode 100644
index 0000000..422b3f5
--- /dev/null
+++ b/extensions/openpower-pels/extended_user_header.hpp
@@ -0,0 +1,254 @@
+#pragma once
+
+#include "bcd_time.hpp"
+#include "data_interface.hpp"
+#include "elog_entry.hpp"
+#include "mtms.hpp"
+#include "registry.hpp"
+#include "section.hpp"
+#include "src.hpp"
+#include "stream.hpp"
+
+namespace openpower
+{
+namespace pels
+{
+
+constexpr uint8_t extendedUserHeaderVersion = 0x01;
+constexpr size_t firmwareVersionSize = 16;
+
+/**
+ * @class ExtendedUserHeader
+ *
+ * This represents the Extended User Header section in a PEL. It is a required
+ * section. It contains code versions, an MTMS subsection, and a string called
+ * a symptom ID.
+ *
+ * The Section base class handles the section header structure that every
+ * PEL section has at offset zero.
+ */
+class ExtendedUserHeader : public Section
+{
+ public:
+ ExtendedUserHeader() = delete;
+ ~ExtendedUserHeader() = default;
+ ExtendedUserHeader(const ExtendedUserHeader&) = default;
+ ExtendedUserHeader& operator=(const ExtendedUserHeader&) = default;
+ ExtendedUserHeader(ExtendedUserHeader&&) = default;
+ ExtendedUserHeader& operator=(ExtendedUserHeader&&) = default;
+
+ /**
+ * @brief Constructor
+ *
+ * Fills in this class's data fields from the stream.
+ *
+ * @param[in] pel - the PEL data stream
+ */
+ explicit ExtendedUserHeader(Stream& pel);
+
+ /**
+ * @brief Constructor
+ *
+ * @param[in] dataIface - The DataInterface object
+ * @param[in] regEntry - The message registry entry for this event
+ * @param[in] src - The SRC section object for this event
+ */
+ ExtendedUserHeader(const DataInterfaceBase& dataIface,
+ const message::Entry& regEntry, const SRC& src);
+
+ /**
+ * @brief Flatten the section into the stream
+ *
+ * @param[in] stream - The stream to write to
+ */
+ void flatten(Stream& stream) const override;
+
+ /**
+ * @brief Returns the size of this section when flattened into a PEL
+ *
+ * @return size_t - the size of the section
+ */
+ size_t flattenedSize()
+ {
+ return Section::flattenedSize() + _mtms.flattenedSize() +
+ _serverFWVersion.size() + _subsystemFWVersion.size() +
+ sizeof(_reserved4B) + sizeof(_refTime) + sizeof(_reserved1B1) +
+ sizeof(_reserved1B2) + sizeof(_reserved1B3) +
+ sizeof(_symptomIDSize) + _symptomIDSize;
+ }
+
+ /**
+ * @brief Returns the server firmware version
+ *
+ * @return std::string - The version
+ */
+ std::string serverFWVersion() const
+ {
+ std::string version;
+ for (size_t i = 0;
+ i < _serverFWVersion.size() && _serverFWVersion[i] != '\0'; i++)
+ {
+ version.push_back(_serverFWVersion[i]);
+ }
+ return version;
+ }
+
+ /**
+ * @brief Returns the subsystem firmware version
+ *
+ * @return std::string - The version
+ */
+ std::string subsystemFWVersion() const
+ {
+ std::string version;
+ for (size_t i = 0;
+ i < _subsystemFWVersion.size() && _subsystemFWVersion[i] != '\0';
+ i++)
+ {
+ version.push_back(_subsystemFWVersion[i]);
+ }
+ return version;
+ }
+
+ /**
+ * @brief Returns the machine type+model
+ *
+ * @return std::string - The MTM
+ */
+ std::string machineTypeModel() const
+ {
+ return _mtms.machineTypeAndModel();
+ }
+
+ /**
+ * @brief Returns the machine serial number
+ *
+ * @return std::string - The serial number
+ */
+ std::string machineSerialNumber() const
+ {
+ return _mtms.machineSerialNumber();
+ }
+
+ /**
+ * @brief Returns the Event Common Reference Time field
+ *
+ * @return BCDTime - The time value
+ */
+ const BCDTime& refTime() const
+ {
+ return _refTime;
+ }
+
+ /**
+ * @brief Returns the symptom ID
+ *
+ * @return std::string - The symptom ID
+ */
+ std::string symptomID() const
+ {
+ std::string symptom;
+ for (size_t i = 0; i < _symptomID.size() && _symptomID[i] != '\0'; i++)
+ {
+ symptom.push_back(_symptomID[i]);
+ }
+ return symptom;
+ }
+
+ private:
+ /**
+ * @brief Fills in the object from the stream data
+ *
+ * @param[in] stream - The stream to read from
+ */
+ void unflatten(Stream& stream);
+
+ /**
+ * @brief Validates the section contents
+ *
+ * Updates _valid (in Section) with the results.
+ */
+ void validate() override;
+
+ /**
+ * @brief Builds the symptom ID
+ *
+ * Uses the message registry to say which SRC fields to add
+ * to the SRC's ASCII string to make the ID, and uses a smart
+ * default if not specified in the registry.
+ *
+ * @param[in] regEntry - The message registry entry for this event
+ * @param[in] src - The SRC section object for this event
+ */
+ void createSymptomID(const message::Entry& regEntry, const SRC& src);
+
+ /**
+ * @brief The structure that holds the machine TM and SN fields.
+ */
+ MTMS _mtms;
+
+ /**
+ * @brief The server firmware version
+ *
+ * NULL terminated.
+ *
+ * The release version of the full firmware image.
+ */
+ std::array<uint8_t, firmwareVersionSize> _serverFWVersion;
+
+ /**
+ * @brief The subsystem firmware version
+ *
+ * NULL terminated.
+ *
+ * On PELs created on the BMC, this will be the BMC code version.
+ */
+ std::array<uint8_t, firmwareVersionSize> _subsystemFWVersion;
+
+ /**
+ * @brief Reserved
+ */
+ uint32_t _reserved4B = 0;
+
+ /**
+ * @brief Event Common Reference Time
+ *
+ * This is not used by PELs created on the BMC.
+ */
+ BCDTime _refTime;
+
+ /**
+ * @brief Reserved
+ */
+ uint8_t _reserved1B1 = 0;
+
+ /**
+ * @brief Reserved
+ */
+ uint8_t _reserved1B2 = 0;
+
+ /**
+ * @brief Reserved
+ */
+ uint8_t _reserved1B3 = 0;
+
+ /**
+ * @brief The size of the symptom ID field
+ */
+ uint8_t _symptomIDSize;
+
+ /**
+ * @brief The symptom ID field
+ *
+ * Describes a unique event signature for the log.
+ * Required for serviceable events, otherwise optional.
+ * When present, must start with the first 8 characters
+ * of the ASCII string field from the SRC.
+ *
+ * NULL terminated.
+ */
+ std::vector<uint8_t> _symptomID;
+};
+
+} // namespace pels
+} // namespace openpower
diff --git a/extensions/openpower-pels/failing_mtms.cpp b/extensions/openpower-pels/failing_mtms.cpp
new file mode 100644
index 0000000..b45c05d
--- /dev/null
+++ b/extensions/openpower-pels/failing_mtms.cpp
@@ -0,0 +1,104 @@
+/**
+ * Copyright © 2019 IBM Corporation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#include "failing_mtms.hpp"
+
+#include "json_utils.hpp"
+#include "pel_types.hpp"
+
+#include <phosphor-logging/log.hpp>
+
+namespace openpower
+{
+namespace pels
+{
+
+using namespace phosphor::logging;
+static constexpr uint8_t failingMTMSVersion = 0x01;
+
+FailingMTMS::FailingMTMS(const DataInterfaceBase& dataIface) :
+ _mtms(dataIface.getMachineTypeModel(), dataIface.getMachineSerialNumber())
+{
+ _header.id = static_cast<uint16_t>(SectionID::failingMTMS);
+ _header.size = FailingMTMS::flattenedSize();
+ _header.version = failingMTMSVersion;
+ _header.subType = 0;
+ _header.componentID = static_cast<uint16_t>(ComponentID::phosphorLogging);
+
+ _valid = true;
+}
+
+FailingMTMS::FailingMTMS(Stream& pel)
+{
+ try
+ {
+ unflatten(pel);
+ validate();
+ }
+ catch (std::exception& e)
+ {
+ log<level::ERR>("Cannot unflatten failing MTM section",
+ entry("ERROR=%s", e.what()));
+ _valid = false;
+ }
+}
+
+void FailingMTMS::validate()
+{
+ bool failed = false;
+
+ if (header().id != static_cast<uint16_t>(SectionID::failingMTMS))
+ {
+ log<level::ERR>("Invalid failing MTMS section ID",
+ entry("ID=0x%X", header().id));
+ failed = true;
+ }
+
+ if (header().version != failingMTMSVersion)
+ {
+ log<level::ERR>("Invalid failing MTMS version",
+ entry("VERSION=0x%X", header().version));
+ failed = true;
+ }
+
+ _valid = (failed) ? false : true;
+}
+
+void FailingMTMS::flatten(Stream& stream) const
+{
+ stream << _header << _mtms;
+}
+
+void FailingMTMS::unflatten(Stream& stream)
+{
+ stream >> _header >> _mtms;
+}
+
+std::optional<std::string> FailingMTMS::getJSON() const
+{
+ std::string json;
+ jsonInsert(json, "Section Version", getNumberString("%d", _header.version),
+ 1);
+ jsonInsert(json, "Sub-section type", getNumberString("%d", _header.subType),
+ 1);
+ jsonInsert(json, "Created by", getNumberString("0x%X", _header.componentID),
+ 1);
+ jsonInsert(json, "Machine Type Model", _mtms.machineTypeAndModel(), 1);
+ jsonInsert(json, "Serial Number", trimEnd(_mtms.machineSerialNumber()), 1);
+ json.erase(json.size() - 2);
+ return json;
+}
+} // namespace pels
+} // namespace openpower
diff --git a/extensions/openpower-pels/failing_mtms.hpp b/extensions/openpower-pels/failing_mtms.hpp
new file mode 100644
index 0000000..8f5cd44
--- /dev/null
+++ b/extensions/openpower-pels/failing_mtms.hpp
@@ -0,0 +1,114 @@
+#pragma once
+
+#include "data_interface.hpp"
+#include "mtms.hpp"
+#include "section.hpp"
+#include "stream.hpp"
+
+namespace openpower
+{
+namespace pels
+{
+
+/**
+ * @class FailingMTMS
+ *
+ * This represents the Failing Enclosure MTMS section in a PEL.
+ * It is a required section. In the BMC case, the enclosure is
+ * the system enclosure.
+ */
+class FailingMTMS : public Section
+{
+ public:
+ FailingMTMS() = delete;
+ ~FailingMTMS() = default;
+ FailingMTMS(const FailingMTMS&) = default;
+ FailingMTMS& operator=(const FailingMTMS&) = default;
+ FailingMTMS(FailingMTMS&&) = default;
+ FailingMTMS& operator=(FailingMTMS&&) = default;
+
+ /**
+ * @brief Constructor
+ *
+ * @param[in] dataIface - The object to use to obtain
+ * the MTM and SN.
+ */
+ explicit FailingMTMS(const DataInterfaceBase& dataIface);
+
+ /**
+ * @brief Constructor
+ *
+ * Fills in this class's data fields from the stream.
+ *
+ * @param[in] pel - the PEL data stream
+ */
+ explicit FailingMTMS(Stream& pel);
+
+ /**
+ * @brief Flatten the section into the stream
+ *
+ * @param[in] stream - The stream to write to
+ */
+ void flatten(Stream& stream) const override;
+
+ /**
+ * @brief Returns the size of this section when flattened into a PEL
+ *
+ * @return size_t - the size of the section
+ */
+ static constexpr size_t flattenedSize()
+ {
+ return Section::flattenedSize() + MTMS::flattenedSize();
+ }
+
+ /**
+ * @brief Returns the machine type+model as a string
+ *
+ * @return std::string the MTM
+ */
+ std::string getMachineTypeModel() const
+ {
+ return _mtms.machineTypeAndModel();
+ }
+
+ /**
+ * @brief Returns the machine serial number as a string
+ *
+ * @return std::string the serial number
+ */
+ std::string getMachineSerialNumber() const
+ {
+ return _mtms.machineSerialNumber();
+ }
+
+ /**
+ * @brief Get section in JSON.
+ * @return std::optional<std::string> - Failing MTMS section in JSON
+ */
+ std::optional<std::string> getJSON() const override;
+
+ private:
+ /**
+ * @brief Validates the section contents
+ *
+ * Updates _valid (in Section) with the results.
+ */
+ void validate() override;
+
+ /**
+ * @brief Fills in the object from the stream data
+ *
+ * @param[in] stream - The stream to read from
+ */
+ void unflatten(Stream& stream);
+
+ /**
+ * @brief The structure that holds the TM and SN fields.
+ * This is also used in other places in the PEL.
+ */
+ MTMS _mtms;
+};
+
+} // namespace pels
+
+} // namespace openpower
diff --git a/extensions/openpower-pels/fru_identity.cpp b/extensions/openpower-pels/fru_identity.cpp
new file mode 100644
index 0000000..8c81e88
--- /dev/null
+++ b/extensions/openpower-pels/fru_identity.cpp
@@ -0,0 +1,113 @@
+/**
+ * Copyright © 2019 IBM Corporation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#include "fru_identity.hpp"
+
+namespace openpower
+{
+namespace pels
+{
+namespace src
+{
+
+FRUIdentity::FRUIdentity(Stream& pel)
+{
+ pel >> _type >> _size >> _flags;
+
+ if (_flags & (pnSupplied | maintProcSupplied))
+ {
+ pel.read(_pnOrProcedureID.data(), _pnOrProcedureID.size());
+ }
+
+ if (_flags & ccinSupplied)
+ {
+ pel.read(_ccin.data(), _ccin.size());
+ }
+
+ if (_flags & snSupplied)
+ {
+ pel.read(_sn.data(), _sn.size());
+ }
+}
+
+std::optional<std::string> FRUIdentity::getPN() const
+{
+ if (hasPN())
+ {
+ // NULL terminated
+ std::string pn{_pnOrProcedureID.data()};
+ return pn;
+ }
+
+ return std::nullopt;
+}
+
+std::optional<std::string> FRUIdentity::getMaintProc() const
+{
+ if (hasMP())
+ {
+ // NULL terminated
+ std::string mp{_pnOrProcedureID.data()};
+ return mp;
+ }
+
+ return std::nullopt;
+}
+
+std::optional<std::string> FRUIdentity::getCCIN() const
+{
+ if (hasCCIN())
+ {
+ std::string ccin{_ccin.begin(), _ccin.begin() + _ccin.size()};
+ return ccin;
+ }
+
+ return std::nullopt;
+}
+
+std::optional<std::string> FRUIdentity::getSN() const
+{
+ if (hasSN())
+ {
+ std::string sn{_sn.begin(), _sn.begin() + _sn.size()};
+ return sn;
+ }
+
+ return std::nullopt;
+}
+
+void FRUIdentity::flatten(Stream& pel) const
+{
+ pel << _type << _size << _flags;
+
+ if (hasPN() || hasMP())
+ {
+ pel.write(_pnOrProcedureID.data(), _pnOrProcedureID.size());
+ }
+
+ if (hasCCIN())
+ {
+ pel.write(_ccin.data(), _ccin.size());
+ }
+
+ if (hasSN())
+ {
+ pel.write(_sn.data(), _sn.size());
+ }
+}
+
+} // namespace src
+} // namespace pels
+} // namespace openpower
diff --git a/extensions/openpower-pels/fru_identity.hpp b/extensions/openpower-pels/fru_identity.hpp
new file mode 100644
index 0000000..ecc2899
--- /dev/null
+++ b/extensions/openpower-pels/fru_identity.hpp
@@ -0,0 +1,222 @@
+#pragma once
+
+#include "stream.hpp"
+
+#include <optional>
+
+namespace openpower
+{
+namespace pels
+{
+namespace src
+{
+
+/**
+ * @class FRUIdentity
+ *
+ * This represents the FRU Identity substructure in the
+ * callout subsection of the SRC PEL section.
+ *
+ * It provides information about the FRU being called out,
+ * such as serial number and part number. A maintenance
+ * procedure name may be used instead of the part number,
+ * and this would be indicated in the flags field.
+ */
+class FRUIdentity
+{
+ public:
+ /**
+ * @brief The failing component type
+ *
+ * Upper nibble of the flags byte
+ */
+ enum FailingComponentType
+ {
+ hardwareFRU = 0x10,
+ codeFRU = 0x20,
+ configError = 0x30,
+ maintenanceProc = 0x40,
+ externalFRU = 0x90,
+ externalCodeFRU = 0xA0,
+ toolFRU = 0xB0,
+ symbolicFRU = 0xC0,
+ symbolicFRUTrustedLocCode = 0xE0
+ };
+
+ /**
+ * @brief The lower nibble of the flags byte
+ */
+ enum Flags
+ {
+ pnSupplied = 0x08,
+ ccinSupplied = 0x04,
+ maintProcSupplied = 0x02,
+ snSupplied = 0x01
+ };
+
+ FRUIdentity() = delete;
+ ~FRUIdentity() = default;
+ FRUIdentity(const FRUIdentity&) = default;
+ FRUIdentity& operator=(const FRUIdentity&) = default;
+ FRUIdentity(FRUIdentity&&) = default;
+ FRUIdentity& operator=(FRUIdentity&&) = default;
+
+ /**
+ * @brief Constructor
+ *
+ * Fills in this class's data fields from the stream.
+ *
+ * @param[in] pel - the PEL data stream
+ */
+ explicit FRUIdentity(Stream& pel);
+
+ /**
+ * @brief Flatten the object into the stream
+ *
+ * @param[in] stream - The stream to write to
+ */
+ void flatten(Stream& pel) const;
+
+ /**
+ * @brief Returns the size of this structure when flattened into a PEL
+ *
+ * @return size_t - The size of the section
+ */
+ size_t flattenedSize() const
+ {
+ return _size;
+ }
+
+ /**
+ * @brief The failing component type for this FRU callout.
+ *
+ * @return FailingComponentType
+ */
+ FailingComponentType failingComponentType() const
+ {
+ return static_cast<FailingComponentType>(_flags & 0xF0);
+ }
+
+ /**
+ * @brief Returns the part number, if supplied
+ *
+ * @return std::optional<std::string>
+ */
+ std::optional<std::string> getPN() const;
+
+ /**
+ * @brief Returns the maintenance procedure, if supplied
+ *
+ * @return std::optional<std::string>
+ */
+ std::optional<std::string> getMaintProc() const;
+
+ /**
+ * @brief Returns the CCIN, if supplied
+ *
+ * @return std::optional<std::string>
+ */
+ std::optional<std::string> getCCIN() const;
+
+ /**
+ * @brief Returns the serial number, if supplied
+ *
+ * @return std::optional<std::string>
+ */
+ std::optional<std::string> getSN() const;
+
+ /**
+ * @brief The type identifier value of this structure.
+ */
+ static const uint16_t substructureType = 0x4944; // "ID"
+
+ private:
+ /**
+ * @brief If the part number is contained in this structure.
+ *
+ * It takes the place of the maintenance procedure ID.
+ *
+ * @return bool
+ */
+ bool hasPN() const
+ {
+ return _flags & pnSupplied;
+ }
+
+ /**
+ * @brief If the CCIN is contained in this structure.
+ *
+ * @return bool
+ */
+ bool hasCCIN() const
+ {
+ return _flags & ccinSupplied;
+ }
+
+ /**
+ * @brief If a maintenance procedure is contained in this structure.
+ *
+ * It takes the place of the part number.
+ *
+ * @return bool
+ */
+ bool hasMP() const
+ {
+ return _flags & maintProcSupplied;
+ }
+
+ /**
+ * @brief If the serial number is contained in this structure.
+ *
+ * @return bool
+ */
+ bool hasSN() const
+ {
+ return _flags & snSupplied;
+ }
+
+ /**
+ * @brief The callout substructure type field. Will be "ID".
+ */
+ uint16_t _type;
+
+ /**
+ * @brief The size of this callout structure.
+ *
+ * Always a multiple of 4.
+ */
+ uint8_t _size;
+
+ /**
+ * @brief The flags byte of this substructure.
+ *
+ * See the FailingComponentType and Flags enums
+ */
+ uint8_t _flags;
+
+ /**
+ * @brief The part number OR maintenance procedure ID,
+ * depending on what the flags field specifies.
+ *
+ * A NULL terminated ASCII string.
+ */
+ std::array<char, 8> _pnOrProcedureID;
+
+ /**
+ * @brief The CCIN VPD keyword
+ *
+ * Four ASCII characters, not NULL terminated.
+ */
+ std::array<char, 4> _ccin;
+
+ /**
+ * @brief The serial number
+ *
+ * Twelve ASCII characters, not NULL terminated.
+ */
+ std::array<char, 12> _sn;
+};
+
+} // namespace src
+} // namespace pels
+} // namespace openpower
diff --git a/extensions/openpower-pels/generic.cpp b/extensions/openpower-pels/generic.cpp
new file mode 100644
index 0000000..528b46d
--- /dev/null
+++ b/extensions/openpower-pels/generic.cpp
@@ -0,0 +1,70 @@
+/**
+ * Copyright © 2019 IBM Corporation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#include "generic.hpp"
+
+#include <phosphor-logging/log.hpp>
+
+namespace openpower
+{
+namespace pels
+{
+
+using namespace phosphor::logging;
+
+void Generic::unflatten(Stream& stream)
+{
+ stream >> _header;
+
+ if (_header.size <= SectionHeader::flattenedSize())
+ {
+ throw std::out_of_range(
+ "Generic::unflatten: SectionHeader::size field too small");
+ }
+
+ size_t dataLength = _header.size - SectionHeader::flattenedSize();
+ _data.resize(dataLength);
+
+ stream >> _data;
+}
+
+void Generic::flatten(Stream& stream) const
+{
+ stream << _header << _data;
+}
+
+Generic::Generic(Stream& pel)
+{
+ try
+ {
+ unflatten(pel);
+ validate();
+ }
+ catch (const std::exception& e)
+ {
+ log<level::ERR>("Cannot unflatten generic section",
+ entry("ERROR=%s", e.what()));
+ _valid = false;
+ }
+}
+
+void Generic::validate()
+{
+ // Nothing to validate
+ _valid = true;
+}
+
+} // namespace pels
+} // namespace openpower
diff --git a/extensions/openpower-pels/generic.hpp b/extensions/openpower-pels/generic.hpp
new file mode 100644
index 0000000..ff85b03
--- /dev/null
+++ b/extensions/openpower-pels/generic.hpp
@@ -0,0 +1,87 @@
+#pragma once
+
+#include "section.hpp"
+#include "stream.hpp"
+
+namespace openpower
+{
+namespace pels
+{
+
+/**
+ * @class Generic
+ *
+ * This class is used for a PEL section when there is no other class to use.
+ * It just contains a vector of the raw data. Its purpose is so that a PEL
+ * can be completely unflattened even if the code doesn't have a class for
+ * every section type.
+ */
+class Generic : public Section
+{
+ public:
+ Generic() = delete;
+ ~Generic() = default;
+ Generic(const Generic&) = default;
+ Generic& operator=(const Generic&) = default;
+ Generic(Generic&&) = default;
+ Generic& operator=(Generic&&) = default;
+
+ /**
+ * @brief Constructor
+ *
+ * Fills in this class's data fields from the stream.
+ *
+ * @param[in] pel - the PEL data stream
+ */
+ explicit Generic(Stream& pel);
+
+ /**
+ * @brief Flatten the section into the stream
+ *
+ * @param[in] stream - The stream to write to
+ */
+ void flatten(Stream& stream) const override;
+
+ /**
+ * @brief Returns the size of this section when flattened into a PEL
+ *
+ * @return size_t - the size of the section
+ */
+ size_t flattenedSize()
+ {
+ return Section::flattenedSize() + _data.size();
+ }
+
+ /**
+ * @brief Returns the raw section data
+ *
+ * @return std::vector<uint8_t>&
+ */
+ const std::vector<uint8_t>& data() const
+ {
+ return _data;
+ }
+
+ private:
+ /**
+ * @brief Fills in the object from the stream data
+ *
+ * @param[in] stream - The stream to read from
+ */
+ void unflatten(Stream& stream);
+
+ /**
+ * @brief Validates the section contents
+ *
+ * Updates _valid (in Section) with the results.
+ */
+ void validate() override;
+
+ /**
+ * @brief The section data
+ */
+ std::vector<uint8_t> _data;
+};
+
+} // namespace pels
+} // namespace openpower
diff --git a/extensions/openpower-pels/host_interface.hpp b/extensions/openpower-pels/host_interface.hpp
new file mode 100644
index 0000000..94c984a
--- /dev/null
+++ b/extensions/openpower-pels/host_interface.hpp
@@ -0,0 +1,211 @@
+#pragma once
+
+#include "data_interface.hpp"
+
+#include <stdint.h>
+
+#include <chrono>
+#include <functional>
+#include <sdeventplus/event.hpp>
+#include <sdeventplus/source/io.hpp>
+
+namespace openpower
+{
+namespace pels
+{
+
+/**
+ * @brief Return codes from sending a command
+ */
+enum class CmdStatus
+{
+ success,
+ failure
+};
+
+/**
+ * @brief Return codes from the command response
+ */
+enum class ResponseStatus
+{
+ success,
+ failure
+};
+
+/**
+ * @class HostInterface
+ *
+ * An abstract base class for sending the 'New PEL available' command
+ * to the host. Used so that the PLDM interfaces can be mocked for
+ * testing the HostNotifier code. The response to this command is
+ * asynchronous, with the intent that other code registers a callback
+ * function to run when the response is received.
+ */
+class HostInterface
+{
+ public:
+ HostInterface() = delete;
+ virtual ~HostInterface() = default;
+ HostInterface(const HostInterface&) = default;
+ HostInterface& operator=(const HostInterface&) = default;
+ HostInterface(HostInterface&&) = default;
+ HostInterface& operator=(HostInterface&&) = default;
+
+ /**
+ * @brief Constructor
+ *
+ * @param[in] event - The sd_event object pointer
+ * @param[in] dataIface - The DataInterface object
+ */
+ HostInterface(sd_event* event, DataInterfaceBase& dataIface) :
+ _event(event), _dataIface(dataIface)
+ {
+ }
+
+ /**
+ * @brief Pure virtual function for sending the 'new PEL available'
+ * asynchronous command to the host.
+ *
+ * @param[in] id - The ID of the new PEL
+ * @param[in] size - The size of the new PEL
+ *
+ * @return CmdStatus - If the send was successful or not
+ */
+ virtual CmdStatus sendNewLogCmd(uint32_t id, uint32_t size) = 0;
+
+ /**
+ * @brief Returns the amount of time to wait before retrying after
+ * a failed send command.
+ *
+ * @return milliseconds - The amount of time to wait
+ */
+ virtual std::chrono::milliseconds getSendRetryDelay() const
+ {
+ return _defaultSendRetryDelay;
+ }
+
+ /**
+ * @brief Returns the amount of time to wait before retrying after
+ * a command receive.
+ *
+ * @return milliseconds - The amount of time to wait
+ */
+ virtual std::chrono::milliseconds getReceiveRetryDelay() const
+ {
+ return _defaultReceiveRetryDelay;
+ }
+
+ /**
+ * @brief Returns the amount of time to wait before retrying if the
+ * host firmware's PEL storage was full and it can't store
+ * any more logs until it is freed up somehow.
+ *
+ * In this class to help with mocking.
+ *
+ * @return milliseconds - The amount of time to wait
+ */
+ virtual std::chrono::milliseconds getHostFullRetryDelay() const
+ {
+ return _defaultHostFullRetryDelay;
+ }
+
+ using ResponseFunction = std::function<void(ResponseStatus)>;
+
+ /**
+ * @brief Sets the function to call on the command receive.
+ *
+ * The success/failure status is passed to the function.
+ *
+ * @param[in] func - The callback function
+ */
+ void setResponseFunction(ResponseFunction func)
+ {
+ _responseFunc = std::move(func);
+ }
+
+ /**
+ * @brief Returns the event object in use
+ *
+ * @return sdeventplus::Event& - The event object
+ */
+ sdeventplus::Event& getEvent()
+ {
+ return _event;
+ }
+
+ /**
+ * @brief Pure virtual function to cancel an in-progress command
+ *
+ * 'In progress' means after the send but before the receive
+ */
+ virtual void cancelCmd() = 0;
+
+ /**
+ * @brief Says if the command is in progress (after send/before receive)
+ *
+ * @return bool - If command is in progress
+ */
+ bool cmdInProgress() const
+ {
+ return _inProgress;
+ }
+
+ protected:
+ /**
+ * @brief Pure virtual function for implementing the asynchronous
+ * command response callback.
+ *
+ * @param[in] io - The sdeventplus IO object that the callback is
+ * invoked from.
+ * @param[in] fd - The file descriptor being used
+ * @param[in] revents - The event status bits
+ */
+ virtual void receive(sdeventplus::source::IO& io, int fd,
+ uint32_t revents) = 0;
+
+ /**
+ * @brief An optional function to call on a successful command response.
+ */
+ std::optional<ResponseFunction> _responseFunc;
+
+ /**
+ * @brief The sd_event wrapper object needed for response callbacks
+ */
+ sdeventplus::Event _event;
+
+ /**
+ * @brief The DataInterface object
+ */
+ DataInterfaceBase& _dataIface;
+
+ /**
+ * @brief Tracks status of after a command is sent and before the
+ * response is received.
+ */
+ bool _inProgress = false;
+
+ private:
+ /**
+ * @brief The default amount of time to wait before retrying
+ * a failed send.
+ *
+ * It is this value for the case where all instance IDs are used
+ * and it takes this long in the PLDM daemon for them to reset.
+ */
+ const std::chrono::milliseconds _defaultSendRetryDelay{6000};
+
+ /**
+ * @brief The default amount of time to wait
+ * before retrying after a failed receive.
+ */
+ const std::chrono::milliseconds _defaultReceiveRetryDelay{1000};
+
+ /**
+ * @brief The default amount of time to wait when the host said it
+ * was full before sending the PEL again.
+ */
+ const std::chrono::milliseconds _defaultHostFullRetryDelay{60000};
+};
+
+} // namespace pels
+} // namespace openpower
diff --git a/extensions/openpower-pels/host_notifier.cpp b/extensions/openpower-pels/host_notifier.cpp
new file mode 100644
index 0000000..2e132b9
--- /dev/null
+++ b/extensions/openpower-pels/host_notifier.cpp
@@ -0,0 +1,435 @@
+/**
+ * Copyright © 2019 IBM Corporation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#include "host_notifier.hpp"
+
+#include <phosphor-logging/log.hpp>
+
+namespace openpower::pels
+{
+
+const auto subscriptionName = "PELHostNotifier";
+const size_t maxRetryAttempts = 15;
+
+using namespace phosphor::logging;
+
+HostNotifier::HostNotifier(Repository& repo, DataInterfaceBase& dataIface,
+ std::unique_ptr<HostInterface> hostIface) :
+ _repo(repo),
+ _dataIface(dataIface), _hostIface(std::move(hostIface)),
+ _retryTimer(_hostIface->getEvent(),
+ std::bind(std::mem_fn(&HostNotifier::retryTimerExpired), this)),
+ _hostFullTimer(
+ _hostIface->getEvent(),
+ std::bind(std::mem_fn(&HostNotifier::hostFullTimerExpired), this))
+{
+ // Subscribe to be told about new PELs.
+ _repo.subscribeToAdds(subscriptionName,
+ std::bind(std::mem_fn(&HostNotifier::newLogCallback),
+ this, std::placeholders::_1));
+
+ // Add any existing PELs to the queue to send them if necessary.
+ _repo.for_each(std::bind(std::mem_fn(&HostNotifier::addPELToQueue), this,
+ std::placeholders::_1));
+
+ // Subscribe to be told about host state changes.
+ _dataIface.subscribeToHostStateChange(
+ subscriptionName,
+ std::bind(std::mem_fun(&HostNotifier::hostStateChange), this,
+ std::placeholders::_1));
+
+ // Set the function to call when the async reponse is received.
+ _hostIface->setResponseFunction(
+ std::bind(std::mem_fn(&HostNotifier::commandResponse), this,
+ std::placeholders::_1));
+
+ // Start sending logs if the host is running
+ if (!_pelQueue.empty() && _dataIface.isHostUp())
+ {
+ doNewLogNotify();
+ }
+}
+
+HostNotifier::~HostNotifier()
+{
+ _repo.unsubscribeFromAdds(subscriptionName);
+ _dataIface.unsubscribeFromHostStateChange(subscriptionName);
+}
+
+bool HostNotifier::addPELToQueue(const PEL& pel)
+{
+ if (enqueueRequired(pel.id()))
+ {
+ _pelQueue.push_back(pel.id());
+ }
+
+ // Return false so that Repo::for_each keeps going.
+ return false;
+}
+
+bool HostNotifier::enqueueRequired(uint32_t id) const
+{
+ bool required = true;
+ Repository::LogID i{Repository::LogID::Pel{id}};
+
+ if (auto attributes = _repo.getPELAttributes(i); attributes)
+ {
+ auto a = attributes.value().get();
+
+ if ((a.hostState == TransmissionState::acked) ||
+ (a.hostState == TransmissionState::badPEL))
+ {
+ required = false;
+ }
+ else if (a.actionFlags.test(hiddenFlagBit) &&
+ (a.hmcState == TransmissionState::acked))
+ {
+ required = false;
+ }
+ else if (a.actionFlags.test(dontReportToHostFlagBit))
+ {
+ required = false;
+ }
+ }
+ else
+ {
+ using namespace phosphor::logging;
+ log<level::ERR>("Host Enqueue: Unable to find PEL ID in repository",
+ entry("PEL_ID=0x%X", id));
+ required = false;
+ }
+
+ return required;
+}
+
+bool HostNotifier::notifyRequired(uint32_t id) const
+{
+ bool notify = true;
+ Repository::LogID i{Repository::LogID::Pel{id}};
+
+ if (auto attributes = _repo.getPELAttributes(i); attributes)
+ {
+ // If already acked by the host, don't send again.
+ // (A safety check as it shouldn't get to this point.)
+ auto a = attributes.value().get();
+ if (a.hostState == TransmissionState::acked)
+ {
+ notify = false;
+ }
+ else if (a.actionFlags.test(hiddenFlagBit))
+ {
+ // If hidden and acked (or will be) acked by the HMC,
+ // also don't send it. (HMC management can come and
+ // go at any time)
+ if ((a.hmcState == TransmissionState::acked) ||
+ _dataIface.isHMCManaged())
+ {
+ notify = false;
+ }
+ }
+ }
+ else
+ {
+ // Must have been deleted since put on the queue.
+ notify = false;
+ }
+
+ return notify;
+}
+
+void HostNotifier::newLogCallback(const PEL& pel)
+{
+ if (!enqueueRequired(pel.id()))
+ {
+ return;
+ }
+
+ _pelQueue.push_back(pel.id());
+
+ // Notify shouldn't happen if host is down or full
+ if (!_dataIface.isHostUp() || _hostFull)
+ {
+ return;
+ }
+
+ // Dispatch a command now if there isn't currently a command
+ // in progress and this is the first log in the queue or it
+ // previously gave up from a hard failure.
+ auto inProgress = (_inProgressPEL != 0) || _hostIface->cmdInProgress() ||
+ _retryTimer.isEnabled();
+
+ auto firstPEL = _pelQueue.size() == 1;
+ auto gaveUp = _retryCount >= maxRetryAttempts;
+
+ if (!inProgress && (firstPEL || gaveUp))
+ {
+ _retryCount = 0;
+
+ // Send a log, but from the event loop, not from here.
+ scheduleDispatch();
+ }
+}
+
+void HostNotifier::scheduleDispatch()
+{
+ _dispatcher = std::make_unique<sdeventplus::source::Defer>(
+ _hostIface->getEvent(), std::bind(std::mem_fn(&HostNotifier::dispatch),
+ this, std::placeholders::_1));
+}
+
+void HostNotifier::dispatch(sdeventplus::source::EventBase& source)
+{
+ _dispatcher.reset();
+
+ doNewLogNotify();
+}
+
+void HostNotifier::doNewLogNotify()
+{
+ if (!_dataIface.isHostUp() || _retryTimer.isEnabled() ||
+ _hostFullTimer.isEnabled())
+ {
+ return;
+ }
+
+ if (_retryCount >= maxRetryAttempts)
+ {
+ // Give up until a new log comes in.
+ if (_retryCount == maxRetryAttempts)
+ {
+ // If this were to really happen, the PLDM interface
+ // would be down and isolating that shouldn't left to
+ // a logging daemon, so just trace. Also, this will start
+ // trying again when the next new log comes in.
+ log<level::ERR>(
+ "PEL Host notifier hit max retry attempts. Giving up for now.",
+ entry("PEL_ID=0x%X", _pelQueue.front()));
+ }
+ return;
+ }
+
+ bool doNotify = false;
+ uint32_t id = 0;
+
+ // Find the PEL to send
+ while (!doNotify && !_pelQueue.empty())
+ {
+ id = _pelQueue.front();
+ _pelQueue.pop_front();
+
+ if (notifyRequired(id))
+ {
+ doNotify = true;
+ }
+ }
+
+ if (doNotify)
+ {
+ // Get the size using the repo attributes
+ Repository::LogID i{Repository::LogID::Pel{id}};
+ if (auto attributes = _repo.getPELAttributes(i); attributes)
+ {
+ auto size = static_cast<size_t>(
+ std::filesystem::file_size((*attributes).get().path));
+ auto rc = _hostIface->sendNewLogCmd(id, size);
+
+ if (rc == CmdStatus::success)
+ {
+ _inProgressPEL = id;
+ }
+ else
+ {
+ // It failed. Retry
+ log<level::ERR>("PLDM send failed", entry("PEL_ID=0x%X", id));
+ _pelQueue.push_front(id);
+ _inProgressPEL = 0;
+ _retryTimer.restartOnce(_hostIface->getSendRetryDelay());
+ }
+ }
+ else
+ {
+ log<level::ERR>("PEL ID not in repository. Cannot notify host",
+ entry("PEL_ID=0x%X", id));
+ }
+ }
+}
+
+void HostNotifier::hostStateChange(bool hostUp)
+{
+ _retryCount = 0;
+ _hostFull = false;
+
+ if (hostUp && !_pelQueue.empty())
+ {
+ doNewLogNotify();
+ }
+ else if (!hostUp)
+ {
+ stopCommand();
+
+ // Reset the state on any PELs that were sent but not acked back
+ // to new so they'll get sent again.
+ for (auto id : _sentPELs)
+ {
+ _pelQueue.push_back(id);
+ _repo.setPELHostTransState(id, TransmissionState::newPEL);
+ }
+
+ _sentPELs.clear();
+
+ if (_hostFullTimer.isEnabled())
+ {
+ _hostFullTimer.setEnabled(false);
+ }
+ }
+}
+
+void HostNotifier::commandResponse(ResponseStatus status)
+{
+ auto id = _inProgressPEL;
+ _inProgressPEL = 0;
+
+ if (status == ResponseStatus::success)
+ {
+ _retryCount = 0;
+
+ _sentPELs.push_back(id);
+
+ _repo.setPELHostTransState(id, TransmissionState::sent);
+
+ // If the host is full, don't send off the next PEL
+ if (!_hostFull && !_pelQueue.empty())
+ {
+ doNewLogNotify();
+ }
+ }
+ else
+ {
+ log<level::ERR>("PLDM command response failure",
+ entry("PEL_ID=0x%X", id));
+ // Retry
+ _pelQueue.push_front(id);
+ _retryTimer.restartOnce(_hostIface->getReceiveRetryDelay());
+ }
+}
+
+void HostNotifier::retryTimerExpired()
+{
+ if (_dataIface.isHostUp())
+ {
+ log<level::INFO>("Attempting command retry",
+ entry("PEL_ID=0x%X", _pelQueue.front()));
+ _retryCount++;
+ doNewLogNotify();
+ }
+}
+
+void HostNotifier::hostFullTimerExpired()
+{
+ doNewLogNotify();
+}
+
+void HostNotifier::stopCommand()
+{
+ _retryCount = 0;
+
+ if (_inProgressPEL != 0)
+ {
+ _pelQueue.push_front(_inProgressPEL);
+ _inProgressPEL = 0;
+ }
+
+ if (_retryTimer.isEnabled())
+ {
+ _retryTimer.setEnabled(false);
+ }
+
+ if (_hostIface->cmdInProgress())
+ {
+ _hostIface->cancelCmd();
+ }
+}
+
+void HostNotifier::ackPEL(uint32_t id)
+{
+ _repo.setPELHostTransState(id, TransmissionState::acked);
+
+ // No longer just 'sent', so remove it from the sent list.
+ auto sent = std::find(_sentPELs.begin(), _sentPELs.end(), id);
+ if (sent != _sentPELs.end())
+ {
+ _sentPELs.erase(sent);
+ }
+
+ // An ack means the host is no longer full
+ if (_hostFullTimer.isEnabled())
+ {
+ _hostFullTimer.setEnabled(false);
+ }
+
+ if (_hostFull)
+ {
+ _hostFull = false;
+
+ // Start sending PELs again, from the event loop
+ if (!_pelQueue.empty())
+ {
+ scheduleDispatch();
+ }
+ }
+}
+
+void HostNotifier::setHostFull(uint32_t id)
+{
+ log<level::INFO>("Received Host full indication", entry("PEL_ID=0x%X", id));
+
+ _hostFull = true;
+
+ // This PEL needs to get re-sent
+ auto sent = std::find(_sentPELs.begin(), _sentPELs.end(), id);
+ if (sent != _sentPELs.end())
+ {
+ _sentPELs.erase(sent);
+ _repo.setPELHostTransState(id, TransmissionState::newPEL);
+
+ if (std::find(_pelQueue.begin(), _pelQueue.end(), id) ==
+ _pelQueue.end())
+ {
+ _pelQueue.push_front(id);
+ }
+ }
+
+ // The only PELs that will be sent when the
+ // host is full is from this timer callback.
+ if (!_hostFullTimer.isEnabled())
+ {
+ _hostFullTimer.restartOnce(_hostIface->getHostFullRetryDelay());
+ }
+}
+
+void HostNotifier::setBadPEL(uint32_t id)
+{
+ log<level::ERR>("PEL rejected by the host", entry("PEL_ID=0x%X", id));
+
+ auto sent = std::find(_sentPELs.begin(), _sentPELs.end(), id);
+ if (sent != _sentPELs.end())
+ {
+ _sentPELs.erase(sent);
+ }
+
+ _repo.setPELHostTransState(id, TransmissionState::badPEL);
+}
+
+} // namespace openpower::pels
diff --git a/extensions/openpower-pels/host_notifier.hpp b/extensions/openpower-pels/host_notifier.hpp
new file mode 100644
index 0000000..cad2b36
--- /dev/null
+++ b/extensions/openpower-pels/host_notifier.hpp
@@ -0,0 +1,319 @@
+#pragma once
+
+#include "host_interface.hpp"
+#include "pel.hpp"
+#include "repository.hpp"
+
+#include <deque>
+#include <sdeventplus/clock.hpp>
+#include <sdeventplus/source/event.hpp>
+#include <sdeventplus/utility/timer.hpp>
+
+namespace openpower::pels
+{
+
+/**
+ * @class HostNotifier
+ *
+ * This class handles notifying the host firmware of new PELs.
+ *
+ * It uses the Repository class's subscription feature to be
+ * notified about new PELs.
+ *
+ * Some PELs do not need to be sent - see enqueueRequired() and
+ * notifyRequired().
+ *
+ * The high level good path flow for sending a single PEL is:
+ *
+ * 1) Send the ID and size of the new PEL to the host.
+ * - The command response is asynchronous.
+ *
+ * 2) The host reads the raw PEL data (outside of this class).
+ *
+ * 3) The host sends the PEL to the OS, and then sends an AckPEL
+ * PLDM command to the PLDM daemon, who makes a D-Bus method
+ * call to this daemon, which calls HostNotifer::ackPEL().
+ *
+ * After this, a PEL never needs to be sent again, but if the
+ * host is rebooted before the ack comes it will.
+ *
+ * The host firmware has a finite amount of space to store PELs before
+ * sending to the OS, and it's possible it will fill up. In this case,
+ * the AckPEL command will have a special response that will tell the
+ * PLDM daemon to call HostReject D-Bus method on this daemon instead
+ * which will invoke HostNotifier::setHostFull(). This will stop new
+ * PELs from being sent, and the first PEL that hits this will have
+ * a timer set to retry again later.
+ */
+class HostNotifier
+{
+ public:
+ HostNotifier() = delete;
+ HostNotifier(const HostNotifier&) = delete;
+ HostNotifier& operator=(const HostNotifier&) = delete;
+ HostNotifier(HostNotifier&&) = delete;
+ HostNotifier& operator=(HostNotifier&&) = delete;
+
+ /**
+ * @brief Constructor
+ *
+ * @param[in] repo - The PEL repository object
+ * @param[in] dataIface - The data interface object
+ * @param[in] hostIface - The host interface object
+ */
+ HostNotifier(Repository& repo, DataInterfaceBase& dataIface,
+ std::unique_ptr<HostInterface> hostIface);
+
+ /**
+ * @brief Destructor
+ */
+ ~HostNotifier();
+
+ /**
+ * @brief Returns the PEL queue size.
+ *
+ * For testing.
+ *
+ * @return size_t - The queue size
+ */
+ size_t queueSize() const
+ {
+ return _pelQueue.size();
+ }
+
+ /**
+ * @brief Specifies if the PEL needs to go onto the queue to be
+ * set to the host.
+ *
+ * Only returns false if:
+ * - Already acked by the host (or they didn't like it)
+ * - Hidden and the HMC already got it
+ * - The 'do not report to host' bit is set
+ *
+ * @param[in] id - The PEL ID
+ *
+ * @return bool - If enqueue is required
+ */
+ bool enqueueRequired(uint32_t id) const;
+
+ /**
+ * @brief If the host still needs to be notified of the PEL
+ * at the time of the notification.
+ *
+ * Only returns false if:
+ * - Already acked by the host
+ * - It's hidden, and the HMC already got or will get it.
+ *
+ * @param[in] id - The PEL ID
+ *
+ * @return bool - If the notify is required
+ */
+ bool notifyRequired(uint32_t id) const;
+
+ /**
+ * @brief Called when the host sends the 'ack' PLDM command.
+ *
+ * This means the PEL never needs to be sent up again.
+ *
+ * If the host was previously full, it is also an indication
+ * it no longer is.
+ *
+ * @param[in] id - The PEL ID
+ */
+ void ackPEL(uint32_t id);
+
+ /**
+ * @brief Called when the host does not have room for more
+ * PELs at this time.
+ *
+ * This can happen when an OS isn't running yet, and the
+ * staging area to hold the PELs before sending them up
+ * to the OS is full. This will stop future PEls from being
+ * sent up, as explained below.
+ *
+ * The PEL with this ID will need to be sent again, so its
+ * state is set back to 'new', and it is removed from the list
+ * of already sent PELs.
+ *
+ * A timer will be started, if it isn't already running, to
+ * issue another send in the hopes that space has been freed
+ * up by then (Receiving an ackPEL response is also an
+ * indication of this if there happened to have been other
+ * PELs in flight).
+ *
+ * @param[in] id - The PEL ID
+ */
+ void setHostFull(uint32_t id);
+
+ /**
+ * @brief Called when the host receives a malformed PEL.
+ *
+ * Ideally this will never happen, as the Repository
+ * class already purges malformed PELs.
+ *
+ * The PEL should never be sent up again.
+ *
+ * @param[in] id - The PEL ID
+ */
+ void setBadPEL(uint32_t id);
+
+ private:
+ /**
+ * @brief This function gets called by the Repository class
+ * when a new PEL is added to it.
+ *
+ * This function puts the PEL on the queue to be sent up if it
+ * needs it, and possibly dispatch the send if the conditions call
+ * for it.
+ *
+ * @param[in] pel - The new PEL
+ */
+ void newLogCallback(const PEL& pel);
+
+ /**
+ * @brief This function runs on every existing PEL at startup
+ * and puts the PEL on the queue to send if necessary.
+ *
+ * @param[in] pel - The PEL
+ *
+ * @return bool - This is an indicator to the Repository::for_each
+ * function to traverse every PEL. Always false.
+ */
+ bool addPELToQueue(const PEL& pel);
+
+ /**
+ * @brief Takes the first PEL from the queue that needs to be
+ * sent, and issues the send if conditions are right.
+ */
+ void doNewLogNotify();
+
+ /**
+ * @brief Creates the event object to handle sending the PLDM
+ * command from the event loop.
+ */
+ void scheduleDispatch();
+
+ /**
+ * @brief Kicks off the PLDM send, but called from the event
+ * loop.
+ *
+ * @param[in] source - The event source object
+ */
+ void dispatch(sdeventplus::source::EventBase& source);
+
+ /**
+ * @brief Called when the host changes state.
+ *
+ * If the new state is host up and there are PELs to send, it
+ * will trigger the first command. If the new state is off, then
+ * it will transfer any PELs that were sent but not acked yet back
+ * to the queue to be sent again.
+ *
+ * @param[in] hostUp - The new host state
+ */
+ void hostStateChange(bool hostUp);
+
+ /**
+ * @brief The callback function invoked after the asynchronous
+ * PLDM receive function is complete.
+ *
+ * If the command was successful, the state of that PEL will
+ * be set to 'sent', and the next send will be triggered.
+ *
+ * If the command failed, a retry timer will be started so it
+ * can be sent again.
+ *
+ * @param[in] status - The response status
+ */
+ void commandResponse(ResponseStatus status);
+
+ /**
+ * @brief The function called when the command failure retry
+ * time is up.
+ *
+ * It will issue a send of the previous PEL and increment the
+ * retry count.
+ */
+ void retryTimerExpired();
+
+ /**
+ * @brief The function called when the 'host full' retry timer
+ * expires.
+ *
+ * This will re-issue a command to try again with the PEL at
+ * the front of the queue.
+ */
+ void hostFullTimerExpired();
+
+ /**
+ * @brief Stops an in progress command
+ *
+ * In progress meaning after the send but before the response.
+ */
+ void stopCommand();
+
+ /**
+ * @brief The PEL repository object
+ */
+ Repository& _repo;
+
+ /**
+ * @brief The data interface object
+ */
+ DataInterfaceBase& _dataIface;
+
+ /**
+ * @brief Base class pointer for the host command interface
+ */
+ std::unique_ptr<HostInterface> _hostIface;
+
+ /**
+ * @brief The list of PEL IDs that need to be sent.
+ */
+ std::deque<uint32_t> _pelQueue;
+
+ /**
+ * @brief The list of IDs that were sent, but not acked yet.
+ *
+ * These move back to _pelQueue on a power off.
+ */
+ std::vector<uint32_t> _sentPELs;
+
+ /**
+ * @brief The ID the PEL where the notification has
+ * been kicked off but the asynchronous response
+ * hasn't been received yet.
+ */
+ uint32_t _inProgressPEL = 0;
+
+ /**
+ * @brief The command retry count
+ */
+ size_t _retryCount = 0;
+
+ /**
+ * @brief Indicates if the host has said it is full and does not
+ * currently have the space for more PELs.
+ */
+ bool _hostFull = false;
+
+ /**
+ * @brief The command retry timer.
+ */
+ sdeventplus::utility::Timer<sdeventplus::ClockId::Monotonic> _retryTimer;
+
+ /**
+ * @brief The host full timer, used to retry sending a PEL if the host
+ * said it is full.
+ */
+ sdeventplus::utility::Timer<sdeventplus::ClockId::Monotonic> _hostFullTimer;
+
+ /**
+ * @brief The object used to dispatch a new PEL send from the
+ * event loop, so the calling function can be returned from
+ * first.
+ */
+ std::unique_ptr<sdeventplus::source::Defer> _dispatcher;
+};
+
+} // namespace openpower::pels
diff --git a/extensions/openpower-pels/json_utils.cpp b/extensions/openpower-pels/json_utils.cpp
new file mode 100644
index 0000000..871bd32
--- /dev/null
+++ b/extensions/openpower-pels/json_utils.cpp
@@ -0,0 +1,207 @@
+/**
+ * Copyright © 2019 IBM Corporation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#include "json_utils.hpp"
+
+#include <stdio.h>
+
+#include <cstring>
+#include <sstream>
+#include <string>
+
+namespace openpower
+{
+namespace pels
+{
+
+std::string escapeJSON(const std::string& input)
+{
+ std::string output;
+ output.reserve(input.length());
+
+ for (const auto c : input)
+ {
+ switch (c)
+ {
+ case '"':
+ output += "\\\"";
+ break;
+ case '/':
+ output += "\\/";
+ break;
+ case '\b':
+ output += "\\b";
+ break;
+ case '\f':
+ output += "\\f";
+ break;
+ case '\n':
+ output += "\\n";
+ break;
+ case '\r':
+ output += "\\r";
+ break;
+ case '\t':
+ output += "\\t";
+ break;
+ case '\\':
+ output += "\\\\";
+ break;
+ default:
+ output += c;
+ break;
+ }
+ }
+
+ return output;
+}
+char* dumpHex(const void* data, size_t size)
+{
+ const int symbolSize = 100;
+ std::string jsonIndent(indentLevel, 0x20);
+ jsonIndent.append("\"");
+ char* buffer = (char*)calloc(10 * size, sizeof(char));
+ char* symbol = (char*)calloc(symbolSize, sizeof(char));
+ char ascii[17];
+ size_t i, j;
+ ascii[16] = '\0';
+ for (i = 0; i < size; ++i)
+ {
+ if (i % 16 == 0)
+ {
+ strcat(buffer, jsonIndent.c_str());
+ }
+ snprintf(symbol, symbolSize, "%02X ", ((unsigned char*)data)[i]);
+ strcat(buffer, symbol);
+ memset(symbol, 0, strlen(symbol));
+ if (((unsigned char*)data)[i] >= ' ' &&
+ ((unsigned char*)data)[i] <= '~')
+ {
+ ascii[i % 16] = ((unsigned char*)data)[i];
+ }
+ else
+ {
+ ascii[i % 16] = '.';
+ }
+ if ((i + 1) % 8 == 0 || i + 1 == size)
+ {
+ std::string asciiString(ascii);
+ asciiString = escapeJSON(asciiString);
+ const char* asciiToPrint = asciiString.c_str();
+ strcat(buffer, " ");
+ if ((i + 1) % 16 == 0)
+ {
+ if (i + 1 != size)
+ {
+ snprintf(symbol, symbolSize, "| %s\",\n", asciiToPrint);
+ }
+ else
+ {
+ snprintf(symbol, symbolSize, "| %s\"\n", asciiToPrint);
+ }
+ strcat(buffer, symbol);
+ memset(symbol, 0, strlen(symbol));
+ }
+ else if (i + 1 == size)
+ {
+ ascii[(i + 1) % 16] = '\0';
+ if ((i + 1) % 16 <= 8)
+ {
+ strcat(buffer, " ");
+ }
+ for (j = (i + 1) % 16; j < 16; ++j)
+ {
+ strcat(buffer, " ");
+ }
+ std::string asciiString2(ascii);
+ asciiString2 = escapeJSON(asciiString2);
+ asciiToPrint = asciiString2.c_str();
+ snprintf(symbol, symbolSize, "| %s\"\n", asciiToPrint);
+ strcat(buffer, symbol);
+ memset(symbol, 0, strlen(symbol));
+ }
+ }
+ }
+ free(symbol);
+ return buffer;
+}
+
+void jsonInsert(std::string& jsonStr, const std::string& fieldName,
+ std::string fieldValue, uint8_t indentCount)
+{
+ const int8_t spacesToAppend =
+ colAlign - (indentCount * indentLevel) - fieldName.length() - 3;
+ const std::string jsonIndent(indentCount * indentLevel, 0x20);
+ jsonStr.append(jsonIndent + "\"" + fieldName + "\":");
+ if (spacesToAppend >= 0)
+ {
+ jsonStr.append(spacesToAppend, 0x20);
+ }
+ else
+ {
+ jsonStr.append(1, 0x20);
+ }
+ jsonStr.append("\"" + fieldValue + "\",\n");
+}
+
+void jsonInsertArray(std::string& jsonStr, const std::string& fieldName,
+ std::vector<std::string>& values, uint8_t indentCount)
+{
+ const std::string jsonIndent(indentCount * indentLevel, 0x20);
+ if (!values.empty())
+ {
+ jsonStr.append(jsonIndent + "\"" + fieldName + "\": [\n");
+ for (size_t i = 0; i < values.size(); i++)
+ {
+ jsonStr.append(colAlign, 0x20);
+ if (i == values.size() - 1)
+ {
+ jsonStr.append("\"" + values[i] + "\"\n");
+ }
+ else
+ {
+ jsonStr.append("\"" + values[i] + "\",\n");
+ }
+ }
+ jsonStr.append(jsonIndent + "],\n");
+ }
+ else
+ {
+ const int8_t spacesToAppend =
+ colAlign - (indentCount * indentLevel) - fieldName.length() - 3;
+ jsonStr.append(jsonIndent + "\"" + fieldName + "\":");
+ if (spacesToAppend > 0)
+ {
+ jsonStr.append(spacesToAppend, 0x20);
+ }
+ else
+ {
+ jsonStr.append(1, 0x20);
+ }
+ jsonStr.append("[],\n");
+ }
+}
+
+std::string trimEnd(std::string s)
+{
+ const char* t = " \t\n\r\f\v";
+ if (s.find_last_not_of(t) != std::string::npos)
+ {
+ s.erase(s.find_last_not_of(t) + 1);
+ }
+ return s;
+}
+} // namespace pels
+} // namespace openpower
diff --git a/extensions/openpower-pels/json_utils.hpp b/extensions/openpower-pels/json_utils.hpp
new file mode 100644
index 0000000..e122f47
--- /dev/null
+++ b/extensions/openpower-pels/json_utils.hpp
@@ -0,0 +1,87 @@
+#pragma once
+
+#include <ctype.h>
+#include <stdio.h>
+
+#include <fstream>
+#include <iomanip>
+#include <iostream>
+#include <string>
+#include <vector>
+
+namespace openpower
+{
+namespace pels
+{
+const uint8_t indentLevel = 4;
+const uint8_t colAlign = 32;
+/**
+ * @brief escape json - use it for PEL hex dumps.
+ * @param[in] std::string - the unescaped JSON as a string literal
+ * @return std::string - escaped JSON string literal
+ */
+std::string escapeJSON(const std::string& input);
+
+/**
+ * @brief get hex dump for PEL section in json format.
+ * @param[in] const void* - Raw PEL data
+ * @param[i] size_t - size of Raw PEL
+ * @return char * - the Hex dump
+ */
+char* dumpHex(const void* data, size_t size);
+
+/**
+ * @brief Inserts key-value into a JSON string
+ *
+ * @param[in] jsonStr - The JSON string
+ * @param[in] fieldName - The JSON key to insert
+ * @param[in] fieldValue - The JSON value to insert
+ * @param[in] indentCount - Indent count for the line
+ */
+void jsonInsert(std::string& jsonStr, const std::string& fieldName,
+ std::string fieldValue, uint8_t indentCount);
+
+/**
+ * @brief Inserts key-value array into a JSON string
+ *
+ * @param[in] jsonStr - The JSON string
+ * @param[in] fieldName - The JSON key to insert
+ * @param[in] values - The JSON array to insert
+ * @param[in] indentCount - Indent count for the line
+ */
+void jsonInsertArray(std::string& jsonStr, const std::string& fieldName,
+ std::vector<std::string>& values, uint8_t indentCount);
+
+/**
+ * @brief Converts an integer to a formatted string
+ * @param[in] format - the format of output string
+ * @param[in] number - the integer to convert
+ * @return std::string - the formatted string
+ */
+template <typename T>
+std::string getNumberString(const char* format, T number)
+{
+ char* value = nullptr;
+ std::string numString;
+
+ static_assert(std::is_integral<T>::value, "Integral required.");
+
+ int len = asprintf(&value, format, number);
+ if (len)
+ {
+ numString = value;
+ }
+ free(value);
+
+ return numString;
+}
+
+/**
+ * @brief helper function to trim trailing whitespaces
+ * @return std::string - trimmed string
+ * @param[in] std::string - string to trim
+ */
+std::string trimEnd(std::string s);
+
+} // namespace pels
+} // namespace openpower
diff --git a/extensions/openpower-pels/log_id.cpp b/extensions/openpower-pels/log_id.cpp
new file mode 100644
index 0000000..debcc99
--- /dev/null
+++ b/extensions/openpower-pels/log_id.cpp
@@ -0,0 +1,112 @@
+/**
+ * Copyright © 2019 IBM Corporation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#include "log_id.hpp"
+
+#include "paths.hpp"
+
+#include <chrono>
+#include <filesystem>
+#include <fstream>
+#include <phosphor-logging/log.hpp>
+
+namespace openpower
+{
+namespace pels
+{
+
+namespace fs = std::filesystem;
+using namespace phosphor::logging;
+
+constexpr uint32_t startingLogID = 1;
+constexpr uint32_t bmcLogIDPrefix = 0x50000000;
+
+namespace detail
+{
+
+uint32_t addLogIDPrefix(uint32_t id)
+{
+ // If redundant BMCs are ever a thing, may need a different prefix.
+ return (id & 0x00FFFFFF) | bmcLogIDPrefix;
+}
+
+uint32_t getTimeBasedLogID()
+{
+ using namespace std::chrono;
+
+ // Use 3 bytes of the nanosecond count since the epoch.
+ uint32_t id =
+ duration_cast<nanoseconds>(system_clock::now().time_since_epoch())
+ .count();
+
+ return addLogIDPrefix(id);
+}
+
+} // namespace detail
+
+uint32_t generatePELID()
+{
+ // Note: there isn't a need to be thread safe.
+
+ static std::string idFilename;
+ if (idFilename.empty())
+ {
+ idFilename = getPELIDFile();
+ }
+
+ uint32_t id = 0;
+
+ if (!fs::exists(idFilename))
+ {
+ auto path = fs::path(idFilename).parent_path();
+ if (!fs::exists(path))
+ {
+ fs::create_directories(path);
+ }
+
+ id = startingLogID;
+ }
+ else
+ {
+ std::ifstream idFile{idFilename};
+ idFile >> id;
+ if (idFile.fail())
+ {
+ // Just make up an ID
+ log<level::ERR>("Unable to read PEL ID File!");
+ return detail::getTimeBasedLogID();
+ }
+ }
+
+ // Wrapping shouldn't be a problem, but check anyway
+ if (id == 0x00FFFFFF)
+ {
+ id = startingLogID;
+ }
+
+ std::ofstream idFile{idFilename};
+ idFile << (id + 1);
+ if (idFile.fail())
+ {
+ // Just make up an ID so we don't reuse one next time
+ log<level::ERR>("Unable to write PEL ID File!");
+ return detail::getTimeBasedLogID();
+ }
+
+ return detail::addLogIDPrefix(id);
+}
+
+} // namespace pels
+} // namespace openpower
diff --git a/extensions/openpower-pels/log_id.hpp b/extensions/openpower-pels/log_id.hpp
new file mode 100644
index 0000000..21f04eb
--- /dev/null
+++ b/extensions/openpower-pels/log_id.hpp
@@ -0,0 +1,47 @@
+#pragma once
+
+#include <cstdint>
+
+namespace openpower
+{
+namespace pels
+{
+
+namespace detail
+{
+
+/**
+ * @brief Adds the 1 byte log creator prefix to the log ID
+ *
+ * @param[in] id - the ID to add it to
+ *
+ * @return - the full log ID
+ */
+uint32_t addLogIDPrefix(uint32_t id);
+
+/**
+ * @brief Generates a PEL ID based on the current time.
+ *
+ * Used for error scenarios where the normal method doesn't
+ * work in order to get a unique ID still.
+ *
+ * @return A unique log ID.
+ */
+uint32_t getTimeBasedLogID();
+
+} // namespace detail
+
+/**
+ * @brief Generates a unique PEL log entry ID every time
+ * it is called.
+ *
+ * This ID is used at offset 0x2C in the Private Header
+ * section of a PEL. For single BMC systems, it must
+ * start with 0x50.
+ *
+ * @return uint32_t - The log ID
+ */
+uint32_t generatePELID();
+
+} // namespace pels
+} // namespace openpower
diff --git a/extensions/openpower-pels/manager.cpp b/extensions/openpower-pels/manager.cpp
new file mode 100644
index 0000000..4eb6772
--- /dev/null
+++ b/extensions/openpower-pels/manager.cpp
@@ -0,0 +1,356 @@
+/**
+ * Copyright © 2019 IBM Corporation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#include "manager.hpp"
+
+#include "additional_data.hpp"
+#include "json_utils.hpp"
+#include "pel.hpp"
+
+#include <unistd.h>
+
+#include <filesystem>
+#include <fstream>
+#include <xyz/openbmc_project/Common/error.hpp>
+
+namespace openpower
+{
+namespace pels
+{
+
+using namespace phosphor::logging;
+namespace fs = std::filesystem;
+namespace rg = openpower::pels::message;
+
+namespace common_error = sdbusplus::xyz::openbmc_project::Common::Error;
+
+namespace additional_data
+{
+constexpr auto rawPEL = "RAWPEL";
+constexpr auto esel = "ESEL";
+} // namespace additional_data
+
+void Manager::create(const std::string& message, uint32_t obmcLogID,
+ uint64_t timestamp, Entry::Level severity,
+ const std::vector<std::string>& additionalData,
+ const std::vector<std::string>& associations)
+{
+ AdditionalData ad{additionalData};
+
+ // If a PEL was passed in via a filename or in an ESEL,
+ // use that. Otherwise, create one.
+ auto rawPelPath = ad.getValue(additional_data::rawPEL);
+ if (rawPelPath)
+ {
+ addRawPEL(*rawPelPath, obmcLogID);
+ }
+ else
+ {
+ auto esel = ad.getValue(additional_data::esel);
+ if (esel)
+ {
+ addESELPEL(*esel, obmcLogID);
+ }
+ else
+ {
+ createPEL(message, obmcLogID, timestamp, severity, additionalData,
+ associations);
+ }
+ }
+}
+
+void Manager::addRawPEL(const std::string& rawPelPath, uint32_t obmcLogID)
+{
+ if (fs::exists(rawPelPath))
+ {
+ std::ifstream file(rawPelPath, std::ios::in | std::ios::binary);
+
+ auto data = std::vector<uint8_t>(std::istreambuf_iterator<char>(file),
+ std::istreambuf_iterator<char>());
+ if (file.fail())
+ {
+ log<level::ERR>("Filesystem error reading a raw PEL",
+ entry("PELFILE=%s", rawPelPath.c_str()),
+ entry("OBMCLOGID=%d", obmcLogID));
+ // TODO, Decide what to do here. Maybe nothing.
+ return;
+ }
+
+ file.close();
+
+ addPEL(data, obmcLogID);
+ }
+ else
+ {
+ log<level::ERR>("Raw PEL file from BMC event log does not exist",
+ entry("PELFILE=%s", (rawPelPath).c_str()),
+ entry("OBMCLOGID=%d", obmcLogID));
+ }
+}
+
+void Manager::addPEL(std::vector<uint8_t>& pelData, uint32_t obmcLogID)
+{
+
+ auto pel = std::make_unique<openpower::pels::PEL>(pelData, obmcLogID);
+ if (pel->valid())
+ {
+ // PELs created by others still need these fields set by us.
+ pel->assignID();
+ pel->setCommitTime();
+
+ try
+ {
+ _repo.add(pel);
+ }
+ catch (std::exception& e)
+ {
+ // Probably a full or r/o filesystem, not much we can do.
+ log<level::ERR>("Unable to add PEL to Repository",
+ entry("PEL_ID=0x%X", pel->id()));
+ }
+ }
+ else
+ {
+ log<level::ERR>("Invalid PEL received from the host",
+ entry("OBMCLOGID=%d", obmcLogID));
+
+ AdditionalData ad;
+ ad.add("PLID", getNumberString("0x%08X", pel->plid()));
+ ad.add("OBMC_LOG_ID", std::to_string(obmcLogID));
+ ad.add("PEL_SIZE", std::to_string(pelData.size()));
+
+ std::string asciiString;
+ auto src = pel->primarySRC();
+ if (src)
+ {
+ asciiString = (*src)->asciiString();
+ }
+
+ ad.add("SRC", asciiString);
+
+ _eventLogger.log("org.open_power.Logging.Error.BadHostPEL",
+ Entry::Level::Error, ad);
+ }
+}
+
+void Manager::addESELPEL(const std::string& esel, uint32_t obmcLogID)
+{
+ std::vector<uint8_t> data;
+
+ try
+ {
+ data = std::move(eselToRawData(esel));
+ }
+ catch (std::exception& e)
+ {
+ // Try to add it below anyway, so it follows the usual bad data path.
+ log<level::ERR>("Problems converting ESEL string to a byte vector");
+ }
+
+ addPEL(data, obmcLogID);
+}
+
+std::vector<uint8_t> Manager::eselToRawData(const std::string& esel)
+{
+ std::vector<uint8_t> data;
+ std::string byteString;
+
+ // As the eSEL string looks like: "50 48 00 ab ..." there are 3
+ // characters per raw byte, and since the actual PEL data starts
+ // at the 16th byte, the code will grab the PEL data starting at
+ // offset 48 in the string.
+ static constexpr size_t pelStart = 16 * 3;
+
+ if (esel.size() <= pelStart)
+ {
+ log<level::ERR>("ESEL data too short",
+ entry("ESEL_SIZE=%d", esel.size()));
+
+ throw std::length_error("ESEL data too short");
+ }
+
+ for (size_t i = pelStart; i < esel.size(); i += 3)
+ {
+ if (i + 1 < esel.size())
+ {
+ byteString = esel.substr(i, 2);
+ data.push_back(std::stoi(byteString, nullptr, 16));
+ }
+ else
+ {
+ log<level::ERR>("ESEL data too short",
+ entry("ESEL_SIZE=%d", esel.size()));
+ throw std::length_error("ESEL data too short");
+ }
+ }
+
+ return data;
+}
+
+void Manager::erase(uint32_t obmcLogID)
+{
+ Repository::LogID id{Repository::LogID::Obmc(obmcLogID)};
+
+ _repo.remove(id);
+}
+
+bool Manager::isDeleteProhibited(uint32_t obmcLogID)
+{
+ return false;
+}
+
+void Manager::createPEL(const std::string& message, uint32_t obmcLogID,
+ uint64_t timestamp,
+ phosphor::logging::Entry::Level severity,
+ const std::vector<std::string>& additionalData,
+ const std::vector<std::string>& associations)
+{
+ auto entry = _registry.lookup(message, rg::LookupType::name);
+ std::string msg;
+
+ if (entry)
+ {
+ AdditionalData ad{additionalData};
+
+ auto pel = std::make_unique<openpower::pels::PEL>(
+ *entry, obmcLogID, timestamp, severity, ad, *_dataIface);
+
+ _repo.add(pel);
+
+ auto src = pel->primarySRC();
+ if (src)
+ {
+ using namespace std::literals::string_literals;
+ auto id = getNumberString("0x%08X", pel->id());
+ msg = "Created PEL "s + id + " with SRC "s + (*src)->asciiString();
+ while (msg.back() == ' ')
+ {
+ msg.pop_back();
+ }
+ log<level::INFO>(msg.c_str());
+ }
+ }
+ else
+ {
+ // TODO ibm-openbmc/dev/1151: Create a new PEL for this case.
+ // For now, just trace it.
+ msg = "Event not found in PEL message registry: " + message;
+ log<level::INFO>(msg.c_str());
+ }
+}
+
+sdbusplus::message::unix_fd Manager::getPEL(uint32_t pelID)
+{
+ Repository::LogID id{Repository::LogID::Pel(pelID)};
+ std::optional<int> fd;
+
+ try
+ {
+ fd = _repo.getPELFD(id);
+ }
+ catch (std::exception& e)
+ {
+ throw common_error::InternalFailure();
+ }
+
+ if (!fd)
+ {
+ throw common_error::InvalidArgument();
+ }
+
+ scheduleFDClose(*fd);
+
+ return *fd;
+}
+
+void Manager::scheduleFDClose(int fd)
+{
+ _fdCloserEventSource = std::make_unique<sdeventplus::source::Defer>(
+ _logManager.getBus().get_event(),
+ std::bind(std::mem_fn(&Manager::closeFD), this, fd,
+ std::placeholders::_1));
+}
+
+void Manager::closeFD(int fd, sdeventplus::source::EventBase& source)
+{
+ close(fd);
+ _fdCloserEventSource.reset();
+}
+
+std::vector<uint8_t> Manager::getPELFromOBMCID(uint32_t obmcLogID)
+{
+ Repository::LogID id{Repository::LogID::Obmc(obmcLogID)};
+ std::optional<std::vector<uint8_t>> data;
+
+ try
+ {
+ data = _repo.getPELData(id);
+ }
+ catch (std::exception& e)
+ {
+ throw common_error::InternalFailure();
+ }
+
+ if (!data)
+ {
+ throw common_error::InvalidArgument();
+ }
+
+ return *data;
+}
+
+void Manager::hostAck(uint32_t pelID)
+{
+ Repository::LogID id{Repository::LogID::Pel(pelID)};
+
+ if (!_repo.hasPEL(id))
+ {
+ throw common_error::InvalidArgument();
+ }
+
+ if (_hostNotifier)
+ {
+ _hostNotifier->ackPEL(pelID);
+ }
+}
+
+void Manager::hostReject(uint32_t pelID, RejectionReason reason)
+{
+ Repository::LogID id{Repository::LogID::Pel(pelID)};
+
+ if (!_repo.hasPEL(id))
+ {
+ throw common_error::InvalidArgument();
+ }
+
+ if (reason == RejectionReason::BadPEL)
+ {
+ AdditionalData data;
+ data.add("BAD_ID", getNumberString("0x%08X", pelID));
+ _eventLogger.log("org.open_power.Logging.Error.SentBadPELToHost",
+ Entry::Level::Informational, data);
+ if (_hostNotifier)
+ {
+ _hostNotifier->setBadPEL(pelID);
+ }
+ }
+ else if ((reason == RejectionReason::HostFull) && _hostNotifier)
+ {
+ _hostNotifier->setHostFull(pelID);
+ }
+}
+
+} // namespace pels
+} // namespace openpower
diff --git a/extensions/openpower-pels/manager.hpp b/extensions/openpower-pels/manager.hpp
new file mode 100644
index 0000000..86030d4
--- /dev/null
+++ b/extensions/openpower-pels/manager.hpp
@@ -0,0 +1,281 @@
+#pragma once
+
+#include "config.h"
+
+#include "data_interface.hpp"
+#include "event_logger.hpp"
+#include "host_notifier.hpp"
+#include "log_manager.hpp"
+#include "paths.hpp"
+#include "registry.hpp"
+#include "repository.hpp"
+
+#include <org/open_power/Logging/PEL/server.hpp>
+#include <sdbusplus/server.hpp>
+#include <sdeventplus/event.hpp>
+#include <sdeventplus/source/event.hpp>
+
+namespace openpower
+{
+namespace pels
+{
+
+using PELInterface = sdbusplus::server::object::object<
+ sdbusplus::org::open_power::Logging::server::PEL>;
+
+/**
+ * @brief PEL manager object
+ */
+class Manager : public PELInterface
+{
+ public:
+ Manager() = delete;
+ ~Manager() = default;
+ Manager(const Manager&) = default;
+ Manager& operator=(const Manager&) = default;
+ Manager(Manager&&) = default;
+ Manager& operator=(Manager&&) = default;
+
+ /**
+ * @brief constructor
+ *
+ * @param[in] logManager - internal::Manager object
+ * @param[in] dataIface - The data interface object
+ * @param[in] creatorFunc - The function that EventLogger will
+ * use for creating event logs
+ */
+ Manager(phosphor::logging::internal::Manager& logManager,
+ std::unique_ptr<DataInterfaceBase> dataIface,
+ EventLogger::LogFunction creatorFunc) :
+ PELInterface(logManager.getBus(), OBJ_LOGGING),
+ _logManager(logManager),
+ _eventLogger(logManager.getBus().get_event(), std::move(creatorFunc)),
+ _repo(getPELRepoPath()),
+ _registry(getMessageRegistryPath() / message::registryFileName),
+ _dataIface(std::move(dataIface))
+ {
+ }
+
+ /**
+ * @brief constructor that enables host notification
+ *
+ * @param[in] logManager - internal::Manager object
+ * @param[in] dataIface - The data interface object
+ * @param[in] creatorFunc - The function that EventLogger will
+ * use for creating event logs
+ * @param[in] hostIface - The hostInterface object
+ */
+ Manager(phosphor::logging::internal::Manager& logManager,
+ std::unique_ptr<DataInterfaceBase> dataIface,
+ EventLogger::LogFunction creatorFunc,
+ std::unique_ptr<HostInterface> hostIface) :
+ Manager(logManager, std::move(dataIface), std::move(creatorFunc))
+ {
+ _hostNotifier = std::make_unique<HostNotifier>(
+ _repo, *(_dataIface.get()), std::move(hostIface));
+ }
+
+ /**
+ * @brief Creates a PEL based on the OpenBMC event log contents. If
+ * a PEL was passed in via the RAWPEL specifier in the
+ * additionalData parameter, use that instead.
+ *
+ * @param[in] message - the event log message property
+ * @param[in] obmcLogID - the corresponding OpenBMC event log id
+ * @param[in] timestamp - the Timestamp property
+ * @param[in] severity - the event log severity
+ * @param[in] additionalData - the AdditionalData property
+ * @param[in] associations - the Associations property
+ */
+ void create(const std::string& message, uint32_t obmcLogID,
+ uint64_t timestamp, phosphor::logging::Entry::Level severity,
+ const std::vector<std::string>& additionalData,
+ const std::vector<std::string>& associations);
+
+ /**
+ * @brief Erase a PEL based on its OpenBMC event log ID
+ *
+ * @param[in] obmcLogID - the corresponding OpenBMC event log id
+ */
+ void erase(uint32_t obmcLogID);
+
+ /** @brief Says if an OpenBMC event log may not be manually deleted at this
+ * time because its corresponding PEL cannot be.
+ *
+ * There are PEL retention policies that can prohibit the manual deletion
+ * of PELs (and therefore OpenBMC event logs).
+ *
+ * @param[in] obmcLogID - the OpenBMC event log ID
+ * @return bool - true if prohibited
+ */
+ bool isDeleteProhibited(uint32_t obmcLogID);
+
+ /**
+ * @brief Return a file descriptor to the raw PEL data
+ *
+ * Throws InvalidArgument if the PEL ID isn't found,
+ * and InternalFailure if anything else fails.
+ *
+ * @param[in] pelID - The PEL ID to get the data for
+ *
+ * @return unix_fd - File descriptor to the file that contains the PEL
+ */
+ sdbusplus::message::unix_fd getPEL(uint32_t pelID) override;
+
+ /**
+ * @brief Returns data for the PEL corresponding to an OpenBMC
+ * event log.
+ *
+ * @param[in] obmcLogID - The OpenBMC event log ID
+ *
+ * @return vector<uint8_t> - The raw PEL data
+ */
+ std::vector<uint8_t> getPELFromOBMCID(uint32_t obmcLogID) override;
+
+ /**
+ * @brief The D-Bus method called when a host successfully processes
+ * a PEL.
+ *
+ * This D-Bus method is called from the PLDM daemon when they get an
+ * 'Ack PEL' PLDM message from the host, which indicates the host
+ * firmware successfully sent it to the OS and this code doesn't need
+ * to send it to the host again.
+ *
+ * @param[in] pelID - The PEL ID
+ */
+ void hostAck(uint32_t pelID) override;
+
+ /**
+ * @brief D-Bus method called when the host rejects a PEL.
+ *
+ * This D-Bus method is called from the PLDM daemon when they get an
+ * 'Ack PEL' PLDM message from the host with a payload that says
+ * something when wrong.
+ *
+ * The choices are either:
+ * * Host Full - The host's staging area is full - try again later
+ * * Malrformed PEL - The host received an invalid PEL
+ *
+ * @param[in] pelID - The PEL ID
+ * @param[in] reason - One of the above two reasons
+ */
+ void hostReject(uint32_t pelID, RejectionReason reason) override;
+
+ /**
+ * @brief Converts the ESEL field in an OpenBMC event log to a
+ * vector of uint8_ts that just contains the PEL data.
+ *
+ * That data string looks like: "50 48 00 ab ..."
+ *
+ * Throws an exception on any failures.
+ *
+ * @param[in] esel - The ESEL string
+ *
+ * @return std::vector<uint8_t> - The contained PEL data
+ */
+ static std::vector<uint8_t> eselToRawData(const std::string& esel);
+
+ private:
+ /**
+ * @brief Adds a received raw PEL to the PEL repository
+ *
+ * @param[in] rawPelPath - The path to the file that contains the
+ * raw PEL.
+ * @param[in] obmcLogID - the corresponding OpenBMC event log id
+ */
+ void addRawPEL(const std::string& rawPelPath, uint32_t obmcLogID);
+
+ /**
+ * @brief Creates a PEL based on the OpenBMC event log contents.
+ *
+ * @param[in] message - The event log message property
+ * @param[in] obmcLogID - the corresponding OpenBMC event log id
+ * @param[in] timestamp - The timestamp property
+ * @param[in] severity - The event log severity
+ * @param[in] additionalData - The AdditionalData property
+ * @param[in] associations - The associations property
+ */
+ void createPEL(const std::string& message, uint32_t obmcLogID,
+ uint64_t timestamp, phosphor::logging::Entry::Level severity,
+ const std::vector<std::string>& additionalData,
+ const std::vector<std::string>& associations);
+
+ /**
+ * @brief Schedules a close of the file descriptor to occur from
+ * the event loop.
+ *
+ * Uses sd_event_add_defer
+ *
+ * @param[in] fd - The file descriptor to close
+ */
+ void scheduleFDClose(int fd);
+
+ /**
+ * @brief Closes the file descriptor passed in.
+ *
+ * This is called from the event loop to close FDs returned
+ * from getPEL().
+ *
+ * @param[in] fd - The file descriptor to close
+ * @param[in] source - The event source object used
+ */
+ void closeFD(int fd, sdeventplus::source::EventBase& source);
+
+ /**
+ * @brief Adds a PEL to the repository given its data
+ *
+ * @param[in] pelData - The PEL to add as a vector of uint8_ts
+ * @param[in] obmcLogID - the OpenBMC event log ID
+ */
+ void addPEL(std::vector<uint8_t>& pelData, uint32_t obmcLogID);
+
+ /**
+ * @brief Adds the PEL stored in the ESEL field of the AdditionalData
+ * property of an OpenBMC event log to the repository.
+ *
+ * @param[in] esel - The ESEL AdditionalData contents
+ * @param[in] obmcLogID - The OpenBMC event log ID
+ */
+ void addESELPEL(const std::string& esel, uint32_t obmcLogID);
+
+ /**
+ * @brief Reference to phosphor-logging's Manager class
+ */
+ phosphor::logging::internal::Manager& _logManager;
+
+ /**
+ * @brief Handles creating event logs/PELs from within
+ * the PEL extension code
+ */
+ EventLogger _eventLogger;
+
+ /**
+ * @brief The PEL repository object
+ */
+ Repository _repo;
+
+ /**
+ * @brief The PEL message registry object
+ */
+ message::Registry _registry;
+
+ /**
+ * @brief The API the PEL sections use to gather data
+ */
+ std::unique_ptr<DataInterfaceBase> _dataIface;
+
+ /**
+ * @brief The HostNotifier object used for telling the
+ * host about new PELs
+ */
+ std::unique_ptr<HostNotifier> _hostNotifier;
+
+ /**
+ * @brief The event source for closing a PEL file descriptor after
+ * it has been returned from the getPEL D-Bus method.
+ */
+ std::unique_ptr<sdeventplus::source::Defer> _fdCloserEventSource;
+};
+
+} // namespace pels
+} // namespace openpower
diff --git a/extensions/openpower-pels/mru.cpp b/extensions/openpower-pels/mru.cpp
new file mode 100644
index 0000000..a79c026
--- /dev/null
+++ b/extensions/openpower-pels/mru.cpp
@@ -0,0 +1,68 @@
+/**
+ * Copyright © 2019 IBM Corporation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#include "mru.hpp"
+
+#include <phosphor-logging/log.hpp>
+
+namespace openpower
+{
+namespace pels
+{
+namespace src
+{
+
+using namespace phosphor::logging;
+
+MRU::MRU(Stream& pel)
+{
+ pel >> _type >> _size >> _flags >> _reserved4B;
+
+ size_t numMRUs = _flags & 0xF;
+
+ for (size_t i = 0; i < numMRUs; i++)
+ {
+ MRUCallout mru;
+ pel >> mru.priority;
+ pel >> mru.id;
+ _mrus.push_back(std::move(mru));
+ }
+
+ size_t actualSize = sizeof(_type) + sizeof(_size) + sizeof(_flags) +
+ sizeof(_reserved4B) +
+ (sizeof(MRUCallout) * _mrus.size());
+ if (_size != actualSize)
+ {
+ log<level::WARNING>("MRU callout section in PEL has listed size that "
+ "doesn't match actual size",
+ entry("SUBSTRUCTURE_SIZE=%lu", _size),
+ entry("NUM_MRUS=%lu", _mrus.size()),
+ entry("ACTUAL_SIZE=%lu", actualSize));
+ }
+}
+
+void MRU::flatten(Stream& pel) const
+{
+ pel << _type << _size << _flags << _reserved4B;
+
+ for (auto& mru : _mrus)
+ {
+ pel << mru.priority;
+ pel << mru.id;
+ }
+}
+} // namespace src
+} // namespace pels
+} // namespace openpower
diff --git a/extensions/openpower-pels/mru.hpp b/extensions/openpower-pels/mru.hpp
new file mode 100644
index 0000000..e2293f1
--- /dev/null
+++ b/extensions/openpower-pels/mru.hpp
@@ -0,0 +1,122 @@
+#pragma once
+
+#include "stream.hpp"
+
+namespace openpower
+{
+namespace pels
+{
+namespace src
+{
+
+/**
+ * @class MRU
+ *
+ * This represents the MRU (Manufacturing Replaceable Unit)
+ * substructure in the callout subsection of the SRC PEL section.
+ *
+ * Manufacturing replaceable units have a finer granularity than
+ * a field replaceable unit, such as a chip on a card, and are
+ * intended to be used during manufacturing.
+ *
+ * This substructure can contain up to 128 MRU callouts, each
+ * containing a MRU ID and a callout priority value.
+ */
+class MRU
+{
+ public:
+ /**
+ * @brief A single MRU callout, which contains a priority
+ * and a MRU ID.
+ *
+ * The priority value is the same priority type character
+ * value as in the parent callout structure. For alignment
+ * purposes it is a 4 byte field, though only the LSB contains
+ * the priority value.
+ */
+ struct MRUCallout
+ {
+ uint32_t priority;
+ uint32_t id;
+ };
+
+ MRU() = delete;
+ ~MRU() = default;
+ MRU(const MRU&) = default;
+ MRU& operator=(const MRU&) = default;
+ MRU(MRU&&) = default;
+ MRU& operator=(MRU&&) = default;
+
+ /**
+ * @brief Constructor
+ *
+ * Fills in this class's data fields from the stream.
+ *
+ * @param[in] pel - the PEL data stream
+ */
+ explicit MRU(Stream& pel);
+
+ /**
+ * @brief Flatten the object into the stream
+ *
+ * @param[in] stream - The stream to write to
+ */
+ void flatten(Stream& pel) const;
+
+ /**
+ * @brief Returns the size of this structure when flattened into a PEL
+ *
+ * @return size_t - The size of the section
+ */
+ size_t flattenedSize() const
+ {
+ return _size;
+ }
+
+ /**
+ * @brief Returns the contained MRU callouts.
+ *
+ * @return const std::vector<MRUCallout>& - The MRUs
+ */
+ const std::vector<MRUCallout>& mrus() const
+ {
+ return _mrus;
+ }
+
+ /**
+ * @brief The type identifier value of this structure.
+ */
+ static const uint16_t substructureType = 0x4D52; // "MR"
+
+ private:
+ /**
+ * @brief The callout substructure type field. Will be 'MR'.
+ */
+ uint16_t _type;
+
+ /**
+ * @brief The size of this callout structure.
+ */
+ uint8_t _size;
+
+ /**
+ * @brief The flags byte of this substructure.
+ *
+ * 0x0Y: Y = number of MRU callouts
+ */
+ uint8_t _flags;
+
+ /**
+ * @brief Reserved 4 bytes
+ */
+ uint32_t _reserved4B;
+
+ /*
+ * @brief The MRU callouts
+ */
+ std::vector<MRUCallout> _mrus;
+};
+
+} // namespace src
+} // namespace pels
+} // namespace openpower
diff --git a/extensions/openpower-pels/mtms.cpp b/extensions/openpower-pels/mtms.cpp
new file mode 100644
index 0000000..3ef8e87
--- /dev/null
+++ b/extensions/openpower-pels/mtms.cpp
@@ -0,0 +1,102 @@
+/**
+ * Copyright © 2019 IBM Corporation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#include "mtms.hpp"
+
+namespace openpower
+{
+namespace pels
+{
+
+MTMS::MTMS()
+{
+ memset(_machineTypeAndModel.data(), 0, mtmSize);
+ memset(_serialNumber.data(), 0, snSize);
+}
+
+MTMS::MTMS(const std::string& typeModel, const std::string& serialNumber)
+{
+ memset(_machineTypeAndModel.data(), 0, mtmSize);
+ memset(_serialNumber.data(), 0, snSize);
+
+ // Copy in as much as the fields as possible
+ for (size_t i = 0; i < mtmSize; i++)
+ {
+ if (typeModel.size() > i)
+ {
+ _machineTypeAndModel[i] = typeModel[i];
+ }
+ }
+
+ for (size_t i = 0; i < snSize; i++)
+ {
+ if (serialNumber.size() > i)
+ {
+ _serialNumber[i] = serialNumber[i];
+ }
+ }
+}
+
+MTMS::MTMS(Stream& stream)
+{
+ for (size_t i = 0; i < mtmSize; i++)
+ {
+ stream >> _machineTypeAndModel[i];
+ }
+
+ for (size_t i = 0; i < snSize; i++)
+ {
+ stream >> _serialNumber[i];
+ }
+}
+
+Stream& operator<<(Stream& s, const MTMS& mtms)
+{
+ for (size_t i = 0; i < MTMS::mtmSize; i++)
+ {
+ s << mtms.machineTypeAndModelRaw()[i];
+ }
+
+ for (size_t i = 0; i < MTMS::snSize; i++)
+ {
+ s << mtms.machineSerialNumberRaw()[i];
+ }
+
+ return s;
+}
+
+Stream& operator>>(Stream& s, MTMS& mtms)
+{
+ std::array<uint8_t, MTMS::mtmSize> mtm;
+
+ for (size_t i = 0; i < MTMS::mtmSize; i++)
+ {
+ s >> mtm[i];
+ }
+
+ mtms.setMachineTypeAndModel(mtm);
+
+ std::array<uint8_t, MTMS::snSize> sn;
+ for (size_t i = 0; i < MTMS::snSize; i++)
+ {
+ s >> sn[i];
+ }
+
+ mtms.setMachineSerialNumber(sn);
+
+ return s;
+}
+} // namespace pels
+} // namespace openpower
diff --git a/extensions/openpower-pels/mtms.hpp b/extensions/openpower-pels/mtms.hpp
new file mode 100644
index 0000000..928fb74
--- /dev/null
+++ b/extensions/openpower-pels/mtms.hpp
@@ -0,0 +1,183 @@
+#pragma once
+
+#include "stream.hpp"
+
+#include <string>
+
+namespace openpower
+{
+namespace pels
+{
+
+/** @class MTMS
+ *
+ * (M)achine (T)ype-(M)odel (S)erialNumber
+ *
+ * Represents the PEL's Machine Type / Model / Serial Number
+ * structure, which shows up in multiple places in a PEL.
+ *
+ * It holds 8 bytes for the Type+Model, and 12 for the SN.
+ * Unused bytes are set to 0s.
+ *
+ * The type and model are combined into a single field.
+ */
+class MTMS
+{
+ public:
+ MTMS(const MTMS&) = default;
+ MTMS& operator=(const MTMS&) = default;
+ MTMS(MTMS&&) = default;
+ MTMS& operator=(MTMS&&) = default;
+ ~MTMS() = default;
+
+ enum
+ {
+ mtmSize = 8,
+ snSize = 12
+ };
+
+ /**
+ * @brief Constructor
+ */
+ MTMS();
+
+ /**
+ * @brief Constructor
+ *
+ * @param[in] typeModel - The machine type+model
+ * @param[in] serialNumber - The machine serial number
+ */
+ MTMS(const std::string& typeModel, const std::string& serialNumber);
+
+ /**
+ * @brief Constructor
+ *
+ * Fills in this class's data fields from the stream.
+ *
+ * @param[in] pel - the PEL data stream
+ */
+ explicit MTMS(Stream& stream);
+
+ /**
+ * @brief Returns the raw machine type/model value
+ *
+ * @return std::array<uint8_t, mtmSize>& - The TM value
+ */
+ const std::array<uint8_t, mtmSize>& machineTypeAndModelRaw() const
+ {
+ return _machineTypeAndModel;
+ }
+
+ /**
+ * @brief Sets the machine type and model field
+ *
+ * @param[in] mtm - The new value
+ */
+ void setMachineTypeAndModel(const std::array<uint8_t, mtmSize>& mtm)
+ {
+ _machineTypeAndModel = mtm;
+ }
+
+ /**
+ * @brief Returns the machine type/model value
+ *
+ * @return std::string - The TM value
+ */
+ std::string machineTypeAndModel() const
+ {
+ std::string mtm;
+
+ // Get everything up to the 0s.
+ for (size_t i = 0; (i < mtmSize) && (_machineTypeAndModel[i] != 0); i++)
+ {
+ mtm.push_back(_machineTypeAndModel[i]);
+ }
+
+ return mtm;
+ }
+
+ /**
+ * @brief Returns the raw machine serial number value
+ *
+ * @return std::array<uint8_t, snSize>& - The SN value
+ */
+ const std::array<uint8_t, snSize>& machineSerialNumberRaw() const
+ {
+ return _serialNumber;
+ }
+
+ /**
+ * @brief Sets the machine serial number field
+ *
+ * @param[in] sn - The new value
+ */
+ void setMachineSerialNumber(const std::array<uint8_t, snSize>& sn)
+ {
+ _serialNumber = sn;
+ }
+
+ /**
+ * @brief Returns the machine serial number value
+ *
+ * @return std::string - The SN value
+ */
+ std::string machineSerialNumber() const
+ {
+ std::string sn;
+
+ // Get everything up to the 0s.
+ for (size_t i = 0; (i < snSize) && (_serialNumber[i] != 0); i++)
+ {
+ sn.push_back(_serialNumber[i]);
+ }
+ return sn;
+ }
+
+ /**
+ * @brief Returns the size of the data when flattened
+ *
+ * @return size_t - The size of the data
+ */
+ static constexpr size_t flattenedSize()
+ {
+ return mtmSize + snSize;
+ }
+
+ private:
+ /**
+ * @brief The type+model value
+ *
+ * Of the form TTTT-MMM where:
+ * TTTT = machine type
+ * MMM = machine model
+ */
+ std::array<uint8_t, mtmSize> _machineTypeAndModel;
+
+ /**
+ * @brief Machine Serial Number
+ */
+ std::array<uint8_t, snSize> _serialNumber;
+};
+
+/**
+ * @brief Stream extraction operator for MTMS
+ *
+ * @param[in] s - the stream
+ * @param[out] mtms - the MTMS object
+ *
+ * @return Stream&
+ */
+Stream& operator>>(Stream& s, MTMS& mtms);
+
+/**
+ * @brief Stream insertion operator for MTMS
+ *
+ * @param[out] s - the stream
+ * @param[in] mtms - the MTMS object
+ *
+ * @return Stream&
+ */
+Stream& operator<<(Stream& s, const MTMS& mtms);
+
+} // namespace pels
+} // namespace openpower
diff --git a/extensions/openpower-pels/openpower-pels.mk b/extensions/openpower-pels/openpower-pels.mk
new file mode 100644
index 0000000..e510b42
--- /dev/null
+++ b/extensions/openpower-pels/openpower-pels.mk
@@ -0,0 +1,72 @@
+phosphor_log_manager_SOURCES += \
+ extensions/openpower-pels/entry_points.cpp \
+ extensions/openpower-pels/host_notifier.cpp \
+ extensions/openpower-pels/manager.cpp \
+ extensions/openpower-pels/pldm_interface.cpp \
+ extensions/openpower-pels/repository.cpp \
+ extensions/openpower-pels/user_data.cpp
+
+phosphor_log_manager_LDADD = \
+ libpel.la
+
+phosphor_log_manager_LDFLAGS += \
+ $(LIBPLDM_LIBS)
+
+phosphor_log_manager_CFLAGS = \
+ $(LIBPLDM_CFLAGS)
+
+noinst_LTLIBRARIES = libpel.la
+
+libpel_la_SOURCES = \
+ extensions/openpower-pels/ascii_string.cpp \
+ extensions/openpower-pels/bcd_time.cpp \
+ extensions/openpower-pels/callout.cpp \
+ extensions/openpower-pels/callouts.cpp \
+ extensions/openpower-pels/data_interface.cpp \
+ extensions/openpower-pels/extended_user_header.cpp \
+ extensions/openpower-pels/failing_mtms.cpp \
+ extensions/openpower-pels/fru_identity.cpp \
+ extensions/openpower-pels/generic.cpp \
+ extensions/openpower-pels/json_utils.cpp \
+ extensions/openpower-pels/log_id.cpp \
+ extensions/openpower-pels/mru.cpp \
+ extensions/openpower-pels/mtms.cpp \
+ extensions/openpower-pels/paths.cpp \
+ extensions/openpower-pels/pce_identity.cpp \
+ extensions/openpower-pels/pel.cpp \
+ extensions/openpower-pels/pel_rules.cpp \
+ extensions/openpower-pels/pel_values.cpp \
+ extensions/openpower-pels/private_header.cpp \
+ extensions/openpower-pels/registry.cpp \
+ extensions/openpower-pels/src.cpp \
+ extensions/openpower-pels/section_factory.cpp \
+ extensions/openpower-pels/severity.cpp \
+ extensions/openpower-pels/user_header.cpp
+
+libpel_ldflags = \
+ $(SYSTEMD_LIBS) \
+ $(PHOSPHOR_LOGGING_LIBS) \
+ $(SDBUSPLUS_LIBS) \
+ $(PHOSPHOR_DBUS_INTERFACES_LIBS) \
+ $(SDEVENTPLUS_LIBS) \
+ -lstdc++fs
+
+libpel_la_LIBADD = $(libpel_ldflags)
+
+libpel_cxx_flags = \
+ $(SYSTEMD_CFLAGS) \
+ $(SDBUSPLUS_CFLAGS) \
+ $(SDEVENTPLUS_CFLAGS) \
+ $(PHOSPHOR_DBUS_INTERFACES_CFLAGS)
+
+registrydir = $(datadir)/phosphor-logging/pels/
+registry_DATA = extensions/openpower-pels/registry/message_registry.json
+
+bin_PROGRAMS += peltool
+
+peltool_SOURCES = \
+ extensions/openpower-pels/tools/peltool.cpp \
+ extensions/openpower-pels/user_data.cpp \
+ extensions/openpower-pels/user_data_json.cpp
+peltool_LDADD = libpel.la
+peltool_CXXFLAGS = "-DPELTOOL"
diff --git a/extensions/openpower-pels/paths.cpp b/extensions/openpower-pels/paths.cpp
new file mode 100644
index 0000000..cf8df2e
--- /dev/null
+++ b/extensions/openpower-pels/paths.cpp
@@ -0,0 +1,49 @@
+/**
+ * Copyright © 2019 IBM Corporation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#include "config.h"
+
+#include "paths.hpp"
+
+#include <filesystem>
+
+namespace openpower
+{
+namespace pels
+{
+
+namespace fs = std::filesystem;
+
+fs::path getPELIDFile()
+{
+ fs::path logIDPath{EXTENSION_PERSIST_DIR};
+ logIDPath /= fs::path{"pels"} / fs::path{"pelID"};
+ return logIDPath;
+}
+
+fs::path getPELRepoPath()
+{
+ std::filesystem::path repoPath{EXTENSION_PERSIST_DIR};
+ repoPath /= "pels";
+ return repoPath;
+}
+
+fs::path getMessageRegistryPath()
+{
+ return std::filesystem::path{"/usr/share/phosphor-logging/pels"};
+}
+
+} // namespace pels
+} // namespace openpower
diff --git a/extensions/openpower-pels/paths.hpp b/extensions/openpower-pels/paths.hpp
new file mode 100644
index 0000000..91d8c5f
--- /dev/null
+++ b/extensions/openpower-pels/paths.hpp
@@ -0,0 +1,24 @@
+#pragma once
+#include <filesystem>
+
+namespace openpower
+{
+namespace pels
+{
+
+/**
+ * @brief Returns the path to the PEL ID file
+ */
+std::filesystem::path getPELIDFile();
+
+/**
+ * @brief Returns the path to the PEL repository
+ */
+std::filesystem::path getPELRepoPath();
+
+/**
+ * @brief Returns the path to the message registry JSON file
+ */
+std::filesystem::path getMessageRegistryPath();
+} // namespace pels
+} // namespace openpower
diff --git a/extensions/openpower-pels/pce_identity.cpp b/extensions/openpower-pels/pce_identity.cpp
new file mode 100644
index 0000000..6a6f257
--- /dev/null
+++ b/extensions/openpower-pels/pce_identity.cpp
@@ -0,0 +1,48 @@
+/**
+ * Copyright © 2019 IBM Corporation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#include "pce_identity.hpp"
+
+namespace openpower
+{
+namespace pels
+{
+namespace src
+{
+
+PCEIdentity::PCEIdentity(Stream& pel)
+{
+ pel >> _type >> _size >> _flags >> _mtms;
+
+ // Whatever is left is the enclosure name.
+ if (_size < (4 + _mtms.flattenedSize()))
+ {
+ throw std::runtime_error("PCE identity structure size field too small");
+ }
+
+ size_t pceNameSize = _size - (4 + _mtms.flattenedSize());
+
+ _pceName.resize(pceNameSize);
+ pel >> _pceName;
+}
+
+void PCEIdentity::flatten(Stream& pel) const
+{
+ pel << _type << _size << _flags << _mtms << _pceName;
+}
+
+} // namespace src
+} // namespace pels
+} // namespace openpower
diff --git a/extensions/openpower-pels/pce_identity.hpp b/extensions/openpower-pels/pce_identity.hpp
new file mode 100644
index 0000000..60c878e
--- /dev/null
+++ b/extensions/openpower-pels/pce_identity.hpp
@@ -0,0 +1,125 @@
+#pragma once
+
+#include "mtms.hpp"
+#include "stream.hpp"
+
+namespace openpower
+{
+namespace pels
+{
+namespace src
+{
+
+/**
+ * @class PCEIdentity
+ *
+ * This represents the PCE (Power Controlling Enclosure) Identity
+ * substructure in the callout subsection of the SRC PEL section.
+ *
+ * It contains the name and machine type/model/SN of that enclosure.
+ *
+ * It is only used for errors in an I/O drawer where another enclosure
+ * may control its power. It is not used in BMC errors and so will
+ * never be created by the BMC, but only unflattened in errors it
+ * receives from the host.
+ */
+class PCEIdentity
+{
+ public:
+ PCEIdentity() = delete;
+ ~PCEIdentity() = default;
+ PCEIdentity(const PCEIdentity&) = default;
+ PCEIdentity& operator=(const PCEIdentity&) = default;
+ PCEIdentity(PCEIdentity&&) = default;
+ PCEIdentity& operator=(PCEIdentity&&) = default;
+
+ /**
+ * @brief Constructor
+ *
+ * Fills in this class's data fields from the stream.
+ *
+ * @param[in] pel - the PEL data stream
+ */
+ explicit PCEIdentity(Stream& pel);
+
+ /**
+ * @brief Flatten the object into the stream
+ *
+ * @param[in] stream - The stream to write to
+ */
+ void flatten(Stream& pel) const;
+
+ /**
+ * @brief Returns the size of this structure when flattened into a PEL
+ *
+ * @return size_t - The size of the structure
+ */
+ size_t flattenedSize()
+ {
+ return _size;
+ }
+
+ /**
+ * @brief The type identifier value of this structure.
+ */
+ static const uint16_t substructureType = 0x5045; // "PE"
+
+ /**
+ * @brief Returns the enclosure name
+ *
+ * @return std::string - The enclosure name
+ */
+ std::string enclosureName() const
+ {
+ // _pceName is NULL terminated
+ std::string name{static_cast<const char*>(_pceName.data())};
+ return name;
+ }
+
+ /**
+ * @brief Returns the MTMS sub structure
+ *
+ * @return const MTMS& - The machine type/model/SN structure.
+ */
+ const MTMS& mtms() const
+ {
+ return _mtms;
+ }
+
+ private:
+ /**
+ * @brief The callout substructure type field. Will be 'PE'.
+ */
+ uint16_t _type;
+
+ /**
+ * @brief The size of this callout structure.
+ *
+ * Always a multiple of 4.
+ */
+ uint8_t _size;
+
+ /**
+ * @brief The flags byte of this substructure.
+ *
+ * Always 0 for this structure.
+ */
+ uint8_t _flags;
+
+ /**
+ * @brief The structure that holds the power controlling enclosure's
+ * machine type, model, and serial number.
+ */
+ MTMS _mtms;
+
+ /**
+ * @brief The name of the power controlling enclosure.
+ *
+ * Null terminated and padded with NULLs to a 4 byte boundary.
+ */
+ std::vector<char> _pceName;
+};
+
+} // namespace src
+} // namespace pels
+} // namespace openpower
diff --git a/extensions/openpower-pels/pel.cpp b/extensions/openpower-pels/pel.cpp
new file mode 100644
index 0000000..e560d5e
--- /dev/null
+++ b/extensions/openpower-pels/pel.cpp
@@ -0,0 +1,395 @@
+/**
+ * Copyright © 2019 IBM Corporation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#include "pel.hpp"
+
+#include "bcd_time.hpp"
+#include "extended_user_header.hpp"
+#include "failing_mtms.hpp"
+#include "json_utils.hpp"
+#include "log_id.hpp"
+#include "pel_rules.hpp"
+#include "pel_values.hpp"
+#include "section_factory.hpp"
+#include "src.hpp"
+#include "stream.hpp"
+#include "user_data_formats.hpp"
+
+#include <iostream>
+#include <phosphor-logging/log.hpp>
+
+namespace openpower
+{
+namespace pels
+{
+namespace message = openpower::pels::message;
+namespace pv = openpower::pels::pel_values;
+
+constexpr auto unknownValue = "Unknown";
+
+PEL::PEL(const message::Entry& entry, uint32_t obmcLogID, uint64_t timestamp,
+ phosphor::logging::Entry::Level severity,
+ const AdditionalData& additionalData,
+ const DataInterfaceBase& dataIface)
+{
+ _ph = std::make_unique<PrivateHeader>(entry.componentID, obmcLogID,
+ timestamp);
+ _uh = std::make_unique<UserHeader>(entry, severity);
+
+ auto src = std::make_unique<SRC>(entry, additionalData);
+
+ auto euh = std::make_unique<ExtendedUserHeader>(dataIface, entry, *src);
+
+ _optionalSections.push_back(std::move(src));
+ _optionalSections.push_back(std::move(euh));
+
+ auto mtms = std::make_unique<FailingMTMS>(dataIface);
+ _optionalSections.push_back(std::move(mtms));
+
+ auto ud = util::makeSysInfoUserDataSection(additionalData, dataIface);
+ _optionalSections.push_back(std::move(ud));
+
+ if (!additionalData.empty())
+ {
+ ud = util::makeADUserDataSection(additionalData);
+
+ // To be safe, check there isn't too much data
+ if (size() + ud->header().size <= _maxPELSize)
+ {
+ _optionalSections.push_back(std::move(ud));
+ }
+ }
+
+ _ph->setSectionCount(2 + _optionalSections.size());
+
+ checkRulesAndFix();
+}
+
+PEL::PEL(std::vector<uint8_t>& data) : PEL(data, 0)
+{
+}
+
+PEL::PEL(std::vector<uint8_t>& data, uint32_t obmcLogID)
+{
+ populateFromRawData(data, obmcLogID);
+}
+
+void PEL::populateFromRawData(std::vector<uint8_t>& data, uint32_t obmcLogID)
+{
+ Stream pelData{data};
+ _ph = std::make_unique<PrivateHeader>(pelData);
+ if (obmcLogID != 0)
+ {
+ _ph->setOBMCLogID(obmcLogID);
+ }
+
+ _uh = std::make_unique<UserHeader>(pelData);
+
+ // Use the section factory to create the rest of the objects
+ for (size_t i = 2; i < _ph->sectionCount(); i++)
+ {
+ auto section = section_factory::create(pelData);
+ _optionalSections.push_back(std::move(section));
+ }
+}
+
+bool PEL::valid() const
+{
+ bool valid = _ph->valid();
+
+ if (valid)
+ {
+ valid = _uh->valid();
+ }
+
+ if (valid)
+ {
+ if (!std::all_of(_optionalSections.begin(), _optionalSections.end(),
+ [](const auto& section) { return section->valid(); }))
+ {
+ valid = false;
+ }
+ }
+
+ return valid;
+}
+
+void PEL::setCommitTime()
+{
+ auto now = std::chrono::system_clock::now();
+ _ph->setCommitTimestamp(getBCDTime(now));
+}
+
+void PEL::assignID()
+{
+ _ph->setID(generatePELID());
+}
+
+void PEL::flatten(std::vector<uint8_t>& pelBuffer) const
+{
+ Stream pelData{pelBuffer};
+
+ if (!valid())
+ {
+ using namespace phosphor::logging;
+ log<level::WARNING>("Unflattening an invalid PEL");
+ }
+
+ _ph->flatten(pelData);
+ _uh->flatten(pelData);
+
+ for (auto& section : _optionalSections)
+ {
+ section->flatten(pelData);
+ }
+}
+
+std::vector<uint8_t> PEL::data() const
+{
+ std::vector<uint8_t> pelData;
+ flatten(pelData);
+ return pelData;
+}
+
+size_t PEL::size() const
+{
+ size_t size = 0;
+
+ if (_ph)
+ {
+ size += _ph->header().size;
+ }
+
+ if (_uh)
+ {
+ size += _uh->header().size;
+ }
+
+ for (const auto& section : _optionalSections)
+ {
+ size += section->header().size;
+ }
+
+ return size;
+}
+
+std::optional<SRC*> PEL::primarySRC() const
+{
+ auto src = std::find_if(
+ _optionalSections.begin(), _optionalSections.end(), [](auto& section) {
+ return section->header().id ==
+ static_cast<uint16_t>(SectionID::primarySRC);
+ });
+ if (src != _optionalSections.end())
+ {
+ return static_cast<SRC*>(src->get());
+ }
+
+ return std::nullopt;
+}
+
+void PEL::checkRulesAndFix()
+{
+ auto [actionFlags, eventType] =
+ pel_rules::check(_uh->actionFlags(), _uh->eventType(), _uh->severity());
+
+ _uh->setActionFlags(actionFlags);
+ _uh->setEventType(eventType);
+}
+
+void PEL::printSectionInJSON(const Section& section, std::string& buf,
+ std::map<uint16_t, size_t>& pluralSections) const
+{
+ char tmpB[5];
+ uint8_t id[] = {static_cast<uint8_t>(section.header().id >> 8),
+ static_cast<uint8_t>(section.header().id)};
+ sprintf(tmpB, "%c%c", id[0], id[1]);
+ std::string sectionID(tmpB);
+ std::string sectionName = pv::sectionTitles.count(sectionID)
+ ? pv::sectionTitles.at(sectionID)
+ : "Unknown Section";
+
+ // Add a count if there are multiple of this type of section
+ auto count = pluralSections.find(section.header().id);
+ if (count != pluralSections.end())
+ {
+ sectionName += " " + std::to_string(count->second);
+ count->second++;
+ }
+
+ if (section.valid())
+ {
+ auto json = section.getJSON();
+ if (json)
+ {
+ buf += "\n\"" + sectionName + "\": {\n";
+ buf += *json + "\n},\n";
+ }
+ else
+ {
+ buf += "\n\"" + sectionName + "\": [\n";
+ std::vector<uint8_t> data;
+ Stream s{data};
+ section.flatten(s);
+ std::string dstr = dumpHex(std::data(data), data.size());
+ buf += dstr + "],\n";
+ }
+ }
+ else
+ {
+ buf += "\n\"Invalid Section\": [\n \"invalid\"\n],\n";
+ }
+}
+
+std::map<uint16_t, size_t> PEL::getPluralSections() const
+{
+ std::map<uint16_t, size_t> sectionCounts;
+
+ for (const auto& section : optionalSections())
+ {
+ if (sectionCounts.find(section->header().id) == sectionCounts.end())
+ {
+ sectionCounts[section->header().id] = 1;
+ }
+ else
+ {
+ sectionCounts[section->header().id]++;
+ }
+ }
+
+ std::map<uint16_t, size_t> sections;
+ for (const auto& [id, count] : sectionCounts)
+ {
+ if (count > 1)
+ {
+ // Start with 0 here and printSectionInJSON()
+ // will increment it as it goes.
+ sections.emplace(id, 0);
+ }
+ }
+
+ return sections;
+}
+
+void PEL::toJSON() const
+{
+ auto sections = getPluralSections();
+
+ std::string buf = "{";
+ printSectionInJSON(*(_ph.get()), buf, sections);
+ printSectionInJSON(*(_uh.get()), buf, sections);
+ for (auto& section : this->optionalSections())
+ {
+ printSectionInJSON(*(section.get()), buf, sections);
+ }
+ buf += "}";
+ std::size_t found = buf.rfind(",");
+ if (found != std::string::npos)
+ buf.replace(found, 1, "");
+ std::cout << buf << std::endl;
+}
+
+namespace util
+{
+
+std::unique_ptr<UserData> makeJSONUserDataSection(const nlohmann::json& json)
+{
+ auto jsonString = json.dump();
+ std::vector<uint8_t> jsonData(jsonString.begin(), jsonString.end());
+
+ // Pad to a 4 byte boundary
+ while ((jsonData.size() % 4) != 0)
+ {
+ jsonData.push_back(0);
+ }
+
+ return std::make_unique<UserData>(
+ static_cast<uint16_t>(ComponentID::phosphorLogging),
+ static_cast<uint8_t>(UserDataFormat::json),
+ static_cast<uint8_t>(UserDataFormatVersion::json), jsonData);
+}
+
+std::unique_ptr<UserData> makeADUserDataSection(const AdditionalData& ad)
+{
+ assert(!ad.empty());
+ nlohmann::json json;
+
+ // Remove the 'ESEL' entry, as it contains a full PEL in the value.
+ if (ad.getValue("ESEL"))
+ {
+ auto newAD = ad;
+ newAD.remove("ESEL");
+ json = newAD.toJSON();
+ }
+ else
+ {
+ json = ad.toJSON();
+ }
+
+ return makeJSONUserDataSection(json);
+}
+
+void addProcessNameToJSON(nlohmann::json& json,
+ const std::optional<std::string>& pid,
+ const DataInterfaceBase& dataIface)
+{
+ std::string name{unknownValue};
+
+ try
+ {
+ if (pid)
+ {
+ auto n = dataIface.getProcessName(*pid);
+ if (n)
+ {
+ name = *n;
+ }
+ }
+ }
+ catch (std::exception& e)
+ {
+ }
+
+ json["Process Name"] = std::move(name);
+}
+
+void addBMCFWVersionIDToJSON(nlohmann::json& json,
+ const DataInterfaceBase& dataIface)
+{
+ auto id = dataIface.getBMCFWVersionID();
+ if (id.empty())
+ {
+ id = unknownValue;
+ }
+
+ json["BMC Version ID"] = std::move(id);
+}
+
+std::unique_ptr<UserData>
+ makeSysInfoUserDataSection(const AdditionalData& ad,
+ const DataInterfaceBase& dataIface)
+{
+ nlohmann::json json;
+
+ addProcessNameToJSON(json, ad.getValue("_PID"), dataIface);
+ addBMCFWVersionIDToJSON(json, dataIface);
+
+ return makeJSONUserDataSection(json);
+}
+
+} // namespace util
+
+} // namespace pels
+} // namespace openpower
diff --git a/extensions/openpower-pels/pel.hpp b/extensions/openpower-pels/pel.hpp
new file mode 100644
index 0000000..5f14354
--- /dev/null
+++ b/extensions/openpower-pels/pel.hpp
@@ -0,0 +1,362 @@
+#pragma once
+
+#include "additional_data.hpp"
+#include "data_interface.hpp"
+#include "private_header.hpp"
+#include "registry.hpp"
+#include "src.hpp"
+#include "user_data.hpp"
+#include "user_header.hpp"
+
+#include <memory>
+#include <vector>
+
+namespace openpower
+{
+namespace pels
+{
+
+/** @class PEL
+ *
+ * @brief This class represents a specific event log format referred to as a
+ * Platform Event Log.
+ *
+ * Every field in a PEL are in structures call sections, of which there are
+ * several types. Some sections are required, and some are optional. In some
+ * cases there may be more than one instance of a section type.
+ *
+ * The only two required sections for every type of PEL are the Private Header
+ * section and User Header section, which must be in the first and second
+ * positions, respectively.
+ *
+ * Every section starts with an 8 byte section header, which has the section
+ * size and type, among other things.
+ *
+ * This class represents all sections with objects.
+ *
+ * The class can be constructed:
+ * - From a full formed flattened PEL.
+ * - From scratch based on an OpenBMC event and its corresponding PEL message
+ * registry entry.
+ *
+ * The data() method allows one to retrieve the PEL as a vector<uint8_t>. This
+ * is the format in which it is stored and transmitted.
+ */
+class PEL
+{
+ public:
+ PEL() = delete;
+ ~PEL() = default;
+ PEL(const PEL&) = delete;
+ PEL& operator=(const PEL&) = delete;
+ PEL(PEL&&) = delete;
+ PEL& operator=(PEL&&) = delete;
+
+ /**
+ * @brief Constructor
+ *
+ * Build a PEL from raw data.
+ *
+ * Note: Neither this nor the following constructor can take a const vector&
+ * because the Stream class that is used to read from the vector cannot take
+ * a const. The alternative is to make a copy of the data, but as PELs can
+ * be up to 16KB that is undesireable.
+ *
+ * @param[in] data - The PEL data
+ */
+ PEL(std::vector<uint8_t>& data);
+
+ /**
+ * @brief Constructor
+ *
+ * Build a PEL from the raw data.
+ *
+ * @param[in] data - the PEL data
+ * @param[in] obmcLogID - the corresponding OpenBMC event log ID
+ */
+ PEL(std::vector<uint8_t>& data, uint32_t obmcLogID);
+
+ /**
+ * @brief Constructor
+ *
+ * Creates a PEL from an OpenBMC event log and its message
+ * registry entry.
+ *
+ * @param[in] entry - The message registry entry for this error
+ * @param[in] obmcLogID - ID of corresponding OpenBMC event log
+ * @param[in] timestamp - Timestamp from the event log
+ * @param[in] severity - Severity from the event log
+ * @param[in] additionalData - The AdditionalData contents
+ * @param[in] dataIface - The data interface object
+ */
+ PEL(const openpower::pels::message::Entry& entry, uint32_t obmcLogID,
+ uint64_t timestamp, phosphor::logging::Entry::Level severity,
+ const AdditionalData& additionalData,
+ const DataInterfaceBase& dataIface);
+
+ /**
+ * @brief Convenience function to return the log ID field from the
+ * Private Header section.
+ *
+ * @return uint32_t - the ID
+ */
+ uint32_t id() const
+ {
+ return _ph->id();
+ }
+
+ /**
+ * @brief Convenience function to return the PLID field from the
+ * Private Header section.
+ *
+ * @return uint32_t - the PLID
+ */
+ uint32_t plid() const
+ {
+ return _ph->plid();
+ }
+
+ /**
+ * @brief Convenience function to return the OpenBMC event log ID field
+ * from the Private Header section.
+ *
+ * @return uint32_t - the OpenBMC event log ID
+ */
+ uint32_t obmcLogID() const
+ {
+ return _ph->obmcLogID();
+ }
+
+ /**
+ * @brief Convenience function to return the commit time field from
+ * the Private Header section.
+ *
+ * @return BCDTime - the timestamp
+ */
+ BCDTime commitTime() const
+ {
+ return _ph->commitTimestamp();
+ }
+
+ /**
+ * @brief Convenience function to return the create time field from
+ * the Private Header section.
+ *
+ * @return BCDTime - the timestamp
+ */
+ BCDTime createTime() const
+ {
+ return _ph->createTimestamp();
+ }
+
+ /**
+ * @brief Gives access to the Private Header section class
+ *
+ * @return const PrivateHeader& - the private header
+ */
+ const PrivateHeader& privateHeader() const
+ {
+ return *_ph;
+ }
+
+ /**
+ * @brief Gives access to the User Header section class
+ *
+ * @return const UserHeader& - the user header
+ */
+ const UserHeader& userHeader() const
+ {
+ return *_uh;
+ }
+
+ /**
+ * @brief Gives access to the primary SRC's section class
+ *
+ * This is technically an optional section, so the return
+ * value is an std::optional<SRC*>.
+ *
+ * @return std::optional<SRC*> - the SRC section object
+ */
+ std::optional<SRC*> primarySRC() const;
+
+ /**
+ * @brief Returns the optional sections, which is everything but
+ * the Private and User Headers.
+ *
+ * @return const std::vector<std::unique_ptr<Section>>&
+ */
+ const std::vector<std::unique_ptr<Section>>& optionalSections() const
+ {
+ return _optionalSections;
+ }
+
+ /**
+ * @brief Returns the PEL data.
+ *
+ * @return std::vector<uint8_t> - the raw PEL data
+ */
+ std::vector<uint8_t> data() const;
+
+ /**
+ * @brief Returns the size of the PEL
+ *
+ * @return size_t The PEL size in bytes
+ */
+ size_t size() const;
+
+ /**
+ * @brief Says if the PEL is valid (the sections are all valid)
+ *
+ * @return bool - if the PEL is valid
+ */
+ bool valid() const;
+
+ /**
+ * @brief Sets the commit timestamp to the current time
+ */
+ void setCommitTime();
+
+ /**
+ * @brief Sets the error log ID field to a unique ID.
+ */
+ void assignID();
+
+ /**
+ * @brief Output a PEL in JSON.
+ */
+ void toJSON() const;
+
+ /**
+ * @brief Sets the host transmission state in the User Header
+ *
+ * @param[in] state - The state value
+ */
+ void setHostTransmissionState(TransmissionState state)
+ {
+ _uh->setHostTransmissionState(static_cast<uint8_t>(state));
+ }
+
+ /**
+ * @brief Returns the host transmission state
+ *
+ * @return HostTransmissionState - The state
+ */
+ TransmissionState hostTransmissionState() const
+ {
+ return static_cast<TransmissionState>(_uh->hostTransmissionState());
+ }
+
+ /**
+ * @brief Sets the HMC transmission state in the User Header
+ *
+ * @param[in] state - The state value
+ */
+ void setHMCTransmissionState(TransmissionState state)
+ {
+ _uh->setHMCTransmissionState(static_cast<uint8_t>(state));
+ }
+
+ /**
+ * @brief Returns the HMC transmission state
+ *
+ * @return HMCTransmissionState - The state
+ */
+ TransmissionState hmcTransmissionState() const
+ {
+ return static_cast<TransmissionState>(_uh->hmcTransmissionState());
+ }
+
+ private:
+ /**
+ * @brief Builds the section objects from a PEL data buffer
+ *
+ * Note: The data parameter cannot be const for the same reasons
+ * as listed in the constructor.
+ *
+ * @param[in] data - The PEL data
+ * @param[in] obmcLogID - The OpenBMC event log ID to use for that
+ * field in the Private Header.
+ */
+ void populateFromRawData(std::vector<uint8_t>& data, uint32_t obmcLogID);
+
+ /**
+ * @brief Flattens the PEL objects into the buffer
+ *
+ * @param[out] pelBuffer - What the data will be written to
+ */
+ void flatten(std::vector<uint8_t>& pelBuffer) const;
+
+ /**
+ * @brief Check that the PEL fields that need to be in agreement
+ * with each other are, and fix them up if necessary.
+ */
+ void checkRulesAndFix();
+
+ /**
+ * @brief Returns a map of the section IDs that appear more than once
+ * in the PEL. The data value for each entry will be set to 0.
+ *
+ * @return std::map<uint16_t, size_t>
+ */
+ std::map<uint16_t, size_t> getPluralSections() const;
+
+ /**
+ * @brief The PEL Private Header section
+ */
+ std::unique_ptr<PrivateHeader> _ph;
+
+ /**
+ * @brief The PEL User Header section
+ */
+ std::unique_ptr<UserHeader> _uh;
+
+ /**
+ * @brief Holds all sections by the PH and UH.
+ */
+ std::vector<std::unique_ptr<Section>> _optionalSections;
+
+ /**
+ * @brief helper function for printing PELs.
+ * @param[in] Section& - section object reference
+ * @param[in] std::string - PEL string
+ * @param[in|out] pluralSections - Map used to track sections counts for
+ * when there is more than 1.
+ */
+ void printSectionInJSON(const Section& section, std::string& buf,
+ std::map<uint16_t, size_t>& pluralSections) const;
+
+ /**
+ * @brief The maximum size a PEL can be in bytes.
+ */
+ static constexpr size_t _maxPELSize = 16384;
+};
+
+namespace util
+{
+
+/**
+ * @brief Create a UserData section containing the AdditionalData
+ * contents as a JSON string.
+ *
+ * @param[in] ad - The AdditionalData contents
+ *
+ * @return std::unique_ptr<UserData> - The section
+ */
+std::unique_ptr<UserData> makeADUserDataSection(const AdditionalData& ad);
+
+/**
+ * @brief Create a UserData section containing various useful pieces
+ * of system information as a JSON string.
+ *
+ * @param[in] ad - The AdditionalData contents
+ * @param[in] dataIface - The data interface object
+ *
+ * @return std::unique_ptr<UserData> - The section
+ */
+std::unique_ptr<UserData>
+ makeSysInfoUserDataSection(const AdditionalData& ad,
+ const DataInterfaceBase& dataIface);
+} // namespace util
+
+} // namespace pels
+} // namespace openpower
diff --git a/extensions/openpower-pels/pel_rules.cpp b/extensions/openpower-pels/pel_rules.cpp
new file mode 100644
index 0000000..be5e5a2
--- /dev/null
+++ b/extensions/openpower-pels/pel_rules.cpp
@@ -0,0 +1,90 @@
+#include "pel_rules.hpp"
+
+#include "pel_types.hpp"
+
+#include <bitset>
+
+namespace openpower
+{
+namespace pels
+{
+namespace pel_rules
+{
+
+std::tuple<uint16_t, uint8_t> check(uint16_t actionFlags, uint8_t eventType,
+ uint8_t severity)
+{
+ std::bitset<16> newActionFlags{actionFlags};
+ uint8_t newEventType = eventType;
+ auto sevType = static_cast<SeverityType>(severity & 0xF0);
+
+ // TODO: This code covers the most common cases. The final tweaking
+ // will be done with ibm-openbmc/dev#1333.
+
+ // Always report, unless specifically told not to
+ if (!newActionFlags.test(dontReportToHostFlagBit))
+ {
+ newActionFlags.set(reportFlagBit);
+ }
+ else
+ {
+ newActionFlags.reset(reportFlagBit);
+ }
+
+ // Call home by BMC not supported
+ newActionFlags.reset(spCallHomeFlagBit);
+
+ switch (sevType)
+ {
+ case SeverityType::nonError:
+ {
+ // Informational errors never need service actions or call home.
+ newActionFlags.reset(serviceActionFlagBit);
+ newActionFlags.reset(callHomeFlagBit);
+
+ // Ensure event type isn't 'not applicable'
+ if (newEventType == static_cast<uint8_t>(EventType::notApplicable))
+ {
+ newEventType =
+ static_cast<uint8_t>(EventType::miscInformational);
+ }
+
+ // The misc info and tracing event types are always hidden.
+ // For other event types, it's up to the creator.
+ if ((newEventType ==
+ static_cast<uint8_t>(EventType::miscInformational)) ||
+ (newEventType == static_cast<uint8_t>(EventType::tracing)))
+ {
+ newActionFlags.set(hiddenFlagBit);
+ }
+ break;
+ }
+ case SeverityType::recovered:
+ {
+ // Recovered errors are hidden, and by definition need no
+ // service action or call home.
+ newActionFlags.set(hiddenFlagBit);
+ newActionFlags.reset(serviceActionFlagBit);
+ newActionFlags.reset(callHomeFlagBit);
+ break;
+ }
+ case SeverityType::predictive:
+ case SeverityType::unrecoverable:
+ case SeverityType::critical:
+ case SeverityType::diagnostic:
+ case SeverityType::symptom:
+ {
+ // Report these others as normal errors.
+ newActionFlags.reset(hiddenFlagBit);
+ newActionFlags.set(serviceActionFlagBit);
+ newActionFlags.set(callHomeFlagBit);
+ break;
+ }
+ }
+
+ return {static_cast<uint16_t>(newActionFlags.to_ulong()), newEventType};
+}
+
+} // namespace pel_rules
+} // namespace pels
+} // namespace openpower
diff --git a/extensions/openpower-pels/pel_rules.hpp b/extensions/openpower-pels/pel_rules.hpp
new file mode 100644
index 0000000..ca05875
--- /dev/null
+++ b/extensions/openpower-pels/pel_rules.hpp
@@ -0,0 +1,32 @@
+#pragma once
+
+#include <cstdint>
+#include <tuple>
+
+namespace openpower
+{
+namespace pels
+{
+namespace pel_rules
+{
+
+/**
+ * @brief Ensure certain PEL fields are in agreement, and fix them if they
+ * aren't. These rules are documented in the README.md in this
+ * directory.
+ *
+ * Note: The message registry schema enforces that there are no undefined
+ * bits set in these fields.
+ *
+ * @param[in] actionFlags - The current Action Flags value
+ * @param[in] eventType - The current Event Type value
+ * @param[in] severity - The current Severity value
+ *
+ * @return std::tuple<actionFlags, eventType> - The corrected values.
+ */
+std::tuple<uint16_t, uint8_t> check(uint16_t actionFlags, uint8_t eventType,
+ uint8_t severity);
+
+} // namespace pel_rules
+} // namespace pels
+} // namespace openpower
diff --git a/extensions/openpower-pels/pel_types.hpp b/extensions/openpower-pels/pel_types.hpp
new file mode 100644
index 0000000..9a33ccf
--- /dev/null
+++ b/extensions/openpower-pels/pel_types.hpp
@@ -0,0 +1,134 @@
+#pragma once
+
+namespace openpower
+{
+namespace pels
+{
+
+/**
+ * @brief Useful component IDs
+ */
+enum class ComponentID
+{
+ phosphorLogging = 0x2000
+};
+
+/**
+ * @brief PEL section IDs
+ */
+enum class SectionID
+{
+ privateHeader = 0x5048, // 'PH'
+ userHeader = 0x5548, // 'UH'
+ primarySRC = 0x5053, // 'PS'
+ secondarySRC = 0x5353, // 'SS'
+ extendedUserHeader = 0x4548, // 'EH'
+ failingMTMS = 0x4D54, // 'MT'
+ dumpLocation = 0x4448, // 'DH'
+ firmwareError = 0x5357, // 'SW'
+ impactedPart = 0x4C50, // 'LP'
+ logicalResource = 0x4C52, // 'LR'
+ hmcID = 0x484D, // 'HM'
+ epow = 0x4550, // 'EP'
+ ioEvent = 0x4945, // 'IE'
+ mfgInfo = 0x4D49, // 'MI'
+ callhome = 0x4348, // 'CH'
+ userData = 0x5544, // 'UD'
+ envInfo = 0x4549, // 'EI'
+ extUserData = 0x4544 // 'ED'
+};
+
+/**
+ * @brief Useful SRC types
+ */
+enum class SRCType
+{
+ bmcError = 0xBD,
+ powerError = 0x11
+};
+
+/**
+ * @brief Creator IDs
+ */
+enum class CreatorID
+{
+ fsp = 'E',
+ hmc = 'C',
+ hostboot = 'B',
+ ioDrawer = 'M',
+ occ = 'T',
+ openBMC = 'O',
+ partFW = 'L',
+ phyp = 'H',
+ powerControl = 'W',
+ powerNV = 'P',
+ sapphire = 'K',
+ slic = 'S',
+};
+
+/**
+ * @brief Useful event scope values
+ */
+enum class EventScope
+{
+ entirePlatform = 0x03
+};
+
+/**
+ * @brief Useful event type values
+ */
+enum class EventType
+{
+ notApplicable = 0x00,
+ miscInformational = 0x01,
+ tracing = 0x02
+};
+
+/**
+ * @brief The major types of severity values, based on the
+ * the left nibble of the severity value.
+ */
+enum class SeverityType
+{
+ nonError = 0x00,
+ recovered = 0x10,
+ predictive = 0x20,
+ unrecoverable = 0x40,
+ critical = 0x50,
+ diagnostic = 0x60,
+ symptom = 0x70
+};
+
+/**
+ * @brief The Action Flags values with the bit
+ * numbering needed by std::bitset.
+ *
+ * Not an enum class so that casting isn't needed
+ * by the bitset operations.
+ */
+enum ActionFlagsBits
+{
+ serviceActionFlagBit = 15, // 0x8000
+ hiddenFlagBit = 14, // 0x4000
+ reportFlagBit = 13, // 0x2000
+ dontReportToHostFlagBit = 12, // 0x1000
+ callHomeFlagBit = 11, // 0x0800
+ isolationIncompleteFlagBit = 10, // 0x0400
+ spCallHomeFlagBit = 8, // 0x0100
+ osSWErrorBit = 7, // 0x0080
+ osHWErrorBit = 6 // 0x0040
+};
+
+/**
+ * @brief The PEL transmission states
+ */
+enum class TransmissionState
+{
+ newPEL = 0,
+ badPEL = 1,
+ sent = 2,
+ acked = 3
+};
+
+} // namespace pels
+} // namespace openpower
diff --git a/extensions/openpower-pels/pel_values.cpp b/extensions/openpower-pels/pel_values.cpp
new file mode 100644
index 0000000..7e7f93b
--- /dev/null
+++ b/extensions/openpower-pels/pel_values.cpp
@@ -0,0 +1,329 @@
+/**
+ * Copyright © 2019 IBM Corporation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#include "pel_values.hpp"
+
+#include <algorithm>
+
+namespace openpower
+{
+namespace pels
+{
+namespace pel_values
+{
+
+// Note: The description fields will be filled in as part of the
+// PEL parser work.
+
+/**
+ * The possible values for the subsystem field in the User Header.
+ */
+const PELValues subsystemValues = {
+ {0x10, "processor", "Processor Subsystem"},
+ {0x11, "processor_fru", "Processor FRU"},
+ {0x12, "processor_chip", "Processor Chip Cache"},
+ {0x13, "processor_unit", "Processor Unit (CPU)"},
+ {0x14, "processor_bus", "Processor Bus Controller"},
+
+ {0x20, "memory", "Memory Subsystem"},
+ {0x21, "memory_ctlr", "Memory Controller"},
+ {0x22, "memory_bus", "Memory Bus Interface"},
+ {0x23, "memory_dimm", "Memory DIMM"},
+ {0x24, "memory_fru", "Memory Card/FRU"},
+ {0x25, "external_cache", "External Cache"},
+
+ {0x30, "io", "I/O Subsystem"},
+ {0x31, "io_hub", "I/O Hub"},
+ {0x32, "io_bridge", "I/O Bridge"},
+ {0x33, "io_bus", "I/O bus interface"},
+ {0x34, "io_processor", "I/O Processor"},
+ {0x35, "io_hub_other", "SMA Hub"},
+ {0x38, "phb", "PCI Bridge Chip"},
+
+ {0x40, "io_adapter", "I/O Adapter Subsystem"},
+ {0x41, "io_adapter_comm", "I/O Adapter Communication"},
+ {0x46, "io_device", "I/O Device Subsystem"},
+ {0x47, "io_device_dasd", "I/O Device Disk"},
+ {0x4C, "io_external_general", "I/O External Peripheral"},
+ {0x4D, "io_external_workstation",
+ "I/O External Peripheral Local Work Station"},
+ {0x4E, "io_storage_mezz", "I/O Storage Mezza Expansion Subsystem"},
+
+ {0x50, "cec_hardware", "CEC Hardware Subsystem"},
+ {0x51, "cec_sp_a", "CEC Hardware: Service Processor A"},
+ {0x52, "cec_sp_b", "CEC Hardware: Service Processor B"},
+ {0x53, "cec_node_controller", "CEC Hardware: Node Controller"},
+ {0x55, "cec_vpd", "CEC Hardware: VPD Interface"},
+ {0x56, "cec_i2c", "CEC Hardware: I2C Devices"},
+ {0x57, "cec_chip_iface", "CEC Hardware: CEC Chip Interface"},
+ {0x58, "cec_clocks", "CEC Hardware: Clock"},
+ {0x59, "cec_op_panel", "CEC Hardware: Operator Panel"},
+ {0x5A, "cec_tod", "CEC Hardware: Time-Of-Day Hardware"},
+ {0x5B, "cec_storage_device", "CEC Hardware: Memory Device"},
+ {0x5C, "cec_sp_hyp_iface",
+ "CEC Hardware: Hypervisor-Service Processor Interface"},
+ {0x5D, "cec_service_network", "CEC Hardware: Service Network"},
+ {0x5E, "cec_sp_hostboot_iface",
+ "CEC Hardware: Hostboot-Service Processor Interface"},
+
+ {0x60, "power", "Power/Cooling Subsystem"},
+ {0x61, "power_supply", "Power Supply"},
+ {0x62, "power_control_hw", "Power Control Hardware"},
+ {0x63, "power_fans", "Fan (AMD)"},
+ {0x64, "power_sequencer", "Digital Power Supply Subsystem"},
+
+ {0x70, "others", "Miscellaneous"},
+ {0x71, "other_hmc", "HMC Subsystem & Hardware"},
+ {0x72, "other_test_tool", "Test Tool"},
+ {0x73, "other_media", "Removable Media"},
+ {0x74, "other_multiple_subsystems", "Multiple Subsystems"},
+ {0x75, "other_na", "Not Applicable"},
+ {0x76, "other_info_src", "Miscellaneous"},
+
+ {0x7A, "surv_hyp_lost_sp",
+ "Hypervisor lost communication with service processor"},
+ {0x7B, "surv_sp_lost_hyp",
+ "Service processor lost communication with Hypervisor"},
+ {0x7C, "surv_sp_lost_hmc", "Service processor lost communication with HMC"},
+ {0x7D, "surv_hmc_lost_lpar",
+ "HMC lost communication with logical partition"},
+ {0x7E, "surv_hmc_lost_bpa", "HMC lost communication with BPA"},
+ {0x7F, "surv_hmc_lost_hmc", "HMC lost communication with another HMC"},
+
+ {0x80, "platform_firmware", "Platform Firmware"},
+ {0x81, "sp_firmware", "Service Processor Firmware"},
+ {0x82, "hyp_firmware", "System Hypervisor Firmware"},
+ {0x83, "partition_firmware", "Partition Firmware"},
+ {0x84, "slic_firmware", "SLIC Firmware"},
+ {0x85, "spcn_firmware", "System Power Control Network Firmware"},
+ {0x86, "bulk_power_firmware_side_a", "Bulk Power Firmware Side A"},
+ {0x87, "hmc_code_firmware", "HMC Code"},
+ {0x88, "bulk_power_firmware_side_b", "Bulk Power Firmware Side B"},
+ {0x89, "virtual_sp", "Virtual Service Processor Firmware"},
+ {0x8A, "hostboot", "HostBoot"},
+ {0x8B, "occ", "OCC"},
+ {0x8D, "bmc_firmware", "BMC Firmware"},
+
+ {0x90, "software", "Software"},
+ {0x91, "os_software", "Operating System software"},
+ {0x92, "xpf_software", "XPF software"},
+ {0x93, "app_software", "Application software"},
+
+ {0xA0, "ext_env", "External Environment"},
+ {0xA1, "input_power_source", "Input Power Source (ac)"},
+ {0xA2, "ambient_temp", "Room Ambient Temperature"},
+ {0xA3, "user_error", "User Error"},
+ {0xA4, "corrosion", "Corrosion"}};
+
+/**
+ * The possible values for the severity field in the User Header.
+ */
+const PELValues severityValues = {
+ {0x00, "non_error", "Informational Event"},
+
+ {0x10, "recovered", "Recovered Error"},
+ {0x20, "predictive", "Predictive Error"},
+ {0x21, "predictive_degraded_perf",
+ "Predictive Error, Degraded Performance"},
+ {0x22, "predictive_reboot", "Predictive Error, Correctable"},
+ {0x23, "predictive_reboot_degraded",
+ "Predictive Error, Correctable, Degraded"},
+ {0x24, "predictive_redundancy_loss", "Predictive Error, Redundancy Lost"},
+
+ {0x40, "unrecoverable", "Unrecoverable Error"},
+ {0x41, "unrecoverable_degraded_perf",
+ "Unrecoverable Error, Degraded Performance"},
+ {0x44, "unrecoverable_redundancy_loss",
+ "Unrecoverable Error, Loss of Redundancy"},
+ {0x45, "unrecoverable_redundancy_loss_perf",
+ "Unrecoverable, Loss of Redundancy + Performance"},
+ {0x48, "unrecoverable_loss_of_function",
+ "Unrecoverable Error, Loss of Function"},
+
+ {0x50, "critical", "Critical Error, Scope of Failure unknown"},
+ {0x51, "critical_system_term", "Critical Error, System Termination"},
+ {0x52, "critical_imminent_failure",
+ "Critical Error, System Failure likely or imminent"},
+ {0x53, "critical_partition_term",
+ "Critical Error, Partition(s) Termination"},
+ {0x54, "critical_partition_imminent_failure",
+ "Critical Error, Partition(s) Failure likely or imminent"},
+
+ {0x60, "diagnostic_error", "Error detected during diagnostic test"},
+ {0x61, "diagnostic_error_incorrect_results",
+ "Diagostic error, resource w/incorrect results"},
+
+ {0x71, "symptom_recovered", "Symptom Recovered"},
+ {0x72, "symptom_predictive", "Symptom Predictive"},
+ {0x74, "symptom_unrecoverable", "Symptom Unrecoverable"},
+ {0x75, "symptom_critical", "Symptom Critical"},
+ {0x76, "symptom_diag_err", "Symptom Diag Err"}};
+
+/**
+ * The possible values for the Event Type field in the User Header.
+ */
+const PELValues eventTypeValues = {
+ {0x00, "na", "Not Applicable"},
+ {0x01, "misc_information_only", "Miscellaneous, Informational Only"},
+ {0x02, "tracing_event", "Tracing Event"},
+ {0x08, "dump_notification", "Dump Notification"}};
+
+/**
+ * The possible values for the Event Scope field in the User Header.
+ */
+const PELValues eventScopeValues = {
+ {0x01, "single_partition", "Single Partition"},
+ {0x02, "multiple_partitions", "Multiple Partitions"},
+ {0x03, "entire_platform", "Entire Platform"},
+ {0x04, "possibly_multiple_platforms", "Multiple Platforms"}};
+
+/**
+ * The possible values for the Action Flags field in the User Header.
+ */
+const PELValues actionFlagsValues = {
+ {0x8000, "service_action", "Service Action Required"},
+ {0x4000, "hidden", "Event not customer viewable"},
+ {0x2000, "report", "Report Externally"},
+ {0x1000, "dont_report", "Do Not Report To Hypervisor"},
+ {0x0800, "call_home", "HMC Call Home"},
+ {0x0400, "isolation_incomplete",
+ "Isolation Incomplete, further analysis required"},
+ {0x0100, "termination", "Service Processor Call Home Required"}};
+
+/**
+ * The possible values for the Callout Priority field in the SRC.
+ */
+const PELValues calloutPriorityValues = {
+ {0x48, "high", "Mandatory, replace all with this type as a unit"},
+ {0x4D, "medium", "Medium Priority"},
+ {0x41, "medium_group_a", "Medium Priority A, replace these as a group"},
+ {0x42, "medium_group_b", "Medium Priority B, replace these as a group"},
+ {0x43, "medium_group_c", "Medium Priority C, replace these as a group"},
+ {0x4C, "low", "Lowest priority replacement"}};
+
+PELValues::const_iterator findByValue(uint32_t value, const PELValues& fields)
+{
+ return std::find_if(fields.begin(), fields.end(),
+ [value](const auto& entry) {
+ return value == std::get<fieldValuePos>(entry);
+ });
+}
+
+PELValues::const_iterator findByName(const std::string& name,
+ const PELValues& fields)
+
+{
+ return std::find_if(fields.begin(), fields.end(),
+ [&name](const auto& entry) {
+ return name == std::get<registryNamePos>(entry);
+ });
+}
+
+/**
+ * @brief Map for section IDs
+ */
+const std::map<std::string, std::string> sectionTitles = {
+ {"PH", "Private Header"},
+ {"UH", "User Header"},
+ {"PS", "Primary SRC"},
+ {"SS", "Secondary SRC"},
+ {"EH", "Extended User Header"},
+ {"MT", "Failing MTMS"},
+ {"DH", "Dump Location"},
+ {"SW", "Firmware Error"},
+ {"LP", "Impacted Partition"},
+ {"LR", "Logical Resource"},
+ {"HM", "HMC ID"},
+ {"EP", "EPOW"},
+ {"IE", "IO Event"},
+ {"MI", "MFG Info"},
+ {"CH", "Call Home"},
+ {"UD", "User Data"},
+ {"EI", "Env Info"},
+ {"ED", "Extended User Data"}};
+
+/**
+ * @brief Map for Procedure Descriptions
+ */
+const std::map<std::string, std::string> procedureDesc = {{"TODO", "TODO"}};
+
+/**
+ * @brief Map for Callout Failing Component Types
+ */
+const std::map<uint8_t, std::string> failingComponentType = {
+ {0x10, "Normal Hardware FRU"},
+ {0x20, "Code FRU"},
+ {0x30, "Configuration error, configuration procedure required"},
+ {0x40, "Maintenance Procedure Required"},
+ {0x90, "External FRU"},
+ {0xA0, "External Code FRU"},
+ {0xB0, "Tool FRU"},
+ {0xC0, "Symbolic FRU"},
+ {0xE0, "Symbolic FRU with trusted location code"}};
+
+/**
+ * @brief Map for Boolean value
+ */
+const std::map<bool, std::string> boolString = {{true, "True"},
+ {false, "False"}};
+
+/**
+ * @brief Map for creator IDs
+ */
+const std::map<std::string, std::string> creatorIDs = {
+
+ {"O", "BMC"}, {"C", "HMC"}, {"H", "PHYP"}, {"L", "Partition FW"},
+ {"S", "SLIC"}, {"B", "Hostboot"}, {"T", "OCC"}, {"M", "I/O Drawer"},
+ {"K", "Sapphire"}, {"P", "PowerNV"}};
+
+/**
+ * @brief Map for transmission states
+ */
+const std::map<TransmissionState, std::string> transmissionStates = {
+ {TransmissionState::newPEL, "Not Sent"},
+ {TransmissionState::badPEL, "Rejected"},
+ {TransmissionState::sent, "Sent"},
+ {TransmissionState::acked, "Acked"}};
+
+std::string getValue(const uint8_t field, const pel_values::PELValues& values)
+{
+
+ auto tmp = pel_values::findByValue(field, values);
+ if (tmp != values.end())
+ {
+ return std::get<pel_values::descriptionPos>(*tmp);
+ }
+ else
+ {
+ return "invalid";
+ }
+}
+
+std::vector<std::string> getValuesBitwise(uint16_t value,
+ const pel_values::PELValues& table)
+{
+ std::vector<std::string> foundValues;
+ std::for_each(
+ table.begin(), table.end(), [&value, &foundValues](const auto& entry) {
+ if (value & std::get<fieldValuePos>(entry))
+ {
+ foundValues.push_back(std::get<descriptionPos>(entry));
+ }
+ });
+ return foundValues;
+}
+} // namespace pel_values
+} // namespace pels
+} // namespace openpower
diff --git a/extensions/openpower-pels/pel_values.hpp b/extensions/openpower-pels/pel_values.hpp
new file mode 100644
index 0000000..2424c63
--- /dev/null
+++ b/extensions/openpower-pels/pel_values.hpp
@@ -0,0 +1,132 @@
+#pragma once
+
+#include "pel_types.hpp"
+
+#include <map>
+#include <string>
+#include <tuple>
+#include <vector>
+
+namespace openpower
+{
+namespace pels
+{
+namespace pel_values
+{
+
+// The actual value as it shows up in the PEL
+const int fieldValuePos = 0;
+
+// The name of the value as specified in the message registry
+const int registryNamePos = 1;
+
+// The description of the field, used by PEL parsers
+const int descriptionPos = 2;
+
+using PELFieldValue = std::tuple<uint32_t, const char*, const char*>;
+using PELValues = std::vector<PELFieldValue>;
+
+/**
+ * @brief Helper function to get values from lookup tables.
+ * @return std::string - the value
+ * @param[in] uint8_t - field to get value for
+ * @param[in] PELValues - lookup table
+ */
+std::string getValue(const uint8_t field, const pel_values::PELValues& values);
+
+/**
+ * @brief Helper function to get value vector from lookup tables.
+ *
+ * @param[in] value - the value to lookup
+ * @param[in] table - lookup table
+ *
+ * @return std::vector<std::string> - the value vector
+ */
+std::vector<std::string> getValuesBitwise(uint16_t value,
+ const pel_values::PELValues& table);
+/**
+ * @brief Find the desired entry in a PELValues table based on the
+ * field value.
+ *
+ * @param[in] value - the PEL value to find
+ * @param[in] fields - the PEL values table to use
+ *
+ * @return PELValues::const_iterator - an iterator to the table entry
+ */
+PELValues::const_iterator findByValue(uint32_t value, const PELValues& fields);
+
+/**
+ * @brief Find the desired entry in a PELValues table based on the
+ * field message registry name.
+ *
+ * @param[in] name - the PEL message registry enum name
+ * @param[in] fields - the PEL values table to use
+ *
+ * @return PELValues::const_iterator - an iterator to the table entry
+ */
+PELValues::const_iterator findByName(const std::string& name,
+ const PELValues& fields);
+
+/**
+ * @brief The values for the 'subsystem' field in the User Header
+ */
+extern const PELValues subsystemValues;
+
+/**
+ * @brief The values for the 'severity' field in the User Header
+ */
+extern const PELValues severityValues;
+
+/**
+ * @brief The values for the 'Event Type' field in the User Header
+ */
+extern const PELValues eventTypeValues;
+
+/**
+ * @brief The values for the 'Event Scope' field in the User Header
+ */
+extern const PELValues eventScopeValues;
+
+/**
+ * @brief The values for the 'Action Flags' field in the User Header
+ */
+extern const PELValues actionFlagsValues;
+
+/**
+ * @brief The values for callout priorities in the SRC section
+ */
+extern const PELValues calloutPriorityValues;
+
+/**
+ * @brief Map for section IDs
+ */
+extern const std::map<std::string, std::string> sectionTitles;
+
+/**
+ * @brief Map for creator IDs
+ */
+extern const std::map<std::string, std::string> creatorIDs;
+
+/**
+ * @brief Map for transmission states
+ */
+extern const std::map<TransmissionState, std::string> transmissionStates;
+
+/**
+ * @brief Map for Procedure Descriptions
+ */
+extern const std::map<std::string, std::string> procedureDesc;
+
+/**
+ * @brief Map for Callout Failing Component Types
+ */
+extern const std::map<uint8_t, std::string> failingComponentType;
+
+/**
+ * @brief Map for Boolean value
+ */
+extern const std::map<bool, std::string> boolString;
+
+} // namespace pel_values
+} // namespace pels
+} // namespace openpower
diff --git a/extensions/openpower-pels/pldm_interface.cpp b/extensions/openpower-pels/pldm_interface.cpp
new file mode 100644
index 0000000..20ecc53
--- /dev/null
+++ b/extensions/openpower-pels/pldm_interface.cpp
@@ -0,0 +1,279 @@
+/**
+ * Copyright © 2019 IBM Corporation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#include "pldm_interface.hpp"
+
+#include <libpldm/base.h>
+#include <libpldm/file_io.h>
+#include <unistd.h>
+
+#include <fstream>
+#include <phosphor-logging/log.hpp>
+
+namespace openpower::pels
+{
+
+using namespace phosphor::logging;
+using namespace sdeventplus;
+using namespace sdeventplus::source;
+
+constexpr auto eidPath = "/usr/share/pldm/host_eid";
+constexpr mctp_eid_t defaultEIDValue = 9;
+
+constexpr uint16_t pelFileType = 0;
+
+PLDMInterface::~PLDMInterface()
+{
+ closeFD();
+}
+
+void PLDMInterface::closeFD()
+{
+ if (_fd >= 0)
+ {
+ close(_fd);
+ _fd = -1;
+ }
+}
+
+void PLDMInterface::readEID()
+{
+ _eid = defaultEIDValue;
+
+ std::ifstream eidFile{eidPath};
+ if (!eidFile.good())
+ {
+ log<level::ERR>("Could not open host EID file");
+ }
+ else
+ {
+ std::string eid;
+ eidFile >> eid;
+ if (!eid.empty())
+ {
+ _eid = atoi(eid.c_str());
+ }
+ else
+ {
+ log<level::ERR>("EID file was empty");
+ }
+ }
+}
+
+void PLDMInterface::open()
+{
+ _fd = pldm_open();
+ if (_fd < 0)
+ {
+ auto e = errno;
+ log<level::ERR>("pldm_open failed", entry("ERRNO=%d", e),
+ entry("RC=%d\n", _fd));
+ throw std::exception{};
+ }
+}
+
+CmdStatus PLDMInterface::sendNewLogCmd(uint32_t id, uint32_t size)
+{
+ try
+ {
+ closeFD();
+
+ open();
+
+ readInstanceID();
+
+ registerReceiveCallback();
+
+ doSend(id, size);
+ }
+ catch (const std::exception& e)
+ {
+ closeFD();
+
+ _inProgress = false;
+ _source.reset();
+ return CmdStatus::failure;
+ }
+
+ _inProgress = true;
+ _receiveTimer.restartOnce(_receiveTimeout);
+ return CmdStatus::success;
+}
+
+void PLDMInterface::registerReceiveCallback()
+{
+ _source = std::make_unique<IO>(
+ _event, _fd, EPOLLIN,
+ std::bind(std::mem_fn(&PLDMInterface::receive), this,
+ std::placeholders::_1, std::placeholders::_2,
+ std::placeholders::_3));
+}
+
+void PLDMInterface::readInstanceID()
+{
+ try
+ {
+ _instanceID = _dataIface.getPLDMInstanceID(_eid);
+ }
+ catch (const std::exception& e)
+ {
+ log<level::ERR>(
+ "Failed to get instance ID from PLDM Requester D-Bus daemon",
+ entry("ERROR=%s", e.what()));
+ throw;
+ }
+}
+
+void PLDMInterface::doSend(uint32_t id, uint32_t size)
+{
+ std::array<uint8_t, sizeof(pldm_msg_hdr) + sizeof(pelFileType) +
+ sizeof(id) + sizeof(uint64_t)>
+ requestMsg;
+
+ auto request = reinterpret_cast<pldm_msg*>(requestMsg.data());
+
+ auto rc = encode_new_file_req(_instanceID, pelFileType, id, size, request);
+ if (rc != PLDM_SUCCESS)
+ {
+ log<level::ERR>("encode_new_file_req failed", entry("RC=%d", rc));
+ throw std::exception{};
+ }
+
+ rc = pldm_send(_eid, _fd, requestMsg.data(), requestMsg.size());
+ if (rc < 0)
+ {
+ auto e = errno;
+ log<level::ERR>("pldm_send failed", entry("RC=%d", rc),
+ entry("ERRNO=%d", e));
+
+ throw std::exception{};
+ }
+}
+
+void PLDMInterface::receive(IO& io, int fd, uint32_t revents)
+{
+ if (!(revents & EPOLLIN))
+ {
+ return;
+ }
+
+ uint8_t* responseMsg = nullptr;
+ size_t responseSize = 0;
+ ResponseStatus status = ResponseStatus::success;
+
+ auto rc = pldm_recv(_eid, fd, _instanceID, &responseMsg, &responseSize);
+ if (rc < 0)
+ {
+ if (rc == PLDM_REQUESTER_INSTANCE_ID_MISMATCH)
+ {
+ // We got a response to someone else's message. Ignore it.
+ return;
+ }
+ else if (rc == PLDM_REQUESTER_NOT_RESP_MSG)
+ {
+ // Due to the MCTP loopback, we may get notified of the message
+ // we just sent.
+ return;
+ }
+
+ auto e = errno;
+ log<level::ERR>("pldm_recv failed", entry("RC=%d", rc),
+ entry("ERRNO=%d", e));
+ status = ResponseStatus::failure;
+
+ responseMsg = nullptr;
+ }
+
+ _inProgress = false;
+ _receiveTimer.setEnabled(false);
+ closeFD();
+ _source.reset();
+
+ if (status == ResponseStatus::success)
+ {
+ uint8_t completionCode = 0;
+ auto response = reinterpret_cast<pldm_msg*>(responseMsg);
+
+ auto decodeRC = decode_new_file_resp(response, PLDM_NEW_FILE_RESP_BYTES,
+ &completionCode);
+ if (decodeRC < 0)
+ {
+ log<level::ERR>("decode_new_file_resp failed",
+ entry("RC=%d", decodeRC));
+ status = ResponseStatus::failure;
+ }
+ else
+ {
+ if (completionCode != PLDM_SUCCESS)
+ {
+ log<level::ERR>("Bad PLDM completion code",
+ entry("COMPLETION_CODE=%d", completionCode));
+ status = ResponseStatus::failure;
+ }
+ }
+ }
+
+ if (_responseFunc)
+ {
+ try
+ {
+ (*_responseFunc)(status);
+ }
+ catch (const std::exception& e)
+ {
+ log<level::ERR>("PLDM response callback threw an exception",
+ entry("ERROR=%s", e.what()));
+ }
+ }
+
+ if (responseMsg)
+ {
+ free(responseMsg);
+ }
+}
+
+void PLDMInterface::receiveTimerExpired()
+{
+ log<level::ERR>("Timed out waiting for PLDM response");
+ cancelCmd();
+
+ if (_responseFunc)
+ {
+ try
+ {
+ (*_responseFunc)(ResponseStatus::failure);
+ }
+ catch (const std::exception& e)
+ {
+ log<level::ERR>("PLDM response callback threw an exception",
+ entry("ERROR=%s", e.what()));
+ }
+ }
+}
+
+void PLDMInterface::cancelCmd()
+{
+ _inProgress = false;
+ _source.reset();
+
+ if (_receiveTimer.isEnabled())
+ {
+ _receiveTimer.setEnabled(false);
+ }
+
+ closeFD();
+}
+
+} // namespace openpower::pels
diff --git a/extensions/openpower-pels/pldm_interface.hpp b/extensions/openpower-pels/pldm_interface.hpp
new file mode 100644
index 0000000..0d679a1
--- /dev/null
+++ b/extensions/openpower-pels/pldm_interface.hpp
@@ -0,0 +1,160 @@
+#pragma once
+
+#include "host_interface.hpp"
+
+#include <libpldm/pldm.h>
+
+#include <chrono>
+#include <memory>
+#include <sdeventplus/clock.hpp>
+#include <sdeventplus/source/io.hpp>
+#include <sdeventplus/utility/timer.hpp>
+
+namespace openpower::pels
+{
+
+/**
+ * @class PLDMInterface
+ *
+ * This class handles sending the 'new file available' PLDM
+ * command to the host to notify it of a new PEL's ID and size.
+ *
+ * The command response is asynchronous.
+ */
+class PLDMInterface : public HostInterface
+{
+ public:
+ PLDMInterface() = delete;
+ PLDMInterface(const PLDMInterface&) = default;
+ PLDMInterface& operator=(const PLDMInterface&) = default;
+ PLDMInterface(PLDMInterface&&) = default;
+ PLDMInterface& operator=(PLDMInterface&&) = default;
+
+ /**
+ * @brief Constructor
+ *
+ * @param[in] event - The sd_event object pointer
+ * @param[in] dataIface - The DataInterface object
+ */
+ PLDMInterface(sd_event* event, DataInterfaceBase& dataIface) :
+ HostInterface(event, dataIface),
+ _receiveTimer(
+ event,
+ std::bind(std::mem_fn(&PLDMInterface::receiveTimerExpired), this))
+ {
+ readEID();
+ }
+
+ /**
+ * @brief Destructor
+ */
+ ~PLDMInterface();
+
+ /**
+ * @brief Kicks off the send of the 'new file available' command
+ * to send up the ID and size of the new PEL.
+ *
+ * @param[in] id - The PEL ID
+ * @param[in] size - The PEL size in bytes
+ *
+ * @return CmdStatus - the success/fail status of the send
+ */
+ CmdStatus sendNewLogCmd(uint32_t id, uint32_t size) override;
+
+ /**
+ * @brief Cancels waiting for a command response
+ */
+ void cancelCmd() override;
+
+ private:
+ /**
+ * @brief The asynchronous callback for getting the response
+ * of the 'new file available' command.
+ *
+ * Calls the response callback that is registered.
+ *
+ * @param[in] io - The event source object
+ * @param[in] fd - The FD used
+ * @param[in] revents - The event bits
+ */
+ void receive(sdeventplus::source::IO& io, int fd,
+ uint32_t revents) override;
+
+ /**
+ * @brief Function called when the receive timer expires.
+ *
+ * This is considered a failure and so will invoke the
+ * registered response callback function with a failure
+ * indication.
+ */
+ void receiveTimerExpired();
+
+ /**
+ * @brief Configures the sdeventplus::source::IO object to
+ * call receive() on EPOLLIN activity on the PLDM FD
+ * which is used for command responses.
+ */
+ void registerReceiveCallback();
+
+ /**
+ * @brief Reads the MCTP endpoint ID out of a file
+ */
+ void readEID();
+
+ /**
+ * @brief Opens the PLDM file descriptor
+ */
+ void open();
+
+ /**
+ * @brief Reads the PLDM instance ID to use for the upcoming
+ * command.
+ */
+ void readInstanceID();
+
+ /**
+ * @brief Encodes and sends the PLDM 'new file available' cmd
+ *
+ * @param[in] id - The PEL ID
+ * @param[in] size - The PEL size in bytes
+ */
+ void doSend(uint32_t id, uint32_t size);
+
+ /**
+ * @brief Closes the PLDM file descriptor
+ */
+ void closeFD();
+
+ /**
+ * @brief The MCTP endpoint ID
+ */
+ mctp_eid_t _eid;
+
+ /**
+ * @brief The PLDM instance ID of the current command
+ */
+ uint8_t _instanceID;
+
+ /**
+ * @brief The PLDM command file descriptor for the current command
+ */
+ int _fd = -1;
+
+ /**
+ * @brief The event object for handling callbacks on the PLDM FD
+ */
+ std::unique_ptr<sdeventplus::source::IO> _source;
+
+ /**
+ * @brief A timer to only allow a certain amount of time for the
+ * async PLDM receive before it is considered a failure.
+ */
+ sdeventplus::utility::Timer<sdeventplus::ClockId::Monotonic> _receiveTimer;
+
+ /**
+ * @brief The command timeout value
+ */
+ const std::chrono::milliseconds _receiveTimeout{10000};
+};
+
+} // namespace openpower::pels
diff --git a/extensions/openpower-pels/private_header.cpp b/extensions/openpower-pels/private_header.cpp
new file mode 100644
index 0000000..1bf54ca
--- /dev/null
+++ b/extensions/openpower-pels/private_header.cpp
@@ -0,0 +1,194 @@
+/**
+ * Copyright © 2019 IBM Corporation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#include "private_header.hpp"
+
+#include "json_utils.hpp"
+#include "log_id.hpp"
+#include "pel_types.hpp"
+#include "pel_values.hpp"
+
+#include <phosphor-logging/log.hpp>
+
+namespace openpower
+{
+namespace pels
+{
+
+namespace pv = openpower::pels::pel_values;
+using namespace phosphor::logging;
+
+PrivateHeader::PrivateHeader(uint16_t componentID, uint32_t obmcLogID,
+ uint64_t timestamp)
+{
+ _header.id = static_cast<uint16_t>(SectionID::privateHeader);
+ _header.size = PrivateHeader::flattenedSize();
+ _header.version = privateHeaderVersion;
+ _header.subType = 0;
+ _header.componentID = componentID;
+
+ _createTimestamp = getBCDTime(timestamp);
+
+ auto now = std::chrono::system_clock::now();
+ _commitTimestamp = getBCDTime(now);
+
+ _creatorID = static_cast<uint8_t>(CreatorID::openBMC);
+
+ // Add support for reminder and telemetry log types here if
+ // ever necessary.
+ _logType = 0;
+
+ _reservedByte = 0;
+
+ // the final section count will be updated later
+ _sectionCount = 1;
+
+ _obmcLogID = obmcLogID;
+
+ _id = generatePELID();
+
+ _plid = _id;
+
+ // Leave _creatorVersion at 0
+
+ _valid = true;
+}
+
+PrivateHeader::PrivateHeader(Stream& pel) :
+ _creatorID(0), _logType(0), _reservedByte(0), _sectionCount(0),
+ _obmcLogID(0), _plid(0), _id(0)
+{
+ try
+ {
+ unflatten(pel);
+ validate();
+ }
+ catch (const std::exception& e)
+ {
+ log<level::ERR>("Cannot unflatten private header",
+ entry("ERROR=%s", e.what()));
+ _valid = false;
+ }
+}
+std::optional<std::string> PrivateHeader::getJSON() const
+{
+ char tmpPhVal[50];
+ sprintf(tmpPhVal, "%02X/%02X/%02X%02X %02X:%02X:%02X",
+ _createTimestamp.month, _createTimestamp.day,
+ _createTimestamp.yearMSB, _createTimestamp.yearLSB,
+ _createTimestamp.hour, _createTimestamp.minutes,
+ _createTimestamp.seconds);
+ std::string phCreateTStr(tmpPhVal);
+ sprintf(tmpPhVal, "%02X/%02X/%02X%02X %02X:%02X:%02X",
+ _commitTimestamp.month, _commitTimestamp.day,
+ _createTimestamp.yearMSB, _commitTimestamp.yearLSB,
+ _commitTimestamp.hour, _commitTimestamp.minutes,
+ _commitTimestamp.seconds);
+ std::string phCommitTStr(tmpPhVal);
+ sprintf(tmpPhVal, "%c", _creatorID);
+ std::string creator(tmpPhVal);
+ creator = pv::creatorIDs.count(creator) ? pv::creatorIDs.at(creator)
+ : "Unknown CreatorID";
+ std::string phCreatorVersionStr =
+ std::string(reinterpret_cast<const char*>(_creatorVersion.version));
+
+ sprintf(tmpPhVal, "0x%X", _header.componentID);
+ std::string phCbStr(tmpPhVal);
+ sprintf(tmpPhVal, "%d", _header.subType);
+ std::string phStStr(tmpPhVal);
+ sprintf(tmpPhVal, "%d", privateHeaderVersion);
+ std::string phVerStr(tmpPhVal);
+ sprintf(tmpPhVal, "0x%X", _plid);
+ std::string phPlatformIDStr(tmpPhVal);
+ sprintf(tmpPhVal, "0x%X", _id);
+ std::string phLogEntryIDStr(tmpPhVal);
+ std::string phObmcIDStr = std::to_string(_obmcLogID);
+ std::string ph;
+ jsonInsert(ph, "Section Version", phVerStr, 1);
+ jsonInsert(ph, "Sub-section type", phStStr, 1);
+ jsonInsert(ph, "Created by", phCbStr, 1);
+ jsonInsert(ph, "Created at", phCreateTStr, 1);
+ jsonInsert(ph, "Committed at", phCommitTStr, 1);
+ jsonInsert(ph, "Creator Subsystem", creator, 1);
+ jsonInsert(ph, "CSSVER", phCreatorVersionStr, 1);
+ jsonInsert(ph, "Platform Log Id", phPlatformIDStr, 1);
+ jsonInsert(ph, "Entry Id", phLogEntryIDStr, 1);
+ jsonInsert(ph, "BMC Event Log Id", phObmcIDStr, 1);
+ ph.erase(ph.size() - 2);
+
+ return ph;
+}
+void PrivateHeader::validate()
+{
+ bool failed = false;
+
+ if (header().id != static_cast<uint16_t>(SectionID::privateHeader))
+ {
+ log<level::ERR>("Invalid private header section ID",
+ entry("ID=0x%X", header().id));
+ failed = true;
+ }
+
+ if (header().version != privateHeaderVersion)
+ {
+ log<level::ERR>("Invalid private header version",
+ entry("VERSION=0x%X", header().version));
+ failed = true;
+ }
+
+ if (_sectionCount < minSectionCount)
+ {
+ log<level::ERR>("Invalid section count in private header",
+ entry("SECTION_COUNT=0x%X", _sectionCount));
+ failed = true;
+ }
+
+ _valid = (failed) ? false : true;
+}
+
+void PrivateHeader::unflatten(Stream& stream)
+{
+ stream >> _header >> _createTimestamp >> _commitTimestamp >> _creatorID >>
+ _logType >> _reservedByte >> _sectionCount >> _obmcLogID >>
+ _creatorVersion >> _plid >> _id;
+}
+
+void PrivateHeader::flatten(Stream& stream) const
+{
+ stream << _header << _createTimestamp << _commitTimestamp << _creatorID
+ << _logType << _reservedByte << _sectionCount << _obmcLogID
+ << _creatorVersion << _plid << _id;
+}
+
+Stream& operator>>(Stream& s, CreatorVersion& cv)
+{
+ for (size_t i = 0; i < sizeof(CreatorVersion); i++)
+ {
+ s >> cv.version[i];
+ }
+ return s;
+}
+
+Stream& operator<<(Stream& s, const CreatorVersion& cv)
+{
+ for (size_t i = 0; i < sizeof(CreatorVersion); i++)
+ {
+ s << cv.version[i];
+ }
+ return s;
+}
+
+} // namespace pels
+} // namespace openpower
diff --git a/extensions/openpower-pels/private_header.hpp b/extensions/openpower-pels/private_header.hpp
new file mode 100644
index 0000000..dd0d504
--- /dev/null
+++ b/extensions/openpower-pels/private_header.hpp
@@ -0,0 +1,312 @@
+#pragma once
+
+#include "bcd_time.hpp"
+#include "section.hpp"
+#include "stream.hpp"
+
+namespace openpower
+{
+namespace pels
+{
+
+struct CreatorVersion
+{
+ uint8_t version[8];
+
+ CreatorVersion()
+ {
+ memset(version, '\0', sizeof(version));
+ }
+};
+
+static constexpr uint8_t privateHeaderVersion = 0x01;
+static constexpr uint8_t minSectionCount = 2;
+
+/**
+ * @class PrivateHeader
+ *
+ * This represents the Private Header section in a PEL. It is required,
+ * and it is always the first section.
+ *
+ * The Section base class handles the section header structure that every
+ * PEL section has at offset zero.
+ *
+ * The fields in this class directly correspond to the order and sizes of
+ * the fields in the section.
+ */
+class PrivateHeader : public Section
+{
+ public:
+ PrivateHeader() = delete;
+ ~PrivateHeader() = default;
+ PrivateHeader(const PrivateHeader&) = default;
+ PrivateHeader& operator=(const PrivateHeader&) = default;
+ PrivateHeader(PrivateHeader&&) = default;
+ PrivateHeader& operator=(PrivateHeader&&) = default;
+
+ /**
+ * @brief Constructor
+ *
+ * Creates a valid PrivateHeader with the passed in data
+ *
+ * @param[in] componentID - the creator's component ID
+ * @param[in] obmcLogID - the corresponding OpenBMC event log ID
+ * @param[in] timestamp - the creation timestamp, in epoch milliseconds
+ */
+ PrivateHeader(uint16_t componentID, uint32_t obmcLogID, uint64_t timestamp);
+
+ /**
+ * @brief Constructor
+ *
+ * Fills in this class's data fields from the stream.
+ *
+ * @param[in] pel - the PEL data stream
+ *
+ */
+ explicit PrivateHeader(Stream& pel);
+
+ /**
+ * @brief Flatten the section into the stream
+ *
+ * @param[in] stream - The stream to write to
+ */
+ void flatten(Stream& stream) const override;
+
+ /**
+ * @brief Returns the creation timestamp
+ *
+ * @return const BCDTime& - the timestamp
+ */
+ const BCDTime& createTimestamp() const
+ {
+ return _createTimestamp;
+ }
+
+ /**
+ * @brief Returns the commit time timestamp
+ *
+ * @return const BCDTime& - the timestamp
+ */
+ const BCDTime& commitTimestamp() const
+ {
+ return _commitTimestamp;
+ }
+
+ /**
+ * @brief Sets the commit timestamp
+ *
+ * @param[in] time - the new timestamp
+ */
+ void setCommitTimestamp(const BCDTime& time)
+ {
+ _commitTimestamp = time;
+ }
+
+ /**
+ * @brief Returns the creator ID field
+ *
+ * @return uint8_t - the creator ID
+ */
+ uint8_t creatorID() const
+ {
+ return _creatorID;
+ }
+
+ /**
+ * @brief Returns the log type field
+ *
+ * @return uint8_t - the log type
+ */
+ uint8_t logType() const
+ {
+ return _logType;
+ }
+
+ /**
+ * @brief Returns the section count field
+ *
+ * @return uint8_t - the section count
+ */
+ uint8_t sectionCount() const
+ {
+ return _sectionCount;
+ }
+
+ /**
+ * @brief Sets the section count field
+ *
+ * @param[in] count - the new section count
+ */
+ void setSectionCount(uint8_t count)
+ {
+ _sectionCount = count;
+ }
+
+ /**
+ * @brief Returns the OpenBMC log ID field
+ *
+ * This is the ID the OpenBMC event log that corresponds
+ * to this PEL.
+ *
+ * @return uint32_t - the OpenBMC event log ID
+ */
+ uint32_t obmcLogID() const
+ {
+ return _obmcLogID;
+ }
+
+ /**
+ * @brief Sets the OpenBMC log ID field
+ *
+ * @param[in] id - the new ID
+ */
+ void setOBMCLogID(uint32_t id)
+ {
+ _obmcLogID = id;
+ }
+
+ /**
+ * @brief Returns the Creator Version field
+ *
+ * @return CreatorVersion& - the creator version
+ */
+ const CreatorVersion& creatorVersion() const
+ {
+ return _creatorVersion;
+ }
+
+ /**
+ * @brief Returns the error log ID field
+ *
+ * @return uint32_t - the error log ID
+ */
+ uint32_t id() const
+ {
+ return _id;
+ }
+
+ /**
+ * @brief Sets the ID field
+ *
+ * @param[in] id - the new ID
+ */
+ void setID(uint32_t id)
+ {
+ _id = id;
+ }
+
+ /**
+ * @brief Returns the platform log ID field
+ *
+ * @return uint32_t - the platform log ID
+ */
+ uint32_t plid() const
+ {
+ return _plid;
+ }
+
+ /**
+ * @brief Returns the size of this section when flattened into a PEL
+ *
+ * @return size_t - the size of the section
+ */
+ static constexpr size_t flattenedSize()
+ {
+ return Section::flattenedSize() + sizeof(_createTimestamp) +
+ sizeof(_commitTimestamp) + sizeof(_creatorID) +
+ sizeof(_logType) + sizeof(_reservedByte) +
+ sizeof(_sectionCount) + sizeof(_obmcLogID) +
+ sizeof(_creatorVersion) + sizeof(_plid) + sizeof(_id);
+ }
+
+ /**
+ * @brief Get section in JSON.
+ * @return std::optional<std::string> - Private header section's JSON
+ */
+ std::optional<std::string> getJSON() const override;
+
+ private:
+ /**
+ * @brief Fills in the object from the stream data
+ *
+ * @param[in] stream - The stream to read from
+ */
+ void unflatten(Stream& stream);
+
+ /**
+ * @brief Validates the section contents
+ *
+ * Updates _valid (in Section) with the results.
+ */
+ void validate() override;
+
+ /**
+ * @brief The creation time timestamp
+ */
+ BCDTime _createTimestamp;
+
+ /**
+ * @brief The commit time timestamp
+ */
+ BCDTime _commitTimestamp;
+
+ /**
+ * @brief The creator ID field
+ */
+ uint8_t _creatorID;
+
+ /**
+ * @brief The log type field
+ */
+ uint8_t _logType;
+
+ /**
+ * @brief A reserved byte.
+ */
+ uint8_t _reservedByte;
+
+ /**
+ * @brief The section count field, which is the total number
+ * of sections in the PEL.
+ */
+ uint8_t _sectionCount;
+
+ /**
+ * @brief The OpenBMC event log ID that corresponds to this PEL.
+ */
+ uint32_t _obmcLogID;
+
+ /**
+ * @brief The creator subsystem version field
+ */
+ CreatorVersion _creatorVersion;
+
+ /**
+ * @brief The platform log ID field
+ */
+ uint32_t _plid;
+
+ /**
+ * @brief The log entry ID field
+ */
+ uint32_t _id;
+};
+
+/**
+ * @brief Stream extraction operator for the CreatorVersion
+ *
+ * @param[in] s - the stream
+ * @param[out] cv - the CreatorVersion object
+ */
+Stream& operator>>(Stream& s, CreatorVersion& cv);
+
+/**
+ * @brief Stream insertion operator for the CreatorVersion
+ *
+ * @param[out] s - the stream
+ * @param[in] cv - the CreatorVersion object
+ */
+Stream& operator<<(Stream& s, const CreatorVersion& cv);
+
+} // namespace pels
+} // namespace openpower
diff --git a/extensions/openpower-pels/registry.cpp b/extensions/openpower-pels/registry.cpp
new file mode 100644
index 0000000..3c4e016
--- /dev/null
+++ b/extensions/openpower-pels/registry.cpp
@@ -0,0 +1,424 @@
+/**
+ * Copyright © 2019 IBM Corporation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#include "registry.hpp"
+
+#include "pel_types.hpp"
+#include "pel_values.hpp"
+
+#include <fstream>
+#include <phosphor-logging/log.hpp>
+
+namespace openpower
+{
+namespace pels
+{
+namespace message
+{
+
+namespace pv = pel_values;
+namespace fs = std::filesystem;
+using namespace phosphor::logging;
+
+constexpr auto debugFilePath = "/etc/phosphor-logging/";
+
+namespace helper
+{
+
+uint8_t getSubsystem(const std::string& subsystemName)
+{
+ // Get the actual value to use in the PEL for the string name
+ auto ss = pv::findByName(subsystemName, pv::subsystemValues);
+ if (ss == pv::subsystemValues.end())
+ {
+ // Schema validation should be catching this.
+ log<level::ERR>("Invalid subsystem name used in message registry",
+ entry("SUBSYSTEM=%s", subsystemName.c_str()));
+
+ throw std::runtime_error("Invalid subsystem used in message registry");
+ }
+
+ return std::get<pv::fieldValuePos>(*ss);
+}
+
+uint8_t getSeverity(const std::string& severityName)
+{
+ auto s = pv::findByName(severityName, pv::severityValues);
+ if (s == pv::severityValues.end())
+ {
+ // Schema validation should be catching this.
+ log<level::ERR>("Invalid severity name used in message registry",
+ entry("SEVERITY=%s", severityName.c_str()));
+
+ throw std::runtime_error("Invalid severity used in message registry");
+ }
+
+ return std::get<pv::fieldValuePos>(*s);
+}
+
+uint16_t getActionFlags(const std::vector<std::string>& flags)
+{
+ uint16_t actionFlags = 0;
+
+ // Make the bitmask based on the array of flag names
+ for (const auto& flag : flags)
+ {
+ auto s = pv::findByName(flag, pv::actionFlagsValues);
+ if (s == pv::actionFlagsValues.end())
+ {
+ // Schema validation should be catching this.
+ log<level::ERR>("Invalid action flag name used in message registry",
+ entry("FLAG=%s", flag.c_str()));
+
+ throw std::runtime_error(
+ "Invalid action flag used in message registry");
+ }
+
+ actionFlags |= std::get<pv::fieldValuePos>(*s);
+ }
+
+ return actionFlags;
+}
+
+uint8_t getEventType(const std::string& eventTypeName)
+{
+ auto t = pv::findByName(eventTypeName, pv::eventTypeValues);
+ if (t == pv::eventTypeValues.end())
+ {
+ log<level::ERR>("Invalid event type used in message registry",
+ entry("EVENT_TYPE=%s", eventTypeName.c_str()));
+
+ throw std::runtime_error("Invalid event type used in message registry");
+ }
+ return std::get<pv::fieldValuePos>(*t);
+}
+
+uint8_t getEventScope(const std::string& eventScopeName)
+{
+ auto s = pv::findByName(eventScopeName, pv::eventScopeValues);
+ if (s == pv::eventScopeValues.end())
+ {
+ log<level::ERR>("Invalid event scope used in registry",
+ entry("EVENT_SCOPE=%s", eventScopeName.c_str()));
+
+ throw std::runtime_error(
+ "Invalid event scope used in message registry");
+ }
+ return std::get<pv::fieldValuePos>(*s);
+}
+
+uint16_t getSRCReasonCode(const nlohmann::json& src, const std::string& name)
+{
+ std::string rc = src["ReasonCode"];
+ uint16_t reasonCode = strtoul(rc.c_str(), nullptr, 16);
+ if (reasonCode == 0)
+ {
+ log<phosphor::logging::level::ERR>(
+ "Invalid reason code in message registry",
+ entry("ERROR_NAME=%s", name.c_str()),
+ entry("REASON_CODE=%s", rc.c_str()));
+
+ throw std::runtime_error("Invalid reason code in message registry");
+ }
+ return reasonCode;
+}
+
+uint8_t getSRCType(const nlohmann::json& src, const std::string& name)
+{
+ // Looks like: "22"
+ std::string srcType = src["Type"];
+ size_t type = strtoul(srcType.c_str(), nullptr, 16);
+ if ((type == 0) || (srcType.size() != 2)) // 1 hex byte
+ {
+ log<phosphor::logging::level::ERR>(
+ "Invalid SRC Type in message registry",
+ entry("ERROR_NAME=%s", name.c_str()),
+ entry("SRC_TYPE=%s", srcType.c_str()));
+
+ throw std::runtime_error("Invalid SRC Type in message registry");
+ }
+
+ return type;
+}
+
+std::optional<std::map<SRC::WordNum, SRC::AdditionalDataField>>
+ getSRCHexwordFields(const nlohmann::json& src, const std::string& name)
+{
+ std::map<SRC::WordNum, SRC::AdditionalDataField> hexwordFields;
+
+ // Build the map of which AdditionalData fields to use for which SRC words
+
+ // Like:
+ // {
+ // "8":
+ // {
+ // "AdditionalDataPropSource": "TEST"
+ // }
+ //
+ // }
+
+ for (const auto& word : src["Words6To9"].items())
+ {
+ std::string num = word.key();
+ size_t wordNum = std::strtoul(num.c_str(), nullptr, 10);
+
+ if (wordNum == 0)
+ {
+ log<phosphor::logging::level::ERR>(
+ "Invalid SRC word number in message registry",
+ entry("ERROR_NAME=%s", name.c_str()),
+ entry("SRC_WORD_NUM=%s", num.c_str()));
+
+ throw std::runtime_error("Invalid SRC word in message registry");
+ }
+
+ auto attributes = word.value();
+ std::string adPropName = attributes["AdditionalDataPropSource"];
+ hexwordFields[wordNum] = std::move(adPropName);
+ }
+
+ if (!hexwordFields.empty())
+ {
+ return hexwordFields;
+ }
+
+ return std::nullopt;
+}
+std::optional<std::vector<SRC::WordNum>>
+ getSRCSymptomIDFields(const nlohmann::json& src, const std::string& name)
+{
+ std::vector<SRC::WordNum> symptomIDFields;
+
+ // Looks like:
+ // "SymptomIDFields": ["SRCWord3", "SRCWord6"],
+
+ for (const std::string& field : src["SymptomIDFields"])
+ {
+ // Just need the last digit off the end, e.g. SRCWord6.
+ // The schema enforces the format of these.
+ auto srcWordNum = field.substr(field.size() - 1);
+ size_t num = std::strtoul(srcWordNum.c_str(), nullptr, 10);
+ if (num == 0)
+ {
+ log<phosphor::logging::level::ERR>(
+ "Invalid symptom ID field in message registry",
+ entry("ERROR_NAME=%s", name.c_str()),
+ entry("FIELD_NAME=%s", srcWordNum.c_str()));
+
+ throw std::runtime_error("Invalid symptom ID in message registry");
+ }
+ symptomIDFields.push_back(num);
+ }
+ if (!symptomIDFields.empty())
+ {
+ return symptomIDFields;
+ }
+
+ return std::nullopt;
+}
+
+uint16_t getComponentID(uint8_t srcType, uint16_t reasonCode,
+ const nlohmann::json& pelEntry, const std::string& name)
+{
+ uint16_t id = 0;
+
+ // If the ComponentID field is there, use that. Otherwise, if it's a
+ // 0xBD BMC error SRC, use the reasoncode.
+ if (pelEntry.find("ComponentID") != pelEntry.end())
+ {
+ std::string componentID = pelEntry["ComponentID"];
+ id = strtoul(componentID.c_str(), nullptr, 16);
+ }
+ else
+ {
+ // On BMC error SRCs (BD), can just get the component ID from
+ // the first byte of the reason code.
+ if (srcType == static_cast<uint8_t>(SRCType::bmcError))
+ {
+ id = reasonCode & 0xFF00;
+ }
+ else
+ {
+ log<level::ERR>("Missing component ID field in message registry",
+ entry("ERROR_NAME=%s", name.c_str()));
+
+ throw std::runtime_error(
+ "Missing component ID field in message registry");
+ }
+ }
+
+ return id;
+}
+
+} // namespace helper
+
+std::optional<Entry> Registry::lookup(const std::string& name, LookupType type,
+ bool toCache)
+{
+ std::optional<nlohmann::json> registryTmp;
+ auto& registryOpt = (_registry) ? _registry : registryTmp;
+ if (!registryOpt)
+ {
+ registryOpt = readRegistry(_registryFile);
+ if (!registryOpt)
+ {
+ return std::nullopt;
+ }
+ else if (toCache)
+ {
+ // Save message registry in memory for peltool
+ _registry = std::move(registryTmp);
+ }
+ }
+ auto& reg = (_registry) ? _registry : registryTmp;
+ const auto& registry = reg.value();
+ // Find an entry with this name in the PEL array.
+ auto e = std::find_if(
+ registry["PELs"].begin(), registry["PELs"].end(),
+ [&name, &type](const auto& j) {
+ return ((name == j["Name"] && type == LookupType::name) ||
+ (name == j["SRC"]["ReasonCode"] &&
+ type == LookupType::reasonCode));
+ });
+
+ if (e != registry["PELs"].end())
+ {
+ // Fill in the Entry structure from the JSON. Most, but not all, fields
+ // are optional.
+
+ try
+ {
+ Entry entry;
+ entry.name = (*e)["Name"];
+ entry.subsystem = helper::getSubsystem((*e)["Subsystem"]);
+
+ if (e->find("ActionFlags") != e->end())
+ {
+ entry.actionFlags = helper::getActionFlags((*e)["ActionFlags"]);
+ }
+
+ if (e->find("MfgActionFlags") != e->end())
+ {
+ entry.mfgActionFlags =
+ helper::getActionFlags((*e)["MfgActionFlags"]);
+ }
+
+ if (e->find("Severity") != e->end())
+ {
+ entry.severity = helper::getSeverity((*e)["Severity"]);
+ }
+
+ if (e->find("MfgSeverity") != e->end())
+ {
+ entry.mfgSeverity = helper::getSeverity((*e)["MfgSeverity"]);
+ }
+
+ if (e->find("EventType") != e->end())
+ {
+ entry.eventType = helper::getEventType((*e)["EventType"]);
+ }
+
+ if (e->find("EventScope") != e->end())
+ {
+ entry.eventScope = helper::getEventScope((*e)["EventScope"]);
+ }
+
+ auto& src = (*e)["SRC"];
+ entry.src.reasonCode = helper::getSRCReasonCode(src, name);
+
+ if (src.find("Type") != src.end())
+ {
+ entry.src.type = helper::getSRCType(src, name);
+ }
+ else
+ {
+ entry.src.type = static_cast<uint8_t>(SRCType::bmcError);
+ }
+
+ // Now that we know the SRC type and reason code,
+ // we can get the component ID.
+ entry.componentID = helper::getComponentID(
+ entry.src.type, entry.src.reasonCode, *e, name);
+
+ if (src.find("Words6To9") != src.end())
+ {
+ entry.src.hexwordADFields =
+ helper::getSRCHexwordFields(src, name);
+ }
+
+ if (src.find("SymptomIDFields") != src.end())
+ {
+ entry.src.symptomID = helper::getSRCSymptomIDFields(src, name);
+ }
+
+ if (src.find("PowerFault") != src.end())
+ {
+ entry.src.powerFault = src["PowerFault"];
+ }
+
+ auto& doc = (*e)["Documentation"];
+ entry.doc.message = doc["Message"];
+ entry.doc.description = doc["Description"];
+ if (doc.find("MessageArgSources") != doc.end())
+ {
+ entry.doc.messageArgSources = doc["MessageArgSources"];
+ }
+
+ return entry;
+ }
+ catch (std::exception& e)
+ {
+ log<level::ERR>("Found invalid message registry field",
+ entry("ERROR=%s", e.what()));
+ }
+ }
+
+ return std::nullopt;
+}
+
+std::optional<nlohmann::json>
+ Registry::readRegistry(const std::filesystem::path& registryFile)
+{
+ // Look in /etc first in case someone put a test file there
+ fs::path debugFile{fs::path{debugFilePath} / registryFileName};
+ nlohmann::json registry;
+ std::ifstream file;
+
+ if (fs::exists(debugFile))
+ {
+ log<level::INFO>("Using debug PEL message registry");
+ file.open(debugFile);
+ }
+ else
+ {
+ file.open(registryFile);
+ }
+
+ try
+ {
+ registry = nlohmann::json::parse(file);
+ }
+ catch (std::exception& e)
+ {
+ log<level::ERR>("Error parsing message registry JSON",
+ entry("JSON_ERROR=%s", e.what()));
+ return std::nullopt;
+ }
+ return registry;
+}
+
+} // namespace message
+} // namespace pels
+} // namespace openpower
diff --git a/extensions/openpower-pels/registry.hpp b/extensions/openpower-pels/registry.hpp
new file mode 100644
index 0000000..0a75d8e
--- /dev/null
+++ b/extensions/openpower-pels/registry.hpp
@@ -0,0 +1,358 @@
+#pragma once
+#include <filesystem>
+#include <nlohmann/json.hpp>
+#include <optional>
+#include <string>
+#include <vector>
+
+namespace openpower
+{
+namespace pels
+{
+namespace message
+{
+
+constexpr auto registryFileName = "message_registry.json";
+enum class LookupType
+{
+ name = 0,
+ reasonCode = 1
+};
+
+/**
+ * @brief Represents the Documentation related fields in the message registry.
+ * It is part of the 'Entry' structure that will be filled in when
+ * an error is looked up in the registry.
+ *
+ * If a field is wrapped by std::optional, it means the field is
+ * optional in the JSON and higher level code knows how to handle it.
+ */
+struct DOC
+{
+ /**
+ * @brief Description of error
+ */
+ std::string description;
+
+ /**
+ * @brief Error message field
+ */
+ std::string message;
+
+ /**
+ * @brief An optional vector of SRC word 6-9 to use as the source of the
+ * numeric arguments that will be substituted into any placeholder
+ * in the Message field.
+ */
+ std::optional<std::vector<std::string>> messageArgSources;
+};
+
+/**
+ * @brief Represents the SRC related fields in the message registry.
+ * It is part of the 'Entry' structure that will be filled in when
+ * an error is looked up in the registry.
+ *
+ * If a field is wrapped by std::optional, it means the field is
+ * optional in the JSON and higher level code knows how to handle it.
+ */
+struct SRC
+{
+ /**
+ * @brief SRC type - The first byte of the ASCII string
+ */
+ uint8_t type;
+
+ /**
+ * @brief The SRC reason code (2nd half of 4B 'ASCII string' word)
+ */
+ uint16_t reasonCode;
+
+ /**
+ * @brief Specifies if the SRC represents a power fault. Optional.
+ */
+ std::optional<bool> powerFault;
+
+ /**
+ * @brief An optional vector of SRC hexword numbers that should be used
+ * along with the SRC ASCII string to build the Symptom ID, which
+ * is a field in the Extended Header section.
+ */
+ using WordNum = size_t;
+ std::optional<std::vector<WordNum>> symptomID;
+
+ /**
+ * @brief Which AdditionalData fields to use to fill in the user defined
+ * SRC hexwords.
+ *
+ * For example, if the AdditionalData event log property contained
+ * "CHIPNUM=42" and this map contained {6, CHIPNUM}, then the code
+ * would put 42 into SRC hexword 6.
+ */
+ using AdditionalDataField = std::string;
+ std::optional<std::map<WordNum, AdditionalDataField>> hexwordADFields;
+
+ SRC() : type(0), reasonCode(0)
+ {
+ }
+};
+
+/**
+ * @brief Represents a message registry entry, which is used for creating a
+ * PEL from an OpenBMC event log.
+ */
+struct Entry
+{
+ /**
+ * @brief The error name, like "xyz.openbmc_project.Error.Foo".
+ */
+ std::string name;
+
+ /**
+ * @brief The component ID of the PEL creator.
+ */
+ uint16_t componentID;
+
+ /**
+ * @brief The PEL subsystem field.
+ */
+ uint8_t subsystem;
+
+ /**
+ * @brief The optional PEL severity field. If not specified, the PEL
+ * will use the severity of the OpenBMC event log.
+ */
+ std::optional<uint8_t> severity;
+
+ /**
+ * @brief The optional severity field to use when in manufacturing tolerance
+ * mode.
+ */
+ std::optional<uint8_t> mfgSeverity;
+
+ /**
+ * @brief The PEL action flags field.
+ */
+ std::optional<uint16_t> actionFlags;
+
+ /**
+ * @brief The optional action flags to use instead when in manufacturing
+ * tolerance mode.
+ */
+ std::optional<uint16_t> mfgActionFlags;
+
+ /**
+ * @brief The PEL event type field. If not specified, higher level code
+ * will decide the value.
+ */
+ std::optional<uint8_t> eventType;
+
+ /**
+ * @brief The PEL event scope field. If not specified, higher level code
+ * will decide the value.
+ */
+ std::optional<uint8_t> eventScope;
+
+ /**
+ * The SRC related fields.
+ */
+ SRC src;
+
+ /**
+ * The Documentation related fields.
+ */
+ DOC doc;
+};
+
+/**
+ * @class Registry
+ *
+ * This class wraps the message registry JSON data and allows one to find
+ * the message registry entry pertaining to the error name.
+ *
+ * So that new registry files can easily be tested, the code will look for
+ * /etc/phosphor-logging/message_registry.json before looking for the real
+ * path.
+ */
+class Registry
+{
+ public:
+ Registry() = delete;
+ ~Registry() = default;
+ Registry(const Registry&) = default;
+ Registry& operator=(const Registry&) = default;
+ Registry(Registry&&) = default;
+ Registry& operator=(Registry&&) = default;
+
+ /**
+ * @brief Constructor
+ * @param[in] registryFile - The path to the file.
+ */
+ explicit Registry(const std::filesystem::path& registryFile) :
+ _registryFile(registryFile)
+ {
+ }
+
+ /**
+ * @brief Find a registry entry based on its error name or reason code.
+ *
+ * This function does do some basic sanity checking on the JSON contents,
+ * but there is also an external program that enforces a schema on the
+ * registry JSON that should catch all of these problems ahead of time.
+ *
+ * @param[in] name - The error name, like xyz.openbmc_project.Error.Foo
+ * - OR
+ * - The reason code, like 0x1001
+ * @param[in] type - LookupType enum value
+ * @param[in] toCache - boolean to cache registry in memory
+ * @return optional<Entry> A filled in message registry structure if
+ * found, otherwise an empty optional object.
+ */
+ std::optional<Entry> lookup(const std::string& name, LookupType type,
+ bool toCache = false);
+
+ private:
+ /**
+ * @brief Parse message registry file using nlohmann::json
+ * @param[in] registryFile - The message registry JSON file
+ * @return optional<nlohmann::json> The full message registry object or an
+ * empty optional object upon failure.
+ */
+ std::optional<nlohmann::json>
+ readRegistry(const std::filesystem::path& registryFile);
+
+ /**
+ * @brief The path to the registry JSON file.
+ */
+ std::filesystem::path _registryFile;
+
+ /**
+ * @brief The full message registry object.
+ */
+ std::optional<nlohmann::json> _registry;
+};
+
+namespace helper
+{
+
+/**
+ * @brief A helper function to get the PEL subsystem value based on
+ * the registry subsystem name.
+ *
+ * @param[in] subsystemName - The registry name for the subsystem
+ *
+ * @return uint8_t The PEL subsystem value
+ */
+uint8_t getSubsystem(const std::string& subsystemName);
+
+/**
+ * @brief A helper function to get the PEL severity value based on
+ * the registry severity name.
+ *
+ * @param[in] severityName - The registry name for the severity
+ *
+ * @return uint8_t The PEL severity value
+ */
+uint8_t getSeverity(const std::string& severityName);
+
+/**
+ * @brief A helper function to get the action flags value based on
+ * the action flag names used in the registry.
+ *
+ * @param[in] flags - The list of flag names from the registry.
+ *
+ * @return uint16_t - The bitfield of flags used in the PEL.
+ */
+uint16_t getActionFlags(const std::vector<std::string>& flags);
+
+/**
+ * @brief A helper function to get the PEL event type value based on
+ * the registry event type name.
+ *
+ * @param[in] eventTypeName - The registry name for the event type
+ *
+ * @return uint8_t The PEL event type value
+ */
+uint8_t getEventType(const std::string& eventTypeName);
+
+/**
+ * @brief A helper function to get the PEL event scope value based on
+ * the registry event scope name.
+ *
+ * @param[in] eventScopeName - The registry name for the event scope
+ *
+ * @return uint8_t The PEL event scope value
+ */
+uint8_t getEventScope(const std::string& eventScopeName);
+
+/**
+ * @brief Reads the "ReasonCode" field out of JSON and converts the string value
+ * such as "0x5555" to a uint16 like 0x5555.
+ *
+ * @param[in] src - The message registry SRC dictionary to read from
+ * @param[in] name - The error name, to use in a trace if things go awry.
+ *
+ * @return uint16_t - The reason code
+ */
+uint16_t getSRCReasonCode(const nlohmann::json& src, const std::string& name);
+
+/**
+ * @brief Reads the "Type" field out of JSON and converts it to the SRC::Type
+ * value.
+ *
+ * @param[in] src - The message registry SRC dictionary to read from
+ * @param[in] name - The error name, to use in a trace if things go awry.
+ *
+ * @return uint8_t - The SRC type value, like 0x11
+ */
+uint8_t getSRCType(const nlohmann::json& src, const std::string& name);
+
+/**
+ * @brief Reads the "Words6To9" field out of JSON and converts it to a map
+ * of the SRC word number to the AdditionalData property field used
+ * to fill it in with.
+ *
+ * @param[in] src - The message registry SRC dictionary to read from
+ * @param[in] name - The error name, to use in a trace if things go awry.
+ *
+ * @return std::optional<std::map<SRC::WordNum, SRC::AdditionalDataField>>
+ */
+std::optional<std::map<SRC::WordNum, SRC::AdditionalDataField>>
+ getSRCHexwordFields(const nlohmann::json& src, const std::string& name);
+
+/**
+ * @brief Reads the "SymptomIDFields" field out of JSON and converts it to
+ * a vector of SRC word numbers.
+ *
+ * @param[in] src - The message registry SRC dictionary to read from
+ * @param[in] name - The error name, to use in a trace if things go awry.
+ *
+ * @return std::optional<std::vector<SRC::WordNum>>
+ */
+std::optional<std::vector<SRC::WordNum>>
+ getSRCSymptomIDFields(const nlohmann::json& src, const std::string& name);
+
+/**
+ * @brief Reads the "ComponentID" field out of JSON and converts it to a
+ * uint16_t like 0xFF00.
+ *
+ * The ComponentID JSON field is only required if the SRC type isn't a BD
+ * BMC SRC, because for those SRCs it can be inferred from the upper byte
+ * of the SRC reasoncode.
+ *
+ * @param[in] srcType - The SRC type
+ * @param[in] reasonCode - The SRC reason code
+ * @param[in] pelEntry - The PEL entry JSON
+ * @param[in] name - The error name, to use in a trace if things go awry.
+ *
+ * @return uin16_t - The component ID, like 0xFF00
+ */
+uint16_t getComponentID(uint8_t srcType, uint16_t reasonCode,
+ const nlohmann::json& pelEntry,
+ const std::string& name);
+
+} // namespace helper
+
+} // namespace message
+
+} // namespace pels
+} // namespace openpower
diff --git a/extensions/openpower-pels/registry/ComponentIDs.md b/extensions/openpower-pels/registry/ComponentIDs.md
new file mode 100644
index 0000000..c74842d
--- /dev/null
+++ b/extensions/openpower-pels/registry/ComponentIDs.md
@@ -0,0 +1,9 @@
+# PEL Component ID List
+
+See [here](README.md#component-ids) for details on how PEL component IDs are
+used.
+
+| Component ID | Repository |
+|--------------|------------|
+| 0x1000 | Common Errors that span repositories |
+| 0x2000 | phosphor-logging |
diff --git a/extensions/openpower-pels/registry/README.md b/extensions/openpower-pels/registry/README.md
new file mode 100644
index 0000000..a0e6f49
--- /dev/null
+++ b/extensions/openpower-pels/registry/README.md
@@ -0,0 +1,240 @@
+# Platform Event Log Message Registry
+On the BMC, PELs are created from the standard event logs provided by
+phosphor-logging using a message registry that provides the PEL related fields.
+The message registry is a JSON file.
+
+## Contents
+* [Component IDs](#component-ids)
+* [Message Registry](#message-registry-fields)
+
+## Component IDs
+A component ID is a 2 byte value of the form 0xYY00 used in a PEL to:
+1. Provide the upper byte (the YY from above) of an SRC reason code in `BD`
+ SRCs.
+2. Reside in the section header of the Private Header PEL section to specify
+ the error log creator's component ID.
+3. Reside in the section header of the User Header section to specify the error
+ log committer's component ID.
+4. Reside in the section header in the User Data section to specify which
+ parser to call to parse that section.
+
+Component IDs are specified in the message registry either as the upper byte of
+the SRC reason code field for `BD` SRCs, or in the standalone `ComponentID`
+field.
+
+Component IDs will be unique on a per-repository basis for errors unique to
+that repository. When the same errors are created by multiple repositories,
+those errors will all share the same component ID. The master list of
+component IDs is [here](ComponentIDs.md).
+
+## Message Registry Fields
+The message registry schema is [here](schema/schema.json), and the message
+registry itself is [here](message_registry.json). The schema will be validated
+either during a bitbake build or during CI, or eventually possibly both.
+
+In the message registry, there are fields for specifying:
+
+### Name
+This is the key into the message registry, and is the Message property
+of the OpenBMC event log that the PEL is being created from.
+
+```
+"Name": "xyz.openbmc_project.Power.Fault"
+```
+
+### Subsystem
+This field is part of the PEL User Header section, and is used to specify
+the subsystem pertaining to the error. It is an enumeration that maps to the
+actual PEL value.
+
+```
+"Subsystem": "power_supply"
+```
+
+### Severity
+This field is part of the PEL User Header section, and is used to specify
+the PEL severity. It is an optional field, if it isn't specified, then the
+severity of the OpenBMC event log will be converted into a PEL severity value.
+
+```
+"Severity": "unrecoverable"
+```
+
+### Mfg Severity
+This is an optional field and is used to override the Severity field when a
+specific manufacturing isolation mode is enabled.
+
+```
+"MfgSeverity": "unrecoverable"
+```
+
+### Event Scope
+This field is part of the PEL User Header section, and is used to specify
+the event scope, as defined by the PEL spec. It is optional and defaults to
+"entire platform".
+
+```
+"EventScope": "entire_platform"
+```
+
+### Event Type
+This field is part of the PEL User Header section, and is used to specify
+the event type, as defined by the PEL spec. It is optional and defaults to
+"not applicable" for non-informational logs, and "misc_information_only" for
+informational ones.
+
+```
+"EventType": "na"
+```
+
+### Action Flags
+This field is part of the PEL User Header section, and is used to specify the
+PEL action flags, as defined by the PEL spec. It is an array of enumerations.
+
+The action flags can usually be deduced from other PEL fields, such as the
+severity or if there are any callouts. As such, this is an optional field and
+if not supplied the code will fill them in based on those fields.
+
+In fact, even if supplied here, the code may still modify them to ensure they
+are correct.
+
+```
+"ActionFlags": ["service_action", "report", "call_home"]
+```
+
+### Mfg Action Flags
+This is an optional field and is used to override the Action Flags field when a
+specific manufacturing isolation mode is enabled.
+
+```
+"MfgActionFlags": ["service_action", "report", "call_home"]
+```
+
+### Component ID
+This is the component ID of the PEL creator, in the form 0xYY00. For `BD`
+SRCs, this is an optional field and if not present the value will be taken from
+the upper byte of the reason code. If present for `BD` SRCs, then this byte
+must match the upper byte of the reason code.
+
+```
+"ComponentID": "0x5500"
+```
+
+### SRC Type
+This specifies the type of SRC to create. The type is the first 2 characters
+of the 8 character ASCII string field of the PEL. The allowed types are `BD`,
+for the standard OpenBMC error, and `11`, for power related errors. It is
+optional and if not specified will default to `BD`.
+
+Note: The ASCII string for BD SRCs looks like: `BDBBCCCC`, where:
+* BD = SRC type
+* BB = PEL subsystem as mentioned above
+* CCCC SRC reason code
+
+For `11` SRCs, it looks like: `1100RRRR`, where RRRR is the SRC reason code.
+
+```
+"Type": "11"
+```
+
+### SRC Reason Code
+This is the 4 character value in the latter half of the SRC ASCII string. It
+is treated as a 2 byte hex value, such as 0x5678. For `BD` SRCs, the first
+byte is the same as the first byte of the component ID field in the Private
+Header section that represents the creator's component ID.
+
+```
+"ReasonCode": "0x5544"
+```
+
+### SRC Symptom ID Fields
+The symptom ID is in the Extended User Header section and is defined in the PEL
+spec as the unique event signature string. It always starts with the ASCII
+string. This field in the message registry allows one to choose which SRC words
+to use in addition to the ASCII string field to form the symptom ID. All words
+are separated by underscores. If not specified, the code will choose a default
+format, which may depend on the SRC type.
+
+For example: ["SRCWord3", "SRCWord9"] would be:
+`<ASCII_STRING>_<SRCWord3>_<SRCWord9>`, which could look like:
+`B181320_00000050_49000000`.
+
+```
+"SymptomIDFields": ["SRCWord3", "SRCWord9"]
+```
+
+### SRC words 6 to 9
+In a PEL, these SRC words are free format and can be filled in by the user as
+desired. On the BMC, the source of these words is the AdditionalData fields in
+the event log. The message registry provides a way for the log creator to
+specify which AdditionalData property field to get the data from, and also to
+define what the SRC word means for use by parsers. If not specified, these SRC
+words will be set to zero in the PEL.
+
+```
+"Words6to9":
+{
+ "6":
+ {
+ "description": "Failing unit number",
+ "AdditionalDataPropSource": "PS_NUM"
+ }
+}
+```
+
+### SRC Power Fault flag
+The SRC has a bit in it to indicate if the error is a power fault. This is an
+optional field in the message registry and defaults to false.
+
+```
+"PowerFault: false
+```
+
+### Documentation Fields
+The documentation fields are used by PEL parsers to display a human readable
+description of a PEL. They are also the source for the Redfish event log
+messages.
+
+#### Message
+This field is used by the BMC's PEL parser as the description of the error log.
+It will also be used in Redfish event logs. It supports argument substitution
+using the %1, %2, etc placeholders allowing any of the SRC user data words 6 -
+9 to be displayed as part of the message. If the placeholders are used, then
+the `MessageArgSources` property must be present to say which SRC words to use
+for each placeholder.
+
+```
+"Message": "Processor %1 had %2 errors"
+```
+
+#### MessageArgSources
+This optional field is required when the Message field contains the %X
+placeholder arguments. It is an array that says which SRC words to get the
+placeholders from. In the example below, SRC word 6 would be used for %1, and
+SRC word 7 for %2.
+
+```
+"MessageArgSources":
+[
+ "SRCWord6", "SRCWord7"
+]
+```
+
+#### Description
+A short description of the error. This is required by the Redfish schema to generate a Redfish message entry, but is not used in Redfish or PEL output.
+
+```
+"Description": "A power fault"
+```
+
+#### Notes
+This is an optional free format text field for keeping any notes for the
+registry entry, as comments are not allowed in JSON. It is an array of strings
+for easier readability of long fields.
+
+```
+"Notes": [
+ "This entry is for every type of power fault.",
+ "There is probably a hardware failure."
+]
+```
diff --git a/extensions/openpower-pels/registry/message_registry.json b/extensions/openpower-pels/registry/message_registry.json
new file mode 100644
index 0000000..12b977b
--- /dev/null
+++ b/extensions/openpower-pels/registry/message_registry.json
@@ -0,0 +1,202 @@
+{
+ "PELs":
+ [
+ {
+ "Name": "xyz.openbmc_project.Common.Error.Timeout",
+ "Subsystem": "bmc_firmware",
+
+ "SRC":
+ {
+ "ReasonCode": "0x1001",
+ "Words6To9":
+ {
+ "6":
+ {
+ "Description": "Timeout in ms",
+ "AdditionalDataPropSource": "TIMEOUT_IN_MSEC"
+ }
+ }
+ },
+
+ "Documentation":
+ {
+ "Description": "This is a generic timeout error",
+ "Message": "An operation timed out",
+ "Notes": [
+ "The journal should contain more information"
+ ]
+ }
+ },
+
+ {
+ "Name": "xyz.openbmc_project.Common.Error.InternalFailure",
+ "Subsystem": "bmc_firmware",
+
+ "SRC":
+ {
+ "ReasonCode": "0x1002",
+ "Words6To9":
+ {
+ }
+ },
+
+ "Documentation":
+ {
+ "Description": "BMC code had a generic internal failure",
+ "Message": "An application had an internal failure",
+ "Notes": [
+ "The journal should contain more information"
+ ]
+ }
+ },
+
+ {
+ "Name": "xyz.openbmc_project.Common.Error.InvalidArgument",
+ "Subsystem": "user_error",
+
+ "SRC":
+ {
+ "ReasonCode": "0x1003",
+ "Words6To9":
+ {
+ }
+ },
+
+ "Documentation":
+ {
+ "Description": "BMC code was given an invalid argument",
+ "Message": "Code was given an invalid argument",
+ "Notes": [
+ "The journal should contain more information"
+ ]
+ }
+ },
+ {
+ "Name": "xyz.openbmc_project.Common.Error.InsufficientPermission",
+ "Subsystem": "user_error",
+
+ "SRC":
+ {
+ "ReasonCode": "0x1004",
+ "Words6To9":
+ {
+ }
+ },
+
+ "Documentation":
+ {
+ "Description": "An operation failed due to insufficient permission",
+ "Message": "An operation failed due to unsufficient permission",
+ "Notes": [
+ "The journal should contain more information"
+ ]
+ }
+ },
+
+ {
+ "Name": "xyz.openbmc_project.Common.Error.NotAllowed",
+ "Subsystem": "user_error",
+
+ "SRC":
+ {
+ "ReasonCode": "0x1005",
+ "Words6To9":
+ {
+ }
+ },
+
+ "Documentation":
+ {
+ "Description": "An operation failed because it isn't allowed",
+ "Message": "An operation failed becuase it isn't allowed",
+ "Notes": [
+ "The journal should contain more information"
+ ]
+ }
+ },
+
+ {
+ "Name": "xyz.openbmc_project.Common.Error.NoCACertificate",
+ "Subsystem": "user_error",
+
+ "SRC":
+ {
+ "ReasonCode": "0x1006",
+ "Words6To9":
+ {
+ }
+ },
+
+ "Documentation":
+ {
+ "Description": "The server's CA certificate has not been provided",
+ "Message": "The server's CA certificate has not been provided"
+ }
+ },
+
+ {
+ "Name": "org.open_power.Logging.Error.SentBadPELToHost",
+ "Subsystem": "bmc_firmware",
+ "Severity": "non_error",
+
+ "SRC":
+ {
+ "ReasonCode": "0x2001",
+ "Words6To9":
+ {
+ "6":
+ {
+ "Description": "The bad PEL ID",
+ "AdditionalDataPropSource": "BAD_ID"
+ }
+ }
+ },
+
+ "Documentation":
+ {
+ "Description": "The BMC sent the host a malformed PEL",
+ "Message": "The BMC sent the host a malformed PEL",
+ "Notes": [
+ "The host firmware rejected that PEL."
+ ]
+ }
+ },
+
+ {
+ "Name": "org.open_power.Logging.Error.BadHostPEL",
+ "Subsystem": "platform_firmware",
+ "Severity": "unrecoverable",
+
+ "SRC":
+ {
+ "ReasonCode": "0x2002",
+ "Words6To9":
+ {
+ "6":
+ {
+ "Description": "The PLID of the invalid PEL",
+ "AdditionalDataPropSource": "PLID"
+ },
+ "7":
+ {
+ "Description": "The corresponding OpenBMC event log ID",
+ "AdditionalDataPropSource": "OBMC_LOG_ID"
+ },
+ "8":
+ {
+ "Description": "The size of the invalid PEL",
+ "AdditionalDataPropSource": "PEL_SIZE"
+ }
+ }
+ },
+
+ "Documentation":
+ {
+ "Description": "The host sent the BMC an invalid PEL",
+ "Message": "The host sent the BMC an invalid PEL",
+ "Notes": [
+ ]
+ }
+ }
+ ]
+}
diff --git a/extensions/openpower-pels/registry/run-ci.sh b/extensions/openpower-pels/registry/run-ci.sh
new file mode 100644
index 0000000..4868f9b
--- /dev/null
+++ b/extensions/openpower-pels/registry/run-ci.sh
@@ -0,0 +1,3 @@
+/usr/bin/env python extensions/openpower-pels/registry/tools/process_registry.py \
+-v -s extensions/openpower-pels/registry/schema/schema.json \
+-r extensions/openpower-pels/registry/message_registry.json
diff --git a/extensions/openpower-pels/registry/schema/registry_example.json b/extensions/openpower-pels/registry/schema/registry_example.json
new file mode 100644
index 0000000..3fdf664
--- /dev/null
+++ b/extensions/openpower-pels/registry/schema/registry_example.json
@@ -0,0 +1,39 @@
+{
+ "PELs":
+ [
+ {
+ "Name": "xyz.openbmc_project.Power.Fault",
+ "Subsystem": "power_supply",
+ "Severity": "unrecoverable",
+ "ActionFlags": ["service_action", "report"],
+
+ "SRC":
+ {
+ "ReasonCode": "0x2030",
+ "SymptomIDFields": ["SRCWord3", "SRCWord6"],
+ "Words6To9":
+ {
+ "6":
+ {
+ "Description": "Failing unit number",
+ "AdditionalDataPropSource": "PS_NUM"
+ }
+ }
+ },
+
+ "Documentation":
+ {
+ "Description": "A PGOOD Fault",
+ "Message": "PS %1 had a PGOOD Fault",
+ "MessageArgSources":
+ [
+ "SRCWord6"
+ ],
+ "Notes": [
+ "In the UserData section there is a JSON",
+ "dump that provides debug information."
+ ]
+ }
+ }
+ ]
+}
diff --git a/extensions/openpower-pels/registry/schema/schema.json b/extensions/openpower-pels/registry/schema/schema.json
new file mode 100644
index 0000000..f41d425
--- /dev/null
+++ b/extensions/openpower-pels/registry/schema/schema.json
@@ -0,0 +1,354 @@
+{
+ "title": "PEL message registry schema",
+ "$id": "http://github.com/openbmc/phosphor-logging/extensions/openpower-pels/registry/schema/schema.json",
+ "description": "This schema describes JSON used for creating PELs from OpenBMC event logs.",
+ "type": "object",
+
+ "properties":
+ {
+ "PELs":
+ {
+ "title": "This is an array of entries that specify PEL fields for event logs",
+ "$ref": "#/definitions/pels"
+ }
+ },
+
+ "additionalProperties": false,
+ "minItems": 1,
+ "uniqueItems": true,
+
+ "definitions":
+ {
+ "pels":
+ {
+ "description": "Each entry in this array is for converting an event log to a PEL",
+ "type": "array",
+ "items":
+ {
+ "description": "The schema for a single event log registry entry",
+ "type": "object",
+ "properties":
+ {
+ "Name": {"$ref": "#/definitions/errorName" },
+
+ "SRC": {"$ref": "#/definitions/src" },
+
+ "Subsystem": {"$ref": "#/definitions/subsystem" },
+
+ "Severity": {"$ref": "#/definitions/severity" },
+
+ "MfgSeverity": {"$ref": "#/definitions/mfgSeverity" },
+
+ "EventScope": {"$ref": "#/definitions/eventScope" },
+
+ "EventType": {"$ref": "#/definitions/eventType" },
+
+ "ActionFlags": {"$ref": "#/definitions/actionFlags" },
+
+ "MfgActionFlags": {"$ref": "#/definitions/mfgActionFlags" },
+
+ "Documentation": {"$ref": "#/definitions/documentation" },
+
+ "ComponentID": {"$ref": "#/definitions/componentID" }
+ },
+
+ "required": ["Name", "SRC", "Subsystem", "Documentation"],
+ "additionalProperties": false
+ }
+ },
+
+ "errorName":
+ {
+ "description": "The 'Message' property of an OpenBMC event log",
+ "type": "string"
+ },
+
+ "componentID":
+ {
+ "description": "The component ID of the PEL creator, in the form 0xYY00. For BD SRCs, this is optional and if not present the component ID will be taken from the upper byte of the reason code.",
+ "type": "string",
+ "pattern": "^0x[0-9a-fA-F]{2}00$"
+ },
+
+ "src":
+ {
+ "description": "Contains fields describing the primary SRC embedded in the PEL",
+ "type": "object",
+
+ "properties":
+ {
+ "Type": {"$ref": "#/definitions/srcType" },
+
+ "ReasonCode": {"$ref": "#/definitions/reasonCode" },
+
+ "SymptomIDFields": {"$ref": "#/definitions/symptomID" },
+
+ "Words6To9": {"$ref": "#/definitions/srcWords6To9" },
+
+ "PowerFault": {"$ref": "#/definitions/powerFault" }
+ },
+
+ "required": ["ReasonCode", "Words6To9"],
+ "additionalProperties": false
+ },
+
+ "documentation":
+ {
+ "description": "This contains event documentation that will be used by tools and parsers.",
+ "type": "object",
+
+ "properties":
+ {
+ "Message": {"$ref": "#/definitions/docMessage" },
+
+ "MessageArgSources": {"$ref": "#/definitions/docMessageArgSources" },
+
+ "Description": {"$ref": "#/definitions/docDescription" },
+
+ "Notes": {"$ref": "#/definitions/docNotes" }
+
+ },
+ "additionalProperties": false,
+ "required": ["Message", "Description"]
+ },
+
+ "srcType":
+ {
+ "description": "The first byte of the SRC ASCII string. Optional and defaults to BD. The '11' SRC is only to be used for events related to power.",
+ "type": "string",
+ "enum": ["BD", "11"]
+ },
+
+ "docNotes":
+ {
+ "description": "Any notes/comments about the error. An array of strings for manual line wrapping. Optional.",
+ "type": "array",
+ "items":
+ {
+ "description": "Notes",
+ "type": "string"
+ }
+ },
+
+ "reasonCode":
+ {
+ "description": "String representation of the 2 byte reason code, like 0xABCD. The reason code is the 2nd half of the 8 character SRC ASCII String field, such as B1FFABCD.",
+ "type": "string",
+ "pattern": "^0x[0-9a-fA-F]{4}$",
+
+ "examples": [
+ "0x3355"
+ ]
+ },
+
+ "subsystem":
+ {
+ "description": "PEL subsystem enumeration. See the PEL spec for more detailed definitions.",
+ "type": "string",
+ "enum": ["processor", "processor_fru", "processor_chip",
+ "processor_unit", "processor_bus",
+
+ "memory", "memory_ctlr", "memory_bus", "memory_dimm",
+ "memory_fru", "external_cache",
+
+ "io", "io_hub", "io_bridge", "io_bus", "io_processor",
+ "io_hub_other", "phb",
+
+ "io_adapter", "io_adapter_comm", "io_device",
+ "io_device_dasd", "io_external_general",
+ "io_external_workstation", "io_storage_mezz",
+
+ "cec_hardware", "cec_sp_a", "cec_sp_b",
+ "cec_node_controller", "cec_vpd",
+ "cec_i2c", "cec_chip_iface", "cec_clocks", "cec_op_panel",
+ "cec_tod", "cec_storage_device", "cec_sp_hyp_iface",
+ "cec_service_network", "cec_sp_hostboot_iface",
+
+ "power", "power_supply", "power_control_hw", "power_fans",
+ "power_sequencer",
+
+ "others", "other_hmc", "other_test_tool", "other_media",
+ "other_multiple_subsystems", "other_na", "other_info_src",
+
+ "surv_hyp_lost_sp", "surv_sp_lost_hyp", "surv_sp_lost_hmc",
+ "surv_hmc_lost_lpar", "surv_hmc_lost_bpa",
+ "surv_hmc_lost_hmc",
+
+ "platform_firmware", "bmc_firmware", "hyp_firmware",
+ "partition_firmware", "slic_firmware", "spcn_firmware",
+ "bulk_power_firmware_side_a", "hmc_code_firmware",
+ "bulk_power_firmware_side_b", "virtual_sp", "hostboot",
+ "occ",
+
+ "software", "os_software", "xpf_software", "app_software",
+
+ "ext_env", "input_power_source", "ambient_temp",
+ "user_error", "corrosion"]
+ },
+
+ "severity":
+ {
+ "description": "PEL severity enumeration. Optional. If not provided, will use the event log severity. See the PEL spec for more detailed definitions.",
+ "type": "string",
+
+ "enum": ["non_error",
+
+ "recovered",
+
+ "predictive", "predictive_degraded_perf",
+ "predictive_reboot", "predictive_reboot_degraded",
+ "predictive_redundancy_loss",
+
+ "unrecoverable", "unrecoverable_degraded_perf",
+ "unrecoverable_redundancy_loss",
+ "unrecoverable_redundancy_loss_perf",
+ "unrecoverable_loss_of_function",
+
+ "critical", "critical_system_term",
+ "critical_imminent_failure",
+ "critical_partition_term",
+ "critical_partition_imminent_failure",
+
+ "diagnostic_error", "diagnostic_error_incorrect_results",
+
+ "symptom_recovered", "symptom_predictive",
+ "symptom_unrecoverable", "symptom_critical",
+ "symptom_diag_err"]
+ },
+
+ "mfgSeverity":
+ {
+ "description": "The PEL severity to use in manufacturing reporting mode",
+ "$ref": "#/definitions/severity"
+ },
+
+ "eventScope":
+ {
+ "description": "The event scope PEL field. Optional and defaults to entire_platform",
+ "type": "string",
+ "enum": ["entire_platform", "single_partition", "multiple_partitions",
+ "possibly_multiple_platforms"]
+ },
+
+ "eventType":
+ {
+ "description": "The event type PEL field. Optional and defaults to na",
+ "type": "string",
+ "enum": ["na", "misc_information_only", "tracing_event",
+ "dump_notification"]
+ },
+
+ "powerFault":
+ {
+ "description": "The Power Fault SRC field (bit 6 in byte 1 of header). Optional and defaults to false",
+ "type": "boolean"
+ },
+
+ "actionFlags":
+ {
+ "description": "The action flags Private Header PEL field",
+ "type": "array",
+ "items":
+ {
+ "description": "List of action flags",
+ "type": "string",
+ "enum": ["service_action", "hidden", "report", "dont_report",
+ "call_home", "isolation_incomplete", "termination"]
+ }
+ },
+
+ "mfgActionFlags":
+ {
+ "description": "The PEL action flags to use in manufacturing reporting mode",
+ "$ref": "#/definitions/actionFlags"
+ },
+
+ "docDescription":
+ {
+ "description": "This is a higher level description of the error. It is required by the Redfish schema to generate a Redfish message entry, but is not used in Redfish or PEL output.",
+ "type": "string"
+ },
+
+ "docMessage":
+ {
+ "description": "The error message. This will show up in parsed PELs, and in the Redfish event logs. It can contain placeholders for numeric values using %1, %2, etc, that come from the SRC words 6-9 as defined by the MessageArgSources property.",
+ "type": "string",
+ "examples": [
+ {"Message": "The code update from level %1 to %2 failed" }
+ ]
+ },
+
+ "docMessageArgSources":
+ {
+ "description": "The SRC word 6-9 to use as the source of the numeric arguments that will be substituted into any placeholder in the Message field. Only required if there are arguments to substitute.",
+ "type": "array",
+ "items":
+ {
+ "type": "string",
+ "enum": ["SRCWord6", "SRCWord7", "SRCWord8", "SRCWord9"]
+ },
+ "additionalItems": false
+ },
+
+ "symptomID":
+ {
+ "description": "Defines a custom Symptom ID, to be appended to the ASCII string word and separated by underscores. The maximum size of the Symptom ID field is 80 characters. The default is ASCIISTRING_SRCWord3 (e.g. B1103500_12345678).",
+ "type": "array",
+ "items":
+ {
+ "type": "string",
+ "enum": ["SRCWord3", "SRCWord4", "SRCWord5", "SRCWord6",
+ "SRCWord7", "SRCWord8", "SRCWord9"]
+ },
+ "minItems": 1,
+ "maxItems": 8,
+ "uniqueItems": true,
+
+ "examples": [
+ ["SRCWord3", "SRCWord6"]
+ ]
+ },
+
+ "srcWords6To9":
+ {
+ "description": "This details what the user defined SRC hex words (6-9) mean, and which AdditionalData properties to get them from. These will be shown in the PEL parser output. Must be present, but can be empty.",
+ "type": "object",
+ "patternProperties":
+ {
+ "^[6-9]$":
+ {
+ "type": "object",
+ "properties":
+ {
+ "Description":
+ {
+ "description": "What the value in the field represents.",
+ "type": "string"
+ },
+ "AdditionalDataPropSource":
+ {
+ "description": "Which AdditionalData property key to get the data from.",
+ "type": "string"
+ }
+ },
+
+ "additionalProperties": false
+ },
+
+ "examples":
+ {
+ "SRCWords6To9":
+ {
+ "6":
+ {
+ "Description": "Failing PSU number",
+ "AdditionalDataPropSource": "PSU_NUM"
+ }
+ }
+ }
+ },
+ "additionalProperties": false
+ }
+
+ }
+}
diff --git a/extensions/openpower-pels/registry/tools/process_registry.py b/extensions/openpower-pels/registry/tools/process_registry.py
new file mode 100755
index 0000000..09ad3e0
--- /dev/null
+++ b/extensions/openpower-pels/registry/tools/process_registry.py
@@ -0,0 +1,167 @@
+#!/usr/bin/env python
+
+import argparse
+import json
+import sys
+
+r"""
+Validates the PEL message registry JSON, which includes checking it against
+a JSON schema using the jsonschema module as well as doing some extra checks
+that can't be encoded in the schema.
+"""
+
+
+def check_duplicate_names(registry_json):
+ r"""
+ Check that there aren't any message registry entries with the same
+ 'Name' field. There may be a use case for this in the future, but there
+ isn't right now.
+
+ registry_json: The message registry JSON
+ """
+
+ names = {}
+ for entry in registry_json['PELs']:
+ if entry['Name'] in names.keys():
+ sys.exit("Found multiple uses of error {}".format(entry['Name']))
+ else:
+ names[entry['Name']] = {}
+
+
+def check_duplicate_reason_codes(registry_json):
+ r"""
+ Check that there aren't any message registry entries with the same
+ 'ReasonCode' field.
+
+ registry_json: The message registry JSON
+ """
+
+ reasonCodes = {}
+ for entry in registry_json['PELs']:
+ if entry['SRC']['ReasonCode'] in reasonCodes.keys():
+ sys.exit("Found duplicate SRC reason code {}".format(
+ entry['SRC']['ReasonCode']))
+ else:
+ reasonCodes[entry['SRC']['ReasonCode']] = {}
+
+
+def check_component_id(registry_json):
+ r"""
+ Check that the upper byte of the ComponentID field matches the upper byte
+ of the ReasonCode field, but not on "11" type SRCs where they aren't
+ supposed to match.
+
+ registry_json: The message registry JSON
+ """
+
+ for entry in registry_json['PELs']:
+
+ # Don't check on "11" SRCs as those reason codes aren't supposed to
+ # match the component ID.
+ if entry.get('Type', '') == "11":
+ continue
+
+ if 'ComponentID' in entry:
+ id = int(entry['ComponentID'], 16)
+ reason_code = int(entry['SRC']['ReasonCode'], 16)
+
+ if (id & 0xFF00) != (reason_code & 0xFF00):
+ sys.exit("Found mismatching component ID {} vs reason "
+ "code {} for error {}".format(
+ entry['ComponentID'],
+ entry['SRC']['ReasonCode'],
+ entry['Name']))
+
+
+def check_message_args(registry_json):
+ r"""
+ Check that if the Message field uses the '%' style placeholders that there
+ are that many entries in the MessageArgSources field. Also checks that
+ the MessageArgSources field is present but only if there are placeholders.
+
+ registry_json: The message registry JSON
+ """
+
+ for entry in registry_json['PELs']:
+ num_placeholders = entry['Documentation']['Message'].count('%')
+ if num_placeholders == 0:
+ continue
+
+ if 'MessageArgSources' not in entry['Documentation']:
+ sys.exit("Missing MessageArgSources property for error {}".
+ format(entry['Name']))
+
+ if num_placeholders != \
+ len(entry['Documentation']['MessageArgSources']):
+ sys.exit("Different number of placeholders found in "
+ "Message vs MessageArgSources for error {}".
+ format(entry['Name']))
+
+
+def validate_schema(registry, schema):
+ r"""
+ Validates the passed in JSON against the passed in schema JSON
+
+ registry: Path of the file containing the registry JSON
+ schema: Path of the file containing the schema JSON
+ Use None to skip the pure schema validation
+ """
+
+ with open(registry) as registry_handle:
+ registry_json = json.load(registry_handle)
+
+ if schema:
+
+ import jsonschema
+
+ with open(schema) as schema_handle:
+ schema_json = json.load(schema_handle)
+
+ try:
+ jsonschema.validate(registry_json, schema_json)
+ except jsonschema.ValidationError as e:
+ print(e)
+ sys.exit("Schema validation failed")
+
+ check_duplicate_names(registry_json)
+
+ check_duplicate_reason_codes(registry_json)
+
+ check_component_id(registry_json)
+
+ check_message_args(registry_json)
+
+
+if __name__ == '__main__':
+
+ parser = argparse.ArgumentParser(
+ description='PEL message registry processor')
+
+ parser.add_argument('-v', '--validate', action='store_true',
+ dest='validate',
+ help='Validate the JSON using the schema')
+
+ parser.add_argument('-s', '--schema-file', dest='schema_file',
+ help='The message registry JSON schema file')
+
+ parser.add_argument('-r', '--registry-file', dest='registry_file',
+ help='The message registry JSON file')
+ parser.add_argument('-k', '--skip-schema-validation', action='store_true',
+ dest='skip_schema',
+ help='Skip running schema validation. '
+ 'Only do the extra checks.')
+
+ args = parser.parse_args()
+
+ if args.validate:
+ if not args.schema_file:
+ sys.exit("Schema file required")
+
+ if not args.registry_file:
+ sys.exit("Registry file required")
+
+ schema = args.schema_file
+ if args.skip_schema:
+ schema = None
+
+ validate_schema(args.registry_file, schema)
diff --git a/extensions/openpower-pels/repository.cpp b/extensions/openpower-pels/repository.cpp
new file mode 100644
index 0000000..3bf3cb0
--- /dev/null
+++ b/extensions/openpower-pels/repository.cpp
@@ -0,0 +1,382 @@
+/**
+ * Copyright © 2019 IBM Corporation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#include "repository.hpp"
+
+#include <fstream>
+#include <phosphor-logging/log.hpp>
+#include <xyz/openbmc_project/Common/File/error.hpp>
+
+namespace openpower
+{
+namespace pels
+{
+
+namespace fs = std::filesystem;
+using namespace phosphor::logging;
+namespace file_error = sdbusplus::xyz::openbmc_project::Common::File::Error;
+
+Repository::Repository(const std::filesystem::path& basePath) :
+ _logPath(basePath / "logs")
+{
+ if (!fs::exists(_logPath))
+ {
+ fs::create_directories(_logPath);
+ }
+
+ restore();
+}
+
+void Repository::restore()
+{
+ for (auto& dirEntry : fs::directory_iterator(_logPath))
+ {
+ try
+ {
+ if (!fs::is_regular_file(dirEntry.path()))
+ {
+ continue;
+ }
+
+ std::ifstream file{dirEntry.path()};
+ std::vector<uint8_t> data{std::istreambuf_iterator<char>(file),
+ std::istreambuf_iterator<char>()};
+ file.close();
+
+ PEL pel{data};
+ if (pel.valid())
+ {
+ // If the host hasn't acked it, reset the host state so
+ // it will get sent up again.
+ if (pel.hostTransmissionState() == TransmissionState::sent)
+ {
+ pel.setHostTransmissionState(TransmissionState::newPEL);
+ try
+ {
+ write(pel, dirEntry.path());
+ }
+ catch (std::exception& e)
+ {
+ log<level::ERR>(
+ "Failed to save PEL after updating host state",
+ entry("PELID=0x%X", pel.id()));
+ }
+ }
+
+ PELAttributes attributes{
+ dirEntry.path(), pel.userHeader().actionFlags(),
+ pel.hostTransmissionState(), pel.hmcTransmissionState()};
+
+ using pelID = LogID::Pel;
+ using obmcID = LogID::Obmc;
+ _pelAttributes.emplace(
+ LogID(pelID(pel.id()), obmcID(pel.obmcLogID())),
+ attributes);
+ }
+ else
+ {
+ log<level::ERR>(
+ "Found invalid PEL file while restoring. Removing.",
+ entry("FILENAME=%s", dirEntry.path().c_str()));
+ fs::remove(dirEntry.path());
+ }
+ }
+ catch (std::exception& e)
+ {
+ log<level::ERR>("Hit exception while restoring PEL File",
+ entry("FILENAME=%s", dirEntry.path().c_str()),
+ entry("ERROR=%s", e.what()));
+ }
+ }
+}
+
+std::string Repository::getPELFilename(uint32_t pelID, const BCDTime& time)
+{
+ char name[50];
+ sprintf(name, "%.2X%.2X%.2X%.2X%.2X%.2X%.2X%.2X_%.8X", time.yearMSB,
+ time.yearLSB, time.month, time.day, time.hour, time.minutes,
+ time.seconds, time.hundredths, pelID);
+ return std::string{name};
+}
+
+void Repository::add(std::unique_ptr<PEL>& pel)
+{
+ pel->setHostTransmissionState(TransmissionState::newPEL);
+ pel->setHMCTransmissionState(TransmissionState::newPEL);
+
+ auto path = _logPath / getPELFilename(pel->id(), pel->commitTime());
+
+ write(*(pel.get()), path);
+
+ PELAttributes attributes{path, pel->userHeader().actionFlags(),
+ pel->hostTransmissionState(),
+ pel->hmcTransmissionState()};
+
+ using pelID = LogID::Pel;
+ using obmcID = LogID::Obmc;
+ _pelAttributes.emplace(LogID(pelID(pel->id()), obmcID(pel->obmcLogID())),
+ attributes);
+
+ processAddCallbacks(*pel);
+}
+
+void Repository::write(const PEL& pel, const fs::path& path)
+{
+ std::ofstream file{path, std::ios::binary};
+
+ if (!file.good())
+ {
+ // If this fails, the filesystem is probably full so it isn't like
+ // we could successfully create yet another error log here.
+ auto e = errno;
+ fs::remove(path);
+ log<level::ERR>("Unable to open PEL file for writing",
+ entry("ERRNO=%d", e), entry("PATH=%s", path.c_str()));
+ throw file_error::Open();
+ }
+
+ auto data = pel.data();
+ file.write(reinterpret_cast<const char*>(data.data()), data.size());
+
+ if (file.fail())
+ {
+ // Same note as above about not being able to create an error log
+ // for this case even if we wanted.
+ auto e = errno;
+ file.close();
+ fs::remove(path);
+ log<level::ERR>("Unable to write PEL file", entry("ERRNO=%d", e),
+ entry("PATH=%s", path.c_str()));
+ throw file_error::Write();
+ }
+}
+
+void Repository::remove(const LogID& id)
+{
+ auto pel = findPEL(id);
+ if (pel != _pelAttributes.end())
+ {
+ fs::remove(pel->second.path);
+ _pelAttributes.erase(pel);
+
+ processDeleteCallbacks(id.pelID.id);
+ }
+}
+
+std::optional<std::vector<uint8_t>> Repository::getPELData(const LogID& id)
+{
+ auto pel = findPEL(id);
+ if (pel != _pelAttributes.end())
+ {
+ std::ifstream file{pel->second.path.c_str()};
+ if (!file.good())
+ {
+ auto e = errno;
+ log<level::ERR>("Unable to open PEL file", entry("ERRNO=%d", e),
+ entry("PATH=%s", pel->second.path.c_str()));
+ throw file_error::Open();
+ }
+
+ std::vector<uint8_t> data{std::istreambuf_iterator<char>(file),
+ std::istreambuf_iterator<char>()};
+ return data;
+ }
+
+ return std::nullopt;
+}
+
+std::optional<sdbusplus::message::unix_fd> Repository::getPELFD(const LogID& id)
+{
+ auto pel = findPEL(id);
+ if (pel != _pelAttributes.end())
+ {
+ FILE* fp = fopen(pel->second.path.c_str(), "rb");
+
+ if (fp == nullptr)
+ {
+ auto e = errno;
+ log<level::ERR>("Unable to open PEL File", entry("ERRNO=%d", e),
+ entry("PATH=%s", pel->second.path.c_str()));
+ throw file_error::Open();
+ }
+
+ // Must leave the file open here. It will be closed by sdbusplus
+ // when it sends it back over D-Bus.
+
+ return fileno(fp);
+ }
+ return std::nullopt;
+}
+
+void Repository::for_each(ForEachFunc func) const
+{
+ for (const auto& [id, attributes] : _pelAttributes)
+ {
+ std::ifstream file{attributes.path};
+
+ if (!file.good())
+ {
+ auto e = errno;
+ log<level::ERR>("Repository::for_each: Unable to open PEL file",
+ entry("ERRNO=%d", e),
+ entry("PATH=%s", attributes.path.c_str()));
+ continue;
+ }
+
+ std::vector<uint8_t> data{std::istreambuf_iterator<char>(file),
+ std::istreambuf_iterator<char>()};
+ file.close();
+
+ PEL pel{data};
+
+ try
+ {
+ if (func(pel))
+ {
+ break;
+ }
+ }
+ catch (std::exception& e)
+ {
+ log<level::ERR>("Repository::for_each function exception",
+ entry("ERROR=%s", e.what()));
+ }
+ }
+}
+
+void Repository::processAddCallbacks(const PEL& pel) const
+{
+ for (auto& [name, func] : _addSubscriptions)
+ {
+ try
+ {
+ func(pel);
+ }
+ catch (std::exception& e)
+ {
+ log<level::ERR>("PEL Repository add callback exception",
+ entry("NAME=%s", name.c_str()),
+ entry("ERROR=%s", e.what()));
+ }
+ }
+}
+
+void Repository::processDeleteCallbacks(uint32_t id) const
+{
+ for (auto& [name, func] : _deleteSubscriptions)
+ {
+ try
+ {
+ func(id);
+ }
+ catch (std::exception& e)
+ {
+ log<level::ERR>("PEL Repository delete callback exception",
+ entry("NAME=%s", name.c_str()),
+ entry("ERROR=%s", e.what()));
+ }
+ }
+}
+
+std::optional<std::reference_wrapper<const Repository::PELAttributes>>
+ Repository::getPELAttributes(const LogID& id) const
+{
+ auto pel = findPEL(id);
+ if (pel != _pelAttributes.end())
+ {
+ return pel->second;
+ }
+
+ return std::nullopt;
+}
+
+void Repository::setPELHostTransState(uint32_t pelID, TransmissionState state)
+{
+ LogID id{LogID::Pel{pelID}};
+ auto attr = std::find_if(_pelAttributes.begin(), _pelAttributes.end(),
+ [&id](const auto& a) { return a.first == id; });
+
+ if ((attr != _pelAttributes.end()) && (attr->second.hostState != state))
+ {
+ PELUpdateFunc func = [state](PEL& pel) {
+ pel.setHostTransmissionState(state);
+ };
+
+ try
+ {
+ updatePEL(attr->second.path, func);
+
+ attr->second.hostState = state;
+ }
+ catch (std::exception& e)
+ {
+ log<level::ERR>("Unable to update PEL host transmission state",
+ entry("PATH=%s", attr->second.path.c_str()),
+ entry("ERROR=%s", e.what()));
+ }
+ }
+}
+
+void Repository::setPELHMCTransState(uint32_t pelID, TransmissionState state)
+{
+ LogID id{LogID::Pel{pelID}};
+ auto attr = std::find_if(_pelAttributes.begin(), _pelAttributes.end(),
+ [&id](const auto& a) { return a.first == id; });
+
+ if ((attr != _pelAttributes.end()) && (attr->second.hmcState != state))
+ {
+ PELUpdateFunc func = [state](PEL& pel) {
+ pel.setHMCTransmissionState(state);
+ };
+
+ try
+ {
+ updatePEL(attr->second.path, func);
+
+ attr->second.hmcState = state;
+ }
+ catch (std::exception& e)
+ {
+ log<level::ERR>("Unable to update PEL HMC transmission state",
+ entry("PATH=%s", attr->second.path.c_str()),
+ entry("ERROR=%s", e.what()));
+ }
+ }
+}
+
+void Repository::updatePEL(const fs::path& path, PELUpdateFunc updateFunc)
+{
+ std::ifstream file{path};
+ std::vector<uint8_t> data{std::istreambuf_iterator<char>(file),
+ std::istreambuf_iterator<char>()};
+ file.close();
+
+ PEL pel{data};
+
+ if (pel.valid())
+ {
+ updateFunc(pel);
+
+ write(pel, path);
+ }
+ else
+ {
+ throw std::runtime_error(
+ "Unable to read a valid PEL when trying to update it");
+ }
+}
+
+} // namespace pels
+} // namespace openpower
diff --git a/extensions/openpower-pels/repository.hpp b/extensions/openpower-pels/repository.hpp
new file mode 100644
index 0000000..e55808f
--- /dev/null
+++ b/extensions/openpower-pels/repository.hpp
@@ -0,0 +1,376 @@
+#pragma once
+#include "bcd_time.hpp"
+#include "pel.hpp"
+
+#include <algorithm>
+#include <bitset>
+#include <filesystem>
+#include <map>
+
+namespace openpower
+{
+namespace pels
+{
+
+/**
+ * @class Repository
+ *
+ * The class handles saving and retrieving PELs on the BMC.
+ */
+class Repository
+{
+ public:
+ /**
+ * @brief Structure of commonly used PEL attributes.
+ */
+ struct PELAttributes
+ {
+ std::filesystem::path path;
+ std::bitset<16> actionFlags;
+ TransmissionState hostState;
+ TransmissionState hmcState;
+
+ PELAttributes() = delete;
+
+ PELAttributes(const std::filesystem::path& p, uint16_t flags,
+ TransmissionState hostState, TransmissionState hmcState) :
+ path(p),
+ actionFlags(flags), hostState(hostState), hmcState(hmcState)
+ {
+ }
+ };
+
+ /**
+ * @brief A structure that holds both the PEL and corresponding
+ * OpenBMC IDs.
+ * Used for correlating the IDs with their data files for quick
+ * lookup. To find a PEL based on just one of the IDs, just use
+ * the constructor that takes that ID.
+ */
+ struct LogID
+ {
+ struct Pel
+ {
+ uint32_t id;
+ explicit Pel(uint32_t i) : id(i)
+ {
+ }
+ };
+ struct Obmc
+ {
+ uint32_t id;
+ explicit Obmc(uint32_t i) : id(i)
+ {
+ }
+ };
+
+ Pel pelID;
+
+ Obmc obmcID;
+
+ LogID(Pel pel, Obmc obmc) : pelID(pel), obmcID(obmc)
+ {
+ }
+
+ explicit LogID(Pel id) : pelID(id), obmcID(0)
+ {
+ }
+
+ explicit LogID(Obmc id) : pelID(0), obmcID(id)
+ {
+ }
+
+ LogID() = delete;
+
+ /**
+ * @brief A == operator that will match on either ID
+ * being equal if the other is zero, so that
+ * one can look up a PEL with just one of the IDs.
+ */
+ bool operator==(const LogID& id) const
+ {
+ if (id.pelID.id != 0)
+ {
+ return id.pelID.id == pelID.id;
+ }
+ if (id.obmcID.id != 0)
+ {
+ return id.obmcID.id == obmcID.id;
+ }
+ return false;
+ }
+
+ bool operator<(const LogID& id) const
+ {
+ return pelID.id < id.pelID.id;
+ }
+ };
+
+ Repository() = delete;
+ ~Repository() = default;
+ Repository(const Repository&) = default;
+ Repository& operator=(const Repository&) = default;
+ Repository(Repository&&) = default;
+ Repository& operator=(Repository&&) = default;
+
+ /**
+ * @brief Constructor
+ *
+ * @param[in] basePath - the base filesystem path for the repository
+ */
+ Repository(const std::filesystem::path& basePath);
+
+ /**
+ * @brief Adds a PEL to the repository
+ *
+ * Throws File.Error.Open or File.Error.Write exceptions on failure
+ *
+ * @param[in] pel - the PEL to add
+ */
+ void add(std::unique_ptr<PEL>& pel);
+
+ /**
+ * @brief Removes a PEL from the repository
+ *
+ * @param[in] id - the ID (either the pel ID, OBMC ID, or both) to remove
+ */
+ void remove(const LogID& id);
+
+ /**
+ * @brief Generates the filename to use for the PEL ID and BCDTime.
+ *
+ * @param[in] pelID - the PEL ID
+ * @param[in] time - the BCD time
+ *
+ * @return string - A filename string of <BCD_time>_<pelID>
+ */
+ static std::string getPELFilename(uint32_t pelID, const BCDTime& time);
+
+ /**
+ * @brief Returns true if the PEL with the specified ID is in the repo.
+ *
+ * @param[in] id - the ID (either the pel ID, OBMC ID, or both)
+ * @return bool - true if that PEL is present
+ */
+ inline bool hasPEL(const LogID& id)
+ {
+ return findPEL(id) != _pelAttributes.end();
+ }
+
+ /**
+ * @brief Returns the PEL data based on its ID.
+ *
+ * If the data can't be found for that ID, then the optional object
+ * will be empty.
+ *
+ * @param[in] id - the LogID to get the PEL for, which can be either a
+ * PEL ID or OpenBMC log ID.
+ * @return std::optional<std::vector<uint8_t>> - the PEL data
+ */
+ std::optional<std::vector<uint8_t>> getPELData(const LogID& id);
+
+ /**
+ * @brief Get a file descriptor to the PEL data
+ *
+ * @param[in] id - The ID to get the FD for
+ *
+ * @return std::optional<sdbusplus::message::unix_fd> -
+ * The FD, or an empty optional object.
+ */
+ std::optional<sdbusplus::message::unix_fd> getPELFD(const LogID& id);
+
+ using ForEachFunc = std::function<bool(const PEL&)>;
+
+ /**
+ * @brief Run a user defined function on every PEL in the repository.
+ *
+ * ForEachFunc takes a const PEL reference, and should return
+ * true to stop iterating and return out of for_each.
+ *
+ * For example, to save up to 100 IDs in the repo into a vector:
+ *
+ * std::vector<uint32_t> ids;
+ * ForEachFunc f = [&ids](const PEL& pel) {
+ * ids.push_back(pel.id());
+ * return ids.size() == 100 ? true : false;
+ * };
+ *
+ * @param[in] func - The function to run.
+ */
+ void for_each(ForEachFunc func) const;
+
+ using AddCallback = std::function<void(const PEL&)>;
+
+ /**
+ * @brief Subscribe to PELs being added to the repository.
+ *
+ * Every time a PEL is added to the repository, the provided
+ * function will be called with the new PEL as the argument.
+ *
+ * The function must be of type void(const PEL&).
+ *
+ * @param[in] name - The subscription name
+ * @param[in] func - The callback function
+ */
+ void subscribeToAdds(const std::string& name, AddCallback func)
+ {
+ if (_addSubscriptions.find(name) == _addSubscriptions.end())
+ {
+ _addSubscriptions.emplace(name, func);
+ }
+ }
+
+ /**
+ * @brief Unsubscribe from new PELs.
+ *
+ * @param[in] name - The subscription name
+ */
+ void unsubscribeFromAdds(const std::string& name)
+ {
+ _addSubscriptions.erase(name);
+ }
+
+ using DeleteCallback = std::function<void(uint32_t)>;
+
+ /**
+ * @brief Subscribe to PELs being deleted from the repository.
+ *
+ * Every time a PEL is deleted from the repository, the provided
+ * function will be called with the PEL ID as the argument.
+ *
+ * The function must be of type void(const uint32_t).
+ *
+ * @param[in] name - The subscription name
+ * @param[in] func - The callback function
+ */
+ void subscribeToDeletes(const std::string& name, DeleteCallback func)
+ {
+ if (_deleteSubscriptions.find(name) == _deleteSubscriptions.end())
+ {
+ _deleteSubscriptions.emplace(name, func);
+ }
+ }
+
+ /**
+ * @brief Unsubscribe from deleted PELs.
+ *
+ * @param[in] name - The subscription name
+ */
+ void unsubscribeFromDeletes(const std::string& name)
+ {
+ _deleteSubscriptions.erase(name);
+ }
+
+ /**
+ * @brief Get the PEL attributes for a PEL
+ *
+ * @param[in] id - The ID to find the attributes for
+ *
+ * @return The attributes or an empty optional if not found
+ */
+ std::optional<std::reference_wrapper<const PELAttributes>>
+ getPELAttributes(const LogID& id) const;
+
+ /**
+ * @brief Sets the host transmission state on a PEL file
+ *
+ * Writes the host transmission state field in the User Header
+ * section in the PEL data specified by the ID.
+ *
+ * @param[in] pelID - The PEL ID
+ * @param[in] state - The state to write
+ */
+ void setPELHostTransState(uint32_t pelID, TransmissionState state);
+
+ /**
+ * @brief Sets the HMC transmission state on a PEL file
+ *
+ * Writes the HMC transmission state field in the User Header
+ * section in the PEL data specified by the ID.
+ *
+ * @param[in] pelID - The PEL ID
+ * @param[in] state - The state to write
+ */
+ void setPELHMCTransState(uint32_t pelID, TransmissionState state);
+
+ private:
+ using PELUpdateFunc = std::function<void(PEL&)>;
+
+ /**
+ * @brief Lets a function modify a PEL and saves the results
+ *
+ * Runs updateFunc (a void(PEL&) function) on the PEL data
+ * on the file specified, and writes the results back to the file.
+ *
+ * @param[in] path - The file path to use
+ * @param[in] updateFunc - The function to run to update the PEL.
+ */
+ void updatePEL(const std::filesystem::path& path, PELUpdateFunc updateFunc);
+
+ /**
+ * @brief Finds an entry in the _pelAttributes map.
+ *
+ * @param[in] id - the ID (either the pel ID, OBMC ID, or both)
+ *
+ * @return an iterator to the entry
+ */
+ std::map<LogID, PELAttributes>::const_iterator
+ findPEL(const LogID& id) const
+ {
+ return std::find_if(_pelAttributes.begin(), _pelAttributes.end(),
+ [&id](const auto& a) { return a.first == id; });
+ }
+
+ /**
+ * @brief Call any subscribed functions for new PELs
+ *
+ * @param[in] pel - The new PEL
+ */
+ void processAddCallbacks(const PEL& pel) const;
+
+ /**
+ * @brief Call any subscribed functions for deleted PELs
+ *
+ * @param[in] id - The ID of the deleted PEL
+ */
+ void processDeleteCallbacks(uint32_t id) const;
+
+ /**
+ * @brief Restores the _pelAttributes map on startup based on the existing
+ * PEL data files.
+ */
+ void restore();
+
+ /**
+ * @brief Stores a PEL object in the filesystem.
+ *
+ * @param[in] pel - The PEL to write
+ * @param[in] path - The file to write to
+ *
+ * Throws exceptions on failures.
+ */
+ void write(const PEL& pel, const std::filesystem::path& path);
+
+ /**
+ * @brief The filesystem path to the PEL logs.
+ */
+ const std::filesystem::path _logPath;
+
+ /**
+ * @brief A map of the PEL/OBMC IDs to PEL attributes.
+ */
+ std::map<LogID, PELAttributes> _pelAttributes;
+
+ /**
+ * @brief Subcriptions for new PELs.
+ */
+ std::map<std::string, AddCallback> _addSubscriptions;
+
+ /**
+ * @brief Subscriptions for deleted PELs.
+ */
+ std::map<std::string, DeleteCallback> _deleteSubscriptions;
+};
+
+} // namespace pels
+} // namespace openpower
diff --git a/extensions/openpower-pels/section.hpp b/extensions/openpower-pels/section.hpp
new file mode 100644
index 0000000..6353e2d
--- /dev/null
+++ b/extensions/openpower-pels/section.hpp
@@ -0,0 +1,92 @@
+#pragma once
+
+#include "section_header.hpp"
+
+#include <optional>
+
+namespace openpower
+{
+namespace pels
+{
+/**
+ * @class Section
+ *
+ * The base class for a PEL section. It contains the SectionHeader
+ * as all sections start with it.
+ *
+ */
+class Section
+{
+ public:
+ Section() = default;
+ virtual ~Section() = default;
+ Section(const Section&) = default;
+ Section& operator=(const Section&) = default;
+ Section(Section&&) = default;
+ Section& operator=(Section&&) = default;
+
+ /**
+ * @brief Returns a reference to the SectionHeader
+ */
+ const SectionHeader& header() const
+ {
+ return _header;
+ }
+
+ /**
+ * @brief Says if the section is valid.
+ */
+ bool valid() const
+ {
+ return _valid;
+ }
+
+ /**
+ * @brief Flatten the section into the stream
+ *
+ * @param[in] stream - The stream to write to
+ */
+ virtual void flatten(Stream& stream) const = 0;
+
+ /**
+ * @brief Get section in JSON. Derived classes to override when required to.
+ * @return std::optional<std::string> - If a section comes with a JSON
+ * repressentation, this would return the string for it.
+ */
+ virtual std::optional<std::string> getJSON() const
+ {
+ return std::nullopt;
+ }
+
+ protected:
+ /**
+ * @brief Returns the flattened size of the section header
+ */
+ static constexpr size_t flattenedSize()
+ {
+ return SectionHeader::flattenedSize();
+ }
+
+ /**
+ * @brief Used to validate the section.
+ *
+ * Implemented by derived classes.
+ */
+ virtual void validate() = 0;
+
+ /**
+ * @brief The section header structure.
+ *
+ * Filled in by derived classes.
+ */
+ SectionHeader _header;
+
+ /**
+ * @brief The section valid flag.
+ *
+ * This is determined by the derived class.
+ */
+ bool _valid = false;
+};
+} // namespace pels
+} // namespace openpower
diff --git a/extensions/openpower-pels/section_factory.cpp b/extensions/openpower-pels/section_factory.cpp
new file mode 100644
index 0000000..43a7d70
--- /dev/null
+++ b/extensions/openpower-pels/section_factory.cpp
@@ -0,0 +1,80 @@
+/**
+ * Copyright © 2019 IBM Corporation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#include "section_factory.hpp"
+
+#include "extended_user_header.hpp"
+#include "failing_mtms.hpp"
+#include "generic.hpp"
+#include "pel_types.hpp"
+#include "private_header.hpp"
+#include "src.hpp"
+#include "user_data.hpp"
+#include "user_header.hpp"
+
+namespace openpower
+{
+namespace pels
+{
+namespace section_factory
+{
+std::unique_ptr<Section> create(Stream& pelData)
+{
+ std::unique_ptr<Section> section;
+
+ // Peek the section ID to create the appriopriate object.
+ // If not enough data remains to do so, an invalid
+ // Generic object will be created in the default case.
+ uint16_t sectionID = 0;
+
+ if (pelData.remaining() >= 2)
+ {
+ pelData >> sectionID;
+ pelData.offset(pelData.offset() - 2);
+ }
+
+ switch (sectionID)
+ {
+ case static_cast<uint16_t>(SectionID::privateHeader):
+ section = std::make_unique<PrivateHeader>(pelData);
+ break;
+ case static_cast<uint16_t>(SectionID::userData):
+ section = std::make_unique<UserData>(pelData);
+ break;
+ case static_cast<uint16_t>(SectionID::userHeader):
+ section = std::make_unique<UserHeader>(pelData);
+ break;
+ case static_cast<uint16_t>(SectionID::failingMTMS):
+ section = std::make_unique<FailingMTMS>(pelData);
+ break;
+ case static_cast<uint16_t>(SectionID::primarySRC):
+ case static_cast<uint16_t>(SectionID::secondarySRC):
+ section = std::make_unique<SRC>(pelData);
+ break;
+ case static_cast<uint16_t>(SectionID::extendedUserHeader):
+ section = std::make_unique<ExtendedUserHeader>(pelData);
+ break;
+ default:
+ // A generic object, but at least an object.
+ section = std::make_unique<Generic>(pelData);
+ break;
+ }
+
+ return section;
+}
+
+} // namespace section_factory
+} // namespace pels
+} // namespace openpower
diff --git a/extensions/openpower-pels/section_factory.hpp b/extensions/openpower-pels/section_factory.hpp
new file mode 100644
index 0000000..8807eba
--- /dev/null
+++ b/extensions/openpower-pels/section_factory.hpp
@@ -0,0 +1,30 @@
+#pragma once
+
+#include "section.hpp"
+#include "stream.hpp"
+
+namespace openpower
+{
+namespace pels
+{
+namespace section_factory
+{
+
+/**
+ * @brief Create a PEL section based on its data
+ *
+ * This creates the appropriate PEL section object based on the section ID in
+ * the first 2 bytes of the stream, but returns the base class Section pointer.
+ *
+ * If there isn't a class specifically for that section, it defaults to
+ * creating an instance of the 'Generic' class.
+ *
+ * @param[in] pelData - The PEL data stream
+ *
+ * @return std::unique_ptr<Section> - class of the appropriate type
+ */
+std::unique_ptr<Section> create(Stream& pelData);
+
+} // namespace section_factory
+} // namespace pels
+} // namespace openpower
diff --git a/extensions/openpower-pels/section_header.hpp b/extensions/openpower-pels/section_header.hpp
new file mode 100644
index 0000000..139fc5d
--- /dev/null
+++ b/extensions/openpower-pels/section_header.hpp
@@ -0,0 +1,109 @@
+#pragma once
+
+#include "stream.hpp"
+
+#include <cstdint>
+
+namespace openpower
+{
+namespace pels
+{
+
+/**
+ * @class SectionHeader
+ *
+ * This header is at the start of every PEL section. It has a size
+ * of 8 bytes.
+ */
+struct SectionHeader
+{
+ public:
+ /**
+ * @brief Constructor
+ */
+ SectionHeader() : id(0), size(0), version(0), subType(0), componentID(0)
+ {
+ }
+
+ /**
+ * @brief Constructor
+ *
+ * @param[in] id - the ID field
+ * @param[in] size - the size field
+ * @param[in] version - the version field
+ * @param[in] subType - the sub-type field
+ * @param[in] componentID - the component ID field
+ */
+ SectionHeader(uint16_t id, uint16_t size, uint8_t version, uint8_t subType,
+ uint16_t componentID) :
+ id(id),
+ size(size), version(version), subType(subType), componentID(componentID)
+ {
+ }
+
+ /**
+ * @brief A two character ASCII field which identifies the section type.
+ */
+ uint16_t id;
+
+ /**
+ * @brief The size of the section in bytes, including this section header.
+ */
+ uint16_t size;
+
+ /**
+ * @brief The section format version.
+ */
+ uint8_t version;
+
+ /**
+ * @brief The section sub-type.
+ */
+ uint8_t subType;
+
+ /**
+ * @brief The component ID, which has various meanings depending on the
+ * section.
+ */
+ uint16_t componentID;
+
+ /**
+ * @brief Returns the size of header when flattened into a PEL.
+ *
+ * @return size_t - the size of the header
+ */
+ static constexpr size_t flattenedSize()
+ {
+ return sizeof(id) + sizeof(size) + sizeof(version) + sizeof(subType) +
+ sizeof(componentID);
+ }
+};
+
+/**
+ * @brief Stream extraction operator for the SectionHeader
+ *
+ * @param[in] s - the stream
+ * @param[out] header - the SectionHeader object
+ */
+inline Stream& operator>>(Stream& s, SectionHeader& header)
+{
+ s >> header.id >> header.size >> header.version >> header.subType >>
+ header.componentID;
+ return s;
+}
+
+/**
+ * @brief Stream insertion operator for the section header
+ *
+ * @param[out] s - the stream
+ * @param[in] header - the SectionHeader object
+ */
+inline Stream& operator<<(Stream& s, const SectionHeader& header)
+{
+ s << header.id << header.size << header.version << header.subType
+ << header.componentID;
+ return s;
+}
+
+} // namespace pels
+} // namespace openpower
diff --git a/extensions/openpower-pels/severity.cpp b/extensions/openpower-pels/severity.cpp
new file mode 100644
index 0000000..2c7c9df
--- /dev/null
+++ b/extensions/openpower-pels/severity.cpp
@@ -0,0 +1,56 @@
+/**
+ * Copyright © 2019 IBM Corporation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#include "severity.hpp"
+
+#include "pel_types.hpp"
+
+namespace openpower
+{
+namespace pels
+{
+
+using LogSeverity = phosphor::logging::Entry::Level;
+
+uint8_t convertOBMCSeverityToPEL(LogSeverity severity)
+{
+ uint8_t pelSeverity = static_cast<uint8_t>(SeverityType::unrecoverable);
+ switch (severity)
+ {
+ case (LogSeverity::Notice):
+ case (LogSeverity::Informational):
+ case (LogSeverity::Debug):
+ pelSeverity = static_cast<uint8_t>(SeverityType::nonError);
+ break;
+
+ case (LogSeverity::Warning):
+ pelSeverity = static_cast<uint8_t>(SeverityType::predictive);
+ break;
+
+ case (LogSeverity::Critical):
+ pelSeverity = static_cast<uint8_t>(SeverityType::critical);
+ break;
+
+ case (LogSeverity::Emergency):
+ case (LogSeverity::Alert):
+ case (LogSeverity::Error):
+ pelSeverity = static_cast<uint8_t>(SeverityType::unrecoverable);
+ break;
+ }
+
+ return pelSeverity;
+}
+} // namespace pels
+} // namespace openpower
diff --git a/extensions/openpower-pels/severity.hpp b/extensions/openpower-pels/severity.hpp
new file mode 100644
index 0000000..f2b9921
--- /dev/null
+++ b/extensions/openpower-pels/severity.hpp
@@ -0,0 +1,20 @@
+#pragma once
+
+#include "elog_entry.hpp"
+
+namespace openpower
+{
+namespace pels
+{
+
+/**
+ * @brief Convert an OpenBMC event log severity to a PEL severity
+ *
+ * @param[in] severity - The OpenBMC event log severity
+ *
+ * @return uint8_t - The PEL severity value
+ */
+uint8_t convertOBMCSeverityToPEL(phosphor::logging::Entry::Level severity);
+
+} // namespace pels
+} // namespace openpower
diff --git a/extensions/openpower-pels/src.cpp b/extensions/openpower-pels/src.cpp
new file mode 100644
index 0000000..db84347
--- /dev/null
+++ b/extensions/openpower-pels/src.cpp
@@ -0,0 +1,444 @@
+/**
+ * Copyright © 2019 IBM Corporation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#include "src.hpp"
+
+#include "json_utils.hpp"
+#include "paths.hpp"
+#include "pel_values.hpp"
+
+#include <phosphor-logging/log.hpp>
+
+namespace openpower
+{
+namespace pels
+{
+namespace pv = openpower::pels::pel_values;
+namespace rg = openpower::pels::message;
+using namespace phosphor::logging;
+
+void SRC::unflatten(Stream& stream)
+{
+ stream >> _header >> _version >> _flags >> _reserved1B >> _wordCount >>
+ _reserved2B >> _size;
+
+ for (auto& word : _hexData)
+ {
+ stream >> word;
+ }
+
+ _asciiString = std::make_unique<src::AsciiString>(stream);
+
+ if (hasAdditionalSections())
+ {
+ // The callouts section is currently the only extra subsection type
+ _callouts = std::make_unique<src::Callouts>(stream);
+ }
+}
+
+void SRC::flatten(Stream& stream) const
+{
+ stream << _header << _version << _flags << _reserved1B << _wordCount
+ << _reserved2B << _size;
+
+ for (auto& word : _hexData)
+ {
+ stream << word;
+ }
+
+ _asciiString->flatten(stream);
+
+ if (_callouts)
+ {
+ _callouts->flatten(stream);
+ }
+}
+
+SRC::SRC(Stream& pel)
+{
+ try
+ {
+ unflatten(pel);
+ validate();
+ }
+ catch (const std::exception& e)
+ {
+ log<level::ERR>("Cannot unflatten SRC", entry("ERROR=%s", e.what()));
+ _valid = false;
+ }
+}
+
+SRC::SRC(const message::Entry& regEntry, const AdditionalData& additionalData)
+{
+ _header.id = static_cast<uint16_t>(SectionID::primarySRC);
+ _header.version = srcSectionVersion;
+ _header.subType = srcSectionSubtype;
+ _header.componentID = regEntry.componentID;
+
+ _version = srcVersion;
+
+ _flags = 0;
+ if (regEntry.src.powerFault.value_or(false))
+ {
+ _flags |= powerFaultEvent;
+ }
+
+ _reserved1B = 0;
+
+ _wordCount = numSRCHexDataWords + 1;
+
+ _reserved2B = 0;
+
+ // There are multiple fields encoded in the hex data words.
+ std::for_each(_hexData.begin(), _hexData.end(),
+ [](auto& word) { word = 0; });
+ setBMCFormat();
+ setBMCPosition();
+ // Partition dump status and partition boot type always 0 for BMC errors.
+ //
+ // TODO: Fill in other fields that aren't available yet.
+
+ // Fill in the last 4 words from the AdditionalData property contents.
+ setUserDefinedHexWords(regEntry, additionalData);
+
+ _asciiString = std::make_unique<src::AsciiString>(regEntry);
+
+ // TODO: add callouts using the Callouts object
+
+ _size = baseSRCSize;
+ _size += _callouts ? _callouts->flattenedSize() : 0;
+ _header.size = Section::flattenedSize() + _size;
+
+ _valid = true;
+}
+
+void SRC::setUserDefinedHexWords(const message::Entry& regEntry,
+ const AdditionalData& ad)
+{
+ if (!regEntry.src.hexwordADFields)
+ {
+ return;
+ }
+
+ // Save the AdditionalData value corresponding to the
+ // adName key in _hexData[wordNum].
+ for (const auto& [wordNum, adName] : *regEntry.src.hexwordADFields)
+ {
+ // Can only set words 6 - 9
+ if (!isUserDefinedWord(wordNum))
+ {
+ log<level::WARNING>("SRC user data word out of range",
+ entry("WORD_NUM=%d", wordNum),
+ entry("ERROR_NAME=%s", regEntry.name.c_str()));
+ continue;
+ }
+
+ auto value = ad.getValue(adName);
+ if (value)
+ {
+ _hexData[getWordIndexFromWordNum(wordNum)] =
+ std::strtoul(value.value().c_str(), nullptr, 0);
+ }
+ else
+ {
+ log<level::WARNING>("Source for user data SRC word not found",
+ entry("ADDITIONALDATA_KEY=%s", adName.c_str()),
+ entry("ERROR_NAME=%s", regEntry.name.c_str()));
+ }
+ }
+}
+
+void SRC::validate()
+{
+ bool failed = false;
+
+ if ((header().id != static_cast<uint16_t>(SectionID::primarySRC)) &&
+ (header().id != static_cast<uint16_t>(SectionID::secondarySRC)))
+ {
+ log<level::ERR>("Invalid SRC section ID",
+ entry("ID=0x%X", header().id));
+ failed = true;
+ }
+
+ // Check the version in the SRC, not in the header
+ if (_version != srcVersion)
+ {
+ log<level::ERR>("Invalid SRC version", entry("VERSION=0x%X", _version));
+ failed = true;
+ }
+
+ _valid = failed ? false : true;
+}
+
+std::optional<std::string> SRC::getErrorDetails(message::Registry& registry,
+ DetailLevel type,
+ bool toCache) const
+{
+ const std::string jsonIndent(indentLevel, 0x20);
+ std::string errorOut;
+ uint8_t errorType =
+ strtoul(asciiString().substr(0, 2).c_str(), nullptr, 16);
+ if (errorType == static_cast<uint8_t>(SRCType::bmcError) ||
+ errorType == static_cast<uint8_t>(SRCType::powerError))
+ {
+ auto entry = registry.lookup("0x" + asciiString().substr(4, 4),
+ rg::LookupType::reasonCode, toCache);
+ if (entry)
+ {
+ errorOut.append(jsonIndent + "\"Error Details\": {\n");
+ auto errorMsg = getErrorMessage(*entry);
+ if (errorMsg)
+ {
+ if (type == DetailLevel::message)
+ {
+ return errorMsg.value();
+ }
+ else
+ {
+ jsonInsert(errorOut, "Message", errorMsg.value(), 2);
+ }
+ }
+ if (entry->src.hexwordADFields)
+ {
+ std::map<size_t, std::string> adFields =
+ entry->src.hexwordADFields.value();
+ for (const auto& hexwordMap : adFields)
+ {
+ jsonInsert(errorOut, hexwordMap.second,
+ getNumberString("0x%X",
+ _hexData[getWordIndexFromWordNum(
+ hexwordMap.first)]),
+ 2);
+ }
+ }
+ errorOut.erase(errorOut.size() - 2);
+ errorOut.append("\n");
+ errorOut.append(jsonIndent + "},\n");
+ return errorOut;
+ }
+ }
+ return std::nullopt;
+}
+
+std::optional<std::string>
+ SRC::getErrorMessage(const message::Entry& regEntry) const
+{
+ try
+ {
+ if (regEntry.doc.messageArgSources)
+ {
+ size_t msgLen = regEntry.doc.message.length();
+ char msg[msgLen + 1];
+ strcpy(msg, regEntry.doc.message.c_str());
+ std::vector<uint32_t> argSourceVals;
+ std::string message;
+ const auto& argValues = regEntry.doc.messageArgSources.value();
+ for (size_t i = 0; i < argValues.size(); ++i)
+ {
+ argSourceVals.push_back(_hexData[getWordIndexFromWordNum(
+ argValues[i].back() - '0')]);
+ }
+ const char* msgPointer = msg;
+ while (*msgPointer)
+ {
+ if (*msgPointer == '%')
+ {
+ msgPointer++;
+ size_t wordIndex = *msgPointer - '0';
+ if (isdigit(*msgPointer) && wordIndex >= 1 &&
+ static_cast<uint16_t>(wordIndex) <=
+ argSourceVals.size())
+ {
+ message.append(getNumberString(
+ "0x%X", argSourceVals[wordIndex - 1]));
+ }
+ else
+ {
+ message.append("%" + std::string(1, *msgPointer));
+ }
+ }
+ else
+ {
+ message.push_back(*msgPointer);
+ }
+ msgPointer++;
+ }
+ return message;
+ }
+ else
+ {
+ return regEntry.doc.message;
+ }
+ }
+ catch (const std::exception& e)
+ {
+ log<level::ERR>("Cannot get error message from registry entry",
+ entry("ERROR=%s", e.what()));
+ }
+ return std::nullopt;
+}
+
+std::optional<std::string> SRC::getCallouts() const
+{
+ if (!_callouts)
+ {
+ return std::nullopt;
+ }
+ std::string printOut;
+ const std::string jsonIndent(indentLevel, 0x20);
+ const auto& callout = _callouts->callouts();
+ const auto& compDescrp = pv::failingComponentType;
+ printOut.append(jsonIndent + "\"Callout Section\": {\n");
+ jsonInsert(printOut, "Callout Count", std::to_string(callout.size()), 2);
+ printOut.append(jsonIndent + jsonIndent + "\"Callouts\": [");
+ for (auto& entry : callout)
+ {
+ printOut.append("{\n");
+ if (entry->fruIdentity())
+ {
+ jsonInsert(
+ printOut, "FRU Type",
+ compDescrp.at(entry->fruIdentity()->failingComponentType()), 3);
+ jsonInsert(printOut, "Priority",
+ pv::getValue(entry->priority(),
+ pel_values::calloutPriorityValues),
+ 3);
+ if (!entry->locationCode().empty())
+ {
+ jsonInsert(printOut, "Location Code", entry->locationCode(), 3);
+ }
+ if (entry->fruIdentity()->getPN().has_value())
+ {
+ jsonInsert(printOut, "Part Number",
+ entry->fruIdentity()->getPN().value(), 3);
+ }
+ if (entry->fruIdentity()->getMaintProc().has_value())
+ {
+ jsonInsert(printOut, "Procedure Number",
+ entry->fruIdentity()->getMaintProc().value(), 3);
+ if (pv::procedureDesc.find(
+ entry->fruIdentity()->getMaintProc().value()) !=
+ pv::procedureDesc.end())
+ {
+ jsonInsert(
+ printOut, "Description",
+ pv::procedureDesc.at(
+ entry->fruIdentity()->getMaintProc().value()),
+ 3);
+ }
+ }
+ if (entry->fruIdentity()->getCCIN().has_value())
+ {
+ jsonInsert(printOut, "CCIN",
+ entry->fruIdentity()->getCCIN().value(), 3);
+ }
+ if (entry->fruIdentity()->getSN().has_value())
+ {
+ jsonInsert(printOut, "Serial Number",
+ entry->fruIdentity()->getSN().value(), 3);
+ }
+ }
+ if (entry->pceIdentity())
+ {
+ const auto& pceIdentMtms = entry->pceIdentity()->mtms();
+ if (!pceIdentMtms.machineTypeAndModel().empty())
+ {
+ jsonInsert(printOut, "PCE MTMS",
+ pceIdentMtms.machineTypeAndModel() + "_" +
+ pceIdentMtms.machineSerialNumber(),
+ 3);
+ }
+ if (!entry->pceIdentity()->enclosureName().empty())
+ {
+ jsonInsert(printOut, "PCE Name",
+ entry->pceIdentity()->enclosureName(), 3);
+ }
+ }
+ if (entry->mru())
+ {
+ const auto& mruCallouts = entry->mru()->mrus();
+ std::string mruId;
+ for (auto& element : mruCallouts)
+ {
+ if (!mruId.empty())
+ {
+ mruId.append(", " + getNumberString("%08X", element.id));
+ }
+ else
+ {
+ mruId.append(getNumberString("%08X", element.id));
+ }
+ }
+ jsonInsert(printOut, "MRU Id", mruId, 3);
+ }
+ printOut.erase(printOut.size() - 2);
+ printOut.append("\n" + jsonIndent + jsonIndent + "}, ");
+ };
+ printOut.erase(printOut.size() - 2);
+ printOut.append("]\n" + jsonIndent + "}");
+ return printOut;
+}
+
+std::optional<std::string> SRC::getJSON() const
+{
+ std::string ps;
+ jsonInsert(ps, "Section Version", getNumberString("%d", _header.version),
+ 1);
+ jsonInsert(ps, "Sub-section type", getNumberString("%d", _header.subType),
+ 1);
+ jsonInsert(ps, "Created by", getNumberString("0x%X", _header.componentID),
+ 1);
+ jsonInsert(ps, "SRC Version", getNumberString("0x%02X", _version), 1);
+ jsonInsert(ps, "SRC Format", getNumberString("0x%02X", _hexData[0] & 0xFF),
+ 1);
+ jsonInsert(ps, "Virtual Progress SRC",
+ pv::boolString.at(_flags & virtualProgressSRC), 1);
+ jsonInsert(ps, "I5/OS Service Event Bit",
+ pv::boolString.at(_flags & i5OSServiceEventBit), 1);
+ jsonInsert(ps, "Hypervisor Dump Initiated",
+ pv::boolString.at(_flags & hypDumpInit), 1);
+ jsonInsert(ps, "Power Control Net Fault",
+ pv::boolString.at(isPowerFaultEvent()), 1);
+ rg::Registry registry(getMessageRegistryPath() / rg::registryFileName);
+ auto errorDetails = getErrorDetails(registry, DetailLevel::json);
+ if (errorDetails)
+ {
+ ps.append(errorDetails.value());
+ }
+ jsonInsert(ps, "Valid Word Count", getNumberString("0x%02X", _wordCount),
+ 1);
+ std::string refcode = asciiString();
+ refcode = refcode.substr(0, refcode.find(0x20));
+ jsonInsert(ps, "Reference Code", refcode, 1);
+ for (size_t i = 2; i <= _wordCount; i++)
+ {
+ jsonInsert(
+ ps, "Hex Word " + std::to_string(i),
+ getNumberString("%08X", _hexData[getWordIndexFromWordNum(i)]), 1);
+ }
+ auto calloutJson = getCallouts();
+ if (calloutJson)
+ {
+ ps.append(calloutJson.value());
+ }
+ else
+ {
+ ps.erase(ps.size() - 2);
+ }
+ return ps;
+}
+
+} // namespace pels
+} // namespace openpower
diff --git a/extensions/openpower-pels/src.hpp b/extensions/openpower-pels/src.hpp
new file mode 100644
index 0000000..2296f6f
--- /dev/null
+++ b/extensions/openpower-pels/src.hpp
@@ -0,0 +1,385 @@
+#pragma once
+
+#include "additional_data.hpp"
+#include "ascii_string.hpp"
+#include "callouts.hpp"
+#include "pel_types.hpp"
+#include "registry.hpp"
+#include "section.hpp"
+#include "stream.hpp"
+
+namespace openpower
+{
+namespace pels
+{
+
+constexpr uint8_t srcSectionVersion = 0x01;
+constexpr uint8_t srcSectionSubtype = 0x01;
+constexpr size_t numSRCHexDataWords = 8;
+constexpr uint8_t srcVersion = 0x02;
+constexpr uint8_t bmcSRCFormat = 0x55;
+constexpr uint8_t primaryBMCPosition = 0x10;
+constexpr size_t baseSRCSize = 72;
+
+enum class DetailLevel
+{
+ message = 0x01,
+ json = 0x02
+};
+/**
+ * @class SRC
+ *
+ * SRC stands for System Reference Code.
+ *
+ * This class represents the SRC sections in the PEL, of which there are 2:
+ * primary SRC and secondary SRC. These are the same structurally, the
+ * difference is that the primary SRC must be the 3rd section in the PEL if
+ * present and there is only one of them, and the secondary SRC sections are
+ * optional and there can be more than one (by definition, for there to be a
+ * secondary SRC, a primary SRC must also exist).
+ *
+ * This section consists of:
+ * - An 8B header (Has the version, flags, hexdata word count, and size fields)
+ * - 8 4B words of hex data
+ * - An ASCII character string
+ * - An optional subsection for Callouts
+ */
+class SRC : public Section
+{
+ public:
+ enum HeaderFlags
+ {
+ additionalSections = 0x01,
+ powerFaultEvent = 0x02,
+ hypDumpInit = 0x04,
+ i5OSServiceEventBit = 0x10,
+ virtualProgressSRC = 0x80
+ };
+
+ SRC() = delete;
+ ~SRC() = default;
+ SRC(const SRC&) = delete;
+ SRC& operator=(const SRC&) = delete;
+ SRC(SRC&&) = delete;
+ SRC& operator=(SRC&&) = delete;
+
+ /**
+ * @brief Constructor
+ *
+ * Fills in this class's data fields from the stream.
+ *
+ * @param[in] pel - the PEL data stream
+ */
+ explicit SRC(Stream& pel);
+
+ /**
+ * @brief Constructor
+ *
+ * Creates the section with data from the PEL message registry entry for
+ * this error, along with the AdditionalData property contents from the
+ * corresponding event log.
+ *
+ * @param[in] regEntry - The message registry entry for this event log
+ * @param[in] additionalData - The AdditionalData properties in this event
+ * log
+ */
+ SRC(const message::Entry& regEntry, const AdditionalData& additionalData);
+
+ /**
+ * @brief Flatten the section into the stream
+ *
+ * @param[in] stream - The stream to write to
+ */
+ void flatten(Stream& stream) const override;
+
+ /**
+ * @brief Returns the SRC version, which is a different field
+ * than the version byte in the section header.
+ *
+ * @return uint8_t
+ */
+ uint8_t version() const
+ {
+ return _version;
+ }
+
+ /**
+ * @brief Returns the flags byte
+ *
+ * @return uint8_t
+ */
+ uint8_t flags() const
+ {
+ return _flags;
+ }
+
+ /**
+ * @brief Returns the hex data word count.
+ *
+ * Even though there always 8 words, this returns 9 due to previous
+ * SRC version formats.
+ *
+ * @return uint8_t
+ */
+ uint8_t hexWordCount() const
+ {
+ return _wordCount;
+ }
+
+ /**
+ * @brief Returns the size of the SRC section, not including the header.
+ *
+ * @return uint16_t
+ */
+ uint16_t size() const
+ {
+ return _size;
+ }
+
+ /**
+ * @brief Returns the 8 hex data words.
+ *
+ * @return const std::array<uint32_t, numSRCHexDataWords>&
+ */
+ const std::array<uint32_t, numSRCHexDataWords>& hexwordData() const
+ {
+ return _hexData;
+ }
+
+ /**
+ * @brief Returns the ASCII string
+ *
+ * @return std::string
+ */
+ std::string asciiString() const
+ {
+ return _asciiString->get();
+ }
+
+ /**
+ * @brief Returns the callouts subsection
+ *
+ * If no callouts, this unique_ptr will be empty
+ *
+ * @return const std::unique_ptr<src::Callouts>&
+ */
+ const std::unique_ptr<src::Callouts>& callouts() const
+ {
+ return _callouts;
+ }
+
+ /**
+ * @brief Returns the size of this section when flattened into a PEL
+ *
+ * @return size_t - the size of the section
+ */
+ size_t flattenedSize() const
+ {
+ return _header.size;
+ }
+
+ /**
+ * @brief Says if this SRC has additional subsections in it
+ *
+ * Note: The callouts section is the only possible subsection.
+ *
+ * @return bool
+ */
+ inline bool hasAdditionalSections() const
+ {
+ return _flags & additionalSections;
+ }
+
+ /**
+ * @brief Indicates if this event log is for a power fault.
+ *
+ * This comes from a field in the message registry for BMC
+ * generated PELs.
+ *
+ * @return bool
+ */
+ inline bool isPowerFaultEvent() const
+ {
+ return _flags & powerFaultEvent;
+ }
+
+ /**
+ * @brief Get the _hexData[] index to use based on the corresponding
+ * SRC word number.
+ *
+ * Converts the specification nomenclature to this data structure.
+ * See the _hexData documentation below for more information.
+ *
+ * @param[in] wordNum - The SRC word number, as defined by the spec.
+ *
+ * @return size_t The corresponding index into _hexData.
+ */
+ inline size_t getWordIndexFromWordNum(size_t wordNum) const
+ {
+ assert(wordNum >= 2 && wordNum <= 9);
+ return wordNum - 2;
+ }
+
+ /**
+ * @brief Get section in JSON.
+ * @return std::optional<std::string> - SRC section's JSON
+ */
+ std::optional<std::string> getJSON() const override;
+
+ /**
+ * @brief Get error details based on refcode and hexwords
+ * @param[in] registry - Registry object
+ * @param[in] type - detail level enum value : single message or full json
+ * @param[in] toCache - boolean to cache registry in memory, default=false
+ * @return std::optional<std::string> - Error details
+ */
+ std::optional<std::string> getErrorDetails(message::Registry& registry,
+ DetailLevel type,
+ bool toCache = false) const;
+
+ private:
+ /**
+ * @brief Fills in the user defined hex words from the
+ * AdditionalData fields.
+ *
+ * When creating this section from a message registry entry,
+ * that entry has a field that says which AdditionalData property
+ * fields to use to fill in the user defined hex data words 6-9
+ * (which correspond to hexData words 4-7).
+ *
+ * For example, given that AdditionalData is a map of string keys
+ * to string values, find the AdditionalData value for AdditionalData
+ * key X, convert it to a uint32_t, and save it in user data word Y.
+ *
+ * @param[in] regEntry - The message registry entry for the error
+ * @param[in] additionalData - The AdditionalData map
+ */
+ void setUserDefinedHexWords(const message::Entry& regEntry,
+ const AdditionalData& additionalData);
+ /**
+ * @brief Fills in the object from the stream data
+ *
+ * @param[in] stream - The stream to read from
+ */
+ void unflatten(Stream& stream);
+
+ /**
+ * @brief Says if the word number is in the range of user defined words.
+ *
+ * This is only used for BMC generated SRCs, where words 6 - 9 are the
+ * user defined ones, meaning that setUserDefinedHexWords() will be
+ * used to fill them in based on the contents of the OpenBMC event log.
+ *
+ * @param[in] wordNum - The SRC word number, as defined by the spec.
+ *
+ * @return bool - If this word number can be filled in by the creator.
+ */
+ inline bool isUserDefinedWord(size_t wordNum) const
+ {
+ return (wordNum >= 6) && (wordNum <= 9);
+ }
+
+ /**
+ * @brief Sets the SRC format byte in the hex word data.
+ */
+ inline void setBMCFormat()
+ {
+ _hexData[0] |= bmcSRCFormat;
+ }
+
+ /**
+ * @brief Sets the hex word field that specifies which BMC
+ * (primary vs backup) created the error.
+ *
+ * Can be hardcoded until there are systems with redundant BMCs.
+ */
+ inline void setBMCPosition()
+ {
+ _hexData[1] |= primaryBMCPosition;
+ }
+
+ /**
+ * @brief Validates the section contents
+ *
+ * Updates _valid (in Section) with the results.
+ */
+ void validate() override;
+
+ /**
+ * @brief Get error description from message registry
+ * @param[in] regEntry - The message registry entry for the error
+ * @return std::optional<std::string> - Error message
+ */
+ std::optional<std::string>
+ getErrorMessage(const message::Entry& regEntry) const;
+
+ /**
+ * @brief Get Callout info in JSON
+ * @return std::optional<std::string> - Callout details
+ */
+ std::optional<std::string> getCallouts() const;
+
+ /**
+ * @brief The SRC version field
+ */
+ uint8_t _version;
+
+ /**
+ * @brief The SRC flags field
+ */
+ uint8_t _flags;
+
+ /**
+ * @brief A byte of reserved data after the flags field
+ */
+ uint8_t _reserved1B;
+
+ /**
+ * @brief The hex data word count.
+ *
+ * To be compatible with previous versions of SRCs, this is
+ * number of hex words (8) + 1 = 9.
+ */
+ uint8_t _wordCount;
+
+ /**
+ * @brief Two bytes of reserved data after the hex word count
+ */
+ uint16_t _reserved2B;
+
+ /**
+ * @brief The total size of the SRC section, not including the section
+ * header.
+ */
+ uint16_t _size;
+
+ /**
+ * @brief The SRC 'hex words'.
+ *
+ * In the spec these are referred to as SRC words 2 - 9 as words 0 and 1
+ * are filled by the 8 bytes of fields from above.
+ */
+ std::array<uint32_t, numSRCHexDataWords> _hexData;
+
+ /**
+ * @brief The 32 byte ASCII character string of the SRC
+ *
+ * It is padded with spaces to fill the 32 bytes.
+ * An example is:
+ * "BD8D1234 "
+ *
+ * That first word is what is commonly referred to as the refcode, and
+ * sometimes also called an SRC.
+ */
+ std::unique_ptr<src::AsciiString> _asciiString;
+
+ /**
+ * @brief The callouts subsection.
+ *
+ * Optional and only created if there are callouts.
+ */
+ std::unique_ptr<src::Callouts> _callouts;
+};
+
+} // namespace pels
+} // namespace openpower
diff --git a/extensions/openpower-pels/stream.hpp b/extensions/openpower-pels/stream.hpp
new file mode 100644
index 0000000..1a776f6
--- /dev/null
+++ b/extensions/openpower-pels/stream.hpp
@@ -0,0 +1,373 @@
+#pragma once
+
+#include <arpa/inet.h>
+#include <byteswap.h>
+
+#include <cassert>
+#include <cstring>
+#include <memory>
+#include <string>
+#include <vector>
+
+namespace openpower
+{
+namespace pels
+{
+
+namespace detail
+{
+/**
+ * @brief A host-to-network implementation for uint64_t
+ *
+ * @param[in] value - the value to convert to
+ * @return uint64_t - the byteswapped value
+ */
+inline uint64_t htonll(uint64_t value)
+{
+ return bswap_64(value);
+}
+
+/**
+ * @brief A network-to-host implementation for uint64_t
+ *
+ * @param[in] value - the value to convert to
+ * @return uint64_t - the byteswapped value
+ */
+inline uint64_t ntohll(uint64_t value)
+{
+ return bswap_64(value);
+}
+} // namespace detail
+
+/**
+ * @class Stream
+ *
+ * This class is used for getting data types into and out of a vector<uint8_t>
+ * that contains data in network byte (big endian) ordering.
+ */
+class Stream
+{
+ public:
+ Stream() = delete;
+ ~Stream() = default;
+ Stream(const Stream&) = default;
+ Stream& operator=(const Stream&) = default;
+ Stream(Stream&&) = default;
+ Stream& operator=(Stream&&) = default;
+
+ /**
+ * @brief Constructor
+ *
+ * @param[in] data - the vector of data
+ */
+ explicit Stream(std::vector<uint8_t>& data) : _data(data), _offset(0)
+ {
+ }
+
+ /**
+ * @brief Constructor
+ *
+ * @param[in] data - the vector of data
+ * @param[in] offset - the starting offset
+ */
+ Stream(std::vector<uint8_t>& data, std::size_t offset) :
+ _data(data), _offset(offset)
+ {
+ if (_offset >= _data.size())
+ {
+ throw std::out_of_range("Offset out of range");
+ }
+ }
+
+ /**
+ * @brief Extraction operator for a uint8_t
+ *
+ * @param[out] value - filled in with the value
+ * @return Stream&
+ */
+ Stream& operator>>(uint8_t& value)
+ {
+ read(&value, 1);
+ return *this;
+ }
+
+ /**
+ * @brief Extraction operator for a char
+ *
+ * @param[out] value -filled in with the value
+ * @return Stream&
+ */
+ Stream& operator>>(char& value)
+ {
+ read(&value, 1);
+ return *this;
+ }
+
+ /**
+ * @brief Extraction operator for a uint16_t
+ *
+ * @param[out] value -filled in with the value
+ * @return Stream&
+ */
+ Stream& operator>>(uint16_t& value)
+ {
+ read(&value, 2);
+ value = htons(value);
+ return *this;
+ }
+
+ /**
+ * @brief Extraction operator for a uint32_t
+ *
+ * @param[out] value -filled in with the value
+ * @return Stream&
+ */
+ Stream& operator>>(uint32_t& value)
+ {
+ read(&value, 4);
+ value = htonl(value);
+ return *this;
+ }
+
+ /**
+ * @brief Extraction operator for a uint64_t
+ *
+ * @param[out] value -filled in with the value
+ * @return Stream&
+ */
+ Stream& operator>>(uint64_t& value)
+ {
+ read(&value, 8);
+ value = detail::htonll(value);
+ return *this;
+ }
+
+ /**
+ * @brief Extraction operator for a std::vector<uint8_t>
+ *
+ * The vector's size is the amount extracted.
+ *
+ * @param[out] value - filled in with the value
+ * @return Stream&
+ */
+ Stream& operator>>(std::vector<uint8_t>& value)
+ {
+ if (!value.empty())
+ {
+ read(value.data(), value.size());
+ }
+ return *this;
+ }
+
+ /**
+ * @brief Extraction operator for a std::vector<char>
+ *
+ * The vector's size is the amount extracted.
+ *
+ * @param[out] value - filled in with the value
+ * @return Stream&
+ */
+ Stream& operator>>(std::vector<char>& value)
+ {
+ if (!value.empty())
+ {
+ read(value.data(), value.size());
+ }
+ return *this;
+ }
+
+ /**
+ * @brief Insert operator for a uint8_t
+ *
+ * @param[in] value - the value to write to the stream
+ * @return Stream&
+ */
+ Stream& operator<<(uint8_t value)
+ {
+ write(&value, 1);
+ return *this;
+ }
+
+ /**
+ * @brief Insert operator for a char
+ *
+ * @param[in] value - the value to write to the stream
+ * @return Stream&
+ */
+ Stream& operator<<(char value)
+ {
+ write(&value, 1);
+ return *this;
+ }
+
+ /**
+ * @brief Insert operator for a uint16_t
+ *
+ * @param[in] value - the value to write to the stream
+ * @return Stream&
+ */
+ Stream& operator<<(uint16_t value)
+ {
+ uint16_t data = ntohs(value);
+ write(&data, 2);
+ return *this;
+ }
+
+ /**
+ * @brief Insert operator for a uint32_t
+ *
+ * @param[in] value - the value to write to the stream
+ * @return Stream&
+ */
+ Stream& operator<<(uint32_t value)
+ {
+ uint32_t data = ntohl(value);
+ write(&data, 4);
+ return *this;
+ }
+
+ /**
+ * @brief Insert operator for a uint64_t
+ *
+ * @param[in] value - the value to write to the stream
+ * @return Stream&
+ */
+ Stream& operator<<(uint64_t value)
+ {
+ uint64_t data = detail::ntohll(value);
+ write(&data, 8);
+ return *this;
+ }
+
+ /**
+ * @brief Insert operator for a std::vector<uint8_t>
+ *
+ * The full vector is written to the stream.
+ *
+ * @param[in] value - the value to write to the stream
+ * @return Stream&
+ */
+ Stream& operator<<(const std::vector<uint8_t>& value)
+ {
+ if (!value.empty())
+ {
+ write(value.data(), value.size());
+ }
+ return *this;
+ }
+
+ /**
+ * @brief Insert operator for a std::vector<char>
+ *
+ * The full vector is written to the stream.
+ *
+ * @param[in] value - the value to write to the stream
+ * @return Stream&
+ */
+ Stream& operator<<(const std::vector<char>& value)
+ {
+ if (!value.empty())
+ {
+ write(value.data(), value.size());
+ }
+ return *this;
+ }
+
+ /**
+ * @brief Sets the offset of the stream
+ *
+ * @param[in] newOffset - the new offset
+ */
+ void offset(std::size_t newOffset)
+ {
+ if (newOffset >= _data.size())
+ {
+ throw std::out_of_range("new offset out of range");
+ }
+
+ _offset = newOffset;
+ }
+
+ /**
+ * @brief Returns the current offset of the stream
+ *
+ * @return size_t - the offset
+ */
+ std::size_t offset() const
+ {
+ return _offset;
+ }
+
+ /**
+ * @brief Returns the remaining bytes left between the current offset
+ * and the data size.
+ *
+ * @return size_t - the remaining size
+ */
+ std::size_t remaining() const
+ {
+ assert(_data.size() >= _offset);
+ return _data.size() - _offset;
+ }
+
+ /**
+ * @brief Reads a specified number of bytes out of a stream
+ *
+ * @param[out] out - filled in with the data
+ * @param[in] size - the size to read
+ */
+ void read(void* out, std::size_t size)
+ {
+ rangeCheck(size);
+ memcpy(out, &_data[_offset], size);
+ _offset += size;
+ }
+
+ /**
+ * @brief Writes a specified number of bytes into the stream
+ *
+ * @param[in] in - the data to write
+ * @param[in] size - the size to write
+ */
+ void write(const void* in, std::size_t size)
+ {
+ size_t newSize = _offset + size;
+ if (newSize > _data.size())
+ {
+ _data.resize(newSize, 0);
+ }
+ memcpy(&_data[_offset], in, size);
+ _offset += size;
+ }
+
+ private:
+ /**
+ * @brief Throws an exception if the size passed in plus the current
+ * offset is bigger than the current data size.
+ * @param[in] size - the size to check
+ */
+ void rangeCheck(std::size_t size)
+ {
+ if (_offset + size > _data.size())
+ {
+ std::string msg{"Attempted stream overflow: offset "};
+ msg += std::to_string(_offset) + " buffer size " +
+ std::to_string(_data.size()) + " op size " +
+ std::to_string(size);
+ throw std::out_of_range(msg.c_str());
+ }
+ }
+
+ /**
+ * @brief The data that the stream accesses.
+ */
+ std::vector<uint8_t>& _data;
+
+ /**
+ * @brief The current offset of the stream.
+ */
+ std::size_t _offset;
+};
+
+} // namespace pels
+} // namespace openpower
diff --git a/extensions/openpower-pels/tools/peltool.cpp b/extensions/openpower-pels/tools/peltool.cpp
new file mode 100644
index 0000000..ed6daa2
--- /dev/null
+++ b/extensions/openpower-pels/tools/peltool.cpp
@@ -0,0 +1,431 @@
+/**
+ * Copyright © 2019 IBM Corporation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#include "config.h"
+
+#include "../bcd_time.hpp"
+#include "../paths.hpp"
+#include "../pel.hpp"
+#include "../pel_types.hpp"
+#include "../pel_values.hpp"
+
+#include <CLI/CLI.hpp>
+#include <bitset>
+#include <iostream>
+#include <phosphor-logging/log.hpp>
+#include <regex>
+#include <string>
+#include <xyz/openbmc_project/Common/File/error.hpp>
+
+namespace fs = std::filesystem;
+using namespace phosphor::logging;
+using namespace openpower::pels;
+namespace file_error = sdbusplus::xyz::openbmc_project::Common::File::Error;
+namespace message = openpower::pels::message;
+namespace pv = openpower::pels::pel_values;
+
+/**
+ * @brief helper function to get PEL commit timestamp from file name
+ * @retrun BCDTime - PEL commit timestamp
+ * @param[in] std::string - file name
+ */
+BCDTime fileNameToTimestamp(const std::string& fileName)
+{
+ std::string token = fileName.substr(0, fileName.find("_"));
+ int i = 0;
+ BCDTime tmp;
+ if (token.length() >= 14)
+ {
+ try
+ {
+ tmp.yearMSB = std::stoi(token.substr(i, 2), 0, 16);
+ }
+ catch (std::exception& err)
+ {
+ std::cout << "Conversion failure: " << err.what() << std::endl;
+ }
+ i += 2;
+ try
+ {
+ tmp.yearLSB = std::stoi(token.substr(i, 2), 0, 16);
+ }
+ catch (std::exception& err)
+ {
+ std::cout << "Conversion failure: " << err.what() << std::endl;
+ }
+ i += 2;
+ try
+ {
+ tmp.month = std::stoi(token.substr(i, 2), 0, 16);
+ }
+ catch (std::exception& err)
+ {
+ std::cout << "Conversion failure: " << err.what() << std::endl;
+ }
+ i += 2;
+ try
+ {
+ tmp.day = std::stoi(token.substr(i, 2), 0, 16);
+ }
+ catch (std::exception& err)
+ {
+ std::cout << "Conversion failure: " << err.what() << std::endl;
+ }
+ i += 2;
+ try
+ {
+ tmp.hour = std::stoi(token.substr(i, 2), 0, 16);
+ }
+ catch (std::exception& err)
+ {
+ std::cout << "Conversion failure: " << err.what() << std::endl;
+ }
+ i += 2;
+ try
+ {
+ tmp.minutes = std::stoi(token.substr(i, 2), 0, 16);
+ }
+ catch (std::exception& err)
+ {
+ std::cout << "Conversion failure: " << err.what() << std::endl;
+ }
+ i += 2;
+ try
+ {
+ tmp.seconds = std::stoi(token.substr(i, 2), 0, 16);
+ }
+ catch (std::exception& err)
+ {
+ std::cout << "Conversion failure: " << err.what() << std::endl;
+ }
+ i += 2;
+ try
+ {
+ tmp.hundredths = std::stoi(token.substr(i, 2), 0, 16);
+ }
+ catch (std::exception& err)
+ {
+ std::cout << "Conversion failure: " << err.what() << std::endl;
+ }
+ }
+ return tmp;
+}
+
+/**
+ * @brief helper function to get PEL id from file name
+ * @retrun uint32_t - PEL id
+ * @param[in] std::string - file name
+ */
+uint32_t fileNameToPELId(const std::string& fileName)
+{
+ uint32_t num = 0;
+ try
+ {
+ num = std::stoi(fileName.substr(fileName.find("_") + 1), 0, 16);
+ }
+ catch (std::exception& err)
+ {
+ std::cout << "Conversion failure: " << err.what() << std::endl;
+ }
+ return num;
+}
+
+/**
+ * @brief helper function to check string suffix
+ * @retrun bool - true with suffix matches
+ * @param[in] std::string - string to check for suffix
+ * @param[in] std::string - suffix string
+ */
+bool ends_with(const std::string& str, const std::string& end)
+{
+ size_t slen = str.size(), elen = end.size();
+ if (slen < elen)
+ return false;
+ while (elen)
+ {
+ if (str[--slen] != end[--elen])
+ return false;
+ }
+ return true;
+}
+
+/**
+ * @brief get data form raw PEL file.
+ * @param[in] std::string Name of file with raw PEL
+ * @return std::vector<uint8_t> char vector read from raw PEL file.
+ */
+std::vector<uint8_t> getFileData(const std::string& name)
+{
+ std::ifstream file(name, std::ifstream::in);
+ if (file.good())
+ {
+ std::vector<uint8_t> data{std::istreambuf_iterator<char>(file),
+ std::istreambuf_iterator<char>()};
+ return data;
+ }
+ else
+ {
+ printf("Can't open raw PEL file");
+ return {};
+ }
+}
+
+template <typename T>
+std::string genPELJSON(T itr, bool hidden, message::Registry& registry)
+{
+ std::size_t found;
+ std::string val;
+ char tmpValStr[50];
+ std::string listStr;
+ char name[50];
+ sprintf(name, "%.2X%.2X%.2X%.2X%.2X%.2X%.2X%.2X_%.8X", itr.second.yearMSB,
+ itr.second.yearLSB, itr.second.month, itr.second.day,
+ itr.second.hour, itr.second.minutes, itr.second.seconds,
+ itr.second.hundredths, itr.first);
+ std::string fileName(name);
+ fileName = EXTENSION_PERSIST_DIR "/pels/logs/" + fileName;
+ try
+ {
+ std::vector<uint8_t> data = getFileData(fileName);
+ if (!data.empty())
+ {
+ PEL pel{data};
+ std::bitset<16> actionFlags{pel.userHeader().actionFlags()};
+ if (pel.valid() && (hidden || !actionFlags.test(hiddenFlagBit)))
+ {
+ // id
+ sprintf(tmpValStr, "0x%X", pel.privateHeader().id());
+ val = std::string(tmpValStr);
+ listStr += "\t\"" + val + "\": {\n";
+ // ASCII
+ if (pel.primarySRC())
+ {
+ val = pel.primarySRC().value()->asciiString();
+ listStr += "\t\t\"SRC\": \"" +
+ val.substr(0, val.find(0x20)) + "\",\n";
+ // Registry message
+ auto regVal = pel.primarySRC().value()->getErrorDetails(
+ registry, DetailLevel::message, true);
+ if (regVal)
+ {
+ val = regVal.value();
+ listStr += "\t\t\"Message\": \"" + val + "\",\n";
+ }
+ }
+ else
+ {
+ listStr += "\t\t\"SRC\": \"No SRC\",\n";
+ }
+ // platformid
+ sprintf(tmpValStr, "0x%X", pel.privateHeader().plid());
+ val = std::string(tmpValStr);
+ listStr += "\t\t\"PLID\": \"" + val + "\",\n";
+ // creatorid
+ sprintf(tmpValStr, "%c", pel.privateHeader().creatorID());
+ std::string creatorID(tmpValStr);
+ val = pv::creatorIDs.count(creatorID)
+ ? pv::creatorIDs.at(creatorID)
+ : "Unknown Creator ID";
+ listStr += "\t\t\"CreatorID\": \"" + val + "\",\n";
+ // subsytem
+ std::string subsystem = pv::getValue(
+ pel.userHeader().subsystem(), pel_values::subsystemValues);
+ listStr += "\t\t\"Subsystem\": \"" + subsystem + "\",\n";
+ // commit time
+ sprintf(tmpValStr, "%02X/%02X/%02X%02X %02X:%02X:%02X",
+ pel.privateHeader().commitTimestamp().month,
+ pel.privateHeader().commitTimestamp().day,
+ pel.privateHeader().commitTimestamp().yearMSB,
+ pel.privateHeader().commitTimestamp().yearLSB,
+ pel.privateHeader().commitTimestamp().hour,
+ pel.privateHeader().commitTimestamp().minutes,
+ pel.privateHeader().commitTimestamp().seconds);
+ val = std::string(tmpValStr);
+ listStr += "\t\t\"Commit Time\": \"" + val + "\",\n";
+ // severity
+ std::string severity = pv::getValue(pel.userHeader().severity(),
+ pel_values::severityValues);
+ listStr += "\t\t\"Sev\": \"" + severity + "\",\n ";
+ // compID
+ sprintf(tmpValStr, "0x%X",
+ pel.privateHeader().header().componentID);
+ val = std::string(tmpValStr);
+ listStr += "\t\t\"CompID\": \"" + val + "\",\n ";
+
+ found = listStr.rfind(",");
+ if (found != std::string::npos)
+ {
+ listStr.replace(found, 1, "");
+ listStr += "\t}, \n";
+ }
+ }
+ }
+ else
+ {
+ log<level::ERR>("Empty PEL file",
+ entry("FILENAME=%s", fileName.c_str()),
+ entry("ERROR=%s", "Empty PEL file"));
+ }
+ }
+ catch (std::exception& e)
+ {
+ log<level::ERR>("Hit exception while reading PEL File",
+ entry("FILENAME=%s", fileName.c_str()),
+ entry("ERROR=%s", e.what()));
+ }
+ return listStr;
+}
+/**
+ * @brief Print a list of PELs
+ */
+void printList(bool order, bool hidden)
+{
+ std::string listStr;
+ std::map<uint32_t, BCDTime> PELs;
+ std::size_t found;
+ listStr = "{\n";
+ for (auto it = fs::directory_iterator(EXTENSION_PERSIST_DIR "/pels/logs");
+ it != fs::directory_iterator(); ++it)
+ {
+ if (!fs::is_regular_file((*it).path()))
+ {
+ continue;
+ }
+ else
+ {
+ PELs.emplace(fileNameToPELId((*it).path().filename()),
+ fileNameToTimestamp((*it).path().filename()));
+ }
+ }
+ message::Registry registry(getMessageRegistryPath() /
+ message::registryFileName);
+ auto buildJSON = [&listStr, &hidden, &registry](const auto& i) {
+ listStr += genPELJSON(i, hidden, registry);
+ };
+ if (order)
+ {
+ std::for_each(PELs.rbegin(), PELs.rend(), buildJSON);
+ }
+ else
+ {
+ std::for_each(PELs.begin(), PELs.end(), buildJSON);
+ }
+
+ found = listStr.rfind(",");
+ if (found != std::string::npos)
+ {
+ listStr.replace(found, 1, "");
+ listStr += "\n}\n";
+ printf("%s", listStr.c_str());
+ }
+}
+
+static void exitWithError(const std::string& help, const char* err)
+{
+ std::cerr << "ERROR: " << err << std::endl << help << std::endl;
+ exit(-1);
+}
+
+int main(int argc, char** argv)
+{
+ CLI::App app{"OpenBMC PEL Tool"};
+ std::string fileName;
+ std::string idPEL;
+ bool listPEL = false;
+ bool listPELDescOrd = false;
+ bool listPELShowHidden = false;
+ app.add_option("-f,--file", fileName,
+ "Display a PEL using its Raw PEL file");
+ app.add_option("-i, --id", idPEL, "Display a PEL based on its ID");
+ app.add_flag("-l", listPEL, "List PELs");
+ app.add_flag("-r", listPELDescOrd, "Reverse order of output");
+ app.add_flag("-s", listPELShowHidden, "Show hidden PELs");
+ CLI11_PARSE(app, argc, argv);
+
+ if (!fileName.empty())
+ {
+ std::vector<uint8_t> data = getFileData(fileName);
+ if (!data.empty())
+ {
+ PEL pel{data};
+ pel.toJSON();
+ }
+ else
+ {
+ exitWithError(app.help("", CLI::AppFormatMode::All),
+ "Raw PEL file can't be read.");
+ }
+ }
+
+ else if (!idPEL.empty())
+ {
+ for (auto it =
+ fs::directory_iterator(EXTENSION_PERSIST_DIR "/pels/logs");
+ it != fs::directory_iterator(); ++it)
+ {
+ if (!fs::is_regular_file((*it).path()))
+ {
+ continue;
+ }
+ try
+ {
+ for (auto& c : idPEL)
+ c = toupper(c);
+ size_t found = idPEL.find("0X");
+ if (found == 0)
+ {
+ idPEL.erase(0, 2);
+ }
+ if (ends_with((*it).path(), idPEL))
+ {
+ std::vector<uint8_t> data = getFileData((*it).path());
+ if (!data.empty())
+ {
+ PEL pel{data};
+ if (pel.valid())
+ {
+ pel.toJSON();
+ }
+ else
+ {
+ log<level::ERR>(
+ "PEL File contains invalid PEL",
+ entry("FILENAME=%s", (*it).path().c_str()),
+ entry("ERROR=%s", "file contains invalid PEL"));
+ }
+ }
+ break;
+ }
+ }
+ catch (std::exception& e)
+ {
+ log<level::ERR>("Hit exception while reading PEL File",
+ entry("FILENAME=%s", (*it).path().c_str()),
+ entry("ERROR=%s", e.what()));
+ }
+ }
+ }
+ else if (listPEL)
+ {
+
+ printList(listPELDescOrd, listPELShowHidden);
+ }
+ else
+ {
+ exitWithError(app.help("", CLI::AppFormatMode::All),
+ "Raw PEL file path not specified.");
+ }
+ return 0;
+}
diff --git a/extensions/openpower-pels/user_data.cpp b/extensions/openpower-pels/user_data.cpp
new file mode 100644
index 0000000..70acbbb
--- /dev/null
+++ b/extensions/openpower-pels/user_data.cpp
@@ -0,0 +1,108 @@
+/**
+ * Copyright © 2019 IBM Corporation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#include "user_data.hpp"
+
+#include "json_utils.hpp"
+#include "pel_types.hpp"
+#include "user_data_formats.hpp"
+#ifdef PELTOOL
+#include "user_data_json.hpp"
+#endif
+
+#include <phosphor-logging/log.hpp>
+
+namespace openpower
+{
+namespace pels
+{
+
+using namespace phosphor::logging;
+
+void UserData::unflatten(Stream& stream)
+{
+ stream >> _header;
+
+ if (_header.size <= SectionHeader::flattenedSize())
+ {
+ throw std::out_of_range(
+ "UserData::unflatten: SectionHeader::size field too small");
+ }
+
+ size_t dataLength = _header.size - SectionHeader::flattenedSize();
+ _data.resize(dataLength);
+
+ stream >> _data;
+}
+
+void UserData::flatten(Stream& stream) const
+{
+ stream << _header << _data;
+}
+
+UserData::UserData(Stream& pel)
+{
+ try
+ {
+ unflatten(pel);
+ validate();
+ }
+ catch (const std::exception& e)
+ {
+ log<level::ERR>("Cannot unflatten user data",
+ entry("ERROR=%s", e.what()));
+ _valid = false;
+ }
+}
+
+UserData::UserData(uint16_t componentID, uint8_t subType, uint8_t version,
+ const std::vector<uint8_t>& data)
+{
+ _header.id = static_cast<uint16_t>(SectionID::userData);
+ _header.size = Section::flattenedSize() + data.size();
+ _header.version = version;
+ _header.subType = subType;
+ _header.componentID = componentID;
+
+ _data = data;
+
+ _valid = true;
+}
+
+void UserData::validate()
+{
+ if (header().id != static_cast<uint16_t>(SectionID::userData))
+ {
+ log<level::ERR>("Invalid user data section ID",
+ entry("ID=0x%X", header().id));
+ _valid = false;
+ }
+ else
+ {
+ _valid = true;
+ }
+}
+
+std::optional<std::string> UserData::getJSON() const
+{
+#ifdef PELTOOL
+ return user_data::getJSON(_header.componentID, _header.subType,
+ _header.version, _data);
+#endif
+ return std::nullopt;
+}
+
+} // namespace pels
+} // namespace openpower
diff --git a/extensions/openpower-pels/user_data.hpp b/extensions/openpower-pels/user_data.hpp
new file mode 100644
index 0000000..3594849
--- /dev/null
+++ b/extensions/openpower-pels/user_data.hpp
@@ -0,0 +1,113 @@
+#pragma once
+
+#include "section.hpp"
+#include "stream.hpp"
+
+namespace openpower
+{
+namespace pels
+{
+
+/**
+ * @class UserData
+ *
+ * This represents the User Data section in a PEL. It is free form data
+ * that the creator knows the contents of. The component ID, version,
+ * and sub-type fields in the section header are used to identify the
+ * format.
+ *
+ * The Section base class handles the section header structure that every
+ * PEL section has at offset zero.
+ */
+class UserData : public Section
+{
+ public:
+ UserData() = delete;
+ ~UserData() = default;
+ UserData(const UserData&) = default;
+ UserData& operator=(const UserData&) = default;
+ UserData(UserData&&) = default;
+ UserData& operator=(UserData&&) = default;
+
+ /**
+ * @brief Constructor
+ *
+ * Fills in this class's data fields from the stream.
+ *
+ * @param[in] pel - the PEL data stream
+ */
+ explicit UserData(Stream& pel);
+
+ /**
+ * @brief Constructor
+ *
+ * Create a valid UserData object with the passed in data.
+ *
+ * The component ID, subtype, and version are used to identify
+ * the data to know which parser to call.
+ *
+ * @param[in] componentID - Component ID of the creator
+ * @param[in] subType - The type of user data
+ * @param[in] version - The version of the data
+ */
+ UserData(uint16_t componentID, uint8_t subType, uint8_t version,
+ const std::vector<uint8_t>& data);
+
+ /**
+ * @brief Flatten the section into the stream
+ *
+ * @param[in] stream - The stream to write to
+ */
+ void flatten(Stream& stream) const override;
+
+ /**
+ * @brief Returns the size of this section when flattened into a PEL
+ *
+ * @return size_t - the size of the section
+ */
+ size_t flattenedSize()
+ {
+ return Section::flattenedSize() + _data.size();
+ }
+
+ /**
+ * @brief Returns the raw section data
+ *
+ * @return std::vector<uint8_t>&
+ */
+ const std::vector<uint8_t>& data() const
+ {
+ return _data;
+ }
+
+ /**
+ * @brief Get the section contents in JSON
+ *
+ * @return The JSON as a string if a parser was found,
+ * otherwise std::nullopt.
+ */
+ std::optional<std::string> getJSON() const override;
+
+ private:
+ /**
+ * @brief Fills in the object from the stream data
+ *
+ * @param[in] stream - The stream to read from
+ */
+ void unflatten(Stream& stream);
+
+ /**
+ * @brief Validates the section contents
+ *
+ * Updates _valid (in Section) with the results.
+ */
+ void validate() override;
+
+ /**
+ * @brief The section data
+ */
+ std::vector<uint8_t> _data;
+};
+
+} // namespace pels
+} // namespace openpower
diff --git a/extensions/openpower-pels/user_data_formats.hpp b/extensions/openpower-pels/user_data_formats.hpp
new file mode 100644
index 0000000..53cb5ee
--- /dev/null
+++ b/extensions/openpower-pels/user_data_formats.hpp
@@ -0,0 +1,19 @@
+#pragma once
+
+namespace openpower
+{
+namespace pels
+{
+
+enum class UserDataFormat
+{
+ json = 1
+};
+
+enum class UserDataFormatVersion
+{
+ json = 1
+};
+
+} // namespace pels
+} // namespace openpower
diff --git a/extensions/openpower-pels/user_data_json.cpp b/extensions/openpower-pels/user_data_json.cpp
new file mode 100644
index 0000000..a86ccb3
--- /dev/null
+++ b/extensions/openpower-pels/user_data_json.cpp
@@ -0,0 +1,153 @@
+/**
+ * Copyright © 2020 IBM Corporation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "user_data_json.hpp"
+
+#include "pel_types.hpp"
+#include "user_data_formats.hpp"
+
+#include <fifo_map.hpp>
+#include <iomanip>
+#include <nlohmann/json.hpp>
+#include <phosphor-logging/log.hpp>
+#include <sstream>
+
+namespace openpower::pels::user_data
+{
+
+using namespace phosphor::logging;
+
+// Use fifo_map as nlohmann::json's map. We are just ignoring the 'less'
+// compare. With this map the keys are kept in FIFO order.
+template <class K, class V, class dummy_compare, class A>
+using fifoMap = nlohmann::fifo_map<K, V, nlohmann::fifo_map_compare<K>, A>;
+using fifoJSON = nlohmann::basic_json<fifoMap>;
+
+/**
+ * @brief Returns a JSON string for use by PEL::printSectionInJSON().
+ *
+ * The returning string will contain a JSON object, but without
+ * the outer {}. If the input JSON isn't a JSON object (dict), then
+ * one will be created with the input added to a 'Data' key.
+ *
+ * @param[in] json - The JSON to convert to a string
+ *
+ * @return std::string - The JSON string
+ */
+std::string prettyJSON(uint16_t componentID, uint8_t subType, uint8_t version,
+ const fifoJSON& json)
+{
+ fifoJSON output;
+ output["Section Version"] = std::to_string(version);
+ output["Sub-section type"] = std::to_string(subType);
+
+ char value[10];
+ sprintf(value, "0x%04X", componentID);
+ output["Created by"] = std::string{value};
+
+ if (!json.is_object())
+ {
+ output["Data"] = json;
+ }
+ else
+ {
+ for (const auto& [key, value] : json.items())
+ {
+ output[key] = value;
+ }
+ }
+
+ // Let nlohmann do the pretty printing.
+ std::stringstream stream;
+ stream << std::setw(4) << output;
+
+ auto jsonString = stream.str();
+
+ // Now it looks like:
+ // {
+ // "Section Version": ...
+ // ...
+ // }
+
+ // Since PEL::printSectionInJSON() will supply the outer { }s,
+ // remove the existing ones.
+
+ // Replace the { and the following newline, and the } and its
+ // preceeding newline.
+ jsonString.erase(0, 2);
+
+ auto pos = jsonString.find_last_of('}');
+ jsonString.erase(pos - 1);
+
+ return jsonString;
+}
+
+/**
+ * @brief Convert to an appropriate JSON string as the data is one of
+ * the formats that we natively support.
+ *
+ * @param[in] componentID - The comp ID from the UserData section header
+ * @param[in] subType - The subtype from the UserData section header
+ * @param[in] version - The version from the UserData section header
+ * @param[in] data - The data itself
+ *
+ * @return std::optional<std::string> - The JSON string if it could be created,
+ * else std::nullopt.
+ */
+std::optional<std::string>
+ getBuiltinFormatJSON(uint16_t componentID, uint8_t subType, uint8_t version,
+ const std::vector<uint8_t>& data)
+{
+ switch (subType)
+ {
+ case static_cast<uint8_t>(UserDataFormat::json):
+ {
+ std::string jsonString{data.begin(), data.begin() + data.size()};
+
+ fifoJSON json = nlohmann::json::parse(jsonString);
+
+ return prettyJSON(componentID, subType, version, json);
+ }
+ default:
+ break;
+ }
+ return std::nullopt;
+}
+
+std::optional<std::string> getJSON(uint16_t componentID, uint8_t subType,
+ uint8_t version,
+ const std::vector<uint8_t>& data)
+{
+ try
+ {
+ switch (componentID)
+ {
+ case static_cast<uint16_t>(ComponentID::phosphorLogging):
+ return getBuiltinFormatJSON(componentID, subType, version,
+ data);
+ default:
+ break;
+ }
+ }
+ catch (std::exception& e)
+ {
+ log<level::ERR>("Failed parsing UserData", entry("ERROR=%s", e.what()));
+ }
+
+ return std::nullopt;
+}
+
+} // namespace openpower::pels::user_data
diff --git a/extensions/openpower-pels/user_data_json.hpp b/extensions/openpower-pels/user_data_json.hpp
new file mode 100644
index 0000000..64fac79
--- /dev/null
+++ b/extensions/openpower-pels/user_data_json.hpp
@@ -0,0 +1,25 @@
+#pragma once
+
+#include <optional>
+#include <string>
+#include <vector>
+
+namespace openpower::pels::user_data
+{
+
+/**
+ * @brief Returns the UserData contents as a formatted JSON string.
+ *
+ * @param[in] componentID - The comp ID from the UserData section header
+ * @param[in] subType - The subtype from the UserData section header
+ * @param[in] version - The version from the UserData section header
+ * @param[in] data - The section data
+ *
+ * @return std::optional<std::string> - The JSON string if it could be created,
+ * else std::nullopt.
+ */
+std::optional<std::string> getJSON(uint16_t componentID, uint8_t subType,
+ uint8_t version,
+ const std::vector<uint8_t>& data);
+
+} // namespace openpower::pels::user_data
diff --git a/extensions/openpower-pels/user_header.cpp b/extensions/openpower-pels/user_header.cpp
new file mode 100644
index 0000000..e29ffdf
--- /dev/null
+++ b/extensions/openpower-pels/user_header.cpp
@@ -0,0 +1,177 @@
+/**
+ * Copyright © 2019 IBM Corporation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#include "user_header.hpp"
+
+#include "json_utils.hpp"
+#include "pel_types.hpp"
+#include "pel_values.hpp"
+#include "severity.hpp"
+
+#include <iostream>
+#include <phosphor-logging/log.hpp>
+
+namespace openpower
+{
+namespace pels
+{
+
+namespace pv = openpower::pels::pel_values;
+using namespace phosphor::logging;
+
+void UserHeader::unflatten(Stream& stream)
+{
+ stream >> _header >> _eventSubsystem >> _eventScope >> _eventSeverity >>
+ _eventType >> _reserved4Byte1 >> _problemDomain >> _problemVector >>
+ _actionFlags >> _states;
+}
+
+void UserHeader::flatten(Stream& stream) const
+{
+ stream << _header << _eventSubsystem << _eventScope << _eventSeverity
+ << _eventType << _reserved4Byte1 << _problemDomain << _problemVector
+ << _actionFlags << _states;
+}
+
+UserHeader::UserHeader(const message::Entry& entry,
+ phosphor::logging::Entry::Level severity)
+{
+ _header.id = static_cast<uint16_t>(SectionID::userHeader);
+ _header.size = UserHeader::flattenedSize();
+ _header.version = userHeaderVersion;
+ _header.subType = 0;
+ _header.componentID = static_cast<uint16_t>(ComponentID::phosphorLogging);
+
+ _eventSubsystem = entry.subsystem;
+
+ _eventScope = entry.eventScope.value_or(
+ static_cast<uint8_t>(EventScope::entirePlatform));
+
+ // Get the severity from the registry if it's there, otherwise get it
+ // from the OpenBMC event log severity value.
+ _eventSeverity =
+ entry.severity.value_or(convertOBMCSeverityToPEL(severity));
+
+ // TODO: ibm-dev/dev/#1144 Handle manufacturing sev & action flags
+
+ if (entry.eventType)
+ {
+ _eventType = *entry.eventType;
+ }
+ else
+ {
+ // There are different default event types for info errors
+ // vs non info ones.
+ auto sevType = static_cast<SeverityType>(_eventSeverity & 0xF0);
+
+ _eventType = (sevType == SeverityType::nonError)
+ ? static_cast<uint8_t>(EventType::miscInformational)
+ : static_cast<uint8_t>(EventType::notApplicable);
+ }
+
+ _reserved4Byte1 = 0;
+
+ // No uses for problem domain or vector
+ _problemDomain = 0;
+ _problemVector = 0;
+
+ // These will be cleaned up later in pel_rules::check()
+ _actionFlags = entry.actionFlags.value_or(0);
+
+ _states = 0;
+
+ _valid = true;
+}
+
+UserHeader::UserHeader(Stream& pel)
+{
+ try
+ {
+ unflatten(pel);
+ validate();
+ }
+ catch (const std::exception& e)
+ {
+ log<level::ERR>("Cannot unflatten user header",
+ entry("ERROR=%s", e.what()));
+ _valid = false;
+ }
+}
+
+void UserHeader::validate()
+{
+ bool failed = false;
+ if (header().id != static_cast<uint16_t>(SectionID::userHeader))
+ {
+ log<level::ERR>("Invalid user header section ID",
+ entry("ID=0x%X", header().id));
+ failed = true;
+ }
+
+ if (header().version != userHeaderVersion)
+ {
+ log<level::ERR>("Invalid user header version",
+ entry("VERSION=0x%X", header().version));
+ failed = true;
+ }
+
+ _valid = (failed) ? false : true;
+}
+
+std::optional<std::string> UserHeader::getJSON() const
+{
+ std::string severity;
+ std::string subsystem;
+ std::string eventScope;
+ std::string eventType;
+ std::vector<std::string> actionFlags;
+ severity = pv::getValue(_eventSeverity, pel_values::severityValues);
+ subsystem = pv::getValue(_eventSubsystem, pel_values::subsystemValues);
+ eventScope = pv::getValue(_eventScope, pel_values::eventScopeValues);
+ eventType = pv::getValue(_eventType, pel_values::eventTypeValues);
+ actionFlags =
+ pv::getValuesBitwise(_actionFlags, pel_values::actionFlagsValues);
+
+ std::string hostState{"Invalid"};
+ auto iter = pv::transmissionStates.find(
+ static_cast<TransmissionState>(hostTransmissionState()));
+ if (iter != pv::transmissionStates.end())
+ {
+ hostState = iter->second;
+ }
+
+ char tmpUhVal[8];
+ sprintf(tmpUhVal, "%d", userHeaderVersion);
+ std::string uhVerStr(tmpUhVal);
+ sprintf(tmpUhVal, "0x%X", _header.componentID);
+ std::string uhCbStr(tmpUhVal);
+ sprintf(tmpUhVal, "%d", _header.subType);
+ std::string uhStStr(tmpUhVal);
+
+ std::string uh;
+ jsonInsert(uh, "Section Version", uhVerStr, 1);
+ jsonInsert(uh, "Sub-section type", uhStStr, 1);
+ jsonInsert(uh, "Log Committed by", uhCbStr, 1);
+ jsonInsert(uh, "Subsystem", subsystem, 1);
+ jsonInsert(uh, "Event Scope", eventScope, 1);
+ jsonInsert(uh, "Event Severity", severity, 1);
+ jsonInsert(uh, "Event Type", eventType, 1);
+ jsonInsertArray(uh, "Action Flags", actionFlags, 1);
+ jsonInsert(uh, "Host Transmission", hostState, 1);
+ uh.erase(uh.size() - 2);
+ return uh;
+}
+} // namespace pels
+} // namespace openpower
diff --git a/extensions/openpower-pels/user_header.hpp b/extensions/openpower-pels/user_header.hpp
new file mode 100644
index 0000000..f1fc66d
--- /dev/null
+++ b/extensions/openpower-pels/user_header.hpp
@@ -0,0 +1,288 @@
+#pragma once
+
+#include "elog_entry.hpp"
+#include "pel_values.hpp"
+#include "registry.hpp"
+#include "section.hpp"
+#include "stream.hpp"
+
+namespace openpower
+{
+namespace pels
+{
+
+static constexpr uint8_t userHeaderVersion = 0x01;
+
+/**
+ * @class UserHeader
+ *
+ * This represents the User Header section in a PEL. It is required,
+ * and it is always the second section.
+ *
+ * The Section base class handles the section header structure that every
+ * PEL section has at offset zero.
+ *
+ * The fields in this class directly correspond to the order and sizes of
+ * the fields in the section.
+ */
+class UserHeader : public Section
+{
+ public:
+ UserHeader() = delete;
+ ~UserHeader() = default;
+ UserHeader(const UserHeader&) = default;
+ UserHeader& operator=(const UserHeader&) = default;
+ UserHeader(UserHeader&&) = default;
+ UserHeader& operator=(UserHeader&&) = default;
+
+ /**
+ * @brief Constructor
+ *
+ * Creates a valid UserHeader with the passed in data.
+ *
+ * @param[in] entry - The message registry entry for this error
+ * @param[in] severity - The OpenBMC event log severity for this error
+ */
+ UserHeader(const message::Entry& entry,
+ phosphor::logging::Entry::Level severity);
+
+ /**
+ * @brief Constructor
+ *
+ * Fills in this class's data fields from the stream.
+ *
+ * @param[in] pel - the PEL data stream
+ */
+ explicit UserHeader(Stream& pel);
+
+ /**
+ * @brief Flatten the section into the stream
+ *
+ * @param[in] stream - The stream to write to
+ */
+ void flatten(Stream& stream) const override;
+
+ /**
+ * @brief Returns the subsystem field.
+ *
+ * @return uint8_t - the subsystem
+ */
+ uint8_t subsystem() const
+ {
+ return _eventSubsystem;
+ }
+
+ /**
+ * @brief Returns the event scope field.
+ *
+ * @return uint8_t - the event scope
+ */
+ uint8_t scope() const
+ {
+ return _eventScope;
+ }
+
+ /**
+ * @brief Returns the severity field.
+ *
+ * @return uint8_t - the severity
+ */
+ uint8_t severity() const
+ {
+ return _eventSeverity;
+ }
+
+ /**
+ * @brief Returns the event type field.
+ *
+ * @return uint8_t - the event type
+ */
+ uint8_t eventType() const
+ {
+ return _eventType;
+ }
+
+ /**
+ * @brief Set the event type field
+ *
+ * @param[in] type - the new event type
+ */
+ void setEventType(uint8_t type)
+ {
+ _eventType = type;
+ }
+
+ /**
+ * @brief Returns the problem domain field.
+ *
+ * @return uint8_t - the problem domain
+ */
+ uint8_t problemDomain() const
+ {
+ return _problemDomain;
+ }
+
+ /**
+ * @brief Returns the problem vector field.
+ *
+ * @return uint8_t - the problem vector
+ */
+ uint8_t problemVector() const
+ {
+ return _problemVector;
+ }
+
+ /**
+ * @brief Returns the action flags field.
+ *
+ * @return uint16_t - the action flags
+ */
+ uint16_t actionFlags() const
+ {
+ return _actionFlags;
+ }
+
+ /**
+ * @brief Sets the action flags field
+ *
+ * @param[in] flags - the new action flags
+ */
+ void setActionFlags(uint16_t flags)
+ {
+ _actionFlags = flags;
+ }
+
+ /**
+ * @brief Returns the host transmission state
+ *
+ * @return uint8_t - the host transmission state
+ */
+ uint8_t hostTransmissionState() const
+ {
+ return _states & 0xFF;
+ }
+
+ /**
+ * @brief Sets the host transmission state
+ *
+ * @param[in] state - the new state
+ */
+ void setHostTransmissionState(uint8_t state)
+ {
+ _states &= 0xFFFFFF00;
+ _states |= state;
+ }
+
+ /**
+ * @brief Returns the HMC transmission state
+ *
+ * (HMC = Hardware Management Console)
+ *
+ * @return uint8_t - the HMC transmission state
+ */
+ uint8_t hmcTransmissionState() const
+ {
+ return (_states & 0x0000FF00) >> 8;
+ }
+
+ /**
+ * @brief Sets the HMC transmission state
+ *
+ * @param[in] state - the new state
+ */
+ void setHMCTransmissionState(uint8_t state)
+ {
+ uint32_t newState = state << 8;
+ _states &= 0xFFFF00FF;
+ _states |= newState;
+ }
+
+ /**
+ * @brief Returns the size of this section when flattened into a PEL
+ *
+ * @return size_t - the size of the section
+ */
+ static constexpr size_t flattenedSize()
+ {
+ return Section::flattenedSize() + sizeof(_eventSubsystem) +
+ sizeof(_eventScope) + sizeof(_eventSeverity) +
+ sizeof(_eventType) + sizeof(_reserved4Byte1) +
+ sizeof(_problemDomain) + sizeof(_problemVector) +
+ sizeof(_actionFlags) + sizeof(_states);
+ }
+
+ /**
+ * @brief Get section in JSON.
+ * @return std::optional<std::string> -User header section's JSON
+ */
+ std::optional<std::string> getJSON() const override;
+
+ private:
+ /**
+ * @brief Fills in the object from the stream data
+ *
+ * @param[in] stream - The stream to read from
+ */
+ void unflatten(Stream& stream);
+
+ /**
+ * @brief Validates the section contents
+ *
+ * Updates _valid (in Section) with the results.
+ */
+ void validate() override;
+
+ /**
+ * @brief The subsystem associated with the event.
+ */
+ uint8_t _eventSubsystem;
+
+ /**
+ * @brief The event scope field.
+ */
+ uint8_t _eventScope;
+
+ /**
+ * @brief The event severity.
+ */
+ uint8_t _eventSeverity;
+
+ /**
+ * @brief The event type.
+ */
+ uint8_t _eventType;
+
+ /**
+ * @brief A reserved word placeholder
+ */
+ uint32_t _reserved4Byte1;
+
+ /**
+ * @brief The problem domain field.
+ */
+ uint8_t _problemDomain;
+
+ /**
+ * @brief The problem vector field.
+ */
+ uint8_t _problemVector;
+
+ /**
+ * @brief The action flags field.
+ */
+ uint16_t _actionFlags;
+
+ /**
+ * @brief The second reserved word that we are
+ * using for storing state information.
+ *
+ * 0x0000AABB
+ * Where:
+ * 0xAA = HMC transmission state
+ * 0xBB = Host transmission state
+ */
+ uint32_t _states;
+};
+
+} // namespace pels
+} // namespace openpower
diff --git a/log_manager.cpp b/log_manager.cpp
index 3dc14d7..569413a 100644
--- a/log_manager.cpp
+++ b/log_manager.cpp
@@ -5,6 +5,7 @@
#include "elog_entry.hpp"
#include "elog_meta.hpp"
#include "elog_serialize.hpp"
+#include "extensions.hpp"
#include <poll.h>
#include <sys/inotify.h>
@@ -12,6 +13,7 @@
#include <systemd/sd-journal.h>
#include <unistd.h>
+#include <cassert>
#include <chrono>
#include <cstdio>
#include <cstring>
@@ -77,20 +79,6 @@ void Manager::commitWithLvl(uint64_t transactionId, std::string errMsg,
void Manager::_commit(uint64_t transactionId, std::string&& errMsg,
Entry::Level errLvl)
{
- if (errLvl < Entry::sevLowerLimit)
- {
- if (realErrors.size() >= ERROR_CAP)
- {
- erase(realErrors.front());
- }
- }
- else
- {
- if (infoErrors.size() >= ERROR_INFO_CAP)
- {
- erase(infoErrors.front());
- }
- }
constexpr const auto transactionIdVar = "TRANSACTION_ID";
// Length of 'TRANSACTION_ID' string.
constexpr const auto transactionIdVarSize = std::strlen(transactionIdVar);
@@ -193,7 +181,30 @@ void Manager::_commit(uint64_t transactionId, std::string&& errMsg,
sd_journal_close(j);
- // Create error Entry dbus object
+ createEntry(errMsg, errLvl, additionalData);
+}
+
+void Manager::createEntry(std::string errMsg, Entry::Level errLvl,
+ std::vector<std::string> additionalData)
+{
+ if (!Extensions::disableDefaultLogCaps())
+ {
+ if (errLvl < Entry::sevLowerLimit)
+ {
+ if (realErrors.size() >= ERROR_CAP)
+ {
+ erase(realErrors.front());
+ }
+ }
+ else
+ {
+ if (infoErrors.size() >= ERROR_INFO_CAP)
+ {
+ erase(infoErrors.front());
+ }
+ }
+ }
+
entryId++;
if (errLvl >= Entry::sevLowerLimit)
{
@@ -217,9 +228,39 @@ void Manager::_commit(uint64_t transactionId, std::string&& errMsg,
std::move(additionalData),
std::move(objects), fwVersion, *this);
serialize(*e);
+
+ doExtensionLogCreate(*e);
+
entries.insert(std::make_pair(entryId, std::move(e)));
}
+void Manager::doExtensionLogCreate(const Entry& entry)
+{
+ // Make the association <endpointpath>/<endpointtype> paths
+ std::vector<std::string> assocs;
+ for (const auto& [forwardType, reverseType, endpoint] :
+ entry.associations())
+ {
+ std::string e{endpoint};
+ e += '/' + reverseType;
+ assocs.push_back(e);
+ }
+
+ for (auto& create : Extensions::getCreateFunctions())
+ {
+ try
+ {
+ create(entry.message(), entry.id(), entry.timestamp(),
+ entry.severity(), entry.additionalData(), assocs);
+ }
+ catch (std::exception& e)
+ {
+ log<level::ERR>("An extension's create function threw an exception",
+ phosphor::logging::entry("ERROR=%s", e.what()));
+ }
+ }
+}
+
void Manager::processMetadata(const std::string& errorName,
const std::vector<std::string>& additionalData,
AssociationList& objects) const
@@ -246,6 +287,27 @@ void Manager::erase(uint32_t entryId)
auto entryFound = entries.find(entryId);
if (entries.end() != entryFound)
{
+ for (auto& func : Extensions::getDeleteProhibitedFunctions())
+ {
+ try
+ {
+ bool prohibited = false;
+ func(entryId, prohibited);
+ if (prohibited)
+ {
+ // Future work remains to throw an error here.
+ return;
+ }
+ }
+ catch (std::exception& e)
+ {
+ log<level::ERR>(
+ "An extension's deleteProhibited function threw "
+ "an exception",
+ entry("ERROR=%s", e.what()));
+ }
+ }
+
// Delete the persistent representation of this error.
fs::path errorPath(ERRLOG_PERSIST_PATH);
errorPath /= std::to_string(entryId);
@@ -267,6 +329,20 @@ void Manager::erase(uint32_t entryId)
removeId(realErrors, entryId);
}
entries.erase(entryFound);
+
+ for (auto& remove : Extensions::getDeleteFunctions())
+ {
+ try
+ {
+ remove(entryId);
+ }
+ catch (std::exception& e)
+ {
+ log<level::ERR>("An extension's delete function threw an "
+ "exception",
+ entry("ERROR=%s", e.what()));
+ }
+ }
}
else
{
@@ -341,7 +417,18 @@ void Manager::journalSync()
duration_cast<microseconds>(steady_clock::now().time_since_epoch())
.count();
- constexpr auto maxRetry = 2;
+ // Each time an error log is committed, a request to sync the journal
+ // must occur and block that error log commit until it completes. A 5sec
+ // block is done to allow sufficient time for the journal to be synced.
+ //
+ // Number of loop iterations = 3 for the following reasons:
+ // Iteration #1: Requests a journal sync by killing the journald service.
+ // Iteration #2: Setup an inotify watch to monitor the synced file that
+ // journald updates with the timestamp the last time the
+ // journal was flushed.
+ // Iteration #3: Poll to wait until inotify reports an event which blocks
+ // the error log from being commited until the sync completes.
+ constexpr auto maxRetry = 3;
for (int i = 0; i < maxRetry; i++)
{
// Read timestamp from synced file
@@ -367,7 +454,7 @@ void Manager::journalSync()
auto timestamp = std::stoll(timestampStr);
if (timestamp >= start)
{
- return;
+ break;
}
}
@@ -386,7 +473,7 @@ void Manager::journalSync()
if (method.is_method_error())
{
log<level::ERR>("Failed to kill journal service");
- return;
+ break;
}
continue;
}
@@ -486,6 +573,16 @@ std::string Manager::readFWVersion()
return version;
}
+void Manager::create(const std::string& message, Entry::Level severity,
+ const std::map<std::string, std::string>& additionalData)
+{
+ // Convert the map into a vector of "key=value" strings
+ std::vector<std::string> ad;
+ metadata::associations::combine(additionalData, ad);
+
+ createEntry(message, severity, ad);
+}
+
} // namespace internal
} // namespace logging
} // namespace phosphor
diff --git a/log_manager.hpp b/log_manager.hpp
index e6714af..c5ae081 100644
--- a/log_manager.hpp
+++ b/log_manager.hpp
@@ -2,6 +2,8 @@
#include "elog_entry.hpp"
#include "xyz/openbmc_project/Collection/DeleteAll/server.hpp"
+#include "xyz/openbmc_project/Logging/Create/server.hpp"
+#include "xyz/openbmc_project/Logging/Entry/server.hpp"
#include "xyz/openbmc_project/Logging/Internal/Manager/server.hpp"
#include <list>
@@ -16,14 +18,14 @@ namespace logging
extern const std::map<std::string, std::vector<std::string>> g_errMetaMap;
extern const std::map<std::string, level> g_errLevelMap;
-using DeleteAllIface = sdbusplus::server::object::object<
- sdbusplus::xyz::openbmc_project::Collection::server::DeleteAll>;
+using CreateIface = sdbusplus::xyz::openbmc_project::Logging::server::Create;
+using DeleteAllIface =
+ sdbusplus::xyz::openbmc_project::Collection::server::DeleteAll;
namespace details
{
-
-template <typename T>
-using ServerObject = typename sdbusplus::server::object::object<T>;
+template <typename... T>
+using ServerObject = typename sdbusplus::server::object::object<T...>;
using ManagerIface =
sdbusplus::xyz::openbmc_project::Logging::Internal::server::Manager;
@@ -119,6 +121,26 @@ class Manager : public details::ServerObject<details::ManagerIface>
*/
int getInfoErrSize();
+ sdbusplus::bus::bus& getBus()
+ {
+ return busLog;
+ }
+
+ /** @brief Creates an event log
+ *
+ * This is an alternative to the _commit() API. It doesn't use
+ * the journal to look up event log metadata like _commit does.
+ *
+ * @param[in] errMsg - The error exception message associated with the
+ * error log to be committed.
+ * @param[in] severity - level of the error
+ * @param[in] additionalData - The AdditionalData property for the error
+ */
+ void create(
+ const std::string& message,
+ sdbusplus::xyz::openbmc_project::Logging::server::Entry::Level severity,
+ const std::map<std::string, std::string>& additionalData);
+
private:
/*
* @fn _commit()
@@ -154,6 +176,24 @@ class Manager : public details::ServerObject<details::ManagerIface>
*/
static std::string readFWVersion();
+ /** @brief Call any create() functions provided by any extensions.
+ * This is called right after an event log is created to allow
+ * extensions to create their own log based on this one.
+ *
+ * @param[in] entry - the new event log entry
+ */
+ void doExtensionLogCreate(const Entry& entry);
+
+ /** @brief Common wrapper for creating an Entry object
+ *
+ * @param[in] errMsg - The error exception message associated with the
+ * error log to be committed.
+ * @param[in] errLvl - level of the error
+ * @param[in] additionalData - The AdditionalData property for the error
+ */
+ void createEntry(std::string errMsg, Entry::Level errLvl,
+ std::vector<std::string> additionalData);
+
/** @brief Persistent sdbusplus DBus bus connection. */
sdbusplus::bus::bus& busLog;
@@ -176,11 +216,13 @@ class Manager : public details::ServerObject<details::ManagerIface>
} // namespace internal
/** @class Manager
- * @brief Implementation for delete all error log entries.
+ * @brief Implementation for deleting all error log entries and
+ * creating new logs.
* @details A concrete implementation for the
- * xyz.openbmc_project.Collection.DeleteAll
+ * xyz.openbmc_project.Collection.DeleteAll and
+ * xyz.openbmc_project.Logging.Create interfaces.
*/
-class Manager : public DeleteAllIface
+class Manager : public details::ServerObject<DeleteAllIface, CreateIface>
{
public:
Manager() = delete;
@@ -199,7 +241,8 @@ class Manager : public DeleteAllIface
*/
Manager(sdbusplus::bus::bus& bus, const std::string& path,
internal::Manager& manager) :
- DeleteAllIface(bus, path.c_str(), true),
+ details::ServerObject<DeleteAllIface, CreateIface>(bus, path.c_str(),
+ true),
manager(manager){};
/** @brief Delete all d-bus objects.
@@ -209,6 +252,21 @@ class Manager : public DeleteAllIface
manager.eraseAll();
}
+ /** @brief D-Bus method call implementation to create an event log.
+ *
+ * @param[in] errMsg - The error exception message associated with the
+ * error log to be committed.
+ * @param[in] severity - Level of the error
+ * @param[in] additionalData - The AdditionalData property for the error
+ */
+ void create(
+ std::string message,
+ sdbusplus::xyz::openbmc_project::Logging::server::Entry::Level severity,
+ std::map<std::string, std::string> additionalData) override
+ {
+ manager.create(message, severity, additionalData);
+ }
+
private:
/** @brief This is a reference to manager object */
internal::Manager& manager;
diff --git a/log_manager_main.cpp b/log_manager_main.cpp
index 48a7dee..42eb982 100644
--- a/log_manager_main.cpp
+++ b/log_manager_main.cpp
@@ -1,14 +1,18 @@
#include "config.h"
+#include "extensions.hpp"
#include "log_manager.hpp"
#include <experimental/filesystem>
#include <sdbusplus/bus.hpp>
#include <sdbusplus/server/manager.hpp>
+#include <sdeventplus/event.hpp>
int main(int argc, char* argv[])
{
auto bus = sdbusplus::bus::new_default();
+ auto event = sdeventplus::Event::get_default();
+ bus.attach_event(event.get(), SD_EVENT_PRIORITY_NORMAL);
// Add sdbusplus ObjectManager for the 'root' path of the logging manager.
sdbusplus::server::manager::manager objManager(bus, OBJ_LOGGING);
@@ -25,11 +29,19 @@ int main(int argc, char* argv[])
bus.request_name(BUSNAME_LOGGING);
- while (true)
+ for (auto& startup : phosphor::logging::Extensions::getStartupFunctions())
{
- bus.process_discard();
- bus.wait();
+ try
+ {
+ startup(iMgr);
+ }
+ catch (std::exception& e)
+ {
+ phosphor::logging::log<phosphor::logging::level::ERR>(
+ "An extension's startup function threw an exception",
+ phosphor::logging::entry("ERROR=%s", e.what()));
+ }
}
- return 0;
+ return event.loop();
}
diff --git a/org.openbmc.Associations.cpp b/org.openbmc.Associations.cpp
deleted file mode 100644
index 25d79fd..0000000
--- a/org.openbmc.Associations.cpp
+++ /dev/null
@@ -1,160 +0,0 @@
-#include <algorithm>
-#include <org/openbmc/Associations/server.hpp>
-#include <sdbusplus/exception.hpp>
-#include <sdbusplus/server.hpp>
-
-namespace sdbusplus
-{
-namespace org
-{
-namespace openbmc
-{
-namespace server
-{
-
-Associations::Associations(bus::bus& bus, const char* path) :
- _org_openbmc_Associations_interface(bus, path, _interface, _vtable, this)
-{
-}
-
-Associations::Associations(
- bus::bus& bus, const char* path,
- const std::map<std::string, PropertiesVariant>& vals) :
- Associations(bus, path)
-{
- for (const auto& v : vals)
- {
- setPropertyByName(v.first, v.second);
- }
-}
-
-auto Associations::associations() const
- -> std::vector<std::tuple<std::string, std::string, std::string>>
-{
- return _associations;
-}
-
-int Associations::_callback_get_associations(sd_bus* bus, const char* path,
- const char* interface,
- const char* property,
- sd_bus_message* reply,
- void* context, sd_bus_error* error)
-{
- using sdbusplus::server::binding::details::convertForMessage;
-
- try
- {
- auto m = message::message(reply);
-#ifndef DISABLE_TRANSACTION
- {
- auto tbus = m.get_bus();
- sdbusplus::server::transaction::Transaction t(tbus, m);
- sdbusplus::server::transaction::set_id(
- std::hash<sdbusplus::server::transaction::Transaction>{}(t));
- }
-#endif
-
- auto o = static_cast<Associations*>(context);
- m.append(convertForMessage(o->associations()));
- }
- catch (sdbusplus::internal_exception_t& e)
- {
- sd_bus_error_set_const(error, e.name(), e.description());
- return -EINVAL;
- }
-
- return true;
-}
-
-auto Associations::associations(
- std::vector<std::tuple<std::string, std::string, std::string>> value)
- -> std::vector<std::tuple<std::string, std::string, std::string>>
-{
- if (_associations != value)
- {
- _associations = value;
- _org_openbmc_Associations_interface.property_changed("associations");
- }
-
- return _associations;
-}
-
-int Associations::_callback_set_associations(sd_bus* bus, const char* path,
- const char* interface,
- const char* property,
- sd_bus_message* value,
- void* context, sd_bus_error* error)
-{
- try
- {
- auto m = message::message(value);
-#ifndef DISABLE_TRANSACTION
- {
- auto tbus = m.get_bus();
- sdbusplus::server::transaction::Transaction t(tbus, m);
- sdbusplus::server::transaction::set_id(
- std::hash<sdbusplus::server::transaction::Transaction>{}(t));
- }
-#endif
-
- auto o = static_cast<Associations*>(context);
-
- std::vector<std::tuple<std::string, std::string, std::string>> v{};
- m.read(v);
- o->associations(v);
- }
- catch (sdbusplus::internal_exception_t& e)
- {
- sd_bus_error_set_const(error, e.name(), e.description());
- return -EINVAL;
- }
-
- return true;
-}
-
-namespace details
-{
-namespace Associations
-{
-static const auto _property_associations = utility::tuple_to_array(
- message::types::type_id<
- std::vector<std::tuple<std::string, std::string, std::string>>>());
-}
-} // namespace details
-
-void Associations::setPropertyByName(const std::string& name,
- const PropertiesVariant& val)
-{
- if (name == "associations")
- {
- auto& v = message::variant_ns::get<
- std::vector<std::tuple<std::string, std::string, std::string>>>(
- val);
- associations(v);
- return;
- }
-}
-
-auto Associations::getPropertyByName(const std::string& name)
- -> PropertiesVariant
-{
- if (name == "associations")
- {
- return associations();
- }
-
- return PropertiesVariant();
-}
-
-const vtable::vtable_t Associations::_vtable[] = {
- vtable::start(),
- vtable::property("associations",
- details::Associations::_property_associations.data(),
- _callback_get_associations, _callback_set_associations,
- vtable::property_::emits_change),
- vtable::end()};
-
-} // namespace server
-} // namespace openbmc
-} // namespace org
-} // namespace sdbusplus
diff --git a/org/openbmc/Associations.interface.yaml b/org/openbmc/Associations.interface.yaml
deleted file mode 100644
index 9be60ce..0000000
--- a/org/openbmc/Associations.interface.yaml
+++ /dev/null
@@ -1,13 +0,0 @@
-description: >
- Implement to delegate org.openbmc.Association interface management
- responsibilities to another application.
-properties:
- - name: associations
- type: array[struct[string,string,string]]
- description: >
- An array of forward, reverse, endpoint tuples where:
- forward - The type of the association.
- reverse - The type of the association to create for the endpoint.
- endpoint - The association endpoint.
-
-# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4
diff --git a/org/openbmc/Associations/server.hpp b/org/openbmc/Associations/server.hpp
deleted file mode 100644
index fb3ff7e..0000000
--- a/org/openbmc/Associations/server.hpp
+++ /dev/null
@@ -1,97 +0,0 @@
-#pragma once
-#include <systemd/sd-bus.h>
-
-#include <sdbusplus/server.hpp>
-#include <tuple>
-
-namespace sdbusplus
-{
-namespace org
-{
-namespace openbmc
-{
-namespace server
-{
-
-class Associations
-{
- public:
- /* Define all of the basic class operations:
- * Not allowed:
- * - Default constructor to avoid nullptrs.
- * - Copy operations due to internal unique_ptr.
- * - Move operations due to 'this' being registered as the
- * 'context' with sdbus.
- * Allowed:
- * - Destructor.
- */
- Associations() = delete;
- Associations(const Associations&) = delete;
- Associations& operator=(const Associations&) = delete;
- Associations(Associations&&) = delete;
- Associations& operator=(Associations&&) = delete;
- virtual ~Associations() = default;
-
- /** @brief Constructor to put object onto bus at a dbus path.
- * @param[in] bus - Bus to attach to.
- * @param[in] path - Path to attach at.
- */
- Associations(bus::bus& bus, const char* path);
-
- using PropertiesVariant = sdbusplus::message::variant<
- std::vector<std::tuple<std::string, std::string, std::string>>>;
-
- /** @brief Constructor to initialize the object from a map of
- * properties.
- *
- * @param[in] bus - Bus to attach to.
- * @param[in] path - Path to attach at.
- * @param[in] vals - Map of property name to value for initialization.
- */
- Associations(bus::bus& bus, const char* path,
- const std::map<std::string, PropertiesVariant>& vals);
-
- /** Get value of associations */
- virtual std::vector<std::tuple<std::string, std::string, std::string>>
- associations() const;
- /** Set value of associations */
- virtual std::vector<std::tuple<std::string, std::string, std::string>>
- associations(
- std::vector<std::tuple<std::string, std::string, std::string>>
- value);
-
- /** @brief Sets a property by name.
- * @param[in] name - A string representation of the property name.
- * @param[in] val - A variant containing the value to set.
- */
- void setPropertyByName(const std::string& name,
- const PropertiesVariant& val);
-
- /** @brief Gets a property by name.
- * @param[in] name - A string representation of the property name.
- * @return - A variant containing the value of the property.
- */
- PropertiesVariant getPropertyByName(const std::string& name);
-
- private:
- /** @brief sd-bus callback for get-property 'associations' */
- static int _callback_get_associations(sd_bus*, const char*, const char*,
- const char*, sd_bus_message*, void*,
- sd_bus_error*);
- /** @brief sd-bus callback for set-property 'associations' */
- static int _callback_set_associations(sd_bus*, const char*, const char*,
- const char*, sd_bus_message*, void*,
- sd_bus_error*);
-
- static constexpr auto _interface = "org.openbmc.Associations";
- static const vtable::vtable_t _vtable[];
- sdbusplus::server::interface::interface _org_openbmc_Associations_interface;
-
- std::vector<std::tuple<std::string, std::string, std::string>>
- _associations{};
-};
-
-} // namespace server
-} // namespace openbmc
-} // namespace org
-} // namespace sdbusplus
diff --git a/phosphor-logging/elog.hpp b/phosphor-logging/elog.hpp
index 5aace07..365080c 100644
--- a/phosphor-logging/elog.hpp
+++ b/phosphor-logging/elog.hpp
@@ -147,7 +147,7 @@ void commit(Entry::Level level)
* @param[in] i_args - Metadata fields to be added to the journal entry
*/
template <typename T, typename... Args>
-void elog(Args... i_args)
+[[noreturn]] void elog(Args... i_args)
{
// Validate if the exception is derived from sdbusplus::exception.
static_assert(std::is_base_of<sdbusplus::exception::exception, T>::value,
diff --git a/phosphor-logging/log.hpp b/phosphor-logging/log.hpp
index 24fa423..14c0faa 100644
--- a/phosphor-logging/log.hpp
+++ b/phosphor-logging/log.hpp
@@ -44,35 +44,6 @@ enum class level
DEBUG = LOG_DEBUG,
};
-/** @fn log()
- * @brief Log message to systemd journal
- * @tparam L - Priority level
- * @param[in] msg - Message to be logged in C-string format
- * @param[in] entry - Metadata fields to be added to the message
- * @details Usage: log<level::XX>(const char*, entry(*format), entry()...);
- * @example log<level::DEBUG>(
- * "Simple Example");
- * char msg_str[] = "File not found";
- * log<level::DEBUG>(
- * msg_str,
- * entry("MY_METADATA=%s_%x, name, number));
- */
-template <level L, typename Msg, typename... Entry>
-void log(Msg msg, Entry... entry);
-
-/** @fn entry()
- * @brief Pack each format string entry as a tuple to be able to validate
- * the string and parameters when multiple entries are passed to be logged.
- * @tparam Arg - Types of first argument
- * @tparam Args - Types of remaining arguments
- * @param[in] arg - First metadata string of form VAR=value where
- * VAR is the variable name in uppercase and
- * value is of any size and format
- * @param[in] args - Remaining metadata strings
- */
-template <typename Arg, typename... Args>
-constexpr auto entry(Arg&& arg, Args&&... args);
-
namespace details
{
@@ -120,24 +91,16 @@ void log(T&& e)
} // namespace details
-template <level L, typename Msg, typename... Entry>
-void log(Msg msg, Entry... e)
+template <class T>
+struct is_char_ptr_argtype
+ : std::integral_constant<
+ bool,
+ (std::is_pointer<typename std::decay<T>::type>::value &&
+ std::is_same<typename std::remove_cv<typename std::remove_pointer<
+ typename std::decay<T>::type>::type>::type,
+ char>::value)>
{
- static_assert((std::is_same<const char*, std::decay_t<Msg>>::value ||
- std::is_same<char*, std::decay_t<Msg>>::value),
- "First parameter must be a C-string.");
-
- constexpr const char* msg_str = "MESSAGE=%s";
- const auto msg_tuple = std::make_tuple(msg_str, std::forward<Msg>(msg));
-
- constexpr auto transactionStr = "TRANSACTION_ID=%lld";
- auto transactionId = sdbusplus::server::transaction::get_id();
-
- auto log_tuple = std::tuple_cat(details::prio<L>(), msg_tuple,
- entry(transactionStr, transactionId),
- std::forward<Entry>(e)...);
- details::log(log_tuple);
-}
+};
template <class T>
struct is_printf_argtype
@@ -151,23 +114,22 @@ struct is_printf_argtype
{
};
-template <class T>
-struct is_char_ptr_argtype
- : std::integral_constant<
- bool,
- (std::is_pointer<typename std::decay<T>::type>::value &&
- std::is_same<typename std::remove_cv<typename std::remove_pointer<
- typename std::decay<T>::type>::type>::type,
- char>::value)>
-{
-};
-
template <bool...>
struct bool_pack;
template <bool... bs>
using all_true = std::is_same<bool_pack<bs..., true>, bool_pack<true, bs...>>;
+/** @fn entry()
+ * @brief Pack each format string entry as a tuple to be able to validate
+ * the string and parameters when multiple entries are passed to be logged.
+ * @tparam Arg - Types of first argument
+ * @tparam Args - Types of remaining arguments
+ * @param[in] arg - First metadata string of form VAR=value where
+ * VAR is the variable name in uppercase and
+ * value is of any size and format
+ * @param[in] args - Remaining metadata strings
+ */
template <typename Arg, typename... Args>
constexpr auto entry(Arg&& arg, Args&&... args)
{
@@ -175,9 +137,39 @@ constexpr auto entry(Arg&& arg, Args&&... args)
"bad argument type: use char*");
static_assert(all_true<is_printf_argtype<Args>::value...>::value,
"bad argument type: use string.c_str() if needed");
- const auto entry_tuple =
- std::make_tuple(std::forward<Arg>(arg), std::forward<Args>(args)...);
- return entry_tuple;
+ return std::make_tuple(std::forward<Arg>(arg), std::forward<Args>(args)...);
+}
+
+/** @fn log()
+ * @brief Log message to systemd journal
+ * @tparam L - Priority level
+ * @param[in] msg - Message to be logged in C-string format
+ * @param[in] entry - Metadata fields to be added to the message
+ * @details Usage: log<level::XX>(const char*, entry(*format), entry()...);
+ * @example log<level::DEBUG>(
+ * "Simple Example");
+ * char msg_str[] = "File not found";
+ * log<level::DEBUG>(
+ * msg_str,
+ * entry("MY_METADATA=%s_%x, name, number));
+ */
+template <level L, typename Msg, typename... Entry>
+void log(Msg msg, Entry... e)
+{
+ static_assert((std::is_same<const char*, std::decay_t<Msg>>::value ||
+ std::is_same<char*, std::decay_t<Msg>>::value),
+ "First parameter must be a C-string.");
+
+ constexpr const char* msg_str = "MESSAGE=%s";
+ const auto msg_tuple = std::make_tuple(msg_str, std::forward<Msg>(msg));
+
+ constexpr auto transactionStr = "TRANSACTION_ID=%lld";
+ auto transactionId = sdbusplus::server::transaction::get_id();
+
+ auto log_tuple = std::tuple_cat(details::prio<L>(), msg_tuple,
+ entry(transactionStr, transactionId),
+ std::forward<Entry>(e)...);
+ details::log(log_tuple);
}
} // namespace logging
diff --git a/phosphor-rsyslog-config/Makefile.am b/phosphor-rsyslog-config/Makefile.am
index 5453473..3a1aeff 100644
--- a/phosphor-rsyslog-config/Makefile.am
+++ b/phosphor-rsyslog-config/Makefile.am
@@ -15,8 +15,7 @@ phosphor_rsyslog_conf_LDADD = \
phosphor_rsyslog_conf_LDFLAGS = \
$(SDBUSPLUS_LIBS) \
- $(PHOSPHOR_DBUS_INTERFACES_LIBS) \
- -lstdc++fs
+ $(PHOSPHOR_DBUS_INTERFACES_LIBS)
phosphor_rsyslog_conf_CXXFLAGS = \
$(SDBUSPLUS_CFLAGS) \
diff --git a/phosphor-rsyslog-config/server-conf.cpp b/phosphor-rsyslog-config/server-conf.cpp
index 16955fc..716421b 100644
--- a/phosphor-rsyslog-config/server-conf.cpp
+++ b/phosphor-rsyslog-config/server-conf.cpp
@@ -15,19 +15,6 @@
#include <string>
-#if __has_include(<filesystem>)
-#include <filesystem>
-#elif __has_include(<experimental/filesystem>)
-#include <experimental/filesystem>
-namespace std
-{
-// splice experimental::filesystem into std
-namespace filesystem = std::experimental::filesystem;
-} // namespace std
-#else
-#error filesystem not available
-#endif
-
namespace phosphor
{
namespace rsyslog_config
@@ -36,7 +23,6 @@ namespace rsyslog_config
namespace utils = phosphor::rsyslog_utils;
using namespace phosphor::logging;
using namespace sdbusplus::xyz::openbmc_project::Common::Error;
-namespace fs = std::filesystem;
std::string Server::address(std::string value)
{
@@ -108,7 +94,6 @@ uint16_t Server::port(uint16_t value)
void Server::writeConfig(const std::string& serverAddress, uint16_t serverPort,
const char* filePath)
{
- fs::create_directory(fs::path(filePath).parent_path());
std::fstream stream(filePath, std::fstream::out);
if (serverPort && !serverAddress.empty())
@@ -118,7 +103,8 @@ void Server::writeConfig(const std::string& serverAddress, uint16_t serverPort,
}
else // this is a disable request
{
- fs::remove(filePath);
+ // write '*.* ~' - this causes rsyslog to discard all messages
+ stream << "*.* ~";
}
restart();
@@ -144,11 +130,6 @@ bool Server::addressValid(const std::string& address)
void Server::restore(const char* filePath)
{
- if (!fs::exists(filePath))
- {
- return;
- }
-
std::fstream stream(filePath, std::fstream::in);
std::string line;
diff --git a/test/Makefile.am b/test/Makefile.am
index 7fd38d1..64aa6d8 100644
--- a/test/Makefile.am
+++ b/test/Makefile.am
@@ -9,7 +9,8 @@ check_PROGRAMS = \
remote_logging_test_address \
remote_logging_test_port \
remote_logging_test_config \
- sdjournal_mock_test
+ sdjournal_mock_test \
+ extensions_test
test_cppflags = \
-Igtest \
@@ -23,7 +24,7 @@ test_cxxflags = \
test_ldflags = \
-lgtest_main -lgtest \
- -lgmock \
+ -lgmock -lstdc++fs \
$(PTHREAD_LIBS) \
$(OESDK_TESTCASE_FLAGS) \
$(PHOSPHOR_DBUS_INTERFACES_LIBS) \
@@ -33,12 +34,12 @@ test_ldadd = \
$(top_builddir)/elog_serialize.o \
$(top_builddir)/elog_entry.o \
$(top_builddir)/log_manager.o \
- $(top_builddir)/org.openbmc.Associations.o \
$(top_builddir)/xyz/openbmc_project/Logging/Internal/Manager/server.o \
$(top_builddir)/elog_meta.o \
$(top_builddir)/elog-lookup.o \
$(top_builddir)/elog-process-metadata.o \
- $(top_builddir)/sdjournal.o
+ $(top_builddir)/sdjournal.o \
+ $(top_builddir)/extensions.o
remote_logging_test_ldadd = \
$(top_builddir)/phosphor-rsyslog-config/server-conf.o \
@@ -49,48 +50,42 @@ elog_errorwrap_test_CXXFLAGS = $(test_cxxflags)
elog_errorwrap_test_SOURCES = elog_errorwrap_test.cpp
elog_errorwrap_test_LDADD = $(test_ldadd)
elog_errorwrap_test_LDFLAGS = \
- $(test_ldflags) \
- -lstdc++fs
+ $(test_ldflags)
serialization_test_path_CPPFLAGS = $(test_cppflags)
serialization_test_path_CXXFLAGS = $(test_cxxflags)
serialization_test_path_SOURCES = serialization_test_path.cpp
serialization_test_path_LDADD = $(test_ldadd)
serialization_test_path_LDFLAGS = \
- $(test_ldflags) \
- -lstdc++fs
+ $(test_ldflags)
serialization_test_properties_CPPFLAGS = $(test_cppflags)
serialization_test_properties_CXXFLAGS = $(test_cxxflags)
serialization_test_properties_SOURCES = serialization_test_properties.cpp
serialization_test_properties_LDADD = $(test_ldadd)
serialization_test_properties_LDFLAGS = \
- $(test_ldflags) \
- -lstdc++fs
+ $(test_ldflags)
remote_logging_test_address_CPPFLAGS = $(test_cppflags)
remote_logging_test_address_CXXFLAGS = $(test_cxxflags)
remote_logging_test_address_SOURCES = remote_logging_test_address.cpp
remote_logging_test_address_LDADD = $(remote_logging_test_ldadd)
remote_logging_test_address_LDFLAGS = \
- $(test_ldflags) \
- -lstdc++fs
+ $(test_ldflags)
remote_logging_test_port_CPPFLAGS = $(test_cppflags)
remote_logging_test_port_CXXFLAGS = $(test_cxxflags)
remote_logging_test_port_SOURCES = remote_logging_test_port.cpp
remote_logging_test_port_LDADD = $(remote_logging_test_ldadd)
remote_logging_test_port_LDFLAGS = \
- $(test_ldflags) \
- -lstdc++fs
+ $(test_ldflags)
remote_logging_test_config_CPPFLAGS = $(test_cppflags)
remote_logging_test_config_CXXFLAGS = $(test_cxxflags)
remote_logging_test_config_SOURCES = remote_logging_test_config.cpp
remote_logging_test_config_LDADD = $(remote_logging_test_ldadd)
remote_logging_test_config_LDFLAGS = \
- $(test_ldflags) \
- -lstdc++fs
+ $(test_ldflags)
sdjournal_mock_test_CPPFLAGS = $(test_cppflags)
sdjournal_mock_test_CXXFLAGS = $(test_cxxflags)
@@ -98,5 +93,22 @@ sdjournal_mock_test_SOURCES = sdtest.cpp
sdjournal_mock_test_LDADD = $(top_builddir)/sdjournal.o
sdjournal_mock_test_LDFLAGS = $(test_ldflags)
+extensions_test_CPPFLAGS = $(test_cppflags)
+extensions_test_CXXFLAGS = $(test_cxxflags)
+extensions_test_SOURCES = extensions_test.cpp
+extensions_test_LDADD = \
+ $(top_builddir)/elog_entry.o \
+ $(top_builddir)/elog-lookup.o \
+ $(top_builddir)/elog_meta.o \
+ $(top_builddir)/elog-process-metadata.o \
+ $(top_builddir)/elog_serialize.o \
+ $(top_builddir)/log_manager.o \
+ $(top_builddir)/xyz/openbmc_project/Logging/Internal/Manager/server.o
+extensions_test_LDFLAGS = $(test_ldflags)
+
# TODO Remove once the test-case failure is resolved openbmc/phosphor-logging#11
XFAIL_TESTS = elog_errorwrap_test
+
+if ENABLE_PEL_EXTENSION
+include openpower-pels/Makefile.include
+endif
diff --git a/test/extensions_test.cpp b/test/extensions_test.cpp
new file mode 100644
index 0000000..17c3395
--- /dev/null
+++ b/test/extensions_test.cpp
@@ -0,0 +1,98 @@
+#include "elog_entry.hpp"
+#include "extensions.hpp"
+
+#include <gtest/gtest.h>
+
+using namespace phosphor::logging;
+
+// gtest doesn't like this happening in another file, so do it here.
+StartupFunctions Extensions::startupFunctions{};
+CreateFunctions Extensions::createFunctions{};
+DeleteFunctions Extensions::deleteFunctions{};
+DeleteProhibitedFunctions Extensions::deleteProhibitedFunctions{};
+Extensions::DefaultErrorCaps Extensions::defaultErrorCaps =
+ Extensions::DefaultErrorCaps::enable;
+
+void startup1(internal::Manager& manager)
+{
+}
+
+void startup2(internal::Manager& manager)
+{
+}
+
+void create1(const std::string& message, uint32_t id, uint64_t timestamp,
+ Entry::Level severity, const AdditionalDataArg& additionalData,
+ const AssociationEndpointsArg& assocs)
+{
+}
+
+void create2(const std::string& message, uint32_t id, uint64_t timestamp,
+ Entry::Level severity, const AdditionalDataArg& additionalData,
+ const AssociationEndpointsArg& assocs)
+{
+}
+
+void deleteLog1(uint32_t id)
+{
+}
+
+void deleteLog2(uint32_t id)
+{
+}
+
+void deleteProhibited1(uint32_t id, bool& prohibited)
+{
+ prohibited = true;
+}
+
+void deleteProhibited2(uint32_t id, bool& prohibited)
+{
+ prohibited = true;
+}
+
+DISABLE_LOG_ENTRY_CAPS();
+REGISTER_EXTENSION_FUNCTION(startup1);
+REGISTER_EXTENSION_FUNCTION(startup2);
+REGISTER_EXTENSION_FUNCTION(create1);
+REGISTER_EXTENSION_FUNCTION(create2);
+REGISTER_EXTENSION_FUNCTION(deleteProhibited1);
+REGISTER_EXTENSION_FUNCTION(deleteProhibited2);
+REGISTER_EXTENSION_FUNCTION(deleteLog1);
+REGISTER_EXTENSION_FUNCTION(deleteLog2);
+
+TEST(ExtensionsTest, FunctionCallTest)
+{
+ auto bus = sdbusplus::bus::new_default();
+ internal::Manager manager(bus, "testpath");
+
+ EXPECT_EQ(Extensions::getStartupFunctions().size(), 2);
+ for (auto& s : Extensions::getStartupFunctions())
+ {
+ s(manager);
+ }
+
+ AdditionalDataArg ad;
+ AssociationEndpointsArg assocs;
+ EXPECT_EQ(Extensions::getCreateFunctions().size(), 2);
+ for (auto& c : Extensions::getCreateFunctions())
+ {
+ c("test", 5, 6, Entry::Level::Informational, ad, assocs);
+ }
+
+ EXPECT_EQ(Extensions::getDeleteFunctions().size(), 2);
+ for (auto& d : Extensions::getDeleteFunctions())
+ {
+ d(5);
+ }
+
+ EXPECT_EQ(Extensions::getDeleteProhibitedFunctions().size(), 2);
+ for (auto& p : Extensions::getDeleteProhibitedFunctions())
+ {
+ bool prohibited = false;
+ p(5, prohibited);
+ EXPECT_TRUE(prohibited);
+ }
+
+ EXPECT_TRUE(Extensions::disableDefaultLogCaps());
+}
diff --git a/test/openpower-pels/Makefile.include b/test/openpower-pels/Makefile.include
new file mode 100644
index 0000000..09eb36b
--- /dev/null
+++ b/test/openpower-pels/Makefile.include
@@ -0,0 +1,364 @@
+TESTS += $(check_PROGRAMS)
+
+check_PROGRAMS += \
+ additional_data_test \
+ ascii_string_test \
+ bcd_time_test \
+ event_logger_test \
+ extended_user_header_test \
+ failing_mtms_test \
+ fru_identity_test \
+ generic_section_test \
+ host_notifier_test \
+ json_utils_test \
+ log_id_test \
+ mru_test \
+ mtms_test \
+ pce_identity_test \
+ pel_manager_test \
+ pel_rules_test \
+ pel_test \
+ pel_values_test \
+ private_header_test \
+ real_pel_test \
+ registry_test \
+ repository_test \
+ section_header_test \
+ severity_test \
+ src_test \
+ src_callout_test \
+ src_callouts_test \
+ stream_test \
+ user_data_test \
+ user_header_test
+
+pel_objects = \
+ $(top_builddir)/extensions/openpower-pels/ascii_string.o \
+ $(top_builddir)/extensions/openpower-pels/bcd_time.o \
+ $(top_builddir)/extensions/openpower-pels/callout.o \
+ $(top_builddir)/extensions/openpower-pels/callouts.o \
+ $(top_builddir)/extensions/openpower-pels/extended_user_header.o \
+ $(top_builddir)/extensions/openpower-pels/failing_mtms.o \
+ $(top_builddir)/extensions/openpower-pels/fru_identity.o \
+ $(top_builddir)/extensions/openpower-pels/generic.o \
+ $(top_builddir)/extensions/openpower-pels/json_utils.o \
+ $(top_builddir)/extensions/openpower-pels/log_id.o \
+ $(top_builddir)/extensions/openpower-pels/mtms.o \
+ $(top_builddir)/extensions/openpower-pels/mru.o \
+ $(top_builddir)/extensions/openpower-pels/pce_identity.o \
+ $(top_builddir)/extensions/openpower-pels/pel.o \
+ $(top_builddir)/extensions/openpower-pels/pel_rules.o \
+ $(top_builddir)/extensions/openpower-pels/pel_values.o \
+ $(top_builddir)/extensions/openpower-pels/private_header.o \
+ $(top_builddir)/extensions/openpower-pels/registry.o \
+ $(top_builddir)/extensions/openpower-pels/section_factory.o \
+ $(top_builddir)/extensions/openpower-pels/severity.o \
+ $(top_builddir)/extensions/openpower-pels/src.o \
+ $(top_builddir)/extensions/openpower-pels/user_data.o \
+ $(top_builddir)/extensions/openpower-pels/user_header.o
+
+additional_data_test_SOURCES = %reldir%/additional_data_test.cpp
+additional_data_test_CPPFLAGS = $(test_cppflags)
+additional_data_test_CXXFLAGS = $(test_cxxflags)
+additional_data_test_LDADD = $(test_ldadd)
+additional_data_test_LDFLAGS = $(test_ldflags)
+
+stream_test_SOURCES = %reldir%/stream_test.cpp
+stream_test_CPPFLAGS = $(test_cppflags)
+stream_test_CXXFLAGS = $(test_cxxflags)
+stream_test_LDADD = $(test_ldadd)
+stream_test_LDFLAGS = $(test_ldflags)
+
+bcd_time_test_SOURCES = \
+ %reldir%/bcd_time_test.cpp
+bcd_time_test_CPPFLAGS = $(test_cppflags)
+bcd_time_test_CXXFLAGS = $(test_cxxflags)
+bcd_time_test_LDADD = \
+ $(test_ldadd) \
+ $(top_builddir)/extensions/openpower-pels/bcd_time.o
+bcd_time_test_LDFLAGS = $(test_ldflags)
+
+section_header_test_SOURCES = \
+ %reldir%/section_header_test.cpp
+section_header_test_CPPFLAGS = $(test_cppflags)
+section_header_test_CXXFLAGS = $(test_cxxflags)
+section_header_test_LDADD = $(test_ldadd)
+section_header_test_LDFLAGS = $(test_ldflags)
+
+private_header_test_SOURCES = \
+ %reldir%/private_header_test.cpp %reldir%/pel_utils.cpp %reldir%/paths.cpp
+private_header_test_CPPFLAGS = $(test_cppflags)
+private_header_test_CXXFLAGS = $(test_cxxflags)
+private_header_test_LDADD = \
+ $(test_ldadd) \
+ $(pel_objects)
+private_header_test_LDFLAGS = $(test_ldflags)
+
+user_header_test_SOURCES = \
+ %reldir%/user_header_test.cpp %reldir%/pel_utils.cpp %reldir%/paths.cpp
+user_header_test_CPPFLAGS = $(test_cppflags)
+user_header_test_CXXFLAGS = $(test_cxxflags)
+user_header_test_LDADD = \
+ $(test_ldadd) \
+ $(pel_objects)
+user_header_test_LDFLAGS = $(test_ldflags)
+
+log_id_test_SOURCES = \
+ %reldir%/log_id_test.cpp %reldir%/paths.cpp
+log_id_test_CPPFLAGS = $(test_cppflags)
+log_id_test_CXXFLAGS = $(test_cxxflags)
+log_id_test_LDADD = \
+ $(test_ldadd) \
+ $(top_builddir)/extensions/openpower-pels/log_id.o
+log_id_test_LDFLAGS = $(test_ldflags)
+
+pel_test_SOURCES = \
+ %reldir%/pel_test.cpp %reldir%/paths.cpp %reldir%/pel_utils.cpp
+pel_test_CPPFLAGS = $(test_cppflags)
+pel_test_CXXFLAGS = $(test_cxxflags)
+pel_test_LDADD = \
+ $(test_ldadd) \
+ $(pel_objects)
+pel_test_LDFLAGS = $(test_ldflags)
+
+real_pel_test_SOURCES = \
+ %reldir%/real_pel_test.cpp %reldir%/paths.cpp %reldir%/pel_utils.cpp
+real_pel_test_CPPFLAGS = $(test_cppflags)
+real_pel_test_CXXFLAGS = $(test_cxxflags)
+real_pel_test_LDADD = \
+ $(test_ldadd) \
+ $(pel_objects)
+real_pel_test_LDFLAGS = $(test_ldflags)
+
+repository_test_SOURCES = \
+ %reldir%/repository_test.cpp %reldir%/paths.cpp %reldir%/pel_utils.cpp
+repository_test_CPPFLAGS = $(test_cppflags)
+repository_test_CXXFLAGS = $(test_cxxflags)
+repository_test_LDADD = \
+ $(test_ldadd) \
+ $(pel_objects) \
+ $(top_builddir)/extensions/openpower-pels/repository.o
+repository_test_LDFLAGS = $(test_ldflags)
+
+pel_manager_test_SOURCES = \
+ %reldir%/pel_manager_test.cpp %reldir%/paths.cpp %reldir%/pel_utils.cpp
+pel_manager_test_CPPFLAGS = $(test_cppflags)
+pel_manager_test_CXXFLAGS = \
+ $(test_cxxflags) \
+ $(SDEVENTPLUS_CFLAGS)
+pel_manager_test_LDADD = \
+ $(test_ldadd) \
+ $(pel_objects) \
+ $(top_builddir)/extensions/openpower-pels/data_interface.o \
+ $(top_builddir)/extensions/openpower-pels/host_notifier.o \
+ $(top_builddir)/extensions/openpower-pels/manager.o \
+ $(top_builddir)/extensions/openpower-pels/repository.o
+pel_manager_test_LDFLAGS = \
+ $(test_ldflags) \
+ $(SDEVENTPLUS_LIBS)
+
+registry_test_SOURCES = \
+ %reldir%/registry_test.cpp %reldir%/paths.cpp
+registry_test_CPPFLAGS = $(test_cppflags)
+registry_test_CXXFLAGS = $(test_cxxflags)
+registry_test_LDADD = \
+ $(test_ldadd) \
+ $(top_builddir)/extensions/openpower-pels/registry.o \
+ $(top_builddir)/extensions/openpower-pels/pel_values.o
+registry_test_LDFLAGS = $(test_ldflags)
+
+severity_test_SOURCES = %reldir%/severity_test.cpp
+severity_test_CPPFLAGS = $(test_cppflags)
+severity_test_CXXFLAGS = $(test_cxxflags)
+severity_test_LDADD = \
+ $(test_ldflags) \
+ $(top_builddir)/extensions/openpower-pels/severity.o
+severity_test_LDFLAGS = $(test_ldflags)
+
+mtms_test_SOURCES = %reldir%/mtms_test.cpp
+mtms_test_CPPFLAGS = $(test_cppflags)
+mtms_test_CXXFLAGS = $(test_cxxflags)
+mtms_test_LDADD = \
+ $(test_ldadd) \
+ $(top_builddir)/extensions/openpower-pels/mtms.o
+mtms_test_LDFLAGS = $(test_ldflags)
+
+failing_mtms_test_SOURCES = %reldir%/failing_mtms_test.cpp
+failing_mtms_test_CPPFLAGS = $(test_cppflags)
+failing_mtms_test_CXXFLAGS = $(test_cxxflags)
+failing_mtms_test_LDADD = \
+ $(test_ldadd) \
+ $(top_builddir)/extensions/openpower-pels/failing_mtms.o \
+ $(top_builddir)/extensions/openpower-pels/json_utils.o \
+ $(top_builddir)/extensions/openpower-pels/mtms.o
+failing_mtms_test_LDFLAGS = $(test_ldflags)
+
+pel_values_test_SOURCES = %reldir%/pel_values_test.cpp
+pel_values_test_CPPFLAGS = $(test_cppflags)
+pel_values_test_CXXFLAGS = $(test_cxxflags)
+pel_values_test_LDADD = \
+ $(test_ldflags) \
+ $(top_builddir)/extensions/openpower-pels/pel_values.o
+pel_values_test_LDFLAGS = $(test_ldflags)
+
+generic_section_test_SOURCES = \
+ %reldir%/generic_section_test.cpp %reldir%/pel_utils.cpp
+generic_section_test_CPPFLAGS = $(test_cppflags)
+generic_section_test_CXXFLAGS = $(test_cxxflags)
+generic_section_test_LDADD = \
+ $(test_ldadd) \
+ $(top_builddir)/extensions/openpower-pels/generic.o
+generic_section_test_LDFLAGS = $(test_ldflags)
+
+user_data_test_SOURCES = \
+ %reldir%/user_data_test.cpp %reldir%/pel_utils.cpp
+user_data_test_CPPFLAGS = $(test_cppflags)
+user_data_test_CXXFLAGS = $(test_cxxflags)
+user_data_test_LDADD = \
+ $(test_ldadd) \
+ $(top_builddir)/extensions/openpower-pels/user_data.o
+user_data_test_LDFLAGS = $(test_ldflags)
+
+ascii_string_test_SOURCES = %reldir%/ascii_string_test.cpp
+ascii_string_test_CPPFLAGS = $(test_cppflags)
+ascii_string_test_CXXFLAGS = $(test_cxxflags)
+ascii_string_test_LDADD = \
+ $(test_ldadd) \
+ $(top_builddir)/extensions/openpower-pels/ascii_string.o
+ascii_string_test_LDFLAGS = $(test_ldflags)
+
+fru_identity_test_SOURCES = %reldir%/fru_identity_test.cpp
+fru_identity_test_CPPFLAGS = $(test_cppflags)
+fru_identity_test_CXXFLAGS = $(test_cxxflags)
+fru_identity_test_LDADD = \
+ $(test_ldadd) \
+ $(top_builddir)/extensions/openpower-pels/fru_identity.o
+fru_identity_test_LDFLAGS = $(test_ldflags)
+
+pce_identity_test_SOURCES = %reldir%/pce_identity_test.cpp
+pce_identity_test_CPPFLAGS = $(test_cppflags)
+pce_identity_test_CXXFLAGS = $(test_cxxflags)
+pce_identity_test_LDADD = \
+ $(test_ldadd) \
+ $(top_builddir)/extensions/openpower-pels/pce_identity.o \
+ $(top_builddir)/extensions/openpower-pels/mtms.o
+pce_identity_test_LDFLAGS = $(test_ldflags)
+
+mru_test_SOURCES = %reldir%/mru_test.cpp
+mru_test_CPPFLAGS = $(test_cppflags)
+mru_test_CXXFLAGS = $(test_cxxflags)
+mru_test_LDADD = \
+ $(test_ldadd) \
+ $(top_builddir)/extensions/openpower-pels/mru.o
+mru_test_LDFLAGS = $(test_ldflags)
+
+src_callout_test_SOURCES = \
+ %reldir%/src_callout_test.cpp \
+ %reldir%/pel_utils.cpp
+src_callout_test_CPPFLAGS = $(test_cppflags)
+src_callout_test_CXXFLAGS = $(test_cxxflags)
+src_callout_test_LDADD = \
+ $(test_ldadd) \
+ $(top_builddir)/extensions/openpower-pels/callout.o \
+ $(top_builddir)/extensions/openpower-pels/fru_identity.o \
+ $(top_builddir)/extensions/openpower-pels/mru.o \
+ $(top_builddir)/extensions/openpower-pels/mtms.o \
+ $(top_builddir)/extensions/openpower-pels/pce_identity.o
+src_callout_test_LDFLAGS = $(test_ldflags)
+
+src_callouts_test_SOURCES = \
+ %reldir%/src_callouts_test.cpp \
+ %reldir%/pel_utils.cpp
+src_callouts_test_CPPFLAGS = $(test_cppflags)
+src_callouts_test_CXXFLAGS = $(test_cxxflags)
+src_callouts_test_LDADD = \
+ $(test_ldadd) \
+ $(top_builddir)/extensions/openpower-pels/callout.o \
+ $(top_builddir)/extensions/openpower-pels/callouts.o \
+ $(top_builddir)/extensions/openpower-pels/fru_identity.o \
+ $(top_builddir)/extensions/openpower-pels/mru.o \
+ $(top_builddir)/extensions/openpower-pels/mtms.o \
+ $(top_builddir)/extensions/openpower-pels/pce_identity.o
+src_callouts_test_LDFLAGS = $(test_ldflags)
+
+src_test_SOURCES = \
+ %reldir%/src_test.cpp \
+ %reldir%/pel_utils.cpp
+src_test_CPPFLAGS = $(test_cppflags)
+src_test_CXXFLAGS = $(test_cxxflags)
+src_test_LDADD = \
+ $(test_ldadd) \
+ $(top_builddir)/extensions/openpower-pels/ascii_string.o \
+ $(top_builddir)/extensions/openpower-pels/callout.o \
+ $(top_builddir)/extensions/openpower-pels/callouts.o \
+ $(top_builddir)/extensions/openpower-pels/fru_identity.o \
+ $(top_builddir)/extensions/openpower-pels/json_utils.o \
+ $(top_builddir)/extensions/openpower-pels/paths.o \
+ $(top_builddir)/extensions/openpower-pels/mru.o \
+ $(top_builddir)/extensions/openpower-pels/mtms.o \
+ $(top_builddir)/extensions/openpower-pels/pce_identity.o \
+ $(top_builddir)/extensions/openpower-pels/pel_values.o \
+ $(top_builddir)/extensions/openpower-pels/registry.o \
+ $(top_builddir)/extensions/openpower-pels/src.o
+src_test_LDFLAGS = $(test_ldflags)
+
+extended_user_header_test_SOURCES = \
+ %reldir%/extended_user_header_test.cpp \
+ %reldir%/pel_utils.cpp
+extended_user_header_test_CPPFLAGS = $(test_cppflags)
+extended_user_header_test_CXXFLAGS = $(test_cxxflags)
+extended_user_header_test_LDADD = \
+ $(test_ldadd) \
+ $(top_builddir)/extensions/openpower-pels/ascii_string.o \
+ $(top_builddir)/extensions/openpower-pels/bcd_time.o \
+ $(top_builddir)/extensions/openpower-pels/callout.o \
+ $(top_builddir)/extensions/openpower-pels/callouts.o \
+ $(top_builddir)/extensions/openpower-pels/data_interface.o \
+ $(top_builddir)/extensions/openpower-pels/extended_user_header.o \
+ $(top_builddir)/extensions/openpower-pels/fru_identity.o \
+ $(top_builddir)/extensions/openpower-pels/json_utils.o \
+ $(top_builddir)/extensions/openpower-pels/mru.o \
+ $(top_builddir)/extensions/openpower-pels/mtms.o \
+ $(top_builddir)/extensions/openpower-pels/paths.o \
+ $(top_builddir)/extensions/openpower-pels/pel_values.o \
+ $(top_builddir)/extensions/openpower-pels/pce_identity.o \
+ $(top_builddir)/extensions/openpower-pels/registry.o \
+ $(top_builddir)/extensions/openpower-pels/src.o
+extended_user_header_test_LDFLAGS = $(test_ldflags)
+
+pel_rules_test_SOURCES = %reldir%/pel_rules_test.cpp
+pel_rules_test_CPPFLAGS = $(test_cppflags)
+pel_rules_test_CXXFLAGS = $(test_cxxflags)
+pel_rules_test_LDADD = \
+ $(test_ldflags) \
+ $(top_builddir)/extensions/openpower-pels/pel_rules.o
+pel_rules_test_LDFLAGS = $(test_ldflags)
+
+host_notifier_test_SOURCES = \
+ %reldir%/host_notifier_test.cpp \
+ %reldir%/paths.cpp \
+ %reldir%/pel_utils.cpp
+host_notifier_test_CPPFLAGS = $(test_cppflags)
+host_notifier_test_CXXFLAGS = $(test_cxxflags) $(SDEVENTPLUS_CFLAGS)
+host_notifier_test_LDADD = \
+ $(test_ldflags) \
+ $(pel_objects) \
+ $(top_builddir)/extensions/openpower-pels/host_notifier.o \
+ $(top_builddir)/extensions/openpower-pels/repository.o
+host_notifier_test_LDFLAGS = $(test_ldflags) $(SDEVENTPLUS_LIBS)
+
+json_utils_test_SOURCES = %reldir%/json_utils_test.cpp
+json_utils_test_CPPFLAGS = $(test_cppflags)
+json_utils_test_CXXFLAGS = $(test_cxxflags)
+json_utils_test_LDADD = \
+ $(test_ldadd) \
+ $(top_builddir)/extensions/openpower-pels/json_utils.o
+json_utils_test_LDFLAGS = $(test_ldflags)
+
+event_logger_test_SOURCES = \
+ %reldir%/event_logger_test.cpp
+event_logger_test_CPPFLAGS = $(test_cppflags)
+event_logger_test_CXXFLAGS = $(test_cxxflags) $(SDEVENTPLUS_CFLAGS)
+event_logger_test_LDADD = \
+ $(test_ldadd)
+event_logger_test_LDFLAGS = $(test_ldflags) $(SDEVENTPLUS_LIBS)
diff --git a/test/openpower-pels/additional_data_test.cpp b/test/openpower-pels/additional_data_test.cpp
new file mode 100644
index 0000000..c93fdf2
--- /dev/null
+++ b/test/openpower-pels/additional_data_test.cpp
@@ -0,0 +1,65 @@
+/**
+ * Copyright © 2019 IBM Corporation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#include "extensions/openpower-pels/additional_data.hpp"
+
+#include <gtest/gtest.h>
+
+using namespace openpower::pels;
+
+TEST(AdditionalDataTest, GetKeywords)
+{
+ std::vector<std::string> data{"KEY1=VALUE1", "KEY2=VALUE2",
+ "KEY3=", "HELLOWORLD", "=VALUE5"};
+ AdditionalData ad{data};
+
+ EXPECT_TRUE(ad.getValue("KEY1"));
+ EXPECT_EQ(*(ad.getValue("KEY1")), "VALUE1");
+
+ EXPECT_TRUE(ad.getValue("KEY2"));
+ EXPECT_EQ(*(ad.getValue("KEY2")), "VALUE2");
+
+ EXPECT_FALSE(ad.getValue("x"));
+
+ auto value3 = ad.getValue("KEY3");
+ EXPECT_TRUE(value3);
+ EXPECT_TRUE((*value3).empty());
+
+ EXPECT_FALSE(ad.getValue("HELLOWORLD"));
+ EXPECT_FALSE(ad.getValue("VALUE5"));
+
+ auto json = ad.toJSON();
+ std::string expected = R"({"KEY1":"VALUE1","KEY2":"VALUE2","KEY3":""})";
+ EXPECT_EQ(json.dump(), expected);
+
+ ad.remove("KEY1");
+ EXPECT_FALSE(ad.getValue("KEY1"));
+}
+
+TEST(AdditionalDataTest, AddData)
+{
+ AdditionalData ad;
+
+ ad.add("KEY1", "VALUE1");
+ EXPECT_EQ(*(ad.getValue("KEY1")), "VALUE1");
+
+ ad.add("KEY2", "VALUE2");
+ EXPECT_EQ(*(ad.getValue("KEY2")), "VALUE2");
+
+ std::map<std::string, std::string> expected{{"KEY1", "VALUE1"},
+ {"KEY2", "VALUE2"}};
+
+ EXPECT_EQ(expected, ad.getData());
+}
diff --git a/test/openpower-pels/ascii_string_test.cpp b/test/openpower-pels/ascii_string_test.cpp
new file mode 100644
index 0000000..96ee4ad
--- /dev/null
+++ b/test/openpower-pels/ascii_string_test.cpp
@@ -0,0 +1,86 @@
+/**
+ * Copyright © 2019 IBM Corporation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#include "extensions/openpower-pels/ascii_string.hpp"
+#include "extensions/openpower-pels/registry.hpp"
+
+#include <gtest/gtest.h>
+
+using namespace openpower::pels;
+
+TEST(AsciiStringTest, AsciiStringTest)
+{
+ // Build the ASCII string from a message registry entry
+ message::Entry entry;
+ entry.src.type = 0xBD;
+ entry.src.reasonCode = 0xABCD;
+ entry.subsystem = 0x37;
+
+ src::AsciiString as{entry};
+
+ auto data = as.get();
+
+ EXPECT_EQ(data, "BD37ABCD ");
+
+ // Now flatten it
+ std::vector<uint8_t> flattenedData;
+ Stream stream{flattenedData};
+
+ as.flatten(stream);
+
+ for (size_t i = 0; i < 32; i++)
+ {
+ EXPECT_EQ(data[i], flattenedData[i]);
+ }
+}
+
+// A 0x11 power SRC doesn't have the subsystem in it
+TEST(AsciiStringTest, PowerErrorTest)
+{
+ message::Entry entry;
+ entry.src.type = 0x11;
+ entry.src.reasonCode = 0xABCD;
+ entry.subsystem = 0x37;
+
+ src::AsciiString as{entry};
+ auto data = as.get();
+
+ EXPECT_EQ(data, "1100ABCD ");
+}
+
+TEST(AsciiStringTest, UnflattenTest)
+{
+ std::vector<uint8_t> rawData{'B', 'D', '5', '6', '1', '2', 'A', 'B'};
+
+ for (int i = 8; i < 32; i++)
+ {
+ rawData.push_back(' ');
+ }
+
+ Stream stream{rawData};
+ src::AsciiString as{stream};
+
+ auto data = as.get();
+
+ EXPECT_EQ(data, "BD5612AB ");
+}
+
+TEST(AsciiStringTest, UnderflowTest)
+{
+ std::vector<uint8_t> rawData{'B', 'D', '5', '6'};
+ Stream stream{rawData};
+
+ EXPECT_THROW(src::AsciiString as{stream}, std::out_of_range);
+}
diff --git a/test/openpower-pels/bcd_time_test.cpp b/test/openpower-pels/bcd_time_test.cpp
new file mode 100644
index 0000000..6fe761c
--- /dev/null
+++ b/test/openpower-pels/bcd_time_test.cpp
@@ -0,0 +1,105 @@
+/**
+ * Copyright © 2019 IBM Corporation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#include "extensions/openpower-pels/bcd_time.hpp"
+
+#include <gtest/gtest.h>
+
+using namespace openpower::pels;
+
+TEST(BCDTimeTest, ToBCDTest)
+{
+ EXPECT_EQ(toBCD(0), 0x00);
+ EXPECT_EQ(toBCD(1), 0x01);
+ EXPECT_EQ(toBCD(10), 0x10);
+ EXPECT_EQ(toBCD(99), 0x99);
+ EXPECT_EQ(toBCD(37), 0x37);
+ EXPECT_EQ(toBCD(60), 0x60);
+ EXPECT_EQ(toBCD(12345678), 0x12345678);
+ EXPECT_EQ(toBCD(0xF), 0x15);
+}
+
+TEST(BCDTimeTest, FlattenUnflattenTest)
+{
+ std::vector<uint8_t> data{1, 2, 3, 4, 5, 6, 7, 8};
+ Stream stream{data};
+ BCDTime bcd;
+
+ // Unflatten
+ stream >> bcd;
+
+ EXPECT_EQ(bcd.yearMSB, 1);
+ EXPECT_EQ(bcd.yearLSB, 2);
+ EXPECT_EQ(bcd.month, 3);
+ EXPECT_EQ(bcd.day, 4);
+ EXPECT_EQ(bcd.hour, 5);
+ EXPECT_EQ(bcd.minutes, 6);
+ EXPECT_EQ(bcd.seconds, 7);
+ EXPECT_EQ(bcd.hundredths, 8);
+
+ // Flatten
+ uint8_t val = 0x20;
+ bcd.yearMSB = val++;
+ bcd.yearLSB = val++;
+ bcd.month = val++;
+ bcd.day = val++;
+ bcd.hour = val++;
+ bcd.minutes = val++;
+ bcd.seconds = val++;
+ bcd.hundredths = val++;
+
+ stream.offset(0);
+ stream << bcd;
+
+ for (size_t i = 0; i < 8; i++)
+ {
+ EXPECT_EQ(data[i], 0x20 + i);
+ }
+}
+
+TEST(BCDTimeTest, ConvertTest)
+{
+ // Convert a time_point into BCDTime
+ tm time_tm;
+ time_tm.tm_year = 125;
+ time_tm.tm_mon = 11;
+ time_tm.tm_mday = 31;
+ time_tm.tm_hour = 15;
+ time_tm.tm_min = 23;
+ time_tm.tm_sec = 42;
+ time_tm.tm_isdst = 0;
+
+ auto timepoint = std::chrono::system_clock::from_time_t(mktime(&time_tm));
+ auto timeInBCD = getBCDTime(timepoint);
+
+ EXPECT_EQ(timeInBCD.yearMSB, 0x20);
+ EXPECT_EQ(timeInBCD.yearLSB, 0x25);
+ EXPECT_EQ(timeInBCD.month, 0x12);
+ EXPECT_EQ(timeInBCD.day, 0x31);
+ EXPECT_EQ(timeInBCD.hour, 0x15);
+ EXPECT_EQ(timeInBCD.minutes, 0x23);
+ EXPECT_EQ(timeInBCD.seconds, 0x42);
+ EXPECT_EQ(timeInBCD.hundredths, 0x00);
+}
+
+TEST(BCDTimeTest, ConvertFromMSTest)
+{
+ auto now = std::chrono::system_clock::now();
+ uint64_t ms = std::chrono::duration_cast<std::chrono::milliseconds>(
+ now.time_since_epoch())
+ .count();
+
+ ASSERT_EQ(getBCDTime(now), getBCDTime(ms));
+}
diff --git a/test/openpower-pels/event_logger_test.cpp b/test/openpower-pels/event_logger_test.cpp
new file mode 100644
index 0000000..1a4d8fc
--- /dev/null
+++ b/test/openpower-pels/event_logger_test.cpp
@@ -0,0 +1,126 @@
+/**
+ * Copyright © 2019 IBM Corporation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#include "extensions/openpower-pels/event_logger.hpp"
+#include "log_manager.hpp"
+
+#include <gtest/gtest.h>
+
+using namespace openpower::pels;
+using namespace phosphor::logging;
+
+class CreateHelper
+{
+ public:
+ void create(const std::string& name, Entry::Level level,
+ const EventLogger::ADMap& ad)
+ {
+ _createCount++;
+ _prevName = name;
+ _prevLevel = level;
+ _prevAD = ad;
+
+ // Try to create another event from within the creation
+ // function. Should never work or else we could get stuck
+ // infinitely creating events.
+ if (_eventLogger)
+ {
+ AdditionalData d;
+ _eventLogger->log(name, level, d);
+ }
+ }
+
+ size_t _createCount = 0;
+ std::string _prevName;
+ Entry::Level _prevLevel;
+ EventLogger::ADMap _prevAD;
+ EventLogger* _eventLogger = nullptr;
+};
+
+void runEvents(sd_event* event, size_t numEvents)
+{
+ sdeventplus::Event e{event};
+
+ for (size_t i = 0; i < numEvents; i++)
+ {
+ e.run(std::chrono::milliseconds(1));
+ }
+}
+
+TEST(EventLoggerTest, TestCreateEvents)
+{
+ sd_event* sdEvent = nullptr;
+ auto r = sd_event_default(&sdEvent);
+ ASSERT_TRUE(r >= 0);
+
+ CreateHelper ch;
+
+ EventLogger eventLogger(
+ sdEvent, std::bind(std::mem_fn(&CreateHelper::create), &ch,
+ std::placeholders::_1, std::placeholders::_2,
+ std::placeholders::_3));
+
+ ch._eventLogger = &eventLogger;
+
+ AdditionalData ad;
+ ad.add("key1", "value1");
+
+ eventLogger.log("one", Entry::Level::Error, ad);
+ EXPECT_EQ(eventLogger.queueSize(), 1);
+
+ runEvents(sdEvent, 1);
+
+ // Verify 1 event was created
+ EXPECT_EQ(eventLogger.queueSize(), 0);
+ EXPECT_EQ(ch._prevName, "one");
+ EXPECT_EQ(ch._prevLevel, Entry::Level::Error);
+ EXPECT_EQ(ch._prevAD, ad.getData());
+ EXPECT_EQ(ch._createCount, 1);
+
+ // Create 2 more, and run 1 event loop at a time and check the results
+ eventLogger.log("two", Entry::Level::Error, ad);
+ eventLogger.log("three", Entry::Level::Error, ad);
+
+ EXPECT_EQ(eventLogger.queueSize(), 2);
+
+ runEvents(sdEvent, 1);
+
+ EXPECT_EQ(ch._createCount, 2);
+ EXPECT_EQ(ch._prevName, "two");
+ EXPECT_EQ(eventLogger.queueSize(), 1);
+
+ runEvents(sdEvent, 1);
+ EXPECT_EQ(ch._createCount, 3);
+ EXPECT_EQ(ch._prevName, "three");
+ EXPECT_EQ(eventLogger.queueSize(), 0);
+
+ // Add them all again and run them all at once
+ eventLogger.log("three", Entry::Level::Error, ad);
+ eventLogger.log("two", Entry::Level::Error, ad);
+ eventLogger.log("one", Entry::Level::Error, ad);
+ runEvents(sdEvent, 3);
+
+ EXPECT_EQ(ch._createCount, 6);
+ EXPECT_EQ(ch._prevName, "one");
+ EXPECT_EQ(eventLogger.queueSize(), 0);
+
+ // Run extra events - doesn't do anything
+ runEvents(sdEvent, 1);
+ EXPECT_EQ(ch._createCount, 6);
+ EXPECT_EQ(ch._prevName, "one");
+ EXPECT_EQ(eventLogger.queueSize(), 0);
+
+ sd_event_unref(sdEvent);
+}
diff --git a/test/openpower-pels/extended_user_header_test.cpp b/test/openpower-pels/extended_user_header_test.cpp
new file mode 100644
index 0000000..5cb3e9f
--- /dev/null
+++ b/test/openpower-pels/extended_user_header_test.cpp
@@ -0,0 +1,301 @@
+/**
+ * Copyright © 2019 IBM Corporation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#include "extensions/openpower-pels/extended_user_header.hpp"
+#include "mocks.hpp"
+#include "pel_utils.hpp"
+
+#include <gtest/gtest.h>
+
+using namespace openpower::pels;
+using ::testing::Return;
+
+const std::vector<uint8_t> sectionData{
+ // section header
+ 'E', 'H', 0x00, 0x60, // ID and Size
+ 0x01, 0x00, // version, subtype
+ 0x03, 0x04, // comp ID
+
+ // MTMS
+ 'T', 'T', 'T', 'T', '-', 'M', 'M', 'M', '1', '2', '3', '4', '5', '6', '7',
+ '8', '9', 'A', 'B', 'C',
+
+ // Server FW version
+ 'S', 'E', 'R', 'V', 'E', 'R', '_', 'V', 'E', 'R', 'S', 'I', 'O', 'N', '\0',
+ '\0',
+
+ // Subsystem FW Version
+ 'B', 'M', 'C', '_', 'V', 'E', 'R', 'S', 'I', 'O', 'N', '\0', '\0', '\0',
+ '\0', '\0',
+
+ // Reserved
+ 0x00, 0x00, 0x00, 0x00,
+
+ // Reference time
+ 0x20, 0x25, 0x10, 0x20, 0x30, 0x40, 0x50, 0x60,
+
+ // Reserved
+ 0x00, 0x00, 0x00,
+
+ // SymptomID length
+ 20,
+
+ // SymptomID
+ 'B', 'D', '8', 'D', '4', '2', '0', '0', '_', '1', '2', '3', '4', '5', '6',
+ '7', '8', '\0', '\0', '\0'};
+
+// The section size without the symptom ID
+const size_t baseSectionSize = 76;
+
+TEST(ExtUserHeaderTest, StreamConstructorTest)
+{
+ auto data = sectionData;
+ Stream stream{data};
+ ExtendedUserHeader euh{stream};
+
+ EXPECT_EQ(euh.valid(), true);
+ EXPECT_EQ(euh.header().id, 0x4548); // EH
+ EXPECT_EQ(euh.header().size, sectionData.size());
+ EXPECT_EQ(euh.header().version, 0x01);
+ EXPECT_EQ(euh.header().subType, 0x00);
+ EXPECT_EQ(euh.header().componentID, 0x0304);
+
+ EXPECT_EQ(euh.flattenedSize(), sectionData.size());
+ EXPECT_EQ(euh.machineTypeModel(), "TTTT-MMM");
+ EXPECT_EQ(euh.machineSerialNumber(), "123456789ABC");
+ EXPECT_EQ(euh.serverFWVersion(), "SERVER_VERSION");
+ EXPECT_EQ(euh.subsystemFWVersion(), "BMC_VERSION");
+ EXPECT_EQ(euh.symptomID(), "BD8D4200_12345678");
+
+ BCDTime time{0x20, 0x25, 0x10, 0x20, 0x30, 0x40, 0x50, 0x60};
+ EXPECT_EQ(time, euh.refTime());
+
+ // Flatten it and make sure nothing changes
+ std::vector<uint8_t> newData;
+ Stream newStream{newData};
+
+ euh.flatten(newStream);
+ EXPECT_EQ(sectionData, newData);
+}
+
+// Same as above, with with symptom ID empty
+TEST(ExtUserHeaderTest, StreamConstructorNoIDTest)
+{
+ auto data = sectionData;
+ data.resize(baseSectionSize);
+ data[3] = baseSectionSize; // The size in the header
+ data.back() = 0; // Symptom ID length
+
+ Stream stream{data};
+ ExtendedUserHeader euh{stream};
+
+ EXPECT_EQ(euh.valid(), true);
+ EXPECT_EQ(euh.header().id, 0x4548); // EH
+ EXPECT_EQ(euh.header().size, baseSectionSize);
+ EXPECT_EQ(euh.header().version, 0x01);
+ EXPECT_EQ(euh.header().subType, 0x00);
+ EXPECT_EQ(euh.header().componentID, 0x0304);
+
+ EXPECT_EQ(euh.flattenedSize(), baseSectionSize);
+ EXPECT_EQ(euh.machineTypeModel(), "TTTT-MMM");
+ EXPECT_EQ(euh.machineSerialNumber(), "123456789ABC");
+ EXPECT_EQ(euh.serverFWVersion(), "SERVER_VERSION");
+ EXPECT_EQ(euh.subsystemFWVersion(), "BMC_VERSION");
+ EXPECT_EQ(euh.symptomID(), "");
+
+ BCDTime time{0x20, 0x25, 0x10, 0x20, 0x30, 0x40, 0x50, 0x60};
+ EXPECT_EQ(time, euh.refTime());
+
+ // Flatten it and make sure nothing changes
+ std::vector<uint8_t> newData;
+ Stream newStream{newData};
+
+ euh.flatten(newStream);
+ EXPECT_EQ(data, newData);
+}
+
+TEST(ExtUserHeaderTest, ConstructorTest)
+{
+ auto srcData = pelDataFactory(TestPELType::primarySRCSection);
+ Stream srcStream{srcData};
+ SRC src{srcStream};
+
+ message::Entry entry; // Empty Symptom ID vector
+
+ {
+ MockDataInterface dataIface;
+
+ EXPECT_CALL(dataIface, getMachineTypeModel())
+ .WillOnce(Return("AAAA-BBB"));
+
+ EXPECT_CALL(dataIface, getMachineSerialNumber())
+ .WillOnce(Return("123456789ABC"));
+
+ EXPECT_CALL(dataIface, getServerFWVersion())
+ .WillOnce(Return("SERVER_VERSION"));
+
+ EXPECT_CALL(dataIface, getBMCFWVersion())
+ .WillOnce(Return("BMC_VERSION"));
+
+ ExtendedUserHeader euh{dataIface, entry, src};
+
+ EXPECT_EQ(euh.valid(), true);
+ EXPECT_EQ(euh.header().id, 0x4548); // EH
+
+ // The symptom ID accounts for the extra 20 bytes
+ EXPECT_EQ(euh.header().size, baseSectionSize + 20);
+ EXPECT_EQ(euh.header().version, 0x01);
+ EXPECT_EQ(euh.header().subType, 0x00);
+ EXPECT_EQ(euh.header().componentID, 0x2000);
+
+ EXPECT_EQ(euh.flattenedSize(), baseSectionSize + 20);
+ EXPECT_EQ(euh.machineTypeModel(), "AAAA-BBB");
+ EXPECT_EQ(euh.machineSerialNumber(), "123456789ABC");
+ EXPECT_EQ(euh.serverFWVersion(), "SERVER_VERSION");
+ EXPECT_EQ(euh.subsystemFWVersion(), "BMC_VERSION");
+
+ // The default symptom ID is the ascii string + word 3
+ EXPECT_EQ(euh.symptomID(), "BD8D5678_03030310");
+
+ BCDTime time;
+ EXPECT_EQ(time, euh.refTime());
+ }
+
+ {
+ MockDataInterface dataIface;
+
+ // These 4 items are too long and will get truncated
+ // in the section.
+ EXPECT_CALL(dataIface, getMachineTypeModel())
+ .WillOnce(Return("AAAA-BBBBBBBBBBB"));
+
+ EXPECT_CALL(dataIface, getMachineSerialNumber())
+ .WillOnce(Return("123456789ABC123456789"));
+
+ EXPECT_CALL(dataIface, getServerFWVersion())
+ .WillOnce(Return("SERVER_VERSION_WAY_TOO_LONG"));
+
+ EXPECT_CALL(dataIface, getBMCFWVersion())
+ .WillOnce(Return("BMC_VERSION_WAY_TOO_LONG"));
+
+ // Use SRC words 3 through 9
+ entry.src.symptomID = {3, 4, 5, 6, 7, 8, 9};
+ ExtendedUserHeader euh{dataIface, entry, src};
+
+ EXPECT_EQ(euh.valid(), true);
+ EXPECT_EQ(euh.header().id, 0x4548); // EH
+ EXPECT_EQ(euh.header().size, baseSectionSize + 72);
+ EXPECT_EQ(euh.header().version, 0x01);
+ EXPECT_EQ(euh.header().subType, 0x00);
+ EXPECT_EQ(euh.header().componentID, 0x2000);
+
+ EXPECT_EQ(euh.flattenedSize(), baseSectionSize + 72);
+ EXPECT_EQ(euh.machineTypeModel(), "AAAA-BBB");
+ EXPECT_EQ(euh.machineSerialNumber(), "123456789ABC");
+ EXPECT_EQ(euh.serverFWVersion(), "SERVER_VERSION_");
+ EXPECT_EQ(euh.subsystemFWVersion(), "BMC_VERSION_WAY");
+
+ EXPECT_EQ(euh.symptomID(), "BD8D5678_03030310_04040404_05050505_"
+ "06060606_07070707_08080808_09090909");
+ BCDTime time;
+ EXPECT_EQ(time, euh.refTime());
+ }
+
+ {
+ MockDataInterface dataIface;
+
+ // Empty fields
+ EXPECT_CALL(dataIface, getMachineTypeModel()).WillOnce(Return(""));
+
+ EXPECT_CALL(dataIface, getMachineSerialNumber()).WillOnce(Return(""));
+
+ EXPECT_CALL(dataIface, getServerFWVersion()).WillOnce(Return(""));
+
+ EXPECT_CALL(dataIface, getBMCFWVersion()).WillOnce(Return(""));
+
+ entry.src.symptomID = {8, 9};
+ ExtendedUserHeader euh{dataIface, entry, src};
+
+ EXPECT_EQ(euh.valid(), true);
+ EXPECT_EQ(euh.header().id, 0x4548); // EH
+ EXPECT_EQ(euh.header().size, baseSectionSize + 28);
+ EXPECT_EQ(euh.header().version, 0x01);
+ EXPECT_EQ(euh.header().subType, 0x00);
+ EXPECT_EQ(euh.header().componentID, 0x2000);
+
+ EXPECT_EQ(euh.flattenedSize(), baseSectionSize + 28);
+ EXPECT_EQ(euh.machineTypeModel(), "");
+ EXPECT_EQ(euh.machineSerialNumber(), "");
+ EXPECT_EQ(euh.serverFWVersion(), "");
+ EXPECT_EQ(euh.subsystemFWVersion(), "");
+
+ EXPECT_EQ(euh.symptomID(), "BD8D5678_08080808_09090909");
+
+ BCDTime time;
+ EXPECT_EQ(time, euh.refTime());
+ }
+
+ {
+ MockDataInterface dataIface;
+
+ EXPECT_CALL(dataIface, getMachineTypeModel())
+ .WillOnce(Return("AAAA-BBB"));
+
+ EXPECT_CALL(dataIface, getMachineSerialNumber())
+ .WillOnce(Return("123456789ABC"));
+
+ EXPECT_CALL(dataIface, getServerFWVersion())
+ .WillOnce(Return("SERVER_VERSION"));
+
+ EXPECT_CALL(dataIface, getBMCFWVersion())
+ .WillOnce(Return("BMC_VERSION"));
+
+ // Way too long, will be truncated
+ entry.src.symptomID = {9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9};
+
+ ExtendedUserHeader euh{dataIface, entry, src};
+
+ EXPECT_EQ(euh.valid(), true);
+ EXPECT_EQ(euh.header().id, 0x4548); // EH
+ EXPECT_EQ(euh.header().size, baseSectionSize + 80);
+ EXPECT_EQ(euh.header().version, 0x01);
+ EXPECT_EQ(euh.header().subType, 0x00);
+ EXPECT_EQ(euh.header().componentID, 0x2000);
+
+ EXPECT_EQ(euh.flattenedSize(), baseSectionSize + 80);
+ EXPECT_EQ(euh.machineTypeModel(), "AAAA-BBB");
+ EXPECT_EQ(euh.machineSerialNumber(), "123456789ABC");
+ EXPECT_EQ(euh.serverFWVersion(), "SERVER_VERSION");
+ EXPECT_EQ(euh.subsystemFWVersion(), "BMC_VERSION");
+
+ EXPECT_EQ(euh.symptomID(),
+ "BD8D5678_09090909_09090909_09090909_09090909_09090909_"
+ "09090909_09090909_0909090");
+
+ BCDTime time;
+ EXPECT_EQ(time, euh.refTime());
+ }
+}
+
+TEST(ExtUserHeaderTest, BadDataTest)
+{
+ auto data = sectionData;
+ data.resize(20);
+
+ Stream stream{data};
+ ExtendedUserHeader euh{stream};
+
+ EXPECT_EQ(euh.valid(), false);
+}
diff --git a/test/openpower-pels/failing_mtms_test.cpp b/test/openpower-pels/failing_mtms_test.cpp
new file mode 100644
index 0000000..e377482
--- /dev/null
+++ b/test/openpower-pels/failing_mtms_test.cpp
@@ -0,0 +1,133 @@
+/**
+ * Copyright © 2019 IBM Corporation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#include "extensions/openpower-pels/failing_mtms.hpp"
+#include "mocks.hpp"
+
+#include <gtest/gtest.h>
+
+using namespace openpower::pels;
+using ::testing::Return;
+
+TEST(FailingMTMSTest, SizeTest)
+{
+ EXPECT_EQ(FailingMTMS::flattenedSize(), 28);
+}
+
+TEST(FailingMTMSTest, ConstructorTest)
+{
+ // Note: the TypeModel field is 8B, and the SN field is 12B
+ {
+ MockDataInterface dataIface;
+
+ EXPECT_CALL(dataIface, getMachineTypeModel())
+ .WillOnce(Return("AAAA-BBB"));
+
+ EXPECT_CALL(dataIface, getMachineSerialNumber())
+ .WillOnce(Return("123456789ABC"));
+
+ FailingMTMS fm{dataIface};
+
+ // Check the section header
+ EXPECT_EQ(fm.header().id, 0x4D54);
+ EXPECT_EQ(fm.header().size, FailingMTMS::flattenedSize());
+ EXPECT_EQ(fm.header().version, 0x01);
+ EXPECT_EQ(fm.header().subType, 0x00);
+ EXPECT_EQ(fm.header().componentID, 0x2000);
+
+ EXPECT_EQ(fm.getMachineTypeModel(), "AAAA-BBB");
+ EXPECT_EQ(fm.getMachineSerialNumber(), "123456789ABC");
+ }
+
+ // longer than the max - will truncate
+ {
+ MockDataInterface dataIface;
+
+ EXPECT_CALL(dataIface, getMachineTypeModel())
+ .WillOnce(Return("AAAA-BBBTOOLONG"));
+
+ EXPECT_CALL(dataIface, getMachineSerialNumber())
+ .WillOnce(Return("123456789ABCTOOLONG"));
+
+ FailingMTMS fm{dataIface};
+
+ EXPECT_EQ(fm.getMachineTypeModel(), "AAAA-BBB");
+ EXPECT_EQ(fm.getMachineSerialNumber(), "123456789ABC");
+ }
+
+ // shorter than the max
+ {
+ MockDataInterface dataIface;
+
+ EXPECT_CALL(dataIface, getMachineTypeModel()).WillOnce(Return("A"));
+
+ EXPECT_CALL(dataIface, getMachineSerialNumber()).WillOnce(Return("1"));
+
+ FailingMTMS fm{dataIface};
+
+ EXPECT_EQ(fm.getMachineTypeModel(), "A");
+ EXPECT_EQ(fm.getMachineSerialNumber(), "1");
+ }
+}
+
+TEST(FailingMTMSTest, StreamConstructorTest)
+{
+ std::vector<uint8_t> data{0x4D, 0x54, 0x00, 0x1C, 0x01, 0x00, 0x20,
+ 0x00, 'T', 'T', 'T', 'T', '-', 'M',
+ 'M', 'M', '1', '2', '3', '4', '5',
+ '6', '7', '8', '9', 'A', 'B', 'C'};
+ Stream stream{data};
+ FailingMTMS fm{stream};
+
+ EXPECT_EQ(fm.valid(), true);
+
+ EXPECT_EQ(fm.header().id, 0x4D54);
+ EXPECT_EQ(fm.header().size, FailingMTMS::flattenedSize());
+ EXPECT_EQ(fm.header().version, 0x01);
+ EXPECT_EQ(fm.header().subType, 0x00);
+ EXPECT_EQ(fm.header().componentID, 0x2000);
+
+ EXPECT_EQ(fm.getMachineTypeModel(), "TTTT-MMM");
+ EXPECT_EQ(fm.getMachineSerialNumber(), "123456789ABC");
+}
+
+TEST(FailingMTMSTest, BadStreamConstructorTest)
+{
+ // too short
+ std::vector<uint8_t> data{
+ 0x4D, 0x54, 0x00, 0x1C, 0x01, 0x00, 0x20, 0x00, 'T', 'T',
+ };
+ Stream stream{data};
+ FailingMTMS fm{stream};
+
+ EXPECT_EQ(fm.valid(), false);
+}
+
+TEST(FailingMTMSTest, FlattenTest)
+{
+ std::vector<uint8_t> data{0x4D, 0x54, 0x00, 0x1C, 0x01, 0x00, 0x20,
+ 0x00, 'T', 'T', 'T', 'T', '-', 'M',
+ 'M', 'M', '1', '2', '3', '4', '5',
+ '6', '7', '8', '9', 'A', 'B', 'C'};
+ Stream stream{data};
+ FailingMTMS fm{stream};
+
+ // flatten and check results
+ std::vector<uint8_t> newData;
+ Stream newStream{newData};
+
+ fm.flatten(newStream);
+ EXPECT_EQ(data, newData);
+}
diff --git a/test/openpower-pels/fru_identity_test.cpp b/test/openpower-pels/fru_identity_test.cpp
new file mode 100644
index 0000000..a6839b8
--- /dev/null
+++ b/test/openpower-pels/fru_identity_test.cpp
@@ -0,0 +1,89 @@
+/**
+ * Copyright © 2019 IBM Corporation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#include "extensions/openpower-pels/fru_identity.hpp"
+
+#include <gtest/gtest.h>
+
+using namespace openpower::pels;
+using namespace openpower::pels::src;
+
+// Unflatten a FRUIdentity that is a HW FRU callout
+TEST(FRUIdentityTest, TestHardwareFRU)
+{
+ // Has PN, SN, CCIN
+ std::vector<uint8_t> data{'I', 'D', 0x1C, 0x1D, // type, size, flags
+ '1', '2', '3', '4', // PN
+ '5', '6', '7', 0x00, 'A', 'A', 'A', 'A', // CCIN
+ '1', '2', '3', '4', '5', '6', '7', '8', // SN
+ '9', 'A', 'B', 'C'};
+
+ Stream stream{data};
+
+ FRUIdentity fru{stream};
+
+ EXPECT_EQ(fru.failingComponentType(), FRUIdentity::hardwareFRU);
+ EXPECT_EQ(fru.flattenedSize(), data.size());
+
+ EXPECT_EQ(fru.getPN().value(), "1234567");
+ EXPECT_EQ(fru.getCCIN().value(), "AAAA");
+ EXPECT_EQ(fru.getSN().value(), "123456789ABC");
+ EXPECT_FALSE(fru.getMaintProc());
+
+ // Flatten
+ std::vector<uint8_t> newData;
+ Stream newStream{newData};
+ fru.flatten(newStream);
+ EXPECT_EQ(data, newData);
+}
+
+// Unflatten a FRUIdentity that is a Maintenance Procedure callout
+TEST(FRUIdentityTest, TestMaintProcedure)
+{
+ // Only contains the maintenance procedure
+ std::vector<uint8_t> data{
+ 0x49, 0x44, 0x0C, 0x42, // type, size, flags
+ '1', '2', '3', '4', '5', '6', '7', 0x00 // Procedure
+ };
+
+ Stream stream{data};
+
+ FRUIdentity fru{stream};
+
+ EXPECT_EQ(fru.failingComponentType(), FRUIdentity::maintenanceProc);
+ EXPECT_EQ(fru.flattenedSize(), data.size());
+
+ EXPECT_EQ(fru.getMaintProc().value(), "1234567");
+ EXPECT_FALSE(fru.getPN());
+ EXPECT_FALSE(fru.getCCIN());
+ EXPECT_FALSE(fru.getSN());
+
+ // Flatten
+ std::vector<uint8_t> newData;
+ Stream newStream{newData};
+ fru.flatten(newStream);
+ EXPECT_EQ(data, newData);
+}
+
+// Try to unflatten garbage data
+TEST(FRUIdentityTest, BadDataTest)
+{
+ std::vector<uint8_t> data{0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0xFF};
+
+ Stream stream{data};
+
+ EXPECT_THROW(FRUIdentity fru{stream}, std::out_of_range);
+}
diff --git a/test/openpower-pels/generic_section_test.cpp b/test/openpower-pels/generic_section_test.cpp
new file mode 100644
index 0000000..7c80894
--- /dev/null
+++ b/test/openpower-pels/generic_section_test.cpp
@@ -0,0 +1,64 @@
+/**
+ * Copyright © 2019 IBM Corporation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#include "extensions/openpower-pels/generic.hpp"
+#include "pel_utils.hpp"
+
+#include <gtest/gtest.h>
+
+using namespace openpower::pels;
+
+TEST(GenericSectionTest, UnflattenFlattenTest)
+{
+ // Use the private header data
+ auto data = pelDataFactory(TestPELType::privateHeaderSection);
+
+ Stream stream(data);
+ Generic section(stream);
+
+ EXPECT_EQ(section.header().id, 0x5048);
+ EXPECT_EQ(section.header().size, data.size());
+ EXPECT_EQ(section.header().version, 0x01);
+ EXPECT_EQ(section.header().subType, 0x02);
+ EXPECT_EQ(section.header().componentID, 0x0304);
+
+ const auto& sectionData = section.data();
+
+ // The data itself starts after the header
+ EXPECT_EQ(sectionData.size(), data.size() - 8);
+
+ for (size_t i = 0; i < sectionData.size(); i++)
+ {
+ EXPECT_EQ(sectionData[i], (data)[i + 8]);
+ }
+
+ // Now flatten
+ std::vector<uint8_t> newData;
+ Stream newStream(newData);
+ section.flatten(newStream);
+
+ EXPECT_EQ(data, newData);
+}
+
+TEST(GenericSectionTest, BadDataTest)
+{
+ // Use the private header data to start with
+ auto data = pelDataFactory(TestPELType::privateHeaderSection);
+ data.resize(4);
+
+ Stream stream(data);
+ Generic section(stream);
+ ASSERT_FALSE(section.valid());
+}
diff --git a/test/openpower-pels/host_notifier_test.cpp b/test/openpower-pels/host_notifier_test.cpp
new file mode 100644
index 0000000..c51d560
--- /dev/null
+++ b/test/openpower-pels/host_notifier_test.cpp
@@ -0,0 +1,680 @@
+/**
+ * Copyright © 2019 IBM Corporation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#include "extensions/openpower-pels/data_interface.hpp"
+#include "extensions/openpower-pels/host_notifier.hpp"
+#include "mocks.hpp"
+#include "pel_utils.hpp"
+
+#include <fcntl.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+
+#include <chrono>
+
+#include <gtest/gtest.h>
+
+using namespace openpower::pels;
+using ::testing::_;
+using ::testing::Invoke;
+using ::testing::NiceMock;
+using ::testing::Return;
+namespace fs = std::filesystem;
+using namespace std::chrono;
+
+const size_t actionFlags0Offset = 66;
+const size_t actionFlags1Offset = 67;
+
+class HostNotifierTest : public CleanPELFiles
+{
+ public:
+ HostNotifierTest() : repo(repoPath)
+ {
+ auto r = sd_event_default(&event);
+ EXPECT_TRUE(r >= 0);
+
+ hostIface =
+ std::make_unique<NiceMock<MockHostInterface>>(event, dataIface);
+
+ mockHostIface = reinterpret_cast<MockHostInterface*>(hostIface.get());
+
+ auto send = [this](uint32_t id, uint32_t size) {
+ return this->mockHostIface->send(0);
+ };
+
+ // Unless otherwise specified, sendNewLogCmd should always pass.
+ ON_CALL(*mockHostIface, sendNewLogCmd(_, _))
+ .WillByDefault(Invoke(send));
+ }
+
+ ~HostNotifierTest()
+ {
+ sd_event_unref(event);
+ }
+
+ protected:
+ sd_event* event;
+ Repository repo;
+ NiceMock<MockDataInterface> dataIface;
+ std::unique_ptr<HostInterface> hostIface;
+ MockHostInterface* mockHostIface;
+};
+
+/**
+ * @brief Create PEL with the specified action flags
+ *
+ * @param[in] actionFlagsMask - Optional action flags to use
+ *
+ * @return std::unique_ptr<PEL>
+ */
+std::unique_ptr<PEL> makePEL(uint16_t actionFlagsMask = 0)
+{
+ static uint32_t obmcID = 1;
+ auto data = pelDataFactory(TestPELType::pelSimple);
+
+ data[actionFlags0Offset] |= actionFlagsMask >> 8;
+ data[actionFlags1Offset] |= actionFlagsMask & 0xFF;
+
+ auto pel = std::make_unique<PEL>(data, obmcID++);
+ pel->assignID();
+ pel->setCommitTime();
+ return pel;
+}
+
+/**
+ * @brief Run an iteration of the event loop.
+ *
+ * An event loop is used for:
+ * 1) timer expiration callbacks
+ * 2) Dispatches
+ * 3) host interface receive callbacks
+ *
+ * @param[in] event - The event object
+ * @param[in] numEvents - number of times to call Event::run()
+ * @param[in] timeout - timeout value for run()
+ */
+void runEvents(sdeventplus::Event& event, size_t numEvents,
+ milliseconds timeout = milliseconds(1))
+{
+ for (size_t i = 0; i < numEvents; i++)
+ {
+ event.run(timeout);
+ }
+}
+
+// Test that host state change callbacks work
+TEST_F(HostNotifierTest, TestHostStateChange)
+{
+ bool hostState = false;
+ bool called = false;
+ DataInterfaceBase::HostStateChangeFunc func = [&hostState,
+ &called](bool state) {
+ hostState = state;
+ called = true;
+ };
+
+ dataIface.subscribeToHostStateChange("test", func);
+
+ // callback called
+ dataIface.changeHostState(true);
+ EXPECT_TRUE(called);
+ EXPECT_TRUE(hostState);
+
+ // No change, not called
+ called = false;
+ dataIface.changeHostState(true);
+ EXPECT_FALSE(called);
+
+ // Called again
+ dataIface.changeHostState(false);
+ EXPECT_FALSE(hostState);
+ EXPECT_TRUE(called);
+
+ // Shouldn't get called after an unsubscribe
+ dataIface.unsubscribeFromHostStateChange("test");
+
+ called = false;
+
+ dataIface.changeHostState(true);
+ EXPECT_FALSE(called);
+}
+
+// Test dealing with how acked PELs are put on the
+// notification queue.
+TEST_F(HostNotifierTest, TestPolicyAckedPEL)
+{
+ HostNotifier notifier{repo, dataIface, std::move(hostIface)};
+
+ auto pel = makePEL();
+ repo.add(pel);
+
+ // This is required
+ EXPECT_TRUE(notifier.enqueueRequired(pel->id()));
+ EXPECT_TRUE(notifier.notifyRequired(pel->id()));
+
+ // Not in the repo
+ EXPECT_FALSE(notifier.enqueueRequired(42));
+ EXPECT_FALSE(notifier.notifyRequired(42));
+
+ // Now set this PEL to host acked
+ repo.setPELHostTransState(pel->id(), TransmissionState::acked);
+
+ // Since it's acked, doesn't need to be enqueued or transmitted
+ EXPECT_FALSE(notifier.enqueueRequired(pel->id()));
+ EXPECT_FALSE(notifier.notifyRequired(pel->id()));
+}
+
+// Test the 'don't report' PEL flag
+TEST_F(HostNotifierTest, TestPolicyDontReport)
+{
+ HostNotifier notifier{repo, dataIface, std::move(hostIface)};
+
+ // dontReportToHostFlagBit
+ auto pel = makePEL(0x1000);
+
+ // Double check the action flag is still set
+ std::bitset<16> actionFlags = pel->userHeader().actionFlags();
+ EXPECT_TRUE(actionFlags.test(dontReportToHostFlagBit));
+
+ repo.add(pel);
+
+ // Don't need to send this to the host
+ EXPECT_FALSE(notifier.enqueueRequired(pel->id()));
+}
+
+// Test that hidden PELs need notification when there
+// is no HMC.
+TEST_F(HostNotifierTest, TestPolicyHiddenNoHMC)
+{
+ HostNotifier notifier{repo, dataIface, std::move(hostIface)};
+
+ // hiddenFlagBit
+ auto pel = makePEL(0x4000);
+
+ // Double check the action flag is still set
+ std::bitset<16> actionFlags = pel->userHeader().actionFlags();
+ EXPECT_TRUE(actionFlags.test(hiddenFlagBit));
+
+ repo.add(pel);
+
+ // Still need to enqueue this
+ EXPECT_TRUE(notifier.enqueueRequired(pel->id()));
+
+ // Still need to send it
+ EXPECT_TRUE(notifier.notifyRequired(pel->id()));
+}
+
+// Don't need to enqueue a hidden log already acked by the HMC
+TEST_F(HostNotifierTest, TestPolicyHiddenWithHMCAcked)
+{
+ HostNotifier notifier{repo, dataIface, std::move(hostIface)};
+
+ // hiddenFlagBit
+ auto pel = makePEL(0x4000);
+
+ // Double check the action flag is still set
+ std::bitset<16> actionFlags = pel->userHeader().actionFlags();
+ EXPECT_TRUE(actionFlags.test(hiddenFlagBit));
+
+ repo.add(pel);
+
+ // No HMC yet, so required
+ EXPECT_TRUE(notifier.enqueueRequired(pel->id()));
+
+ repo.setPELHMCTransState(pel->id(), TransmissionState::acked);
+
+ // Not required anymore
+ EXPECT_FALSE(notifier.enqueueRequired(pel->id()));
+}
+
+// Test that changing the HMC manage status affects
+// the policy with hidden log notification.
+TEST_F(HostNotifierTest, TestPolicyHiddenWithHMCManaged)
+{
+ HostNotifier notifier{repo, dataIface, std::move(hostIface)};
+
+ // hiddenFlagBit
+ auto pel = makePEL(0x4000);
+
+ repo.add(pel);
+
+ // The first time, the HMC managed is false
+ EXPECT_TRUE(notifier.notifyRequired(pel->id()));
+
+ dataIface.setHMCManaged(true);
+
+ // This time, HMC managed is true so no need to notify
+ EXPECT_FALSE(notifier.notifyRequired(pel->id()));
+}
+
+// Test that PELs are enqueued on startup
+TEST_F(HostNotifierTest, TestStartup)
+{
+ // Give the repo 10 PELs to start with
+ for (int i = 0; i < 10; i++)
+ {
+ auto pel = makePEL();
+ repo.add(pel);
+ }
+
+ HostNotifier notifier{repo, dataIface, std::move(hostIface)};
+
+ ASSERT_EQ(notifier.queueSize(), 10);
+
+ // Now add 10 more after the notifier is watching
+ for (int i = 0; i < 10; i++)
+ {
+ auto pel = makePEL();
+ repo.add(pel);
+ }
+
+ ASSERT_EQ(notifier.queueSize(), 20);
+}
+
+// Test the simple path were PELs get sent to the host
+TEST_F(HostNotifierTest, TestSendCmd)
+{
+ sdeventplus::Event sdEvent{event};
+
+ HostNotifier notifier{repo, dataIface, std::move(hostIface)};
+
+ // Add a PEL with the host off
+ auto pel = makePEL();
+ repo.add(pel);
+
+ EXPECT_EQ(notifier.queueSize(), 1);
+
+ dataIface.changeHostState(true);
+
+ runEvents(sdEvent, 1);
+
+ // It was sent up
+ EXPECT_EQ(mockHostIface->numCmdsProcessed(), 1);
+ EXPECT_EQ(notifier.queueSize(), 0);
+
+ // Verify the state was written to the PEL.
+ Repository::LogID id{Repository::LogID::Pel{pel->id()}};
+ auto data = repo.getPELData(id);
+ PEL pelFromRepo{*data};
+ EXPECT_EQ(pelFromRepo.hostTransmissionState(), TransmissionState::sent);
+
+ // Add a few more PELs. They will get sent.
+ pel = makePEL();
+ repo.add(pel);
+
+ // Dispatch it by hitting the event loop (no commands sent yet)
+ // Don't need to test this step discretely in the future
+ runEvents(sdEvent, 1);
+ EXPECT_EQ(mockHostIface->numCmdsProcessed(), 1);
+ EXPECT_EQ(notifier.queueSize(), 0);
+
+ // Send the command
+ runEvents(sdEvent, 1);
+
+ EXPECT_EQ(mockHostIface->numCmdsProcessed(), 2);
+ EXPECT_EQ(notifier.queueSize(), 0);
+
+ pel = makePEL();
+ repo.add(pel);
+
+ // dispatch and process the command
+ runEvents(sdEvent, 2);
+
+ EXPECT_EQ(mockHostIface->numCmdsProcessed(), 3);
+ EXPECT_EQ(notifier.queueSize(), 0);
+}
+
+// Test that if the class is created with the host up,
+// it will send PELs
+TEST_F(HostNotifierTest, TestStartAfterHostUp)
+{
+ // Add PELs right away
+ auto pel = makePEL();
+ repo.add(pel);
+ pel = makePEL();
+ repo.add(pel);
+
+ sdeventplus::Event sdEvent{event};
+
+ // Create the HostNotifier class with the host already up
+ dataIface.changeHostState(true);
+ HostNotifier notifier{repo, dataIface, std::move(hostIface)};
+
+ // It should start sending PELs right away
+ runEvents(sdEvent, 2);
+
+ EXPECT_EQ(mockHostIface->numCmdsProcessed(), 2);
+ EXPECT_EQ(notifier.queueSize(), 0);
+}
+
+// Test that a single failure will cause a retry
+TEST_F(HostNotifierTest, TestHostRetry)
+{
+ sdeventplus::Event sdEvent{event};
+
+ HostNotifier notifier{repo, dataIface, std::move(hostIface)};
+
+ auto sendFailure = [this](uint32_t id, uint32_t size) {
+ return this->mockHostIface->send(1);
+ };
+ auto sendSuccess = [this](uint32_t id, uint32_t size) {
+ return this->mockHostIface->send(0);
+ };
+
+ EXPECT_CALL(*mockHostIface, sendNewLogCmd(_, _))
+ .WillOnce(Invoke(sendFailure))
+ .WillOnce(Invoke(sendSuccess))
+ .WillOnce(Invoke(sendSuccess));
+
+ dataIface.changeHostState(true);
+
+ auto pel = makePEL();
+ repo.add(pel);
+
+ // Dispatch and handle the command
+ runEvents(sdEvent, 2);
+
+ // The command failed, so the queue isn't empty
+ EXPECT_EQ(mockHostIface->numCmdsProcessed(), 1);
+ EXPECT_EQ(notifier.queueSize(), 1);
+
+ // Run the events again to let the timer expire and the
+ // command to be retried, which will be successful.
+ runEvents(sdEvent, 2, mockHostIface->getReceiveRetryDelay());
+
+ EXPECT_EQ(mockHostIface->numCmdsProcessed(), 2);
+ EXPECT_EQ(notifier.queueSize(), 0);
+
+ // This one should pass with no problems
+ pel = makePEL();
+ repo.add(pel);
+
+ // Dispatch and handle the command
+ runEvents(sdEvent, 2);
+
+ EXPECT_EQ(mockHostIface->numCmdsProcessed(), 3);
+ EXPECT_EQ(notifier.queueSize(), 0);
+}
+
+// Test that all commands fail and notifier will give up
+TEST_F(HostNotifierTest, TestHardFailure)
+{
+ sdeventplus::Event sdEvent{event};
+
+ HostNotifier notifier{repo, dataIface, std::move(hostIface)};
+
+ // Every call will fail
+ auto sendFailure = [this](uint32_t id, uint32_t size) {
+ return this->mockHostIface->send(1);
+ };
+
+ EXPECT_CALL(*mockHostIface, sendNewLogCmd(_, _))
+ .WillRepeatedly(Invoke(sendFailure));
+
+ dataIface.changeHostState(true);
+
+ auto pel = makePEL();
+ repo.add(pel);
+
+ // Clock more retries than necessary
+ runEvents(sdEvent, 40, mockHostIface->getReceiveRetryDelay());
+
+ // Should have stopped after the 15 Tries
+ EXPECT_EQ(mockHostIface->numCmdsProcessed(), 15);
+ EXPECT_EQ(notifier.queueSize(), 1);
+
+ // Now add another PEL, and it should start trying again
+ // though it will also eventually give up
+ pel = makePEL();
+ repo.add(pel);
+
+ runEvents(sdEvent, 40, mockHostIface->getReceiveRetryDelay());
+
+ // Tried an additional 15 times
+ EXPECT_EQ(mockHostIface->numCmdsProcessed(), 30);
+ EXPECT_EQ(notifier.queueSize(), 2);
+}
+
+// Cancel an in progress command
+TEST_F(HostNotifierTest, TestCancelCmd)
+{
+ sdeventplus::Event sdEvent{event};
+ HostNotifier notifier{repo, dataIface, std::move(hostIface)};
+
+ dataIface.changeHostState(true);
+
+ // Add and send one PEL, but don't enter the event loop
+ // so the receive function can't run.
+ auto pel = makePEL();
+ repo.add(pel);
+
+ // Not dispatched yet
+ EXPECT_EQ(notifier.queueSize(), 1);
+
+ // Dispatch it
+ runEvents(sdEvent, 1);
+
+ // It was sent and off the queue
+ EXPECT_EQ(notifier.queueSize(), 0);
+
+ // This will cancel the receive
+ dataIface.changeHostState(false);
+
+ // Back on the queue
+ EXPECT_EQ(notifier.queueSize(), 1);
+
+ // Turn the host back on and make sure
+ // commands will work again
+ dataIface.changeHostState(true);
+
+ runEvents(sdEvent, 1);
+
+ EXPECT_EQ(mockHostIface->numCmdsProcessed(), 1);
+ EXPECT_EQ(notifier.queueSize(), 0);
+}
+
+// Test that acking a PEL persist across power cycles
+TEST_F(HostNotifierTest, TestPowerCycleAndAcks)
+{
+ sdeventplus::Event sdEvent{event};
+
+ HostNotifier notifier{repo, dataIface, std::move(hostIface)};
+
+ // Add 2 PELs with host off
+ auto pel = makePEL();
+ repo.add(pel);
+ auto id1 = pel->id();
+
+ pel = makePEL();
+ repo.add(pel);
+ auto id2 = pel->id();
+
+ dataIface.changeHostState(true);
+
+ runEvents(sdEvent, 2);
+
+ // The were both sent.
+ EXPECT_EQ(mockHostIface->numCmdsProcessed(), 2);
+ EXPECT_EQ(notifier.queueSize(), 0);
+
+ dataIface.changeHostState(false);
+
+ // Those PELs weren't acked, so they will get sent again
+ EXPECT_EQ(notifier.queueSize(), 2);
+
+ // Power back on and send them again
+ dataIface.changeHostState(true);
+ runEvents(sdEvent, 2);
+
+ EXPECT_EQ(mockHostIface->numCmdsProcessed(), 4);
+ EXPECT_EQ(notifier.queueSize(), 0);
+
+ // Ack them and verify the state in the PEL.
+ notifier.ackPEL(id1);
+ notifier.ackPEL(id2);
+
+ Repository::LogID id{Repository::LogID::Pel{id1}};
+ auto data = repo.getPELData(id);
+ PEL pelFromRepo1{*data};
+ EXPECT_EQ(pelFromRepo1.hostTransmissionState(), TransmissionState::acked);
+
+ id.pelID.id = id2;
+ data = repo.getPELData(id);
+ PEL pelFromRepo2{*data};
+ EXPECT_EQ(pelFromRepo2.hostTransmissionState(), TransmissionState::acked);
+
+ // Power back off, and they should't get re-added
+ dataIface.changeHostState(false);
+
+ EXPECT_EQ(notifier.queueSize(), 0);
+}
+
+// Test the host full condition
+TEST_F(HostNotifierTest, TestHostFull)
+{
+ // The full interaction with the host is:
+ // BMC: new PEL available
+ // Host: ReadPELFile (not modeled here)
+ // Host: Ack(id) (if not full), or HostFull(id)
+ // BMC: if full and any new PELs come in, don't sent them
+ // Start a timer and try again
+ // Host responds with either Ack or full
+ // and repeat
+
+ sdeventplus::Event sdEvent{event};
+ HostNotifier notifier{repo, dataIface, std::move(hostIface)};
+
+ dataIface.changeHostState(true);
+
+ // Add and dispatch/send one PEL
+ auto pel = makePEL();
+ auto id = pel->id();
+ repo.add(pel);
+ runEvents(sdEvent, 2);
+
+ EXPECT_EQ(mockHostIface->numCmdsProcessed(), 1);
+ EXPECT_EQ(notifier.queueSize(), 0);
+
+ // Host is full
+ notifier.setHostFull(id);
+
+ // It goes back on the queue
+ EXPECT_EQ(notifier.queueSize(), 1);
+
+ // The transmission state goes back to new
+ Repository::LogID i{Repository::LogID::Pel{id}};
+ auto data = repo.getPELData(i);
+ PEL pelFromRepo{*data};
+ EXPECT_EQ(pelFromRepo.hostTransmissionState(), TransmissionState::newPEL);
+
+ // Clock it, nothing should be sent still.
+ runEvents(sdEvent, 1);
+
+ EXPECT_EQ(mockHostIface->numCmdsProcessed(), 1);
+ EXPECT_EQ(notifier.queueSize(), 1);
+
+ // Add another PEL and clock it, still nothing sent
+ pel = makePEL();
+ repo.add(pel);
+ runEvents(sdEvent, 2);
+ EXPECT_EQ(mockHostIface->numCmdsProcessed(), 1);
+ EXPECT_EQ(notifier.queueSize(), 2);
+
+ // Let the host full timer expire to trigger a retry.
+ // Add some extra event passes just to be sure nothing new is sent.
+ runEvents(sdEvent, 5, mockHostIface->getHostFullRetryDelay());
+
+ // The timer expiration will send just the 1, not both
+ EXPECT_EQ(mockHostIface->numCmdsProcessed(), 2);
+ EXPECT_EQ(notifier.queueSize(), 1);
+
+ // Host still full
+ notifier.setHostFull(id);
+
+ // Let the host full timer attempt again
+ runEvents(sdEvent, 2, mockHostIface->getHostFullRetryDelay());
+ EXPECT_EQ(mockHostIface->numCmdsProcessed(), 3);
+
+ // Add yet another PEL with the retry timer expired.
+ // It shouldn't get sent out.
+ pel = makePEL();
+ repo.add(pel);
+ runEvents(sdEvent, 2);
+ EXPECT_EQ(mockHostIface->numCmdsProcessed(), 3);
+
+ // The last 2 PELs still on the queue
+ EXPECT_EQ(notifier.queueSize(), 2);
+
+ // Host no longer full, it finally acks the first PEL
+ notifier.ackPEL(id);
+
+ // Now the remaining 2 PELs will be dispatched
+ runEvents(sdEvent, 3);
+
+ EXPECT_EQ(mockHostIface->numCmdsProcessed(), 5);
+ EXPECT_EQ(notifier.queueSize(), 0);
+}
+
+// Test when the host says it was send a malformed PEL
+TEST_F(HostNotifierTest, TestBadPEL)
+{
+ sdeventplus::Event sdEvent{event};
+
+ {
+ Repository repo1{repoPath};
+ HostNotifier notifier{repo1, dataIface, std::move(hostIface)};
+
+ dataIface.changeHostState(true);
+
+ // Add a PEL and dispatch and send it
+ auto pel = makePEL();
+ auto id = pel->id();
+ repo1.add(pel);
+
+ runEvents(sdEvent, 2);
+ EXPECT_EQ(mockHostIface->numCmdsProcessed(), 1);
+ EXPECT_EQ(notifier.queueSize(), 0);
+
+ // The host rejected it.
+ notifier.setBadPEL(id);
+
+ // Doesn't go back on the queue
+ EXPECT_EQ(notifier.queueSize(), 0);
+
+ // Check the state was saved in the PEL itself
+ Repository::LogID i{Repository::LogID::Pel{id}};
+ auto data = repo1.getPELData(i);
+ PEL pelFromRepo{*data};
+ EXPECT_EQ(pelFromRepo.hostTransmissionState(),
+ TransmissionState::badPEL);
+
+ dataIface.changeHostState(false);
+
+ // Ensure it doesn't go back on the queue on a power cycle
+ EXPECT_EQ(notifier.queueSize(), 0);
+ }
+
+ // Now restore the repo, and make sure it doesn't come back
+ {
+ Repository repo1{repoPath};
+
+ std::unique_ptr<HostInterface> hostIface1 =
+ std::make_unique<MockHostInterface>(event, dataIface);
+
+ HostNotifier notifier{repo1, dataIface, std::move(hostIface1)};
+
+ EXPECT_EQ(notifier.queueSize(), 0);
+ }
+}
diff --git a/test/openpower-pels/json_utils_test.cpp b/test/openpower-pels/json_utils_test.cpp
new file mode 100644
index 0000000..60ca00d
--- /dev/null
+++ b/test/openpower-pels/json_utils_test.cpp
@@ -0,0 +1,50 @@
+/**
+ * Copyright © 2019 IBM Corporation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#include "extensions/openpower-pels/json_utils.hpp"
+
+#include <gtest/gtest.h>
+
+using namespace openpower::pels;
+
+TEST(JsonUtilsTest, TrimEndTest)
+{
+ std::string testStr("Test string 1");
+ EXPECT_EQ(trimEnd(testStr), "Test string 1");
+ testStr = "Test string 2 ";
+ EXPECT_EQ(trimEnd(testStr), "Test string 2");
+ testStr = " Test string 3 ";
+ EXPECT_EQ(trimEnd(testStr), " Test string 3");
+}
+
+TEST(JsonUtilsTest, NumberToStringTest)
+{
+ size_t number = 123;
+ EXPECT_EQ(getNumberString("%d", number), "123");
+ EXPECT_EQ(getNumberString("%03X", number), "07B");
+ EXPECT_EQ(getNumberString("0x%X", number), "0x7B");
+ ASSERT_EXIT((getNumberString("%123", number), exit(0)),
+ ::testing::KilledBySignal(SIGSEGV), ".*");
+}
+
+TEST(JsonUtilsTest, JsonInsertTest)
+{
+ std::string json;
+ jsonInsert(json, "Key", "Value1", 1);
+ EXPECT_EQ(json, " \"Key\": \"Value1\",\n");
+ jsonInsert(json, "Keyxxxxxxxxxxxxxxxxxxxxxxxxxx", "Value2", 2);
+ EXPECT_EQ(json, " \"Key\": \"Value1\",\n"
+ " \"Keyxxxxxxxxxxxxxxxxxxxxxxxxxx\": \"Value2\",\n");
+}
diff --git a/test/openpower-pels/log_id_test.cpp b/test/openpower-pels/log_id_test.cpp
new file mode 100644
index 0000000..93dff83
--- /dev/null
+++ b/test/openpower-pels/log_id_test.cpp
@@ -0,0 +1,57 @@
+/**
+ * Copyright © 2019 IBM Corporation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#include "extensions/openpower-pels/log_id.hpp"
+#include "extensions/openpower-pels/paths.hpp"
+
+#include <arpa/inet.h>
+
+#include <filesystem>
+
+#include <gtest/gtest.h>
+
+using namespace openpower::pels;
+namespace fs = std::filesystem;
+
+TEST(LogIdTest, TimeBasedIDTest)
+{
+ uint32_t lastID = 0;
+ for (int i = 0; i < 10; i++)
+ {
+ auto id = detail::getTimeBasedLogID();
+
+ EXPECT_EQ(id & 0xFF000000, 0x50000000);
+ EXPECT_NE(id, lastID);
+ lastID = id;
+ }
+}
+
+TEST(LogIdTest, IDTest)
+{
+ EXPECT_EQ(generatePELID(), 0x50000001);
+ EXPECT_EQ(generatePELID(), 0x50000002);
+ EXPECT_EQ(generatePELID(), 0x50000003);
+ EXPECT_EQ(generatePELID(), 0x50000004);
+ EXPECT_EQ(generatePELID(), 0x50000005);
+ EXPECT_EQ(generatePELID(), 0x50000006);
+
+ auto backingFile = getPELIDFile();
+ fs::remove(backingFile);
+ EXPECT_EQ(generatePELID(), 0x50000001);
+ EXPECT_EQ(generatePELID(), 0x50000002);
+ EXPECT_EQ(generatePELID(), 0x50000003);
+
+ fs::remove_all(fs::path{backingFile}.parent_path());
+}
diff --git a/test/openpower-pels/mocks.hpp b/test/openpower-pels/mocks.hpp
new file mode 100644
index 0000000..8b055dd
--- /dev/null
+++ b/test/openpower-pels/mocks.hpp
@@ -0,0 +1,239 @@
+#include "extensions/openpower-pels/data_interface.hpp"
+#include "extensions/openpower-pels/host_interface.hpp"
+
+#include <fcntl.h>
+
+#include <filesystem>
+#include <sdeventplus/source/io.hpp>
+
+#include <gmock/gmock.h>
+
+namespace openpower
+{
+namespace pels
+{
+
+class MockDataInterface : public DataInterfaceBase
+{
+ public:
+ MockDataInterface()
+ {
+ }
+ MOCK_METHOD(std::string, getMachineTypeModel, (), (const override));
+ MOCK_METHOD(std::string, getMachineSerialNumber, (), (const override));
+ MOCK_METHOD(std::string, getServerFWVersion, (), (const override));
+ MOCK_METHOD(std::string, getBMCFWVersion, (), (const override));
+ MOCK_METHOD(std::string, getBMCFWVersionID, (), (const override));
+
+ void changeHostState(bool newState)
+ {
+ setHostState(newState);
+ }
+
+ void setHMCManaged(bool managed)
+ {
+ _hmcManaged = managed;
+ }
+};
+
+/**
+ * @brief The mock HostInterface class
+ *
+ * This replaces the PLDM calls with a FIFO for the asynchronous
+ * responses.
+ */
+class MockHostInterface : public HostInterface
+{
+ public:
+ /**
+ * @brief Constructor
+ *
+ * @param[in] event - The sd_event object
+ * @param[in] dataIface - The DataInterface class
+ */
+ MockHostInterface(sd_event* event, DataInterfaceBase& dataIface) :
+ HostInterface(event, dataIface)
+ {
+ char templ[] = "/tmp/cmdfifoXXXXXX";
+ std::filesystem::path dir = mkdtemp(templ);
+ _fifo = dir / "fifo";
+ }
+
+ /**
+ * @brief Destructor
+ */
+ virtual ~MockHostInterface()
+ {
+ std::filesystem::remove_all(_fifo.parent_path());
+ }
+
+ MOCK_METHOD(CmdStatus, sendNewLogCmd, (uint32_t, uint32_t), (override));
+
+ /**
+ * @brief Cancels waiting for a command response
+ */
+ virtual void cancelCmd() override
+ {
+ _inProgress = false;
+ _source = nullptr;
+ }
+
+ /**
+ * @brief Returns the amount of time to wait before retrying after
+ * a failed send command.
+ *
+ * @return milliseconds - The amount of time to wait
+ */
+ virtual std::chrono::milliseconds getSendRetryDelay() const override
+ {
+ return std::chrono::milliseconds(2);
+ }
+
+ /**
+ * @brief Returns the amount of time to wait before retrying after
+ * a command receive.
+ *
+ * @return milliseconds - The amount of time to wait
+ */
+ virtual std::chrono::milliseconds getReceiveRetryDelay() const override
+ {
+ return std::chrono::milliseconds(2);
+ }
+
+ /**
+ * @brief Returns the amount of time to wait before retrying if the
+ * host firmware's PEL storage was full and it can't store
+ * any more logs until it is freed up somehow.
+ *
+ * @return milliseconds - The amount of time to wait
+ */
+ virtual std::chrono::milliseconds getHostFullRetryDelay() const override
+ {
+ return std::chrono::milliseconds(400);
+ }
+
+ /**
+ * @brief Returns the number of commands processed
+ */
+ size_t numCmdsProcessed() const
+ {
+ return _cmdsProcessed;
+ }
+
+ /**
+ * @brief Writes the data passed in to the FIFO
+ *
+ * @param[in] hostResponse - use a 0 to indicate success
+ *
+ * @return CmdStatus - success or failure
+ */
+ CmdStatus send(uint8_t hostResponse)
+ {
+ // Create a FIFO once.
+ if (!std::filesystem::exists(_fifo))
+ {
+ if (mkfifo(_fifo.c_str(), 0622))
+ {
+ ADD_FAILURE() << "Failed mkfifo " << _fifo << strerror(errno);
+ exit(-1);
+ }
+ }
+
+ // Open it and register the reponse callback to
+ // be used on FD activity.
+ int fd = open(_fifo.c_str(), O_NONBLOCK | O_RDWR);
+ EXPECT_TRUE(fd >= 0) << "Unable to open FIFO";
+
+ auto callback = [this](sdeventplus::source::IO& source, int fd,
+ uint32_t events) {
+ this->receive(source, fd, events);
+ };
+
+ try
+ {
+ _source = std::make_unique<sdeventplus::source::IO>(
+ _event, fd, EPOLLIN,
+ std::bind(callback, std::placeholders::_1,
+ std::placeholders::_2, std::placeholders::_3));
+ }
+ catch (std::exception& e)
+ {
+ ADD_FAILURE() << "Event exception: " << e.what();
+ close(fd);
+ return CmdStatus::failure;
+ }
+
+ // Write the fake host reponse to the FIFO
+ auto bytesWritten = write(fd, &hostResponse, sizeof(hostResponse));
+ EXPECT_EQ(bytesWritten, sizeof(hostResponse));
+
+ _inProgress = true;
+
+ return CmdStatus::success;
+ }
+
+ protected:
+ /**
+ * @brief Reads the data written to the fifo and then calls
+ * the subscriber's callback.
+ *
+ * Nonzero data indicates a command failure (for testing bad path).
+ *
+ * @param[in] source - The event source object
+ * @param[in] fd - The file descriptor used
+ * @param[in] events - The event bits
+ */
+ void receive(sdeventplus::source::IO& source, int fd,
+ uint32_t events) override
+ {
+ if (!(events & EPOLLIN))
+ {
+ return;
+ }
+
+ _inProgress = false;
+
+ int newFD = open(_fifo.c_str(), O_NONBLOCK | O_RDONLY);
+ ASSERT_TRUE(newFD >= 0) << "Failed to open FIFO";
+
+ // Read the host success/failure response from the FIFO.
+ uint8_t data;
+ auto bytesRead = read(newFD, &data, sizeof(data));
+ EXPECT_EQ(bytesRead, sizeof(data));
+
+ close(newFD);
+
+ ResponseStatus status = ResponseStatus::success;
+ if (data != 0)
+ {
+ status = ResponseStatus::failure;
+ }
+
+ if (_responseFunc)
+ {
+ (*_responseFunc)(status);
+ }
+
+ // Keep account of the number of commands responses for testing.
+ _cmdsProcessed++;
+ }
+
+ private:
+ /**
+ * @brief The event source for the fifo
+ */
+ std::unique_ptr<sdeventplus::source::IO> _source;
+
+ /**
+ * @brief the path to the fifo
+ */
+ std::filesystem::path _fifo;
+
+ /**
+ * @brief The number of commands processed
+ */
+ size_t _cmdsProcessed = 0;
+};
+
+} // namespace pels
+} // namespace openpower
diff --git a/test/openpower-pels/mru_test.cpp b/test/openpower-pels/mru_test.cpp
new file mode 100644
index 0000000..e73c69a
--- /dev/null
+++ b/test/openpower-pels/mru_test.cpp
@@ -0,0 +1,74 @@
+/**
+ * Copyright © 2019 IBM Corporation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#include "extensions/openpower-pels/mru.hpp"
+
+#include <gtest/gtest.h>
+
+using namespace openpower::pels;
+using namespace openpower::pels::src;
+
+TEST(MRUTest, TestConstructor)
+{
+ std::vector<uint8_t> data{
+ 'M', 'R', 0x28, 0x04, // ID, size, flags
+ 0x00, 0x00, 0x00, 0x00, // Reserved
+ 0x00, 0x00, 0x00, 'H', // priority for MRU ID 0
+ 0x01, 0x01, 0x01, 0x01, // MRU ID 0
+ 0x00, 0x00, 0x00, 'M', // priority for MRU ID 1
+ 0x02, 0x02, 0x02, 0x02, // MRU ID 1
+ 0x00, 0x00, 0x00, 'L', // priority for MRU ID 2
+ 0x03, 0x03, 0x03, 0x03, // MRU ID 2
+ 0x00, 0x00, 0x00, 'H', // priority for MRU ID 3
+ 0x04, 0x04, 0x04, 0x04, // MRU ID 3
+ };
+
+ Stream stream{data};
+
+ MRU mru{stream};
+
+ EXPECT_EQ(mru.flattenedSize(), data.size());
+ EXPECT_EQ(mru.mrus().size(), 4);
+
+ EXPECT_EQ(mru.mrus().at(0).priority, 'H');
+ EXPECT_EQ(mru.mrus().at(0).id, 0x01010101);
+ EXPECT_EQ(mru.mrus().at(1).priority, 'M');
+ EXPECT_EQ(mru.mrus().at(1).id, 0x02020202);
+ EXPECT_EQ(mru.mrus().at(2).priority, 'L');
+ EXPECT_EQ(mru.mrus().at(2).id, 0x03030303);
+ EXPECT_EQ(mru.mrus().at(3).priority, 'H');
+ EXPECT_EQ(mru.mrus().at(3).id, 0x04040404);
+
+ // Now flatten
+ std::vector<uint8_t> newData;
+ Stream newStream{newData};
+
+ mru.flatten(newStream);
+ EXPECT_EQ(data, newData);
+}
+
+TEST(MRUTest, TestBadData)
+{
+ // 4 MRUs expected, but only 1
+ std::vector<uint8_t> data{
+ 'M', 'R', 0x28, 0x04, // ID, size, flags
+ 0x00, 0x00, 0x00, 0x00, // Reserved
+ 0x00, 0x00, 0x00, 'H', // priority 0
+ 0x01, 0x01, 0x01, 0x01, // MRU ID 0
+ };
+
+ Stream stream{data};
+ EXPECT_THROW(MRU mru{stream}, std::out_of_range);
+}
diff --git a/test/openpower-pels/mtms_test.cpp b/test/openpower-pels/mtms_test.cpp
new file mode 100644
index 0000000..b9c0929
--- /dev/null
+++ b/test/openpower-pels/mtms_test.cpp
@@ -0,0 +1,122 @@
+/**
+ * Copyright © 2019 IBM Corporation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#include "extensions/openpower-pels/mtms.hpp"
+
+#include <gtest/gtest.h>
+
+using namespace openpower::pels;
+
+TEST(MTMSTest, SizeTest)
+{
+ EXPECT_EQ(MTMS::flattenedSize(), 20);
+}
+
+TEST(MTMSTest, ConstructorTest)
+{
+ {
+ std::string tm{"TTTT-MMM"};
+ std::string sn{"123456789ABC"};
+
+ MTMS mtms{tm, sn};
+
+ std::array<uint8_t, 8> t{'T', 'T', 'T', 'T', '-', 'M', 'M', 'M'};
+ EXPECT_EQ(t, mtms.machineTypeAndModelRaw());
+ EXPECT_EQ("TTTT-MMM", mtms.machineTypeAndModel());
+
+ std::array<uint8_t, 12> s{'1', '2', '3', '4', '5', '6',
+ '7', '8', '9', 'A', 'B', 'C'};
+ EXPECT_EQ(s, mtms.machineSerialNumberRaw());
+ EXPECT_EQ("123456789ABC", mtms.machineSerialNumber());
+ }
+
+ {
+ // too long- truncate it
+ std::string tm{"TTTT-MMME"};
+ std::string sn{"123456789ABCE"};
+
+ MTMS mtms{tm, sn};
+
+ std::array<uint8_t, 8> t{'T', 'T', 'T', 'T', '-', 'M', 'M', 'M'};
+ EXPECT_EQ(t, mtms.machineTypeAndModelRaw());
+
+ std::array<uint8_t, 12> s{'1', '2', '3', '4', '5', '6',
+ '7', '8', '9', 'A', 'B', 'C'};
+ EXPECT_EQ(s, mtms.machineSerialNumberRaw());
+ }
+
+ {
+ // short
+ std::string tm{"TTTT"};
+ std::string sn{"1234"};
+
+ MTMS mtms{tm, sn};
+
+ std::array<uint8_t, 8> t{'T', 'T', 'T', 'T', 0, 0, 0, 0};
+ EXPECT_EQ(t, mtms.machineTypeAndModelRaw());
+ EXPECT_EQ("TTTT", mtms.machineTypeAndModel());
+
+ std::array<uint8_t, 12> s{'1', '2', '3', '4', 0, 0, 0, 0, 0, 0, 0, 0};
+ EXPECT_EQ(s, mtms.machineSerialNumberRaw());
+ EXPECT_EQ("1234", mtms.machineSerialNumber());
+ }
+
+ {
+ // Stream constructor
+ std::vector<uint8_t> data{'T', 'T', 'T', 'T', '-', 'M', 'M',
+ 'M', '1', '2', '3', '4', '5', '6',
+ '7', '8', '9', 'A', 'B', 'C'};
+ Stream stream{data};
+
+ MTMS mtms{stream};
+
+ EXPECT_EQ("TTTT-MMM", mtms.machineTypeAndModel());
+
+ EXPECT_EQ("123456789ABC", mtms.machineSerialNumber());
+ }
+}
+
+TEST(MTMSTest, OperatorExtractTest)
+{
+ std::string tm{"TTTT-MMM"};
+ std::string sn{"123456789ABC"};
+
+ MTMS mtms{tm, sn};
+
+ // Check that we can extract the same data
+ std::vector<uint8_t> data;
+ Stream stream{data};
+ stream << mtms;
+
+ std::vector<uint8_t> expected{'T', 'T', 'T', 'T', '-', 'M', 'M',
+ 'M', '1', '2', '3', '4', '5', '6',
+ '7', '8', '9', 'A', 'B', 'C'};
+ EXPECT_EQ(expected, data);
+}
+
+TEST(MTMSTest, OperatorInsertTest)
+{
+ std::vector<uint8_t> data{'T', 'T', 'T', 'T', '-', 'M', 'M', 'M', '1', '2',
+ '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C'};
+ Stream stream{data};
+
+ // Check that when we insert data it is what's expected
+ MTMS mtms;
+ stream >> mtms;
+
+ EXPECT_EQ("TTTT-MMM", mtms.machineTypeAndModel());
+
+ EXPECT_EQ("123456789ABC", mtms.machineSerialNumber());
+}
diff --git a/test/openpower-pels/paths.cpp b/test/openpower-pels/paths.cpp
new file mode 100644
index 0000000..254d2d0
--- /dev/null
+++ b/test/openpower-pels/paths.cpp
@@ -0,0 +1,67 @@
+/**
+ * Copyright © 2019 IBM Corporation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#include "extensions/openpower-pels/paths.hpp"
+
+#include <filesystem>
+
+namespace openpower
+{
+namespace pels
+{
+
+// Use paths that work in unit tests.
+
+std::filesystem::path getPELIDFile()
+{
+ static std::string idFile;
+
+ if (idFile.empty())
+ {
+ char templ[] = "/tmp/logidtestXXXXXX";
+ std::filesystem::path dir = mkdtemp(templ);
+ idFile = dir / "logid";
+ }
+ return idFile;
+}
+
+std::filesystem::path getPELRepoPath()
+{
+ static std::string repoPath;
+
+ if (repoPath.empty())
+ {
+ char templ[] = "/tmp/repopathtestXXXXXX";
+ std::filesystem::path dir = mkdtemp(templ);
+ repoPath = dir;
+ }
+ return repoPath;
+}
+
+std::filesystem::path getMessageRegistryPath()
+{
+ static std::string registryPath;
+
+ if (registryPath.empty())
+ {
+ char templ[] = "/tmp/msgregtestXXXXXX";
+ registryPath = mkdtemp(templ);
+ }
+
+ return registryPath;
+}
+
+} // namespace pels
+} // namespace openpower
diff --git a/test/openpower-pels/pce_identity_test.cpp b/test/openpower-pels/pce_identity_test.cpp
new file mode 100644
index 0000000..de41dbb
--- /dev/null
+++ b/test/openpower-pels/pce_identity_test.cpp
@@ -0,0 +1,57 @@
+/**
+ * Copyright © 2019 IBM Corporation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#include "extensions/openpower-pels/pce_identity.hpp"
+
+#include <gtest/gtest.h>
+
+using namespace openpower::pels;
+using namespace openpower::pels::src;
+
+TEST(PCEIdentityTest, TestConstructor)
+{
+ std::vector<uint8_t> data{
+ 'P', 'E', 0x24, 0x00, // type, size, flags
+ 'T', 'T', 'T', 'T', '-', 'M', 'M', 'M', // MTM
+ '1', '2', '3', '4', '5', '6', '7', // SN
+ '8', '9', 'A', 'B', 'C', 'P', 'C', 'E', // Name + null padded
+ 'N', 'A', 'M', 'E', '1', '2', 0x00, 0x00, 0x00};
+
+ Stream stream{data};
+
+ PCEIdentity pce{stream};
+
+ EXPECT_EQ(pce.flattenedSize(), data.size());
+ EXPECT_EQ(pce.enclosureName(), "PCENAME12");
+ EXPECT_EQ(pce.mtms().machineTypeAndModel(), "TTTT-MMM");
+ EXPECT_EQ(pce.mtms().machineSerialNumber(), "123456789ABC");
+
+ // Flatten it
+ std::vector<uint8_t> newData;
+ Stream newStream{newData};
+ pce.flatten(newStream);
+
+ EXPECT_EQ(data, newData);
+}
+
+TEST(PCEIdentityTest, TestBadData)
+{
+ std::vector<uint8_t> data{
+ 'P', 'E', 0x20, 0x00, 'T', 'T', 'T', 'T', '-',
+ };
+
+ Stream stream{data};
+ EXPECT_THROW(PCEIdentity pce{stream}, std::out_of_range);
+}
diff --git a/test/openpower-pels/pel_manager_test.cpp b/test/openpower-pels/pel_manager_test.cpp
new file mode 100644
index 0000000..b020d9d
--- /dev/null
+++ b/test/openpower-pels/pel_manager_test.cpp
@@ -0,0 +1,514 @@
+/**
+ * Copyright © 2019 IBM Corporation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#include "extensions/openpower-pels/manager.hpp"
+#include "log_manager.hpp"
+#include "pel_utils.hpp"
+
+#include <fstream>
+#include <regex>
+#include <xyz/openbmc_project/Common/error.hpp>
+
+#include <gtest/gtest.h>
+
+using namespace openpower::pels;
+namespace fs = std::filesystem;
+
+class TestLogger
+{
+ public:
+ void log(const std::string& name, phosphor::logging::Entry::Level level,
+ const EventLogger::ADMap& additionalData)
+ {
+ errName = name;
+ errLevel = level;
+ ad = additionalData;
+ }
+
+ std::string errName;
+ phosphor::logging::Entry::Level errLevel;
+ EventLogger::ADMap ad;
+};
+
+class ManagerTest : public CleanPELFiles
+{
+ public:
+ ManagerTest() : logManager(bus, "logging_path")
+ {
+ sd_event_default(&sdEvent);
+ bus.attach_event(sdEvent, SD_EVENT_PRIORITY_NORMAL);
+ }
+
+ ~ManagerTest()
+ {
+ sd_event_unref(sdEvent);
+ }
+
+ sdbusplus::bus::bus bus = sdbusplus::bus::new_default();
+ phosphor::logging::internal::Manager logManager;
+ sd_event* sdEvent;
+ TestLogger logger;
+};
+
+fs::path makeTempDir()
+{
+ char path[] = "/tmp/tempnameXXXXXX";
+ std::filesystem::path dir = mkdtemp(path);
+ return dir;
+}
+
+std::optional<fs::path> findAnyPELInRepo()
+{
+ // PELs are named <timestamp>_<ID>
+ std::regex expr{"\\d+_\\d+"};
+
+ for (auto& f : fs::directory_iterator(getPELRepoPath() / "logs"))
+ {
+ if (std::regex_search(f.path().string(), expr))
+ {
+ return f.path();
+ }
+ }
+ return std::nullopt;
+}
+
+// Test that using the RAWPEL=<file> with the Manager::create() call gets
+// a PEL saved in the repository.
+TEST_F(ManagerTest, TestCreateWithPEL)
+{
+ std::unique_ptr<DataInterfaceBase> dataIface =
+ std::make_unique<DataInterface>(bus);
+
+ openpower::pels::Manager manager{
+ logManager, std::move(dataIface),
+ std::bind(std::mem_fn(&TestLogger::log), &logger, std::placeholders::_1,
+ std::placeholders::_2, std::placeholders::_3)};
+
+ // Create a PEL, write it to a file, and pass that filename into
+ // the create function.
+ auto data = pelDataFactory(TestPELType::pelSimple);
+
+ fs::path pelFilename = makeTempDir() / "rawpel";
+ std::ofstream pelFile{pelFilename};
+ pelFile.write(reinterpret_cast<const char*>(data.data()), data.size());
+ pelFile.close();
+
+ std::string adItem = "RAWPEL=" + pelFilename.string();
+ std::vector<std::string> additionalData{adItem};
+ std::vector<std::string> associations;
+
+ manager.create("error message", 42, 0,
+ phosphor::logging::Entry::Level::Error, additionalData,
+ associations);
+
+ // Find the file in the PEL repository directory
+ auto pelPathInRepo = findAnyPELInRepo();
+
+ EXPECT_TRUE(pelPathInRepo);
+
+ // Now remove it based on its OpenBMC event log ID
+ manager.erase(42);
+
+ pelPathInRepo = findAnyPELInRepo();
+
+ EXPECT_FALSE(pelPathInRepo);
+
+ fs::remove_all(pelFilename.parent_path());
+}
+
+TEST_F(ManagerTest, TestCreateWithInvalidPEL)
+{
+ std::unique_ptr<DataInterfaceBase> dataIface =
+ std::make_unique<DataInterface>(bus);
+
+ openpower::pels::Manager manager{
+ logManager, std::move(dataIface),
+ std::bind(std::mem_fn(&TestLogger::log), &logger, std::placeholders::_1,
+ std::placeholders::_2, std::placeholders::_3)};
+
+ // Create a PEL, write it to a file, and pass that filename into
+ // the create function.
+ auto data = pelDataFactory(TestPELType::pelSimple);
+
+ // Truncate it to make it invalid.
+ data.resize(200);
+
+ fs::path pelFilename = makeTempDir() / "rawpel";
+ std::ofstream pelFile{pelFilename};
+ pelFile.write(reinterpret_cast<const char*>(data.data()), data.size());
+ pelFile.close();
+
+ std::string adItem = "RAWPEL=" + pelFilename.string();
+ std::vector<std::string> additionalData{adItem};
+ std::vector<std::string> associations;
+
+ manager.create("error message", 42, 0,
+ phosphor::logging::Entry::Level::Error, additionalData,
+ associations);
+
+ // Run the event loop to log the bad PEL event
+ sdeventplus::Event e{sdEvent};
+ e.run(std::chrono::milliseconds(1));
+
+ PEL invalidPEL{data};
+ EXPECT_EQ(logger.errName, "org.open_power.Logging.Error.BadHostPEL");
+ EXPECT_EQ(logger.errLevel, phosphor::logging::Entry::Level::Error);
+ EXPECT_EQ(std::stoi(logger.ad["PLID"], nullptr, 16), invalidPEL.plid());
+ EXPECT_EQ(logger.ad["OBMC_LOG_ID"], "42");
+ EXPECT_EQ(logger.ad["SRC"], (*invalidPEL.primarySRC())->asciiString());
+ EXPECT_EQ(logger.ad["PEL_SIZE"], std::to_string(data.size()));
+
+ fs::remove_all(pelFilename.parent_path());
+}
+
+// Test that the message registry can be used to build a PEL.
+TEST_F(ManagerTest, TestCreateWithMessageRegistry)
+{
+ const auto registry = R"(
+{
+ "PELs":
+ [
+ {
+ "Name": "xyz.openbmc_project.Error.Test",
+ "Subsystem": "power_supply",
+ "ActionFlags": ["service_action", "report"],
+ "SRC":
+ {
+ "ReasonCode": "0x2030"
+ },
+ "Documentation":
+ {
+ "Description": "A PGOOD Fault",
+ "Message": "PS had a PGOOD Fault"
+ }
+ }
+ ]
+}
+)";
+
+ auto path = getMessageRegistryPath();
+ fs::create_directories(path);
+ path /= "message_registry.json";
+
+ std::ofstream registryFile{path};
+ registryFile << registry;
+ registryFile.close();
+
+ std::unique_ptr<DataInterfaceBase> dataIface =
+ std::make_unique<DataInterface>(logManager.getBus());
+
+ openpower::pels::Manager manager{
+ logManager, std::move(dataIface),
+ std::bind(std::mem_fn(&TestLogger::log), &logger, std::placeholders::_1,
+ std::placeholders::_2, std::placeholders::_3)};
+
+ std::vector<std::string> additionalData;
+ std::vector<std::string> associations;
+
+ // Create the event log to create the PEL from.
+ manager.create("xyz.openbmc_project.Error.Test", 33, 0,
+ phosphor::logging::Entry::Level::Error, additionalData,
+ associations);
+
+ // Ensure a PEL was created in the repository
+ auto pelFile = findAnyPELInRepo();
+ ASSERT_TRUE(pelFile);
+
+ auto data = readPELFile(*pelFile);
+ PEL pel(*data);
+
+ // Spot check it. Other testcases cover the details.
+ EXPECT_TRUE(pel.valid());
+ EXPECT_EQ(pel.obmcLogID(), 33);
+ EXPECT_EQ(pel.primarySRC().value()->asciiString(),
+ "BD612030 ");
+
+ // Remove it
+ manager.erase(33);
+ pelFile = findAnyPELInRepo();
+ EXPECT_FALSE(pelFile);
+
+ // Create an event log that can't be found in the registry.
+ manager.create("xyz.openbmc_project.Error.Foo", 33, 0,
+ phosphor::logging::Entry::Level::Error, additionalData,
+ associations);
+
+ // Currently, no PEL should be created. Eventually, a 'missing registry
+ // entry' PEL will be there.
+ pelFile = findAnyPELInRepo();
+ EXPECT_FALSE(pelFile);
+}
+
+TEST_F(ManagerTest, TestDBusMethods)
+{
+ std::unique_ptr<DataInterfaceBase> dataIface =
+ std::make_unique<DataInterface>(bus);
+
+ Manager manager{logManager, std::move(dataIface),
+ std::bind(std::mem_fn(&TestLogger::log), &logger,
+ std::placeholders::_1, std::placeholders::_2,
+ std::placeholders::_3)};
+
+ // Create a PEL, write it to a file, and pass that filename into
+ // the create function so there's one in the repo.
+ auto data = pelDataFactory(TestPELType::pelSimple);
+
+ fs::path pelFilename = makeTempDir() / "rawpel";
+ std::ofstream pelFile{pelFilename};
+ pelFile.write(reinterpret_cast<const char*>(data.data()), data.size());
+ pelFile.close();
+
+ std::string adItem = "RAWPEL=" + pelFilename.string();
+ std::vector<std::string> additionalData{adItem};
+ std::vector<std::string> associations;
+
+ manager.create("error message", 42, 0,
+ phosphor::logging::Entry::Level::Error, additionalData,
+ associations);
+
+ // getPELFromOBMCID
+ auto newData = manager.getPELFromOBMCID(42);
+ EXPECT_EQ(newData.size(), data.size());
+
+ // Read the PEL to get the ID for later
+ PEL pel{newData};
+ auto id = pel.id();
+
+ EXPECT_THROW(
+ manager.getPELFromOBMCID(id + 1),
+ sdbusplus::xyz::openbmc_project::Common::Error::InvalidArgument);
+
+ // getPEL
+ auto unixfd = manager.getPEL(id);
+
+ // Get the size
+ struct stat s;
+ int r = fstat(unixfd, &s);
+ ASSERT_EQ(r, 0);
+ auto size = s.st_size;
+
+ // Open the FD and check the contents
+ FILE* fp = fdopen(unixfd, "r");
+ ASSERT_NE(fp, nullptr);
+
+ std::vector<uint8_t> fdData;
+ fdData.resize(size);
+ r = fread(fdData.data(), 1, size, fp);
+ EXPECT_EQ(r, size);
+
+ EXPECT_EQ(newData, fdData);
+
+ fclose(fp);
+
+ // Run the event loop to close the FD
+ sdeventplus::Event e{sdEvent};
+ e.run(std::chrono::milliseconds(1));
+
+ EXPECT_THROW(
+ manager.getPEL(id + 1),
+ sdbusplus::xyz::openbmc_project::Common::Error::InvalidArgument);
+
+ // hostAck
+ manager.hostAck(id);
+
+ EXPECT_THROW(
+ manager.hostAck(id + 1),
+ sdbusplus::xyz::openbmc_project::Common::Error::InvalidArgument);
+
+ // hostReject
+ manager.hostReject(id, Manager::RejectionReason::BadPEL);
+
+ // Run the event loop to log the bad PEL event
+ e.run(std::chrono::milliseconds(1));
+
+ EXPECT_EQ(logger.errName, "org.open_power.Logging.Error.SentBadPELToHost");
+ EXPECT_EQ(id, std::stoi(logger.ad["BAD_ID"], nullptr, 16));
+
+ manager.hostReject(id, Manager::RejectionReason::HostFull);
+
+ EXPECT_THROW(
+ manager.hostReject(id + 1, Manager::RejectionReason::BadPEL),
+ sdbusplus::xyz::openbmc_project::Common::Error::InvalidArgument);
+
+ fs::remove_all(pelFilename.parent_path());
+}
+
+// An ESEL from the wild
+const std::string esel{
+ "00 00 df 00 00 00 00 20 00 04 12 01 6f aa 00 00 "
+ "50 48 00 30 01 00 33 00 00 00 00 07 5c 69 cc 0d 00 00 00 07 5c d5 50 db "
+ "42 00 00 10 00 00 00 00 00 00 00 00 00 00 00 00 90 00 00 4e 90 00 00 4e "
+ "55 48 00 18 01 00 09 00 8a 03 40 00 00 00 00 00 ff ff 00 00 00 00 00 00 "
+ "50 53 00 50 01 01 00 00 02 00 00 09 33 2d 00 48 00 00 00 e0 00 00 10 00 "
+ "00 00 00 00 00 20 00 00 00 0c 00 02 00 00 00 fa 00 00 0c e4 00 00 00 12 "
+ "42 43 38 41 33 33 32 44 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 "
+ "20 20 20 20 20 20 20 20 55 44 00 1c 01 06 01 00 02 54 41 4b 00 00 00 06 "
+ "00 00 00 55 00 01 f9 20 00 00 00 00 55 44 00 24 01 06 01 00 01 54 41 4b "
+ "00 00 00 05 00 00 00 00 00 00 00 00 00 00 00 00 23 01 00 02 00 05 00 00 "
+ "55 44 00 0c 01 0b 01 00 0f 01 00 00 55 44 00 10 01 04 01 00 0f 9f de 6a "
+ "00 01 00 00 55 44 00 7c 00 0c 01 00 00 13 0c 02 00 fa 0c e4 16 00 01 2c "
+ "0c 1c 16 00 00 fa 0a f0 14 00 00 fa 0b b8 14 00 00 be 09 60 12 00 01 2c "
+ "0d 7a 12 00 00 fa 0c 4e 10 00 00 fa 0c e4 10 00 00 be 0a 8c 16 00 01 2c "
+ "0c 1c 16 00 01 09 09 f6 16 00 00 fa 09 f6 14 00 00 fa 0b b8 14 00 00 fa "
+ "0a f0 14 00 00 be 08 ca 12 00 01 2c 0c e4 12 00 00 fa 0b 54 10 00 00 fa "
+ "0c 2d 10 00 00 be 08 ca 55 44 00 58 01 03 01 00 00 00 00 00 00 05 31 64 "
+ "00 00 00 00 00 05 0d d4 00 00 00 00 40 5f 06 e0 00 00 00 00 40 5d d2 00 "
+ "00 00 00 00 40 57 d3 d0 00 00 00 00 40 58 f6 a0 00 00 00 00 40 54 c9 34 "
+ "00 00 00 00 40 55 9a 10 00 00 00 00 40 4c 0a 80 00 00 00 00 00 00 27 14 "
+ "55 44 01 84 01 01 01 00 48 6f 73 74 62 6f 6f 74 20 42 75 69 6c 64 20 49 "
+ "44 3a 20 68 6f 73 74 62 6f 6f 74 2d 66 65 63 37 34 64 66 2d 70 30 61 38 "
+ "37 64 63 34 2f 68 62 69 63 6f 72 65 2e 62 69 6e 00 49 42 4d 2d 77 69 74 "
+ "68 65 72 73 70 6f 6f 6e 2d 4f 50 39 2d 76 32 2e 34 2d 39 2e 32 33 34 0a "
+ "09 6f 70 2d 62 75 69 6c 64 2d 38 32 66 34 63 66 30 0a 09 62 75 69 6c 64 "
+ "72 6f 6f 74 2d 32 30 31 39 2e 30 35 2e 32 2d 31 30 2d 67 38 39 35 39 31 "
+ "31 34 0a 09 73 6b 69 62 6f 6f 74 2d 76 36 2e 35 2d 31 38 2d 67 34 37 30 "
+ "66 66 62 35 66 32 39 64 37 0a 09 68 6f 73 74 62 6f 6f 74 2d 66 65 63 37 "
+ "34 64 66 2d 70 30 61 38 37 64 63 34 0a 09 6f 63 63 2d 65 34 35 39 37 61 "
+ "62 0a 09 6c 69 6e 75 78 2d 35 2e 32 2e 31 37 2d 6f 70 65 6e 70 6f 77 65 "
+ "72 31 2d 70 64 64 63 63 30 33 33 0a 09 70 65 74 69 74 62 6f 6f 74 2d 76 "
+ "31 2e 31 30 2e 34 0a 09 6d 61 63 68 69 6e 65 2d 78 6d 6c 2d 63 36 32 32 "
+ "63 62 35 2d 70 37 65 63 61 62 33 64 0a 09 68 6f 73 74 62 6f 6f 74 2d 62 "
+ "69 6e 61 72 69 65 73 2d 36 36 65 39 61 36 30 0a 09 63 61 70 70 2d 75 63 "
+ "6f 64 65 2d 70 39 2d 64 64 32 2d 76 34 0a 09 73 62 65 2d 36 30 33 33 30 "
+ "65 30 0a 09 68 63 6f 64 65 2d 68 77 30 39 32 31 31 39 61 2e 6f 70 6d 73 "
+ "74 0a 00 00 55 44 00 70 01 04 01 00 0f 9f de 6a 00 05 00 00 07 5f 1d f4 "
+ "30 32 43 59 34 37 30 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 "
+ "00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 "
+ "0b ac 54 02 59 41 31 39 33 34 36 39 37 30 35 38 00 00 00 00 00 00 05 22 "
+ "a1 58 01 8a 00 58 40 20 17 18 4d 2c 00 00 00 fc 01 a1 00 00 55 44 00 14 "
+ "01 08 01 00 00 00 00 01 00 00 00 5a 00 00 00 05 55 44 03 fc 01 15 31 00 "
+ "01 28 00 42 46 41 50 49 00 00 00 00 00 00 00 00 00 00 00 00 00 00 03 f4 "
+ "00 00 00 00 00 00 03 f4 00 00 00 0b 00 00 00 00 00 00 00 3d 2c 9b c2 84 "
+ "00 00 01 e4 00 48 43 4f fb ed 70 b1 00 00 02 01 00 00 00 00 00 00 00 09 "
+ "00 00 00 00 00 11 bd 20 00 00 00 00 00 01 f8 80 00 00 00 00 00 00 00 01 "
+ "00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 16 00 00 00 00 00 00 01 2c "
+ "00 00 00 00 00 00 07 d0 00 00 00 00 00 00 0c 1c 00 00 00 64 00 00 00 3d "
+ "2c 9b d1 11 00 00 01 e4 00 48 43 4f fb ed 70 b1 00 00 02 01 00 00 00 00 "
+ "00 00 00 0a 00 00 00 00 00 13 b5 a0 00 00 00 00 00 01 f8 80 00 00 00 00 "
+ "00 00 00 01 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 10 00 00 00 00 "
+ "00 00 00 be 00 00 00 00 00 00 07 d0 00 00 00 00 00 00 0a 8c 00 00 00 64 "
+ "00 00 00 3d 2c 9b df 98 00 00 01 e4 00 48 43 4f fb ed 70 b1 00 00 02 01 "
+ "00 00 00 00 00 00 00 0b 00 00 00 00 00 15 ae 20 00 00 00 00 00 01 f8 80 "
+ "00 00 00 00 00 00 00 01 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 10 "
+ "00 00 00 00 00 00 00 fa 00 00 00 00 00 00 07 d0 00 00 00 00 00 00 0c e4 "
+ "00 00 00 64 00 00 00 3d 2c 9b ea b7 00 00 01 e4 00 48 43 4f fb ed 70 b1 "
+ "00 00 02 01 00 00 00 00 00 00 00 0c 00 00 00 00 00 17 a6 a0 00 00 00 00 "
+ "00 01 f8 80 00 00 00 00 00 00 00 01 00 00 00 00 00 00 00 00 00 00 00 00 "
+ "00 00 00 12 00 00 00 00 00 00 00 fa 00 00 00 00 00 00 07 d0 00 00 00 00 "
+ "00 00 0c 4e 00 00 00 64 00 00 00 3d 2c 9b f6 27 00 00 01 e4 00 48 43 4f "
+ "fb ed 70 b1 00 00 02 01 00 00 00 00 00 00 00 0d 00 00 00 00 00 19 9f 20 "
+ "00 00 00 00 00 01 f8 80 00 00 00 00 00 00 00 01 00 00 00 00 00 00 00 00 "
+ "00 00 00 00 00 00 00 12 00 00 00 00 00 00 01 2c 00 00 00 00 00 00 07 d0 "
+ "00 00 00 00 00 00 0d 7a 00 00 00 64 00 00 00 3d 2c 9c 05 75 00 00 01 e4 "
+ "00 48 43 4f fb ed 70 b1 00 00 02 01 00 00 00 00 00 00 00 0e 00 00 00 00 "
+ "00 1b 97 a0 00 00 00 00 00 01 f8 80 00 00 00 00 00 00 00 01 00 00 00 00 "
+ "00 00 00 00 00 00 00 00 00 00 00 14 00 00 00 00 00 00 00 be 00 00 00 00 "
+ "00 00 07 d0 00 00 00 00 00 00 09 60 00 00 00 64 00 00 00 3d 2c 9c 11 29 "
+ "00 00 01 e4 00 48 43 4f fb ed 70 b1 00 00 02 01 00 00 00 00 00 00 00 0f "
+ "00 00 00 00 00 1d 90 20 00 00 00 00 00 01 f8 80 00 00 00 00 00 00 00 01 "
+ "00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 14 00 00 00 00 00 00 00 fa "
+ "00 00 00 00 00 00 07 d0 00 00 00 00 00 00 0b b8 00 00 00 64 00 00 00 3d "
+ "2c 9c 1c 45 00 00 01 e4 00 48 43 4f fb ed 70 b1 00 00 02 01 00 00 00 00 "
+ "00 00 00 10 00 00 00 00 00 1f 88 a0 00 00 00 00 00 01 f8 80 00 00 00 00 "
+ "00 00 00 01 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 16 00 00 00 00 "
+ "00 00 00 fa 00 00 00 00 00 00 07 d0 00 00 00 00 00 00 0a f0 00 00 00 64 "
+ "00 00 00 3d 2c 9c 2b 14 00 00 01 e4 00 48 43 4f fb ed 70 b1 00 00 02 01 "
+ "00 00 00 00 00 00 00 11 00 00 00 00 00 21 81 20 00 00 00 00 00 01 f8 80 "
+ "00 00 00 00 00 00 00 01 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 16 "
+ "00 00 00 00 00 00 01 2c 00 00 00 00 00 00 07 d0 00 00 00 00 00 00 0c 1c "
+ "00 00 00 64 00 00 00 3d 2d 6d 8f 9e 00 00 01 e4 00 00 43 4f 52 d7 9c 36 "
+ "00 00 04 73 00 00 00 1c 00 00 00 3d 2d 6d 99 ac 00 00 01 e4 00 10 43 4f "
+ "3f f2 02 3d 00 00 05 58 00 00 00 00 02 00 00 01 00 00 00 00 00 00 00 40 "
+ "00 00 00 2c 55 44 00 30 01 15 31 00 01 28 00 42 46 41 50 49 5f 44 42 47 "
+ "00 00 00 00 00 00 00 00 00 00 00 28 00 00 00 00 00 00 00 28 00 00 00 00 "
+ "00 00 00 00 55 44 01 74 01 15 31 00 01 28 00 42 46 41 50 49 5f 49 00 00 "
+ "00 00 00 00 00 00 00 00 00 00 01 6c 00 00 00 00 00 00 01 6c 00 00 00 0b "
+ "00 00 00 00 00 00 00 3c 0d 52 18 5e 00 00 01 e4 00 08 43 4f 46 79 94 13 "
+ "00 00 0a 5b 00 00 00 00 00 00 2c 00 00 00 00 24 00 00 00 3c 0d 6b 26 6c "
+ "00 00 01 e4 00 00 43 4f 4e 9b 18 74 00 00 01 03 00 00 00 1c 00 00 00 3c "
+ "12 b9 2d 13 00 00 01 e4 00 00 43 4f ea 31 ed d4 00 00 05 c4 00 00 00 1c "
+ "00 00 00 3c 13 02 73 53 00 00 01 e4 00 00 43 4f ea 31 ed d4 00 00 05 c4 "
+ "00 00 00 1c 00 00 00 3c 13 04 7c 94 00 00 01 e4 00 00 43 4f ea 31 ed d4 "
+ "00 00 05 c4 00 00 00 1c 00 00 00 3c 13 06 ad e1 00 00 01 e4 00 00 43 4f "
+ "ea 31 ed d4 00 00 05 c4 00 00 00 1c 00 00 00 3c 13 07 3f 77 00 00 01 e4 "
+ "00 00 43 4f 5e 4a 55 32 00 00 10 f2 00 00 00 1c 00 00 00 3c 13 07 4e e4 "
+ "00 00 01 e4 00 00 43 4f 5e 4a 55 32 00 00 0d 68 00 00 00 1c 00 00 00 3c "
+ "13 36 79 18 00 00 01 e4 00 00 43 4f ea 31 ed d4 00 00 05 c4 00 00 00 1c "
+ "00 00 00 3d 2c 9c 36 70 00 00 01 e4 00 00 43 4f 23 45 90 97 00 00 02 47 "
+ "00 00 00 1c 00 00 00 3d 2d 6d a3 ed 00 00 01 e4 00 08 43 4f 74 3a 5b 1a "
+ "00 00 04 cc 00 00 00 00 02 00 00 01 00 00 00 24 55 44 00 30 01 15 31 00 "
+ "01 28 00 42 53 43 41 4e 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 28 "
+ "00 00 00 00 00 00 00 28 00 00 00 00 00 00 00 00"};
+
+TEST_F(ManagerTest, TestESELToRawData)
+{
+ auto data = Manager::eselToRawData(esel);
+
+ EXPECT_EQ(data.size(), 2464);
+
+ PEL pel{data};
+ EXPECT_TRUE(pel.valid());
+}
+
+TEST_F(ManagerTest, TestCreateWithESEL)
+{
+ std::unique_ptr<DataInterfaceBase> dataIface =
+ std::make_unique<DataInterface>(bus);
+
+ openpower::pels::Manager manager{
+ logManager, std::move(dataIface),
+ std::bind(std::mem_fn(&TestLogger::log), &logger, std::placeholders::_1,
+ std::placeholders::_2, std::placeholders::_3)};
+
+ {
+ std::string adItem = "ESEL=" + esel;
+ std::vector<std::string> additionalData{adItem};
+ std::vector<std::string> associations;
+
+ manager.create("error message", 37, 0,
+ phosphor::logging::Entry::Level::Error, additionalData,
+ associations);
+
+ auto data = manager.getPELFromOBMCID(37);
+ PEL pel{data};
+ EXPECT_TRUE(pel.valid());
+ }
+
+ // Now an invalid one
+ {
+ std::string adItem = "ESEL=" + esel;
+
+ // Crop it
+ adItem.resize(adItem.size() - 300);
+
+ std::vector<std::string> additionalData{adItem};
+ std::vector<std::string> associations;
+
+ manager.create("error message", 38, 0,
+ phosphor::logging::Entry::Level::Error, additionalData,
+ associations);
+
+ EXPECT_THROW(
+ manager.getPELFromOBMCID(38),
+ sdbusplus::xyz::openbmc_project::Common::Error::InvalidArgument);
+
+ // Run the event loop to log the bad PEL event
+ sdeventplus::Event e{sdEvent};
+ e.run(std::chrono::milliseconds(1));
+
+ EXPECT_EQ(logger.errName, "org.open_power.Logging.Error.BadHostPEL");
+ EXPECT_EQ(logger.errLevel, phosphor::logging::Entry::Level::Error);
+ }
+}
diff --git a/test/openpower-pels/pel_rules_test.cpp b/test/openpower-pels/pel_rules_test.cpp
new file mode 100644
index 0000000..38e5b58
--- /dev/null
+++ b/test/openpower-pels/pel_rules_test.cpp
@@ -0,0 +1,72 @@
+#include "extensions/openpower-pels/pel_rules.hpp"
+
+#include <gtest/gtest.h>
+
+using namespace openpower::pels;
+
+struct CheckParams
+{
+ // pel_rules::check() inputs
+ uint16_t actionFlags;
+ uint8_t eventType;
+ uint8_t severity;
+
+ // pel_rules::check() expected outputs
+ uint16_t expectedActionFlags;
+ uint8_t expectedEventType;
+};
+
+const uint8_t sevInfo = 0x00;
+const uint8_t sevRecovered = 0x10;
+const uint8_t sevPredictive = 0x20;
+const uint8_t sevUnrecov = 0x40;
+const uint8_t sevCrit = 0x50;
+const uint8_t sevDiagnostic = 0x60;
+const uint8_t sevSymptom = 0x70;
+
+const uint8_t typeNA = 0x00;
+const uint8_t typeMisc = 0x01;
+const uint8_t typeTracing = 0x02;
+const uint8_t typeDumpNotif = 0x08;
+
+TEST(PELRulesTest, TestCheckRules)
+{
+ // Impossible to cover all combinations, but
+ // do some interesting ones.
+ std::vector<CheckParams> testParams{
+ // Informational errors w/ empty action flags
+ // and different event types.
+ {0, typeNA, sevInfo, 0x6000, typeMisc},
+ {0, typeMisc, sevInfo, 0x6000, typeMisc},
+ {0, typeTracing, sevInfo, 0x6000, typeTracing},
+ {0, typeDumpNotif, sevInfo, 0x2000, typeDumpNotif},
+
+ // Informational errors with wrong action flags
+ {0x8900, typeNA, sevInfo, 0x6000, typeMisc},
+
+ // Informational errors with extra valid action flags
+ {0x00C0, typeMisc, sevInfo, 0x60C0, typeMisc},
+
+ // Informational - don't report
+ {0x1000, typeMisc, sevInfo, 0x5000, typeMisc},
+
+ // Recovered will report as hidden
+ {0, typeNA, sevRecovered, 0x6000, typeNA},
+
+ // The 5 error severities will have:
+ // service action, report, call home
+ {0, typeNA, sevPredictive, 0xA800, typeNA},
+ {0, typeNA, sevUnrecov, 0xA800, typeNA},
+ {0, typeNA, sevCrit, 0xA800, typeNA},
+ {0, typeNA, sevDiagnostic, 0xA800, typeNA},
+ {0, typeNA, sevSymptom, 0xA800, typeNA}};
+
+ for (const auto& entry : testParams)
+ {
+ auto [actionFlags, type] = pel_rules::check(
+ entry.actionFlags, entry.eventType, entry.severity);
+
+ EXPECT_EQ(actionFlags, entry.expectedActionFlags);
+ EXPECT_EQ(type, entry.expectedEventType);
+ }
+}
diff --git a/test/openpower-pels/pel_test.cpp b/test/openpower-pels/pel_test.cpp
new file mode 100644
index 0000000..2cf58d7
--- /dev/null
+++ b/test/openpower-pels/pel_test.cpp
@@ -0,0 +1,387 @@
+/**
+ * Copyright © 2019 IBM Corporation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#include "elog_entry.hpp"
+#include "extensions/openpower-pels/generic.hpp"
+#include "extensions/openpower-pels/pel.hpp"
+#include "mocks.hpp"
+#include "pel_utils.hpp"
+
+#include <filesystem>
+#include <fstream>
+
+#include <gtest/gtest.h>
+
+namespace fs = std::filesystem;
+using namespace openpower::pels;
+using ::testing::Return;
+
+class PELTest : public CleanLogID
+{
+};
+
+TEST_F(PELTest, FlattenTest)
+{
+ auto data = pelDataFactory(TestPELType::pelSimple);
+ auto pel = std::make_unique<PEL>(data);
+
+ // Check a few fields
+ EXPECT_TRUE(pel->valid());
+ EXPECT_EQ(pel->id(), 0x80818283);
+ EXPECT_EQ(pel->plid(), 0x50515253);
+ EXPECT_EQ(pel->userHeader().subsystem(), 0x10);
+ EXPECT_EQ(pel->userHeader().actionFlags(), 0x80C0);
+
+ // Test that data in == data out
+ auto flattenedData = pel->data();
+ EXPECT_EQ(data, flattenedData);
+ EXPECT_EQ(flattenedData.size(), pel->size());
+}
+
+TEST_F(PELTest, CommitTimeTest)
+{
+ auto data = pelDataFactory(TestPELType::pelSimple);
+ auto pel = std::make_unique<PEL>(data);
+
+ auto origTime = pel->commitTime();
+ pel->setCommitTime();
+ auto newTime = pel->commitTime();
+
+ EXPECT_NE(origTime, newTime);
+
+ // Make a new PEL and check new value is still there
+ auto newData = pel->data();
+ auto newPel = std::make_unique<PEL>(newData);
+ EXPECT_EQ(newTime, newPel->commitTime());
+}
+
+TEST_F(PELTest, AssignIDTest)
+{
+ auto data = pelDataFactory(TestPELType::pelSimple);
+ auto pel = std::make_unique<PEL>(data);
+
+ auto origID = pel->id();
+ pel->assignID();
+ auto newID = pel->id();
+
+ EXPECT_NE(origID, newID);
+
+ // Make a new PEL and check new value is still there
+ auto newData = pel->data();
+ auto newPel = std::make_unique<PEL>(newData);
+ EXPECT_EQ(newID, newPel->id());
+}
+
+TEST_F(PELTest, WithLogIDTest)
+{
+ auto data = pelDataFactory(TestPELType::pelSimple);
+ auto pel = std::make_unique<PEL>(data, 0x42);
+
+ EXPECT_TRUE(pel->valid());
+ EXPECT_EQ(pel->obmcLogID(), 0x42);
+}
+
+TEST_F(PELTest, InvalidPELTest)
+{
+ auto data = pelDataFactory(TestPELType::pelSimple);
+
+ // Too small
+ data.resize(PrivateHeader::flattenedSize());
+
+ auto pel = std::make_unique<PEL>(data);
+
+ EXPECT_TRUE(pel->privateHeader().valid());
+ EXPECT_FALSE(pel->userHeader().valid());
+ EXPECT_FALSE(pel->valid());
+
+ // Now corrupt the private header
+ data = pelDataFactory(TestPELType::pelSimple);
+ data.at(0) = 0;
+ pel = std::make_unique<PEL>(data);
+
+ EXPECT_FALSE(pel->privateHeader().valid());
+ EXPECT_TRUE(pel->userHeader().valid());
+ EXPECT_FALSE(pel->valid());
+}
+
+TEST_F(PELTest, EmptyDataTest)
+{
+ std::vector<uint8_t> data;
+ auto pel = std::make_unique<PEL>(data);
+
+ EXPECT_FALSE(pel->privateHeader().valid());
+ EXPECT_FALSE(pel->userHeader().valid());
+ EXPECT_FALSE(pel->valid());
+}
+
+TEST_F(PELTest, CreateFromRegistryTest)
+{
+ message::Entry regEntry;
+ uint64_t timestamp = 5;
+
+ regEntry.name = "test";
+ regEntry.subsystem = 5;
+ regEntry.actionFlags = 0xC000;
+ regEntry.src.type = 0xBD;
+ regEntry.src.reasonCode = 0x1234;
+
+ std::vector<std::string> data{"KEY1=VALUE1"};
+ AdditionalData ad{data};
+ MockDataInterface dataIface;
+
+ PEL pel{regEntry, 42, timestamp, phosphor::logging::Entry::Level::Error, ad,
+ dataIface};
+
+ EXPECT_TRUE(pel.valid());
+ EXPECT_EQ(pel.privateHeader().obmcLogID(), 42);
+ EXPECT_EQ(pel.userHeader().severity(), 0x40);
+
+ EXPECT_EQ(pel.primarySRC().value()->asciiString(),
+ "BD051234 ");
+
+ // Check that certain optional sections have been created
+ size_t mtmsCount = 0;
+ size_t euhCount = 0;
+ size_t udCount = 0;
+
+ for (const auto& section : pel.optionalSections())
+ {
+ if (section->header().id ==
+ static_cast<uint16_t>(SectionID::failingMTMS))
+ {
+ mtmsCount++;
+ }
+ else if (section->header().id ==
+ static_cast<uint16_t>(SectionID::extendedUserHeader))
+ {
+ euhCount++;
+ }
+ else if (section->header().id ==
+ static_cast<uint16_t>(SectionID::userData))
+ {
+ udCount++;
+ }
+ }
+
+ EXPECT_EQ(mtmsCount, 1);
+ EXPECT_EQ(euhCount, 1);
+ EXPECT_EQ(udCount, 2); // AD section and sysInfo section
+}
+
+// Test that we'll create Generic optional sections for sections that
+// there aren't explicit classes for.
+TEST_F(PELTest, GenericSectionTest)
+{
+ auto data = pelDataFactory(TestPELType::pelSimple);
+
+ std::vector<uint8_t> section1{0x58, 0x58, // ID 'XX'
+ 0x00, 0x18, // Size
+ 0x01, 0x02, // version, subtype
+ 0x03, 0x04, // comp ID
+
+ // some data
+ 0x20, 0x30, 0x05, 0x09, 0x11, 0x1E, 0x1, 0x63,
+ 0x20, 0x31, 0x06, 0x0F, 0x09, 0x22, 0x3A,
+ 0x00};
+
+ std::vector<uint8_t> section2{
+ 0x59, 0x59, // ID 'YY'
+ 0x00, 0x20, // Size
+ 0x01, 0x02, // version, subtype
+ 0x03, 0x04, // comp ID
+
+ // some data
+ 0x20, 0x30, 0x05, 0x09, 0x11, 0x1E, 0x1, 0x63, 0x20, 0x31, 0x06, 0x0F,
+ 0x09, 0x22, 0x3A, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08};
+
+ // Add the new sections at the end
+ data.insert(data.end(), section1.begin(), section1.end());
+ data.insert(data.end(), section2.begin(), section2.end());
+
+ // Increment the section count
+ data.at(27) += 2;
+ auto origData = data;
+
+ PEL pel{data};
+
+ const auto& sections = pel.optionalSections();
+
+ bool foundXX = false;
+ bool foundYY = false;
+
+ // Check that we can find these 2 Generic sections
+ for (const auto& section : sections)
+ {
+ if (section->header().id == 0x5858)
+ {
+ foundXX = true;
+ EXPECT_NE(dynamic_cast<Generic*>(section.get()), nullptr);
+ }
+ else if (section->header().id == 0x5959)
+ {
+ foundYY = true;
+ EXPECT_NE(dynamic_cast<Generic*>(section.get()), nullptr);
+ }
+ }
+
+ EXPECT_TRUE(foundXX);
+ EXPECT_TRUE(foundYY);
+
+ // Now flatten and check
+ auto newData = pel.data();
+
+ EXPECT_EQ(origData, newData);
+}
+
+// Test that an invalid section will still get a Generic object
+TEST_F(PELTest, InvalidGenericTest)
+{
+ auto data = pelDataFactory(TestPELType::pelSimple);
+
+ // Not a valid section
+ std::vector<uint8_t> section1{0x01, 0x02, 0x03};
+
+ data.insert(data.end(), section1.begin(), section1.end());
+
+ // Increment the section count
+ data.at(27) += 1;
+
+ PEL pel{data};
+ EXPECT_FALSE(pel.valid());
+
+ const auto& sections = pel.optionalSections();
+
+ bool foundGeneric = false;
+ for (const auto& section : sections)
+ {
+ if (dynamic_cast<Generic*>(section.get()) != nullptr)
+ {
+ foundGeneric = true;
+ EXPECT_EQ(section->valid(), false);
+ break;
+ }
+ }
+
+ EXPECT_TRUE(foundGeneric);
+}
+
+// Create a UserData section out of AdditionalData
+TEST_F(PELTest, MakeUDSectionTest)
+{
+ std::vector<std::string> ad{"KEY1=VALUE1", "KEY2=VALUE2", "KEY3=VALUE3",
+ "ESEL=TEST"};
+ AdditionalData additionalData{ad};
+
+ auto ud = util::makeADUserDataSection(additionalData);
+
+ EXPECT_TRUE(ud->valid());
+ EXPECT_EQ(ud->header().id, 0x5544);
+ EXPECT_EQ(ud->header().version, 0x01);
+ EXPECT_EQ(ud->header().subType, 0x01);
+ EXPECT_EQ(ud->header().componentID, 0x2000);
+
+ const auto& d = ud->data();
+
+ std::string jsonString{d.begin(), d.end()};
+
+ std::string expectedJSON =
+ R"({"KEY1":"VALUE1","KEY2":"VALUE2","KEY3":"VALUE3"})";
+
+ // The actual data is null padded to a 4B boundary.
+ std::vector<uint8_t> expectedData;
+ expectedData.resize(52, '\0');
+ memcpy(expectedData.data(), expectedJSON.data(), expectedJSON.size());
+
+ EXPECT_EQ(d, expectedData);
+
+ // Ensure we can read this as JSON
+ auto newJSON = nlohmann::json::parse(jsonString);
+ EXPECT_EQ(newJSON["KEY1"], "VALUE1");
+ EXPECT_EQ(newJSON["KEY2"], "VALUE2");
+ EXPECT_EQ(newJSON["KEY3"], "VALUE3");
+}
+
+// Create the UserData section that contains system info
+TEST_F(PELTest, SysInfoSectionTest)
+{
+ MockDataInterface dataIface;
+
+ EXPECT_CALL(dataIface, getBMCFWVersionID()).WillOnce(Return("ABCD1234"));
+
+ std::string pid = "_PID=" + std::to_string(getpid());
+ std::vector<std::string> ad{pid};
+ AdditionalData additionalData{ad};
+
+ auto ud = util::makeSysInfoUserDataSection(additionalData, dataIface);
+
+ EXPECT_TRUE(ud->valid());
+ EXPECT_EQ(ud->header().id, 0x5544);
+ EXPECT_EQ(ud->header().version, 0x01);
+ EXPECT_EQ(ud->header().subType, 0x01);
+ EXPECT_EQ(ud->header().componentID, 0x2000);
+
+ // Pull out the JSON data and check it.
+ const auto& d = ud->data();
+ std::string jsonString{d.begin(), d.end()};
+ auto json = nlohmann::json::parse(jsonString);
+
+ // Ensure the 'Process Name' entry contains 'pel_test'
+ auto name = json["Process Name"].get<std::string>();
+ EXPECT_NE(name.find("pel_test"), std::string::npos);
+
+ auto version = json["BMC Version ID"].get<std::string>();
+ EXPECT_EQ(version, "ABCD1234");
+}
+
+// Test that the sections that override
+// virtual std::optional<std::string> Section::getJSON() const
+// return valid JSON.
+TEST_F(PELTest, SectionJSONTest)
+{
+ auto data = pelDataFactory(TestPELType::pelSimple);
+ PEL pel{data};
+
+ // Check that all JSON returned from the sections is
+ // parseable by nlohmann::json, which will throw an
+ // exception and fail the test if there is a problem.
+
+ // The getJSON() response needs to be wrapped in a { } to make
+ // actual valid JSON (PEL::toJSON() usually handles that).
+
+ auto jsonString = pel.privateHeader().getJSON();
+
+ // PrivateHeader always prints JSON
+ ASSERT_TRUE(jsonString);
+ *jsonString = '{' + *jsonString + '}';
+ auto json = nlohmann::json::parse(*jsonString);
+
+ jsonString = pel.userHeader().getJSON();
+
+ // UserHeader always prints JSON
+ ASSERT_TRUE(jsonString);
+ *jsonString = '{' + *jsonString + '}';
+ json = nlohmann::json::parse(*jsonString);
+
+ for (const auto& section : pel.optionalSections())
+ {
+ // The optional sections may or may not have implemented getJSON().
+ jsonString = section->getJSON();
+ if (jsonString)
+ {
+ *jsonString = '{' + *jsonString + '}';
+ auto json = nlohmann::json::parse(*jsonString);
+ }
+ }
+}
diff --git a/test/openpower-pels/pel_utils.cpp b/test/openpower-pels/pel_utils.cpp
new file mode 100644
index 0000000..4560b2f
--- /dev/null
+++ b/test/openpower-pels/pel_utils.cpp
@@ -0,0 +1,297 @@
+/**
+ * Copyright © 2019 IBM Corporation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#include "pel_utils.hpp"
+
+#include "extensions/openpower-pels/private_header.hpp"
+#include "extensions/openpower-pels/user_header.hpp"
+
+#include <fstream>
+
+#include <gtest/gtest.h>
+
+namespace fs = std::filesystem;
+using namespace openpower::pels;
+
+std::filesystem::path CleanLogID::pelIDFile{};
+std::filesystem::path CleanPELFiles::pelIDFile{};
+std::filesystem::path CleanPELFiles::repoPath{};
+std::filesystem::path CleanPELFiles::registryPath{};
+
+const std::vector<uint8_t> privateHeaderSection{
+ // section header
+ 0x50, 0x48, // ID 'PH'
+ 0x00, 0x30, // Size
+ 0x01, 0x02, // version, subtype
+ 0x03, 0x04, // comp ID
+
+ 0x20, 0x30, 0x05, 0x09, 0x11, 0x1E, 0x1, 0x63, // create timestamp
+ 0x20, 0x31, 0x06, 0x0F, 0x09, 0x22, 0x3A, 0x00, // commit timestamp
+ 0xAA, // creatorID
+ 0x00, // logtype
+ 0x00, // reserved
+ 0x02, // section count
+ 0x90, 0x91, 0x92, 0x93, // OpenBMC log ID
+ 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0, // creator version
+ 0x50, 0x51, 0x52, 0x53, // plid
+ 0x80, 0x81, 0x82, 0x83};
+
+const std::vector<uint8_t> userHeaderSection{
+ // section header
+ 0x55, 0x48, // ID 'UH'
+ 0x00, 0x18, // Size
+ 0x01, 0x0A, // version, subtype
+ 0x0B, 0x0C, // comp ID
+
+ 0x10, 0x04, // subsystem, scope
+ 0x20, 0x00, // severity, type
+ 0x00, 0x00, 0x00, 0x00, // reserved
+ 0x03, 0x04, // problem domain, vector
+ 0x80, 0xC0, // action flags
+ 0x00, 0x00, 0x00, 0x00 // reserved
+};
+
+const std::vector<uint8_t> srcSectionNoCallouts{
+
+ // Header
+ 'P', 'S', 0x00, 0x50, 0x01, 0x01, 0x02, 0x02,
+
+ 0x02, 0x00, 0x00, // version, flags, reserved
+ 0x09, 0x00, 0x00, // hex word count, reserved2B
+ 0x00, 0x48, // SRC structure size
+
+ // Hex words 2 - 9
+ 0x02, 0x02, 0x02, 0x55, 0x03, 0x03, 0x03, 0x10, 0x04, 0x04, 0x04, 0x04,
+ 0x05, 0x05, 0x05, 0x05, 0x06, 0x06, 0x06, 0x06, 0x07, 0x07, 0x07, 0x07,
+ 0x08, 0x08, 0x08, 0x08, 0x09, 0x09, 0x09, 0x09,
+ // ASCII string
+ 'B', 'D', '8', 'D', '5', '6', '7', '8', ' ', ' ', ' ', ' ', ' ', ' ', ' ',
+ ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ',
+ ' ', ' '};
+
+const std::vector<uint8_t> failingMTMSSection{
+ // Header
+ 0x4D, 0x54, 0x00, 0x1C, 0x01, 0x00, 0x20, 0x00,
+
+ 'T', 'T', 'T', 'T', '-', 'M', 'M', 'M', '1', '2',
+ '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C'};
+
+const std::vector<uint8_t> UserDataSection{
+ // Header
+ 0x55, 0x44, 0x00, 0x10, 0x00, 0x00, 0x20, 0x00,
+
+ 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08};
+
+const std::vector<uint8_t> ExtUserHeaderSection{
+ // Header
+ 'E', 'H', 0x00, 0x60, 0x01, 0x00, 0x03, 0x04,
+
+ // MTMS
+ 'T', 'T', 'T', 'T', '-', 'M', 'M', 'M', '1', '2', '3', '4', '5', '6', '7',
+ '8', '9', 'A', 'B', 'C',
+
+ // Server FW version
+ 'S', 'E', 'R', 'V', 'E', 'R', '_', 'V', 'E', 'R', 'S', 'I', 'O', 'N', '\0',
+ '\0',
+
+ // Subsystem FW Version
+ 'B', 'M', 'C', '_', 'V', 'E', 'R', 'S', 'I', 'O', 'N', '\0', '\0', '\0',
+ '\0', '\0',
+
+ 0x00, 0x00, 0x00, 0x00, // Reserved
+ 0x20, 0x25, 0x10, 0x20, 0x30, 0x40, 0x50, 0x60, // Ref time
+ 0x00, 0x00, 0x00, // Reserved
+
+ // SymptomID length and symptom ID
+ 20, 'B', 'D', '8', 'D', '4', '2', '0', '0', '_', '1', '2', '3', '4', '5',
+ '6', '7', '8', '\0', '\0', '\0'};
+
+const std::vector<uint8_t> srcFRUIdentityCallout{
+ 'I', 'D', 0x1C, 0x1D, // type, size, flags
+ '1', '2', '3', '4', // PN
+ '5', '6', '7', 0x00, 'A', 'A', 'A', 'A', // CCIN
+ '1', '2', '3', '4', '5', '6', '7', '8', // SN
+ '9', 'A', 'B', 'C'};
+
+const std::vector<uint8_t> srcPCEIdentityCallout{
+ 'P', 'E', 0x24, 0x00, // type, size, flags
+ 'T', 'T', 'T', 'T', '-', 'M', 'M', 'M', // MTM
+ '1', '2', '3', '4', '5', '6', '7', // SN
+ '8', '9', 'A', 'B', 'C', 'P', 'C', 'E', // Name + null padded
+ 'N', 'A', 'M', 'E', '1', '2', 0x00, 0x00, 0x00};
+
+const std::vector<uint8_t> srcMRUCallout{
+ 'M', 'R', 0x28, 0x04, // ID, size, flags
+ 0x00, 0x00, 0x00, 0x00, // Reserved
+ 0x00, 0x00, 0x00, 'H', // priority 0
+ 0x01, 0x01, 0x01, 0x01, // MRU ID 0
+ 0x00, 0x00, 0x00, 'M', // priority 1
+ 0x02, 0x02, 0x02, 0x02, // MRU ID 1
+ 0x00, 0x00, 0x00, 'L', // priority 2
+ 0x03, 0x03, 0x03, 0x03, // MRU ID 2
+ 0x00, 0x00, 0x00, 'H', // priority 3
+ 0x04, 0x04, 0x04, 0x04, // MRU ID 3
+};
+
+constexpr size_t sectionCountOffset = 27;
+
+std::vector<uint8_t> pelDataFactory(TestPELType type)
+{
+ std::vector<uint8_t> data;
+
+ switch (type)
+ {
+ case TestPELType::pelSimple:
+ data.insert(data.end(), privateHeaderSection.begin(),
+ privateHeaderSection.end());
+ data.insert(data.end(), userHeaderSection.begin(),
+ userHeaderSection.end());
+ data.insert(data.end(), srcSectionNoCallouts.begin(),
+ srcSectionNoCallouts.end());
+ data.insert(data.end(), failingMTMSSection.begin(),
+ failingMTMSSection.end());
+ data.insert(data.end(), UserDataSection.begin(),
+ UserDataSection.end());
+ data.insert(data.end(), ExtUserHeaderSection.begin(),
+ ExtUserHeaderSection.end());
+ data.at(sectionCountOffset) = 6;
+ break;
+ case TestPELType::privateHeaderSection:
+ data.insert(data.end(), privateHeaderSection.begin(),
+ privateHeaderSection.end());
+ break;
+ case TestPELType::userHeaderSection:
+ data.insert(data.end(), userHeaderSection.begin(),
+ userHeaderSection.end());
+ break;
+ case TestPELType::primarySRCSection:
+ data.insert(data.end(), srcSectionNoCallouts.begin(),
+ srcSectionNoCallouts.end());
+ break;
+ case TestPELType::primarySRCSection2Callouts:
+ {
+ // Start with the no-callouts SRC, and add the callouts section
+ // from above.
+ auto src = srcSectionNoCallouts;
+ auto callouts =
+ srcDataFactory(TestSRCType::calloutSection2Callouts);
+
+ src.insert(src.end(), callouts.begin(), callouts.end());
+
+ // Set the flag that says there are callouts
+ // One byte after the 8B header
+ src[8 + 1] |= 0x01;
+
+ // Set the new sizes
+ uint16_t size = src.size();
+ Stream stream{src};
+
+ stream.offset(2); // In the header
+ stream << size;
+
+ // In the SRC - the size field doesn't include the header
+ size -= 8;
+ stream.offset(8 + 6);
+ stream << size;
+
+ data.insert(data.end(), src.begin(), src.end());
+ break;
+ }
+ case TestPELType::failingMTMSSection:
+ data.insert(data.end(), failingMTMSSection.begin(),
+ failingMTMSSection.end());
+ }
+ return data;
+}
+
+std::vector<uint8_t> srcDataFactory(TestSRCType type)
+{
+ switch (type)
+ {
+ case TestSRCType::fruIdentityStructure:
+ return srcFRUIdentityCallout;
+
+ case TestSRCType::pceIdentityStructure:
+ return srcPCEIdentityCallout;
+
+ case TestSRCType::mruStructure:
+ return srcMRUCallout;
+
+ case TestSRCType::calloutStructureA:
+ {
+ // Add just the FRU identity substructure to the base structure
+ std::vector<uint8_t> data{
+ 0xFF, 0x28, 'H', 4, // size, flags, priority, LC length
+ 'U', '4', '2', 0x00 // LC
+ };
+
+ data.insert(data.end(), srcFRUIdentityCallout.begin(),
+ srcFRUIdentityCallout.end());
+
+ // The final size
+ data[0] = data.size();
+ return data;
+ }
+ case TestSRCType::calloutStructureB:
+ {
+ // Add all 3 substructures to the base structure
+
+ std::vector<uint8_t> data{
+ 0xFF, 0x2F, 'L', 8, // size, flags, priority, LC length
+ 'U', '1', '2', '-', 'P', '1', 0x00, 0x00 // LC
+ };
+ data.insert(data.end(), srcFRUIdentityCallout.begin(),
+ srcFRUIdentityCallout.end());
+ data.insert(data.end(), srcPCEIdentityCallout.begin(),
+ srcPCEIdentityCallout.end());
+ data.insert(data.end(), srcMRUCallout.begin(), srcMRUCallout.end());
+
+ // The final size
+ data[0] = data.size();
+ return data;
+ }
+ case TestSRCType::calloutSection2Callouts:
+ {
+ std::vector<uint8_t> data{0xC0, 0x00, 0x00,
+ 0x00}; // ID, flags, length in words
+
+ // Add 2 callouts
+ auto callout = srcDataFactory(TestSRCType::calloutStructureA);
+ data.insert(data.end(), callout.begin(), callout.end());
+
+ callout = srcDataFactory(TestSRCType::calloutStructureB);
+ data.insert(data.end(), callout.begin(), callout.end());
+
+ // Set the actual word length value at offset 2
+ Stream stream{data};
+ uint16_t wordLength = data.size() / 4;
+ stream.offset(2);
+ stream << wordLength;
+ stream.offset(0);
+
+ return data;
+ }
+ }
+ return {};
+}
+
+std::unique_ptr<std::vector<uint8_t>> readPELFile(const fs::path& path)
+{
+ std::ifstream file{path};
+
+ auto pel = std::make_unique<std::vector<uint8_t>>(
+ std::istreambuf_iterator<char>(file), std::istreambuf_iterator<char>());
+ return pel;
+}
diff --git a/test/openpower-pels/pel_utils.hpp b/test/openpower-pels/pel_utils.hpp
new file mode 100644
index 0000000..fae62cd
--- /dev/null
+++ b/test/openpower-pels/pel_utils.hpp
@@ -0,0 +1,106 @@
+#include "extensions/openpower-pels/paths.hpp"
+
+#include <filesystem>
+#include <memory>
+#include <vector>
+
+#include <gtest/gtest.h>
+
+/**
+ * @brief Test fixture to remove the pelID file that PELs use.
+ */
+class CleanLogID : public ::testing::Test
+{
+ protected:
+ static void SetUpTestCase()
+ {
+ pelIDFile = openpower::pels::getPELIDFile();
+ }
+
+ static void TearDownTestCase()
+ {
+ std::filesystem::remove_all(
+ std::filesystem::path{pelIDFile}.parent_path());
+ }
+
+ static std::filesystem::path pelIDFile;
+};
+
+class CleanPELFiles : public ::testing::Test
+{
+ protected:
+ void SetUp() override
+ {
+ pelIDFile = openpower::pels::getPELIDFile();
+ repoPath = openpower::pels::getPELRepoPath();
+ registryPath = openpower::pels::getMessageRegistryPath();
+ }
+
+ void TearDown() override
+ {
+ std::filesystem::remove_all(
+ std::filesystem::path{pelIDFile}.parent_path());
+ std::filesystem::remove_all(repoPath);
+ std::filesystem::remove_all(registryPath);
+ }
+
+ static std::filesystem::path pelIDFile;
+ static std::filesystem::path repoPath;
+ static std::filesystem::path registryPath;
+};
+
+/**
+ * @brief Tells the factory which PEL to create
+ */
+enum class TestPELType
+{
+ pelSimple,
+ privateHeaderSection,
+ userHeaderSection,
+ primarySRCSection,
+ primarySRCSection2Callouts,
+ failingMTMSSection
+};
+
+/**
+ * @brief Tells the SRC factory which data to create
+ */
+enum class TestSRCType
+{
+ fruIdentityStructure,
+ pceIdentityStructure,
+ mruStructure,
+ calloutStructureA,
+ calloutStructureB,
+ calloutSection2Callouts
+};
+
+/**
+ * @brief PEL data factory, for testing
+ *
+ * @param[in] type - the type of data to create
+ *
+ * @return std::vector<uint8_t> - the PEL data
+ */
+std::vector<uint8_t> pelDataFactory(TestPELType type);
+
+/**
+ * @brief SRC data factory, for testing
+ *
+ * Provides pieces of the SRC PEL section, such as a callout.
+ *
+ * @param[in] type - the type of data to create
+ *
+ * @return std::vector<uint8_t> - The SRC data
+ */
+std::vector<uint8_t> srcDataFactory(TestSRCType type);
+
+/**
+ * @brief Helper function to read raw PEL data from a file
+ *
+ * @param[in] path - the path to read
+ *
+ * @return std::unique_ptr<std::vector<uint8_t>> - the data from the file
+ */
+std::unique_ptr<std::vector<uint8_t>>
+ readPELFile(const std::filesystem::path& path);
diff --git a/test/openpower-pels/pel_values_test.cpp b/test/openpower-pels/pel_values_test.cpp
new file mode 100644
index 0000000..843cb07
--- /dev/null
+++ b/test/openpower-pels/pel_values_test.cpp
@@ -0,0 +1,40 @@
+/**
+ * Copyright © 2019 IBM Corporation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#include "extensions/openpower-pels/pel_values.hpp"
+
+#include <gtest/gtest.h>
+
+using namespace openpower::pels::pel_values;
+
+TEST(PELFieldsTest, TestFindFields)
+{
+ auto s = findByValue(0x5D, subsystemValues);
+ ASSERT_NE(s, subsystemValues.end());
+ ASSERT_EQ(0x5D, std::get<fieldValuePos>(*s));
+ ASSERT_EQ("cec_service_network", std::get<registryNamePos>(*s));
+
+ s = findByName("cec_clocks", subsystemValues);
+ ASSERT_NE(s, subsystemValues.end());
+ ASSERT_EQ(0x58, std::get<fieldValuePos>(*s));
+ ASSERT_EQ("cec_clocks", std::get<registryNamePos>(*s));
+ ASSERT_EQ("CEC Hardware: Clock", std::get<descriptionPos>(*s));
+
+ s = findByValue(0xFF, subsystemValues);
+ ASSERT_EQ(s, subsystemValues.end());
+
+ s = findByName("foo", subsystemValues);
+ ASSERT_EQ(s, subsystemValues.end());
+}
diff --git a/test/openpower-pels/private_header_test.cpp b/test/openpower-pels/private_header_test.cpp
new file mode 100644
index 0000000..0349a94
--- /dev/null
+++ b/test/openpower-pels/private_header_test.cpp
@@ -0,0 +1,194 @@
+/**
+ * Copyright © 2019 IBM Corporation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#include "extensions/openpower-pels/private_header.hpp"
+#include "pel_utils.hpp"
+
+#include <gtest/gtest.h>
+
+using namespace openpower::pels;
+
+class PrivateHeaderTest : public CleanLogID
+{
+};
+
+TEST_F(PrivateHeaderTest, SizeTest)
+{
+ EXPECT_EQ(PrivateHeader::flattenedSize(), 48);
+}
+
+TEST_F(PrivateHeaderTest, UnflattenFlattenTest)
+{
+ auto data = pelDataFactory(TestPELType::privateHeaderSection);
+
+ Stream stream(data);
+ PrivateHeader ph(stream);
+ EXPECT_EQ(ph.valid(), true);
+
+ EXPECT_EQ(ph.header().id, 0x5048);
+ EXPECT_EQ(ph.header().size, PrivateHeader::flattenedSize());
+ EXPECT_EQ(ph.header().version, 0x01);
+ EXPECT_EQ(ph.header().subType, 0x02);
+ EXPECT_EQ(ph.header().componentID, 0x0304);
+
+ auto ct = ph.createTimestamp();
+ EXPECT_EQ(ct.yearMSB, 0x20);
+ EXPECT_EQ(ct.yearLSB, 0x30);
+ EXPECT_EQ(ct.month, 0x05);
+ EXPECT_EQ(ct.day, 0x09);
+ EXPECT_EQ(ct.hour, 0x11);
+ EXPECT_EQ(ct.minutes, 0x1E);
+ EXPECT_EQ(ct.seconds, 0x01);
+ EXPECT_EQ(ct.hundredths, 0x63);
+
+ auto mt = ph.commitTimestamp();
+ EXPECT_EQ(mt.yearMSB, 0x20);
+ EXPECT_EQ(mt.yearLSB, 0x31);
+ EXPECT_EQ(mt.month, 0x06);
+ EXPECT_EQ(mt.day, 0x0F);
+ EXPECT_EQ(mt.hour, 0x09);
+ EXPECT_EQ(mt.minutes, 0x22);
+ EXPECT_EQ(mt.seconds, 0x3A);
+ EXPECT_EQ(mt.hundredths, 0x00);
+
+ EXPECT_EQ(ph.creatorID(), 0xAA);
+ EXPECT_EQ(ph.logType(), 0x00);
+ EXPECT_EQ(ph.sectionCount(), 0x02);
+ EXPECT_EQ(ph.obmcLogID(), 0x90919293);
+
+ char expected[] = {0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x00};
+ EXPECT_TRUE(memcmp(ph.creatorVersion().version, expected, 8) == 0);
+
+ EXPECT_EQ(ph.plid(), 0x50515253);
+ EXPECT_EQ(ph.id(), 0x80818283);
+
+ // Now flatten into a vector and check that this vector
+ // matches the original one.
+ std::vector<uint8_t> newData;
+ Stream newStream(newData);
+
+ ph.flatten(newStream);
+ EXPECT_EQ(data, newData);
+
+ // Change a field, then flatten and unflatten again
+ ph.setID(0x55);
+
+ newStream.offset(0);
+ newData.clear();
+ ph.flatten(newStream);
+ EXPECT_NE(data, newData);
+
+ newStream.offset(0);
+ PrivateHeader newPH(newStream);
+
+ EXPECT_TRUE(newPH.valid());
+ EXPECT_EQ(newPH.id(), 0x55);
+}
+
+TEST_F(PrivateHeaderTest, ShortDataTest)
+{
+ auto data = pelDataFactory(TestPELType::privateHeaderSection);
+ data.resize(PrivateHeader::flattenedSize() - 1);
+ Stream stream(data);
+
+ PrivateHeader ph(stream);
+
+ EXPECT_EQ(ph.valid(), false);
+}
+
+TEST_F(PrivateHeaderTest, CorruptDataTest1)
+{
+ auto data = pelDataFactory(TestPELType::privateHeaderSection);
+ Stream stream(data);
+
+ data.at(0) = 0; // corrupt the section ID
+
+ PrivateHeader ph(stream);
+
+ EXPECT_EQ(ph.valid(), false);
+}
+
+TEST_F(PrivateHeaderTest, CorruptDataTest2)
+{
+ auto data = pelDataFactory(TestPELType::privateHeaderSection);
+ Stream stream(data);
+
+ data.at(4) = 0x22; // corrupt the version
+
+ PrivateHeader ph(stream);
+
+ EXPECT_EQ(ph.valid(), false);
+}
+
+TEST_F(PrivateHeaderTest, CorruptDataTest3)
+{
+ auto data = pelDataFactory(TestPELType::privateHeaderSection);
+ Stream stream(data);
+
+ data.at(27) = 1; // corrupt the section count
+
+ PrivateHeader ph(stream);
+
+ EXPECT_EQ(ph.valid(), false);
+}
+
+// Construct a PrivateHeader from scratch
+TEST_F(PrivateHeaderTest, ConstructionTest)
+{
+ tm time_tm;
+ time_tm.tm_year = 125;
+ time_tm.tm_mon = 11;
+ time_tm.tm_mday = 31;
+ time_tm.tm_hour = 15;
+ time_tm.tm_min = 23;
+ time_tm.tm_sec = 42;
+ time_tm.tm_isdst = 0;
+
+ // Convert the above time into a uint64_t in ms since the epoch time
+ auto timepoint = std::chrono::system_clock::from_time_t(mktime(&time_tm));
+ auto timestamp = std::chrono::duration_cast<std::chrono::milliseconds>(
+ timepoint.time_since_epoch())
+ .count();
+
+ PrivateHeader ph(0x3300, 42, timestamp);
+
+ EXPECT_TRUE(ph.valid());
+ EXPECT_EQ(ph.header().id, 0x5048);
+ EXPECT_EQ(ph.header().size, PrivateHeader::flattenedSize());
+ EXPECT_EQ(ph.header().version, 0x01);
+ EXPECT_EQ(ph.header().subType, 0x00);
+ EXPECT_EQ(ph.header().componentID, 0x3300);
+
+ auto& ct = ph.createTimestamp();
+ EXPECT_EQ(ct.yearMSB, 0x20);
+ EXPECT_EQ(ct.yearLSB, 0x25);
+ EXPECT_EQ(ct.month, 0x12);
+ EXPECT_EQ(ct.day, 0x31);
+ EXPECT_EQ(ct.hour, 0x15);
+ EXPECT_EQ(ct.minutes, 0x23);
+ EXPECT_EQ(ct.seconds, 0x42);
+ EXPECT_EQ(ct.hundredths, 0x00);
+
+ EXPECT_EQ(ph.creatorID(), 'O');
+ EXPECT_EQ(ph.logType(), 0x00);
+ EXPECT_EQ(ph.sectionCount(), 0x01);
+ EXPECT_EQ(ph.obmcLogID(), 42);
+
+ char expected[] = {0, 0, 0, 0, 0, 0, 0, 0};
+ EXPECT_TRUE(memcmp(ph.creatorVersion().version, expected, 8) == 0);
+
+ EXPECT_EQ(ph.id(), 0x50000001);
+ EXPECT_EQ(ph.id(), ph.plid());
+}
diff --git a/test/openpower-pels/real_pel_test.cpp b/test/openpower-pels/real_pel_test.cpp
new file mode 100644
index 0000000..a170812
--- /dev/null
+++ b/test/openpower-pels/real_pel_test.cpp
@@ -0,0 +1,570 @@
+/**
+ * Copyright © 2019 IBM Corporation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#include "elog_entry.hpp"
+#include "extensions/openpower-pels/pel.hpp"
+#include "pel_utils.hpp"
+
+#include <filesystem>
+#include <fstream>
+
+#include <gtest/gtest.h>
+
+namespace fs = std::filesystem;
+using namespace openpower::pels;
+
+class PELTest : public CleanLogID
+{
+};
+
+// A PEL from a real system
+const std::vector<uint8_t> realPELData{
+ 0x50, 0x48, 0x0, 0x30, 0x1, 0x0, 0xA8, 0x0, 0x20, 0x19, 0x6, 0x14,
+ 0x12, 0x0, 0x41, 0x51, 0x20, 0x19, 0x6, 0x14, 0x12, 0x0, 0x41, 0x56,
+ 0x45, 0x0, 0x1, 0x14, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x50, 0x1, 0xF, 0xA5, 0x50, 0x1, 0xF, 0xA5,
+ 0x55, 0x48, 0x0, 0x18, 0x1, 0x0, 0xF1, 0x0, 0x81, 0x3, 0x0, 0x1,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x60, 0x0, 0x0, 0x1, 0x44, 0x0,
+ 0x50, 0x53, 0x0, 0x74, 0x1, 0x1, 0xA8, 0x0, 0x2, 0x1, 0x0, 0x9,
+ 0x0, 0x0, 0x0, 0x6C, 0x3, 0x1, 0x0, 0xF0, 0x2C, 0xC6, 0x1B, 0x10,
+ 0xC1, 0x39, 0x20, 0x0, 0x40, 0x0, 0x0, 0xFF, 0x10, 0x69, 0x14, 0xD8,
+ 0x0, 0x0, 0x0, 0x1, 0x0, 0x0, 0x0, 0x3, 0x0, 0x0, 0x0, 0x0,
+ 0x42, 0x31, 0x38, 0x31, 0x41, 0x38, 0x30, 0x45, 0x20, 0x20, 0x20, 0x20,
+ 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
+ 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0xC0, 0x0, 0x0, 0x9,
+ 0x10, 0x28, 0x48, 0x0, 0x49, 0x44, 0xC, 0x42, 0x46, 0x53, 0x50, 0x53,
+ 0x50, 0x30, 0x34, 0x0, 0x10, 0x28, 0x4C, 0x0, 0x49, 0x44, 0xC, 0x42,
+ 0x46, 0x53, 0x50, 0x53, 0x50, 0x30, 0x36, 0x0, 0x45, 0x48, 0x0, 0x60,
+ 0x1, 0x0, 0x31, 0x0, 0x38, 0x34, 0x30, 0x38, 0x2D, 0x45, 0x38, 0x45,
+ 0x31, 0x30, 0x36, 0x37, 0x41, 0x44, 0x56, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x54, 0x56, 0x38, 0x36, 0x30, 0x5F, 0x32, 0x30, 0x37, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x62, 0x30, 0x36, 0x31, 0x33, 0x61, 0x5F, 0x31,
+ 0x39, 0x32, 0x34, 0x2E, 0x38, 0x36, 0x31, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x14,
+ 0x42, 0x31, 0x38, 0x31, 0x41, 0x38, 0x30, 0x45, 0x5F, 0x32, 0x43, 0x43,
+ 0x36, 0x31, 0x42, 0x31, 0x30, 0x0, 0x0, 0x0, 0x55, 0x44, 0x0, 0x9C,
+ 0x2, 0x4, 0x31, 0x0, 0x0, 0x0, 0xB, 0x53, 0x2F, 0x6F, 0x70, 0x74,
+ 0x2F, 0x66, 0x69, 0x70, 0x73, 0x2F, 0x62, 0x69, 0x6E, 0x2F, 0x66, 0x77,
+ 0x64, 0x62, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x66, 0x69, 0x70, 0x73,
+ 0x38, 0x36, 0x31, 0x2F, 0x62, 0x30, 0x36, 0x31, 0x33, 0x61, 0x5F, 0x31,
+ 0x39, 0x32, 0x34, 0x2E, 0x38, 0x36, 0x31, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x0, 0x0, 0x0, 0x2,
+ 0x50, 0x0, 0x0, 0x2, 0x20, 0x0, 0x1, 0xC, 0x0, 0x0, 0x0, 0x9,
+ 0x0, 0x4, 0x70, 0xD0, 0x0, 0x0, 0x0, 0x0, 0x4D, 0x54, 0x0, 0x1C,
+ 0x1, 0x0, 0x31, 0x0, 0x38, 0x34, 0x30, 0x38, 0x2D, 0x45, 0x38, 0x45,
+ 0x31, 0x30, 0x36, 0x37, 0x41, 0x44, 0x56, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x55, 0x44, 0x1, 0xF8, 0x1, 0xC, 0x31, 0x0, 0x1, 0x28, 0x4, 0x42,
+ 0x46, 0x57, 0x44, 0x42, 0x45, 0x52, 0x52, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0xF0, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x1, 0xF0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x7,
+ 0x0, 0x0, 0x5, 0x1C, 0x2D, 0x86, 0x75, 0xD0, 0x0, 0x0, 0xC, 0x9B,
+ 0x0, 0x30, 0x43, 0x4F, 0x27, 0x67, 0x44, 0xB7, 0x0, 0x0, 0x0, 0xB5,
+ 0x66, 0x77, 0x64, 0x62, 0x54, 0x72, 0x61, 0x63, 0x45, 0x72, 0x72, 0x49,
+ 0x6E, 0x66, 0x6F, 0x0, 0x73, 0x65, 0x74, 0x42, 0x75, 0x73, 0x79, 0x54,
+ 0x69, 0x6D, 0x65, 0x6F, 0x75, 0x74, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1A,
+ 0x0, 0x0, 0xA8, 0x9, 0x50, 0x1, 0xE, 0xE3, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x4C, 0x0, 0x0, 0x5, 0x33, 0x1F, 0xEA, 0xE7, 0x12,
+ 0x0, 0x0, 0xD, 0x9D, 0x0, 0x30, 0x43, 0x4F, 0x27, 0x67, 0x44, 0xB7,
+ 0x0, 0x0, 0x0, 0xB5, 0x66, 0x77, 0x64, 0x62, 0x54, 0x72, 0x61, 0x63,
+ 0x45, 0x72, 0x72, 0x49, 0x6E, 0x66, 0x6F, 0x0, 0x73, 0x65, 0x74, 0x42,
+ 0x75, 0x73, 0x79, 0x54, 0x69, 0x6D, 0x65, 0x6F, 0x75, 0x74, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x1A, 0x0, 0x0, 0xA8, 0x9, 0x50, 0x1, 0xF, 0xA,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x4C, 0x0, 0x0, 0x5, 0x38,
+ 0x26, 0x43, 0xFB, 0x66, 0x0, 0x0, 0xB, 0x7D, 0x0, 0x30, 0x43, 0x4F,
+ 0x27, 0x67, 0x44, 0xB7, 0x0, 0x0, 0x0, 0xB5, 0x66, 0x77, 0x64, 0x62,
+ 0x54, 0x72, 0x61, 0x63, 0x45, 0x72, 0x72, 0x49, 0x6E, 0x66, 0x6F, 0x0,
+ 0x73, 0x65, 0x74, 0x42, 0x75, 0x73, 0x79, 0x54, 0x69, 0x6D, 0x65, 0x6F,
+ 0x75, 0x74, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1A, 0x0, 0x0, 0xA8, 0x9,
+ 0x50, 0x1, 0xF, 0xE, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x4C,
+ 0x0, 0x0, 0x5, 0x72, 0x13, 0xB8, 0x3D, 0x1C, 0x0, 0x0, 0x23, 0x33,
+ 0x0, 0x30, 0x43, 0x4F, 0x27, 0x67, 0x44, 0xB7, 0x0, 0x0, 0x0, 0xB5,
+ 0x66, 0x77, 0x64, 0x62, 0x54, 0x72, 0x61, 0x63, 0x45, 0x72, 0x72, 0x49,
+ 0x6E, 0x66, 0x6F, 0x0, 0x73, 0x65, 0x74, 0x42, 0x75, 0x73, 0x79, 0x54,
+ 0x69, 0x6D, 0x65, 0x6F, 0x75, 0x74, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1A,
+ 0x0, 0x0, 0xA8, 0x9, 0x50, 0x1, 0xF, 0x5D, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x4C, 0x0, 0x0, 0x5, 0x88, 0x20, 0xE1, 0xE0, 0x7,
+ 0x0, 0x0, 0xD, 0x82, 0x0, 0x30, 0x43, 0x4F, 0x27, 0x67, 0x44, 0xB7,
+ 0x0, 0x0, 0x0, 0xB5, 0x66, 0x77, 0x64, 0x62, 0x54, 0x72, 0x61, 0x63,
+ 0x45, 0x72, 0x72, 0x49, 0x6E, 0x66, 0x6F, 0x0, 0x73, 0x65, 0x74, 0x42,
+ 0x75, 0x73, 0x79, 0x54, 0x69, 0x6D, 0x65, 0x6F, 0x75, 0x74, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x1A, 0x0, 0x0, 0xA8, 0x9, 0x50, 0x1, 0xF, 0x9B,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x4C, 0x0, 0x0, 0x5, 0x89,
+ 0x12, 0xE, 0xFE, 0x6E, 0x0, 0x0, 0xB, 0x69, 0x0, 0x30, 0x43, 0x4F,
+ 0x27, 0x67, 0x44, 0xB7, 0x0, 0x0, 0x0, 0xB5, 0x66, 0x77, 0x64, 0x62,
+ 0x54, 0x72, 0x61, 0x63, 0x45, 0x72, 0x72, 0x49, 0x6E, 0x66, 0x6F, 0x0,
+ 0x73, 0x65, 0x74, 0x42, 0x75, 0x73, 0x79, 0x54, 0x69, 0x6D, 0x65, 0x6F,
+ 0x75, 0x74, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1A, 0x0, 0x0, 0xA8, 0x9,
+ 0x50, 0x1, 0xF, 0xA4, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x4C,
+ 0x55, 0x44, 0x0, 0x40, 0x1, 0x1, 0xA8, 0x0, 0x6E, 0x6F, 0x20, 0x73,
+ 0x75, 0x63, 0x68, 0x20, 0x74, 0x61, 0x62, 0x6C, 0x65, 0x3A, 0x20, 0x63,
+ 0x62, 0x6C, 0x76, 0x5F, 0x63, 0x61, 0x62, 0x6C, 0x65, 0x5F, 0x63, 0x6F,
+ 0x6E, 0x6E, 0x65, 0x63, 0x74, 0x69, 0x6F, 0x6E, 0x5F, 0x73, 0x74, 0x61,
+ 0x74, 0x75, 0x73, 0x5F, 0x70, 0x75, 0x62, 0x6C, 0x69, 0x63, 0x5F, 0x76,
+ 0x69, 0x65, 0x77, 0x0, 0x55, 0x44, 0x1, 0xE8, 0x1, 0xC, 0x31, 0x0,
+ 0x1, 0x28, 0x4, 0x42, 0x46, 0x57, 0x44, 0x42, 0x53, 0x52, 0x56, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0xE0,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0xE0, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0xB, 0x0, 0x0, 0x5, 0x89, 0x11, 0x9B, 0xBA, 0x9,
+ 0x0, 0x0, 0x23, 0x88, 0x0, 0x18, 0x43, 0x4F, 0x80, 0x10, 0xFA, 0x28,
+ 0x0, 0x0, 0xC, 0xFD, 0x73, 0x65, 0x74, 0x42, 0x75, 0x73, 0x79, 0x54,
+ 0x69, 0x6D, 0x65, 0x6F, 0x75, 0x74, 0x0, 0x0, 0xDB, 0xFB, 0xCB, 0x98,
+ 0x0, 0x1, 0xD4, 0xC0, 0x0, 0x0, 0x0, 0x34, 0x0, 0x0, 0x5, 0x89,
+ 0x12, 0x4, 0x1A, 0xF1, 0x0, 0x0, 0x23, 0x88, 0x0, 0x18, 0x43, 0x4F,
+ 0xCB, 0x1C, 0xBA, 0x66, 0x0, 0x0, 0x1F, 0x6D, 0x63, 0x68, 0x61, 0x6E,
+ 0x67, 0x65, 0x55, 0x73, 0x65, 0x43, 0x6E, 0x74, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0xC, 0xDB, 0xFB, 0xCB, 0x98, 0x0, 0x0, 0x0, 0x34,
+ 0x0, 0x0, 0x5, 0x89, 0x12, 0x36, 0x78, 0xA9, 0x0, 0x0, 0x23, 0x88,
+ 0x0, 0x0, 0x43, 0x4F, 0xCB, 0x12, 0xB6, 0x5D, 0x0, 0x0, 0x9, 0xF2,
+ 0x0, 0x0, 0x0, 0x1C, 0x0, 0x0, 0x5, 0x89, 0x12, 0x36, 0xB8, 0x93,
+ 0x0, 0x0, 0x23, 0x88, 0x0, 0x10, 0x43, 0x4F, 0x89, 0x7D, 0x0, 0x8C,
+ 0x0, 0x0, 0x23, 0x5D, 0x73, 0x65, 0x72, 0x76, 0x44, 0x65, 0x74, 0x61,
+ 0x63, 0x68, 0x0, 0x0, 0xDB, 0xFB, 0xCB, 0x98, 0x0, 0x0, 0x0, 0x2C,
+ 0x0, 0x0, 0x5, 0x89, 0x12, 0x36, 0xD2, 0x7A, 0x0, 0x0, 0x23, 0x88,
+ 0x0, 0x0, 0x43, 0x4F, 0xBD, 0xF0, 0x6E, 0xE3, 0x0, 0x0, 0x9, 0xF4,
+ 0x0, 0x0, 0x0, 0x1C, 0x0, 0x0, 0x5, 0x89, 0x12, 0xAA, 0x51, 0x96,
+ 0x0, 0x0, 0x23, 0x88, 0x0, 0x0, 0x43, 0x4F, 0x8F, 0x6A, 0x83, 0x3,
+ 0x0, 0x0, 0x9, 0xA9, 0x0, 0x0, 0x0, 0x1C, 0x0, 0x0, 0x5, 0x89,
+ 0x12, 0xAE, 0x7B, 0x85, 0x0, 0x0, 0x23, 0x88, 0x0, 0x10, 0x43, 0x4F,
+ 0xF3, 0x35, 0x3F, 0x8D, 0x0, 0x0, 0x22, 0x35, 0x73, 0x65, 0x72, 0x76,
+ 0x41, 0x74, 0x74, 0x61, 0x63, 0x68, 0x0, 0x0, 0x9D, 0x86, 0xE6, 0xD3,
+ 0x0, 0x0, 0x0, 0x2C, 0x0, 0x0, 0x5, 0x89, 0x13, 0xA5, 0x23, 0xFA,
+ 0x0, 0x0, 0x23, 0x88, 0x0, 0x0, 0x43, 0x4F, 0xFC, 0x5A, 0x7F, 0x97,
+ 0x0, 0x0, 0x9, 0xB3, 0x0, 0x0, 0x0, 0x1C, 0x0, 0x0, 0x5, 0x89,
+ 0x14, 0x5B, 0x57, 0xCE, 0x0, 0x0, 0x23, 0x88, 0x0, 0x0, 0x43, 0x4F,
+ 0x4, 0x88, 0x89, 0x7B, 0x0, 0x0, 0x9, 0x3B, 0x0, 0x0, 0x0, 0x1C,
+ 0x0, 0x0, 0x5, 0x89, 0x14, 0x5F, 0xCC, 0x54, 0x0, 0x0, 0x23, 0x88,
+ 0x0, 0x50, 0x43, 0x4F, 0x94, 0xA2, 0x26, 0x7C, 0x0, 0x0, 0x14, 0xE1,
+ 0x70, 0x72, 0x65, 0x70, 0x61, 0x72, 0x65, 0x53, 0x74, 0x6D, 0x74, 0x0,
+ 0x0, 0x0, 0x0, 0x1, 0x6E, 0x6F, 0x20, 0x73, 0x75, 0x63, 0x68, 0x20,
+ 0x74, 0x61, 0x62, 0x6C, 0x65, 0x3A, 0x20, 0x63, 0x62, 0x6C, 0x76, 0x5F,
+ 0x63, 0x61, 0x62, 0x6C, 0x65, 0x5F, 0x63, 0x6F, 0x6E, 0x6E, 0x65, 0x63,
+ 0x74, 0x69, 0x6F, 0x6E, 0x5F, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x5F,
+ 0x70, 0x75, 0x62, 0x6C, 0x69, 0x63, 0x5F, 0x76, 0x69, 0x65, 0x77, 0x0,
+ 0x10, 0x69, 0x14, 0xD8, 0x0, 0x0, 0x0, 0x3, 0x0, 0x0, 0x0, 0x6C,
+ 0x55, 0x44, 0x3, 0xC0, 0x1, 0xC, 0x31, 0x0, 0x1, 0x28, 0x4, 0x42,
+ 0x46, 0x57, 0x44, 0x42, 0x53, 0x51, 0x4C, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x3, 0xB8, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x3, 0xB8, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xB,
+ 0x0, 0x0, 0x5, 0x89, 0x5, 0xFC, 0x1E, 0x6C, 0x0, 0x0, 0x23, 0x88,
+ 0x0, 0x40, 0x43, 0x4F, 0x92, 0x41, 0x1B, 0xD4, 0x0, 0x0, 0x21, 0x6,
+ 0x14, 0x9, 0xC2, 0x11, 0x73, 0x65, 0x6C, 0x65, 0x63, 0x74, 0x20, 0x2A,
+ 0x20, 0x66, 0x72, 0x6F, 0x6D, 0x20, 0x73, 0x79, 0x73, 0x2E, 0x70, 0x6F,
+ 0x6C, 0x69, 0x63, 0x79, 0x20, 0x77, 0x68, 0x65, 0x72, 0x65, 0x20, 0x6E,
+ 0x61, 0x6D, 0x65, 0x20, 0x3D, 0x20, 0x27, 0x70, 0x6F, 0x77, 0x65, 0x72,
+ 0x2D, 0x64, 0x65, 0x66, 0x2D, 0x66, 0x69, 0x6C, 0x65, 0x6E, 0x61, 0x6D,
+ 0x65, 0x73, 0x27, 0x0, 0x0, 0x0, 0x0, 0x5C, 0x0, 0x0, 0x5, 0x89,
+ 0x6, 0xB1, 0x42, 0x3B, 0x0, 0x0, 0x23, 0x88, 0x0, 0x40, 0x43, 0x4F,
+ 0x92, 0x41, 0x1B, 0xD4, 0x0, 0x0, 0x21, 0x6, 0x14, 0x9, 0xC2, 0x11,
+ 0x73, 0x65, 0x6C, 0x65, 0x63, 0x74, 0x20, 0x2A, 0x20, 0x66, 0x72, 0x6F,
+ 0x6D, 0x20, 0x73, 0x79, 0x73, 0x2E, 0x70, 0x6F, 0x6C, 0x69, 0x63, 0x79,
+ 0x20, 0x77, 0x68, 0x65, 0x72, 0x65, 0x20, 0x6E, 0x61, 0x6D, 0x65, 0x20,
+ 0x3D, 0x20, 0x27, 0x70, 0x6F, 0x77, 0x65, 0x72, 0x2D, 0x64, 0x65, 0x66,
+ 0x2D, 0x66, 0x69, 0x6C, 0x65, 0x6E, 0x61, 0x6D, 0x65, 0x73, 0x27, 0x0,
+ 0x0, 0x0, 0x0, 0x5C, 0x0, 0x0, 0x5, 0x89, 0x7, 0x14, 0xD2, 0x68,
+ 0x0, 0x0, 0x23, 0x88, 0x0, 0x40, 0x43, 0x4F, 0x92, 0x41, 0x1B, 0xD4,
+ 0x0, 0x0, 0x21, 0x6, 0x14, 0x9, 0xC2, 0x11, 0x73, 0x65, 0x6C, 0x65,
+ 0x63, 0x74, 0x20, 0x2A, 0x20, 0x66, 0x72, 0x6F, 0x6D, 0x20, 0x73, 0x79,
+ 0x73, 0x2E, 0x70, 0x6F, 0x6C, 0x69, 0x63, 0x79, 0x20, 0x77, 0x68, 0x65,
+ 0x72, 0x65, 0x20, 0x6E, 0x61, 0x6D, 0x65, 0x20, 0x3D, 0x20, 0x27, 0x70,
+ 0x6F, 0x77, 0x65, 0x72, 0x2D, 0x64, 0x65, 0x66, 0x2D, 0x66, 0x69, 0x6C,
+ 0x65, 0x6E, 0x61, 0x6D, 0x65, 0x73, 0x27, 0x0, 0x0, 0x0, 0x0, 0x5C,
+ 0x0, 0x0, 0x5, 0x89, 0x7, 0x9A, 0x9D, 0x6F, 0x0, 0x0, 0x23, 0x88,
+ 0x0, 0x40, 0x43, 0x4F, 0x92, 0x41, 0x1B, 0xD4, 0x0, 0x0, 0x21, 0x6,
+ 0x14, 0x9, 0xC2, 0x11, 0x73, 0x65, 0x6C, 0x65, 0x63, 0x74, 0x20, 0x2A,
+ 0x20, 0x66, 0x72, 0x6F, 0x6D, 0x20, 0x73, 0x79, 0x73, 0x2E, 0x70, 0x6F,
+ 0x6C, 0x69, 0x63, 0x79, 0x20, 0x77, 0x68, 0x65, 0x72, 0x65, 0x20, 0x6E,
+ 0x61, 0x6D, 0x65, 0x20, 0x3D, 0x20, 0x27, 0x70, 0x6F, 0x77, 0x65, 0x72,
+ 0x2D, 0x64, 0x65, 0x66, 0x2D, 0x66, 0x69, 0x6C, 0x65, 0x6E, 0x61, 0x6D,
+ 0x65, 0x73, 0x27, 0x0, 0x0, 0x0, 0x0, 0x5C, 0x0, 0x0, 0x5, 0x89,
+ 0x7, 0xD4, 0xF8, 0x2D, 0x0, 0x0, 0x23, 0x88, 0x0, 0x40, 0x43, 0x4F,
+ 0x92, 0x41, 0x1B, 0xD4, 0x0, 0x0, 0x21, 0x6, 0x14, 0x9, 0xC2, 0x11,
+ 0x73, 0x65, 0x6C, 0x65, 0x63, 0x74, 0x20, 0x2A, 0x20, 0x66, 0x72, 0x6F,
+ 0x6D, 0x20, 0x73, 0x79, 0x73, 0x2E, 0x70, 0x6F, 0x6C, 0x69, 0x63, 0x79,
+ 0x20, 0x77, 0x68, 0x65, 0x72, 0x65, 0x20, 0x6E, 0x61, 0x6D, 0x65, 0x20,
+ 0x3D, 0x20, 0x27, 0x70, 0x6F, 0x77, 0x65, 0x72, 0x2D, 0x64, 0x65, 0x66,
+ 0x2D, 0x66, 0x69, 0x6C, 0x65, 0x6E, 0x61, 0x6D, 0x65, 0x73, 0x27, 0x0,
+ 0x0, 0x0, 0x0, 0x5C, 0x0, 0x0, 0x5, 0x89, 0x12, 0xB4, 0xBC, 0xA6,
+ 0x0, 0x0, 0x23, 0x88, 0x0, 0x38, 0x43, 0x4F, 0x92, 0x41, 0x1B, 0xD4,
+ 0x0, 0x0, 0x21, 0x6, 0x9D, 0x86, 0xE6, 0xD3, 0x41, 0x54, 0x54, 0x41,
+ 0x43, 0x48, 0x20, 0x44, 0x41, 0x54, 0x41, 0x42, 0x41, 0x53, 0x45, 0x20,
+ 0x27, 0x2F, 0x6F, 0x70, 0x74, 0x2F, 0x70, 0x31, 0x2F, 0x66, 0x77, 0x73,
+ 0x6D, 0x2F, 0x73, 0x79, 0x73, 0x74, 0x65, 0x6D, 0x2E, 0x73, 0x79, 0x73,
+ 0x27, 0x20, 0x41, 0x53, 0x20, 0x73, 0x79, 0x73, 0x3B, 0xA, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x54, 0x0, 0x0, 0x5, 0x89, 0x12, 0xBC, 0xD8, 0x8A,
+ 0x0, 0x0, 0x23, 0x88, 0x0, 0x38, 0x43, 0x4F, 0x92, 0x41, 0x1B, 0xD4,
+ 0x0, 0x0, 0x21, 0x6, 0x9D, 0x86, 0xE6, 0xD3, 0x41, 0x54, 0x54, 0x41,
+ 0x43, 0x48, 0x20, 0x44, 0x41, 0x54, 0x41, 0x42, 0x41, 0x53, 0x45, 0x20,
+ 0x27, 0x2F, 0x6F, 0x70, 0x74, 0x2F, 0x70, 0x30, 0x2F, 0x66, 0x77, 0x73,
+ 0x6D, 0x2F, 0x73, 0x79, 0x73, 0x74, 0x65, 0x6D, 0x2E, 0x70, 0x30, 0x27,
+ 0x20, 0x41, 0x53, 0x20, 0x70, 0x30, 0x3B, 0xA, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x54, 0x0, 0x0, 0x5, 0x89, 0x13, 0x97, 0x51, 0x64,
+ 0x0, 0x0, 0x23, 0x88, 0x0, 0x38, 0x43, 0x4F, 0x92, 0x41, 0x1B, 0xD4,
+ 0x0, 0x0, 0x21, 0x6, 0x9D, 0x86, 0xE6, 0xD3, 0x41, 0x54, 0x54, 0x41,
+ 0x43, 0x48, 0x20, 0x44, 0x41, 0x54, 0x41, 0x42, 0x41, 0x53, 0x45, 0x20,
+ 0x27, 0x2F, 0x6F, 0x70, 0x74, 0x2F, 0x70, 0x31, 0x2F, 0x66, 0x77, 0x73,
+ 0x6D, 0x2F, 0x73, 0x79, 0x73, 0x74, 0x65, 0x6D, 0x2E, 0x70, 0x31, 0x27,
+ 0x20, 0x41, 0x53, 0x20, 0x70, 0x31, 0x3B, 0xA, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x54, 0x0, 0x0, 0x5, 0x89, 0x13, 0xA2, 0x85, 0x51,
+ 0x0, 0x0, 0x23, 0x88, 0x0, 0x38, 0x43, 0x4F, 0x92, 0x41, 0x1B, 0xD4,
+ 0x0, 0x0, 0x21, 0x6, 0x9D, 0x86, 0xE6, 0xD3, 0x41, 0x54, 0x54, 0x41,
+ 0x43, 0x48, 0x20, 0x44, 0x41, 0x54, 0x41, 0x42, 0x41, 0x53, 0x45, 0x20,
+ 0x27, 0x2F, 0x6F, 0x70, 0x74, 0x2F, 0x70, 0x33, 0x2F, 0x66, 0x77, 0x73,
+ 0x6D, 0x2F, 0x73, 0x79, 0x73, 0x74, 0x65, 0x6D, 0x2E, 0x70, 0x33, 0x27,
+ 0x20, 0x41, 0x53, 0x20, 0x70, 0x33, 0x3B, 0xA, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x54, 0x0, 0x0, 0x5, 0x89, 0x14, 0x5B, 0x84, 0x7B,
+ 0x0, 0x0, 0x23, 0x88, 0x0, 0x58, 0x43, 0x4F, 0x92, 0x41, 0x1B, 0xD4,
+ 0x0, 0x0, 0x21, 0x6, 0x9D, 0x86, 0xE6, 0xD3, 0x73, 0x65, 0x6C, 0x65,
+ 0x63, 0x74, 0x20, 0x65, 0x72, 0x72, 0x6F, 0x72, 0x5F, 0x65, 0x69, 0x64,
+ 0x20, 0x66, 0x72, 0x6F, 0x6D, 0x20, 0x63, 0x62, 0x6C, 0x76, 0x5F, 0x63,
+ 0x61, 0x62, 0x6C, 0x65, 0x5F, 0x63, 0x6F, 0x6E, 0x6E, 0x65, 0x63, 0x74,
+ 0x69, 0x6F, 0x6E, 0x5F, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x5F, 0x70,
+ 0x75, 0x62, 0x6C, 0x69, 0x63, 0x5F, 0x76, 0x69, 0x65, 0x77, 0x20, 0x77,
+ 0x68, 0x65, 0x72, 0x65, 0x20, 0x65, 0x72, 0x72, 0x6F, 0x72, 0x5F, 0x65,
+ 0x69, 0x64, 0x20, 0x21, 0x3D, 0x20, 0x30, 0x0, 0x0, 0x0, 0x0, 0x74,
+ 0x55, 0x44, 0x0, 0xC, 0x1, 0xC, 0x31, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x53, 0x53, 0x0, 0x50, 0x1, 0x1, 0xA8, 0x0, 0x2, 0x0, 0x0, 0x9,
+ 0x0, 0x0, 0x0, 0x48, 0x3, 0x1, 0x0, 0xF0, 0x2C, 0xC6, 0x19, 0x10,
+ 0xC1, 0x39, 0x20, 0x0, 0x40, 0x0, 0x0, 0xFF, 0x9D, 0x86, 0xE6, 0xD3,
+ 0x0, 0x0, 0x0, 0x3, 0x10, 0x69, 0x14, 0xD8, 0x0, 0x0, 0x0, 0x0,
+ 0x42, 0x31, 0x38, 0x31, 0x41, 0x38, 0x30, 0x45, 0x20, 0x20, 0x20, 0x20,
+ 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
+ 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x55, 0x44, 0x1, 0xF4,
+ 0x1, 0xC, 0x31, 0x0, 0x1, 0x28, 0x4, 0x42, 0x46, 0x57, 0x44, 0x42,
+ 0x45, 0x52, 0x52, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x1, 0xEC, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0xEC,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x7, 0x0, 0x0, 0x5, 0x33,
+ 0x1F, 0xEA, 0xE7, 0x12, 0x0, 0x0, 0xD, 0x9D, 0x0, 0x30, 0x43, 0x4F,
+ 0x27, 0x67, 0x44, 0xB7, 0x0, 0x0, 0x0, 0xB5, 0x66, 0x77, 0x64, 0x62,
+ 0x54, 0x72, 0x61, 0x63, 0x45, 0x72, 0x72, 0x49, 0x6E, 0x66, 0x6F, 0x0,
+ 0x73, 0x65, 0x74, 0x42, 0x75, 0x73, 0x79, 0x54, 0x69, 0x6D, 0x65, 0x6F,
+ 0x75, 0x74, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1A, 0x0, 0x0, 0xA8, 0x9,
+ 0x50, 0x1, 0xF, 0xA, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x4C,
+ 0x0, 0x0, 0x5, 0x38, 0x26, 0x43, 0xFB, 0x66, 0x0, 0x0, 0xB, 0x7D,
+ 0x0, 0x30, 0x43, 0x4F, 0x27, 0x67, 0x44, 0xB7, 0x0, 0x0, 0x0, 0xB5,
+ 0x66, 0x77, 0x64, 0x62, 0x54, 0x72, 0x61, 0x63, 0x45, 0x72, 0x72, 0x49,
+ 0x6E, 0x66, 0x6F, 0x0, 0x73, 0x65, 0x74, 0x42, 0x75, 0x73, 0x79, 0x54,
+ 0x69, 0x6D, 0x65, 0x6F, 0x75, 0x74, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1A,
+ 0x0, 0x0, 0xA8, 0x9, 0x50, 0x1, 0xF, 0xE, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x4C, 0x0, 0x0, 0x5, 0x72, 0x13, 0xB8, 0x3D, 0x1C,
+ 0x0, 0x0, 0x23, 0x33, 0x0, 0x30, 0x43, 0x4F, 0x27, 0x67, 0x44, 0xB7,
+ 0x0, 0x0, 0x0, 0xB5, 0x66, 0x77, 0x64, 0x62, 0x54, 0x72, 0x61, 0x63,
+ 0x45, 0x72, 0x72, 0x49, 0x6E, 0x66, 0x6F, 0x0, 0x73, 0x65, 0x74, 0x42,
+ 0x75, 0x73, 0x79, 0x54, 0x69, 0x6D, 0x65, 0x6F, 0x75, 0x74, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x1A, 0x0, 0x0, 0xA8, 0x9, 0x50, 0x1, 0xF, 0x5D,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x4C, 0x0, 0x0, 0x5, 0x88,
+ 0x20, 0xE1, 0xE0, 0x7, 0x0, 0x0, 0xD, 0x82, 0x0, 0x30, 0x43, 0x4F,
+ 0x27, 0x67, 0x44, 0xB7, 0x0, 0x0, 0x0, 0xB5, 0x66, 0x77, 0x64, 0x62,
+ 0x54, 0x72, 0x61, 0x63, 0x45, 0x72, 0x72, 0x49, 0x6E, 0x66, 0x6F, 0x0,
+ 0x73, 0x65, 0x74, 0x42, 0x75, 0x73, 0x79, 0x54, 0x69, 0x6D, 0x65, 0x6F,
+ 0x75, 0x74, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1A, 0x0, 0x0, 0xA8, 0x9,
+ 0x50, 0x1, 0xF, 0x9B, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x4C,
+ 0x0, 0x0, 0x5, 0x89, 0x12, 0xE, 0xFE, 0x6E, 0x0, 0x0, 0xB, 0x69,
+ 0x0, 0x30, 0x43, 0x4F, 0x27, 0x67, 0x44, 0xB7, 0x0, 0x0, 0x0, 0xB5,
+ 0x66, 0x77, 0x64, 0x62, 0x54, 0x72, 0x61, 0x63, 0x45, 0x72, 0x72, 0x49,
+ 0x6E, 0x66, 0x6F, 0x0, 0x73, 0x65, 0x74, 0x42, 0x75, 0x73, 0x79, 0x54,
+ 0x69, 0x6D, 0x65, 0x6F, 0x75, 0x74, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1A,
+ 0x0, 0x0, 0xA8, 0x9, 0x50, 0x1, 0xF, 0xA4, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x4C, 0x0, 0x0, 0x5, 0x89, 0x15, 0x7A, 0x13, 0xA4,
+ 0x0, 0x0, 0x23, 0x88, 0x0, 0x2C, 0x43, 0x4F, 0x27, 0x67, 0x44, 0xB7,
+ 0x0, 0x0, 0x0, 0xB5, 0x66, 0x77, 0x64, 0x62, 0x54, 0x72, 0x61, 0x63,
+ 0x45, 0x72, 0x72, 0x49, 0x6E, 0x66, 0x6F, 0x0, 0x73, 0x65, 0x72, 0x76,
+ 0x45, 0x78, 0x65, 0x63, 0x75, 0x74, 0x65, 0x0, 0x0, 0x0, 0x0, 0x1B,
+ 0x0, 0x0, 0xA8, 0xE, 0x50, 0x1, 0xF, 0xA5, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x48, 0x55, 0x44, 0x1, 0xE8, 0x1, 0xC, 0x31, 0x0,
+ 0x1, 0x28, 0x4, 0x42, 0x46, 0x57, 0x44, 0x42, 0x53, 0x52, 0x56, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0xE0,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0xE0, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x9, 0x0, 0x0, 0x5, 0x89, 0x12, 0x36, 0xB8, 0x93,
+ 0x0, 0x0, 0x23, 0x88, 0x0, 0x10, 0x43, 0x4F, 0x89, 0x7D, 0x0, 0x8C,
+ 0x0, 0x0, 0x23, 0x5D, 0x73, 0x65, 0x72, 0x76, 0x44, 0x65, 0x74, 0x61,
+ 0x63, 0x68, 0x0, 0x0, 0xDB, 0xFB, 0xCB, 0x98, 0x0, 0x0, 0x0, 0x2C,
+ 0x0, 0x0, 0x5, 0x89, 0x12, 0x36, 0xD2, 0x7A, 0x0, 0x0, 0x23, 0x88,
+ 0x0, 0x0, 0x43, 0x4F, 0xBD, 0xF0, 0x6E, 0xE3, 0x0, 0x0, 0x9, 0xF4,
+ 0x0, 0x0, 0x0, 0x1C, 0x0, 0x0, 0x5, 0x89, 0x12, 0xAA, 0x51, 0x96,
+ 0x0, 0x0, 0x23, 0x88, 0x0, 0x0, 0x43, 0x4F, 0x8F, 0x6A, 0x83, 0x3,
+ 0x0, 0x0, 0x9, 0xA9, 0x0, 0x0, 0x0, 0x1C, 0x0, 0x0, 0x5, 0x89,
+ 0x12, 0xAE, 0x7B, 0x85, 0x0, 0x0, 0x23, 0x88, 0x0, 0x10, 0x43, 0x4F,
+ 0xF3, 0x35, 0x3F, 0x8D, 0x0, 0x0, 0x22, 0x35, 0x73, 0x65, 0x72, 0x76,
+ 0x41, 0x74, 0x74, 0x61, 0x63, 0x68, 0x0, 0x0, 0x9D, 0x86, 0xE6, 0xD3,
+ 0x0, 0x0, 0x0, 0x2C, 0x0, 0x0, 0x5, 0x89, 0x13, 0xA5, 0x23, 0xFA,
+ 0x0, 0x0, 0x23, 0x88, 0x0, 0x0, 0x43, 0x4F, 0xFC, 0x5A, 0x7F, 0x97,
+ 0x0, 0x0, 0x9, 0xB3, 0x0, 0x0, 0x0, 0x1C, 0x0, 0x0, 0x5, 0x89,
+ 0x14, 0x5B, 0x57, 0xCE, 0x0, 0x0, 0x23, 0x88, 0x0, 0x0, 0x43, 0x4F,
+ 0x4, 0x88, 0x89, 0x7B, 0x0, 0x0, 0x9, 0x3B, 0x0, 0x0, 0x0, 0x1C,
+ 0x0, 0x0, 0x5, 0x89, 0x14, 0x5F, 0xCC, 0x54, 0x0, 0x0, 0x23, 0x88,
+ 0x0, 0x50, 0x43, 0x4F, 0x94, 0xA2, 0x26, 0x7C, 0x0, 0x0, 0x14, 0xE1,
+ 0x70, 0x72, 0x65, 0x70, 0x61, 0x72, 0x65, 0x53, 0x74, 0x6D, 0x74, 0x0,
+ 0x0, 0x0, 0x0, 0x1, 0x6E, 0x6F, 0x20, 0x73, 0x75, 0x63, 0x68, 0x20,
+ 0x74, 0x61, 0x62, 0x6C, 0x65, 0x3A, 0x20, 0x63, 0x62, 0x6C, 0x76, 0x5F,
+ 0x63, 0x61, 0x62, 0x6C, 0x65, 0x5F, 0x63, 0x6F, 0x6E, 0x6E, 0x65, 0x63,
+ 0x74, 0x69, 0x6F, 0x6E, 0x5F, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x5F,
+ 0x70, 0x75, 0x62, 0x6C, 0x69, 0x63, 0x5F, 0x76, 0x69, 0x65, 0x77, 0x0,
+ 0x10, 0x69, 0x14, 0xD8, 0x0, 0x0, 0x0, 0x3, 0x0, 0x0, 0x0, 0x6C,
+ 0x0, 0x0, 0x5, 0x89, 0x15, 0x79, 0xCE, 0xB1, 0x0, 0x0, 0x23, 0x88,
+ 0x0, 0x68, 0x43, 0x4F, 0xF9, 0xB, 0x8E, 0x3C, 0x0, 0x0, 0x21, 0x3E,
+ 0x73, 0x65, 0x72, 0x76, 0x45, 0x78, 0x65, 0x63, 0x75, 0x74, 0x65, 0x0,
+ 0x9D, 0x86, 0xE6, 0xD3, 0x0, 0x0, 0x0, 0x3, 0x73, 0x65, 0x6C, 0x65,
+ 0x63, 0x74, 0x20, 0x65, 0x72, 0x72, 0x6F, 0x72, 0x5F, 0x65, 0x69, 0x64,
+ 0x20, 0x66, 0x72, 0x6F, 0x6D, 0x20, 0x63, 0x62, 0x6C, 0x76, 0x5F, 0x63,
+ 0x61, 0x62, 0x6C, 0x65, 0x5F, 0x63, 0x6F, 0x6E, 0x6E, 0x65, 0x63, 0x74,
+ 0x69, 0x6F, 0x6E, 0x5F, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x5F, 0x70,
+ 0x75, 0x62, 0x6C, 0x69, 0x63, 0x5F, 0x76, 0x69, 0x65, 0x77, 0x20, 0x77,
+ 0x68, 0x65, 0x72, 0x65, 0x20, 0x65, 0x72, 0x72, 0x6F, 0x72, 0x5F, 0x65,
+ 0x69, 0x64, 0x20, 0x21, 0x3D, 0x20, 0x30, 0x0, 0x0, 0x0, 0x0, 0x84,
+ 0x55, 0x44, 0x3, 0xC0, 0x1, 0xC, 0x31, 0x0, 0x1, 0x28, 0x4, 0x42,
+ 0x46, 0x57, 0x44, 0x42, 0x53, 0x51, 0x4C, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x3, 0xB8, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x3, 0xB8, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xB,
+ 0x0, 0x0, 0x5, 0x89, 0x5, 0xFC, 0x1E, 0x6C, 0x0, 0x0, 0x23, 0x88,
+ 0x0, 0x40, 0x43, 0x4F, 0x92, 0x41, 0x1B, 0xD4, 0x0, 0x0, 0x21, 0x6,
+ 0x14, 0x9, 0xC2, 0x11, 0x73, 0x65, 0x6C, 0x65, 0x63, 0x74, 0x20, 0x2A,
+ 0x20, 0x66, 0x72, 0x6F, 0x6D, 0x20, 0x73, 0x79, 0x73, 0x2E, 0x70, 0x6F,
+ 0x6C, 0x69, 0x63, 0x79, 0x20, 0x77, 0x68, 0x65, 0x72, 0x65, 0x20, 0x6E,
+ 0x61, 0x6D, 0x65, 0x20, 0x3D, 0x20, 0x27, 0x70, 0x6F, 0x77, 0x65, 0x72,
+ 0x2D, 0x64, 0x65, 0x66, 0x2D, 0x66, 0x69, 0x6C, 0x65, 0x6E, 0x61, 0x6D,
+ 0x65, 0x73, 0x27, 0x0, 0x0, 0x0, 0x0, 0x5C, 0x0, 0x0, 0x5, 0x89,
+ 0x6, 0xB1, 0x42, 0x3B, 0x0, 0x0, 0x23, 0x88, 0x0, 0x40, 0x43, 0x4F,
+ 0x92, 0x41, 0x1B, 0xD4, 0x0, 0x0, 0x21, 0x6, 0x14, 0x9, 0xC2, 0x11,
+ 0x73, 0x65, 0x6C, 0x65, 0x63, 0x74, 0x20, 0x2A, 0x20, 0x66, 0x72, 0x6F,
+ 0x6D, 0x20, 0x73, 0x79, 0x73, 0x2E, 0x70, 0x6F, 0x6C, 0x69, 0x63, 0x79,
+ 0x20, 0x77, 0x68, 0x65, 0x72, 0x65, 0x20, 0x6E, 0x61, 0x6D, 0x65, 0x20,
+ 0x3D, 0x20, 0x27, 0x70, 0x6F, 0x77, 0x65, 0x72, 0x2D, 0x64, 0x65, 0x66,
+ 0x2D, 0x66, 0x69, 0x6C, 0x65, 0x6E, 0x61, 0x6D, 0x65, 0x73, 0x27, 0x0,
+ 0x0, 0x0, 0x0, 0x5C, 0x0, 0x0, 0x5, 0x89, 0x7, 0x14, 0xD2, 0x68,
+ 0x0, 0x0, 0x23, 0x88, 0x0, 0x40, 0x43, 0x4F, 0x92, 0x41, 0x1B, 0xD4,
+ 0x0, 0x0, 0x21, 0x6, 0x14, 0x9, 0xC2, 0x11, 0x73, 0x65, 0x6C, 0x65,
+ 0x63, 0x74, 0x20, 0x2A, 0x20, 0x66, 0x72, 0x6F, 0x6D, 0x20, 0x73, 0x79,
+ 0x73, 0x2E, 0x70, 0x6F, 0x6C, 0x69, 0x63, 0x79, 0x20, 0x77, 0x68, 0x65,
+ 0x72, 0x65, 0x20, 0x6E, 0x61, 0x6D, 0x65, 0x20, 0x3D, 0x20, 0x27, 0x70,
+ 0x6F, 0x77, 0x65, 0x72, 0x2D, 0x64, 0x65, 0x66, 0x2D, 0x66, 0x69, 0x6C,
+ 0x65, 0x6E, 0x61, 0x6D, 0x65, 0x73, 0x27, 0x0, 0x0, 0x0, 0x0, 0x5C,
+ 0x0, 0x0, 0x5, 0x89, 0x7, 0x9A, 0x9D, 0x6F, 0x0, 0x0, 0x23, 0x88,
+ 0x0, 0x40, 0x43, 0x4F, 0x92, 0x41, 0x1B, 0xD4, 0x0, 0x0, 0x21, 0x6,
+ 0x14, 0x9, 0xC2, 0x11, 0x73, 0x65, 0x6C, 0x65, 0x63, 0x74, 0x20, 0x2A,
+ 0x20, 0x66, 0x72, 0x6F, 0x6D, 0x20, 0x73, 0x79, 0x73, 0x2E, 0x70, 0x6F,
+ 0x6C, 0x69, 0x63, 0x79, 0x20, 0x77, 0x68, 0x65, 0x72, 0x65, 0x20, 0x6E,
+ 0x61, 0x6D, 0x65, 0x20, 0x3D, 0x20, 0x27, 0x70, 0x6F, 0x77, 0x65, 0x72,
+ 0x2D, 0x64, 0x65, 0x66, 0x2D, 0x66, 0x69, 0x6C, 0x65, 0x6E, 0x61, 0x6D,
+ 0x65, 0x73, 0x27, 0x0, 0x0, 0x0, 0x0, 0x5C, 0x0, 0x0, 0x5, 0x89,
+ 0x7, 0xD4, 0xF8, 0x2D, 0x0, 0x0, 0x23, 0x88, 0x0, 0x40, 0x43, 0x4F,
+ 0x92, 0x41, 0x1B, 0xD4, 0x0, 0x0, 0x21, 0x6, 0x14, 0x9, 0xC2, 0x11,
+ 0x73, 0x65, 0x6C, 0x65, 0x63, 0x74, 0x20, 0x2A, 0x20, 0x66, 0x72, 0x6F,
+ 0x6D, 0x20, 0x73, 0x79, 0x73, 0x2E, 0x70, 0x6F, 0x6C, 0x69, 0x63, 0x79,
+ 0x20, 0x77, 0x68, 0x65, 0x72, 0x65, 0x20, 0x6E, 0x61, 0x6D, 0x65, 0x20,
+ 0x3D, 0x20, 0x27, 0x70, 0x6F, 0x77, 0x65, 0x72, 0x2D, 0x64, 0x65, 0x66,
+ 0x2D, 0x66, 0x69, 0x6C, 0x65, 0x6E, 0x61, 0x6D, 0x65, 0x73, 0x27, 0x0,
+ 0x0, 0x0, 0x0, 0x5C, 0x0, 0x0, 0x5, 0x89, 0x12, 0xB4, 0xBC, 0xA6,
+ 0x0, 0x0, 0x23, 0x88, 0x0, 0x38, 0x43, 0x4F, 0x92, 0x41, 0x1B, 0xD4,
+ 0x0, 0x0, 0x21, 0x6, 0x9D, 0x86, 0xE6, 0xD3, 0x41, 0x54, 0x54, 0x41,
+ 0x43, 0x48, 0x20, 0x44, 0x41, 0x54, 0x41, 0x42, 0x41, 0x53, 0x45, 0x20,
+ 0x27, 0x2F, 0x6F, 0x70, 0x74, 0x2F, 0x70, 0x31, 0x2F, 0x66, 0x77, 0x73,
+ 0x6D, 0x2F, 0x73, 0x79, 0x73, 0x74, 0x65, 0x6D, 0x2E, 0x73, 0x79, 0x73,
+ 0x27, 0x20, 0x41, 0x53, 0x20, 0x73, 0x79, 0x73, 0x3B, 0xA, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x54, 0x0, 0x0, 0x5, 0x89, 0x12, 0xBC, 0xD8, 0x8A,
+ 0x0, 0x0, 0x23, 0x88, 0x0, 0x38, 0x43, 0x4F, 0x92, 0x41, 0x1B, 0xD4,
+ 0x0, 0x0, 0x21, 0x6, 0x9D, 0x86, 0xE6, 0xD3, 0x41, 0x54, 0x54, 0x41,
+ 0x43, 0x48, 0x20, 0x44, 0x41, 0x54, 0x41, 0x42, 0x41, 0x53, 0x45, 0x20,
+ 0x27, 0x2F, 0x6F, 0x70, 0x74, 0x2F, 0x70, 0x30, 0x2F, 0x66, 0x77, 0x73,
+ 0x6D, 0x2F, 0x73, 0x79, 0x73, 0x74, 0x65, 0x6D, 0x2E, 0x70, 0x30, 0x27,
+ 0x20, 0x41, 0x53, 0x20, 0x70, 0x30, 0x3B, 0xA, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x54, 0x0, 0x0, 0x5, 0x89, 0x13, 0x97, 0x51, 0x64,
+ 0x0, 0x0, 0x23, 0x88, 0x0, 0x38, 0x43, 0x4F, 0x92, 0x41, 0x1B, 0xD4,
+ 0x0, 0x0, 0x21, 0x6, 0x9D, 0x86, 0xE6, 0xD3, 0x41, 0x54, 0x54, 0x41,
+ 0x43, 0x48, 0x20, 0x44, 0x41, 0x54, 0x41, 0x42, 0x41, 0x53, 0x45, 0x20,
+ 0x27, 0x2F, 0x6F, 0x70, 0x74, 0x2F, 0x70, 0x31, 0x2F, 0x66, 0x77, 0x73,
+ 0x6D, 0x2F, 0x73, 0x79, 0x73, 0x74, 0x65, 0x6D, 0x2E, 0x70, 0x31, 0x27,
+ 0x20, 0x41, 0x53, 0x20, 0x70, 0x31, 0x3B, 0xA, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x54, 0x0, 0x0, 0x5, 0x89, 0x13, 0xA2, 0x85, 0x51,
+ 0x0, 0x0, 0x23, 0x88, 0x0, 0x38, 0x43, 0x4F, 0x92, 0x41, 0x1B, 0xD4,
+ 0x0, 0x0, 0x21, 0x6, 0x9D, 0x86, 0xE6, 0xD3, 0x41, 0x54, 0x54, 0x41,
+ 0x43, 0x48, 0x20, 0x44, 0x41, 0x54, 0x41, 0x42, 0x41, 0x53, 0x45, 0x20,
+ 0x27, 0x2F, 0x6F, 0x70, 0x74, 0x2F, 0x70, 0x33, 0x2F, 0x66, 0x77, 0x73,
+ 0x6D, 0x2F, 0x73, 0x79, 0x73, 0x74, 0x65, 0x6D, 0x2E, 0x70, 0x33, 0x27,
+ 0x20, 0x41, 0x53, 0x20, 0x70, 0x33, 0x3B, 0xA, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x54, 0x0, 0x0, 0x5, 0x89, 0x14, 0x5B, 0x84, 0x7B,
+ 0x0, 0x0, 0x23, 0x88, 0x0, 0x58, 0x43, 0x4F, 0x92, 0x41, 0x1B, 0xD4,
+ 0x0, 0x0, 0x21, 0x6, 0x9D, 0x86, 0xE6, 0xD3, 0x73, 0x65, 0x6C, 0x65,
+ 0x63, 0x74, 0x20, 0x65, 0x72, 0x72, 0x6F, 0x72, 0x5F, 0x65, 0x69, 0x64,
+ 0x20, 0x66, 0x72, 0x6F, 0x6D, 0x20, 0x63, 0x62, 0x6C, 0x76, 0x5F, 0x63,
+ 0x61, 0x62, 0x6C, 0x65, 0x5F, 0x63, 0x6F, 0x6E, 0x6E, 0x65, 0x63, 0x74,
+ 0x69, 0x6F, 0x6E, 0x5F, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x5F, 0x70,
+ 0x75, 0x62, 0x6C, 0x69, 0x63, 0x5F, 0x76, 0x69, 0x65, 0x77, 0x20, 0x77,
+ 0x68, 0x65, 0x72, 0x65, 0x20, 0x65, 0x72, 0x72, 0x6F, 0x72, 0x5F, 0x65,
+ 0x69, 0x64, 0x20, 0x21, 0x3D, 0x20, 0x30, 0x0, 0x0, 0x0, 0x0, 0x74,
+ 0x55, 0x44, 0x0, 0xC, 0x1, 0xC, 0x31, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x53, 0x53, 0x0, 0x50, 0x1, 0x1, 0xA8, 0x0, 0x2, 0x0, 0x0, 0x9,
+ 0x0, 0x0, 0x0, 0x48, 0x3, 0x1, 0x0, 0xF0, 0x2C, 0xC6, 0x67, 0x10,
+ 0xC1, 0x39, 0x20, 0x0, 0x40, 0x0, 0x0, 0xFF, 0x0, 0x0, 0x0, 0x3,
+ 0x9D, 0x86, 0xE6, 0xD3, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x42, 0x31, 0x38, 0x31, 0x41, 0x38, 0x30, 0x35, 0x20, 0x20, 0x20, 0x20,
+ 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
+ 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x55, 0x44, 0x1, 0xF4,
+ 0x1, 0xC, 0x31, 0x0, 0x1, 0x28, 0x4, 0x42, 0x46, 0x57, 0x44, 0x42,
+ 0x45, 0x52, 0x52, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x1, 0xEC, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0xEC,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x7, 0x0, 0x0, 0x5, 0x38,
+ 0x26, 0x43, 0xFB, 0x66, 0x0, 0x0, 0xB, 0x7D, 0x0, 0x30, 0x43, 0x4F,
+ 0x27, 0x67, 0x44, 0xB7, 0x0, 0x0, 0x0, 0xB5, 0x66, 0x77, 0x64, 0x62,
+ 0x54, 0x72, 0x61, 0x63, 0x45, 0x72, 0x72, 0x49, 0x6E, 0x66, 0x6F, 0x0,
+ 0x73, 0x65, 0x74, 0x42, 0x75, 0x73, 0x79, 0x54, 0x69, 0x6D, 0x65, 0x6F,
+ 0x75, 0x74, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1A, 0x0, 0x0, 0xA8, 0x9,
+ 0x50, 0x1, 0xF, 0xE, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x4C,
+ 0x0, 0x0, 0x5, 0x72, 0x13, 0xB8, 0x3D, 0x1C, 0x0, 0x0, 0x23, 0x33,
+ 0x0, 0x30, 0x43, 0x4F, 0x27, 0x67, 0x44, 0xB7, 0x0, 0x0, 0x0, 0xB5,
+ 0x66, 0x77, 0x64, 0x62, 0x54, 0x72, 0x61, 0x63, 0x45, 0x72, 0x72, 0x49,
+ 0x6E, 0x66, 0x6F, 0x0, 0x73, 0x65, 0x74, 0x42, 0x75, 0x73, 0x79, 0x54,
+ 0x69, 0x6D, 0x65, 0x6F, 0x75, 0x74, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1A,
+ 0x0, 0x0, 0xA8, 0x9, 0x50, 0x1, 0xF, 0x5D, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x4C, 0x0, 0x0, 0x5, 0x88, 0x20, 0xE1, 0xE0, 0x7,
+ 0x0, 0x0, 0xD, 0x82, 0x0, 0x30, 0x43, 0x4F, 0x27, 0x67, 0x44, 0xB7,
+ 0x0, 0x0, 0x0, 0xB5, 0x66, 0x77, 0x64, 0x62, 0x54, 0x72, 0x61, 0x63,
+ 0x45, 0x72, 0x72, 0x49, 0x6E, 0x66, 0x6F, 0x0, 0x73, 0x65, 0x74, 0x42,
+ 0x75, 0x73, 0x79, 0x54, 0x69, 0x6D, 0x65, 0x6F, 0x75, 0x74, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x1A, 0x0, 0x0, 0xA8, 0x9, 0x50, 0x1, 0xF, 0x9B,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x4C, 0x0, 0x0, 0x5, 0x89,
+ 0x12, 0xE, 0xFE, 0x6E, 0x0, 0x0, 0xB, 0x69, 0x0, 0x30, 0x43, 0x4F,
+ 0x27, 0x67, 0x44, 0xB7, 0x0, 0x0, 0x0, 0xB5, 0x66, 0x77, 0x64, 0x62,
+ 0x54, 0x72, 0x61, 0x63, 0x45, 0x72, 0x72, 0x49, 0x6E, 0x66, 0x6F, 0x0,
+ 0x73, 0x65, 0x74, 0x42, 0x75, 0x73, 0x79, 0x54, 0x69, 0x6D, 0x65, 0x6F,
+ 0x75, 0x74, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1A, 0x0, 0x0, 0xA8, 0x9,
+ 0x50, 0x1, 0xF, 0xA4, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x4C,
+ 0x0, 0x0, 0x5, 0x89, 0x15, 0x7A, 0x13, 0xA4, 0x0, 0x0, 0x23, 0x88,
+ 0x0, 0x2C, 0x43, 0x4F, 0x27, 0x67, 0x44, 0xB7, 0x0, 0x0, 0x0, 0xB5,
+ 0x66, 0x77, 0x64, 0x62, 0x54, 0x72, 0x61, 0x63, 0x45, 0x72, 0x72, 0x49,
+ 0x6E, 0x66, 0x6F, 0x0, 0x73, 0x65, 0x72, 0x76, 0x45, 0x78, 0x65, 0x63,
+ 0x75, 0x74, 0x65, 0x0, 0x0, 0x0, 0x0, 0x1B, 0x0, 0x0, 0xA8, 0xE,
+ 0x50, 0x1, 0xF, 0xA5, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x48,
+ 0x0, 0x0, 0x5, 0x89, 0x16, 0xA5, 0xF7, 0xAC, 0x0, 0x0, 0xB, 0x69,
+ 0x0, 0x30, 0x43, 0x4F, 0x27, 0x67, 0x44, 0xB7, 0x0, 0x0, 0x0, 0xB5,
+ 0x66, 0x77, 0x64, 0x62, 0x54, 0x72, 0x61, 0x63, 0x45, 0x72, 0x72, 0x49,
+ 0x6E, 0x66, 0x6F, 0x0, 0x65, 0x78, 0x65, 0x63, 0x75, 0x74, 0x65, 0x4E,
+ 0x6F, 0x46, 0x6D, 0x74, 0x53, 0x74, 0x72, 0x0, 0x0, 0x0, 0x0, 0x1B,
+ 0x0, 0x0, 0xA8, 0xE, 0x50, 0x1, 0xF, 0xA5, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x4C, 0x55, 0x44, 0x1, 0xEC, 0x1, 0xC, 0x31, 0x0,
+ 0x1, 0x28, 0x4, 0x42, 0x46, 0x57, 0x44, 0x42, 0x43, 0x4D, 0x44, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0xE4,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0xE4, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0xD, 0x0, 0x0, 0x5, 0x89, 0x5, 0xF9, 0x4B, 0x5C,
+ 0x0, 0x0, 0x23, 0x72, 0x0, 0x0, 0x43, 0x4F, 0x32, 0x5F, 0xF6, 0x1,
+ 0x0, 0x0, 0x1, 0x59, 0x0, 0x0, 0x0, 0x1C, 0x0, 0x0, 0x5, 0x89,
+ 0x6, 0x36, 0xBE, 0xE8, 0x0, 0x0, 0x23, 0x72, 0x0, 0x0, 0x43, 0x4F,
+ 0xC9, 0x49, 0xE2, 0x7E, 0x0, 0x0, 0x1, 0xD6, 0x0, 0x0, 0x0, 0x1C,
+ 0x0, 0x0, 0x5, 0x89, 0x6, 0xAE, 0x6C, 0x5C, 0x0, 0x0, 0x23, 0x72,
+ 0x0, 0x0, 0x43, 0x4F, 0x32, 0x5F, 0xF6, 0x1, 0x0, 0x0, 0x1, 0x59,
+ 0x0, 0x0, 0x0, 0x1C, 0x0, 0x0, 0x5, 0x89, 0x6, 0xC3, 0x48, 0x7A,
+ 0x0, 0x0, 0x23, 0x72, 0x0, 0x0, 0x43, 0x4F, 0xC9, 0x49, 0xE2, 0x7E,
+ 0x0, 0x0, 0x1, 0xD6, 0x0, 0x0, 0x0, 0x1C, 0x0, 0x0, 0x5, 0x89,
+ 0x7, 0x12, 0x50, 0xB6, 0x0, 0x0, 0x23, 0x72, 0x0, 0x0, 0x43, 0x4F,
+ 0x32, 0x5F, 0xF6, 0x1, 0x0, 0x0, 0x1, 0x59, 0x0, 0x0, 0x0, 0x1C,
+ 0x0, 0x0, 0x5, 0x89, 0x7, 0x2C, 0x43, 0xFE, 0x0, 0x0, 0x23, 0x72,
+ 0x0, 0x0, 0x43, 0x4F, 0xC9, 0x49, 0xE2, 0x7E, 0x0, 0x0, 0x1, 0xD6,
+ 0x0, 0x0, 0x0, 0x1C, 0x0, 0x0, 0x5, 0x89, 0x7, 0x97, 0x92, 0x5F,
+ 0x0, 0x0, 0x23, 0x72, 0x0, 0x0, 0x43, 0x4F, 0x32, 0x5F, 0xF6, 0x1,
+ 0x0, 0x0, 0x1, 0x59, 0x0, 0x0, 0x0, 0x1C, 0x0, 0x0, 0x5, 0x89,
+ 0x7, 0xAF, 0x7C, 0xC6, 0x0, 0x0, 0x23, 0x72, 0x0, 0x0, 0x43, 0x4F,
+ 0xC9, 0x49, 0xE2, 0x7E, 0x0, 0x0, 0x1, 0xD6, 0x0, 0x0, 0x0, 0x1C,
+ 0x0, 0x0, 0x5, 0x89, 0x7, 0xD2, 0x84, 0xF, 0x0, 0x0, 0x23, 0x72,
+ 0x0, 0x0, 0x43, 0x4F, 0x32, 0x5F, 0xF6, 0x1, 0x0, 0x0, 0x1, 0x59,
+ 0x0, 0x0, 0x0, 0x1C, 0x0, 0x0, 0x5, 0x89, 0x8, 0x6E, 0xBC, 0x17,
+ 0x0, 0x0, 0x23, 0x72, 0x0, 0x0, 0x43, 0x4F, 0xC9, 0x49, 0xE2, 0x7E,
+ 0x0, 0x0, 0x1, 0xD6, 0x0, 0x0, 0x0, 0x1C, 0x0, 0x0, 0x5, 0x89,
+ 0x13, 0xA8, 0x5C, 0x6F, 0x0, 0x0, 0xB, 0x69, 0x0, 0x0, 0x43, 0x4F,
+ 0x32, 0x5F, 0xF6, 0x1, 0x0, 0x0, 0x1, 0x59, 0x0, 0x0, 0x0, 0x1C,
+ 0x0, 0x0, 0x5, 0x89, 0x16, 0xA5, 0x8F, 0x96, 0x0, 0x0, 0xB, 0x69,
+ 0x0, 0x6C, 0x43, 0x4F, 0x9A, 0x1E, 0xAD, 0xA5, 0x0, 0x0, 0x1, 0x9D,
+ 0x65, 0x78, 0x65, 0x63, 0x75, 0x74, 0x65, 0x4E, 0x6F, 0x46, 0x6D, 0x74,
+ 0x53, 0x74, 0x72, 0x0, 0x0, 0x0, 0x0, 0x3, 0x73, 0x65, 0x6C, 0x65,
+ 0x63, 0x74, 0x20, 0x65, 0x72, 0x72, 0x6F, 0x72, 0x5F, 0x65, 0x69, 0x64,
+ 0x20, 0x66, 0x72, 0x6F, 0x6D, 0x20, 0x63, 0x62, 0x6C, 0x76, 0x5F, 0x63,
+ 0x61, 0x62, 0x6C, 0x65, 0x5F, 0x63, 0x6F, 0x6E, 0x6E, 0x65, 0x63, 0x74,
+ 0x69, 0x6F, 0x6E, 0x5F, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x5F, 0x70,
+ 0x75, 0x62, 0x6C, 0x69, 0x63, 0x5F, 0x76, 0x69, 0x65, 0x77, 0x20, 0x77,
+ 0x68, 0x65, 0x72, 0x65, 0x20, 0x65, 0x72, 0x72, 0x6F, 0x72, 0x5F, 0x65,
+ 0x69, 0x64, 0x20, 0x21, 0x3D, 0x20, 0x30, 0x0, 0x9D, 0x86, 0xE6, 0xD3,
+ 0x0, 0x0, 0x0, 0x88, 0x55, 0x44, 0x1, 0xD0, 0x1, 0xC, 0x31, 0x0,
+ 0x1, 0x28, 0x4, 0x42, 0x46, 0x57, 0x44, 0x42, 0x55, 0x54, 0x49, 0x4C,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0xC8,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0xC8, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x4, 0x0, 0x0, 0x5, 0x89, 0x13, 0xA7, 0xC0, 0xB6,
+ 0x0, 0x0, 0xB, 0x69, 0x0, 0x3C, 0x43, 0x4F, 0x4C, 0x53, 0x3A, 0xE0,
+ 0x0, 0x0, 0x1, 0xF7, 0x45, 0x58, 0x49, 0x54, 0x0, 0x0, 0x0, 0x0,
+ 0x41, 0x54, 0x54, 0x41, 0x43, 0x48, 0x0, 0x0, 0x9D, 0x86, 0xE6, 0xD3,
+ 0x0, 0x0, 0xB, 0x60, 0x0, 0x0, 0xB, 0x69, 0x2F, 0x6F, 0x70, 0x74,
+ 0x2F, 0x66, 0x69, 0x70, 0x73, 0x2F, 0x62, 0x69, 0x6E, 0x2F, 0x68, 0x65,
+ 0x61, 0x6C, 0x74, 0x68, 0x6D, 0x6F, 0x6E, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x43, 0x8F, 0x0, 0x0, 0x0, 0x58, 0x0, 0x0, 0x5, 0x89,
+ 0x13, 0xA8, 0xF8, 0x3A, 0x0, 0x0, 0xB, 0x69, 0x0, 0x88, 0x43, 0x4F,
+ 0x4C, 0x53, 0x3A, 0xE0, 0x0, 0x0, 0x1, 0xF7, 0x45, 0x4E, 0x54, 0x52,
+ 0x0, 0x0, 0x0, 0x0, 0x73, 0x65, 0x6C, 0x65, 0x63, 0x74, 0x20, 0x65,
+ 0x72, 0x72, 0x6F, 0x72, 0x5F, 0x65, 0x69, 0x64, 0x20, 0x66, 0x72, 0x6F,
+ 0x6D, 0x20, 0x63, 0x62, 0x6C, 0x76, 0x5F, 0x63, 0x61, 0x62, 0x6C, 0x65,
+ 0x5F, 0x63, 0x6F, 0x6E, 0x6E, 0x65, 0x63, 0x74, 0x69, 0x6F, 0x6E, 0x5F,
+ 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x5F, 0x70, 0x75, 0x62, 0x6C, 0x69,
+ 0x63, 0x5F, 0x76, 0x69, 0x65, 0x77, 0x20, 0x77, 0x68, 0x65, 0x72, 0x65,
+ 0x20, 0x65, 0x72, 0x72, 0x6F, 0x72, 0x5F, 0x65, 0x69, 0x64, 0x20, 0x21,
+ 0x3D, 0x20, 0x30, 0x0, 0x9D, 0x86, 0xE6, 0xD3, 0x0, 0x0, 0xB, 0x60,
+ 0x0, 0x0, 0xB, 0x69, 0x2F, 0x6F, 0x70, 0x74, 0x2F, 0x66, 0x69, 0x70,
+ 0x73, 0x2F, 0x62, 0x69, 0x6E, 0x2F, 0x68, 0x65, 0x61, 0x6C, 0x74, 0x68,
+ 0x6D, 0x6F, 0x6E, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0xA4, 0x0, 0x0, 0x5, 0x89, 0x16, 0xA5, 0x62, 0xFB,
+ 0x0, 0x0, 0xB, 0x69, 0x0, 0x88, 0x43, 0x4F, 0x4C, 0x53, 0x3A, 0xE0,
+ 0x0, 0x0, 0x1, 0xF7, 0x45, 0x58, 0x49, 0x54, 0x0, 0x0, 0x0, 0x0,
+ 0x73, 0x65, 0x6C, 0x65, 0x63, 0x74, 0x20, 0x65, 0x72, 0x72, 0x6F, 0x72,
+ 0x5F, 0x65, 0x69, 0x64, 0x20, 0x66, 0x72, 0x6F, 0x6D, 0x20, 0x63, 0x62,
+ 0x6C, 0x76, 0x5F, 0x63, 0x61, 0x62, 0x6C, 0x65, 0x5F, 0x63, 0x6F, 0x6E,
+ 0x6E, 0x65, 0x63, 0x74, 0x69, 0x6F, 0x6E, 0x5F, 0x73, 0x74, 0x61, 0x74,
+ 0x75, 0x73, 0x5F, 0x70, 0x75, 0x62, 0x6C, 0x69, 0x63, 0x5F, 0x76, 0x69,
+ 0x65, 0x77, 0x20, 0x77, 0x68, 0x65, 0x72, 0x65, 0x20, 0x65, 0x72, 0x72,
+ 0x6F, 0x72, 0x5F, 0x65, 0x69, 0x64, 0x20, 0x21, 0x3D, 0x20, 0x30, 0x0,
+ 0x9D, 0x86, 0xE6, 0xD3, 0x0, 0x0, 0xB, 0x60, 0x0, 0x0, 0xB, 0x69,
+ 0x2F, 0x6F, 0x70, 0x74, 0x2F, 0x66, 0x69, 0x70, 0x73, 0x2F, 0x62, 0x69,
+ 0x6E, 0x2F, 0x68, 0x65, 0x61, 0x6C, 0x74, 0x68, 0x6D, 0x6F, 0x6E, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xC5, 0x63, 0x0, 0x0, 0x0, 0xA4};
+
+TEST_F(PELTest, RealPELTest)
+{
+ auto origData = realPELData;
+ PEL pel{origData};
+
+ EXPECT_TRUE(pel.valid());
+
+ // Check that the flat data is correct
+ auto flat = pel.data();
+ EXPECT_EQ(realPELData, flat);
+ EXPECT_EQ(realPELData.size(), pel.size());
+
+ // Check that the code can extract an object for every section.
+ //(The PrivateHeader and UserHeader account for the + 2 below.)
+ const auto& sections = pel.optionalSections();
+ EXPECT_EQ(pel.privateHeader().sectionCount(), sections.size() + 2);
+
+ auto src = pel.primarySRC();
+ EXPECT_EQ(src.value()->asciiString(), "B181A80E ");
+
+ // Check that the last section (a 'UD' section) is indeed the last
+ // section object by checking the ID and the last byte.
+ auto& last = pel.optionalSections().back();
+ EXPECT_EQ(last->header().id, 0x5544); // "UD"
+
+ std::vector<uint8_t> lastSectionData;
+ Stream stream{lastSectionData};
+ last->flatten(stream);
+ EXPECT_EQ(lastSectionData.back(), 0xA4);
+}
diff --git a/test/openpower-pels/registry_test.cpp b/test/openpower-pels/registry_test.cpp
new file mode 100644
index 0000000..2944ce0
--- /dev/null
+++ b/test/openpower-pels/registry_test.cpp
@@ -0,0 +1,336 @@
+/**
+ * Copyright © 2019 IBM Corporation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#include "extensions/openpower-pels/registry.hpp"
+
+#include <filesystem>
+#include <fstream>
+#include <nlohmann/json.hpp>
+
+#include <gtest/gtest.h>
+
+using namespace openpower::pels::message;
+namespace fs = std::filesystem;
+
+const auto registryData = R"(
+{
+ "PELs":
+ [
+ {
+ "Name": "xyz.openbmc_project.Power.Fault",
+ "Subsystem": "power_supply",
+
+ "SRC":
+ {
+ "ReasonCode": "0x2030"
+ },
+
+ "Documentation":
+ {
+ "Description": "A PGOOD Fault",
+ "Message": "PS had a PGOOD Fault"
+ }
+ },
+
+ {
+ "Name": "xyz.openbmc_project.Power.OverVoltage",
+ "Subsystem": "power_control_hw",
+ "Severity": "unrecoverable",
+ "MfgSeverity": "non_error",
+ "ActionFlags": ["service_action", "report", "call_home"],
+ "MfgActionFlags": ["hidden"],
+
+ "SRC":
+ {
+ "ReasonCode": "0x2333",
+ "Type": "BD",
+ "SymptomIDFields": ["SRCWord5", "SRCWord6", "SRCWord7"],
+ "PowerFault": true,
+ "Words6To9":
+ {
+ "6":
+ {
+ "description": "Failing unit number",
+ "AdditionalDataPropSource": "PS_NUM"
+ },
+
+ "7":
+ {
+ "description": "bad voltage",
+ "AdditionalDataPropSource": "VOLTAGE"
+ }
+ }
+ },
+
+ "Documentation":
+ {
+ "Description": "A PGOOD Fault",
+ "Message": "PS %1 had a PGOOD Fault",
+ "MessageArgSources":
+ [
+ "SRCWord6"
+ ],
+ "Notes": [
+ "In the UserData section there is a JSON",
+ "dump that provides debug information."
+ ]
+ }
+ }
+ ]
+}
+)";
+
+class RegistryTest : public ::testing::Test
+{
+ protected:
+ static void SetUpTestCase()
+ {
+ char path[] = "/tmp/regtestXXXXXX";
+ regDir = mkdtemp(path);
+ }
+
+ static void TearDownTestCase()
+ {
+ fs::remove_all(regDir);
+ }
+
+ static std::string writeData(const char* data)
+ {
+ fs::path path = regDir / "registry.json";
+ std::ofstream stream{path};
+ stream << data;
+ return path;
+ }
+
+ static fs::path regDir;
+};
+
+fs::path RegistryTest::regDir{};
+
+TEST_F(RegistryTest, TestNoEntry)
+{
+ auto path = RegistryTest::writeData(registryData);
+ Registry registry{path};
+
+ auto entry = registry.lookup("foo", LookupType::name);
+ EXPECT_FALSE(entry);
+}
+
+TEST_F(RegistryTest, TestFindEntry)
+{
+ auto path = RegistryTest::writeData(registryData);
+ Registry registry{path};
+
+ auto entry = registry.lookup("xyz.openbmc_project.Power.OverVoltage",
+ LookupType::name);
+ ASSERT_TRUE(entry);
+ EXPECT_EQ(entry->name, "xyz.openbmc_project.Power.OverVoltage");
+ EXPECT_EQ(entry->subsystem, 0x62);
+ EXPECT_EQ(*(entry->severity), 0x40);
+ EXPECT_EQ(*(entry->mfgSeverity), 0x00);
+ EXPECT_EQ(*(entry->actionFlags), 0xA800);
+ EXPECT_EQ(*(entry->mfgActionFlags), 0x4000);
+ EXPECT_EQ(entry->componentID, 0x2300);
+ EXPECT_FALSE(entry->eventType);
+ EXPECT_FALSE(entry->eventScope);
+
+ EXPECT_EQ(entry->src.type, 0xBD);
+ EXPECT_EQ(entry->src.reasonCode, 0x2333);
+ EXPECT_EQ(*(entry->src.powerFault), true);
+
+ auto& hexwords = entry->src.hexwordADFields;
+ EXPECT_TRUE(hexwords);
+ EXPECT_EQ((*hexwords).size(), 2);
+
+ auto word = (*hexwords).find(6);
+ EXPECT_NE(word, (*hexwords).end());
+ EXPECT_EQ(word->second, "PS_NUM");
+
+ word = (*hexwords).find(7);
+ EXPECT_NE(word, (*hexwords).end());
+ EXPECT_EQ(word->second, "VOLTAGE");
+
+ auto& sid = entry->src.symptomID;
+ EXPECT_TRUE(sid);
+ EXPECT_EQ((*sid).size(), 3);
+ EXPECT_NE(std::find((*sid).begin(), (*sid).end(), 5), (*sid).end());
+ EXPECT_NE(std::find((*sid).begin(), (*sid).end(), 6), (*sid).end());
+ EXPECT_NE(std::find((*sid).begin(), (*sid).end(), 7), (*sid).end());
+
+ EXPECT_EQ(entry->doc.description, "A PGOOD Fault");
+ EXPECT_EQ(entry->doc.message, "PS %1 had a PGOOD Fault");
+ auto& hexwordSource = entry->doc.messageArgSources;
+ EXPECT_TRUE(hexwordSource);
+ EXPECT_EQ((*hexwordSource).size(), 1);
+ EXPECT_EQ((*hexwordSource).front(), "SRCWord6");
+
+ entry = registry.lookup("0x2333", LookupType::reasonCode);
+ ASSERT_TRUE(entry);
+ EXPECT_EQ(entry->name, "xyz.openbmc_project.Power.OverVoltage");
+}
+
+// Check the entry that mostly uses defaults
+TEST_F(RegistryTest, TestFindEntryMinimal)
+{
+ auto path = RegistryTest::writeData(registryData);
+ Registry registry{path};
+
+ auto entry =
+ registry.lookup("xyz.openbmc_project.Power.Fault", LookupType::name);
+ ASSERT_TRUE(entry);
+ EXPECT_EQ(entry->name, "xyz.openbmc_project.Power.Fault");
+ EXPECT_EQ(entry->subsystem, 0x61);
+ EXPECT_FALSE(entry->severity);
+ EXPECT_FALSE(entry->mfgSeverity);
+ EXPECT_FALSE(entry->mfgActionFlags);
+ EXPECT_FALSE(entry->actionFlags);
+ EXPECT_EQ(entry->componentID, 0x2000);
+ EXPECT_FALSE(entry->eventType);
+ EXPECT_FALSE(entry->eventScope);
+
+ EXPECT_EQ(entry->src.reasonCode, 0x2030);
+ EXPECT_EQ(entry->src.type, 0xBD);
+ EXPECT_FALSE(entry->src.powerFault);
+ EXPECT_FALSE(entry->src.hexwordADFields);
+ EXPECT_FALSE(entry->src.symptomID);
+}
+
+TEST_F(RegistryTest, TestBadJSON)
+{
+ auto path = RegistryTest::writeData("bad {} json");
+
+ Registry registry{path};
+
+ EXPECT_FALSE(registry.lookup("foo", LookupType::name));
+}
+
+// Test the helper functions the use the pel_values data.
+TEST_F(RegistryTest, TestHelperFunctions)
+{
+ using namespace openpower::pels::message::helper;
+ EXPECT_EQ(getSubsystem("input_power_source"), 0xA1);
+ EXPECT_THROW(getSubsystem("foo"), std::runtime_error);
+
+ EXPECT_EQ(getSeverity("symptom_recovered"), 0x71);
+ EXPECT_THROW(getSeverity("foo"), std::runtime_error);
+
+ EXPECT_EQ(getEventType("dump_notification"), 0x08);
+ EXPECT_THROW(getEventType("foo"), std::runtime_error);
+
+ EXPECT_EQ(getEventScope("possibly_multiple_platforms"), 0x04);
+ EXPECT_THROW(getEventScope("foo"), std::runtime_error);
+
+ std::vector<std::string> flags{"service_action", "dont_report",
+ "termination"};
+ EXPECT_EQ(getActionFlags(flags), 0x9100);
+
+ flags.clear();
+ flags.push_back("foo");
+ EXPECT_THROW(getActionFlags(flags), std::runtime_error);
+}
+
+TEST_F(RegistryTest, TestGetSRCReasonCode)
+{
+ using namespace openpower::pels::message::helper;
+ EXPECT_EQ(getSRCReasonCode(R"({"ReasonCode": "0x5555"})"_json, "foo"),
+ 0x5555);
+
+ EXPECT_THROW(getSRCReasonCode(R"({"ReasonCode": "ZZZZ"})"_json, "foo"),
+ std::runtime_error);
+}
+
+TEST_F(RegistryTest, TestGetSRCType)
+{
+ using namespace openpower::pels::message::helper;
+ EXPECT_EQ(getSRCType(R"({"Type": "11"})"_json, "foo"), 0x11);
+ EXPECT_EQ(getSRCType(R"({"Type": "BF"})"_json, "foo"), 0xBF);
+
+ EXPECT_THROW(getSRCType(R"({"Type": "1"})"_json, "foo"),
+ std::runtime_error);
+
+ EXPECT_THROW(getSRCType(R"({"Type": "111"})"_json, "foo"),
+ std::runtime_error);
+}
+
+TEST_F(RegistryTest, TestGetSRCHexwordFields)
+{
+ using namespace openpower::pels::message::helper;
+ const auto hexwords = R"(
+ {"Words6To9":
+ {
+ "8":
+ {
+ "AdditionalDataPropSource": "TEST"
+ }
+ }
+ })"_json;
+
+ auto fields = getSRCHexwordFields(hexwords, "foo");
+ EXPECT_TRUE(fields);
+ auto word = fields->find(8);
+ EXPECT_NE(word, fields->end());
+
+ const auto theInvalidRWord = R"(
+ {"Words6To9":
+ {
+ "R":
+ {
+ "AdditionalDataPropSource": "TEST"
+ }
+ }
+ })"_json;
+
+ EXPECT_THROW(getSRCHexwordFields(theInvalidRWord, "foo"),
+ std::runtime_error);
+}
+
+TEST_F(RegistryTest, TestGetSRCSymptomIDFields)
+{
+ using namespace openpower::pels::message::helper;
+ const auto sID = R"(
+ {
+ "SymptomIDFields": ["SRCWord3", "SRCWord4", "SRCWord5"]
+ })"_json;
+
+ auto fields = getSRCSymptomIDFields(sID, "foo");
+ EXPECT_NE(std::find(fields->begin(), fields->end(), 3), fields->end());
+ EXPECT_NE(std::find(fields->begin(), fields->end(), 4), fields->end());
+ EXPECT_NE(std::find(fields->begin(), fields->end(), 5), fields->end());
+
+ const auto badField = R"(
+ {
+ "SymptomIDFields": ["SRCWord3", "SRCWord4", "SRCWord"]
+ })"_json;
+
+ EXPECT_THROW(getSRCSymptomIDFields(badField, "foo"), std::runtime_error);
+}
+
+TEST_F(RegistryTest, TestGetComponentID)
+{
+ using namespace openpower::pels::message::helper;
+
+ // Get it from the JSON
+ auto id =
+ getComponentID(0xBD, 0x4200, R"({"ComponentID":"0x4200"})"_json, "foo");
+ EXPECT_EQ(id, 0x4200);
+
+ // Get it from the reason code on a 0xBD SRC
+ id = getComponentID(0xBD, 0x6700, R"({})"_json, "foo");
+ EXPECT_EQ(id, 0x6700);
+
+ // Not present on a 0x11 SRC
+ EXPECT_THROW(getComponentID(0x11, 0x8800, R"({})"_json, "foo"),
+ std::runtime_error);
+}
diff --git a/test/openpower-pels/repository_test.cpp b/test/openpower-pels/repository_test.cpp
new file mode 100644
index 0000000..446e10e
--- /dev/null
+++ b/test/openpower-pels/repository_test.cpp
@@ -0,0 +1,436 @@
+/**
+ * Copyright © 2019 IBM Corporation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#include "extensions/openpower-pels/paths.hpp"
+#include "extensions/openpower-pels/repository.hpp"
+#include "pel_utils.hpp"
+
+#include <ext/stdio_filebuf.h>
+
+#include <filesystem>
+
+#include <gtest/gtest.h>
+
+using namespace openpower::pels;
+namespace fs = std::filesystem;
+
+/**
+ * Clean the Repo after every testcase.
+ * And because we have PEL object, also clean up
+ * the log ID.
+ */
+class RepositoryTest : public CleanLogID
+{
+ protected:
+ void SetUp() override
+ {
+ repoPath = getPELRepoPath();
+ }
+
+ void TearDown() override
+ {
+ fs::remove_all(repoPath);
+ }
+
+ fs::path repoPath;
+};
+
+TEST_F(RepositoryTest, FilenameTest)
+{
+ BCDTime date = {0x20, 0x30, 0x11, 0x28, 0x13, 0x6, 0x7, 0x8};
+
+ EXPECT_EQ(Repository::getPELFilename(0x12345678, date),
+ "2030112813060708_12345678");
+
+ EXPECT_EQ(Repository::getPELFilename(0xAABBCCDD, date),
+ "2030112813060708_AABBCCDD");
+
+ EXPECT_EQ(Repository::getPELFilename(0x3AFF1, date),
+ "2030112813060708_0003AFF1");
+
+ EXPECT_EQ(Repository::getPELFilename(100, date),
+ "2030112813060708_00000064");
+
+ EXPECT_EQ(Repository::getPELFilename(0, date), "2030112813060708_00000000");
+}
+
+TEST_F(RepositoryTest, AddTest)
+{
+ Repository repo{repoPath};
+ auto data = pelDataFactory(TestPELType::pelSimple);
+ auto pel = std::make_unique<PEL>(data);
+
+ repo.add(pel);
+
+ // Check that the PEL was stored where it was supposed to be,
+ // and that it wrote the PEL data.
+ const auto ts = pel->privateHeader().commitTimestamp();
+ auto name = Repository::getPELFilename(pel->id(), ts);
+
+ fs::path file = repoPath / "logs" / name;
+ EXPECT_TRUE(fs::exists(file));
+
+ auto newData = readPELFile(file);
+ auto pelData = pel->data();
+ EXPECT_EQ(*newData, pelData);
+}
+
+TEST_F(RepositoryTest, RestoreTest)
+{
+ using pelID = Repository::LogID::Pel;
+ using obmcID = Repository::LogID::Obmc;
+
+ std::vector<Repository::LogID> ids;
+
+ {
+ Repository repo{repoPath};
+
+ // Add some PELs to the repository
+ {
+ auto data = pelDataFactory(TestPELType::pelSimple);
+ auto pel = std::make_unique<PEL>(data, 1);
+ pel->assignID();
+ repo.add(pel);
+ ids.emplace_back(pelID(pel->id()), obmcID(1));
+ }
+ {
+ auto data = pelDataFactory(TestPELType::pelSimple);
+ auto pel = std::make_unique<PEL>(data, 2);
+ pel->assignID();
+ repo.add(pel);
+ ids.emplace_back(pelID(pel->id()), obmcID(2));
+ }
+
+ // Check they're there
+ EXPECT_TRUE(repo.hasPEL(ids[0]));
+ EXPECT_TRUE(repo.hasPEL(ids[1]));
+
+ // Do some other search tests while we're here.
+
+ // Search based on PEL ID
+ Repository::LogID id(pelID(ids[0].pelID));
+ EXPECT_TRUE(repo.hasPEL(id));
+
+ // Search based on OBMC log ID
+ id.pelID.id = 0;
+ id.obmcID = ids[0].obmcID;
+ EXPECT_TRUE(repo.hasPEL(id));
+
+ // ... based on the other PEL ID
+ id.pelID = ids[1].pelID;
+ id.obmcID.id = 0;
+ EXPECT_TRUE(repo.hasPEL(id));
+
+ // Not found
+ id.pelID.id = 99;
+ id.obmcID.id = 100;
+ EXPECT_FALSE(repo.hasPEL(id));
+ }
+
+ {
+ // Restore and check they're still there, then
+ // remove them.
+ Repository repo{repoPath};
+ EXPECT_TRUE(repo.hasPEL(ids[0]));
+ EXPECT_TRUE(repo.hasPEL(ids[1]));
+
+ repo.remove(ids[0]);
+ EXPECT_FALSE(repo.hasPEL(ids[0]));
+
+ repo.remove(ids[1]);
+ EXPECT_FALSE(repo.hasPEL(ids[1]));
+ }
+}
+
+TEST_F(RepositoryTest, TestGetPELData)
+{
+ using ID = Repository::LogID;
+ Repository repo{repoPath};
+
+ ID badID{ID::Pel(42)};
+ auto noData = repo.getPELData(badID);
+ EXPECT_FALSE(noData);
+
+ // Add a PEL to the repo, and get the data back with getPELData.
+ auto data = pelDataFactory(TestPELType::pelSimple);
+ auto dataCopy = data;
+ auto pel = std::make_unique<PEL>(data);
+ auto pelID = pel->id();
+ repo.add(pel);
+
+ ID id{ID::Pel(pelID)};
+ auto pelData = repo.getPELData(id);
+
+ ASSERT_TRUE(pelData);
+ EXPECT_EQ(dataCopy, *pelData);
+}
+
+TEST_F(RepositoryTest, TestForEach)
+{
+ Repository repo{repoPath};
+
+ // Add 2 PELs
+ auto data = pelDataFactory(TestPELType::pelSimple);
+ auto pel = std::make_unique<PEL>(data);
+ repo.add(pel);
+
+ pel = std::make_unique<PEL>(data);
+ pel->assignID();
+ pel->setCommitTime();
+ repo.add(pel);
+
+ // Make a function that saves the IDs
+ std::vector<uint32_t> ids;
+ Repository::ForEachFunc f1 = [&ids](const PEL& pel) {
+ ids.push_back(pel.id());
+ return false;
+ };
+
+ repo.for_each(f1);
+
+ EXPECT_EQ(ids.size(), 2);
+
+ // Stop after the first time in.
+ Repository::ForEachFunc f2 = [&ids](const PEL& pel) {
+ ids.push_back(pel.id());
+ return true;
+ };
+
+ ids.clear();
+ repo.for_each(f2);
+ EXPECT_EQ(ids.size(), 1);
+}
+
+TEST_F(RepositoryTest, TestSubscriptions)
+{
+ std::vector<uint32_t> added;
+ std::vector<uint32_t> removed;
+
+ Repository::AddCallback ac = [&added](const PEL& pel) {
+ added.push_back(pel.id());
+ };
+
+ Repository::DeleteCallback dc = [&removed](uint32_t id) {
+ removed.push_back(id);
+ };
+
+ Repository repo{repoPath};
+ repo.subscribeToAdds("test", ac);
+ repo.subscribeToDeletes("test", dc);
+
+ auto data = pelDataFactory(TestPELType::pelSimple);
+ auto pel = std::make_unique<PEL>(data);
+ auto pelID = pel->id();
+ repo.add(pel);
+
+ EXPECT_EQ(added.size(), 1);
+
+ using ID = Repository::LogID;
+ ID id{ID::Pel(pelID)};
+ repo.remove(id);
+
+ EXPECT_EQ(removed.size(), 1);
+
+ repo.unsubscribeFromAdds("test");
+ repo.unsubscribeFromDeletes("test");
+
+ added.clear();
+ removed.clear();
+
+ repo.add(pel);
+ EXPECT_EQ(added.size(), 0);
+
+ repo.remove(id);
+ EXPECT_EQ(removed.size(), 0);
+}
+
+TEST_F(RepositoryTest, TestGetAttributes)
+{
+ uint32_t pelID = 0;
+ std::bitset<16> actionFlags;
+
+ {
+ Repository repo{repoPath};
+
+ // Add a PEL to the repo
+ auto data = pelDataFactory(TestPELType::pelSimple);
+ auto pel = std::make_unique<PEL>(data);
+ repo.add(pel);
+
+ pelID = pel->id();
+ actionFlags = pel->userHeader().actionFlags();
+
+ using ID = Repository::LogID;
+ ID id{ID::Pel(pelID)};
+
+ auto a = repo.getPELAttributes(id);
+ EXPECT_TRUE(a);
+ EXPECT_EQ((*a).get().actionFlags, actionFlags);
+
+ id.pelID.id = 0;
+ a = repo.getPELAttributes(id);
+ EXPECT_FALSE(a);
+ }
+
+ {
+ // Restore the repository and check again
+ Repository repo{repoPath};
+
+ using ID = Repository::LogID;
+ ID id{ID::Pel(pelID)};
+
+ auto a = repo.getPELAttributes(id);
+ EXPECT_TRUE(a);
+ EXPECT_EQ((*a).get().actionFlags, actionFlags);
+
+ id.pelID.id = 0;
+ a = repo.getPELAttributes(id);
+ EXPECT_FALSE(a);
+ }
+}
+
+TEST_F(RepositoryTest, TestSetHostState)
+{
+ // Add a PEL to the repo
+ auto data = pelDataFactory(TestPELType::pelSimple);
+ auto pel = std::make_unique<PEL>(data);
+ using ID = Repository::LogID;
+ ID id{ID::Pel(pel->id())};
+
+ {
+ Repository repo{repoPath};
+
+ repo.add(pel);
+
+ auto a = repo.getPELAttributes(id);
+ EXPECT_EQ((*a).get().hostState, TransmissionState::newPEL);
+
+ repo.setPELHostTransState(pel->id(), TransmissionState::acked);
+
+ // First, check the attributes
+ a = repo.getPELAttributes(id);
+ EXPECT_EQ((*a).get().hostState, TransmissionState::acked);
+
+ // Next, check the PEL data itself
+ auto pelData = repo.getPELData(id);
+ PEL newPEL{*pelData};
+ EXPECT_EQ(newPEL.hostTransmissionState(), TransmissionState::acked);
+ }
+
+ {
+ // Now restore, and check again
+ Repository repo{repoPath};
+
+ // First, check the attributes
+ auto a = repo.getPELAttributes(id);
+ EXPECT_EQ((*a).get().hostState, TransmissionState::acked);
+
+ // Next, check the PEL data itself
+ auto pelData = repo.getPELData(id);
+ PEL newPEL{*pelData};
+ EXPECT_EQ(newPEL.hostTransmissionState(), TransmissionState::acked);
+ }
+}
+
+TEST_F(RepositoryTest, TestSetHMCState)
+{
+ // Add a PEL to the repo
+ auto data = pelDataFactory(TestPELType::pelSimple);
+ auto pel = std::make_unique<PEL>(data);
+ using ID = Repository::LogID;
+ ID id{ID::Pel(pel->id())};
+
+ {
+ Repository repo{repoPath};
+
+ repo.add(pel);
+
+ auto a = repo.getPELAttributes(id);
+ EXPECT_EQ((*a).get().hmcState, TransmissionState::newPEL);
+
+ repo.setPELHMCTransState(pel->id(), TransmissionState::acked);
+
+ // First, check the attributes
+ a = repo.getPELAttributes(id);
+ EXPECT_EQ((*a).get().hmcState, TransmissionState::acked);
+
+ // Next, check the PEL data itself
+ auto pelData = repo.getPELData(id);
+ PEL newPEL{*pelData};
+ EXPECT_EQ(newPEL.hmcTransmissionState(), TransmissionState::acked);
+ }
+
+ {
+ // Now restore, and check again
+ Repository repo{repoPath};
+
+ // First, check the attributes
+ auto a = repo.getPELAttributes(id);
+ EXPECT_EQ((*a).get().hmcState, TransmissionState::acked);
+
+ // Next, check the PEL data itself
+ auto pelData = repo.getPELData(id);
+ PEL newPEL{*pelData};
+ EXPECT_EQ(newPEL.hmcTransmissionState(), TransmissionState::acked);
+ }
+}
+
+TEST_F(RepositoryTest, TestGetPELFD)
+{
+ Repository repo{repoPath};
+
+ auto data = pelDataFactory(TestPELType::pelSimple);
+ auto pel = std::make_unique<PEL>(data);
+ pel->setCommitTime();
+ pel->assignID();
+
+ repo.add(pel);
+
+ using ID = Repository::LogID;
+ ID id{ID::Pel(pel->id())};
+
+ auto fd = repo.getPELFD(id);
+
+ EXPECT_TRUE(fd);
+
+ // Get the size
+ struct stat s;
+ int r = fstat(*fd, &s);
+ ASSERT_EQ(r, 0);
+
+ auto size = s.st_size;
+
+ // Read the PEL data out of the FD
+ FILE* fp = fdopen(*fd, "r");
+ ASSERT_NE(fp, nullptr);
+
+ std::vector<uint8_t> newData;
+ newData.resize(size);
+ r = fread(newData.data(), 1, size, fp);
+ EXPECT_EQ(r, size);
+
+ PEL newPEL{newData};
+
+ EXPECT_TRUE(newPEL.valid());
+ EXPECT_EQ(newPEL.id(), pel->id());
+
+ fclose(fp);
+
+ // Call getPELFD again, this time with a bad ID
+ id.pelID.id = 42;
+ fd = repo.getPELFD(id);
+
+ EXPECT_FALSE(fd);
+}
diff --git a/test/openpower-pels/section_header_test.cpp b/test/openpower-pels/section_header_test.cpp
new file mode 100644
index 0000000..b8ff732
--- /dev/null
+++ b/test/openpower-pels/section_header_test.cpp
@@ -0,0 +1,55 @@
+/**
+ * Copyright © 2019 IBM Corporation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "extensions/openpower-pels/section_header.hpp"
+
+#include <gtest/gtest.h>
+
+using namespace openpower::pels;
+
+TEST(SectionHeaderTest, SizeTest)
+{
+ EXPECT_EQ(SectionHeader::flattenedSize(), 8);
+}
+
+TEST(SectionHeaderTest, UnflattenTest)
+{
+ std::vector<uint8_t> data{0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88};
+ Stream reader{data};
+ SectionHeader header;
+
+ reader >> header;
+
+ EXPECT_EQ(header.id, 0x1122);
+ EXPECT_EQ(header.size, 0x3344);
+ EXPECT_EQ(header.version, 0x55);
+ EXPECT_EQ(header.subType, 0x66);
+ EXPECT_EQ(header.componentID, 0x7788);
+}
+
+TEST(SectionHeaderTest, FlattenTest)
+{
+ SectionHeader header{0xAABB, 0xCCDD, 0xEE, 0xFF, 0xA0A0};
+
+ std::vector<uint8_t> data;
+ Stream writer{data};
+
+ writer << header;
+
+ std::vector<uint8_t> expected{0xAA, 0xBB, 0xCC, 0xDD,
+ 0xEE, 0xFF, 0xA0, 0xA0};
+ EXPECT_EQ(data, expected);
+}
diff --git a/test/openpower-pels/severity_test.cpp b/test/openpower-pels/severity_test.cpp
new file mode 100644
index 0000000..29c3bc3
--- /dev/null
+++ b/test/openpower-pels/severity_test.cpp
@@ -0,0 +1,33 @@
+/**
+ * Copyright © 2019 IBM Corporation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#include "extensions/openpower-pels/severity.hpp"
+
+#include <gtest/gtest.h>
+
+using namespace openpower::pels;
+using LogSeverity = phosphor::logging::Entry::Level;
+
+TEST(SeverityTest, SeverityMapTest)
+{
+ ASSERT_EQ(convertOBMCSeverityToPEL(LogSeverity::Informational), 0x00);
+ ASSERT_EQ(convertOBMCSeverityToPEL(LogSeverity::Notice), 0x00);
+ ASSERT_EQ(convertOBMCSeverityToPEL(LogSeverity::Debug), 0x00);
+ ASSERT_EQ(convertOBMCSeverityToPEL(LogSeverity::Warning), 0x20);
+ ASSERT_EQ(convertOBMCSeverityToPEL(LogSeverity::Critical), 0x50);
+ ASSERT_EQ(convertOBMCSeverityToPEL(LogSeverity::Emergency), 0x40);
+ ASSERT_EQ(convertOBMCSeverityToPEL(LogSeverity::Alert), 0x40);
+ ASSERT_EQ(convertOBMCSeverityToPEL(LogSeverity::Error), 0x40);
+}
diff --git a/test/openpower-pels/src_callout_test.cpp b/test/openpower-pels/src_callout_test.cpp
new file mode 100644
index 0000000..3fd2a5c
--- /dev/null
+++ b/test/openpower-pels/src_callout_test.cpp
@@ -0,0 +1,161 @@
+/**
+ * Copyright © 2019 IBM Corporation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#include "extensions/openpower-pels/callout.hpp"
+#include "pel_utils.hpp"
+
+#include <gtest/gtest.h>
+
+using namespace openpower::pels;
+using namespace openpower::pels::src;
+
+// Unflatten the callout section with all three substructures
+TEST(CalloutTest, TestUnflattenAllSubstructures)
+{
+ // The base data.
+ std::vector<uint8_t> data{
+ 0xFF, 0x2F, 'H', 8, // size, flags, priority, LC length
+ 'U', '1', '2', '-', 'P', '1', 0x00, 0x00 // LC
+ };
+
+ auto fruIdentity = srcDataFactory(TestSRCType::fruIdentityStructure);
+ auto pceIdentity = srcDataFactory(TestSRCType::pceIdentityStructure);
+ auto mrus = srcDataFactory(TestSRCType::mruStructure);
+
+ // Add all 3 substructures
+ data.insert(data.end(), fruIdentity.begin(), fruIdentity.end());
+ data.insert(data.end(), pceIdentity.begin(), pceIdentity.end());
+ data.insert(data.end(), mrus.begin(), mrus.end());
+
+ // The final size
+ data[0] = data.size();
+
+ Stream stream{data};
+ Callout callout{stream};
+
+ EXPECT_EQ(callout.flattenedSize(), data.size());
+ EXPECT_EQ(callout.priority(), 'H');
+ EXPECT_EQ(callout.locationCode(), "U12-P1");
+
+ // Spot check the 3 substructures
+ EXPECT_TRUE(callout.fruIdentity());
+ EXPECT_EQ(callout.fruIdentity()->getSN(), "123456789ABC");
+
+ EXPECT_TRUE(callout.pceIdentity());
+ EXPECT_EQ(callout.pceIdentity()->enclosureName(), "PCENAME12");
+
+ EXPECT_TRUE(callout.mru());
+ EXPECT_EQ(callout.mru()->mrus().size(), 4);
+ EXPECT_EQ(callout.mru()->mrus().at(3).id, 0x04040404);
+
+ // Now flatten
+ std::vector<uint8_t> newData;
+ Stream newStream{newData};
+
+ callout.flatten(newStream);
+ EXPECT_EQ(data, newData);
+}
+
+TEST(CalloutTest, TestUnflattenOneSubstructure)
+{
+ std::vector<uint8_t> data{
+ 0xFF, 0x28, 'H', 0x08, // size, flags, priority, LC length
+ 'U', '1', '2', '-', 'P', '1', 0x00, 0x00 // LC
+ };
+
+ auto fruIdentity = srcDataFactory(TestSRCType::fruIdentityStructure);
+
+ data.insert(data.end(), fruIdentity.begin(), fruIdentity.end());
+
+ // The final size
+ data[0] = data.size();
+
+ Stream stream{data};
+ Callout callout{stream};
+
+ EXPECT_EQ(callout.flattenedSize(), data.size());
+
+ // Spot check the substructure
+ EXPECT_TRUE(callout.fruIdentity());
+ EXPECT_EQ(callout.fruIdentity()->getSN(), "123456789ABC");
+
+ // Not present
+ EXPECT_FALSE(callout.pceIdentity());
+ EXPECT_FALSE(callout.mru());
+
+ // Now flatten
+ std::vector<uint8_t> newData;
+ Stream newStream{newData};
+
+ callout.flatten(newStream);
+ EXPECT_EQ(data, newData);
+}
+
+TEST(CalloutTest, TestUnflattenTwoSubstructures)
+{
+ std::vector<uint8_t> data{
+ 0xFF, 0x2B, 'H', 0x08, // size, flags, priority, LC length
+ 'U', '1', '2', '-', 'P', '1', 0x00, 0x00 // LC
+ };
+
+ auto fruIdentity = srcDataFactory(TestSRCType::fruIdentityStructure);
+ auto pceIdentity = srcDataFactory(TestSRCType::pceIdentityStructure);
+
+ data.insert(data.end(), fruIdentity.begin(), fruIdentity.end());
+ data.insert(data.end(), pceIdentity.begin(), pceIdentity.end());
+
+ // The final size
+ data[0] = data.size();
+
+ Stream stream{data};
+ Callout callout{stream};
+
+ EXPECT_EQ(callout.flattenedSize(), data.size());
+
+ // Spot check the 2 substructures
+ EXPECT_TRUE(callout.fruIdentity());
+ EXPECT_EQ(callout.fruIdentity()->getSN(), "123456789ABC");
+
+ EXPECT_TRUE(callout.pceIdentity());
+ EXPECT_EQ(callout.pceIdentity()->enclosureName(), "PCENAME12");
+
+ // Not present
+ EXPECT_FALSE(callout.mru());
+
+ // Now flatten
+ std::vector<uint8_t> newData;
+ Stream newStream{newData};
+
+ callout.flatten(newStream);
+ EXPECT_EQ(data, newData);
+}
+
+TEST(CalloutTest, TestNoLocationCode)
+{
+ std::vector<uint8_t> data{
+ 0xFF, 0x2B, 'H', 0x00 // size, flags, priority, LC length
+ };
+
+ auto fruIdentity = srcDataFactory(TestSRCType::fruIdentityStructure);
+ data.insert(data.end(), fruIdentity.begin(), fruIdentity.end());
+
+ // The final size
+ data[0] = data.size();
+
+ Stream stream{data};
+ Callout callout{stream};
+
+ EXPECT_TRUE(callout.locationCode().empty());
+}
diff --git a/test/openpower-pels/src_callouts_test.cpp b/test/openpower-pels/src_callouts_test.cpp
new file mode 100644
index 0000000..f192c15
--- /dev/null
+++ b/test/openpower-pels/src_callouts_test.cpp
@@ -0,0 +1,91 @@
+/**
+ * Copyright © 2019 IBM Corporation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#include "extensions/openpower-pels/callouts.hpp"
+#include "pel_utils.hpp"
+
+#include <gtest/gtest.h>
+
+using namespace openpower::pels;
+using namespace openpower::pels::src;
+
+TEST(CalloutsTest, UnflattenFlattenTest)
+{
+ std::vector<uint8_t> data{0xC0, 0x00, 0x00,
+ 0x00}; // ID, flags, length in words
+
+ // Add 2 callouts
+ auto callout = srcDataFactory(TestSRCType::calloutStructureA);
+ data.insert(data.end(), callout.begin(), callout.end());
+
+ callout = srcDataFactory(TestSRCType::calloutStructureB);
+ data.insert(data.end(), callout.begin(), callout.end());
+
+ Stream stream{data};
+
+ // Set the actual word length value at offset 2
+ uint16_t wordLength = data.size() / 4;
+ stream.offset(2);
+ stream << wordLength;
+ stream.offset(0);
+
+ Callouts callouts{stream};
+
+ EXPECT_EQ(callouts.flattenedSize(), data.size());
+ EXPECT_EQ(callouts.callouts().size(), 2);
+
+ // spot check that each callout has the right substructures
+ EXPECT_TRUE(callouts.callouts().front()->fruIdentity());
+ EXPECT_FALSE(callouts.callouts().front()->pceIdentity());
+ EXPECT_FALSE(callouts.callouts().front()->mru());
+
+ EXPECT_TRUE(callouts.callouts().back()->fruIdentity());
+ EXPECT_TRUE(callouts.callouts().back()->pceIdentity());
+ EXPECT_TRUE(callouts.callouts().back()->mru());
+
+ // Flatten
+ std::vector<uint8_t> newData;
+ Stream newStream{newData};
+
+ callouts.flatten(newStream);
+ EXPECT_EQ(data, newData);
+}
+
+TEST(CalloutsTest, BadDataTest)
+{
+ // Start out with a valid 2 callout object, then truncate it.
+ std::vector<uint8_t> data{0xC0, 0x00, 0x00,
+ 0x00}; // ID, flags, length in words
+
+ // Add 2 callouts
+ auto callout = srcDataFactory(TestSRCType::calloutStructureA);
+ data.insert(data.end(), callout.begin(), callout.end());
+
+ callout = srcDataFactory(TestSRCType::calloutStructureB);
+ data.insert(data.end(), callout.begin(), callout.end());
+
+ Stream stream{data};
+
+ // Set the actual word length value at offset 2
+ uint16_t wordLength = data.size() / 4;
+ stream.offset(2);
+ stream << wordLength;
+ stream.offset(0);
+
+ // Shorten the data by an arbitrary amount so unflattening goes awry.
+ data.resize(data.size() - 37);
+
+ EXPECT_THROW(Callouts callouts{stream}, std::out_of_range);
+}
diff --git a/test/openpower-pels/src_test.cpp b/test/openpower-pels/src_test.cpp
new file mode 100644
index 0000000..b966344
--- /dev/null
+++ b/test/openpower-pels/src_test.cpp
@@ -0,0 +1,261 @@
+/**
+ * Copyright © 2019 IBM Corporation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#include "extensions/openpower-pels/src.hpp"
+#include "pel_utils.hpp"
+
+#include <fstream>
+
+#include <gtest/gtest.h>
+
+using namespace openpower::pels;
+namespace fs = std::filesystem;
+
+const auto testRegistry = R"(
+{
+"PELs":
+[
+ {
+ "Name": "xyz.openbmc_project.Error.Test",
+ "Subsystem": "bmc_firmware",
+ "SRC":
+ {
+ "ReasonCode": "0xABCD",
+ "Words6To9":
+ {
+ "6":
+ {
+ "Description": "Component ID",
+ "AdditionalDataPropSource": "COMPID"
+ },
+ "7":
+ {
+ "Description": "Failure count",
+ "AdditionalDataPropSource": "FREQUENCY"
+ },
+ "8":
+ {
+ "Description": "Time period",
+ "AdditionalDataPropSource": "DURATION"
+ },
+ "9":
+ {
+ "Description": "Error code",
+ "AdditionalDataPropSource": "ERRORCODE"
+ }
+ }
+ },
+ "Documentation":
+ {
+ "Description": "A Component Fault",
+ "Message": "Comp %1 failed %2 times over %3 secs with ErrorCode %4",
+ "MessageArgSources":
+ [
+ "SRCWord6", "SRCWord7", "SRCWord8", "SRCWord9"
+ ]
+ }
+ }
+]
+}
+)";
+
+class SRCTest : public ::testing::Test
+{
+ protected:
+ static void SetUpTestCase()
+ {
+ char path[] = "/tmp/srctestXXXXXX";
+ regDir = mkdtemp(path);
+ }
+
+ static void TearDownTestCase()
+ {
+ fs::remove_all(regDir);
+ }
+
+ static std::string writeData(const char* data)
+ {
+ fs::path path = regDir / "registry.json";
+ std::ofstream stream{path};
+ stream << data;
+ return path;
+ }
+
+ static fs::path regDir;
+};
+
+fs::path SRCTest::regDir{};
+
+TEST_F(SRCTest, UnflattenFlattenTestNoCallouts)
+{
+ auto data = pelDataFactory(TestPELType::primarySRCSection);
+
+ Stream stream{data};
+ SRC src{stream};
+
+ EXPECT_TRUE(src.valid());
+
+ EXPECT_EQ(src.header().id, 0x5053);
+ EXPECT_EQ(src.header().size, 0x50);
+ EXPECT_EQ(src.header().version, 0x01);
+ EXPECT_EQ(src.header().subType, 0x01);
+ EXPECT_EQ(src.header().componentID, 0x0202);
+
+ EXPECT_EQ(src.version(), 0x02);
+ EXPECT_EQ(src.flags(), 0x00);
+ EXPECT_EQ(src.hexWordCount(), 9);
+ EXPECT_EQ(src.size(), 0x48);
+
+ const auto& hexwords = src.hexwordData();
+ EXPECT_EQ(0x02020255, hexwords[0]);
+ EXPECT_EQ(0x03030310, hexwords[1]);
+ EXPECT_EQ(0x04040404, hexwords[2]);
+ EXPECT_EQ(0x05050505, hexwords[3]);
+ EXPECT_EQ(0x06060606, hexwords[4]);
+ EXPECT_EQ(0x07070707, hexwords[5]);
+ EXPECT_EQ(0x08080808, hexwords[6]);
+ EXPECT_EQ(0x09090909, hexwords[7]);
+
+ EXPECT_EQ(src.asciiString(), "BD8D5678 ");
+ EXPECT_FALSE(src.callouts());
+
+ // Flatten
+ std::vector<uint8_t> newData;
+ Stream newStream{newData};
+
+ src.flatten(newStream);
+ EXPECT_EQ(data, newData);
+}
+
+TEST_F(SRCTest, UnflattenFlattenTest2Callouts)
+{
+ auto data = pelDataFactory(TestPELType::primarySRCSection2Callouts);
+
+ Stream stream{data};
+ SRC src{stream};
+
+ EXPECT_TRUE(src.valid());
+ EXPECT_EQ(src.flags(), 0x01); // Additional sections within the SRC.
+
+ // Spot check the SRC fields, but they're the same as above
+ EXPECT_EQ(src.asciiString(), "BD8D5678 ");
+
+ // There should be 2 callouts
+ const auto& calloutsSection = src.callouts();
+ ASSERT_TRUE(calloutsSection);
+ const auto& callouts = calloutsSection->callouts();
+ EXPECT_EQ(callouts.size(), 2);
+
+ // spot check that each callout has the right substructures
+ EXPECT_TRUE(callouts.front()->fruIdentity());
+ EXPECT_FALSE(callouts.front()->pceIdentity());
+ EXPECT_FALSE(callouts.front()->mru());
+
+ EXPECT_TRUE(callouts.back()->fruIdentity());
+ EXPECT_TRUE(callouts.back()->pceIdentity());
+ EXPECT_TRUE(callouts.back()->mru());
+
+ // Flatten
+ std::vector<uint8_t> newData;
+ Stream newStream{newData};
+
+ src.flatten(newStream);
+ EXPECT_EQ(data, newData);
+}
+
+// Create an SRC from the message registry
+TEST_F(SRCTest, CreateTestNoCallouts)
+{
+ message::Entry entry;
+ entry.src.type = 0xBD;
+ entry.src.reasonCode = 0xABCD;
+ entry.subsystem = 0x42;
+ entry.src.powerFault = true;
+ entry.src.hexwordADFields = {{5, "TEST1"}, // Not a user defined word
+ {6, "TEST1"},
+ {7, "TEST2"},
+ {8, "TEST3"},
+ {9, "TEST4"}};
+
+ // Values for the SRC words pointed to above
+ std::vector<std::string> adData{"TEST1=0x12345678", "TEST2=12345678",
+ "TEST3=0XDEF", "TEST4=Z"};
+ AdditionalData ad{adData};
+ SRC src{entry, ad};
+
+ EXPECT_TRUE(src.valid());
+ EXPECT_TRUE(src.isPowerFaultEvent());
+ EXPECT_EQ(src.size(), baseSRCSize);
+
+ const auto& hexwords = src.hexwordData();
+
+ // The spec always refers to SRC words 2 - 9, and as the hexwordData()
+ // array index starts at 0 use the math in the [] below to make it easier
+ // to tell what is being accessed.
+ EXPECT_EQ(hexwords[2 - 2] & 0xF0000000, 0); // Partition dump status
+ EXPECT_EQ(hexwords[2 - 2] & 0x00F00000, 0); // Partition boot type
+ EXPECT_EQ(hexwords[2 - 2] & 0x000000FF, 0x55); // SRC format
+ EXPECT_EQ(hexwords[3 - 2] & 0x000000FF, 0x10); // BMC position
+
+ // Validate more fields here as the code starts filling them in.
+
+ // Ensure hex word 5 wasn't allowed to be set to TEST1's contents
+ EXPECT_EQ(hexwords[5 - 2], 0);
+
+ // The user defined hex word fields specifed in the additional data.
+ EXPECT_EQ(hexwords[6 - 2], 0x12345678); // TEST1
+ EXPECT_EQ(hexwords[7 - 2], 12345678); // TEST2
+ EXPECT_EQ(hexwords[8 - 2], 0xdef); // TEST3
+ EXPECT_EQ(hexwords[9 - 2], 0); // TEST4, but can't convert a 'Z'
+
+ EXPECT_EQ(src.asciiString(), "BD42ABCD ");
+
+ // No callouts
+ EXPECT_FALSE(src.callouts());
+
+ // May as well spot check the flatten/unflatten
+ std::vector<uint8_t> data;
+ Stream stream{data};
+ src.flatten(stream);
+
+ stream.offset(0);
+ SRC newSRC{stream};
+
+ EXPECT_TRUE(newSRC.valid());
+ EXPECT_EQ(newSRC.isPowerFaultEvent(), src.isPowerFaultEvent());
+ EXPECT_EQ(newSRC.asciiString(), src.asciiString());
+ EXPECT_FALSE(newSRC.callouts());
+}
+
+// Test the getErrorDetails function
+TEST_F(SRCTest, MessageSubstitutionTest)
+{
+ auto path = SRCTest::writeData(testRegistry);
+ message::Registry registry{path};
+ auto entry = registry.lookup("0xABCD", message::LookupType::reasonCode);
+
+ std::vector<std::string> adData{"COMPID=0x1", "FREQUENCY=0x4",
+ "DURATION=30", "ERRORCODE=0x01ABCDEF"};
+ AdditionalData ad{adData};
+
+ SRC src{*entry, ad};
+ EXPECT_TRUE(src.valid());
+
+ auto errorDetails = src.getErrorDetails(registry, DetailLevel::message);
+ ASSERT_TRUE(errorDetails);
+ EXPECT_EQ(
+ errorDetails.value(),
+ "Comp 0x1 failed 0x4 times over 0x1E secs with ErrorCode 0x1ABCDEF");
+}
diff --git a/test/openpower-pels/stream_test.cpp b/test/openpower-pels/stream_test.cpp
new file mode 100644
index 0000000..361cfc5
--- /dev/null
+++ b/test/openpower-pels/stream_test.cpp
@@ -0,0 +1,197 @@
+/**
+ * Copyright © 2019 IBM Corporation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#include "extensions/openpower-pels/stream.hpp"
+
+#include <iostream>
+
+#include <gtest/gtest.h>
+
+using namespace openpower::pels;
+
+TEST(StreamTest, TestExtract)
+{
+ std::vector<uint8_t> data{0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77,
+ 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
+ 0x08, 'h', 'e', 'l', 'l', 'o'};
+ Stream stream{data};
+
+ {
+ uint8_t v;
+ stream >> v;
+ EXPECT_EQ(v, 0x11);
+ }
+ {
+ uint16_t v;
+ stream >> v;
+ EXPECT_EQ(v, 0x2233);
+ }
+ {
+ uint32_t v;
+ stream >> v;
+ EXPECT_EQ(v, 0x44556677);
+ }
+ {
+ uint64_t v;
+ stream >> v;
+ EXPECT_EQ(v, 0x0102030405060708);
+ }
+ {
+ char v[6] = {0};
+ stream.read(v, 5);
+ EXPECT_EQ(memcmp(v, "hello", 5), 0);
+ }
+
+ EXPECT_EQ(stream.remaining(), 0);
+
+ // At the end, so should throw.
+ uint8_t v;
+ EXPECT_THROW(stream >> v, std::out_of_range);
+}
+
+TEST(StreamTest, InputTestNoExpansion)
+{
+ std::vector<uint8_t> data(256, 0);
+ Stream stream(data);
+ uint8_t v1 = 0x11;
+ uint16_t v2 = 0x2233;
+ uint64_t v3 = 0x445566778899AABB;
+ uint32_t v4 = 0xCCDDEEFF;
+
+ stream << v3 << v2 << v4 << v1;
+
+ uint8_t e1;
+ uint16_t e2;
+ uint64_t e3;
+ uint32_t e4;
+
+ stream.offset(0);
+ stream >> e3 >> e2 >> e4 >> e1;
+
+ EXPECT_EQ(v1, e1);
+ EXPECT_EQ(v2, e2);
+ EXPECT_EQ(v3, e3);
+ EXPECT_EQ(v4, e4);
+}
+
+TEST(StreamTest, InputTestExpansion)
+{
+ // The stream will expand the underlying vector
+ std::vector<uint8_t> data;
+ Stream stream(data);
+
+ uint32_t v1 = 0xAABBCCDD;
+ stream << v1;
+
+ stream.offset(0);
+ uint32_t e1;
+ stream >> e1;
+ EXPECT_EQ(data.size(), 4);
+ EXPECT_EQ(v1, e1);
+
+ stream.offset(2);
+
+ uint64_t v2 = 0x0102030405060708;
+ stream << v2;
+
+ EXPECT_EQ(data.size(), 10);
+ uint64_t e2;
+ stream.offset(2);
+ stream >> e2;
+
+ EXPECT_EQ(v2, e2);
+
+ auto origSize = data.size();
+ uint8_t v3 = 0xCC;
+ stream << v3;
+
+ EXPECT_EQ(origSize + 1, data.size());
+ stream.offset(stream.offset() - 1);
+ uint8_t e3;
+ stream >> e3;
+ EXPECT_EQ(v3, e3);
+}
+
+TEST(StreamTest, ReadWriteTest)
+{
+ std::vector<uint8_t> data{0x11, 0x22, 0x33, 0x44, 0x55, 0x66,
+ 0x77, 0x88, 0x99, 0xaa, 0xbb, 0xcc};
+ Stream stream{data};
+ uint8_t buf[data.size()];
+
+ stream.read(buf, data.size());
+
+ for (size_t i = 0; i < data.size(); i++)
+ {
+ EXPECT_EQ(buf[i], data[i]);
+
+ // for the next test
+ buf[i] = 0x20 + i;
+ }
+
+ stream.offset(6);
+ stream.write(buf, 6);
+ for (size_t i = 0; i < 6; i++)
+ {
+ EXPECT_EQ(buf[i], data[i + 6]);
+ }
+}
+
+TEST(StreamTest, TestOffsets)
+{
+ std::vector<uint8_t> data{0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77};
+ Stream stream{data, 3};
+
+ {
+ uint8_t v;
+ stream >> v;
+ EXPECT_EQ(v, 0x44);
+ EXPECT_EQ(stream.offset(), 4);
+ }
+
+ stream.offset(6);
+
+ {
+ uint8_t v;
+ stream >> v;
+ EXPECT_EQ(v, 0x77);
+ EXPECT_EQ(stream.offset(), 7);
+ EXPECT_EQ(stream.remaining(), 0);
+ }
+
+ EXPECT_THROW(stream.offset(100), std::out_of_range);
+}
+
+TEST(StreamTest, TestVectorInsertExtract)
+{
+ std::vector<uint8_t> toInsert{0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77};
+ std::vector<uint8_t> data;
+
+ // Insert
+ Stream stream{data};
+ stream << toInsert;
+ EXPECT_EQ(data, toInsert);
+
+ // Extract
+ std::vector<uint8_t> toExtract;
+ toExtract.resize(toInsert.size());
+ stream.offset(0);
+ stream >> toExtract;
+
+ EXPECT_EQ(data, toExtract);
+
+ // Go off the end
+ EXPECT_THROW(stream >> toExtract, std::out_of_range);
+}
diff --git a/test/openpower-pels/user_data_test.cpp b/test/openpower-pels/user_data_test.cpp
new file mode 100644
index 0000000..a957a4d
--- /dev/null
+++ b/test/openpower-pels/user_data_test.cpp
@@ -0,0 +1,106 @@
+/**
+ * Copyright © 2019 IBM Corporation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#include "extensions/openpower-pels/user_data.hpp"
+#include "pel_utils.hpp"
+
+#include <gtest/gtest.h>
+
+using namespace openpower::pels;
+
+std::vector<uint8_t> udSectionData{0x55, 0x44, // ID 'UD'
+ 0x00, 0x10, // Size
+ 0x01, 0x02, // version, subtype
+ 0x03, 0x04, // comp ID
+
+ // Data
+ 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17,
+ 0x18};
+
+TEST(UserDataTest, UnflattenFlattenTest)
+{
+ Stream stream(udSectionData);
+ UserData ud(stream);
+
+ EXPECT_TRUE(ud.valid());
+ EXPECT_EQ(ud.header().id, 0x5544);
+ EXPECT_EQ(ud.header().size, udSectionData.size());
+ EXPECT_EQ(ud.header().version, 0x01);
+ EXPECT_EQ(ud.header().subType, 0x02);
+ EXPECT_EQ(ud.header().componentID, 0x0304);
+
+ const auto& data = ud.data();
+
+ // The data itself starts after the header
+ EXPECT_EQ(data.size(), udSectionData.size() - 8);
+
+ for (size_t i = 0; i < data.size(); i++)
+ {
+ EXPECT_EQ(data[i], udSectionData[i + 8]);
+ }
+
+ // Now flatten
+ std::vector<uint8_t> newData;
+ Stream newStream(newData);
+ ud.flatten(newStream);
+
+ EXPECT_EQ(udSectionData, newData);
+}
+
+TEST(UserDataTest, BadDataTest)
+{
+ auto data = udSectionData;
+ data.resize(4);
+
+ Stream stream(data);
+ UserData ud(stream);
+ EXPECT_FALSE(ud.valid());
+}
+
+TEST(UserDataTest, BadSizeFieldTest)
+{
+ auto data = udSectionData;
+
+ {
+ data[3] = 0xFF; // Set the size field too large
+ Stream stream(data);
+ UserData ud(stream);
+ EXPECT_FALSE(ud.valid());
+ }
+ {
+ data[3] = 0x7; // Set the size field too small
+ Stream stream(data);
+ UserData ud(stream);
+ EXPECT_FALSE(ud.valid());
+ }
+}
+
+TEST(UserDataTest, ConstructorTest)
+{
+ std::vector<uint8_t> data{0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88};
+
+ UserData ud(0x1112, 0x42, 0x01, data);
+ EXPECT_TRUE(ud.valid());
+
+ EXPECT_EQ(ud.header().id, 0x5544);
+ EXPECT_EQ(ud.header().size, 16);
+ EXPECT_EQ(ud.header().version, 0x01);
+ EXPECT_EQ(ud.header().subType, 0x42);
+ EXPECT_EQ(ud.header().componentID, 0x1112);
+
+ const auto& d = ud.data();
+
+ EXPECT_EQ(d, data);
+}
diff --git a/test/openpower-pels/user_header_test.cpp b/test/openpower-pels/user_header_test.cpp
new file mode 100644
index 0000000..7b8842c
--- /dev/null
+++ b/test/openpower-pels/user_header_test.cpp
@@ -0,0 +1,163 @@
+/**
+ * Copyright © 2019 IBM Corporation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#include "elog_entry.hpp"
+#include "extensions/openpower-pels/pel_types.hpp"
+#include "extensions/openpower-pels/private_header.hpp"
+#include "extensions/openpower-pels/user_header.hpp"
+#include "pel_utils.hpp"
+
+#include <gtest/gtest.h>
+
+using namespace openpower::pels;
+
+TEST(UserHeaderTest, SizeTest)
+{
+ EXPECT_EQ(UserHeader::flattenedSize(), 24);
+}
+
+TEST(UserHeaderTest, UnflattenFlattenTest)
+{
+ auto data = pelDataFactory(TestPELType::userHeaderSection);
+
+ Stream stream(data);
+ UserHeader uh(stream);
+ EXPECT_EQ(uh.valid(), true);
+
+ EXPECT_EQ(uh.header().id, 0x5548);
+ EXPECT_EQ(uh.header().size, UserHeader::flattenedSize());
+ EXPECT_EQ(uh.header().version, 0x01);
+ EXPECT_EQ(uh.header().subType, 0x0A);
+ EXPECT_EQ(uh.header().componentID, 0x0B0C);
+
+ EXPECT_EQ(uh.subsystem(), 0x10);
+ EXPECT_EQ(uh.scope(), 0x04);
+ EXPECT_EQ(uh.severity(), 0x20);
+ EXPECT_EQ(uh.eventType(), 0x00);
+ EXPECT_EQ(uh.problemDomain(), 0x03);
+ EXPECT_EQ(uh.problemVector(), 0x04);
+ EXPECT_EQ(uh.actionFlags(), 0x80C0);
+
+ // Now flatten into a vector and check that this vector
+ // matches the original one.
+ std::vector<uint8_t> newData;
+ Stream newStream(newData);
+
+ uh.flatten(newStream);
+ EXPECT_EQ(data, newData);
+}
+
+TEST(UserHeaderTest, ShortDataTest)
+{
+ auto data = pelDataFactory(TestPELType::userHeaderSection);
+ data.resize(data.size() - 1);
+
+ Stream stream(data);
+ UserHeader uh(stream);
+
+ EXPECT_EQ(uh.valid(), false);
+}
+
+TEST(UserHeaderTest, CorruptDataTest1)
+{
+ auto data = pelDataFactory(TestPELType::userHeaderSection);
+ data.resize(data.size() - 1);
+
+ data.at(0) = 0; // corrupt the section ID
+
+ Stream stream(data);
+ UserHeader uh(stream);
+
+ EXPECT_EQ(uh.valid(), false);
+}
+
+TEST(UserHeaderTest, CorruptDataTest2)
+{
+ auto data = pelDataFactory(TestPELType::userHeaderSection);
+
+ data.at(4) = 0x22; // corrupt the version
+
+ Stream stream(data);
+ UserHeader uh(stream);
+
+ EXPECT_EQ(uh.valid(), false);
+}
+
+// Construct the User Header from the message registry
+TEST(UserHeaderTest, ConstructionTest)
+{
+ using namespace openpower::pels::message;
+ Entry regEntry;
+
+ regEntry.name = "test";
+ regEntry.subsystem = 5;
+ regEntry.severity = 0x40;
+ regEntry.actionFlags = 0xC000;
+ regEntry.eventType = 1;
+ regEntry.eventScope = 2;
+
+ UserHeader uh(regEntry, phosphor::logging::Entry::Level::Error);
+
+ ASSERT_TRUE(uh.valid());
+ EXPECT_EQ(uh.header().id, 0x5548);
+ EXPECT_EQ(uh.header().size, UserHeader::flattenedSize());
+ EXPECT_EQ(uh.header().version, 0x01);
+ EXPECT_EQ(uh.header().subType, 0x00);
+ EXPECT_EQ(uh.header().componentID,
+ static_cast<uint16_t>(ComponentID::phosphorLogging));
+
+ ASSERT_EQ(uh.subsystem(), 5);
+ ASSERT_EQ(uh.severity(), 0x40);
+ ASSERT_EQ(uh.eventType(), 1);
+ ASSERT_EQ(uh.scope(), 2);
+ ASSERT_EQ(uh.problemDomain(), 0);
+ ASSERT_EQ(uh.problemVector(), 0);
+ ASSERT_EQ(uh.actionFlags(), 0xC000);
+}
+
+// Test that the severity comes from the event log if not
+// in the message registry
+TEST(UserHeaderTest, UseEventLogSevTest)
+{
+ using namespace openpower::pels::message;
+ Entry regEntry;
+
+ regEntry.name = "test";
+ regEntry.subsystem = 5;
+ regEntry.actionFlags = 0xC000;
+ regEntry.eventType = 1;
+ regEntry.eventScope = 2;
+ // Leave off severity
+
+ UserHeader uh(regEntry, phosphor::logging::Entry::Level::Error);
+ ASSERT_EQ(uh.severity(), 0x40);
+}
+
+// Test that the optional event type & scope fields work
+TEST(UserHeaderTest, DefaultEventTypeScopeTest)
+{
+ using namespace openpower::pels::message;
+ Entry regEntry;
+
+ regEntry.name = "test";
+ regEntry.subsystem = 5;
+ regEntry.severity = 0x40;
+ regEntry.actionFlags = 0xC000;
+
+ UserHeader uh(regEntry, phosphor::logging::Entry::Level::Error);
+
+ ASSERT_EQ(uh.eventType(), 0);
+ ASSERT_EQ(uh.scope(), 0x03);
+}
diff --git a/test/remote_logging_test_config.cpp b/test/remote_logging_test_config.cpp
index b34a43f..cba54ea 100644
--- a/test/remote_logging_test_config.cpp
+++ b/test/remote_logging_test_config.cpp
@@ -3,19 +3,6 @@
#include <fstream>
#include <string>
-#if __has_include(<filesystem>)
-#include <filesystem>
-#elif __has_include(<experimental/filesystem>)
-#include <experimental/filesystem>
-namespace std
-{
-// splice experimental::filesystem into std
-namespace filesystem = std::experimental::filesystem;
-} // namespace std
-#else
-#error filesystem not available
-#endif
-
namespace phosphor
{
namespace logging
@@ -34,13 +21,13 @@ std::string getConfig(const char* filePath)
TEST_F(TestRemoteLogging, testOnlyAddress)
{
config->address("1.1.1.1");
- EXPECT_EQ(fs::exists(configFilePath.c_str()), false);
+ EXPECT_EQ(getConfig(configFilePath.c_str()), "*.* ~");
}
TEST_F(TestRemoteLogging, testOnlyPort)
{
config->port(100);
- EXPECT_EQ(fs::exists(configFilePath.c_str()), false);
+ EXPECT_EQ(getConfig(configFilePath.c_str()), "*.* ~");
}
TEST_F(TestRemoteLogging, testGoodConfig)
@@ -56,7 +43,7 @@ TEST_F(TestRemoteLogging, testClearAddress)
config->port(100);
EXPECT_EQ(getConfig(configFilePath.c_str()), "*.* @@1.1.1.1:100");
config->address("");
- EXPECT_EQ(fs::exists(configFilePath.c_str()), false);
+ EXPECT_EQ(getConfig(configFilePath.c_str()), "*.* ~");
}
TEST_F(TestRemoteLogging, testClearPort)
@@ -65,7 +52,7 @@ TEST_F(TestRemoteLogging, testClearPort)
config->port(100);
EXPECT_EQ(getConfig(configFilePath.c_str()), "*.* @@1.1.1.1:100");
config->port(0);
- EXPECT_EQ(fs::exists(configFilePath.c_str()), false);
+ EXPECT_EQ(getConfig(configFilePath.c_str()), "*.* ~");
}
} // namespace test
diff --git a/tools/phosphor-logging/templates/elog-lookup-template.mako.cpp b/tools/phosphor-logging/templates/elog-lookup-template.mako.cpp
index e29b7a9..2e5c785 100644
--- a/tools/phosphor-logging/templates/elog-lookup-template.mako.cpp
+++ b/tools/phosphor-logging/templates/elog-lookup-template.mako.cpp
@@ -16,24 +16,30 @@ namespace logging
const std::map<std::string,std::vector<std::string>> g_errMetaMap = {
% for name in errors:
<%
+ meta_string = ""
meta_list = []
- if(name in meta):
+ if(name in meta and meta[name]):
meta_list = meta[name]
- meta_string = '\",\"'.join(meta_list)
+ meta_string = '\",\"'.join(meta_list)
parent = parents[name]
while parent:
- tmpparent = parent.split('.')
- ## Name is the last item
- parent_name = tmpparent[-1]
- parent_meta_short = '\",\"'.join(meta[parent])
- meta_string = meta_string + "\",\"" + parent_meta_short
+ if (parent in meta and meta[parent]):
+ parent_meta_short = '\",\"'.join(meta[parent])
+ if (meta_string):
+ meta_string = meta_string + "\",\"" + parent_meta_short
+ else:
+ meta_string = parent_meta_short
parent = parents[parent]
if ("example.xyz.openbmc_project" not in name):
index = name.rfind('.')
name = name[:index] + ".Error" + name[index:]
%>\
+ %if (meta_string):
{"${name}",{"${meta_string}"}},
+ %else:
+ {"${name}",{}},
+ %endif
% endfor
};
OpenPOWER on IntegriCloud