123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627 |
- /*
- * Copyright (c) Contributors, http://opensimulator.org/
- * See CONTRIBUTORS.TXT for a full list of copyright holders.
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions are met:
- * * Redistributions of source code must retain the above copyright
- * notice, this list of conditions and the following disclaimer.
- * * Redistributions in binary form must reproduce the above copyright
- * notice, this list of conditions and the following disclaimer in the
- * documentation and/or other materials provided with the distribution.
- * * Neither the name of the OpenSimulator Project nor the
- * names of its contributors may be used to endorse or promote products
- * derived from this software without specific prior written permission.
- *
- * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY
- * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
- * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
- * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY
- * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
- * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
- * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
- * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
- * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
- * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- */
- using System;
- using System.Buffers;
- using System.Collections.Generic;
- using System.IO;
- using System.Runtime.CompilerServices;
- using System.Text.Json;
- using System.Text.RegularExpressions;
- using System.Xml;
- //using log4net;
- namespace OpenSim.Region.Framework.Scenes
- {
- public class LinksetData
- {
- public int m_MemoryLimit;
- public int m_MemoryUsed;
- private readonly object linksetDataLock = new();
- public Dictionary<string, LinksetDataEntry> Data;
- public LinksetData(int limit)
- {
- Data = new Dictionary<string, LinksetDataEntry>();
- m_MemoryLimit = limit;
- m_MemoryUsed = 0;
- }
- public LinksetData(int limit, int used)
- {
- Data = new Dictionary<string, LinksetDataEntry>();
- m_MemoryLimit = limit;
- m_MemoryUsed = used;
- }
- public LinksetData Copy()
- {
- lock (linksetDataLock)
- {
- var copy = new LinksetData(m_MemoryLimit, m_MemoryUsed);
- foreach (var entry in Data)
- {
- LinksetDataEntry val = entry.Value.Copy();
- copy.Data[entry.Key] = val;
- }
- return copy;
- }
- }
- /// <summary>
- /// Adds or updates a entry to linkset data with optional password
- /// </summary>
- /// <returns>
- /// return values must match values expected by LSL
- /// </returns>
- public int AddOrUpdate(string key, string value, string pass)
- {
- int deltaMem;
- lock (linksetDataLock)
- {
- if (Data.TryGetValue(key, out LinksetDataEntry entry))
- {
- if (!entry.CheckPassword(pass))
- return 3;
- if (entry.Value == value)
- return 5;
- deltaMem = value.Length - entry.Value.Length;
- if ((m_MemoryUsed + deltaMem) > m_MemoryLimit)
- return 1;
- m_MemoryUsed += deltaMem;
- if(m_MemoryUsed < 0)
- m_MemoryUsed = 0;
- entry.Value = value;
- return 0;
- }
- deltaMem = value.Length + key.Length;
- if (!string.IsNullOrEmpty(pass))
- deltaMem += pass.Length;
- if ((m_MemoryUsed + deltaMem) > m_MemoryLimit)
- return 1;
- m_MemoryUsed += deltaMem;
- Data[key] = new LinksetDataEntry()
- {
- Value = value,
- Password = pass
- };
- return 0;
- }
- }
- public int AddOrUpdate(string key, string value)
- {
- int deltaMem;
- lock (linksetDataLock)
- {
- if (Data.TryGetValue(key, out LinksetDataEntry entry))
- {
- if (entry.IsProtected)
- return 3;
- if (entry.Value == value)
- return 5;
- deltaMem = value.Length - entry.Value.Length;
- if ((m_MemoryUsed + deltaMem) > m_MemoryLimit)
- return 1;
- entry.Value = value;
- m_MemoryUsed += deltaMem;
- return 0;
- }
- deltaMem = value.Length + key.Length;
- if ((m_MemoryUsed + deltaMem) > m_MemoryLimit)
- return 1;
- m_MemoryUsed += deltaMem;
- Data[key] = new LinksetDataEntry()
- {
- Value = value,
- Password = null
- };
- return 0;
- }
- }
- /// <summary>
- /// Deletes a named key from the key value store
- /// </summary>
- /// <param name="key">The key value we're removing</param>
- /// <param name="pass">The password for a protected field (or string.Empty if not protected)</param>
- /// <returns>
- /// return values must match values expected by LSL
- /// </returns>
- public int Remove(string key, string pass)
- {
- lock (linksetDataLock)
- {
- if (Data.Count <= 0)
- return 4;
- if (!Data.TryGetValue(key, out LinksetDataEntry entry))
- return 4;
- if (!entry.CheckPassword(pass))
- return 3;
- Data.Remove(key);
- m_MemoryUsed -= key.Length + entry.Value.Length;
- if (!string.IsNullOrEmpty(entry.Password))
- m_MemoryUsed -= entry.Password.Length;
- if (m_MemoryUsed < 0)
- m_MemoryUsed = 0;
- return 0;
- }
- }
- public int Remove(string key)
- {
- lock (linksetDataLock)
- {
- if (Data.Count <= 0)
- return 4;
- if(string.IsNullOrEmpty(key))
- return 4;
- if (!Data.TryGetValue(key, out LinksetDataEntry entry))
- return 4;
- if (entry.IsProtected)
- return 3;
- Data.Remove(key);
- m_MemoryUsed -= key.Length + entry.Value.Length;
- if (m_MemoryUsed < 0)
- m_MemoryUsed = 0;
- return 0;
- }
- }
- public string Get(string key, string pass)
- {
- lock (linksetDataLock)
- {
- return (Data.TryGetValue(key, out LinksetDataEntry entry) && entry.CheckPassword(pass)) ? entry.Value : string.Empty;
- }
- }
- public string Get(string key)
- {
- lock (linksetDataLock)
- {
- return (Data.TryGetValue(key, out LinksetDataEntry entry) && entry.IsNotProtected) ? entry.Value : string.Empty;
- }
- }
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- public bool HasData()
- {
- return Data.Count > 0;
- }
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- public int Count()
- {
- return Data.Count;
- }
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- public int Free()
- {
- int free = m_MemoryLimit - m_MemoryUsed;
- return free > 0 ? free : 0;
- }
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- public int Used()
- {
- return m_MemoryUsed;
- }
- public string[] RemoveByPattern(string pattern, string pass, out int notDeleted)
- {
- notDeleted = 0;
- List<string> ret;
- lock (linksetDataLock)
- {
- if (Data.Count <= 0)
- return Array.Empty<string>();
- try
- {
- ret = new List<string>();
- Regex reg = new(pattern, RegexOptions.CultureInvariant, TimeSpan.FromMilliseconds(1));
- foreach (var kvp in Data)
- {
- if (reg.IsMatch(kvp.Key))
- {
- if (kvp.Value.CheckPassword(pass))
- {
- int mem = kvp.Value.Value.Length + kvp.Key.Length;
- if(kvp.Value.IsProtected)
- mem += kvp.Value.Password.Length;
- m_MemoryUsed -= mem;
- ret.Add(kvp.Key);
- }
- else
- notDeleted++;
- }
- }
- }
- catch
- {
- notDeleted = 0;
- return Array.Empty<string>();
- }
- foreach (string k in ret)
- Data.Remove(k);
- if (m_MemoryUsed < 0)
- m_MemoryUsed = 0;
- return ret.ToArray();
- }
- }
- public int CountByPattern(string pattern)
- {
- lock (linksetDataLock)
- {
- if (Data.Count <= 0)
- return 0;
- try
- {
- Regex reg = new(pattern, RegexOptions.CultureInvariant, TimeSpan.FromMilliseconds(1));
- int ret = 0;
- foreach (string k in Data.Keys)
- {
- if (reg.IsMatch(k))
- ret++;
- }
- return ret;
- }
- catch
- {
- return 0;
- }
- }
- }
- public string[] ListKeysByPatttern(string pattern, int start, int count)
- {
- List<string> lkeys;
- lock (linksetDataLock)
- {
- if (Data.Count <= 0 || start >= Data.Count)
- return Array.Empty<string>();
- try
- {
- Regex reg = new(pattern, RegexOptions.CultureInvariant, TimeSpan.FromMilliseconds(1));
- lkeys = new(Data.Count);
- foreach (string k in Data.Keys)
- {
- if (reg.IsMatch(k))
- lkeys.Add(k);
- }
- }
- catch
- {
- return Array.Empty<string>();
- }
- }
- if (lkeys.Count == 0)
- return Array.Empty<string>();
- lkeys.Sort();
- if (start < 0)
- start = 0;
- if (count < 1 || start + count > lkeys.Count)
- count = lkeys.Count - start;
- List<string> result = lkeys.GetRange(start, count);
- return result.ToArray();
- }
- public string[] ListKeys(int start, int count)
- {
- string[] keys;
- lock (linksetDataLock)
- {
- if (Data.Count <= 0 || start >= Data.Count)
- return Array.Empty<string>();
- keys = new string[Data.Count];
- Data.Keys.CopyTo(keys, 0);
- }
- Array.Sort(keys);
- if (start < 0)
- start = 0;
- if (count < 1)
- return keys[start..];
- int end = start + count;
- if (end >= keys.Length)
- return keys[start..];
- return keys[start..end];
- }
- /// <summary>
- /// Merge the linksetData present in another Linkset into this one.
- /// If a key is present in our linksetData it wins, dont overide it.
- /// </summary>
- /// <param name="otherLinkset"></param>
- public void MergeOther(LinksetData otherLinksetData)
- {
- if (otherLinksetData is null || otherLinksetData.Data is null || otherLinksetData.Count() == 0)
- return;
- lock (linksetDataLock)
- {
- if(m_MemoryUsed + otherLinksetData.Used() < m_MemoryLimit)
- {
- foreach (var kvp in otherLinksetData.Data)
- {
- if (Data.TryAdd(kvp.Key, kvp.Value))
- {
- m_MemoryUsed += kvp.Key.Length + kvp.Value.Value.Length;
- if (!string.IsNullOrEmpty(kvp.Value.Password))
- m_MemoryUsed += kvp.Value.Password.Length;
- }
- }
- return;
- }
- SortedList<string,LinksetDataEntry> otherOrdered = new(otherLinksetData.Data);
- foreach (var kvp in otherOrdered)
- {
- int mem = kvp.Key.Length + kvp.Value.Value.Length;
- if (!string.IsNullOrEmpty(kvp.Value.Password))
- mem += kvp.Value.Password.Length;
- if (m_MemoryUsed + mem >= m_MemoryLimit)
- return;
- if (Data.TryAdd(kvp.Key, kvp.Value))
- {
- m_MemoryUsed += mem;
- if (!string.IsNullOrEmpty(kvp.Value.Password))
- m_MemoryUsed += kvp.Value.Password.Length;
- }
- }
- otherLinksetData.Data = null;
- otherLinksetData.m_MemoryUsed = 0;
- }
- }
- /// <summary>
- /// ResetLinksetData - clear the list and update the accounting.
- /// </summary>
- public void ResetLinksetData()
- {
- lock (linksetDataLock)
- {
- if (Data.Count <= 0)
- return;
- Data.Clear();
- m_MemoryUsed = 0;
- }
- }
- public string ToJson()
- {
- lock (linksetDataLock)
- {
- return JsonSerializer.Serialize<Dictionary<string, LinksetDataEntry>>(Data);
- }
- }
- public void ToXML(XmlTextWriter writer)
- {
- if (Data.Count < 1)
- return;
- using MemoryStream ms = new(m_MemoryUsed);
- ToBin(ms);
- if (ms.Length < 1)
- return;
- writer.WriteStartElement("lnkstdt");
- writer.WriteBase64(ms.GetBuffer(), 0, (int)ms.Length);
- writer.WriteEndElement();
- }
- public static LinksetData FromXML(ReadOnlySpan<char> data)
- {
- if (data.Length < 8)
- return null;
- int minLength = ((data.Length * 3) + 3) / 4;
- byte[] bindata = ArrayPool<byte>.Shared.Rent(minLength);
- try
- {
- if (Convert.TryFromBase64Chars(data, bindata, out int bytesWritten))
- return FromBin(bindata);
- }
- catch
- {
- }
- finally
- {
- ArrayPool<byte>.Shared.Return(bindata);
- }
- return null;
- }
- public byte[] ToBin()
- {
- if(Data.Count < 1)
- return null;
- using MemoryStream ms = new(m_MemoryUsed);
- ToBin(ms);
- return ms.Length > 0 ? ms.ToArray() : null;
- }
- public void ToBin(MemoryStream ms)
- {
- try
- {
- using BinaryWriter bw = new BinaryWriter(ms, System.Text.Encoding.UTF8, true);
- bw.Write((byte)1); // storage version
- bw.Write7BitEncodedInt(m_MemoryLimit);
- lock (linksetDataLock)
- {
- bw.Write7BitEncodedInt(Data.Count);
- foreach (var kvp in Data)
- {
- bw.Write(kvp.Key);
- bw.Write(kvp.Value.Value);
- if(kvp.Value.IsProtected)
- bw.Write(kvp.Value.Password);
- else
- bw.Write((byte)0);
- }
- }
- return;
- }
- catch { }
- ms.SetLength(0);
- }
- public static LinksetData FromBin(byte[] data)
- {
- if (data.Length < 8)
- return null;
- try
- {
- using BinaryReader br = new BinaryReader(new MemoryStream(data));
- int version = br.Read7BitEncodedInt();
- int memoryLimit = br.Read7BitEncodedInt();
- if(memoryLimit < 0 || memoryLimit > 256 * 1024)
- memoryLimit = 256 * 1024;
- int count = br.Read7BitEncodedInt();
- if(count == 0)
- return null;
- LinksetData ld = new LinksetData(memoryLimit);
- for(int i = 0; i < count; i++)
- {
- string key = br.ReadString();
- if (key.Length == 0)
- continue;
- ld.m_MemoryUsed += key.Length;
- string value = br.ReadString();
- if(value.Length == 0)
- continue;
- ld.m_MemoryUsed += value.Length;
- string pass = br.ReadString();
- ld.m_MemoryUsed += pass.Length;
- if(ld.m_MemoryUsed > memoryLimit)
- break;
- if(pass.Length > 0)
- {
- ld.Data[key] = new LinksetDataEntry()
- {
- Value = value,
- Password = pass
- };
- }
- else
- {
- ld.Data[key] = new LinksetDataEntry()
- {
- Value = value,
- Password = null
- };
- }
- }
- return ld;
- }
- catch
- {
- }
- return null;
- }
- }
- public class LinksetDataEntry
- {
- public string Password;
- public string Value;
- public LinksetDataEntry() { }
- public LinksetDataEntry(string value, string password)
- {
- Value = value;
- Password = password;
- }
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- public bool CheckPassword(string pass)
- {
- // A undocumented caveat for LinksetData appears to be that even for unprotected values,
- // if a pass is provided, it is still treated as protected
- return string.IsNullOrEmpty(Password) || (Password == pass);
- }
- public LinksetDataEntry Copy()
- {
- return new LinksetDataEntry
- {
- Password = Password,
- Value = Value
- };
- }
- public bool IsProtected
- {
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- get { return !string.IsNullOrEmpty(Password); }
- }
- public bool IsNotProtected
- {
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- get { return string.IsNullOrEmpty(Password); }
- }
- }
- }
|