ObjectId.cs 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542
  1. using System.Diagnostics;
  2. using System.Runtime.CompilerServices;
  3. using System.Security.Cryptography;
  4. using System.Text;
  5. namespace PlcSiemens.Core.Common
  6. {
  7. /// <summary>Represents an ObjectId
  8. /// </summary>
  9. [Serializable]
  10. public struct ObjectId : IComparable<ObjectId>, IEquatable<ObjectId>
  11. {
  12. // private static fields
  13. private static readonly DateTime __unixEpoch;
  14. private static readonly long __dateTimeMaxValueMillisecondsSinceEpoch;
  15. private static readonly long __dateTimeMinValueMillisecondsSinceEpoch;
  16. private static ObjectId __emptyInstance = default(ObjectId);
  17. private static int __staticMachine;
  18. private static short __staticPid;
  19. private static int __staticIncrement; // high byte will be masked out when generating new ObjectId
  20. private static uint[] _lookup32 = Enumerable.Range(0, 256).Select(i =>
  21. {
  22. string s = i.ToString("x2");
  23. return ((uint)s[0]) + ((uint)s[1] << 16);
  24. }).ToArray();
  25. // we're using 14 bytes instead of 12 to hold the ObjectId in memory but unlike a byte[] there is no additional object on the heap
  26. // the extra two bytes are not visible to anyone outside of this class and they buy us considerable simplification
  27. // an additional advantage of this representation is that it will serialize to JSON without any 64 bit overflow problems
  28. private int _timestamp;
  29. private int _machine;
  30. private short _pid;
  31. private int _increment;
  32. // static constructor
  33. static ObjectId()
  34. {
  35. __unixEpoch = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);
  36. __dateTimeMaxValueMillisecondsSinceEpoch = (DateTime.MaxValue - __unixEpoch).Ticks / 10000;
  37. __dateTimeMinValueMillisecondsSinceEpoch = (DateTime.MinValue - __unixEpoch).Ticks / 10000;
  38. __staticMachine = GetMachineHash();
  39. __staticIncrement = (new Random()).Next();
  40. __staticPid = (short)GetCurrentProcessId();
  41. }
  42. // constructors
  43. /// <summary>
  44. /// Initializes a new instance of the ObjectId class.
  45. /// </summary>
  46. /// <param name="bytes">The bytes.</param>
  47. public ObjectId(byte[] bytes)
  48. {
  49. if (bytes == null)
  50. {
  51. throw new ArgumentNullException("bytes");
  52. }
  53. Unpack(bytes, out _timestamp, out _machine, out _pid, out _increment);
  54. }
  55. /// <summary>
  56. /// Initializes a new instance of the ObjectId class.
  57. /// </summary>
  58. /// <param name="timestamp">The timestamp (expressed as a DateTime).</param>
  59. /// <param name="machine">The machine hash.</param>
  60. /// <param name="pid">The PID.</param>
  61. /// <param name="increment">The increment.</param>
  62. public ObjectId(DateTime timestamp, int machine, short pid, int increment)
  63. : this(GetTimestampFromDateTime(timestamp), machine, pid, increment)
  64. {
  65. }
  66. /// <summary>
  67. /// Initializes a new instance of the ObjectId class.
  68. /// </summary>
  69. /// <param name="timestamp">The timestamp.</param>
  70. /// <param name="machine">The machine hash.</param>
  71. /// <param name="pid">The PID.</param>
  72. /// <param name="increment">The increment.</param>
  73. public ObjectId(int timestamp, int machine, short pid, int increment)
  74. {
  75. if ((machine & 0xff000000) != 0)
  76. {
  77. throw new ArgumentOutOfRangeException("machine", "The machine value must be between 0 and 16777215 (it must fit in 3 bytes).");
  78. }
  79. if ((increment & 0xff000000) != 0)
  80. {
  81. throw new ArgumentOutOfRangeException("increment", "The increment value must be between 0 and 16777215 (it must fit in 3 bytes).");
  82. }
  83. _timestamp = timestamp;
  84. _machine = machine;
  85. _pid = pid;
  86. _increment = increment;
  87. }
  88. /// <summary>
  89. /// Initializes a new instance of the ObjectId class.
  90. /// </summary>
  91. /// <param name="value">The value.</param>
  92. public ObjectId(string value)
  93. {
  94. if (value == null)
  95. {
  96. throw new ArgumentNullException("value");
  97. }
  98. Unpack(ParseHexString(value), out _timestamp, out _machine, out _pid, out _increment);
  99. }
  100. // public static properties
  101. /// <summary>
  102. /// Gets an instance of ObjectId where the value is empty.
  103. /// </summary>
  104. public static ObjectId Empty
  105. {
  106. get { return __emptyInstance; }
  107. }
  108. // public properties
  109. /// <summary>
  110. /// Gets the timestamp.
  111. /// </summary>
  112. public int Timestamp
  113. {
  114. get { return _timestamp; }
  115. }
  116. /// <summary>
  117. /// Gets the machine.
  118. /// </summary>
  119. public int Machine
  120. {
  121. get { return _machine; }
  122. }
  123. /// <summary>
  124. /// Gets the PID.
  125. /// </summary>
  126. public short Pid
  127. {
  128. get { return _pid; }
  129. }
  130. /// <summary>
  131. /// Gets the increment.
  132. /// </summary>
  133. public int Increment
  134. {
  135. get { return _increment; }
  136. }
  137. /// <summary>
  138. /// Gets the creation time (derived from the timestamp).
  139. /// </summary>
  140. public DateTime CreationTime
  141. {
  142. get { return __unixEpoch.AddSeconds(_timestamp); }
  143. }
  144. // public operators
  145. /// <summary>
  146. /// Compares two ObjectIds.
  147. /// </summary>
  148. /// <param name="lhs">The first ObjectId.</param>
  149. /// <param name="rhs">The other ObjectId</param>
  150. /// <returns>True if the first ObjectId is less than the second ObjectId.</returns>
  151. public static bool operator <(ObjectId lhs, ObjectId rhs)
  152. {
  153. return lhs.CompareTo(rhs) < 0;
  154. }
  155. /// <summary>
  156. /// Compares two ObjectIds.
  157. /// </summary>
  158. /// <param name="lhs">The first ObjectId.</param>
  159. /// <param name="rhs">The other ObjectId</param>
  160. /// <returns>True if the first ObjectId is less than or equal to the second ObjectId.</returns>
  161. public static bool operator <=(ObjectId lhs, ObjectId rhs)
  162. {
  163. return lhs.CompareTo(rhs) <= 0;
  164. }
  165. /// <summary>
  166. /// Compares two ObjectIds.
  167. /// </summary>
  168. /// <param name="lhs">The first ObjectId.</param>
  169. /// <param name="rhs">The other ObjectId.</param>
  170. /// <returns>True if the two ObjectIds are equal.</returns>
  171. public static bool operator ==(ObjectId lhs, ObjectId rhs)
  172. {
  173. return lhs.Equals(rhs);
  174. }
  175. /// <summary>
  176. /// Compares two ObjectIds.
  177. /// </summary>
  178. /// <param name="lhs">The first ObjectId.</param>
  179. /// <param name="rhs">The other ObjectId.</param>
  180. /// <returns>True if the two ObjectIds are not equal.</returns>
  181. public static bool operator !=(ObjectId lhs, ObjectId rhs)
  182. {
  183. return !(lhs == rhs);
  184. }
  185. /// <summary>
  186. /// Compares two ObjectIds.
  187. /// </summary>
  188. /// <param name="lhs">The first ObjectId.</param>
  189. /// <param name="rhs">The other ObjectId</param>
  190. /// <returns>True if the first ObjectId is greather than or equal to the second ObjectId.</returns>
  191. public static bool operator >=(ObjectId lhs, ObjectId rhs)
  192. {
  193. return lhs.CompareTo(rhs) >= 0;
  194. }
  195. /// <summary>
  196. /// Compares two ObjectIds.
  197. /// </summary>
  198. /// <param name="lhs">The first ObjectId.</param>
  199. /// <param name="rhs">The other ObjectId</param>
  200. /// <returns>True if the first ObjectId is greather than the second ObjectId.</returns>
  201. public static bool operator >(ObjectId lhs, ObjectId rhs)
  202. {
  203. return lhs.CompareTo(rhs) > 0;
  204. }
  205. // public static methods
  206. /// <summary>
  207. /// Generates a new ObjectId with a unique value.
  208. /// </summary>
  209. /// <returns>An ObjectId.</returns>
  210. public static ObjectId GenerateNewId()
  211. {
  212. return GenerateNewId(GetTimestampFromDateTime(DateTime.UtcNow));
  213. }
  214. /// <summary>
  215. /// Generates a new ObjectId with a unique value (with the timestamp component based on a given DateTime).
  216. /// </summary>
  217. /// <param name="timestamp">The timestamp component (expressed as a DateTime).</param>
  218. /// <returns>An ObjectId.</returns>
  219. public static ObjectId GenerateNewId(DateTime timestamp)
  220. {
  221. return GenerateNewId(GetTimestampFromDateTime(timestamp));
  222. }
  223. /// <summary>
  224. /// Generates a new ObjectId with a unique value (with the given timestamp).
  225. /// </summary>
  226. /// <param name="timestamp">The timestamp component.</param>
  227. /// <returns>An ObjectId.</returns>
  228. public static ObjectId GenerateNewId(int timestamp)
  229. {
  230. int increment = Interlocked.Increment(ref __staticIncrement) & 0x00ffffff; // only use low order 3 bytes
  231. return new ObjectId(timestamp, __staticMachine, __staticPid, increment);
  232. }
  233. /// <summary>
  234. /// Generates a new ObjectId string with a unique value.
  235. /// </summary>
  236. /// <returns>The string value of the new generated ObjectId.</returns>
  237. public static string GenerateNewStringId()
  238. {
  239. return GenerateNewId().ToString();
  240. }
  241. /// <summary>
  242. /// Packs the components of an ObjectId into a byte array.
  243. /// </summary>
  244. /// <param name="timestamp">The timestamp.</param>
  245. /// <param name="machine">The machine hash.</param>
  246. /// <param name="pid">The PID.</param>
  247. /// <param name="increment">The increment.</param>
  248. /// <returns>A byte array.</returns>
  249. public static byte[] Pack(int timestamp, int machine, short pid, int increment)
  250. {
  251. if ((machine & 0xff000000) != 0)
  252. {
  253. throw new ArgumentOutOfRangeException("machine", "The machine value must be between 0 and 16777215 (it must fit in 3 bytes).");
  254. }
  255. if ((increment & 0xff000000) != 0)
  256. {
  257. throw new ArgumentOutOfRangeException("increment", "The increment value must be between 0 and 16777215 (it must fit in 3 bytes).");
  258. }
  259. byte[] bytes = new byte[12];
  260. bytes[0] = (byte)(timestamp >> 24);
  261. bytes[1] = (byte)(timestamp >> 16);
  262. bytes[2] = (byte)(timestamp >> 8);
  263. bytes[3] = (byte)(timestamp);
  264. bytes[4] = (byte)(machine >> 16);
  265. bytes[5] = (byte)(machine >> 8);
  266. bytes[6] = (byte)(machine);
  267. bytes[7] = (byte)(pid >> 8);
  268. bytes[8] = (byte)(pid);
  269. bytes[9] = (byte)(increment >> 16);
  270. bytes[10] = (byte)(increment >> 8);
  271. bytes[11] = (byte)(increment);
  272. return bytes;
  273. }
  274. /// <summary>
  275. /// Parses a string and creates a new ObjectId.
  276. /// </summary>
  277. /// <param name="s">The string value.</param>
  278. /// <returns>A ObjectId.</returns>
  279. public static ObjectId Parse(string s)
  280. {
  281. if (s == null)
  282. {
  283. throw new ArgumentNullException("s");
  284. }
  285. if (s.Length != 24)
  286. {
  287. throw new ArgumentOutOfRangeException("s", "ObjectId string value must be 24 characters.");
  288. }
  289. return new ObjectId(ParseHexString(s));
  290. }
  291. /// <summary>
  292. /// Unpacks a byte array into the components of an ObjectId.
  293. /// </summary>
  294. /// <param name="bytes">A byte array.</param>
  295. /// <param name="timestamp">The timestamp.</param>
  296. /// <param name="machine">The machine hash.</param>
  297. /// <param name="pid">The PID.</param>
  298. /// <param name="increment">The increment.</param>
  299. public static void Unpack(byte[] bytes, out int timestamp, out int machine, out short pid, out int increment)
  300. {
  301. if (bytes == null)
  302. {
  303. throw new ArgumentNullException("bytes");
  304. }
  305. if (bytes.Length != 12)
  306. {
  307. throw new ArgumentOutOfRangeException("bytes", "Byte array must be 12 bytes long.");
  308. }
  309. timestamp = (bytes[0] << 24) + (bytes[1] << 16) + (bytes[2] << 8) + bytes[3];
  310. machine = (bytes[4] << 16) + (bytes[5] << 8) + bytes[6];
  311. pid = (short)((bytes[7] << 8) + bytes[8]);
  312. increment = (bytes[9] << 16) + (bytes[10] << 8) + bytes[11];
  313. }
  314. // private static methods
  315. /// <summary>
  316. /// Gets the current process id. This method exists because of how CAS operates on the call stack, checking
  317. /// for permissions before executing the method. Hence, if we inlined this call, the calling method would not execute
  318. /// before throwing an exception requiring the try/catch at an even higher level that we don't necessarily control.
  319. /// </summary>
  320. [MethodImpl(MethodImplOptions.NoInlining)]
  321. private static int GetCurrentProcessId()
  322. {
  323. return Process.GetCurrentProcess().Id;
  324. }
  325. private static int GetMachineHash()
  326. {
  327. var hostName = Environment.MachineName; // use instead of Dns.HostName so it will work offline
  328. var md5 = MD5.Create();
  329. var hash = md5.ComputeHash(Encoding.UTF8.GetBytes(hostName));
  330. return (hash[0] << 16) + (hash[1] << 8) + hash[2]; // use first 3 bytes of hash
  331. }
  332. private static int GetTimestampFromDateTime(DateTime timestamp)
  333. {
  334. return (int)Math.Floor((ToUniversalTime(timestamp) - __unixEpoch).TotalSeconds);
  335. }
  336. // public methods
  337. /// <summary>
  338. /// Compares this ObjectId to another ObjectId.
  339. /// </summary>
  340. /// <param name="other">The other ObjectId.</param>
  341. /// <returns>A 32-bit signed integer that indicates whether this ObjectId is less than, equal to, or greather than the other.</returns>
  342. public int CompareTo(ObjectId other)
  343. {
  344. int r = _timestamp.CompareTo(other._timestamp);
  345. if (r != 0) { return r; }
  346. r = _machine.CompareTo(other._machine);
  347. if (r != 0) { return r; }
  348. r = _pid.CompareTo(other._pid);
  349. if (r != 0) { return r; }
  350. return _increment.CompareTo(other._increment);
  351. }
  352. /// <summary>
  353. /// Compares this ObjectId to another ObjectId.
  354. /// </summary>
  355. /// <param name="rhs">The other ObjectId.</param>
  356. /// <returns>True if the two ObjectIds are equal.</returns>
  357. public bool Equals(ObjectId rhs)
  358. {
  359. return
  360. _timestamp == rhs._timestamp &&
  361. _machine == rhs._machine &&
  362. _pid == rhs._pid &&
  363. _increment == rhs._increment;
  364. }
  365. /// <summary>
  366. /// Compares this ObjectId to another object.
  367. /// </summary>
  368. /// <param name="obj">The other object.</param>
  369. /// <returns>True if the other object is an ObjectId and equal to this one.</returns>
  370. public override bool Equals(object obj)
  371. {
  372. if (obj is ObjectId)
  373. {
  374. return Equals((ObjectId)obj);
  375. }
  376. else
  377. {
  378. return false;
  379. }
  380. }
  381. /// <summary>
  382. /// Gets the hash code.
  383. /// </summary>
  384. /// <returns>The hash code.</returns>
  385. public override int GetHashCode()
  386. {
  387. int hash = 17;
  388. hash = 37 * hash + _timestamp.GetHashCode();
  389. hash = 37 * hash + _machine.GetHashCode();
  390. hash = 37 * hash + _pid.GetHashCode();
  391. hash = 37 * hash + _increment.GetHashCode();
  392. return hash;
  393. }
  394. /// <summary>
  395. /// Converts the ObjectId to a byte array.
  396. /// </summary>
  397. /// <returns>A byte array.</returns>
  398. public byte[] ToByteArray()
  399. {
  400. return Pack(_timestamp, _machine, _pid, _increment);
  401. }
  402. /// <summary>
  403. /// Returns a string representation of the value.
  404. /// </summary>
  405. /// <returns>A string representation of the value.</returns>
  406. public override string ToString()
  407. {
  408. return ToHexString(ToByteArray());
  409. }
  410. /// <summary>
  411. /// Parses a hex string into its equivalent byte array.
  412. /// </summary>
  413. /// <param name="s">The hex string to parse.</param>
  414. /// <returns>The byte equivalent of the hex string.</returns>
  415. public static byte[] ParseHexString(string s)
  416. {
  417. if (s == null)
  418. {
  419. throw new ArgumentNullException("s");
  420. }
  421. if (s.Length % 2 == 1)
  422. {
  423. throw new Exception("The binary key cannot have an odd number of digits");
  424. }
  425. byte[] arr = new byte[s.Length >> 1];
  426. for (int i = 0; i < s.Length >> 1; ++i)
  427. {
  428. arr[i] = (byte)((GetHexVal(s[i << 1]) << 4) + (GetHexVal(s[(i << 1) + 1])));
  429. }
  430. return arr;
  431. }
  432. /// <summary>
  433. /// Converts a byte array to a hex string.
  434. /// </summary>
  435. /// <param name="bytes">The byte array.</param>
  436. /// <returns>A hex string.</returns>
  437. public static string ToHexString(byte[] bytes)
  438. {
  439. if (bytes == null)
  440. {
  441. throw new ArgumentNullException("bytes");
  442. }
  443. var result = new char[bytes.Length * 2];
  444. for (int i = 0; i < bytes.Length; i++)
  445. {
  446. var val = _lookup32[bytes[i]];
  447. result[2 * i] = (char)val;
  448. result[2 * i + 1] = (char)(val >> 16);
  449. }
  450. return new string(result);
  451. }
  452. /// <summary>
  453. /// Converts a DateTime to number of milliseconds since Unix epoch.
  454. /// </summary>
  455. /// <param name="dateTime">A DateTime.</param>
  456. /// <returns>Number of seconds since Unix epoch.</returns>
  457. public static long ToMillisecondsSinceEpoch(DateTime dateTime)
  458. {
  459. var utcDateTime = ToUniversalTime(dateTime);
  460. return (utcDateTime - __unixEpoch).Ticks / 10000;
  461. }
  462. /// <summary>
  463. /// Converts a DateTime to UTC (with special handling for MinValue and MaxValue).
  464. /// </summary>
  465. /// <param name="dateTime">A DateTime.</param>
  466. /// <returns>The DateTime in UTC.</returns>
  467. public static DateTime ToUniversalTime(DateTime dateTime)
  468. {
  469. if (dateTime == DateTime.MinValue)
  470. {
  471. return DateTime.SpecifyKind(DateTime.MinValue, DateTimeKind.Utc);
  472. }
  473. else if (dateTime == DateTime.MaxValue)
  474. {
  475. return DateTime.SpecifyKind(DateTime.MaxValue, DateTimeKind.Utc);
  476. }
  477. else
  478. {
  479. return dateTime.ToUniversalTime();
  480. }
  481. }
  482. private static int GetHexVal(char hex)
  483. {
  484. int val = (int)hex;
  485. //For uppercase A-F letters:
  486. //return val - (val < 58 ? 48 : 55);
  487. //For lowercase a-f letters:
  488. //return val - (val < 58 ? 48 : 87);
  489. //Or the two combined, but a bit slower:
  490. return val - (val < 58 ? 48 : (val < 97 ? 55 : 87));
  491. }
  492. }
  493. }