001/*
002 * Copyright 2022-2024 Ping Identity Corporation
003 * All Rights Reserved.
004 */
005/*
006 * Copyright 2022-2024 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) 2022-2024 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.sdk.unboundidds.logs.v2.syntax;
037
038
039
040import com.unboundid.util.ByteStringBuffer;
041import com.unboundid.util.Debug;
042import com.unboundid.util.NotNull;
043import com.unboundid.util.ThreadSafety;
044import com.unboundid.util.ThreadSafetyLevel;
045import com.unboundid.util.json.JSONBuffer;
046
047import static com.unboundid.ldap.sdk.unboundidds.logs.v2.syntax.
048                   LogSyntaxMessages.*;
049
050
051
052/**
053 * This class defines a log field syntax for values that are integers.  This
054 * syntax does not support redacting or tokenizing individual components within
055 * the integers.  Redacted integer values will have a string representation of
056 * "-999999999999999999".  Tokenized integer values will have a string
057 * representation of "-999999999" followed by nine digits that correspond to a
058 * token value generated from the actual value.
059 * <BR>
060 * <BLOCKQUOTE>
061 *   <B>NOTE:</B>  This class, and other classes within the
062 *   {@code com.unboundid.ldap.sdk.unboundidds} package structure, are only
063 *   supported for use against Ping Identity, UnboundID, and
064 *   Nokia/Alcatel-Lucent 8661 server products.  These classes provide support
065 *   for proprietary functionality or for external specifications that are not
066 *   considered stable or mature enough to be guaranteed to work in an
067 *   interoperable way with other types of LDAP servers.
068 * </BLOCKQUOTE>
069 */
070@ThreadSafety(level=ThreadSafetyLevel.COMPLETELY_THREADSAFE)
071public final class IntegerLogFieldSyntax
072       extends LogFieldSyntax<Long>
073{
074  /**
075   * The name for this syntax.
076   */
077  @NotNull public static final String SYNTAX_NAME = "integer";
078
079
080
081  /**
082   * The string representation that will be used for a floating-point value that
083   * is completely redacted.
084   */
085  @NotNull private static final String REDACTED_INTEGER_STRING =
086       "-999999999999999999";
087
088
089
090  /**
091   * A singleton instance of this log field syntax.
092   */
093  @NotNull private static final IntegerLogFieldSyntax INSTANCE =
094       new IntegerLogFieldSyntax();
095
096
097
098  /**
099   * Creates a new instance of this log field syntax implementation.
100   */
101  private IntegerLogFieldSyntax()
102  {
103    super(100);
104  }
105
106
107
108  /**
109   * Retrieves a singleton instance of this log field syntax.
110   *
111   * @return  A singleton instance of this log field syntax.
112   */
113  @NotNull()
114  public static IntegerLogFieldSyntax getInstance()
115  {
116    return INSTANCE;
117  }
118
119
120
121  /**
122   * {@inheritDoc}
123   */
124  @Override()
125  @NotNull()
126  public String getSyntaxName()
127  {
128    return SYNTAX_NAME;
129  }
130
131
132
133  /**
134   * Appends a sanitized string representation of the provided integer to the
135   * given buffer.
136   *
137   * @param  value   The value to be appended.
138   * @param  buffer  The buffer to which the string representation should be
139   *                 appended.  It must not be {@code null}.
140   */
141  public void valueToSanitizedString(final int value,
142                                     @NotNull final ByteStringBuffer buffer)
143  {
144    buffer.append(value);
145  }
146
147
148
149  /**
150   * Appends a sanitized string representation of the provided long to the given
151   * buffer.
152   *
153   * @param  value   The value to be appended.
154   * @param  buffer  The buffer to which the string representation should be
155   *                 appended.  It must not be {@code null}.
156   */
157  public void valueToSanitizedString(final long value,
158                                     @NotNull final ByteStringBuffer buffer)
159  {
160    buffer.append(value);
161  }
162
163
164
165  /**
166   * {@inheritDoc}
167   */
168  @Override()
169  public void valueToSanitizedString(@NotNull final Long value,
170                                     @NotNull final ByteStringBuffer buffer)
171  {
172    buffer.append(value);
173  }
174
175
176
177  /**
178   * {@inheritDoc}
179   */
180  @Override()
181  public void logSanitizedFieldToTextFormattedLog(
182                   @NotNull final String fieldName,
183                   @NotNull final Long fieldValue,
184                   @NotNull final ByteStringBuffer buffer)
185  {
186    buffer.append(' ');
187    buffer.append(fieldName);
188    buffer.append('=');
189    buffer.append(fieldValue.longValue());
190  }
191
192
193
194  /**
195   * {@inheritDoc}
196   */
197  @Override()
198  public void logSanitizedFieldToJSONFormattedLog(
199                   @NotNull final String fieldName,
200                   @NotNull final Long fieldValue,
201                   @NotNull final JSONBuffer buffer)
202  {
203    buffer.appendNumber(fieldName, fieldValue);
204  }
205
206
207
208  /**
209   * {@inheritDoc}
210   */
211  @Override()
212  public void logSanitizedValueToJSONFormattedLog(
213              @NotNull final Long value,
214              @NotNull final JSONBuffer buffer)
215  {
216    buffer.appendNumber(value);
217  }
218
219
220
221  /**
222   * {@inheritDoc}
223   */
224  @Override()
225  @NotNull()
226  public Long parseValue(@NotNull final String valueString)
227         throws RedactedValueException, TokenizedValueException,
228                LogSyntaxException
229  {
230    try
231    {
232      return Long.parseLong(valueString);
233    }
234    catch (final Exception e)
235    {
236      Debug.debugException(e);
237      if (valueStringIncludesRedactedComponent(valueString))
238      {
239        throw new RedactedValueException(
240             ERR_INTEGER_LOG_SYNTAX_CANNOT_PARSE_REDACTED.get(), e);
241      }
242      else if (valueStringIncludesTokenizedComponent(valueString))
243      {
244        throw new TokenizedValueException(
245             ERR_INTEGER_LOG_SYNTAX_CANNOT_PARSE_TOKENIZED.get(), e);
246      }
247      else
248      {
249        throw new LogSyntaxException(
250             ERR_INTEGER_LOG_SYNTAX_CANNOT_PARSE.get(), e);
251      }
252    }
253  }
254
255
256
257  /**
258   * {@inheritDoc}
259   */
260  @Override()
261  public boolean valueStringIsCompletelyRedacted(
262                      @NotNull final String valueString)
263  {
264    return valueString.equals(REDACTED_STRING) ||
265         valueString.equals(REDACTED_INTEGER_STRING);
266  }
267
268
269
270  /**
271   * {@inheritDoc}
272   */
273  @Override()
274  public boolean completelyRedactedValueConformsToSyntax()
275  {
276    return true;
277  }
278
279
280
281  /**
282   * {@inheritDoc}
283   */
284  @Override()
285  public void redactEntireValue(@NotNull final ByteStringBuffer buffer)
286  {
287    buffer.append(REDACTED_INTEGER_STRING);
288  }
289
290
291
292  /**
293   * {@inheritDoc}
294   */
295  @Override()
296  public void logCompletelyRedactedFieldToTextFormattedLog(
297                   @NotNull final String fieldName,
298                   @NotNull final ByteStringBuffer buffer)
299  {
300    buffer.append(' ');
301    buffer.append(fieldName);
302    buffer.append('=');
303    buffer.append(REDACTED_INTEGER_STRING);
304  }
305
306
307
308  /**
309   * {@inheritDoc}
310   */
311  @Override()
312  public void logCompletelyRedactedFieldToJSONFormattedLog(
313                   @NotNull final String fieldName,
314                   @NotNull final JSONBuffer buffer)
315  {
316    buffer.appendNumber(fieldName, REDACTED_INTEGER_STRING);
317  }
318
319
320
321  /**
322   * {@inheritDoc}
323   */
324  @Override()
325  public void logCompletelyRedactedValueToJSONFormattedLog(
326                   @NotNull final JSONBuffer buffer)
327  {
328    buffer.appendNumber(REDACTED_INTEGER_STRING);
329  }
330
331
332
333  /**
334   * {@inheritDoc}
335   */
336  @Override()
337  public boolean supportsRedactedComponents()
338  {
339    return false;
340  }
341
342
343
344  /**
345   * {@inheritDoc}
346   */
347  @Override()
348  public boolean valueStringIncludesRedactedComponent(
349                      @NotNull final String valueString)
350  {
351    return valueStringIsCompletelyRedacted(valueString);
352  }
353
354
355
356  /**
357   * {@inheritDoc}
358   */
359  @Override()
360  public boolean valueWithRedactedComponentsConformsToSyntax()
361  {
362    return true;
363  }
364
365
366
367  /**
368   * {@inheritDoc}
369   */
370  @Override()
371  public void logRedactedComponentsFieldToTextFormattedLog(
372                   @NotNull final String fieldName,
373                   @NotNull final Long fieldValue,
374                   @NotNull final ByteStringBuffer buffer)
375  {
376    logCompletelyRedactedFieldToTextFormattedLog(fieldName, buffer);
377  }
378
379
380
381  /**
382   * {@inheritDoc}
383   */
384  @Override()
385  public void logRedactedComponentsFieldToJSONFormattedLog(
386                   @NotNull final String fieldName,
387                   @NotNull final Long fieldValue,
388                   @NotNull final JSONBuffer buffer)
389  {
390    logCompletelyRedactedFieldToJSONFormattedLog(fieldName, buffer);
391  }
392
393
394
395  /**
396   * {@inheritDoc}
397   */
398  @Override()
399  public void logRedactedComponentsValueToJSONFormattedLog(
400                   @NotNull final Long value,
401                   @NotNull final JSONBuffer buffer)
402  {
403    logCompletelyRedactedValueToJSONFormattedLog(buffer);
404  }
405
406
407
408  /**
409   * {@inheritDoc}
410   */
411  @Override()
412  public boolean valueStringIsCompletelyTokenized(
413                      @NotNull final String valueString)
414  {
415    if (super.valueStringIsCompletelyTokenized(valueString))
416    {
417      return true;
418    }
419
420    return ((valueString.length() == 19) &&
421         valueString.startsWith("-999999999") &&
422         (! valueString.equals(REDACTED_INTEGER_STRING)));
423  }
424
425
426
427  /**
428   * {@inheritDoc}
429   */
430  @Override()
431  public boolean completelyTokenizedValueConformsToSyntax()
432  {
433    return true;
434  }
435
436
437
438  /**
439   * {@inheritDoc}
440   */
441  @Override()
442  public void tokenizeEntireValue(@NotNull final Long value,
443                                  @NotNull final byte[] pepper,
444                                  @NotNull final ByteStringBuffer buffer)
445  {
446    // Get the bytes that comprise the bitwise encoding of the provided value.
447    final long longValue = value;
448    final byte[] valueBytes =
449    {
450      (byte) ((longValue >> 56) & 0xFFL),
451      (byte) ((longValue >> 48) & 0xFFL),
452      (byte) ((longValue >> 40) & 0xFFL),
453      (byte) ((longValue >> 32) & 0xFFL),
454      (byte) ((longValue >> 24) & 0xFFL),
455      (byte) ((longValue >> 16) & 0xFFL),
456      (byte) ((longValue >> 8) & 0xFFL),
457      (byte) (longValue & 0xFFL)
458    };
459
460
461    // Concatenate the value bytes and the pepper and compute a SHA-256 digest
462    // of the result.
463    final byte[] tokenDigest;
464    final ByteStringBuffer tempBuffer = getTemporaryBuffer();
465    try
466    {
467      tempBuffer.append(valueBytes);
468      tempBuffer.append(pepper);
469      tokenDigest = sha256(tempBuffer);
470    }
471    finally
472    {
473      releaseTemporaryBuffer(tempBuffer);
474    }
475
476
477    // Use the first four bytes of the token digest to generate a positive
478    // integer whose string representation is exactly ten digits long.  To do
479    // this, AND the first byte with 0x7F (which will make it positive) and OR
480    // the first byte with 0x40 (which will ensure that the value will be
481    // greater than or equal to 1073741824, and we already know that int
482    // values cannot exceed 2147483647, so that means it will be exactly ten
483    // digits).
484    final int tokenValueInt =
485         (((tokenDigest[0] & 0x7F) | 0x40) << 24) |
486         ((tokenDigest[1] & 0xFF) << 16) |
487         ((tokenDigest[2] & 0xFF) << 8) |
488         (tokenDigest[3] & 0xFF);
489
490
491    // Take the last nine digits of the string representation of the generated
492    // integer.
493    String tokenDigits = String.valueOf(tokenValueInt).substring(1);
494
495
496    // Make sure that the resulting nine-digit string is not "999999999", so
497    // that the tokenized value won't be confused with a redacted value.
498    if (tokenDigits.equals("999999999"))
499    {
500      tokenDigits = "000000000";
501    }
502
503
504    // Finally, generate the tokenized representation.  It will be "-999999999"
505    // followed by the token digits generated above.
506    buffer.append("-999999999");
507    buffer.append(tokenDigits);
508  }
509
510
511
512  /**
513   * {@inheritDoc}
514   */
515  @Override()
516  public void logCompletelyTokenizedFieldToTextFormattedLog(
517                   @NotNull final String fieldName,
518                   @NotNull final Long fieldValue,
519                   @NotNull final byte[] pepper,
520                   @NotNull final ByteStringBuffer buffer)
521  {
522    buffer.append(' ');
523    buffer.append(fieldName);
524    buffer.append('=');
525    tokenizeEntireValue(fieldValue, pepper, buffer);
526  }
527
528
529
530  /**
531   * {@inheritDoc}
532   */
533  @Override()
534  public void logCompletelyTokenizedFieldToJSONFormattedLog(
535                   @NotNull final String fieldName,
536                   @NotNull final Long fieldValue,
537                   @NotNull final byte[] pepper,
538                   @NotNull final JSONBuffer buffer)
539  {
540    buffer.appendNumber(fieldName, tokenizeEntireValue(fieldValue, pepper));
541  }
542
543
544
545  /**
546   * {@inheritDoc}
547   */
548  @Override()
549  public void logCompletelyTokenizedValueToJSONFormattedLog(
550                   @NotNull final Long value,
551                   @NotNull final byte[] pepper,
552                   @NotNull final JSONBuffer buffer)
553  {
554    buffer.appendNumber(tokenizeEntireValue(value, pepper));
555  }
556
557
558
559  /**
560   * {@inheritDoc}
561   */
562  @Override()
563  public boolean supportsTokenizedComponents()
564  {
565    return false;
566  }
567
568
569
570  /**
571   * {@inheritDoc}
572   */
573  @Override()
574  public boolean valueStringIncludesTokenizedComponent(
575                      @NotNull final String valueString)
576  {
577    return valueStringIsCompletelyTokenized(valueString);
578  }
579
580
581
582  /**
583   * {@inheritDoc}
584   */
585  @Override()
586  public boolean valueWithTokenizedComponentsConformsToSyntax()
587  {
588    return true;
589  }
590
591
592
593  /**
594   * {@inheritDoc}
595   */
596  @Override()
597  public void logTokenizedComponentsFieldToTextFormattedLog(
598                   @NotNull final String fieldName,
599                   @NotNull final Long fieldValue,
600                   @NotNull final byte[] pepper,
601                   @NotNull final ByteStringBuffer buffer)
602  {
603    logCompletelyTokenizedFieldToTextFormattedLog(fieldName, fieldValue, pepper,
604         buffer);
605  }
606
607
608
609  /**
610   * {@inheritDoc}
611   */
612  @Override()
613  public void logTokenizedComponentsFieldToJSONFormattedLog(
614                   @NotNull final String fieldName,
615                   @NotNull final Long fieldValue,
616                   @NotNull final byte[] pepper,
617                   @NotNull final JSONBuffer buffer)
618  {
619    logCompletelyTokenizedFieldToJSONFormattedLog(fieldName, fieldValue, pepper,
620         buffer);
621  }
622
623
624
625  /**
626   * {@inheritDoc}
627   */
628  @Override()
629  public void logTokenizedComponentsValueToJSONFormattedLog(
630                   @NotNull final Long value,
631                   @NotNull final byte[] pepper,
632                   @NotNull final JSONBuffer buffer)
633  {
634    logCompletelyTokenizedValueToJSONFormattedLog(value, pepper, buffer);
635  }
636}