119 lines
4.3 KiB
C#
119 lines
4.3 KiB
C#
using System.Text;
|
|
using Nostr.Client.Messages;
|
|
|
|
namespace NostrRelay;
|
|
|
|
/// <summary>
|
|
/// Simple fixed length nostr event storage
|
|
/// </summary>
|
|
public static class NostrBuf
|
|
{
|
|
public static byte[] Encode(NostrEvent ev)
|
|
{
|
|
var buf = new byte[CalculateSize(ev)];
|
|
var span = buf.AsSpan();
|
|
span[0] = 0x01; // version 1
|
|
Convert.FromHexString(ev.Id!).CopyTo(span[1..33]);
|
|
Convert.FromHexString(ev.Pubkey!).CopyTo(span[33..65]);
|
|
Convert.FromHexString(ev.Sig!).CopyTo(span[65..129]);
|
|
BitConverter.GetBytes((ulong)ToUnixTime(ev.CreatedAt!.Value)).CopyTo(span[129..137]);
|
|
BitConverter.GetBytes((uint)ev.Kind).CopyTo(span[137..141]);
|
|
var contentBytes = Encoding.UTF8.GetBytes(ev.Content!);
|
|
BitConverter.GetBytes((uint)contentBytes.Length).CopyTo(span[141..145]);
|
|
var pos = 145;
|
|
contentBytes.CopyTo(span[pos..(pos += contentBytes.Length)]);
|
|
BitConverter.GetBytes((ushort)ev.Tags!.Count).CopyTo(span[pos..(pos += 2)]);
|
|
|
|
foreach (var tag in ev.Tags)
|
|
{
|
|
var tagKey = Encoding.UTF8.GetBytes(tag.TagIdentifier);
|
|
span[pos++] = (byte)tagKey.Length;
|
|
tagKey.CopyTo(span[pos..(pos += tagKey.Length)]);
|
|
|
|
span[pos++] = (byte)tag.AdditionalData.Length;
|
|
foreach (var tagAdd in tag.AdditionalData)
|
|
{
|
|
var tagAddBytes = Encoding.UTF8.GetBytes(tagAdd);
|
|
BitConverter.GetBytes((ushort)tagAddBytes.Length).CopyTo(span[pos..(pos += 2)]);
|
|
tagAddBytes.CopyTo(span[pos..(pos += tagAddBytes.Length)]);
|
|
}
|
|
}
|
|
|
|
return buf;
|
|
}
|
|
|
|
public static NostrEvent? Decode(Span<byte> data)
|
|
{
|
|
var version = data[0];
|
|
if (version != 0x01) throw new Exception("Version not supported");
|
|
|
|
var id = Convert.ToHexString(data[1..33]).ToLower();
|
|
var pubkey = Convert.ToHexString(data[33..65]).ToLower();
|
|
var sig = Convert.ToHexString(data[65..129]).ToLower();
|
|
var createdAt = BitConverter.ToUInt64(data[129..137]);
|
|
var kind = BitConverter.ToUInt32(data[137..141]);
|
|
var contentLen = BitConverter.ToUInt32(data[141..145]);
|
|
var pos = 145;
|
|
var content = Encoding.UTF8.GetString(data[pos..(pos += (int)contentLen)]);
|
|
var nTags = BitConverter.ToUInt16(data[pos..(pos += 2)]);
|
|
|
|
var tags = new List<NostrEventTag>();
|
|
for (var x = 0; x < nTags; x++)
|
|
{
|
|
var keyLen = data[pos++];
|
|
var keyString = Encoding.UTF8.GetString(data[pos..(pos += keyLen)]);
|
|
|
|
var nElements = data[pos++];
|
|
var elms = new string[nElements];
|
|
for (var y = 0; y < nElements; y++)
|
|
{
|
|
var elmLen = BitConverter.ToUInt16(data[pos..(pos += 2)]);
|
|
var elmString = Encoding.UTF8.GetString(data[pos..(pos += elmLen)]);
|
|
elms[y] = elmString;
|
|
}
|
|
|
|
tags.Add(new(keyString, elms));
|
|
}
|
|
|
|
return new NostrEvent()
|
|
{
|
|
Id = id,
|
|
Pubkey = pubkey,
|
|
Sig = sig,
|
|
CreatedAt = DateTimeOffset.FromUnixTimeSeconds((long)createdAt).UtcDateTime,
|
|
Kind = (NostrKind)kind,
|
|
Content = content,
|
|
Tags = new NostrEventTags(tags)
|
|
};
|
|
}
|
|
|
|
public static long CalculateSize(NostrEvent ev)
|
|
{
|
|
const long fixedPart =
|
|
1 // version byte
|
|
+ 32 // id
|
|
+ 32 // pubkey
|
|
+ 64 // sig
|
|
+ 8 // created_at (uint64)
|
|
+ 4 // kind (uint32)
|
|
+ 4 // len(content) (uint32)
|
|
+ 2; // len(tags) (uint16)
|
|
|
|
var variableLength =
|
|
Encoding.UTF8.GetByteCount(ev.Content!)
|
|
+ ev.Tags!.Sum(a =>
|
|
1 + // 1 byte for length of first tag element (key)
|
|
Encoding.UTF8.GetByteCount(a.TagIdentifier) + // length of the tag key
|
|
a.AdditionalData.Length + // 1 byte - number of elements
|
|
a.AdditionalData.Sum(b => Encoding.UTF8.GetByteCount(b)) + // length of the content of the items in the tag
|
|
(a.AdditionalData.Length * 2)); // 2 bytes per tag element for length prefix
|
|
|
|
return fixedPart + variableLength;
|
|
}
|
|
|
|
private static long ToUnixTime(DateTime dt)
|
|
{
|
|
return dt.ToUniversalTime().Ticks / 10000000L - 62135596800L;
|
|
}
|
|
}
|