diff options
Diffstat (limited to 'embeddable-dll-service/csharp/TunnelDll/Ringlogger.cs')
-rw-r--r-- | embeddable-dll-service/csharp/TunnelDll/Ringlogger.cs | 205 |
1 files changed, 205 insertions, 0 deletions
diff --git a/embeddable-dll-service/csharp/TunnelDll/Ringlogger.cs b/embeddable-dll-service/csharp/TunnelDll/Ringlogger.cs new file mode 100644 index 00000000..9db01fc8 --- /dev/null +++ b/embeddable-dll-service/csharp/TunnelDll/Ringlogger.cs @@ -0,0 +1,205 @@ +/* SPDX-License-Identifier: MIT + * + * Copyright (C) 2019-2022 WireGuard LLC. All Rights Reserved. + */ + +using System; +using System.IO; +using System.IO.MemoryMappedFiles; +using System.Text; +using System.Collections.Generic; +using System.Threading; +using System.Runtime.CompilerServices; + +namespace Tunnel +{ + public class Ringlogger + { + private struct UnixTimestamp + { + private Int64 _ns; + public UnixTimestamp(Int64 ns) => _ns = ns; + public bool IsEmpty => _ns == 0; + public static UnixTimestamp Empty => new UnixTimestamp(0); + public static UnixTimestamp Now + { + get + { + var now = DateTimeOffset.UtcNow; + var ns = (now.Subtract(DateTimeOffset.FromUnixTimeSeconds(0)).Ticks * 100) % 1000000000; + return new UnixTimestamp(now.ToUnixTimeSeconds() * 1000000000 + ns); + } + } + public Int64 Nanoseconds => _ns; + public override string ToString() + { + return DateTimeOffset.FromUnixTimeSeconds(_ns / 1000000000).LocalDateTime.ToString("yyyy'-'MM'-'dd HH':'mm':'ss'.'") + ((_ns % 1000000000).ToString() + "00000").Substring(0, 6); + } + } + private struct Line + { + private const int maxLineLength = 512; + private const int offsetTimeNs = 0; + private const int offsetLine = 8; + + private readonly MemoryMappedViewAccessor _view; + private readonly int _start; + public Line(MemoryMappedViewAccessor view, UInt32 index) => (_view, _start) = (view, (int)(Log.HeaderBytes + index * Bytes)); + + public static int Bytes => maxLineLength + offsetLine; + + public UnixTimestamp Timestamp + { + get => new UnixTimestamp(_view.ReadInt64(_start + offsetTimeNs)); + set => _view.Write(_start + offsetTimeNs, value.Nanoseconds); + } + + public string Text + { + get + { + var textBytes = new byte[maxLineLength]; + _view.ReadArray(_start + offsetLine, textBytes, 0, textBytes.Length); + var nullByte = Array.IndexOf<byte>(textBytes, 0); + if (nullByte <= 0) + return null; + return Encoding.UTF8.GetString(textBytes, 0, nullByte); + } + set + { + if (value == null) + { + _view.WriteArray(_start + offsetLine, new byte[maxLineLength], 0, maxLineLength); + return; + } + var textBytes = Encoding.UTF8.GetBytes(value); + var bytesToWrite = Math.Min(maxLineLength - 1, textBytes.Length); + _view.Write(_start + offsetLine + bytesToWrite, (byte)0); + _view.WriteArray(_start + offsetLine, textBytes, 0, bytesToWrite); + } + } + + public override string ToString() + { + var time = Timestamp; + if (time.IsEmpty) + return null; + var text = Text; + if (text == null) + return null; + return string.Format("{0}: {1}", time, text); + } + } + private struct Log + { + private const UInt32 maxLines = 2048; + private const UInt32 magic = 0xbadbabe; + private const int offsetMagic = 0; + private const int offsetNextIndex = 4; + private const int offsetLines = 8; + + private readonly MemoryMappedViewAccessor _view; + public Log(MemoryMappedViewAccessor view) => _view = view; + + public static int HeaderBytes => offsetLines; + public static int Bytes => (int)(HeaderBytes + Line.Bytes * maxLines); + + public UInt32 ExpectedMagic => magic; + public UInt32 Magic + { + get => _view.ReadUInt32(offsetMagic); + set => _view.Write(offsetMagic, value); + } + + public UInt32 NextIndex + { + get => _view.ReadUInt32(offsetNextIndex); + set => _view.Write(offsetNextIndex, value); + } + public unsafe UInt32 InsertNextIndex() + { + byte* pointer = null; + _view.SafeMemoryMappedViewHandle.AcquirePointer(ref pointer); + var ret = (UInt32)Interlocked.Increment(ref Unsafe.AsRef<Int32>(pointer + offsetNextIndex)); + _view.SafeMemoryMappedViewHandle.ReleasePointer(); + return ret; + } + + public UInt32 LineCount => maxLines; + public Line this[UInt32 i] => new Line(_view, i % maxLines); + + public void Clear() => _view.WriteArray(0, new byte[Bytes], 0, Bytes); + } + + private readonly Log _log; + private readonly string _tag; + + public Ringlogger(string filename, string tag) + { + var file = File.Open(filename, FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.ReadWrite | FileShare.Delete); + file.SetLength(Log.Bytes); + var mmap = MemoryMappedFile.CreateFromFile(file, null, 0, MemoryMappedFileAccess.ReadWrite, HandleInheritability.None, false); + var view = mmap.CreateViewAccessor(0, Log.Bytes, MemoryMappedFileAccess.ReadWrite); + _log = new Log(view); + if (_log.Magic != _log.ExpectedMagic) + { + _log.Clear(); + _log.Magic = _log.ExpectedMagic; + } + _tag = tag; + } + + public void Write(string line) + { + var time = UnixTimestamp.Now; + var entry = _log[_log.InsertNextIndex() - 1]; + entry.Timestamp = UnixTimestamp.Empty; + entry.Text = null; + entry.Text = string.Format("[{0}] {1}", _tag, line.Trim()); + entry.Timestamp = time; + } + + public void WriteTo(TextWriter writer) + { + var start = _log.NextIndex; + for (UInt32 i = 0; i < _log.LineCount; ++i) + { + var entry = _log[i + start]; + if (entry.Timestamp.IsEmpty) + continue; + var text = entry.ToString(); + if (text == null) + continue; + writer.WriteLine(text); + } + } + + public static readonly UInt32 CursorAll = UInt32.MaxValue; + public List<string> FollowFromCursor(ref UInt32 cursor) + { + var lines = new List<string>((int)_log.LineCount); + var i = cursor; + var all = cursor == CursorAll; + if (all) + i = _log.NextIndex; + for (UInt32 l = 0; l < _log.LineCount; ++l, ++i) + { + if (!all && i % _log.LineCount == _log.NextIndex % _log.LineCount) + break; + var entry = _log[i]; + if (entry.Timestamp.IsEmpty) + { + if (all) + continue; + break; + } + cursor = (i + 1) % _log.LineCount; + var text = entry.ToString(); + if (text == null) + continue; + lines.Add(text); + } + return lines; + } + } +} |