diff --git a/DynamicKey/ARDynamicKey/README.md b/DynamicKey/ARDynamicKey/README.md
index 1c58883..4d84e37 100644
--- a/DynamicKey/ARDynamicKey/README.md
+++ b/DynamicKey/ARDynamicKey/README.md
@@ -24,6 +24,7 @@ Sample Code for generating AccessToken are available on the following platforms:
+ Node.js
+ Python
+ PHP
+ + C#
### **YOUR IMPLEMENTATIONS ARE VERY WELCOME.**
@@ -205,3 +206,46 @@ if __name__ == "__main__":
```
+
+### C#
+```c#
+using AnyRtcTools;
+using System;
+
+namespace Sample
+{
+ class Program
+ {
+ static void Main(string[] args)
+ {
+ var appID = "970CA35de60c44645bbae8a215061b33";
+ var appCertificate = "5CFd2fd1755d40ecb72977518be15d3b";
+ var channelName = "7d72365eb983485397e3e3f9d460bdda";
+ var userAccount = "2882341273";
+
+ var expireTimeInSeconds = 3600;
+ var currentTimestamp = DateTime.UtcNow.Subtract(
+ new DateTime(1970, 1, 1)).TotalSeconds;
+ var privilegeExpiredTs = (int)currentTimestamp
+ + expireTimeInSeconds;
+
+ var token = new AccessToken(appID,
+ appCertificate, channelName, userAccount);
+ token.AddPrivilege(AccessToken.kJoinChannel,
+ privilegeExpiredTs);
+ //token.AddPrivilege(AccessToken.kPublishVideoStream,
+ // privilegeExpiredTs);
+ //token.AddPrivilege(AccessToken.kPublishAudioStream,
+ // privilegeExpiredTs);
+ //token.AddPrivilege(AccessToken.kPublishDataStream,
+ // privilegeExpiredTs);
+
+ Console.WriteLine(token.Build());
+
+ Console.ReadLine();
+ }
+ }
+}
+
+```
+
diff --git a/DynamicKey/ARDynamicKey/csharp/AnyRtcTools.Tests/AccessTokenTest.cs b/DynamicKey/ARDynamicKey/csharp/AnyRtcTools.Tests/AccessTokenTest.cs
new file mode 100644
index 0000000..ba98a02
--- /dev/null
+++ b/DynamicKey/ARDynamicKey/csharp/AnyRtcTools.Tests/AccessTokenTest.cs
@@ -0,0 +1,43 @@
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+
+namespace AnyRtcTools.Tests
+{
+ [TestClass]
+ public class AccessTokenTest
+ {
+ private string appID = "970CA35de60c44645bbae8a215061b33";
+ private string appCertificate = "5CFd2fd1755d40ecb72977518be15d3b";
+ private string channelName = "7d72365eb983485397e3e3f9d460bdda";
+ private string uid = "2882341273";
+ private int expireTimestamp = 1446455471;
+ private int salt = 1;
+ private int ts = 1111111;
+
+ [TestMethod]
+ public void TestBuild()
+ {
+ var expected = "006970CA35de60c44645bbae8a215061b33IACV0fZUBw+72cVoL9eyGGh3Q6Poi8bgjwVLnyKSJyOXR7dIfRBXoFHlEAABAAAAR/QQAAEAAQCvKDdW";
+
+ var key = new AccessToken(appID, appCertificate, channelName, uid);
+ key._salt = salt;
+ key._ts = ts;
+ key.AddPrivilege(AccessToken.kJoinChannel, expireTimestamp);
+
+ var result = key.Build();
+
+ Assert.AreEqual(expected, result);
+
+ // test uid = 0
+ expected = "006970CA35de60c44645bbae8a215061b33IACw1o7htY6ISdNRtku3p9tjTPi0jCKf9t49UHJhzCmL6bdIfRAAAAAAEAABAAAAR/QQAAEAAQCvKDdW";
+
+ var uid_zero = "";
+ key = new AccessToken(appID, appCertificate, channelName, uid_zero);
+ key._salt = salt;
+ key._ts = ts;
+ key.AddPrivilege(AccessToken.kJoinChannel, expireTimestamp);
+
+ result = key.Build();
+ Assert.AreEqual(expected, result);
+ }
+ }
+}
diff --git a/DynamicKey/ARDynamicKey/csharp/AnyRtcTools.Tests/AnyRtcTools.Tests.csproj b/DynamicKey/ARDynamicKey/csharp/AnyRtcTools.Tests/AnyRtcTools.Tests.csproj
new file mode 100644
index 0000000..29045a1
--- /dev/null
+++ b/DynamicKey/ARDynamicKey/csharp/AnyRtcTools.Tests/AnyRtcTools.Tests.csproj
@@ -0,0 +1,19 @@
+
+
+
+ netcoreapp2.2
+
+ false
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/DynamicKey/ARDynamicKey/csharp/AnyRtcTools.sln b/DynamicKey/ARDynamicKey/csharp/AnyRtcTools.sln
new file mode 100644
index 0000000..10eaad4
--- /dev/null
+++ b/DynamicKey/ARDynamicKey/csharp/AnyRtcTools.sln
@@ -0,0 +1,93 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio 15
+VisualStudioVersion = 15.0.28307.1340
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AnyRtcTools", "AnyRtcTools\AnyRtcTools.csproj", "{EB61A8BA-9739-44F0-A9AD-8A44E052922F}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Sample", "Sample\Sample.csproj", "{D79D4486-176D-4B73-9305-8F350A75C844}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AnyRtcTools.Tests", "AnyRtcTools.Tests\AnyRtcTools.Tests.csproj", "{931463CF-3D94-46F1-AE27-AB28369B1376}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|Any CPU = Debug|Any CPU
+ Debug|ARM = Debug|ARM
+ Debug|ARM64 = Debug|ARM64
+ Debug|x64 = Debug|x64
+ Debug|x86 = Debug|x86
+ Release|Any CPU = Release|Any CPU
+ Release|ARM = Release|ARM
+ Release|ARM64 = Release|ARM64
+ Release|x64 = Release|x64
+ Release|x86 = Release|x86
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {EB61A8BA-9739-44F0-A9AD-8A44E052922F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {EB61A8BA-9739-44F0-A9AD-8A44E052922F}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {EB61A8BA-9739-44F0-A9AD-8A44E052922F}.Debug|ARM.ActiveCfg = Debug|Any CPU
+ {EB61A8BA-9739-44F0-A9AD-8A44E052922F}.Debug|ARM.Build.0 = Debug|Any CPU
+ {EB61A8BA-9739-44F0-A9AD-8A44E052922F}.Debug|ARM64.ActiveCfg = Debug|Any CPU
+ {EB61A8BA-9739-44F0-A9AD-8A44E052922F}.Debug|ARM64.Build.0 = Debug|Any CPU
+ {EB61A8BA-9739-44F0-A9AD-8A44E052922F}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {EB61A8BA-9739-44F0-A9AD-8A44E052922F}.Debug|x64.Build.0 = Debug|Any CPU
+ {EB61A8BA-9739-44F0-A9AD-8A44E052922F}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {EB61A8BA-9739-44F0-A9AD-8A44E052922F}.Debug|x86.Build.0 = Debug|Any CPU
+ {EB61A8BA-9739-44F0-A9AD-8A44E052922F}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {EB61A8BA-9739-44F0-A9AD-8A44E052922F}.Release|Any CPU.Build.0 = Release|Any CPU
+ {EB61A8BA-9739-44F0-A9AD-8A44E052922F}.Release|ARM.ActiveCfg = Release|Any CPU
+ {EB61A8BA-9739-44F0-A9AD-8A44E052922F}.Release|ARM.Build.0 = Release|Any CPU
+ {EB61A8BA-9739-44F0-A9AD-8A44E052922F}.Release|ARM64.ActiveCfg = Release|Any CPU
+ {EB61A8BA-9739-44F0-A9AD-8A44E052922F}.Release|ARM64.Build.0 = Release|Any CPU
+ {EB61A8BA-9739-44F0-A9AD-8A44E052922F}.Release|x64.ActiveCfg = Release|Any CPU
+ {EB61A8BA-9739-44F0-A9AD-8A44E052922F}.Release|x64.Build.0 = Release|Any CPU
+ {EB61A8BA-9739-44F0-A9AD-8A44E052922F}.Release|x86.ActiveCfg = Release|Any CPU
+ {EB61A8BA-9739-44F0-A9AD-8A44E052922F}.Release|x86.Build.0 = Release|Any CPU
+ {D79D4486-176D-4B73-9305-8F350A75C844}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {D79D4486-176D-4B73-9305-8F350A75C844}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {D79D4486-176D-4B73-9305-8F350A75C844}.Debug|ARM.ActiveCfg = Debug|Any CPU
+ {D79D4486-176D-4B73-9305-8F350A75C844}.Debug|ARM.Build.0 = Debug|Any CPU
+ {D79D4486-176D-4B73-9305-8F350A75C844}.Debug|ARM64.ActiveCfg = Debug|Any CPU
+ {D79D4486-176D-4B73-9305-8F350A75C844}.Debug|ARM64.Build.0 = Debug|Any CPU
+ {D79D4486-176D-4B73-9305-8F350A75C844}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {D79D4486-176D-4B73-9305-8F350A75C844}.Debug|x64.Build.0 = Debug|Any CPU
+ {D79D4486-176D-4B73-9305-8F350A75C844}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {D79D4486-176D-4B73-9305-8F350A75C844}.Debug|x86.Build.0 = Debug|Any CPU
+ {D79D4486-176D-4B73-9305-8F350A75C844}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {D79D4486-176D-4B73-9305-8F350A75C844}.Release|Any CPU.Build.0 = Release|Any CPU
+ {D79D4486-176D-4B73-9305-8F350A75C844}.Release|ARM.ActiveCfg = Release|Any CPU
+ {D79D4486-176D-4B73-9305-8F350A75C844}.Release|ARM.Build.0 = Release|Any CPU
+ {D79D4486-176D-4B73-9305-8F350A75C844}.Release|ARM64.ActiveCfg = Release|Any CPU
+ {D79D4486-176D-4B73-9305-8F350A75C844}.Release|ARM64.Build.0 = Release|Any CPU
+ {D79D4486-176D-4B73-9305-8F350A75C844}.Release|x64.ActiveCfg = Release|Any CPU
+ {D79D4486-176D-4B73-9305-8F350A75C844}.Release|x64.Build.0 = Release|Any CPU
+ {D79D4486-176D-4B73-9305-8F350A75C844}.Release|x86.ActiveCfg = Release|Any CPU
+ {D79D4486-176D-4B73-9305-8F350A75C844}.Release|x86.Build.0 = Release|Any CPU
+ {931463CF-3D94-46F1-AE27-AB28369B1376}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {931463CF-3D94-46F1-AE27-AB28369B1376}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {931463CF-3D94-46F1-AE27-AB28369B1376}.Debug|ARM.ActiveCfg = Debug|Any CPU
+ {931463CF-3D94-46F1-AE27-AB28369B1376}.Debug|ARM.Build.0 = Debug|Any CPU
+ {931463CF-3D94-46F1-AE27-AB28369B1376}.Debug|ARM64.ActiveCfg = Debug|Any CPU
+ {931463CF-3D94-46F1-AE27-AB28369B1376}.Debug|ARM64.Build.0 = Debug|Any CPU
+ {931463CF-3D94-46F1-AE27-AB28369B1376}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {931463CF-3D94-46F1-AE27-AB28369B1376}.Debug|x64.Build.0 = Debug|Any CPU
+ {931463CF-3D94-46F1-AE27-AB28369B1376}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {931463CF-3D94-46F1-AE27-AB28369B1376}.Debug|x86.Build.0 = Debug|Any CPU
+ {931463CF-3D94-46F1-AE27-AB28369B1376}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {931463CF-3D94-46F1-AE27-AB28369B1376}.Release|Any CPU.Build.0 = Release|Any CPU
+ {931463CF-3D94-46F1-AE27-AB28369B1376}.Release|ARM.ActiveCfg = Release|Any CPU
+ {931463CF-3D94-46F1-AE27-AB28369B1376}.Release|ARM.Build.0 = Release|Any CPU
+ {931463CF-3D94-46F1-AE27-AB28369B1376}.Release|ARM64.ActiveCfg = Release|Any CPU
+ {931463CF-3D94-46F1-AE27-AB28369B1376}.Release|ARM64.Build.0 = Release|Any CPU
+ {931463CF-3D94-46F1-AE27-AB28369B1376}.Release|x64.ActiveCfg = Release|Any CPU
+ {931463CF-3D94-46F1-AE27-AB28369B1376}.Release|x64.Build.0 = Release|Any CPU
+ {931463CF-3D94-46F1-AE27-AB28369B1376}.Release|x86.ActiveCfg = Release|Any CPU
+ {931463CF-3D94-46F1-AE27-AB28369B1376}.Release|x86.Build.0 = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+ GlobalSection(ExtensibilityGlobals) = postSolution
+ SolutionGuid = {2C01FC33-D2A5-4D79-923B-874A5EE99420}
+ EndGlobalSection
+EndGlobal
diff --git a/DynamicKey/ARDynamicKey/csharp/AnyRtcTools/AccessToken.cs b/DynamicKey/ARDynamicKey/csharp/AnyRtcTools/AccessToken.cs
new file mode 100644
index 0000000..82d64cb
--- /dev/null
+++ b/DynamicKey/ARDynamicKey/csharp/AnyRtcTools/AccessToken.cs
@@ -0,0 +1,108 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Security.Cryptography;
+using System.Text;
+
+namespace AnyRtcTools
+{
+ public class AccessToken
+ {
+ public const ushort kJoinChannel = 1;
+ public const ushort kPublishAudioStream = 2;
+ public const ushort kPublishVideoStream = 3;
+ public const ushort kPublishDataStream = 4;
+ public const ushort kPublishAudiocdn = 5;
+ public const ushort kPublishVideoCdn = 6;
+ public const ushort kRequestPublishAudioStream = 7;
+ public const ushort kRequestPublishVideoStream = 8;
+ public const ushort kRequestPublishDataStream = 9;
+ public const ushort kInvitePublishAudioStream = 10;
+ public const ushort kInvitePublishVideoStream = 11;
+ public const ushort kInvitePublishDataStream = 12;
+ public const ushort kAdministrateChannel = 101;
+ public const ushort kRtmLogin = 1000;
+
+ public const int VERSION_LENGTH = 3;
+ public const int APP_ID_LENGTH = 32;
+
+ private readonly string _appID;
+ private readonly string _appCertificate;
+ private readonly string _channelName;
+ private readonly string _uid;
+ public int _ts;
+ public int _salt;
+ private SortedDictionary _messages;
+
+ public AccessToken(string appID, string appCertificate, string channelName, string uid)
+ {
+ _appID = appID;
+ _appCertificate = appCertificate;
+ _channelName = channelName;
+ _uid = uid;
+
+ _ts = (int)(DateTime.UtcNow.Subtract(new DateTime(1970, 1, 1)).TotalSeconds) + 24 * 3600;
+ var random = new Random();
+ _salt = random.Next(1, 99999999);
+ }
+
+ public void AddPrivilege(ushort privilege, int expireTimestamp)
+ {
+ if (_messages == null)
+ {
+ _messages = new SortedDictionary();
+ }
+
+ _messages.Add(privilege, expireTimestamp);
+ }
+
+ public string Build()
+ {
+ if (_messages == null)
+ {
+ throw new Exception("Please specify some privileges first.");
+ }
+
+ var m = BitConverter.GetBytes(_salt)
+ .Concat(BitConverter.GetBytes(_ts))
+ .Concat(PackMapUint32(_messages));
+
+ var val = Encoding.UTF8.GetBytes(_appID)
+ .Concat(Encoding.UTF8.GetBytes(_channelName))
+ .Concat(Encoding.UTF8.GetBytes(_uid))
+ .Concat(m).ToArray();
+
+ HMACSHA256 hmacSha256 = new HMACSHA256(Encoding.UTF8.GetBytes(_appCertificate));
+ var signature = hmacSha256.ComputeHash(val);
+
+ var crc32 = new CRC32Cls();
+ var crc_channel_name = crc32.GetCRC32Str(_channelName) & 0xffffffff;
+ var crc_uid = crc32.GetCRC32Str(_uid) & 0xffffffff;
+
+ var content = PackString(signature)
+ .Concat(BitConverter.GetBytes(crc_channel_name))
+ .Concat(BitConverter.GetBytes(crc_uid))
+ .Concat(PackString(m.ToArray())).ToArray();
+
+ return $"006{_appID}{Convert.ToBase64String(content)}";
+ }
+
+ private static byte[] PackString(byte[] strBytes)
+ {
+ return BitConverter.GetBytes((ushort)(strBytes.Length))
+ .Concat(strBytes).ToArray();
+ }
+
+ private static byte[] PackMapUint32(SortedDictionary ms)
+ {
+ var m = BitConverter.GetBytes((ushort)(ms.Count));
+ IEnumerable ret = m.ToList();
+ foreach (var kvp in ms)
+ {
+ ret = ret.Concat(BitConverter.GetBytes(kvp.Key))
+ .Concat(BitConverter.GetBytes(kvp.Value));
+ }
+ return ret.ToArray();
+ }
+ }
+}
diff --git a/DynamicKey/ARDynamicKey/csharp/AnyRtcTools/AnyRtcTools.csproj b/DynamicKey/ARDynamicKey/csharp/AnyRtcTools/AnyRtcTools.csproj
new file mode 100644
index 0000000..9f5c4f4
--- /dev/null
+++ b/DynamicKey/ARDynamicKey/csharp/AnyRtcTools/AnyRtcTools.csproj
@@ -0,0 +1,7 @@
+
+
+
+ netstandard2.0
+
+
+
diff --git a/DynamicKey/ARDynamicKey/csharp/AnyRtcTools/CRC32Cls.cs b/DynamicKey/ARDynamicKey/csharp/AnyRtcTools/CRC32Cls.cs
new file mode 100644
index 0000000..1a3a17a
--- /dev/null
+++ b/DynamicKey/ARDynamicKey/csharp/AnyRtcTools/CRC32Cls.cs
@@ -0,0 +1,49 @@
+using System.Text;
+
+namespace AnyRtcTools
+{
+ class CRC32Cls
+ {
+ /*
+ *
+ * author: uusystem
+ * blog url: https://www.cnblogs.com/tianjifa/p/9216985.html
+ *
+ */
+ protected uint[] Crc32Table;
+ //生成CRC32码表
+ public void GetCRC32Table()
+ {
+ uint Crc;
+ Crc32Table = new uint[256];
+ int i, j;
+ for (i = 0; i < 256; i++)
+ {
+ Crc = (uint)i;
+ for (j = 8; j > 0; j--)
+ {
+ if ((Crc & 1) == 1)
+ Crc = (Crc >> 1) ^ 0xEDB88320;
+ else
+ Crc >>= 1;
+ }
+ Crc32Table[i] = Crc;
+ }
+ }
+
+ //获取字符串的CRC32校验值
+ public uint GetCRC32Str(string sInputString)
+ {
+ //生成码表
+ GetCRC32Table();
+ byte[] buffer = Encoding.UTF8.GetBytes(sInputString);
+ uint value = 0xffffffff;
+ int len = buffer.Length;
+ for (int i = 0; i < len; i++)
+ {
+ value = (value >> 8) ^ Crc32Table[(value & 0xFF) ^ buffer[i]];
+ }
+ return value ^ 0xffffffff;
+ }
+ }
+}
diff --git a/DynamicKey/ARDynamicKey/csharp/Sample/Program.cs b/DynamicKey/ARDynamicKey/csharp/Sample/Program.cs
new file mode 100644
index 0000000..38f1463
--- /dev/null
+++ b/DynamicKey/ARDynamicKey/csharp/Sample/Program.cs
@@ -0,0 +1,30 @@
+using AnyRtcTools;
+using System;
+
+namespace Sample
+{
+ class Program
+ {
+ static void Main(string[] args)
+ {
+ var appID = "970CA35de60c44645bbae8a215061b33";
+ var appCertificate = "5CFd2fd1755d40ecb72977518be15d3b";
+ var channelName = "7d72365eb983485397e3e3f9d460bdda";
+ var userAccount = "2882341273";
+
+ var expireTimeInSeconds = 3600;
+ var currentTimestamp = DateTime.UtcNow.Subtract(new DateTime(1970, 1, 1)).TotalSeconds;
+ var privilegeExpiredTs = (int)currentTimestamp + expireTimeInSeconds;
+
+ var token = new AccessToken(appID, appCertificate, channelName, userAccount);
+ token.AddPrivilege(AccessToken.kJoinChannel, privilegeExpiredTs);
+ //token.AddPrivilege(AccessToken.kPublishVideoStream, privilegeExpiredTs);
+ //token.AddPrivilege(AccessToken.kPublishAudioStream, privilegeExpiredTs);
+ //token.AddPrivilege(AccessToken.kPublishDataStream, privilegeExpiredTs);
+
+ Console.WriteLine(token.Build());
+
+ Console.ReadLine();
+ }
+ }
+}
diff --git a/DynamicKey/ARDynamicKey/csharp/Sample/Sample.csproj b/DynamicKey/ARDynamicKey/csharp/Sample/Sample.csproj
new file mode 100644
index 0000000..0ff0d2b
--- /dev/null
+++ b/DynamicKey/ARDynamicKey/csharp/Sample/Sample.csproj
@@ -0,0 +1,12 @@
+
+
+
+ Exe
+ netcoreapp2.2
+
+
+
+
+
+
+