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}