001/* 002 * Copyright 2022-2026 Revetware LLC. 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016 017package com.soklet; 018 019import org.jspecify.annotations.NonNull; 020import org.jspecify.annotations.Nullable; 021 022import javax.annotation.concurrent.ThreadSafe; 023 024import static java.util.Objects.requireNonNull; 025 026/** 027 * Secret-free deployment-comparison fingerprint for the active MCP 028 * trace-correlation configuration. 029 * <p> 030 * This value is operational metadata only, not an authentication or token 031 * derivation input. 032 * 033 * @author <a href="https://www.revetkn.com">Mark Allen</a> 034 */ 035@ThreadSafe 036public final class McpTraceCorrelationFingerprint { 037 @NonNull 038 private final String value; 039 040 @NonNull 041 static McpTraceCorrelationFingerprint fromValue( 042 @NonNull String value) { 043 return new McpTraceCorrelationFingerprint(value); 044 } 045 046 private McpTraceCorrelationFingerprint(@NonNull String value) { 047 requireNonNull(value); 048 if (!isCanonicalSha256Base64Url(value)) 049 throw new IllegalArgumentException( 050 "Trace-correlation fingerprint must be a canonical unpadded Base64URL SHA-256 value."); 051 this.value = value; 052 } 053 054 /** @return unpadded Base64URL fingerprint value */ 055 @NonNull 056 public String getValue() { 057 return this.value; 058 } 059 060 /** @return whether this value has the same encoded fingerprint */ 061 @Override 062 public boolean equals(@Nullable Object other) { 063 if (this == other) 064 return true; 065 if (!(other instanceof McpTraceCorrelationFingerprint fingerprint)) 066 return false; 067 return this.value.equals(fingerprint.value); 068 } 069 070 /** @return value-based hash code */ 071 @Override 072 public int hashCode() { 073 return this.value.hashCode(); 074 } 075 076 /** @return the unpadded Base64URL fingerprint value */ 077 @Override 078 @NonNull 079 public String toString() { 080 return this.value; 081 } 082 083 private static boolean isCanonicalSha256Base64Url(@NonNull String value) { 084 if (value.length() != 43) 085 return false; 086 for (int index = 0; index < value.length(); ++index) { 087 char character = value.charAt(index); 088 if (!((character >= 'A' && character <= 'Z') 089 || (character >= 'a' && character <= 'z') 090 || (character >= '0' && character <= '9') 091 || character == '-' || character == '_')) 092 return false; 093 } 094 char finalCharacter = value.charAt(value.length() - 1); 095 return "AEIMQUYcgkosw048".indexOf(finalCharacter) >= 0; 096 } 097}