// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. using System.Collections; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.IO.Hashing; using System.Runtime.InteropServices; namespace System.Runtime.Serialization.BinaryFormat; internal sealed class RecordMap : IReadOnlyDictionary<int, SerializationRecord> { private readonly Dictionary<int, SerializationRecord> _map = new(CollisionResistantInt32Comparer.Instance); public IEnumerable<int> Keys => _map.Keys; public IEnumerable<SerializationRecord> Values => _map.Values; public int Count => _map.Count; public SerializationRecord this[int objectId] => _map[objectId]; public bool ContainsKey(int key) => _map.ContainsKey(key); public bool TryGetValue(int key, [MaybeNullWhen(false)] out SerializationRecord value) => _map.TryGetValue(key, out value); public IEnumerator<KeyValuePair<int, SerializationRecord>> GetEnumerator() => _map.GetEnumerator(); IEnumerator IEnumerable.GetEnumerator() => _map.GetEnumerator(); internal void Add(SerializationRecord record) { // From https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-nrbf/0a192be0-58a1-41d0-8a54-9c91db0ab7bf: // "If the ObjectId is not referenced by any MemberReference in the serialization stream, // then the ObjectId SHOULD be positive, but MAY be negative." if (record.ObjectId != SerializationRecord.NoId) { if (record.ObjectId < 0) { // Negative record Ids should never be referenced. Duplicate negative ids can be // exported by the writer. The root object Id can be negative. _map[record.ObjectId] = record; } else { #if NET if (_map.TryAdd(record.ObjectId, record)) { return; } #else if (!_map.ContainsKey(record.ObjectId)) { _map.Add(record.ObjectId, record); return; } #endif throw new SerializationException(SR.Format(SR.Serialization_DuplicateSerializationRecordId, record.ObjectId)); } } } internal SerializationRecord GetRootRecord(SerializedStreamHeaderRecord header) { SerializationRecord rootRecord = _map[header.RootId]; if (rootRecord is SystemClassWithMembersAndTypesRecord systemClass) { // update the record map, so it's visible also to those who access it via Id _map[header.RootId] = rootRecord = systemClass.TryToMapToUserFriendly(); } return rootRecord; } // keys (32-bit integer ids) are payload-provided so we need a collision-resistant comparer private sealed class CollisionResistantInt32Comparer : IEqualityComparer<int> { internal static CollisionResistantInt32Comparer Instance { get; } = new(); private CollisionResistantInt32Comparer() { } public bool Equals(int x, int y) => x == y; public int GetHashCode(int obj) { #if NET Span<int> integers = new(ref obj); #else Span<int> integers = stackalloc int[1] { obj }; #endif return (int)XxHash32.HashToUInt32(MemoryMarshal.AsBytes(integers)); } } } |