1 package org.metricshub.ipmi.core.coding.payload.lan;
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.payload.CompletionCode;
26
27 public class IPMIException extends Exception {
28
29 private static final long serialVersionUID = 1L;
30
31 /** First command-specific code (IPMI 2.0 Table 5-2): 01h-7Eh are OEM, 80h-BEh command-specific. */
32 private static final int COMMAND_SPECIFIC_CODES_START = 0x80;
33
34 private static final int GENERIC_CODES_START = 0xC0;
35
36 private final CompletionCode completionCode;
37
38 private final int rawCode;
39
40 /** The meaning of the code for the command that got it, or null. */
41 private final String description;
42
43 public IPMIException(CompletionCode completionCode) {
44 this(completionCode, completionCode.getCode());
45 }
46
47 /**
48 * @param completionCode the decoded completion code, {@link CompletionCode#Unknown} for a code the library
49 * does not list
50 * @param rawCode the completion code byte as the BMC sent it
51 */
52 public IPMIException(CompletionCode completionCode, int rawCode) {
53 this(completionCode, rawCode, null);
54 }
55
56 /**
57 * Creates the exception for a code whose meaning depends on the command: an OEM or command-specific code that
58 * {@link CompletionCode} does not list.
59 *
60 * @param completionCode the decoded completion code, {@link CompletionCode#Unknown} for a code the library
61 * does not list
62 * @param rawCode the completion code byte as the BMC sent it
63 * @param description the meaning of the code for the command that got it, returned by {@link #getMessage()}, or
64 * null for the generic text
65 */
66 public IPMIException(CompletionCode completionCode, int rawCode, String description) {
67 this.completionCode = completionCode;
68 this.rawCode = rawCode;
69 this.description = description;
70 }
71
72 /**
73 * @return whether the code is one Table 5-2 reserves: 7Fh, BFh, D7h-FEh
74 */
75 private static boolean isReserved(int rawCode) {
76 return rawCode == 0x7f || rawCode == 0xbf || (rawCode >= 0xd7 && rawCode <= 0xfe);
77 }
78
79 public CompletionCode getCompletionCode() {
80 return completionCode;
81 }
82
83 /**
84 * @return the completion code byte as the BMC sent it, meaningful when {@link #getCompletionCode()} is
85 * {@link CompletionCode#Unknown}
86 */
87 public int getRawCode() {
88 return rawCode;
89 }
90
91 @Override
92 public String getMessage() {
93 if (description != null) {
94 return description;
95 }
96 if (completionCode == CompletionCode.Unknown && rawCode >= 0) {
97 String kind = isReserved(rawCode) ?
98 "Reserved" :
99 rawCode < COMMAND_SPECIFIC_CODES_START ?
100 "OEM" :
101 rawCode < GENERIC_CODES_START ? "Command-specific" : "Generic";
102 return String.format("%s completion code %02Xh.", kind, rawCode);
103 }
104 return completionCode.getMessage();
105 }
106
107 /**
108 * Describes the error reply as a sentence without its final period: the message, then the completion code in
109 * hexadecimal, named once.
110 *
111 * @return the description, {@code Requested Sensor, data, or record not present (completion code CBh)} or
112 * {@code Command-specific completion code 81h} for instance
113 */
114 public String describe() {
115 if (rawCode < 0 || (description == null && completionCode == CompletionCode.Unknown)) {
116 // No code to name, or the message already names it
117 return withoutPeriod(getMessage());
118 }
119 return describe(getMessage(), rawCode);
120 }
121
122 /**
123 * Describes a completion code as a sentence without its final period: its meaning, then the code in hexadecimal.
124 *
125 * @param meaning the meaning of the code, with or without a final period
126 * @param rawCode the completion code byte
127 * @return the description, {@code Invalid session handle (completion code 80h)} for instance
128 */
129 public static String describe(String meaning, int rawCode) {
130 return withoutPeriod(meaning) + String.format(" (completion code %02Xh)", rawCode);
131 }
132
133 private static String withoutPeriod(String sentence) {
134 return sentence.endsWith(".") ? sentence.substring(0, sentence.length() - 1) : sentence;
135 }
136 }