diff --git a/AGENTS.md b/AGENTS.md index 70a2db6..78010e7 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -6,7 +6,7 @@ The code is formatted with the MetricsHub Eclipse formatter profile (`metricshub Checkstyle (`checkstyle.xml`, byte-identical to jawk's) runs at `verify` with `failOnViolation=true`. Fix violations rather than suppressing them; when a suppression is justified, wrap the code in `// CHECKSTYLE.OFF: ` / `// CHECKSTYLE.ON: ` comments. -All Java source files under `src/main/java` must include the proper LGPL-3 license header (the `license-maven-plugin` check covers `main/java/**/*.java` only; tests, Markdown and resources carry no header). When you add a new source file, run `mvn license:update-file-header` before committing (and before building, since the build fails if a source file lacks the header). +All Java source files under `src/main/java` must include the proper LGPL-3 license header (the `license-maven-plugin` check covers `main/java/**/*.java` only; tests, Markdown and resources carry no header). When you add a new source file, run `mvn license:update-file-header` before committing (and before building, since the build fails if a source file lacks the header). The plugin only adds missing headers; it is configured never to rewrite existing ones, so do not edit copyright lines in bulk. The library is a fork of the Verax Systems IPMI Library for Java: files derived from Verax code (`org.metricshub.ipmi.core`) say `Copyright 2023 Verax Systems, MetricsHub`, files written by MetricsHub say `Copyright 2023 MetricsHub`. A new file gets a MetricsHub copyright line; if it contains code moved from a Verax-derived file, give it that file's copyright line. All public methods must have proper Javadoc. Check the output of Maven to identify issues with Javadoc and fix these issues. @@ -26,7 +26,7 @@ Unit tests must not depend on a real BMC. Exercising the RMCP+ session code agai ## Code quality reports -Code quality reports (checkstyle, pmd/cpd, spotbugs) are generated by `mvn verify site` into ./target/checkstyle-result.xml, ./target/pmd.xml, ./target/cpd.xml and ./target/spotbugsXml.xml. Checkstyle and PMD are gated (the build fails on any error); CPD and SpotBugs are not yet gated (issues #115 and #116 track the clean-up). Do not add new violations: check the reports for the files you changed before committing and submitting your code. On JDK 21+ the SpotBugs plugin version inherited from the parent POM cannot read the JDK class files; run `mvn com.github.spotbugs:spotbugs-maven-plugin:4.10.4.1:spotbugs` instead. +Code quality reports (checkstyle, pmd/cpd, spotbugs) are generated by `mvn verify site` into ./target/checkstyle-result.xml, ./target/pmd.xml, ./target/cpd.xml and ./target/spotbugsXml.xml. Checkstyle and PMD are gated (the build fails on any error); CPD is gated on duplications of 100 tokens or more (`cpd-check` at `verify`), while the site report lists those of 50 tokens or more; SpotBugs is not yet gated (issue #116 tracks the clean-up). Extract shared code instead of copying it: `AbstractSensorRecord` holds what the Full, Compact and Event-Only sensor records share, and `IpmiCommandCoder.validateResponse()` the response checks of every command. Do not add new violations: check the reports for the files you changed before committing and submitting your code. On JDK 21+ the SpotBugs plugin version inherited from the parent POM cannot read the JDK class files; run `mvn com.github.spotbugs:spotbugs-maven-plugin:4.10.4.1:spotbugs` instead. ## Documentation diff --git a/README.md b/README.md index 3f1d6c6..f6afafd 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,7 @@ The IPMI Java Client is a library that communicates with the IPMI host, fetches ## Upgrading -Version 1.2.03 makes the `protected` fields of the protocol classes (`AbstractIpmiRunner`, `MessageHandler`, `IpmiLanMessage`, `ConfidentialityAlgorithm`, `IntegrityAlgorithm`) `private`. Subclasses must use the new `protected` accessors instead; see [Upgrading from 1.2.02](https://metricshub.org/ipmi-java/#upgrading-from-1-2-02) for the list. The `IpmiClient` API is unchanged. +Version 1.2.03 makes the `protected` fields of the protocol classes (`AbstractIpmiRunner`, `MessageHandler`, `IpmiLanMessage`, `ConfidentialityAlgorithm`, `IntegrityAlgorithm`) `private`. Subclasses must use the new `protected` accessors instead; see [Upgrading from 1.2.02](https://metricshub.org/ipmi-java/#upgrading-from-1-2-02) for the list. The `IpmiClient` API is unchanged. The Full, Compact and Event-Only sensor records now share the `AbstractSensorRecord` superclass, and commands can check responses with `IpmiCommandCoder.validateResponse()`; both are described on the same page. ## Build instructions @@ -54,8 +54,10 @@ But it is strongly recommended to only use [GitHub Actions "Release to Maven Cen ## License -License is GNU General Lesser Public License (LGPL) version 3.0. Each source file includes the LGPL-3 header (build will fail otherwise). -To update source files with the proper header, simply execute the below command: +License is GNU General Lesser Public License (LGPL) version 3.0. The IPMI Library for Java by Verax Systems is published under the GNU GPL v3; this fork uses it under a commercial (non-GPL) license granted by Verax Systems to Sentry Software in 2021. The source files derived from it keep Verax Systems as copyright holder. + +Each source file includes the LGPL-3 header (build will fail otherwise). +To add the header to new source files, simply execute the below command (existing headers are never modified): ```bash mvn license:update-file-header diff --git a/pom.xml b/pom.xml index 8763365..141ec6d 100644 --- a/pom.xml +++ b/pom.xml @@ -148,13 +148,14 @@ - + org.apache.maven.plugins maven-pmd-plugin 3.28.0 ${maven.compiler.release} + 100 pmd.xml @@ -164,17 +165,23 @@ check + cpd-check - + org.codehaus.mojo license-maven-plugin lgpl_v3 + false + false diff --git a/src/main/java/org/metricshub/ipmi/client/model/Sensor.java b/src/main/java/org/metricshub/ipmi/client/model/Sensor.java index 6419fd8..8ee79c4 100644 --- a/src/main/java/org/metricshub/ipmi/client/model/Sensor.java +++ b/src/main/java/org/metricshub/ipmi/client/model/Sensor.java @@ -23,6 +23,7 @@ */ import org.metricshub.ipmi.core.coding.commands.sdr.GetSensorReadingResponseData; +import org.metricshub.ipmi.core.coding.commands.sdr.record.AbstractSensorRecord; import org.metricshub.ipmi.core.coding.commands.sdr.record.CompactSensorRecord; import org.metricshub.ipmi.core.coding.commands.sdr.record.EntityId; import org.metricshub.ipmi.core.coding.commands.sdr.record.FullSensorRecord; @@ -70,21 +71,12 @@ public boolean isFull() { } /** - * Cast the current record to a {@link CompactSensorRecord} + * Cast the current record to the {@link AbstractSensorRecord} shared by the Compact and Full sensor records * - * @return {@link CompactSensorRecord} instance + * @return {@link AbstractSensorRecord} instance, or null if the record is neither Compact nor Full */ - private CompactSensorRecord getCompactSensorRecord() { - return (CompactSensorRecord) sensorRecord; - } - - /** - * Cast the current record to a {@link FullSensorRecord} - * - * @return {@link FullSensorRecord} instance - */ - private FullSensorRecord getFullSensorRecord() { - return (FullSensorRecord) sensorRecord; + private AbstractSensorRecord getSensorRecord() { + return isCompact() || isFull() ? (AbstractSensorRecord) sensorRecord : null; } /** @@ -93,13 +85,8 @@ private FullSensorRecord getFullSensorRecord() { * @return {@link EntityId} instance */ public EntityId getEntityId() { - - if (isCompact()) { - return getCompactSensorRecord().getEntityId(); - } else if (isFull()) { - return getFullSensorRecord().getEntityId(); - } - return null; + final AbstractSensorRecord record = getSensorRecord(); + return record == null ? null : record.getEntityId(); } /** @@ -108,12 +95,8 @@ public EntityId getEntityId() { * @return {@link Byte} value or null if the type of the sensor cannot be detected. */ public Byte getDeviceId() { - if (isCompact()) { - return getCompactSensorRecord().getEntityInstanceNumber(); - } else if (isFull()) { - return getFullSensorRecord().getEntityInstanceNumber(); - } - return null; + final AbstractSensorRecord record = getSensorRecord(); + return record == null ? null : record.getEntityInstanceNumber(); } /** @@ -122,12 +105,8 @@ public Byte getDeviceId() { * @return {@link String} value */ public String getName() { - if (isCompact()) { - return getCompactSensorRecord().getName(); - } else if (isFull()) { - return getFullSensorRecord().getName(); - } - return null; + final AbstractSensorRecord record = getSensorRecord(); + return record == null ? null : record.getName(); } /** diff --git a/src/main/java/org/metricshub/ipmi/client/runner/GetSensorsRunner.java b/src/main/java/org/metricshub/ipmi/client/runner/GetSensorsRunner.java index 82d6dde..58144db 100644 --- a/src/main/java/org/metricshub/ipmi/client/runner/GetSensorsRunner.java +++ b/src/main/java/org/metricshub/ipmi/client/runner/GetSensorsRunner.java @@ -36,6 +36,7 @@ import org.metricshub.ipmi.core.coding.commands.sdr.GetSensorReadingResponseData; import org.metricshub.ipmi.core.coding.commands.sdr.ReserveSdrRepository; import org.metricshub.ipmi.core.coding.commands.sdr.ReserveSdrRepositoryResponseData; +import org.metricshub.ipmi.core.coding.commands.sdr.record.AbstractSensorRecord; import org.metricshub.ipmi.core.coding.commands.sdr.record.CompactSensorRecord; import org.metricshub.ipmi.core.coding.commands.sdr.record.FullSensorRecord; import org.metricshub.ipmi.core.coding.commands.sdr.record.ReadingType; @@ -84,8 +85,11 @@ public List call() throws Exception { // repository (see #getSensorData for details). sensorRecord = super.getSensorData(reservationId); + // Only Full and Compact sensor records have a reading associated + // with them (see IPMI specification for details) if (sensorRecord instanceof FullSensorRecord || sensorRecord instanceof CompactSensorRecord) { - int recordReadingId = getReadingId(sensorRecord); + int recordReadingId = TypeConverter + .byteToInt(((AbstractSensorRecord) sensorRecord).getSensorNumber()); // If our record has got a reading associated, we get request // for it @@ -138,38 +142,15 @@ static String buildStates(final GetSensorReadingResponseData data, final SensorR } try { - final String deviceName; - final List events; - if (sensorRecord instanceof CompactSensorRecord) { + final AbstractSensorRecord record = (AbstractSensorRecord) sensorRecord; + final String deviceName = record.getName(); - CompactSensorRecord compactSensorRecord = (CompactSensorRecord) sensorRecord; - - deviceName = compactSensorRecord.getName(); - - if (compactSensorRecord.getEventReadingType() == OEM_EVENT_READING_TYPE) { - return buildOemState(data.getRaw(), deviceName); - } - - events = data - .getStatesAsserted( - compactSensorRecord.getSensorType(), - compactSensorRecord.getEventReadingType()); - } else { - - FullSensorRecord fullSensorRecord = (FullSensorRecord) sensorRecord; - - deviceName = fullSensorRecord.getName(); - - if (fullSensorRecord.getEventReadingType() == OEM_EVENT_READING_TYPE) { - return buildOemState(data.getRaw(), deviceName); - } - - events = data - .getStatesAsserted( - fullSensorRecord.getSensorType(), - fullSensorRecord.getEventReadingType()); + if (record.getEventReadingType() == OEM_EVENT_READING_TYPE) { + return buildOemState(data.getRaw(), deviceName); } + final List events = data.getStatesAsserted(record.getSensorType(), record.getEventReadingType()); + return appendReadingTypes(events, deviceName); } catch (Exception e) { @@ -252,26 +233,4 @@ private GetSensorReadingResponseData getSensorRecordReading(final int recordRead } return null; } - - /** - * Get the reading id which is required by the BMC to answer reading commands. - * - * @param sensorRecord {@link SensorRecord} instance expected as Full or Compact. - * @return The sensor number of the record otherwise -1 if cannot determine the record type. - */ - private int getReadingId(final SensorRecord sensorRecord) { - // We check if the received record is either FullSensorRecord or - // CompactSensorRecord, since these types have readings - // associated with them (see IPMI specification for details). - if (sensorRecord instanceof FullSensorRecord) { - FullSensorRecord fsr = (FullSensorRecord) sensorRecord; - return TypeConverter.byteToInt(fsr.getSensorNumber()); - - } else if (sensorRecord instanceof CompactSensorRecord) { - CompactSensorRecord csr = (CompactSensorRecord) sensorRecord; - return TypeConverter.byteToInt(csr.getSensorNumber()); - } - - return -1; - } } diff --git a/src/main/java/org/metricshub/ipmi/core/coding/commands/IpmiCommandCoder.java b/src/main/java/org/metricshub/ipmi/core/coding/commands/IpmiCommandCoder.java index 859e69f..ac7f5ac 100644 --- a/src/main/java/org/metricshub/ipmi/core/coding/commands/IpmiCommandCoder.java +++ b/src/main/java/org/metricshub/ipmi/core/coding/commands/IpmiCommandCoder.java @@ -23,8 +23,10 @@ */ import org.metricshub.ipmi.core.coding.PayloadCoder; +import org.metricshub.ipmi.core.coding.payload.CompletionCode; import org.metricshub.ipmi.core.coding.payload.IpmiPayload; import org.metricshub.ipmi.core.coding.payload.PlainMessage; +import org.metricshub.ipmi.core.coding.payload.lan.IPMIException; import org.metricshub.ipmi.core.coding.payload.lan.IpmiLanResponse; import org.metricshub.ipmi.core.coding.payload.lan.NetworkFunction; import org.metricshub.ipmi.core.coding.protocol.AuthenticationType; @@ -75,6 +77,28 @@ public boolean isCommandResponse(IpmiMessage message) { } } + /** + * Checks that the message is a successful response to this command. + * + * @param message {@link IpmiMessage} wrapping the IPMI response + * @return the IPMI command data of the response + * @throws IllegalArgumentException when the message is not an IPMI LAN response to this command + * @throws IPMIException when the completion code of the response is not {@link CompletionCode#Ok} + */ + protected byte[] validateResponse(IpmiMessage message) throws IPMIException { + if (!isCommandResponse(message)) { + throw new IllegalArgumentException("This is not a response for " + getClass().getSimpleName() + " command"); + } + if (!(message.getPayload() instanceof IpmiLanResponse)) { + throw new IllegalArgumentException("Invalid response payload"); + } + IpmiLanResponse response = (IpmiLanResponse) message.getPayload(); + if (response.getCompletionCode() != CompletionCode.Ok) { + throw new IPMIException(response.getCompletionCode()); + } + return response.getIpmiCommandData(); + } + /** * Retrieves command code specific for command represented by this class * diff --git a/src/main/java/org/metricshub/ipmi/core/coding/commands/chassis/ChassisControl.java b/src/main/java/org/metricshub/ipmi/core/coding/commands/chassis/ChassisControl.java index 8a8e691..f9a86d9 100644 --- a/src/main/java/org/metricshub/ipmi/core/coding/commands/chassis/ChassisControl.java +++ b/src/main/java/org/metricshub/ipmi/core/coding/commands/chassis/ChassisControl.java @@ -29,11 +29,9 @@ import org.metricshub.ipmi.core.coding.commands.IpmiCommandCoder; import org.metricshub.ipmi.core.coding.commands.IpmiVersion; import org.metricshub.ipmi.core.coding.commands.ResponseData; -import org.metricshub.ipmi.core.coding.payload.CompletionCode; import org.metricshub.ipmi.core.coding.payload.IpmiPayload; import org.metricshub.ipmi.core.coding.payload.lan.IPMIException; import org.metricshub.ipmi.core.coding.payload.lan.IpmiLanRequest; -import org.metricshub.ipmi.core.coding.payload.lan.IpmiLanResponse; import org.metricshub.ipmi.core.coding.payload.lan.NetworkFunction; import org.metricshub.ipmi.core.coding.protocol.AuthenticationType; import org.metricshub.ipmi.core.coding.protocol.IpmiMessage; @@ -96,18 +94,8 @@ public ResponseData getResponseData(IpmiMessage message) throws IPMIException, NoSuchAlgorithmException, InvalidKeyException { - if (!isCommandResponse(message)) { - throw new IllegalArgumentException( - "This is not a response for Get Chassis Status command"); - } - if (!(message.getPayload() instanceof IpmiLanResponse)) { - throw new IllegalArgumentException("Invalid response payload"); - } - if (((IpmiLanResponse) message.getPayload()).getCompletionCode() != CompletionCode.Ok) { - throw new IPMIException( - ((IpmiLanResponse) message.getPayload()) - .getCompletionCode()); - } + validateResponse(message); + return new ChassisControlResponseData(); } diff --git a/src/main/java/org/metricshub/ipmi/core/coding/commands/chassis/GetChassisStatus.java b/src/main/java/org/metricshub/ipmi/core/coding/commands/chassis/GetChassisStatus.java index 5faa29f..9a708ba 100644 --- a/src/main/java/org/metricshub/ipmi/core/coding/commands/chassis/GetChassisStatus.java +++ b/src/main/java/org/metricshub/ipmi/core/coding/commands/chassis/GetChassisStatus.java @@ -29,11 +29,9 @@ import org.metricshub.ipmi.core.coding.commands.IpmiCommandCoder; import org.metricshub.ipmi.core.coding.commands.IpmiVersion; import org.metricshub.ipmi.core.coding.commands.ResponseData; -import org.metricshub.ipmi.core.coding.payload.CompletionCode; import org.metricshub.ipmi.core.coding.payload.lan.IPMIException; import org.metricshub.ipmi.core.coding.payload.lan.IpmiLanMessage; import org.metricshub.ipmi.core.coding.payload.lan.IpmiLanRequest; -import org.metricshub.ipmi.core.coding.payload.lan.IpmiLanResponse; import org.metricshub.ipmi.core.coding.payload.lan.NetworkFunction; import org.metricshub.ipmi.core.coding.protocol.AuthenticationType; import org.metricshub.ipmi.core.coding.protocol.IpmiMessage; @@ -91,20 +89,7 @@ public ResponseData getResponseData(IpmiMessage message) throws IPMIException, NoSuchAlgorithmException, InvalidKeyException { - if (!isCommandResponse(message)) { - throw new IllegalArgumentException( - "This is not a response for Get Chassis Status command"); - } - if (!(message.getPayload() instanceof IpmiLanResponse)) { - throw new IllegalArgumentException("Invalid response payload"); - } - if (((IpmiLanResponse) message.getPayload()).getCompletionCode() != CompletionCode.Ok) { - throw new IPMIException( - ((IpmiLanResponse) message.getPayload()) - .getCompletionCode()); - } - - byte[] raw = message.getPayload().getIpmiCommandData(); + byte[] raw = validateResponse(message); if (raw == null || (raw.length != 3 && raw.length != 4)) { throw new IllegalArgumentException( diff --git a/src/main/java/org/metricshub/ipmi/core/coding/commands/fru/GetFruInventoryAreaInfo.java b/src/main/java/org/metricshub/ipmi/core/coding/commands/fru/GetFruInventoryAreaInfo.java index eee1061..1deeac2 100644 --- a/src/main/java/org/metricshub/ipmi/core/coding/commands/fru/GetFruInventoryAreaInfo.java +++ b/src/main/java/org/metricshub/ipmi/core/coding/commands/fru/GetFruInventoryAreaInfo.java @@ -29,11 +29,9 @@ import org.metricshub.ipmi.core.coding.commands.IpmiCommandCoder; import org.metricshub.ipmi.core.coding.commands.IpmiVersion; import org.metricshub.ipmi.core.coding.commands.ResponseData; -import org.metricshub.ipmi.core.coding.payload.CompletionCode; import org.metricshub.ipmi.core.coding.payload.IpmiPayload; import org.metricshub.ipmi.core.coding.payload.lan.IPMIException; import org.metricshub.ipmi.core.coding.payload.lan.IpmiLanRequest; -import org.metricshub.ipmi.core.coding.payload.lan.IpmiLanResponse; import org.metricshub.ipmi.core.coding.payload.lan.NetworkFunction; import org.metricshub.ipmi.core.coding.protocol.AuthenticationType; import org.metricshub.ipmi.core.coding.protocol.IpmiMessage; @@ -100,20 +98,7 @@ public ResponseData getResponseData(IpmiMessage message) NoSuchAlgorithmException, InvalidKeyException { - if (!isCommandResponse(message)) { - throw new IllegalArgumentException( - "This is not a response for Get FRU Inventory Info command"); - } - if (!(message.getPayload() instanceof IpmiLanResponse)) { - throw new IllegalArgumentException("Invalid response payload"); - } - if (((IpmiLanResponse) message.getPayload()).getCompletionCode() != CompletionCode.Ok) { - throw new IPMIException( - ((IpmiLanResponse) message.getPayload()) - .getCompletionCode()); - } - - byte[] raw = message.getPayload().getIpmiCommandData(); + byte[] raw = validateResponse(message); if (raw == null || raw.length != 3) { throw new IllegalArgumentException( diff --git a/src/main/java/org/metricshub/ipmi/core/coding/commands/fru/ReadFruData.java b/src/main/java/org/metricshub/ipmi/core/coding/commands/fru/ReadFruData.java index 7601e18..94a6fd2 100644 --- a/src/main/java/org/metricshub/ipmi/core/coding/commands/fru/ReadFruData.java +++ b/src/main/java/org/metricshub/ipmi/core/coding/commands/fru/ReadFruData.java @@ -33,11 +33,9 @@ import org.metricshub.ipmi.core.coding.commands.fru.record.ProductInfo; import org.metricshub.ipmi.core.coding.commands.sdr.GetSdr; import org.metricshub.ipmi.core.coding.commands.sdr.record.FruDeviceLocatorRecord; -import org.metricshub.ipmi.core.coding.payload.CompletionCode; import org.metricshub.ipmi.core.coding.payload.IpmiPayload; import org.metricshub.ipmi.core.coding.payload.lan.IPMIException; import org.metricshub.ipmi.core.coding.payload.lan.IpmiLanRequest; -import org.metricshub.ipmi.core.coding.payload.lan.IpmiLanResponse; import org.metricshub.ipmi.core.coding.payload.lan.NetworkFunction; import org.metricshub.ipmi.core.coding.protocol.AuthenticationType; import org.metricshub.ipmi.core.coding.protocol.IpmiMessage; @@ -179,20 +177,7 @@ public ResponseData getResponseData(IpmiMessage message) NoSuchAlgorithmException, InvalidKeyException { - if (!isCommandResponse(message)) { - throw new IllegalArgumentException( - "This is not a response for Get SDR Repository Info command"); - } - if (!(message.getPayload() instanceof IpmiLanResponse)) { - throw new IllegalArgumentException("Invalid response payload"); - } - if (((IpmiLanResponse) message.getPayload()).getCompletionCode() != CompletionCode.Ok) { - throw new IPMIException( - ((IpmiLanResponse) message.getPayload()) - .getCompletionCode()); - } - - byte[] raw = message.getPayload().getIpmiCommandData(); + byte[] raw = validateResponse(message); if (raw == null || raw.length < 2) { throw new IllegalArgumentException( diff --git a/src/main/java/org/metricshub/ipmi/core/coding/commands/payload/ActivatePayload.java b/src/main/java/org/metricshub/ipmi/core/coding/commands/payload/ActivatePayload.java index a26eeb1..dc78743 100644 --- a/src/main/java/org/metricshub/ipmi/core/coding/commands/payload/ActivatePayload.java +++ b/src/main/java/org/metricshub/ipmi/core/coding/commands/payload/ActivatePayload.java @@ -26,11 +26,9 @@ import org.metricshub.ipmi.core.coding.commands.IpmiCommandCoder; import org.metricshub.ipmi.core.coding.commands.IpmiVersion; import org.metricshub.ipmi.core.coding.commands.ResponseData; -import org.metricshub.ipmi.core.coding.payload.CompletionCode; import org.metricshub.ipmi.core.coding.payload.IpmiPayload; import org.metricshub.ipmi.core.coding.payload.lan.IPMIException; import org.metricshub.ipmi.core.coding.payload.lan.IpmiLanRequest; -import org.metricshub.ipmi.core.coding.payload.lan.IpmiLanResponse; import org.metricshub.ipmi.core.coding.payload.lan.NetworkFunction; import org.metricshub.ipmi.core.coding.protocol.AuthenticationType; import org.metricshub.ipmi.core.coding.protocol.IpmiMessage; @@ -130,19 +128,7 @@ public ResponseData getResponseData(IpmiMessage message) throws IPMIException, NoSuchAlgorithmException, InvalidKeyException { - if (!isCommandResponse(message)) { - throw new IllegalArgumentException("This is not a response for Activate Payload command"); - } - - if (!(message.getPayload() instanceof IpmiLanResponse)) { - throw new IllegalArgumentException("Invalid response payload"); - } - - if (((IpmiLanResponse) message.getPayload()).getCompletionCode() != CompletionCode.Ok) { - throw new IPMIException(((IpmiLanResponse) message.getPayload()).getCompletionCode()); - } - - MessageReader messageReader = new MessageReader(message.getPayload().getData()); + MessageReader messageReader = new MessageReader(validateResponse(message)); ActivatePayloadResponseData responseData = createEmptyResponse(); diff --git a/src/main/java/org/metricshub/ipmi/core/coding/commands/payload/GetChannelPayloadSupport.java b/src/main/java/org/metricshub/ipmi/core/coding/commands/payload/GetChannelPayloadSupport.java index 434dbe9..59a3cd5 100644 --- a/src/main/java/org/metricshub/ipmi/core/coding/commands/payload/GetChannelPayloadSupport.java +++ b/src/main/java/org/metricshub/ipmi/core/coding/commands/payload/GetChannelPayloadSupport.java @@ -25,11 +25,9 @@ import org.metricshub.ipmi.core.coding.commands.IpmiCommandCoder; import org.metricshub.ipmi.core.coding.commands.IpmiVersion; import org.metricshub.ipmi.core.coding.commands.ResponseData; -import org.metricshub.ipmi.core.coding.payload.CompletionCode; import org.metricshub.ipmi.core.coding.payload.IpmiPayload; import org.metricshub.ipmi.core.coding.payload.lan.IPMIException; import org.metricshub.ipmi.core.coding.payload.lan.IpmiLanRequest; -import org.metricshub.ipmi.core.coding.payload.lan.IpmiLanResponse; import org.metricshub.ipmi.core.coding.payload.lan.NetworkFunction; import org.metricshub.ipmi.core.coding.protocol.AuthenticationType; import org.metricshub.ipmi.core.coding.protocol.IpmiMessage; @@ -137,22 +135,10 @@ public ResponseData getResponseData(IpmiMessage message) throws IPMIException, NoSuchAlgorithmException, InvalidKeyException { - if (!isCommandResponse(message)) { - throw new IllegalArgumentException("This is not a response for Get Payload Info command"); - } - - if (!(message.getPayload() instanceof IpmiLanResponse)) { - throw new IllegalArgumentException("Invalid response payload"); - } - - if (((IpmiLanResponse) message.getPayload()).getCompletionCode() != CompletionCode.Ok) { - throw new IPMIException(((IpmiLanResponse) message.getPayload()).getCompletionCode()); - } + byte[] responseData = validateResponse(message); GetChannelPayloadSupportResponseData data = new GetChannelPayloadSupportResponseData(); - byte[] responseData = message.getPayload().getData(); - data.setStandardPayloads(responseData[0]); data.setSessionSetupPayloads(responseData[1]); data.setOemPayloads(responseData[2]); diff --git a/src/main/java/org/metricshub/ipmi/core/coding/commands/payload/GetPayloadActivationStatus.java b/src/main/java/org/metricshub/ipmi/core/coding/commands/payload/GetPayloadActivationStatus.java index 3319eed..c27ee4a 100644 --- a/src/main/java/org/metricshub/ipmi/core/coding/commands/payload/GetPayloadActivationStatus.java +++ b/src/main/java/org/metricshub/ipmi/core/coding/commands/payload/GetPayloadActivationStatus.java @@ -26,11 +26,9 @@ import org.metricshub.ipmi.core.coding.commands.IpmiCommandCoder; import org.metricshub.ipmi.core.coding.commands.IpmiVersion; import org.metricshub.ipmi.core.coding.commands.ResponseData; -import org.metricshub.ipmi.core.coding.payload.CompletionCode; import org.metricshub.ipmi.core.coding.payload.IpmiPayload; import org.metricshub.ipmi.core.coding.payload.lan.IPMIException; import org.metricshub.ipmi.core.coding.payload.lan.IpmiLanRequest; -import org.metricshub.ipmi.core.coding.payload.lan.IpmiLanResponse; import org.metricshub.ipmi.core.coding.payload.lan.NetworkFunction; import org.metricshub.ipmi.core.coding.protocol.AuthenticationType; import org.metricshub.ipmi.core.coding.protocol.IpmiMessage; @@ -88,19 +86,8 @@ public ResponseData getResponseData(IpmiMessage message) throws IPMIException, NoSuchAlgorithmException, InvalidKeyException { - if (!isCommandResponse(message)) { - throw new IllegalArgumentException("This is not a response for Get Payload Activation Status"); - } - - if (!(message.getPayload() instanceof IpmiLanResponse)) { - throw new IllegalArgumentException("Invalid response payload"); - } - - if (((IpmiLanResponse) message.getPayload()).getCompletionCode() != CompletionCode.Ok) { - throw new IPMIException(((IpmiLanResponse) message.getPayload()).getCompletionCode()); - } - MessageReader messageReader = new MessageReader(message.getPayload().getData()); + MessageReader messageReader = new MessageReader(validateResponse(message)); GetPayloadActivationStatusResponseData responseData = new GetPayloadActivationStatusResponseData(); diff --git a/src/main/java/org/metricshub/ipmi/core/coding/commands/sdr/GetSdr.java b/src/main/java/org/metricshub/ipmi/core/coding/commands/sdr/GetSdr.java index 512c761..8c02955 100644 --- a/src/main/java/org/metricshub/ipmi/core/coding/commands/sdr/GetSdr.java +++ b/src/main/java/org/metricshub/ipmi/core/coding/commands/sdr/GetSdr.java @@ -29,11 +29,9 @@ import org.metricshub.ipmi.core.coding.commands.IpmiCommandCoder; import org.metricshub.ipmi.core.coding.commands.IpmiVersion; import org.metricshub.ipmi.core.coding.commands.ResponseData; -import org.metricshub.ipmi.core.coding.payload.CompletionCode; import org.metricshub.ipmi.core.coding.payload.IpmiPayload; import org.metricshub.ipmi.core.coding.payload.lan.IPMIException; import org.metricshub.ipmi.core.coding.payload.lan.IpmiLanRequest; -import org.metricshub.ipmi.core.coding.payload.lan.IpmiLanResponse; import org.metricshub.ipmi.core.coding.payload.lan.NetworkFunction; import org.metricshub.ipmi.core.coding.protocol.AuthenticationType; import org.metricshub.ipmi.core.coding.protocol.IpmiMessage; @@ -154,20 +152,7 @@ public ResponseData getResponseData(IpmiMessage message) throws IPMIException, NoSuchAlgorithmException, InvalidKeyException { - if (!isCommandResponse(message)) { - throw new IllegalArgumentException( - "This is not a response for Get SDR command"); - } - if (!(message.getPayload() instanceof IpmiLanResponse)) { - throw new IllegalArgumentException("Invalid response payload"); - } - if (((IpmiLanResponse) message.getPayload()).getCompletionCode() != CompletionCode.Ok) { - throw new IPMIException( - ((IpmiLanResponse) message.getPayload()) - .getCompletionCode()); - } - - byte[] raw = message.getPayload().getIpmiCommandData(); + byte[] raw = validateResponse(message); if (raw == null || raw.length < 3) { throw new IllegalArgumentException( diff --git a/src/main/java/org/metricshub/ipmi/core/coding/commands/sdr/GetSdrRepositoryInfo.java b/src/main/java/org/metricshub/ipmi/core/coding/commands/sdr/GetSdrRepositoryInfo.java index d25bd09..dc30902 100644 --- a/src/main/java/org/metricshub/ipmi/core/coding/commands/sdr/GetSdrRepositoryInfo.java +++ b/src/main/java/org/metricshub/ipmi/core/coding/commands/sdr/GetSdrRepositoryInfo.java @@ -26,11 +26,9 @@ import org.metricshub.ipmi.core.coding.commands.IpmiCommandCoder; import org.metricshub.ipmi.core.coding.commands.IpmiVersion; import org.metricshub.ipmi.core.coding.commands.ResponseData; -import org.metricshub.ipmi.core.coding.payload.CompletionCode; import org.metricshub.ipmi.core.coding.payload.IpmiPayload; import org.metricshub.ipmi.core.coding.payload.lan.IPMIException; import org.metricshub.ipmi.core.coding.payload.lan.IpmiLanRequest; -import org.metricshub.ipmi.core.coding.payload.lan.IpmiLanResponse; import org.metricshub.ipmi.core.coding.payload.lan.NetworkFunction; import org.metricshub.ipmi.core.coding.protocol.AuthenticationType; import org.metricshub.ipmi.core.coding.protocol.IpmiMessage; @@ -89,20 +87,7 @@ public ResponseData getResponseData(IpmiMessage message) throws IPMIException, NoSuchAlgorithmException, InvalidKeyException { - if (!isCommandResponse(message)) { - throw new IllegalArgumentException( - "This is not a response for Get SDR Repository Info command"); - } - if (!(message.getPayload() instanceof IpmiLanResponse)) { - throw new IllegalArgumentException("Invalid response payload"); - } - if (((IpmiLanResponse) message.getPayload()).getCompletionCode() != CompletionCode.Ok) { - throw new IPMIException( - ((IpmiLanResponse) message.getPayload()) - .getCompletionCode()); - } - - byte[] raw = message.getPayload().getIpmiCommandData(); + byte[] raw = validateResponse(message); if (raw == null || raw.length != 14) { throw new IllegalArgumentException( diff --git a/src/main/java/org/metricshub/ipmi/core/coding/commands/sdr/GetSensorReading.java b/src/main/java/org/metricshub/ipmi/core/coding/commands/sdr/GetSensorReading.java index 991ff22..247218e 100644 --- a/src/main/java/org/metricshub/ipmi/core/coding/commands/sdr/GetSensorReading.java +++ b/src/main/java/org/metricshub/ipmi/core/coding/commands/sdr/GetSensorReading.java @@ -28,11 +28,9 @@ import org.metricshub.ipmi.core.coding.commands.IpmiCommandCoder; import org.metricshub.ipmi.core.coding.commands.IpmiVersion; import org.metricshub.ipmi.core.coding.commands.ResponseData; -import org.metricshub.ipmi.core.coding.payload.CompletionCode; import org.metricshub.ipmi.core.coding.payload.IpmiPayload; import org.metricshub.ipmi.core.coding.payload.lan.IPMIException; import org.metricshub.ipmi.core.coding.payload.lan.IpmiLanRequest; -import org.metricshub.ipmi.core.coding.payload.lan.IpmiLanResponse; import org.metricshub.ipmi.core.coding.payload.lan.NetworkFunction; import org.metricshub.ipmi.core.coding.protocol.AuthenticationType; import org.metricshub.ipmi.core.coding.protocol.IpmiMessage; @@ -93,20 +91,7 @@ public ResponseData getResponseData(IpmiMessage message) NoSuchAlgorithmException, InvalidKeyException { - if (!isCommandResponse(message)) { - throw new IllegalArgumentException( - "This is not a response for Get Sensor Reading command"); - } - if (!(message.getPayload() instanceof IpmiLanResponse)) { - throw new IllegalArgumentException("Invalid response payload"); - } - if (((IpmiLanResponse) message.getPayload()).getCompletionCode() != CompletionCode.Ok) { - throw new IPMIException( - ((IpmiLanResponse) message.getPayload()) - .getCompletionCode()); - } - - byte[] raw = message.getPayload().getIpmiCommandData(); + byte[] raw = validateResponse(message); if (raw.length < 2) { throw new IllegalStateException("Invalid data length"); diff --git a/src/main/java/org/metricshub/ipmi/core/coding/commands/sdr/ReserveSdrRepository.java b/src/main/java/org/metricshub/ipmi/core/coding/commands/sdr/ReserveSdrRepository.java index 6dd2ae6..b028dd5 100644 --- a/src/main/java/org/metricshub/ipmi/core/coding/commands/sdr/ReserveSdrRepository.java +++ b/src/main/java/org/metricshub/ipmi/core/coding/commands/sdr/ReserveSdrRepository.java @@ -26,11 +26,9 @@ import org.metricshub.ipmi.core.coding.commands.IpmiCommandCoder; import org.metricshub.ipmi.core.coding.commands.IpmiVersion; import org.metricshub.ipmi.core.coding.commands.ResponseData; -import org.metricshub.ipmi.core.coding.payload.CompletionCode; import org.metricshub.ipmi.core.coding.payload.IpmiPayload; import org.metricshub.ipmi.core.coding.payload.lan.IPMIException; import org.metricshub.ipmi.core.coding.payload.lan.IpmiLanRequest; -import org.metricshub.ipmi.core.coding.payload.lan.IpmiLanResponse; import org.metricshub.ipmi.core.coding.payload.lan.NetworkFunction; import org.metricshub.ipmi.core.coding.protocol.AuthenticationType; import org.metricshub.ipmi.core.coding.protocol.IpmiMessage; @@ -87,20 +85,7 @@ public ResponseData getResponseData(IpmiMessage message) throws IPMIException, NoSuchAlgorithmException, InvalidKeyException { - if (!isCommandResponse(message)) { - throw new IllegalArgumentException( - "This is not a response for Reserve SDR Repository command"); - } - if (!(message.getPayload() instanceof IpmiLanResponse)) { - throw new IllegalArgumentException("Invalid response payload"); - } - if (((IpmiLanResponse) message.getPayload()).getCompletionCode() != CompletionCode.Ok) { - throw new IPMIException( - ((IpmiLanResponse) message.getPayload()) - .getCompletionCode()); - } - - byte[] raw = message.getPayload().getIpmiCommandData(); + byte[] raw = validateResponse(message); if (raw == null || raw.length != 2) { throw new IllegalArgumentException( diff --git a/src/main/java/org/metricshub/ipmi/core/coding/commands/sdr/record/AbstractSensorRecord.java b/src/main/java/org/metricshub/ipmi/core/coding/commands/sdr/record/AbstractSensorRecord.java new file mode 100644 index 0000000..50463f6 --- /dev/null +++ b/src/main/java/org/metricshub/ipmi/core/coding/commands/sdr/record/AbstractSensorRecord.java @@ -0,0 +1,502 @@ +package org.metricshub.ipmi.core.coding.commands.sdr.record; + +/*- + * ╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲ + * IPMI Java Client + * ჻჻჻჻჻჻ + * Copyright 2023 Verax Systems, MetricsHub + * ჻჻჻჻჻჻ + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Lesser Public License for more details. + * + * You should have received a copy of the GNU General Lesser Public + * License along with this program. If not, see + * . + * ╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱ + */ + +import org.metricshub.ipmi.core.common.TypeConverter; + +import java.util.Arrays; + +/** + * Fields shared by the Full, Compact and Event-Only sensor records (IPMI 2.0 tables 43-1, 43-2 and 43-3), and the + * methods decoding them. + *

+ * Every sensor record has the sensor owner, sensor number, entity, sensor type, event/reading type, direction and ID + * string. The capabilities and units are only defined for Full and Compact records: an Event-Only record has no + * reading, so its units are null and its hysteresis and thresholds are not readable. The record sharing + * fields are only defined for Compact and Event-Only records: a Full record describes a single sensor, so its share + * count is 0 and it has no ID string instance modifier. + */ +public abstract class AbstractSensorRecord extends SensorRecord { + + private byte sensorOwnerId; + + private AddressType addressType; + + private byte channelNumber; + + private byte sensorOwnerLun; + + private byte sensorNumber; + + private EntityId entityId; + + private boolean entityPhysical; + + private byte entityInstanceNumber; + + private boolean hysteresisReadable; + + private boolean thresholdsReadable; + + private SensorType sensorType; + + private int eventReadingType; + + private RateUnit rateUnit; + + private ModifierUnitUsage modifierUnitUsage; + + private SensorUnit sensorBaseUnit; + + private SensorUnit sensorModifierUnit; + + private SensorDirection sensorDirection; + + private String name; + + private InstanceModifierType idInstanceModifierType; + + private int shareCount; + + private boolean entityInstanceIncrements; + + private int idInstanceModifierOffset; + + /** + * Decodes the sensor owner, sensor number and entity (record bytes 5 to 9, at the same position in every sensor + * record), then the sensor type and the event/reading type. + * + * @param recordData + * - raw data containing the whole record + * @param sensorTypeIndex + * - index of the sensor type byte, followed by the event/reading type byte + */ + protected void populateSensorHeader(byte[] recordData, int sensorTypeIndex) { + setSensorOwnerId(TypeConverter.intToByte((TypeConverter.byteToInt(recordData[5]) & 0xfe) >> 1)); + setAddressType(AddressType.parseInt(TypeConverter.byteToInt(recordData[5]) & 0x01)); + setChannelNumber(TypeConverter.intToByte((TypeConverter.byteToInt(recordData[6]) & 0xf0) >> 4)); + setSensorOwnerLun(TypeConverter.intToByte(TypeConverter.byteToInt(recordData[6]) & 0x3)); + setSensorNumber(recordData[7]); + setEntityId(EntityId.parseInt(TypeConverter.byteToInt(recordData[8]))); + setEntityPhysical((TypeConverter.byteToInt(recordData[9]) & 0x80) == 0); + setEntityInstanceNumber(TypeConverter.intToByte(TypeConverter.byteToInt(recordData[9]) & 0x7f)); + setSensorType(SensorType.parseInt(TypeConverter.byteToInt(recordData[sensorTypeIndex]))); + setEventReadingType(TypeConverter.byteToInt(recordData[sensorTypeIndex + 1])); + } + + /** + * Decodes the sensor capabilities (record byte 11) and the sensor units (record bytes 20 to 22) of Full and + * Compact records. + * + * @param recordData + * - raw data containing the whole record + */ + protected void populateCapabilitiesAndUnits(byte[] recordData) { + setHysteresisReadable(isFieldReadable((TypeConverter.byteToInt(recordData[11]) & 0x30) >> 4)); + setThresholdsReadable(isFieldReadable((TypeConverter.byteToInt(recordData[11]) & 0xc) >> 2)); + setRateUnit(RateUnit.parseInt((TypeConverter.byteToInt(recordData[20]) & 0x38) >> 3)); + setModifierUnitUsage(ModifierUnitUsage.parseInt((TypeConverter.byteToInt(recordData[20]) & 0x6) >> 1)); + setSensorBaseUnit(SensorUnit.parseInt(TypeConverter.byteToInt(recordData[21]))); + setSensorModifierUnit(SensorUnit.parseInt(TypeConverter.byteToInt(recordData[22]))); + } + + /** + * @param field + * - 2-bit hysteresis or threshold access support field of the sensor capabilities + * @return whether the field is readable (readable, or readable and settable) + */ + private static boolean isFieldReadable(int field) { + return field == 1 || field == 2; + } + + /** + * Decodes the sensor direction and the record sharing fields of Compact and Event-Only records. + * + * @param recordData + * - raw data containing the whole record + * @param index + * - index of the byte holding the sensor direction, ID string instance modifier type and share count, + * followed by the byte holding the entity instance sharing and ID string instance modifier offset + */ + protected void populateSharing(byte[] recordData, int index) { + setSensorDirection(SensorDirection.parseInt((TypeConverter.byteToInt(recordData[index]) & 0xc0) >> 6)); + setIdInstanceModifierType( + InstanceModifierType.parseInt((TypeConverter.byteToInt(recordData[index]) & 0x30) >> 4)); + setShareCount(TypeConverter.byteToInt(recordData[index]) & 0xf); + setEntityInstanceIncrements((TypeConverter.byteToInt(recordData[index + 1]) & 0x80) != 0); + setIdInstanceModifierOffset(TypeConverter.byteToInt(recordData[index + 1]) & 0x7f); + } + + /** + * Decodes the sensor ID string, which runs to the end of the record. + * + * @param recordData + * - raw data containing the whole record + * @param typeIndex + * - index of the ID string type/length byte, followed by the ID string bytes + */ + protected void populateName(byte[] recordData, int typeIndex) { + setName(decodeName(recordData[typeIndex], Arrays.copyOfRange(recordData, typeIndex + 1, recordData.length))); + } + + /** + * @return the 7-bit I2C slave address or system software ID of the sensor owner + */ + public byte getSensorOwnerId() { + return sensorOwnerId; + } + + /** + * @param sensorOwnerId + * - the 7-bit I2C slave address or system software ID of the sensor owner + */ + public void setSensorOwnerId(byte sensorOwnerId) { + this.sensorOwnerId = sensorOwnerId; + } + + /** + * @return whether {@link #getSensorOwnerId()} is an IPMB slave address or a system software ID + */ + public AddressType getAddressType() { + return addressType; + } + + /** + * @param addressType + * - whether the sensor owner ID is an IPMB slave address or a system software ID + */ + public void setAddressType(AddressType addressType) { + this.addressType = addressType; + } + + /** + * @return the channel number of the sensor owner + */ + public byte getChannelNumber() { + return channelNumber; + } + + /** + * @param channelNumber + * - the channel number of the sensor owner + */ + public void setChannelNumber(byte channelNumber) { + this.channelNumber = channelNumber; + } + + /** + * @return the LUN of the sensor owner + */ + public byte getSensorOwnerLun() { + return sensorOwnerLun; + } + + /** + * @param sensorOwnerLun + * - the LUN of the sensor owner + */ + public void setSensorOwnerLun(byte sensorOwnerLun) { + this.sensorOwnerLun = sensorOwnerLun; + } + + /** + * @return the sensor number, unique for its owner and LUN + */ + public byte getSensorNumber() { + return sensorNumber; + } + + /** + * @param sensorNumber + * - the sensor number, unique for its owner and LUN + */ + public void setSensorNumber(byte sensorNumber) { + this.sensorNumber = sensorNumber; + } + + /** + * @return the physical entity the sensor is monitoring + */ + public EntityId getEntityId() { + return entityId; + } + + /** + * @param entityId + * - the physical entity the sensor is monitoring + */ + public void setEntityId(EntityId entityId) { + this.entityId = entityId; + } + + /** + * @return true if the entity is physical, false if it is logical + */ + public boolean isEntityPhysical() { + return entityPhysical; + } + + /** + * @param entityPhysical + * - true if the entity is physical, false if it is logical + */ + public void setEntityPhysical(boolean entityPhysical) { + this.entityPhysical = entityPhysical; + } + + /** + * @return the instance number of the entity + */ + public byte getEntityInstanceNumber() { + return entityInstanceNumber; + } + + /** + * @param entityInstanceNumber + * - the instance number of the entity + */ + public void setEntityInstanceNumber(byte entityInstanceNumber) { + this.entityInstanceNumber = entityInstanceNumber; + } + + /** + * @return whether the hysteresis of the sensor can be read (always false for Event-Only records) + */ + public boolean isHysteresisReadable() { + return hysteresisReadable; + } + + /** + * @param hysteresisReadable + * - whether the hysteresis of the sensor can be read + */ + public void setHysteresisReadable(boolean hysteresisReadable) { + this.hysteresisReadable = hysteresisReadable; + } + + /** + * @return whether the thresholds of the sensor can be read (always false for Event-Only records) + */ + public boolean isThresholdsReadable() { + return thresholdsReadable; + } + + /** + * @param thresholdsReadable + * - whether the thresholds of the sensor can be read + */ + public void setThresholdsReadable(boolean thresholdsReadable) { + this.thresholdsReadable = thresholdsReadable; + } + + /** + * @return the sensor type + */ + public SensorType getSensorType() { + return sensorType; + } + + /** + * @param sensorType + * - the sensor type + */ + public void setSensorType(SensorType sensorType) { + this.sensorType = sensorType; + } + + /** + * @return the event/reading type code (IPMI 2.0 table 42-1) + */ + public int getEventReadingType() { + return eventReadingType; + } + + /** + * @param eventReadingType + * - the event/reading type code (IPMI 2.0 table 42-1) + */ + public void setEventReadingType(int eventReadingType) { + this.eventReadingType = eventReadingType; + } + + /** + * @return the rate unit of the reading (null for Event-Only records) + */ + public RateUnit getRateUnit() { + return rateUnit; + } + + /** + * @param rateUnit + * - the rate unit of the reading + */ + public void setRateUnit(RateUnit rateUnit) { + this.rateUnit = rateUnit; + } + + /** + * @return how the modifier unit combines with the base unit (null for Event-Only records) + */ + public ModifierUnitUsage getModifierUnitUsage() { + return modifierUnitUsage; + } + + /** + * @param modifierUnitUsage + * - how the modifier unit combines with the base unit + */ + public void setModifierUnitUsage(ModifierUnitUsage modifierUnitUsage) { + this.modifierUnitUsage = modifierUnitUsage; + } + + /** + * @return the base unit of the reading (null for Event-Only records) + */ + public SensorUnit getSensorBaseUnit() { + return sensorBaseUnit; + } + + /** + * @param sensorBaseUnit + * - the base unit of the reading + */ + public void setSensorBaseUnit(SensorUnit sensorBaseUnit) { + this.sensorBaseUnit = sensorBaseUnit; + } + + /** + * @return the modifier unit of the reading (null for Event-Only records) + */ + public SensorUnit getSensorModifierUnit() { + return sensorModifierUnit; + } + + /** + * @param sensorModifierUnit + * - the modifier unit of the reading + */ + public void setSensorModifierUnit(SensorUnit sensorModifierUnit) { + this.sensorModifierUnit = sensorModifierUnit; + } + + /** + * @return whether the sensor monitors an input or an output of the entity + */ + public SensorDirection getSensorDirection() { + return sensorDirection; + } + + /** + * @param sensorDirection + * - whether the sensor monitors an input or an output of the entity + */ + public void setSensorDirection(SensorDirection sensorDirection) { + this.sensorDirection = sensorDirection; + } + + /** + * @return the sensor ID string + */ + public String getName() { + return name; + } + + /** + * @param name + * - the sensor ID string + */ + public void setName(String name) { + this.name = name; + } + + /** + * The instance modifier is a character(s) that software can append to the end of the ID String. This field + * selects whether the appended character(s) will be numeric or alpha. + * + * @return the type of the ID string instance modifier (null for Full records) + */ + public InstanceModifierType getIdInstanceModifierType() { + return idInstanceModifierType; + } + + /** + * @param idInstanceModifierType + * - the type of the ID string instance modifier + */ + public void setIdInstanceModifierType(InstanceModifierType idInstanceModifierType) { + this.idInstanceModifierType = idInstanceModifierType; + } + + /** + * Sensor numbers sharing this record are sequential starting with the sensor number specified by the Sensor + * Number field for this record. + * + * @return the number of sensors sharing this record (0 for Full records) + */ + public int getShareCount() { + return shareCount; + } + + /** + * @param shareCount + * - the number of sensors sharing this record + */ + public void setShareCount(int shareCount) { + this.shareCount = shareCount; + } + + /** + * @return whether the entity instance number increments for each sensor sharing this record + */ + public boolean isEntityInstanceIncrements() { + return entityInstanceIncrements; + } + + /** + * @param entityInstanceIncrements + * - whether the entity instance number increments for each sensor sharing this record + */ + public void setEntityInstanceIncrements(boolean entityInstanceIncrements) { + this.entityInstanceIncrements = entityInstanceIncrements; + } + + /** + * Suppose sensor ID is 'Temp' for 'Temperature Sensor', share count = 3, ID string instance modifier = numeric, + * instance modifier offset = 5 - then the sensors could be identified as: Temp 5, Temp 6, Temp 7
+ * If the modifier = alpha, offset=0 corresponds to 'A', offset=25 corresponds to 'Z', and offset = 26 corresponds + * to 'AA', thus, for offset=26 the sensors could be identified as: Temp AA, Temp AB, Temp AC + * + * @return the offset of the ID string instance modifier + */ + public int getIdInstanceModifierOffset() { + return idInstanceModifierOffset; + } + + /** + * @param idInstanceModifierOffset + * - the offset of the ID string instance modifier + */ + public void setIdInstanceModifierOffset(int idInstanceModifierOffset) { + this.idInstanceModifierOffset = idInstanceModifierOffset; + } +} diff --git a/src/main/java/org/metricshub/ipmi/core/coding/commands/sdr/record/CompactSensorRecord.java b/src/main/java/org/metricshub/ipmi/core/coding/commands/sdr/record/CompactSensorRecord.java index a45ddbe..0ea83e3 100644 --- a/src/main/java/org/metricshub/ipmi/core/coding/commands/sdr/record/CompactSensorRecord.java +++ b/src/main/java/org/metricshub/ipmi/core/coding/commands/sdr/record/CompactSensorRecord.java @@ -22,376 +22,22 @@ * ╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱ */ -import org.metricshub.ipmi.core.common.TypeConverter; - /** * Wrapper for Compact Sensor Record format */ -public class CompactSensorRecord extends SensorRecord { - - private byte sensorOwnerId; - - private AddressType addressType; - - private byte channelNumber; - - private byte sensorOwnerLun; - - private byte sensorNumber; - - private EntityId entityId; - - /** - * Entity is physical if true, logical otherwise. - */ - private boolean entityPhysical; - - private byte entityInstanceNumber; - - private boolean hysteresisReadable; - - private boolean thresholdsReadable; - - private SensorType sensorType; - - private int eventReadingType; - - private RateUnit rateUnit; - - private ModifierUnitUsage modifierUnitUsage; - - private SensorUnit sensorBaseUnit; - - private SensorUnit sensorModifierUnit; - - private SensorDirection sensorDirection; - - private String name; - - /** - * The instance modifier is a character(s) that software can append to the - * end of the ID String. This field selects whether the appended - * character(s) will be numeric or alpha. - */ - private InstanceModifierType idInstanceModifierType; - - /** - * Sensor numbers sharing this record are sequential starting with the - * sensor number specified by the Sensor Number field for this record. - */ - private int shareCount; - - private boolean entityInstanceIncrements; - - /** - * Suppose sensor ID is 'Temp' for 'Temperature Sensor', share count = 3, ID - * string instance modifier = numeric, instance modifier offset = 5 - then - * the sensors could be identified as: Temp 5, Temp 6, Temp 7
- * If the modifier = alpha, offset=0 corresponds to 'A', offset=25 - * corresponds to 'Z', and offset = 26 corresponds to 'AA', thus, for - * offset=26 the sensors could be identified as: Temp AA, Temp AB, Temp AC - */ - private int idInstanceModifierOffset; +public class CompactSensorRecord extends AbstractSensorRecord { @Override protected void populateTypeSpecficValues( byte[] recordData, SensorRecord record) { - setSensorOwnerId( - TypeConverter - .intToByte( - (TypeConverter - .byteToInt(recordData[5]) - & 0xfe) >> 1)); - - setAddressType( - AddressType - .parseInt( - TypeConverter - .byteToInt(recordData[5]) - & 0x01)); - - setChannelNumber( - TypeConverter - .intToByte( - (TypeConverter - .byteToInt(recordData[6]) - & 0xf0) >> 4)); - - setSensorOwnerLun( - TypeConverter - .intToByte( - TypeConverter - .byteToInt(recordData[6]) - & 0x3)); - - setSensorNumber(recordData[7]); - - setEntityId(EntityId.parseInt(TypeConverter.byteToInt(recordData[8]))); - - setEntityPhysical((TypeConverter.byteToInt(recordData[9]) & 0x80) == 0); - - setEntityInstanceNumber( - TypeConverter - .intToByte( - TypeConverter - .byteToInt(recordData[9]) - & 0x7f)); - - int hysteresis = (TypeConverter.byteToInt(recordData[11]) & 0x30) >> 4; - - if (hysteresis == 1 /* hysteresis readable */ - || hysteresis == 2 /* hysteresis readable & settable */) { - setHysteresisReadable(true); - } else { - setHysteresisReadable(false); - } - - int thresholds = (TypeConverter.byteToInt(recordData[11]) & 0xc) >> 2; - - if (thresholds == 1 /* thresholds readable */ - || thresholds == 2 /* thresholds readable & settable */) { - setThresholdsReadable(true); - } else { - setThresholdsReadable(false); - } - - setSensorType( - SensorType - .parseInt( - TypeConverter - .byteToInt(recordData[12]))); - - setEventReadingType(TypeConverter.byteToInt(recordData[13])); - - setRateUnit( - RateUnit - .parseInt((TypeConverter.byteToInt(recordData[20]) & 0x38) >> 3)); - - setModifierUnitUsage( - ModifierUnitUsage - .parseInt( - (TypeConverter - .byteToInt(recordData[20]) - & 0x6) >> 1)); - - setSensorBaseUnit( - SensorUnit - .parseInt( - TypeConverter - .byteToInt(recordData[21]))); - - setSensorModifierUnit( - SensorUnit - .parseInt( - TypeConverter - .byteToInt(recordData[22]))); - - setSensorDirection( - SensorDirection - .parseInt( - (TypeConverter - .byteToInt(recordData[23]) - & 0xc0) >> 6)); - - setIdInstanceModifierType( - InstanceModifierType - .parseInt( - (TypeConverter - .byteToInt(recordData[23]) - & 0x30) >> 4)); - - setShareCount(TypeConverter.byteToInt(recordData[23]) & 0xf); - - setEntityInstanceIncrements((TypeConverter.byteToInt(recordData[24]) & 0x80) != 0); - - setIdInstanceModifierOffset(TypeConverter.byteToInt(recordData[24]) & 0x7f); - + populateSensorHeader(recordData, 12); + populateCapabilitiesAndUnits(recordData); + populateSharing(recordData, 23); if (recordData.length >= 32) { - byte[] nameData = new byte[recordData.length - 32]; - - System.arraycopy(recordData, 32, nameData, 0, nameData.length); - - setName(decodeName(recordData[31], nameData)); + populateName(recordData, 31); } } - public byte getSensorOwnerId() { - return sensorOwnerId; - } - - public void setSensorOwnerId(byte sensorOwnerId) { - this.sensorOwnerId = sensorOwnerId; - } - - public AddressType getAddressType() { - return addressType; - } - - public void setAddressType(AddressType addressType) { - this.addressType = addressType; - } - - public byte getChannelNumber() { - return channelNumber; - } - - public void setChannelNumber(byte channelNumber) { - this.channelNumber = channelNumber; - } - - public byte getSensorOwnerLun() { - return sensorOwnerLun; - } - - public void setSensorOwnerLun(byte sensorOwnerLun) { - this.sensorOwnerLun = sensorOwnerLun; - } - - public byte getSensorNumber() { - return sensorNumber; - } - - public void setSensorNumber(byte sensorNumber) { - this.sensorNumber = sensorNumber; - } - - public EntityId getEntityId() { - return entityId; - } - - public void setEntityId(EntityId entityId) { - this.entityId = entityId; - } - - public boolean isEntityPhysical() { - return entityPhysical; - } - - public void setEntityPhysical(boolean entityPhysical) { - this.entityPhysical = entityPhysical; - } - - public byte getEntityInstanceNumber() { - return entityInstanceNumber; - } - - public void setEntityInstanceNumber(byte entityInstanceNumber) { - this.entityInstanceNumber = entityInstanceNumber; - } - - public boolean isHysteresisReadable() { - return hysteresisReadable; - } - - public void setHysteresisReadable(boolean hysteresisReadable) { - this.hysteresisReadable = hysteresisReadable; - } - - public boolean isThresholdsReadable() { - return thresholdsReadable; - } - - public void setThresholdsReadable(boolean thresholdsReadable) { - this.thresholdsReadable = thresholdsReadable; - } - - public SensorType getSensorType() { - return sensorType; - } - - public void setSensorType(SensorType sensorType) { - this.sensorType = sensorType; - } - - public int getEventReadingType() { - return eventReadingType; - } - - public void setEventReadingType(int eventReadingType) { - this.eventReadingType = eventReadingType; - } - - public RateUnit getRateUnit() { - return rateUnit; - } - - public void setRateUnit(RateUnit rateUnit) { - this.rateUnit = rateUnit; - } - - public ModifierUnitUsage getModifierUnitUsage() { - return modifierUnitUsage; - } - - public void setModifierUnitUsage(ModifierUnitUsage modifierUnitUsage) { - this.modifierUnitUsage = modifierUnitUsage; - } - - public SensorUnit getSensorBaseUnit() { - return sensorBaseUnit; - } - - public void setSensorBaseUnit(SensorUnit sensorBaseUnit) { - this.sensorBaseUnit = sensorBaseUnit; - } - - public SensorUnit getSensorModifierUnit() { - return sensorModifierUnit; - } - - public void setSensorModifierUnit(SensorUnit sensorModifierUnit) { - this.sensorModifierUnit = sensorModifierUnit; - } - - public SensorDirection getSensorDirection() { - return sensorDirection; - } - - public void setSensorDirection(SensorDirection sensorDirection) { - this.sensorDirection = sensorDirection; - } - - public InstanceModifierType getIdInstanceModifierType() { - return idInstanceModifierType; - } - - public void setIdInstanceModifierType( - InstanceModifierType idInstanceModifierType) { - this.idInstanceModifierType = idInstanceModifierType; - } - - public int getShareCount() { - return shareCount; - } - - public void setShareCount(int shareCount) { - this.shareCount = shareCount; - } - - public boolean isEntityInstanceIncrements() { - return entityInstanceIncrements; - } - - public void setEntityInstanceIncrements(boolean entityInstanceIncrements) { - this.entityInstanceIncrements = entityInstanceIncrements; - } - - public int getIdInstanceModifierOffset() { - return idInstanceModifierOffset; - } - - public void setIdInstanceModifierOffset(int idInstanceModifierOffset) { - this.idInstanceModifierOffset = idInstanceModifierOffset; - } - - public void setName(String name) { - this.name = name; - } - - public String getName() { - return name; - } - // TODO: Generate multiple records from instance modifiers (?) } diff --git a/src/main/java/org/metricshub/ipmi/core/coding/commands/sdr/record/EventOnlyRecord.java b/src/main/java/org/metricshub/ipmi/core/coding/commands/sdr/record/EventOnlyRecord.java index 8066d28..87ccfbd 100644 --- a/src/main/java/org/metricshub/ipmi/core/coding/commands/sdr/record/EventOnlyRecord.java +++ b/src/main/java/org/metricshub/ipmi/core/coding/commands/sdr/record/EventOnlyRecord.java @@ -22,277 +22,20 @@ * ╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱ */ -import org.metricshub.ipmi.core.common.TypeConverter; - /** * This record provides a mechanism to associate FRU and Entity information with * a physical or logical sensor that generates events, but cannot otherwise be * accessed. This is typical of software-generated events, such as events * generated by BIOS. */ -public class EventOnlyRecord extends SensorRecord { - - private byte sensorOwnerId; - - private AddressType addressType; - - private byte channelNumber; - - private byte sensorOwnerLun; - - private byte sensorNumber; - - private EntityId entityId; - - /** - * Entity is physical if true, logical otherwise. - */ - private boolean entityPhysical; - - private byte entityInstanceNumber; - - private SensorType sensorType; - - private int eventReadingType; - - private SensorDirection sensorDirection; - - private String name; - - /** - * The instance modifier is a character(s) that software can append to the - * end of the ID String. This field selects whether the appended - * character(s) will be numeric or alpha. - */ - private InstanceModifierType idInstanceModifierType; - - /** - * Sensor numbers sharing this record are sequential starting with the - * sensor number specified by the Sensor Number field for this record. - */ - private int shareCount; - - private boolean entityInstanceIncrements; - - /** - * Suppose sensor ID is 'Temp' for 'Temperature Sensor', share count = 3, ID - * string instance modifier = numeric, instance modifier offset = 5 - then - * the sensors could be identified as: Temp 5, Temp 6, Temp 7
- * If the modifier = alpha, offset=0 corresponds to 'A', offset=25 - * corresponds to 'Z', and offset = 26 corresponds to 'AA', thus, for - * offset=26 the sensors could be identified as: Temp AA, Temp AB, Temp AC - */ - private int idInstanceModifierOffset; +public class EventOnlyRecord extends AbstractSensorRecord { @Override protected void populateTypeSpecficValues( byte[] recordData, SensorRecord record) { - - setSensorOwnerId( - TypeConverter - .intToByte( - (TypeConverter - .byteToInt(recordData[5]) - & 0xfe) >> 1)); - - setAddressType( - AddressType - .parseInt( - TypeConverter - .byteToInt(recordData[5]) - & 0x01)); - - setChannelNumber( - TypeConverter - .intToByte( - (TypeConverter - .byteToInt(recordData[6]) - & 0xf0) >> 4)); - - setSensorOwnerLun( - TypeConverter - .intToByte( - TypeConverter - .byteToInt(recordData[6]) - & 0x3)); - - setSensorNumber(recordData[7]); - - setEntityId(EntityId.parseInt(TypeConverter.byteToInt(recordData[8]))); - - setEntityPhysical((TypeConverter.byteToInt(recordData[9]) & 0x80) == 0); - - setEntityInstanceNumber( - TypeConverter - .intToByte( - TypeConverter - .byteToInt(recordData[9]) - & 0x7f)); - - setSensorType( - SensorType - .parseInt( - TypeConverter - .byteToInt(recordData[10]))); - - setEventReadingType(TypeConverter.byteToInt(recordData[11])); - - setSensorDirection( - SensorDirection - .parseInt( - (TypeConverter - .byteToInt(recordData[12]) - & 0xc0) >> 6)); - - setIdInstanceModifierType( - InstanceModifierType - .parseInt( - (TypeConverter - .byteToInt(recordData[12]) - & 0x30) >> 4)); - - setShareCount(TypeConverter.byteToInt(recordData[12]) & 0xf); - - setEntityInstanceIncrements((TypeConverter.byteToInt(recordData[13]) & 0x80) != 0); - - setIdInstanceModifierOffset(TypeConverter.byteToInt(recordData[13]) & 0x7f); - - byte[] nameData = new byte[recordData.length - 17]; - - System.arraycopy(recordData, 17, nameData, 0, nameData.length); - - setName(decodeName(recordData[16], nameData)); - + populateSensorHeader(recordData, 10); + populateSharing(recordData, 12); + populateName(recordData, 16); } - - public byte getSensorOwnerId() { - return sensorOwnerId; - } - - public void setSensorOwnerId(byte sensorOwnerId) { - this.sensorOwnerId = sensorOwnerId; - } - - public AddressType getAddressType() { - return addressType; - } - - public void setAddressType(AddressType addressType) { - this.addressType = addressType; - } - - public byte getChannelNumber() { - return channelNumber; - } - - public void setChannelNumber(byte channelNumber) { - this.channelNumber = channelNumber; - } - - public byte getSensorOwnerLun() { - return sensorOwnerLun; - } - - public void setSensorOwnerLun(byte sensorOwnerLun) { - this.sensorOwnerLun = sensorOwnerLun; - } - - public byte getSensorNumber() { - return sensorNumber; - } - - public void setSensorNumber(byte sensorNumber) { - this.sensorNumber = sensorNumber; - } - - public EntityId getEntityId() { - return entityId; - } - - public void setEntityId(EntityId entityId) { - this.entityId = entityId; - } - - public boolean isEntityPhysical() { - return entityPhysical; - } - - public void setEntityPhysical(boolean entityPhysical) { - this.entityPhysical = entityPhysical; - } - - public byte getEntityInstanceNumber() { - return entityInstanceNumber; - } - - public void setEntityInstanceNumber(byte entityInstanceNumber) { - this.entityInstanceNumber = entityInstanceNumber; - } - - public SensorType getSensorType() { - return sensorType; - } - - public void setSensorType(SensorType sensorType) { - this.sensorType = sensorType; - } - - public int getEventReadingType() { - return eventReadingType; - } - - public void setEventReadingType(int eventReadingType) { - this.eventReadingType = eventReadingType; - } - - public SensorDirection getSensorDirection() { - return sensorDirection; - } - - public void setSensorDirection(SensorDirection sensorDirection) { - this.sensorDirection = sensorDirection; - } - - public String getName() { - return name; - } - - public void setName(String name) { - this.name = name; - } - - public InstanceModifierType getIdInstanceModifierType() { - return idInstanceModifierType; - } - - public void setIdInstanceModifierType( - InstanceModifierType idInstanceModifierType) { - this.idInstanceModifierType = idInstanceModifierType; - } - - public int getShareCount() { - return shareCount; - } - - public void setShareCount(int shareCount) { - this.shareCount = shareCount; - } - - public boolean isEntityInstanceIncrements() { - return entityInstanceIncrements; - } - - public void setEntityInstanceIncrements(boolean entityInstanceIncrements) { - this.entityInstanceIncrements = entityInstanceIncrements; - } - - public int getIdInstanceModifierOffset() { - return idInstanceModifierOffset; - } - - public void setIdInstanceModifierOffset(int idInstanceModifierOffset) { - this.idInstanceModifierOffset = idInstanceModifierOffset; - } - } diff --git a/src/main/java/org/metricshub/ipmi/core/coding/commands/sdr/record/FullSensorRecord.java b/src/main/java/org/metricshub/ipmi/core/coding/commands/sdr/record/FullSensorRecord.java index db438a7..8a7beed 100644 --- a/src/main/java/org/metricshub/ipmi/core/coding/commands/sdr/record/FullSensorRecord.java +++ b/src/main/java/org/metricshub/ipmi/core/coding/commands/sdr/record/FullSensorRecord.java @@ -27,42 +27,7 @@ /** * Wrapper class for Full Sensor Record format */ -public class FullSensorRecord extends SensorRecord { - - private byte sensorOwnerId; - - private AddressType addressType; - - private byte channelNumber; - - private byte sensorOwnerLun; - - private byte sensorNumber; - - private EntityId entityId; - - /** - * Entity is physical if true, logical otherwise. - */ - private boolean entityPhysical; - - private byte entityInstanceNumber; - - private boolean hysteresisReadable; - - private boolean thresholdsReadable; - - private SensorType sensorType; - - private int eventReadingType; - - private RateUnit rateUnit; - - private ModifierUnitUsage modifierUnitUsage; - - private SensorUnit sensorBaseUnit; - - private SensorUnit sensorModifierUnit; +public class FullSensorRecord extends AbstractSensorRecord { private double m; @@ -74,8 +39,6 @@ public class FullSensorRecord extends SensorRecord { private int rExp; - private SensorDirection sensorDirection; - private double nominalReading; private double normalMaximum; @@ -98,8 +61,6 @@ public class FullSensorRecord extends SensorRecord { private double lowerNonCriticalThreshold; - private String name; - private byte sensorUnits1; private int linearization; @@ -108,83 +69,8 @@ public class FullSensorRecord extends SensorRecord { protected void populateTypeSpecficValues( byte[] recordData, SensorRecord record) { - setSensorOwnerId( - TypeConverter - .intToByte( - (TypeConverter - .byteToInt(recordData[5]) - & 0xfe) >> 1)); - - setAddressType( - AddressType - .parseInt( - TypeConverter - .byteToInt(recordData[5]) - & 0x01)); - - setChannelNumber( - TypeConverter - .intToByte( - (TypeConverter - .byteToInt(recordData[6]) - & 0xf0) >> 4)); - - setSensorOwnerLun( - TypeConverter - .intToByte( - TypeConverter - .byteToInt(recordData[6]) - & 0x3)); - - setSensorNumber(recordData[7]); - - setEntityId(EntityId.parseInt(TypeConverter.byteToInt(recordData[8]))); - - setEntityPhysical((TypeConverter.byteToInt(recordData[9]) & 0x80) == 0); - - setEntityInstanceNumber( - TypeConverter - .intToByte( - TypeConverter - .byteToInt(recordData[9]) - & 0x7f)); - - int hysteresis = (TypeConverter.byteToInt(recordData[11]) & 0x30) >> 4; - setHysteresisReadable(isFieldReadable(hysteresis)); - - int thresholds = (TypeConverter.byteToInt(recordData[11]) & 0xc) >> 2; - setThresholdsReadable(isFieldReadable(thresholds)); - - setSensorType( - SensorType - .parseInt( - TypeConverter - .byteToInt(recordData[12]))); - - setEventReadingType(TypeConverter.byteToInt(recordData[13])); - - setRateUnit( - RateUnit - .parseInt((TypeConverter.byteToInt(recordData[20]) & 0x38) >> 3)); - - setModifierUnitUsage( - ModifierUnitUsage - .parseInt( - (TypeConverter - .byteToInt(recordData[20]) - & 0x6) >> 1)); - - setSensorBaseUnit( - SensorUnit - .parseInt( - TypeConverter - .byteToInt(recordData[21]))); - - setSensorModifierUnit( - SensorUnit - .parseInt( - TypeConverter - .byteToInt(recordData[22]))); + populateSensorHeader(recordData, 12); + populateCapabilitiesAndUnits(recordData); int calcM = TypeConverter.byteToInt(recordData[24]); @@ -287,148 +173,11 @@ protected void populateTypeSpecficValues( } } - byte[] nameData = new byte[recordData.length - 48]; - - System.arraycopy(recordData, 48, nameData, 0, nameData.length); - - setName(decodeName(recordData[47], nameData)); + populateName(recordData, 47); linearization = TypeConverter.byteToInt(recordData[23]) & 0x7f; } - private boolean isFieldReadable(int field) { - return field == 1 /* field readable */ - || field == 2 /* field readable & settable */; - } - - public void setSensorOwnerId(byte sensorOwnerId) { - this.sensorOwnerId = sensorOwnerId; - } - - public byte getSensorOwnerId() { - return sensorOwnerId; - } - - public AddressType getAddressType() { - return addressType; - } - - public void setAddressType(AddressType addressType) { - this.addressType = addressType; - } - - public byte getChannelNumber() { - return channelNumber; - } - - public void setChannelNumber(byte channelNumber) { - this.channelNumber = channelNumber; - } - - public byte getSensorOwnerLun() { - return sensorOwnerLun; - } - - public void setSensorOwnerLun(byte sensorOwnerLun) { - this.sensorOwnerLun = sensorOwnerLun; - } - - public byte getSensorNumber() { - return sensorNumber; - } - - public void setSensorNumber(byte sensorNumber) { - this.sensorNumber = sensorNumber; - } - - public EntityId getEntityId() { - return entityId; - } - - public void setEntityId(EntityId entityId) { - this.entityId = entityId; - } - - public boolean isEntityPhysical() { - return entityPhysical; - } - - public void setEntityPhysical(boolean entityPhysical) { - this.entityPhysical = entityPhysical; - } - - public byte getEntityInstanceNumber() { - return entityInstanceNumber; - } - - public void setEntityInstanceNumber(byte entityInstanceNumber) { - this.entityInstanceNumber = entityInstanceNumber; - } - - public boolean isHysteresisReadable() { - return hysteresisReadable; - } - - public void setHysteresisReadable(boolean hysteresisReadable) { - this.hysteresisReadable = hysteresisReadable; - } - - public boolean isThresholdsReadable() { - return thresholdsReadable; - } - - public void setThresholdsReadable(boolean thresholdsReadable) { - this.thresholdsReadable = thresholdsReadable; - } - - public SensorType getSensorType() { - return sensorType; - } - - public void setSensorType(SensorType sensorType) { - this.sensorType = sensorType; - } - - public int getEventReadingType() { - return eventReadingType; - } - - public void setEventReadingType(int eventReadingType) { - this.eventReadingType = eventReadingType; - } - - public RateUnit getRateUnit() { - return rateUnit; - } - - public void setRateUnit(RateUnit rateUnit) { - this.rateUnit = rateUnit; - } - - public ModifierUnitUsage getModifierUnitUsage() { - return modifierUnitUsage; - } - - public void setModifierUnitUsage(ModifierUnitUsage modifierUnitUsage) { - this.modifierUnitUsage = modifierUnitUsage; - } - - public SensorUnit getSensorBaseUnit() { - return sensorBaseUnit; - } - - public void setSensorBaseUnit(SensorUnit sensorBaseUnit) { - this.sensorBaseUnit = sensorBaseUnit; - } - - public SensorUnit getSensorModifierUnit() { - return sensorModifierUnit; - } - - public void setSensorModifierUnit(SensorUnit sensorModifierUnit) { - this.sensorModifierUnit = sensorModifierUnit; - } - private double getM() { return m; } @@ -469,14 +218,6 @@ private int getrExp() { return rExp; } - public SensorDirection getSensorDirection() { - return sensorDirection; - } - - public void setSensorDirection(SensorDirection sensorDirection) { - this.sensorDirection = sensorDirection; - } - public double getNominalReading() { return nominalReading; } @@ -567,14 +308,6 @@ public void setLowerNonCriticalThreshold(double lowerNonCriticalThreshold) { this.lowerNonCriticalThreshold = lowerNonCriticalThreshold; } - public String getName() { - return name; - } - - public void setName(String name) { - this.name = name; - } - /** * Converts to units-based value using the 'y=Mx+B' formula. 1's or 2's * complement signed or unsigned per flag bits in Sensor Units 1. diff --git a/src/main/java/org/metricshub/ipmi/core/coding/commands/sel/GetSelEntry.java b/src/main/java/org/metricshub/ipmi/core/coding/commands/sel/GetSelEntry.java index c57e05e..c11b092 100644 --- a/src/main/java/org/metricshub/ipmi/core/coding/commands/sel/GetSelEntry.java +++ b/src/main/java/org/metricshub/ipmi/core/coding/commands/sel/GetSelEntry.java @@ -26,11 +26,9 @@ import org.metricshub.ipmi.core.coding.commands.IpmiCommandCoder; import org.metricshub.ipmi.core.coding.commands.IpmiVersion; import org.metricshub.ipmi.core.coding.commands.ResponseData; -import org.metricshub.ipmi.core.coding.payload.CompletionCode; import org.metricshub.ipmi.core.coding.payload.IpmiPayload; import org.metricshub.ipmi.core.coding.payload.lan.IPMIException; import org.metricshub.ipmi.core.coding.payload.lan.IpmiLanRequest; -import org.metricshub.ipmi.core.coding.payload.lan.IpmiLanResponse; import org.metricshub.ipmi.core.coding.payload.lan.NetworkFunction; import org.metricshub.ipmi.core.coding.protocol.AuthenticationType; import org.metricshub.ipmi.core.coding.protocol.IpmiMessage; @@ -114,20 +112,7 @@ public ResponseData getResponseData(IpmiMessage message) throws IPMIException, NoSuchAlgorithmException, InvalidKeyException { - if (!isCommandResponse(message)) { - throw new IllegalArgumentException( - "This is not a response for Get SEL Entry command"); - } - if (!(message.getPayload() instanceof IpmiLanResponse)) { - throw new IllegalArgumentException("Invalid response payload"); - } - if (((IpmiLanResponse) message.getPayload()).getCompletionCode() != CompletionCode.Ok) { - throw new IPMIException( - ((IpmiLanResponse) message.getPayload()) - .getCompletionCode()); - } - - byte[] raw = message.getPayload().getIpmiCommandData(); + byte[] raw = validateResponse(message); if (raw == null || raw.length < 3) { throw new IllegalArgumentException( diff --git a/src/main/java/org/metricshub/ipmi/core/coding/commands/sel/GetSelInfo.java b/src/main/java/org/metricshub/ipmi/core/coding/commands/sel/GetSelInfo.java index 7127c28..f69979c 100644 --- a/src/main/java/org/metricshub/ipmi/core/coding/commands/sel/GetSelInfo.java +++ b/src/main/java/org/metricshub/ipmi/core/coding/commands/sel/GetSelInfo.java @@ -26,11 +26,9 @@ import org.metricshub.ipmi.core.coding.commands.IpmiCommandCoder; import org.metricshub.ipmi.core.coding.commands.IpmiVersion; import org.metricshub.ipmi.core.coding.commands.ResponseData; -import org.metricshub.ipmi.core.coding.payload.CompletionCode; import org.metricshub.ipmi.core.coding.payload.IpmiPayload; import org.metricshub.ipmi.core.coding.payload.lan.IPMIException; import org.metricshub.ipmi.core.coding.payload.lan.IpmiLanRequest; -import org.metricshub.ipmi.core.coding.payload.lan.IpmiLanResponse; import org.metricshub.ipmi.core.coding.payload.lan.NetworkFunction; import org.metricshub.ipmi.core.coding.protocol.AuthenticationType; import org.metricshub.ipmi.core.coding.protocol.IpmiMessage; @@ -87,20 +85,7 @@ public ResponseData getResponseData(IpmiMessage message) throws IPMIException, NoSuchAlgorithmException, InvalidKeyException { - if (!isCommandResponse(message)) { - throw new IllegalArgumentException( - "This is not a response for Get SEL Info command"); - } - if (!(message.getPayload() instanceof IpmiLanResponse)) { - throw new IllegalArgumentException("Invalid response payload"); - } - if (((IpmiLanResponse) message.getPayload()).getCompletionCode() != CompletionCode.Ok) { - throw new IPMIException( - ((IpmiLanResponse) message.getPayload()) - .getCompletionCode()); - } - - byte[] raw = message.getPayload().getIpmiCommandData(); + byte[] raw = validateResponse(message); if (raw == null || raw.length != 14) { throw new IllegalArgumentException("Invalid response payload length: " + (raw != null ? raw.length : "null")); diff --git a/src/main/java/org/metricshub/ipmi/core/coding/commands/sel/ReserveSel.java b/src/main/java/org/metricshub/ipmi/core/coding/commands/sel/ReserveSel.java index 4b438ed..96a2ced 100644 --- a/src/main/java/org/metricshub/ipmi/core/coding/commands/sel/ReserveSel.java +++ b/src/main/java/org/metricshub/ipmi/core/coding/commands/sel/ReserveSel.java @@ -26,11 +26,9 @@ import org.metricshub.ipmi.core.coding.commands.IpmiCommandCoder; import org.metricshub.ipmi.core.coding.commands.IpmiVersion; import org.metricshub.ipmi.core.coding.commands.ResponseData; -import org.metricshub.ipmi.core.coding.payload.CompletionCode; import org.metricshub.ipmi.core.coding.payload.IpmiPayload; import org.metricshub.ipmi.core.coding.payload.lan.IPMIException; import org.metricshub.ipmi.core.coding.payload.lan.IpmiLanRequest; -import org.metricshub.ipmi.core.coding.payload.lan.IpmiLanResponse; import org.metricshub.ipmi.core.coding.payload.lan.NetworkFunction; import org.metricshub.ipmi.core.coding.protocol.AuthenticationType; import org.metricshub.ipmi.core.coding.protocol.IpmiMessage; @@ -88,20 +86,7 @@ public ResponseData getResponseData(IpmiMessage message) NoSuchAlgorithmException, InvalidKeyException { - if (!isCommandResponse(message)) { - throw new IllegalArgumentException( - "This is not a response for Reserve SEL command"); - } - if (!(message.getPayload() instanceof IpmiLanResponse)) { - throw new IllegalArgumentException("Invalid response payload"); - } - if (((IpmiLanResponse) message.getPayload()).getCompletionCode() != CompletionCode.Ok) { - throw new IPMIException( - ((IpmiLanResponse) message.getPayload()) - .getCompletionCode()); - } - - byte[] raw = message.getPayload().getIpmiCommandData(); + byte[] raw = validateResponse(message); if (raw == null || raw.length != 2) { throw new IllegalArgumentException( diff --git a/src/main/java/org/metricshub/ipmi/core/coding/commands/session/GetChannelAuthenticationCapabilities.java b/src/main/java/org/metricshub/ipmi/core/coding/commands/session/GetChannelAuthenticationCapabilities.java index cc0f39e..3068803 100644 --- a/src/main/java/org/metricshub/ipmi/core/coding/commands/session/GetChannelAuthenticationCapabilities.java +++ b/src/main/java/org/metricshub/ipmi/core/coding/commands/session/GetChannelAuthenticationCapabilities.java @@ -27,11 +27,9 @@ import org.metricshub.ipmi.core.coding.commands.IpmiVersion; import org.metricshub.ipmi.core.coding.commands.PrivilegeLevel; import org.metricshub.ipmi.core.coding.commands.ResponseData; -import org.metricshub.ipmi.core.coding.payload.CompletionCode; import org.metricshub.ipmi.core.coding.payload.IpmiPayload; import org.metricshub.ipmi.core.coding.payload.lan.IPMIException; import org.metricshub.ipmi.core.coding.payload.lan.IpmiLanRequest; -import org.metricshub.ipmi.core.coding.payload.lan.IpmiLanResponse; import org.metricshub.ipmi.core.coding.payload.lan.NetworkFunction; import org.metricshub.ipmi.core.coding.protocol.AuthenticationType; import org.metricshub.ipmi.core.coding.protocol.IpmiMessage; @@ -225,21 +223,9 @@ public NetworkFunction getNetworkFunction() { @Override public ResponseData getResponseData(IpmiMessage message) throws IPMIException { - if (!isCommandResponse(message)) { - throw new IllegalArgumentException( - "This is not a response for Get Channel Authentication Capabilities command"); - } - if (!(message.getPayload() instanceof IpmiLanResponse)) { - throw new IllegalArgumentException("Invalid response payload"); - } - if (((IpmiLanResponse) message.getPayload()).getCompletionCode() != CompletionCode.Ok) { - throw new IPMIException( - ((IpmiLanResponse) message.getPayload()) - .getCompletionCode()); - } - GetChannelAuthenticationCapabilitiesResponseData responseData = new GetChannelAuthenticationCapabilitiesResponseData(); + byte[] raw = validateResponse(message); - byte[] raw = message.getPayload().getIpmiCommandData(); + GetChannelAuthenticationCapabilitiesResponseData responseData = new GetChannelAuthenticationCapabilitiesResponseData(); if (raw.length != 8) { throw new IllegalArgumentException("Data has invalid length"); diff --git a/src/main/java/org/metricshub/ipmi/core/coding/commands/session/SetSessionPrivilegeLevel.java b/src/main/java/org/metricshub/ipmi/core/coding/commands/session/SetSessionPrivilegeLevel.java index b5503c2..2fc1406 100644 --- a/src/main/java/org/metricshub/ipmi/core/coding/commands/session/SetSessionPrivilegeLevel.java +++ b/src/main/java/org/metricshub/ipmi/core/coding/commands/session/SetSessionPrivilegeLevel.java @@ -27,11 +27,9 @@ import org.metricshub.ipmi.core.coding.commands.IpmiVersion; import org.metricshub.ipmi.core.coding.commands.PrivilegeLevel; import org.metricshub.ipmi.core.coding.commands.ResponseData; -import org.metricshub.ipmi.core.coding.payload.CompletionCode; import org.metricshub.ipmi.core.coding.payload.IpmiPayload; import org.metricshub.ipmi.core.coding.payload.lan.IPMIException; import org.metricshub.ipmi.core.coding.payload.lan.IpmiLanRequest; -import org.metricshub.ipmi.core.coding.payload.lan.IpmiLanResponse; import org.metricshub.ipmi.core.coding.payload.lan.NetworkFunction; import org.metricshub.ipmi.core.coding.protocol.AuthenticationType; import org.metricshub.ipmi.core.coding.protocol.IpmiMessage; @@ -95,15 +93,7 @@ public ResponseData getResponseData(IpmiMessage message) throws IPMIException, NoSuchAlgorithmException, InvalidKeyException { - if (!isCommandResponse(message)) { - throw new IllegalArgumentException("This is not a response for Get SEL Entry command"); - } - if (!(message.getPayload() instanceof IpmiLanResponse)) { - throw new IllegalArgumentException("Invalid response payload"); - } - if (((IpmiLanResponse) message.getPayload()).getCompletionCode() != CompletionCode.Ok) { - throw new IPMIException(((IpmiLanResponse) message.getPayload()).getCompletionCode()); - } + validateResponse(message); return new SetSessionPrivilegeLevelResponseData(); } diff --git a/src/site/markdown/index.md b/src/site/markdown/index.md index 3b3f187..58dc7bd 100644 --- a/src/site/markdown/index.md +++ b/src/site/markdown/index.md @@ -82,3 +82,28 @@ The `IpmiClient` API is unchanged. Classes that **extend** the library's protoco | `IntegrityAlgorithm` | `sik` | `getSik()`, `setSik(byte[])` | `IpmiClient`, `IpmiResultConverter`, `Utils`, `DeviceDescription`, `ReadingTypeDescription` and `MessageComposer` are now `final` (they only had private constructors, so they could not be subclassed anyway). + +### Sensor records + +`FullSensorRecord`, `CompactSensorRecord` and `EventOnlyRecord` now extend the new `AbstractSensorRecord` (itself a `SensorRecord`), which holds the fields the three record types share: sensor owner and number, entity, sensor type, event/reading type, direction, name (ID string), capabilities, units and record sharing. Their getters and setters keep the same signatures, so existing code compiles unchanged, and code that handles several record types can use `AbstractSensorRecord` instead of testing each type: + +```java +if (record instanceof AbstractSensorRecord) { + AbstractSensorRecord sensor = (AbstractSensorRecord) record; + System.out.println(sensor.getName() + ": " + sensor.getSensorType()); +} +``` + +A record type now also inherits the getters of fields it does not define, which return defaults: + +| Record | Field | Value | +| --- | --- | --- | +| `EventOnlyRecord` (no reading) | `getRateUnit()`, `getModifierUnitUsage()`, `getSensorBaseUnit()`, `getSensorModifierUnit()` | `null` | +| `EventOnlyRecord` (no reading) | `isHysteresisReadable()`, `isThresholdsReadable()` | `false` | +| `FullSensorRecord` (a single sensor) | `getShareCount()`, `getIdInstanceModifierOffset()` | `0` | +| `FullSensorRecord` (a single sensor) | `getIdInstanceModifierType()` | `null` | +| `FullSensorRecord` (a single sensor) | `isEntityInstanceIncrements()` | `false` | + +### Command responses + +Commands that extend `IpmiCommandCoder` can call the new `protected` method `validateResponse(IpmiMessage)`, which checks that a message is a successful response to the command and returns its data: it throws `IllegalArgumentException` for a response to another command or a payload that is not an IPMI LAN response, and `IPMIException` for a completion code other than `Ok`. The message of the `IllegalArgumentException` now names the command class (three commands used to name the wrong command). diff --git a/src/test/java/org/metricshub/ipmi/core/coding/commands/IpmiCommandCoderTest.java b/src/test/java/org/metricshub/ipmi/core/coding/commands/IpmiCommandCoderTest.java new file mode 100644 index 0000000..27da00b --- /dev/null +++ b/src/test/java/org/metricshub/ipmi/core/coding/commands/IpmiCommandCoderTest.java @@ -0,0 +1,75 @@ +package org.metricshub.ipmi.core.coding.commands; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import org.junit.jupiter.api.Test; +import org.metricshub.ipmi.core.coding.commands.sdr.ReserveSdrRepository; +import org.metricshub.ipmi.core.coding.commands.sdr.ReserveSdrRepositoryResponseData; +import org.metricshub.ipmi.core.coding.payload.CompletionCode; +import org.metricshub.ipmi.core.coding.payload.lan.IPMIException; +import org.metricshub.ipmi.core.coding.payload.lan.IpmiLanResponse; +import org.metricshub.ipmi.core.coding.protocol.AuthenticationType; +import org.metricshub.ipmi.core.coding.protocol.IpmiMessage; +import org.metricshub.ipmi.core.coding.protocol.Ipmiv15Message; +import org.metricshub.ipmi.core.coding.security.CipherSuite; + +class IpmiCommandCoderTest { + + private static final ReserveSdrRepository COMMAND = new ReserveSdrRepository( + IpmiVersion.V20, + CipherSuite.getEmpty(), + AuthenticationType.RMCPPlus); + + /** + * Build a message wrapping an IPMI LAN response (IPMI 2.0 table 13-5) from the BMC to remote console software. + */ + private static IpmiMessage response(byte command, int completionCode, int... data) { + byte[] raw = new byte[8 + data.length]; + raw[0] = (byte) 0x81; // requester address + raw[1] = (byte) 0x2c; // storage response network function, LUN 0 + raw[2] = (byte) -(0x81 + 0x2c); // checksum 1 + raw[3] = 0x20; // responder address + raw[4] = 0x04; // sequence number 1, LUN 0 + raw[5] = command; + raw[6] = (byte) completionCode; + for (int i = 0; i < data.length; i++) { + raw[7 + i] = (byte) data[i]; + } + int checksum = 0; + for (int i = 3; i < raw.length - 1; i++) { + checksum += raw[i]; + } + raw[raw.length - 1] = (byte) -checksum; // checksum 2 + + IpmiMessage message = new Ipmiv15Message(); + message.setPayload(new IpmiLanResponse(raw)); + return message; + } + + @Test + void successfulResponseIsDecoded() throws Exception { + ReserveSdrRepositoryResponseData data = (ReserveSdrRepositoryResponseData) COMMAND + .getResponseData(response(CommandCodes.RESERVE_SDR_REPOSITORY, 0x00, 0x34, 0x12)); + + assertEquals(0x1234, data.getReservationId()); + } + + @Test + void errorCompletionCodeIsThrown() { + IPMIException e = assertThrows( + IPMIException.class, + () -> COMMAND.getResponseData(response(CommandCodes.RESERVE_SDR_REPOSITORY, 0xc5))); + + assertEquals(CompletionCode.ReservationCanceled, e.getCompletionCode()); + } + + @Test + void responseToAnotherCommandIsRejected() { + IllegalArgumentException e = assertThrows( + IllegalArgumentException.class, + () -> COMMAND.getResponseData(response(CommandCodes.GET_SDR, 0x00, 0x34, 0x12))); + + assertEquals("This is not a response for ReserveSdrRepository command", e.getMessage()); + } +} diff --git a/src/test/java/org/metricshub/ipmi/core/coding/commands/sdr/record/SensorRecordTest.java b/src/test/java/org/metricshub/ipmi/core/coding/commands/sdr/record/SensorRecordTest.java index eab5fa5..e9b40e5 100644 --- a/src/test/java/org/metricshub/ipmi/core/coding/commands/sdr/record/SensorRecordTest.java +++ b/src/test/java/org/metricshub/ipmi/core/coding/commands/sdr/record/SensorRecordTest.java @@ -2,8 +2,11 @@ import static org.junit.jupiter.api.Assertions.assertArrayEquals; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertInstanceOf; +import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; import org.junit.jupiter.api.Test; @@ -83,6 +86,170 @@ void unknownStandardRecordTypeIsRejected() { { 0x00 }))); } + /** + * Build a byte array from int values, so that test records can use unsigned notation. + */ + private static byte[] bytes(int... values) { + byte[] result = new byte[values.length]; + for (int i = 0; i < values.length; i++) { + result[i] = (byte) values[i]; + } + return result; + } + + // Record bytes 5-9, common to the Full, Compact and Event-Only sensor records: owner ID 20h (system software ID), + // channel 5, LUN 2, sensor number 9Ah, entity 7 (system board), logical entity instance 3 + private static final int[] SENSOR_KEY_AND_ENTITY = { 0x41, 0x52, 0x9a, 0x07, 0x83 }; + + private static byte[] sensorRecord(int type, int... body) { + int[] payload = new int[SENSOR_KEY_AND_ENTITY.length + body.length]; + System.arraycopy(SENSOR_KEY_AND_ENTITY, 0, payload, 0, SENSOR_KEY_AND_ENTITY.length); + System.arraycopy(body, 0, payload, SENSOR_KEY_AND_ENTITY.length, body.length); + return record(0x0042, type, bytes(payload)); + } + + // @formatter:off + private static final byte[] FULL_SENSOR_RECORD = sensorRecord(0x01, + 0x04, // 10: initialization, thresholds present + 0x24, // 11: capabilities, hysteresis readable/settable, thresholds readable + 0x02, 0x01, // 12-13: voltage sensor, threshold reading type + 0x00, 0x00, 0x00, 0x00, 0x3f, 0x00, // 14-19: masks, all thresholds readable + 0x00, 0x04, 0x00, // 20-22: unsigned, no rate, volts, no modifier unit + 0x00, // 23: linear + 0x0a, 0x04, 0x05, 0x10, 0x01, 0xe1, // 24-29: M=10, B=5, input, R exp=-2, B exp=1 + 0x00, // 30: analog characteristics + 0x64, 0x78, 0x50, 0xff, 0x00, // 31-35: nominal, normal max/min, sensor max/min + 0xc8, 0xbe, 0xb4, 0x14, 0x1e, 0x28, // 36-41: UNR, UC, UNC, LNR, LC, LNC + 0x00, 0x00, 0x00, 0x00, 0x00, // 42-46: hysteresis, reserved, OEM + 0xc5, 'V', 'c', 'o', 'r', 'e'); // 47-52: 8-bit ASCII ID string + + private static final byte[] COMPACT_SENSOR_RECORD = sensorRecord(0x02, + 0x00, // 10: initialization + 0x38, // 11: capabilities, fixed hysteresis, thresholds readable/settable + 0x01, 0x01, // 12-13: temperature sensor, threshold reading type + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 14-19: masks + 0x92, 0x01, 0x04, // 20-22: per ms, divided by modifier unit, degrees C, volts + 0x53, 0x85, // 23-24: input, alpha modifier, share count 3, entity instance increments, offset 5 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 25-30: hysteresis, reserved, OEM + 0xc4, 'T', 'e', 'm', 'p'); // 31-35: 8-bit ASCII ID string + + private static final byte[] EVENT_ONLY_RECORD = sensorRecord(0x03, + 0x12, 0x6f, // 10-11: system event sensor, sensor-specific reading type + 0x82, 0x11, // 12-13: output, numeric modifier, share count 2, offset 17 + 0x00, 0x00, // 14-15: reserved, OEM + 0xc4, 'B', 'I', 'O', 'S'); // 16-20: 8-bit ASCII ID string + // @formatter:on + + @Test + void fullSensorRecordIsDecoded() { + FullSensorRecord full = assertInstanceOf( + FullSensorRecord.class, + SensorRecord.populateSensorRecord(FULL_SENSOR_RECORD)); + + assertEquals(0x42, full.getId()); + assertEquals(0x20, full.getSensorOwnerId()); + assertEquals(AddressType.SystemSoftwareId, full.getAddressType()); + assertEquals(5, full.getChannelNumber()); + assertEquals(2, full.getSensorOwnerLun()); + assertEquals((byte) 0x9a, full.getSensorNumber()); + assertEquals(EntityId.SystemBoard, full.getEntityId()); + assertFalse(full.isEntityPhysical()); + assertEquals(3, full.getEntityInstanceNumber()); + assertTrue(full.isHysteresisReadable()); + assertTrue(full.isThresholdsReadable()); + assertEquals(SensorType.Voltage, full.getSensorType()); + assertEquals(0x01, full.getEventReadingType()); + assertEquals(RateUnit.None, full.getRateUnit()); + assertEquals(ModifierUnitUsage.None, full.getModifierUnitUsage()); + assertEquals(SensorUnit.Volts, full.getSensorBaseUnit()); + assertEquals(SensorUnit.Unspecified, full.getSensorModifierUnit()); + assertEquals(SensorDirection.Input, full.getSensorDirection()); + assertEquals("Vcore", full.getName()); + + // y = (M * x + B * 10^Bexp) * 10^Rexp = (10 * x + 50) / 100 + assertEquals(10.5, full.getNominalReading(), 1e-9); + assertEquals(12.5, full.getNormalMaximum(), 1e-9); + assertEquals(8.5, full.getNormalMinimum(), 1e-9); + assertEquals(26.0, full.getSensorMaximumReading(), 1e-9); + assertEquals(0.5, full.getSensorMinmumReading(), 1e-9); + assertEquals(20.5, full.getUpperNonRecoverableThreshold(), 1e-9); + assertEquals(19.5, full.getUpperCriticalThreshold(), 1e-9); + assertEquals(18.5, full.getUpperNonCriticalThreshold(), 1e-9); + assertEquals(2.5, full.getLowerNonRecoverableThreshold(), 1e-9); + assertEquals(3.5, full.getLowerCriticalThreshold(), 1e-9); + assertEquals(4.5, full.getLowerNonCriticalThreshold(), 1e-9); + assertEquals(0.05, full.getSensorResolution(), 1e-9); + } + + @Test + void compactSensorRecordIsDecoded() { + CompactSensorRecord compact = assertInstanceOf( + CompactSensorRecord.class, + SensorRecord.populateSensorRecord(COMPACT_SENSOR_RECORD)); + + assertEquals(0x42, compact.getId()); + assertEquals(0x20, compact.getSensorOwnerId()); + assertEquals(AddressType.SystemSoftwareId, compact.getAddressType()); + assertEquals(5, compact.getChannelNumber()); + assertEquals(2, compact.getSensorOwnerLun()); + assertEquals((byte) 0x9a, compact.getSensorNumber()); + assertEquals(EntityId.SystemBoard, compact.getEntityId()); + assertFalse(compact.isEntityPhysical()); + assertEquals(3, compact.getEntityInstanceNumber()); + assertFalse(compact.isHysteresisReadable()); + assertTrue(compact.isThresholdsReadable()); + assertEquals(SensorType.Temperature, compact.getSensorType()); + assertEquals(0x01, compact.getEventReadingType()); + assertEquals(RateUnit.Miliseconds, compact.getRateUnit()); + assertEquals(ModifierUnitUsage.Divide, compact.getModifierUnitUsage()); + assertEquals(SensorUnit.DegreesC, compact.getSensorBaseUnit()); + assertEquals(SensorUnit.Volts, compact.getSensorModifierUnit()); + assertEquals(SensorDirection.Input, compact.getSensorDirection()); + assertEquals(InstanceModifierType.Alpha, compact.getIdInstanceModifierType()); + assertEquals(3, compact.getShareCount()); + assertTrue(compact.isEntityInstanceIncrements()); + assertEquals(5, compact.getIdInstanceModifierOffset()); + assertEquals("Temp", compact.getName()); + } + + @Test + void compactSensorRecordWithoutIdStringHasNoName() { + byte[] truncated = new byte[31]; + System.arraycopy(COMPACT_SENSOR_RECORD, 0, truncated, 0, truncated.length); + + CompactSensorRecord compact = assertInstanceOf( + CompactSensorRecord.class, + SensorRecord.populateSensorRecord(truncated)); + + assertEquals(SensorType.Temperature, compact.getSensorType()); + assertNull(compact.getName()); + } + + @Test + void eventOnlyRecordIsDecoded() { + EventOnlyRecord eventOnly = assertInstanceOf( + EventOnlyRecord.class, + SensorRecord.populateSensorRecord(EVENT_ONLY_RECORD)); + + assertEquals(0x42, eventOnly.getId()); + assertEquals(0x20, eventOnly.getSensorOwnerId()); + assertEquals(AddressType.SystemSoftwareId, eventOnly.getAddressType()); + assertEquals(5, eventOnly.getChannelNumber()); + assertEquals(2, eventOnly.getSensorOwnerLun()); + assertEquals((byte) 0x9a, eventOnly.getSensorNumber()); + assertEquals(EntityId.SystemBoard, eventOnly.getEntityId()); + assertFalse(eventOnly.isEntityPhysical()); + assertEquals(3, eventOnly.getEntityInstanceNumber()); + assertEquals(SensorType.SystemEvent, eventOnly.getSensorType()); + assertEquals(0x6f, eventOnly.getEventReadingType()); + assertEquals(SensorDirection.Output, eventOnly.getSensorDirection()); + assertEquals(InstanceModifierType.Numeric, eventOnly.getIdInstanceModifierType()); + assertEquals(2, eventOnly.getShareCount()); + assertFalse(eventOnly.isEntityInstanceIncrements()); + assertEquals(17, eventOnly.getIdInstanceModifierOffset()); + assertEquals("BIOS", eventOnly.getName()); + } + @Test void recordShorterThanHeaderIsRejected() { assertThrows(