8585 * limitations under the License.
8686 */
8787public final class ByteUtilities {
88- // Security Configuration - using dynamic property reading for testability
88+ // Security Configuration - cached for performance with property change detection
8989 // Default limits used when security is enabled but no custom limits specified
9090 private static final int DEFAULT_MAX_HEX_STRING_LENGTH = 1000000 ; // 1MB hex string
9191 private static final int DEFAULT_MAX_ARRAY_SIZE = 10000000 ; // 10MB byte array
92-
92+
93+ // Property names
94+ private static final String PROP_SECURITY_ENABLED = "byteutilities.security.enabled" ;
95+ private static final String PROP_MAX_HEX_LENGTH = "byteutilities.max.hex.string.length" ;
96+ private static final String PROP_MAX_ARRAY_SIZE = "byteutilities.max.array.size" ;
97+
98+ // Cached security settings with property change detection
99+ private static volatile String cachedSecurityEnabledProp ;
100+ private static volatile boolean cachedSecurityEnabled ;
101+ private static volatile String cachedMaxHexLengthProp ;
102+ private static volatile int cachedMaxHexLength ;
103+ private static volatile String cachedMaxArraySizeProp ;
104+ private static volatile int cachedMaxArraySize ;
105+
106+ // Maximum array size that can be safely doubled without overflow
107+ private static final int MAX_SAFE_ARRAY_SIZE = Integer .MAX_VALUE / 2 ;
108+
93109 private static boolean isSecurityEnabled () {
94- return Boolean .parseBoolean (System .getProperty ("byteutilities.security.enabled" , "false" ));
110+ String currentProp = System .getProperty (PROP_SECURITY_ENABLED , "false" );
111+ if (!currentProp .equals (cachedSecurityEnabledProp )) {
112+ cachedSecurityEnabledProp = currentProp ;
113+ cachedSecurityEnabled = Boolean .parseBoolean (currentProp );
114+ }
115+ return cachedSecurityEnabled ;
95116 }
96-
117+
97118 private static int getMaxHexStringLength () {
98119 if (!isSecurityEnabled ()) {
99120 return 0 ; // Disabled
100121 }
101- String value = System .getProperty ("byteutilities.max.hex.string.length" );
102- if (value == null ) {
103- return DEFAULT_MAX_HEX_STRING_LENGTH ;
122+ String currentProp = System .getProperty (PROP_MAX_HEX_LENGTH );
123+ if (currentProp == null ) {
124+ cachedMaxHexLengthProp = null ;
125+ cachedMaxHexLength = DEFAULT_MAX_HEX_STRING_LENGTH ;
126+ return cachedMaxHexLength ;
104127 }
105- try {
106- int limit = Integer .parseInt (value );
107- return limit <= 0 ? 0 : limit ; // 0 or negative means disabled
108- } catch (NumberFormatException e ) {
109- return DEFAULT_MAX_HEX_STRING_LENGTH ;
128+ if (!currentProp .equals (cachedMaxHexLengthProp )) {
129+ cachedMaxHexLengthProp = currentProp ;
130+ try {
131+ int limit = Integer .parseInt (currentProp );
132+ cachedMaxHexLength = limit <= 0 ? 0 : limit ;
133+ } catch (NumberFormatException e ) {
134+ cachedMaxHexLength = DEFAULT_MAX_HEX_STRING_LENGTH ;
135+ }
110136 }
137+ return cachedMaxHexLength ;
111138 }
112-
139+
113140 private static int getMaxArraySize () {
114141 if (!isSecurityEnabled ()) {
115142 return 0 ; // Disabled
116143 }
117- String value = System .getProperty ("byteutilities.max.array.size" );
118- if (value == null ) {
119- return DEFAULT_MAX_ARRAY_SIZE ;
144+ String currentProp = System .getProperty (PROP_MAX_ARRAY_SIZE );
145+ if (currentProp == null ) {
146+ cachedMaxArraySizeProp = null ;
147+ cachedMaxArraySize = DEFAULT_MAX_ARRAY_SIZE ;
148+ return cachedMaxArraySize ;
120149 }
121- try {
122- int limit = Integer .parseInt (value );
123- return limit <= 0 ? 0 : limit ; // 0 or negative means disabled
124- } catch (NumberFormatException e ) {
125- return DEFAULT_MAX_ARRAY_SIZE ;
150+ if (!currentProp .equals (cachedMaxArraySizeProp )) {
151+ cachedMaxArraySizeProp = currentProp ;
152+ try {
153+ int limit = Integer .parseInt (currentProp );
154+ cachedMaxArraySize = limit <= 0 ? 0 : limit ;
155+ } catch (NumberFormatException e ) {
156+ cachedMaxArraySize = DEFAULT_MAX_ARRAY_SIZE ;
157+ }
126158 }
159+ return cachedMaxArraySize ;
127160 }
128161
129- // For encode: Array of hex digits.
130- static final char [] HEX_ARRAY = "0123456789ABCDEF" .toCharArray ();
162+ // For encode: Array of hex digits (private - use toHexChar() for public access)
163+ private static final char [] HEX_ARRAY = "0123456789ABCDEF" .toCharArray ();
131164
132165 // For decode: Precomputed lookup table for hex digits.
133166 // Maps ASCII codes (0–127) to their hex value or -1 if invalid.
@@ -228,6 +261,12 @@ public static String encode(final byte[] bytes) {
228261 if (maxArraySize > 0 && bytes .length > maxArraySize ) {
229262 throw new SecurityException ("Byte array size exceeds maximum allowed: " + maxArraySize );
230263 }
264+
265+ // Check for integer overflow: bytes.length * 2 must not overflow
266+ if (bytes .length > MAX_SAFE_ARRAY_SIZE ) {
267+ throw new IllegalArgumentException ("Byte array too large to encode: length " + bytes .length +
268+ " exceeds maximum safe size " + MAX_SAFE_ARRAY_SIZE );
269+ }
231270 char [] hexChars = new char [bytes .length * 2 ];
232271 for (int i = 0 , j = 0 ; i < bytes .length ; i ++) {
233272 int v = bytes [i ] & 0xFF ;
@@ -291,6 +330,17 @@ public static int indexOf(byte[] data, byte[] pattern, int start) {
291330 return -1 ;
292331 }
293332
333+ // Fast path for single-byte patterns
334+ if (patternLen == 1 ) {
335+ byte target = pattern [0 ];
336+ for (int i = start ; i < dataLen ; i ++) {
337+ if (data [i ] == target ) {
338+ return i ;
339+ }
340+ }
341+ return -1 ;
342+ }
343+
294344 final int limit = dataLen - patternLen ;
295345 outer :
296346 for (int i = start ; i <= limit ; i ++) {
@@ -303,4 +353,104 @@ public static int indexOf(byte[] data, byte[] pattern, int start) {
303353 }
304354 return -1 ;
305355 }
356+
357+ /**
358+ * Finds the last occurrence of a byte pattern within a byte array, searching backwards from the specified index.
359+ * <p>
360+ * This method searches backwards from the start position to find the last occurrence
361+ * of the pattern. It is useful for locating byte sequences when you need the rightmost match.
362+ * </p>
363+ *
364+ * <h3>Example Usage</h3>
365+ * <pre>{@code
366+ * byte[] data = {0x02, 0x03, 0x00, 0x02, 0x03};
367+ * byte[] pattern = {0x02, 0x03};
368+ * int index = ByteUtilities.lastIndexOf(data, pattern, data.length - 1); // Returns 3
369+ * int prev = ByteUtilities.lastIndexOf(data, pattern, 2); // Returns 0
370+ * }</pre>
371+ *
372+ * @param data the byte array to search within
373+ * @param pattern the byte pattern to find
374+ * @param start the index to start searching backwards from (inclusive)
375+ * @return the index of the last occurrence of the pattern, or -1 if not found
376+ * or if any parameter is invalid (null arrays, negative start, etc.)
377+ */
378+ public static int lastIndexOf (byte [] data , byte [] pattern , int start ) {
379+ if (data == null || pattern == null || start < 0 || pattern .length == 0 ) {
380+ return -1 ;
381+ }
382+ final int dataLen = data .length ;
383+ final int patternLen = pattern .length ;
384+ if (patternLen > dataLen ) {
385+ return -1 ;
386+ }
387+
388+ // Adjust start to the last valid position where pattern could fit
389+ int effectiveStart = Math .min (start , dataLen - patternLen );
390+ if (effectiveStart < 0 ) {
391+ return -1 ;
392+ }
393+
394+ // Fast path for single-byte patterns
395+ if (patternLen == 1 ) {
396+ byte target = pattern [0 ];
397+ for (int i = effectiveStart ; i >= 0 ; i --) {
398+ if (data [i ] == target ) {
399+ return i ;
400+ }
401+ }
402+ return -1 ;
403+ }
404+
405+ outer :
406+ for (int i = effectiveStart ; i >= 0 ; i --) {
407+ for (int j = 0 ; j < patternLen ; j ++) {
408+ if (data [i + j ] != pattern [j ]) {
409+ continue outer ;
410+ }
411+ }
412+ return i ;
413+ }
414+ return -1 ;
415+ }
416+
417+ /**
418+ * Finds the last occurrence of a byte pattern within a byte array.
419+ * <p>
420+ * This is a convenience method that searches from the end of the data array.
421+ * </p>
422+ *
423+ * @param data the byte array to search within
424+ * @param pattern the byte pattern to find
425+ * @return the index of the last occurrence of the pattern, or -1 if not found
426+ * or if any parameter is invalid
427+ * @see #lastIndexOf(byte[], byte[], int)
428+ */
429+ public static int lastIndexOf (byte [] data , byte [] pattern ) {
430+ if (data == null ) {
431+ return -1 ;
432+ }
433+ return lastIndexOf (data , pattern , data .length - 1 );
434+ }
435+
436+ /**
437+ * Checks if a byte array contains the specified byte pattern.
438+ * <p>
439+ * This is a convenience method equivalent to {@code indexOf(data, pattern, 0) >= 0}.
440+ * </p>
441+ *
442+ * <h3>Example Usage</h3>
443+ * <pre>{@code
444+ * byte[] data = {0x00, 0x01, 0x02, 0x03};
445+ * byte[] pattern = {0x01, 0x02};
446+ * boolean found = ByteUtilities.contains(data, pattern); // Returns true
447+ * }</pre>
448+ *
449+ * @param data the byte array to search within
450+ * @param pattern the byte pattern to find
451+ * @return true if the pattern is found within data, false otherwise
452+ */
453+ public static boolean contains (byte [] data , byte [] pattern ) {
454+ return indexOf (data , pattern , 0 ) >= 0 ;
455+ }
306456}
0 commit comments