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