View Javadoc
1   /*
2     (C) Copyright IBM Corp. 2011
3   
4     THIS FILE IS PROVIDED UNDER THE TERMS OF THE ECLIPSE PUBLIC LICENSE
5     ("AGREEMENT"). ANY USE, REPRODUCTION OR DISTRIBUTION OF THIS FILE
6     CONSTITUTES RECIPIENTS ACCEPTANCE OF THE AGREEMENT.
7   
8     You can obtain a current copy of the Eclipse Public License from
9     http://www.opensource.org/licenses/eclipse-1.0.php
10  
11    @author : Dave Blaschke, IBM, blaschke@us.ibm.com
12   * 
13   * Change History
14   * Flag       Date        Prog         Description
15   *------------------------------------------------------------------------------- 
16   * 3397922    2011-08-30  blaschke-oss support OctetString 
17   */
18  
19  package org.metricshub.wbem.sblim.cimclient.internal.cim;
20  
21  /*-
22   * ╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲
23   * WBEM Java Client
24   * ჻჻჻჻჻჻
25   * Copyright 2023 - 2025 MetricsHub
26   * ჻჻჻჻჻჻
27   * Licensed under the Apache License, Version 2.0 (the "License");
28   * you may not use this file except in compliance with the License.
29   * You may obtain a copy of the License at
30   *
31   *      http://www.apache.org/licenses/LICENSE-2.0
32   *
33   * Unless required by applicable law or agreed to in writing, software
34   * distributed under the License is distributed on an "AS IS" BASIS,
35   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
36   * See the License for the specific language governing permissions and
37   * limitations under the License.
38   * ╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱
39   */
40  
41  import org.metricshub.wbem.javax.cim.UnsignedInteger8;
42  
43  /**
44   * This class represents a CIM octet string, or length-prefixed string, where
45   * the length is four octets (32 bits) AND includes the four octets it occupies.
46   * In other words, the length of the octet string is the number of characters in
47   * the string plus four. There are three possible representations: <br>
48   * <br>
49   * 1) Byte array - This is an array of UnsignedInteger8 (unit8) objects. The
50   * first four bytes contain the length, so the ASCII string "DEB" would be
51   * represented as { 0x00, 0x00, 0x00, 0x07, 0x44, 0x45, 0x42 }<br>
52   * <br>
53   * 2) Hexadecimal string - This is a string of hexadecimal digits. The four
54   * bytes after the initial "0x" contain the length, so the ASCII string "DEB"
55   * would be represented as "0x00000007444542".<br>
56   * <br>
57   * 3) ASCII string<br>
58   * <br>
59   * One of these representations is passed into a constructor. The other
60   * representations are created on demand when a get() method is invoked or when
61   * the equals() method is invoked and the two octet strings have no
62   * representations in common.
63   */
64  public class CIMOctetString {
65  	private UnsignedInteger8 iBytes[];
66  
67  	private String iASCIIString;
68  
69  	private char iReplacementChar; // 0xFF indicates ASCII constructor used
70  
71  	private String iHexString;
72  
73  	private int iLength;
74  
75  	/**
76  	 * Constructs a <code>CIMOctetString</code> from the given byte array.
77  	 *
78  	 * @param pBytes
79  	 *            Byte array representation of octet string.
80  	 * @throws IllegalArgumentException
81  	 */
82  	public CIMOctetString(UnsignedInteger8 pBytes[]) throws IllegalArgumentException {
83  		// Minimum (empty) byte array is { 0x00 0x00 0x00 0x04 }
84  		if (pBytes == null || pBytes.length < 4) throw new IllegalArgumentException(
85  			"Array of bytes must contain at least four bytes"
86  		);
87  
88  		// Verify there are no null entries in byte array
89  		for (int i = pBytes.length - 1; i >= 0; i--) if (pBytes[i] == null) throw new IllegalArgumentException(
90  			"Array of bytes must not contain any null bytes"
91  		);
92  
93  		// Calculate length
94  		this.iLength =
95  			pBytes[3].byteValue() +
96  			(pBytes[2].byteValue() * 0x100) +
97  			(pBytes[1].byteValue() * 0x10000) +
98  			(pBytes[0].byteValue() * 0x1000000);
99  
100 		// Verify calculated length matches actual length
101 		if (this.iLength != pBytes.length) throw new IllegalArgumentException(
102 			"Array of bytes contains invalid length: found " + this.iLength + ", expected " + pBytes.length
103 		);
104 
105 		// Save byte array in new object
106 		this.iBytes = new UnsignedInteger8[this.iLength];
107 		for (int i = this.iLength - 1; i >= 0; i--) this.iBytes[i] = pBytes[i];
108 	}
109 
110 	/**
111 	 * Constructs a <code>CIMOctetString</code> from the given string.
112 	 *
113 	 * @param pString
114 	 *            String representation of octet string.
115 	 * @param pIsHex
116 	 *            <code>true</code> if string is hexadecimal string,
117 	 *            <code>false</code> if string is ASCII string.
118 	 *
119 	 * @throws IllegalArgumentException
120 	 */
121 	public CIMOctetString(String pString, boolean pIsHex) throws IllegalArgumentException {
122 		if (pString == null) throw new IllegalArgumentException("String cannot be null");
123 
124 		if (pIsHex) {
125 			// Minimum (empty) hexadecimal string is "0x00000004"
126 			if (pString.length() < 10) throw new IllegalArgumentException(
127 				"Hexadecimal string must contain \"0x\" and at least four pairs of hex digits"
128 			);
129 
130 			// Verify hexadecimal string starts with "0x"
131 			if (pString.charAt(0) != '0' || pString.charAt(1) != 'x') throw new IllegalArgumentException(
132 				"Hexadecimal string must begin with \"0x\""
133 			);
134 
135 			// Calculate length
136 			try {
137 				this.iLength = Integer.parseInt(pString.substring(2, 10), 16);
138 			} catch (NumberFormatException e) {
139 				throw new IllegalArgumentException("Hexadecimal string length could not be parsed: " + e.toString());
140 			}
141 
142 			// Verify calculated length matches actual length
143 			if ((this.iLength * 2) + 2 != pString.length()) throw new IllegalArgumentException(
144 				"Hexadecimal string contains invalid length: found " +
145 				this.iLength +
146 				", expected " +
147 				((pString.length() - 2 / 2))
148 			);
149 
150 			// Verify remainder of hexadecimal string contains only hexadecimal
151 			// digits
152 			for (int i = pString.length() - 1; i >= 10; i--) {
153 				char ch = pString.charAt(i);
154 				if (
155 					!((ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'f') || (ch >= 'A' && ch <= 'F'))
156 				) throw new IllegalArgumentException(
157 					"Hexadecimal string could not be parsed, invalid character \'" + ch + "\' at index " + i
158 				);
159 			}
160 
161 			// Save hexadecimal string in new object
162 			this.iHexString = new String(pString);
163 		} else {
164 			// Calculate length
165 			this.iLength = pString.length() + 4;
166 
167 			// Save ASCII string in new object and indicate constructor used
168 			this.iASCIIString = new String(pString);
169 			this.iReplacementChar = 0xFF;
170 		}
171 	}
172 
173 	/**
174 	 * Takes a CIM octet string and returns <code>true</code> if it is equal to
175 	 * this CIM octet string. Otherwise, it returns <code>false</code>. Two
176 	 * octet strings are considered equal if all of their common representations
177 	 * are equal. If the octet strings have no representations in common, this
178 	 * method will build the missing one, starting with byte array and then
179 	 * hexadecmial string.
180 	 *
181 	 * NOTE: The ASCII string representation is only considered if both octet
182 	 * strings were constructed with an ASCII string.
183 	 *
184 	 * @param pObj
185 	 *            The object to be compared a CIM element.
186 	 * @return <code>true</code> if the specified CIM octet string equals this
187 	 *         CIM octet string, <code>false</code> otherwise.
188 	 */
189 	@Override
190 	public synchronized boolean equals(Object pObj) {
191 		// Verify parameter is CIMOctetString instance
192 		if (!(pObj instanceof CIMOctetString)) return false;
193 
194 		CIMOctetString that = (CIMOctetString) pObj;
195 		int numCompares = 0;
196 
197 		// Verify lengths are same
198 		if (this.iLength != that.iLength) return false;
199 
200 		// Verify byte arrays match if both non-null
201 		if (this.iBytes != null && that.iBytes != null) {
202 			for (int i = this.iLength - 1; i >= 0; i--) if (
203 				this.iBytes[i].byteValue() != that.iBytes[i].byteValue()
204 			) return false;
205 			numCompares++;
206 		}
207 
208 		// Verify hexadecimal strings match if both non-null
209 		if (this.iHexString != null && that.iHexString != null) {
210 			if (!this.iHexString.equalsIgnoreCase(that.iHexString)) return false;
211 			numCompares++;
212 		}
213 
214 		// Verify ASCII strings match if both non-null
215 		if (this.iASCIIString != null && that.iASCIIString != null && this.iReplacementChar == that.iReplacementChar) {
216 			if (!this.iASCIIString.equalsIgnoreCase(that.iASCIIString)) return false;
217 			numCompares++;
218 		}
219 
220 		// Octet strings equal if at least one representation equal
221 		if (numCompares > 0) return true;
222 
223 		// At this point, the two CIMOctetString instances have no
224 		// representations in common - time to make one
225 
226 		if (this.iBytes != null && that.iBytes == null) {
227 			that.getBytes();
228 			if (this.iBytes != null && that.iBytes != null) {
229 				for (int i = this.iLength - 1; i >= 0; i--) if (
230 					this.iBytes[i].byteValue() != that.iBytes[i].byteValue()
231 				) return false;
232 				numCompares++;
233 			}
234 		}
235 
236 		// Octet strings equal if byte arrays equal
237 		if (numCompares > 0) return true;
238 
239 		if (this.iBytes == null && that.iBytes != null) {
240 			getBytes();
241 			if (this.iBytes != null && that.iBytes != null) {
242 				for (int i = this.iLength - 1; i >= 0; i--) if (
243 					this.iBytes[i].byteValue() != that.iBytes[i].byteValue()
244 				) return false;
245 				numCompares++;
246 			}
247 		}
248 
249 		// Octet strings equal if byte arrays equal
250 		if (numCompares > 0) return true;
251 
252 		if (this.iHexString != null && that.iHexString == null) {
253 			that.getHexString();
254 			if (this.iHexString != null && that.iHexString != null) {
255 				if (!this.iHexString.equalsIgnoreCase(that.iHexString)) return false;
256 				numCompares++;
257 			}
258 		}
259 
260 		// Octet strings equal if byte arrays equal
261 		if (numCompares > 0) return true;
262 
263 		if (this.iHexString == null && that.iHexString != null) {
264 			getHexString();
265 			if (this.iHexString != null && that.iHexString != null) {
266 				if (!this.iHexString.equalsIgnoreCase(that.iHexString)) return false;
267 				numCompares++;
268 			}
269 		}
270 
271 		// Octet strings equal if byte arrays equal
272 		if (numCompares > 0) return true;
273 
274 		return false;
275 	}
276 
277 	/**
278 	 * Returns ASCII string representation of octet string with non-printable
279 	 * characters replaced by <code>pReplacementChar</code>. If the ASCII string
280 	 * has not yet been created, it is created from the byte array or
281 	 * hexadecimal string.
282 	 *
283 	 * @param pReplacementChar
284 	 *            Replacement character for non-printable characters which must
285 	 *            be between 0x20 and 0x7E, inclusive.
286 	 * @return ASCII string representation of octet string.
287 	 */
288 	public synchronized String getASCIIString(char pReplacementChar) {
289 		// If ASCII string constructor used, return original string
290 		if (this.iASCIIString != null && this.iReplacementChar == 0xFF) return this.iASCIIString;
291 
292 		// Verify replacement character is printable
293 		if (pReplacementChar <= 0x1F || pReplacementChar >= 0x7F) throw new IllegalArgumentException(
294 			"Replacement character not printable"
295 		);
296 
297 		// If we already did this once, return previous string
298 		if (this.iASCIIString != null && this.iReplacementChar == pReplacementChar) return this.iASCIIString;
299 
300 		// Construct new ASCII string
301 		StringBuilder str = new StringBuilder("");
302 		if (this.iBytes != null) {
303 			for (int i = 4; i < this.iBytes.length; i++) {
304 				char ch = (char) this.iBytes[i].byteValue();
305 				if (ch <= 0x1F || ch >= 0x7F) str.append(pReplacementChar); else str.append(ch);
306 			}
307 		} else /* (this.iHexString != null) */{
308 			for (int i = 10; i < this.iHexString.length(); i += 2) {
309 				char ch = (char) Integer.parseInt(this.iHexString.substring(i, i + 2), 16);
310 				if (ch <= 0x1F || ch >= 0x7F) str.append(pReplacementChar); else str.append(ch);
311 			}
312 		}
313 
314 		// Save ASCII string in new object and indicate which replacement
315 		// character used
316 		this.iASCIIString = new String(str);
317 		this.iReplacementChar = pReplacementChar;
318 
319 		return this.iASCIIString;
320 	}
321 
322 	/**
323 	 * Returns byte array representation of octet string. If the byte array has
324 	 * not yet been created, it is created from the hexadecimal string or ASCII
325 	 * string.
326 	 *
327 	 * @return Byte array representation of octet string.
328 	 */
329 	public synchronized UnsignedInteger8[] getBytes() {
330 		if (this.iBytes != null) return this.iBytes;
331 
332 		if (this.iHexString != null) {
333 			convertHexStringToBytes();
334 		} else /* if (this.iASCIIString != null) */{
335 			convertASCIIStringToBytes();
336 		}
337 
338 		return this.iBytes;
339 	}
340 
341 	/**
342 	 * Returns hexadecimal string representation of octet string. If the
343 	 * hexadecimal string has not yet been created, it is created from the byte
344 	 * array or ASCII string.
345 	 *
346 	 * @return Hexadecimal string representation of octet string.
347 	 */
348 	public synchronized String getHexString() {
349 		if (this.iHexString != null) return this.iHexString;
350 
351 		if (this.iBytes != null) {
352 			convertBytesToHexString();
353 		} else /* if (this.iASCIIString != null) */{
354 			convertASCIIStringToHexString();
355 		}
356 
357 		return this.iHexString;
358 	}
359 
360 	/**
361 	 * Returns hash code value for octet string.
362 	 *
363 	 * @return Hash code value for octet string.
364 	 */
365 	@Override
366 	public int hashCode() {
367 		return toString().toLowerCase().hashCode();
368 	}
369 
370 	/**
371 	 * Returns length of octet string, where length is number of octets plus
372 	 * four.
373 	 *
374 	 * @return Length of octet string.
375 	 */
376 	public int length() {
377 		return this.iLength;
378 	}
379 
380 	/**
381 	 * Returns string representation of octet string.
382 	 *
383 	 * @return String representation of octet string.
384 	 */
385 	@Override
386 	public String toString() {
387 		return getHexString();
388 	}
389 
390 	private void convertBytesToHexString() {
391 		// Start with "0x"
392 		StringBuilder str = new StringBuilder("0x");
393 
394 		// Append length
395 		String len = Integer.toHexString(this.iLength);
396 		for (int i = 8 - len.length(); i > 0; i--) str.append('0');
397 		str.append(len);
398 
399 		// Append string
400 		for (int i = 4; i < this.iLength; i++) {
401 			String octet = Integer.toHexString(this.iBytes[i].intValue());
402 			if (octet.length() == 1) str.append('0');
403 			str.append(octet);
404 		}
405 
406 		// Save hexadecimal string in new object
407 		this.iHexString = new String(str);
408 		// debug("convertBytesToHexString: from {" + toBytesString() + "} to \""
409 		// + this.iHexString + "\"");
410 	}
411 
412 	private void convertHexStringToBytes() {
413 		// Save byte array in new object
414 		this.iBytes = new UnsignedInteger8[this.iLength];
415 
416 		// Convert each octet in hexadecimal string to byte
417 		for (int idxByte = 0, idxStr = 2, len = this.iHexString.length(); idxStr < len; idxByte++, idxStr += 2) {
418 			short s;
419 			try {
420 				s = Short.parseShort(this.iHexString.substring(idxStr, idxStr + 2), 16);
421 			} catch (NumberFormatException e) {
422 				throw new IllegalArgumentException("Hex string length could not be parsed: " + e.toString());
423 			}
424 			this.iBytes[idxByte] = new UnsignedInteger8(s);
425 		}
426 		// debug("convertHexStringToBytes: from \"" + this.iHexString +
427 		// "\" to {" + toBytesString() + "}");
428 	}
429 
430 	private void convertASCIIStringToBytes() {
431 		// Save byte array in new object
432 		this.iBytes = new UnsignedInteger8[this.iLength];
433 
434 		// Convert length
435 		this.iBytes[0] = new UnsignedInteger8((short) ((this.iLength >> 24) & 0xFF));
436 		this.iBytes[1] = new UnsignedInteger8((short) ((this.iLength >> 16) & 0xFF));
437 		this.iBytes[2] = new UnsignedInteger8((short) ((this.iLength >> 8) & 0xFF));
438 		this.iBytes[3] = new UnsignedInteger8((short) (this.iLength & 0xFF));
439 
440 		// Convert each character in ASCII string to byte
441 		for (int idxStr = 0, idxByte = 4; idxStr < this.iASCIIString.length(); idxStr++, idxByte++) this.iBytes[idxByte] =
442 			new UnsignedInteger8((short) (this.iASCIIString.charAt(idxStr)));
443 		// debug("convertASCIIStringToBytes: from \"" + this.iASCIIString +
444 		// "\" to {" + toBytesString() + "}");
445 	}
446 
447 	private void convertASCIIStringToHexString() {
448 		// Start with "0x"
449 		StringBuilder str = new StringBuilder("0x");
450 
451 		// Append length
452 		String len = Integer.toHexString(this.iLength);
453 		for (int i = 8 - len.length(); i > 0; i--) str.append('0');
454 		str.append(len);
455 
456 		// Append string
457 		for (int idxAsc = 0; idxAsc < this.iASCIIString.length(); idxAsc++) {
458 			String octet = Integer.toHexString((this.iASCIIString.charAt(idxAsc)));
459 			if (octet.length() == 1) str.append('0');
460 			str.append(octet);
461 		}
462 
463 		// Save hexadecimal string in new object
464 		this.iHexString = new String(str);
465 		// debug("convertASCIIStringToHexString: from \"" + this.iASCIIString +
466 		// "\" to \"" + this.iHexString + "\"");
467 	}
468 	// private String toBytesString() {
469 	// StringBuilder str = new StringBuilder();
470 	//
471 	// for (int i = 0; i < this.iLength; i++) {
472 	// String octet = Integer.toHexString((this.iBytes[i].intValue()));
473 	// if (i > 0) str.append(' ');
474 	// if (octet.length() == 1) str.append('0');
475 	// str.append(octet);
476 	// }
477 	// return new String(str);
478 	// }
479 
480 	// private void debug(String str) {
481 	// System.out.println(str);
482 	// }
483 }