LinksetData.cs 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627
  1. /*
  2. * Copyright (c) Contributors, http://opensimulator.org/
  3. * See CONTRIBUTORS.TXT for a full list of copyright holders.
  4. *
  5. * Redistribution and use in source and binary forms, with or without
  6. * modification, are permitted provided that the following conditions are met:
  7. * * Redistributions of source code must retain the above copyright
  8. * notice, this list of conditions and the following disclaimer.
  9. * * Redistributions in binary form must reproduce the above copyright
  10. * notice, this list of conditions and the following disclaimer in the
  11. * documentation and/or other materials provided with the distribution.
  12. * * Neither the name of the OpenSimulator Project nor the
  13. * names of its contributors may be used to endorse or promote products
  14. * derived from this software without specific prior written permission.
  15. *
  16. * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY
  17. * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
  18. * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
  19. * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY
  20. * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
  21. * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
  22. * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
  23. * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
  24. * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
  25. * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  26. */
  27. using System;
  28. using System.Buffers;
  29. using System.Collections.Generic;
  30. using System.IO;
  31. using System.Runtime.CompilerServices;
  32. using System.Text.Json;
  33. using System.Text.RegularExpressions;
  34. using System.Xml;
  35. //using log4net;
  36. namespace OpenSim.Region.Framework.Scenes
  37. {
  38. public class LinksetData
  39. {
  40. public int m_MemoryLimit;
  41. public int m_MemoryUsed;
  42. private readonly object linksetDataLock = new();
  43. public Dictionary<string, LinksetDataEntry> Data;
  44. public LinksetData(int limit)
  45. {
  46. Data = new Dictionary<string, LinksetDataEntry>();
  47. m_MemoryLimit = limit;
  48. m_MemoryUsed = 0;
  49. }
  50. public LinksetData(int limit, int used)
  51. {
  52. Data = new Dictionary<string, LinksetDataEntry>();
  53. m_MemoryLimit = limit;
  54. m_MemoryUsed = used;
  55. }
  56. public LinksetData Copy()
  57. {
  58. lock (linksetDataLock)
  59. {
  60. var copy = new LinksetData(m_MemoryLimit, m_MemoryUsed);
  61. foreach (var entry in Data)
  62. {
  63. LinksetDataEntry val = entry.Value.Copy();
  64. copy.Data[entry.Key] = val;
  65. }
  66. return copy;
  67. }
  68. }
  69. /// <summary>
  70. /// Adds or updates a entry to linkset data with optional password
  71. /// </summary>
  72. /// <returns>
  73. /// return values must match values expected by LSL
  74. /// </returns>
  75. public int AddOrUpdate(string key, string value, string pass)
  76. {
  77. int deltaMem;
  78. lock (linksetDataLock)
  79. {
  80. if (Data.TryGetValue(key, out LinksetDataEntry entry))
  81. {
  82. if (!entry.CheckPassword(pass))
  83. return 3;
  84. if (entry.Value == value)
  85. return 5;
  86. deltaMem = value.Length - entry.Value.Length;
  87. if ((m_MemoryUsed + deltaMem) > m_MemoryLimit)
  88. return 1;
  89. m_MemoryUsed += deltaMem;
  90. if(m_MemoryUsed < 0)
  91. m_MemoryUsed = 0;
  92. entry.Value = value;
  93. return 0;
  94. }
  95. deltaMem = value.Length + key.Length;
  96. if (!string.IsNullOrEmpty(pass))
  97. deltaMem += pass.Length;
  98. if ((m_MemoryUsed + deltaMem) > m_MemoryLimit)
  99. return 1;
  100. m_MemoryUsed += deltaMem;
  101. Data[key] = new LinksetDataEntry()
  102. {
  103. Value = value,
  104. Password = pass
  105. };
  106. return 0;
  107. }
  108. }
  109. public int AddOrUpdate(string key, string value)
  110. {
  111. int deltaMem;
  112. lock (linksetDataLock)
  113. {
  114. if (Data.TryGetValue(key, out LinksetDataEntry entry))
  115. {
  116. if (entry.IsProtected)
  117. return 3;
  118. if (entry.Value == value)
  119. return 5;
  120. deltaMem = value.Length - entry.Value.Length;
  121. if ((m_MemoryUsed + deltaMem) > m_MemoryLimit)
  122. return 1;
  123. entry.Value = value;
  124. m_MemoryUsed += deltaMem;
  125. return 0;
  126. }
  127. deltaMem = value.Length + key.Length;
  128. if ((m_MemoryUsed + deltaMem) > m_MemoryLimit)
  129. return 1;
  130. m_MemoryUsed += deltaMem;
  131. Data[key] = new LinksetDataEntry()
  132. {
  133. Value = value,
  134. Password = null
  135. };
  136. return 0;
  137. }
  138. }
  139. /// <summary>
  140. /// Deletes a named key from the key value store
  141. /// </summary>
  142. /// <param name="key">The key value we're removing</param>
  143. /// <param name="pass">The password for a protected field (or string.Empty if not protected)</param>
  144. /// <returns>
  145. /// return values must match values expected by LSL
  146. /// </returns>
  147. public int Remove(string key, string pass)
  148. {
  149. lock (linksetDataLock)
  150. {
  151. if (Data.Count <= 0)
  152. return 4;
  153. if (!Data.TryGetValue(key, out LinksetDataEntry entry))
  154. return 4;
  155. if (!entry.CheckPassword(pass))
  156. return 3;
  157. Data.Remove(key);
  158. m_MemoryUsed -= key.Length + entry.Value.Length;
  159. if (!string.IsNullOrEmpty(entry.Password))
  160. m_MemoryUsed -= entry.Password.Length;
  161. if (m_MemoryUsed < 0)
  162. m_MemoryUsed = 0;
  163. return 0;
  164. }
  165. }
  166. public int Remove(string key)
  167. {
  168. lock (linksetDataLock)
  169. {
  170. if (Data.Count <= 0)
  171. return 4;
  172. if(string.IsNullOrEmpty(key))
  173. return 4;
  174. if (!Data.TryGetValue(key, out LinksetDataEntry entry))
  175. return 4;
  176. if (entry.IsProtected)
  177. return 3;
  178. Data.Remove(key);
  179. m_MemoryUsed -= key.Length + entry.Value.Length;
  180. if (m_MemoryUsed < 0)
  181. m_MemoryUsed = 0;
  182. return 0;
  183. }
  184. }
  185. public string Get(string key, string pass)
  186. {
  187. lock (linksetDataLock)
  188. {
  189. return (Data.TryGetValue(key, out LinksetDataEntry entry) && entry.CheckPassword(pass)) ? entry.Value : string.Empty;
  190. }
  191. }
  192. public string Get(string key)
  193. {
  194. lock (linksetDataLock)
  195. {
  196. return (Data.TryGetValue(key, out LinksetDataEntry entry) && entry.IsNotProtected) ? entry.Value : string.Empty;
  197. }
  198. }
  199. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  200. public bool HasData()
  201. {
  202. return Data.Count > 0;
  203. }
  204. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  205. public int Count()
  206. {
  207. return Data.Count;
  208. }
  209. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  210. public int Free()
  211. {
  212. int free = m_MemoryLimit - m_MemoryUsed;
  213. return free > 0 ? free : 0;
  214. }
  215. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  216. public int Used()
  217. {
  218. return m_MemoryUsed;
  219. }
  220. public string[] RemoveByPattern(string pattern, string pass, out int notDeleted)
  221. {
  222. notDeleted = 0;
  223. List<string> ret;
  224. lock (linksetDataLock)
  225. {
  226. if (Data.Count <= 0)
  227. return Array.Empty<string>();
  228. try
  229. {
  230. ret = new List<string>();
  231. Regex reg = new(pattern, RegexOptions.CultureInvariant, TimeSpan.FromMilliseconds(1));
  232. foreach (var kvp in Data)
  233. {
  234. if (reg.IsMatch(kvp.Key))
  235. {
  236. if (kvp.Value.CheckPassword(pass))
  237. {
  238. int mem = kvp.Value.Value.Length + kvp.Key.Length;
  239. if(kvp.Value.IsProtected)
  240. mem += kvp.Value.Password.Length;
  241. m_MemoryUsed -= mem;
  242. ret.Add(kvp.Key);
  243. }
  244. else
  245. notDeleted++;
  246. }
  247. }
  248. }
  249. catch
  250. {
  251. notDeleted = 0;
  252. return Array.Empty<string>();
  253. }
  254. foreach (string k in ret)
  255. Data.Remove(k);
  256. if (m_MemoryUsed < 0)
  257. m_MemoryUsed = 0;
  258. return ret.ToArray();
  259. }
  260. }
  261. public int CountByPattern(string pattern)
  262. {
  263. lock (linksetDataLock)
  264. {
  265. if (Data.Count <= 0)
  266. return 0;
  267. try
  268. {
  269. Regex reg = new(pattern, RegexOptions.CultureInvariant, TimeSpan.FromMilliseconds(1));
  270. int ret = 0;
  271. foreach (string k in Data.Keys)
  272. {
  273. if (reg.IsMatch(k))
  274. ret++;
  275. }
  276. return ret;
  277. }
  278. catch
  279. {
  280. return 0;
  281. }
  282. }
  283. }
  284. public string[] ListKeysByPatttern(string pattern, int start, int count)
  285. {
  286. List<string> lkeys;
  287. lock (linksetDataLock)
  288. {
  289. if (Data.Count <= 0 || start >= Data.Count)
  290. return Array.Empty<string>();
  291. try
  292. {
  293. Regex reg = new(pattern, RegexOptions.CultureInvariant, TimeSpan.FromMilliseconds(1));
  294. lkeys = new(Data.Count);
  295. foreach (string k in Data.Keys)
  296. {
  297. if (reg.IsMatch(k))
  298. lkeys.Add(k);
  299. }
  300. }
  301. catch
  302. {
  303. return Array.Empty<string>();
  304. }
  305. }
  306. if (lkeys.Count == 0)
  307. return Array.Empty<string>();
  308. lkeys.Sort();
  309. if (start < 0)
  310. start = 0;
  311. if (count < 1 || start + count > lkeys.Count)
  312. count = lkeys.Count - start;
  313. List<string> result = lkeys.GetRange(start, count);
  314. return result.ToArray();
  315. }
  316. public string[] ListKeys(int start, int count)
  317. {
  318. string[] keys;
  319. lock (linksetDataLock)
  320. {
  321. if (Data.Count <= 0 || start >= Data.Count)
  322. return Array.Empty<string>();
  323. keys = new string[Data.Count];
  324. Data.Keys.CopyTo(keys, 0);
  325. }
  326. Array.Sort(keys);
  327. if (start < 0)
  328. start = 0;
  329. if (count < 1)
  330. return keys[start..];
  331. int end = start + count;
  332. if (end >= keys.Length)
  333. return keys[start..];
  334. return keys[start..end];
  335. }
  336. /// <summary>
  337. /// Merge the linksetData present in another Linkset into this one.
  338. /// If a key is present in our linksetData it wins, dont overide it.
  339. /// </summary>
  340. /// <param name="otherLinkset"></param>
  341. public void MergeOther(LinksetData otherLinksetData)
  342. {
  343. if (otherLinksetData is null || otherLinksetData.Data is null || otherLinksetData.Count() == 0)
  344. return;
  345. lock (linksetDataLock)
  346. {
  347. if(m_MemoryUsed + otherLinksetData.Used() < m_MemoryLimit)
  348. {
  349. foreach (var kvp in otherLinksetData.Data)
  350. {
  351. if (Data.TryAdd(kvp.Key, kvp.Value))
  352. {
  353. m_MemoryUsed += kvp.Key.Length + kvp.Value.Value.Length;
  354. if (!string.IsNullOrEmpty(kvp.Value.Password))
  355. m_MemoryUsed += kvp.Value.Password.Length;
  356. }
  357. }
  358. return;
  359. }
  360. SortedList<string,LinksetDataEntry> otherOrdered = new(otherLinksetData.Data);
  361. foreach (var kvp in otherOrdered)
  362. {
  363. int mem = kvp.Key.Length + kvp.Value.Value.Length;
  364. if (!string.IsNullOrEmpty(kvp.Value.Password))
  365. mem += kvp.Value.Password.Length;
  366. if (m_MemoryUsed + mem >= m_MemoryLimit)
  367. return;
  368. if (Data.TryAdd(kvp.Key, kvp.Value))
  369. {
  370. m_MemoryUsed += mem;
  371. if (!string.IsNullOrEmpty(kvp.Value.Password))
  372. m_MemoryUsed += kvp.Value.Password.Length;
  373. }
  374. }
  375. otherLinksetData.Data = null;
  376. otherLinksetData.m_MemoryUsed = 0;
  377. }
  378. }
  379. /// <summary>
  380. /// ResetLinksetData - clear the list and update the accounting.
  381. /// </summary>
  382. public void ResetLinksetData()
  383. {
  384. lock (linksetDataLock)
  385. {
  386. if (Data.Count <= 0)
  387. return;
  388. Data.Clear();
  389. m_MemoryUsed = 0;
  390. }
  391. }
  392. public string ToJson()
  393. {
  394. lock (linksetDataLock)
  395. {
  396. return JsonSerializer.Serialize<Dictionary<string, LinksetDataEntry>>(Data);
  397. }
  398. }
  399. public void ToXML(XmlTextWriter writer)
  400. {
  401. if (Data.Count < 1)
  402. return;
  403. using MemoryStream ms = new(m_MemoryUsed);
  404. ToBin(ms);
  405. if (ms.Length < 1)
  406. return;
  407. writer.WriteStartElement("lnkstdt");
  408. writer.WriteBase64(ms.GetBuffer(), 0, (int)ms.Length);
  409. writer.WriteEndElement();
  410. }
  411. public static LinksetData FromXML(ReadOnlySpan<char> data)
  412. {
  413. if (data.Length < 8)
  414. return null;
  415. int minLength = ((data.Length * 3) + 3) / 4;
  416. byte[] bindata = ArrayPool<byte>.Shared.Rent(minLength);
  417. try
  418. {
  419. if (Convert.TryFromBase64Chars(data, bindata, out int bytesWritten))
  420. return FromBin(bindata);
  421. }
  422. catch
  423. {
  424. }
  425. finally
  426. {
  427. ArrayPool<byte>.Shared.Return(bindata);
  428. }
  429. return null;
  430. }
  431. public byte[] ToBin()
  432. {
  433. if(Data.Count < 1)
  434. return null;
  435. using MemoryStream ms = new(m_MemoryUsed);
  436. ToBin(ms);
  437. return ms.Length > 0 ? ms.ToArray() : null;
  438. }
  439. public void ToBin(MemoryStream ms)
  440. {
  441. try
  442. {
  443. using BinaryWriter bw = new BinaryWriter(ms, System.Text.Encoding.UTF8, true);
  444. bw.Write((byte)1); // storage version
  445. bw.Write7BitEncodedInt(m_MemoryLimit);
  446. lock (linksetDataLock)
  447. {
  448. bw.Write7BitEncodedInt(Data.Count);
  449. foreach (var kvp in Data)
  450. {
  451. bw.Write(kvp.Key);
  452. bw.Write(kvp.Value.Value);
  453. if(kvp.Value.IsProtected)
  454. bw.Write(kvp.Value.Password);
  455. else
  456. bw.Write((byte)0);
  457. }
  458. }
  459. return;
  460. }
  461. catch { }
  462. ms.SetLength(0);
  463. }
  464. public static LinksetData FromBin(byte[] data)
  465. {
  466. if (data.Length < 8)
  467. return null;
  468. try
  469. {
  470. using BinaryReader br = new BinaryReader(new MemoryStream(data));
  471. int version = br.Read7BitEncodedInt();
  472. int memoryLimit = br.Read7BitEncodedInt();
  473. if(memoryLimit < 0 || memoryLimit > 256 * 1024)
  474. memoryLimit = 256 * 1024;
  475. int count = br.Read7BitEncodedInt();
  476. if(count == 0)
  477. return null;
  478. LinksetData ld = new LinksetData(memoryLimit);
  479. for(int i = 0; i < count; i++)
  480. {
  481. string key = br.ReadString();
  482. if (key.Length == 0)
  483. continue;
  484. ld.m_MemoryUsed += key.Length;
  485. string value = br.ReadString();
  486. if(value.Length == 0)
  487. continue;
  488. ld.m_MemoryUsed += value.Length;
  489. string pass = br.ReadString();
  490. ld.m_MemoryUsed += pass.Length;
  491. if(ld.m_MemoryUsed > memoryLimit)
  492. break;
  493. if(pass.Length > 0)
  494. {
  495. ld.Data[key] = new LinksetDataEntry()
  496. {
  497. Value = value,
  498. Password = pass
  499. };
  500. }
  501. else
  502. {
  503. ld.Data[key] = new LinksetDataEntry()
  504. {
  505. Value = value,
  506. Password = null
  507. };
  508. }
  509. }
  510. return ld;
  511. }
  512. catch
  513. {
  514. }
  515. return null;
  516. }
  517. }
  518. public class LinksetDataEntry
  519. {
  520. public string Password;
  521. public string Value;
  522. public LinksetDataEntry() { }
  523. public LinksetDataEntry(string value, string password)
  524. {
  525. Value = value;
  526. Password = password;
  527. }
  528. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  529. public bool CheckPassword(string pass)
  530. {
  531. // A undocumented caveat for LinksetData appears to be that even for unprotected values,
  532. // if a pass is provided, it is still treated as protected
  533. return string.IsNullOrEmpty(Password) || (Password == pass);
  534. }
  535. public LinksetDataEntry Copy()
  536. {
  537. return new LinksetDataEntry
  538. {
  539. Password = Password,
  540. Value = Value
  541. };
  542. }
  543. public bool IsProtected
  544. {
  545. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  546. get { return !string.IsNullOrEmpty(Password); }
  547. }
  548. public bool IsNotProtected
  549. {
  550. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  551. get { return string.IsNullOrEmpty(Password); }
  552. }
  553. }
  554. }