295 lines
9.2 KiB
C#
295 lines
9.2 KiB
C#
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<string, TopicAliasMappingMetadata>
|
|
{
|
|
private AVLTree<UInt32, int> index_Hash = new AVLTree<UInt32, int>(new UInt32Comparer());
|
|
private AVLTree<UInt16, int> index_Alias = new AVLTree<UInt16, int>(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<int> 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<int> FindByHash(UInt32 hash) => this.index_Hash.Find(hash);
|
|
public List<int> FindByAlias(UInt16 alias) => this.index_Alias.Find(alias);
|
|
|
|
public int Count() => this.index_Hash.ElemCount;
|
|
}
|
|
|
|
public sealed class TopicAliasMappingMetadataService : MetadataService<TopicAliasMappingMetadata, string>
|
|
{
|
|
public TopicAliasMappingMetadataService(IndexingService<string, TopicAliasMappingMetadata> indexingService)
|
|
: base(indexingService, new FindDeletedMetadataIndexFinder<TopicAliasMappingMetadata>())
|
|
{
|
|
}
|
|
|
|
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<string>
|
|
{
|
|
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<string, TopicAliasMappingMetadata, TopicAliasMappingIndexingService, TopicAliasMappingMetadataService>
|
|
{
|
|
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;
|
|
}
|
|
}
|
|
}
|