001package gudusoft.gsqlparser.slpc.identity; 002 003import gudusoft.gsqlparser.slpc.model.SlpcObject; 004 005import java.io.ByteArrayOutputStream; 006import java.math.BigInteger; 007import java.nio.ByteBuffer; 008import java.nio.CharBuffer; 009import java.nio.charset.CharacterCodingException; 010import java.nio.charset.CodingErrorAction; 011import java.nio.charset.StandardCharsets; 012import java.security.MessageDigest; 013import java.security.NoSuchAlgorithmException; 014import java.util.ArrayList; 015import java.util.Collections; 016import java.util.Comparator; 017import java.util.HashMap; 018import java.util.LinkedHashMap; 019import java.util.List; 020import java.util.Map; 021 022/** Normative SLPC identity/fingerprint V1 implementation (node fingerprint V2). */ 023public final class SlpcIdentity { 024 025 private SlpcIdentity() { 026 } 027 028 public static byte[] u64(long value) { 029 if (value < 0) { 030 throw new IllegalArgumentException("negative framed integer: " + value); 031 } 032 return ByteBuffer.allocate(8).putLong(value).array(); 033 } 034 035 public static byte[] string(String value) { 036 if (value == null) { 037 return new byte[] {0}; 038 } 039 byte[] raw = strictUtf8(value); 040 return concat(new byte[] {1}, u64(raw.length), raw); 041 } 042 043 private static byte[] strictUtf8(String value) { 044 try { 045 ByteBuffer encoded = StandardCharsets.UTF_8.newEncoder() 046 .onMalformedInput(CodingErrorAction.REPORT) 047 .onUnmappableCharacter(CodingErrorAction.REPORT) 048 .encode(CharBuffer.wrap(value)); 049 byte[] result = new byte[encoded.remaining()]; 050 encoded.get(result); 051 return result; 052 } catch (CharacterCodingException invalid) { 053 throw new IllegalArgumentException("SLPC_MALFORMED_IDENTIFIER: string is not encodable as strict UTF-8", invalid); 054 } 055 } 056 057 public static byte[] integer(long value) { 058 return concat(new byte[] {2}, u64(value)); 059 } 060 061 public static byte[] framedList(List<byte[]> values) { 062 ByteArrayOutputStream output = new ByteArrayOutputStream(); 063 write(output, new byte[] {3}); 064 write(output, u64(values.size())); 065 for (byte[] value : values) { 066 write(output, value); 067 } 068 return output.toByteArray(); 069 } 070 071 public static String artifactId(SlpcObject artifact) { 072 SlpcObject contentHash = artifact.object("contentHash"); 073 return digest("slart1-", "SLPC-ARTIFACT-ID-V1", concat( 074 string(requiredString(artifact, "sourceKind")), 075 string(requiredString(artifact, "logicalLocator")), 076 string(requiredString(artifact, "sqlDialect")), 077 string(contentHash == null ? null : contentHash.string("algorithm")), 078 string(contentHash == null ? null : contentHash.string("value")))); 079 } 080 081 public static String statementId(SlpcObject statement) { 082 SlpcObject fingerprint = statement.object("queryFingerprint"); 083 return digest("slstmt1-", "SLPC-STATEMENT-ID-V1", concat( 084 string(requiredString(statement, "artifactId")), 085 integer(requiredLong(statement, "statementIndex")), 086 string(fingerprint == null ? null : fingerprint.string("algorithm")), 087 string(fingerprint == null ? null : fingerprint.string("value")))); 088 } 089 090 public static String endpointId(SlpcObject endpoint) { 091 return digest("sle1-", "SLPC-ENDPOINT-ID-V1", concat( 092 string(requiredString(endpoint, "endpointLevel")), 093 string(requiredString(endpoint, "objectKind")), 094 endpointIdentityBytes(requiredObject(endpoint, "identity")), 095 endpointScopeBytes(endpoint))); 096 } 097 098 public static String candidateEndpointKey(SlpcObject endpoint) { 099 String canonical = endpoint.string("canonicalEndpointId"); 100 if (canonical != null && !canonical.isEmpty()) { 101 return canonical; 102 } 103 List<byte[]> display = new ArrayList<byte[]>(); 104 for (Object item : requiredArray(endpoint, "displaySegments")) { 105 SlpcObject segment = asObject(item); 106 display.add(concat( 107 string(requiredString(segment, "kind")), 108 string(requiredString(segment, "display")), 109 string(requiredString(segment, "inputForm")), 110 string(requiredString(segment, "provenance")))); 111 } 112 return digest("slcefp1-", "SLPC-CANDIDATE-ENDPOINT-FP-V1", concat( 113 string(requiredString(endpoint, "endpointLevel")), 114 string(requiredString(endpoint, "objectKind")), 115 framedList(display), 116 endpointIdentityBytes(requiredObject(endpoint, "identity")), 117 endpointScopeBytes(endpoint))); 118 } 119 120 public static String factId(SlpcObject fact, Map<String, SlpcObject> endpoints) { 121 String sourceRef = fact.string("sourceEndpointRef"); 122 String sourceId = sourceRef == null ? null : requiredEndpoint(endpoints, sourceRef).string("canonicalEndpointId"); 123 String targetId = requiredString(requiredEndpoint(endpoints, requiredString(fact, "targetEndpointRef")), "canonicalEndpointId"); 124 return digest("slf1-", "SLPC-FACT-ID-V1", concat( 125 string(sourceId), string(targetId), string(requiredString(fact, "axis")), 126 string(requiredString(fact, "role")), string(requiredString(fact, "operationKind")), 127 string(requiredString(fact, "operationInputKind")))); 128 } 129 130 public static String factCandidateFingerprint(SlpcObject fact, Map<String, SlpcObject> endpoints) { 131 String sourceRef = fact.string("sourceEndpointRef"); 132 String sourceKey = sourceRef == null ? null : candidateEndpointKey(requiredEndpoint(endpoints, sourceRef)); 133 String targetKey = candidateEndpointKey(requiredEndpoint(endpoints, requiredString(fact, "targetEndpointRef"))); 134 return digest("slfcfp1-", "SLPC-FACT-CANDIDATE-FP-V1", concat( 135 string(sourceKey), string(targetKey), string(requiredString(fact, "axis")), 136 string(requiredString(fact, "role")), string(requiredString(fact, "operationKind")), 137 string(requiredString(fact, "operationInputKind")))); 138 } 139 140 public static String datasetOperationInfluenceId(SlpcObject record, Map<String, SlpcObject> endpoints) { 141 return digest("slop1-", "SLPC-DATASET-OP-ID-V1", concat( 142 string(requiredString(requiredEndpoint(endpoints, requiredString(record, "sourceEndpointRef")), "canonicalEndpointId")), 143 string(requiredString(requiredEndpoint(endpoints, requiredString(record, "targetEndpointRef")), "canonicalEndpointId")), 144 string("SORT"))); 145 } 146 147 public static String datasetOperationCandidateFingerprint(SlpcObject record, Map<String, SlpcObject> endpoints) { 148 return digest("slopcfp1-", "SLPC-DATASET-OP-CANDIDATE-FP-V1", concat( 149 string(candidateEndpointKey(requiredEndpoint(endpoints, requiredString(record, "sourceEndpointRef")))), 150 string(candidateEndpointKey(requiredEndpoint(endpoints, requiredString(record, "targetEndpointRef")))), 151 string("SORT"))); 152 } 153 154 public static String objectRelationshipId(SlpcObject record, Map<String, SlpcObject> endpoints) { 155 return digest("slor1-", "SLPC-OBJECT-RELATIONSHIP-ID-V1", 156 relationshipSemanticPayload(record, endpoints, false)); 157 } 158 159 public static String objectRelationshipCandidateFingerprint(SlpcObject record, Map<String, SlpcObject> endpoints) { 160 return digest("slorcfp1-", "SLPC-OBJECT-RELATIONSHIP-CANDIDATE-FP-V1", 161 relationshipSemanticPayload(record, endpoints, true)); 162 } 163 164 public static String statementOperationId(SlpcObject record, Map<String, SlpcObject> endpoints) { 165 Map<String, List<String>> byRole = new HashMap<String, List<String>>(); 166 for (Object item : requiredArray(record, "participants")) { 167 SlpcObject participant = asObject(item); 168 String role = requiredString(participant, "role"); 169 if (!byRole.containsKey(role)) byRole.put(role, new ArrayList<String>()); 170 byRole.get(role).add(candidateEndpointKey(requiredEndpoint(endpoints, requiredString(participant, "endpointRef")))); 171 } 172 List<OperationRole> roles = operationRoles(requiredString(record, "operationKind")); 173 List<byte[]> groups = new ArrayList<byte[]>(); 174 for (OperationRole role : roles) { 175 List<String> values = byRole.containsKey(role.name) 176 ? new ArrayList<String>(byRole.get(role.name)) : new ArrayList<String>(); 177 if (role.setLike) { 178 Collections.sort(values, UTF8_STRING_COMPARATOR); 179 ArrayList<String> distinct = new ArrayList<String>(); 180 for (String value : values) { 181 if (distinct.isEmpty() || !distinct.get(distinct.size() - 1).equals(value)) distinct.add(value); 182 } 183 values = distinct; 184 } 185 List<byte[]> framed = new ArrayList<byte[]>(); 186 for (String value : values) framed.add(string(value)); 187 groups.add(concat(string(role.name), framedList(framed))); 188 } 189 return digest("slso1-", "SLPC-STATEMENT-OPERATION-ID-V1", concat( 190 string(requiredString(record, "statementId")), integer(requiredLong(record, "operationOrdinal")), 191 string(requiredString(record, "operationKind")), string(requiredString(record, "contentSemantics")), 192 framedList(groups))); 193 } 194 195 public static String joinSemanticsId(SlpcObject record, Map<String, SlpcObject> endpoints) { 196 List<byte[]> pairs = new ArrayList<byte[]>(); 197 for (Object item : requiredArray(record, "keyPairs")) { 198 SlpcObject pair = asObject(item); 199 pairs.add(concat(integer(requiredLong(pair, "ordinal")), 200 string(candidateEndpointKey(requiredEndpoint(endpoints, requiredString(pair, "leftFieldEndpointRef")))), 201 string(candidateEndpointKey(requiredEndpoint(endpoints, requiredString(pair, "rightFieldEndpointRef")))), 202 string(requiredString(pair, "operator")))); 203 } 204 SlpcObject predicate = record.object("predicate"); 205 SlpcObject fingerprint = predicate == null ? null : predicate.object("fingerprint"); 206 return digest("sljoin1-", "SLPC-JOIN-SEMANTICS-ID-V1", concat( 207 string(requiredString(record, "statementId")), integer(requiredLong(record, "joinOrdinal")), 208 string(requiredString(record, "joinType")), string(requiredString(record, "conditionForm")), 209 string(candidateEndpointKey(requiredEndpoint(endpoints, requiredString(record, "leftRelationEndpointRef")))), 210 string(candidateEndpointKey(requiredEndpoint(endpoints, requiredString(record, "rightRelationEndpointRef")))), 211 string(requiredString(record, "leftSideSemantics")), string(requiredString(record, "rightSideSemantics")), 212 framedList(pairs), string(fingerprint == null ? null : fingerprint.string("algorithm")), 213 string(fingerprint == null ? null : fingerprint.string("value")))); 214 } 215 216 public static String intermediateNodeFingerprint(SlpcObject node, SlpcObject graph, 217 Map<String, SlpcObject> endpoints) { 218 SlpcObject evidence = node.object("nodeEvidence"); 219 SlpcObject fingerprint = evidence == null ? null : evidence.object("fingerprint"); 220 String parentRef = node.string("parentNodeRef"); 221 Long parentOrdinal = null; 222 if (parentRef != null) { 223 for (Object item : requiredArray(graph, "nodes")) { 224 SlpcObject candidate = asObject(item); 225 if (parentRef.equals(candidate.string("nodeRef"))) { 226 parentOrdinal = requiredLong(candidate, "nodeOrdinal"); 227 break; 228 } 229 } 230 if (parentOrdinal == null) throw new IllegalArgumentException("intermediate node references missing parent: " + parentRef); 231 } 232 String boundaryRef = node.string("boundaryEndpointRef"); 233 return digest("slignfp2-", "SLPC-INTERMEDIATE-NODE-FP-V2", concat( 234 string(requiredString(graph, "statementId")), integer(requiredLong(graph, "graphOrdinal")), 235 integer(requiredLong(node, "nodeOrdinal")), string(requiredString(node, "nodeKind")), 236 string(node.string("localName")), parentOrdinal == null ? string(null) : integer(parentOrdinal.longValue()), 237 string(boundaryRef == null ? null : candidateEndpointKey(requiredEndpoint(endpoints, boundaryRef))), 238 string(fingerprint == null ? null : fingerprint.string("algorithm")), 239 string(fingerprint == null ? null : fingerprint.string("value")))); 240 } 241 242 public static String intermediateEdgeFingerprint(SlpcObject edge, Map<String, SlpcObject> endpoints, 243 Map<String, SlpcObject> nodes) { 244 String[] transform = evidenceFingerprint(edge.object("transformation")); 245 String[] predicate = evidenceFingerprint(edge.object("predicate")); 246 return digest("sligefp1-", "SLPC-INTERMEDIATE-EDGE-FP-V1", concat( 247 anchorBytes(requiredObject(edge, "source"), endpoints, nodes), 248 anchorBytes(requiredObject(edge, "target"), endpoints, nodes), 249 string(requiredString(edge, "axis")), string(requiredString(edge, "role")), 250 string(requiredString(edge, "operationKind")), string(requiredString(edge, "operationInputKind")), 251 string(transform[0]), string(transform[1]), string(predicate[0]), string(predicate[1]))); 252 } 253 254 public static String intermediatePathFingerprint(SlpcObject path, Map<String, SlpcObject> edges) { 255 List<byte[]> fingerprints = new ArrayList<byte[]>(); 256 for (Object item : requiredArray(path, "edgeRefs")) { 257 fingerprints.add(string(requiredString(requiredRecord(edges, (String) item, "edge"), "edgeFingerprint"))); 258 } 259 return digest("sligpfp1-", "SLPC-INTERMEDIATE-PATH-FP-V1", framedList(fingerprints)); 260 } 261 262 public static String intermediateGraphFingerprint(SlpcObject graph) { 263 List<String> nodes = fieldValues(requiredArray(graph, "nodes"), "nodeFingerprint"); 264 List<String> edges = fieldValues(requiredArray(graph, "edges"), "edgeFingerprint"); 265 List<String> paths = fieldValues(requiredArray(graph, "paths"), "pathFingerprint"); 266 Collections.sort(nodes); Collections.sort(edges); Collections.sort(paths); 267 return digest("sliggfp1-", "SLPC-INTERMEDIATE-GRAPH-FP-V1", concat( 268 string(requiredString(graph, "statementId")), integer(requiredLong(graph, "graphOrdinal")), 269 string(requiredString(graph, "graphKind")), framedStrings(nodes), framedStrings(edges), framedStrings(paths))); 270 } 271 272 public static String interactiveStepFingerprint(SlpcObject step, SlpcObject graph, 273 Map<String, SlpcObject> endpoints, Map<String, SlpcObject> nodes, Map<String, SlpcObject> edges) { 274 String[] evidence = evidenceFingerprint(step.object("expressionEvidence")); 275 List<byte[]> bindings = new ArrayList<byte[]>(); 276 for (Object item : requiredArray(step, "inputBindings")) { 277 SlpcObject binding = asObject(item); 278 SlpcObject edge = requiredRecord(edges, requiredString(binding, "edgeRef"), "edge"); 279 bindings.add(concat(integer(requiredLong(binding, "ordinal")), string(requiredString(binding, "roleCode")), 280 string(requiredString(edge, "edgeFingerprint")))); 281 } 282 return digest("sligsfp1-", "SLPC-INTERACTIVE-EXPLAIN-STEP-FP-V1", concat( 283 string(requiredString(graph, "graphFingerprint")), integer(requiredLong(step, "stepOrdinal")), 284 string(requiredString(step, "operationFamily")), string(requiredString(step, "operationCode")), 285 framedList(bindings), anchorBytes(requiredObject(step, "resultAnchor"), endpoints, nodes), 286 string(evidence[0]), string(evidence[1]))); 287 } 288 289 public static String occurrenceId(SlpcObject occurrence, Map<String, String> ownerSemanticKeys) { 290 SlpcObject owner = requiredObject(occurrence, "ownerRef"); 291 String ownerKey = requiredString(owner, "kind") + "\u0000" + requiredString(owner, "id"); 292 String semanticKey = ownerSemanticKeys.get(ownerKey); 293 if (semanticKey == null) throw new IllegalArgumentException("missing occurrence owner semantic key: " + ownerKey); 294 String[] transformation = evidenceFingerprint(occurrence.object("transformation")); 295 String[] predicate = evidenceFingerprint(occurrence.object("predicate")); 296 SlpcObject origin = requiredObject(occurrence, "origin"); 297 return digest("slocc1-", "SLPC-OCCURRENCE-ID-V1", concat( 298 string(requiredString(owner, "kind")), string(semanticKey), string(requiredString(occurrence, "artifactId")), 299 string(occurrence.string("statementId")), string(requiredString(occurrence, "evidenceKind")), 300 string(requiredString(origin, "engineKind")), string(requiredString(origin, "adapterName")), 301 string(occurrence.string("procedureCanonicalEndpointId")), string(transformation[0]), string(transformation[1]), 302 string(predicate[0]), string(predicate[1]))); 303 } 304 305 public static String literalTextHash(String sqlDialect, String literalKind, String literalText) { 306 return digest("", "SLPC-LITERAL-TEXT-V1", concat(string(sqlDialect), string(literalKind), string(literalText))); 307 } 308 309 public static String redactedLiteralHash(String sqlDialect, String literalKind, long redactionOrdinal) { 310 return digest("", "SLPC-REDACTED-LITERAL-V1", concat(string(sqlDialect), string(literalKind), integer(redactionOrdinal))); 311 } 312 313 public static Map<String, SlpcObject> index(List<Object> records, String key) { 314 LinkedHashMap<String, SlpcObject> result = new LinkedHashMap<String, SlpcObject>(); 315 for (Object item : records) { 316 SlpcObject record = asObject(item); 317 String value = requiredString(record, key); 318 if (result.put(value, record) != null) throw new IllegalArgumentException("duplicate " + key + ": " + value); 319 } 320 return result; 321 } 322 323 private static byte[] relationshipSemanticPayload(SlpcObject record, Map<String, SlpcObject> endpoints, boolean candidate) { 324 String kind = requiredString(record, "relationshipKind"); 325 List<String> roleOrder = relationshipRoles(kind); 326 Map<String, String> participants = new HashMap<String, String>(); 327 for (Object item : requiredArray(record, "participants")) { 328 SlpcObject participant = asObject(item); 329 participants.put(requiredString(participant, "role"), requiredString(participant, "endpointRef")); 330 } 331 List<byte[]> keys = new ArrayList<byte[]>(); 332 for (String role : roleOrder) { 333 SlpcObject endpoint = requiredEndpoint(endpoints, participants.get(role)); 334 String key = candidate ? candidateEndpointKey(endpoint) : requiredString(endpoint, "canonicalEndpointId"); 335 keys.add(concat(string(role), string(key))); 336 } 337 return concat(string(kind), framedList(keys), relationshipDetailBytes(record, endpoints, candidate)); 338 } 339 340 private static byte[] relationshipDetailBytes(SlpcObject record, Map<String, SlpcObject> endpoints, boolean candidate) { 341 String kind = requiredString(record, "relationshipKind"); 342 List<byte[]> details = new ArrayList<byte[]>(); 343 if ("PRIMARY_KEY".equals(kind) || "UNIQUE_KEY".equals(kind)) { 344 for (Object item : requiredArray(record, "keyFields")) { 345 SlpcObject field = asObject(item); 346 details.add(concat(integer(requiredLong(field, "ordinal")), 347 string(endpointKey(endpoints, requiredString(field, "fieldEndpointRef"), candidate)))); 348 } 349 return framedList(details); 350 } 351 if ("FOREIGN_KEY_REFERENCE".equals(kind)) { 352 for (Object item : requiredArray(record, "keyPairs")) { 353 SlpcObject pair = asObject(item); 354 details.add(concat(integer(requiredLong(pair, "ordinal")), 355 string(endpointKey(endpoints, requiredString(pair, "foreignKeyFieldRef"), candidate)), 356 string(endpointKey(endpoints, requiredString(pair, "referencedFieldRef"), candidate)))); 357 } 358 return framedList(details); 359 } 360 return new byte[] {0}; 361 } 362 363 private static String endpointKey(Map<String, SlpcObject> endpoints, String ref, boolean candidate) { 364 SlpcObject endpoint = requiredEndpoint(endpoints, ref); 365 return candidate ? candidateEndpointKey(endpoint) : requiredString(endpoint, "canonicalEndpointId"); 366 } 367 368 private static byte[] endpointScopeBytes(SlpcObject endpoint) { 369 SlpcObject scope = endpoint.object("scope"); 370 if (scope == null) return new byte[] {0}; 371 return concat(string("SCOPE"), string(requiredString(scope, "scopeKind")), 372 string(requiredString(scope, "scopeId")), string(scope.string("definedAtStatementId"))); 373 } 374 375 private static byte[] endpointIdentityBytes(SlpcObject identity) { 376 String kind = requiredString(identity, "kind"); 377 if ("COMPOSITE_EXACT".equals(kind) || "COMPOSITE_APPROXIMATE".equals(kind)) { 378 List<byte[]> segments = new ArrayList<byte[]>(); 379 for (Object item : requiredArray(identity, "segments")) segments.add(segmentBytes(asObject(item))); 380 return concat(string(kind), string(requiredString(identity, "qualificationPolicyId")), framedList(segments)); 381 } 382 if ("AUTHORITATIVE_EXTERNAL".equals(kind)) { 383 return concat(string(kind), string(requiredString(identity, "system")), 384 string(requiredString(identity, "identityVersion")), string(requiredString(identity, "opaqueId"))); 385 } 386 if ("URI_EXACT".equals(kind)) { 387 return concat(string(kind), string(requiredString(identity, "normalizationPolicyId")), 388 string(requiredString(identity, "scheme")), string("SHA256"), string(requiredString(identity, "locatorDigest"))); 389 } 390 if ("UNAVAILABLE".equals(kind)) return concat(string(kind), string(requiredString(identity, "reason"))); 391 throw new IllegalArgumentException("unsupported endpoint identity: " + kind); 392 } 393 394 private static byte[] segmentBytes(SlpcObject segment) { 395 String kind = requiredString(segment, "identityKind"); 396 byte[] base = concat(string(requiredString(segment, "segmentKind")), string(kind)); 397 if ("GSP_PERSISTENT_V1".equals(kind) || "GSP_APPROXIMATE_V1".equals(kind)) { 398 return concat(base, string(requiredString(segment, "policyId")), string(requiredString(segment, "objectGroup")), 399 string(requiredString(segment, "payload"))); 400 } 401 if ("EXTERNAL_SEGMENT_V1".equals(kind)) { 402 return concat(base, string(requiredString(segment, "system")), string(requiredString(segment, "identityVersion")), 403 string(requiredString(segment, "opaqueId"))); 404 } 405 throw new IllegalArgumentException("unsupported identity segment kind: " + kind); 406 } 407 408 private static byte[] anchorBytes(SlpcObject anchor, Map<String, SlpcObject> endpoints, Map<String, SlpcObject> nodes) { 409 if ("ENDPOINT".equals(requiredString(anchor, "kind"))) { 410 return concat(string("ENDPOINT"), 411 string(candidateEndpointKey(requiredEndpoint(endpoints, requiredString(anchor, "endpointRef")))), 412 string(requiredString(anchor, "portCode"))); 413 } 414 SlpcObject node = requiredRecord(nodes, requiredString(anchor, "nodeRef"), "node"); 415 return concat(string("NODE"), string(requiredString(node, "nodeFingerprint")), string(requiredString(anchor, "portCode"))); 416 } 417 418 private static String[] evidenceFingerprint(SlpcObject evidence) { 419 if (evidence == null) return new String[] {null, null}; 420 SlpcObject fingerprint = requiredObject(evidence, "fingerprint"); 421 return new String[] {requiredString(fingerprint, "algorithm"), requiredString(fingerprint, "value")}; 422 } 423 424 private static byte[] framedStrings(List<String> values) { 425 List<byte[]> framed = new ArrayList<byte[]>(); 426 for (String value : values) framed.add(string(value)); 427 return framedList(framed); 428 } 429 430 private static List<String> fieldValues(List<Object> values, String field) { 431 List<String> result = new ArrayList<String>(); 432 for (Object value : values) result.add(requiredString(asObject(value), field)); 433 return result; 434 } 435 436 private static String digest(String prefix, String domain, byte[] payload) { 437 try { 438 MessageDigest digest = MessageDigest.getInstance("SHA-256"); 439 digest.update(domain.getBytes(StandardCharsets.US_ASCII)); 440 digest.update((byte) 0); 441 digest.update(payload); 442 byte[] value = digest.digest(); 443 StringBuilder hex = new StringBuilder(prefix); 444 for (byte item : value) hex.append(String.format("%02x", item & 0xff)); 445 return hex.toString(); 446 } catch (NoSuchAlgorithmException impossible) { 447 throw new IllegalStateException("SHA-256 is required by the Java runtime", impossible); 448 } 449 } 450 451 private static byte[] concat(byte[]... values) { 452 ByteArrayOutputStream output = new ByteArrayOutputStream(); 453 for (byte[] value : values) write(output, value); 454 return output.toByteArray(); 455 } 456 457 private static void write(ByteArrayOutputStream output, byte[] value) { 458 output.write(value, 0, value.length); 459 } 460 461 private static SlpcObject requiredEndpoint(Map<String, SlpcObject> endpoints, String ref) { 462 return requiredRecord(endpoints, ref, "endpoint"); 463 } 464 465 private static SlpcObject requiredRecord(Map<String, SlpcObject> records, String ref, String kind) { 466 SlpcObject value = records.get(ref); 467 if (value == null) throw new IllegalArgumentException("missing " + kind + " reference: " + ref); 468 return value; 469 } 470 471 private static SlpcObject requiredObject(SlpcObject object, String field) { 472 SlpcObject value = object.object(field); 473 if (value == null) throw new IllegalArgumentException("missing object field: " + field); 474 return value; 475 } 476 477 private static List<Object> requiredArray(SlpcObject object, String field) { 478 List<Object> value = object.array(field); 479 if (value == null) throw new IllegalArgumentException("missing array field: " + field); 480 return value; 481 } 482 483 private static String requiredString(SlpcObject object, String field) { 484 String value = object.string(field); 485 if (value == null) throw new IllegalArgumentException("missing string field: " + field); 486 return value; 487 } 488 489 private static long requiredLong(SlpcObject object, String field) { 490 BigInteger value = object.integer(field); 491 if (value == null) throw new IllegalArgumentException("missing integer field: " + field); 492 return value.longValueExact(); 493 } 494 495 private static SlpcObject asObject(Object value) { 496 if (!(value instanceof SlpcObject)) throw new IllegalArgumentException("expected object"); 497 return (SlpcObject) value; 498 } 499 500 private static List<String> relationshipRoles(String kind) { 501 if ("PRIMARY_KEY".equals(kind) || "UNIQUE_KEY".equals(kind)) return list("KEY_RELATION"); 502 if ("FOREIGN_KEY_REFERENCE".equals(kind)) return list("FOREIGN_KEY_RELATION", "REFERENCED_RELATION"); 503 if ("CALLS".equals(kind)) return list("CALLER_ROUTINE", "CALLEE_ROUTINE"); 504 if ("STREAMS_FROM".equals(kind)) return list("STREAM", "BASE_RELATION"); 505 if ("SYNONYM_OF".equals(kind)) return list("SYNONYM", "BASE_OBJECT"); 506 if ("READS_FROM".equals(kind)) return list("CONSUMER_RELATION", "STORAGE_ASSET"); 507 if ("WRITES_TO".equals(kind)) return list("PRODUCER_RELATION", "STORAGE_ASSET"); 508 throw new IllegalArgumentException("unsupported relationship kind: " + kind); 509 } 510 511 private static List<OperationRole> operationRoles(String kind) { 512 if ("QUERY".equals(kind)) return roles("READ_RELATION", true, "OUTPUT_RELATION", true); 513 if ("INSERT".equals(kind) || "UPDATE".equals(kind) || "DELETE".equals(kind)) return roles("WRITE_TARGET", false, "READ_SOURCE", true); 514 if ("MERGE".equals(kind)) return roles("MERGE_TARGET", false, "MERGE_SOURCE", true); 515 if ("CREATE_TABLE".equals(kind)) return roles("CREATED_OBJECT", false); 516 if ("CREATE_TABLE_AS".equals(kind) || "CREATE_VIEW".equals(kind) || "CREATE_MATERIALIZED_VIEW".equals(kind)) return roles("CREATED_OBJECT", false, "READ_SOURCE", true); 517 if ("CREATE_EXTERNAL_TABLE".equals(kind) || "CREATE_STAGE".equals(kind)) return roles("CREATED_OBJECT", false, "STORAGE_SOURCE", true); 518 if ("CREATE_STREAM".equals(kind)) return roles("CREATED_STREAM", false, "BASE_RELATION", false); 519 if ("CREATE_ROUTINE".equals(kind)) return roles("CREATED_ROUTINE", false); 520 if ("CREATE_SYNONYM".equals(kind)) return roles("SYNONYM", false, "BASE_OBJECT", false); 521 if ("CREATE_INDEX".equals(kind)) return roles("CREATED_INDEX", false, "INDEXED_RELATION", false); 522 if ("RENAME".equals(kind)) return roles("BEFORE_OBJECT", false, "AFTER_OBJECT", false); 523 if ("SWAP".equals(kind)) return roles("SWAP_LEFT_OBJECT", false, "SWAP_RIGHT_OBJECT", false); 524 if ("CLONE".equals(kind)) return roles("CLONE_SOURCE", false, "CLONE_TARGET", false); 525 if ("LOAD".equals(kind)) return roles("STORAGE_SOURCE", true, "WRITE_TARGET", false); 526 if ("UNLOAD".equals(kind) || "INSERT_OVERWRITE_DIRECTORY".equals(kind)) return roles("READ_SOURCE", true, "STORAGE_TARGET", false); 527 if ("CALL_ROUTINE".equals(kind)) return roles("CALLEE_ROUTINE", false, "ARGUMENT_SOURCE", true); 528 if ("ALTER".equals(kind) || "DROP".equals(kind) || "TRUNCATE".equals(kind)) return roles("AFFECTED_OBJECT", false); 529 if ("OTHER".equals(kind)) return roles("AFFECTED_OBJECT", true); 530 throw new IllegalArgumentException("unsupported operation kind: " + kind); 531 } 532 533 private static List<OperationRole> roles(Object... values) { 534 List<OperationRole> result = new ArrayList<OperationRole>(); 535 for (int index = 0; index < values.length; index += 2) { 536 result.add(new OperationRole((String) values[index], ((Boolean) values[index + 1]).booleanValue())); 537 } 538 return result; 539 } 540 541 private static List<String> list(String... values) { 542 ArrayList<String> result = new ArrayList<String>(); 543 Collections.addAll(result, values); 544 return result; 545 } 546 547 private static final class OperationRole { 548 private final String name; 549 private final boolean setLike; 550 private OperationRole(String name, boolean setLike) { this.name = name; this.setLike = setLike; } 551 } 552 553 private static final Comparator<String> UTF8_STRING_COMPARATOR = new Comparator<String>() { 554 @Override public int compare(String left, String right) { 555 byte[] a = left.getBytes(StandardCharsets.UTF_8); byte[] b = right.getBytes(StandardCharsets.UTF_8); 556 int common = Math.min(a.length, b.length); 557 for (int i = 0; i < common; i++) { int d = (a[i] & 255) - (b[i] & 255); if (d != 0) return d; } 558 return a.length - b.length; 559 } 560 }; 561}