Compare commits
39 commits
Author | SHA1 | Date | |
---|---|---|---|
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 |
282 changed files with 16898 additions and 3827 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
|
3
.gitignore
vendored
3
.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
|
||||||
|
@ -31,3 +33,4 @@ antlr-*.jar
|
||||||
# binary
|
# binary
|
||||||
bin/
|
bin/
|
||||||
release/
|
release/
|
||||||
|
obj/
|
||||||
|
|
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
|
||||||
|
|
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));
|
||||||
|
}
|
||||||
|
|
||||||
|
var placementPolicy = new FrostFsPlacementPolicy(true, new FrostFsReplica(1));
|
||||||
|
|
||||||
|
var createContainerParam = new PrmContainerCreate(
|
||||||
|
new FrostFsContainerInfo(BasicAcl.PublicRW, new FrostFsPlacementPolicy(true, new FrostFsReplica(1))));
|
||||||
|
|
||||||
|
var containerId = await client.CreateContainerAsync(createContainerParam);
|
||||||
|
|
||||||
|
using var fileStream = File.OpenRead(@"C:\Users\Paul\Pictures\cat.jpeg");
|
||||||
|
|
||||||
|
var param = new PrmObjectPut
|
||||||
|
{
|
||||||
|
Header = new FrostFsObjectHeader(
|
||||||
|
containerId: containerId,
|
||||||
|
type: FrostFsObjectType.Regular,
|
||||||
|
[new FrostFsAttribute("fileName", "test")]),
|
||||||
|
Payload = fileStream
|
||||||
|
};
|
||||||
|
|
||||||
|
FrostFsObjectId objectId = await client.PutObjectAsync(param);
|
||||||
|
|
||||||
|
var filter = new FilterByAttribute(FrostFsMatchType.Equals, "fileName", "test");
|
||||||
|
|
||||||
|
await foreach (var objId in client.SearchObjectsAsync(new PrmObjectSearch(containerId) { Filters = [filter] }))
|
||||||
|
{
|
||||||
|
var objHeader = await client.GetObjectHeadAsync(new PrmObjectHeadGet(containerId, objId));
|
||||||
|
}
|
||||||
|
|
||||||
|
var @object = await client.GetObjectAsync(new PrmObjectGet(containerId, objectId));
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
### Object
|
|
||||||
|
|
||||||
```csharp
|
|
||||||
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>);
|
|
||||||
|
|
||||||
// Search regular objects
|
|
||||||
var objectsIds = await fsClient.SearchObjectAsync(
|
|
||||||
cId,
|
|
||||||
ObjectFilter.RootFilter()
|
|
||||||
);
|
|
||||||
|
|
||||||
// Put object
|
|
||||||
var f = File.OpenRead("cat.jpg");
|
|
||||||
var cat = new ObjectHeader(
|
|
||||||
containerId: cId,
|
|
||||||
type: ObjectType.Regular,
|
|
||||||
new ObjectAttribute("Filename", "cat.jpg")
|
|
||||||
);
|
|
||||||
var oId = await fsClient.PutObjectAsync(cat, f);
|
|
||||||
|
|
||||||
// Get object header
|
|
||||||
var objHeader = await fsClient.GetObjectHeadAsync(cId, oId);
|
|
||||||
|
|
||||||
// Get object
|
|
||||||
var obj = await fsClient.GetObjectAsync(cId, oId);
|
|
||||||
```
|
|
22
src/FrostFS.SDK.Client/Caches.cs
Normal file
22
src/FrostFS.SDK.Client/Caches.cs
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
using Microsoft.Extensions.Caching.Memory;
|
||||||
|
|
||||||
|
namespace FrostFS.SDK.Client;
|
||||||
|
|
||||||
|
internal static class Caches
|
||||||
|
{
|
||||||
|
private static readonly IMemoryCache _ownersCache = new MemoryCache(new MemoryCacheOptions
|
||||||
|
{
|
||||||
|
// TODO: get from options?
|
||||||
|
SizeLimit = 256
|
||||||
|
});
|
||||||
|
|
||||||
|
private static readonly IMemoryCache _containersCache = new MemoryCache(new MemoryCacheOptions
|
||||||
|
{
|
||||||
|
// TODO: get from options?
|
||||||
|
SizeLimit = 1024
|
||||||
|
});
|
||||||
|
|
||||||
|
internal static IMemoryCache Owners => _ownersCache;
|
||||||
|
|
||||||
|
internal static IMemoryCache Containers => _containersCache;
|
||||||
|
}
|
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; } = key.PublicKey().ToString();
|
||||||
|
|
||||||
|
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,25 @@
|
||||||
|
using System;
|
||||||
|
|
||||||
|
namespace FrostFS.SDK.Client;
|
||||||
|
|
||||||
|
public class FrostFsResponseException : FrostFsException
|
||||||
|
{
|
||||||
|
public FrostFsResponseStatus? Status { get; private set; }
|
||||||
|
|
||||||
|
public FrostFsResponseException()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public FrostFsResponseException(FrostFsResponseStatus status)
|
||||||
|
{
|
||||||
|
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 Google.Protobuf;
|
||||||
|
|
||||||
|
namespace FrostFS.SDK.Cryptography;
|
||||||
|
|
||||||
|
public static class FrostFsExtensions
|
||||||
|
{
|
||||||
|
public static ByteString Sha256(this IMessage data)
|
||||||
|
{
|
||||||
|
return ByteString.CopyFrom(data.ToByteArray().Sha256());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Guid ToUuid(this ByteString id)
|
||||||
|
{
|
||||||
|
if (id == null)
|
||||||
|
throw new ArgumentNullException(nameof(id));
|
||||||
|
|
||||||
|
var orderedBytes = GetGuidBytesDirectOrder(id.Span);
|
||||||
|
|
||||||
|
return new Guid(orderedBytes);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Serializes Guid to binary representation in direct order bytes format
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="id"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public static byte[] ToBytes(this Guid id)
|
||||||
|
{
|
||||||
|
var bytes = id.ToByteArray();
|
||||||
|
|
||||||
|
var orderedBytes = GetGuidBytesDirectOrder(bytes);
|
||||||
|
|
||||||
|
return orderedBytes;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static byte[] GetGuidBytesDirectOrder(ReadOnlySpan<byte> source)
|
||||||
|
{
|
||||||
|
if (source.Length != 16)
|
||||||
|
throw new ArgumentException("Wrong uuid binary format");
|
||||||
|
|
||||||
|
return [
|
||||||
|
source[3],
|
||||||
|
source[2],
|
||||||
|
source[1],
|
||||||
|
source[0],
|
||||||
|
source[5],
|
||||||
|
source[4],
|
||||||
|
source[7],
|
||||||
|
source[6],
|
||||||
|
source[8],
|
||||||
|
source[9],
|
||||||
|
source[10],
|
||||||
|
source[11],
|
||||||
|
source[12],
|
||||||
|
source[13],
|
||||||
|
source[14],
|
||||||
|
source[15]
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
44
src/FrostFS.SDK.Client/FrostFS.SDK.Client.csproj
Normal file
44
src/FrostFS.SDK.Client/FrostFS.SDK.Client.csproj
Normal file
|
@ -0,0 +1,44 @@
|
||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>netstandard2.0</TargetFramework>
|
||||||
|
<LangVersion>12.0</LangVersion>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
<AnalysisMode>AllEnabledByDefault</AnalysisMode>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<EnableNETAnalyzers>true</EnableNETAnalyzers>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<CodeAnalysisTreatWarningsAsErrors>true</CodeAnalysisTreatWarningsAsErrors>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<_SkipUpgradeNetAnalyzersNuGetWarning>true</_SkipUpgradeNetAnalyzersNuGetWarning>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<EnforceCodeStyleInBuild>true</EnforceCodeStyleInBuild>
|
||||||
|
</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" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\FrostFS.SDK.Cryptography\FrostFS.SDK.Cryptography.csproj" />
|
||||||
|
<ProjectReference Include="..\FrostFS.SDK.Protos\FrostFS.SDK.Protos.csproj" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
</Project>
|
473
src/FrostFS.SDK.Client/FrostFSClient.cs
Normal file
473
src/FrostFS.SDK.Client/FrostFSClient.cs
Normal file
|
@ -0,0 +1,473 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
using Frostfs.V2.Ape;
|
||||||
|
|
||||||
|
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
|
||||||
|
};
|
||||||
|
|
||||||
|
// TODO: define timeout logic
|
||||||
|
// CheckFrostFsVersionSupport(new Context { Timeout = TimeSpan.FromSeconds(20) });
|
||||||
|
}
|
||||||
|
|
||||||
|
internal FrostFSClient(WrapperPrm prm, SessionCache cache)
|
||||||
|
{
|
||||||
|
ClientCtx = new ClientContext(
|
||||||
|
client: this,
|
||||||
|
key: new ClientKey(prm.Key),
|
||||||
|
owner: FrostFsOwner.FromKey(prm.Key!),
|
||||||
|
channel: prm.GrpcChannelFactory(prm.Address),
|
||||||
|
version: new FrostFsVersion(2, 13))
|
||||||
|
{
|
||||||
|
SessionCache = cache,
|
||||||
|
Interceptors = prm.Interceptors,
|
||||||
|
Callback = prm.Callback
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
#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<Chain[]> 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);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task<FrostFsContainerId> CreateContainerAsync(PrmContainerCreate args, CallContext ctx)
|
||||||
|
{
|
||||||
|
return GetContainerService().CreateContainerAsync(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().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 async void CheckFrostFsVersionSupport(CallContext ctx)
|
||||||
|
{
|
||||||
|
var service = GetNetmapService();
|
||||||
|
var localNodeInfo = await service.GetLocalNodeInfoAsync(ctx).ConfigureAwait(false);
|
||||||
|
|
||||||
|
if (!localNodeInfo.Version.IsSupported(ClientCtx.Version))
|
||||||
|
{
|
||||||
|
var msg = $"FrostFS {localNodeInfo.Version} is not supported.";
|
||||||
|
throw new FrostFsException(msg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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 = 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 = 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 = 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 = 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 = 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 = ObjectServiceClient ?? (
|
||||||
|
invoker != null
|
||||||
|
? new ObjectServiceClient(invoker)
|
||||||
|
: new ObjectServiceClient(ClientCtx.Channel));
|
||||||
|
|
||||||
|
ObjectServiceProvider = new ObjectServiceProvider(ObjectServiceClient, ClientCtx);
|
||||||
|
}
|
||||||
|
|
||||||
|
return ObjectServiceProvider;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<string?> Dial(CallContext ctx)
|
||||||
|
{
|
||||||
|
var service = GetAccouningService();
|
||||||
|
_ = await service.GetBallance(ctx).ConfigureAwait(false);
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool RestartIfUnhealthy(CallContext ctx)
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
}
|
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;
|
||||||
|
}
|
||||||
|
}
|
68
src/FrostFS.SDK.Client/Interfaces/IFrostFSClient.cs
Normal file
68
src/FrostFS.SDK.Client/Interfaces/IFrostFSClient.cs
Normal file
|
@ -0,0 +1,68 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
using Frostfs.V2.Ape;
|
||||||
|
|
||||||
|
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<Chain[]> ListChainAsync(PrmApeChainList args, CallContext ctx);
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Container
|
||||||
|
Task<FrostFsContainerInfo> GetContainerAsync(PrmContainerGet args, CallContext ctx);
|
||||||
|
|
||||||
|
IAsyncEnumerable<FrostFsContainerId> ListContainersAsync(PrmContainerGetAll args, CallContext ctx);
|
||||||
|
|
||||||
|
Task<FrostFsContainerId> CreateContainerAsync(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
|
||||||
|
|
||||||
|
public Task<string?> Dial(CallContext ctx);
|
||||||
|
}
|
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());
|
||||||
|
}
|
||||||
|
}
|
48
src/FrostFS.SDK.Client/Mappers/ContainerId.cs
Normal file
48
src/FrostFS.SDK.Client/Mappers/ContainerId.cs
Normal file
|
@ -0,0 +1,48 @@
|
||||||
|
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 ContainerIdMapper
|
||||||
|
{
|
||||||
|
private static readonly MemoryCacheEntryOptions _oneHourExpiration = new MemoryCacheEntryOptions()
|
||||||
|
.SetSlidingExpiration(TimeSpan.FromHours(1))
|
||||||
|
.SetSize(1);
|
||||||
|
|
||||||
|
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));
|
||||||
|
|
||||||
|
if (!Caches.Containers.TryGetValue(containerId, out ContainerID? message))
|
||||||
|
{
|
||||||
|
message = new ContainerID
|
||||||
|
{
|
||||||
|
Value = ByteString.CopyFrom(Base58.Decode(containerId))
|
||||||
|
};
|
||||||
|
|
||||||
|
Caches.Containers.Set(containerId, message, _oneHourExpiration);
|
||||||
|
}
|
||||||
|
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
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
22
src/FrostFS.SDK.Client/Mappers/Netmap/PlacementPolicy.cs
Normal file
22
src/FrostFS.SDK.Client/Mappers/Netmap/PlacementPolicy.cs
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
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.Replicas.Select(replica => replica.ToModel()).ToArray()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
27
src/FrostFS.SDK.Client/Mappers/Netmap/Replica.cs
Normal file
27
src/FrostFS.SDK.Client/Mappers/Netmap/Replica.cs
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
using System;
|
||||||
|
|
||||||
|
using FrostFS.Netmap;
|
||||||
|
|
||||||
|
namespace FrostFS.SDK.Client;
|
||||||
|
|
||||||
|
public static class ReplicaMapper
|
||||||
|
{
|
||||||
|
public static Replica ToMessage(this FrostFsReplica replica)
|
||||||
|
{
|
||||||
|
return new Replica
|
||||||
|
{
|
||||||
|
Count = (uint)replica.Count,
|
||||||
|
Selector = replica.Selector
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public static FrostFsReplica ToModel(this Replica replica)
|
||||||
|
{
|
||||||
|
if (replica is null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(replica));
|
||||||
|
}
|
||||||
|
|
||||||
|
return new FrostFsReplica((int)replica.Count, replica.Selector);
|
||||||
|
}
|
||||||
|
}
|
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,32 @@
|
||||||
|
using System;
|
||||||
|
|
||||||
|
using FrostFS.Object;
|
||||||
|
|
||||||
|
namespace FrostFS.SDK.Client.Mappers.GRPC;
|
||||||
|
|
||||||
|
public static class ObjectAttributeMapper
|
||||||
|
{
|
||||||
|
public static Header.Types.Attribute ToMessage(this FrostFsAttributePair attribute)
|
||||||
|
{
|
||||||
|
if (attribute is null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(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()
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
55
src/FrostFS.SDK.Client/Mappers/Object/ObjectHeaderMapper.cs
Normal file
55
src/FrostFS.SDK.Client/Mappers/Object/ObjectHeaderMapper.cs
Normal file
|
@ -0,0 +1,55 @@
|
||||||
|
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 = null;
|
||||||
|
|
||||||
|
if (header.Split != null)
|
||||||
|
{
|
||||||
|
var children = header.Split.Children.Count != 0 ? new ReadOnlyCollection<FrostFsObjectId>(
|
||||||
|
header.Split.Children.Select(x => x.ToModel()).ToList()) : null;
|
||||||
|
|
||||||
|
split = new FrostFsSplit(new SplitId(header.Split.SplitId.ToUuid()),
|
||||||
|
header.Split.Previous?.ToModel(),
|
||||||
|
header.Split.Parent?.ToModel(),
|
||||||
|
header.Split.ParentHeader?.ToModel(),
|
||||||
|
null,
|
||||||
|
children);
|
||||||
|
}
|
||||||
|
|
||||||
|
var model = new FrostFsObjectHeader(
|
||||||
|
new FrostFsContainerId(Base58.Encode(header.ContainerId.Value.Span)),
|
||||||
|
objTypeName,
|
||||||
|
header.Attributes.Select(attribute => attribute.ToModel()).ToArray(),
|
||||||
|
split,
|
||||||
|
header.OwnerId.ToModel(),
|
||||||
|
header.Version.ToModel())
|
||||||
|
{
|
||||||
|
PayloadLength = header.PayloadLength,
|
||||||
|
};
|
||||||
|
|
||||||
|
return model;
|
||||||
|
}
|
||||||
|
}
|
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 = ByteString.CopyFrom(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 = ByteString.CopyFrom(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!;
|
||||||
|
}
|
||||||
|
}
|
28
src/FrostFS.SDK.Client/Mappers/Session/SessionMapper.cs
Normal file
28
src/FrostFS.SDK.Client/Mappers/Session/SessionMapper.cs
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
using System;
|
||||||
|
|
||||||
|
using Google.Protobuf;
|
||||||
|
|
||||||
|
namespace FrostFS.SDK.Client;
|
||||||
|
|
||||||
|
public static class SessionMapper
|
||||||
|
{
|
||||||
|
public static byte[] Serialize(this Session.SessionToken token)
|
||||||
|
{
|
||||||
|
if (token is null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(token));
|
||||||
|
}
|
||||||
|
|
||||||
|
byte[] bytes = new byte[token.CalculateSize()];
|
||||||
|
using CodedOutputStream stream = new(bytes);
|
||||||
|
token.WriteTo(stream);
|
||||||
|
|
||||||
|
return bytes;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Session.SessionToken Deserialize(this Session.SessionToken token, byte[] bytes)
|
||||||
|
{
|
||||||
|
token.MergeFrom(bytes);
|
||||||
|
return token;
|
||||||
|
}
|
||||||
|
}
|
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 = ByteString.CopyFrom(signature.Key),
|
||||||
|
Scheme = scheme,
|
||||||
|
Sign = ByteString.CopyFrom(signature.Sign)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
18
src/FrostFS.SDK.Client/Mappers/Status.cs
Normal file
18
src/FrostFS.SDK.Client/Mappers/Status.cs
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
using System;
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
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];
|
||||||
|
}
|
||||||
|
}
|
63
src/FrostFS.SDK.Client/Models/Chain/ChainTarget.cs
Normal file
63
src/FrostFS.SDK.Client/Models/Chain/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);
|
||||||
|
}
|
||||||
|
}
|
41
src/FrostFS.SDK.Client/Models/Chain/FrostFsChain.cs
Normal file
41
src/FrostFS.SDK.Client/Models/Chain/FrostFsChain.cs
Normal file
|
@ -0,0 +1,41 @@
|
||||||
|
using Google.Protobuf;
|
||||||
|
|
||||||
|
namespace FrostFS.SDK.Client;
|
||||||
|
|
||||||
|
public struct FrostFsChain(byte[] raw) : System.IEquatable<FrostFsChain>
|
||||||
|
{
|
||||||
|
private ByteString? grpcRaw;
|
||||||
|
|
||||||
|
public byte[] Raw { get; } = raw;
|
||||||
|
|
||||||
|
internal ByteString GetRaw()
|
||||||
|
{
|
||||||
|
return grpcRaw ??= ByteString.CopyFrom(Raw);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override readonly bool Equals(object obj)
|
||||||
|
{
|
||||||
|
var chain = (FrostFsChain)obj;
|
||||||
|
return Equals(chain);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override readonly int GetHashCode()
|
||||||
|
{
|
||||||
|
return Raw.GetHashCode();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool operator ==(FrostFsChain left, FrostFsChain right)
|
||||||
|
{
|
||||||
|
return left.Equals(right);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool operator !=(FrostFsChain left, FrostFsChain right)
|
||||||
|
{
|
||||||
|
return !(left == right);
|
||||||
|
}
|
||||||
|
|
||||||
|
public readonly bool Equals(FrostFsChain other)
|
||||||
|
{
|
||||||
|
return Raw == other.Raw;
|
||||||
|
}
|
||||||
|
}
|
10
src/FrostFS.SDK.Client/Models/Chain/FrostFsTargetType.cs
Normal file
10
src/FrostFS.SDK.Client/Models/Chain/FrostFsTargetType.cs
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
namespace FrostFS.SDK.Client;
|
||||||
|
|
||||||
|
public enum FrostFsTargetType
|
||||||
|
{
|
||||||
|
Undefined = 0,
|
||||||
|
Namespace,
|
||||||
|
Container,
|
||||||
|
User,
|
||||||
|
Group
|
||||||
|
}
|
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,58 @@
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
|
||||||
|
internal ContainerID ContainerID
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
}
|
109
src/FrostFS.SDK.Client/Models/Containers/FrostFsContainerInfo.cs
Normal file
109
src/FrostFS.SDK.Client/Models/Containers/FrostFsContainerInfo.cs
Normal file
|
@ -0,0 +1,109 @@
|
||||||
|
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");
|
||||||
|
}
|
||||||
|
|
||||||
|
this.container = new Container.Container()
|
||||||
|
{
|
||||||
|
PlacementPolicy = PlacementPolicy.Value.GetPolicy(),
|
||||||
|
Nonce = ByteString.CopyFrom(Nonce.ToBytes()),
|
||||||
|
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 = content.Sha256() };
|
||||||
|
}
|
||||||
|
|
||||||
|
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";
|
||||||
|
}
|
10
src/FrostFS.SDK.Client/Models/Netmap/FrostFsFilter.cs
Normal file
10
src/FrostFS.SDK.Client/Models/Netmap/FrostFsFilter.cs
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
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;
|
||||||
|
}
|
265
src/FrostFS.SDK.Client/Models/Netmap/FrostFsNetmapSnapshot.cs
Normal file
265
src/FrostFS.SDK.Client/Models/Netmap/FrostFsNetmapSnapshot.cs
Normal file
|
@ -0,0 +1,265 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Collections.ObjectModel;
|
||||||
|
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, spanWeigths);
|
||||||
|
|
||||||
|
Tools.SortHasherSliceByWeightValue(result[i].ToList<FrostFsNodeInfo>(), spanWeigths, hash);
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
|
||||||
|
foreach (var f in expr.Filters)
|
||||||
|
policy.Filters.Add(f);
|
||||||
|
|
||||||
|
policy.Selectors.Add(expr.Selector);
|
||||||
|
|
||||||
|
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[^1];
|
||||||
|
|
||||||
|
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
|
||||||
|
};
|
||||||
|
|
||||||
|
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(Context.mainFilterName)
|
||||||
|
{
|
||||||
|
Count = p.Replicas[i].CountNodes()
|
||||||
|
};
|
||||||
|
|
||||||
|
var nodes = c.GetSelection(s);
|
||||||
|
|
||||||
|
var arg = new List<List<FrostFsNodeInfo>>(nodes.Count);
|
||||||
|
for (int j = 0; j < nodes.Count; j++)
|
||||||
|
{
|
||||||
|
arg[i] = nodes[j];
|
||||||
|
}
|
||||||
|
|
||||||
|
result[i].AddRange(FlattenNodes(arg));
|
||||||
|
|
||||||
|
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];
|
||||||
|
|
||||||
|
var arg = new List<List<FrostFsNodeInfo>>(nodes.Count);
|
||||||
|
for (int j = 0; j < nodes.Count; j++)
|
||||||
|
{
|
||||||
|
arg[i] = nodes[j];
|
||||||
|
}
|
||||||
|
|
||||||
|
result[i].AddRange(FlattenNodes(arg));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,94 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.ObjectModel;
|
||||||
|
using System.Linq;
|
||||||
|
|
||||||
|
using FrostFS.Netmap;
|
||||||
|
using FrostFS.SDK.Client;
|
||||||
|
|
||||||
|
namespace FrostFS.SDK;
|
||||||
|
|
||||||
|
public struct FrostFsPlacementPolicy(bool unique, params FrostFsReplica[] replicas)
|
||||||
|
: IEquatable<FrostFsPlacementPolicy>
|
||||||
|
{
|
||||||
|
private PlacementPolicy policy;
|
||||||
|
|
||||||
|
public FrostFsReplica[] Replicas { get; private set; } = replicas;
|
||||||
|
|
||||||
|
public Collection<FrostFsSelector> Selectors { get; } = [];
|
||||||
|
|
||||||
|
public Collection<FrostFsFilter> Filters { get; } = [];
|
||||||
|
|
||||||
|
public bool Unique { get; private set; } = unique;
|
||||||
|
|
||||||
|
public uint BackupFactor { get; set; }
|
||||||
|
|
||||||
|
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
|
||||||
|
};
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
10
src/FrostFS.SDK.Client/Models/Netmap/FrostFsSelector.cs
Normal file
10
src/FrostFS.SDK.Client/Models/Netmap/FrostFsSelector.cs
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
namespace FrostFS.SDK;
|
||||||
|
|
||||||
|
public class FrostFsSelector(string name)
|
||||||
|
{
|
||||||
|
public string Name { get; } = name;
|
||||||
|
public uint Count { get; set; }
|
||||||
|
public uint Clause { get; set; }
|
||||||
|
public string? Attribute { get; set; }
|
||||||
|
public string? Filter { get; set; }
|
||||||
|
}
|
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
|
||||||
|
}
|
433
src/FrostFS.SDK.Client/Models/Netmap/Placement/Context.cs
Normal file
433
src/FrostFS.SDK.Client/Models/Netmap/Placement/Context.cs
Normal file
|
@ -0,0 +1,433 @@
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (filter.Operation == (int)Operation.EQ ||
|
||||||
|
filter.Operation == (int)Operation.NE ||
|
||||||
|
filter.Operation == (int)Operation.LIKE ||
|
||||||
|
filter.Operation == (int)Operation.GT ||
|
||||||
|
filter.Operation == (int)Operation.GE ||
|
||||||
|
filter.Operation == (int)Operation.LT ||
|
||||||
|
filter.Operation == (int)Operation.LE)
|
||||||
|
{
|
||||||
|
var n = uint.Parse(filter.Value, CultureInfo.InvariantCulture);
|
||||||
|
NumCache[filter.Value] = n;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
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 == (uint)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 = [];
|
||||||
|
|
||||||
|
foreach (var res in result)
|
||||||
|
{
|
||||||
|
Tools.AppendWeightsTo(res.nodes, weightFunc, ws);
|
||||||
|
Tools.SortHasherSliceByWeightValue(res.nodes.ToList(), ws, HrwSeedHash);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return [.. result];
|
||||||
|
}
|
||||||
|
|
||||||
|
static double CalcBucketWeight(List<FrostFsNodeInfo> ns, IAggregator 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(new List<FrostFsNodeInfo>(ns[..maxNodesInBucket]));
|
||||||
|
}
|
||||||
|
else if (ns.Length >= nodesInBucket)
|
||||||
|
{
|
||||||
|
fallback.Add(new List<FrostFsNodeInfo>(ns));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (res.Count < bucketCount)
|
||||||
|
{
|
||||||
|
// Fallback to using minimum allowed backup factor (1).
|
||||||
|
res = 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();
|
||||||
|
Tools.SortHasherSliceByWeightValue(hashers, weights, HrwSeedHash);
|
||||||
|
}
|
||||||
|
|
||||||
|
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[f.Key] == 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[start..end];
|
||||||
|
|
||||||
|
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:
|
||||||
|
{
|
||||||
|
var attr = f.Key switch
|
||||||
|
{
|
||||||
|
FrostFsNodeInfo.AttrPrice => nodeInfo.Price,
|
||||||
|
FrostFsNodeInfo.AttrCapacity => nodeInfo.GetCapacity(),
|
||||||
|
_ => uint.Parse(nodeInfo.Attributes[f.Key], CultureInfo.InvariantCulture),
|
||||||
|
};
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
18
src/FrostFS.SDK.Client/Models/Netmap/Placement/HasherList.cs
Normal file
18
src/FrostFS.SDK.Client/Models/Netmap/Placement/HasherList.cs
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
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 *= (double)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);
|
||||||
|
}
|
||||||
|
}
|
140
src/FrostFS.SDK.Client/Models/Netmap/Placement/Tools.cs
Normal file
140
src/FrostFS.SDK.Client/Models/Netmap/Placement/Tools.cs
Normal file
|
@ -0,0 +1,140 @@
|
||||||
|
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, 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 void SortHasherSliceByWeightValue<T>(List<T> nodes, Span<double> weights, ulong hash) where T : IHasher
|
||||||
|
{
|
||||||
|
if (nodes.Count == 0)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
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 ulong[nodes.Count];
|
||||||
|
|
||||||
|
if (allEquals)
|
||||||
|
{
|
||||||
|
for (int i = 0; i < dist.Length; i++)
|
||||||
|
{
|
||||||
|
var x = nodes[i].Hash();
|
||||||
|
dist[i] = Distance(x, hash);
|
||||||
|
}
|
||||||
|
|
||||||
|
SortHasherByDistance(nodes, dist, true);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < dist.Length; i++)
|
||||||
|
{
|
||||||
|
var d = Distance(nodes[i].Hash(), hash);
|
||||||
|
dist[i] = ulong.MaxValue - (ulong)(d * weights[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
SortHasherByDistance(nodes, dist, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
internal static void 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)
|
||||||
|
{
|
||||||
|
nodes = new List<T>(indexes
|
||||||
|
.OrderBy(x => x.dist)
|
||||||
|
.Select(x => x.nodeInfo).ToArray());
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
nodes = new List<T>(indexes
|
||||||
|
.OrderByDescending(x => x.dist)
|
||||||
|
.Select(x => x.nodeInfo)
|
||||||
|
.ToArray());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,8 @@
|
||||||
|
namespace FrostFS.SDK;
|
||||||
|
|
||||||
|
public class FrostFsAttributePair(string key, string value)
|
||||||
|
{
|
||||||
|
public string Key { get; set; } = key;
|
||||||
|
|
||||||
|
public string Value { get; set; } = value;
|
||||||
|
}
|
|
@ -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));
|
||||||
|
}
|
||||||
|
}
|
74
src/FrostFS.SDK.Client/Models/Object/FrostFsObject.cs
Normal file
74
src/FrostFS.SDK.Client/Models/Object/FrostFsObject.cs
Normal file
|
@ -0,0 +1,74 @@
|
||||||
|
using System;
|
||||||
|
|
||||||
|
namespace FrostFS.SDK;
|
||||||
|
|
||||||
|
public class FrostFsObject
|
||||||
|
{
|
||||||
|
private byte[]? bytes;
|
||||||
|
|
||||||
|
/// <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; }
|
||||||
|
|
||||||
|
internal byte[] SingleObjectPayload
|
||||||
|
{
|
||||||
|
get { return bytes ?? []; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The size of payload cannot exceed <c>MaxObjectSize</c> value from <c>NetworkSettings</c>
|
||||||
|
/// Used only for PutSingleObject method
|
||||||
|
/// </summary>
|
||||||
|
/// <value>Buffer for output data</value>
|
||||||
|
public void SetSingleObjectPayload(byte[] bytes)
|
||||||
|
{
|
||||||
|
this.bytes = bytes;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <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;
|
||||||
|
}
|
||||||
|
}
|
28
src/FrostFS.SDK.Client/Models/Object/FrostFsObjectId.cs
Normal file
28
src/FrostFS.SDK.Client/Models/Object/FrostFsObjectId.cs
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
using System;
|
||||||
|
|
||||||
|
using FrostFS.SDK.Cryptography;
|
||||||
|
|
||||||
|
namespace FrostFS.SDK;
|
||||||
|
|
||||||
|
public class FrostFsObjectId(string id)
|
||||||
|
{
|
||||||
|
public string Value { get; } = id;
|
||||||
|
|
||||||
|
public static FrostFsObjectId FromHash(ReadOnlySpan<byte> hash)
|
||||||
|
{
|
||||||
|
if (hash.Length != Constants.Sha256HashLength)
|
||||||
|
throw new FormatException("ObjectID must be a sha256 hash.");
|
||||||
|
|
||||||
|
return new FrostFsObjectId(Base58.Encode(hash));
|
||||||
|
}
|
||||||
|
|
||||||
|
public byte[] ToHash()
|
||||||
|
{
|
||||||
|
return Base58.Decode(Value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override string ToString()
|
||||||
|
{
|
||||||
|
return Value;
|
||||||
|
}
|
||||||
|
}
|
39
src/FrostFS.SDK.Client/Models/Object/FrostFsOwner.cs
Normal file
39
src/FrostFS.SDK.Client/Models/Object/FrostFsOwner.cs
Normal file
|
@ -0,0 +1,39 @@
|
||||||
|
using System.Security.Cryptography;
|
||||||
|
|
||||||
|
using FrostFS.Refs;
|
||||||
|
using FrostFS.SDK.Client.Mappers.GRPC;
|
||||||
|
using FrostFS.SDK.Cryptography;
|
||||||
|
|
||||||
|
|
||||||
|
namespace FrostFS.SDK;
|
||||||
|
|
||||||
|
public class FrostFsOwner(string id)
|
||||||
|
{
|
||||||
|
private OwnerID? ownerID;
|
||||||
|
|
||||||
|
public string Value { get; } = id;
|
||||||
|
|
||||||
|
public static FrostFsOwner FromKey(ECDsa key)
|
||||||
|
{
|
||||||
|
return new FrostFsOwner(key.PublicKey().PublicKeyToAddress());
|
||||||
|
}
|
||||||
|
|
||||||
|
internal OwnerID OwnerID
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
ownerID ??= this.ToMessage();
|
||||||
|
return ownerID;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public byte[] ToHash()
|
||||||
|
{
|
||||||
|
return Base58.Decode(Value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override string ToString()
|
||||||
|
{
|
||||||
|
return Value;
|
||||||
|
}
|
||||||
|
}
|
27
src/FrostFS.SDK.Client/Models/Object/FrostFsRange.cs
Normal file
27
src/FrostFS.SDK.Client/Models/Object/FrostFsRange.cs
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
namespace FrostFS.SDK;
|
||||||
|
|
||||||
|
public readonly struct FrostFsRange(ulong offset, ulong length) : System.IEquatable<FrostFsRange>
|
||||||
|
{
|
||||||
|
public ulong Offset { get; } = offset;
|
||||||
|
|
||||||
|
public ulong Length { get; } = length;
|
||||||
|
|
||||||
|
public override readonly bool Equals(object obj) => this == (FrostFsRange)obj;
|
||||||
|
|
||||||
|
public override readonly int GetHashCode() => $"{Offset}{Length}".GetHashCode();
|
||||||
|
|
||||||
|
public static bool operator ==(FrostFsRange left, FrostFsRange right)
|
||||||
|
{
|
||||||
|
return left.Equals(right);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool operator !=(FrostFsRange left, FrostFsRange right)
|
||||||
|
{
|
||||||
|
return !(left == right);
|
||||||
|
}
|
||||||
|
|
||||||
|
public readonly bool Equals(FrostFsRange other)
|
||||||
|
{
|
||||||
|
return this == other;
|
||||||
|
}
|
||||||
|
}
|
27
src/FrostFS.SDK.Client/Models/Object/FrostFsSplit.cs
Normal file
27
src/FrostFS.SDK.Client/Models/Object/FrostFsSplit.cs
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
using System.Collections.ObjectModel;
|
||||||
|
|
||||||
|
namespace FrostFS.SDK;
|
||||||
|
|
||||||
|
public class FrostFsSplit(SplitId splitId,
|
||||||
|
FrostFsObjectId? previous = null,
|
||||||
|
FrostFsObjectId? parent = null,
|
||||||
|
FrostFsObjectHeader? parentHeader = null,
|
||||||
|
FrostFsSignature? parentSignature = null,
|
||||||
|
ReadOnlyCollection<FrostFsObjectId>? children = null)
|
||||||
|
{
|
||||||
|
public FrostFsSplit() : this(new SplitId())
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public SplitId SplitId { get; private set; } = splitId;
|
||||||
|
|
||||||
|
public FrostFsObjectId? Previous { get; } = previous;
|
||||||
|
|
||||||
|
public FrostFsObjectId? Parent { get; } = parent;
|
||||||
|
|
||||||
|
public FrostFsSignature? ParentSignature { get; } = parentSignature;
|
||||||
|
|
||||||
|
public FrostFsObjectHeader? ParentHeader { get; set; } = parentHeader;
|
||||||
|
|
||||||
|
public ReadOnlyCollection<FrostFsObjectId>? Children { get; } = children;
|
||||||
|
}
|
28
src/FrostFS.SDK.Client/Models/Object/FrostFsSplitInfo.cs
Normal file
28
src/FrostFS.SDK.Client/Models/Object/FrostFsSplitInfo.cs
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
using FrostFS.Object;
|
||||||
|
using FrostFS.SDK.Cryptography;
|
||||||
|
|
||||||
|
namespace FrostFS.SDK;
|
||||||
|
|
||||||
|
public class FrostFsSplitInfo
|
||||||
|
{
|
||||||
|
private readonly SplitInfo _splitInfo;
|
||||||
|
|
||||||
|
private SplitId? _splitId;
|
||||||
|
|
||||||
|
private FrostFsObjectId? _link;
|
||||||
|
|
||||||
|
private FrostFsObjectId? _lastPart;
|
||||||
|
|
||||||
|
internal FrostFsSplitInfo(SplitInfo splitInfo)
|
||||||
|
{
|
||||||
|
_splitInfo = splitInfo;
|
||||||
|
}
|
||||||
|
|
||||||
|
public SplitId SplitId => _splitId ??= new SplitId(_splitInfo.SplitId.ToUuid());
|
||||||
|
|
||||||
|
public FrostFsObjectId? Link => _link ??= _splitInfo.Link == null
|
||||||
|
? null : FrostFsObjectId.FromHash(_splitInfo.Link.Value.Span);
|
||||||
|
|
||||||
|
public FrostFsObjectId? LastPart => _lastPart ??= _splitInfo.LastPart == null
|
||||||
|
? null : FrostFsObjectId.FromHash(_splitInfo.LastPart.Value.Span);
|
||||||
|
}
|
10
src/FrostFS.SDK.Client/Models/Object/IObjectReader.cs
Normal file
10
src/FrostFS.SDK.Client/Models/Object/IObjectReader.cs
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
using System;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace FrostFS.SDK;
|
||||||
|
|
||||||
|
public interface IObjectReader : IDisposable
|
||||||
|
{
|
||||||
|
ValueTask<ReadOnlyMemory<byte>?> ReadChunk(CancellationToken cancellationToken = default);
|
||||||
|
}
|
62
src/FrostFS.SDK.Client/Models/Object/SplitId.cs
Normal file
62
src/FrostFS.SDK.Client/Models/Object/SplitId.cs
Normal file
|
@ -0,0 +1,62 @@
|
||||||
|
using System;
|
||||||
|
|
||||||
|
using FrostFS.SDK.Cryptography;
|
||||||
|
|
||||||
|
using Google.Protobuf;
|
||||||
|
|
||||||
|
namespace FrostFS.SDK;
|
||||||
|
|
||||||
|
public class SplitId
|
||||||
|
{
|
||||||
|
private readonly Guid id;
|
||||||
|
|
||||||
|
private ByteString? message;
|
||||||
|
|
||||||
|
public SplitId()
|
||||||
|
{
|
||||||
|
this.id = Guid.NewGuid();
|
||||||
|
}
|
||||||
|
|
||||||
|
public SplitId(Guid id)
|
||||||
|
{
|
||||||
|
this.id = id;
|
||||||
|
}
|
||||||
|
|
||||||
|
private SplitId(byte[] binary)
|
||||||
|
{
|
||||||
|
this.id = new Guid(binary);
|
||||||
|
}
|
||||||
|
|
||||||
|
private SplitId(string str)
|
||||||
|
{
|
||||||
|
this.id = new Guid(str);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static SplitId CreateFromBinary(byte[] binaryData)
|
||||||
|
{
|
||||||
|
return new SplitId(binaryData);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static SplitId CreateFromString(string stringData)
|
||||||
|
{
|
||||||
|
return new SplitId(stringData);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override string ToString()
|
||||||
|
{
|
||||||
|
return this.id.ToString();
|
||||||
|
}
|
||||||
|
|
||||||
|
public byte[]? ToBinary()
|
||||||
|
{
|
||||||
|
if (this.id == Guid.Empty)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
return this.id.ToBytes();
|
||||||
|
}
|
||||||
|
|
||||||
|
public ByteString? GetSplitId()
|
||||||
|
{
|
||||||
|
return this.message ??= ByteString.CopyFrom(ToBinary());
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,14 @@
|
||||||
|
namespace FrostFS.SDK;
|
||||||
|
|
||||||
|
public class FrostFsResponseStatus(FrostFsStatusCode code, string? message = null)
|
||||||
|
{
|
||||||
|
public FrostFsStatusCode Code { get; set; } = code;
|
||||||
|
public string Message { get; set; } = message ?? string.Empty;
|
||||||
|
|
||||||
|
public bool IsSuccess => Code == FrostFsStatusCode.Success;
|
||||||
|
|
||||||
|
public override string ToString()
|
||||||
|
{
|
||||||
|
return $"Response status: {Code}. Message: {Message}.";
|
||||||
|
}
|
||||||
|
}
|
10
src/FrostFS.SDK.Client/Models/Response/FrostFsSignature.cs
Normal file
10
src/FrostFS.SDK.Client/Models/Response/FrostFsSignature.cs
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
namespace FrostFS.SDK;
|
||||||
|
|
||||||
|
public class FrostFsSignature()
|
||||||
|
{
|
||||||
|
public byte[]? Key { get; set; }
|
||||||
|
|
||||||
|
public byte[]? Sign { get; set; }
|
||||||
|
|
||||||
|
public SignatureScheme Scheme { get; set; }
|
||||||
|
}
|
20
src/FrostFS.SDK.Client/Models/Response/MetaHeader.cs
Normal file
20
src/FrostFS.SDK.Client/Models/Response/MetaHeader.cs
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
namespace FrostFS.SDK;
|
||||||
|
|
||||||
|
public class MetaHeader(FrostFsVersion version, int epoch, int ttl)
|
||||||
|
{
|
||||||
|
public FrostFsVersion Version { get; set; } = version;
|
||||||
|
public int Epoch { get; set; } = epoch;
|
||||||
|
public int Ttl { get; set; } = ttl;
|
||||||
|
|
||||||
|
public static MetaHeader Default()
|
||||||
|
{
|
||||||
|
return new MetaHeader(
|
||||||
|
new FrostFsVersion(
|
||||||
|
major: 2,
|
||||||
|
minor: 13
|
||||||
|
),
|
||||||
|
epoch: 0,
|
||||||
|
ttl: 2
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
117
src/FrostFS.SDK.Client/Models/Session/FrostFsSessionToken.cs
Normal file
117
src/FrostFS.SDK.Client/Models/Session/FrostFsSessionToken.cs
Normal file
|
@ -0,0 +1,117 @@
|
||||||
|
using System;
|
||||||
|
|
||||||
|
using FrostFS.Refs;
|
||||||
|
|
||||||
|
using FrostFS.SDK.Client;
|
||||||
|
using FrostFS.SDK.Cryptography;
|
||||||
|
using FrostFS.Session;
|
||||||
|
|
||||||
|
using Google.Protobuf;
|
||||||
|
|
||||||
|
namespace FrostFS.SDK;
|
||||||
|
|
||||||
|
public class FrostFsSessionToken
|
||||||
|
{
|
||||||
|
private Guid _id;
|
||||||
|
private ReadOnlyMemory<byte> _sessionKey;
|
||||||
|
private readonly SessionToken.Types.Body _body;
|
||||||
|
|
||||||
|
private FrostFsSessionToken()
|
||||||
|
{
|
||||||
|
ProtoId = ByteString.Empty;
|
||||||
|
ProtoSessionKey = ByteString.Empty;
|
||||||
|
_body = new SessionToken.Types.Body();
|
||||||
|
}
|
||||||
|
|
||||||
|
internal FrostFsSessionToken(SessionToken token)
|
||||||
|
{
|
||||||
|
ProtoId = token.Body.Id;
|
||||||
|
ProtoSessionKey = token.Body.SessionKey;
|
||||||
|
|
||||||
|
_body = token.Body;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Guid Id
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
if (_id == Guid.Empty)
|
||||||
|
_id = ProtoId.ToUuid();
|
||||||
|
|
||||||
|
return _id;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public ReadOnlyMemory<byte> SessionKey
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
if (_sessionKey.IsEmpty)
|
||||||
|
_sessionKey = ProtoSessionKey.Memory;
|
||||||
|
|
||||||
|
return _sessionKey;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal ByteString ProtoId { get; }
|
||||||
|
|
||||||
|
internal ByteString ProtoSessionKey { get; }
|
||||||
|
|
||||||
|
public SessionToken CreateContainerToken(ContainerID? containerId, ContainerSessionContext.Types.Verb verb, ClientKey key)
|
||||||
|
{
|
||||||
|
if (key is null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(key));
|
||||||
|
}
|
||||||
|
|
||||||
|
SessionToken sessionToken = new() { Body = _body.Clone() };
|
||||||
|
|
||||||
|
sessionToken.Body.Container = new() { Verb = verb };
|
||||||
|
|
||||||
|
if (containerId != null)
|
||||||
|
sessionToken.Body.Container.ContainerId = containerId;
|
||||||
|
else
|
||||||
|
sessionToken.Body.Container.Wildcard = true;
|
||||||
|
|
||||||
|
sessionToken.Body.SessionKey = key.PublicKeyProto;
|
||||||
|
|
||||||
|
sessionToken.Signature = key.ECDsaKey.SignMessagePart(sessionToken.Body);
|
||||||
|
|
||||||
|
return sessionToken;
|
||||||
|
}
|
||||||
|
|
||||||
|
public SessionToken CreateObjectTokenContext(Address address, ObjectSessionContext.Types.Verb verb, ClientKey key)
|
||||||
|
{
|
||||||
|
if (address is null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(address));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (key is null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(key));
|
||||||
|
}
|
||||||
|
|
||||||
|
SessionToken sessionToken = new()
|
||||||
|
{
|
||||||
|
Body = _body.Clone()
|
||||||
|
};
|
||||||
|
|
||||||
|
ObjectSessionContext.Types.Target target = new() { Container = address.ContainerId };
|
||||||
|
|
||||||
|
if (address.ObjectId != null)
|
||||||
|
target.Objects.Add(address.ObjectId);
|
||||||
|
|
||||||
|
sessionToken.Body.Object = new()
|
||||||
|
{
|
||||||
|
Target = target,
|
||||||
|
Verb = verb
|
||||||
|
};
|
||||||
|
|
||||||
|
sessionToken.Body.SessionKey = key.PublicKeyProto;
|
||||||
|
|
||||||
|
sessionToken.Signature = key.ECDsaKey.SignMessagePart(sessionToken.Body);
|
||||||
|
|
||||||
|
return sessionToken;
|
||||||
|
}
|
||||||
|
}
|
68
src/FrostFS.SDK.Client/ObjectWriter.cs
Normal file
68
src/FrostFS.SDK.Client/ObjectWriter.cs
Normal file
|
@ -0,0 +1,68 @@
|
||||||
|
using System;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
using FrostFS.Object;
|
||||||
|
using FrostFS.SDK.Client.Interfaces;
|
||||||
|
|
||||||
|
using Google.Protobuf;
|
||||||
|
|
||||||
|
namespace FrostFS.SDK.Client
|
||||||
|
{
|
||||||
|
internal sealed class ObjectWriter : IObjectWriter
|
||||||
|
{
|
||||||
|
private readonly ClientContext ctx;
|
||||||
|
private readonly PrmObjectPutBase args;
|
||||||
|
private readonly ObjectStreamer<PutRequest, PutResponse> streamer;
|
||||||
|
private bool disposedValue;
|
||||||
|
|
||||||
|
internal ObjectWriter(ClientContext ctx, PrmObjectPutBase args, ObjectStreamer<PutRequest, PutResponse> streamer)
|
||||||
|
{
|
||||||
|
this.ctx = ctx;
|
||||||
|
this.args = args;
|
||||||
|
this.streamer = streamer;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task WriteAsync(ReadOnlyMemory<byte> memory)
|
||||||
|
{
|
||||||
|
var chunkRequest = new PutRequest
|
||||||
|
{
|
||||||
|
Body = new PutRequest.Types.Body
|
||||||
|
{
|
||||||
|
Chunk = UnsafeByteOperations.UnsafeWrap(memory)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
chunkRequest.Sign(this.ctx.Key.ECDsaKey);
|
||||||
|
|
||||||
|
await streamer.Write(chunkRequest).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<FrostFsObjectId> CompleteAsync()
|
||||||
|
{
|
||||||
|
var response = await streamer.Close().ConfigureAwait(false);
|
||||||
|
|
||||||
|
Verifier.CheckResponse(response);
|
||||||
|
|
||||||
|
return FrostFsObjectId.FromHash(response.Body.ObjectId.Value.Span);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Dispose(bool disposing)
|
||||||
|
{
|
||||||
|
if (!disposedValue)
|
||||||
|
{
|
||||||
|
if (disposing)
|
||||||
|
{
|
||||||
|
streamer.Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
disposedValue = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
Dispose(disposing: true);
|
||||||
|
GC.SuppressFinalize(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
44
src/FrostFS.SDK.Client/Parameters/CallContext.cs
Normal file
44
src/FrostFS.SDK.Client/Parameters/CallContext.cs
Normal file
|
@ -0,0 +1,44 @@
|
||||||
|
using System;
|
||||||
|
using System.Threading;
|
||||||
|
|
||||||
|
namespace FrostFS.SDK.Client;
|
||||||
|
|
||||||
|
public readonly struct CallContext(TimeSpan timeout, CancellationToken cancellationToken = default) : IEquatable<CallContext>
|
||||||
|
{
|
||||||
|
public CancellationToken CancellationToken { get; } = cancellationToken;
|
||||||
|
|
||||||
|
public TimeSpan Timeout { get; } = timeout;
|
||||||
|
|
||||||
|
internal readonly DateTime? GetDeadline()
|
||||||
|
{
|
||||||
|
return Timeout.Ticks > 0 ? DateTime.UtcNow.Add(Timeout) : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override readonly bool Equals(object obj)
|
||||||
|
{
|
||||||
|
if (obj == null || obj is not CallContext)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
return Equals((CallContext)obj);
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool Equals(CallContext other)
|
||||||
|
{
|
||||||
|
return Timeout == other.Timeout && CancellationToken.Equals(other.CancellationToken);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override int GetHashCode()
|
||||||
|
{
|
||||||
|
return CancellationToken.GetHashCode() ^ Timeout.GetHashCode();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool operator ==(CallContext left, CallContext right)
|
||||||
|
{
|
||||||
|
return left.Equals(right);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool operator !=(CallContext left, CallContext right)
|
||||||
|
{
|
||||||
|
return !(left == right);
|
||||||
|
}
|
||||||
|
}
|
12
src/FrostFS.SDK.Client/Parameters/ISessionToken.cs
Normal file
12
src/FrostFS.SDK.Client/Parameters/ISessionToken.cs
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
namespace FrostFS.SDK.Client;
|
||||||
|
|
||||||
|
public interface ISessionToken
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Object represents token of the FrostFS Object session. A session is opened between any two sides of the
|
||||||
|
/// system, and implements a mechanism for transferring the power of attorney of actions to another network
|
||||||
|
/// member. The session has a limited validity period, and applies to a strictly defined set of operations.
|
||||||
|
/// </summary>
|
||||||
|
/// <value>Instance of the session obtained from the server</value>
|
||||||
|
FrostFsSessionToken? SessionToken { get; }
|
||||||
|
}
|
43
src/FrostFS.SDK.Client/Parameters/PrmApeChainAdd.cs
Normal file
43
src/FrostFS.SDK.Client/Parameters/PrmApeChainAdd.cs
Normal file
|
@ -0,0 +1,43 @@
|
||||||
|
namespace FrostFS.SDK.Client;
|
||||||
|
|
||||||
|
public readonly struct PrmApeChainAdd(FrostFsChainTarget target, FrostFsChain chain, string[]? xheaders = null) : System.IEquatable<PrmApeChainAdd>
|
||||||
|
{
|
||||||
|
public FrostFsChainTarget Target { get; } = target;
|
||||||
|
|
||||||
|
public FrostFsChain Chain { get; } = chain;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// FrostFS request X-Headers
|
||||||
|
/// </summary>
|
||||||
|
public string[] XHeaders { get; } = xheaders ?? [];
|
||||||
|
|
||||||
|
public override readonly bool Equals(object obj)
|
||||||
|
{
|
||||||
|
if (obj == null || obj is not PrmApeChainAdd)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
return Equals((PrmApeChainAdd)obj);
|
||||||
|
}
|
||||||
|
|
||||||
|
public readonly bool Equals(PrmApeChainAdd other)
|
||||||
|
{
|
||||||
|
return Target == other.Target
|
||||||
|
&& Chain == other.Chain
|
||||||
|
&& XHeaders == other.XHeaders;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override readonly int GetHashCode()
|
||||||
|
{
|
||||||
|
return Chain.GetHashCode() ^ Target.GetHashCode() ^ XHeaders.GetHashCode();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool operator ==(PrmApeChainAdd left, PrmApeChainAdd right)
|
||||||
|
{
|
||||||
|
return left.Equals(right);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool operator !=(PrmApeChainAdd left, PrmApeChainAdd right)
|
||||||
|
{
|
||||||
|
return !(left == right);
|
||||||
|
}
|
||||||
|
}
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue