using System; using System.Collections.Generic; using System.IO; using System.Linq; using Best.HTTP.Shared.PlatformSupport.Memory; using Best.HTTP.Shared.PlatformSupport.Threading; using Best.HTTP.Shared.Databases; using Best.HTTP.Shared.Databases.Indexing; using Best.HTTP.Shared.Databases.Indexing.Comparers; using Best.HTTP.Shared.Databases.MetadataIndexFinders; using Best.HTTP.Shared.Databases.Utils; namespace Best.MQTT.Databases { public sealed class TopicAliasMappingDatabaseOptions : DatabaseOptions { public TopicAliasMappingDatabaseOptions(string dbName) : base(dbName) { base.UseHashFile = false; } } public sealed class TopicAliasMappingMetadata : Metadata { internal UInt32 hash; internal UInt16 alias; internal bool sentToServer; public override void SaveTo(Stream stream) { base.SaveTo(stream); stream.EncodeUnsignedVariableByteInteger(this.hash); stream.EncodeUnsignedVariableByteInteger(this.alias); } public override void LoadFrom(Stream stream) { base.LoadFrom(stream); this.hash = (UInt32)stream.DecodeUnsignedVariableByteInteger(); this.alias = (UInt16)stream.DecodeUnsignedVariableByteInteger(); } } public sealed class TopicAliasMappingIndexingService : IndexingService { private AVLTree index_Hash = new AVLTree(new UInt32Comparer()); private AVLTree index_Alias = new AVLTree(new UInt16Comparer()); public override void Index(TopicAliasMappingMetadata metadata) { base.Index(metadata); this.index_Hash.Add(metadata.hash, metadata.Index); this.index_Alias.Add(metadata.alias, metadata.Index); } public override void Remove(TopicAliasMappingMetadata metadata) { base.Remove(metadata); this.index_Hash.Remove(metadata.hash); this.index_Alias.Remove(metadata.alias); } public override void Clear() { base.Clear(); this.index_Hash.Clear(); this.index_Alias.Clear(); } public override IEnumerable GetOptimizedIndexes() => this.index_Hash.WalkHorizontal(); public bool ContainsHash(UInt32 hash) => this.index_Hash.ContainsKey(hash); public bool ContainsAlias(UInt16 alias) => this.index_Alias.ContainsKey(alias); public List FindByHash(UInt32 hash) => this.index_Hash.Find(hash); public List FindByAlias(UInt16 alias) => this.index_Alias.Find(alias); public int Count() => this.index_Hash.ElemCount; } public sealed class TopicAliasMappingMetadataService : MetadataService { public TopicAliasMappingMetadataService(IndexingService indexingService) : base(indexingService, new FindDeletedMetadataIndexFinder()) { } public TopicAliasMappingMetadata Create(string hash, UInt16 alias, int filePos, int length) { var result = base.CreateDefault(hash, filePos, length, (content, metadata) => { metadata.hash = MurmurHash2.Hash(content); metadata.alias = alias; }); return result; } } public sealed class TopicAliasMappingDiskContentParser : IDiskContentParser { public void Encode(Stream stream, string content) { StreamUtil.WriteLengthPrefixedString(stream, content); } public string Parse(Stream stream, int length) { return StreamUtil.ReadLengthPrefixedString(stream); } } public sealed class TopicAliasMappingDatabase : Database { public TopicAliasMappingDatabase(string directory, string dbName, TopicAliasMappingIndexingService indexingService) : base(directory, new TopicAliasMappingDatabaseOptions(dbName), indexingService, new TopicAliasMappingDiskContentParser(), new TopicAliasMappingMetadataService(indexingService)) { } public void Add(string topicName, UInt16 topicAliasMaximum) { using (new WriteLock(this.rwlock)) { // find free alias UInt16 alias = 0; for (UInt16 i = 1; i <= topicAliasMaximum; i++) { if (!this.IndexingService.ContainsAlias(i)) { alias = i; break; } } if (alias == 0) throw new Exception("Couldn't found free alias!"); Add(alias, topicName); } } private void Add(UInt16 alias, string topicName) { var (pos, length) = this.DiskManager.Append(topicName); this.MetadataService.Create(topicName, alias, pos, length); this.FlagDirty(1); } public void Set(UInt16 alias, string topicName) { using (new WriteLock(this.rwlock)) { var metadataIndexes = this.IndexingService.FindByAlias(alias); if (metadataIndexes == null) { Add(alias, topicName); return; } this.Delete(metadataIndexes); Add(alias, topicName); } } public string Find(UInt16 alias) { if (alias == 0) return null; using (new ReadLock(this.rwlock)) { var metadataIndexes = this.IndexingService.FindByAlias(alias); if (metadataIndexes == null) return null; return this.FromMetadataIndexes(metadataIndexes).FirstOrDefault(); } } public (UInt16 alias, bool sentToServer) Find(string topicName) { if (string.IsNullOrEmpty(topicName)) return (0, false); using (new ReadLock(this.rwlock)) { if (this.IndexingService.Count() == 0) return (0, false); var hash = MurmurHash2.Hash(topicName); var metadataIndexes = this.IndexingService.FindByHash(hash); if (metadataIndexes == null) return (0, false); var metadata = this.MetadataService.Metadatas[metadataIndexes[0]]; return (metadata.alias, metadata.sentToServer); } } public UInt16 Count() { using (new ReadLock(this.rwlock)) return (UInt16)this.IndexingService.Count(); } internal void SetSent(ushort alias, bool sentToServer) { using (new WriteLock(this.rwlock)) { var metadataIndexes = this.IndexingService.FindByAlias(alias); if (metadataIndexes == null) return; this.MetadataService.Metadatas[metadataIndexes[0]].sentToServer = sentToServer; this.FlagDirty(1); } } } // https://github.com/jitbit/MurmurHash.net public class MurmurHash2 { const uint m = 0x5bd1e995; const int r = 24; public static uint Hash(string str) { var count = System.Text.Encoding.UTF8.GetByteCount(str); var buffer = BufferPool.Get(count, true); System.Text.Encoding.UTF8.GetBytes(str, 0, str.Length, buffer, 0); var hash = Hash(buffer, count); BufferPool.Release(buffer); return hash; } private static uint Hash(byte[] data, int dataLength, uint seed = 0xc58f1a7a) { int length = dataLength; if (length == 0) return 0; uint h = seed ^ (uint)length; int currentIndex = 0; while (length >= 4) { uint k = (uint)(data[currentIndex++] | data[currentIndex++] << 8 | data[currentIndex++] << 16 | data[currentIndex++] << 24); k *= m; k ^= k >> r; k *= m; h *= m; h ^= k; length -= 4; } switch (length) { case 3: h ^= (UInt16)(data[currentIndex++] | data[currentIndex++] << 8); h ^= (uint)(data[currentIndex] << 16); h *= m; break; case 2: h ^= (UInt16)(data[currentIndex++] | data[currentIndex] << 8); h *= m; break; case 1: h ^= data[currentIndex]; h *= m; break; default: break; } h ^= h >> 13; h *= m; h ^= h >> 15; return h; } } }