View Javadoc
1   package org.metricshub.ipmi.core.coding.protocol.encoder;
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.protocol.AuthenticationType;
26  import org.metricshub.ipmi.core.coding.protocol.IpmiMessage;
27  import org.metricshub.ipmi.core.coding.protocol.Ipmiv20Message;
28  import org.metricshub.ipmi.core.coding.protocol.PayloadType;
29  import org.metricshub.ipmi.core.common.TypeConverter;
30  
31  import java.security.InvalidKeyException;
32  
33  /**
34   * Encodes IPMI v2.0 message.
35   */
36  public class Protocolv20Encoder extends ProtocolEncoder {
37      /**
38       * @param ipmiMessage
39       *            - IPMI message to be encoded. Must be {@link Ipmiv20Message}.
40       * @throws IllegalArgumentException
41       *             when IPMI protocol version or authentication type is
42       *             incorrect.
43       * @throws InvalidKeyException
44       *             - when initiation of the confidentiality algorithm fails
45       * @see Ipmiv20Message
46       */
47      @Override
48      public byte[] encode(IpmiMessage ipmiMessage) throws InvalidKeyException {
49          if (!(ipmiMessage instanceof Ipmiv20Message)) {
50              throw new IllegalArgumentException(
51                      "IPMIMessage must be in 2.0 version.");
52          }
53          Ipmiv20Message message = (Ipmiv20Message) ipmiMessage;
54  
55          byte[] payload = message.getPayload().getEncryptedPayload();
56  
57          if (payload == null) {
58              message.getPayload().encryptPayload(
59                      message.getConfidentialityAlgorithm());
60              payload = message.getPayload().getEncryptedPayload();
61          }
62  
63          byte[] raw = new byte[getMessageLength(message)];
64  
65          if (message.getAuthenticationType() != AuthenticationType.RMCPPlus) {
66              throw new IllegalArgumentException(
67                      "Authentication type must be RMCP+ for IPMI v2.0");
68          }
69  
70          raw[0] = encodeAuthenticationType(message.getAuthenticationType());
71  
72          int offset = 1;
73  
74          raw[offset] = encodePayloadType(message.isPayloadEncrypted(),
75                  message.isPayloadAuthenticated(), message.getPayloadType());
76  
77          ++offset;
78  
79          if (message.getPayloadType() == PayloadType.Oem) {
80              encodeOEMIANA(message.getOemIANA(), raw, offset);
81              offset += 4;
82  
83              encodeOEMPayloadId(message.getOemPayloadID(), raw, offset);
84              offset += 2;
85          }
86  
87          encodeSessionId(message.getSessionID(), raw, offset);
88          offset += 4;
89  
90          encodeSessionSequenceNumber(message.getSessionSequenceNumber(), raw,
91                  offset);
92          offset += 4;
93  
94          encodePayloadLength(payload.length, raw, offset);
95          offset += 2;
96  
97          offset = encodePayload(payload, raw, offset);
98  
99          if (message.isPayloadAuthenticated() && message.getSessionID() != 0) {
100             encodeSessionTrailer(message.getAuthCode(), raw, offset);
101         }
102 
103         return raw;
104     }
105 
106     /**
107      * Calculates length of the IPMI message.
108      *
109      * @param ipmiMessage
110      *            - message which length is to be calculated
111      */
112     private int getMessageLength(Ipmiv20Message ipmiMessage) {
113         int length = 12
114                 + ipmiMessage.getConfidentialityAlgorithm()
115                         .getConfidentialityOverheadSize(
116                                 ipmiMessage.getPayloadLength())
117                 + ipmiMessage.getPayloadLength();
118 
119         if (ipmiMessage.getPayloadType() == PayloadType.Oem) {
120             length += 6;
121         }
122 
123         if (ipmiMessage.isPayloadAuthenticated()
124                 && ipmiMessage.getSessionID() != 0) {
125             if (ipmiMessage.getAuthCode() != null) {
126                 if ((length + ipmiMessage.getAuthCode().length + 2) % 4 != 0) {
127                     length += 4 - (length + ipmiMessage.getAuthCode().length + 2) % 4;
128                 }
129                 length += ipmiMessage.getAuthCode().length;
130             }
131             length += 2;
132         }
133 
134         return length;
135     }
136 
137     private byte encodePayloadType(boolean isEncrypted,
138             boolean isAuthenticated, PayloadType payloadType) {
139         byte result = 0;
140 
141         if (isEncrypted) {
142             result |= TypeConverter.intToByte(0x80);
143         }
144 
145         if (isAuthenticated) {
146             result |= TypeConverter.intToByte(0x40);
147         }
148 
149         result |= TypeConverter.intToByte(payloadType.getCode());
150 
151         return result;
152     }
153 
154     /**
155      * Encodes OEM IANA and inserts it into message at given offset.
156      *
157      * @param value
158      * @param message
159      *            - IPMI message being created
160      * @param offset
161      * @throws IndexOutOfBoundsException
162      *             when message is too short to hold value at given offset
163      */
164     private void encodeOEMIANA(int value, byte[] message, int offset) {
165         encodeInt(value, message, offset);
166     }
167 
168     /**
169      * Encodes OEM payload ID and inserts it into message at given offset. To
170      * implement manufacturer-specific OEM Payload ID encoding, override this
171      * function.
172      *
173      * @param value
174      * @param message
175      *            - IPMI message being created
176      * @param offset
177      * @throws IndexOutOfBoundsException
178      *             when message is too short to hold value at given offset
179      * @throws IllegalArgumentException
180      *             when value is incorrect.
181      */
182     protected void encodeOEMPayloadId(Object value, byte[] message, int offset) {
183         byte[] oemId = null;
184         try {
185             oemId = (byte[]) value;
186         } catch (Exception e) {
187             throw new IllegalArgumentException("Value is corrupted", e);
188         }
189         if (oemId.length != 2) {
190             throw new IllegalArgumentException("Value has invalid length");
191         }
192         if (oemId.length + offset > message.length) {
193             throw new IndexOutOfBoundsException("Message is too short");
194         }
195 
196         System.arraycopy(oemId, 0, message, offset, 2);
197     }
198 
199     @Override
200     protected void encodePayloadLength(int value, byte[] message, int offset) {
201         byte[] payloadLength = TypeConverter.intToLittleEndianByteArray(value);
202         message[offset] = payloadLength[0];
203         message[offset + 1] = payloadLength[1];
204     }
205 
206     /**
207      * Creates session trailer. <br>
208      * Encodes Authorization Code and inserts it into message. <br>
209      * Adds integrity pad if needed and inserts pad length and next header
210      * fields.
211      *
212      * @param authCode
213      *            - Value of the Authorization Code
214      * @param message
215      *            - IPMI message being created
216      * @param offset
217      *            - Should point at the beginning of the session trailer.
218      * @throws IndexOutOfBoundsException
219      *             when message is too short to hold value at given offset
220      * @return Offset pointing after Authorization Code
221      */
222     private int encodeSessionTrailer(final byte[] authCode, final byte[] message, final int offset) {
223         int pad = 0;
224 
225         if (authCode != null && authCode.length + offset > message.length) {
226             throw new IndexOutOfBoundsException("Message is too short");
227         }
228 
229         if (authCode != null) {
230             pad = (offset + authCode.length + 2) % 4;
231         }
232 
233         if (pad > 0) {
234             pad = 4 - pad;
235         } else {
236             pad = 0;
237         }
238 
239         int currentOffset = offset;
240 
241         for (int i = 0; i < pad; ++i) {
242             message[currentOffset] = TypeConverter.intToByte(0xff);
243             ++currentOffset;
244         }
245 
246         message[currentOffset] = TypeConverter.intToByte(pad);
247         ++currentOffset;
248 
249         // Next header - reserved
250         message[currentOffset] = TypeConverter.intToByte(0x07);
251         ++currentOffset;
252         if (authCode != null) {
253             System.arraycopy(authCode, 0, message, currentOffset, authCode.length);
254             currentOffset += authCode.length;
255         }
256 
257         return currentOffset;
258     }
259 }