1 package org.metricshub.ipmi.core.connection;
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.PayloadCoder;
26 import org.metricshub.ipmi.core.coding.protocol.Ipmiv20Message;
27 import org.metricshub.ipmi.core.connection.queue.MessageQueue;
28 import org.metricshub.ipmi.core.sm.StateMachine;
29 import org.metricshub.ipmi.core.sm.events.Sendv20Message;
30 import org.metricshub.ipmi.core.sm.states.SessionValid;
31
32 import org.slf4j.Logger;
33 import org.slf4j.LoggerFactory;
34
35 /**
36 * Class used for handling outgoing and incoming messages for a {@link Connection}.
37 */
38 public abstract class MessageHandler {
39
40 private static final Logger logger = LoggerFactory.getLogger(MessageHandler.class);
41
42 protected int lastReceivedSequenceNumber = 0;
43 protected final MessageQueue messageQueue;
44 protected final Connection connection;
45
46 public MessageHandler(Connection connection, int timeout, int minSequenceNumber, int maxSequenceNumber) {
47 this.messageQueue = new MessageQueue(connection, timeout, minSequenceNumber, maxSequenceNumber);
48 this.connection = connection;
49 }
50
51 /**
52 * Attempts to send message encoded by given {@link PayloadCoder} to the remote system.
53 *
54 * @param payloadCoder
55 * instance of {@link PayloadCoder} that will produce payload for the message being sent.
56 * @param stateMachine
57 * {@link StateMachine} for the currenr connection.
58 * @param sessionId
59 * ID of the current session.
60 * @param isOneWay
61 * flag indicating, if message is one way and we shouldn't await response,
62 * or it isn't and needs response from remote system
63 * @return sequence number of the sent message
64 * @throws ConnectionException when could not send message due to some problems with connection
65 */
66 public int sendMessage(PayloadCoder payloadCoder, StateMachine stateMachine, int sessionId, boolean isOneWay)
67 throws ConnectionException {
68 validateSessionState(stateMachine);
69
70 int seq = isOneWay ? messageQueue.getSequenceNumber() : messageQueue.add(payloadCoder);
71 if (seq > 0) {
72 stateMachine.doTransition(new Sendv20Message(payloadCoder, sessionId, seq, connection.getNextSessionSequenceNumber()));
73 }
74
75 return seq;
76 }
77
78 /**
79 * Attempts to retry sending message with given tag, assuming that this message exists in message queue.
80 *
81 * @param tag
82 * tag of the message that we want to resend
83 * @param stateMachine
84 * {@link StateMachine} for the currenr connection.
85 * @param sessionId
86 * ID of the current session.
87 * @return sequence number of the retried message (should be the same as original message tag) or -1 if no message was found in the queue.
88 * @throws ConnectionException when could not send message due to some problems with connection
89 */
90 public int retryMessage(int tag, StateMachine stateMachine, int sessionId) throws ConnectionException {
91 validateSessionState(stateMachine);
92
93 PayloadCoder payloadCoder = messageQueue.getMessageFromQueue(tag);
94
95 if (payloadCoder == null) {
96 return -1;
97 }
98
99 stateMachine.doTransition(new Sendv20Message(payloadCoder, sessionId, tag, connection.getNextSessionSequenceNumber()));
100
101 return tag;
102 }
103
104 private void validateSessionState(StateMachine stateMachine) throws ConnectionException {
105 if (stateMachine.getCurrent().getClass() != SessionValid.class) {
106 throw new ConnectionException("Illegal connection state: " + stateMachine.getCurrent().getClass().getSimpleName());
107 }
108 }
109
110 /**
111 * Checks if received message is inside "sliding window range", and if it is,
112 * further processes the message in a cimplementation-specific way.
113 *
114 * @param message
115 */
116 public void handleIncomingMessage(Ipmiv20Message message) {
117
118 int seq = message.getSessionSequenceNumber();
119
120 if (seq != 0 && (seq > lastReceivedSequenceNumber + 15 || seq < lastReceivedSequenceNumber - 16)) {
121 logger.debug("Dropping message " + seq);
122 return; // if the message's sequence number gets out of the sliding
123 // window range we need to drop it
124 }
125
126 if (seq != 0) {
127 lastReceivedSequenceNumber = (seq > lastReceivedSequenceNumber ? seq : lastReceivedSequenceNumber);
128 }
129
130 handleIncomingMessageInternal(message);
131 }
132
133 public void setTimeout(int timeout) {
134 messageQueue.setTimeout(timeout);
135 }
136
137 public void tearDown() {
138 messageQueue.tearDown();
139 }
140
141 public int getSequenceNumber() {
142 return messageQueue.getSequenceNumber();
143 }
144
145 /**
146 * Abstract method for implementation-specific logic for handling incomming IPMI message.
147 *
148 * @param message
149 * IPMI message received from BMC
150 */
151 protected abstract void handleIncomingMessageInternal(Ipmiv20Message message);
152
153 }