blob: 1e94212cff06a70a8234aa69306f3bc34a8dd04b [file] [log] [blame]
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
namespace Apache.Fory;
internal readonly record struct CanonicalRefSignature(
Type Type,
ulong HashLo,
ulong HashHi,
int Length);
internal sealed class CanonicalRefEntry
{
public required byte[] Bytes { get; init; }
public required object Object { get; init; }
}
public sealed class ReadContext
{
private const int MaxParsedTypeMetaEntries = 8192;
private readonly record struct CachedTypeMetaEntry(TypeMeta TypeMeta, int SkipBytesAfterHeader);
private readonly ReusableArray<TypeMeta> _readTypeMetas = new();
private readonly Dictionary<ulong, CachedTypeMetaEntry> _cachedTypeMetasByHeader = [];
private TypeMeta? _firstReadTypeMeta;
private bool _hasFirstReadTypeMeta;
private ulong _lastMetaHeader;
private CachedTypeMetaEntry _lastTypeMeta;
private bool _hasLastMetaHeader;
private readonly List<MetaString> _readMetaStrings = [];
internal readonly UInt64Map<TypeInfo> _readTypeInfoByType = new();
internal readonly Dictionary<CanonicalRefSignature, List<CanonicalRefEntry>> _canonicalRefCache = [];
internal readonly List<uint> _reservedRefIds = [];
private readonly int _maxDynamicReadDepth;
internal Type? _typeMetaType;
internal TypeMeta? _typeMeta;
internal UInt64Map<TypeMeta>? _typeMetaByType;
internal Type? _cachedTypeMetaType;
internal TypeMeta? _cachedTypeMeta;
internal int _currentDynamicReadDepth;
public ReadContext(
ByteReader reader,
TypeResolver typeResolver,
bool trackRef,
bool compatible = false,
bool checkStructVersion = false,
int maxDynamicReadDepth = 20)
{
if (maxDynamicReadDepth <= 0)
{
throw new ArgumentOutOfRangeException(nameof(maxDynamicReadDepth), "MaxDepth must be greater than 0.");
}
Reader = reader;
TypeResolver = typeResolver;
TrackRef = trackRef;
Compatible = compatible;
CheckStructVersion = checkStructVersion;
RefReader = new RefReader();
_maxDynamicReadDepth = maxDynamicReadDepth;
}
public ByteReader Reader { get; private set; }
public TypeResolver TypeResolver { get; }
public bool TrackRef { get; }
public bool Compatible { get; }
public bool CheckStructVersion { get; }
internal RefReader RefReader { get; }
internal void ResetFor(ByteReader reader)
{
Reader = reader;
Reset();
}
internal TypeMeta? GetReadTypeMeta(int index)
{
if (index < 0)
{
return null;
}
if (index == 0)
{
return _hasFirstReadTypeMeta ? _firstReadTypeMeta : null;
}
return _readTypeMetas.Get(index - 1);
}
internal void StoreReadTypeMeta(TypeMeta typeMeta, int index)
{
if (index < 0)
{
throw new InvalidDataException("negative type meta index");
}
if (index == 0)
{
_firstReadTypeMeta = typeMeta;
_hasFirstReadTypeMeta = true;
return;
}
if (!_hasFirstReadTypeMeta)
{
throw new InvalidDataException(
$"type meta index gap: index={index}, missing index 0");
}
int listIndex = index - 1;
if (listIndex == _readTypeMetas.Count)
{
_readTypeMetas.Add(typeMeta);
return;
}
if (listIndex < _readTypeMetas.Count)
{
_readTypeMetas.Set(listIndex, typeMeta);
return;
}
throw new InvalidDataException(
$"type meta index gap: index={index}, count={_readTypeMetas.Count + 1}");
}
internal bool TryGetCachedReadTypeMeta(ulong header, out TypeMeta typeMeta, out int skipBytesAfterHeader)
{
if (_hasLastMetaHeader && _lastMetaHeader == header)
{
typeMeta = _lastTypeMeta.TypeMeta;
skipBytesAfterHeader = _lastTypeMeta.SkipBytesAfterHeader;
return true;
}
if (_cachedTypeMetasByHeader.TryGetValue(header, out CachedTypeMetaEntry cached))
{
_lastMetaHeader = header;
_lastTypeMeta = cached;
_hasLastMetaHeader = true;
typeMeta = cached.TypeMeta;
skipBytesAfterHeader = cached.SkipBytesAfterHeader;
return true;
}
typeMeta = null!;
skipBytesAfterHeader = 0;
return false;
}
internal void CacheReadTypeMeta(ulong header, TypeMeta typeMeta, int skipBytesAfterHeader)
{
CachedTypeMetaEntry cached = new(typeMeta, skipBytesAfterHeader);
_lastMetaHeader = header;
_lastTypeMeta = cached;
_hasLastMetaHeader = true;
if (_cachedTypeMetasByHeader.Count < MaxParsedTypeMetaEntries)
{
_cachedTypeMetasByHeader.TryAdd(header, cached);
}
}
internal MetaString? GetReadMetaString(int index)
{
return index >= 0 && index < _readMetaStrings.Count ? _readMetaStrings[index] : null;
}
internal void AppendReadMetaString(MetaString value)
{
_readMetaStrings.Add(value);
}
internal TypeMeta ReadTypeMeta()
{
uint indexMarker = Reader.ReadVarUInt32();
bool isRef = (indexMarker & 1) == 1;
int index = checked((int)(indexMarker >> 1));
if (isRef)
{
TypeMeta? cached = GetReadTypeMeta(index);
if (cached is null)
{
throw new InvalidDataException($"unknown type meta ref index {index}");
}
return cached;
}
ulong header = Reader.ReadUInt64();
if (TryGetCachedReadTypeMeta(header, out TypeMeta cachedTypeMeta, out int skipBytesAfterHeader))
{
Reader.Skip(skipBytesAfterHeader);
StoreReadTypeMeta(cachedTypeMeta, index);
return cachedTypeMeta;
}
int headerStartCursor = Reader.Cursor - sizeof(ulong);
Reader.MoveBack(sizeof(ulong));
TypeMeta typeMeta = TypeMeta.Decode(Reader);
int consumedTypeMetaBytes = Reader.Cursor - headerStartCursor;
int parsedSkipBytesAfterHeader = consumedTypeMetaBytes - sizeof(ulong);
StoreReadTypeMeta(typeMeta, index);
CacheReadTypeMeta(header, typeMeta, parsedSkipBytesAfterHeader);
return typeMeta;
}
internal void StoreTypeMeta(Type type, TypeMeta typeMeta)
{
ulong typeKey = TypeMapKey.Get(type);
if (_cachedTypeMetaType == type && ReferenceEquals(_cachedTypeMeta, typeMeta))
{
return;
}
if (ReferenceEquals(_typeMetaType, type))
{
if (ReferenceEquals(_typeMeta, typeMeta))
{
_cachedTypeMetaType = type;
_cachedTypeMeta = typeMeta;
return;
}
_typeMeta = typeMeta;
_cachedTypeMetaType = type;
_cachedTypeMeta = typeMeta;
return;
}
if (_typeMetaType is null)
{
_typeMetaType = type;
_typeMeta = typeMeta;
_cachedTypeMetaType = type;
_cachedTypeMeta = typeMeta;
return;
}
if (_typeMetaByType is null)
{
_typeMetaByType = new UInt64Map<TypeMeta>();
if (_typeMeta is not null)
{
_typeMetaByType.Set(TypeMapKey.Get(_typeMetaType!), _typeMeta);
}
}
else if (_typeMetaByType.TryGetValue(typeKey, out TypeMeta? existing) &&
ReferenceEquals(existing, typeMeta))
{
_cachedTypeMetaType = type;
_cachedTypeMeta = typeMeta;
return;
}
_typeMetaByType.Set(typeKey, typeMeta);
_cachedTypeMetaType = type;
_cachedTypeMeta = typeMeta;
}
public TypeMeta? GetTypeMeta<T>()
{
return GetTypeMeta(typeof(T));
}
private TypeMeta? GetTypeMeta(Type type)
{
ulong typeKey = TypeMapKey.Get(type);
if (_cachedTypeMetaType == type && _cachedTypeMeta is not null)
{
return _cachedTypeMeta;
}
if (ReferenceEquals(_typeMetaType, type) &&
_typeMeta is not null)
{
_cachedTypeMetaType = type;
_cachedTypeMeta = _typeMeta;
return _typeMeta;
}
if (_typeMetaByType is null ||
!_typeMetaByType.TryGetValue(typeKey, out TypeMeta? typeMeta) ||
typeMeta is null)
{
return null;
}
_cachedTypeMetaType = type;
_cachedTypeMeta = typeMeta;
return typeMeta;
}
internal void SetReadTypeInfo(Type type, TypeInfo typeInfo)
{
_readTypeInfoByType.Set(TypeMapKey.Get(type), typeInfo);
}
internal TypeInfo? GetReadTypeInfo(Type type)
{
return _readTypeInfoByType.TryGetValue(TypeMapKey.Get(type), out TypeInfo? typeInfo) ? typeInfo : null;
}
internal void ClearReadTypeInfo(Type type)
{
_readTypeInfoByType.Remove(TypeMapKey.Get(type));
}
public void StoreRef(object? value)
{
if (_reservedRefIds.Count == 0)
{
return;
}
RefReader.StoreRefAt(_reservedRefIds[^1], value);
}
internal void SetReservedRefId(uint refId)
{
_reservedRefIds.Add(refId);
}
internal void ClearReservedRefId()
{
if (_reservedRefIds.Count > 0)
{
_reservedRefIds.RemoveAt(_reservedRefIds.Count - 1);
}
}
internal void IncreaseReadDepth()
{
_currentDynamicReadDepth += 1;
if (_currentDynamicReadDepth > _maxDynamicReadDepth)
{
throw new InvalidDataException(
$"maximum dynamic object nesting depth ({_maxDynamicReadDepth}) exceeded. current depth: {_currentDynamicReadDepth}");
}
}
internal void DecreaseReadDepth()
{
if (_currentDynamicReadDepth > 0)
{
_currentDynamicReadDepth -= 1;
}
}
internal T CanonicalizeNonTrackingRef<T>(T value, int start, int end)
{
if (!TrackRef || end <= start || value is null || value is not object obj)
{
return value;
}
byte[] bytes = new byte[end - start];
Array.Copy(Reader.Storage, start, bytes, 0, bytes.Length);
(ulong hashLo, ulong hashHi) = MurmurHash3.X64_128(bytes, 47);
CanonicalRefSignature signature = new(obj.GetType(), hashLo, hashHi, bytes.Length);
if (_canonicalRefCache.TryGetValue(signature, out List<CanonicalRefEntry>? bucket))
{
foreach (CanonicalRefEntry entry in bucket)
{
if (entry.Bytes.AsSpan().SequenceEqual(bytes))
{
return (T)entry.Object;
}
}
bucket.Add(new CanonicalRefEntry { Bytes = bytes, Object = obj });
return value;
}
_canonicalRefCache[signature] =
[
new CanonicalRefEntry { Bytes = bytes, Object = obj },
];
return value;
}
internal void Reset()
{
RefReader.Reset();
_typeMetaType = null;
_typeMeta = null;
_typeMetaByType?.ClearKeys();
_readTypeInfoByType.ClearKeys();
_canonicalRefCache.Clear();
_reservedRefIds.Clear();
_cachedTypeMetaType = null;
_cachedTypeMeta = null;
_currentDynamicReadDepth = 0;
_firstReadTypeMeta = null;
_hasFirstReadTypeMeta = false;
_readTypeMetas.Clear();
_readMetaStrings.Clear();
}
}