1 package org.metricshub.ipmi.core.sm;
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 java.io.IOException;
26 import java.net.InetAddress;
27 import java.util.ArrayList;
28 import java.util.List;
29
30 import org.metricshub.ipmi.core.coding.rmcp.RmcpDecoder;
31 import org.metricshub.ipmi.core.common.Constants;
32 import org.metricshub.ipmi.core.sm.actions.StateMachineAction;
33 import org.metricshub.ipmi.core.sm.events.StateMachineEvent;
34 import org.metricshub.ipmi.core.sm.states.SessionValid;
35 import org.metricshub.ipmi.core.sm.states.State;
36 import org.metricshub.ipmi.core.sm.states.Uninitialized;
37 import org.metricshub.ipmi.core.transport.Messenger;
38 import org.metricshub.ipmi.core.transport.UdpListener;
39 import org.metricshub.ipmi.core.transport.UdpMessage;
40
41 /**
42 * State machine for connecting and acquiring session with the remote host via
43 * IPMI v.2.0.
44 */
45 public class StateMachine implements UdpListener {
46
47 private List<MachineObserver> observers;
48
49 private State current;
50
51 private Messenger messenger;
52 private InetAddress remoteMachineAddress;
53 private int remoteMachinePort;
54
55 private boolean initialized;
56
57 public State getCurrent() {
58 return current;
59 }
60
61 public void setCurrent(State current) {
62 this.current = current;
63 current.onEnter(this);
64 }
65
66 /**
67 * Initializes the State Machine
68 *
69 * @param messenger
70 * - {@link Messenger} connected to the
71 * {@link Constants#IPMI_PORT}
72 */
73 public StateMachine(Messenger messenger) {
74 this.messenger = messenger;
75 observers = new ArrayList<MachineObserver>();
76 initialized = false;
77 }
78
79 /**
80 * Sends message via {@link #messenger} to the managed system.
81 *
82 * @param message
83 * - the encoded message
84 * @throws IOException
85 * - when sending of the message fails
86 */
87 public void sendMessage(byte[] message) throws IOException {
88 UdpMessage udpMessage = new UdpMessage();
89 udpMessage.setAddress(getRemoteMachineAddress());
90 udpMessage.setPort(getRemoteMachinePort());
91 udpMessage.setMessage(message);
92 messenger.send(udpMessage);
93 }
94
95 public InetAddress getRemoteMachineAddress() {
96 return remoteMachineAddress;
97 }
98
99 public int getRemoteMachinePort() {
100 return remoteMachinePort;
101 }
102
103 /**
104 * Sends a notification of an action to all {@link MachineObserver}s
105 *
106 * @param action
107 * - a {@link StateMachineAction} to perform
108 */
109 public void doExternalAction(StateMachineAction action) {
110 for (MachineObserver observer : observers) {
111 if (observer != null) {
112 observer.notify(action);
113 }
114 }
115 }
116
117 /**
118 * Sets the State Machine in the initial state.
119 *
120 * @param address
121 * - IP address of the remote machine.
122 * @param port
123 * - UDP remoteMachinePort of the remote machine
124 * @see #stop()
125 */
126 public void start(InetAddress address, int port) {
127 messenger.register(this);
128 remoteMachineAddress = address;
129 this.remoteMachinePort = port;
130 setCurrent(new Uninitialized());
131 initialized = true;
132 }
133
134 /**
135 * Cleans up the machine resources.
136 *
137 * @see #start(InetAddress, int)
138 */
139 public void stop() {
140 messenger.unregister(this);
141 initialized = false;
142 }
143
144 /**
145 * @return true if {@link StateMachine} is initialized, false otherwise.
146 * @see #start(InetAddress, int)
147 * @see #stop()
148 */
149 public boolean isActive() {
150 return initialized;
151 }
152
153 /**
154 * Performs a {@link State} transition according to the event and
155 * {@link #current} state
156 *
157 * @param event
158 * - {@link StateMachineEvent} invoking the transition
159 * @throws NullPointerException
160 * - when machine was not yet started
161 * @see #start(InetAddress, int)
162 */
163 public void doTransition(StateMachineEvent event) {
164 if (!initialized) {
165 throw new NullPointerException("State machine not started");
166 }
167 current.doTransition(this, event);
168 }
169
170 @Override
171 public void notifyMessage(UdpMessage message) {
172 if (message.getAddress().equals(getRemoteMachineAddress()) && message.getPort() == getRemoteMachinePort()) {
173 current.doAction(this, RmcpDecoder.decode(message.getMessage()));
174 }
175 }
176
177 /**
178 * Registers the listener in the {@link StateMachine} so it will be notified
179 * of the {@link StateMachineAction}s performed via
180 * {@link #doExternalAction(StateMachineAction)}
181 *
182 * @param observer
183 * - {@link MachineObserver} to register
184 */
185 public void register(MachineObserver observer) {
186 observers.add(observer);
187 }
188
189 /**
190 * @return true if {@link StateMachine} is at the point when it acquires
191 * session and will send sessionless messages
192 */
193 public boolean isSessionChallenging() {
194 return !initialized || getCurrent().getClass() == SessionValid.class;
195 }
196 }