From e22d31f400bf663e1f33ce5c32fde7f893163b53 Mon Sep 17 00:00:00 2001 From: Eddy Luo Date: Fri, 7 Jun 2019 17:23:21 -0700 Subject: [PATCH 1/4] Added some v3 implementations --- .../CorrelationVectorTests.cs | 75 ++++ .../CorrelationVector.cs | 15 +- .../CorrelationVectorV3.cs | 335 ++++++++++++++++++ 3 files changed, 424 insertions(+), 1 deletion(-) create mode 100644 src/Microsoft.CorrelationVector/CorrelationVectorV3.cs diff --git a/src/Microsoft.CorrelationVector.UnitTests/CorrelationVectorTests.cs b/src/Microsoft.CorrelationVector.UnitTests/CorrelationVectorTests.cs index 04dc1d7..21aaf7f 100644 --- a/src/Microsoft.CorrelationVector.UnitTests/CorrelationVectorTests.cs +++ b/src/Microsoft.CorrelationVector.UnitTests/CorrelationVectorTests.cs @@ -2,6 +2,7 @@ // Licensed under the MIT License. using System; +using System.Globalization; using System.Threading.Tasks; using Microsoft.VisualStudio.TestTools.UnitTesting; @@ -153,6 +154,33 @@ public void ParseCorrelationVectorV2Test() Assert.AreEqual("3", splitVector[1], "Correlation Vector extension was not parsed properly"); Assert.AreEqual("4", splitVector[2], "Correlation Vector extension was not parsed properly"); Assert.AreEqual("5", splitVector[3], "Correlation Vector extension was not parsed properly"); + } + + [TestMethod] + public void ParseCorrelationVectorV3Test() + { + var correlationVector = CorrelationVector.Parse("A.Y58xO9ov0kmpPvkiuzMUVA.3.4.A"); + var splitVector = correlationVector.Value.Split('.'); + + Assert.AreEqual(CorrelationVectorVersion.V3, correlationVector.Version, "Correlation Vector version should be V3"); + Assert.AreEqual(5, splitVector.Length, "Correlation Vector was not parsed properly"); + Assert.AreEqual("Y58xO9ov0kmpPvkiuzMUVA", correlationVector.Base, "Correlation Vector base was not parsed properly"); + Assert.AreEqual("3", splitVector[2], "Correlation Vector extension was not parsed properly"); + Assert.AreEqual("4", splitVector[3], "Correlation Vector extension was not parsed properly"); + Assert.AreEqual(10, correlationVector.Extension, "Correlation Vector extension was not parsed properly"); + } + + [TestMethod] + public void ParseCorrelationVectorV3Test2() + { + var correlationVector = CorrelationVector.Parse("A.Y58xO9ov0kmpPvkiuzMUVA"); + var splitVector = correlationVector.Value.Split('.'); + + Assert.AreEqual(CorrelationVectorVersion.V3, correlationVector.Version, "Correlation Vector version should be V3"); + Assert.AreEqual("Y58xO9ov0kmpPvkiuzMUVA", correlationVector.Base, "Correlation Vector base was not parsed properly"); + Assert.AreEqual("3", splitVector[2], "Correlation Vector extension was not parsed properly"); + Assert.AreEqual("4", splitVector[3], "Correlation Vector extension was not parsed properly"); + Assert.AreEqual(10, correlationVector.Extension, "Correlation Vector extension was not parsed properly"); } [TestMethod] @@ -317,6 +345,7 @@ public void SpinSortValidation() for (int i = 0; i < 100; i++) { // The cV after a Spin will look like .0..0, so the spinValue is at index = 2. + CorrelationVector newVector = CorrelationVector.Spin(vector.Value, spinParameters); var spinValue = uint.Parse(CorrelationVector.Spin(vector.Value, spinParameters).Value.Split('.')[2]); // Count the number of times the counter wraps. @@ -335,6 +364,52 @@ public void SpinSortValidation() Assert.IsTrue(wrappedCounter <= 1); } + [TestMethod] + public void SpinSortValidationV3() + { + var vector = new CorrelationVectorV3(); + var spinParameters = new SpinParameters + { + Entropy = SpinEntropy.Four, + Interval = SpinCounterInterval.Fine, + Periodicity = SpinCounterPeriodicity.Long + }; + + uint lastSpinValue = 0; + var wrappedCounter = 0; + for (int i = 0; i < 100; i++) + { + // The cV after a Spin will look like .0_.0, so the spinValue is at index = 2. + CorrelationVector newVector = CorrelationVectorV3.Spin(vector.Value, spinParameters); + var spinValue = uint.Parse(newVector.Value.Split('.', '_')[2], System.Globalization.NumberStyles.HexNumber, CultureInfo.InvariantCulture); + + // Count the number of times the counter wraps. + if (spinValue <= lastSpinValue) + { + wrappedCounter++; + } + + lastSpinValue = spinValue; + + // Wait for 10ms. + Task.Delay(10).Wait(); + } + + // The counter should wrap at most 1 time. + Assert.IsTrue(wrappedCounter <= 1); + } + + [TestMethod] + public void TestResetV3() + { + CorrelationVector.ValidateCorrelationVectorDuringCreation = false; + const string baseVector = "A.KZY+dsX2jEaZesgCPjJ2Ng.7FFFFFFF.7FFFFFFF.7FFFFFFF.7FFFFFFF.7FFFFFFF.7FFFFFFF.7FFFFFFF.7FFFFFFF.7FFFFFFF.7FFFFFFF.7FFFFFFF.FFF"; + + // we hit 127 chars limit, will reset vector + var vector = CorrelationVector.Parse(baseVector); + Assert.AreEqual(string.Concat(baseVector, CorrelationVectorV2.TerminationSign), vector.Value); + } + [TestMethod] public void SpinPastMaxWithTerminationSignV2() { diff --git a/src/Microsoft.CorrelationVector/CorrelationVector.cs b/src/Microsoft.CorrelationVector/CorrelationVector.cs index 2e5cf75..680fe8a 100644 --- a/src/Microsoft.CorrelationVector/CorrelationVector.cs +++ b/src/Microsoft.CorrelationVector/CorrelationVector.cs @@ -74,6 +74,19 @@ private static CorrelationVectorVersion InferVersion(string correlationVector) { return CorrelationVectorVersion.V2; } + else if (index == 1) + { + // cV version indicated by starting single letter for V3 and after + if (correlationVector.Substring(0, index) == "A") + { + return CorrelationVectorVersion.V3; + } + else + { + //By default not reporting error, just return V1 + return CorrelationVectorVersion.V1; + } + } else { //By default not reporting error, just return V1 @@ -89,7 +102,7 @@ private static CorrelationVectorVersion InferVersion(string correlationVector) public static CorrelationVector Parse(string correlationVector) { CorrelationVectorVersion version = InferVersion(correlationVector); - return RunStaticMethod(correlationVector, version, CorrelationVectorV1.Parse, CorrelationVectorV2.Parse); + return RunStaticMethod(correlationVector, version, CorrelationVectorV1.Parse, CorrelationVectorV2.Parse, CorrelationVectorV3.Parse); } diff --git a/src/Microsoft.CorrelationVector/CorrelationVectorV3.cs b/src/Microsoft.CorrelationVector/CorrelationVectorV3.cs new file mode 100644 index 0000000..bc751b7 --- /dev/null +++ b/src/Microsoft.CorrelationVector/CorrelationVectorV3.cs @@ -0,0 +1,335 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Text; +using System.Threading; + +namespace Microsoft.CorrelationVector +{ + public sealed class CorrelationVectorV3 : CorrelationVector + { + internal new const byte MaxVectorLength = 127; + internal const byte BaseLength = 22; + + /// + /// Version character for Correlation Vector v3.0 + /// + public const char VersionChar = 'A'; + + /// + /// Standard delimiter used for suffix, version separation + /// + public const char StandardDelim = '.'; + + /// + /// Delimiter used for a cV that has had a reset operation + /// + public const char ResetDelim = '#'; + + /// + /// Delimiter used for a cV that is interoperable with a W3C traceparent + /// + public const char SpanDelim = '-'; + + /// + /// Delimiter used for a cV that uses a Spin operation + /// + public const char SpinDelim = '_'; + + private static Random rng = new Random(); + + /// + /// Returns the full string representation of the Correlation Vector. + /// + public override string Value + { + get + { + // Convert extension to hex before returning + string hexExtension = extension.ToString("X"); + return string.Concat(this.BaseVector, ".", hexExtension); + } + } + + /// + /// The full correlation vector, excluding the suffix at the end. + /// This includes the "A." at the beginning. + /// + internal readonly string BaseVector; + + /// + /// The suffix at the end of the correlation vector, as an integer. + /// + private int extension = 0; + + /// + /// Returns the cV base of the correlation vector. + /// Example: cV with Value A.e8iECJiOvUGPvOVtchxG9g.F.A.23 returns e8iECJiOvUGPvOVtchxG9g + /// + public override string Base + { + get + { + // Search from first letter after "A." and stop at the first instance of a delimiter + return this.Value.Substring(2, this.Value.IndexOfAny(new char[] { StandardDelim, ResetDelim, SpanDelim, SpinDelim }, 2) - 2); + } + } + + public override int Extension + { + get + { + return this.extension; + } + } + + /// + /// Initializes a new instance of the class. + /// This should only be called if there is no correlation vector in the message header. + /// + public CorrelationVectorV3() + : this(GetUniqueValue(), 0, false) + { + } + + /// + /// Initializes a new instance of the class. + /// This should only be called if there is no correlation vector in the message header. + /// + public CorrelationVectorV3(Guid vectorBase) + : this(vectorBase.GetBaseFromGuid(BaseLength), 0, false) + { + } + + private static string GetUniqueValue() + { + return Guid.NewGuid().GetBaseFromGuid(BaseLength); + } + + private CorrelationVectorV3(string baseVector, int extension, bool immutable, bool appendVersion = true) + { + // first append the "A." unless it is not required to + string baseVectorWithVersion; + if (appendVersion) + { + baseVectorWithVersion = String.Concat(VersionChar, StandardDelim, baseVector); + } + else + { + baseVectorWithVersion = baseVector; + } + this.BaseVector = baseVectorWithVersion; + this.Version = CorrelationVectorVersion.V3; + this.extension = extension; + // this.immutable = immutable || CorrelationVector.IsOversized(baseVector, extension, version); + } + + /// + /// Creates a new correlation vector by extending an existing value. This should be + /// done at the entry point of an operation. + /// + /// + /// Taken from the message header indicated by . + /// + /// A new correlation vector extended from the current vector. + public new static CorrelationVectorV3 Extend(string correlationVector) + { + /* + if (CorrelationVectorV3.IsImmutable(correlationVector)) + { + return CorrelationVectorV3.Parse(correlationVector); + } + + if (CorrelationVectorV3.ValidateCorrelationVectorDuringCreation) + { + CorrelationVectorV3.Validate(correlationVector); + } + + if (CorrelationVectorV3.IsOversized(correlationVector, 0)) + { + } + */ + return new CorrelationVectorV3(correlationVector, 0, false, false); + } + + /// + /// Creates a new correlation vector by parsing its string representation. + /// + /// correlationVector. + /// Important: Make sure to include the "A." at the beginning! + /// CorrelationVector + public new static CorrelationVectorV3 Parse(string correlationVector) + { + if (!string.IsNullOrEmpty(correlationVector)) + { + int p = correlationVector.LastIndexOf('.'); + // bool oversized = CorrelationVectorV3.IsOversized(correlationVector, 0); + if (p > 0) + { + string extensionValue = correlationVector.Substring(p + 1); + int extension; + if (int.TryParse(extensionValue, NumberStyles.HexNumber, CultureInfo.InvariantCulture, out extension) && extension >= 0) + { + return new CorrelationVectorV3(correlationVector.Substring(0, p), extension, false, false); + } + } + } + + return new CorrelationVectorV3(); + } + + /// + /// Creates a new correlation vector with a W3C traceparent. + /// + /// + /// CorrelationVector + public static CorrelationVectorV3 Span(string traceparent) + { + return new CorrelationVectorV3(traceparent, 0, false, true); + } + + /// + /// Creates a new correlation vector by applying the Spin operator to an existing value. + /// This should be done at the entry point of an operation. + /// + /// + /// Taken from the message header indicated by . + /// + /// A new correlation vector extended from the current vector. + public new static CorrelationVectorV3 Spin(string correlationVector) + { + SpinParameters defaultParameters = new SpinParameters + { + Interval = SpinCounterInterval.Coarse, + Periodicity = SpinCounterPeriodicity.Short, + Entropy = SpinEntropy.Two + }; + + return CorrelationVectorV3.Spin(correlationVector, defaultParameters); + } + + /// + /// Creates a new correlation vector by applying the Spin operator to an existing value. + /// This should be done at the entry point of an operation. + /// + /// + /// Taken from the message header indicated by . + /// + /// + /// The parameters to use when applying the Spin operator. + /// + /// A new correlation vector extended from the current vector. + public new static CorrelationVectorV3 Spin(string correlationVector, SpinParameters parameters) + { + if (CorrelationVectorV3.ValidateCorrelationVectorDuringCreation) + { + // CorrelationVectorV3.Validate(correlationVector); + } + + byte[] entropy = new byte[parameters.EntropyBytes]; + rng.NextBytes(entropy); + + ulong value = (ulong)(DateTime.UtcNow.Ticks >> parameters.TicksBitsToDrop); + for (int i = 0; i < parameters.EntropyBytes; i++) + { + value = (value << 32) | Convert.ToUInt64(entropy[i]); + } + + // Generate a bitmask and mask the lower TotalBits in the value. + // The mask is generated by (1 << TotalBits) - 1. We need to handle the edge case + // when shifting 64 bits, as it wraps around. + value &= (parameters.TotalBits == 64 ? 0 : (ulong)1 << parameters.TotalBits) - 1; + + string s = unchecked((uint)value).ToString("X8"); + if (parameters.TotalBits > 32) + { + s = string.Concat((value >> 32).ToString("X8"), s); + } + + string baseVector = string.Concat(correlationVector, SpinDelim, s); + if (CorrelationVectorV3.IsOversized(baseVector, 0)) + { + // Reset operation here + return CorrelationVectorV3.Parse(correlationVector); + } + + return new CorrelationVectorV3(baseVector, 0, false, false); + } + + public override Tuple Reset() + { + // First identify what the oversized value is for the cV. + string oversizedValue = "adfkljdaslkfjadlksjfladsfjasdlfjasdljfdal"; + // Then get a sort/entropy value + string resetValue = GetTickValue().ToString("X16"); + + string newVector = String.Concat(VersionChar, StandardDelim, this.Base, ResetDelim, resetValue); + + return new Tuple(newVector, oversizedValue); + } + + private static ulong GetTickValue() + { + SpinParameters parameters = new SpinParameters + { + Interval = SpinCounterInterval.Coarse, + Periodicity = SpinCounterPeriodicity.Short, + Entropy = SpinEntropy.Four + }; + byte[] entropy = new byte[parameters.EntropyBytes]; + rng.NextBytes(entropy); + + ulong value = (ulong)(DateTime.UtcNow.Ticks >> parameters.TicksBitsToDrop); + for (int i = 0; i < parameters.EntropyBytes; i++) + { + value = (value << 32) | Convert.ToUInt64(entropy[i]); + } + + // Generate a bitmask and mask the lower TotalBits in the value. + // The mask is generated by (1 << TotalBits) - 1. We need to handle the edge case + // when shifting 64 bits, as it wraps around. + value &= (parameters.TotalBits == 64 ? 0 : (ulong)1 << parameters.TotalBits) - 1; + + return value; + } + + public override string Increment() + { + /* + if (this.immutable) + { + return this.Value; + } + */ + int snapshot = 0; + int next = 0; + do + { + snapshot = this.extension; + if (snapshot == int.MaxValue) // 7FFFFFFF + { + return this.Value; + } + next = snapshot + 1; + if (CorrelationVectorV3.IsOversized(this.BaseVector, next)) + { + // Reset this stuff + return this.Value; + } + } + while (snapshot != Interlocked.CompareExchange(ref this.extension, next, snapshot)); + return string.Concat(this.BaseVector, ".", next); + } + + private static bool IsOversized(string baseVector, int extension) + { + if (!string.IsNullOrEmpty(baseVector)) + { + int size = baseVector.Length + 1 + + (extension > 0 ? (int)Math.Log(extension, 16) : 0) + 1; + return size > MaxVectorLength; + } + return false; + } + } +} From f6f787a9817db0bd64f2c25e2aae42286ccaa1ec Mon Sep 17 00:00:00 2001 From: Eddy Luo Date: Thu, 13 Jun 2019 17:35:11 -0700 Subject: [PATCH 2/4] Added basic implementation of cV v3 Reset --- .../CorrelationVectorTests.cs | 104 +++++++++++++++--- .../CorrelationVector.cs | 4 +- .../CorrelationVectorV3.cs | 61 +++++----- 3 files changed, 115 insertions(+), 54 deletions(-) diff --git a/src/Microsoft.CorrelationVector.UnitTests/CorrelationVectorTests.cs b/src/Microsoft.CorrelationVector.UnitTests/CorrelationVectorTests.cs index cfa63b9..e64ef34 100644 --- a/src/Microsoft.CorrelationVector.UnitTests/CorrelationVectorTests.cs +++ b/src/Microsoft.CorrelationVector.UnitTests/CorrelationVectorTests.cs @@ -31,6 +31,18 @@ public void CreateV2CorrelationVectorTest() Assert.AreEqual(2, splitVector.Length, "Correlation Vector should be created with two components separated by a '.'"); Assert.AreEqual(22, splitVector[0].Length, "Correlation Vector base should be 22 characters long"); Assert.AreEqual("0", splitVector[1], "Correlation Vector extension should start with zero"); + } + + [TestMethod] + public void CreateV3CorrelationVectorTest() + { + var correlationVector = new CorrelationVectorV3(); + var splitVector = correlationVector.Value.Split('.'); + + Assert.AreEqual(3, splitVector.Length, "Correlation Vector should be created with three components separated by a '.'"); + Assert.AreEqual("A", splitVector[0], "Correlation Vector v3 should start with \"A\"."); + Assert.AreEqual(22, splitVector[1].Length, "Correlation Vector base should be 22 characters long"); + Assert.AreEqual("0", splitVector[2], "Correlation Vector extension should start with zero"); } [TestMethod] @@ -55,6 +67,19 @@ public void CreateCorrelationVectorFromGuidTestV2() Assert.AreEqual(2, splitVector.Length, "Correlation Vector should be created with two components separated by a '.'"); Assert.AreEqual(22, splitVector[0].Length, "Correlation Vector base should be 22 characters long"); Assert.AreEqual("0", splitVector[1], "Correlation Vector extension should start with zero"); + } + + [TestMethod] + public void CreateCorrelationVectorFromGuidTestV3() + { + var guid = System.Guid.NewGuid(); + var correlationVector = new CorrelationVectorV3(guid); + var splitVector = correlationVector.Value.Split('.'); + + Assert.AreEqual(3, splitVector.Length, "Correlation Vector should be created with three components separated by a '.'"); + Assert.AreEqual("A", splitVector[0], "Correlation Vector v3 should start with \"A\"."); + Assert.AreEqual(22, splitVector[1].Length, "Correlation Vector base should be 22 characters long"); + Assert.AreEqual("0", splitVector[2], "Correlation Vector extension should start with zero"); } [TestMethod] @@ -73,6 +98,16 @@ public void GetBaseAsGuidV2Test() var correlationVector = new CorrelationVectorV2(guid); Guid baseAsGuid = correlationVector.GetBaseAsGuid(); + Assert.AreEqual(guid, baseAsGuid, "Correlation Vector base as a guid should be the same as the initial guid"); + } + + [TestMethod] + public void GetBaseAsGuidV3Test() + { + var guid = System.Guid.NewGuid(); + var correlationVector = new CorrelationVectorV3(guid); + Guid baseAsGuid = correlationVector.GetBaseAsGuid(); + Assert.AreEqual(guid, baseAsGuid, "Correlation Vector base as a guid should be the same as the initial guid"); } @@ -167,20 +202,7 @@ public void ParseCorrelationVectorV3Test() Assert.AreEqual("Y58xO9ov0kmpPvkiuzMUVA", correlationVector.Base, "Correlation Vector base was not parsed properly"); Assert.AreEqual("3", splitVector[2], "Correlation Vector extension was not parsed properly"); Assert.AreEqual("4", splitVector[3], "Correlation Vector extension was not parsed properly"); - Assert.AreEqual(10, correlationVector.Extension, "Correlation Vector extension was not parsed properly"); - } - - [TestMethod] - public void ParseCorrelationVectorV3Test2() - { - var correlationVector = CorrelationVector.Parse("A.Y58xO9ov0kmpPvkiuzMUVA"); - var splitVector = correlationVector.Value.Split('.'); - - Assert.AreEqual(CorrelationVectorVersion.V3, correlationVector.Version, "Correlation Vector version should be V3"); - Assert.AreEqual("Y58xO9ov0kmpPvkiuzMUVA", correlationVector.Base, "Correlation Vector base was not parsed properly"); - Assert.AreEqual("3", splitVector[2], "Correlation Vector extension was not parsed properly"); - Assert.AreEqual("4", splitVector[3], "Correlation Vector extension was not parsed properly"); - Assert.AreEqual(10, correlationVector.Extension, "Correlation Vector extension was not parsed properly"); + Assert.AreEqual(0xA, correlationVector.Extension, "Correlation Vector extension was not parsed properly"); } [TestMethod] @@ -282,6 +304,15 @@ public void ThrowWithTooBigCorrelationVectorValueV2() /* Bigger than 127 chars */ var vector = CorrelationVector.Extend("KZY+dsX2jEaZesgCPjJ2Ng.2147483647.2147483647.2147483647.2147483647.2147483647.2147483647.2147483647.2147483647.2147483647.2147483647"); }); + } + + [TestMethod] + public void ResetWithTooBigCorrelationVectorValueV3() + { + CorrelationVector.ValidateCorrelationVectorDuringCreation = true; + /* Bigger than 127 chars */ + var vector = CorrelationVector.Extend("A.KZY+dsX2jEaZesgCPjJ2Ng.7FFFFFFF.7FFFFFFF.7FFFFFFF.7FFFFFFF.7FFFFFFF.7FFFFFFF.7FFFFFFF.7FFFFFFF.7FFFFFFF.7FFFFFFF.7FFFFFFF.7FFFFFFF"); + Assert.IsTrue(vector.Value.Contains("#"), "Reset vector must contain reset indicator"); } [TestMethod] @@ -375,13 +406,14 @@ public void SpinSortValidationV3() Periodicity = SpinCounterPeriodicity.Long }; - uint lastSpinValue = 0; + ulong lastSpinValue = 0; var wrappedCounter = 0; for (int i = 0; i < 100; i++) { // The cV after a Spin will look like .0_.0, so the spinValue is at index = 2. CorrelationVector newVector = CorrelationVectorV3.Spin(vector.Value, spinParameters); - var spinValue = uint.Parse(newVector.Value.Split('.', '_')[2], System.Globalization.NumberStyles.HexNumber, CultureInfo.InvariantCulture); + string hexValue = newVector.Value.Split('.', '_')[3]; + var spinValue = ulong.Parse(hexValue, NumberStyles.HexNumber, CultureInfo.InvariantCulture); // Count the number of times the counter wraps. if (spinValue <= lastSpinValue) @@ -406,8 +438,8 @@ public void TestResetV3() const string baseVector = "A.KZY+dsX2jEaZesgCPjJ2Ng.7FFFFFFF.7FFFFFFF.7FFFFFFF.7FFFFFFF.7FFFFFFF.7FFFFFFF.7FFFFFFF.7FFFFFFF.7FFFFFFF.7FFFFFFF.7FFFFFFF.FFF"; // we hit 127 chars limit, will reset vector - var vector = CorrelationVector.Parse(baseVector); - Assert.AreEqual(string.Concat(baseVector, CorrelationVectorV2.TerminationSign), vector.Value); + Tuple resetValues = CorrelationVector.Parse(baseVector).Reset(); + Assert.AreEqual(baseVector, resetValues.Item2, "The stored vector is different from the base vector."); } [TestMethod] @@ -421,6 +453,31 @@ public void SpinPastMaxWithTerminationSignV2() Assert.AreEqual(string.Concat(baseVector, CorrelationVectorV2.TerminationSign), vector.Value); } + [TestMethod] + public void SpinPastMaxWithResetV3() + { + CorrelationVector.ValidateCorrelationVectorDuringCreation = false; + const string baseVector = "A.KZY+dsX2jEaZesgCPjJ2Ng.7FFFFFFF.7FFFFFFF.7FFFFFFF.7FFFFFFF.7FFFFFFF.7FFFFFFF.7FFFFFFF.7FFFFFFF.7FFFFFFF.7FFFFFFF.7FFFFFFF"; + + // we hit 127 chars limit, will reset vector and show value + var vector = CorrelationVector.Spin(baseVector); + Assert.IsTrue(vector.Value.Contains("#"), "Reset vector must contain reset indicator"); + } + + [TestMethod] + public void IncrementPastMaxWithResetV3() + { + CorrelationVector.ValidateCorrelationVectorDuringCreation = false; + const string baseVector = "A.PmvzQKgYek6Sdk/T5sWaqw.1.FA.A1.23_B6A5E62FC38E9974.1_B6A6A13E588CF82F.2A.AB.213_B6A92D24A00C0F9B.47.8B.12.34.A123.2B.23.41.FF"; + + var vectorToIncrement = CorrelationVector.Parse(baseVector); + // we hit 127 chars limit, will reset vector and show value + var incrementedString = vectorToIncrement.Increment(); + var newVector = CorrelationVector.Parse(incrementedString); + Assert.IsTrue(incrementedString.Contains("#"), "Reset vector must contain reset indicator"); + Assert.AreEqual(0x100, newVector.Extension, "Vector with extension FF should increment to 100"); + } + [TestMethod] public void ExtendPastMaxWithTerminationSign() { @@ -443,6 +500,17 @@ public void ExtendPastMaxWithTerminationSignV2() Assert.AreEqual(string.Concat(baseVector, CorrelationVectorV2.TerminationSign), vector.Value); } + [TestMethod] + public void ExtendPastMaxWithResetV3() + { + CorrelationVector.ValidateCorrelationVectorDuringCreation = false; + const string baseVector = "A.KZY+dsX2jEaZesgCPjJ2Ng.7FFFFFFF.7FFFFFFF.7FFFFFFF.7FFFFFFF.7FFFFFFF.7FFFFFFF.7FFFFFFF.7FFFFFFF.7FFFFFFF.7FFFFFFF.7FFFFFFF.FFF"; + + // we hit 127 chars limit, will append "!" to vector + var vector = CorrelationVector.Extend(baseVector); + Assert.IsTrue(vector.Value.Contains("#"), "Reset vector must contain reset indicator"); + } + [TestMethod] public void ImmutableWithTerminationSign() { diff --git a/src/Microsoft.CorrelationVector/CorrelationVector.cs b/src/Microsoft.CorrelationVector/CorrelationVector.cs index 675518f..6d97817 100644 --- a/src/Microsoft.CorrelationVector/CorrelationVector.cs +++ b/src/Microsoft.CorrelationVector/CorrelationVector.cs @@ -109,7 +109,7 @@ public static CorrelationVector Parse(string correlationVector) public static CorrelationVector Extend(string correlationVector) { CorrelationVectorVersion version = InferVersion(correlationVector); - return RunStaticMethod(correlationVector, version, CorrelationVectorV1.Extend, CorrelationVectorV2.Extend); + return RunStaticMethod(correlationVector, version, CorrelationVectorV1.Extend, CorrelationVectorV2.Extend, CorrelationVectorV3.Extend); } public static CorrelationVector Spin(string correlationVector) @@ -132,6 +132,8 @@ public static CorrelationVector Spin(string correlationVector, SpinParameters pa return CorrelationVectorV1.Spin(correlationVector, parameters); case CorrelationVectorVersion.V2: return CorrelationVectorV2.Spin(correlationVector, parameters); + case CorrelationVectorVersion.V3: + return CorrelationVectorV3.Spin(correlationVector, parameters); default: return null; } diff --git a/src/Microsoft.CorrelationVector/CorrelationVectorV3.cs b/src/Microsoft.CorrelationVector/CorrelationVectorV3.cs index bc751b7..fb855e5 100644 --- a/src/Microsoft.CorrelationVector/CorrelationVectorV3.cs +++ b/src/Microsoft.CorrelationVector/CorrelationVectorV3.cs @@ -134,21 +134,14 @@ private CorrelationVectorV3(string baseVector, int extension, bool immutable, bo /// A new correlation vector extended from the current vector. public new static CorrelationVectorV3 Extend(string correlationVector) { - /* - if (CorrelationVectorV3.IsImmutable(correlationVector)) - { - return CorrelationVectorV3.Parse(correlationVector); - } - if (CorrelationVectorV3.ValidateCorrelationVectorDuringCreation) { - CorrelationVectorV3.Validate(correlationVector); } if (CorrelationVectorV3.IsOversized(correlationVector, 0)) { + return CorrelationVectorV3.Parse(CorrelationVectorV3.Parse(correlationVector).Reset().Item1); } - */ return new CorrelationVectorV3(correlationVector, 0, false, false); } @@ -226,19 +219,7 @@ public static CorrelationVectorV3 Span(string traceparent) // CorrelationVectorV3.Validate(correlationVector); } - byte[] entropy = new byte[parameters.EntropyBytes]; - rng.NextBytes(entropy); - - ulong value = (ulong)(DateTime.UtcNow.Ticks >> parameters.TicksBitsToDrop); - for (int i = 0; i < parameters.EntropyBytes; i++) - { - value = (value << 32) | Convert.ToUInt64(entropy[i]); - } - - // Generate a bitmask and mask the lower TotalBits in the value. - // The mask is generated by (1 << TotalBits) - 1. We need to handle the edge case - // when shifting 64 bits, as it wraps around. - value &= (parameters.TotalBits == 64 ? 0 : (ulong)1 << parameters.TotalBits) - 1; + ulong value = GetTickValue(parameters); string s = unchecked((uint)value).ToString("X8"); if (parameters.TotalBits > 32) @@ -249,8 +230,10 @@ public static CorrelationVectorV3 Span(string traceparent) string baseVector = string.Concat(correlationVector, SpinDelim, s); if (CorrelationVectorV3.IsOversized(baseVector, 0)) { - // Reset operation here - return CorrelationVectorV3.Parse(correlationVector); + string valueToResetFrom = string.Concat(baseVector, ".", 0); + var oversizedVector = Parse(valueToResetFrom); + Tuple resetValues = oversizedVector.Reset(); + return CorrelationVectorV3.Parse(resetValues.Item1); } return new CorrelationVectorV3(baseVector, 0, false, false); @@ -258,31 +241,37 @@ public static CorrelationVectorV3 Span(string traceparent) public override Tuple Reset() { - // First identify what the oversized value is for the cV. - string oversizedValue = "adfkljdaslkfjadlksjfladsfjasdlfjasdljfdal"; - // Then get a sort/entropy value - string resetValue = GetTickValue().ToString("X16"); - - string newVector = String.Concat(VersionChar, StandardDelim, this.Base, ResetDelim, resetValue); - - return new Tuple(newVector, oversizedValue); + return Reset(this.Value); } - private static ulong GetTickValue() + public Tuple Reset(string oversizedValue) { SpinParameters parameters = new SpinParameters { Interval = SpinCounterInterval.Coarse, - Periodicity = SpinCounterPeriodicity.Short, + Periodicity = SpinCounterPeriodicity.Long, Entropy = SpinEntropy.Four }; + + string newExtension = oversizedValue.Substring(oversizedValue.LastIndexOf('.')+1); + + // Then get a sort/entropy value + string resetValue = GetTickValue(parameters).ToString("X16"); + + string newVector = String.Concat(VersionChar, StandardDelim, this.Base, ResetDelim, resetValue, StandardDelim, newExtension); + + return new Tuple(newVector, oversizedValue); + } + + private static ulong GetTickValue(SpinParameters parameters) + { byte[] entropy = new byte[parameters.EntropyBytes]; rng.NextBytes(entropy); ulong value = (ulong)(DateTime.UtcNow.Ticks >> parameters.TicksBitsToDrop); for (int i = 0; i < parameters.EntropyBytes; i++) { - value = (value << 32) | Convert.ToUInt64(entropy[i]); + value = (value << 8) | Convert.ToUInt64(entropy[i]); } // Generate a bitmask and mask the lower TotalBits in the value. @@ -313,8 +302,10 @@ public override string Increment() next = snapshot + 1; if (CorrelationVectorV3.IsOversized(this.BaseVector, next)) { + string valueToResetFrom = string.Concat(this.BaseVector, ".", next.ToString("X")); + Tuple resetValues = Reset(valueToResetFrom); // Reset this stuff - return this.Value; + return resetValues.Item1; } } while (snapshot != Interlocked.CompareExchange(ref this.extension, next, snapshot)); From ea0b5c9697a5e6e26be62137f12e987a8ba62806 Mon Sep 17 00:00:00 2001 From: Eddy Luo Date: Fri, 14 Jun 2019 15:42:50 -0700 Subject: [PATCH 3/4] Added trace parent to cV conversion and vector tests --- .../CorrelationVectorTests.cs | 20 +++++++++++++++++++ .../CorrelationVectorV3.cs | 20 ++++++++++++++++++- 2 files changed, 39 insertions(+), 1 deletion(-) diff --git a/src/Microsoft.CorrelationVector.UnitTests/CorrelationVectorTests.cs b/src/Microsoft.CorrelationVector.UnitTests/CorrelationVectorTests.cs index e64ef34..319ed98 100644 --- a/src/Microsoft.CorrelationVector.UnitTests/CorrelationVectorTests.cs +++ b/src/Microsoft.CorrelationVector.UnitTests/CorrelationVectorTests.cs @@ -439,6 +439,7 @@ public void TestResetV3() // we hit 127 chars limit, will reset vector Tuple resetValues = CorrelationVector.Parse(baseVector).Reset(); + Assert.IsTrue(resetValues.Item1.Contains("#"), "Reset vector must contain reset indicator"); Assert.AreEqual(baseVector, resetValues.Item2, "The stored vector is different from the base vector."); } @@ -511,6 +512,25 @@ public void ExtendPastMaxWithResetV3() Assert.IsTrue(vector.Value.Contains("#"), "Reset vector must contain reset indicator"); } + [TestMethod] + public void ConvertTraceparentV3() + { + const string traceparent = "00-0af7651916cd43dd8448eb211c80319c-b9c7c989f97918e1-01"; + string[] traceSections = traceparent.Split('-'); + var vector = CorrelationVectorV3.Span(traceparent); + + var traceIDBytes = new byte[traceSections[1].Length / 2]; + for (var i = 0; i < traceIDBytes.Length; i++) + { + traceIDBytes[i] = Convert.ToByte(traceSections[1].Substring(i * 2, 2), 16); + } + string paddedBase = vector.Base.PadRight(24, '='); + byte[] cvBaseBytes = Convert.FromBase64String(paddedBase); + Assert.IsTrue(vector.Value.Contains("-"), "Span vector must contain span indicator"); + Assert.AreEqual(22, vector.Base.Length, "Correlation Vector base should be 22 characters long"); + CollectionAssert.AreEqual(traceIDBytes, cvBaseBytes, "Trace ID bytes and cV base bytes must be equal"); + } + [TestMethod] public void ImmutableWithTerminationSign() { diff --git a/src/Microsoft.CorrelationVector/CorrelationVectorV3.cs b/src/Microsoft.CorrelationVector/CorrelationVectorV3.cs index fb855e5..3c28057 100644 --- a/src/Microsoft.CorrelationVector/CorrelationVectorV3.cs +++ b/src/Microsoft.CorrelationVector/CorrelationVectorV3.cs @@ -178,7 +178,25 @@ private CorrelationVectorV3(string baseVector, int extension, bool immutable, bo /// CorrelationVector public static CorrelationVectorV3 Span(string traceparent) { - return new CorrelationVectorV3(traceparent, 0, false, true); + // Format: version_format-trace_id-parent_id-trace_flags + // We convert the trace_id into a cV base and append the parent_id to it. + string[] traceSections = traceparent.Split(SpanDelim); + var trace_id = traceSections[1]; + var parent_id = traceSections[2]; + var converted_base = ConvertTraceIdToCvBase(trace_id); + var converted_parent_id = parent_id.ToUpperInvariant(); + string newBaseVector = String.Concat(converted_base, SpanDelim, converted_parent_id); + return new CorrelationVectorV3(newBaseVector, 0, false, true); + } + + private static string ConvertTraceIdToCvBase(string trace_id) + { + var hexBytes = new byte[trace_id.Length / 2]; + for (var i = 0; i < hexBytes.Length; i++) + { + hexBytes[i] = Convert.ToByte(trace_id.Substring(i * 2, 2), 16); + } + return Convert.ToBase64String(hexBytes).Substring(0, BaseLength); } /// From 25c1b14ffa286773df6fbfc462fe424afa04a71b Mon Sep 17 00:00:00 2001 From: Eddy Luo Date: Mon, 17 Jun 2019 13:04:29 -0700 Subject: [PATCH 4/4] Added "oversized vector" values --- .../CorrelationVectorTests.cs | 3 +++ src/Microsoft.CorrelationVector/CorrelationVectorV3.cs | 7 +++++++ 2 files changed, 10 insertions(+) diff --git a/src/Microsoft.CorrelationVector.UnitTests/CorrelationVectorTests.cs b/src/Microsoft.CorrelationVector.UnitTests/CorrelationVectorTests.cs index 319ed98..a6a4e05 100644 --- a/src/Microsoft.CorrelationVector.UnitTests/CorrelationVectorTests.cs +++ b/src/Microsoft.CorrelationVector.UnitTests/CorrelationVectorTests.cs @@ -519,11 +519,14 @@ public void ConvertTraceparentV3() string[] traceSections = traceparent.Split('-'); var vector = CorrelationVectorV3.Span(traceparent); + // Convert trace ID to bytes var traceIDBytes = new byte[traceSections[1].Length / 2]; for (var i = 0; i < traceIDBytes.Length; i++) { traceIDBytes[i] = Convert.ToByte(traceSections[1].Substring(i * 2, 2), 16); } + + // Convert 64-bit representation of base to bytes string paddedBase = vector.Base.PadRight(24, '='); byte[] cvBaseBytes = Convert.FromBase64String(paddedBase); Assert.IsTrue(vector.Value.Contains("-"), "Span vector must contain span indicator"); diff --git a/src/Microsoft.CorrelationVector/CorrelationVectorV3.cs b/src/Microsoft.CorrelationVector/CorrelationVectorV3.cs index 3c28057..0c645b5 100644 --- a/src/Microsoft.CorrelationVector/CorrelationVectorV3.cs +++ b/src/Microsoft.CorrelationVector/CorrelationVectorV3.cs @@ -36,6 +36,11 @@ public sealed class CorrelationVectorV3 : CorrelationVector /// public const char SpinDelim = '_'; + /// + /// The vector stored in a Reset operation + /// + public string StoredVector = null; + private static Random rng = new Random(); /// @@ -278,6 +283,8 @@ public Tuple Reset(string oversizedValue) string newVector = String.Concat(VersionChar, StandardDelim, this.Base, ResetDelim, resetValue, StandardDelim, newExtension); + // Store oversized vector in StoredVector for later use + StoredVector = oversizedValue; return new Tuple(newVector, oversizedValue); }