View Javadoc
1   package org.metricshub.ipmi.core.sm.states;
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.Encoder;
26  import org.metricshub.ipmi.core.coding.PayloadCoder;
27  import org.metricshub.ipmi.core.coding.commands.IpmiVersion;
28  import org.metricshub.ipmi.core.coding.commands.session.CloseSession;
29  import org.metricshub.ipmi.core.coding.protocol.AuthenticationType;
30  import org.metricshub.ipmi.core.coding.protocol.Ipmiv20Message;
31  import org.metricshub.ipmi.core.coding.protocol.PayloadType;
32  import org.metricshub.ipmi.core.coding.protocol.decoder.ProtocolDecoder;
33  import org.metricshub.ipmi.core.coding.protocol.decoder.Protocolv20Decoder;
34  import org.metricshub.ipmi.core.coding.protocol.encoder.Protocolv20Encoder;
35  import org.metricshub.ipmi.core.coding.rmcp.RmcpMessage;
36  import org.metricshub.ipmi.core.coding.security.CipherSuite;
37  import org.metricshub.ipmi.core.sm.StateMachine;
38  import org.metricshub.ipmi.core.sm.actions.ErrorAction;
39  import org.metricshub.ipmi.core.sm.actions.MessageAction;
40  import org.metricshub.ipmi.core.sm.events.Sendv20Message;
41  import org.metricshub.ipmi.core.sm.events.StateMachineEvent;
42  import org.metricshub.ipmi.core.sm.events.Timeout;
43  
44  /**
45   * {@link State} in which the session is valid and sending IPMI commands to the
46   * remote machine is enabled.
47   * <ul>
48   * <li>Sends an IPMI v2.0 message on
49   * {@link Sendv20Message}
50   * <li>Transits to {@link Authcap} on {@link Timeout}
51   * <li>Sends {@link CloseSession}
52   * and transits to {@link Authcap} on {@link org.metricshub.ipmi.core.coding.commands.session.CloseSession}</li>
53   * </ul>
54   */
55  public class SessionValid extends State {
56  
57  	private CipherSuite cipherSuite;
58  
59  	private int sessionId;
60  
61  	public CipherSuite getCipherSuite() {
62  		return cipherSuite;
63  	}
64  
65  	/**
66  	 * Initiates the state.
67  	 *
68  	 * @param cipherSuite
69  	 *        - {@link CipherSuite} used during the session.
70  	 */
71  	public SessionValid(CipherSuite cipherSuite, int sessionId) {
72  		this.cipherSuite = cipherSuite;
73  		this.sessionId = sessionId;
74  	}
75  
76  	@Override
77  	public void doTransition(StateMachine stateMachine, StateMachineEvent machineEvent) {
78  		if (machineEvent instanceof Sendv20Message) {
79  			Sendv20Message event = (Sendv20Message) machineEvent;
80  			try {
81  				stateMachine.sendMessage(encode(event));
82  			} catch (Exception e) {
83  				stateMachine.doExternalAction(new ErrorAction(e));
84  			}
85  		} else if (machineEvent instanceof Timeout) {
86  			stateMachine.setCurrent(new Authcap());
87  		} else if (machineEvent instanceof org.metricshub.ipmi.core.sm.events.CloseSession) {
88  			org.metricshub.ipmi.core.sm.events.CloseSession event = (org.metricshub.ipmi.core.sm.events.CloseSession) machineEvent;
89  
90  			try {
91  				stateMachine.setCurrent(new Authcap());
92  				stateMachine.sendMessage(
93  						Encoder.encode(
94  								new Protocolv20Encoder(),
95  								closeSession(event.getSessionId()),
96  								event.getMessageSequenceNumber(),
97  								event.getSessionSequenceNumber(),
98  								event.getSessionId()));
99  			} catch (Exception e) {
100 				stateMachine.setCurrent(this);
101 				stateMachine.doExternalAction(new ErrorAction(e));
102 			}
103 		} else {
104 			stateMachine.doExternalAction(new ErrorAction(new IllegalArgumentException("Invalid transition")));
105 		}
106 
107 	}
108 
109 	/**
110 	 * Encodes the command of the event with the parameters of this session. The session signs and encrypts the
111 	 * message, so the command carries no session parameters of its own: they are set on it and the message encoded
112 	 * under the lock of the coder, as one command instance may be sent on several connections at once.
113 	 */
114 	private byte[] encode(Sendv20Message event) throws Exception {
115 		PayloadCoder coder = event.getPayloadCoder();
116 		synchronized (coder) {
117 			coder.setSessionParameters(IpmiVersion.V20, cipherSuite, AuthenticationType.RMCPPlus);
118 			return Encoder.encode(
119 					new Protocolv20Encoder(),
120 					coder,
121 					event.getMessageSequenceNumber(),
122 					event.getSessionSequenceNumber(),
123 					event.getSessionId());
124 		}
125 	}
126 
127 	private CloseSession closeSession(int closedSessionId) {
128 		CloseSession closeSession = new CloseSession(closedSessionId);
129 		closeSession.setSessionParameters(IpmiVersion.V20, cipherSuite, AuthenticationType.RMCPPlus);
130 		return closeSession;
131 	}
132 
133 	@Override
134 	public void doAction(StateMachine stateMachine, RmcpMessage message) {
135 		if (ProtocolDecoder.decodeAuthenticationType(message) != AuthenticationType.RMCPPlus) {
136 			return; // this isn't IPMI v2.0 message so we ignore it
137 		}
138 		if (Protocolv20Decoder.decodeSessionID(message) == 0) {
139 			return; // this is a sessionless message so we drop it
140 		}
141 		Protocolv20Decoder decoder = new Protocolv20Decoder(cipherSuite);
142 		PayloadType payloadType = Protocolv20Decoder.decodePayloadType(message.getData()[1]);
143 
144 		if (payloadType != PayloadType.Ipmi && payloadType != PayloadType.Sol) {
145 			return;
146 		}
147 		if (Protocolv20Decoder.decodeSessionID(message) != sessionId) {
148 			return; // this message belongs to other session so we ignore it
149 		}
150 		try {
151 			Ipmiv20Message message20 = (Ipmiv20Message) decoder.decode(message);
152 			if (message20.getSessionID() == sessionId) {
153 				stateMachine.doExternalAction(new MessageAction(message20));
154 			}
155 		} catch (Exception e) {
156 			// A message that fails the integrity check or cannot be decoded is discarded (IPMI 2.0 section 6.12.4):
157 			// the request it may have answered is sent again when it times out
158 		}
159 	}
160 
161 }