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