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 java.util.Date;
041import java.util.GregorianCalendar;
042
043import com.unboundid.util.ByteStringBuffer;
044import com.unboundid.util.Debug;
045import com.unboundid.util.NotNull;
046import com.unboundid.util.StaticUtils;
047import com.unboundid.util.ThreadSafety;
048import com.unboundid.util.ThreadSafetyLevel;
049import com.unboundid.util.json.JSONBuffer;
050
051import static com.unboundid.ldap.sdk.unboundidds.logs.v2.syntax.
052                   LogSyntaxMessages.*;
053
054
055
056/**
057 * This class defines a log field syntax for values that are timestamps
058 * represented in the ISO 8601 format described in RFC 3339.  This syntax does
059 * not support redacting or tokenizing individual components within the
060 * timestamps.  Redacted generalized time values will have a string
061 * representation of "9999-01-01T00:00:00.000Z", which corresponds to midnight
062 * UTC of January 1 in the year 9999.  Tokenized values will have a year of 8888
063 * (in the UTC time zone).
064 * <BR>
065 * <BLOCKQUOTE>
066 *   <B>NOTE:</B>  This class, and other classes within the
067 *   {@code com.unboundid.ldap.sdk.unboundidds} package structure, are only
068 *   supported for use against Ping Identity, UnboundID, and
069 *   Nokia/Alcatel-Lucent 8661 server products.  These classes provide support
070 *   for proprietary functionality or for external specifications that are not
071 *   considered stable or mature enough to be guaranteed to work in an
072 *   interoperable way with other types of LDAP servers.
073 * </BLOCKQUOTE>
074 */
075@ThreadSafety(level=ThreadSafetyLevel.COMPLETELY_THREADSAFE)
076public final class RFC3339TimestampLogFieldSyntax
077       extends LogFieldSyntax<Date>
078{
079  /**
080   * The name for this syntax.
081   */
082  @NotNull public static final String SYNTAX_NAME = "rfc-3339-timestamp";
083
084
085
086  /**
087   * The string that will be used for completely redacted RFC 3339 timestamp
088   * values.
089   */
090  @NotNull private static final String REDACTED_RFC_3339_TIMESTAMP_STRING =
091       "9999-01-01T00:00:00.000Z";
092
093
094
095  /**
096   * The year that will be used for dates that represent tokenized RFC 3339
097   * timestamp values.
098   */
099  private static final int TOKENIZED_DATE_YEAR = 8888;
100
101
102
103  /**
104   * A singleton instance of this log field syntax.
105   */
106  @NotNull private static final RFC3339TimestampLogFieldSyntax INSTANCE =
107       new RFC3339TimestampLogFieldSyntax();
108
109
110
111  /**
112   * Creates a new instance of this log field syntax implementation.
113   */
114  private RFC3339TimestampLogFieldSyntax()
115  {
116    super(100);
117  }
118
119
120
121  /**
122   * Retrieves a singleton instance of this log field syntax.
123   *
124   * @return  A singleton instance of this log field syntax.
125   */
126  @NotNull()
127  public static RFC3339TimestampLogFieldSyntax getInstance()
128  {
129    return INSTANCE;
130  }
131
132
133
134  /**
135   * {@inheritDoc}
136   */
137  @Override()
138  @NotNull()
139  public String getSyntaxName()
140  {
141    return SYNTAX_NAME;
142  }
143
144
145
146  /**
147   * {@inheritDoc}
148   */
149  @Override()
150  public void valueToSanitizedString(@NotNull final Date value,
151                                     @NotNull final ByteStringBuffer buffer)
152  {
153    buffer.append(StaticUtils.encodeRFC3339Time(value));
154  }
155
156
157
158  /**
159   * {@inheritDoc}
160   */
161  @Override()
162  public void logSanitizedFieldToTextFormattedLog(
163                   @NotNull final String fieldName,
164                   @NotNull final Date fieldValue,
165                   @NotNull final ByteStringBuffer buffer)
166  {
167    buffer.append(' ');
168    buffer.append(fieldName);
169    buffer.append("=\"");
170    valueToSanitizedString(fieldValue, buffer);
171    buffer.append('"');
172  }
173
174
175
176  /**
177   * {@inheritDoc}
178   */
179  @Override()
180  public void logSanitizedFieldToJSONFormattedLog(
181                   @NotNull final String fieldName,
182                   @NotNull final Date fieldValue,
183                   @NotNull final JSONBuffer buffer)
184  {
185    buffer.appendString(fieldName, valueToSanitizedString(fieldValue));
186  }
187
188
189
190  /**
191   * {@inheritDoc}
192   */
193  @Override()
194  public void logSanitizedValueToJSONFormattedLog(
195              @NotNull final Date value,
196              @NotNull final JSONBuffer buffer)
197  {
198    buffer.appendString(valueToSanitizedString(value));
199  }
200
201
202
203  /**
204   * {@inheritDoc}
205   */
206  @Override()
207  @NotNull()
208  public Date parseValue(@NotNull final String valueString)
209         throws RedactedValueException, TokenizedValueException,
210                LogSyntaxException
211  {
212    try
213    {
214      return StaticUtils.decodeRFC3339Time(valueString);
215    }
216    catch (final Exception e)
217    {
218      Debug.debugException(e);
219      if (valueStringIncludesRedactedComponent(valueString))
220      {
221        throw new RedactedValueException(
222             ERR_RFC_3339_LOG_SYNTAX_CANNOT_PARSE_REDACTED.get(), e);
223      }
224      else if (valueStringIncludesTokenizedComponent(valueString))
225      {
226        throw new TokenizedValueException(
227             ERR_RFC_3339_LOG_SYNTAX_CANNOT_PARSE_TOKENIZED.get(), e);
228      }
229      else
230      {
231        throw new LogSyntaxException(
232             ERR_RFC_3339_LOG_SYNTAX_CANNOT_PARSE.get(), e);
233      }
234    }
235  }
236
237
238
239  /**
240   * {@inheritDoc}
241   */
242  @Override()
243  public boolean valueStringIsCompletelyRedacted(
244                      @NotNull final String valueString)
245  {
246    return valueString.equals(REDACTED_STRING) ||
247         valueString.equals(REDACTED_RFC_3339_TIMESTAMP_STRING);
248  }
249
250
251
252  /**
253   * {@inheritDoc}
254   */
255  @Override()
256  public void redactEntireValue(@NotNull final ByteStringBuffer buffer)
257  {
258    buffer.append(REDACTED_RFC_3339_TIMESTAMP_STRING);
259  }
260
261
262
263  /**
264   * {@inheritDoc}
265   */
266  @Override()
267  public boolean completelyRedactedValueConformsToSyntax()
268  {
269    return true;
270  }
271
272
273
274  /**
275   * {@inheritDoc}
276   */
277  @Override()
278  public void logCompletelyRedactedFieldToTextFormattedLog(
279                   @NotNull final String fieldName,
280                   @NotNull final ByteStringBuffer buffer)
281  {
282    buffer.append(' ');
283    buffer.append(fieldName);
284    buffer.append("=\"");
285    buffer.append(REDACTED_RFC_3339_TIMESTAMP_STRING);
286    buffer.append('"');
287  }
288
289
290
291  /**
292   * {@inheritDoc}
293   */
294  @Override()
295  public void logCompletelyRedactedFieldToJSONFormattedLog(
296                   @NotNull final String fieldName,
297                   @NotNull final JSONBuffer buffer)
298  {
299    buffer.appendString(fieldName, REDACTED_RFC_3339_TIMESTAMP_STRING);
300  }
301
302
303
304  /**
305   * {@inheritDoc}
306   */
307  @Override()
308  public void logCompletelyRedactedValueToJSONFormattedLog(
309                   @NotNull final JSONBuffer buffer)
310  {
311    buffer.appendString(REDACTED_RFC_3339_TIMESTAMP_STRING);
312  }
313
314
315
316  /**
317   * {@inheritDoc}
318   */
319  @Override()
320  public boolean supportsRedactedComponents()
321  {
322    return false;
323  }
324
325
326
327  /**
328   * {@inheritDoc}
329   */
330  @Override()
331  public boolean valueStringIncludesRedactedComponent(
332                      @NotNull final String valueString)
333  {
334    return valueStringIsCompletelyRedacted(valueString);
335  }
336
337
338
339  /**
340   * {@inheritDoc}
341   */
342  @Override()
343  public boolean valueWithRedactedComponentsConformsToSyntax()
344  {
345    return true;
346  }
347
348
349
350  /**
351   * {@inheritDoc}
352   */
353  @Override()
354  public void logRedactedComponentsFieldToTextFormattedLog(
355                   @NotNull final String fieldName,
356                   @NotNull final Date fieldValue,
357                   @NotNull final ByteStringBuffer buffer)
358  {
359    logCompletelyRedactedFieldToTextFormattedLog(fieldName, buffer);
360  }
361
362
363
364  /**
365   * {@inheritDoc}
366   */
367  @Override()
368  public void logRedactedComponentsFieldToJSONFormattedLog(
369                   @NotNull final String fieldName,
370                   @NotNull final Date fieldValue,
371                   @NotNull final JSONBuffer buffer)
372  {
373    logCompletelyRedactedFieldToJSONFormattedLog(fieldName, buffer);
374  }
375
376
377
378  /**
379   * {@inheritDoc}
380   */
381  @Override()
382  public void logRedactedComponentsValueToJSONFormattedLog(
383                   @NotNull final Date value,
384                   @NotNull final JSONBuffer buffer)
385  {
386    logCompletelyRedactedValueToJSONFormattedLog(buffer);
387  }
388
389
390
391  /**
392   * {@inheritDoc}
393   */
394  @Override()
395  public boolean valueStringIsCompletelyTokenized(
396                      @NotNull final String valueString)
397  {
398    if (super.valueStringIsCompletelyTokenized(valueString))
399    {
400      return true;
401    }
402
403    return valueString.startsWith("8888-");
404  }
405
406
407
408  /**
409   * {@inheritDoc}
410   */
411  @Override()
412  public boolean completelyTokenizedValueConformsToSyntax()
413  {
414    return true;
415  }
416
417
418
419  /**
420   * {@inheritDoc}
421   */
422  @Override()
423  public void tokenizeEntireValue(@NotNull final Date value,
424                                  @NotNull final byte[] pepper,
425                                  @NotNull final ByteStringBuffer buffer)
426  {
427    // Concatenate the long value of the provided date and the pepper, and
428    // generate a SHA-256 digest from the result.
429    final byte[] tokenDigest;
430    final ByteStringBuffer tempBuffer = getTemporaryBuffer();
431    try
432    {
433      tempBuffer.append(value.getTime());
434      tempBuffer.append(pepper);
435      tokenDigest = sha256(tempBuffer);
436    }
437    finally
438    {
439      releaseTemporaryBuffer(tempBuffer);
440    }
441
442
443    // Generate a long value from the first eight digits of the digest.
444    long tokenizedTime = 0L;
445    for (int i=0; i < 8; i++)
446    {
447      tokenizedTime <<= 8;
448      tokenizedTime |= (tokenDigest[i] & 0xFFL);
449    }
450
451
452    // Create a Gregorian calendar in the UTC time zone, seed it with the
453    // tokenized time, and set the year to 8888.
454    final GregorianCalendar tokenCalendar =
455         new GregorianCalendar(StaticUtils.getUTCTimeZone());
456    tokenCalendar.setTimeInMillis(tokenizedTime);
457    tokenCalendar.set(GregorianCalendar.YEAR, TOKENIZED_DATE_YEAR);
458
459
460    // Append an RFC 3339 timestamp representation of the calendar value to the
461    // provided buffer.
462    buffer.append(StaticUtils.encodeRFC3339Time(tokenCalendar.getTime()));
463  }
464
465
466
467  /**
468   * {@inheritDoc}
469   */
470  @Override()
471  public void logCompletelyTokenizedFieldToTextFormattedLog(
472                   @NotNull final String fieldName,
473                   @NotNull final Date fieldValue,
474                   @NotNull final byte[] pepper,
475                   @NotNull final ByteStringBuffer buffer)
476  {
477    buffer.append(' ');
478    buffer.append(fieldName);
479    buffer.append("=\"");
480    tokenizeEntireValue(fieldValue, pepper, buffer);
481    buffer.append('"');
482  }
483
484
485
486  /**
487   * {@inheritDoc}
488   */
489  @Override()
490  public void logCompletelyTokenizedFieldToJSONFormattedLog(
491                   @NotNull final String fieldName,
492                   @NotNull final Date fieldValue,
493                   @NotNull final byte[] pepper,
494                   @NotNull final JSONBuffer buffer)
495  {
496    buffer.appendString(fieldName, tokenizeEntireValue(fieldValue, pepper));
497  }
498
499
500
501  /**
502   * {@inheritDoc}
503   */
504  @Override()
505  public void logCompletelyTokenizedValueToJSONFormattedLog(
506                   @NotNull final Date value,
507                   @NotNull final byte[] pepper,
508                   @NotNull final JSONBuffer buffer)
509  {
510    buffer.appendString(tokenizeEntireValue(value, pepper));
511  }
512
513
514
515  /**
516   * {@inheritDoc}
517   */
518  @Override()
519  public boolean supportsTokenizedComponents()
520  {
521    return false;
522  }
523
524
525
526  /**
527   * {@inheritDoc}
528   */
529  @Override()
530  public boolean valueStringIncludesTokenizedComponent(
531                      @NotNull final String valueString)
532  {
533    return valueStringIsCompletelyTokenized(valueString);
534  }
535
536
537
538  /**
539   * {@inheritDoc}
540   */
541  @Override()
542  public boolean valueWithTokenizedComponentsConformsToSyntax()
543  {
544    return true;
545  }
546
547
548
549  /**
550   * {@inheritDoc}
551   */
552  @Override()
553  public void logTokenizedComponentsFieldToTextFormattedLog(
554                   @NotNull final String fieldName,
555                   @NotNull final Date fieldValue,
556                   @NotNull final byte[] pepper,
557                   @NotNull final ByteStringBuffer buffer)
558  {
559    logCompletelyTokenizedFieldToTextFormattedLog(fieldName, fieldValue, pepper,
560         buffer);
561  }
562
563
564
565  /**
566   * {@inheritDoc}
567   */
568  @Override()
569  public void logTokenizedComponentsFieldToJSONFormattedLog(
570                   @NotNull final String fieldName,
571                   @NotNull final Date fieldValue,
572                   @NotNull final byte[] pepper,
573                   @NotNull final JSONBuffer buffer)
574  {
575    logCompletelyTokenizedFieldToJSONFormattedLog(fieldName, fieldValue, pepper,
576         buffer);
577  }
578
579
580
581  /**
582   * {@inheritDoc}
583   */
584  @Override()
585  public void logTokenizedComponentsValueToJSONFormattedLog(
586                   @NotNull final Date value,
587                   @NotNull final byte[] pepper,
588                   @NotNull final JSONBuffer buffer)
589  {
590    logCompletelyTokenizedValueToJSONFormattedLog(value, pepper, buffer);
591  }
592}