diff --git a/.pubnub.yml b/.pubnub.yml index 0ce28efd2..a95340763 100644 --- a/.pubnub.yml +++ b/.pubnub.yml @@ -1,8 +1,13 @@ name: c-sharp -version: "7.3.14" +version: "7.3.15" schema: 1 scm: github.com/pubnub/c-sharp changelog: + - date: 2025-07-03 + version: v7.3.15 + changes: + - type: feature + text: "Implemented an in-house CBOR solution for ParseToken() handling to reduce total SDK+dependencies size." - date: 2025-06-25 version: v7.3.14 changes: @@ -919,7 +924,7 @@ features: - QUERY-PARAM supported-platforms: - - version: Pubnub 'C#' 7.3.14 + version: Pubnub 'C#' 7.3.15 platforms: - Windows 10 and up - Windows Server 2008 and up @@ -930,7 +935,7 @@ supported-platforms: - .Net Framework 4.6.1+ - .Net Framework 6.0 - - version: PubnubPCL 'C#' 7.3.14 + version: PubnubPCL 'C#' 7.3.15 platforms: - Xamarin.Android - Xamarin.iOS @@ -950,7 +955,7 @@ supported-platforms: - .Net Core - .Net 6.0 - - version: PubnubUWP 'C#' 7.3.14 + version: PubnubUWP 'C#' 7.3.15 platforms: - Windows Phone 10 - Universal Windows Apps @@ -974,7 +979,7 @@ sdks: distribution-type: source distribution-repository: GitHub package-name: Pubnub - location: https://github.com/pubnub/c-sharp/releases/tag/v7.3.14.0 + location: https://github.com/pubnub/c-sharp/releases/tag/v7.3.15.0 requires: - name: ".Net" @@ -1257,7 +1262,7 @@ sdks: distribution-type: source distribution-repository: GitHub package-name: PubNubPCL - location: https://github.com/pubnub/c-sharp/releases/tag/v7.3.14.0 + location: https://github.com/pubnub/c-sharp/releases/tag/v7.3.15.0 requires: - name: ".Net Core" @@ -1616,7 +1621,7 @@ sdks: distribution-type: source distribution-repository: GitHub package-name: PubnubUWP - location: https://github.com/pubnub/c-sharp/releases/tag/v7.3.14.0 + location: https://github.com/pubnub/c-sharp/releases/tag/v7.3.15.0 requires: - name: "Universal Windows Platform Development" diff --git a/CHANGELOG b/CHANGELOG index 72c6128fd..64725daf1 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,3 +1,7 @@ +v7.3.15 - July 03 2025 +----------------------------- +- Added: implemented an in-house CBOR solution for ParseToken() handling to reduce total SDK+dependencies size. + v7.3.14 - June 25 2025 ----------------------------- - Modified: updated log type from Error to Warn for TaskCanceledException. diff --git a/src/Api/PubnubApi/CBOR.cs b/src/Api/PubnubApi/CBOR.cs new file mode 100644 index 000000000..075cf6efa --- /dev/null +++ b/src/Api/PubnubApi/CBOR.cs @@ -0,0 +1,509 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Text.RegularExpressions; + +namespace PubnubApi +{ + /// + /// A simple not-all-covering implementation of CBOR serialisation/deserialisation for internal Pubnub ParseToken() usage + /// Converted to C# from https://github.com/seba-aln/CBORCodec + /// + internal static class CBOR + { + private const byte TYPE_MASK = 0b11100000; + private const byte ADDITIONAL_MASK = 0b00011111; + + public const byte TYPE_UNSIGNED_INT = 0b00000000; + public const byte TYPE_NEGATIVE_INT = 0b00100000; + public const byte TYPE_BYTE_STRING = 0b01000000; + public const byte TYPE_TEXT_STRING = 0b01100000; + public const byte TYPE_ARRAY = 0b10000000; + public const byte TYPE_HASHMAP = 0b10100000; + public const byte TYPE_FLOAT = 0b11100000; + + private const byte ADDITIONAL_LENGTH_1B = 24; + private const byte ADDITIONAL_LENGTH_2B = 25; + private const byte ADDITIONAL_LENGTH_4B = 26; + private const byte ADDITIONAL_LENGTH_8B = 27; + + private const byte ADDITIONAL_TYPE_INDEFINITE = 31; + + private const byte INDEFINITE_BREAK = 0b11111111; + + private static readonly byte[] additionalLength = { + ADDITIONAL_LENGTH_1B, + ADDITIONAL_LENGTH_2B, + ADDITIONAL_LENGTH_4B, + ADDITIONAL_LENGTH_8B, + }; + + private static readonly Dictionary additionalLengthBytes = new Dictionary + { + { ADDITIONAL_LENGTH_1B, 1 }, + { ADDITIONAL_LENGTH_2B, 2 }, + { ADDITIONAL_LENGTH_4B, 4 }, + { ADDITIONAL_LENGTH_8B, 8 }, + }; + + private const string SIMPLE_VALUE_FALSE = "F4"; + private const string SIMPLE_VALUE_TRUE = "F5"; + private const string SIMPLE_VALUE_NULL = "F6"; + private const string SIMPLE_VALUE_UNDEF = "F7"; + + private static readonly Dictionary simpleValues = new Dictionary + { + { SIMPLE_VALUE_FALSE, false }, + { SIMPLE_VALUE_TRUE, true }, + { SIMPLE_VALUE_NULL, null }, + { SIMPLE_VALUE_UNDEF, null } + }; + + /// + /// Decode incoming hexadecimal string of data and outputing decoded values + /// + /// Hexadecimal string to decode + /// Decoded value + /// Thrown when input is invalid or unsupported type + public static object Decode(string value) + { + value = SanitizeInput(value); + var data = SplitIntoBytes(value); + return ParseData(ref data); + } + + private static object ParseData(ref List data) + { + if (data.Count == 0) + { + throw new Exception("Unexpected end of data"); + } + + var byteStr = data[0]; + data.RemoveAt(0); + + if (simpleValues.ContainsKey(byteStr)) + { + return simpleValues[byteStr]; + } + + var byteValue = Convert.ToByte(byteStr, 16); + var type = (byte)(byteValue & TYPE_MASK); + var additional = (byte)(byteValue & ADDITIONAL_MASK); + + switch (type) + { + case TYPE_NEGATIVE_INT: + case TYPE_UNSIGNED_INT: + long value; + if (additionalLength.Contains(additional)) + { + value = Convert.ToInt64(GetData(ref data, additionalLengthBytes[additional]), 16); + } + else + { + value = additional; + } + if (type == TYPE_NEGATIVE_INT) + { + value = -1 - value; + } + return value; + + case TYPE_FLOAT: + if (additional <= 23) + { + return additional; + } + else if (additional == ADDITIONAL_LENGTH_1B) + { + return GetData(ref data); + } + else + { + return DecodeFloat(GetData(ref data, additionalLengthBytes[additional]), additional); + } + + case TYPE_BYTE_STRING: + // For byte strings, return the raw bytes + byte[] resultBytes; + if (additionalLength.Contains(additional)) + { + var length = Convert.ToInt32(GetData(ref data, additionalLengthBytes[additional]), 16); + resultBytes = HexToBytes(GetData(ref data, length)); + } + else if (additional == ADDITIONAL_TYPE_INDEFINITE) + { + resultBytes = HexToBytes(GetIndefiniteData(ref data)); + } + else + { + resultBytes = HexToBytes(GetData(ref data, additional)); + } + return resultBytes; + + case TYPE_TEXT_STRING: + // For text strings, convert to UTF-8 string + string result; + if (additionalLength.Contains(additional)) + { + var length = Convert.ToInt32(GetData(ref data, additionalLengthBytes[additional]), 16); + result = HexToBinary(GetData(ref data, length)); + } + else if (additional == ADDITIONAL_TYPE_INDEFINITE) + { + result = HexToBinary(GetIndefiniteData(ref data)); + } + else + { + result = HexToBinary(GetData(ref data, additional)); + } + return result; + + case TYPE_ARRAY: + var arrayResult = new List(); + int arrayLength; + if (additionalLength.Contains(additional)) + { + arrayLength = Convert.ToInt32(GetData(ref data, additionalLengthBytes[additional]), 16); + } + else + { + arrayLength = additional; + } + + for (int i = 0; i < arrayLength; i++) + { + arrayResult.Add(ParseData(ref data)); + } + return arrayResult; + + case TYPE_HASHMAP: + var hashmapResult = new Dictionary(); + int hashmapLength; + if (additionalLength.Contains(additional)) + { + hashmapLength = Convert.ToInt32(GetData(ref data, additionalLengthBytes[additional]), 16); + } + else + { + hashmapLength = additional; + } + + for (int i = 0; i < hashmapLength; i++) + { + var key = ParseData(ref data); + var val = ParseData(ref data); + hashmapResult[key] = val; + } + return hashmapResult; + + default: + throw new Exception($"Unsupported Type {Convert.ToString(type, 2)}"); + } + } + + private static double DecodeFloat(string value, byte precision) + { + var bytes = Convert.ToUInt64(value, 16); + switch (precision) + { + case ADDITIONAL_LENGTH_2B: + var sign = (bytes & 0b1000000000000000) >> 15; + var exp = (bytes & 0b0111110000000000) >> 10; + var mant = bytes & 0b1111111111; + double result; + if (exp == 0) + { + result = Math.Pow(2, -14) * (mant / 1024.0); + } + else if (exp == 0b11111) + { + result = double.PositiveInfinity; + } + else + { + result = Math.Pow(2, exp - 15) * (1 + mant / 1024.0); + } + return (sign == 1 ? -1 : 1) * result; + + case ADDITIONAL_LENGTH_4B: + var sign32 = (bytes >> 31) == 1 ? -1 : 1; + var x = (bytes & ((1UL << 23) - 1)) + (1UL << 23) * ((bytes >> 31) | 1); + var exp32 = (int)((bytes >> 23) & 0xFF) - 127; + return x * Math.Pow(2, exp32 - 23) * sign32; + + case ADDITIONAL_LENGTH_8B: + var sign64 = (bytes >> 63) == 1 ? -1 : 1; + var exp64 = (bytes >> 52) & 0x7ff; + var mant64 = bytes & 0xfffffffffffff; + + double val; + if (exp64 == 0) + { + val = mant64 * Math.Pow(2, -(1022 + 52)); + } + else if (exp64 != 0b11111111111) + { + val = (mant64 + (1UL << 52)) * Math.Pow(2, exp64 - (1023 + 52)); + } + else + { + val = mant64 == 0 ? double.PositiveInfinity : double.NaN; + } + return sign64 * val; + + default: + throw new Exception($"Unsupported float precision: {precision}"); + } + } + + private static string GetData(ref List data, int bytes = 1) + { + var result = new StringBuilder(); + for (int i = 1; i <= bytes; i++) + { + if (data.Count == 0) + { + throw new Exception("Unexpected end of data"); + } + result.Append(data[0]); + data.RemoveAt(0); + } + return result.ToString(); + } + + private static string GetIndefiniteData(ref List data) + { + var result = new StringBuilder(); + do + { + if (data.Count == 0) + { + throw new Exception("Unexpected end of data"); + } + var byteStr = data[0]; + data.RemoveAt(0); + if (Convert.ToByte(byteStr, 16) == INDEFINITE_BREAK) + { + break; + } + result.Append(byteStr); + } while (data.Count > 0); + return result.ToString(); + } + + /// + /// Removes spaces, converts string to upper case and throws exception if input is not a valid hexadecimal string + /// + /// Input string to sanitize + /// Sanitized hexadecimal string + /// Thrown when input contains invalid characters + private static string SanitizeInput(string value) + { + value = value.Replace(" ", "").ToUpperInvariant(); + if (!Regex.IsMatch(value, "^[A-F0-9]*$")) + { + throw new Exception("Invalid Input"); + } + return value; + } + + /// + /// Sanitizes the output value so it contains even number of characters and returns it upper cased + /// + /// Hexadecimal value to sanitize + /// Should the length of output be in powers of two (2, 4, 8, 16) + /// Sanitized hexadecimal string + private static string SanitizeOutput(string value, bool useByteLength = false) + { + value = value.ToUpperInvariant(); + var length = value.Length; + + if (useByteLength) + { + if (length == 1 || length == 3) + { + value = "0" + value; + } + else if (length > 4 && length < 8) + { + value = value.PadLeft(8, '0'); + } + else if (length > 8 && length < 16) + { + value = value.PadLeft(16, '0'); + } + } + else if (length % 2 == 1) + { + value = "0" + value; + } + + return value; + } + + /// + /// Encodes value to a hexadecimal CBOR string. Because C# does not differentiate byte strings and text strings + /// the only way to manipulate output type of strings is to pass a string type (one of CBOR::TYPE_TEXT_STRING and + /// CBOR::TYPE_BYTE_STRING). + /// + /// Value to encode + /// Type of string encoding + /// Hexadecimal CBOR string + /// Thrown when unsupported type is passed + public static string Encode(object value, byte stringType = TYPE_TEXT_STRING) + { + if (value == null) + { + return SIMPLE_VALUE_NULL; + } + + switch (value) + { + case bool boolValue: + return boolValue ? SIMPLE_VALUE_TRUE : SIMPLE_VALUE_FALSE; + + case long: + case int: + var numValue = Convert.ToInt64(value); + var type = TYPE_UNSIGNED_INT; + if (numValue < 0) + { + type = TYPE_NEGATIVE_INT; + numValue = Math.Abs(numValue + 1); + } + if (numValue <= 23) + { + return SanitizeOutput(Convert.ToString(type | numValue, 16)); + } + else + { + var hexValue = SanitizeOutput(Convert.ToString(numValue, 16), true); + var lengthHeader = additionalLengthBytes.FirstOrDefault(x => x.Value == hexValue.Length / 2).Key; + var header = SanitizeOutput(Convert.ToString(type | lengthHeader, 16)); + return header + hexValue; + } + + case string stringValue: + var strType = stringType; + var hexString = SanitizeOutput(BinaryToHex(Encoding.UTF8.GetBytes(stringValue))); + var strLength = hexString.Length / 2; + var strHeader = BuildHeader(strType, strLength); + return strHeader + hexString; + + case double doubleValue: + var floatHeader = Convert.ToString(TYPE_FLOAT | ADDITIONAL_LENGTH_8B, 16); + var floatBytes = BitConverter.GetBytes(doubleValue); + if (BitConverter.IsLittleEndian) + { + Array.Reverse(floatBytes); + } + var floatHex = BitConverter.ToString(floatBytes).Replace("-", ""); + return floatHeader + floatHex; + + case List listValue: + var listLength = listValue.Count; + var listType = TYPE_ARRAY; + var listResult = BuildHeader(listType, listLength); + foreach (var element in listValue) + { + listResult += Encode(element, stringType); + } + return SanitizeOutput(listResult); + + case Dictionary dictValue: + var dictLength = dictValue.Count; + var dictType = TYPE_HASHMAP; + var dictResult = BuildHeader(dictType, dictLength); + foreach (var kvp in dictValue) + { + dictResult += Encode(kvp.Key, stringType); + dictResult += Encode(kvp.Value, stringType); + } + return SanitizeOutput(dictResult); + + default: + throw new Exception($"Unsupported type passed to encoding: {value.GetType().Name}"); + } + } + + private static string BuildHeader(byte type, long length) + { + if (length > 0xffffffffffff) + { + var header = Convert.ToString(type | ADDITIONAL_TYPE_INDEFINITE, 16); + var footer = Convert.ToString(INDEFINITE_BREAK, 16); + return header + footer; + } + else if (length > 0xffffffff) + { + var header = Convert.ToString(type | ADDITIONAL_LENGTH_8B, 16) + SanitizeOutput(Convert.ToString(length, 16)); + return header; + } + else if (length > 0xffff) + { + var header = Convert.ToString(type | ADDITIONAL_LENGTH_4B, 16) + SanitizeOutput(Convert.ToString(length, 16)); + return header; + } + else if (length > 0xff) + { + var header = Convert.ToString(type | ADDITIONAL_LENGTH_2B, 16) + SanitizeOutput(Convert.ToString(length, 16)); + return header; + } + else if (length > 23) + { + var header = Convert.ToString(type | ADDITIONAL_LENGTH_1B, 16) + SanitizeOutput(Convert.ToString(length, 16)); + return header; + } + else + { + var header = Convert.ToString(type | length, 16); + return header; + } + } + + private static List SplitIntoBytes(string hexString) + { + var result = new List(); + for (int i = 0; i < hexString.Length; i += 2) + { + if (i + 1 < hexString.Length) + { + result.Add(hexString.Substring(i, 2)); + } + else + { + result.Add(hexString.Substring(i, 1)); + } + } + return result; + } + + private static string HexToBinary(string hex) + { + var bytes = new byte[hex.Length / 2]; + for (int i = 0; i < bytes.Length; i++) + { + bytes[i] = Convert.ToByte(hex.Substring(i * 2, 2), 16); + } + return Encoding.UTF8.GetString(bytes); + } + + private static byte[] HexToBytes(string hex) + { + var bytes = new byte[hex.Length / 2]; + for (int i = 0; i < bytes.Length; i++) + { + bytes[i] = Convert.ToByte(hex.Substring(i * 2, 2), 16); + } + return bytes; + } + + public static string BinaryToHex(byte[] bytes) + { + return BitConverter.ToString(bytes).Replace("-", ""); + } + } +} \ No newline at end of file diff --git a/src/Api/PubnubApi/ClientNetworkStatus.cs b/src/Api/PubnubApi/ClientNetworkStatus.cs index c2e879dd8..c38cf9b36 100644 --- a/src/Api/PubnubApi/ClientNetworkStatus.cs +++ b/src/Api/PubnubApi/ClientNetworkStatus.cs @@ -35,11 +35,11 @@ internal static bool CheckInternetStatus(bool systemActive, PNOperationType } catch (AggregateException ae) { foreach (var ie in ae.InnerExceptions) { - PubnubConfiguation?.Logger?.Error($"AggregateException CheckInternetStatus : {ie.GetType().Name} {ie.Message}"); + PubnubConfiguation?.Logger?.Warn($"AggregateException CheckInternetStatus : {ie.GetType().Name} {ie.Message}"); } } catch (Exception ex) { - PubnubConfiguation?.Logger?.Error($"CheckInternetStatus Exception: {ex.Message}"); + PubnubConfiguation?.Logger?.Warn($"CheckInternetStatus Exception: {ex.Message}"); } return networkStatus; diff --git a/src/Api/PubnubApi/EndPoint/TokenManager.cs b/src/Api/PubnubApi/EndPoint/TokenManager.cs index 3be92c002..049a5dfc8 100644 --- a/src/Api/PubnubApi/EndPoint/TokenManager.cs +++ b/src/Api/PubnubApi/EndPoint/TokenManager.cs @@ -1,13 +1,7 @@ using System; using System.Collections.Generic; -using System.Linq; using System.Text; -using System.Text.RegularExpressions; -using System.Collections; -using System.Reflection; -using Newtonsoft.Json; -using PeterO.Cbor; -using System.Globalization; +using System.Linq; #if !NET35 && !NET40 using System.Collections.Concurrent; #endif @@ -20,11 +14,9 @@ public class TokenManager : IDisposable private readonly IJsonPluggableLibrary jsonLib; private readonly string pubnubInstanceId; private readonly PubnubLogModule logger; - private static ConcurrentDictionary dToken - { - get; - set; - } = new ConcurrentDictionary(); + + private static ConcurrentDictionary dToken { get; set; } = + new ConcurrentDictionary(); public TokenManager(PNConfiguration config, IJsonPluggableLibrary jsonPluggableLibrary, string instanceId) { @@ -36,19 +28,18 @@ public TokenManager(PNConfiguration config, IJsonPluggableLibrary jsonPluggableL { dToken.TryAdd(instanceId, null); } - } - public string AuthToken + public string AuthToken { - get - { + get + { string tkn; - dToken.TryGetValue(pubnubInstanceId, out tkn); - return tkn; - } + dToken.TryGetValue(pubnubInstanceId, out tkn); + return tkn; + } } - + private static string GetDisplayableBytes(byte[] currentBytes) { StringBuilder outBuilder = new StringBuilder("{ "); @@ -74,32 +65,30 @@ public PNTokenContent ParseToken(string token) string refinedToken = token.Replace('_', '/').Replace('-', '+'); byte[] tokenByteArray = Convert.FromBase64String(refinedToken); logger?.Debug($"TokenManager Token Bytes = {GetDisplayableBytes(tokenByteArray)}"); - using (System.IO.MemoryStream ms = new System.IO.MemoryStream(tokenByteArray)) - { - CBORObject cborObj = CBORObject.DecodeFromBytes(tokenByteArray); - logger?.Debug($"RAW CBOR {cborObj.ToJSONString()}"); + + var cborObj = CBOR.Decode(CBOR.BinaryToHex(tokenByteArray)); - if (cborObj != null) + if (cborObj is Dictionary cborDict) + { + result = new PNTokenContent(); + result.Meta = new Dictionary(); + result.Resources = new PNTokenResources { - result = new PNTokenContent(); - result.Meta = new Dictionary(); - result.Resources = new PNTokenResources { - Channels = new Dictionary(), - ChannelGroups = new Dictionary(), - Uuids = new Dictionary(), - Users = new Dictionary(), - Spaces = new Dictionary() - }; - result.Patterns = new PNTokenPatterns { - Channels = new Dictionary(), - ChannelGroups = new Dictionary(), - Uuids = new Dictionary(), - Users = new Dictionary(), - Spaces = new Dictionary() - }; - ParseCBOR(cborObj, "", ref result); - } - + Channels = new Dictionary(), + ChannelGroups = new Dictionary(), + Uuids = new Dictionary(), + Users = new Dictionary(), + Spaces = new Dictionary() + }; + result.Patterns = new PNTokenPatterns + { + Channels = new Dictionary(), + ChannelGroups = new Dictionary(), + Uuids = new Dictionary(), + Users = new Dictionary(), + Spaces = new Dictionary() + }; + ParseCBOR(cborDict, ref result); } } } @@ -110,224 +99,106 @@ public PNTokenContent ParseToken(string token) return result; } - private void ParseCBOR(CBORObject cbor, string parent, ref PNTokenContent pnGrantTokenDecoded) + private string ByteToString(object value) { - foreach (KeyValuePair kvp in cbor.Entries) - { - if (kvp.Key.Type.ToString().Equals("ByteString", StringComparison.OrdinalIgnoreCase)) - { -#if NETSTANDARD10 || NETSTANDARD11 - UTF8Encoding utf8 = new UTF8Encoding(true, true); - byte[] keyBytes = kvp.Key.GetByteString(); - string key = utf8.GetString(keyBytes, 0, keyBytes.Length); -#else - string key = Encoding.ASCII.GetString(kvp.Key.GetByteString()); -#endif - ParseCBORValue(key, parent, kvp, ref pnGrantTokenDecoded); - } - else if (kvp.Key.Type.ToString().Equals("TextString", StringComparison.OrdinalIgnoreCase)) - { - logger?.Debug($"ParseCBOR TextString Key {kvp.Key}-{kvp.Value}-{kvp.Value.Type}"); - ParseCBORValue(kvp.Key.ToString(), parent, kvp, ref pnGrantTokenDecoded); - } - else - { - logger?.Debug($"Others Key {kvp.Key}-{kvp.Key.Type}-{kvp.Value}-{kvp.Value.Type}"); - } - - } + return value is byte[] bytes ? Encoding.UTF8.GetString(bytes) : value.ToString(); } - private void ParseCBORValue(string key, string parent, KeyValuePair kvp, ref PNTokenContent pnGrantTokenDecoded) + private void FillTokenPermissionMapping(Dictionary permissionValues, + PNTokenPermissionMappingBase permissionMapping) { - if (kvp.Value.Type.ToString().Equals("Map", StringComparison.OrdinalIgnoreCase)) + foreach (var resourceKvp in permissionValues) { - logger?.Debug($"ParseCBORValue Map Key {key}"); - var p = string.Format(CultureInfo.InvariantCulture, "{0}{1}{2}", parent, string.IsNullOrEmpty(parent) ? "" : ":", key); - ParseCBOR(kvp.Value, p, ref pnGrantTokenDecoded); - } - else if (kvp.Value.Type.ToString().Equals("ByteString", StringComparison.OrdinalIgnoreCase)) - { -#if NETSTANDARD10 || NETSTANDARD11 - UTF8Encoding utf8 = new UTF8Encoding(true, true); - byte[] valBytes = kvp.Value.GetByteString(); - string val = utf8.GetString(valBytes, 0, valBytes.Length); -#else - string val = Encoding.ASCII.GetString(kvp.Value.GetByteString()); -#endif - logger?.Debug($"ByteString Value {key}-{val}"); - FillGrantToken(parent, key, kvp.Value, typeof(string), ref pnGrantTokenDecoded); - } - else if (kvp.Value.Type.ToString().Equals("Integer", StringComparison.OrdinalIgnoreCase)) - { - logger?.Debug($" Integer Value {key}-{kvp.Value}"); - FillGrantToken(parent, key, kvp.Value, typeof(int), ref pnGrantTokenDecoded); - } - else if (kvp.Value.Type.ToString().Equals("TextString", StringComparison.OrdinalIgnoreCase)) - { - logger?.Debug($"TextString Value {key}-{kvp.Value}"); - FillGrantToken(parent, key, kvp.Value, typeof(string), ref pnGrantTokenDecoded); - } - else - { - logger?.Debug($"Others Key Value {kvp.Key.Type}-{kvp.Value.Type}-{key}-{kvp.Value}"); - } - } - - private string ReplaceBoundaryQuotes(string key) - { - if (key != null && !string.IsNullOrEmpty(key) && key.Length >= 2 - && string.Equals(key.Substring(0, 1), "\"", StringComparison.OrdinalIgnoreCase) - && string.Equals(key.Substring(key.Length - 1, 1), "\"", StringComparison.OrdinalIgnoreCase)) - { - key = key.Remove(key.Length - 1, 1).Remove(0, 1); - key = Regex.Unescape(key); - } - return key; - } - - private void FillGrantToken(string parent, string key, object val, Type type, ref PNTokenContent pnGrantTokenDecoded) - { - key = ReplaceBoundaryQuotes(key); - int i = 0; - long l = 0; - switch (type.Name) - { - case "Int32": - if (!int.TryParse(val.ToString(), out i)) - { - //do nothing. - } - break; - case "Int64": - if (!long.TryParse(val.ToString(), out l)) - { - //do nothing - } - break; - case "String": - // do nothing - break; - default: - break; - } - switch (key) - { - case "v": - pnGrantTokenDecoded.Version = i; - break; - case "t": - pnGrantTokenDecoded.Timestamp = i; - break; - case "ttl": - pnGrantTokenDecoded.TTL = i; - break; - case "meta": - pnGrantTokenDecoded.Meta = val as Dictionary; - break; - case "uuid": - pnGrantTokenDecoded.AuthorizedUuid = ((CBORObject)val).AsString(); - break; - case "sig": -#if NETSTANDARD10 || NETSTANDARD11 - UTF8Encoding utf8 = new UTF8Encoding(true, true); - byte[] keyBytes = (byte[])val; - pnGrantTokenDecoded.Signature = utf8.GetString(keyBytes, 0, keyBytes.Length); -#else - byte[] sigBytes = ((CBORObject)val).GetByteString(); - string base64String = Convert.ToBase64String(sigBytes); - pnGrantTokenDecoded.Signature = base64String; -#endif - break; - default: - PNTokenAuthValues rp = GetResourcePermission(i); - switch (parent) + var resourceType = ByteToString(resourceKvp.Key); + foreach (var authKvp in resourceKvp.Value as Dictionary) + { + var resourceId = ByteToString(authKvp.Key); + var authValuesAsInt = Convert.ToInt32(authKvp.Value); + var resourcePermission = GetResourcePermission(authValuesAsInt); + switch (resourceType) { - case "meta": - if (!pnGrantTokenDecoded.Meta.ContainsKey(key)) - { - switch (type.Name) - { - case "Int32": - pnGrantTokenDecoded.Meta.Add(key, ((CBORObject)val).AsInt32()); - break; - case "Int64": - pnGrantTokenDecoded.Meta.Add(key, ((CBORObject)val).ToObject()); - break; - case "String": - pnGrantTokenDecoded.Meta.Add(key, ((CBORObject)val).AsString()); - break; - default: - break; - } - } - break; - case "res:spc": - if (!pnGrantTokenDecoded.Resources.Spaces.ContainsKey(key)) - { - pnGrantTokenDecoded.Resources.Spaces.Add(key, rp); - } - break; - case "res:usr": - if (!pnGrantTokenDecoded.Resources.Users.ContainsKey(key)) - { - pnGrantTokenDecoded.Resources.Users.Add(key, rp); - } - break; - case "res:uuid": - if (!pnGrantTokenDecoded.Resources.Uuids.ContainsKey(key)) - { - pnGrantTokenDecoded.Resources.Uuids.Add(key, rp); - } - break; - case "res:chan": - if (!pnGrantTokenDecoded.Resources.Channels.ContainsKey(key)) - { - pnGrantTokenDecoded.Resources.Channels.Add(key, rp); - } - break; - case "res:grp": - if (!pnGrantTokenDecoded.Resources.ChannelGroups.ContainsKey(key)) - { - pnGrantTokenDecoded.Resources.ChannelGroups.Add(key, rp); - } - break; - case "pat:spc": - if (!pnGrantTokenDecoded.Patterns.Spaces.ContainsKey(key)) + case "spc": + if (!permissionMapping.Spaces.ContainsKey(resourceId)) { - pnGrantTokenDecoded.Patterns.Spaces.Add(key, rp); + permissionMapping.Spaces.Add(resourceId, resourcePermission); } + break; - case "pat:usr": - if (!pnGrantTokenDecoded.Patterns.Users.ContainsKey(key)) + case "usr": + if (!permissionMapping.Users.ContainsKey(resourceId)) { - pnGrantTokenDecoded.Patterns.Users.Add(key, rp); + permissionMapping.Users.Add(resourceId, resourcePermission); } + break; - case "pat:uuid": - if (!pnGrantTokenDecoded.Patterns.Uuids.ContainsKey(key)) + case "uuid": + if (!permissionMapping.Uuids.ContainsKey(resourceId)) { - pnGrantTokenDecoded.Patterns.Uuids.Add(key, rp); + permissionMapping.Uuids.Add(resourceId, resourcePermission); } + break; - case "pat:chan": - if (!pnGrantTokenDecoded.Patterns.Channels.ContainsKey(key)) + case "chan": + if (!permissionMapping.Channels.ContainsKey(resourceId)) { - pnGrantTokenDecoded.Patterns.Channels.Add(key, rp); + permissionMapping.Channels.Add(resourceId, resourcePermission); } + break; - case "pat:grp": - if (!pnGrantTokenDecoded.Patterns.ChannelGroups.ContainsKey(key)) + case "grp": + if (!permissionMapping.ChannelGroups.ContainsKey(resourceId)) { - pnGrantTokenDecoded.Patterns.ChannelGroups.Add(key, rp); + permissionMapping.ChannelGroups.Add(resourceId, resourcePermission); } break; default: + logger?.Error($"Unidentified resource ID when parsing permission mappings! {resourceId}"); break; } - break; + } } } + + private void ParseCBOR(Dictionary cbor, ref PNTokenContent pnGrantTokenDecoded) + { + foreach (var kvp in cbor) + { + var key = ByteToString(kvp.Key); + switch (key) + { + case "v": + pnGrantTokenDecoded.Version = Convert.ToInt32(kvp.Value); + break; + case "t": + pnGrantTokenDecoded.Timestamp = Convert.ToInt64(kvp.Value); + break; + case "ttl": + pnGrantTokenDecoded.TTL = Convert.ToInt32(kvp.Value); + break; + case "meta": + pnGrantTokenDecoded.Meta = (kvp.Value as Dictionary).ToDictionary(x => ByteToString(x.Key), y => y.Value); + break; + case "uuid": + pnGrantTokenDecoded.AuthorizedUuid = ByteToString(kvp.Value); + break; + case "sig": + byte[] sigBytes = kvp.Value as byte[]; + string base64String = Convert.ToBase64String(sigBytes); + pnGrantTokenDecoded.Signature = base64String; + break; + case "res": + FillTokenPermissionMapping(kvp.Value as Dictionary, pnGrantTokenDecoded.Resources); + break; + case "pat": + FillTokenPermissionMapping(kvp.Value as Dictionary, pnGrantTokenDecoded.Patterns); + break; + default: + logger?.Error($"Unidentified key when parsing token! {key}"); + break; + } + } + } + public void SetAuthToken(string token) { dToken.AddOrUpdate(pubnubInstanceId, token, (k, o) => token); @@ -347,6 +218,7 @@ private static PNTokenAuthValues GetResourcePermission(int combinedVal) rp.Update = (combinedVal & (int)GrantBitFlag.UPDATE) != 0; rp.Join = (combinedVal & (int)GrantBitFlag.JOIN) != 0; } + return rp; } @@ -356,10 +228,10 @@ internal void Destroy() { dToken[pubnubInstanceId] = null; } - } -#region IDisposable Support + #region IDisposable Support + private bool disposedValue; protected virtual void DisposeInternal(bool disposing) @@ -379,7 +251,7 @@ void IDisposable.Dispose() { DisposeInternal(true); } -#endregion + #endregion } -} +} \ No newline at end of file diff --git a/src/Api/PubnubApi/EventEngine/Common/EventEmitter.cs b/src/Api/PubnubApi/EventEngine/Common/EventEmitter.cs index 519f5c807..bb7a8f109 100644 --- a/src/Api/PubnubApi/EventEngine/Common/EventEmitter.cs +++ b/src/Api/PubnubApi/EventEngine/Common/EventEmitter.cs @@ -165,7 +165,7 @@ public void EmitEvent(object e) catch (Exception ex) { decryptMessage = "**DECRYPT ERROR**"; - configuration?.Logger?.Error("Failed to decrypt message on channel {currentMessageChannel} due to exception={ex}.\n Message might be not encrypted"); + configuration?.Logger?.Warn("Failed to decrypt message on channel {currentMessageChannel} due to exception={ex}.\n Message might be not encrypted"); } object decodeMessage = jsonLibrary.DeserializeToObject((decryptMessage == "**DECRYPT ERROR**") ? jsonLibrary.SerializeToJsonString(payload) : decryptMessage); diff --git a/src/Api/PubnubApi/Properties/AssemblyInfo.cs b/src/Api/PubnubApi/Properties/AssemblyInfo.cs index db4165da1..f99f4f478 100644 --- a/src/Api/PubnubApi/Properties/AssemblyInfo.cs +++ b/src/Api/PubnubApi/Properties/AssemblyInfo.cs @@ -11,8 +11,8 @@ [assembly: AssemblyProduct("Pubnub C# SDK")] [assembly: AssemblyCopyright("Copyright © 2021")] [assembly: AssemblyTrademark("")] -[assembly: AssemblyVersion("7.3.14.0")] -[assembly: AssemblyFileVersion("7.3.14.0")] +[assembly: AssemblyVersion("7.3.15.0")] +[assembly: AssemblyFileVersion("7.3.15.0")] // Setting ComVisible to false makes the types in this assembly not visible // to COM components. If you need to access a type in this assembly from // COM, set the ComVisible attribute to true on that type. diff --git a/src/Api/PubnubApi/PubnubApi.csproj b/src/Api/PubnubApi/PubnubApi.csproj index aa160d28c..fa7f6b0b5 100644 --- a/src/Api/PubnubApi/PubnubApi.csproj +++ b/src/Api/PubnubApi/PubnubApi.csproj @@ -14,7 +14,7 @@ Pubnub - 7.3.14.0 + 7.3.15.0 PubNub C# .NET - Web Data Push API Pandu Masabathula PubNub @@ -22,7 +22,7 @@ http://pubnub.s3.amazonaws.com/2011/powered-by-pubnub/pubnub-icon-600x600.png true https://github.com/pubnub/c-sharp/ - Updated log type from Error to Warn for TaskCanceledException. + Implemented an in-house CBOR solution for ParseToken() handling to reduce total SDK+dependencies size. Web Data Push Real-time Notifications ESB Message Broadcasting Distributed Computing PubNub is a Massively Scalable Web Push Service for Web and Mobile Games. This is a cloud-based service for broadcasting messages to thousands of web and mobile clients simultaneously @@ -66,7 +66,6 @@ None - diff --git a/src/Api/PubnubApi/Transport/HttpClientService.cs b/src/Api/PubnubApi/Transport/HttpClientService.cs index 5b643630f..11c39ddea 100644 --- a/src/Api/PubnubApi/Transport/HttpClientService.cs +++ b/src/Api/PubnubApi/Transport/HttpClientService.cs @@ -79,7 +79,7 @@ public async Task GetRequest(TransportRequest transportReques } catch (Exception e) { - logger?.Error( + logger?.Warn( $"HttpClient Service: Exception for http call url {transportRequest.RequestUrl}, exception message: {e.Message}, stacktrace: {e.StackTrace}"); transportResponse = new TransportResponse() { @@ -149,7 +149,7 @@ public async Task PostRequest(TransportRequest transportReque } catch (Exception e) { - logger?.Error( + logger?.Warn( $"Exception for http call url {transportRequest.RequestUrl}, exception message: {e.Message}, stacktrace: {e.StackTrace}"); transportResponse = new TransportResponse() { @@ -228,7 +228,7 @@ public async Task PutRequest(TransportRequest transportReques } catch (Exception e) { - logger?.Error( + logger?.Warn( $"Exception for http call url {transportRequest.RequestUrl}, exception message: {e.Message}, stacktrace: {e.StackTrace}"); transportResponse = new TransportResponse() { @@ -291,7 +291,7 @@ public async Task DeleteRequest(TransportRequest transportReq } catch (Exception e) { - logger?.Error( + logger?.Warn( $"Exception for http call url {transportRequest.RequestUrl}, exception message: {e.Message}, stacktrace: {e.StackTrace}"); transportResponse = new TransportResponse() { @@ -371,7 +371,7 @@ public async Task PatchRequest(TransportRequest transportRequ } catch (Exception e) { - logger?.Error( + logger?.Warn( $"Exception for http call url {transportRequest.RequestUrl}, exception message: {e.Message}, stacktrace: {e.StackTrace}"); transportResponse = new TransportResponse() { diff --git a/src/Api/PubnubApiPCL/PubnubApiPCL.csproj b/src/Api/PubnubApiPCL/PubnubApiPCL.csproj index 5f95ff31b..3479dc3da 100644 --- a/src/Api/PubnubApiPCL/PubnubApiPCL.csproj +++ b/src/Api/PubnubApiPCL/PubnubApiPCL.csproj @@ -14,7 +14,7 @@ PubnubPCL - 7.3.14.0 + 7.3.15.0 PubNub C# .NET - Web Data Push API Pandu Masabathula PubNub @@ -22,7 +22,7 @@ http://pubnub.s3.amazonaws.com/2011/powered-by-pubnub/pubnub-icon-600x600.png true https://github.com/pubnub/c-sharp/ - Updated log type from Error to Warn for TaskCanceledException. + Implemented an in-house CBOR solution for ParseToken() handling to reduce total SDK+dependencies size. Web Data Push Real-time Notifications ESB Message Broadcasting Distributed Computing PubNub is a Massively Scalable Web Push Service for Web and Mobile Games. This is a cloud-based service for broadcasting messages to thousands of web and mobile clients simultaneously @@ -50,6 +50,9 @@ Callbacks\SubscribeCallback.cs + + CBOR.cs + ClientNetworkStatus.cs @@ -637,7 +640,6 @@ None - diff --git a/src/Api/PubnubApiUWP/PubnubApiUWP.csproj b/src/Api/PubnubApiUWP/PubnubApiUWP.csproj index 3b7446838..14bf690ef 100644 --- a/src/Api/PubnubApiUWP/PubnubApiUWP.csproj +++ b/src/Api/PubnubApiUWP/PubnubApiUWP.csproj @@ -16,7 +16,7 @@ PubnubUWP - 7.3.14.0 + 7.3.15.0 PubNub C# .NET - Web Data Push API Pandu Masabathula PubNub @@ -24,7 +24,7 @@ http://pubnub.s3.amazonaws.com/2011/powered-by-pubnub/pubnub-icon-600x600.png true https://github.com/pubnub/c-sharp/ - Updated log type from Error to Warn for TaskCanceledException. + Implemented an in-house CBOR solution for ParseToken() handling to reduce total SDK+dependencies size. Web Data Push Real-time Notifications ESB Message Broadcasting Distributed Computing PubNub is a Massively Scalable Web Push Service for Web and Mobile Games. This is a cloud-based service for broadcasting messages to thousands of web and mobile clients simultaneously @@ -171,6 +171,9 @@ Callbacks\SubscribeCallback.cs + + CBOR.cs + ClientNetworkStatus.cs @@ -777,7 +780,6 @@ 13.0.3 - 4.3.0 diff --git a/src/Api/PubnubApiUnity/PubnubApiUnity.csproj b/src/Api/PubnubApiUnity/PubnubApiUnity.csproj index 53c2e5075..1aa10a5e8 100644 --- a/src/Api/PubnubApiUnity/PubnubApiUnity.csproj +++ b/src/Api/PubnubApiUnity/PubnubApiUnity.csproj @@ -15,7 +15,7 @@ PubnubApiUnity - 7.3.14.0 + 7.3.15.0 PubNub C# .NET - Web Data Push API Pandu Masabathula PubNub @@ -62,6 +62,9 @@ Callbacks\SubscribeCallback.cs + + CBOR.cs + ClientNetworkStatus.cs @@ -717,7 +720,6 @@ -