001/* 002 * Copyright 2008-2020 Ping Identity Corporation 003 * All Rights Reserved. 004 */ 005/* 006 * Copyright 2008-2020 Ping Identity Corporation 007 * 008 * Licensed under the Apache License, Version 2.0 (the "License"); 009 * you may not use this file except in compliance with the License. 010 * You may obtain a copy of the License at 011 * 012 * http://www.apache.org/licenses/LICENSE-2.0 013 * 014 * Unless required by applicable law or agreed to in writing, software 015 * distributed under the License is distributed on an "AS IS" BASIS, 016 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 017 * See the License for the specific language governing permissions and 018 * limitations under the License. 019 */ 020/* 021 * Copyright (C) 2008-2020 Ping Identity Corporation 022 * 023 * This program is free software; you can redistribute it and/or modify 024 * it under the terms of the GNU General Public License (GPLv2 only) 025 * or the terms of the GNU Lesser General Public License (LGPLv2.1 only) 026 * as published by the Free Software Foundation. 027 * 028 * This program is distributed in the hope that it will be useful, 029 * but WITHOUT ANY WARRANTY; without even the implied warranty of 030 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 031 * GNU General Public License for more details. 032 * 033 * You should have received a copy of the GNU General Public License 034 * along with this program; if not, see <http://www.gnu.org/licenses>. 035 */ 036package com.unboundid.ldap.matchingrules; 037 038 039 040import com.unboundid.asn1.ASN1OctetString; 041import com.unboundid.ldap.sdk.LDAPException; 042import com.unboundid.ldap.sdk.ResultCode; 043import com.unboundid.util.Debug; 044import com.unboundid.util.StaticUtils; 045import com.unboundid.util.ThreadSafety; 046import com.unboundid.util.ThreadSafetyLevel; 047 048import static com.unboundid.ldap.matchingrules.MatchingRuleMessages.*; 049 050 051 052/** 053 * This class provides an implementation of a matching rule that performs 054 * equality and ordering comparisons against values that should be integers. 055 * Substring matching is not supported. 056 */ 057@ThreadSafety(level=ThreadSafetyLevel.COMPLETELY_THREADSAFE) 058public final class IntegerMatchingRule 059 extends MatchingRule 060{ 061 /** 062 * The singleton instance that will be returned from the {@code getInstance} 063 * method. 064 */ 065 private static final IntegerMatchingRule INSTANCE = 066 new IntegerMatchingRule(); 067 068 069 070 /** 071 * The name for the integerMatch equality matching rule. 072 */ 073 public static final String EQUALITY_RULE_NAME = "integerMatch"; 074 075 076 077 /** 078 * The name for the integerMatch equality matching rule, formatted in all 079 * lowercase characters. 080 */ 081 static final String LOWER_EQUALITY_RULE_NAME = 082 StaticUtils.toLowerCase(EQUALITY_RULE_NAME); 083 084 085 086 /** 087 * The OID for the integerMatch equality matching rule. 088 */ 089 public static final String EQUALITY_RULE_OID = "2.5.13.14"; 090 091 092 093 /** 094 * The name for the integerOrderingMatch ordering matching rule. 095 */ 096 public static final String ORDERING_RULE_NAME = "integerOrderingMatch"; 097 098 099 100 /** 101 * The name for the integerOrderingMatch ordering matching rule, formatted 102 * in all lowercase characters. 103 */ 104 static final String LOWER_ORDERING_RULE_NAME = 105 StaticUtils.toLowerCase(ORDERING_RULE_NAME); 106 107 108 109 /** 110 * The OID for the integerOrderingMatch ordering matching rule. 111 */ 112 public static final String ORDERING_RULE_OID = "2.5.13.15"; 113 114 115 116 /** 117 * The serial version UID for this serializable class. 118 */ 119 private static final long serialVersionUID = -9056942146971528818L; 120 121 122 123 /** 124 * Creates a new instance of this integer matching rule. 125 */ 126 public IntegerMatchingRule() 127 { 128 // No implementation is required. 129 } 130 131 132 133 /** 134 * Retrieves a singleton instance of this matching rule. 135 * 136 * @return A singleton instance of this matching rule. 137 */ 138 public static IntegerMatchingRule getInstance() 139 { 140 return INSTANCE; 141 } 142 143 144 145 /** 146 * {@inheritDoc} 147 */ 148 @Override() 149 public String getEqualityMatchingRuleName() 150 { 151 return EQUALITY_RULE_NAME; 152 } 153 154 155 156 /** 157 * {@inheritDoc} 158 */ 159 @Override() 160 public String getEqualityMatchingRuleOID() 161 { 162 return EQUALITY_RULE_OID; 163 } 164 165 166 167 /** 168 * {@inheritDoc} 169 */ 170 @Override() 171 public String getOrderingMatchingRuleName() 172 { 173 return ORDERING_RULE_NAME; 174 } 175 176 177 178 /** 179 * {@inheritDoc} 180 */ 181 @Override() 182 public String getOrderingMatchingRuleOID() 183 { 184 return ORDERING_RULE_OID; 185 } 186 187 188 189 /** 190 * {@inheritDoc} 191 */ 192 @Override() 193 public String getSubstringMatchingRuleName() 194 { 195 return null; 196 } 197 198 199 200 /** 201 * {@inheritDoc} 202 */ 203 @Override() 204 public String getSubstringMatchingRuleOID() 205 { 206 return null; 207 } 208 209 210 211 /** 212 * {@inheritDoc} 213 */ 214 @Override() 215 public boolean valuesMatch(final ASN1OctetString value1, 216 final ASN1OctetString value2) 217 throws LDAPException 218 { 219 return normalize(value1).equals(normalize(value2)); 220 } 221 222 223 224 /** 225 * {@inheritDoc} 226 */ 227 @Override() 228 public boolean matchesAnyValue(final ASN1OctetString assertionValue, 229 final ASN1OctetString[] attributeValues) 230 throws LDAPException 231 { 232 if ((assertionValue == null) || (attributeValues == null) || 233 (attributeValues.length == 0)) 234 { 235 return false; 236 } 237 238 final ASN1OctetString normalizedAssertionValue = normalize(assertionValue); 239 240 for (final ASN1OctetString attributeValue : attributeValues) 241 { 242 try 243 { 244 if (normalizedAssertionValue.equalsIgnoreType( 245 normalize(attributeValue))) 246 { 247 return true; 248 } 249 } 250 catch (final Exception e) 251 { 252 Debug.debugException(e); 253 } 254 } 255 256 return false; 257 } 258 259 260 261 /** 262 * {@inheritDoc} 263 */ 264 @Override() 265 public boolean matchesSubstring(final ASN1OctetString value, 266 final ASN1OctetString subInitial, 267 final ASN1OctetString[] subAny, 268 final ASN1OctetString subFinal) 269 throws LDAPException 270 { 271 throw new LDAPException(ResultCode.INAPPROPRIATE_MATCHING, 272 ERR_INTEGER_SUBSTRING_MATCHING_NOT_SUPPORTED.get()); 273 } 274 275 276 277 /** 278 * {@inheritDoc} 279 */ 280 @Override() 281 public int compareValues(final ASN1OctetString value1, 282 final ASN1OctetString value2) 283 throws LDAPException 284 { 285 final byte[] norm1Bytes = normalize(value1).getValue(); 286 final byte[] norm2Bytes = normalize(value2).getValue(); 287 288 if (norm1Bytes[0] == '-') 289 { 290 if (norm2Bytes[0] == '-') 291 { 292 // Both values are negative. The smaller negative is the larger value. 293 if (norm1Bytes.length < norm2Bytes.length) 294 { 295 return 1; 296 } 297 else if (norm1Bytes.length > norm2Bytes.length) 298 { 299 return -1; 300 } 301 else 302 { 303 for (int i=1; i < norm1Bytes.length; i++) 304 { 305 final int difference = norm2Bytes[i] - norm1Bytes[i]; 306 if (difference != 0) 307 { 308 return difference; 309 } 310 } 311 312 return 0; 313 } 314 } 315 else 316 { 317 // The first is negative and the second is positive. 318 return -1; 319 } 320 } 321 else 322 { 323 if (norm2Bytes[0] == '-') 324 { 325 // The first is positive and the second is negative. 326 return 1; 327 } 328 else 329 { 330 // Both values are positive. 331 if (norm1Bytes.length < norm2Bytes.length) 332 { 333 return -1; 334 } 335 else if (norm1Bytes.length > norm2Bytes.length) 336 { 337 return 1; 338 } 339 else 340 { 341 for (int i=0; i < norm1Bytes.length; i++) 342 { 343 final int difference = norm1Bytes[i] - norm2Bytes[i]; 344 if (difference != 0) 345 { 346 return difference; 347 } 348 } 349 350 return 0; 351 } 352 } 353 } 354 } 355 356 357 358 /** 359 * {@inheritDoc} 360 */ 361 @Override() 362 public ASN1OctetString normalize(final ASN1OctetString value) 363 throws LDAPException 364 { 365 // It is likely that the provided value is already acceptable, so we should 366 // try to validate it without any unnecessary allocation. 367 final byte[] valueBytes = value.getValue(); 368 if (valueBytes.length == 0) 369 { 370 throw new LDAPException(ResultCode.INVALID_ATTRIBUTE_SYNTAX, 371 ERR_INTEGER_ZERO_LENGTH_NOT_ALLOWED.get()); 372 } 373 374 if ((valueBytes[0] == ' ') || (valueBytes[valueBytes.length-1] == ' ')) 375 { 376 // There is either a leading or trailing space, which needs to be 377 // stripped out so we'll have to allocate memory for this. 378 final String valueStr = value.stringValue().trim(); 379 if (valueStr.isEmpty()) 380 { 381 throw new LDAPException(ResultCode.INVALID_ATTRIBUTE_SYNTAX, 382 ERR_INTEGER_ZERO_LENGTH_NOT_ALLOWED.get()); 383 } 384 385 for (int i=0; i < valueStr.length(); i++) 386 { 387 switch (valueStr.charAt(i)) 388 { 389 case '-': 390 // This is only acceptable as the first character, and only if it is 391 // followed by one or more other characters. 392 if ((i != 0) || (valueStr.length() == 1)) 393 { 394 throw new LDAPException(ResultCode.INVALID_ATTRIBUTE_SYNTAX, 395 ERR_INTEGER_INVALID_CHARACTER.get(i)); 396 } 397 break; 398 399 case '0': 400 // This is acceptable anywhere except the as first character unless 401 // it is the only character, or as the second character if the first 402 // character is a dash. 403 if (((i == 0) && (valueStr.length() > 1)) || 404 ((i == 1) && (valueStr.charAt(0) == '-'))) 405 { 406 throw new LDAPException(ResultCode.INVALID_ATTRIBUTE_SYNTAX, 407 ERR_INTEGER_INVALID_LEADING_ZERO.get()); 408 } 409 break; 410 411 case '1': 412 case '2': 413 case '3': 414 case '4': 415 case '5': 416 case '6': 417 case '7': 418 case '8': 419 case '9': 420 // These are always acceptable. 421 break; 422 423 default: 424 throw new LDAPException(ResultCode.INVALID_ATTRIBUTE_SYNTAX, 425 ERR_INTEGER_INVALID_CHARACTER.get(i)); 426 } 427 } 428 429 return new ASN1OctetString(valueStr); 430 } 431 432 433 // Perform the validation against the contents of the byte array. 434 for (int i=0; i < valueBytes.length; i++) 435 { 436 switch (valueBytes[i]) 437 { 438 case '-': 439 // This is only acceptable as the first character, and only if it is 440 // followed by one or more other characters. 441 if ((i != 0) || (valueBytes.length == 1)) 442 { 443 throw new LDAPException(ResultCode.INVALID_ATTRIBUTE_SYNTAX, 444 ERR_INTEGER_INVALID_CHARACTER.get(i)); 445 } 446 break; 447 448 case '0': 449 // This is acceptable anywhere except the as first character unless 450 // it is the only character, or as the second character if the first 451 // character is a dash. 452 if (((i == 0) && (valueBytes.length > 1)) || 453 ((i == 1) && (valueBytes[0] == '-'))) 454 { 455 throw new LDAPException(ResultCode.INVALID_ATTRIBUTE_SYNTAX, 456 ERR_INTEGER_INVALID_LEADING_ZERO.get()); 457 } 458 break; 459 460 case '1': 461 case '2': 462 case '3': 463 case '4': 464 case '5': 465 case '6': 466 case '7': 467 case '8': 468 case '9': 469 // These are always acceptable. 470 break; 471 472 default: 473 throw new LDAPException(ResultCode.INVALID_ATTRIBUTE_SYNTAX, 474 ERR_INTEGER_INVALID_CHARACTER.get(i)); 475 } 476 } 477 478 return value; 479 } 480 481 482 483 /** 484 * {@inheritDoc} 485 */ 486 @Override() 487 public ASN1OctetString normalizeSubstring(final ASN1OctetString value, 488 final byte substringType) 489 throws LDAPException 490 { 491 throw new LDAPException(ResultCode.INAPPROPRIATE_MATCHING, 492 ERR_INTEGER_SUBSTRING_MATCHING_NOT_SUPPORTED.get()); 493 } 494}