Compare commits
64 commits
Author | SHA1 | Date | |
---|---|---|---|
20586d8ada | |||
f099edb17b | |||
764b669295 | |||
c88eea1f82 | |||
5f451c8818 | |||
42f4507638 | |||
b390778201 | |||
30af614558 | |||
45e73a6f8e | |||
87fe8db674 | |||
5e86f53b0e | |||
9eb742da77 | |||
98cfd82313 | |||
f93e33b49b | |||
6ae96c1d77 | |||
809bd90352 | |||
32a7e64538 | |||
d6fe034453 | |||
9364d60b96 | |||
6988fcedae | |||
8835b23ed3 | |||
bd8eb7cc60 | |||
2e56c13946 | |||
195854a45b | |||
43e300c773 | |||
568bdc67e8 | |||
8637515869 | |||
db9b93b2e6 | |||
543247e4d9 | |||
abd9b5d0d0 | |||
c9418a1894 | |||
9bb7b5eff8 | |||
749000a090 | |||
766f61a5f7 | |||
c406df1a78 | |||
14dc76898e | |||
003b7fdfdd | |||
bff8d67867 | |||
ee20798379 | |||
c9a75ea025 | |||
d1271df207 | |||
d7dbbf8da8 | |||
|
704ce41173 | ||
|
6562aa27a5 | ||
|
1a02ac2ae7 | ||
|
22e2a53551 | ||
|
2a28806ace | ||
|
18126ea763 | ||
|
6083834582 | ||
|
0ddde467cd | ||
|
35fe791406 | ||
|
3206abc33e | ||
|
816e1eb2f1 | ||
7b9c19f37c | |||
00a1e9412f | |||
fefa2da218 | |||
ae67b12313 | |||
605463ec24 | |||
17492ee871 | |||
f5d1899dd2 | |||
c988ff3c76 | |||
b69d22966f | |||
545e647d7b | |||
|
0c4723c705 |
308 changed files with 18137 additions and 3844 deletions
901
.editorconfig
Normal file
901
.editorconfig
Normal file
|
@ -0,0 +1,901 @@
|
||||||
|
[*.cs]
|
||||||
|
|
||||||
|
# CA1001: Types that own disposable fields should be disposable
|
||||||
|
dotnet_diagnostic.CA1001.severity = warning
|
||||||
|
|
||||||
|
# CA1000: Do not declare static members on generic types
|
||||||
|
dotnet_diagnostic.CA1000.severity = warning
|
||||||
|
|
||||||
|
# CA1002: Do not expose generic lists
|
||||||
|
dotnet_diagnostic.CA1002.severity = warning
|
||||||
|
|
||||||
|
# CA1003: Use generic event handler instances
|
||||||
|
dotnet_diagnostic.CA1003.severity = warning
|
||||||
|
|
||||||
|
# CA1005: Avoid excessive parameters on generic types
|
||||||
|
dotnet_diagnostic.CA1005.severity = warning
|
||||||
|
|
||||||
|
# CA1008: Enums should have zero value
|
||||||
|
dotnet_diagnostic.CA1008.severity = warning
|
||||||
|
|
||||||
|
# CA1010: Generic interface should also be implemented
|
||||||
|
dotnet_diagnostic.CA1010.severity = warning
|
||||||
|
|
||||||
|
# CA1012: Abstract types should not have public constructors
|
||||||
|
dotnet_diagnostic.CA1012.severity = warning
|
||||||
|
|
||||||
|
# CA1014: Mark assemblies with CLSCompliant
|
||||||
|
dotnet_diagnostic.CA1014.severity = warning
|
||||||
|
|
||||||
|
# CA1016: Mark assemblies with assembly version
|
||||||
|
dotnet_diagnostic.CA1016.severity = warning
|
||||||
|
|
||||||
|
# CA1017: Mark assemblies with ComVisible
|
||||||
|
dotnet_diagnostic.CA1017.severity = warning
|
||||||
|
|
||||||
|
# CA1018: Mark attributes with AttributeUsageAttribute
|
||||||
|
dotnet_diagnostic.CA1018.severity = warning
|
||||||
|
|
||||||
|
# CA1019: Define accessors for attribute arguments
|
||||||
|
dotnet_diagnostic.CA1019.severity = warning
|
||||||
|
|
||||||
|
# CA1021: Avoid out parameters
|
||||||
|
dotnet_diagnostic.CA1021.severity = warning
|
||||||
|
|
||||||
|
# CA1024: Use properties where appropriate
|
||||||
|
dotnet_diagnostic.CA1024.severity = warning
|
||||||
|
|
||||||
|
# CA1027: Mark enums with FlagsAttribute
|
||||||
|
dotnet_diagnostic.CA1027.severity = warning
|
||||||
|
|
||||||
|
# CA1028: Enum Storage should be Int32
|
||||||
|
dotnet_diagnostic.CA1028.severity = warning
|
||||||
|
|
||||||
|
# CA1030: Use events where appropriate
|
||||||
|
dotnet_diagnostic.CA1030.severity = warning
|
||||||
|
|
||||||
|
# CA1031: Do not catch general exception types
|
||||||
|
dotnet_diagnostic.CA1031.severity = warning
|
||||||
|
|
||||||
|
# CA1033: Interface methods should be callable by child types
|
||||||
|
dotnet_diagnostic.CA1033.severity = warning
|
||||||
|
|
||||||
|
# CA1034: Nested types should not be visible
|
||||||
|
dotnet_diagnostic.CA1034.severity = warning
|
||||||
|
|
||||||
|
# CA1036: Override methods on comparable types
|
||||||
|
dotnet_diagnostic.CA1036.severity = warning
|
||||||
|
|
||||||
|
# CA1040: Avoid empty interfaces
|
||||||
|
dotnet_diagnostic.CA1040.severity = warning
|
||||||
|
|
||||||
|
# CA1041: Provide ObsoleteAttribute message
|
||||||
|
dotnet_diagnostic.CA1041.severity = warning
|
||||||
|
|
||||||
|
# CA1043: Use Integral Or String Argument For Indexers
|
||||||
|
dotnet_diagnostic.CA1043.severity = warning
|
||||||
|
|
||||||
|
# CA1044: Properties should not be write only
|
||||||
|
dotnet_diagnostic.CA1044.severity = warning
|
||||||
|
|
||||||
|
# CA1045: Do not pass types by reference
|
||||||
|
dotnet_diagnostic.CA1045.severity = warning
|
||||||
|
|
||||||
|
# CA1046: Do not overload equality operator on reference types
|
||||||
|
dotnet_diagnostic.CA1046.severity = warning
|
||||||
|
|
||||||
|
# CA1050: Declare types in namespaces
|
||||||
|
dotnet_diagnostic.CA1050.severity = warning
|
||||||
|
|
||||||
|
# CA1051: Do not declare visible instance fields
|
||||||
|
dotnet_diagnostic.CA1051.severity = warning
|
||||||
|
|
||||||
|
# CA1052: Static holder types should be Static or NotInheritable
|
||||||
|
dotnet_diagnostic.CA1052.severity = warning
|
||||||
|
|
||||||
|
# CA1054: URI-like parameters should not be strings
|
||||||
|
dotnet_diagnostic.CA1054.severity = warning
|
||||||
|
|
||||||
|
# CA1055: URI-like return values should not be strings
|
||||||
|
dotnet_diagnostic.CA1055.severity = warning
|
||||||
|
|
||||||
|
# CA1056: URI-like properties should not be strings
|
||||||
|
dotnet_diagnostic.CA1056.severity = warning
|
||||||
|
|
||||||
|
# CA1058: Types should not extend certain base types
|
||||||
|
dotnet_diagnostic.CA1058.severity = warning
|
||||||
|
|
||||||
|
# CA1060: Move pinvokes to native methods class
|
||||||
|
dotnet_diagnostic.CA1060.severity = warning
|
||||||
|
|
||||||
|
# CA1061: Do not hide base class methods
|
||||||
|
dotnet_diagnostic.CA1061.severity = warning
|
||||||
|
|
||||||
|
# CA1062: Validate arguments of public methods
|
||||||
|
dotnet_diagnostic.CA1062.severity = warning
|
||||||
|
|
||||||
|
# CA1063: Implement IDisposable Correctly
|
||||||
|
dotnet_diagnostic.CA1063.severity = warning
|
||||||
|
|
||||||
|
# CA1064: Exceptions should be public
|
||||||
|
dotnet_diagnostic.CA1064.severity = warning
|
||||||
|
|
||||||
|
# CA1065: Do not raise exceptions in unexpected locations
|
||||||
|
dotnet_diagnostic.CA1065.severity = warning
|
||||||
|
|
||||||
|
# CA1066: Implement IEquatable when overriding Object.Equals
|
||||||
|
dotnet_diagnostic.CA1066.severity = warning
|
||||||
|
|
||||||
|
# CA1067: Override Object.Equals(object) when implementing IEquatable<T>
|
||||||
|
dotnet_diagnostic.CA1067.severity = warning
|
||||||
|
|
||||||
|
# CA1068: CancellationToken parameters must come last
|
||||||
|
dotnet_diagnostic.CA1068.severity = warning
|
||||||
|
|
||||||
|
# CA1069: Enums values should not be duplicated
|
||||||
|
dotnet_diagnostic.CA1069.severity = warning
|
||||||
|
|
||||||
|
# CA1070: Do not declare event fields as virtual
|
||||||
|
dotnet_diagnostic.CA1070.severity = warning
|
||||||
|
|
||||||
|
# CA1303: Do not pass literals as localized parameters
|
||||||
|
dotnet_diagnostic.CA1303.severity = warning
|
||||||
|
|
||||||
|
# CA1304: Specify CultureInfo
|
||||||
|
dotnet_diagnostic.CA1304.severity = warning
|
||||||
|
|
||||||
|
# CA1305: Specify IFormatProvider
|
||||||
|
dotnet_diagnostic.CA1305.severity = warning
|
||||||
|
|
||||||
|
# CA1307: Specify StringComparison for clarity
|
||||||
|
dotnet_diagnostic.CA1307.severity = warning
|
||||||
|
|
||||||
|
# CA1308: Normalize strings to uppercase
|
||||||
|
dotnet_diagnostic.CA1308.severity = warning
|
||||||
|
|
||||||
|
# CA1310: Specify StringComparison for correctness
|
||||||
|
dotnet_diagnostic.CA1310.severity = warning
|
||||||
|
|
||||||
|
# CA1401: P/Invokes should not be visible
|
||||||
|
dotnet_diagnostic.CA1401.severity = warning
|
||||||
|
|
||||||
|
# CA1416: Validate platform compatibility
|
||||||
|
dotnet_diagnostic.CA1416.severity = warning
|
||||||
|
|
||||||
|
# CA1417: Do not use 'OutAttribute' on string parameters for P/Invokes
|
||||||
|
dotnet_diagnostic.CA1417.severity = warning
|
||||||
|
|
||||||
|
# CA1418: Use valid platform string
|
||||||
|
dotnet_diagnostic.CA1418.severity = warning
|
||||||
|
|
||||||
|
# CA1419: Provide a parameterless constructor that is as visible as the containing type for concrete types derived from 'System.Runtime.InteropServices.SafeHandle'
|
||||||
|
dotnet_diagnostic.CA1419.severity = warning
|
||||||
|
|
||||||
|
# CA1420: Property, type, or attribute requires runtime marshalling
|
||||||
|
dotnet_diagnostic.CA1420.severity = warning
|
||||||
|
|
||||||
|
# CA1421: This method uses runtime marshalling even when the 'DisableRuntimeMarshallingAttribute' is applied
|
||||||
|
dotnet_diagnostic.CA1421.severity = warning
|
||||||
|
|
||||||
|
# CA1422: Validate platform compatibility
|
||||||
|
dotnet_diagnostic.CA1422.severity = warning
|
||||||
|
|
||||||
|
# CA1501: Avoid excessive inheritance
|
||||||
|
dotnet_diagnostic.CA1501.severity = warning
|
||||||
|
|
||||||
|
# CA1502: Avoid excessive complexity
|
||||||
|
dotnet_diagnostic.CA1502.severity = warning
|
||||||
|
|
||||||
|
# CA1505: Avoid unmaintainable code
|
||||||
|
dotnet_diagnostic.CA1505.severity = warning
|
||||||
|
|
||||||
|
# CA1506: Avoid excessive class coupling
|
||||||
|
# dotnet_diagnostic.CA1506.severity = warning
|
||||||
|
|
||||||
|
# CA1509: Invalid entry in code metrics rule specification file
|
||||||
|
dotnet_diagnostic.CA1509.severity = warning
|
||||||
|
|
||||||
|
# CA1510: Use ArgumentNullException throw helper
|
||||||
|
dotnet_diagnostic.CA1510.severity = warning
|
||||||
|
|
||||||
|
# CA1511: Use ArgumentException throw helper
|
||||||
|
dotnet_diagnostic.CA1511.severity = warning
|
||||||
|
|
||||||
|
# CA1512: Use ArgumentOutOfRangeException throw helper
|
||||||
|
dotnet_diagnostic.CA1512.severity = warning
|
||||||
|
|
||||||
|
# CA1513: Use ObjectDisposedException throw helper
|
||||||
|
dotnet_diagnostic.CA1513.severity = warning
|
||||||
|
|
||||||
|
# CA1700: Do not name enum values 'Reserved'
|
||||||
|
dotnet_diagnostic.CA1700.severity = warning
|
||||||
|
|
||||||
|
# CA1707: Identifiers should not contain underscores
|
||||||
|
dotnet_diagnostic.CA1707.severity = warning
|
||||||
|
|
||||||
|
# CA1708: Identifiers should differ by more than case
|
||||||
|
dotnet_diagnostic.CA1708.severity = none
|
||||||
|
|
||||||
|
# CA1710: Identifiers should have correct suffix
|
||||||
|
dotnet_diagnostic.CA1710.severity = warning
|
||||||
|
|
||||||
|
# CA1711: Identifiers should not have incorrect suffix
|
||||||
|
dotnet_diagnostic.CA1711.severity = warning
|
||||||
|
|
||||||
|
# CA1712: Do not prefix enum values with type name
|
||||||
|
dotnet_diagnostic.CA1712.severity = warning
|
||||||
|
|
||||||
|
# CA1713: Events should not have 'Before' or 'After' prefix
|
||||||
|
dotnet_diagnostic.CA1713.severity = warning
|
||||||
|
|
||||||
|
# CA1715: Identifiers should have correct prefix
|
||||||
|
dotnet_diagnostic.CA1715.severity = warning
|
||||||
|
|
||||||
|
# CA1716: Identifiers should not match keywords
|
||||||
|
dotnet_diagnostic.CA1716.severity = none
|
||||||
|
|
||||||
|
# CA1720: Identifier contains type name
|
||||||
|
dotnet_diagnostic.CA1720.severity = warning
|
||||||
|
|
||||||
|
# CA1721: Property names should not match get methods
|
||||||
|
dotnet_diagnostic.CA1721.severity = warning
|
||||||
|
|
||||||
|
# CA1724: Type names should not match namespaces
|
||||||
|
dotnet_diagnostic.CA1724.severity = warning
|
||||||
|
|
||||||
|
# CA1725: Parameter names should match base declaration
|
||||||
|
dotnet_diagnostic.CA1725.severity = warning
|
||||||
|
|
||||||
|
# CA1727: Use PascalCase for named placeholders
|
||||||
|
dotnet_diagnostic.CA1727.severity = warning
|
||||||
|
|
||||||
|
# CA1806: Do not ignore method results
|
||||||
|
dotnet_diagnostic.CA1806.severity = warning
|
||||||
|
|
||||||
|
# CA1810: Initialize reference type static fields inline
|
||||||
|
dotnet_diagnostic.CA1810.severity = warning
|
||||||
|
|
||||||
|
# CA1813: Avoid unsealed attributes
|
||||||
|
dotnet_diagnostic.CA1813.severity = warning
|
||||||
|
|
||||||
|
# CA1814: Prefer jagged arrays over multidimensional
|
||||||
|
dotnet_diagnostic.CA1814.severity = warning
|
||||||
|
|
||||||
|
# CA1815: Override equals and operator equals on value types
|
||||||
|
dotnet_diagnostic.CA1815.severity = warning
|
||||||
|
|
||||||
|
# CA1816: Dispose methods should call SuppressFinalize
|
||||||
|
dotnet_diagnostic.CA1816.severity = warning
|
||||||
|
|
||||||
|
# CA1819: Properties should not return arrays
|
||||||
|
# dotnet_diagnostic.CA1819.severity = warning
|
||||||
|
|
||||||
|
# CA1820: Test for empty strings using string length
|
||||||
|
dotnet_diagnostic.CA1820.severity = warning
|
||||||
|
|
||||||
|
# CA1821: Remove empty Finalizers
|
||||||
|
dotnet_diagnostic.CA1821.severity = warning
|
||||||
|
|
||||||
|
# CA1822: Mark members as static
|
||||||
|
dotnet_diagnostic.CA1822.severity = warning
|
||||||
|
|
||||||
|
# CA1823: Avoid unused private fields
|
||||||
|
dotnet_diagnostic.CA1823.severity = warning
|
||||||
|
|
||||||
|
# CA1826: Do not use Enumerable methods on indexable collections
|
||||||
|
dotnet_diagnostic.CA1826.severity = warning
|
||||||
|
|
||||||
|
# CA1827: Do not use Count() or LongCount() when Any() can be used
|
||||||
|
dotnet_diagnostic.CA1827.severity = warning
|
||||||
|
|
||||||
|
# CA1828: Do not use CountAsync() or LongCountAsync() when AnyAsync() can be used
|
||||||
|
dotnet_diagnostic.CA1828.severity = warning
|
||||||
|
|
||||||
|
# CA1829: Use Length/Count property instead of Count() when available
|
||||||
|
dotnet_diagnostic.CA1829.severity = warning
|
||||||
|
|
||||||
|
# CA1830: Prefer strongly-typed Append and Insert method overloads on StringBuilder
|
||||||
|
dotnet_diagnostic.CA1830.severity = warning
|
||||||
|
|
||||||
|
# CA1831: Use AsSpan or AsMemory instead of Range-based indexers when appropriate
|
||||||
|
dotnet_diagnostic.CA1831.severity = warning
|
||||||
|
|
||||||
|
# CA1832: Use AsSpan or AsMemory instead of Range-based indexers when appropriate
|
||||||
|
dotnet_diagnostic.CA1832.severity = warning
|
||||||
|
|
||||||
|
# CA1833: Use AsSpan or AsMemory instead of Range-based indexers when appropriate
|
||||||
|
dotnet_diagnostic.CA1833.severity = warning
|
||||||
|
|
||||||
|
# CA1834: Consider using 'StringBuilder.Append(char)' when applicable
|
||||||
|
dotnet_diagnostic.CA1834.severity = warning
|
||||||
|
|
||||||
|
# CA1835: Prefer the 'Memory'-based overloads for 'ReadAsync' and 'WriteAsync'
|
||||||
|
dotnet_diagnostic.CA1835.severity = warning
|
||||||
|
|
||||||
|
# CA1836: Prefer IsEmpty over Count
|
||||||
|
dotnet_diagnostic.CA1836.severity = warning
|
||||||
|
|
||||||
|
# CA1837: Use 'Environment.ProcessId'
|
||||||
|
dotnet_diagnostic.CA1837.severity = warning
|
||||||
|
|
||||||
|
# CA1838: Avoid 'StringBuilder' parameters for P/Invokes
|
||||||
|
dotnet_diagnostic.CA1838.severity = warning
|
||||||
|
|
||||||
|
# CA1839: Use 'Environment.ProcessPath'
|
||||||
|
dotnet_diagnostic.CA1839.severity = warning
|
||||||
|
|
||||||
|
# CA1840: Use 'Environment.CurrentManagedThreadId'
|
||||||
|
dotnet_diagnostic.CA1840.severity = warning
|
||||||
|
|
||||||
|
# CA1842: Do not use 'WhenAll' with a single task
|
||||||
|
dotnet_diagnostic.CA1842.severity = warning
|
||||||
|
|
||||||
|
# CA1843: Do not use 'WaitAll' with a single task
|
||||||
|
dotnet_diagnostic.CA1843.severity = warning
|
||||||
|
|
||||||
|
# CA1844: Provide memory-based overrides of async methods when subclassing 'Stream'
|
||||||
|
dotnet_diagnostic.CA1844.severity = warning
|
||||||
|
|
||||||
|
# CA1846: Prefer 'AsSpan' over 'Substring'
|
||||||
|
dotnet_diagnostic.CA1846.severity = warning
|
||||||
|
|
||||||
|
# CA1847: Use char literal for a single character lookup
|
||||||
|
dotnet_diagnostic.CA1847.severity = warning
|
||||||
|
|
||||||
|
# CA1848: Use the LoggerMessage delegates
|
||||||
|
dotnet_diagnostic.CA1848.severity = warning
|
||||||
|
|
||||||
|
# CA1849: Call async methods when in an async method
|
||||||
|
dotnet_diagnostic.CA1849.severity = warning
|
||||||
|
|
||||||
|
# CA1850: Prefer static 'HashData' method over 'ComputeHash'
|
||||||
|
dotnet_diagnostic.CA1850.severity = warning
|
||||||
|
|
||||||
|
# CA1852: Seal internal types
|
||||||
|
dotnet_diagnostic.CA1852.severity = warning
|
||||||
|
|
||||||
|
# CA1853: Unnecessary call to 'Dictionary.ContainsKey(key)'
|
||||||
|
dotnet_diagnostic.CA1853.severity = warning
|
||||||
|
|
||||||
|
# CA1854: Prefer the 'IDictionary.TryGetValue(TKey, out TValue)' method
|
||||||
|
dotnet_diagnostic.CA1854.severity = warning
|
||||||
|
|
||||||
|
# CA1858: Use 'StartsWith' instead of 'IndexOf'
|
||||||
|
dotnet_diagnostic.CA1858.severity = warning
|
||||||
|
|
||||||
|
# CA1859: Use concrete types when possible for improved performance
|
||||||
|
dotnet_diagnostic.CA1859.severity = warning
|
||||||
|
|
||||||
|
# CA1860: Avoid using 'Enumerable.Any()' extension method
|
||||||
|
dotnet_diagnostic.CA1860.severity = warning
|
||||||
|
|
||||||
|
# CA1861: Avoid constant arrays as arguments
|
||||||
|
dotnet_diagnostic.CA1861.severity = warning
|
||||||
|
|
||||||
|
# CA1862: Use the 'StringComparison' method overloads to perform case-insensitive string comparisons
|
||||||
|
dotnet_diagnostic.CA1862.severity = warning
|
||||||
|
|
||||||
|
# CA1863: Use 'CompositeFormat'
|
||||||
|
dotnet_diagnostic.CA1863.severity = warning
|
||||||
|
|
||||||
|
# CA1864: Prefer the 'IDictionary.TryAdd(TKey, TValue)' method
|
||||||
|
dotnet_diagnostic.CA1864.severity = warning
|
||||||
|
|
||||||
|
# CA1868: Unnecessary call to 'Contains(item)'
|
||||||
|
dotnet_diagnostic.CA1868.severity = warning
|
||||||
|
|
||||||
|
# CA1869: Cache and reuse 'JsonSerializerOptions' instances
|
||||||
|
dotnet_diagnostic.CA1869.severity = warning
|
||||||
|
|
||||||
|
# CA2000: Dispose objects before losing scope
|
||||||
|
dotnet_diagnostic.CA2000.severity = warning
|
||||||
|
|
||||||
|
# CA2002: Do not lock on objects with weak identity
|
||||||
|
dotnet_diagnostic.CA2002.severity = warning
|
||||||
|
|
||||||
|
# CA2007: Consider calling ConfigureAwait on the awaited task
|
||||||
|
dotnet_diagnostic.CA2007.severity = warning
|
||||||
|
|
||||||
|
# CA2008: Do not create tasks without passing a TaskScheduler
|
||||||
|
dotnet_diagnostic.CA2008.severity = warning
|
||||||
|
|
||||||
|
# CA2009: Do not call ToImmutableCollection on an ImmutableCollection value
|
||||||
|
dotnet_diagnostic.CA2009.severity = warning
|
||||||
|
|
||||||
|
# CA2011: Avoid infinite recursion
|
||||||
|
dotnet_diagnostic.CA2011.severity = warning
|
||||||
|
|
||||||
|
# CA2012: Use ValueTasks correctly
|
||||||
|
dotnet_diagnostic.CA2012.severity = warning
|
||||||
|
|
||||||
|
# CA2013: Do not use ReferenceEquals with value types
|
||||||
|
dotnet_diagnostic.CA2013.severity = warning
|
||||||
|
|
||||||
|
# CA2015: Do not define finalizers for types derived from MemoryManager<T>
|
||||||
|
dotnet_diagnostic.CA2015.severity = warning
|
||||||
|
|
||||||
|
# CA2017: Parameter count mismatch
|
||||||
|
dotnet_diagnostic.CA2017.severity = warning
|
||||||
|
|
||||||
|
# CA2018: 'Buffer.BlockCopy' expects the number of bytes to be copied for the 'count' argument
|
||||||
|
dotnet_diagnostic.CA2018.severity = warning
|
||||||
|
|
||||||
|
# CA2019: Improper 'ThreadStatic' field initialization
|
||||||
|
dotnet_diagnostic.CA2019.severity = warning
|
||||||
|
|
||||||
|
# CA2021: Do not call Enumerable.Cast<T> or Enumerable.OfType<T> with incompatible types
|
||||||
|
dotnet_diagnostic.CA2021.severity = warning
|
||||||
|
|
||||||
|
# CA2100: Review SQL queries for security vulnerabilities
|
||||||
|
dotnet_diagnostic.CA2100.severity = warning
|
||||||
|
|
||||||
|
# CA2101: Specify marshaling for P/Invoke string arguments
|
||||||
|
dotnet_diagnostic.CA2101.severity = warning
|
||||||
|
|
||||||
|
# CA2119: Seal methods that satisfy private interfaces
|
||||||
|
dotnet_diagnostic.CA2119.severity = warning
|
||||||
|
|
||||||
|
# CA2153: Do Not Catch Corrupted State Exceptions
|
||||||
|
dotnet_diagnostic.CA2153.severity = warning
|
||||||
|
|
||||||
|
# CA2200: Rethrow to preserve stack details
|
||||||
|
dotnet_diagnostic.CA2200.severity = warning
|
||||||
|
|
||||||
|
# CA2201: Do not raise reserved exception types
|
||||||
|
dotnet_diagnostic.CA2201.severity = warning
|
||||||
|
|
||||||
|
# CA2207: Initialize value type static fields inline
|
||||||
|
dotnet_diagnostic.CA2207.severity = warning
|
||||||
|
|
||||||
|
# CA2208: Instantiate argument exceptions correctly
|
||||||
|
dotnet_diagnostic.CA2208.severity = warning
|
||||||
|
|
||||||
|
# CA2211: Non-constant fields should not be visible
|
||||||
|
dotnet_diagnostic.CA2211.severity = warning
|
||||||
|
|
||||||
|
# CA2213: Disposable fields should be disposed
|
||||||
|
dotnet_diagnostic.CA2213.severity = warning
|
||||||
|
|
||||||
|
# CA2214: Do not call overridable methods in constructors
|
||||||
|
dotnet_diagnostic.CA2214.severity = warning
|
||||||
|
|
||||||
|
# CA2215: Dispose methods should call base class dispose
|
||||||
|
dotnet_diagnostic.CA2215.severity = warning
|
||||||
|
|
||||||
|
# CA2216: Disposable types should declare finalizer
|
||||||
|
dotnet_diagnostic.CA2216.severity = warning
|
||||||
|
|
||||||
|
# CA2217: Do not mark enums with FlagsAttribute
|
||||||
|
dotnet_diagnostic.CA2217.severity = warning
|
||||||
|
|
||||||
|
# CA2219: Do not raise exceptions in finally clauses
|
||||||
|
dotnet_diagnostic.CA2219.severity = warning
|
||||||
|
|
||||||
|
# CA2225: Operator overloads have named alternates
|
||||||
|
dotnet_diagnostic.CA2225.severity = warning
|
||||||
|
|
||||||
|
# CA2226: Operators should have symmetrical overloads
|
||||||
|
dotnet_diagnostic.CA2226.severity = warning
|
||||||
|
|
||||||
|
# CA2227: Collection properties should be read only
|
||||||
|
dotnet_diagnostic.CA2227.severity = warning
|
||||||
|
|
||||||
|
# CA2231: Overload operator equals on overriding value type Equals
|
||||||
|
dotnet_diagnostic.CA2231.severity = warning
|
||||||
|
|
||||||
|
# CA2235: Mark all non-serializable fields
|
||||||
|
dotnet_diagnostic.CA2235.severity = warning
|
||||||
|
|
||||||
|
# CA2237: Mark ISerializable types with serializable
|
||||||
|
dotnet_diagnostic.CA2237.severity = warning
|
||||||
|
|
||||||
|
# CA2241: Provide correct arguments to formatting methods
|
||||||
|
dotnet_diagnostic.CA2241.severity = warning
|
||||||
|
|
||||||
|
# CA2242: Test for NaN correctly
|
||||||
|
dotnet_diagnostic.CA2242.severity = warning
|
||||||
|
|
||||||
|
# CA2243: Attribute string literals should parse correctly
|
||||||
|
dotnet_diagnostic.CA2243.severity = warning
|
||||||
|
|
||||||
|
# CA2244: Do not duplicate indexed element initializations
|
||||||
|
dotnet_diagnostic.CA2244.severity = warning
|
||||||
|
|
||||||
|
# CA2245: Do not assign a property to itself
|
||||||
|
dotnet_diagnostic.CA2245.severity = warning
|
||||||
|
|
||||||
|
# CA2246: Assigning symbol and its member in the same statement
|
||||||
|
dotnet_diagnostic.CA2246.severity = warning
|
||||||
|
|
||||||
|
# CA2247: Argument passed to TaskCompletionSource constructor should be TaskCreationOptions enum instead of TaskContinuationOptions enum
|
||||||
|
dotnet_diagnostic.CA2247.severity = warning
|
||||||
|
|
||||||
|
# CA2248: Provide correct 'enum' argument to 'Enum.HasFlag'
|
||||||
|
dotnet_diagnostic.CA2248.severity = warning
|
||||||
|
|
||||||
|
# CA2249: Consider using 'string.Contains' instead of 'string.IndexOf'
|
||||||
|
dotnet_diagnostic.CA2249.severity = warning
|
||||||
|
|
||||||
|
# CA2250: Use 'ThrowIfCancellationRequested'
|
||||||
|
dotnet_diagnostic.CA2250.severity = warning
|
||||||
|
|
||||||
|
# CA2251: Use 'string.Equals'
|
||||||
|
dotnet_diagnostic.CA2251.severity = warning
|
||||||
|
|
||||||
|
# CA2253: Named placeholders should not be numeric values
|
||||||
|
dotnet_diagnostic.CA2253.severity = warning
|
||||||
|
|
||||||
|
# CA2254: Template should be a static expression
|
||||||
|
dotnet_diagnostic.CA2254.severity = warning
|
||||||
|
|
||||||
|
# CA2255: The 'ModuleInitializer' attribute should not be used in libraries
|
||||||
|
dotnet_diagnostic.CA2255.severity = warning
|
||||||
|
|
||||||
|
# CA2256: All members declared in parent interfaces must have an implementation in a DynamicInterfaceCastableImplementation-attributed interface
|
||||||
|
dotnet_diagnostic.CA2256.severity = warning
|
||||||
|
|
||||||
|
# CA2257: Members defined on an interface with the 'DynamicInterfaceCastableImplementationAttribute' should be 'static'
|
||||||
|
dotnet_diagnostic.CA2257.severity = warning
|
||||||
|
|
||||||
|
# CA2258: Providing a 'DynamicInterfaceCastableImplementation' interface in Visual Basic is unsupported
|
||||||
|
dotnet_diagnostic.CA2258.severity = warning
|
||||||
|
|
||||||
|
# CA2259: 'ThreadStatic' only affects static fields
|
||||||
|
dotnet_diagnostic.CA2259.severity = warning
|
||||||
|
|
||||||
|
# CA2261: Do not use ConfigureAwaitOptions.SuppressThrowing with Task<TResult>
|
||||||
|
dotnet_diagnostic.CA2261.severity = warning
|
||||||
|
|
||||||
|
# CA2300: Do not use insecure deserializer BinaryFormatter
|
||||||
|
dotnet_diagnostic.CA2300.severity = warning
|
||||||
|
|
||||||
|
# CA2301: Do not call BinaryFormatter.Deserialize without first setting BinaryFormatter.Binder
|
||||||
|
dotnet_diagnostic.CA2301.severity = warning
|
||||||
|
|
||||||
|
# CA2302: Ensure BinaryFormatter.Binder is set before calling BinaryFormatter.Deserialize
|
||||||
|
dotnet_diagnostic.CA2302.severity = warning
|
||||||
|
|
||||||
|
# CA2305: Do not use insecure deserializer LosFormatter
|
||||||
|
dotnet_diagnostic.CA2305.severity = warning
|
||||||
|
|
||||||
|
# CA2310: Do not use insecure deserializer NetDataContractSerializer
|
||||||
|
dotnet_diagnostic.CA2310.severity = warning
|
||||||
|
|
||||||
|
# CA2311: Do not deserialize without first setting NetDataContractSerializer.Binder
|
||||||
|
dotnet_diagnostic.CA2311.severity = warning
|
||||||
|
|
||||||
|
# CA2312: Ensure NetDataContractSerializer.Binder is set before deserializing
|
||||||
|
dotnet_diagnostic.CA2312.severity = warning
|
||||||
|
|
||||||
|
# CA2315: Do not use insecure deserializer ObjectStateFormatter
|
||||||
|
dotnet_diagnostic.CA2315.severity = warning
|
||||||
|
|
||||||
|
# CA2321: Do not deserialize with JavaScriptSerializer using a SimpleTypeResolver
|
||||||
|
dotnet_diagnostic.CA2321.severity = warning
|
||||||
|
|
||||||
|
# CA2322: Ensure JavaScriptSerializer is not initialized with SimpleTypeResolver before deserializing
|
||||||
|
dotnet_diagnostic.CA2322.severity = warning
|
||||||
|
|
||||||
|
# CA2326: Do not use TypeNameHandling values other than None
|
||||||
|
dotnet_diagnostic.CA2326.severity = warning
|
||||||
|
|
||||||
|
# CA2327: Do not use insecure JsonSerializerSettings
|
||||||
|
dotnet_diagnostic.CA2327.severity = warning
|
||||||
|
|
||||||
|
# CA2328: Ensure that JsonSerializerSettings are secure
|
||||||
|
dotnet_diagnostic.CA2328.severity = warning
|
||||||
|
|
||||||
|
# CA2329: Do not deserialize with JsonSerializer using an insecure configuration
|
||||||
|
dotnet_diagnostic.CA2329.severity = warning
|
||||||
|
|
||||||
|
# CA2330: Ensure that JsonSerializer has a secure configuration when deserializing
|
||||||
|
dotnet_diagnostic.CA2330.severity = warning
|
||||||
|
|
||||||
|
# CA2350: Do not use DataTable.ReadXml() with untrusted data
|
||||||
|
dotnet_diagnostic.CA2350.severity = warning
|
||||||
|
|
||||||
|
# CA2351: Do not use DataSet.ReadXml() with untrusted data
|
||||||
|
dotnet_diagnostic.CA2351.severity = warning
|
||||||
|
|
||||||
|
# CA2361: Ensure auto-generated class containing DataSet.ReadXml() is not used with untrusted data
|
||||||
|
dotnet_diagnostic.CA2361.severity = warning
|
||||||
|
|
||||||
|
# CA3001: Review code for SQL injection vulnerabilities
|
||||||
|
dotnet_diagnostic.CA3001.severity = warning
|
||||||
|
|
||||||
|
# CA3002: Review code for XSS vulnerabilities
|
||||||
|
dotnet_diagnostic.CA3002.severity = warning
|
||||||
|
|
||||||
|
# CA3003: Review code for file path injection vulnerabilities
|
||||||
|
dotnet_diagnostic.CA3003.severity = warning
|
||||||
|
|
||||||
|
# CA3004: Review code for information disclosure vulnerabilities
|
||||||
|
dotnet_diagnostic.CA3004.severity = warning
|
||||||
|
|
||||||
|
# CA3005: Review code for LDAP injection vulnerabilities
|
||||||
|
dotnet_diagnostic.CA3005.severity = warning
|
||||||
|
|
||||||
|
# CA3006: Review code for process command injection vulnerabilities
|
||||||
|
dotnet_diagnostic.CA3006.severity = warning
|
||||||
|
|
||||||
|
# CA3007: Review code for open redirect vulnerabilities
|
||||||
|
dotnet_diagnostic.CA3007.severity = warning
|
||||||
|
|
||||||
|
# CA3008: Review code for XPath injection vulnerabilities
|
||||||
|
dotnet_diagnostic.CA3008.severity = warning
|
||||||
|
|
||||||
|
# CA3009: Review code for XML injection vulnerabilities
|
||||||
|
dotnet_diagnostic.CA3009.severity = warning
|
||||||
|
|
||||||
|
# CA3010: Review code for XAML injection vulnerabilities
|
||||||
|
dotnet_diagnostic.CA3010.severity = warning
|
||||||
|
|
||||||
|
# CA3011: Review code for DLL injection vulnerabilities
|
||||||
|
dotnet_diagnostic.CA3011.severity = warning
|
||||||
|
|
||||||
|
# CA3012: Review code for regex injection vulnerabilities
|
||||||
|
dotnet_diagnostic.CA3012.severity = warning
|
||||||
|
|
||||||
|
# CA3061: Do Not Add Schema By URL
|
||||||
|
dotnet_diagnostic.CA3061.severity = warning
|
||||||
|
|
||||||
|
# CA3075: Insecure DTD processing in XML
|
||||||
|
dotnet_diagnostic.CA3075.severity = warning
|
||||||
|
|
||||||
|
# CA3076: Insecure XSLT script processing
|
||||||
|
dotnet_diagnostic.CA3076.severity = warning
|
||||||
|
|
||||||
|
# CA3077: Insecure Processing in API Design, XmlDocument and XmlTextReader
|
||||||
|
dotnet_diagnostic.CA3077.severity = warning
|
||||||
|
|
||||||
|
# CA3147: Mark Verb Handlers With Validate Antiforgery Token
|
||||||
|
dotnet_diagnostic.CA3147.severity = warning
|
||||||
|
|
||||||
|
# CA5350: Do Not Use Weak Cryptographic Algorithms
|
||||||
|
dotnet_diagnostic.CA5350.severity = warning
|
||||||
|
|
||||||
|
# CA5351: Do Not Use Broken Cryptographic Algorithms
|
||||||
|
dotnet_diagnostic.CA5351.severity = warning
|
||||||
|
|
||||||
|
# CA5358: Review cipher mode usage with cryptography experts
|
||||||
|
dotnet_diagnostic.CA5358.severity = warning
|
||||||
|
|
||||||
|
# CA5359: Do Not Disable Certificate Validation
|
||||||
|
dotnet_diagnostic.CA5359.severity = warning
|
||||||
|
|
||||||
|
# CA5360: Do Not Call Dangerous Methods In Deserialization
|
||||||
|
dotnet_diagnostic.CA5360.severity = warning
|
||||||
|
|
||||||
|
# CA5361: Do Not Disable SChannel Use of Strong Crypto
|
||||||
|
dotnet_diagnostic.CA5361.severity = warning
|
||||||
|
|
||||||
|
# CA5362: Potential reference cycle in deserialized object graph
|
||||||
|
dotnet_diagnostic.CA5362.severity = warning
|
||||||
|
|
||||||
|
# CA5363: Do Not Disable Request Validation
|
||||||
|
dotnet_diagnostic.CA5363.severity = warning
|
||||||
|
|
||||||
|
# CA5364: Do Not Use Deprecated Security Protocols
|
||||||
|
dotnet_diagnostic.CA5364.severity = warning
|
||||||
|
|
||||||
|
# CA5365: Do Not Disable HTTP Header Checking
|
||||||
|
dotnet_diagnostic.CA5365.severity = warning
|
||||||
|
|
||||||
|
# CA5366: Use XmlReader for 'DataSet.ReadXml()'
|
||||||
|
dotnet_diagnostic.CA5366.severity = warning
|
||||||
|
|
||||||
|
# CA5367: Do Not Serialize Types With Pointer Fields
|
||||||
|
dotnet_diagnostic.CA5367.severity = warning
|
||||||
|
|
||||||
|
# CA5368: Set ViewStateUserKey For Classes Derived From Page
|
||||||
|
dotnet_diagnostic.CA5368.severity = warning
|
||||||
|
|
||||||
|
# CA5369: Use XmlReader for 'XmlSerializer.Deserialize()'
|
||||||
|
dotnet_diagnostic.CA5369.severity = warning
|
||||||
|
|
||||||
|
# CA5370: Use XmlReader for XmlValidatingReader constructor
|
||||||
|
dotnet_diagnostic.CA5370.severity = warning
|
||||||
|
|
||||||
|
# CA5371: Use XmlReader for 'XmlSchema.Read()'
|
||||||
|
dotnet_diagnostic.CA5371.severity = warning
|
||||||
|
|
||||||
|
# CA5372: Use XmlReader for XPathDocument constructor
|
||||||
|
dotnet_diagnostic.CA5372.severity = warning
|
||||||
|
|
||||||
|
# CA5373: Do not use obsolete key derivation function
|
||||||
|
dotnet_diagnostic.CA5373.severity = warning
|
||||||
|
|
||||||
|
# CA5374: Do Not Use XslTransform
|
||||||
|
dotnet_diagnostic.CA5374.severity = warning
|
||||||
|
|
||||||
|
# CA5375: Do Not Use Account Shared Access Signature
|
||||||
|
dotnet_diagnostic.CA5375.severity = warning
|
||||||
|
|
||||||
|
# CA5376: Use SharedAccessProtocol HttpsOnly
|
||||||
|
dotnet_diagnostic.CA5376.severity = warning
|
||||||
|
|
||||||
|
# CA5377: Use Container Level Access Policy
|
||||||
|
dotnet_diagnostic.CA5377.severity = warning
|
||||||
|
|
||||||
|
# CA5378: Do not disable ServicePointManagerSecurityProtocols
|
||||||
|
dotnet_diagnostic.CA5378.severity = warning
|
||||||
|
|
||||||
|
# CA5379: Ensure Key Derivation Function algorithm is sufficiently strong
|
||||||
|
dotnet_diagnostic.CA5379.severity = warning
|
||||||
|
|
||||||
|
# CA5380: Do Not Add Certificates To Root Store
|
||||||
|
dotnet_diagnostic.CA5380.severity = warning
|
||||||
|
|
||||||
|
# CA5381: Ensure Certificates Are Not Added To Root Store
|
||||||
|
dotnet_diagnostic.CA5381.severity = warning
|
||||||
|
|
||||||
|
# CA5382: Use Secure Cookies In ASP.NET Core
|
||||||
|
dotnet_diagnostic.CA5382.severity = warning
|
||||||
|
|
||||||
|
# CA5383: Ensure Use Secure Cookies In ASP.NET Core
|
||||||
|
dotnet_diagnostic.CA5383.severity = warning
|
||||||
|
|
||||||
|
# CA5384: Do Not Use Digital Signature Algorithm (DSA)
|
||||||
|
dotnet_diagnostic.CA5384.severity = warning
|
||||||
|
|
||||||
|
# CA5385: Use Rivest-Shamir-Adleman (RSA) Algorithm With Sufficient Key Size
|
||||||
|
dotnet_diagnostic.CA5385.severity = warning
|
||||||
|
|
||||||
|
# CA5386: Avoid hardcoding SecurityProtocolType value
|
||||||
|
dotnet_diagnostic.CA5386.severity = warning
|
||||||
|
|
||||||
|
# CA5387: Do Not Use Weak Key Derivation Function With Insufficient Iteration Count
|
||||||
|
dotnet_diagnostic.CA5387.severity = warning
|
||||||
|
|
||||||
|
# CA5388: Ensure Sufficient Iteration Count When Using Weak Key Derivation Function
|
||||||
|
dotnet_diagnostic.CA5388.severity = warning
|
||||||
|
|
||||||
|
# CA5389: Do Not Add Archive Item's Path To The Target File System Path
|
||||||
|
dotnet_diagnostic.CA5389.severity = warning
|
||||||
|
|
||||||
|
# CA5390: Do not hard-code encryption key
|
||||||
|
dotnet_diagnostic.CA5390.severity = warning
|
||||||
|
|
||||||
|
# CA5391: Use antiforgery tokens in ASP.NET Core MVC controllers
|
||||||
|
dotnet_diagnostic.CA5391.severity = warning
|
||||||
|
|
||||||
|
# CA5392: Use DefaultDllImportSearchPaths attribute for P/Invokes
|
||||||
|
dotnet_diagnostic.CA5392.severity = warning
|
||||||
|
|
||||||
|
# CA5393: Do not use unsafe DllImportSearchPath value
|
||||||
|
dotnet_diagnostic.CA5393.severity = warning
|
||||||
|
|
||||||
|
# CA5394: Do not use insecure randomness
|
||||||
|
dotnet_diagnostic.CA5394.severity = warning
|
||||||
|
|
||||||
|
# CA5395: Miss HttpVerb attribute for action methods
|
||||||
|
dotnet_diagnostic.CA5395.severity = warning
|
||||||
|
|
||||||
|
# CA5396: Set HttpOnly to true for HttpCookie
|
||||||
|
dotnet_diagnostic.CA5396.severity = warning
|
||||||
|
|
||||||
|
# CA5397: Do not use deprecated SslProtocols values
|
||||||
|
dotnet_diagnostic.CA5397.severity = warning
|
||||||
|
|
||||||
|
# CA5398: Avoid hardcoded SslProtocols values
|
||||||
|
dotnet_diagnostic.CA5398.severity = warning
|
||||||
|
|
||||||
|
# CA5399: HttpClients should enable certificate revocation list checks
|
||||||
|
dotnet_diagnostic.CA5399.severity = warning
|
||||||
|
|
||||||
|
# CA5400: Ensure HttpClient certificate revocation list check is not disabled
|
||||||
|
dotnet_diagnostic.CA5400.severity = warning
|
||||||
|
|
||||||
|
# CA5401: Do not use CreateEncryptor with non-default IV
|
||||||
|
dotnet_diagnostic.CA5401.severity = warning
|
||||||
|
|
||||||
|
# CA5402: Use CreateEncryptor with the default IV
|
||||||
|
dotnet_diagnostic.CA5402.severity = warning
|
||||||
|
|
||||||
|
# CA5403: Do not hard-code certificate
|
||||||
|
dotnet_diagnostic.CA5403.severity = warning
|
||||||
|
|
||||||
|
# CA5404: Do not disable token validation checks
|
||||||
|
dotnet_diagnostic.CA5404.severity = warning
|
||||||
|
|
||||||
|
# CA5405: Do not always skip token validation in delegates
|
||||||
|
dotnet_diagnostic.CA5405.severity = warning
|
||||||
|
|
||||||
|
# CA1032: Implement standard exception constructors
|
||||||
|
dotnet_diagnostic.CA1032.severity = warning
|
||||||
|
|
||||||
|
# CA1200: Avoid using cref tags with a prefix
|
||||||
|
dotnet_diagnostic.CA1200.severity = warning
|
||||||
|
|
||||||
|
# CA1309: Use ordinal string comparison
|
||||||
|
dotnet_diagnostic.CA1309.severity = warning
|
||||||
|
|
||||||
|
# CA1311: Specify a culture or use an invariant version
|
||||||
|
dotnet_diagnostic.CA1311.severity = warning
|
||||||
|
|
||||||
|
# CA1507: Use nameof to express symbol names
|
||||||
|
dotnet_diagnostic.CA1507.severity = warning
|
||||||
|
|
||||||
|
# CA1508: Avoid dead conditional code
|
||||||
|
dotnet_diagnostic.CA1508.severity = warning
|
||||||
|
|
||||||
|
# CA1802: Use literals where appropriate
|
||||||
|
dotnet_diagnostic.CA1802.severity = warning
|
||||||
|
|
||||||
|
# CA1805: Do not initialize unnecessarily
|
||||||
|
dotnet_diagnostic.CA1805.severity = warning
|
||||||
|
|
||||||
|
# CA1812: Avoid uninstantiated internal classes
|
||||||
|
dotnet_diagnostic.CA1812.severity = warning
|
||||||
|
|
||||||
|
# CA1824: Mark assemblies with NeutralResourcesLanguageAttribute
|
||||||
|
dotnet_diagnostic.CA1824.severity = warning
|
||||||
|
|
||||||
|
# CA1825: Avoid zero-length array allocations
|
||||||
|
dotnet_diagnostic.CA1825.severity = warning
|
||||||
|
|
||||||
|
# CA1841: Prefer Dictionary.Contains methods
|
||||||
|
dotnet_diagnostic.CA1841.severity = warning
|
||||||
|
|
||||||
|
# CA1845: Use span-based 'string.Concat'
|
||||||
|
dotnet_diagnostic.CA1845.severity = warning
|
||||||
|
|
||||||
|
# CA1851: Possible multiple enumerations of 'IEnumerable' collection
|
||||||
|
dotnet_diagnostic.CA1851.severity = warning
|
||||||
|
|
||||||
|
# CA1855: Prefer 'Clear' over 'Fill'
|
||||||
|
dotnet_diagnostic.CA1855.severity = warning
|
||||||
|
|
||||||
|
# CA1856: Incorrect usage of ConstantExpected attribute
|
||||||
|
dotnet_diagnostic.CA1856.severity = warning
|
||||||
|
|
||||||
|
# CA1857: A constant is expected for the parameter
|
||||||
|
dotnet_diagnostic.CA1857.severity = warning
|
||||||
|
|
||||||
|
# CA1865: Use char overload
|
||||||
|
dotnet_diagnostic.CA1865.severity = warning
|
||||||
|
|
||||||
|
# CA1866: Use char overload
|
||||||
|
dotnet_diagnostic.CA1866.severity = warning
|
||||||
|
|
||||||
|
# CA1867: Use char overload
|
||||||
|
dotnet_diagnostic.CA1867.severity = warning
|
||||||
|
|
||||||
|
# CA1870: Use a cached 'SearchValues' instance
|
||||||
|
dotnet_diagnostic.CA1870.severity = warning
|
||||||
|
|
||||||
|
# CA2014: Do not use stackalloc in loops
|
||||||
|
dotnet_diagnostic.CA2014.severity = warning
|
||||||
|
|
||||||
|
# CA2016: Forward the 'CancellationToken' parameter to methods
|
||||||
|
dotnet_diagnostic.CA2016.severity = warning
|
||||||
|
|
||||||
|
# CA2020: Prevent behavioral change
|
||||||
|
dotnet_diagnostic.CA2020.severity = warning
|
||||||
|
|
||||||
|
# CA2234: Pass system uri objects instead of strings
|
||||||
|
dotnet_diagnostic.CA2234.severity = warning
|
||||||
|
|
||||||
|
# CA2252: This API requires opting into preview features
|
||||||
|
dotnet_diagnostic.CA2252.severity = warning
|
||||||
|
|
||||||
|
# CA2260: Use correct type parameter
|
||||||
|
dotnet_diagnostic.CA2260.severity = warning
|
||||||
|
|
||||||
|
# CA2352: Unsafe DataSet or DataTable in serializable type can be vulnerable to remote code execution attacks
|
||||||
|
dotnet_diagnostic.CA2352.severity = warning
|
||||||
|
|
||||||
|
# CA2353: Unsafe DataSet or DataTable in serializable type
|
||||||
|
dotnet_diagnostic.CA2353.severity = warning
|
||||||
|
|
||||||
|
# CA2354: Unsafe DataSet or DataTable in deserialized object graph can be vulnerable to remote code execution attacks
|
||||||
|
dotnet_diagnostic.CA2354.severity = warning
|
||||||
|
|
||||||
|
# CA2355: Unsafe DataSet or DataTable type found in deserializable object graph
|
||||||
|
dotnet_diagnostic.CA2355.severity = warning
|
||||||
|
|
||||||
|
# CA2356: Unsafe DataSet or DataTable type in web deserializable object graph
|
||||||
|
dotnet_diagnostic.CA2356.severity = warning
|
||||||
|
|
||||||
|
# CA2362: Unsafe DataSet or DataTable in auto-generated serializable type can be vulnerable to remote code execution attacks
|
||||||
|
dotnet_diagnostic.CA2362.severity = warning
|
22
.forgejo/workflows/lint-build.yml
Normal file
22
.forgejo/workflows/lint-build.yml
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
name: lint-build
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
pull_request:
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
lint-build:
|
||||||
|
name: dotnet${{ matrix.dotnet }}
|
||||||
|
runs-on: docker
|
||||||
|
container: git.frostfs.info/truecloudlab/env:dotnet-${{ matrix.dotnet }}
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
dotnet:
|
||||||
|
- '8.0'
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v3
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
|
||||||
|
# Dotnet runs code analyzers on build (if configured):
|
||||||
|
# https://learn.microsoft.com/en-us/dotnet/fundamentals/code-analysis/overview?tabs=net-8#enable-on-build
|
||||||
|
- run: dotnet build
|
35
.forgejo/workflows/publish.yml
Normal file
35
.forgejo/workflows/publish.yml
Normal file
|
@ -0,0 +1,35 @@
|
||||||
|
on:
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
image:
|
||||||
|
name: Publish NuGet packages
|
||||||
|
runs-on: docker
|
||||||
|
container: git.frostfs.info/truecloudlab/env:dotnet-8.0
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v3
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
|
||||||
|
- name: Build NuGet packages
|
||||||
|
# `dotnet build` implies and replaces `dotnet pack` thanks to `GeneratePackageOnBuild`
|
||||||
|
run: dotnet build
|
||||||
|
|
||||||
|
- name: Publish unsigned NuGet packages
|
||||||
|
run: |-
|
||||||
|
dotnet nuget add source \
|
||||||
|
--name "$NUGET_REGISTRY" \
|
||||||
|
--username "$NUGET_REGISTRY_USER" \
|
||||||
|
--password "$NUGET_REGISTRY_PASSWORD" \
|
||||||
|
--store-password-in-clear-text \
|
||||||
|
"$NUGET_REGISTRY_URL"
|
||||||
|
find -iname '*.nupkg' | grep . | xargs -d'\n' -t -n1 \
|
||||||
|
dotnet nuget push --source "$NUGET_REGISTRY"
|
||||||
|
env:
|
||||||
|
NUGET_REGISTRY: TrueCloudLab
|
||||||
|
NUGET_REGISTRY_URL: https://git.frostfs.info/api/packages/TrueCloudLab/nuget/index.json
|
||||||
|
NUGET_REGISTRY_USER: ${{secrets.NUGET_REGISTRY_USER}}
|
||||||
|
NUGET_REGISTRY_PASSWORD: ${{secrets.NUGET_REGISTRY_PASSWORD}}
|
||||||
|
if: >-
|
||||||
|
startsWith(github.ref, 'refs/tags/v') &&
|
||||||
|
(github.event_name == 'workflow_dispatch' || github.event_name == 'push')
|
8
.gitignore
vendored
8
.gitignore
vendored
|
@ -4,6 +4,7 @@
|
||||||
*.dll
|
*.dll
|
||||||
*.so
|
*.so
|
||||||
*.dylib
|
*.dylib
|
||||||
|
*.pdb
|
||||||
|
|
||||||
# Test binary, built with `go test -c`
|
# Test binary, built with `go test -c`
|
||||||
*.test
|
*.test
|
||||||
|
@ -17,6 +18,7 @@ vendor/
|
||||||
# IDE
|
# IDE
|
||||||
.idea
|
.idea
|
||||||
.vscode
|
.vscode
|
||||||
|
.vs
|
||||||
|
|
||||||
# coverage
|
# coverage
|
||||||
coverage.txt
|
coverage.txt
|
||||||
|
@ -30,4 +32,8 @@ antlr-*.jar
|
||||||
|
|
||||||
# binary
|
# binary
|
||||||
bin/
|
bin/
|
||||||
release/
|
obj/
|
||||||
|
|
||||||
|
# Repository signing keys
|
||||||
|
release/maintainer.*
|
||||||
|
release/ca.*
|
||||||
|
|
3
CODEOWNERS
Normal file
3
CODEOWNERS
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
.* @PavelGrossSpb
|
||||||
|
.forgejo/.* @potyarkin
|
||||||
|
Makefile @potyarkin
|
|
@ -1,12 +1,20 @@
|
||||||
|
|
||||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FrostFS.SDK.ProtosV2", "src\FrostFS.SDK.ProtosV2\FrostFS.SDK.ProtosV2.csproj", "{A087047E-2505-4137-97CC-689A5AD58A0C}"
|
# Visual Studio Version 17
|
||||||
|
VisualStudioVersion = 17.5.002.0
|
||||||
|
MinimumVisualStudioVersion = 10.0.40219.1
|
||||||
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FrostFS.SDK.Client", "src\FrostFS.SDK.Client\FrostFS.SDK.Client.csproj", "{50D8F61F-C302-4AC9-8D8A-AB0B8C0988C3}"
|
||||||
EndProject
|
EndProject
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FrostFS.SDK.ClientV2", "src\FrostFS.SDK.ClientV2\FrostFS.SDK.ClientV2.csproj", "{F9CE6347-111A-4CE6-8BB2-545469838F47}"
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FrostFS.SDK.Cryptography", "src\FrostFS.SDK.Cryptography\FrostFS.SDK.Cryptography.csproj", "{3D804F4A-B0B2-47A5-B006-BE447BE64B50}"
|
||||||
EndProject
|
EndProject
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FrostFS.SDK.Cryptography", "src\FrostFS.SDK.Cryptography\FrostFS.SDK.Cryptography.csproj", "{599E2FF8-12C0-414D-B295-4C971A0A1A63}"
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FrostFS.SDK.Protos", "src\FrostFS.SDK.Protos\FrostFS.SDK.Protos.csproj", "{5012EF96-9C9E-4E77-BC78-B4111EE54107}"
|
||||||
EndProject
|
EndProject
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FrostFS.SDK.ModelsV2", "src\FrostFS.SDK.ModelsV2\FrostFS.SDK.ModelsV2.csproj", "{3F6E5EE6-2AAC-45BE-A5E2-B83D11F90CC3}"
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FrostFS.SDK.Tests", "src\FrostFS.SDK.Tests\FrostFS.SDK.Tests.csproj", "{8FDA7E0D-9C75-4874-988E-6592CD28F76C}"
|
||||||
|
EndProject
|
||||||
|
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{2F030ACD-F87C-4E83-9A68-4CC5DF03AD90}"
|
||||||
|
ProjectSection(SolutionItems) = preProject
|
||||||
|
.editorconfig = .editorconfig
|
||||||
|
EndProjectSection
|
||||||
EndProject
|
EndProject
|
||||||
Global
|
Global
|
||||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
|
@ -14,25 +22,24 @@ Global
|
||||||
Release|Any CPU = Release|Any CPU
|
Release|Any CPU = Release|Any CPU
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||||
{A087047E-2505-4137-97CC-689A5AD58A0C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
{50D8F61F-C302-4AC9-8D8A-AB0B8C0988C3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
{A087047E-2505-4137-97CC-689A5AD58A0C}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
{50D8F61F-C302-4AC9-8D8A-AB0B8C0988C3}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
{A087047E-2505-4137-97CC-689A5AD58A0C}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
{50D8F61F-C302-4AC9-8D8A-AB0B8C0988C3}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
{A087047E-2505-4137-97CC-689A5AD58A0C}.Release|Any CPU.Build.0 = Release|Any CPU
|
{50D8F61F-C302-4AC9-8D8A-AB0B8C0988C3}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
{F9CE6347-111A-4CE6-8BB2-545469838F47}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
{3D804F4A-B0B2-47A5-B006-BE447BE64B50}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
{F9CE6347-111A-4CE6-8BB2-545469838F47}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
{3D804F4A-B0B2-47A5-B006-BE447BE64B50}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
{F9CE6347-111A-4CE6-8BB2-545469838F47}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
{3D804F4A-B0B2-47A5-B006-BE447BE64B50}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
{F9CE6347-111A-4CE6-8BB2-545469838F47}.Release|Any CPU.Build.0 = Release|Any CPU
|
{3D804F4A-B0B2-47A5-B006-BE447BE64B50}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
{B04EBDF5-A446-49D6-B1D7-7D729D94E8E0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
{5012EF96-9C9E-4E77-BC78-B4111EE54107}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
{B04EBDF5-A446-49D6-B1D7-7D729D94E8E0}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
{5012EF96-9C9E-4E77-BC78-B4111EE54107}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
{B04EBDF5-A446-49D6-B1D7-7D729D94E8E0}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
{5012EF96-9C9E-4E77-BC78-B4111EE54107}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
{B04EBDF5-A446-49D6-B1D7-7D729D94E8E0}.Release|Any CPU.Build.0 = Release|Any CPU
|
{5012EF96-9C9E-4E77-BC78-B4111EE54107}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
{599E2FF8-12C0-414D-B295-4C971A0A1A63}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
{8FDA7E0D-9C75-4874-988E-6592CD28F76C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
{599E2FF8-12C0-414D-B295-4C971A0A1A63}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
{8FDA7E0D-9C75-4874-988E-6592CD28F76C}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
{599E2FF8-12C0-414D-B295-4C971A0A1A63}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
{8FDA7E0D-9C75-4874-988E-6592CD28F76C}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
{599E2FF8-12C0-414D-B295-4C971A0A1A63}.Release|Any CPU.Build.0 = Release|Any CPU
|
{8FDA7E0D-9C75-4874-988E-6592CD28F76C}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
{3F6E5EE6-2AAC-45BE-A5E2-B83D11F90CC3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
EndGlobalSection
|
||||||
{3F6E5EE6-2AAC-45BE-A5E2-B83D11F90CC3}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
GlobalSection(SolutionProperties) = preSolution
|
||||||
{3F6E5EE6-2AAC-45BE-A5E2-B83D11F90CC3}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
HideSolutionNode = FALSE
|
||||||
{3F6E5EE6-2AAC-45BE-A5E2-B83D11F90CC3}.Release|Any CPU.Build.0 = Release|Any CPU
|
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
EndGlobal
|
EndGlobal
|
||||||
|
|
58
Makefile
Normal file
58
Makefile
Normal file
|
@ -0,0 +1,58 @@
|
||||||
|
DOTNET?=dotnet
|
||||||
|
DOCKER?=docker
|
||||||
|
|
||||||
|
NUGET_REGISTRY?=TrueCloudLab
|
||||||
|
NUGET_REGISTRY_URL?=https://git.frostfs.info/api/packages/TrueCloudLab/nuget/index.json
|
||||||
|
NUGET_REGISTRY_USER?=
|
||||||
|
NUGET_REGISTRY_PASSWORD?=
|
||||||
|
|
||||||
|
NUPKG=find -iname '*.nupkg' | grep . | xargs -d'\n' -t -r -n1
|
||||||
|
RFC3161_TSA?=http://timestamp.digicert.com
|
||||||
|
|
||||||
|
|
||||||
|
.PHONY: build
|
||||||
|
build:
|
||||||
|
$(DOTNET) build
|
||||||
|
|
||||||
|
|
||||||
|
.PHONY: sign
|
||||||
|
sign: export NUGET_CERT_REVOCATION_MODE=offline
|
||||||
|
sign: release/maintainer.pfx
|
||||||
|
$(NUPKG) $(DOTNET) nuget sign --overwrite --certificate-path $< --timestamper "$(RFC3161_TSA)"
|
||||||
|
@rm -v "$<" # maintainer.pfx is not password protected and must be ephemeral
|
||||||
|
$(NUPKG) $(DOTNET) nuget verify
|
||||||
|
|
||||||
|
|
||||||
|
.PHONY: publish
|
||||||
|
publish:
|
||||||
|
$(NUPKG) $(DOTNET) nuget verify
|
||||||
|
$(NUPKG) $(DOTNET) nuget push --source "$(NUGET_REGISTRY)"
|
||||||
|
|
||||||
|
|
||||||
|
.PHONY: nuget-registry
|
||||||
|
nuget-registry:
|
||||||
|
ifeq (,$(NUGET_REGISTRY_USER))
|
||||||
|
$(error NUGET_REGISTRY_USER not set)
|
||||||
|
endif
|
||||||
|
ifeq (,$(NUGET_REGISTRY_PASSWORD))
|
||||||
|
$(error NUGET_REGISTRY_PASSWORD not set)
|
||||||
|
endif
|
||||||
|
$(DOTNET) nuget add source \
|
||||||
|
--name "$(NUGET_REGISTRY)" \
|
||||||
|
--username "$(NUGET_REGISTRY_USER)" \
|
||||||
|
--password "$(NUGET_REGISTRY_PASSWORD)" \
|
||||||
|
--store-password-in-clear-text \
|
||||||
|
"$(NUGET_REGISTRY_URL)"
|
||||||
|
|
||||||
|
|
||||||
|
.PHONY: clean
|
||||||
|
clean:
|
||||||
|
-$(NUPKG) rm -v
|
||||||
|
|
||||||
|
|
||||||
|
.PHONY: container
|
||||||
|
container:
|
||||||
|
$(DOCKER) run --pull=always --rm -it -v "$$PWD:/src" -w /src git.frostfs.info/truecloudlab/env:dotnet-8.0
|
||||||
|
|
||||||
|
|
||||||
|
include release/codesign.mk
|
102
README.md
102
README.md
|
@ -21,60 +21,62 @@ neo-go wallet export -w <path_to_your_wallet> -d <address_from_p1>
|
||||||
### Container
|
### Container
|
||||||
|
|
||||||
```csharp
|
```csharp
|
||||||
|
using FrostFS.SDK;
|
||||||
using FrostFS.SDK.ClientV2;
|
using FrostFS.SDK.ClientV2;
|
||||||
using FrostFS.SDK.ModelsV2;
|
|
||||||
using FrostFS.SDK.ModelsV2.Enums;
|
|
||||||
using FrostFS.SDK.ModelsV2.Netmap;
|
|
||||||
|
|
||||||
var fsClient = new Client(<your_key>, <your_host>);
|
using Microsoft.Extensions.Options;
|
||||||
|
|
||||||
// List containers
|
var Key = "KwHDAJ66o8FoLBjVbjP2sWBmgBMGjt7Vv4boA7xQrBoAYBE397Aq";
|
||||||
var containersIds = await fsClient.ListContainersAsync();
|
var Host = "http://172.22.33.44:8080";
|
||||||
|
|
||||||
// Create container
|
var options = Options.Create(new SingleOwnerClientSettings
|
||||||
var placementPolicy = new PlacementPolicy(true, new Replica(1));
|
{
|
||||||
var containerId = await fsClient.CreateContainerAsync(
|
Key = Key,
|
||||||
new Container(
|
Host = Host
|
||||||
BasicAcl.PublicRW,
|
});
|
||||||
placementPolicy
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
// Get container
|
using var client = Client.GetSingleOwnerInstance(options);
|
||||||
var container = await fsClient.GetContainerAsync(cId);
|
|
||||||
|
|
||||||
// Delete container
|
await foreach (var cid in client.ListContainersAsync())
|
||||||
await fsClient.DeleteContainerAsync(containerId);
|
{
|
||||||
```
|
await client.DeleteContainerAsync(new PrmContainerDelete(cid));
|
||||||
|
}
|
||||||
### Object
|
|
||||||
|
var placementPolicy = new FrostFsPlacementPolicy(true, new FrostFsReplica(1));
|
||||||
```csharp
|
|
||||||
using FrostFS.SDK.ClientV2;
|
var createContainerParam = new PrmContainerCreate(
|
||||||
using FrostFS.SDK.ModelsV2;
|
new FrostFsContainerInfo(BasicAcl.PublicRW, new FrostFsPlacementPolicy(true, new FrostFsReplica(1))));
|
||||||
using FrostFS.SDK.ModelsV2.Enums;
|
|
||||||
using FrostFS.SDK.ModelsV2.Netmap;
|
var containerId = await client.PutContainerAsync(createContainerParam);
|
||||||
|
|
||||||
var fsClient = new Client(<your_key>, <your_host>);
|
using var fileStream = File.OpenRead(@"cat.jpeg");
|
||||||
|
|
||||||
// Search regular objects
|
var param = new PrmObjectPut
|
||||||
var objectsIds = await fsClient.SearchObjectAsync(
|
{
|
||||||
cId,
|
Header = new FrostFsObjectHeader(
|
||||||
ObjectFilter.RootFilter()
|
containerId: containerId,
|
||||||
);
|
type: FrostFsObjectType.Regular,
|
||||||
|
[new FrostFsAttribute("fileName", "test")]),
|
||||||
// Put object
|
Payload = fileStream
|
||||||
var f = File.OpenRead("cat.jpg");
|
};
|
||||||
var cat = new ObjectHeader(
|
|
||||||
containerId: cId,
|
FrostFsObjectId objectId = await client.PutObjectAsync(param);
|
||||||
type: ObjectType.Regular,
|
|
||||||
new ObjectAttribute("Filename", "cat.jpg")
|
var filter = new FilterByAttribute(FrostFsMatchType.Equals, "fileName", "test");
|
||||||
);
|
|
||||||
var oId = await fsClient.PutObjectAsync(cat, f);
|
await foreach (var objId in client.SearchObjectsAsync(new PrmObjectSearch(containerId) { Filters = [filter] }))
|
||||||
|
{
|
||||||
// Get object header
|
var objHeader = await client.GetObjectHeadAsync(new PrmObjectHeadGet(containerId, objId));
|
||||||
var objHeader = await fsClient.GetObjectHeadAsync(cId, oId);
|
}
|
||||||
|
|
||||||
// Get object
|
var @object = await client.GetObjectAsync(new PrmObjectGet(containerId, objectId));
|
||||||
var obj = await fsClient.GetObjectAsync(cId, oId);
|
|
||||||
|
var downloadedBytes = new byte[@object.Header.PayloadLength];
|
||||||
|
MemoryStream ms = new(downloadedBytes);
|
||||||
|
|
||||||
|
ReadOnlyMemory<byte>? chunk = null;
|
||||||
|
while ((chunk = await @object.ObjectReader!.ReadChunk()) != null)
|
||||||
|
{
|
||||||
|
ms.Write(chunk.Value.Span);
|
||||||
|
}
|
||||||
```
|
```
|
BIN
keyfile.snk
Normal file
BIN
keyfile.snk
Normal file
Binary file not shown.
82
release/README.md
Normal file
82
release/README.md
Normal file
|
@ -0,0 +1,82 @@
|
||||||
|
# Release process
|
||||||
|
|
||||||
|
## Preparing release
|
||||||
|
|
||||||
|
_TBD_
|
||||||
|
|
||||||
|
## Trusting TrueCloudLab code signing CA certificate
|
||||||
|
|
||||||
|
Verifying signatures (and signing) TrueCloudLab packages requires adding
|
||||||
|
[TrueCloudLab Code Signing CA](ca.cert) to the list of trusted roots.
|
||||||
|
|
||||||
|
On Linux this can be done by appending [release/ca.cert](ca.cert) to one of:
|
||||||
|
|
||||||
|
- `/etc/pki/ca-trust/extracted/pem/objsign-ca-bundle.pem`: compatible with
|
||||||
|
[update-ca-trust] and originally proposed in [.NET design docs]
|
||||||
|
- `…/dotnet/sdk/X.Y.ZZZ/trustedroots/codesignctl.pem`: [fallback] codesigning certificate trust list for .NET
|
||||||
|
|
||||||
|
[update-ca-trust]: https://www.linux.org/docs/man8/update-ca-trust.html
|
||||||
|
[.NET design docs]: https://github.com/dotnet/designs/blob/main/accepted/2021/signed-package-verification/re-enable-signed-package-verification-technical.md#linux
|
||||||
|
[fallback]: https://github.com/dotnet/sdk/blob/11150c0ec9020625308edeec555a8b78dbfb2aa5/src/Layout/redist/trustedroots/README.md
|
||||||
|
|
||||||
|
## Signing Nuget packages
|
||||||
|
|
||||||
|
Repository maintainer places `maintainer.cert` and `maintainer.key` (see below
|
||||||
|
regarding obtaining these files) into `release/` directory and then
|
||||||
|
executes:
|
||||||
|
|
||||||
|
```console
|
||||||
|
$ make build sign
|
||||||
|
```
|
||||||
|
|
||||||
|
## Uploading packages to Nuget registry
|
||||||
|
|
||||||
|
**IMPORTANT: the following steps upload all `*.nupkg` files located under
|
||||||
|
`src/`. Maintainer MUST make sure that no unnecessary package versions will be
|
||||||
|
uploaded to the registry.**
|
||||||
|
|
||||||
|
Configure registry credentials (once per machine):
|
||||||
|
|
||||||
|
```console
|
||||||
|
$ make nuget-registry NUGET_REGISTRY_USER=username NUGET_REGISTRY_PASSWORD=token
|
||||||
|
```
|
||||||
|
|
||||||
|
Publish all locally built packages (implicitly clear existing `*.nupkg` and
|
||||||
|
rebuild current version only):
|
||||||
|
|
||||||
|
```console
|
||||||
|
$ make clean build sign publish
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
## Obtaining release signing certificate
|
||||||
|
|
||||||
|
Repository maintainer owns and keeps safe the release signing key
|
||||||
|
(`maintainer.key`). Private key should never leave maintainer's machine and
|
||||||
|
should be considered a highly sensitive secret.
|
||||||
|
|
||||||
|
- Generating new maintainer key and the corresponding CSR:
|
||||||
|
|
||||||
|
```console
|
||||||
|
$ make maintainer.csr
|
||||||
|
...lines skipped...
|
||||||
|
Enter PEM pass phrase:
|
||||||
|
Verifying - Enter PEM pass phrase:
|
||||||
|
-----
|
||||||
|
IMPORTANT: Keep maintainer.key private!
|
||||||
|
|
||||||
|
Certificate signing request is ready.
|
||||||
|
Send maintainer.csr to CA administrator to obtain the certificate.
|
||||||
|
```
|
||||||
|
|
||||||
|
Resulting CSR (`maintainer.csr`) does not contain any sensitive
|
||||||
|
cryptographic material and may be passed to CA administrator through regular
|
||||||
|
communication channels.
|
||||||
|
|
||||||
|
- CA administrator then issues the certificate (`make maintainer.cert`) and
|
||||||
|
sends it back to the maintainer to be used in combination with
|
||||||
|
`maintainer.key`
|
||||||
|
|
||||||
|
This procedure should be repeated once per machine per `maintainer.cert`
|
||||||
|
lifetime (1 year) - typically just once per year since we expect the
|
||||||
|
maintainer to use only a single computer to sign releases.
|
34
release/ca.cert
Normal file
34
release/ca.cert
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
-----BEGIN CERTIFICATE-----
|
||||||
|
MIIF2zCCA8OgAwIBAgIUa9xC/RgvFtUG/xeR016nn0B4K0YwDQYJKoZIhvcNAQEL
|
||||||
|
BQAwdTELMAkGA1UEBhMCUlUxFTATBgNVBAoMDFRydWVDbG91ZExhYjEVMBMGA1UE
|
||||||
|
CwwMVHJ1ZUNsb3VkTGFiMTgwNgYDVQQDDC9UcnVlQ2xvdWRMYWIgQ29kZSBTaWdu
|
||||||
|
aW5nIENlcnRpZmljYXRlIEF1dGhvcml0eTAeFw0yNTA0MTAxNTI2MTFaFw0zNTA0
|
||||||
|
MDgxNTI2MTFaMHUxCzAJBgNVBAYTAlJVMRUwEwYDVQQKDAxUcnVlQ2xvdWRMYWIx
|
||||||
|
FTATBgNVBAsMDFRydWVDbG91ZExhYjE4MDYGA1UEAwwvVHJ1ZUNsb3VkTGFiIENv
|
||||||
|
ZGUgU2lnbmluZyBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkwggIiMA0GCSqGSIb3DQEB
|
||||||
|
AQUAA4ICDwAwggIKAoICAQCyANB4cjf+ZEAFx9RiUYXCAOPMV+jyqgcVbhzh2YKc
|
||||||
|
9SlvGKRlc00Ar1RlFcrycdkIrTKmhhobiWsFp7UgphwLqRTCb5NB6qfUoWhnfiD9
|
||||||
|
m0OBgeVX5wivVaibRI9PSTbFDcIhYUiNvwFJ6GduH/9zOxf1BvuL7LMaoyhIDcg/
|
||||||
|
XVLuekE2lnX83zsedv0v/2jyyMY9Ct6N2BXzyHSAzSdYYg0F9Qu9fIMAPjoKhWPc
|
||||||
|
PnotqaACjb1DScLUr3E/o2W1FfprTT2Pip/0AXxO4wixl4QWh9HeOKV22KRcCHo6
|
||||||
|
3wNdg5q1ZVGTNBW0+yoB4jsSG8/JM+2Ujhc1ZnYH10armvGq/0Oc2YQE00960Wy8
|
||||||
|
t0drCFWJUO1XHNeBxkkupmj7N1TAPbixtfiGZJhECOWOJwyMpcKixlt5P0cNH4N/
|
||||||
|
p3vjyrGQxGLBIkgV/QgjfGkpTHKT1/H40YK6DliWJc01KfNTqn0K+/TIyF0n26kD
|
||||||
|
BWYVlvDh5P1+V9DGuD2zeXB3PstoifD/Pd7D8wuqpm17noFE19MLp94xv03q9nEa
|
||||||
|
jRMEd2J2buTLvMh5BBVH0Sm38QAHpSIZ9O3dSLvvjlALbVtwmcsNE9fgxiue3vTB
|
||||||
|
iXNW8wWs+/DMYwbWyBoCwORxVOdOyc1JLn7qAAEUBweilPVXpWuzMLdUsifPiqrV
|
||||||
|
dQIDAQABo2MwYTAdBgNVHQ4EFgQUEz4y/RvQMmbUFvf5JbGe/0PZR90wHwYDVR0j
|
||||||
|
BBgwFoAUEz4y/RvQMmbUFvf5JbGe/0PZR90wDwYDVR0TAQH/BAUwAwEB/zAOBgNV
|
||||||
|
HQ8BAf8EBAMCAgQwDQYJKoZIhvcNAQELBQADggIBAF79W9hMGnrKUWLDOcoeXDEn
|
||||||
|
+gZxd5rjeF0tNEQGbWKGTJAGQdprOkzWQ47PewpFeS+ePpjOglBAt7cV2ZnOugAT
|
||||||
|
Brx31vYpNAvYnUCYn+/IUqD8S/U4uErVS9zG9QjirZWo56eJP62vnScKuApCQCbA
|
||||||
|
pp0zrIyJ+2lQKzlMOENRqzYYA/UTOqYTtnW6x2D8goVqCmDXpgpxEp5XliFjJSr6
|
||||||
|
dOjiopNWMvaV3R/Bnd4i41taM7M6HpIV+gbXmEKEFS0ejVfzT8z1pTigN7GBqbxf
|
||||||
|
nXD03eLUIsbMDv4ZQPrheN7nKnjRUn8kxz0SSK1m2YDrXW51m8fOs6aTvwC/cNe+
|
||||||
|
FJMqQMF32i4IXVfUbyUJi+JMvawqm2wEY46vrh7siprY6rXsAzCKJo07i6jvUwnT
|
||||||
|
TXMldSCPgTEqzT2JBzzr0tRfuPKsv0/NqflHvwfuMRCpcZ7jJZ700iN92xXkiQHP
|
||||||
|
DmCZOILXcNclAth3nAnyY4XE5a8myv8bwYaPdJdIFlV+BoU/8mClDeA8ck4rDy12
|
||||||
|
T5YChKew2oiL4j4B6v9/yrDjD1IT0gv4BWyPhb/n390BCEXt8g9auNcT0s6O8kEc
|
||||||
|
VUDVc1519ocMCuWVuqUK9o2w0zu50/pBn4hVLfT3QyW8sqtlRKghOWtqZzigvCWF
|
||||||
|
VjATeO5F/Z7OSDebHUGv
|
||||||
|
-----END CERTIFICATE-----
|
74
release/codesign.mk
Normal file
74
release/codesign.mk
Normal file
|
@ -0,0 +1,74 @@
|
||||||
|
PKI_ROLE?=maintainer
|
||||||
|
PKI_DIR?=release
|
||||||
|
|
||||||
|
# Note: Only RSA signatures are supported (NU3013)
|
||||||
|
# https://learn.microsoft.com/en-us/nuget/reference/errors-and-warnings/nu3013)
|
||||||
|
|
||||||
|
|
||||||
|
ifeq ($(PKI_ROLE),maintainer)
|
||||||
|
.PHONY: maintainer.csr
|
||||||
|
maintainer.csr: $(PKI_DIR)/maintainer.csr
|
||||||
|
$(PKI_DIR)/maintainer.csr: KEY=$(patsubst %.csr,%.key,$@)
|
||||||
|
$(PKI_DIR)/maintainer.csr:
|
||||||
|
openssl req \
|
||||||
|
-new \
|
||||||
|
-newkey rsa:4096 \
|
||||||
|
-keyout $(KEY) \
|
||||||
|
-out $@ \
|
||||||
|
-sha256 \
|
||||||
|
-addext keyUsage=critical,digitalSignature \
|
||||||
|
-addext extendedKeyUsage=critical,codeSigning,msCodeCom \
|
||||||
|
-subj "/C=RU/O=TrueCloudLab/OU=TrueCloudLab/CN=frostfs-sdk-csharp Release Team"
|
||||||
|
@echo "IMPORTANT: Keep $(KEY) private!\n"
|
||||||
|
@echo "Certificate signing request is ready.\nSend $@ to CA administrator to obtain the certificate."
|
||||||
|
|
||||||
|
$(PKI_DIR)/maintainer.pfx: $(PKI_DIR)/maintainer.cert $(PKI_DIR)/maintainer.key $(PKI_DIR)/ca.cert
|
||||||
|
openssl verify \
|
||||||
|
-CAfile $(PKI_DIR)/ca.cert \
|
||||||
|
$(PKI_DIR)/maintainer.cert
|
||||||
|
openssl pkcs12 \
|
||||||
|
-export \
|
||||||
|
-out $@ \
|
||||||
|
-inkey $(PKI_DIR)/maintainer.key \
|
||||||
|
-in $(PKI_DIR)/maintainer.cert \
|
||||||
|
-CAfile $(PKI_DIR)/ca.cert \
|
||||||
|
-chain \
|
||||||
|
-passout pass:
|
||||||
|
endif
|
||||||
|
|
||||||
|
|
||||||
|
ifeq ($(PKI_ROLE),ca)
|
||||||
|
.PHONY: maintainer.cert
|
||||||
|
maintainer.cert: $(PKI_DIR)/maintainer.cert
|
||||||
|
$(PKI_DIR)/maintainer.cert: CSR=$(patsubst %.cert,%.csr,$@)
|
||||||
|
$(PKI_DIR)/maintainer.cert: $(PKI_DIR)/ca.key $(PKI_DIR)/ca.cert
|
||||||
|
openssl req -noout -text -in $(CSR)
|
||||||
|
@read -p "Review the CSR above. Press Enter to continue, Ctrl+C to cancel
" -r null
|
||||||
|
openssl x509 \
|
||||||
|
-req \
|
||||||
|
-days 365 \
|
||||||
|
-in $(CSR) \
|
||||||
|
-copy_extensions copy \
|
||||||
|
-ext keyUsage,extendedKeyUsage \
|
||||||
|
-CA $(PKI_DIR)/ca.cert \
|
||||||
|
-CAkey $(PKI_DIR)/ca.key \
|
||||||
|
-CAcreateserial \
|
||||||
|
-out $@
|
||||||
|
echo >> $@
|
||||||
|
cat $(PKI_DIR)/ca.cert >> $@
|
||||||
|
openssl x509 -noout -text -in $@ -fingerprint -sha256
|
||||||
|
@echo "Certificate is ready.\nSend $@ back to maintainer."
|
||||||
|
|
||||||
|
$(PKI_DIR)/ca.key: CERT=$(patsubst %.key,%.cert,$@)
|
||||||
|
$(PKI_DIR)/ca.key:
|
||||||
|
openssl req \
|
||||||
|
-x509 \
|
||||||
|
-newkey rsa:4096 \
|
||||||
|
-keyout $@ \
|
||||||
|
-out $(CERT) \
|
||||||
|
-sha256 \
|
||||||
|
-days 3650 \
|
||||||
|
-addext keyUsage=critical,keyCertSign \
|
||||||
|
-subj "/C=RU/O=TrueCloudLab/OU=TrueCloudLab/CN=TrueCloudLab Code Signing Certificate Authority"
|
||||||
|
@echo "IMPORTANT: Keep $@ private!\n"
|
||||||
|
endif
|
36
src/FrostFS.SDK.Client/ApeRules/Actions.cs
Normal file
36
src/FrostFS.SDK.Client/ApeRules/Actions.cs
Normal file
|
@ -0,0 +1,36 @@
|
||||||
|
namespace FrostFS.SDK.Client;
|
||||||
|
|
||||||
|
public struct Actions(bool inverted, string[] names) : System.IEquatable<Actions>
|
||||||
|
{
|
||||||
|
public bool Inverted { get; set; } = inverted;
|
||||||
|
|
||||||
|
public string[] Names { get; set; } = names;
|
||||||
|
|
||||||
|
public override readonly bool Equals(object obj)
|
||||||
|
{
|
||||||
|
if (obj == null || obj is not Actions)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
return Equals((Actions)obj);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override readonly int GetHashCode()
|
||||||
|
{
|
||||||
|
return Inverted.GetHashCode() ^ string.Join(string.Empty, Names).GetHashCode();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool operator ==(Actions left, Actions right)
|
||||||
|
{
|
||||||
|
return left.Equals(right);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool operator !=(Actions left, Actions right)
|
||||||
|
{
|
||||||
|
return !(left == right);
|
||||||
|
}
|
||||||
|
|
||||||
|
public readonly bool Equals(Actions other)
|
||||||
|
{
|
||||||
|
return this.GetHashCode().Equals(other.GetHashCode());
|
||||||
|
}
|
||||||
|
}
|
63
src/FrostFS.SDK.Client/ApeRules/ChainTarget.cs
Normal file
63
src/FrostFS.SDK.Client/ApeRules/ChainTarget.cs
Normal file
|
@ -0,0 +1,63 @@
|
||||||
|
using System;
|
||||||
|
|
||||||
|
using Frostfs.V2.Ape;
|
||||||
|
|
||||||
|
namespace FrostFS.SDK.Client;
|
||||||
|
|
||||||
|
public struct FrostFsChainTarget(FrostFsTargetType type, string name) : IEquatable<FrostFsChainTarget>
|
||||||
|
{
|
||||||
|
private ChainTarget? chainTarget;
|
||||||
|
|
||||||
|
public FrostFsTargetType Type { get; } = type;
|
||||||
|
|
||||||
|
public string Name { get; } = name;
|
||||||
|
|
||||||
|
internal ChainTarget GetChainTarget()
|
||||||
|
{
|
||||||
|
return chainTarget ??= new ChainTarget
|
||||||
|
{
|
||||||
|
Type = GetTargetType(Type),
|
||||||
|
Name = Name
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private static TargetType GetTargetType(FrostFsTargetType type)
|
||||||
|
{
|
||||||
|
return type switch
|
||||||
|
{
|
||||||
|
FrostFsTargetType.Undefined => TargetType.Undefined,
|
||||||
|
FrostFsTargetType.Namespace => TargetType.Namespace,
|
||||||
|
FrostFsTargetType.Container => TargetType.Container,
|
||||||
|
FrostFsTargetType.User => TargetType.User,
|
||||||
|
FrostFsTargetType.Group => TargetType.Group,
|
||||||
|
_ => throw new ArgumentException("Unexpected value for TargetType", nameof(type)),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public override readonly bool Equals(object obj)
|
||||||
|
{
|
||||||
|
if (obj == null || obj is not FrostFsChainTarget)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
return Equals((FrostFsChainTarget)obj);
|
||||||
|
}
|
||||||
|
public readonly bool Equals(FrostFsChainTarget other)
|
||||||
|
{
|
||||||
|
return Type == other.Type && Name.Equals(other.Name, StringComparison.Ordinal);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override readonly int GetHashCode()
|
||||||
|
{
|
||||||
|
return Name.GetHashCode() ^ (int)Type;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool operator ==(FrostFsChainTarget left, FrostFsChainTarget right)
|
||||||
|
{
|
||||||
|
return left.Equals(right);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool operator !=(FrostFsChainTarget left, FrostFsChainTarget right)
|
||||||
|
{
|
||||||
|
return !(left == right);
|
||||||
|
}
|
||||||
|
}
|
43
src/FrostFS.SDK.Client/ApeRules/Condition.cs
Normal file
43
src/FrostFS.SDK.Client/ApeRules/Condition.cs
Normal file
|
@ -0,0 +1,43 @@
|
||||||
|
namespace FrostFS.SDK.Client;
|
||||||
|
|
||||||
|
public struct Condition : System.IEquatable<Condition>
|
||||||
|
{
|
||||||
|
public ConditionType Op { get; set; }
|
||||||
|
|
||||||
|
public ConditionKindType Kind { get; set; }
|
||||||
|
|
||||||
|
public string? Key { get; set; }
|
||||||
|
|
||||||
|
public string? Value { get; set; }
|
||||||
|
|
||||||
|
public override bool Equals(object obj)
|
||||||
|
{
|
||||||
|
if (obj == null || obj is not Condition)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
return Equals((Condition)obj);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override readonly int GetHashCode()
|
||||||
|
{
|
||||||
|
return Op.GetHashCode()
|
||||||
|
^ Kind.GetHashCode()
|
||||||
|
^ (Key != null ? Key.GetHashCode() : 0)
|
||||||
|
^ (Value != null ? Value.GetHashCode() : 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool operator ==(Condition left, Condition right)
|
||||||
|
{
|
||||||
|
return left.Equals(right);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool operator !=(Condition left, Condition right)
|
||||||
|
{
|
||||||
|
return !(left == right);
|
||||||
|
}
|
||||||
|
|
||||||
|
public readonly bool Equals(Condition other)
|
||||||
|
{
|
||||||
|
return this.GetHashCode().Equals(other.GetHashCode());
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,7 @@
|
||||||
|
namespace FrostFS.SDK.Client;
|
||||||
|
|
||||||
|
public enum ConditionKindType
|
||||||
|
{
|
||||||
|
Resource,
|
||||||
|
Request
|
||||||
|
}
|
36
src/FrostFS.SDK.Client/ApeRules/Enums/ConditionType.cs
Normal file
36
src/FrostFS.SDK.Client/ApeRules/Enums/ConditionType.cs
Normal file
|
@ -0,0 +1,36 @@
|
||||||
|
namespace FrostFS.SDK.Client;
|
||||||
|
|
||||||
|
public enum ConditionType
|
||||||
|
{
|
||||||
|
CondStringEquals,
|
||||||
|
|
||||||
|
CondStringNotEquals,
|
||||||
|
CondStringEqualsIgnoreCase,
|
||||||
|
|
||||||
|
CondStringNotEqualsIgnoreCase,
|
||||||
|
CondStringLike,
|
||||||
|
|
||||||
|
CondStringNotLike,
|
||||||
|
CondStringLessThan,
|
||||||
|
|
||||||
|
CondStringLessThanEquals,
|
||||||
|
CondStringGreaterThan,
|
||||||
|
|
||||||
|
CondStringGreaterThanEquals,
|
||||||
|
|
||||||
|
// Numeric condition operators.
|
||||||
|
CondNumericEquals,
|
||||||
|
|
||||||
|
CondNumericNotEquals,
|
||||||
|
CondNumericLessThan,
|
||||||
|
|
||||||
|
CondNumericLessThanEquals,
|
||||||
|
CondNumericGreaterThan,
|
||||||
|
|
||||||
|
CondNumericGreaterThanEquals,
|
||||||
|
|
||||||
|
CondSliceContains,
|
||||||
|
|
||||||
|
CondIPAddress,
|
||||||
|
CondNotIPAddress,
|
||||||
|
}
|
10
src/FrostFS.SDK.Client/ApeRules/Enums/FrostFsTargetType.cs
Normal file
10
src/FrostFS.SDK.Client/ApeRules/Enums/FrostFsTargetType.cs
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
namespace FrostFS.SDK.Client;
|
||||||
|
|
||||||
|
public enum FrostFsTargetType
|
||||||
|
{
|
||||||
|
Undefined,
|
||||||
|
Namespace,
|
||||||
|
Container,
|
||||||
|
User,
|
||||||
|
Group
|
||||||
|
}
|
9
src/FrostFS.SDK.Client/ApeRules/Enums/Status.cs
Normal file
9
src/FrostFS.SDK.Client/ApeRules/Enums/Status.cs
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
namespace FrostFS.SDK.Client;
|
||||||
|
|
||||||
|
public enum RuleStatus
|
||||||
|
{
|
||||||
|
Allow,
|
||||||
|
NoRuleFound,
|
||||||
|
AccessDenied,
|
||||||
|
QuotaLimitReached
|
||||||
|
}
|
10
src/FrostFS.SDK.Client/ApeRules/FrostFsChain.cs
Normal file
10
src/FrostFS.SDK.Client/ApeRules/FrostFsChain.cs
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
namespace FrostFS.SDK.Client;
|
||||||
|
|
||||||
|
public class FrostFsChain
|
||||||
|
{
|
||||||
|
public byte[] ID { get; set; } = [];
|
||||||
|
|
||||||
|
public FrostFsRule[] Rules { get; set; } = [];
|
||||||
|
|
||||||
|
public RuleMatchType MatchType { get; set; }
|
||||||
|
}
|
18
src/FrostFS.SDK.Client/ApeRules/FrostFsRule.cs
Normal file
18
src/FrostFS.SDK.Client/ApeRules/FrostFsRule.cs
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
namespace FrostFS.SDK.Client;
|
||||||
|
|
||||||
|
public class FrostFsRule
|
||||||
|
{
|
||||||
|
public RuleStatus Status { get; set; }
|
||||||
|
|
||||||
|
// Actions the operation is applied to.
|
||||||
|
public Actions Actions { get; set; }
|
||||||
|
|
||||||
|
// List of the resources the operation is applied to.
|
||||||
|
public Resources Resources { get; set; }
|
||||||
|
|
||||||
|
// True iff individual conditions must be combined with the logical OR.
|
||||||
|
// By default AND is used, so _each_ condition must pass.
|
||||||
|
public bool Any { get; set; }
|
||||||
|
|
||||||
|
public Condition[]? Conditions { get; set; }
|
||||||
|
}
|
10
src/FrostFS.SDK.Client/ApeRules/MatchType.cs
Normal file
10
src/FrostFS.SDK.Client/ApeRules/MatchType.cs
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
namespace FrostFS.SDK.Client;
|
||||||
|
|
||||||
|
public enum RuleMatchType
|
||||||
|
{
|
||||||
|
// DenyPriority rejects the request if any `Deny` is specified.
|
||||||
|
DenyPriority,
|
||||||
|
|
||||||
|
// FirstMatch returns the first rule action matched to the request.
|
||||||
|
FirstMatch
|
||||||
|
}
|
36
src/FrostFS.SDK.Client/ApeRules/Resources.cs
Normal file
36
src/FrostFS.SDK.Client/ApeRules/Resources.cs
Normal file
|
@ -0,0 +1,36 @@
|
||||||
|
namespace FrostFS.SDK.Client;
|
||||||
|
|
||||||
|
public struct Resources(bool inverted, string[] names) : System.IEquatable<Resources>
|
||||||
|
{
|
||||||
|
public bool Inverted { get; set; } = inverted;
|
||||||
|
|
||||||
|
public string[] Names { get; set; } = names;
|
||||||
|
|
||||||
|
public override readonly bool Equals(object obj)
|
||||||
|
{
|
||||||
|
if (obj == null || obj is not Resources)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
return Equals((Resources)obj);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override readonly int GetHashCode()
|
||||||
|
{
|
||||||
|
return Inverted.GetHashCode() ^ string.Join(string.Empty, Names).GetHashCode();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool operator ==(Resources left, Resources right)
|
||||||
|
{
|
||||||
|
return left.Equals(right);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool operator !=(Resources left, Resources right)
|
||||||
|
{
|
||||||
|
return !(left == right);
|
||||||
|
}
|
||||||
|
|
||||||
|
public readonly bool Equals(Resources other)
|
||||||
|
{
|
||||||
|
return this.GetHashCode().Equals(other.GetHashCode());
|
||||||
|
}
|
||||||
|
}
|
502
src/FrostFS.SDK.Client/ApeRules/RuleSerializer.cs
Normal file
502
src/FrostFS.SDK.Client/ApeRules/RuleSerializer.cs
Normal file
|
@ -0,0 +1,502 @@
|
||||||
|
using System;
|
||||||
|
|
||||||
|
namespace FrostFS.SDK.Client;
|
||||||
|
|
||||||
|
internal static class RuleSerializer
|
||||||
|
{
|
||||||
|
const byte Version = 0; // increase if breaking change
|
||||||
|
const int ByteSize = 1;
|
||||||
|
const int UInt8Size = ByteSize;
|
||||||
|
const int BoolSize = ByteSize;
|
||||||
|
const long NullSlice = -1;
|
||||||
|
const int NullSliceSize = 1;
|
||||||
|
const byte ByteTrue = 1;
|
||||||
|
const byte ByteFalse = 0;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// maxSliceLen taken from https://github.com/neo-project/neo/blob/38218bbee5bbe8b33cd8f9453465a19381c9a547/src/Neo/IO/Helper.cs#L77
|
||||||
|
/// </summary>
|
||||||
|
const int MaxSliceLen = 0x1000000;
|
||||||
|
|
||||||
|
const int ChainMarshalVersion = 0;
|
||||||
|
|
||||||
|
internal static byte[] Serialize(FrostFsChain chain)
|
||||||
|
{
|
||||||
|
int s = UInt8Size // Marshaller version
|
||||||
|
+ UInt8Size // Chain version
|
||||||
|
+ SliceSize(chain.ID, b => ByteSize)
|
||||||
|
+ SliceSize(chain.Rules, RuleSize)
|
||||||
|
+ UInt8Size; // MatchType
|
||||||
|
|
||||||
|
byte[] buf = new byte[s];
|
||||||
|
|
||||||
|
int offset = UInt8Marshal(buf, 0, Version);
|
||||||
|
offset = UInt8Marshal(buf, offset, ChainMarshalVersion);
|
||||||
|
offset = SliceMarshal(buf, offset, chain.ID, ByteMarshal);
|
||||||
|
offset = SliceMarshal(buf, offset, chain.Rules, MarshalRule);
|
||||||
|
offset = UInt8Marshal(buf, offset, (byte)chain.MatchType);
|
||||||
|
|
||||||
|
VerifyMarshal(buf, offset);
|
||||||
|
|
||||||
|
return buf;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal static FrostFsChain Deserialize(byte[] data)
|
||||||
|
{
|
||||||
|
if (data is null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(data));
|
||||||
|
}
|
||||||
|
|
||||||
|
FrostFsChain chain = new();
|
||||||
|
|
||||||
|
var (offset, version) = UInt8Unmarshal(data, 0);
|
||||||
|
|
||||||
|
if (version != Version)
|
||||||
|
{
|
||||||
|
throw new FrostFsException($"unsupported marshaller version {version}");
|
||||||
|
}
|
||||||
|
|
||||||
|
(offset, byte chainVersion) = UInt8Unmarshal(data, offset);
|
||||||
|
|
||||||
|
if (chainVersion != ChainMarshalVersion)
|
||||||
|
{
|
||||||
|
throw new FrostFsException($"unsupported chain version {chainVersion}");
|
||||||
|
}
|
||||||
|
|
||||||
|
(offset, chain.ID) = SliceUnmarshal(data, offset, UInt8Unmarshal);
|
||||||
|
|
||||||
|
(offset, chain.Rules) = SliceUnmarshal(data, offset, UnmarshalRule);
|
||||||
|
|
||||||
|
(offset, var matchTypeV) = UInt8Unmarshal(data, offset);
|
||||||
|
|
||||||
|
chain.MatchType = (RuleMatchType)matchTypeV;
|
||||||
|
|
||||||
|
VerifyUnmarshal(data, offset);
|
||||||
|
|
||||||
|
return chain;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int Int64Size(long value)
|
||||||
|
{
|
||||||
|
// https://cs.opensource.google/go/go/+/master:src/encoding/binary/varint.go;l=92;drc=dac9b9ddbd5160c5f4552410f5f8281bd5eed38c
|
||||||
|
// and
|
||||||
|
// https://cs.opensource.google/go/go/+/master:src/encoding/binary/varint.go;l=41;drc=dac9b9ddbd5160c5f4552410f5f8281bd5eed38c
|
||||||
|
ulong ux = (ulong)value << 1;
|
||||||
|
if (value < 0)
|
||||||
|
{
|
||||||
|
ux = ~ux;
|
||||||
|
}
|
||||||
|
|
||||||
|
int size = 0;
|
||||||
|
while (ux >= 0x80)
|
||||||
|
{
|
||||||
|
size++;
|
||||||
|
ux >>= 7;
|
||||||
|
}
|
||||||
|
|
||||||
|
return size + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int SliceSize<T>(T[] slice, Func<T, int> sizeOf)
|
||||||
|
{
|
||||||
|
if (slice == null)
|
||||||
|
{
|
||||||
|
return NullSliceSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Assuming Int64Size is the size of the slice
|
||||||
|
var size = Int64Size(slice.Length);
|
||||||
|
foreach (var v in slice)
|
||||||
|
{
|
||||||
|
size += sizeOf(v);
|
||||||
|
}
|
||||||
|
|
||||||
|
return size;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int StringSize(string? s)
|
||||||
|
{
|
||||||
|
var len = s != null ? s.Length : 0;
|
||||||
|
return Int64Size(len) + len;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int ActionsSize(Actions action)
|
||||||
|
{
|
||||||
|
return BoolSize // Inverted
|
||||||
|
+ SliceSize(action.Names, StringSize);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int ResourcesSize(Resources resource)
|
||||||
|
{
|
||||||
|
return BoolSize // Inverted
|
||||||
|
+ SliceSize(resource.Names, StringSize);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int ConditionSize(Condition condition)
|
||||||
|
{
|
||||||
|
return ByteSize // Op
|
||||||
|
+ ByteSize // Object
|
||||||
|
+ StringSize(condition.Key)
|
||||||
|
+ StringSize(condition.Value);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int RuleSize(FrostFsRule rule)
|
||||||
|
{
|
||||||
|
if (rule is null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(rule));
|
||||||
|
}
|
||||||
|
|
||||||
|
return ByteSize // Status
|
||||||
|
+ ActionsSize(rule.Actions)
|
||||||
|
+ ResourcesSize(rule.Resources)
|
||||||
|
+ BoolSize // Any
|
||||||
|
+ SliceSize(rule.Conditions!, ConditionSize);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int UInt8Marshal(byte[] buf, int offset, byte value)
|
||||||
|
{
|
||||||
|
if (buf.Length - offset < 1)
|
||||||
|
{
|
||||||
|
throw new FrostFsException("Not enough bytes left to serialize value of type byte");
|
||||||
|
}
|
||||||
|
|
||||||
|
buf[offset] = value;
|
||||||
|
|
||||||
|
return offset + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int ByteMarshal(byte[] buf, int offset, byte value)
|
||||||
|
{
|
||||||
|
return UInt8Marshal(buf, offset, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
// PutVarint encodes an int64 into buf and returns the number of bytes written.
|
||||||
|
// If the buffer is too small, PutVarint will panic.
|
||||||
|
private static int PutVarint(byte[] buf, int offset, long x)
|
||||||
|
{
|
||||||
|
var ux = (ulong)x << 1;
|
||||||
|
|
||||||
|
if (x < 0)
|
||||||
|
{
|
||||||
|
ux = ~ux;
|
||||||
|
}
|
||||||
|
|
||||||
|
return PutUvarint(buf, offset, ux);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int PutUvarint(byte[] buf, int offset, ulong x)
|
||||||
|
{
|
||||||
|
while (x >= 0x80)
|
||||||
|
{
|
||||||
|
buf[offset] = (byte)(x | 0x80);
|
||||||
|
x >>= 7;
|
||||||
|
offset++;
|
||||||
|
}
|
||||||
|
|
||||||
|
buf[offset] = (byte)x;
|
||||||
|
|
||||||
|
return offset + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int Int64Marshal(byte[] buf, int offset, long v)
|
||||||
|
{
|
||||||
|
if (buf.Length - offset < Int64Size(v))
|
||||||
|
{
|
||||||
|
throw new FrostFsException("Not enough bytes left to serialize value of type long");
|
||||||
|
}
|
||||||
|
|
||||||
|
return PutVarint(buf, offset, v);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int SliceMarshal<T>(byte[] buf, int offset, T[] slice, Func<byte[], int, T, int> marshalT)
|
||||||
|
{
|
||||||
|
if (slice == null)
|
||||||
|
{
|
||||||
|
return Int64Marshal(buf, offset, NullSlice);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (slice.Length > MaxSliceLen)
|
||||||
|
{
|
||||||
|
throw new FrostFsException($"slice size if too big: {slice.Length}");
|
||||||
|
}
|
||||||
|
|
||||||
|
offset = Int64Marshal(buf, offset, slice.Length);
|
||||||
|
|
||||||
|
foreach (var v in slice)
|
||||||
|
{
|
||||||
|
offset = marshalT(buf, offset, v);
|
||||||
|
}
|
||||||
|
|
||||||
|
return offset;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int BoolMarshal(byte[] buf, int offset, bool value)
|
||||||
|
{
|
||||||
|
return UInt8Marshal(buf, offset, value ? ByteTrue : ByteFalse);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int StringMarshal(byte[] buf, int offset, string value)
|
||||||
|
{
|
||||||
|
if (value == null)
|
||||||
|
{
|
||||||
|
throw new FrostFsException($"string value is null");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (value.Length > MaxSliceLen)
|
||||||
|
{
|
||||||
|
throw new FrostFsException($"string is too long: {value.Length}");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (buf.Length - offset < Int64Size(value.Length) + value.Length)
|
||||||
|
{
|
||||||
|
throw new FrostFsException($"Not enough bytes left to serialize value of type string with length {value.Length}");
|
||||||
|
}
|
||||||
|
|
||||||
|
offset = Int64Marshal(buf, offset, value.Length);
|
||||||
|
|
||||||
|
if (string.IsNullOrEmpty(value))
|
||||||
|
{
|
||||||
|
return offset;
|
||||||
|
}
|
||||||
|
|
||||||
|
Buffer.BlockCopy(System.Text.Encoding.UTF8.GetBytes(value), 0, buf, offset, value.Length);
|
||||||
|
|
||||||
|
return offset + value.Length;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int MarshalActions(byte[] buf, int offset, Actions action)
|
||||||
|
{
|
||||||
|
offset = BoolMarshal(buf, offset, action.Inverted);
|
||||||
|
|
||||||
|
return SliceMarshal(buf, offset, action.Names, StringMarshal);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int MarshalCondition(byte[] buf, int offset, Condition condition)
|
||||||
|
{
|
||||||
|
offset = ByteMarshal(buf, offset, (byte)condition.Op);
|
||||||
|
|
||||||
|
offset = ByteMarshal(buf, offset, (byte)condition.Kind);
|
||||||
|
|
||||||
|
offset = StringMarshal(buf, offset, condition.Key!);
|
||||||
|
|
||||||
|
return StringMarshal(buf, offset, condition.Value!);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int MarshalRule(byte[] buf, int offset, FrostFsRule rule)
|
||||||
|
{
|
||||||
|
if (rule is null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(rule));
|
||||||
|
}
|
||||||
|
|
||||||
|
offset = ByteMarshal(buf, offset, (byte)rule.Status);
|
||||||
|
|
||||||
|
offset = MarshalActions(buf, offset, rule.Actions);
|
||||||
|
|
||||||
|
offset = MarshalResources(buf, offset, rule.Resources);
|
||||||
|
|
||||||
|
offset = BoolMarshal(buf, offset, rule.Any);
|
||||||
|
|
||||||
|
return SliceMarshal(buf, offset, rule.Conditions!, MarshalCondition);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int MarshalResources(byte[] buf, int offset, Resources resources)
|
||||||
|
{
|
||||||
|
offset = BoolMarshal(buf, offset, resources.Inverted);
|
||||||
|
|
||||||
|
return SliceMarshal(buf, offset, resources.Names, StringMarshal);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void VerifyMarshal(byte[] buf, int lastOffset)
|
||||||
|
{
|
||||||
|
if (buf.Length != lastOffset)
|
||||||
|
{
|
||||||
|
throw new FrostFsException("actual data size differs from expected");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static (int, bool) BoolUnmarshal(byte[] buf, int offset)
|
||||||
|
{
|
||||||
|
(offset, byte val) = UInt8Unmarshal(buf, offset);
|
||||||
|
return (offset, val == ByteTrue);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static (int, string) StringUnmarshal(byte[] buf, int offset)
|
||||||
|
{
|
||||||
|
(offset, long size) = Int64Unmarshal(buf, offset);
|
||||||
|
|
||||||
|
if (size == 0)
|
||||||
|
{
|
||||||
|
return (offset, string.Empty);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (size > MaxSliceLen)
|
||||||
|
{
|
||||||
|
throw new FrostFsException($"string is too long: '{size}'");
|
||||||
|
}
|
||||||
|
if (size < 0)
|
||||||
|
{
|
||||||
|
throw new FrostFsException($"invalid string size: '{size}'");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (buf.Length - offset < size)
|
||||||
|
{
|
||||||
|
throw new FrostFsException($"not enough bytes left to string value");
|
||||||
|
}
|
||||||
|
|
||||||
|
return (offset + (int)size, System.Text.Encoding.UTF8.GetString(buf, offset, (int)size));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static (int, Actions) UnmarshalActions(byte[] buf, int offset)
|
||||||
|
{
|
||||||
|
Actions action = new();
|
||||||
|
(offset, action.Inverted) = BoolUnmarshal(buf, offset);
|
||||||
|
|
||||||
|
(offset, action.Names) = SliceUnmarshal(buf, offset, StringUnmarshal);
|
||||||
|
|
||||||
|
return (offset, action);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static (int, Resources) UnmarshalResources(byte[] buf, int offset)
|
||||||
|
{
|
||||||
|
Resources res = new();
|
||||||
|
|
||||||
|
(offset, res.Inverted) = BoolUnmarshal(buf, offset);
|
||||||
|
(offset, res.Names) = SliceUnmarshal(buf, offset, StringUnmarshal);
|
||||||
|
|
||||||
|
return (offset, res);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static (int, Condition) UnmarshalCondition(byte[] buf, int offset)
|
||||||
|
{
|
||||||
|
Condition cond = new();
|
||||||
|
(offset, var op) = UInt8Unmarshal(buf, offset);
|
||||||
|
|
||||||
|
cond.Op = (ConditionType)op;
|
||||||
|
|
||||||
|
(offset, var kind) = UInt8Unmarshal(buf, offset);
|
||||||
|
|
||||||
|
cond.Kind = (ConditionKindType)kind;
|
||||||
|
|
||||||
|
(offset, cond.Key) = StringUnmarshal(buf, offset);
|
||||||
|
|
||||||
|
(offset, cond.Value) = StringUnmarshal(buf, offset);
|
||||||
|
|
||||||
|
return (offset, cond);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static (int, FrostFsRule) UnmarshalRule(byte[] buf, int offset)
|
||||||
|
{
|
||||||
|
FrostFsRule rule = new();
|
||||||
|
|
||||||
|
(offset, byte statusV) = UInt8Unmarshal(buf, offset);
|
||||||
|
rule.Status = (RuleStatus)statusV;
|
||||||
|
|
||||||
|
(offset, rule.Actions) = UnmarshalActions(buf, offset);
|
||||||
|
|
||||||
|
(offset, rule.Resources) = UnmarshalResources(buf, offset);
|
||||||
|
|
||||||
|
(offset, rule.Any) = BoolUnmarshal(buf, offset);
|
||||||
|
|
||||||
|
(offset, rule.Conditions) = SliceUnmarshal(buf, offset, UnmarshalCondition);
|
||||||
|
|
||||||
|
return (offset, rule);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static (int, byte) UInt8Unmarshal(byte[] buf, int offset)
|
||||||
|
{
|
||||||
|
if (buf.Length - offset < 1)
|
||||||
|
{
|
||||||
|
throw new FrostFsException($"not enough bytes left to read a value of type 'byte' from offset {offset}");
|
||||||
|
}
|
||||||
|
|
||||||
|
return (offset + 1, buf[offset]);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static (int, long) Int64Unmarshal(byte[] buf, int offset)
|
||||||
|
{
|
||||||
|
if (buf.Length - offset < sizeof(long))
|
||||||
|
{
|
||||||
|
throw new FrostFsException($"not enough bytes left to read a value of type 'long' from offset {offset}");
|
||||||
|
}
|
||||||
|
|
||||||
|
return Varint(buf, offset);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static (int, T[]) SliceUnmarshal<T>(byte[] buf, int offset, Func<byte[], int, (int, T)> unmarshalT)
|
||||||
|
{
|
||||||
|
var (newOffset, size) = Varint(buf, offset);
|
||||||
|
|
||||||
|
if (size == NullSlice)
|
||||||
|
{
|
||||||
|
return (newOffset, []);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (size > MaxSliceLen)
|
||||||
|
{
|
||||||
|
throw new FrostFsException($"slice size is too big: '{size}'");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (size < 0)
|
||||||
|
{
|
||||||
|
throw new FrostFsException($"invalid slice size: '{size}'");
|
||||||
|
}
|
||||||
|
|
||||||
|
var result = new T[size];
|
||||||
|
for (int i = 0; i < result.Length; i++)
|
||||||
|
{
|
||||||
|
(newOffset, result[i]) = unmarshalT(buf, newOffset);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (newOffset, result);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void VerifyUnmarshal(byte[] buf, int offset)
|
||||||
|
{
|
||||||
|
if (buf.Length != offset)
|
||||||
|
{
|
||||||
|
throw new FrostFsException("unmarshalled bytes left");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int MaxVarIntLen64 = 10;
|
||||||
|
|
||||||
|
public static (int, long) Varint(byte[] buf, int offset)
|
||||||
|
{
|
||||||
|
var (ux, n) = Uvarint(buf, offset); // ok to continue in presence of error
|
||||||
|
long x = (long)ux >> 1;
|
||||||
|
if ((ux & 1) != 0)
|
||||||
|
{
|
||||||
|
x = ~x;
|
||||||
|
}
|
||||||
|
return (n, x);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static (ulong, int) Uvarint(byte[] buf, int offset)
|
||||||
|
{
|
||||||
|
ulong x = 0;
|
||||||
|
int s = 0;
|
||||||
|
|
||||||
|
for (int i = offset; i < buf.Length; i++)
|
||||||
|
{
|
||||||
|
byte b = buf[i];
|
||||||
|
if (i == MaxVarIntLen64)
|
||||||
|
{
|
||||||
|
return (0, -(i + 1)); // overflow
|
||||||
|
}
|
||||||
|
if (b < 0x80)
|
||||||
|
{
|
||||||
|
if (i == MaxVarIntLen64 - 1 && b > 1)
|
||||||
|
{
|
||||||
|
return (0, -(i + 1)); // overflow
|
||||||
|
}
|
||||||
|
return (x | ((ulong)b << s), i + 1);
|
||||||
|
}
|
||||||
|
x |= (ulong)(b & 0x7f) << s;
|
||||||
|
s += 7;
|
||||||
|
}
|
||||||
|
return (0, 0);
|
||||||
|
}
|
||||||
|
}
|
14
src/FrostFS.SDK.Client/AssemblyInfo.cs
Normal file
14
src/FrostFS.SDK.Client/AssemblyInfo.cs
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
using System.Reflection;
|
||||||
|
using System.Runtime.CompilerServices;
|
||||||
|
|
||||||
|
[assembly: AssemblyCompany("TrueCloudLab")]
|
||||||
|
[assembly: InternalsVisibleTo("FrostFS.SDK.Tests, PublicKey=" +
|
||||||
|
"002400000480000094000000060200000024000052534131000400000100010089b992fb14ebcf"+
|
||||||
|
"4b85b4b1a3af1897290c52ff85a106035c47dc5604cbaa58ae3180f5c9b8523fee5dd1bb9ea9cf"+
|
||||||
|
"e15ab287e6239c98d5dfa91615bd77485d523a3a3f65a4e5028454cedd5ac4d9eca6da18b81985"+
|
||||||
|
"ac6905d33cc64b5a2587050c16f67b71ef8889dbd3c90ef7cc0b06bbbe09886601d195f5db179a"+
|
||||||
|
"3c2a25b1")]
|
||||||
|
[assembly: AssemblyFileVersion("1.0.6.0")]
|
||||||
|
[assembly: AssemblyProduct("FrostFS.SDK.Client")]
|
||||||
|
[assembly: AssemblyTitle("FrostFS.SDK.Client")]
|
||||||
|
[assembly: AssemblyVersion("1.0.6")]
|
13
src/FrostFS.SDK.Client/Caches.cs
Normal file
13
src/FrostFS.SDK.Client/Caches.cs
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
using Microsoft.Extensions.Caching.Memory;
|
||||||
|
|
||||||
|
namespace FrostFS.SDK.Client;
|
||||||
|
|
||||||
|
internal static class Caches
|
||||||
|
{
|
||||||
|
private static readonly IMemoryCache _ownersCache = new MemoryCache(new MemoryCacheOptions
|
||||||
|
{
|
||||||
|
SizeLimit = 256
|
||||||
|
});
|
||||||
|
|
||||||
|
internal static IMemoryCache Owners => _ownersCache;
|
||||||
|
}
|
18
src/FrostFS.SDK.Client/CllientKey.cs
Normal file
18
src/FrostFS.SDK.Client/CllientKey.cs
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
using System.Security.Cryptography;
|
||||||
|
|
||||||
|
using FrostFS.SDK.Cryptography;
|
||||||
|
|
||||||
|
using Google.Protobuf;
|
||||||
|
|
||||||
|
namespace FrostFS.SDK.Client;
|
||||||
|
|
||||||
|
public class ClientKey(ECDsa key)
|
||||||
|
{
|
||||||
|
internal ECDsa ECDsaKey { get; } = key;
|
||||||
|
|
||||||
|
internal ByteString PublicKeyProto { get; } = ByteString.CopyFrom(key.PublicKey());
|
||||||
|
|
||||||
|
internal string PublicKey { get; } = Base58.Encode(key.PublicKey());
|
||||||
|
|
||||||
|
internal FrostFsOwner Owner { get; } = new FrostFsOwner(key.PublicKey().PublicKeyToAddress());
|
||||||
|
}
|
18
src/FrostFS.SDK.Client/Exceptions/FrostFsException.cs
Normal file
18
src/FrostFS.SDK.Client/Exceptions/FrostFsException.cs
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
using System;
|
||||||
|
|
||||||
|
namespace FrostFS.SDK.Client;
|
||||||
|
|
||||||
|
public class FrostFsException : Exception
|
||||||
|
{
|
||||||
|
public FrostFsException()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public FrostFsException(string message) : base(message)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public FrostFsException(string message, Exception innerException) : base(message, innerException)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,18 @@
|
||||||
|
using System;
|
||||||
|
|
||||||
|
namespace FrostFS.SDK.Client;
|
||||||
|
|
||||||
|
public class FrostFsInvalidObjectException : FrostFsException
|
||||||
|
{
|
||||||
|
public FrostFsInvalidObjectException()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public FrostFsInvalidObjectException(string message) : base(message)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public FrostFsInvalidObjectException(string message, Exception innerException) : base(message, innerException)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,26 @@
|
||||||
|
using System;
|
||||||
|
|
||||||
|
namespace FrostFS.SDK.Client;
|
||||||
|
|
||||||
|
public class FrostFsResponseException : FrostFsException
|
||||||
|
{
|
||||||
|
public FrostFsResponseStatus? Status { get; private set; }
|
||||||
|
|
||||||
|
public FrostFsResponseException()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public FrostFsResponseException(FrostFsResponseStatus status)
|
||||||
|
: base(status != null ? status.Message != null ? "" : "" : "")
|
||||||
|
{
|
||||||
|
Status = status;
|
||||||
|
}
|
||||||
|
|
||||||
|
public FrostFsResponseException(string message) : base(message)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public FrostFsResponseException(string message, Exception innerException) : base(message, innerException)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
18
src/FrostFS.SDK.Client/Exceptions/FrostFsStreamException.cs
Normal file
18
src/FrostFS.SDK.Client/Exceptions/FrostFsStreamException.cs
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
using System;
|
||||||
|
|
||||||
|
namespace FrostFS.SDK.Client;
|
||||||
|
|
||||||
|
public class FrostFsStreamException : FrostFsException
|
||||||
|
{
|
||||||
|
public FrostFsStreamException()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public FrostFsStreamException(string message) : base(message)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public FrostFsStreamException(string message, Exception innerException) : base(message, innerException)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
18
src/FrostFS.SDK.Client/Exceptions/SessionExpiredException.cs
Normal file
18
src/FrostFS.SDK.Client/Exceptions/SessionExpiredException.cs
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
using System;
|
||||||
|
|
||||||
|
namespace FrostFS.SDK.Client;
|
||||||
|
|
||||||
|
public class SessionExpiredException : FrostFsException
|
||||||
|
{
|
||||||
|
public SessionExpiredException()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public SessionExpiredException(string message) : base(message)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public SessionExpiredException(string message, Exception innerException) : base(message, innerException)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,18 @@
|
||||||
|
using System;
|
||||||
|
|
||||||
|
namespace FrostFS.SDK.Client;
|
||||||
|
|
||||||
|
public class SessionNotFoundException : FrostFsException
|
||||||
|
{
|
||||||
|
public SessionNotFoundException()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public SessionNotFoundException(string message) : base(message)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public SessionNotFoundException(string message, Exception innerException) : base(message, innerException)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
62
src/FrostFS.SDK.Client/Extensions/FrostFsExtensions.cs
Normal file
62
src/FrostFS.SDK.Client/Extensions/FrostFsExtensions.cs
Normal file
|
@ -0,0 +1,62 @@
|
||||||
|
using System;
|
||||||
|
using System.Security.Cryptography;
|
||||||
|
using Google.Protobuf;
|
||||||
|
|
||||||
|
namespace FrostFS.SDK.Cryptography;
|
||||||
|
|
||||||
|
public static class FrostFsExtensions
|
||||||
|
{
|
||||||
|
public static byte[] Sha256(this IMessage data)
|
||||||
|
{
|
||||||
|
using var sha256 = SHA256.Create();
|
||||||
|
using HashStream stream = new(sha256);
|
||||||
|
data.WriteTo(stream);
|
||||||
|
return stream.Hash();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Guid ToUuid(this ByteString id)
|
||||||
|
{
|
||||||
|
if (id == null)
|
||||||
|
throw new ArgumentNullException(nameof(id));
|
||||||
|
|
||||||
|
return new Guid(
|
||||||
|
(id[0] << 24) + (id[1] << 16) + (id[2] << 8) + id[3],
|
||||||
|
(short)((id[4] << 8) + id[5]),
|
||||||
|
(short)((id[6] << 8) + id[7]),
|
||||||
|
id[8],
|
||||||
|
id[9],
|
||||||
|
id[10],
|
||||||
|
id[11],
|
||||||
|
id[12],
|
||||||
|
id[13],
|
||||||
|
id[14],
|
||||||
|
id[15]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Serializes Guid to binary representation in direct order bytes format
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="id"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public unsafe static void ToBytes(this Guid id, Span<byte> span)
|
||||||
|
{
|
||||||
|
var pGuid = (byte*)&id;
|
||||||
|
|
||||||
|
span[0] = pGuid[3];
|
||||||
|
span[1] = pGuid[2];
|
||||||
|
span[2] = pGuid[1];
|
||||||
|
span[3] = pGuid[0];
|
||||||
|
span[4] = pGuid[5];
|
||||||
|
span[5] = pGuid[4];
|
||||||
|
span[6] = pGuid[7];
|
||||||
|
span[7] = pGuid[6];
|
||||||
|
span[8] = pGuid[8];
|
||||||
|
span[9] = pGuid[9];
|
||||||
|
span[10] = pGuid[10];
|
||||||
|
span[11] = pGuid[11];
|
||||||
|
span[12] = pGuid[12];
|
||||||
|
span[13] = pGuid[13];
|
||||||
|
span[14] = pGuid[14];
|
||||||
|
span[15] = pGuid[15];
|
||||||
|
}
|
||||||
|
}
|
58
src/FrostFS.SDK.Client/FrostFS.SDK.Client.csproj
Normal file
58
src/FrostFS.SDK.Client/FrostFS.SDK.Client.csproj
Normal file
|
@ -0,0 +1,58 @@
|
||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>netstandard2.0</TargetFramework>
|
||||||
|
<LangVersion>12.0</LangVersion>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
<AnalysisMode>AllEnabledByDefault</AnalysisMode>
|
||||||
|
<PackageId>FrostFS.SDK.Client</PackageId>
|
||||||
|
<Version>1.0.6</Version>
|
||||||
|
<Description>
|
||||||
|
C# SDK for FrostFS gRPC native protocol
|
||||||
|
</Description>
|
||||||
|
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<EnableNETAnalyzers>true</EnableNETAnalyzers>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<CodeAnalysisTreatWarningsAsErrors>true</CodeAnalysisTreatWarningsAsErrors>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<_SkipUpgradeNetAnalyzersNuGetWarning>true</_SkipUpgradeNetAnalyzersNuGetWarning>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<EnforceCodeStyleInBuild>true</EnforceCodeStyleInBuild>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
|
||||||
|
<AllowUnsafeBlocks>True</AllowUnsafeBlocks>
|
||||||
|
<SignAssembly>True</SignAssembly>
|
||||||
|
<AssemblyOriginatorKeyFile>.\\..\\..\\keyfile.snk</AssemblyOriginatorKeyFile>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="Microsoft.Bcl.AsyncInterfaces" Version="7.0.0" />
|
||||||
|
<PackageReference Include="Microsoft.CodeAnalysis.NetAnalyzers" Version="7.0.4">
|
||||||
|
<PrivateAssets>all</PrivateAssets>
|
||||||
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
|
</PackageReference>
|
||||||
|
<PackageReference Include="Microsoft.Extensions.Caching.Abstractions" Version="7.0.0" />
|
||||||
|
<PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="7.0.0" />
|
||||||
|
<PackageReference Include="Microsoft.Extensions.Options" Version="7.0.1" />
|
||||||
|
<PackageReference Include="System.Diagnostics.DiagnosticSource" Version="7.0.0" />
|
||||||
|
<PackageReference Include="System.Runtime.Caching" Version="7.0.0" />
|
||||||
|
<PackageReference Include="System.Text.Json" Version="7.0.1" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\FrostFS.SDK.Cryptography\FrostFS.SDK.Cryptography.csproj" />
|
||||||
|
<ProjectReference Include="..\FrostFS.SDK.Protos\FrostFS.SDK.Protos.csproj" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
</Project>
|
434
src/FrostFS.SDK.Client/FrostFSClient.cs
Normal file
434
src/FrostFS.SDK.Client/FrostFSClient.cs
Normal file
|
@ -0,0 +1,434 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
using FrostFS.SDK.Client.Interfaces;
|
||||||
|
using FrostFS.SDK.Client.Services;
|
||||||
|
using FrostFS.SDK.Cryptography;
|
||||||
|
using FrostFS.Session;
|
||||||
|
|
||||||
|
using Grpc.Core;
|
||||||
|
using Grpc.Core.Interceptors;
|
||||||
|
|
||||||
|
using Microsoft.Extensions.Options;
|
||||||
|
|
||||||
|
using static Frostfs.V2.Apemanager.APEManagerService;
|
||||||
|
using static FrostFS.Accounting.AccountingService;
|
||||||
|
using static FrostFS.Container.ContainerService;
|
||||||
|
using static FrostFS.Netmap.NetmapService;
|
||||||
|
using static FrostFS.Object.ObjectService;
|
||||||
|
using static FrostFS.Session.SessionService;
|
||||||
|
|
||||||
|
namespace FrostFS.SDK.Client;
|
||||||
|
|
||||||
|
public class FrostFSClient : IFrostFSClient
|
||||||
|
{
|
||||||
|
internal ContainerServiceClient? ContainerServiceClient { get; set; }
|
||||||
|
internal ContainerServiceProvider? ContainerServiceProvider { get; set; }
|
||||||
|
|
||||||
|
internal NetmapServiceClient? NetmapServiceClient { get; set; }
|
||||||
|
internal NetmapServiceProvider? NetmapServiceProvider { get; set; }
|
||||||
|
|
||||||
|
internal APEManagerServiceClient? ApeManagerServiceClient { get; set; }
|
||||||
|
internal ApeManagerServiceProvider? ApeManagerServiceProvider { get; set; }
|
||||||
|
|
||||||
|
internal SessionServiceClient? SessionServiceClient { get; set; }
|
||||||
|
internal SessionServiceProvider? SessionServiceProvider { get; set; }
|
||||||
|
|
||||||
|
internal ObjectServiceClient? ObjectServiceClient { get; set; }
|
||||||
|
internal ObjectServiceProvider? ObjectServiceProvider { get; set; }
|
||||||
|
|
||||||
|
internal AccountingServiceClient? AccountingServiceClient { get; set; }
|
||||||
|
internal AccountingServiceProvider? AccountingServiceProvider { get; set; }
|
||||||
|
|
||||||
|
internal ClientContext ClientCtx { get; set; }
|
||||||
|
|
||||||
|
public static IFrostFSClient GetInstance(IOptions<ClientSettings> clientOptions, Func<string, ChannelBase> grpcChannelFactory)
|
||||||
|
{
|
||||||
|
if (clientOptions is null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(clientOptions));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (grpcChannelFactory is null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(grpcChannelFactory));
|
||||||
|
}
|
||||||
|
|
||||||
|
return new FrostFSClient(clientOptions, grpcChannelFactory);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// For test only. Provide custom implementation or mock object to inject required logic instead of internal gRPC client.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="clientOptions">Global setting for client</param>
|
||||||
|
/// <param name="channelOptions">Setting for gRPC channel</param>
|
||||||
|
/// <param name="containerService">ContainerService.ContainerServiceClient implementation</param>
|
||||||
|
/// <param name="netmapService">Netmap.NetmapService.NetmapServiceClient implementation</param>
|
||||||
|
/// <param name="sessionService">Session.SessionService.SessionServiceClient implementation</param>
|
||||||
|
/// <param name="objectService">Object.ObjectService.ObjectServiceClient implementation</param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public static IFrostFSClient GetTestInstance(
|
||||||
|
IOptions<ClientSettings> settings,
|
||||||
|
Func<string, ChannelBase> grpcChannelFactory,
|
||||||
|
NetmapServiceClient netmapService,
|
||||||
|
SessionServiceClient sessionService,
|
||||||
|
ContainerServiceClient containerService,
|
||||||
|
ObjectServiceClient objectService)
|
||||||
|
{
|
||||||
|
if (settings is null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(settings));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (grpcChannelFactory is null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(grpcChannelFactory));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (netmapService is null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(netmapService));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sessionService is null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(sessionService));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (containerService is null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(containerService));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (objectService is null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(objectService));
|
||||||
|
}
|
||||||
|
|
||||||
|
return new FrostFSClient(
|
||||||
|
settings, channel: grpcChannelFactory(settings.Value.Host), containerService, netmapService, sessionService, objectService);
|
||||||
|
}
|
||||||
|
|
||||||
|
private FrostFSClient(
|
||||||
|
IOptions<ClientSettings> settings,
|
||||||
|
ChannelBase channel,
|
||||||
|
ContainerServiceClient containerService,
|
||||||
|
NetmapServiceClient netmapService,
|
||||||
|
SessionServiceClient sessionService,
|
||||||
|
ObjectServiceClient objectService)
|
||||||
|
{
|
||||||
|
if (settings is null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(settings));
|
||||||
|
}
|
||||||
|
|
||||||
|
var ecdsaKey = settings.Value.Key.LoadWif();
|
||||||
|
|
||||||
|
ClientCtx = new ClientContext(
|
||||||
|
client: this,
|
||||||
|
key: new ClientKey(ecdsaKey),
|
||||||
|
owner: FrostFsOwner.FromKey(ecdsaKey),
|
||||||
|
channel: channel,
|
||||||
|
version: new FrostFsVersion(2, 13))
|
||||||
|
{
|
||||||
|
SessionCache = new SessionCache(0),
|
||||||
|
Callback = settings.Value.Callback,
|
||||||
|
Interceptors = settings.Value.Interceptors
|
||||||
|
};
|
||||||
|
|
||||||
|
ContainerServiceClient = containerService ?? throw new ArgumentNullException(nameof(containerService));
|
||||||
|
NetmapServiceClient = netmapService ?? throw new ArgumentNullException(nameof(netmapService));
|
||||||
|
SessionServiceClient = sessionService ?? throw new ArgumentNullException(nameof(sessionService));
|
||||||
|
ObjectServiceClient = objectService ?? throw new ArgumentNullException(nameof(objectService));
|
||||||
|
}
|
||||||
|
|
||||||
|
private FrostFSClient(IOptions<ClientSettings> settings, Func<string, ChannelBase> grpcChannelFactory)
|
||||||
|
{
|
||||||
|
var clientSettings = (settings?.Value) ?? throw new ArgumentNullException(nameof(settings), "Options value must be initialized");
|
||||||
|
|
||||||
|
clientSettings.Validate();
|
||||||
|
|
||||||
|
var ecdsaKey = clientSettings.Key.LoadWif();
|
||||||
|
|
||||||
|
ClientCtx = new ClientContext(
|
||||||
|
this,
|
||||||
|
key: new ClientKey(ecdsaKey),
|
||||||
|
owner: FrostFsOwner.FromKey(ecdsaKey),
|
||||||
|
channel: grpcChannelFactory(settings.Value.Host),
|
||||||
|
version: new FrostFsVersion(2, 13))
|
||||||
|
{
|
||||||
|
SessionCache = new SessionCache(0),
|
||||||
|
Callback = settings.Value.Callback,
|
||||||
|
Interceptors = settings.Value.Interceptors
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
#region ApeManagerImplementation
|
||||||
|
public Task<ReadOnlyMemory<byte>> AddChainAsync(PrmApeChainAdd args, CallContext ctx)
|
||||||
|
{
|
||||||
|
return GetApeManagerService().AddChainAsync(args, ctx);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task RemoveChainAsync(PrmApeChainRemove args, CallContext ctx)
|
||||||
|
{
|
||||||
|
return GetApeManagerService().RemoveChainAsync(args, ctx);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task<FrostFsChain[]> ListChainAsync(PrmApeChainList args, CallContext ctx)
|
||||||
|
{
|
||||||
|
return GetApeManagerService().ListChainAsync(args, ctx);
|
||||||
|
}
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region ContainerImplementation
|
||||||
|
public Task<FrostFsContainerInfo> GetContainerAsync(PrmContainerGet args, CallContext ctx)
|
||||||
|
{
|
||||||
|
return GetContainerService().GetContainerAsync(args, ctx);
|
||||||
|
}
|
||||||
|
|
||||||
|
public IAsyncEnumerable<FrostFsContainerId> ListContainersAsync(PrmContainerGetAll args, CallContext ctx)
|
||||||
|
{
|
||||||
|
return GetContainerService().ListContainersAsync(args, ctx);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Obsolete("Use PutContainerAsync method")]
|
||||||
|
public Task<FrostFsContainerId> CreateContainerAsync(PrmContainerCreate args, CallContext ctx)
|
||||||
|
{
|
||||||
|
return GetContainerService().PutContainerAsync(args, ctx);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task<FrostFsContainerId> PutContainerAsync(PrmContainerCreate args, CallContext ctx)
|
||||||
|
{
|
||||||
|
return GetContainerService().PutContainerAsync(args, ctx);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task DeleteContainerAsync(PrmContainerDelete args, CallContext ctx)
|
||||||
|
{
|
||||||
|
return GetContainerService().DeleteContainerAsync(args, ctx);
|
||||||
|
}
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region NetworkImplementation
|
||||||
|
public Task<FrostFsNetmapSnapshot> GetNetmapSnapshotAsync(CallContext ctx)
|
||||||
|
{
|
||||||
|
return GetNetmapService().GetNetmapSnapshotAsync(ctx);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task<FrostFsNodeInfo> GetNodeInfoAsync(CallContext ctx)
|
||||||
|
{
|
||||||
|
return GetNetmapService().GetLocalNodeInfoAsync(ctx);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task<NetworkSettings> GetNetworkSettingsAsync(CallContext ctx)
|
||||||
|
{
|
||||||
|
return GetNetmapService().GetNetworkSettingsAsync(ctx);
|
||||||
|
}
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region ObjectImplementation
|
||||||
|
public Task<FrostFsHeaderResult> GetObjectHeadAsync(PrmObjectHeadGet args, CallContext ctx)
|
||||||
|
{
|
||||||
|
return GetObjectService().GetObjectHeadAsync(args, ctx);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task<FrostFsObject> GetObjectAsync(PrmObjectGet args, CallContext ctx)
|
||||||
|
{
|
||||||
|
return GetObjectService().GetObjectAsync(args, ctx);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task<RangeReader> GetRangeAsync(PrmRangeGet args, CallContext ctx)
|
||||||
|
{
|
||||||
|
return GetObjectService().GetRangeAsync(args, ctx);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task<ReadOnlyMemory<byte>[]> GetRangeHashAsync(PrmRangeHashGet args, CallContext ctx)
|
||||||
|
{
|
||||||
|
return GetObjectService().GetRangeHashAsync(args, ctx);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task<IObjectWriter> PutObjectAsync(PrmObjectPut args, CallContext ctx)
|
||||||
|
{
|
||||||
|
return GetObjectService().PutStreamObjectAsync(args, ctx);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task<FrostFsObjectId> PutClientCutObjectAsync(PrmObjectClientCutPut args, CallContext ctx)
|
||||||
|
{
|
||||||
|
return GetObjectService().PutClientCutSingleObjectAsync(args, ctx);
|
||||||
|
// return GetObjectService().PutClientCutObjectAsync(args, ctx);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task<FrostFsObjectId> PutSingleObjectAsync(PrmSingleObjectPut args, CallContext ctx)
|
||||||
|
{
|
||||||
|
return GetObjectService().PutSingleObjectAsync(args, ctx);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task<FrostFsObjectId> PatchObjectAsync(PrmObjectPatch args, CallContext ctx)
|
||||||
|
{
|
||||||
|
return GetObjectService().PatchObjectAsync(args, ctx);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task DeleteObjectAsync(PrmObjectDelete args, CallContext ctx)
|
||||||
|
{
|
||||||
|
return GetObjectService().DeleteObjectAsync(args, ctx);
|
||||||
|
}
|
||||||
|
|
||||||
|
public IAsyncEnumerable<FrostFsObjectId> SearchObjectsAsync(PrmObjectSearch args, CallContext ctx)
|
||||||
|
{
|
||||||
|
return GetObjectService().SearchObjectsAsync(args, ctx);
|
||||||
|
}
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Session Implementation
|
||||||
|
public async Task<FrostFsSessionToken> CreateSessionAsync(PrmSessionCreate args, CallContext ctx)
|
||||||
|
{
|
||||||
|
var token = await CreateSessionInternalAsync(args, ctx).ConfigureAwait(false);
|
||||||
|
|
||||||
|
return new FrostFsSessionToken(token);
|
||||||
|
}
|
||||||
|
|
||||||
|
internal Task<SessionToken> CreateSessionInternalAsync(PrmSessionCreate args, CallContext ctx)
|
||||||
|
{
|
||||||
|
var service = GetSessionService();
|
||||||
|
return service.CreateSessionAsync(args, ctx);
|
||||||
|
}
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Accounting Implementation
|
||||||
|
public async Task<Accounting.Decimal> GetBalanceAsync(CallContext ctx)
|
||||||
|
{
|
||||||
|
return await GetAccouningService().GetBallance(ctx).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
private CallInvoker? CreateInvoker()
|
||||||
|
{
|
||||||
|
CallInvoker? callInvoker = null;
|
||||||
|
|
||||||
|
if (ClientCtx.Interceptors != null)
|
||||||
|
{
|
||||||
|
foreach (var interceptor in ClientCtx.Interceptors)
|
||||||
|
callInvoker = AddInvoker(callInvoker, interceptor);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ClientCtx.Callback != null)
|
||||||
|
callInvoker = AddInvoker(callInvoker, new MetricsInterceptor(ClientCtx.Callback));
|
||||||
|
|
||||||
|
if (ClientCtx.PoolErrorHandler != null)
|
||||||
|
callInvoker = AddInvoker(callInvoker, new ErrorInterceptor(ClientCtx.PoolErrorHandler));
|
||||||
|
|
||||||
|
return callInvoker;
|
||||||
|
|
||||||
|
CallInvoker AddInvoker(CallInvoker? callInvoker, Interceptor interceptor)
|
||||||
|
{
|
||||||
|
if (callInvoker == null)
|
||||||
|
callInvoker = ClientCtx.Channel.Intercept(interceptor);
|
||||||
|
else
|
||||||
|
callInvoker = callInvoker.Intercept(interceptor);
|
||||||
|
|
||||||
|
return callInvoker;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private NetmapServiceProvider GetNetmapService()
|
||||||
|
{
|
||||||
|
if (NetmapServiceProvider == null)
|
||||||
|
{
|
||||||
|
var invoker = CreateInvoker();
|
||||||
|
|
||||||
|
NetmapServiceClient ??= (
|
||||||
|
invoker != null
|
||||||
|
? new NetmapServiceClient(invoker)
|
||||||
|
: new NetmapServiceClient(ClientCtx.Channel));
|
||||||
|
|
||||||
|
NetmapServiceProvider = new NetmapServiceProvider(NetmapServiceClient, ClientCtx);
|
||||||
|
}
|
||||||
|
|
||||||
|
return NetmapServiceProvider;
|
||||||
|
}
|
||||||
|
|
||||||
|
private SessionServiceProvider GetSessionService()
|
||||||
|
{
|
||||||
|
if (SessionServiceProvider == null)
|
||||||
|
{
|
||||||
|
var invoker = CreateInvoker();
|
||||||
|
|
||||||
|
SessionServiceClient ??= (
|
||||||
|
invoker != null
|
||||||
|
? new SessionServiceClient(invoker)
|
||||||
|
: new SessionServiceClient(ClientCtx.Channel));
|
||||||
|
|
||||||
|
SessionServiceProvider = new SessionServiceProvider(SessionServiceClient, ClientCtx);
|
||||||
|
}
|
||||||
|
|
||||||
|
return SessionServiceProvider;
|
||||||
|
}
|
||||||
|
|
||||||
|
private ApeManagerServiceProvider GetApeManagerService()
|
||||||
|
{
|
||||||
|
if (ApeManagerServiceProvider == null)
|
||||||
|
{
|
||||||
|
var invoker = CreateInvoker();
|
||||||
|
|
||||||
|
ApeManagerServiceClient ??= (
|
||||||
|
invoker != null
|
||||||
|
? new APEManagerServiceClient(invoker)
|
||||||
|
: new APEManagerServiceClient(ClientCtx.Channel));
|
||||||
|
|
||||||
|
ApeManagerServiceProvider = new ApeManagerServiceProvider(ApeManagerServiceClient, ClientCtx);
|
||||||
|
}
|
||||||
|
|
||||||
|
return ApeManagerServiceProvider;
|
||||||
|
}
|
||||||
|
|
||||||
|
private AccountingServiceProvider GetAccouningService()
|
||||||
|
{
|
||||||
|
if (this.AccountingServiceProvider == null)
|
||||||
|
{
|
||||||
|
var invoker = CreateInvoker();
|
||||||
|
|
||||||
|
AccountingServiceClient ??= (
|
||||||
|
invoker != null
|
||||||
|
? new AccountingServiceClient(invoker)
|
||||||
|
: new AccountingServiceClient(ClientCtx.Channel));
|
||||||
|
|
||||||
|
AccountingServiceProvider = new AccountingServiceProvider(AccountingServiceClient, ClientCtx);
|
||||||
|
}
|
||||||
|
|
||||||
|
return AccountingServiceProvider;
|
||||||
|
}
|
||||||
|
|
||||||
|
private ContainerServiceProvider GetContainerService()
|
||||||
|
{
|
||||||
|
if (this.ContainerServiceProvider == null)
|
||||||
|
{
|
||||||
|
var invoker = CreateInvoker();
|
||||||
|
|
||||||
|
ContainerServiceClient ??= (
|
||||||
|
invoker != null
|
||||||
|
? new ContainerServiceClient(invoker)
|
||||||
|
: new ContainerServiceClient(ClientCtx.Channel));
|
||||||
|
|
||||||
|
ContainerServiceProvider = new ContainerServiceProvider(ContainerServiceClient, ClientCtx);
|
||||||
|
}
|
||||||
|
|
||||||
|
return ContainerServiceProvider;
|
||||||
|
}
|
||||||
|
|
||||||
|
private ObjectServiceProvider GetObjectService()
|
||||||
|
{
|
||||||
|
if (this.ObjectServiceProvider == null)
|
||||||
|
{
|
||||||
|
var invoker = CreateInvoker();
|
||||||
|
|
||||||
|
ObjectServiceClient ??= (
|
||||||
|
invoker != null
|
||||||
|
? new ObjectServiceClient(invoker)
|
||||||
|
: new ObjectServiceClient(ClientCtx.Channel));
|
||||||
|
|
||||||
|
ObjectServiceProvider = new ObjectServiceProvider(ObjectServiceClient, ClientCtx);
|
||||||
|
}
|
||||||
|
|
||||||
|
return ObjectServiceProvider;
|
||||||
|
}
|
||||||
|
}
|
8
src/FrostFS.SDK.Client/GlobalSuppressions.cs
Normal file
8
src/FrostFS.SDK.Client/GlobalSuppressions.cs
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
// This file is used by Code Analysis to maintain SuppressMessage
|
||||||
|
// attributes that are applied to this project.
|
||||||
|
// Project-level suppressions either have no target or are given
|
||||||
|
// a specific target and scoped to a namespace, type, member, etc.
|
||||||
|
|
||||||
|
using System.Diagnostics.CodeAnalysis;
|
||||||
|
|
||||||
|
[assembly: SuppressMessage("Security", "CA5394:Do not use insecure randomness", Justification = "<Pending>", Scope = "member", Target = "~M:FrostFS.SDK.Client.Sampler.Next~System.Int32")]
|
68
src/FrostFS.SDK.Client/Interceptors/ErrorInterceptor.cs
Normal file
68
src/FrostFS.SDK.Client/Interceptors/ErrorInterceptor.cs
Normal file
|
@ -0,0 +1,68 @@
|
||||||
|
using System;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
using Grpc.Core;
|
||||||
|
using Grpc.Core.Interceptors;
|
||||||
|
|
||||||
|
namespace FrostFS.SDK.Client;
|
||||||
|
|
||||||
|
[System.Diagnostics.CodeAnalysis.SuppressMessage("Design", "CA1062:Validate arguments of public methods",
|
||||||
|
Justification = "parameters are provided by GRPC infrastructure")]
|
||||||
|
public class ErrorInterceptor(Action<Exception> handler) : Interceptor
|
||||||
|
{
|
||||||
|
public override AsyncUnaryCall<TResponse> AsyncUnaryCall<TRequest, TResponse>(
|
||||||
|
TRequest request,
|
||||||
|
ClientInterceptorContext<TRequest, TResponse> context,
|
||||||
|
AsyncUnaryCallContinuation<TRequest, TResponse> continuation)
|
||||||
|
{
|
||||||
|
var call = continuation(request, context);
|
||||||
|
|
||||||
|
return new AsyncUnaryCall<TResponse>(
|
||||||
|
HandleUnaryResponse(call),
|
||||||
|
call.ResponseHeadersAsync,
|
||||||
|
call.GetStatus,
|
||||||
|
call.GetTrailers,
|
||||||
|
call.Dispose);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override AsyncClientStreamingCall<TRequest, TResponse> AsyncClientStreamingCall<TRequest, TResponse>(
|
||||||
|
ClientInterceptorContext<TRequest, TResponse> context,
|
||||||
|
AsyncClientStreamingCallContinuation<TRequest, TResponse> continuation)
|
||||||
|
{
|
||||||
|
var call = continuation(context);
|
||||||
|
|
||||||
|
return new AsyncClientStreamingCall<TRequest, TResponse>(
|
||||||
|
call.RequestStream,
|
||||||
|
HandleStreamResponse(call),
|
||||||
|
call.ResponseHeadersAsync,
|
||||||
|
call.GetStatus,
|
||||||
|
call.GetTrailers,
|
||||||
|
call.Dispose);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task<TResponse> HandleUnaryResponse<TResponse>(AsyncUnaryCall<TResponse> call)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return await call;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
handler(ex);
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task<TResponse> HandleStreamResponse<TRequest, TResponse>(AsyncClientStreamingCall<TRequest, TResponse> call)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return await call;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
handler(ex);
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
75
src/FrostFS.SDK.Client/Interceptors/MetricsInterceptor.cs
Normal file
75
src/FrostFS.SDK.Client/Interceptors/MetricsInterceptor.cs
Normal file
|
@ -0,0 +1,75 @@
|
||||||
|
using System;
|
||||||
|
using System.Diagnostics;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
using Grpc.Core;
|
||||||
|
using Grpc.Core.Interceptors;
|
||||||
|
|
||||||
|
namespace FrostFS.SDK.Client;
|
||||||
|
|
||||||
|
[System.Diagnostics.CodeAnalysis.SuppressMessage("Design", "CA1062:Validate arguments of public methods",
|
||||||
|
Justification = "parameters are provided by GRPC infrastructure")]
|
||||||
|
public class MetricsInterceptor(Action<CallStatistics> callback) : Interceptor
|
||||||
|
{
|
||||||
|
public override AsyncUnaryCall<TResponse> AsyncUnaryCall<TRequest, TResponse>(
|
||||||
|
TRequest request,
|
||||||
|
ClientInterceptorContext<TRequest, TResponse> context,
|
||||||
|
AsyncUnaryCallContinuation<TRequest, TResponse> continuation)
|
||||||
|
{
|
||||||
|
var call = continuation(request, context);
|
||||||
|
|
||||||
|
return new AsyncUnaryCall<TResponse>(
|
||||||
|
HandleUnaryResponse(call),
|
||||||
|
call.ResponseHeadersAsync,
|
||||||
|
call.GetStatus,
|
||||||
|
call.GetTrailers,
|
||||||
|
call.Dispose);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override AsyncClientStreamingCall<TRequest, TResponse> AsyncClientStreamingCall<TRequest, TResponse>(
|
||||||
|
ClientInterceptorContext<TRequest, TResponse> context,
|
||||||
|
AsyncClientStreamingCallContinuation<TRequest, TResponse> continuation)
|
||||||
|
{
|
||||||
|
var call = continuation(context);
|
||||||
|
|
||||||
|
return new AsyncClientStreamingCall<TRequest, TResponse>(
|
||||||
|
call.RequestStream,
|
||||||
|
HandleStreamResponse(call),
|
||||||
|
call.ResponseHeadersAsync,
|
||||||
|
call.GetStatus,
|
||||||
|
call.GetTrailers,
|
||||||
|
call.Dispose);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task<TResponse> HandleUnaryResponse<TResponse>(AsyncUnaryCall<TResponse> call)
|
||||||
|
{
|
||||||
|
var watch = new Stopwatch();
|
||||||
|
watch.Start();
|
||||||
|
|
||||||
|
var response = await call;
|
||||||
|
|
||||||
|
watch.Stop();
|
||||||
|
|
||||||
|
var elapsed = watch.ElapsedTicks * 1_000_000 / Stopwatch.Frequency;
|
||||||
|
|
||||||
|
callback(new CallStatistics { MethodName = call.ToString(), ElapsedMicroSeconds = elapsed });
|
||||||
|
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task<TResponse> HandleStreamResponse<TRequest, TResponse>(AsyncClientStreamingCall<TRequest, TResponse> call)
|
||||||
|
{
|
||||||
|
var watch = new Stopwatch();
|
||||||
|
watch.Start();
|
||||||
|
|
||||||
|
var response = await call;
|
||||||
|
|
||||||
|
watch.Stop();
|
||||||
|
|
||||||
|
var elapsed = watch.ElapsedTicks * 1_000_000 / Stopwatch.Frequency;
|
||||||
|
|
||||||
|
callback(new CallStatistics { MethodName = call.ToString(), ElapsedMicroSeconds = elapsed });
|
||||||
|
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
}
|
67
src/FrostFS.SDK.Client/Interfaces/IFrostFSClient.cs
Normal file
67
src/FrostFS.SDK.Client/Interfaces/IFrostFSClient.cs
Normal file
|
@ -0,0 +1,67 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace FrostFS.SDK.Client.Interfaces;
|
||||||
|
|
||||||
|
public interface IFrostFSClient
|
||||||
|
{
|
||||||
|
#region Network
|
||||||
|
Task<FrostFsNetmapSnapshot> GetNetmapSnapshotAsync(CallContext ctx);
|
||||||
|
|
||||||
|
Task<FrostFsNodeInfo> GetNodeInfoAsync(CallContext ctx);
|
||||||
|
|
||||||
|
Task<NetworkSettings> GetNetworkSettingsAsync(CallContext ctx);
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Session
|
||||||
|
Task<FrostFsSessionToken> CreateSessionAsync(PrmSessionCreate args, CallContext ctx);
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region ApeManager
|
||||||
|
Task<ReadOnlyMemory<byte>> AddChainAsync(PrmApeChainAdd args, CallContext ctx);
|
||||||
|
|
||||||
|
Task RemoveChainAsync(PrmApeChainRemove args, CallContext ctx);
|
||||||
|
|
||||||
|
Task<FrostFsChain[]> ListChainAsync(PrmApeChainList args, CallContext ctx);
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Container
|
||||||
|
Task<FrostFsContainerInfo> GetContainerAsync(PrmContainerGet args, CallContext ctx);
|
||||||
|
|
||||||
|
IAsyncEnumerable<FrostFsContainerId> ListContainersAsync(PrmContainerGetAll args, CallContext ctx);
|
||||||
|
|
||||||
|
[Obsolete("Use PutContainerAsync method")]
|
||||||
|
Task<FrostFsContainerId> CreateContainerAsync(PrmContainerCreate args, CallContext ctx);
|
||||||
|
|
||||||
|
Task<FrostFsContainerId> PutContainerAsync(PrmContainerCreate args, CallContext ctx);
|
||||||
|
|
||||||
|
Task DeleteContainerAsync(PrmContainerDelete args, CallContext ctx);
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Object
|
||||||
|
Task<FrostFsHeaderResult> GetObjectHeadAsync(PrmObjectHeadGet args, CallContext ctx);
|
||||||
|
|
||||||
|
Task<FrostFsObject> GetObjectAsync(PrmObjectGet args, CallContext ctx);
|
||||||
|
|
||||||
|
Task<RangeReader> GetRangeAsync(PrmRangeGet args, CallContext ctx);
|
||||||
|
|
||||||
|
Task<ReadOnlyMemory<byte>[]> GetRangeHashAsync(PrmRangeHashGet args, CallContext ctx);
|
||||||
|
|
||||||
|
Task<IObjectWriter> PutObjectAsync(PrmObjectPut args, CallContext ctx);
|
||||||
|
|
||||||
|
Task<FrostFsObjectId> PutClientCutObjectAsync(PrmObjectClientCutPut args, CallContext ctx);
|
||||||
|
|
||||||
|
Task<FrostFsObjectId> PutSingleObjectAsync(PrmSingleObjectPut args, CallContext ctx);
|
||||||
|
|
||||||
|
Task<FrostFsObjectId> PatchObjectAsync(PrmObjectPatch args, CallContext ctx);
|
||||||
|
|
||||||
|
Task DeleteObjectAsync(PrmObjectDelete args, CallContext ctx);
|
||||||
|
|
||||||
|
IAsyncEnumerable<FrostFsObjectId> SearchObjectsAsync(PrmObjectSearch args, CallContext ctx);
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Account
|
||||||
|
Task<Accounting.Decimal> GetBalanceAsync(CallContext ctx);
|
||||||
|
#endregion
|
||||||
|
}
|
12
src/FrostFS.SDK.Client/Interfaces/IObjectWriter.cs
Normal file
12
src/FrostFS.SDK.Client/Interfaces/IObjectWriter.cs
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
using System;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace FrostFS.SDK.Client.Interfaces
|
||||||
|
{
|
||||||
|
public interface IObjectWriter : IDisposable
|
||||||
|
{
|
||||||
|
Task WriteAsync(ReadOnlyMemory<byte> memory);
|
||||||
|
|
||||||
|
Task<FrostFsObjectId> CompleteAsync();
|
||||||
|
}
|
||||||
|
}
|
24
src/FrostFS.SDK.Client/Logging/FrostFsMessages.cs
Normal file
24
src/FrostFS.SDK.Client/Logging/FrostFsMessages.cs
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
||||||
|
namespace FrostFS.SDK.Client;
|
||||||
|
|
||||||
|
internal static partial class FrostFsMessages
|
||||||
|
{
|
||||||
|
[LoggerMessage(100,
|
||||||
|
LogLevel.Warning,
|
||||||
|
"Failed to create frostfs session token for client. Address {address}, {error}",
|
||||||
|
EventName = nameof(SessionCreationError))]
|
||||||
|
internal static partial void SessionCreationError(ILogger logger, string address, string error);
|
||||||
|
|
||||||
|
[LoggerMessage(101,
|
||||||
|
LogLevel.Warning,
|
||||||
|
"Error threshold reached. Address {address}, threshold {threshold}",
|
||||||
|
EventName = nameof(ErrorЕhresholdReached))]
|
||||||
|
internal static partial void ErrorЕhresholdReached(ILogger logger, string address, uint threshold);
|
||||||
|
|
||||||
|
[LoggerMessage(102,
|
||||||
|
LogLevel.Warning,
|
||||||
|
"Health has changed: {address} healthy {healthy}, reason {error}",
|
||||||
|
EventName = nameof(HealthChanged))]
|
||||||
|
internal static partial void HealthChanged(ILogger logger, string address, bool healthy, string error);
|
||||||
|
}
|
22
src/FrostFS.SDK.Client/Mappers/Container.cs
Normal file
22
src/FrostFS.SDK.Client/Mappers/Container.cs
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
using System;
|
||||||
|
using System.Linq;
|
||||||
|
|
||||||
|
using FrostFS.SDK.Cryptography;
|
||||||
|
|
||||||
|
namespace FrostFS.SDK.Client.Mappers.GRPC;
|
||||||
|
|
||||||
|
public static class ContainerMapper
|
||||||
|
{
|
||||||
|
public static FrostFsContainerInfo ToModel(this Container.Container container)
|
||||||
|
{
|
||||||
|
if (container == null)
|
||||||
|
throw new ArgumentNullException(nameof(container));
|
||||||
|
|
||||||
|
return new FrostFsContainerInfo(
|
||||||
|
container.PlacementPolicy.ToModel(),
|
||||||
|
container.Attributes?.Select(a => new FrostFsAttributePair(a.Key, a.Value)).ToArray(),
|
||||||
|
container.Version?.ToModel(),
|
||||||
|
container.OwnerId?.ToModel(),
|
||||||
|
container.Nonce?.ToUuid());
|
||||||
|
}
|
||||||
|
}
|
38
src/FrostFS.SDK.Client/Mappers/ContainerId.cs
Normal file
38
src/FrostFS.SDK.Client/Mappers/ContainerId.cs
Normal file
|
@ -0,0 +1,38 @@
|
||||||
|
using System;
|
||||||
|
|
||||||
|
using FrostFS.Refs;
|
||||||
|
using FrostFS.SDK.Cryptography;
|
||||||
|
|
||||||
|
using Google.Protobuf;
|
||||||
|
|
||||||
|
namespace FrostFS.SDK.Client.Mappers.GRPC;
|
||||||
|
|
||||||
|
public static class ContainerIdMapper
|
||||||
|
{
|
||||||
|
public static ContainerID ToMessage(this FrostFsContainerId model)
|
||||||
|
{
|
||||||
|
if (model is null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(model));
|
||||||
|
}
|
||||||
|
|
||||||
|
var containerId = model.GetValue() ?? throw new ArgumentNullException(nameof(model));
|
||||||
|
|
||||||
|
var message = new ContainerID
|
||||||
|
{
|
||||||
|
Value = UnsafeByteOperations.UnsafeWrap(Base58.Decode(containerId))
|
||||||
|
};
|
||||||
|
|
||||||
|
return message!;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static FrostFsContainerId ToModel(this ContainerID message)
|
||||||
|
{
|
||||||
|
if (message is null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(message));
|
||||||
|
}
|
||||||
|
|
||||||
|
return new FrostFsContainerId(Base58.Encode(message.Value.Span));
|
||||||
|
}
|
||||||
|
}
|
23
src/FrostFS.SDK.Client/Mappers/MetaHeader.cs
Normal file
23
src/FrostFS.SDK.Client/Mappers/MetaHeader.cs
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
using System;
|
||||||
|
|
||||||
|
using FrostFS.Session;
|
||||||
|
|
||||||
|
namespace FrostFS.SDK.Client.Mappers.GRPC;
|
||||||
|
|
||||||
|
public static class MetaHeaderMapper
|
||||||
|
{
|
||||||
|
public static RequestMetaHeader ToMessage(this MetaHeader metaHeader)
|
||||||
|
{
|
||||||
|
if (metaHeader is null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(metaHeader));
|
||||||
|
}
|
||||||
|
|
||||||
|
return new RequestMetaHeader
|
||||||
|
{
|
||||||
|
Version = metaHeader.Version.ToMessage(),
|
||||||
|
Epoch = (uint)metaHeader.Epoch,
|
||||||
|
Ttl = (uint)metaHeader.Ttl
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
23
src/FrostFS.SDK.Client/Mappers/Netmap/Netmap.cs
Normal file
23
src/FrostFS.SDK.Client/Mappers/Netmap/Netmap.cs
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
using System;
|
||||||
|
using System.Linq;
|
||||||
|
|
||||||
|
using FrostFS.Netmap;
|
||||||
|
|
||||||
|
namespace FrostFS.SDK.Client;
|
||||||
|
|
||||||
|
public static class NetmapMapper
|
||||||
|
{
|
||||||
|
public static FrostFsNetmapSnapshot ToModel(this NetmapSnapshotResponse netmap)
|
||||||
|
{
|
||||||
|
if (netmap is null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(netmap));
|
||||||
|
}
|
||||||
|
|
||||||
|
return new FrostFsNetmapSnapshot(
|
||||||
|
netmap.Body.Netmap.Epoch,
|
||||||
|
netmap.Body.Netmap.Nodes
|
||||||
|
.Select(n => n.ToModel(netmap.MetaHeader.Version))
|
||||||
|
.ToArray());
|
||||||
|
}
|
||||||
|
}
|
45
src/FrostFS.SDK.Client/Mappers/Netmap/NodeInfo.cs
Normal file
45
src/FrostFS.SDK.Client/Mappers/Netmap/NodeInfo.cs
Normal file
|
@ -0,0 +1,45 @@
|
||||||
|
using System;
|
||||||
|
using System.Linq;
|
||||||
|
|
||||||
|
using FrostFS.Netmap;
|
||||||
|
using FrostFS.SDK.Client.Mappers.GRPC;
|
||||||
|
|
||||||
|
namespace FrostFS.SDK.Client;
|
||||||
|
|
||||||
|
public static class NodeInfoMapper
|
||||||
|
{
|
||||||
|
public static FrostFsNodeInfo ToModel(this LocalNodeInfoResponse.Types.Body node)
|
||||||
|
{
|
||||||
|
if (node is null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(node));
|
||||||
|
}
|
||||||
|
|
||||||
|
return node.NodeInfo.ToModel(node.Version);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static FrostFsNodeInfo ToModel(this NodeInfo nodeInfo, Refs.Version version)
|
||||||
|
{
|
||||||
|
if (nodeInfo is null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(nodeInfo));
|
||||||
|
}
|
||||||
|
|
||||||
|
NodeState state = nodeInfo.State switch
|
||||||
|
{
|
||||||
|
NodeInfo.Types.State.Unspecified => NodeState.Unspecified,
|
||||||
|
NodeInfo.Types.State.Online => NodeState.Online,
|
||||||
|
NodeInfo.Types.State.Offline => NodeState.Offline,
|
||||||
|
NodeInfo.Types.State.Maintenance => NodeState.Maintenance,
|
||||||
|
_ => throw new ArgumentException($"Unknown NodeState. Value: '{nodeInfo.State}'.")
|
||||||
|
};
|
||||||
|
|
||||||
|
return new FrostFsNodeInfo(
|
||||||
|
version: version.ToModel(),
|
||||||
|
state: state,
|
||||||
|
addresses: [.. nodeInfo.Addresses],
|
||||||
|
attributes: nodeInfo.Attributes.ToDictionary(n => n.Key, n => n.Value),
|
||||||
|
publicKey: nodeInfo.PublicKey.Memory
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
25
src/FrostFS.SDK.Client/Mappers/Netmap/PlacementPolicy.cs
Normal file
25
src/FrostFS.SDK.Client/Mappers/Netmap/PlacementPolicy.cs
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
using System;
|
||||||
|
using System.Linq;
|
||||||
|
|
||||||
|
using FrostFS.Netmap;
|
||||||
|
|
||||||
|
namespace FrostFS.SDK.Client;
|
||||||
|
|
||||||
|
public static class PlacementPolicyMapper
|
||||||
|
{
|
||||||
|
public static FrostFsPlacementPolicy ToModel(this PlacementPolicy placementPolicy)
|
||||||
|
{
|
||||||
|
if (placementPolicy is null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(placementPolicy));
|
||||||
|
}
|
||||||
|
|
||||||
|
return new FrostFsPlacementPolicy(
|
||||||
|
placementPolicy.Unique,
|
||||||
|
placementPolicy.ContainerBackupFactor,
|
||||||
|
[.. placementPolicy.Selectors.Select(s => s.ToModel())],
|
||||||
|
[.. placementPolicy.Filters.Select(f => f.ToModel())],
|
||||||
|
[.. placementPolicy.Replicas.Select(r => r.ToModel())]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
106
src/FrostFS.SDK.Client/Mappers/Netmap/Replica.cs
Normal file
106
src/FrostFS.SDK.Client/Mappers/Netmap/Replica.cs
Normal file
|
@ -0,0 +1,106 @@
|
||||||
|
using System;
|
||||||
|
using System.Linq;
|
||||||
|
|
||||||
|
using FrostFS.Netmap;
|
||||||
|
|
||||||
|
namespace FrostFS.SDK.Client;
|
||||||
|
|
||||||
|
public static class PolicyMapper
|
||||||
|
{
|
||||||
|
public static Replica ToMessage(this FrostFsReplica replica)
|
||||||
|
{
|
||||||
|
return new Replica
|
||||||
|
{
|
||||||
|
Count = (uint)replica.Count,
|
||||||
|
Selector = replica.Selector,
|
||||||
|
EcDataCount = replica.EcDataCount,
|
||||||
|
EcParityCount = replica.EcParityCount
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public static FrostFsReplica ToModel(this Replica replica)
|
||||||
|
{
|
||||||
|
if (replica is null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(replica));
|
||||||
|
}
|
||||||
|
|
||||||
|
return new FrostFsReplica((int)replica.Count, replica.Selector)
|
||||||
|
{
|
||||||
|
EcDataCount = replica.EcDataCount,
|
||||||
|
EcParityCount = replica.EcParityCount
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Selector ToMessage(this FrostFsSelector selector)
|
||||||
|
{
|
||||||
|
if (selector is null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(selector));
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Selector
|
||||||
|
{
|
||||||
|
Name = selector.Name,
|
||||||
|
Count = selector.Count,
|
||||||
|
Clause = (Clause)selector.Clause,
|
||||||
|
Attribute = selector.Attribute,
|
||||||
|
Filter = selector.Filter
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public static FrostFsSelector ToModel(this Selector selector)
|
||||||
|
{
|
||||||
|
if (selector is null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(selector));
|
||||||
|
}
|
||||||
|
|
||||||
|
var model = new FrostFsSelector(selector.Name)
|
||||||
|
{
|
||||||
|
Count = selector.Count,
|
||||||
|
Clause = (int)selector.Clause,
|
||||||
|
Attribute = selector.Attribute,
|
||||||
|
Filter = selector.Filter
|
||||||
|
};
|
||||||
|
|
||||||
|
return model;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Filter ToMessage(this FrostFsFilter filter)
|
||||||
|
{
|
||||||
|
if (filter is null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(filter));
|
||||||
|
}
|
||||||
|
|
||||||
|
var message = new Filter
|
||||||
|
{
|
||||||
|
Name = filter.Name,
|
||||||
|
Key = filter.Key,
|
||||||
|
Op = (Operation)filter.Operation,
|
||||||
|
Value = filter.Value,
|
||||||
|
};
|
||||||
|
|
||||||
|
message.Filters.AddRange(filter.Filters.Select(f => f.ToMessage()));
|
||||||
|
|
||||||
|
return message;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static FrostFsFilter ToModel(this Filter filter)
|
||||||
|
{
|
||||||
|
if (filter is null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(filter));
|
||||||
|
}
|
||||||
|
|
||||||
|
var model = new FrostFsFilter(
|
||||||
|
filter.Name,
|
||||||
|
filter.Key,
|
||||||
|
(int)filter.Op,
|
||||||
|
filter.Value,
|
||||||
|
[.. filter.Filters.Select(f => f.ToModel())]);
|
||||||
|
|
||||||
|
return model;
|
||||||
|
}
|
||||||
|
}
|
12
src/FrostFS.SDK.Client/Mappers/Object/Object.cs
Normal file
12
src/FrostFS.SDK.Client/Mappers/Object/Object.cs
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
namespace FrostFS.SDK.Client.Mappers.GRPC;
|
||||||
|
|
||||||
|
internal static class ObjectMapper
|
||||||
|
{
|
||||||
|
internal static FrostFsObject ToModel(this Object.Object obj)
|
||||||
|
{
|
||||||
|
return new FrostFsObject(obj.Header.ToModel())
|
||||||
|
{
|
||||||
|
ObjectId = FrostFsObjectId.FromHash(obj.ObjectId.Value.Span)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,27 @@
|
||||||
|
using System;
|
||||||
|
|
||||||
|
using FrostFS.Object;
|
||||||
|
|
||||||
|
namespace FrostFS.SDK.Client.Mappers.GRPC;
|
||||||
|
|
||||||
|
public static class ObjectAttributeMapper
|
||||||
|
{
|
||||||
|
public static Header.Types.Attribute ToMessage(this FrostFsAttributePair attribute)
|
||||||
|
{
|
||||||
|
return new Header.Types.Attribute
|
||||||
|
{
|
||||||
|
Key = attribute.Key,
|
||||||
|
Value = attribute.Value
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public static FrostFsAttributePair ToModel(this Header.Types.Attribute attribute)
|
||||||
|
{
|
||||||
|
if (attribute is null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(attribute));
|
||||||
|
}
|
||||||
|
|
||||||
|
return new FrostFsAttributePair(attribute.Key, attribute.Value);
|
||||||
|
}
|
||||||
|
}
|
34
src/FrostFS.SDK.Client/Mappers/Object/ObjectFilterMapper.cs
Normal file
34
src/FrostFS.SDK.Client/Mappers/Object/ObjectFilterMapper.cs
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
using System;
|
||||||
|
|
||||||
|
using FrostFS.Object;
|
||||||
|
|
||||||
|
namespace FrostFS.SDK.Client.Mappers.GRPC;
|
||||||
|
|
||||||
|
public static class ObjectFilterMapper
|
||||||
|
{
|
||||||
|
public static SearchRequest.Types.Body.Types.Filter ToMessage(this IObjectFilter filter)
|
||||||
|
{
|
||||||
|
if (filter is null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(filter));
|
||||||
|
}
|
||||||
|
|
||||||
|
var objMatchTypeName = filter.MatchType switch
|
||||||
|
{
|
||||||
|
FrostFsMatchType.Unspecified => MatchType.Unspecified,
|
||||||
|
FrostFsMatchType.Equals => MatchType.StringEqual,
|
||||||
|
FrostFsMatchType.NotEquals => MatchType.StringNotEqual,
|
||||||
|
FrostFsMatchType.KeyAbsent => MatchType.NotPresent,
|
||||||
|
FrostFsMatchType.StartsWith => MatchType.CommonPrefix,
|
||||||
|
|
||||||
|
_ => throw new ArgumentException($"Unknown MatchType. Value: '{filter.MatchType}'.")
|
||||||
|
};
|
||||||
|
|
||||||
|
return new SearchRequest.Types.Body.Types.Filter
|
||||||
|
{
|
||||||
|
MatchType = objMatchTypeName,
|
||||||
|
Key = filter.Key,
|
||||||
|
Value = filter.GetSerializedValue()
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
63
src/FrostFS.SDK.Client/Mappers/Object/ObjectHeaderMapper.cs
Normal file
63
src/FrostFS.SDK.Client/Mappers/Object/ObjectHeaderMapper.cs
Normal file
|
@ -0,0 +1,63 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.ObjectModel;
|
||||||
|
using System.Linq;
|
||||||
|
|
||||||
|
using FrostFS.Object;
|
||||||
|
using FrostFS.SDK.Cryptography;
|
||||||
|
|
||||||
|
namespace FrostFS.SDK.Client.Mappers.GRPC;
|
||||||
|
|
||||||
|
public static class ObjectHeaderMapper
|
||||||
|
{
|
||||||
|
public static FrostFsObjectHeader ToModel(this Header header)
|
||||||
|
{
|
||||||
|
if (header is null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(header));
|
||||||
|
}
|
||||||
|
|
||||||
|
var objTypeName = header.ObjectType switch
|
||||||
|
{
|
||||||
|
ObjectType.Regular => FrostFsObjectType.Regular,
|
||||||
|
ObjectType.Lock => FrostFsObjectType.Lock,
|
||||||
|
ObjectType.Tombstone => FrostFsObjectType.Tombstone,
|
||||||
|
_ => throw new ArgumentException($"Unknown ObjectType. Value: '{header.ObjectType}'.")
|
||||||
|
};
|
||||||
|
|
||||||
|
FrostFsSplit? split = header!.Split != null
|
||||||
|
? header.Split.ToModel()
|
||||||
|
: null;
|
||||||
|
|
||||||
|
var model = new FrostFsObjectHeader(
|
||||||
|
new FrostFsContainerId(Base58.Encode(header.ContainerId.Value.Span)),
|
||||||
|
objTypeName,
|
||||||
|
[.. header.Attributes.Select(attribute => attribute.ToModel())],
|
||||||
|
split,
|
||||||
|
header.OwnerId.ToModel(),
|
||||||
|
header.Version.ToModel())
|
||||||
|
{
|
||||||
|
PayloadLength = header.PayloadLength,
|
||||||
|
};
|
||||||
|
|
||||||
|
return model;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static FrostFsSplit ToModel(this Header.Types.Split split)
|
||||||
|
{
|
||||||
|
if (split is null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(split));
|
||||||
|
}
|
||||||
|
|
||||||
|
var children = split!.Children.Count != 0
|
||||||
|
? new ReadOnlyCollection<FrostFsObjectId>([.. split.Children.Select(x => x.ToModel())])
|
||||||
|
: null;
|
||||||
|
|
||||||
|
return new FrostFsSplit(new SplitId(split.SplitId.ToUuid()),
|
||||||
|
split.Previous?.ToModel(),
|
||||||
|
split.Parent?.ToModel(),
|
||||||
|
split.ParentHeader?.ToModel(),
|
||||||
|
null,
|
||||||
|
children);
|
||||||
|
}
|
||||||
|
}
|
33
src/FrostFS.SDK.Client/Mappers/Object/ObjectId.cs
Normal file
33
src/FrostFS.SDK.Client/Mappers/Object/ObjectId.cs
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
using System;
|
||||||
|
|
||||||
|
using FrostFS.Refs;
|
||||||
|
|
||||||
|
using Google.Protobuf;
|
||||||
|
|
||||||
|
namespace FrostFS.SDK.Client.Mappers.GRPC;
|
||||||
|
|
||||||
|
public static class ObjectIdMapper
|
||||||
|
{
|
||||||
|
public static ObjectID ToMessage(this FrostFsObjectId objectId)
|
||||||
|
{
|
||||||
|
if (objectId is null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(objectId));
|
||||||
|
}
|
||||||
|
|
||||||
|
return new ObjectID
|
||||||
|
{
|
||||||
|
Value = UnsafeByteOperations.UnsafeWrap(objectId.ToHash())
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public static FrostFsObjectId ToModel(this ObjectID objectId)
|
||||||
|
{
|
||||||
|
if (objectId is null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(objectId));
|
||||||
|
}
|
||||||
|
|
||||||
|
return FrostFsObjectId.FromHash(objectId.Value.Span);
|
||||||
|
}
|
||||||
|
}
|
54
src/FrostFS.SDK.Client/Mappers/OwnerId.cs
Normal file
54
src/FrostFS.SDK.Client/Mappers/OwnerId.cs
Normal file
|
@ -0,0 +1,54 @@
|
||||||
|
using System;
|
||||||
|
|
||||||
|
using FrostFS.Refs;
|
||||||
|
using FrostFS.SDK.Cryptography;
|
||||||
|
|
||||||
|
using Google.Protobuf;
|
||||||
|
|
||||||
|
using Microsoft.Extensions.Caching.Memory;
|
||||||
|
|
||||||
|
namespace FrostFS.SDK.Client.Mappers.GRPC;
|
||||||
|
|
||||||
|
public static class OwnerIdMapper
|
||||||
|
{
|
||||||
|
private static readonly MemoryCacheEntryOptions _oneHourExpiration = new MemoryCacheEntryOptions()
|
||||||
|
.SetSlidingExpiration(TimeSpan.FromHours(1))
|
||||||
|
.SetSize(1);
|
||||||
|
|
||||||
|
public static OwnerID ToMessage(this FrostFsOwner model)
|
||||||
|
{
|
||||||
|
if (model is null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(model));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!Caches.Owners.TryGetValue(model, out OwnerID? message))
|
||||||
|
{
|
||||||
|
message = new OwnerID
|
||||||
|
{
|
||||||
|
Value = UnsafeByteOperations.UnsafeWrap(model.ToHash())
|
||||||
|
};
|
||||||
|
|
||||||
|
Caches.Owners.Set(model, message, _oneHourExpiration);
|
||||||
|
}
|
||||||
|
|
||||||
|
return message!;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static FrostFsOwner ToModel(this OwnerID message)
|
||||||
|
{
|
||||||
|
if (message is null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(message));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!Caches.Owners.TryGetValue(message, out FrostFsOwner? model))
|
||||||
|
{
|
||||||
|
model = new FrostFsOwner(Base58.Encode(message.Value.Span));
|
||||||
|
|
||||||
|
Caches.Owners.Set(message, model, _oneHourExpiration);
|
||||||
|
}
|
||||||
|
|
||||||
|
return model!;
|
||||||
|
}
|
||||||
|
}
|
31
src/FrostFS.SDK.Client/Mappers/SignatureMapper.cs
Normal file
31
src/FrostFS.SDK.Client/Mappers/SignatureMapper.cs
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
using System;
|
||||||
|
|
||||||
|
using Google.Protobuf;
|
||||||
|
|
||||||
|
namespace FrostFS.SDK.Client.Mappers.GRPC;
|
||||||
|
|
||||||
|
public static class SignatureMapper
|
||||||
|
{
|
||||||
|
public static Refs.Signature ToMessage(this FrostFsSignature signature)
|
||||||
|
{
|
||||||
|
if (signature is null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(signature));
|
||||||
|
}
|
||||||
|
|
||||||
|
var scheme = signature.Scheme switch
|
||||||
|
{
|
||||||
|
SignatureScheme.EcdsaRfc6979Sha256 => Refs.SignatureScheme.EcdsaRfc6979Sha256,
|
||||||
|
SignatureScheme.EcdsaRfc6979Sha256WalletConnect => Refs.SignatureScheme.EcdsaRfc6979Sha256WalletConnect,
|
||||||
|
SignatureScheme.EcdsaSha512 => Refs.SignatureScheme.EcdsaSha512,
|
||||||
|
_ => throw new ArgumentException(nameof(signature.Scheme), $"Unexpected enum value: {signature.Scheme}")
|
||||||
|
};
|
||||||
|
|
||||||
|
return new Refs.Signature
|
||||||
|
{
|
||||||
|
Key = UnsafeByteOperations.UnsafeWrap(signature.Key),
|
||||||
|
Scheme = scheme,
|
||||||
|
Sign = UnsafeByteOperations.UnsafeWrap(signature.Sign)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
22
src/FrostFS.SDK.Client/Mappers/Status.cs
Normal file
22
src/FrostFS.SDK.Client/Mappers/Status.cs
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
using System;
|
||||||
|
using System.Linq;
|
||||||
|
|
||||||
|
namespace FrostFS.SDK.Client.Mappers.GRPC;
|
||||||
|
|
||||||
|
public static class StatusMapper
|
||||||
|
{
|
||||||
|
public static FrostFsResponseStatus ToModel(this Status.Status status)
|
||||||
|
{
|
||||||
|
if (status is null)
|
||||||
|
return new FrostFsResponseStatus(FrostFsStatusCode.Success);
|
||||||
|
|
||||||
|
var codeName = Enum.GetName(typeof(FrostFsStatusCode), status.Code);
|
||||||
|
|
||||||
|
return codeName is null
|
||||||
|
? throw new ArgumentException($"Unknown StatusCode. Value: '{status.Code}'.")
|
||||||
|
: new FrostFsResponseStatus(
|
||||||
|
(FrostFsStatusCode)status.Code,
|
||||||
|
status.Message,
|
||||||
|
string.Join(", ", status.Details.Select(d => System.Text.Encoding.UTF8.GetString([.. d.Value]))));
|
||||||
|
}
|
||||||
|
}
|
85
src/FrostFS.SDK.Client/Mappers/Version.cs
Normal file
85
src/FrostFS.SDK.Client/Mappers/Version.cs
Normal file
|
@ -0,0 +1,85 @@
|
||||||
|
using System.Collections;
|
||||||
|
using System.Threading;
|
||||||
|
|
||||||
|
using FrostFS.Refs;
|
||||||
|
|
||||||
|
namespace FrostFS.SDK.Client.Mappers.GRPC;
|
||||||
|
|
||||||
|
public static class VersionMapper
|
||||||
|
{
|
||||||
|
private static readonly Hashtable _cacheMessages = [];
|
||||||
|
private static readonly Hashtable _cacheModels = [];
|
||||||
|
private static SpinLock _spinlock;
|
||||||
|
|
||||||
|
public static Version ToMessage(this FrostFsVersion model)
|
||||||
|
{
|
||||||
|
if (model is null)
|
||||||
|
{
|
||||||
|
throw new System.ArgumentNullException(nameof(model));
|
||||||
|
}
|
||||||
|
|
||||||
|
var key = model.Major << 16 + model.Minor;
|
||||||
|
|
||||||
|
if (!_cacheMessages.ContainsKey(key))
|
||||||
|
{
|
||||||
|
bool lockTaken = false;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
_spinlock.Enter(ref lockTaken);
|
||||||
|
var message = new Version
|
||||||
|
{
|
||||||
|
Major = (uint)model.Major,
|
||||||
|
Minor = (uint)model.Minor
|
||||||
|
};
|
||||||
|
|
||||||
|
_cacheMessages.Add(key, message);
|
||||||
|
return message;
|
||||||
|
}
|
||||||
|
catch (System.ArgumentException)
|
||||||
|
{
|
||||||
|
// ignore attempt to add duplicate
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
if (lockTaken)
|
||||||
|
_spinlock.Exit(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (Version)_cacheMessages[key];
|
||||||
|
}
|
||||||
|
|
||||||
|
public static FrostFsVersion ToModel(this Version message)
|
||||||
|
{
|
||||||
|
if (message is null)
|
||||||
|
{
|
||||||
|
throw new System.ArgumentNullException(nameof(message));
|
||||||
|
}
|
||||||
|
|
||||||
|
var key = (int)message.Major << 16 + (int)message.Minor;
|
||||||
|
|
||||||
|
if (!_cacheModels.ContainsKey(key))
|
||||||
|
{
|
||||||
|
bool lockTaken = false;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
_spinlock.Enter(ref lockTaken);
|
||||||
|
var model = new FrostFsVersion((int)message.Major, (int)message.Minor);
|
||||||
|
|
||||||
|
_cacheModels.Add(key, model);
|
||||||
|
return model;
|
||||||
|
}
|
||||||
|
catch (System.ArgumentException)
|
||||||
|
{
|
||||||
|
// ignore attempt to add duplicate
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
if (lockTaken)
|
||||||
|
_spinlock.Exit(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (FrostFsVersion)_cacheModels[key];
|
||||||
|
}
|
||||||
|
}
|
37
src/FrostFS.SDK.Client/Models/Client/ClientSettings.cs
Normal file
37
src/FrostFS.SDK.Client/Models/Client/ClientSettings.cs
Normal file
|
@ -0,0 +1,37 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.ObjectModel;
|
||||||
|
using System.Globalization;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
using Grpc.Core.Interceptors;
|
||||||
|
|
||||||
|
namespace FrostFS.SDK;
|
||||||
|
|
||||||
|
public class ClientSettings
|
||||||
|
{
|
||||||
|
protected static readonly string errorTemplate = "{0} is required parameter";
|
||||||
|
|
||||||
|
public string Host { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
public string Key { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
public Action<CallStatistics>? Callback { get; set; }
|
||||||
|
|
||||||
|
public Collection<Interceptor> Interceptors { get; } = [];
|
||||||
|
|
||||||
|
public void Validate()
|
||||||
|
{
|
||||||
|
StringBuilder? errors = null;
|
||||||
|
|
||||||
|
if (string.IsNullOrWhiteSpace(Host))
|
||||||
|
(errors = new StringBuilder(128)).AppendLine(string.Format(CultureInfo.InvariantCulture, errorTemplate, nameof(Host)));
|
||||||
|
|
||||||
|
if (string.IsNullOrWhiteSpace(Key))
|
||||||
|
(errors ??= new StringBuilder(128)).AppendLine(string.Format(CultureInfo.InvariantCulture, errorTemplate, nameof(Key)));
|
||||||
|
|
||||||
|
if (errors != null)
|
||||||
|
{
|
||||||
|
throw new ArgumentException(errors.ToString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,55 @@
|
||||||
|
using FrostFS.Refs;
|
||||||
|
using FrostFS.SDK.Client;
|
||||||
|
using FrostFS.SDK.Client.Mappers.GRPC;
|
||||||
|
using FrostFS.SDK.Cryptography;
|
||||||
|
|
||||||
|
namespace FrostFS.SDK;
|
||||||
|
|
||||||
|
public class FrostFsContainerId
|
||||||
|
{
|
||||||
|
private string? modelId;
|
||||||
|
private ContainerID? containerID;
|
||||||
|
|
||||||
|
public FrostFsContainerId(string id)
|
||||||
|
{
|
||||||
|
this.modelId = id;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal FrostFsContainerId(ContainerID id)
|
||||||
|
{
|
||||||
|
this.containerID = id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public string GetValue()
|
||||||
|
{
|
||||||
|
if (this.modelId != null)
|
||||||
|
return this.modelId;
|
||||||
|
|
||||||
|
if (containerID != null)
|
||||||
|
{
|
||||||
|
this.modelId = Base58.Encode(containerID.Value.Span);
|
||||||
|
return this.modelId;
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new FrostFsInvalidObjectException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public ContainerID GetContainerID()
|
||||||
|
{
|
||||||
|
if (this.containerID != null)
|
||||||
|
return this.containerID;
|
||||||
|
|
||||||
|
if (modelId != null)
|
||||||
|
{
|
||||||
|
this.containerID = this.ToMessage();
|
||||||
|
return this.containerID;
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new FrostFsInvalidObjectException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public override string ToString()
|
||||||
|
{
|
||||||
|
return GetValue();
|
||||||
|
}
|
||||||
|
}
|
112
src/FrostFS.SDK.Client/Models/Containers/FrostFsContainerInfo.cs
Normal file
112
src/FrostFS.SDK.Client/Models/Containers/FrostFsContainerInfo.cs
Normal file
|
@ -0,0 +1,112 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.ObjectModel;
|
||||||
|
using System.Linq;
|
||||||
|
|
||||||
|
using FrostFS.SDK.Client;
|
||||||
|
using FrostFS.SDK.Client.Mappers.GRPC;
|
||||||
|
using FrostFS.SDK.Cryptography;
|
||||||
|
|
||||||
|
using Google.Protobuf;
|
||||||
|
|
||||||
|
namespace FrostFS.SDK;
|
||||||
|
|
||||||
|
public class FrostFsContainerInfo
|
||||||
|
{
|
||||||
|
private Container.Container.Types.Attribute[]? grpsAttributes;
|
||||||
|
private ReadOnlyCollection<FrostFsAttributePair>? attributes;
|
||||||
|
private FrostFsPlacementPolicy? placementPolicy;
|
||||||
|
private Guid? nonce;
|
||||||
|
|
||||||
|
private Container.Container? container;
|
||||||
|
|
||||||
|
public FrostFsContainerInfo(
|
||||||
|
FrostFsPlacementPolicy placementPolicy,
|
||||||
|
FrostFsAttributePair[]? attributes = null,
|
||||||
|
FrostFsVersion? version = null,
|
||||||
|
FrostFsOwner? owner = null,
|
||||||
|
Guid? nonce = null)
|
||||||
|
{
|
||||||
|
this.placementPolicy = placementPolicy;
|
||||||
|
Version = version;
|
||||||
|
Owner = owner;
|
||||||
|
this.nonce = nonce;
|
||||||
|
|
||||||
|
if (attributes != null)
|
||||||
|
this.attributes = new ReadOnlyCollection<FrostFsAttributePair>(attributes);
|
||||||
|
}
|
||||||
|
|
||||||
|
internal FrostFsContainerInfo(Container.Container container)
|
||||||
|
{
|
||||||
|
this.container = container;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Guid Nonce
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
nonce ??= container?.Nonce != null ? container.Nonce.ToUuid() : Guid.NewGuid();
|
||||||
|
return nonce.Value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public FrostFsPlacementPolicy? PlacementPolicy
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
placementPolicy ??= container?.PlacementPolicy?.ToModel();
|
||||||
|
return placementPolicy;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public ReadOnlyCollection<FrostFsAttributePair>? Attributes
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
if (attributes == null && grpsAttributes != null)
|
||||||
|
attributes = new ReadOnlyCollection<FrostFsAttributePair>(grpsAttributes.Select(a => new FrostFsAttributePair(a.Key, a.Value)).ToList());
|
||||||
|
|
||||||
|
return attributes;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public FrostFsVersion? Version { get; private set; }
|
||||||
|
|
||||||
|
public FrostFsOwner? Owner { get; private set; }
|
||||||
|
|
||||||
|
internal Container.Container.Types.Attribute[]? GetGrpsAttributes()
|
||||||
|
{
|
||||||
|
grpsAttributes ??= Attributes?
|
||||||
|
.Select(a => new Container.Container.Types.Attribute { Key = a.Key, Value = a.Value })
|
||||||
|
.ToArray();
|
||||||
|
|
||||||
|
return grpsAttributes;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal Container.Container GetContainer()
|
||||||
|
{
|
||||||
|
if (this.container == null)
|
||||||
|
{
|
||||||
|
if (PlacementPolicy == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException("PlacementPolicy is null");
|
||||||
|
}
|
||||||
|
|
||||||
|
Span<byte> nonce = stackalloc byte[16];
|
||||||
|
Nonce.ToBytes(nonce);
|
||||||
|
|
||||||
|
this.container = new Container.Container()
|
||||||
|
{
|
||||||
|
PlacementPolicy = PlacementPolicy.Value.GetPolicy(),
|
||||||
|
Nonce = ByteString.CopyFrom(nonce),
|
||||||
|
OwnerId = Owner?.OwnerID,
|
||||||
|
Version = Version?.VersionID
|
||||||
|
};
|
||||||
|
|
||||||
|
var attribs = GetGrpsAttributes();
|
||||||
|
if (attribs != null)
|
||||||
|
this.container.Attributes.AddRange(attribs);
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.container;
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,6 +1,6 @@
|
||||||
namespace FrostFS.SDK.ModelsV2.Enums;
|
namespace FrostFS.SDK;
|
||||||
|
|
||||||
public enum ObjectMatchType
|
public enum FrostFsMatchType
|
||||||
{
|
{
|
||||||
Unspecified = 0,
|
Unspecified = 0,
|
||||||
Equals = 1,
|
Equals = 1,
|
8
src/FrostFS.SDK.Client/Models/Enums/FrostFsObjectType.cs
Normal file
8
src/FrostFS.SDK.Client/Models/Enums/FrostFsObjectType.cs
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
namespace FrostFS.SDK;
|
||||||
|
|
||||||
|
public enum FrostFsObjectType
|
||||||
|
{
|
||||||
|
Regular = 0,
|
||||||
|
Tombstone = 1,
|
||||||
|
Lock = 3
|
||||||
|
}
|
|
@ -1,6 +1,6 @@
|
||||||
namespace FrostFS.SDK.ModelsV2.Enums;
|
namespace FrostFS.SDK;
|
||||||
|
|
||||||
public enum StatusCode
|
public enum FrostFsStatusCode
|
||||||
{
|
{
|
||||||
Success = 0,
|
Success = 0,
|
||||||
Internal = 1024,
|
Internal = 1024,
|
|
@ -1,4 +1,4 @@
|
||||||
namespace FrostFS.SDK.ModelsV2.Enums;
|
namespace FrostFS.SDK;
|
||||||
|
|
||||||
public enum NodeState
|
public enum NodeState
|
||||||
{
|
{
|
8
src/FrostFS.SDK.Client/Models/Enums/SignatureScheme.cs
Normal file
8
src/FrostFS.SDK.Client/Models/Enums/SignatureScheme.cs
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
namespace FrostFS.SDK;
|
||||||
|
|
||||||
|
public enum SignatureScheme
|
||||||
|
{
|
||||||
|
EcdsaSha512,
|
||||||
|
EcdsaRfc6979Sha256,
|
||||||
|
EcdsaRfc6979Sha256WalletConnect
|
||||||
|
}
|
7
src/FrostFS.SDK.Client/Models/Misc/CallStatistics.cs
Normal file
7
src/FrostFS.SDK.Client/Models/Misc/CallStatistics.cs
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
namespace FrostFS.SDK;
|
||||||
|
|
||||||
|
public class CallStatistics
|
||||||
|
{
|
||||||
|
public string? MethodName { get; set; }
|
||||||
|
public long ElapsedMicroSeconds { get; set; }
|
||||||
|
}
|
21
src/FrostFS.SDK.Client/Models/Misc/CheckSum.cs
Normal file
21
src/FrostFS.SDK.Client/Models/Misc/CheckSum.cs
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
using System;
|
||||||
|
|
||||||
|
using FrostFS.SDK.Cryptography;
|
||||||
|
|
||||||
|
namespace FrostFS.SDK;
|
||||||
|
|
||||||
|
public class CheckSum
|
||||||
|
{
|
||||||
|
private byte[]? hash;
|
||||||
|
private string? text;
|
||||||
|
|
||||||
|
public static CheckSum CreateCheckSum(byte[] content)
|
||||||
|
{
|
||||||
|
return new CheckSum { hash = DataHasher.Sha256(content.AsSpan()) };
|
||||||
|
}
|
||||||
|
|
||||||
|
public override string ToString()
|
||||||
|
{
|
||||||
|
return text ??= BitConverter.ToString(hash).Replace("-", "");
|
||||||
|
}
|
||||||
|
}
|
52
src/FrostFS.SDK.Client/Models/Misc/Constants.cs
Normal file
52
src/FrostFS.SDK.Client/Models/Misc/Constants.cs
Normal file
|
@ -0,0 +1,52 @@
|
||||||
|
namespace FrostFS.SDK;
|
||||||
|
|
||||||
|
public static class Constants
|
||||||
|
{
|
||||||
|
public const int ObjectChunkSize = 3 * (1 << 20);
|
||||||
|
public const int Sha256HashLength = 32;
|
||||||
|
|
||||||
|
// HeaderPrefix is a prefix of key to object header value or property.
|
||||||
|
public const string HeaderPrefix = "$Object:";
|
||||||
|
|
||||||
|
// FilterHeaderVersion is a filter key to "version" field of the object header.
|
||||||
|
public const string FilterHeaderVersion = HeaderPrefix + "version";
|
||||||
|
|
||||||
|
// FilterHeaderObjectID is a filter key to "object_id" field of the object.
|
||||||
|
public const string FilterHeaderObjectID = HeaderPrefix + "objectID";
|
||||||
|
|
||||||
|
// FilterHeaderContainerID is a filter key to "container_id" field of the object header.
|
||||||
|
public const string FilterHeaderContainerID = HeaderPrefix + "containerID";
|
||||||
|
|
||||||
|
// FilterHeaderOwnerID is a filter key to "owner_id" field of the object header.
|
||||||
|
public const string FilterHeaderOwnerID = HeaderPrefix + "ownerID";
|
||||||
|
|
||||||
|
// FilterHeaderCreationEpoch is a filter key to "creation_epoch" field of the object header.
|
||||||
|
public const string FilterHeaderCreationEpoch = HeaderPrefix + "creationEpoch";
|
||||||
|
|
||||||
|
// FilterHeaderPayloadLength is a filter key to "payload_length" field of the object header.
|
||||||
|
public const string FilterHeaderPayloadLength = HeaderPrefix + "payloadLength";
|
||||||
|
|
||||||
|
// FilterHeaderPayloadHash is a filter key to "payload_hash" field of the object header.
|
||||||
|
public const string FilterHeaderPayloadHash = HeaderPrefix + "payloadHash";
|
||||||
|
|
||||||
|
// FilterHeaderObjectType is a filter key to "object_type" field of the object header.
|
||||||
|
public const string FilterHeaderObjectType = HeaderPrefix + "objectType";
|
||||||
|
|
||||||
|
// FilterHeaderHomomorphicHash is a filter key to "homomorphic_hash" field of the object header.
|
||||||
|
public const string FilterHeaderHomomorphicHash = HeaderPrefix + "homomorphicHash";
|
||||||
|
|
||||||
|
// FilterHeaderParent is a filter key to "split.parent" field of the object header.
|
||||||
|
public const string FilterHeaderParent = HeaderPrefix + "split.parent";
|
||||||
|
|
||||||
|
// FilterHeaderSplitID is a filter key to "split.splitID" field of the object header.
|
||||||
|
public const string FilterHeaderSplitID = HeaderPrefix + "split.splitID";
|
||||||
|
|
||||||
|
// FilterHeaderECParent is a filter key to "ec.parent" field of the object header.
|
||||||
|
public const string FilterHeaderECParent = HeaderPrefix + "ec.parent";
|
||||||
|
|
||||||
|
// FilterPropertyRoot is a filter key to check if regular object is on top of split hierarchy.
|
||||||
|
public const string FilterHeaderRoot = HeaderPrefix + "ROOT";
|
||||||
|
|
||||||
|
// FilterPropertyPhy is a filter key to check if an object physically stored on a node.
|
||||||
|
public const string FilterHeaderPhy = HeaderPrefix + "PHY";
|
||||||
|
}
|
28
src/FrostFS.SDK.Client/Models/Netmap/FrostFsFilter.cs
Normal file
28
src/FrostFS.SDK.Client/Models/Netmap/FrostFsFilter.cs
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
using System.Linq;
|
||||||
|
using FrostFS.Netmap;
|
||||||
|
|
||||||
|
namespace FrostFS.SDK;
|
||||||
|
|
||||||
|
public class FrostFsFilter(string name, string key, int operation, string value, FrostFsFilter[] filters) : IFrostFsFilter
|
||||||
|
{
|
||||||
|
public string Name { get; } = name;
|
||||||
|
public string Key { get; } = key;
|
||||||
|
public int Operation { get; } = operation;
|
||||||
|
public string Value { get; } = value;
|
||||||
|
public FrostFsFilter[] Filters { get; } = filters;
|
||||||
|
|
||||||
|
internal Filter GetMessage()
|
||||||
|
{
|
||||||
|
var filter = new Filter()
|
||||||
|
{
|
||||||
|
Name = Name,
|
||||||
|
Key = Key,
|
||||||
|
Op = (Operation)Operation,
|
||||||
|
Value = Value,
|
||||||
|
};
|
||||||
|
|
||||||
|
filter.Filters.AddRange(Filters.Select(f => f.GetMessage()));
|
||||||
|
|
||||||
|
return filter;
|
||||||
|
}
|
||||||
|
}
|
246
src/FrostFS.SDK.Client/Models/Netmap/FrostFsNetmapSnapshot.cs
Normal file
246
src/FrostFS.SDK.Client/Models/Netmap/FrostFsNetmapSnapshot.cs
Normal file
|
@ -0,0 +1,246 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
|
||||||
|
using FrostFS.SDK.Client;
|
||||||
|
using FrostFS.SDK.Client.Models.Netmap.Placement;
|
||||||
|
using FrostFS.SDK.Cryptography;
|
||||||
|
|
||||||
|
namespace FrostFS.SDK;
|
||||||
|
|
||||||
|
public class FrostFsNetmapSnapshot(ulong epoch, IReadOnlyList<FrostFsNodeInfo> nodeInfoCollection)
|
||||||
|
{
|
||||||
|
public ulong Epoch { get; private set; } = epoch;
|
||||||
|
|
||||||
|
public IReadOnlyList<FrostFsNodeInfo> NodeInfoCollection { get; private set; } = nodeInfoCollection;
|
||||||
|
|
||||||
|
internal static INormalizer NewReverseMinNorm(double minV)
|
||||||
|
{
|
||||||
|
return new ReverseMinNorm { min = minV };
|
||||||
|
}
|
||||||
|
|
||||||
|
// newSigmoidNorm returns a normalizer which
|
||||||
|
// normalize values in range of 0.0 to 1.0 to a scaled sigmoid.
|
||||||
|
internal static INormalizer NewSigmoidNorm(double scale)
|
||||||
|
{
|
||||||
|
return new SigmoidNorm(scale);
|
||||||
|
}
|
||||||
|
|
||||||
|
// PlacementVectors sorts container nodes returned by ContainerNodes method
|
||||||
|
// and returns placement vectors for the entity identified by the given pivot.
|
||||||
|
// For example, in order to build node list to store the object, binary-encoded
|
||||||
|
// object identifier can be used as pivot. Result is deterministic for
|
||||||
|
// the fixed NetMap and parameters.
|
||||||
|
public FrostFsNodeInfo[][] PlacementVectors(FrostFsNodeInfo[][] vectors, byte[] pivot)
|
||||||
|
{
|
||||||
|
if (vectors is null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(vectors));
|
||||||
|
}
|
||||||
|
|
||||||
|
using var murmur3 = new Murmur3(0);
|
||||||
|
var hash = murmur3.GetCheckSum64(pivot);
|
||||||
|
|
||||||
|
var wf = Tools.DefaultWeightFunc(NodeInfoCollection.ToArray());
|
||||||
|
|
||||||
|
var result = new FrostFsNodeInfo[vectors.Length][];
|
||||||
|
var maxSize = vectors.Max(x => x.Length);
|
||||||
|
|
||||||
|
var spanWeigths = new double[maxSize];
|
||||||
|
|
||||||
|
for (int i = 0; i < vectors.Length; i++)
|
||||||
|
{
|
||||||
|
result[i] = new FrostFsNodeInfo[vectors[i].Length];
|
||||||
|
|
||||||
|
for (int j = 0; j < vectors[i].Length; j++)
|
||||||
|
{
|
||||||
|
result[i][j] = vectors[i][j];
|
||||||
|
}
|
||||||
|
|
||||||
|
Tools.AppendWeightsTo(result[i], wf, ref spanWeigths);
|
||||||
|
|
||||||
|
result[i] = Tools.SortHasherSliceByWeightValue(result[i].ToList<FrostFsNodeInfo>(), spanWeigths, hash).ToArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
// SelectFilterNodes returns a two-dimensional list of nodes as a result of applying the
|
||||||
|
// given SelectFilterExpr to the NetMap.
|
||||||
|
// If the SelectFilterExpr contains only filters, the result contains a single row with the
|
||||||
|
// result of the last filter application.
|
||||||
|
// If the SelectFilterExpr contains only selectors, the result contains the selection rows
|
||||||
|
// of the last select application.
|
||||||
|
List<List<FrostFsNodeInfo>> SelectFilterNodes(SelectFilterExpr expr)
|
||||||
|
{
|
||||||
|
var policy = new FrostFsPlacementPolicy(false, expr.Cbf, [expr.Selector], expr.Filters);
|
||||||
|
|
||||||
|
var ctx = new Context(this)
|
||||||
|
{
|
||||||
|
Cbf = expr.Cbf
|
||||||
|
};
|
||||||
|
|
||||||
|
ctx.ProcessFilters(policy);
|
||||||
|
ctx.ProcessSelectors(policy);
|
||||||
|
|
||||||
|
var ret = new List<List<FrostFsNodeInfo>>();
|
||||||
|
|
||||||
|
if (expr.Selector == null)
|
||||||
|
{
|
||||||
|
var lastFilter = expr.Filters.Last();
|
||||||
|
|
||||||
|
var subCollestion = new List<FrostFsNodeInfo>();
|
||||||
|
ret.Add(subCollestion);
|
||||||
|
|
||||||
|
foreach (var nodeInfo in NodeInfoCollection)
|
||||||
|
{
|
||||||
|
if (ctx.Match(ctx.ProcessedFilters[lastFilter.Name], nodeInfo))
|
||||||
|
{
|
||||||
|
subCollestion.Add(nodeInfo);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (expr.Selector.Name != null)
|
||||||
|
{
|
||||||
|
var sel = ctx.GetSelection(ctx.ProcessedSelectors[expr.Selector.Name]);
|
||||||
|
|
||||||
|
foreach (var ns in sel)
|
||||||
|
{
|
||||||
|
var subCollestion = new List<FrostFsNodeInfo>();
|
||||||
|
ret.Add(subCollestion);
|
||||||
|
foreach (var n in ns)
|
||||||
|
{
|
||||||
|
subCollestion.Add(n);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal static Func<FrostFsNodeInfo, double> NewWeightFunc(INormalizer capNorm, INormalizer priceNorm)
|
||||||
|
{
|
||||||
|
return new Func<FrostFsNodeInfo, double>((FrostFsNodeInfo nodeInfo) =>
|
||||||
|
{
|
||||||
|
return capNorm.Normalize(nodeInfo.GetCapacity()) * priceNorm.Normalize(nodeInfo.Price);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private static FrostFsNodeInfo[] FlattenNodes(List<List<FrostFsNodeInfo>> nodes)
|
||||||
|
{
|
||||||
|
int sz = 0;
|
||||||
|
foreach (var ns in nodes)
|
||||||
|
{
|
||||||
|
sz += ns.Count;
|
||||||
|
}
|
||||||
|
|
||||||
|
var result = new FrostFsNodeInfo[sz];
|
||||||
|
|
||||||
|
int i = 0;
|
||||||
|
foreach (var ns in nodes)
|
||||||
|
{
|
||||||
|
foreach (var n in ns)
|
||||||
|
{
|
||||||
|
result[i++] = n;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ContainerNodes returns two-dimensional list of nodes as a result of applying
|
||||||
|
// given PlacementPolicy to the NetMap. Each line of the list corresponds to a
|
||||||
|
// replica descriptor. Line order corresponds to order of ReplicaDescriptor list
|
||||||
|
// in the policy. Nodes are pre-filtered according to the Filter list from
|
||||||
|
// the policy, and then selected by Selector list. Result is deterministic for
|
||||||
|
// the fixed NetMap and parameters.
|
||||||
|
//
|
||||||
|
// Result can be used in PlacementVectors.
|
||||||
|
public FrostFsNodeInfo[][] ContainerNodes(FrostFsPlacementPolicy p, byte[]? pivot)
|
||||||
|
{
|
||||||
|
var c = new Context(this)
|
||||||
|
{
|
||||||
|
Cbf = p.BackupFactor == 0 ? 3 : p.BackupFactor
|
||||||
|
};
|
||||||
|
|
||||||
|
if (pivot != null && pivot.Length > 0)
|
||||||
|
{
|
||||||
|
c.HrwSeed = pivot;
|
||||||
|
|
||||||
|
using var murmur = new Murmur3(0);
|
||||||
|
c.HrwSeedHash = murmur.GetCheckSum64(pivot);
|
||||||
|
}
|
||||||
|
|
||||||
|
c.ProcessFilters(p);
|
||||||
|
c.ProcessSelectors(p);
|
||||||
|
|
||||||
|
var unique = p.IsUnique();
|
||||||
|
|
||||||
|
var result = new List<List<FrostFsNodeInfo>>(p.Replicas.Length);
|
||||||
|
for (int i = 0; i < p.Replicas.Length; i++)
|
||||||
|
{
|
||||||
|
result.Add([]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Note that the cached selectors are not used when the policy contains the UNIQUE flag.
|
||||||
|
// This is necessary because each selection vector affects potentially the subsequent vectors
|
||||||
|
// and thus we call getSelection in such case, in order to take into account nodes previously
|
||||||
|
// marked as used by earlier replicas.
|
||||||
|
for (int i = 0; i < p.Replicas.Length; i++)
|
||||||
|
{
|
||||||
|
var sName = p.Replicas[i].Selector;
|
||||||
|
|
||||||
|
if (string.IsNullOrEmpty(sName) && !(p.Replicas.Length == 1 && p.Selectors.Count == 1))
|
||||||
|
{
|
||||||
|
var s = new FrostFsSelector(string.Empty)
|
||||||
|
{
|
||||||
|
Count = p.Replicas[i].CountNodes(),
|
||||||
|
Filter = Context.mainFilterName
|
||||||
|
};
|
||||||
|
|
||||||
|
var nodes = c.GetSelection(s);
|
||||||
|
result[i].AddRange(FlattenNodes(nodes));
|
||||||
|
|
||||||
|
if (unique)
|
||||||
|
{
|
||||||
|
foreach (var n in result[i])
|
||||||
|
{
|
||||||
|
c.UsedNodes[n.Hash()] = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (unique)
|
||||||
|
{
|
||||||
|
if (!c.ProcessedSelectors.TryGetValue(sName, out var s) || s == null)
|
||||||
|
{
|
||||||
|
throw new FrostFsException($"selector not found: {sName}");
|
||||||
|
}
|
||||||
|
|
||||||
|
var nodes = c.GetSelection(c.ProcessedSelectors[sName]);
|
||||||
|
|
||||||
|
result[i].AddRange(FlattenNodes(nodes));
|
||||||
|
|
||||||
|
foreach (var n in result[i])
|
||||||
|
{
|
||||||
|
c.UsedNodes[n.Hash()] = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var nodes = c.Selections[sName];
|
||||||
|
result[i].AddRange(FlattenNodes(nodes));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var collection = new FrostFsNodeInfo[result.Count][];
|
||||||
|
for (int i = 0; i < result.Count; i++)
|
||||||
|
{
|
||||||
|
collection[i] = [.. result[i]];
|
||||||
|
}
|
||||||
|
|
||||||
|
return collection;
|
||||||
|
}
|
||||||
|
}
|
80
src/FrostFS.SDK.Client/Models/Netmap/FrostFsNodeInfo.cs
Normal file
80
src/FrostFS.SDK.Client/Models/Netmap/FrostFsNodeInfo.cs
Normal file
|
@ -0,0 +1,80 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Globalization;
|
||||||
|
|
||||||
|
using FrostFS.SDK.Client.Models.Netmap.Placement;
|
||||||
|
using FrostFS.SDK.Cryptography;
|
||||||
|
|
||||||
|
namespace FrostFS.SDK;
|
||||||
|
|
||||||
|
public class FrostFsNodeInfo(
|
||||||
|
FrostFsVersion version,
|
||||||
|
NodeState state,
|
||||||
|
IReadOnlyCollection<string> addresses,
|
||||||
|
IReadOnlyDictionary<string, string> attributes,
|
||||||
|
ReadOnlyMemory<byte> publicKey) : IHasher
|
||||||
|
{
|
||||||
|
private ulong _hash;
|
||||||
|
|
||||||
|
// attrPrice is a key to the node attribute that indicates the
|
||||||
|
// price in GAS tokens for storing one GB of data during one Epoch.
|
||||||
|
internal const string AttrPrice = "Price";
|
||||||
|
|
||||||
|
// attrCapacity is a key to the node attribute that indicates the
|
||||||
|
// total available disk space in Gigabytes.
|
||||||
|
internal const string AttrCapacity = "Capacity";
|
||||||
|
|
||||||
|
// attrExternalAddr is a key for the attribute storing node external addresses.
|
||||||
|
internal const string AttrExternalAddr = "ExternalAddr";
|
||||||
|
|
||||||
|
// sepExternalAddr is a separator for multi-value ExternalAddr attribute.
|
||||||
|
internal const string SepExternalAddr = ",";
|
||||||
|
|
||||||
|
private ulong price = ulong.MaxValue;
|
||||||
|
|
||||||
|
public NodeState State { get; } = state;
|
||||||
|
|
||||||
|
public FrostFsVersion Version { get; } = version;
|
||||||
|
|
||||||
|
public IReadOnlyCollection<string> Addresses { get; } = addresses;
|
||||||
|
|
||||||
|
public IReadOnlyDictionary<string, string> Attributes { get; } = attributes;
|
||||||
|
|
||||||
|
public ReadOnlyMemory<byte> PublicKey { get; } = publicKey;
|
||||||
|
|
||||||
|
public ulong Hash()
|
||||||
|
{
|
||||||
|
if (_hash == 0)
|
||||||
|
{
|
||||||
|
using var murmur3 = new Murmur3(0);
|
||||||
|
murmur3.Initialize();
|
||||||
|
_hash = murmur3.GetCheckSum64(PublicKey.ToArray());
|
||||||
|
}
|
||||||
|
|
||||||
|
return _hash;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal ulong GetCapacity()
|
||||||
|
{
|
||||||
|
if (!Attributes.TryGetValue(AttrCapacity, out var val))
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
return ulong.Parse(val, CultureInfo.InvariantCulture);
|
||||||
|
}
|
||||||
|
|
||||||
|
internal ulong Price
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
if (price == ulong.MaxValue)
|
||||||
|
{
|
||||||
|
if (!Attributes.TryGetValue(AttrPrice, out var val))
|
||||||
|
price = 0;
|
||||||
|
else
|
||||||
|
price = uint.Parse(val, CultureInfo.InvariantCulture);
|
||||||
|
}
|
||||||
|
|
||||||
|
return price;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
109
src/FrostFS.SDK.Client/Models/Netmap/FrostFsPlacementPolicy.cs
Normal file
109
src/FrostFS.SDK.Client/Models/Netmap/FrostFsPlacementPolicy.cs
Normal file
|
@ -0,0 +1,109 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.ObjectModel;
|
||||||
|
using System.Linq;
|
||||||
|
|
||||||
|
using FrostFS.Netmap;
|
||||||
|
using FrostFS.SDK.Client;
|
||||||
|
|
||||||
|
namespace FrostFS.SDK;
|
||||||
|
|
||||||
|
public struct FrostFsPlacementPolicy(bool unique,
|
||||||
|
uint backupFactor,
|
||||||
|
Collection<FrostFsSelector> selectors,
|
||||||
|
Collection<FrostFsFilter> filters,
|
||||||
|
params FrostFsReplica[] replicas)
|
||||||
|
: IEquatable<FrostFsPlacementPolicy>
|
||||||
|
{
|
||||||
|
private PlacementPolicy? policy;
|
||||||
|
|
||||||
|
public FrostFsReplica[] Replicas { get; } = replicas;
|
||||||
|
|
||||||
|
public Collection<FrostFsSelector> Selectors { get; } = selectors;
|
||||||
|
|
||||||
|
public Collection<FrostFsFilter> Filters { get; } = filters;
|
||||||
|
|
||||||
|
public bool Unique { get; } = unique;
|
||||||
|
|
||||||
|
public uint BackupFactor { get; } = backupFactor;
|
||||||
|
|
||||||
|
public override readonly bool Equals(object obj)
|
||||||
|
{
|
||||||
|
if (obj is null)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
var other = (FrostFsPlacementPolicy)obj;
|
||||||
|
|
||||||
|
return Equals(other);
|
||||||
|
}
|
||||||
|
|
||||||
|
public PlacementPolicy GetPolicy()
|
||||||
|
{
|
||||||
|
if (policy == null)
|
||||||
|
{
|
||||||
|
policy = new PlacementPolicy
|
||||||
|
{
|
||||||
|
Filters = { },
|
||||||
|
Selectors = { },
|
||||||
|
Replicas = { },
|
||||||
|
Unique = Unique,
|
||||||
|
ContainerBackupFactor = BackupFactor
|
||||||
|
};
|
||||||
|
|
||||||
|
if (Selectors != null && Selectors.Count > 0)
|
||||||
|
{
|
||||||
|
policy.Selectors.AddRange(Selectors.Select(s => s.GetMessage()));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Filters != null && Filters.Count > 0)
|
||||||
|
{
|
||||||
|
policy.Filters.AddRange(Filters.Select(s => s.ToMessage()));
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var replica in Replicas)
|
||||||
|
{
|
||||||
|
policy.Replicas.Add(replica.ToMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return policy;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal readonly bool IsUnique()
|
||||||
|
{
|
||||||
|
return Unique || Replicas.Any(r => r.EcDataCount != 0 || r.EcParityCount != 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override readonly int GetHashCode()
|
||||||
|
{
|
||||||
|
return Unique ? 17 : 0 + Replicas.GetHashCode();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool operator ==(FrostFsPlacementPolicy left, FrostFsPlacementPolicy right)
|
||||||
|
{
|
||||||
|
return left.Equals(right);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool operator !=(FrostFsPlacementPolicy left, FrostFsPlacementPolicy right)
|
||||||
|
{
|
||||||
|
return !(left == right);
|
||||||
|
}
|
||||||
|
|
||||||
|
public readonly bool Equals(FrostFsPlacementPolicy other)
|
||||||
|
{
|
||||||
|
var notEqual = Unique != other.Unique
|
||||||
|
|| Replicas.Length != other.Replicas.Length;
|
||||||
|
|
||||||
|
if (notEqual)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
foreach (var replica in Replicas)
|
||||||
|
{
|
||||||
|
if (!other.Replicas.Any(r => r.Equals(replica)))
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
59
src/FrostFS.SDK.Client/Models/Netmap/FrostFsReplica.cs
Normal file
59
src/FrostFS.SDK.Client/Models/Netmap/FrostFsReplica.cs
Normal file
|
@ -0,0 +1,59 @@
|
||||||
|
using System;
|
||||||
|
|
||||||
|
namespace FrostFS.SDK;
|
||||||
|
|
||||||
|
public struct FrostFsReplica : IEquatable<FrostFsReplica>
|
||||||
|
{
|
||||||
|
public int Count { get; set; }
|
||||||
|
public string Selector { get; set; }
|
||||||
|
public uint EcDataCount { get; set; }
|
||||||
|
public uint EcParityCount { get; set; }
|
||||||
|
|
||||||
|
public FrostFsReplica(int count, string? selector = null)
|
||||||
|
{
|
||||||
|
selector ??= string.Empty;
|
||||||
|
|
||||||
|
Count = count;
|
||||||
|
Selector = selector;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override readonly bool Equals(object obj)
|
||||||
|
{
|
||||||
|
if (obj is null)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
var other = (FrostFsReplica)obj;
|
||||||
|
|
||||||
|
return Count == other.Count && Selector == other.Selector;
|
||||||
|
}
|
||||||
|
|
||||||
|
public readonly uint CountNodes()
|
||||||
|
{
|
||||||
|
return Count != 0 ? (uint)Count : EcDataCount + EcParityCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override readonly int GetHashCode()
|
||||||
|
{
|
||||||
|
return (Count + Selector.GetHashCode()) ^ (int)EcDataCount ^ (int)EcParityCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool operator ==(FrostFsReplica left, FrostFsReplica right)
|
||||||
|
{
|
||||||
|
return left.Equals(right);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool operator !=(FrostFsReplica left, FrostFsReplica right)
|
||||||
|
{
|
||||||
|
return !(left == right);
|
||||||
|
}
|
||||||
|
|
||||||
|
public readonly bool Equals(FrostFsReplica other)
|
||||||
|
{
|
||||||
|
return Count == other.Count
|
||||||
|
&& Selector == other.Selector
|
||||||
|
&& EcDataCount == other.EcDataCount
|
||||||
|
&& EcParityCount == other.EcParityCount;
|
||||||
|
}
|
||||||
|
}
|
24
src/FrostFS.SDK.Client/Models/Netmap/FrostFsSelector.cs
Normal file
24
src/FrostFS.SDK.Client/Models/Netmap/FrostFsSelector.cs
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
using FrostFS.Netmap;
|
||||||
|
|
||||||
|
namespace FrostFS.SDK;
|
||||||
|
|
||||||
|
public class FrostFsSelector(string name)
|
||||||
|
{
|
||||||
|
public string Name { get; } = name;
|
||||||
|
public uint Count { get; set; }
|
||||||
|
public int Clause { get; set; }
|
||||||
|
public string? Attribute { get; set; }
|
||||||
|
public string? Filter { get; set; }
|
||||||
|
|
||||||
|
internal Selector GetMessage()
|
||||||
|
{
|
||||||
|
return new Selector()
|
||||||
|
{
|
||||||
|
Name = Name,
|
||||||
|
Clause = (Clause)Clause,
|
||||||
|
Count = Count,
|
||||||
|
Filter = Filter ?? string.Empty,
|
||||||
|
Attribute = Attribute ?? string.Empty,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
36
src/FrostFS.SDK.Client/Models/Netmap/FrostFsVersion.cs
Normal file
36
src/FrostFS.SDK.Client/Models/Netmap/FrostFsVersion.cs
Normal file
|
@ -0,0 +1,36 @@
|
||||||
|
using FrostFS.Refs;
|
||||||
|
using FrostFS.SDK.Client.Mappers.GRPC;
|
||||||
|
|
||||||
|
namespace FrostFS.SDK;
|
||||||
|
|
||||||
|
public class FrostFsVersion(int major, int minor)
|
||||||
|
{
|
||||||
|
private Version? version;
|
||||||
|
|
||||||
|
public int Major { get; set; } = major;
|
||||||
|
public int Minor { get; set; } = minor;
|
||||||
|
|
||||||
|
internal Version VersionID
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
this.version ??= this.ToMessage();
|
||||||
|
return this.version;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool IsSupported(FrostFsVersion version)
|
||||||
|
{
|
||||||
|
if (version is null)
|
||||||
|
{
|
||||||
|
throw new System.ArgumentNullException(nameof(version));
|
||||||
|
}
|
||||||
|
|
||||||
|
return Major == version.Major;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override string ToString()
|
||||||
|
{
|
||||||
|
return $"v{Major}.{Minor}";
|
||||||
|
}
|
||||||
|
}
|
11
src/FrostFS.SDK.Client/Models/Netmap/IFrostFsFilter.cs
Normal file
11
src/FrostFS.SDK.Client/Models/Netmap/IFrostFsFilter.cs
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
namespace FrostFS.SDK
|
||||||
|
{
|
||||||
|
public interface IFrostFsFilter
|
||||||
|
{
|
||||||
|
FrostFsFilter[] Filters { get; }
|
||||||
|
string Key { get; }
|
||||||
|
string Name { get; }
|
||||||
|
int Operation { get; }
|
||||||
|
string Value { get; }
|
||||||
|
}
|
||||||
|
}
|
7
src/FrostFS.SDK.Client/Models/Netmap/NodeAttrPair.cs
Normal file
7
src/FrostFS.SDK.Client/Models/Netmap/NodeAttrPair.cs
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
namespace FrostFS.SDK;
|
||||||
|
|
||||||
|
struct NodeAttrPair
|
||||||
|
{
|
||||||
|
internal string attr;
|
||||||
|
internal FrostFsNodeInfo[] nodes;
|
||||||
|
}
|
8
src/FrostFS.SDK.Client/Models/Netmap/Placement/Clause.cs
Normal file
8
src/FrostFS.SDK.Client/Models/Netmap/Placement/Clause.cs
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
namespace FrostFS.SDK.Client.Models.Netmap.Placement;
|
||||||
|
|
||||||
|
public enum FrostFsClause
|
||||||
|
{
|
||||||
|
Unspecified = 0,
|
||||||
|
Same,
|
||||||
|
Distinct
|
||||||
|
}
|
456
src/FrostFS.SDK.Client/Models/Netmap/Placement/Context.cs
Normal file
456
src/FrostFS.SDK.Client/Models/Netmap/Placement/Context.cs
Normal file
|
@ -0,0 +1,456 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Globalization;
|
||||||
|
using System.Linq;
|
||||||
|
|
||||||
|
namespace FrostFS.SDK.Client.Models.Netmap.Placement;
|
||||||
|
|
||||||
|
internal struct Context
|
||||||
|
{
|
||||||
|
private const string errInvalidFilterName = "filter name is invalid";
|
||||||
|
private const string errInvalidFilterOp = "invalid filter operation";
|
||||||
|
private const string errFilterNotFound = "filter not found";
|
||||||
|
private const string errNonEmptyFilters = "simple filter contains sub-filters";
|
||||||
|
private const string errNotEnoughNodes = "not enough nodes to SELECT from";
|
||||||
|
private const string errUnnamedTopFilter = "unnamed top-level filter";
|
||||||
|
|
||||||
|
internal const string mainFilterName = "*";
|
||||||
|
internal const string likeWildcard = "*";
|
||||||
|
|
||||||
|
// network map to operate on
|
||||||
|
internal FrostFsNetmapSnapshot NetMap { get; }
|
||||||
|
|
||||||
|
// cache of processed filters
|
||||||
|
internal Dictionary<string, FrostFsFilter> ProcessedFilters { get; } = [];
|
||||||
|
|
||||||
|
// cache of processed selectors
|
||||||
|
internal Dictionary<string, FrostFsSelector> ProcessedSelectors { get; } = [];
|
||||||
|
|
||||||
|
// stores results of selector processing
|
||||||
|
internal Dictionary<string, List<List<FrostFsNodeInfo>>> Selections { get; } = [];
|
||||||
|
|
||||||
|
// cache of parsed numeric values
|
||||||
|
internal Dictionary<string, ulong> NumCache { get; } = [];
|
||||||
|
|
||||||
|
internal byte[]? HrwSeed { get; set; }
|
||||||
|
|
||||||
|
// hrw.Hash of hrwSeed
|
||||||
|
internal ulong HrwSeedHash { get; set; }
|
||||||
|
|
||||||
|
// container backup factor
|
||||||
|
internal uint Cbf { get; set; }
|
||||||
|
|
||||||
|
// nodes already used in previous selections, which is needed when the placement
|
||||||
|
// policy uses the UNIQUE flag. Nodes marked as used are not used in subsequent
|
||||||
|
// base selections.
|
||||||
|
internal Dictionary<ulong, bool> UsedNodes { get; } = [];
|
||||||
|
|
||||||
|
// If true, returns an error when netmap does not contain enough nodes for selection.
|
||||||
|
// By default best effort is taken.
|
||||||
|
internal bool Strict { get; set; }
|
||||||
|
|
||||||
|
// weightFunc is a weighting function for determining node priority
|
||||||
|
// which combines low price and high performance
|
||||||
|
private readonly Func<FrostFsNodeInfo, double> weightFunc;
|
||||||
|
|
||||||
|
public Context(FrostFsNetmapSnapshot netMap)
|
||||||
|
{
|
||||||
|
NetMap = netMap;
|
||||||
|
weightFunc = Tools.DefaultWeightFunc(NetMap.NodeInfoCollection);
|
||||||
|
}
|
||||||
|
|
||||||
|
internal void ProcessFilters(FrostFsPlacementPolicy policy)
|
||||||
|
{
|
||||||
|
foreach (var filter in policy.Filters)
|
||||||
|
{
|
||||||
|
ProcessFilter(filter, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
readonly void ProcessFilter(FrostFsFilter filter, bool top)
|
||||||
|
{
|
||||||
|
var filterName = filter.Name;
|
||||||
|
if (filterName == mainFilterName)
|
||||||
|
{
|
||||||
|
throw new FrostFsException($"{errInvalidFilterName}: '{errInvalidFilterName}' is reserved");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (top && string.IsNullOrEmpty(filterName))
|
||||||
|
{
|
||||||
|
throw new FrostFsException(errUnnamedTopFilter);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!top && !string.IsNullOrEmpty(filterName) && !ProcessedFilters.ContainsKey(filterName))
|
||||||
|
{
|
||||||
|
throw new FrostFsException(errFilterNotFound);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (filter.Operation == (int)Operation.AND ||
|
||||||
|
filter.Operation == (int)Operation.OR ||
|
||||||
|
filter.Operation == (int)Operation.NOT)
|
||||||
|
{
|
||||||
|
foreach (var f in filter.Filters)
|
||||||
|
ProcessFilter(f, false);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (filter.Filters.Length != 0)
|
||||||
|
{
|
||||||
|
throw new FrostFsException(errNonEmptyFilters);
|
||||||
|
}
|
||||||
|
else if (!top && !string.IsNullOrEmpty(filterName))
|
||||||
|
{
|
||||||
|
// named reference
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (filter.Operation)
|
||||||
|
{
|
||||||
|
case (int)Operation.EQ:
|
||||||
|
case (int)Operation.NE:
|
||||||
|
case (int)Operation.LIKE:
|
||||||
|
break;
|
||||||
|
case (int)Operation.GT:
|
||||||
|
case (int)Operation.GE:
|
||||||
|
case (int)Operation.LT:
|
||||||
|
case (int)Operation.LE:
|
||||||
|
{
|
||||||
|
var n = uint.Parse(filter.Value, CultureInfo.InvariantCulture);
|
||||||
|
NumCache[filter.Value] = n;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
throw new FrostFsException($"{errInvalidFilterOp}: {filter.Operation}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (top)
|
||||||
|
{
|
||||||
|
ProcessedFilters[filterName] = filter;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// processSelectors processes selectors and returns error is any of them is invalid.
|
||||||
|
internal void ProcessSelectors(FrostFsPlacementPolicy policy)
|
||||||
|
{
|
||||||
|
foreach (var selector in policy.Selectors)
|
||||||
|
{
|
||||||
|
var filterName = selector.Filter;
|
||||||
|
if (filterName != mainFilterName)
|
||||||
|
{
|
||||||
|
if (selector.Filter == null || !ProcessedFilters.ContainsKey(selector.Filter))
|
||||||
|
{
|
||||||
|
throw new FrostFsException($"{errFilterNotFound}: SELECT FROM '{filterName}'");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ProcessedSelectors[selector.Name] = selector;
|
||||||
|
|
||||||
|
var selection = GetSelection(selector);
|
||||||
|
|
||||||
|
Selections[selector.Name] = selection;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// calcNodesCount returns number of buckets and minimum number of nodes in every bucket
|
||||||
|
// for the given selector.
|
||||||
|
static (int bucketCount, int nodesInBucket) CalcNodesCount(FrostFsSelector selector)
|
||||||
|
{
|
||||||
|
return selector.Clause == (int)FrostFsClause.Same
|
||||||
|
? (1, (int)selector.Count)
|
||||||
|
: ((int)selector.Count, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// getSelectionBase returns nodes grouped by selector attribute.
|
||||||
|
// It it guaranteed that each pair will contain at least one node.
|
||||||
|
internal NodeAttrPair[] GetSelectionBase(FrostFsSelector selector)
|
||||||
|
{
|
||||||
|
var fName = selector.Filter ?? throw new FrostFsException("Filter name for selector is empty");
|
||||||
|
|
||||||
|
_ = ProcessedFilters.TryGetValue(fName, out var f);
|
||||||
|
|
||||||
|
var isMain = fName == mainFilterName;
|
||||||
|
var result = new List<NodeAttrPair>();
|
||||||
|
|
||||||
|
var nodeMap = new Dictionary<string, List<FrostFsNodeInfo>>();
|
||||||
|
var attr = selector.Attribute;
|
||||||
|
|
||||||
|
foreach (var node in NetMap.NodeInfoCollection)
|
||||||
|
{
|
||||||
|
if (UsedNodes.ContainsKey(node.Hash()))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isMain || Match(f, node))
|
||||||
|
{
|
||||||
|
if (attr == null)
|
||||||
|
{
|
||||||
|
// Default attribute is transparent identifier which is different for every node.
|
||||||
|
result.Add(new NodeAttrPair { attr = "", nodes = [node] });
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var v = node.Attributes[attr];
|
||||||
|
if (!nodeMap.TryGetValue(v, out var nodes) || nodes == null)
|
||||||
|
{
|
||||||
|
nodeMap[v] = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
nodeMap[v].Add(node);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!string.IsNullOrEmpty(attr))
|
||||||
|
{
|
||||||
|
foreach (var v in nodeMap)
|
||||||
|
{
|
||||||
|
result.Add(new NodeAttrPair() { attr = v.Key, nodes = [.. v.Value] });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (HrwSeed != null && HrwSeed.Length != 0)
|
||||||
|
{
|
||||||
|
double[] ws = [];
|
||||||
|
|
||||||
|
var sortedNodes = new NodeAttrPair[result.Count];
|
||||||
|
|
||||||
|
for (int i = 0; i < result.Count; i++)
|
||||||
|
{
|
||||||
|
var res = result[i];
|
||||||
|
Tools.AppendWeightsTo(res.nodes, weightFunc, ref ws);
|
||||||
|
sortedNodes[i].nodes = Tools.SortHasherSliceByWeightValue(res.nodes.ToList(), ws, HrwSeedHash).ToArray();
|
||||||
|
sortedNodes[i].attr = result[i].attr;
|
||||||
|
}
|
||||||
|
|
||||||
|
return sortedNodes;
|
||||||
|
}
|
||||||
|
return [.. result];
|
||||||
|
}
|
||||||
|
|
||||||
|
static double CalcBucketWeight(List<FrostFsNodeInfo> ns, MeanIQRAgg a, Func<FrostFsNodeInfo, double> wf)
|
||||||
|
{
|
||||||
|
foreach (var node in ns)
|
||||||
|
{
|
||||||
|
a.Add(wf(node));
|
||||||
|
}
|
||||||
|
|
||||||
|
return a.Compute();
|
||||||
|
}
|
||||||
|
|
||||||
|
// getSelection returns nodes grouped by s.attribute.
|
||||||
|
// Last argument specifies if more buckets can be used to fulfill CBF.
|
||||||
|
internal List<List<FrostFsNodeInfo>> GetSelection(FrostFsSelector s)
|
||||||
|
{
|
||||||
|
var (bucketCount, nodesInBucket) = CalcNodesCount(s);
|
||||||
|
|
||||||
|
var buckets = GetSelectionBase(s);
|
||||||
|
|
||||||
|
if (Strict && buckets.Length < bucketCount)
|
||||||
|
throw new FrostFsException($"errNotEnoughNodes: '{s.Name}'");
|
||||||
|
|
||||||
|
// We need deterministic output in case there is no pivot.
|
||||||
|
// If pivot is set, buckets are sorted by HRW.
|
||||||
|
// However, because initial order influences HRW order for buckets with equal weights,
|
||||||
|
// we also need to have deterministic input to HRW sorting routine.
|
||||||
|
if (HrwSeed == null || HrwSeed.Length == 0)
|
||||||
|
{
|
||||||
|
buckets = string.IsNullOrEmpty(s.Attribute)
|
||||||
|
? [.. buckets.OrderBy(b => b.nodes[0].Hash())]
|
||||||
|
: [.. buckets.OrderBy(b => b.attr)];
|
||||||
|
}
|
||||||
|
|
||||||
|
var maxNodesInBucket = nodesInBucket * (int)Cbf;
|
||||||
|
|
||||||
|
var res = new List<List<FrostFsNodeInfo>>(buckets.Length);
|
||||||
|
var fallback = new List<List<FrostFsNodeInfo>>(buckets.Length);
|
||||||
|
|
||||||
|
for (int i = 0; i < buckets.Length; i++)
|
||||||
|
{
|
||||||
|
var ns = buckets[i].nodes;
|
||||||
|
if (ns.Length >= maxNodesInBucket)
|
||||||
|
{
|
||||||
|
res.Add(ns.Take(maxNodesInBucket).ToList());
|
||||||
|
}
|
||||||
|
else if (ns.Length >= nodesInBucket)
|
||||||
|
{
|
||||||
|
fallback.Add(new List<FrostFsNodeInfo>(ns));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (res.Count < bucketCount)
|
||||||
|
{
|
||||||
|
// Fallback to using minimum allowed backup factor (1).
|
||||||
|
res.AddRange(fallback);
|
||||||
|
|
||||||
|
if (Strict && res.Count < bucketCount)
|
||||||
|
{
|
||||||
|
throw new FrostFsException($"{errNotEnoughNodes}: {s}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (HrwSeed != null && HrwSeed.Length != 0)
|
||||||
|
{
|
||||||
|
var weights = new double[res.Count];
|
||||||
|
var a = new MeanIQRAgg();
|
||||||
|
|
||||||
|
for (int i = 0; i < res.Count; i++)
|
||||||
|
{
|
||||||
|
a.Clear();
|
||||||
|
weights[i] = CalcBucketWeight(res[i], a, weightFunc);
|
||||||
|
}
|
||||||
|
|
||||||
|
var hashers = res.Select(r => new HasherList(r)).ToList();
|
||||||
|
hashers = Tools.SortHasherSliceByWeightValue(hashers, weights, HrwSeedHash);
|
||||||
|
|
||||||
|
for (int i = 0; i < res.Count; i++)
|
||||||
|
{
|
||||||
|
res[i] = hashers[i].Nodes;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (res.Count < bucketCount)
|
||||||
|
{
|
||||||
|
if (Strict && res.Count == 0)
|
||||||
|
{
|
||||||
|
throw new FrostFsException(errNotEnoughNodes);
|
||||||
|
}
|
||||||
|
|
||||||
|
bucketCount = res.Count;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (string.IsNullOrEmpty(s.Attribute))
|
||||||
|
{
|
||||||
|
fallback = res.Skip(bucketCount).ToList();
|
||||||
|
res = res.Take(bucketCount).ToList();
|
||||||
|
|
||||||
|
for (int i = 0; i < fallback.Count; i++)
|
||||||
|
{
|
||||||
|
var index = i % bucketCount;
|
||||||
|
if (res[index].Count >= maxNodesInBucket)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
res[index].AddRange(fallback[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return res.Take(bucketCount).ToList();
|
||||||
|
}
|
||||||
|
|
||||||
|
internal bool MatchKeyValue(FrostFsFilter f, FrostFsNodeInfo nodeInfo)
|
||||||
|
{
|
||||||
|
switch (f.Operation)
|
||||||
|
{
|
||||||
|
case (int)Operation.EQ:
|
||||||
|
return nodeInfo.Attributes.TryGetValue(f.Key, out var val) && val == f.Value;
|
||||||
|
case (int)Operation.LIKE:
|
||||||
|
{
|
||||||
|
var hasPrefix = f.Value.StartsWith(likeWildcard, StringComparison.Ordinal);
|
||||||
|
var hasSuffix = f.Value.EndsWith(likeWildcard, StringComparison.Ordinal);
|
||||||
|
|
||||||
|
var start = hasPrefix ? likeWildcard.Length : 0;
|
||||||
|
var end = hasSuffix ? f.Value.Length - likeWildcard.Length : f.Value.Length;
|
||||||
|
var str = f.Value.Substring(start, end - start);
|
||||||
|
|
||||||
|
if (hasPrefix && hasSuffix)
|
||||||
|
return nodeInfo.Attributes[f.Key].Contains(str);
|
||||||
|
|
||||||
|
if (hasPrefix && !hasSuffix)
|
||||||
|
return nodeInfo.Attributes[f.Key].EndsWith(str, StringComparison.Ordinal);
|
||||||
|
|
||||||
|
if (!hasPrefix && hasSuffix)
|
||||||
|
return nodeInfo.Attributes[f.Key].StartsWith(str, StringComparison.Ordinal);
|
||||||
|
|
||||||
|
|
||||||
|
return nodeInfo.Attributes[f.Key] == f.Value;
|
||||||
|
}
|
||||||
|
case (int)Operation.NE:
|
||||||
|
return nodeInfo.Attributes[f.Key] != f.Value;
|
||||||
|
default:
|
||||||
|
{
|
||||||
|
ulong attr;
|
||||||
|
switch (f.Key)
|
||||||
|
{
|
||||||
|
case FrostFsNodeInfo.AttrPrice:
|
||||||
|
attr = nodeInfo.Price;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case FrostFsNodeInfo.AttrCapacity:
|
||||||
|
attr = nodeInfo.GetCapacity();
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
if (!ulong.TryParse(nodeInfo.Attributes[f.Key], NumberStyles.Integer, CultureInfo.InvariantCulture, out attr))
|
||||||
|
return false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (f.Operation)
|
||||||
|
{
|
||||||
|
case (int)Operation.GT:
|
||||||
|
return attr > NumCache[f.Value];
|
||||||
|
case (int)Operation.GE:
|
||||||
|
return attr >= NumCache[f.Value];
|
||||||
|
case (int)Operation.LT:
|
||||||
|
return attr < NumCache[f.Value];
|
||||||
|
case (int)Operation.LE:
|
||||||
|
return attr <= NumCache[f.Value];
|
||||||
|
default:
|
||||||
|
// do nothing and return false
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// will not happen if context was created from f (maybe panic?)
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// match matches f against b. It returns no errors because
|
||||||
|
// filter should have been parsed during context creation
|
||||||
|
// and missing node properties are considered as a regular fail.
|
||||||
|
internal bool Match(FrostFsFilter f, FrostFsNodeInfo nodeInfo)
|
||||||
|
{
|
||||||
|
switch (f.Operation)
|
||||||
|
{
|
||||||
|
case (int)Operation.NOT:
|
||||||
|
{
|
||||||
|
var inner = f.Filters;
|
||||||
|
var fSub = inner[0];
|
||||||
|
|
||||||
|
if (!string.IsNullOrEmpty(inner[0].Name))
|
||||||
|
{
|
||||||
|
fSub = ProcessedFilters[inner[0].Name];
|
||||||
|
}
|
||||||
|
return !Match(fSub, nodeInfo);
|
||||||
|
}
|
||||||
|
case (int)Operation.AND:
|
||||||
|
case (int)Operation.OR:
|
||||||
|
{
|
||||||
|
for (int i = 0; i < f.Filters.Length; i++)
|
||||||
|
{
|
||||||
|
var fSub = f.Filters[i];
|
||||||
|
|
||||||
|
if (!string.IsNullOrEmpty(f.Filters[i].Name))
|
||||||
|
{
|
||||||
|
fSub = ProcessedFilters[f.Filters[i].Name];
|
||||||
|
}
|
||||||
|
|
||||||
|
var ok = Match(fSub, nodeInfo);
|
||||||
|
|
||||||
|
if (ok == (f.Operation == (int)Operation.OR))
|
||||||
|
{
|
||||||
|
return ok;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return f.Operation == (int)Operation.AND;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return MatchKeyValue(f, nodeInfo);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
26
src/FrostFS.SDK.Client/Models/Netmap/Placement/HasherList.cs
Normal file
26
src/FrostFS.SDK.Client/Models/Netmap/Placement/HasherList.cs
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace FrostFS.SDK.Client.Models.Netmap.Placement;
|
||||||
|
|
||||||
|
internal sealed class HasherList : IHasher
|
||||||
|
{
|
||||||
|
private readonly List<FrostFsNodeInfo> _nodes;
|
||||||
|
|
||||||
|
internal HasherList(List<FrostFsNodeInfo> nodes)
|
||||||
|
{
|
||||||
|
_nodes = nodes;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal List<FrostFsNodeInfo> Nodes
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return _nodes;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public ulong Hash()
|
||||||
|
{
|
||||||
|
return _nodes.Count > 0 ? _nodes[0].Hash() : 0;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,8 @@
|
||||||
|
namespace FrostFS.SDK.Client.Models.Netmap.Placement;
|
||||||
|
|
||||||
|
internal interface IAggregator
|
||||||
|
{
|
||||||
|
void Add(double d);
|
||||||
|
|
||||||
|
double Compute();
|
||||||
|
}
|
|
@ -0,0 +1,6 @@
|
||||||
|
namespace FrostFS.SDK.Client.Models.Netmap.Placement;
|
||||||
|
|
||||||
|
internal interface IHasher
|
||||||
|
{
|
||||||
|
ulong Hash();
|
||||||
|
}
|
|
@ -0,0 +1,6 @@
|
||||||
|
namespace FrostFS.SDK.Client.Models.Netmap.Placement;
|
||||||
|
|
||||||
|
interface INormalizer
|
||||||
|
{
|
||||||
|
double Normalize(double w);
|
||||||
|
}
|
20
src/FrostFS.SDK.Client/Models/Netmap/Placement/MeanAgg.cs
Normal file
20
src/FrostFS.SDK.Client/Models/Netmap/Placement/MeanAgg.cs
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
namespace FrostFS.SDK.Client.Models.Netmap.Placement;
|
||||||
|
|
||||||
|
internal struct MeanAgg
|
||||||
|
{
|
||||||
|
private double mean;
|
||||||
|
private int count;
|
||||||
|
|
||||||
|
internal void Add(double n)
|
||||||
|
{
|
||||||
|
int c = count + 1;
|
||||||
|
mean = mean * count / c + n / c;
|
||||||
|
|
||||||
|
count++;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal readonly double Compute()
|
||||||
|
{
|
||||||
|
return mean;
|
||||||
|
}
|
||||||
|
}
|
65
src/FrostFS.SDK.Client/Models/Netmap/Placement/MeanIQRAgg.cs
Normal file
65
src/FrostFS.SDK.Client/Models/Netmap/Placement/MeanIQRAgg.cs
Normal file
|
@ -0,0 +1,65 @@
|
||||||
|
using System.Collections.ObjectModel;
|
||||||
|
using System.Linq;
|
||||||
|
|
||||||
|
namespace FrostFS.SDK.Client.Models.Netmap.Placement;
|
||||||
|
|
||||||
|
internal struct MeanIQRAgg : IAggregator
|
||||||
|
{
|
||||||
|
private const int minLn = 4;
|
||||||
|
internal Collection<double> arr = [];
|
||||||
|
|
||||||
|
public MeanIQRAgg()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public readonly void Add(double d)
|
||||||
|
{
|
||||||
|
arr.Add(d);
|
||||||
|
}
|
||||||
|
|
||||||
|
public readonly double Compute()
|
||||||
|
{
|
||||||
|
var length = arr.Count;
|
||||||
|
if (length == 0)
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
var sorted = arr.OrderBy(p => p).ToArray();
|
||||||
|
|
||||||
|
double minV, maxV;
|
||||||
|
|
||||||
|
if (arr.Count < minLn)
|
||||||
|
{
|
||||||
|
minV = sorted[0];
|
||||||
|
maxV = sorted[length - 1];
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var start = length / minLn;
|
||||||
|
var end = length * 3 / minLn - 1;
|
||||||
|
|
||||||
|
minV = sorted[start];
|
||||||
|
maxV = sorted[end];
|
||||||
|
}
|
||||||
|
|
||||||
|
var count = 0;
|
||||||
|
double sum = 0;
|
||||||
|
|
||||||
|
foreach (var e in sorted)
|
||||||
|
{
|
||||||
|
if (e >= minV && e <= maxV)
|
||||||
|
{
|
||||||
|
sum += e;
|
||||||
|
count++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return sum / count;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal readonly void Clear()
|
||||||
|
{
|
||||||
|
arr.Clear();
|
||||||
|
}
|
||||||
|
}
|
27
src/FrostFS.SDK.Client/Models/Netmap/Placement/MinAgg.cs
Normal file
27
src/FrostFS.SDK.Client/Models/Netmap/Placement/MinAgg.cs
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
namespace FrostFS.SDK.Client.Models.Netmap.Placement;
|
||||||
|
|
||||||
|
internal struct MinAgg
|
||||||
|
{
|
||||||
|
private double min;
|
||||||
|
private bool minFound;
|
||||||
|
|
||||||
|
internal void Add(double n)
|
||||||
|
{
|
||||||
|
if (!minFound)
|
||||||
|
{
|
||||||
|
min = n;
|
||||||
|
minFound = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (n < min)
|
||||||
|
{
|
||||||
|
min = n;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal readonly double Compute()
|
||||||
|
{
|
||||||
|
return min;
|
||||||
|
}
|
||||||
|
}
|
16
src/FrostFS.SDK.Client/Models/Netmap/Placement/Operation.cs
Normal file
16
src/FrostFS.SDK.Client/Models/Netmap/Placement/Operation.cs
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
namespace FrostFS.SDK.Client.Models.Netmap.Placement;
|
||||||
|
|
||||||
|
public enum Operation
|
||||||
|
{
|
||||||
|
Unspecified = 0,
|
||||||
|
EQ,
|
||||||
|
NE,
|
||||||
|
GT,
|
||||||
|
GE,
|
||||||
|
LT,
|
||||||
|
LE,
|
||||||
|
OR,
|
||||||
|
AND,
|
||||||
|
NOT,
|
||||||
|
LIKE
|
||||||
|
}
|
|
@ -0,0 +1,8 @@
|
||||||
|
namespace FrostFS.SDK.Client.Models.Netmap.Placement;
|
||||||
|
|
||||||
|
internal struct ReverseMinNorm : INormalizer
|
||||||
|
{
|
||||||
|
internal double min;
|
||||||
|
|
||||||
|
public readonly double Normalize(double w) => (min + 1) / (w + 1);
|
||||||
|
}
|
|
@ -0,0 +1,10 @@
|
||||||
|
using System.Collections.ObjectModel;
|
||||||
|
|
||||||
|
namespace FrostFS.SDK.Client.Models.Netmap.Placement;
|
||||||
|
|
||||||
|
internal struct SelectFilterExpr(uint cbf, FrostFsSelector selector, Collection<FrostFsFilter> filters)
|
||||||
|
{
|
||||||
|
internal uint Cbf { get; } = cbf;
|
||||||
|
internal FrostFsSelector Selector { get; } = selector;
|
||||||
|
internal Collection<FrostFsFilter> Filters { get; } = filters;
|
||||||
|
}
|
|
@ -0,0 +1,23 @@
|
||||||
|
namespace FrostFS.SDK.Client.Models.Netmap.Placement;
|
||||||
|
|
||||||
|
internal readonly struct SigmoidNorm : INormalizer
|
||||||
|
{
|
||||||
|
private readonly double _scale;
|
||||||
|
|
||||||
|
internal SigmoidNorm(double scale)
|
||||||
|
{
|
||||||
|
_scale = scale;
|
||||||
|
}
|
||||||
|
|
||||||
|
public readonly double Normalize(double w)
|
||||||
|
{
|
||||||
|
if (_scale == 0)
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
var x = w / _scale;
|
||||||
|
|
||||||
|
return x / (1 + x);
|
||||||
|
}
|
||||||
|
}
|
138
src/FrostFS.SDK.Client/Models/Netmap/Placement/Tools.cs
Normal file
138
src/FrostFS.SDK.Client/Models/Netmap/Placement/Tools.cs
Normal file
|
@ -0,0 +1,138 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
|
||||||
|
using static FrostFS.SDK.FrostFsNetmapSnapshot;
|
||||||
|
|
||||||
|
namespace FrostFS.SDK.Client.Models.Netmap.Placement;
|
||||||
|
|
||||||
|
public static class Tools
|
||||||
|
{
|
||||||
|
internal static ulong Distance(ulong x, ulong y)
|
||||||
|
{
|
||||||
|
var acc = x ^ y;
|
||||||
|
acc ^= acc >> 33;
|
||||||
|
acc *= 0xff51afd7ed558ccd;
|
||||||
|
acc ^= acc >> 33;
|
||||||
|
acc *= 0xc4ceb9fe1a85ec53;
|
||||||
|
acc ^= acc >> 33;
|
||||||
|
|
||||||
|
return acc;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal static double ReverceNormalize(double r, double w)
|
||||||
|
{
|
||||||
|
return (r + 1) / (w + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
internal static double Normalize(double r, double w)
|
||||||
|
{
|
||||||
|
if (r == 0)
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
var x = w / r;
|
||||||
|
return x / (1 + x);
|
||||||
|
}
|
||||||
|
|
||||||
|
internal static void AppendWeightsTo(FrostFsNodeInfo[] nodes, Func<FrostFsNodeInfo, double> wf, ref double[] weights)
|
||||||
|
{
|
||||||
|
if (weights.Length < nodes.Length)
|
||||||
|
{
|
||||||
|
weights = new double[nodes.Length];
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < nodes.Length; i++)
|
||||||
|
{
|
||||||
|
weights[i] = wf(nodes[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal static List<T> SortHasherSliceByWeightValue<T>(List<T> nodes, Span<double> weights, ulong hash) where T : IHasher
|
||||||
|
{
|
||||||
|
if (nodes.Count == 0)
|
||||||
|
{
|
||||||
|
return nodes;
|
||||||
|
}
|
||||||
|
|
||||||
|
var allEquals = true;
|
||||||
|
|
||||||
|
if (weights.Length > 1)
|
||||||
|
{
|
||||||
|
for (int i = 1; i < weights.Length; i++)
|
||||||
|
{
|
||||||
|
if (weights[i] != weights[0])
|
||||||
|
{
|
||||||
|
allEquals = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var dist = new double[nodes.Count];
|
||||||
|
|
||||||
|
if (allEquals)
|
||||||
|
{
|
||||||
|
for (int i = 0; i < dist.Length; i++)
|
||||||
|
{
|
||||||
|
var x = nodes[i].Hash();
|
||||||
|
dist[i] = Distance(x, hash);
|
||||||
|
}
|
||||||
|
|
||||||
|
return SortHasherByDistance(nodes, dist, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < dist.Length; i++)
|
||||||
|
{
|
||||||
|
var d = Distance(nodes[i].Hash(), hash);
|
||||||
|
dist[i] = (ulong.MaxValue - d) * weights[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
return SortHasherByDistance(nodes, dist, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
internal static List<T> SortHasherByDistance<T, N>(List<T> nodes, N[] dist, bool asc)
|
||||||
|
{
|
||||||
|
IndexedValue<T, N>[] indexes = new IndexedValue<T, N>[nodes.Count];
|
||||||
|
for (int i = 0; i < dist.Length; i++)
|
||||||
|
{
|
||||||
|
indexes[i] = new IndexedValue<T, N>() { nodeInfo = nodes[i], dist = dist[i] };
|
||||||
|
}
|
||||||
|
|
||||||
|
if (asc)
|
||||||
|
{
|
||||||
|
return new List<T>(indexes
|
||||||
|
.OrderBy(x => x.dist)
|
||||||
|
.Select(x => x.nodeInfo).ToArray());
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return new List<T>(indexes
|
||||||
|
.OrderByDescending(x => x.dist)
|
||||||
|
.Select(x => x.nodeInfo));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal static Func<FrostFsNodeInfo, double> DefaultWeightFunc(IReadOnlyList<FrostFsNodeInfo> nodes)
|
||||||
|
{
|
||||||
|
MeanAgg mean = new();
|
||||||
|
MinAgg minV = new();
|
||||||
|
|
||||||
|
foreach (var node in nodes)
|
||||||
|
{
|
||||||
|
mean.Add(node.GetCapacity());
|
||||||
|
minV.Add(node.Price);
|
||||||
|
}
|
||||||
|
|
||||||
|
return NewWeightFunc(
|
||||||
|
NewSigmoidNorm(mean.Compute()),
|
||||||
|
NewReverseMinNorm(minV.Compute()));
|
||||||
|
}
|
||||||
|
|
||||||
|
private struct IndexedValue<T, N>
|
||||||
|
{
|
||||||
|
internal T nodeInfo;
|
||||||
|
internal N dist;
|
||||||
|
}
|
||||||
|
}
|
48
src/FrostFS.SDK.Client/Models/Object/FrostFsAddress.cs
Normal file
48
src/FrostFS.SDK.Client/Models/Object/FrostFsAddress.cs
Normal file
|
@ -0,0 +1,48 @@
|
||||||
|
using FrostFS.Refs;
|
||||||
|
using FrostFS.SDK.Client.Mappers.GRPC;
|
||||||
|
|
||||||
|
namespace FrostFS.SDK;
|
||||||
|
|
||||||
|
public class FrostFsAddress
|
||||||
|
{
|
||||||
|
private FrostFsObjectId? frostFsObjectId;
|
||||||
|
private FrostFsContainerId? frostFsContainerId;
|
||||||
|
private ObjectID? objectId;
|
||||||
|
private ContainerID? containerId;
|
||||||
|
|
||||||
|
public FrostFsAddress(FrostFsContainerId frostFsContainerId, FrostFsObjectId frostFsObjectId)
|
||||||
|
{
|
||||||
|
FrostFsObjectId = frostFsObjectId ?? throw new System.ArgumentNullException(nameof(frostFsObjectId));
|
||||||
|
FrostFsContainerId = frostFsContainerId ?? throw new System.ArgumentNullException(nameof(frostFsContainerId));
|
||||||
|
}
|
||||||
|
|
||||||
|
internal FrostFsAddress(ObjectID objectId, ContainerID containerId)
|
||||||
|
{
|
||||||
|
ObjectId = objectId ?? throw new System.ArgumentNullException(nameof(objectId));
|
||||||
|
ContainerId = containerId ?? throw new System.ArgumentNullException(nameof(containerId));
|
||||||
|
}
|
||||||
|
|
||||||
|
public FrostFsObjectId FrostFsObjectId
|
||||||
|
{
|
||||||
|
get => frostFsObjectId ??= objectId!.ToModel();
|
||||||
|
set => frostFsObjectId = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public FrostFsContainerId FrostFsContainerId
|
||||||
|
{
|
||||||
|
get => frostFsContainerId ??= containerId!.ToModel();
|
||||||
|
set => frostFsContainerId = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ObjectID ObjectId
|
||||||
|
{
|
||||||
|
get => objectId ??= frostFsObjectId!.ToMessage();
|
||||||
|
set => objectId = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ContainerID ContainerId
|
||||||
|
{
|
||||||
|
get => containerId ??= frostFsContainerId!.ToMessage();
|
||||||
|
set => containerId = value;
|
||||||
|
}
|
||||||
|
}
|
36
src/FrostFS.SDK.Client/Models/Object/FrostFsAttributePair.cs
Normal file
36
src/FrostFS.SDK.Client/Models/Object/FrostFsAttributePair.cs
Normal file
|
@ -0,0 +1,36 @@
|
||||||
|
namespace FrostFS.SDK;
|
||||||
|
|
||||||
|
public struct FrostFsAttributePair(string key, string value) : System.IEquatable<FrostFsAttributePair>
|
||||||
|
{
|
||||||
|
public string Key { get; set; } = key;
|
||||||
|
|
||||||
|
public string Value { get; set; } = value;
|
||||||
|
|
||||||
|
public override bool Equals(object obj)
|
||||||
|
{
|
||||||
|
if (obj == null || obj is not FrostFsAttributePair)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
return Equals((FrostFsAttributePair)obj);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override int GetHashCode()
|
||||||
|
{
|
||||||
|
return Key.GetHashCode() ^ Value.GetHashCode();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool operator ==(FrostFsAttributePair left, FrostFsAttributePair right)
|
||||||
|
{
|
||||||
|
return left.Equals(right);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool operator !=(FrostFsAttributePair left, FrostFsAttributePair right)
|
||||||
|
{
|
||||||
|
return !(left == right);
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool Equals(FrostFsAttributePair other)
|
||||||
|
{
|
||||||
|
return GetHashCode().Equals(other.GetHashCode());
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,8 @@
|
||||||
|
namespace FrostFS.SDK;
|
||||||
|
|
||||||
|
public class FrostFsHeaderResult
|
||||||
|
{
|
||||||
|
public FrostFsObjectHeader? HeaderInfo { get; internal set; }
|
||||||
|
|
||||||
|
public FrostFsSplitInfo? SplitInfo { get; internal set; }
|
||||||
|
}
|
|
@ -0,0 +1,9 @@
|
||||||
|
namespace FrostFS.SDK;
|
||||||
|
|
||||||
|
public class FrostFsLargeObject(FrostFsContainerId container) : FrostFsObject(container)
|
||||||
|
{
|
||||||
|
public ulong PayloadLength
|
||||||
|
{
|
||||||
|
get { return Header!.PayloadLength; }
|
||||||
|
}
|
||||||
|
}
|
21
src/FrostFS.SDK.Client/Models/Object/FrostFsLinkObject.cs
Normal file
21
src/FrostFS.SDK.Client/Models/Object/FrostFsLinkObject.cs
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Collections.ObjectModel;
|
||||||
|
|
||||||
|
namespace FrostFS.SDK;
|
||||||
|
|
||||||
|
public class FrostFsLinkObject : FrostFsObject
|
||||||
|
{
|
||||||
|
public FrostFsLinkObject(FrostFsContainerId containerId,
|
||||||
|
SplitId splitId,
|
||||||
|
FrostFsObjectHeader largeObjectHeader,
|
||||||
|
IList<FrostFsObjectId> children)
|
||||||
|
: base(containerId)
|
||||||
|
{
|
||||||
|
Header!.Split = new FrostFsSplit(splitId,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
largeObjectHeader,
|
||||||
|
null,
|
||||||
|
new ReadOnlyCollection<FrostFsObjectId>(children));
|
||||||
|
}
|
||||||
|
}
|
64
src/FrostFS.SDK.Client/Models/Object/FrostFsObject.cs
Normal file
64
src/FrostFS.SDK.Client/Models/Object/FrostFsObject.cs
Normal file
|
@ -0,0 +1,64 @@
|
||||||
|
using System;
|
||||||
|
|
||||||
|
namespace FrostFS.SDK;
|
||||||
|
|
||||||
|
public class FrostFsObject
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Creates new instance from <c>ObjectHeader</c>
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="header"></param> <summary>
|
||||||
|
public FrostFsObject(FrostFsObjectHeader header)
|
||||||
|
{
|
||||||
|
Header = header;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates new instance with specified parameters
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="container"></param>
|
||||||
|
/// <param name="objectType"></param>
|
||||||
|
public FrostFsObject(FrostFsContainerId container, FrostFsObjectType objectType = FrostFsObjectType.Regular)
|
||||||
|
{
|
||||||
|
Header = new FrostFsObjectHeader(containerId: container, type: objectType);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Header contains metadata for the object
|
||||||
|
/// </summary>
|
||||||
|
/// <value></value>
|
||||||
|
public FrostFsObjectHeader Header { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The value is calculated internally as a hash of ObjectHeader. Do not use pre-calculated value is the object has been changed.
|
||||||
|
/// </summary>
|
||||||
|
public FrostFsObjectId? ObjectId
|
||||||
|
{
|
||||||
|
get; set;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A payload is obtained via stream reader
|
||||||
|
/// </summary>
|
||||||
|
/// <value>Reader for received data</value>
|
||||||
|
public IObjectReader? ObjectReader { get; set; }
|
||||||
|
|
||||||
|
public ReadOnlyMemory<byte> SingleObjectPayload { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Provide SHA256 hash of the payload. If null, the hash is calculated by internal logic
|
||||||
|
/// </summary>
|
||||||
|
public byte[]? PayloadHash { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Applied only for the last Object in chain in case of manual multipart uploading
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="largeObject">Parent for multipart object</param>
|
||||||
|
public void SetParent(FrostFsObjectHeader largeObjectHeader)
|
||||||
|
{
|
||||||
|
if (Header?.Split == null)
|
||||||
|
throw new ArgumentNullException(nameof(largeObjectHeader), "Split value must not be null");
|
||||||
|
|
||||||
|
Header.Split.ParentHeader = largeObjectHeader;
|
||||||
|
}
|
||||||
|
}
|
111
src/FrostFS.SDK.Client/Models/Object/FrostFsObjectFilter.cs
Normal file
111
src/FrostFS.SDK.Client/Models/Object/FrostFsObjectFilter.cs
Normal file
|
@ -0,0 +1,111 @@
|
||||||
|
namespace FrostFS.SDK;
|
||||||
|
|
||||||
|
public interface IObjectFilter
|
||||||
|
{
|
||||||
|
public FrostFsMatchType MatchType { get; set; }
|
||||||
|
public string Key { get; set; }
|
||||||
|
|
||||||
|
string? GetSerializedValue();
|
||||||
|
}
|
||||||
|
|
||||||
|
public abstract class FrostFsObjectFilter<T>(FrostFsMatchType matchType, string key, T value) : IObjectFilter
|
||||||
|
{
|
||||||
|
public FrostFsMatchType MatchType { get; set; } = matchType;
|
||||||
|
public string Key { get; set; } = key;
|
||||||
|
|
||||||
|
public T Value { get; set; } = value;
|
||||||
|
|
||||||
|
public string? GetSerializedValue()
|
||||||
|
{
|
||||||
|
return Value?.ToString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates filter to search by Attribute
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="matchType">Match type</param>
|
||||||
|
/// <param name="key">Attribute key</param>
|
||||||
|
/// <param name="value">Attribute value</param>
|
||||||
|
public class FilterByAttributePair(FrostFsMatchType matchType, string key, string value) : FrostFsObjectFilter<string>(matchType, key, value) { }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates filter to search by ObjectId
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="matchType">Match type</param>
|
||||||
|
/// <param name="objectId">ObjectId</param>
|
||||||
|
public class FilterByObjectId(FrostFsMatchType matchType, FrostFsObjectId objectId) : FrostFsObjectFilter<FrostFsObjectId>(matchType, Constants.FilterHeaderObjectID, objectId) { }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates filter to search by OwnerId
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="matchType">Match type</param>
|
||||||
|
/// <param name="ownerId">ObjectId</param>
|
||||||
|
public class FilterByOwnerId(FrostFsMatchType matchType, FrostFsOwner ownerId) : FrostFsObjectFilter<FrostFsOwner>(matchType, Constants.FilterHeaderOwnerID, ownerId) { }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates filter to search by Version
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="matchType">Match type</param>
|
||||||
|
/// <param name="version">Version</param>
|
||||||
|
public class FilterByVersion(FrostFsMatchType matchType, FrostFsVersion version) : FrostFsObjectFilter<FrostFsVersion>(matchType, Constants.FilterHeaderVersion, version) { }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates filter to search by ContainerId
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="matchType">Match type</param>
|
||||||
|
/// <param name="containerId">ContainerId</param>
|
||||||
|
public class FilterByContainerId(FrostFsMatchType matchType, FrostFsContainerId containerId) : FrostFsObjectFilter<FrostFsContainerId>(matchType, Constants.FilterHeaderContainerID, containerId) { }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates filter to search by creation Epoch
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="matchType">Match type</param>
|
||||||
|
/// <param name="epoch">Creation Epoch</param>
|
||||||
|
public class FilterByEpoch(FrostFsMatchType matchType, ulong epoch) : FrostFsObjectFilter<ulong>(matchType, Constants.FilterHeaderCreationEpoch, epoch) { }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates filter to search by Payload Length
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="matchType">Match type</param>
|
||||||
|
/// <param name="payloadLength">Payload Length</param>
|
||||||
|
public class FilterByPayloadLength(FrostFsMatchType matchType, ulong payloadLength) : FrostFsObjectFilter<ulong>(matchType, Constants.FilterHeaderPayloadLength, payloadLength) { }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates filter to search by Payload Hash
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="matchType">Match type</param>
|
||||||
|
/// <param name="payloadHash">Payload Hash</param>
|
||||||
|
public class FilterByPayloadHash(FrostFsMatchType matchType, CheckSum payloadHash) : FrostFsObjectFilter<CheckSum>(matchType, Constants.FilterHeaderPayloadHash, payloadHash) { }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates filter to search by Parent
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="matchType">Match type</param>
|
||||||
|
/// <param name="parentId">Parent</param>
|
||||||
|
public class FilterByParent(FrostFsMatchType matchType, FrostFsObjectId parentId) : FrostFsObjectFilter<FrostFsObjectId>(matchType, Constants.FilterHeaderParent, parentId) { }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates filter to search by SplitId
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="matchType">Match type</param>
|
||||||
|
/// <param name="splitId">SplitId</param>
|
||||||
|
public class FilterBySplitId(FrostFsMatchType matchType, SplitId splitId) : FrostFsObjectFilter<SplitId>(matchType, Constants.FilterHeaderSplitID, splitId) { }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates filter to search by Payload Hash
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="matchType">Match type</param>
|
||||||
|
/// <param name="ecParentId">Payload Hash</param>
|
||||||
|
public class FilterByECParent(FrostFsMatchType matchType, FrostFsObjectId ecParentId) : FrostFsObjectFilter<FrostFsObjectId>(matchType, Constants.FilterHeaderECParent, ecParentId) { }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates filter to search Root objects
|
||||||
|
/// </summary>
|
||||||
|
public class FilterByRootObject() : FrostFsObjectFilter<string>(FrostFsMatchType.Unspecified, Constants.FilterHeaderRoot, string.Empty) { }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates filter to search objects that are physically stored on the server
|
||||||
|
/// </summary
|
||||||
|
public class FilterByPhysicallyStored() : FrostFsObjectFilter<string>(FrostFsMatchType.Unspecified, Constants.FilterHeaderPhy, string.Empty) { }
|
||||||
|
|
90
src/FrostFS.SDK.Client/Models/Object/FrostFsObjectHeader.cs
Normal file
90
src/FrostFS.SDK.Client/Models/Object/FrostFsObjectHeader.cs
Normal file
|
@ -0,0 +1,90 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.ObjectModel;
|
||||||
|
using System.Linq;
|
||||||
|
|
||||||
|
using FrostFS.Object;
|
||||||
|
|
||||||
|
using FrostFS.SDK.Client.Mappers.GRPC;
|
||||||
|
|
||||||
|
namespace FrostFS.SDK;
|
||||||
|
|
||||||
|
public class FrostFsObjectHeader(
|
||||||
|
FrostFsContainerId containerId,
|
||||||
|
FrostFsObjectType type = FrostFsObjectType.Regular,
|
||||||
|
FrostFsAttributePair[]? attributes = null,
|
||||||
|
FrostFsSplit? split = null,
|
||||||
|
FrostFsOwner? owner = null,
|
||||||
|
FrostFsVersion? version = null)
|
||||||
|
{
|
||||||
|
private Header? header;
|
||||||
|
private Container.Container.Types.Attribute[]? grpsAttributes;
|
||||||
|
|
||||||
|
public ReadOnlyCollection<FrostFsAttributePair>? Attributes { get; internal set; } =
|
||||||
|
attributes == null ? null :
|
||||||
|
new ReadOnlyCollection<FrostFsAttributePair>(attributes);
|
||||||
|
|
||||||
|
public FrostFsContainerId ContainerId { get; } = containerId;
|
||||||
|
|
||||||
|
public ulong PayloadLength { get; set; }
|
||||||
|
|
||||||
|
public byte[]? PayloadCheckSum { get; set; }
|
||||||
|
|
||||||
|
public FrostFsObjectType ObjectType { get; } = type;
|
||||||
|
|
||||||
|
public FrostFsOwner? OwnerId { get; internal set; } = owner;
|
||||||
|
|
||||||
|
public FrostFsVersion? Version { get; internal set; } = version;
|
||||||
|
|
||||||
|
public FrostFsSplit? Split { get; internal set; } = split;
|
||||||
|
|
||||||
|
internal Container.Container.Types.Attribute[]? GetGrpsAttributes()
|
||||||
|
{
|
||||||
|
grpsAttributes ??= Attributes?
|
||||||
|
.Select(a => new Container.Container.Types.Attribute { Key = a.Key, Value = a.Value })
|
||||||
|
.ToArray();
|
||||||
|
|
||||||
|
return grpsAttributes;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Header GetHeader()
|
||||||
|
{
|
||||||
|
if (header == null)
|
||||||
|
{
|
||||||
|
var objTypeName = ObjectType switch
|
||||||
|
{
|
||||||
|
FrostFsObjectType.Regular => Object.ObjectType.Regular,
|
||||||
|
FrostFsObjectType.Lock => Object.ObjectType.Lock,
|
||||||
|
FrostFsObjectType.Tombstone => Object.ObjectType.Tombstone,
|
||||||
|
_ => throw new ArgumentException($"Unknown ObjectType. Value: '{ObjectType}'.")
|
||||||
|
};
|
||||||
|
|
||||||
|
this.header = new Header
|
||||||
|
{
|
||||||
|
OwnerId = OwnerId?.ToMessage(),
|
||||||
|
Version = Version?.ToMessage(),
|
||||||
|
ContainerId = ContainerId.ToMessage(),
|
||||||
|
ObjectType = objTypeName,
|
||||||
|
PayloadLength = PayloadLength
|
||||||
|
};
|
||||||
|
|
||||||
|
if (Attributes != null)
|
||||||
|
{
|
||||||
|
foreach (var attribute in Attributes)
|
||||||
|
{
|
||||||
|
this.header.Attributes.Add(attribute.ToMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var split = Split;
|
||||||
|
if (split != null)
|
||||||
|
{
|
||||||
|
this.header.Split = new Header.Types.Split
|
||||||
|
{
|
||||||
|
SplitId = split!.SplitId != null ? split.SplitId.GetSplitId() : null
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.header;
|
||||||
|
}
|
||||||
|
}
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Reference in a new issue