/// <summary> /// Produces a hash for the paths of adjacent bnodes for a bnode, /// incorporating all information about its subgraph of bnodes. /// </summary> /// <remarks> /// Produces a hash for the paths of adjacent bnodes for a bnode, /// incorporating all information about its subgraph of bnodes. This method /// will recursively pick adjacent bnode permutations that produce the /// lexicographically-least 'path' serializations. /// </remarks> /// <param name="id">the ID of the bnode to hash paths for.</param> /// <param name="bnodes">the map of bnode quads.</param> /// <param name="namer">the canonical bnode namer.</param> /// <param name="pathNamer">the namer used to assign names to adjacent bnodes.</param> /// <param name="callback">(err, result) called once the operation completes.</param> private static NormalizeUtils.HashResult HashPaths(string id, IDictionary <string, IDictionary <string, object> > bnodes, UniqueNamer namer, UniqueNamer pathNamer) { #if !PORTABLE MessageDigest md = null; try { // create SHA-1 digest md = MessageDigest.GetInstance("SHA-1"); JObject groups = new JObject(); IList <string> groupHashes; IList <RDFDataset.Quad> quads = (IList <RDFDataset.Quad>)bnodes[id]["quads"]; for (int hpi = 0; ; hpi++) { if (hpi == quads.Count) { // done , hash groups groupHashes = new List <string>(groups.GetKeys()); ((List <string>)groupHashes).Sort(StringComparer.CurrentCultureIgnoreCase); for (int hgi = 0; ; hgi++) { if (hgi == groupHashes.Count) { NormalizeUtils.HashResult res = new NormalizeUtils.HashResult(); res.hash = EncodeHex(md.Digest()); res.pathNamer = pathNamer; return(res); } // digest group hash string groupHash = groupHashes[hgi]; md.Update(JsonLD.JavaCompat.GetBytesForString(groupHash, "UTF-8")); // choose a path and namer from the permutations string chosenPath = null; UniqueNamer chosenNamer = null; NormalizeUtils.Permutator permutator = new NormalizeUtils.Permutator((JArray)groups[groupHash]); while (true) { bool contPermutation = false; bool breakOut = false; JArray permutation = permutator.Next(); UniqueNamer pathNamerCopy = pathNamer.Clone(); // build adjacent path string path = string.Empty; JArray recurse = new JArray(); foreach (string bnode in permutation) { // use canonical name if available if (namer.IsNamed(bnode)) { path += namer.GetName(bnode); } else { // recurse if bnode isn't named in the path // yet if (!pathNamerCopy.IsNamed(bnode)) { recurse.Add(bnode); } path += pathNamerCopy.GetName(bnode); } // skip permutation if path is already >= chosen // path if (chosenPath != null && path.Length >= chosenPath.Length && string.CompareOrdinal (path, chosenPath) > 0) { // return nextPermutation(true); if (permutator.HasNext()) { contPermutation = true; } else { // digest chosen path and update namer md.Update(JsonLD.JavaCompat.GetBytesForString(chosenPath, "UTF-8")); pathNamer = chosenNamer; // hash the nextGroup breakOut = true; } break; } } // if we should do the next permutation if (contPermutation) { continue; } // if we should stop processing this group if (breakOut) { break; } // does the next recursion for (int nrn = 0; ; nrn++) { if (nrn == recurse.Count) { // return nextPermutation(false); if (chosenPath == null || string.CompareOrdinal(path, chosenPath) < 0) { chosenPath = path; chosenNamer = pathNamerCopy; } if (!permutator.HasNext()) { // digest chosen path and update namer md.Update(JsonLD.JavaCompat.GetBytesForString(chosenPath, "UTF-8")); pathNamer = chosenNamer; // hash the nextGroup breakOut = true; } break; } // do recursion string bnode_1 = (string)recurse[nrn]; NormalizeUtils.HashResult result = HashPaths(bnode_1, bnodes, namer, pathNamerCopy); path += pathNamerCopy.GetName(bnode_1) + "<" + result.hash + ">"; pathNamerCopy = result.pathNamer; // skip permutation if path is already >= chosen // path if (chosenPath != null && path.Length >= chosenPath.Length && string.CompareOrdinal (path, chosenPath) > 0) { // return nextPermutation(true); if (!permutator.HasNext()) { // digest chosen path and update namer md.Update(JsonLD.JavaCompat.GetBytesForString(chosenPath, "UTF-8")); pathNamer = chosenNamer; // hash the nextGroup breakOut = true; } break; } } // do next recursion // if we should stop processing this group if (breakOut) { break; } } } } // get adjacent bnode IDictionary <string, object> quad = (IDictionary <string, object>)quads[hpi]; string bnode_2 = GetAdjacentBlankNodeName((IDictionary <string, object>)quad["subject" ], id); string direction = null; if (bnode_2 != null) { // normal property direction = "p"; } else { bnode_2 = GetAdjacentBlankNodeName((IDictionary <string, object>)quad["object"], id ); if (bnode_2 != null) { // reverse property direction = "r"; } } if (bnode_2 != null) { // get bnode name (try canonical, path, then hash) string name; if (namer.IsNamed(bnode_2)) { name = namer.GetName(bnode_2); } else { if (pathNamer.IsNamed(bnode_2)) { name = pathNamer.GetName(bnode_2); } else { name = HashQuads(bnode_2, bnodes, namer); } } // hash direction, property, end bnode name/hash using (MessageDigest md1 = MessageDigest.GetInstance("SHA-1")) { // String toHash = direction + (String) ((Map<String, // Object>) quad.get("predicate")).get("value") + name; md1.Update(JsonLD.JavaCompat.GetBytesForString(direction, "UTF-8")); md1.Update(JsonLD.JavaCompat.GetBytesForString(((string)((IDictionary <string, object>)quad["predicate"])["value"]), "UTF-8")); md1.Update(JsonLD.JavaCompat.GetBytesForString(name, "UTF-8")); string groupHash = EncodeHex(md1.Digest()); if (groups.ContainsKey(groupHash)) { ((JArray)groups[groupHash]).Add(bnode_2); } else { JArray tmp = new JArray(); tmp.Add(bnode_2); groups[groupHash] = tmp; } } } } } catch { // TODO: i don't expect that SHA-1 is even NOT going to be // available? // look into this further throw; } finally { md?.Dispose(); } #else throw new PlatformNotSupportedException(); #endif }
private IDictionary <string, Object> hashNDegreeQuads(IdentifierIssuer issuer, string id) { /* * 1) Create a hash to related blank nodes map for storing hashes that * identify related blank nodes. * Note: 2) and 3) handled within `createHashToRelated` */ IDictionary <string, IList <string> > hashToRelated = this.createHashToRelated(issuer, id); /* * 4) Create an empty string, data to hash. * Note: We create a hash object instead. */ string mdString = ""; /* * 5) For each related hash to blank node list mapping in hash to * related blank nodes map, sorted lexicographically by related hash: */ sortMapKeys(hashToRelated); foreach (var hash in hashToRelated.Keys) { var blankNodes = hashToRelated[hash]; // 5.1) Append the related hash to the data to hash. mdString += hash; // 5.2) Create a string chosen path. string chosenPath = " "; // 5.3) Create an unset chosen issuer variable. IdentifierIssuer chosenIssuer = null; // 5.4) For each permutation of blank node list: string path = ""; List <string> recursionList = null; IdentifierIssuer issuerCopy = null; bool skipToNextPerm = false; NormalizeUtils.Permutator permutator = new NormalizeUtils.Permutator(JArray.FromObject(blankNodes)); while (permutator.HasNext()) { var permutation = permutator.Next(); // 5.4.1) Create a copy of issuer, issuer copy. issuerCopy = (IdentifierIssuer)issuer.Clone(); // 5.4.2) Create a string path. path = ""; /* * 5.4.3) Create a recursion list, to store blank node * identifiers that must be recursively processed by this * algorithm. */ recursionList = new List <string>(); // 5.4.4) For each related in permutation: foreach (var related in permutation) { /* * 5.4.4.1) If a canonical identifier has been issued for * related, append it to path. */ if (this.canonicalIssuer.hasID(related.ToString())) { path += this.canonicalIssuer.getId(related.ToString()); } // 5.4.4.2) Otherwise: else { /* * 5.4.4.2.1) If issuer copy has not issued an * identifier for related, append related to recursion * list. */ if (!issuerCopy.hasID(related.ToString())) { recursionList.Add(related.ToString()); } /* * 5.4.4.2.2) Use the Issue Identifier algorithm, * passing issuer copy and related and append the result * to path. */ path += issuerCopy.getId(related.ToString()); } /* * 5.4.4.3) If chosen path is not empty and the length of * path is greater than or equal to the length of chosen * path and path is lexicographically greater than chosen * path, then skip to the next permutation. */ if (chosenPath.Length != 0 && path.Length >= chosenPath.Length && path.CompareTo(chosenPath) == 1) { skipToNextPerm = true; break; } } } if (skipToNextPerm) { continue; } // 5.4.5) For each related in recursion list: foreach (var related in recursionList) { /* * 5.4.5.1) Set result to the result of recursively * executing the Hash N-Degree Quads algorithm, passing * related for identifier and issuer copy for path * identifier issuer. */ IDictionary <string, object> result = hashNDegreeQuads(issuerCopy, related); /* * 5.4.5.2) Use the Issue Identifier algorithm, passing * issuer copy and related and append the result to path. */ path += "<" + (string)result["hash"] + ">"; /* * 5.4.5.4) Set issuer copy to the identifier issuer in * result. */ issuerCopy = (IdentifierIssuer)result["issuer"]; /* * 5.4.5.5) If chosen path is not empty and the length of * path is greater than or equal to the length of chosen * path and path is lexicographically greater than chosen * path, then skip to the next permutation. */ if (chosenPath.Length != 0 && path.Length >= chosenPath.Length && path.CompareTo(chosenPath) == 1) { skipToNextPerm = true; break; } } if (skipToNextPerm) { continue; } /* * 5.4.6) If chosen path is empty or path is lexicographically * less than chosen path, set chosen path to path and chosen * issuer to issuer copy. */ if (chosenPath.Length == 0 || path.CompareTo(chosenPath) == -1) { chosenPath = path; chosenIssuer = issuerCopy; } // 5.5) Append chosen path to data to hash. mdString += chosenPath; // 5.6) Replace issuer, by reference, with chosen issuer. issuer = chosenIssuer; /* * 6) Return issuer and the hash that results from passing data to hash * through the hash algorithm. */ } /* * 6) Return issuer and the hash that results from passing data to hash * through the hash algorithm. */ IDictionary <string, object> hashQuad = new Dictionary <string, object>(); hashQuad.Add("hash", NormalizeUtils.sha256Hash(Encoding.ASCII.GetBytes(mdString))); hashQuad.Add("issuer", issuer); return(hashQuad); }