View Javadoc
1   package org.metricshub.ipmi.core.coding.commands.fru;
2   
3   /*-
4    * ╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲
5    * IPMI Java Client
6    * ჻჻჻჻჻჻
7    * Copyright 2023 Verax Systems, MetricsHub
8    * ჻჻჻჻჻჻
9    * This program is free software: you can redistribute it and/or modify
10   * it under the terms of the GNU Lesser General Public License as
11   * published by the Free Software Foundation, either version 3 of the
12   * License, or (at your option) any later version.
13   *
14   * This program is distributed in the hope that it will be useful,
15   * but WITHOUT ANY WARRANTY; without even the implied warranty of
16   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17   * GNU General Lesser Public License for more details.
18   *
19   * You should have received a copy of the GNU General Lesser Public
20   * License along with this program.  If not, see
21   * <http://www.gnu.org/licenses/lgpl-3.0.html>.
22   * ╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱
23   */
24  
25  import org.metricshub.ipmi.core.coding.commands.CommandCodes;
26  import org.metricshub.ipmi.core.coding.commands.IpmiCommandCoder;
27  import org.metricshub.ipmi.core.coding.commands.IpmiVersion;
28  import org.metricshub.ipmi.core.coding.commands.ResponseData;
29  import org.metricshub.ipmi.core.coding.commands.fru.record.BoardInfo;
30  import org.metricshub.ipmi.core.coding.commands.fru.record.ChassisInfo;
31  import org.metricshub.ipmi.core.coding.commands.fru.record.FruRecord;
32  import org.metricshub.ipmi.core.coding.commands.fru.record.MultiRecordInfo;
33  import org.metricshub.ipmi.core.coding.commands.fru.record.ProductInfo;
34  import org.metricshub.ipmi.core.coding.commands.sdr.GetSdr;
35  import org.metricshub.ipmi.core.coding.commands.sdr.record.FruDeviceLocatorRecord;
36  import org.metricshub.ipmi.core.coding.payload.CompletionCode;
37  import org.metricshub.ipmi.core.coding.payload.IpmiPayload;
38  import org.metricshub.ipmi.core.coding.payload.lan.IPMIException;
39  import org.metricshub.ipmi.core.coding.payload.lan.IpmiLanRequest;
40  import org.metricshub.ipmi.core.coding.payload.lan.IpmiLanResponse;
41  import org.metricshub.ipmi.core.coding.payload.lan.NetworkFunction;
42  import org.metricshub.ipmi.core.coding.protocol.AuthenticationType;
43  import org.metricshub.ipmi.core.coding.protocol.IpmiMessage;
44  import org.metricshub.ipmi.core.coding.security.CipherSuite;
45  import org.metricshub.ipmi.core.common.TypeConverter;
46  
47  import java.security.InvalidKeyException;
48  import java.security.NoSuchAlgorithmException;
49  import java.util.ArrayList;
50  import java.util.List;
51  
52  /**
53   * A wrapper class for Read FRU Data Command request. <br>
54   * The command returns the specified data from the FRU Inventory Info area.
55   */
56  public class ReadFruData extends IpmiCommandCoder {
57  
58      private int offset;
59  
60      private int size;
61  
62      private int fruId;
63  
64      /**
65       * Initiates ReadFruData for both encoding and decoding. Sets session
66       * parameters to default.
67       *
68       * @see IpmiCommandCoder#setSessionParameters(IpmiVersion, CipherSuite,
69       *      AuthenticationType)
70       * @param fruId
71       *            - ID of the FRU to get info from. Must be less than 256. To
72       *            get FRU ID use {@link GetSdr} to retrieve
73       *            {@link FruDeviceLocatorRecord}.
74       * @param unit
75       *            - {@link BaseUnit} indicating if the FRU device is accessed in
76       *            {@link BaseUnit#Bytes} or {@link BaseUnit#Words}
77       * @param offset
78       *            - offset to read in units specified by unit
79       * @param countToRead
80       *            - size of the area to read in unit. Cannot exceed 255;
81       */
82      public ReadFruData(int fruId, BaseUnit unit, int offset, int countToRead) {
83          super();
84  
85          if (countToRead > 255) {
86              throw new IllegalArgumentException(
87                      "Count to read cannot exceed 255");
88          }
89  
90          if (fruId > 255) {
91              throw new IllegalArgumentException("FRU ID cannot exceed 255");
92          }
93  
94          this.offset = offset * unit.getSize();
95  
96          size = countToRead * unit.getSize();
97  
98          this.fruId = fruId;
99          // TODO: Check if Count To Read field is encoded in words if the FRU is
100         // addressed in words (requires different server settings).
101     }
102 
103     /**
104      * Initiates ReadFruData for both encoding and decoding.
105      *
106      * @param version
107      *            - IPMI version of the command.
108      * @param cipherSuite
109      *            - {@link CipherSuite} containing authentication,
110      *            confidentiality and integrity algorithms for this session.
111      * @param authenticationType
112      *            - Type of authentication used. Must be RMCPPlus for IPMI v2.0.
113      * @param fruId
114      *            - ID of the FRU to get info from. Must be less than 256. To
115      *            get FRU ID use {@link GetSdr} to retrieve
116      *            {@link FruDeviceLocatorRecord}.
117      * @param unit
118      *            - {@link BaseUnit} indicating if the FRU device is accessed in
119      *            {@link BaseUnit#Bytes} or {@link BaseUnit#Words}
120      * @param offset
121      *            - offset to read in units specified by unit
122      * @param countToRead
123      *            - size of the area to read in unit. Cannot exceed 255;
124      */
125     public ReadFruData(IpmiVersion version, CipherSuite cipherSuite,
126             AuthenticationType authenticationType, int fruId, BaseUnit unit,
127             int offset, int countToRead) {
128         super(version, cipherSuite, authenticationType);
129 
130         if (countToRead > 255) {
131             throw new IllegalArgumentException(
132                     "Count to read cannot exceed 255");
133         }
134 
135         if (fruId > 255) {
136             throw new IllegalArgumentException("FRU ID cannot exceed 255");
137         }
138 
139         this.offset = offset * unit.getSize();
140 
141         size = countToRead * unit.getSize();
142 
143         this.fruId = fruId;
144         // TODO: Check if Count To Read field is encoded in words if the FRU is
145         // addressed in words (requires different server settings).
146     }
147 
148     @Override
149     public byte getCommandCode() {
150         return CommandCodes.READ_FRU_DATA;
151     }
152 
153     @Override
154     public NetworkFunction getNetworkFunction() {
155         return NetworkFunction.StorageRequest;
156     }
157 
158     @Override
159     protected IpmiPayload preparePayload(int sequenceNumber)
160             throws NoSuchAlgorithmException, InvalidKeyException {
161         byte[] payload = new byte[4];
162         payload[0] = TypeConverter.intToByte(fruId);
163         byte[] buffer = TypeConverter.intToLittleEndianByteArray(offset);
164         payload[1] = buffer[0];
165         payload[2] = buffer[1];
166         payload[3] = TypeConverter.intToByte(size);
167 
168         return new IpmiLanRequest(getNetworkFunction(), getCommandCode(),
169                 payload, TypeConverter.intToByte(sequenceNumber));
170     }
171 
172     @Override
173     public ResponseData getResponseData(IpmiMessage message) throws IPMIException, NoSuchAlgorithmException, InvalidKeyException {
174 
175         if (!isCommandResponse(message)) {
176             throw new IllegalArgumentException(
177                     "This is not a response for Get SDR Repository Info command");
178         }
179         if (!(message.getPayload() instanceof IpmiLanResponse)) {
180             throw new IllegalArgumentException("Invalid response payload");
181         }
182         if (((IpmiLanResponse) message.getPayload()).getCompletionCode() != CompletionCode.Ok) {
183             throw new IPMIException(
184                     ((IpmiLanResponse) message.getPayload())
185                             .getCompletionCode());
186         }
187 
188         byte[] raw = message.getPayload().getIpmiCommandData();
189 
190         if (raw == null || raw.length < 2) {
191             throw new IllegalArgumentException(
192                     "Invalid response payload length");
193         }
194 
195         ReadFruDataResponseData responseData = new ReadFruDataResponseData();
196 
197         int sizeFromResponse = TypeConverter.byteToInt(raw[0]);
198 
199         byte[] fruData = new byte[sizeFromResponse];
200 
201         System.arraycopy(raw, 1, fruData, 0, sizeFromResponse);
202 
203         responseData.setFruData(fruData);
204 
205         return responseData;
206     }
207 
208     /**
209      * Decodes {@link FruRecord}s from data provided by {@link ReadFruData}
210      * command. Size of the FRU Inventory Area might exceed size of the
211      * communication packet so it might come in many
212      * {@link ReadFruDataResponseData} packets.
213      *
214      * @param fruData
215      *            - list of {@link ReadFruDataResponseData} containing FRU data
216      * @return list of {@link FruRecord}s containing decoded FRU data.
217      */
218     @SuppressWarnings("unused")
219     public static List<FruRecord> decodeFruData(
220             List<ReadFruDataResponseData> fruData) {
221 
222         int size = 0;
223 
224         ArrayList<FruRecord> list = new ArrayList<FruRecord>();
225 
226         for (ReadFruDataResponseData responseData : fruData) {
227             size += responseData.getFruData().length;
228         }
229 
230         byte[] data = new byte[size];
231 
232         int offset = 0;
233 
234         for (ReadFruDataResponseData responseData : fruData) {
235             int length = responseData.getFruData().length;
236             System.arraycopy(responseData.getFruData(), 0, data, offset, length);
237             offset += length;
238         }
239 
240         if (data[0] == 0x1) {
241 
242             int chassisOffset = TypeConverter.byteToInt(data[2]) * 8;
243             int boardOffset = TypeConverter.byteToInt(data[3]) * 8;
244             int productInfoOffset = TypeConverter.byteToInt(data[4]) * 8;
245             int multiRecordOffset = TypeConverter.byteToInt(data[5]) * 8;
246 
247             if (chassisOffset != 0) {
248                 list.add(new ChassisInfo(data, chassisOffset));
249             }
250             if (boardOffset != 0) {
251                 list.add(new BoardInfo(data, boardOffset));
252             }
253             if (productInfoOffset != 0) {
254                 list.add(new ProductInfo(data, productInfoOffset));
255             }
256             if (multiRecordOffset != 0) {
257                 addMultirecords(list, data, multiRecordOffset);
258             }
259         } else if (false) {
260             // TODO: Recognize SPD record (returned from DIMM FRUs)
261         } else {
262             throw new IllegalArgumentException("Invalid format version: " + data[0]);
263         }
264 
265         return list;
266     }
267 
268     private static void addMultirecords(ArrayList<FruRecord> list, byte[] data, int multiRecordOffset) {
269         int currentMultirecordOffset = multiRecordOffset;
270 
271         while ((TypeConverter.byteToInt(data[currentMultirecordOffset + 1]) & 0x80) == 0) {
272             list.add(MultiRecordInfo.populateMultiRecord(data, currentMultirecordOffset));
273             currentMultirecordOffset += TypeConverter.byteToInt(data[currentMultirecordOffset + 2]) + 5;
274         }
275     }
276 
277 }