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