1 package org.sentrysoftware.ipmi.core.api.sol;
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25 import org.sentrysoftware.ipmi.core.api.async.ConnectionHandle;
26 import org.sentrysoftware.ipmi.core.api.async.InboundSolMessageListener;
27 import org.sentrysoftware.ipmi.core.api.sync.IpmiConnector;
28 import org.sentrysoftware.ipmi.core.coding.PayloadCoder;
29 import org.sentrysoftware.ipmi.core.coding.commands.IpmiVersion;
30 import org.sentrysoftware.ipmi.core.coding.commands.PrivilegeLevel;
31 import org.sentrysoftware.ipmi.core.coding.commands.payload.ActivateSolPayload;
32 import org.sentrysoftware.ipmi.core.coding.commands.payload.ActivateSolPayloadResponseData;
33 import org.sentrysoftware.ipmi.core.coding.commands.payload.DeactivatePayload;
34 import org.sentrysoftware.ipmi.core.coding.commands.payload.GetPayloadActivationStatus;
35 import org.sentrysoftware.ipmi.core.coding.commands.payload.GetPayloadActivationStatusResponseData;
36 import org.sentrysoftware.ipmi.core.coding.commands.session.SetSessionPrivilegeLevel;
37 import org.sentrysoftware.ipmi.core.coding.payload.CompletionCode;
38 import org.sentrysoftware.ipmi.core.coding.payload.lan.IPMIException;
39 import org.sentrysoftware.ipmi.core.coding.payload.sol.SolAckState;
40 import org.sentrysoftware.ipmi.core.coding.payload.sol.SolMessage;
41 import org.sentrysoftware.ipmi.core.coding.payload.sol.SolOperation;
42 import org.sentrysoftware.ipmi.core.coding.protocol.AuthenticationType;
43 import org.sentrysoftware.ipmi.core.coding.protocol.PayloadType;
44 import org.sentrysoftware.ipmi.core.coding.security.CipherSuite;
45 import org.sentrysoftware.ipmi.core.coding.sol.SolCoder;
46 import org.sentrysoftware.ipmi.core.coding.sol.SolResponseData;
47 import org.sentrysoftware.ipmi.core.common.Constants;
48 import org.sentrysoftware.ipmi.core.common.TypeConverter;
49 import org.sentrysoftware.ipmi.core.connection.Session;
50 import org.sentrysoftware.ipmi.core.connection.SessionException;
51 import org.sentrysoftware.ipmi.core.connection.SessionManager;
52
53 import org.slf4j.Logger;
54 import org.slf4j.LoggerFactory;
55
56 import java.io.Closeable;
57 import java.io.IOException;
58 import java.nio.charset.Charset;
59 import java.util.Arrays;
60 import java.util.HashSet;
61 import java.util.LinkedList;
62 import java.util.List;
63 import java.util.Set;
64
65
66
67
68 public class SerialOverLan implements Closeable {
69
70 private static final Logger logger = LoggerFactory.getLogger(SerialOverLan.class);
71
72 private final IpmiConnector connector;
73 private final Session session;
74 private final InboundSolMessageListener inboundMessageListener;
75 private final List<SolEventListener> eventListeners;
76
77 private boolean isSessionInternal;
78 private int payloadInstance;
79 private int maxPayloadSize;
80 private boolean closed;
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102 public SerialOverLan(IpmiConnector connector, String remoteHost, int remotePort, String user, String password,
103 CipherSuiteSelectionHandler cipherSuiteSelectionHandler) throws SOLException, SessionException {
104 this(connector, SessionManager.establishSession(connector, remoteHost, remotePort, user, password, cipherSuiteSelectionHandler));
105
106 this.isSessionInternal = true;
107 }
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127 public SerialOverLan(IpmiConnector connector, String remoteHost, String user, String password,
128 CipherSuiteSelectionHandler cipherSuiteSelectionHandler) throws SOLException, SessionException {
129 this(connector, remoteHost, Constants.IPMI_PORT, user, password, cipherSuiteSelectionHandler);
130 }
131
132
133
134
135
136
137
138
139
140
141
142 public SerialOverLan(IpmiConnector connector, Session session) throws SOLException, SessionException {
143 this.connector = connector;
144
145 int solPayloadPort = activatePayload(connector, session.getConnectionHandle());
146
147 this.session = resolveSession(connector, session.getConnectionHandle(), session, solPayloadPort);
148
149 this.eventListeners = new LinkedList<SolEventListener>();
150 this.inboundMessageListener = new InboundSolMessageListener(connector,
151 this.session.getConnectionHandle(), eventListeners);
152
153 connector.registerIncomingMessageListener(inboundMessageListener);
154 this.closed = false;
155 }
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175 private Session resolveSession(IpmiConnector connector, ConnectionHandle connectionHandle, Session session, int solPayloadPort) throws SOLException, SessionException {
176 if (solPayloadPort != connectionHandle.getRemotePort()) {
177 Session alternativeSession = connector.getExistingSessionForCriteria(connectionHandle.getRemoteAddress(),
178 solPayloadPort, connectionHandle.getUser());
179
180 if (alternativeSession == null) {
181 CipherSuiteSelectionHandler cipherSuiteSelector = new SpecificCipherSuiteSelector(connectionHandle.getCipherSuite());
182
183 alternativeSession = SessionManager.establishSession(connector, connectionHandle.getRemoteAddress().getHostAddress(),
184 solPayloadPort, connectionHandle.getUser(), connectionHandle.getPassword(), cipherSuiteSelector);
185 this.isSessionInternal = true;
186
187 } else {
188 this.isSessionInternal = false;
189 }
190
191 activatePayload(connector, alternativeSession.getConnectionHandle());
192
193 return alternativeSession;
194 } else {
195 this.isSessionInternal = false;
196 return session;
197 }
198 }
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213 private int activatePayload(IpmiConnector connector, ConnectionHandle connectionHandle) throws SOLException {
214 try {
215 this.payloadInstance = getFirstAvailablePayloadInstance(connector, connectionHandle);
216
217 ActivateSolPayload activatePayload = new ActivateSolPayload(connectionHandle.getCipherSuite(), payloadInstance);
218 ActivateSolPayloadResponseData activatePayloadResponseData = getActivatePayloadResponse(connector,
219 connectionHandle, activatePayload);
220
221 this.maxPayloadSize = activatePayloadResponseData.getInboundPayloadSize();
222
223 return activatePayloadResponseData.getPayloadUdpPortNumber();
224
225 } catch (Exception e) {
226 throw new SOLException("Cannot activate SOL payload due to exception during activation process", e);
227 }
228 }
229
230 private ActivateSolPayloadResponseData getActivatePayloadResponse(IpmiConnector connector, ConnectionHandle connectionHandle, ActivateSolPayload activatePayload) throws Exception {
231 ActivateSolPayloadResponseData activatePayloadResponseData;
232
233 try {
234 activatePayloadResponseData = (ActivateSolPayloadResponseData) connector.sendMessage(connectionHandle,
235 activatePayload);
236 } catch (IPMIException e) {
237 if (e.getCompletionCode() == CompletionCode.InsufficentPrivilege) {
238 raiseSessionPrivileges(connector, connectionHandle);
239
240 activatePayloadResponseData = (ActivateSolPayloadResponseData) connector.sendMessage(
241 connectionHandle, activatePayload);
242 } else {
243 throw e;
244 }
245 }
246
247 return activatePayloadResponseData;
248 }
249
250
251
252
253
254
255
256
257
258
259
260
261 private int getFirstAvailablePayloadInstance(IpmiConnector connector, ConnectionHandle connectionHandle) throws Exception {
262 GetPayloadActivationStatus getPayloadActivationStatus = new GetPayloadActivationStatus(connectionHandle.getCipherSuite(),
263 PayloadType.Sol);
264 GetPayloadActivationStatusResponseData getActivationResponseData = (GetPayloadActivationStatusResponseData) connector.sendMessage(
265 connectionHandle, getPayloadActivationStatus);
266
267 if (getActivationResponseData.getInstanceCapacity() <= 0 || getActivationResponseData.getAvailableInstances().isEmpty()) {
268 throw new SOLException("Cannot activate SOL payload, as there are no available payload instances.");
269 }
270
271 return TypeConverter.byteToInt(getActivationResponseData.getAvailableInstances().get(0));
272 }
273
274
275
276
277
278
279
280
281
282
283
284 private void raiseSessionPrivileges(IpmiConnector connector, ConnectionHandle connectionHandle) throws Exception {
285 SetSessionPrivilegeLevel setSessionPrivilegeLevel = new SetSessionPrivilegeLevel(IpmiVersion.V20,
286 connectionHandle.getCipherSuite(), AuthenticationType.RMCPPlus, PrivilegeLevel.Administrator);
287 connector.sendMessage(connectionHandle, setSessionPrivilegeLevel);
288 }
289
290
291
292
293
294
295
296
297
298
299 public boolean writeByte(byte singleByte) {
300 return writeBytes(new byte[] {singleByte});
301 }
302
303
304
305
306
307
308
309
310
311 public boolean writeBytes(byte[] buffer) {
312 boolean result = true;
313
314 int maxBufferSize = maxPayloadSize - SolMessage.PAYLOAD_HEADER_LENGTH;
315 byte[] remainingBytes = buffer;
316 int currentIndex = 0;
317
318 while (remainingBytes.length - currentIndex > maxBufferSize) {
319 byte[] bufferChunk = Arrays.copyOfRange(remainingBytes, currentIndex, maxBufferSize);
320 currentIndex += maxBufferSize;
321
322 result &= sendMessage(bufferChunk);
323
324 if (!result) {
325 return false;
326 }
327 }
328
329 if (remainingBytes.length - currentIndex > 0) {
330 remainingBytes = Arrays.copyOfRange(remainingBytes, currentIndex, remainingBytes.length);
331 result &= sendMessage(remainingBytes);
332 }
333
334 return result;
335 }
336
337
338
339
340
341
342
343
344
345 public boolean writeInt(int singleInt) {
346 return writeBytes(new byte[] {TypeConverter.intToByte(singleInt)});
347 }
348
349
350
351
352
353
354
355
356
357 public boolean writeIntArray(int[] buffer) {
358 byte[] byteBuffer = new byte[buffer.length];
359
360 for (int i = 0; i < buffer.length; i++) {
361 byteBuffer[i] = TypeConverter.intToByte(buffer[i]);
362 }
363
364 return writeBytes(byteBuffer);
365 }
366
367
368
369
370
371
372
373
374
375 public boolean writeString(String string) {
376 return writeBytes(string.getBytes());
377 }
378
379
380
381
382
383
384
385
386
387
388
389 public boolean writeString(String string, Charset charset) {
390 return writeBytes(string.getBytes(charset));
391 }
392
393
394
395
396
397
398
399 public byte[] readBytes() {
400 return readBytes(inboundMessageListener.getAvailableBytesCount());
401 }
402
403
404
405
406
407
408
409
410
411
412 public byte[] readBytes(int byteCount) {
413 return inboundMessageListener.readBytes(byteCount);
414 }
415
416
417
418
419
420
421
422
423
424
425
426
427 public byte[] readBytes(int byteCount, int timeout) {
428 waitForData(byteCount, timeout);
429
430 return readBytes(byteCount);
431 }
432
433
434
435
436
437
438
439 public int[] readIntArray() {
440 return readIntArray(inboundMessageListener.getAvailableBytesCount());
441 }
442
443
444
445
446
447
448
449
450
451
452 public int[] readIntArray(int byteCount) {
453 byte[] bytesArray = readBytes(byteCount);
454 int[] intArray = new int[bytesArray.length];
455
456 for (int i = 0; i < bytesArray.length; i++) {
457 intArray[i] = TypeConverter.byteToInt(bytesArray[i]);
458 }
459
460 return intArray;
461 }
462
463
464
465
466
467
468
469
470
471
472
473
474 public int[] readIntArray(int byteCount, int timeout) {
475 waitForData(byteCount, timeout);
476
477 return readIntArray(byteCount);
478 }
479
480
481
482
483
484
485
486 public String readString() {
487 return readString(inboundMessageListener.getAvailableBytesCount());
488 }
489
490
491
492
493
494
495
496
497
498 public String readString(int byteCount) {
499 return new String(readBytes(byteCount));
500 }
501
502
503
504
505
506
507
508
509
510
511
512
513 public String readString(int byteCount, int timeout) {
514 waitForData(byteCount, timeout);
515
516 return readString(byteCount);
517 }
518
519
520
521
522
523
524
525
526
527
528 public String readString(Charset charset) {
529 return readString(charset, inboundMessageListener.getAvailableBytesCount());
530 }
531
532
533
534
535
536
537
538
539
540
541
542 public String readString(Charset charset, int byteCount) {
543 return new String(readBytes(byteCount), charset);
544 }
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559 public String readString(Charset charset, int byteCount, int timeout) {
560 waitForData(byteCount, timeout);
561
562 return readString(charset, byteCount);
563 }
564
565 private void waitForData(int wantedByteCount, int timeout) {
566 long startTime = System.currentTimeMillis();
567
568 while (isTooFewBytesAvailable(wantedByteCount) && timeoutNotHit(timeout, startTime)) {
569
570 }
571 }
572
573 private boolean isTooFewBytesAvailable(int wantedByteCount) {
574 return inboundMessageListener.getAvailableBytesCount() < wantedByteCount;
575 }
576
577 private boolean timeoutNotHit(int timeout, long startTime) {
578 return System.currentTimeMillis() - startTime < timeout;
579 }
580
581
582
583
584
585
586
587
588 public boolean invokeOperations(SolOperation... operations) {
589 Set<SolOperation> operationSet = new HashSet<SolOperation>();
590
591 for (SolOperation operation : operations) {
592 operationSet.add(operation);
593 }
594
595 return sendMessage(operationSet);
596 }
597
598
599
600
601
602
603
604 public void registerEventListener(SolEventListener listener) {
605 eventListeners.add(listener);
606 }
607
608
609
610
611
612
613
614 public void unregisterEventListener(SolEventListener listener) {
615 eventListeners.remove(listener);
616 }
617
618 private boolean sendMessage(byte[] characterData) {
619 SolCoder payloadCoder = new SolCoder(characterData, session.getConnectionHandle().getCipherSuite());
620
621 SolResponseData responseData = sendPayload(payloadCoder);
622
623 notifyResponseListeners(characterData, new HashSet<SolOperation>(), responseData);
624
625 byte[] remainingCharacterData = characterData;
626
627 while (isNackForMessagePart(responseData, remainingCharacterData.length)) {
628 remainingCharacterData = Arrays.copyOfRange(remainingCharacterData,
629 responseData.getAcceptedCharactersNumber(), remainingCharacterData.length);
630
631 SolCoder partialPayloadCoder = new SolCoder(remainingCharacterData, session.getConnectionHandle().getCipherSuite());
632 responseData = sendPayload(partialPayloadCoder);
633
634 notifyResponseListeners(remainingCharacterData, new HashSet<SolOperation>(), responseData);
635 }
636
637 return responseData != null && responseData.getAcknowledgeState() == SolAckState.ACK;
638 }
639
640 private boolean sendMessage(Set<SolOperation> operations) {
641 SolCoder payloadCoder = new SolCoder(operations, session.getConnectionHandle().getCipherSuite());
642
643 SolResponseData responseData = sendPayload(payloadCoder);
644
645 notifyResponseListeners(new byte[0], operations, responseData);
646
647 return responseData != null && responseData.getAcknowledgeState() == SolAckState.ACK;
648 }
649
650 private void notifyResponseListeners(byte[] characterData, Set<SolOperation> solOperations, SolResponseData responseData) {
651 if (responseData != null && responseData.getStatuses() != null && !responseData.getStatuses().isEmpty()) {
652 for (SolEventListener listener : eventListeners) {
653 listener.processResponseEvent(responseData.getStatuses(), characterData, solOperations);
654 }
655 }
656 }
657
658 private SolResponseData sendPayload(PayloadCoder payloadCoder) {
659 ConnectionHandle connectionHandle = session.getConnectionHandle();
660
661 try {
662 SolResponseData responseData = (SolResponseData) connector.sendMessage(connectionHandle, payloadCoder);
663
664 int actualRetries = 0;
665
666 while (isNackForWholeMessage(responseData) && actualRetries < connector.getRetries()) {
667 actualRetries++;
668
669 responseData = (SolResponseData) connector.retryMessage(connectionHandle,
670 responseData.getRequestSequenceNumber(), PayloadType.Sol);
671 }
672
673 return responseData;
674 } catch (Exception e) {
675 logger.error("Error while sending message", e);
676 return null;
677 }
678 }
679
680 @Override
681 public synchronized void close() throws IOException {
682 if (!closed) {
683 try {
684 ConnectionHandle connectionHandle = session.getConnectionHandle();
685
686 DeactivatePayload deactivatePayload = new DeactivatePayload(connectionHandle.getCipherSuite(),
687 PayloadType.Sol, payloadInstance);
688
689 connector.sendMessage(connectionHandle, deactivatePayload);
690
691 if (isSessionInternal) {
692 connector.closeSession(connectionHandle);
693 connector.tearDown();
694 }
695
696 closed = true;
697 } catch (Exception e) {
698 throw new IOException("Error while closing Serial over LAN instance", e);
699 }
700
701 }
702 }
703
704 private boolean isNackForWholeMessage(SolResponseData responseData) {
705 return responseData != null
706 && responseData.getAcknowledgeState() == SolAckState.NACK
707 && responseData.getAcceptedCharactersNumber() == 0;
708 }
709
710 private boolean isNackForMessagePart(SolResponseData responseData, int previousMessageDataLength) {
711 return responseData != null
712 && responseData.getAcknowledgeState() == SolAckState.NACK
713 && responseData.getAcceptedCharactersNumber() > 0
714 && responseData.getAcceptedCharactersNumber() < previousMessageDataLength;
715 }
716
717 }