diff --git a/Notice.txt b/Notice.txt index a50af7e89b86..e0e5d546a6e2 100644 --- a/Notice.txt +++ b/Notice.txt @@ -89,6 +89,7 @@ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + ---------------- ** System.Text.Json @@ -119,6 +120,34 @@ SOFTWARE. ---------------- +** System.Formats.Cbor + +The MIT License (MIT) + +Copyright (c) .NET Foundation and Contributors + +All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +---------------- + ** BouncyCastle.Cryptography The MIT License (MIT) diff --git a/buildtools/NoticeForZips.txt b/buildtools/NoticeForZips.txt index c4d083a2e713..c64d1970a114 100644 --- a/buildtools/NoticeForZips.txt +++ b/buildtools/NoticeForZips.txt @@ -89,6 +89,7 @@ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + ---------------- ** System.Numerics.Vectors @@ -116,6 +117,7 @@ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + ---------------- ** System.Text.Json @@ -146,6 +148,34 @@ SOFTWARE. ---------------- +** System.Formats.Cbor + +The MIT License (MIT) + +Copyright (c) .NET Foundation and Contributors + +All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +---------------- + ** BouncyCastle.Cryptography The MIT License (MIT) diff --git a/buildtools/build.proj b/buildtools/build.proj index c1af958d9709..2e751e358916 100644 --- a/buildtools/build.proj +++ b/buildtools/build.proj @@ -416,7 +416,7 @@ Command="$(powershell) -ExecutionPolicy Unrestricted -NoProfile -File create-nuget-packages.ps1 -PackageList "$(ServiceList)""/> - + @@ -456,6 +456,11 @@ + + + + + diff --git a/extensions/AWSSDK.Extensions.sln b/extensions/AWSSDK.Extensions.sln index 3b45806eb294..c2d06ce0e53b 100644 --- a/extensions/AWSSDK.Extensions.sln +++ b/extensions/AWSSDK.Extensions.sln @@ -74,6 +74,12 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AWSSDK.CommonTest", "..\sdk EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AWSSDK.S3.NetFramework", "..\sdk\src\Services\S3\AWSSDK.S3.NetFramework.csproj", "{1A313326-988F-4FF2-992E-6D7E50DCF598}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AWSSDK.Extensions.CborProtocol.NetFramework", "src\AWSSDK.Extensions.CborProtocol\AWSSDK.Extensions.CborProtocol.NetFramework.csproj", "{4FCCBD72-8E9B-D74B-6538-D8684D181230}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AWSSDK.Extensions.CborProtocol.NetStandard", "src\AWSSDK.Extensions.CborProtocol\AWSSDK.Extensions.CborProtocol.NetStandard.csproj", "{24CBBC97-409E-BAC8-0553-D260AB0B8C6A}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CborProtocol.Tests.NetFramework", "test\CborProtocol.Tests\CborProtocol.Tests.NetFramework.csproj", "{9FFDECAB-94D6-7EB1-1A5B-487492600755}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -204,6 +210,18 @@ Global {1A313326-988F-4FF2-992E-6D7E50DCF598}.Debug|Any CPU.Build.0 = Debug|Any CPU {1A313326-988F-4FF2-992E-6D7E50DCF598}.Release|Any CPU.ActiveCfg = Release|Any CPU {1A313326-988F-4FF2-992E-6D7E50DCF598}.Release|Any CPU.Build.0 = Release|Any CPU + {4FCCBD72-8E9B-D74B-6538-D8684D181230}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {4FCCBD72-8E9B-D74B-6538-D8684D181230}.Debug|Any CPU.Build.0 = Debug|Any CPU + {4FCCBD72-8E9B-D74B-6538-D8684D181230}.Release|Any CPU.ActiveCfg = Release|Any CPU + {4FCCBD72-8E9B-D74B-6538-D8684D181230}.Release|Any CPU.Build.0 = Release|Any CPU + {24CBBC97-409E-BAC8-0553-D260AB0B8C6A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {24CBBC97-409E-BAC8-0553-D260AB0B8C6A}.Debug|Any CPU.Build.0 = Debug|Any CPU + {24CBBC97-409E-BAC8-0553-D260AB0B8C6A}.Release|Any CPU.ActiveCfg = Release|Any CPU + {24CBBC97-409E-BAC8-0553-D260AB0B8C6A}.Release|Any CPU.Build.0 = Release|Any CPU + {9FFDECAB-94D6-7EB1-1A5B-487492600755}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {9FFDECAB-94D6-7EB1-1A5B-487492600755}.Debug|Any CPU.Build.0 = Debug|Any CPU + {9FFDECAB-94D6-7EB1-1A5B-487492600755}.Release|Any CPU.ActiveCfg = Release|Any CPU + {9FFDECAB-94D6-7EB1-1A5B-487492600755}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -240,6 +258,9 @@ Global {58BD3C7A-087C-4ECB-87C6-A3445025BEF5} = {A960D001-40B3-4B1A-A890-D1049FB7586E} {AF5B5402-7C9A-4E5E-B0C2-9278BAE0435C} = {A960D001-40B3-4B1A-A890-D1049FB7586E} {1A313326-988F-4FF2-992E-6D7E50DCF598} = {0BA39F07-84D6-420B-82D3-6DC3AF016C65} + {4FCCBD72-8E9B-D74B-6538-D8684D181230} = {3D822DC2-ED2E-4434-BC4F-CE7FCD846B02} + {24CBBC97-409E-BAC8-0553-D260AB0B8C6A} = {3D822DC2-ED2E-4434-BC4F-CE7FCD846B02} + {9FFDECAB-94D6-7EB1-1A5B-487492600755} = {A960D001-40B3-4B1A-A890-D1049FB7586E} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {949367A4-5683-4FD3-93F4-A2CEA6EECB21} diff --git a/extensions/src/AWSSDK.Extensions.CborProtocol/AWSSDK.Extensions.CborProtocol.NetFramework.csproj b/extensions/src/AWSSDK.Extensions.CborProtocol/AWSSDK.Extensions.CborProtocol.NetFramework.csproj new file mode 100644 index 000000000000..3bfc21e83660 --- /dev/null +++ b/extensions/src/AWSSDK.Extensions.CborProtocol/AWSSDK.Extensions.CborProtocol.NetFramework.csproj @@ -0,0 +1,45 @@ + + + + net472 + AWSSDK.Extensions.CborProtocol + AWSSDK.Extensions.CborProtocol + false + false + false + false + false + false + false + false + true + true + $(NoWarn);CS1591 + True + + + + + + + + + + ..\..\..\sdk\awssdk.dll.snk + + + + + $(AWSKeyFile) + + + + + + + + + + + + diff --git a/extensions/src/AWSSDK.Extensions.CborProtocol/AWSSDK.Extensions.CborProtocol.NetStandard.csproj b/extensions/src/AWSSDK.Extensions.CborProtocol/AWSSDK.Extensions.CborProtocol.NetStandard.csproj new file mode 100644 index 000000000000..e326b545b9bc --- /dev/null +++ b/extensions/src/AWSSDK.Extensions.CborProtocol/AWSSDK.Extensions.CborProtocol.NetStandard.csproj @@ -0,0 +1,51 @@ + + + + netstandard2.0;netcoreapp3.1;net8.0 + AWSSDK.Extensions.CborProtocol + AWSSDK.Extensions.CborProtocol + false + false + false + false + false + false + false + false + true + true + true + $(NoWarn);CS1591 + True + + + + + + + + IL2026,IL2075 + true + + + + + + ..\..\..\sdk\awssdk.dll.snk + + + + + $(AWSKeyFile) + + + + + + + + + + + + diff --git a/extensions/src/AWSSDK.Extensions.CborProtocol/AWSSDK.Extensions.CborProtocol.nuspec b/extensions/src/AWSSDK.Extensions.CborProtocol/AWSSDK.Extensions.CborProtocol.nuspec new file mode 100644 index 000000000000..e9f9b10e759a --- /dev/null +++ b/extensions/src/AWSSDK.Extensions.CborProtocol/AWSSDK.Extensions.CborProtocol.nuspec @@ -0,0 +1,52 @@ + + + + AWSSDK.Extensions.CborProtocol + AWSSDK - Extensions for Cbor protocol support + 4.0.0.0 + Amazon Web Services + This package contains shared serialization and deserialization logic for services that use Cbor protocol in the AWS SDK for .NET. + en-US + Apache-2.0 + https://github.com/aws/aws-sdk-net/ + AWS Amazon aws-sdk-v4 + images\AWSLogo.png + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/extensions/src/AWSSDK.Extensions.CborProtocol/CborWriterExtensions.cs b/extensions/src/AWSSDK.Extensions.CborProtocol/CborWriterExtensions.cs new file mode 100644 index 000000000000..b799e829142b --- /dev/null +++ b/extensions/src/AWSSDK.Extensions.CborProtocol/CborWriterExtensions.cs @@ -0,0 +1,122 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +using System; +using System.Formats.Cbor; +using Amazon.Util; + +namespace Amazon.Extensions.CborProtocol +{ + public static class CborWriterExtensions + { + /// + /// Writes the DateTime as UnixEpochSeconds which is the only type we support for CBOR. + /// + /// The CBOR writer to use. + /// The DateTime value to write. + public static void WriteDateTime(this CborWriter writer, DateTime value) + { + writer.WriteTag(CborTag.UnixTimeSeconds); + writer.WriteOptimizedNumber(AWSSDKUtils.ConvertToUnixEpochSecondsDouble(value)); + } + + /// + /// Writes a double using the smallest CBOR representation that preserves value and precision. + /// + /// The CBOR writer to use. + /// The double value to write. + public static void WriteOptimizedNumber(this CborWriter writer, double value) + { + if (double.IsNaN(value) || double.IsInfinity(value)) + { + writer.WriteDouble(value); // Write NaN or Infinity as a double. + return; + } + + // If the value is an integer (without fractional part), write it as Int64 or UInt64. + if (value % 1 == 0) + { + if (value >= long.MinValue && value <= long.MaxValue) + { + // If the value fits within the signed 64-bit integer (long) range, + // WriteInt64 serializes it into the smallest CBOR type representation + // that can contain its value without loss of precision. + writer.WriteInt64((long)value); + return; + } + + if (value >= 0 && value <= ulong.MaxValue) + { + // If the value is non-negative and fits within the unsigned 64-bit range, + // WriteUInt64 serializes it into the smallest possible CBOR type representation. + writer.WriteUInt64((ulong)value); + return; + } + } + + // Check if value can safely be represented as float32 + float floatCandidate = (float)value; + if ((double)floatCandidate == value) + { + WriteOptimizedNumber(writer, floatCandidate); + return; + } + + // If none of the above conditions are satisfied, write the value as a double. + writer.WriteDouble(value); + } + + /// + /// Writes a float using the smallest CBOR representation that preserves value and precision. + /// This method uses manual encoding to avoid writing as a half-precision float. + /// + /// The CBOR writer to use. + /// The float value to write. + public static void WriteOptimizedNumber(this CborWriter writer, float value) + { + // If the value is an integer (without fractional part), write it as Int64 or UInt64. + if (value % 1 == 0) + { + if (value >= long.MinValue && value <= long.MaxValue) + { + // If the value fits within the signed 64-bit integer (long) range, + // WriteInt64 serializes it into the smallest CBOR type representation + // that can contain its value without loss of precision. + writer.WriteInt64((long)value); + return; + } + + if (value >= 0 && value <= ulong.MaxValue) + { + // If the value is non-negative and fits within the unsigned 64-bit range, + // WriteUInt64 serializes it into the smallest possible CBOR type representation. + writer.WriteUInt64((ulong)value); + return; + } + } + + // Manual encoding to avoid half-precision floats + var bytes = new byte[5]; + bytes[0] = 0xFA; // CBOR float32 marker + BitConverter.GetBytes(value).CopyTo(bytes, 1); + + // Ensure the bytes are in the correct endian order for CBOR. + if (BitConverter.IsLittleEndian) + Array.Reverse(bytes, 1, 4); + + writer.WriteEncodedValue(bytes); + } + } +} diff --git a/extensions/src/AWSSDK.Extensions.CborProtocol/Directory.Build.props b/extensions/src/AWSSDK.Extensions.CborProtocol/Directory.Build.props new file mode 100644 index 000000000000..7ace368652d0 --- /dev/null +++ b/extensions/src/AWSSDK.Extensions.CborProtocol/Directory.Build.props @@ -0,0 +1,6 @@ + + + + $(MSBuildProjectDirectory)\obj\$(MSBuildProjectName) + + \ No newline at end of file diff --git a/extensions/src/AWSSDK.Extensions.CborProtocol/Internal/CborStreamReader.cs b/extensions/src/AWSSDK.Extensions.CborProtocol/Internal/CborStreamReader.cs new file mode 100644 index 000000000000..2bf22a499507 --- /dev/null +++ b/extensions/src/AWSSDK.Extensions.CborProtocol/Internal/CborStreamReader.cs @@ -0,0 +1,295 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ +using Amazon.Runtime.Internal.Util; +using System; +using System.Buffers; +using System.Collections.Generic; +using System.Formats.Cbor; +using System.IO; + +namespace Amazon.Extensions.CborProtocol.Internal +{ + /// + /// A streaming CBOR reader that processes large CBOR data streams in chunks without loading + /// the entire payload into memory at once. This class wraps + /// to provide streaming capabilities while maintaining the same reading interface. + /// + public class CborStreamReader : IDisposable + { + /// + /// Enum to track the type of CBOR container (map or array) + /// for state management within the CborStreamReader. + /// + private enum CborContainerType + { + Map, + Array + } + + private static readonly ILogger _logger = Logger.GetLogger(typeof(CborStreamReader)); + private readonly Stack _nestingStack = new Stack(); + private readonly Stream _stream; + private byte[] _buffer; + private CborReader _internalCborReader; + private int _currentChunkSize; + + /// + /// Initializes a new instance of the class that reads CBOR data + /// from the specified stream. + /// + /// The input stream containing CBOR-formatted data. + /// The stream must be readable and remain open for the lifetime of the reader. + /// Thrown when the stream parameter is null. + /// + /// The reader uses a configurable initial buffer size (from ) + /// and will automatically resize the buffer if needed to handle larger CBOR items. + /// + public CborStreamReader(Stream stream) + { + _stream = stream ?? throw new ArgumentNullException(nameof(stream)); + _buffer = ArrayPool.Shared.Rent(AWSConfigs.CborReaderInitialBufferSize); + + _currentChunkSize = _stream.Read(_buffer, 0, _buffer.Length); + var memorySlice = new ReadOnlyMemory(_buffer, 0, _currentChunkSize); + + // We must allow multiple root values because when refilling the new chunk is just a fragment of the whole stream. + _internalCborReader = new CborReader(memorySlice, allowMultipleRootLevelValues: true); + } + + /// + /// This method is called when a read operation fails because it needs more + /// data than is currently available in the buffer. It handles stitching leftover + /// data with a new chunk from the stream and, if necessary, resizing the buffer. + /// + /// Number of bytes to skip before reading new data (e.g., 1 to skip CBOR break byte 0xFF) + private void RefillBuffer(int bytesToSkip = 0) + { + int leftoverBytesCount = _internalCborReader.BytesRemaining; + + // Determine where the leftover bytes start + int leftoverStartIndex = _currentChunkSize - leftoverBytesCount; + + // If we are skipping bytes, we need to move the start forward + leftoverStartIndex += bytesToSkip; + leftoverBytesCount = leftoverBytesCount - bytesToSkip; + + // If the leftover data completely fills the buffer, grow it + if (leftoverBytesCount >= _buffer.Length) + { + int newSize = _buffer.Length * 2; + var newBuffer = ArrayPool.Shared.Rent(newSize); + + Buffer.BlockCopy(_buffer, leftoverStartIndex, newBuffer, 0, leftoverBytesCount); + ArrayPool.Shared.Return(_buffer); + _buffer = newBuffer; + } + else if (leftoverBytesCount > 0) // Shift leftovers (after skipping) to the beginning of the buffer + { + Buffer.BlockCopy(_buffer, leftoverStartIndex, _buffer, 0, leftoverBytesCount); + } + + // Read from stream into buffer after leftovers + int bytesReadFromStream = _stream.Read(_buffer, leftoverBytesCount, _buffer.Length - leftoverBytesCount); + + // Update the total size of valid data in our buffer. + _currentChunkSize = leftoverBytesCount + bytesReadFromStream; + + // Check for a malformed stream: if we have leftovers but the stream is empty, + // it means the CBOR data was truncated. + if (bytesReadFromStream == 0 && leftoverBytesCount > 0) + { + throw new CborContentException("Stream ended unexpectedly with an incomplete CBOR data item."); + } + + var newMemorySlice = new ReadOnlyMemory(_buffer, 0, _currentChunkSize); + _internalCborReader.Reset(newMemorySlice); + + _logger.DebugFormat("Buffer refilled: read {0} byte(s), total in buffer now: {1}.", bytesReadFromStream, _currentChunkSize); + } + + /// + /// Executes a CBOR read operation, refilling the buffer and retrying if a CborContentException is thrown. + /// + /// The return type of the CBOR read operation. + /// A delegate representing the read operation to execute. + /// The result of the read operation. + /// + /// Thrown if too many retries are attempted or if the stream ends unexpectedly. + /// + private T ExecuteRead(Func readOperation) + { + int maxRetries = 64; + int retryCount = 0; + + while (true) + { + try + { + return readOperation(_internalCborReader); + } + catch (CborContentException ex) + { + if (_currentChunkSize == 0 && _internalCborReader.BytesRemaining == 0) + { + // Fail fast if we’ve already consumed all input and nothing remains to refill. + throw; + } + + if (++retryCount > maxRetries) + { + throw new CborContentException("Too many retries during CBOR stream parsing. Possible malformed or infinite data.", ex); + } + + _logger.Debug(ex, "CborContentException caught (attempt #{0}), attempting to refill buffer.", retryCount); + // Attempt to refill and retry the operation. + RefillBuffer(); + } + } + } + + + private bool IsNextByteEndOfContainer() + { + int unreadOffset = _currentChunkSize - _internalCborReader.BytesRemaining; + if (unreadOffset < _currentChunkSize) + return _buffer[unreadOffset] == 0xFF; // 0xFF indicates "break" in indefinite-length map/array + + return false; + } + + private void ReadEndContainer(CborContainerType expectedType, CborReaderState expectedEndState, Action readEndAction) + { + ExecuteRead(r => + { + if (_nestingStack.Count == 0 || _nestingStack.Peek() != expectedType) + throw new CborContentException($"Unexpected end of {expectedType.ToString().ToLowerInvariant()}."); + + var state = CborReaderState.Finished; + try + { + state = r.PeekState(); + } + catch (CborContentException) + { + // This exception is expected in two cases: + // 1. When we've reached the end of the current CBOR buffer chunk and need to refill. + // 2. When the current buffer does not contain the start of the array/map we're trying to end. + // In both cases, the internal CborReader's state will be `Finished` and we will trigger a buffer refill. + // This is not a true error, but logging at Info level can help trace how we arrived at a certain state. + _logger.DebugFormat("CborContentException caught during PeekState while expecting end of {0}", expectedType.ToString().ToLowerInvariant()); + } + + if (state == expectedEndState) + { + readEndAction(r); + _nestingStack.Pop(); + return true; + } + + if (state == CborReaderState.Finished) + { + if (IsNextByteEndOfContainer()) + { + RefillBuffer(1); // Skip the break marker (0xFF) + } + else + { + RefillBuffer(0); // This means we are in a definite-length map/array which doesn't end with 0xFF. + } + _nestingStack.Pop(); + return true; + } + + throw new CborContentException($"Expected end of {expectedType.ToString().ToLowerInvariant()} but could not parse it."); + }); + } + + + public void ReadEndMap() + { + ReadEndContainer(CborContainerType.Map, CborReaderState.EndMap, (reader) => reader.ReadEndMap()); + } + + public void ReadEndArray() + { + ReadEndContainer(CborContainerType.Array, CborReaderState.EndArray, (r) => r.ReadEndArray()); + } + + + public int? ReadStartMap() => ExecuteRead(reader => + { + var result = reader.ReadStartMap(); + _nestingStack.Push(CborContainerType.Map); + return result; + }); + + public int? ReadStartArray() => ExecuteRead(reader => + { + var result = reader.ReadStartArray(); + _nestingStack.Push(CborContainerType.Array); + return result; + }); + + + public CborReaderState PeekState() + { + return ExecuteRead(r => + { + try + { + return r.PeekState(); + } + catch (CborContentException ex) + { + // Translate a Break code to the appropriate container end state + // based on our own nesting stack. + if (_nestingStack.Count > 0) + { + var inferredState = _nestingStack.Peek() == CborContainerType.Map + ? CborReaderState.EndMap + : CborReaderState.EndArray; + + _logger.Debug(ex, "CborContentException during PeekState interpreted as {0} due to nesting stack.", inferredState); + return inferredState; + } + // If our stack is empty, it's a genuine error. + throw; + } + }); + } + + public string ReadTextString() => ExecuteRead(r => r.ReadTextString()); + public int ReadInt32() => ExecuteRead(r => r.ReadInt32()); + public long ReadInt64() => ExecuteRead(r => r.ReadInt64()); + public decimal ReadDecimal() => ExecuteRead(r => r.ReadDecimal()); + public double ReadDouble() => ExecuteRead(r => r.ReadDouble()); + public bool ReadBoolean() => ExecuteRead(r => r.ReadBoolean()); + public float ReadSingle() => ExecuteRead(r => r.ReadSingle()); + public CborTag ReadTag() => ExecuteRead(r => r.ReadTag()); + public byte[] ReadByteString() => ExecuteRead(r => r.ReadByteString()); + public void ReadNull() => ExecuteRead(r => { r.ReadNull(); return true; }); + public void SkipValue() => ExecuteRead(r => { r.SkipValue(); return true; }); + public int CurrentDepth => _internalCborReader.CurrentDepth; + + public void Dispose() + { + if (_buffer != null) + { + ArrayPool.Shared.Return(_buffer); + _buffer = null; + } + } + } +} diff --git a/extensions/src/AWSSDK.Extensions.CborProtocol/Internal/CborWriterPool.cs b/extensions/src/AWSSDK.Extensions.CborProtocol/Internal/CborWriterPool.cs new file mode 100644 index 000000000000..27b3862c6bfd --- /dev/null +++ b/extensions/src/AWSSDK.Extensions.CborProtocol/Internal/CborWriterPool.cs @@ -0,0 +1,66 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ +using System; +using System.Collections.Concurrent; +using System.Formats.Cbor; +using System.Threading; + +namespace Amazon.Extensions.CborProtocol.Internal +{ + public static class CborWriterPool + { + // Internal pool storage using thread-safe collection + private static readonly ConcurrentBag _pool = new ConcurrentBag(); + + // Maximum number of CborWriter instances the pool can hold + private static int _maxPoolSize = 16; + + /// + /// Gets or sets the maximum size of the writer pool. + /// Minimum value is 1. + /// + public static int MaxPoolSize + { + get => Volatile.Read(ref _maxPoolSize); + set => Volatile.Write(ref _maxPoolSize, Math.Max(1, value)); + } + + /// + /// Retrieves a CborWriter from the pool, or creates a new one if the pool is empty. + /// + public static CborWriter Rent() + { + if (_pool.TryTake(out var writer)) + { + return writer; + } + // Create a new CborWriter if the pool is empty + return new CborWriter(CborConformanceMode.Canonical, true); + } + + /// + /// Returns a CborWriter to the pool for reuse. + /// If the pool is already full then the writer will be discard. + /// + public static void Return(CborWriter writer) + { + if (_pool.Count >= Volatile.Read(ref _maxPoolSize)) + return; + + writer.Reset(); // Ensure the writer is in a clean state before pooling + _pool.Add(writer); + } + } +} diff --git a/extensions/src/AWSSDK.Extensions.CborProtocol/Internal/EventStreams/CborEventStreamPublisher.cs b/extensions/src/AWSSDK.Extensions.CborProtocol/Internal/EventStreams/CborEventStreamPublisher.cs new file mode 100644 index 000000000000..677e72fbf271 --- /dev/null +++ b/extensions/src/AWSSDK.Extensions.CborProtocol/Internal/EventStreams/CborEventStreamPublisher.cs @@ -0,0 +1,41 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +using System.Formats.Cbor; +using Amazon.Extensions.CborProtocol.Internal.Transform; +using Amazon.Runtime.EventStreams; +using Amazon.Runtime.Internal; + +namespace Amazon.Extensions.CborProtocol.Internal.EventStreams +{ + /// + /// Base class for cbor event stream publishers. + /// + public abstract class CborEventStreamPublisher : EventStreamPublisher + { + /// + /// Construct a CborMarshallerContext that subclasses can use to run the marshaller for the event type + /// that should be sent. + /// + /// The writer that the marshaller will use to write as it is marshalling the user's object into it's CBOR representation. + /// + protected static CborMarshallerContext CreateCborMarshallerContext(CborWriter writer) + { + // The original request and service are not needed for event serialization so placeholder values are used. + IRequest request = new DefaultRequest(new EventStreamRequest(), "eventstream"); + return new CborMarshallerContext(request, writer); + } + } +} diff --git a/extensions/src/AWSSDK.Extensions.CborProtocol/Internal/Transform/CborErrorResponseUnmarshaller.cs b/extensions/src/AWSSDK.Extensions.CborProtocol/Internal/Transform/CborErrorResponseUnmarshaller.cs new file mode 100644 index 000000000000..06c336c692f3 --- /dev/null +++ b/extensions/src/AWSSDK.Extensions.CborProtocol/Internal/Transform/CborErrorResponseUnmarshaller.cs @@ -0,0 +1,126 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ +using Amazon.Runtime; +using Amazon.Runtime.Internal; +using Amazon.Runtime.Internal.Transform; +using Amazon.Runtime.Internal.Util; +using Amazon.Util; +using System; +using System.Collections.Generic; +using System.Formats.Cbor; +using System.IO; +using System.Net; + +namespace Amazon.Extensions.CborProtocol.Internal.Transform +{ + /// + /// Class for unmarshalling CBOR service responses. + /// + public class CborErrorResponseUnmarshaller : ICborUnmarshaller + { + /// + /// Build an ErrorResponse from Cbor + /// + /// The Cbor parsing context. + /// Usually an Amazon.Runtime.Internal.CborUnmarshallerContext. + /// An ErrorResponse object. + public ErrorResponse Unmarshall(CborUnmarshallerContext context) + { + var errorType = ErrorType.Unknown; + + if (context.ResponseData.StatusCode == HttpStatusCode.BadRequest) + { + errorType = ErrorType.Sender; + } + else if (context.ResponseData.StatusCode == HttpStatusCode.InternalServerError) + { + errorType = ErrorType.Receiver; + } + + var response = new ErrorResponse + { + Type = errorType, + StatusCode = context.ResponseData.StatusCode, + }; + + var reader = context.Reader; + reader.ReadStartMap(); + while (reader.PeekState() != CborReaderState.EndMap) + { + string propertyName = reader.ReadTextString().ToLowerInvariant(); + switch (propertyName) + { + case "__type": + { + context.AddPathSegment("__type"); + var unmarshaller = CborStringUnmarshaller.Instance; + var type = unmarshaller.Unmarshall(context); + response.Code = SanitizeErrorType(type); + context.PopPathSegment(); + break; + } + case "message": + { + context.AddPathSegment("message"); + var unmarshaller = CborStringUnmarshaller.Instance; + response.Message = unmarshaller.Unmarshall(context); + context.PopPathSegment(); + break; + } + default: + reader.SkipValue(); + break; + } + } + + if (context.ResponseData.IsHeaderPresent(HeaderKeys.RequestIdHeader)) + { + response.RequestId = context.ResponseData.GetHeaderValue(HeaderKeys.RequestIdHeader); + } + + return response; + } + + /// + /// Extracts the error type from a Smithy shape identifier string. + /// The input is expected to be in the format "namespace#ErrorType[:additionalInfo]". + /// Returns the error type portion (e.g., "ErrorType"). + /// + private string SanitizeErrorType(string type) + { + int start = type.IndexOf('#'); + start = start == -1 ? 0 : start + 1; + + int end = type.IndexOf(':', start); + end = end == -1 ? type.Length : end; + + return type.Substring(start, end - start).Trim(); + } + + private static CborErrorResponseUnmarshaller instance; + + /// + /// Return an instance of CborErrorResponseUnmarshaller. + /// + /// + public static CborErrorResponseUnmarshaller GetInstance() + { + if (instance == null) + instance = new CborErrorResponseUnmarshaller(); + + return instance; + } + } +} diff --git a/extensions/src/AWSSDK.Extensions.CborProtocol/Internal/Transform/CborMarshallerContext.cs b/extensions/src/AWSSDK.Extensions.CborProtocol/Internal/Transform/CborMarshallerContext.cs new file mode 100644 index 000000000000..351ecd75d68c --- /dev/null +++ b/extensions/src/AWSSDK.Extensions.CborProtocol/Internal/Transform/CborMarshallerContext.cs @@ -0,0 +1,38 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +using Amazon.Runtime; +using Amazon.Runtime.Internal; +using Amazon.Runtime.Internal.Transform; +using Amazon.Runtime.Internal.Util; +using Amazon.Util; +using System; +using System.Collections.Generic; +using System.Formats.Cbor; +using System.IO; + +namespace Amazon.Extensions.CborProtocol.Internal.Transform +{ + public class CborMarshallerContext : MarshallerContext + { + public CborWriter Writer { get; private set; } + + public CborMarshallerContext(IRequest request, CborWriter writer) + : base(request) + { + Writer = writer; + } + } +} diff --git a/extensions/src/AWSSDK.Extensions.CborProtocol/Internal/Transform/CborResponseUnmarshaller.cs b/extensions/src/AWSSDK.Extensions.CborProtocol/Internal/Transform/CborResponseUnmarshaller.cs new file mode 100644 index 000000000000..08197d5ed5a2 --- /dev/null +++ b/extensions/src/AWSSDK.Extensions.CborProtocol/Internal/Transform/CborResponseUnmarshaller.cs @@ -0,0 +1,75 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +using Amazon.Runtime; +using Amazon.Runtime.Internal; +using Amazon.Runtime.Internal.Transform; +using Amazon.Runtime.Internal.Util; +using Amazon.Util; +using System; +using System.Collections.Generic; +using System.Formats.Cbor; +using System.IO; +using System.Net; + +namespace Amazon.Extensions.CborProtocol.Internal.Transform +{ + public abstract class CborResponseUnmarshaller : ResponseUnmarshaller + { + public override AmazonWebServiceResponse Unmarshall(UnmarshallerContext input) + { + CborUnmarshallerContext context = input as CborUnmarshallerContext; + if (context == null) + throw new InvalidOperationException("Unsupported UnmarshallerContext"); + + string requestId = context.ResponseData.GetHeaderValue(HeaderKeys.RequestIdHeader); + try + { + var response = Unmarshall(context); + response.ResponseMetadata = new ResponseMetadata(); + response.ResponseMetadata.RequestId = requestId; + return response; + } + catch (Exception e) + { + throw new AmazonUnmarshallingException(requestId, context.CurrentPath, e, context.ResponseData.StatusCode); + } + } + + public override AmazonServiceException UnmarshallException(UnmarshallerContext input, Exception innerException, HttpStatusCode statusCode) + { + CborUnmarshallerContext context = input as CborUnmarshallerContext; + if (context == null) + throw new InvalidOperationException("Unsupported UnmarshallerContext"); + var responseException = UnmarshallException(context, innerException, statusCode); + responseException.RequestId = context.ResponseData.GetHeaderValue(HeaderKeys.RequestIdHeader); + return responseException; + } + + public abstract AmazonWebServiceResponse Unmarshall(CborUnmarshallerContext input); + + public abstract AmazonServiceException UnmarshallException(CborUnmarshallerContext input, Exception innerException, HttpStatusCode statusCode); + + protected override UnmarshallerContext ConstructUnmarshallerContext(Stream responseStream, bool maintainResponseBody, IWebResponseData response, bool isException) + { + return new CborUnmarshallerContext(responseStream, maintainResponseBody, response, isException, null); + } + + protected override UnmarshallerContext ConstructUnmarshallerContext(Stream responseStream, bool maintainResponseBody, IWebResponseData response, bool isException, IRequestContext requestContext) + { + return new CborUnmarshallerContext(responseStream, maintainResponseBody, response, isException, requestContext); + } + } +} diff --git a/extensions/src/AWSSDK.Extensions.CborProtocol/Internal/Transform/CborSimpleTypeUnmarshaller.cs b/extensions/src/AWSSDK.Extensions.CborProtocol/Internal/Transform/CborSimpleTypeUnmarshaller.cs new file mode 100644 index 000000000000..72e9e2f80759 --- /dev/null +++ b/extensions/src/AWSSDK.Extensions.CborProtocol/Internal/Transform/CborSimpleTypeUnmarshaller.cs @@ -0,0 +1,587 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +using System; +using System.Collections.Generic; +using System.Formats.Cbor; +using System.Globalization; +using System.IO; +using Amazon; +using Amazon.Runtime; +using Amazon.Runtime.Internal.Util; +using Amazon.Util; + +namespace Amazon.Extensions.CborProtocol.Internal.Transform +{ + static class CborSimpleTypeUnmarshaller + { + public static T Unmarshall(CborUnmarshallerContext context) + { + var reader = context.Reader; + object value; + + if ( + ( + typeof(T) == typeof(int?) || + typeof(T) == typeof(long?) || + typeof(T) == typeof(decimal?) || + typeof(T) == typeof(double?) || + typeof(T) == typeof(bool?) || + typeof(T) == typeof(float?) || + typeof(T) == typeof(string) + ) + && reader.PeekState() == CborReaderState.Null + ) + { + reader.ReadNull(); + value = default(T); + } + else if (typeof(T) == typeof(string)) + value = reader.ReadTextString(); + else if (typeof(T) == typeof(int) || typeof(T) == typeof(int?)) + value = reader.ReadInt32(); + else if (typeof(T) == typeof(long) || typeof(T) == typeof(long?)) + value = reader.ReadInt64(); + else if (typeof(T) == typeof(decimal) || typeof(T) == typeof(decimal?)) + value = reader.ReadDecimal(); + else if (typeof(T) == typeof(double) || typeof(T) == typeof(double?)) + value = reader.ReadDouble(); + else if (typeof(T) == typeof(bool) || typeof(T) == typeof(bool?)) + value = reader.ReadBoolean(); + else if (typeof(T) == typeof(float) || typeof(T) == typeof(float?)) + value = reader.ReadSingle(); + else if (typeof(T) == typeof(byte)) + { + int result = reader.ReadInt32(); + if (result > byte.MaxValue) + { + throw new OverflowException($"{result} value exceeds byte range."); + } + value = (byte)result; + } + else + throw new NotSupportedException( + $"CBOR deserialization for type {typeof(T)} is not supported." + ); + + return (T)value; + } + } + + public class CborIntUnmarshaller : ICborUnmarshaller + { + private CborIntUnmarshaller() { } + + private static CborIntUnmarshaller _instance = new CborIntUnmarshaller(); + + public static CborIntUnmarshaller Instance + { + get { return _instance; } + } + + public int Unmarshall(CborUnmarshallerContext context) + { + return CborSimpleTypeUnmarshaller.Unmarshall(context); + } + } + + public class CborNullableIntUnmarshaller : ICborUnmarshaller + { + private CborNullableIntUnmarshaller() { } + + private static CborNullableIntUnmarshaller _instance = new CborNullableIntUnmarshaller(); + + public static CborNullableIntUnmarshaller Instance + { + get { return _instance; } + } + + public int? Unmarshall(CborUnmarshallerContext context) + { + return CborSimpleTypeUnmarshaller.Unmarshall(context); + } + } + + public class CborLongUnmarshaller : ICborUnmarshaller + { + private CborLongUnmarshaller() { } + + private static CborLongUnmarshaller _instance = new CborLongUnmarshaller(); + + public static CborLongUnmarshaller Instance + { + get { return _instance; } + } + + public long Unmarshall(CborUnmarshallerContext context) + { + return CborSimpleTypeUnmarshaller.Unmarshall(context); + } + } + + public class CborNullableLongUnmarshaller : ICborUnmarshaller + { + private CborNullableLongUnmarshaller() { } + + private static CborNullableLongUnmarshaller _instance = new CborNullableLongUnmarshaller(); + + public static CborNullableLongUnmarshaller Instance + { + get { return _instance; } + } + + public long? Unmarshall(CborUnmarshallerContext context) + { + return CborSimpleTypeUnmarshaller.Unmarshall(context); + } + } + + public class CborFloatUnmarshaller : ICborUnmarshaller + { + private CborFloatUnmarshaller() { } + + private static CborFloatUnmarshaller _instance = new CborFloatUnmarshaller(); + + public static CborFloatUnmarshaller Instance + { + get { return _instance; } + } + + public float Unmarshall(CborUnmarshallerContext context) + { + return CborSimpleTypeUnmarshaller.Unmarshall(context); + } + } + + public class CborNullableFloatUnmarshaller : ICborUnmarshaller + { + private CborNullableFloatUnmarshaller() { } + + private static CborNullableFloatUnmarshaller _instance = + new CborNullableFloatUnmarshaller(); + + public static CborNullableFloatUnmarshaller Instance + { + get { return _instance; } + } + + public float? Unmarshall(CborUnmarshallerContext context) + { + return CborSimpleTypeUnmarshaller.Unmarshall(context); + } + } + + public class CborDoubleUnmarshaller : ICborUnmarshaller + { + private CborDoubleUnmarshaller() { } + + private static CborDoubleUnmarshaller _instance = new CborDoubleUnmarshaller(); + + public static CborDoubleUnmarshaller Instance + { + get { return _instance; } + } + + public double Unmarshall(CborUnmarshallerContext context) + { + return CborSimpleTypeUnmarshaller.Unmarshall(context); + } + } + + public class CborNullableDoubleUnmarshaller + : ICborUnmarshaller + { + private CborNullableDoubleUnmarshaller() { } + + private static CborNullableDoubleUnmarshaller _instance = + new CborNullableDoubleUnmarshaller(); + + public static CborNullableDoubleUnmarshaller Instance + { + get { return _instance; } + } + + public double? Unmarshall(CborUnmarshallerContext context) + { + return CborSimpleTypeUnmarshaller.Unmarshall(context); + } + } + + public class CborDecimalUnmarshaller : ICborUnmarshaller + { + private CborDecimalUnmarshaller() { } + + private static CborDecimalUnmarshaller _instance = new CborDecimalUnmarshaller(); + + public static CborDecimalUnmarshaller Instance + { + get { return _instance; } + } + + public decimal Unmarshall(CborUnmarshallerContext context) + { + return CborSimpleTypeUnmarshaller.Unmarshall(context); + } + } + + public class CborNullableDecimalUnmarshaller + : ICborUnmarshaller + { + private CborNullableDecimalUnmarshaller() { } + + private static CborNullableDecimalUnmarshaller _instance = + new CborNullableDecimalUnmarshaller(); + + public static CborNullableDecimalUnmarshaller Instance + { + get { return _instance; } + } + + public decimal? Unmarshall(CborUnmarshallerContext context) + { + return CborSimpleTypeUnmarshaller.Unmarshall(context); + } + } + + /// + /// Unmarshaller for bool fields + /// + public class CborBoolUnmarshaller : ICborUnmarshaller + { + private CborBoolUnmarshaller() { } + + private static CborBoolUnmarshaller _instance = new CborBoolUnmarshaller(); + + public static CborBoolUnmarshaller Instance + { + get { return _instance; } + } + + public bool Unmarshall(CborUnmarshallerContext context) + { + return CborSimpleTypeUnmarshaller.Unmarshall(context); + } + } + + /// + /// Unmarshaller for bool fields + /// + public class CborNullableBoolUnmarshaller : ICborUnmarshaller + { + private CborNullableBoolUnmarshaller() { } + + private static CborNullableBoolUnmarshaller _instance = new CborNullableBoolUnmarshaller(); + + public static CborNullableBoolUnmarshaller Instance + { + get { return _instance; } + } + + public bool? Unmarshall(CborUnmarshallerContext context) + { + return CborSimpleTypeUnmarshaller.Unmarshall(context); + } + } + + /// + /// Unmarshaller for string fields + /// + public class CborStringUnmarshaller : ICborUnmarshaller + { + private CborStringUnmarshaller() { } + + private static CborStringUnmarshaller _instance = new CborStringUnmarshaller(); + + public static CborStringUnmarshaller Instance + { + get { return _instance; } + } + + public string Unmarshall(CborUnmarshallerContext context) + { + return CborSimpleTypeUnmarshaller.Unmarshall(context); + } + } + + public class CborByteUnmarshaller : ICborUnmarshaller + { + private CborByteUnmarshaller() { } + + private static CborByteUnmarshaller _instance = new CborByteUnmarshaller(); + + public static CborByteUnmarshaller Instance + { + get { return _instance; } + } + + public byte Unmarshall(CborUnmarshallerContext context) + { + return CborSimpleTypeUnmarshaller.Unmarshall(context); + } + } + + public class CborDateTimeUnmarshaller : ICborUnmarshaller + { + private CborDateTimeUnmarshaller() { } + + private static CborDateTimeUnmarshaller _instance = new CborDateTimeUnmarshaller(); + + public static CborDateTimeUnmarshaller Instance + { + get { return _instance; } + } + + public DateTime Unmarshall(CborUnmarshallerContext context) + { + var tag = context.Reader.ReadTag(); + if (tag == CborTag.UnixTimeSeconds) + { + var epochSeconds = CborSimpleTypeUnmarshaller.Unmarshall(context); + return AWSSDKUtils.ConvertFromUnixDoubleEpochSeconds(epochSeconds); + } + throw new NotSupportedException($"Unsupported CBOR tag: {tag}"); + } + } + + public class CborNullableDateTimeUnmarshaller + : ICborUnmarshaller + { + private CborNullableDateTimeUnmarshaller() { } + + private static CborNullableDateTimeUnmarshaller _instance = + new CborNullableDateTimeUnmarshaller(); + + public static CborNullableDateTimeUnmarshaller Instance + { + get { return _instance; } + } + + public DateTime? Unmarshall(CborUnmarshallerContext context) + { + if (context.Reader.PeekState() == CborReaderState.Null) + { + context.Reader.ReadNull(); + return null; + } + return CborDateTimeUnmarshaller.Instance.Unmarshall(context); + } + } + + public class CborNullableDateTimeEpochLongMillisecondsUnmarshaller + : ICborUnmarshaller + { + + private CborNullableDateTimeEpochLongMillisecondsUnmarshaller() { } + + private static CborNullableDateTimeEpochLongMillisecondsUnmarshaller _instance = + new CborNullableDateTimeEpochLongMillisecondsUnmarshaller(); + + public static CborNullableDateTimeEpochLongMillisecondsUnmarshaller Instance + { + get { return _instance; } + } + + public DateTime? Unmarshall(CborUnmarshallerContext context) + { + if (context.Reader.PeekState() == CborReaderState.Null) + { + context.Reader.ReadNull(); + return null; + } + var millseconds = CborLongUnmarshaller.Instance.Unmarshall(context); + var ret = Amazon.Util.AWSSDKUtils.EPOCH_START.AddMilliseconds(millseconds); + return ret; + } + } + + public class CborMemoryStreamUnmarshaller + : ICborUnmarshaller + { + private CborMemoryStreamUnmarshaller() { } + + private static CborMemoryStreamUnmarshaller _instance = new CborMemoryStreamUnmarshaller(); + + public static CborMemoryStreamUnmarshaller Instance + { + get { return _instance; } + } + + public MemoryStream Unmarshall(CborUnmarshallerContext context) + { + if (context.Reader.PeekState() == CborReaderState.Null) + { + context.Reader.ReadNull(); + return null; + } + + var bytes = context.Reader.ReadByteString(); + MemoryStream stream = new MemoryStream(bytes, 0, bytes.Length, true, true); + return stream; + } + } + + /// + /// Unmarshaller for ResponseMetadata + /// + public class CborResponseMetadataUnmarshaller + : ICborUnmarshaller + { + private CborResponseMetadataUnmarshaller() { } + + private static CborResponseMetadataUnmarshaller _instance = + new CborResponseMetadataUnmarshaller(); + + public static CborResponseMetadataUnmarshaller Instance + { + get { return _instance; } + } + + public ResponseMetadata Unmarshall(CborUnmarshallerContext context) + { + var metadata = new ResponseMetadata(); + var reader = context.Reader; + reader.ReadStartMap(); + + while (reader.PeekState() != CborReaderState.EndMap) + { + string propertyName = reader.ReadTextString(); + + switch (propertyName) + { + case "RequestId": + metadata.RequestId = CborStringUnmarshaller.Instance.Unmarshall(context); + break; + + default: + reader.SkipValue(); + break; + } + } + + reader.ReadEndMap(); + return metadata; + } + } + + public class CborKeyValueUnmarshaller + : ICborUnmarshaller, CborUnmarshallerContext> + where KUnmarshaller : ICborUnmarshaller + where VUnmarshaller : ICborUnmarshaller + { + private readonly KUnmarshaller keyUnmarshaller; + private readonly VUnmarshaller valueUnmarshaller; + + public CborKeyValueUnmarshaller( + KUnmarshaller keyUnmarshaller, + VUnmarshaller valueUnmarshaller + ) + { + this.keyUnmarshaller = keyUnmarshaller; + this.valueUnmarshaller = valueUnmarshaller; + } + + public KeyValuePair Unmarshall(CborUnmarshallerContext context) + { + var key = keyUnmarshaller.Unmarshall(context); + var value = valueUnmarshaller.Unmarshall(context); + return new KeyValuePair(key, value); + } + } + + public class CborListUnmarshaller + : ICborUnmarshaller, CborUnmarshallerContext> + where TUnmarshaller : ICborUnmarshaller + { + private readonly TUnmarshaller itemUnmarshaller; + + public CborListUnmarshaller(TUnmarshaller itemUnmarshaller) + { + this.itemUnmarshaller = itemUnmarshaller; + } + + public List Unmarshall(CborUnmarshallerContext context) + { + var reader = context.Reader; + + if (reader.PeekState() == CborReaderState.Null) + { + reader.ReadNull(); + return AWSConfigs.InitializeCollections ? new List() : null; + } + + reader.ReadStartArray(); + var list = new AlwaysSendList(); + + while (reader.PeekState() != CborReaderState.EndArray) + { + list.Add(itemUnmarshaller.Unmarshall(context)); + } + + reader.ReadEndArray(); + return list; + } + } + + public class CborDictionaryUnmarshaller + : ICborUnmarshaller, CborUnmarshallerContext> + where TKeyUnmarshaller : ICborUnmarshaller + where TValueUnmarshaller : ICborUnmarshaller + { + private readonly CborKeyValueUnmarshaller< + TKey, + TValue, + TKeyUnmarshaller, + TValueUnmarshaller + > kvUnmarshaller; + + public CborDictionaryUnmarshaller( + TKeyUnmarshaller keyUnmarshaller, + TValueUnmarshaller valueUnmarshaller + ) + { + kvUnmarshaller = new CborKeyValueUnmarshaller< + TKey, + TValue, + TKeyUnmarshaller, + TValueUnmarshaller + >(keyUnmarshaller, valueUnmarshaller); + } + + public Dictionary Unmarshall(CborUnmarshallerContext context) + { + var reader = context.Reader; + + if (reader.PeekState() == CborReaderState.Null) + { + reader.ReadNull(); + return AWSConfigs.InitializeCollections ? new Dictionary() : null; + } + + reader.ReadStartMap(); + var dictionary = new AlwaysSendDictionary(); + + while (reader.PeekState() != CborReaderState.EndMap) + { + var pair = kvUnmarshaller.Unmarshall(context); + + if (pair.Value != null) + dictionary.Add(pair.Key, pair.Value); + } + + reader.ReadEndMap(); + return dictionary; + } + } +} diff --git a/extensions/src/AWSSDK.Extensions.CborProtocol/Internal/Transform/CborUnmarshallerContext.cs b/extensions/src/AWSSDK.Extensions.CborProtocol/Internal/Transform/CborUnmarshallerContext.cs new file mode 100644 index 000000000000..fff36702e5f9 --- /dev/null +++ b/extensions/src/AWSSDK.Extensions.CborProtocol/Internal/Transform/CborUnmarshallerContext.cs @@ -0,0 +1,150 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +using System; +using System.Buffers; +using System.Collections.Generic; +using System.Formats.Cbor; +using System.IO; +using System.Linq; +using Amazon; +using Amazon.Runtime; +using Amazon.Runtime.Internal.Transform; +using Amazon.Runtime.Internal.Util; + +namespace Amazon.Extensions.CborProtocol.Internal.Transform +{ + public class CborUnmarshallerContext : UnmarshallerContext + { + private bool disposed = false; + private readonly Stack _pathStack = new Stack(); + + // There isn't a direct way to check if the reader is at the start of the document, + // and we don't need it for the unmarshalling logic anyway. + public override bool IsStartOfDocument => throw new NotImplementedException(); + + public override bool IsEndElement => Reader.PeekState() == CborReaderState.Finished; + public override bool IsStartElement => Reader.PeekState() == CborReaderState.StartMap; + + public Stream Stream { get; set; } + public CborStreamReader Reader { get; set; } + public override string CurrentPath => string.Join("/", _pathStack.Reverse()); + public override int CurrentDepth => Reader.CurrentDepth; + + public CborUnmarshallerContext( + Stream responseStream, + bool maintainResponseBody, + IWebResponseData responseData, + bool isException = false + ) + : this(responseStream, maintainResponseBody, responseData, isException, null) { } + + public CborUnmarshallerContext( + Stream responseStream, + bool maintainResponseBody, + IWebResponseData responseData, + bool isException, + IRequestContext requestContext + ) + { + if (isException) + { + WrappingStream = new CachingWrapperStream(responseStream); + } + else if (maintainResponseBody) + { + WrappingStream = new CachingWrapperStream( + responseStream, + AWSConfigs.LoggingConfig.LogResponsesSizeLimit + ); + } + + if (isException || maintainResponseBody) + { + responseStream = WrappingStream; + } + + if (responseData != null) + { + bool parsedContentLengthHeader = long.TryParse( + responseData.GetHeaderValue("Content-Length"), + out long contentLength + ); + if (parsedContentLengthHeader && contentLength == 0) + { + IsEmptyResponse = true; + } + + if ( + parsedContentLengthHeader + && responseData.ContentLength == contentLength + && string.IsNullOrEmpty(responseData.GetHeaderValue("Content-Encoding")) + && requestContext?.OriginalRequest?.CoreChecksumMode + == CoreChecksumResponseBehavior.ENABLED + ) + { + base.SetupCRCStream(responseData, responseStream, contentLength); + base.SetupFlexibleChecksumStream( + responseData, + CrcStream ?? responseStream, + contentLength, + requestContext + ); + } + } + + this.WebResponseData = responseData; + this.MaintainResponseBody = maintainResponseBody; + this.IsException = isException; + + Stream = FlexibleChecksumStream ?? CrcStream ?? responseStream; + Reader = new CborStreamReader(Stream); + } + + /// + /// Adds a new segment to the current path stack. + /// Typically used when entering a new object property. + /// + /// The segment name to add + public void AddPathSegment(string segment) + { + _pathStack.Push(segment); + } + + /// + /// Removes the most recent segment from the path stack. + /// Typically used when exiting a property. + /// + /// The segment that was removed. + public string PopPathSegment() + { + return _pathStack.Pop(); + } + + protected override void Dispose(bool disposing) + { + if (!this.disposed) + { + if (disposing && Reader != null) + { + Reader.Dispose(); + Reader = null; + } + disposed = true; + } + base.Dispose(disposing); + } + } +} diff --git a/extensions/src/AWSSDK.Extensions.CborProtocol/Internal/Transform/ICborErrorResponseUnmarshaller.cs b/extensions/src/AWSSDK.Extensions.CborProtocol/Internal/Transform/ICborErrorResponseUnmarshaller.cs new file mode 100644 index 000000000000..7157078d4105 --- /dev/null +++ b/extensions/src/AWSSDK.Extensions.CborProtocol/Internal/Transform/ICborErrorResponseUnmarshaller.cs @@ -0,0 +1,35 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +using Amazon.Runtime.Internal; + +namespace Amazon.Extensions.CborProtocol.Internal.Transform +{ + /// + /// The interface for unmarshalling a cbor error response. + /// + /// The type to unmarshall. + /// The cbor unmarshaller context + public interface ICborErrorResponseUnmarshaller + { + /// + /// The unmarshall method accepts a context, the error response and returns the modeled exception. + /// + /// The CBOR Unmarshaller context + /// The error response + /// T, the error shape that is unmarshalled from the CBOR context + T Unmarshall(TCborUnmarshallerContext context, ErrorResponse errorResponse); + } +} diff --git a/extensions/src/AWSSDK.Extensions.CborProtocol/Internal/Transform/ICborUnmarshaller.cs b/extensions/src/AWSSDK.Extensions.CborProtocol/Internal/Transform/ICborUnmarshaller.cs new file mode 100644 index 000000000000..ff380bfc2567 --- /dev/null +++ b/extensions/src/AWSSDK.Extensions.CborProtocol/Internal/Transform/ICborUnmarshaller.cs @@ -0,0 +1,30 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +namespace Amazon.Extensions.CborProtocol.Internal.Transform +{ + /// + /// Interface for unmarshallers which unmarshall objects from response data. + /// The Unmarshallers are stateless, and only encode the rules for what data + /// in the Cbor stream goes into what members of an object. + /// + /// The type of object the unmarshaller returns + /// The type of the Cbor unmashaller context, + /// which contains the state during parsing of the Cbor stream. + public interface ICborUnmarshaller + { + T Unmarshall(TCborUnmarshallerContext input); + } +} diff --git a/extensions/src/AWSSDK.Extensions.CborProtocol/Properties/AssemblyInfo.cs b/extensions/src/AWSSDK.Extensions.CborProtocol/Properties/AssemblyInfo.cs new file mode 100644 index 000000000000..d9897716be94 --- /dev/null +++ b/extensions/src/AWSSDK.Extensions.CborProtocol/Properties/AssemblyInfo.cs @@ -0,0 +1,19 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +[assembly: AssemblyTitle("AWSSDK.Extensions.CborProtocol")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("Amazon.com, Inc")] +[assembly: AssemblyProduct("AWS SDK for .NET extensions for Cbor protocol support")] +[assembly: AssemblyDescription("AWS SDK for .NET extensions for Cbor protocol support")] +[assembly: AssemblyCopyright("Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.")] +[assembly: AssemblyTrademark("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +[assembly: AssemblyVersion("4.0")] +[assembly: AssemblyFileVersion("4.0.0.0")] diff --git a/extensions/test/CborProtocol.Tests/CborProtocol.Tests.NetFramework.csproj b/extensions/test/CborProtocol.Tests/CborProtocol.Tests.NetFramework.csproj new file mode 100644 index 000000000000..c32accb1f826 --- /dev/null +++ b/extensions/test/CborProtocol.Tests/CborProtocol.Tests.NetFramework.csproj @@ -0,0 +1,30 @@ + + + net472 + CborProtocol.Tests + CborProtocol.Tests + false + false + false + false + false + false + false + false + true + Latest + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + + \ No newline at end of file diff --git a/extensions/test/CborProtocol.Tests/CborStreamReaderTests.cs b/extensions/test/CborProtocol.Tests/CborStreamReaderTests.cs new file mode 100644 index 000000000000..4f2806b5fa18 --- /dev/null +++ b/extensions/test/CborProtocol.Tests/CborStreamReaderTests.cs @@ -0,0 +1,408 @@ +using Amazon.Extensions.CborProtocol; +using Amazon.Extensions.CborProtocol.Internal; +using System; +using System.Formats.Cbor; +using System.IO; +using System.Linq; +using Xunit; + +namespace Amazon.CborProtocol.Tests; + + +public class BufferSizeConfigFixture : IDisposable +{ + private readonly int _originalValue; + + public BufferSizeConfigFixture() + { + // Save original value + _originalValue = AWSConfigs.CborReaderInitialBufferSize; + + // Set a small buffer size to easily test larger streams. + AWSConfigs.CborReaderInitialBufferSize = 100; + } + + public void Dispose() + { + // Restore original value + AWSConfigs.CborReaderInitialBufferSize = _originalValue; + } +} + +public class CborStreamReaderTests : IClassFixture +{ + + [Fact] + public void Unmarshall_SimpleObject_FitsInInitialBuffer() + { + var writer = new CborWriter(); + writer.WriteStartMap(2); + writer.WriteTextString("Key1"); + writer.WriteTextString("Value1"); + writer.WriteTextString("Key2"); + writer.WriteInt32(123); + writer.WriteEndMap(); + + byte[] bytes = writer.Encode(); + var stream = new MemoryStream(bytes); + + using var reader = new CborStreamReader(stream); + + reader.ReadStartMap(); + Assert.Equal("Key1", reader.ReadTextString()); + Assert.Equal("Value1", reader.ReadTextString()); + Assert.Equal("Key2", reader.ReadTextString()); + reader.SkipValue(); // Skip integer + reader.ReadEndMap(); + Assert.Equal(CborReaderState.Finished, reader.PeekState()); + + } + + [Theory] + [InlineData(2)] + [InlineData(4)] + [InlineData(8)] + public void Unmarshall_ItemLargerThanBuffer_ResizesMultipleTimes(int multiplier) + { + // Create a string that will force multiple buffer resizes. + int stringSize = (AWSConfigs.CborReaderInitialBufferSize * multiplier) + 50; + var veryLargeString = new string('B', stringSize); + + var writer = new CborWriter(); + writer.WriteTextString(veryLargeString); + byte[] bytes = writer.Encode(); + var stream = new MemoryStream(bytes); + + using var reader = new CborStreamReader(stream); + + string result = reader.ReadTextString(); + Assert.Equal(veryLargeString, result); + + } + + [Fact] + public void Unmarshall_ObjectCrossesBufferBoundary_RefillsCorrectly() + { + var writer = new CborWriter(); + writer.WriteStartMap(null); + writer.WriteTextString("key1"); + writer.WriteTextString(new string('A', 90)); + writer.WriteTextString("key2"); + writer.WriteStartMap(null); + writer.WriteTextString("key3"); + writer.WriteTextString("value2"); + writer.WriteEndMap(); + writer.WriteEndMap(); + + byte[] bytes = writer.Encode(); + var stream = new MemoryStream(bytes); + + using var reader = new CborStreamReader(stream); + + reader.ReadStartMap(); + Assert.Equal("key1", reader.ReadTextString()); + string value1 = reader.ReadTextString(); // This read will consume most of the first chunk. + Assert.Equal(90, value1.Length); + Assert.Equal("key2", reader.ReadTextString()); + reader.ReadStartMap(); + + Assert.Equal("key3", reader.ReadTextString()); + Assert.Equal("value2", reader.ReadTextString()); + reader.ReadEndMap(); + reader.ReadEndMap(); + + } + + [Fact] + public void SkipValue_OnLargeNestedMap_Succeeds() + { + var writer = new CborWriter(); + writer.WriteStartArray(2); + + // Start the large nested map + writer.WriteStartMap(1); + writer.WriteTextString("large_key"); + writer.WriteTextString(new string('X', 300)); // This will force refills + writer.WriteEndMap(); + + // The value we want to read after the skip + writer.WriteInt32(99); + writer.WriteEndArray(); + + var stream = new MemoryStream(writer.Encode()); + + using var reader = new CborStreamReader(stream); + + reader.ReadStartArray(); + + // Skip the entire large map object + reader.SkipValue(); + + // Assert that we can correctly read the next item + Assert.Equal(99, reader.ReadInt32()); + + reader.ReadEndArray(); + Assert.Equal(CborReaderState.Finished, reader.PeekState()); + + } + + [Fact] + public void ReadEndArray_WhenInMap_ThrowsCborContentException() + { + // Arrange: A simple map + var writer = new CborWriter(); + writer.WriteStartMap(0); + writer.WriteEndMap(); + var stream = new MemoryStream(writer.Encode()); + + // Act & Assert + using var reader = new CborStreamReader(stream); + + reader.ReadStartMap(); + Assert.Throws(() => reader.ReadEndArray()); + + } + + [Fact] + public void Unmarshall_IncompleteStream_ThrowsCborContentException() + { + // CBOR header for a string of length 20, but we only provide 10 bytes of data. + byte[] bytes = Convert.FromBase64String("eBQKCgoKCgoKCg=="); + var stream = new MemoryStream(bytes); + + using var reader = new CborStreamReader(stream); + + // The reader will attempt to read the string, realize it's incomplete, + // try to refill, find no more data, and then throw an exception. + Assert.Throws(() => reader.ReadTextString()); + + } + + + [Fact] + public void Unmarshall_EmptyStream_ThrowsOnRead() + { + byte[] bytes = new byte[0]; + var stream = new MemoryStream(bytes); + + using var reader = new CborStreamReader(stream); + + // Attempting to read from a finished reader should throw. + Assert.Throws(() => reader.ReadStartMap()); + + } + + [Fact] + public void Unmarshall_ComplexObject_SpansMultipleBuffers() + { + // Create a single map object with three large items. + var writer = new CborWriter(); + writer.WriteStartMap(3); + + writer.WriteTextString("item1"); + writer.WriteTextString(new string('A', 80)); // Item 1 + + writer.WriteTextString("item2"); + writer.WriteTextString(new string('B', 80)); // Item 2 (will cross boundary) + + writer.WriteTextString("item3"); + writer.WriteTextString(new string('C', 80)); // Item 3 (in second and third chunk) + + writer.WriteEndMap(); + byte[] bytes = writer.Encode(); + var stream = new MemoryStream(bytes); + + using var reader = new CborStreamReader(stream); + + reader.ReadStartMap(); + + Assert.Equal("item1", reader.ReadTextString()); + Assert.Equal(80, reader.ReadTextString().Length); + + Assert.Equal("item2", reader.ReadTextString()); + Assert.Equal(80, reader.ReadTextString().Length); + + Assert.Equal("item3", reader.ReadTextString()); + Assert.Equal(80, reader.ReadTextString().Length); + + reader.ReadEndMap(); + Assert.Equal(CborReaderState.Finished, reader.PeekState()); + + } + + [Fact] + public void Unmarshall_DefiniteLength_NestedMapAndArray() + { + var writer = new CborWriter(); + writer.WriteStartMap(1); + writer.WriteTextString("nestedArray"); + writer.WriteStartArray(2); + writer.WriteInt32(1); + writer.WriteInt32(2); + writer.WriteEndArray(); + writer.WriteEndMap(); + + var stream = new MemoryStream(writer.Encode()); + + using var reader = new CborStreamReader(stream); + reader.ReadStartMap(); + Assert.Equal("nestedArray", reader.ReadTextString()); + reader.ReadStartArray(); + Assert.Equal(1, reader.ReadInt32()); + Assert.Equal(2, reader.ReadInt32()); + reader.ReadEndArray(); + reader.ReadEndMap(); + } + + [Fact] + public void Unmarshall_IndefiniteLengthArray_CrossesBuffer_EndsWithBreak() + { + var writer = new CborWriter(); + writer.WriteStartArray(null); + writer.WriteTextString(new string('X', 90)); // Fills buffer + writer.WriteTextString("done"); + writer.WriteEndArray(); + + var stream = new MemoryStream(writer.Encode()); + + using var reader = new CborStreamReader(stream); + reader.ReadStartArray(); + Assert.Equal(90, reader.ReadTextString().Length); + Assert.Equal("done", reader.ReadTextString()); + reader.ReadEndArray(); + } + + [Fact] + public void Unmarshall_NestedIndefinite_MapArrayStructure() + { + var writer = new CborWriter(); + writer.WriteStartMap(null); + writer.WriteTextString("array"); + writer.WriteStartArray(null); + writer.WriteTextString("one"); + writer.WriteTextString("two"); + writer.WriteEndArray(); + writer.WriteEndMap(); + + var stream = new MemoryStream(writer.Encode()); + + using var reader = new CborStreamReader(stream); + reader.ReadStartMap(); + Assert.Equal("array", reader.ReadTextString()); + reader.ReadStartArray(); + Assert.Equal("one", reader.ReadTextString()); + Assert.Equal("two", reader.ReadTextString()); + reader.ReadEndArray(); + reader.ReadEndMap(); + } + + [Fact] + public void Unmarshall_EmptyIndefiniteArray() + { + var writer = new CborWriter(); + writer.WriteStartArray(null); + writer.WriteEndArray(); + + var stream = new MemoryStream(writer.Encode()); + + using var reader = new CborStreamReader(stream); + reader.ReadStartArray(); + reader.ReadEndArray(); + } + + [Fact] + public void Unmarshall_IncompleteMap_ThrowsCborContentException() + { + var writer = new CborWriter(); + writer.WriteStartMap(1); + writer.WriteTextString("key"); + writer.WriteTextString("value"); + writer.WriteEndMap(); + + var bytes = writer.Encode().Take(5).ToArray(); // Truncate it + var stream = new MemoryStream(bytes); + + using var reader = new CborStreamReader(stream); + reader.ReadStartMap(); + Assert.Equal("key", reader.ReadTextString()); + Assert.Throws(() => reader.ReadTextString()); + } + + [Fact] + public void Unmarshall_SkipValue_CrossesBufferBoundary() + { + var writer = new CborWriter(); + writer.WriteStartArray(2); + writer.WriteTextString(new string('X', 200)); + writer.WriteInt32(42); + writer.WriteEndArray(); + + var stream = new MemoryStream(writer.Encode()); + + using var reader = new CborStreamReader(stream); + reader.ReadStartArray(); + reader.SkipValue(); // This string skip will require buffer refill + Assert.Equal(42, reader.ReadInt32()); + reader.ReadEndArray(); + } + + [Fact] + public void Unmarshall_ByteString_CrossingBuffer() + { + var writer = new CborWriter(); + byte[] largeByteArray = new byte[150]; // Larger than buffer size + new Random().NextBytes(largeByteArray); + + writer.WriteByteString(largeByteArray); + + using var reader = new CborStreamReader(new MemoryStream(writer.Encode())); + byte[] result = reader.ReadByteString(); + + Assert.Equal(largeByteArray, result); + } + + [Fact] + public void Unmarshall_CborTags() + { + var writer = new CborWriter(); + writer.WriteTag(CborTag.DateTimeString); + writer.WriteTextString("2025-07-29T00:00:00Z"); + + using var reader = new CborStreamReader(new MemoryStream(writer.Encode())); + CborTag tag = reader.ReadTag(); + string dateStr = reader.ReadTextString(); + + Assert.Equal(CborTag.DateTimeString, tag); + Assert.Equal("2025-07-29T00:00:00Z", dateStr); + } + + [Fact] + public void Unmarshall_DeeplyNestedStructure() + { + var writer = new CborWriter(); + // Create a deeply nested structure + for (int i = 0; i < 10; i++) + { + writer.WriteStartMap(1); + writer.WriteTextString($"level{i}"); + } + writer.WriteTextString("value"); + for (int i = 0; i < 10; i++) + { + writer.WriteEndMap(); + } + + using var reader = new CborStreamReader(new MemoryStream(writer.Encode())); + for (int i = 0; i < 10; i++) + { + reader.ReadStartMap(); + Assert.Equal($"level{i}", reader.ReadTextString()); + } + Assert.Equal("value", reader.ReadTextString()); + for (int i = 0; i < 10; i++) + { + reader.ReadEndMap(); + } + } +} + diff --git a/extensions/test/CborProtocol.Tests/WriteDateTimeTests.cs b/extensions/test/CborProtocol.Tests/WriteDateTimeTests.cs new file mode 100644 index 000000000000..a96a70940674 --- /dev/null +++ b/extensions/test/CborProtocol.Tests/WriteDateTimeTests.cs @@ -0,0 +1,45 @@ +using System; +using System.Formats.Cbor; +using Xunit; +using Amazon.Extensions.CborProtocol; + +namespace Amazon.CborProtocol.Tests; + +public class WriteDateTimeTests +{ + [Theory] + [InlineData("1969-12-31T23:59:59Z", -1.0, CborReaderState.NegativeInteger, 2)] + [InlineData("1970-01-01T00:00:01Z", 1.0, CborReaderState.UnsignedInteger, 2)] + [InlineData("1970-01-01T00:00:00.500Z", 0.5, CborReaderState.SinglePrecisionFloat, 6)] + [InlineData("2025-01-01T00:00:00Z", 1735689600.0, CborReaderState.UnsignedInteger, 6)] + [InlineData("2025-06-19T20:15:28.468Z", 1750364128.468, CborReaderState.DoublePrecisionFloat, 10)] + public void WriteDateTime_EncodesCorrectly(string isoDate, double expectedUnixEpoch, CborReaderState expectedState, int expectedTotalBytes) + { + var dt = DateTime.Parse(isoDate); + var writer = new CborWriter(); + writer.WriteDateTime(dt); + + var encoded = writer.Encode(); + var reader = new CborReader(encoded); + var tag = reader.ReadTag(); + + Assert.Equal(CborTag.UnixTimeSeconds, tag); + Assert.Equal(expectedState, reader.PeekState()); + + double value = 0; + + if (expectedState == CborReaderState.SinglePrecisionFloat) + value = reader.ReadSingle(); + else if (expectedState == CborReaderState.DoublePrecisionFloat) + value = reader.ReadDouble(); + else if (expectedState == CborReaderState.UnsignedInteger) + value = reader.ReadUInt64(); + else if (expectedState == CborReaderState.NegativeInteger) + value = reader.ReadInt64(); + else + Assert.Fail($"CBOR state not found: ${reader.PeekState()}"); + + Assert.Equal(expectedUnixEpoch, value); + Assert.Equal(expectedTotalBytes, encoded.Length); + } +} diff --git a/extensions/test/CborProtocol.Tests/WriteOptimizedNumberTests.cs b/extensions/test/CborProtocol.Tests/WriteOptimizedNumberTests.cs new file mode 100644 index 000000000000..28233a67a5f3 --- /dev/null +++ b/extensions/test/CborProtocol.Tests/WriteOptimizedNumberTests.cs @@ -0,0 +1,55 @@ +using System.Formats.Cbor; +using Xunit; +using Amazon.Extensions.CborProtocol; + +namespace Amazon.CborProtocol.Tests; + +public class WriteOptimizedNumberTests +{ + [Theory] + [InlineData(0, 1)] // Fits in initial byte (major type + value) + [InlineData(23, 1)] // Still in initial byte + [InlineData(24, 2)] // One additional byte for UInt8 + [InlineData(255, 2)] // UInt8 + [InlineData(256, 3)] // UInt16 + [InlineData(65535, 3)] // UInt16 max + [InlineData(65536, 5)] // UInt32 + [InlineData(uint.MaxValue, 5)] // UInt32 + [InlineData((long)uint.MaxValue + 1, 9)] // UInt64 + public void WriteOptimizedNumber_UsesExpectedEncodingLength_ForUInt64(double value, int expectedBytes) + { + var writer = new CborWriter(); + writer.WriteOptimizedNumber((double)value); + var encoded = writer.Encode(); + + Assert.Equal(expectedBytes, encoded.Length); + } + + [Theory] + [InlineData(-1, 1)] // Negative integers encoded compactly + [InlineData(-24, 1)] + [InlineData(-25, 2)] // Beyond negative byte range + [InlineData(int.MinValue, 5)] // Should require full 32 bits + [InlineData(long.MinValue, 9)] // Full 64-bit negative integer + public void WriteOptimizedNumber_UsesExpectedEncodingLength_ForNegativeIntegers(long value, int expectedBytes) + { + var writer = new CborWriter(); + writer.WriteOptimizedNumber((double)value); + var encoded = writer.Encode(); + + Assert.Equal(expectedBytes, encoded.Length); + } + + [Theory] + [InlineData(1.5, 5, 0xFA)] // float32 marker + 4 bytes + [InlineData(123456789.12345, 9, 0xFB)] // Doesn't fit in float32 + public void WriteOptimizedNumber_Float32_IsManuallyEncodedAsFiveBytes(double value, int expectedBytes, byte floatMarker) + { + var writer = new CborWriter(); + writer.WriteOptimizedNumber(value); + var encoded = writer.Encode(); + + Assert.Equal(expectedBytes, encoded.Length); + Assert.Equal(floatMarker, encoded[0]); // Check float32 CBOR marker + } +} diff --git a/generator/.DevConfigs/6a90e4df-251e-45f8-808a-1a088cf0b25e.json b/generator/.DevConfigs/6a90e4df-251e-45f8-808a-1a088cf0b25e.json new file mode 100644 index 000000000000..456516f1488e --- /dev/null +++ b/generator/.DevConfigs/6a90e4df-251e-45f8-808a-1a088cf0b25e.json @@ -0,0 +1,10 @@ + +{ + "core": { + "changeLogMessages": [ + "Add support for RPCv2 CBOR protocol." + ], + "type": "patch", + "updateMinimum": false + } +} \ No newline at end of file diff --git a/generator/ProtocolTestsGenerator/settings.gradle.kts b/generator/ProtocolTestsGenerator/settings.gradle.kts index 3f62b17aa313..1f559fb377d0 100644 --- a/generator/ProtocolTestsGenerator/settings.gradle.kts +++ b/generator/ProtocolTestsGenerator/settings.gradle.kts @@ -13,7 +13,8 @@ dependencyResolutionManagement { versionCatalogs { create("codegen") { version("smithy", "1.60.3") - library("protocol-tests", "software.amazon.smithy", "smithy-aws-protocol-tests").versionRef("smithy") + library("protocol-tests", "software.amazon.smithy", "smithy-protocol-tests").versionRef("smithy") + library("protocol-aws-tests", "software.amazon.smithy", "smithy-aws-protocol-tests").versionRef("smithy") library("codegen-core", "software.amazon.smithy", "smithy-codegen-core").versionRef("smithy") library("protocol-tests-traits", "software.amazon.smithy", "smithy-protocol-test-traits").versionRef("smithy") library("aws-traits", "software.amazon.smithy", "smithy-aws-traits").versionRef("smithy") diff --git a/generator/ProtocolTestsGenerator/smithy-dotnet-codegen/src/main/java/software/amazon/smithy/dotnet/codegen/HttpProtocolTestGenerator.java b/generator/ProtocolTestsGenerator/smithy-dotnet-codegen/src/main/java/software/amazon/smithy/dotnet/codegen/HttpProtocolTestGenerator.java index 0d4aedb908b6..c8b3f1924b74 100644 --- a/generator/ProtocolTestsGenerator/smithy-dotnet-codegen/src/main/java/software/amazon/smithy/dotnet/codegen/HttpProtocolTestGenerator.java +++ b/generator/ProtocolTestsGenerator/smithy-dotnet-codegen/src/main/java/software/amazon/smithy/dotnet/codegen/HttpProtocolTestGenerator.java @@ -16,6 +16,7 @@ package software.amazon.smithy.dotnet.codegen; import software.amazon.smithy.aws.traits.ServiceTrait; +import software.amazon.smithy.model.traits.TitleTrait; import software.amazon.smithy.codegen.core.CodegenException; import software.amazon.smithy.dotnet.codegen.utils.ProtocolTestUtils; import software.amazon.smithy.model.Model; @@ -51,7 +52,14 @@ public HttpProtocolTestGenerator( this.model = context.model(); this.service = settings.getService(model); this.context = context; - this.serviceNamespace = service.getTrait(ServiceTrait.class).get().getSdkId().replace(" ", ""); + + String serviceNamespace = null; + if (service.getTrait(ServiceTrait.class).isPresent()) + serviceNamespace = service.getTrait(ServiceTrait.class).get().getSdkId(); + else if (service.getTrait(TitleTrait.class).isPresent()) + serviceNamespace = service.getTrait(TitleTrait.class).get().getValue().replace("Service", ""); + + this.serviceNamespace = serviceNamespace.replace(" ", ""); } @Override @@ -60,6 +68,8 @@ public void run() { OperationIndex operationIndex = OperationIndex.of(model); for (OperationShape operation : new TreeSet<>(topDownIndex.getContainedOperations(service))) { var operationName = operation.getId().getName(); + if (ProtocolTestCustomizations.OperationsToSkip.contains(operationName)) + continue; context.writerDelegator().useFileWriter(operationName + ".cs", serviceName, writer -> { this.writer = writer; addServiceProtocolSpecificImports(); @@ -87,6 +97,8 @@ private void addServiceProtocolSpecificImports() { } else if (this.serviceName.toLowerCase().contains("xml")) { writer.addImport(serviceName, "System.Xml"); writer.addImport(serviceName, "System.Xml.Linq"); + } else if (this.serviceName.toLowerCase().contains("rpcv2")) { + writer.addImport(serviceName, "Amazon.Extensions.CborProtocol.Internal.Transform"); } } @@ -94,9 +106,8 @@ private void generateErrorResponseTests(OperationShape operation, OperationIndex for (StructureShape error : index.getErrors(operation, service)) { error.getTrait(HttpResponseTestsTrait.class).ifPresent(trait -> { for (HttpResponseTestCase httpResponseTestCase : trait.getTestCasesFor(AppliesTo.CLIENT)) { - if (!ProtocolTestCustomizations.TestsToSkip.contains(httpResponseTestCase.getId())){ + if(!ProtocolTestCustomizations.TestsToSkip.contains(httpResponseTestCase.getId())) generateErrorResponseTest(operation, error, httpResponseTestCase); - } } }); } @@ -139,7 +150,10 @@ private void arrangeResponseTestBlock(HttpResponseTestCase httpResponseTestCase) for (var header : httpResponseTestCase.getHeaders().keySet()) { writer.write("webResponseData.Headers[$S] = $S;", header, httpResponseTestCase.getHeaders().get(header)); } - writer.write("byte[] bytes = Encoding.ASCII.GetBytes($S);", httpResponseTestCase.getBody()); + if (this.marshallerType.equals("Cbor")) + writer.write("byte[] bytes = Convert.FromBase64String($S);", httpResponseTestCase.getBody()); + else + writer.write("byte[] bytes = Encoding.ASCII.GetBytes($S);", httpResponseTestCase.getBody()); writer.write("var stream = new MemoryStream(bytes);"); writer.write("var context = new $LUnmarshallerContext(stream,true,webResponseData);", marshallerType); } @@ -285,6 +299,9 @@ private void assertRequestBody(HttpRequestTestCase httpRequestTestCase) { } else if (httpRequestTestCase.getProtocol().getName().equals("restXml")) { writer.write("var expectedBody = $S;", httpRequestTestCase.getBody()); writer.write("XmlTestUtils.AssertBody(marshalledRequest,expectedBody);"); + } else if (this.marshallerType.equals("Cbor")) { + writer.write("var expectedBody = $S;", httpRequestTestCase.getBody()); + writer.write("CborProtocolUtils.AssertBody(marshalledRequest, expectedBody);"); } else { throw new CodegenException("Unsupported protocol detected while generating request test block."); } @@ -345,6 +362,8 @@ private void setMarshallerType(String protocol) { this.marshallerType = "Json"; } else if (protocol.toLowerCase().contains("xml") || protocol.toLowerCase().contains("query")) { this.marshallerType = "Xml"; + } else if (protocol.toLowerCase().contains("cbor")) { + this.marshallerType = "Cbor"; } } diff --git a/generator/ProtocolTestsGenerator/smithy-dotnet-codegen/src/main/java/software/amazon/smithy/dotnet/codegen/customizations/ProtocolTestCustomizations.java b/generator/ProtocolTestsGenerator/smithy-dotnet-codegen/src/main/java/software/amazon/smithy/dotnet/codegen/customizations/ProtocolTestCustomizations.java index eba95dd47131..e58df27f6a0c 100644 --- a/generator/ProtocolTestsGenerator/smithy-dotnet-codegen/src/main/java/software/amazon/smithy/dotnet/codegen/customizations/ProtocolTestCustomizations.java +++ b/generator/ProtocolTestsGenerator/smithy-dotnet-codegen/src/main/java/software/amazon/smithy/dotnet/codegen/customizations/ProtocolTestCustomizations.java @@ -20,6 +20,14 @@ private ProtocolTestCustomizations() { "SparseBooleanMap", "SparseNumberMap" ); + + // These operations don't exist in C2J + public static final List OperationsToSkip = Arrays.asList( + "RpcV2CborSparseMaps", + "OperationWithDefaults", + "SparseNullsOperation" + ); + //The rename is written in smithy and since we're generating from the C2J structures we will skip this test. public static final List TestsToSkip = Arrays.asList( "RestJsonSerializeRenamedStructureUnionValue", diff --git a/generator/ProtocolTestsGenerator/smithy-dotnet-protocol-test/build.gradle.kts b/generator/ProtocolTestsGenerator/smithy-dotnet-protocol-test/build.gradle.kts index 5ae891f3ea15..874b34ae93b3 100644 --- a/generator/ProtocolTestsGenerator/smithy-dotnet-protocol-test/build.gradle.kts +++ b/generator/ProtocolTestsGenerator/smithy-dotnet-protocol-test/build.gradle.kts @@ -27,6 +27,7 @@ repositories { dependencies { implementation(project(":smithy-dotnet-codegen")) + implementation(codegen.protocol.aws.tests) implementation(codegen.protocol.tests) } diff --git a/generator/ProtocolTestsGenerator/smithy-dotnet-protocol-test/smithy-build.json b/generator/ProtocolTestsGenerator/smithy-dotnet-protocol-test/smithy-build.json index c56d2960f12b..b515994b6fea 100644 --- a/generator/ProtocolTestsGenerator/smithy-dotnet-protocol-test/smithy-build.json +++ b/generator/ProtocolTestsGenerator/smithy-dotnet-protocol-test/smithy-build.json @@ -145,6 +145,60 @@ "packageVersion": "0.0.1" } } + }, + "RpcV2Protocol": { + "transforms": [ + { + "name": "includeServices", + "args": { + "services": [ + "smithy.protocoltests.rpcv2Cbor#RpcV2Protocol" + ] + } + } + ], + "plugins": { + "dotnet-protocol-test-codegen": { + "service": "smithy.protocoltests.rpcv2Cbor#RpcV2Protocol", + "packageVersion": "0.0.1" + } + } + }, + "NonQueryCompatibleRpcV2Protocol": { + "transforms": [ + { + "name": "includeServices", + "args": { + "services": [ + "aws.protocoltests.rpcv2cbor#NonQueryCompatibleRpcV2Protocol" + ] + } + } + ], + "plugins": { + "dotnet-protocol-test-codegen": { + "service": "aws.protocoltests.rpcv2cbor#NonQueryCompatibleRpcV2Protocol", + "packageVersion": "0.0.1" + } + } + }, + "QueryCompatibleRpcV2Protocol": { + "transforms": [ + { + "name": "includeServices", + "args": { + "services": [ + "aws.protocoltests.rpcv2cbor#QueryCompatibleRpcV2Protocol" + ] + } + } + ], + "plugins": { + "dotnet-protocol-test-codegen": { + "service": "aws.protocoltests.rpcv2cbor#QueryCompatibleRpcV2Protocol", + "packageVersion": "0.0.1" + } + } } } } \ No newline at end of file diff --git a/generator/ServiceClientGeneratorLib/ExceptionShape.cs b/generator/ServiceClientGeneratorLib/ExceptionShape.cs index a8b92f27d5ee..d9d344ff9cae 100644 --- a/generator/ServiceClientGeneratorLib/ExceptionShape.cs +++ b/generator/ServiceClientGeneratorLib/ExceptionShape.cs @@ -82,6 +82,20 @@ public string Code } } + /// + /// Returns the original shape name of the exception specified in the json model. + /// This is used to find the exception type for CBOR as the exception response contains + /// the Shape ID rather than the error code. + /// https://smithy.io/2.0/additional-specs/protocols/smithy-rpc-v2.html#operation-error-serialization + /// + public string ShapeOriginalName + { + get + { + return base.Name; + } + } + /// /// Determines if the exception is marked retryable /// diff --git a/generator/ServiceClientGeneratorLib/GeneratorDriver.cs b/generator/ServiceClientGeneratorLib/GeneratorDriver.cs index c9c5f7f24f9f..53591e2af300 100644 --- a/generator/ServiceClientGeneratorLib/GeneratorDriver.cs +++ b/generator/ServiceClientGeneratorLib/GeneratorDriver.cs @@ -1171,6 +1171,10 @@ void GenerateNuspec() } } + if (Configuration.ServiceModel.Type == ServiceType.Cbor) + { + awsDependencies.Add(string.Format("AWSSDK.Extensions.CborProtocol"), "[4.0.0.0, 5.0)"); + } var nugetAssemblyName = Configuration.AssemblyTitle; var nugetAssemblyTitle = Configuration.AssemblyTitle.Replace("AWSSDK.", "AWSSDK - "); @@ -1402,7 +1406,7 @@ internal static bool WriteFile(string baseOutputDir, /// Sets the marshaller of the generator based on the service type /// /// The marshaller to be set - /// If the service type is a type of json then normalizeMarshallers is set to true, false otherwise + /// If the service type is using structure marshallers then normalizeMarshallers is set to true, false otherwise void GetRequestMarshaller(out BaseRequestMarshaller marshaller, out bool normalizeMarshallers) { normalizeMarshallers = false; @@ -1413,6 +1417,10 @@ void GetRequestMarshaller(out BaseRequestMarshaller marshaller, out bool normali marshaller = new JsonRPCRequestMarshaller(); normalizeMarshallers = true; break; + case ServiceType.Cbor: + marshaller = new CborRequestMarshaller(); + normalizeMarshallers = true; + break; case ServiceType.Query: marshaller = new AWSQueryRequestMarshaller(); break; @@ -1435,6 +1443,8 @@ BaseRequestMarshaller GetStructureMarshaller() case ServiceType.Rest_Json: case ServiceType.Json: return new JsonRPCStructureMarshaller(); + case ServiceType.Cbor: + return new CborStructureMarshaller(); default: throw new Exception("No structure marshaller for service type: " + this.Configuration.ServiceModel.Type); } @@ -1456,6 +1466,8 @@ BaseResponseUnmarshaller GetResponseUnmarshaller() return new AWSQueryResponseUnmarshaller(); case ServiceType.Rest_Xml: return new RestXmlResponseUnmarshaller(); + case ServiceType.Cbor: + return new CborResponseUnmarshaller(); default: throw new Exception("No response unmarshaller for service type: " + this.Configuration.ServiceModel.Type); } @@ -1476,6 +1488,8 @@ BaseResponseUnmarshaller GetStructureUnmarshaller() return new AWSQueryStructureUnmarshaller(); case ServiceType.Rest_Xml: return new RestXmlStructureUnmarshaller(); + case ServiceType.Cbor: + return new CborStructureUnmarshaller(); default: throw new Exception("No structure unmarshaller for service type: " + this.Configuration.ServiceModel.Type); } @@ -1495,6 +1509,8 @@ BaseResponseUnmarshaller GetExceptionUnmarshaller() return new AWSQueryExceptionUnmarshaller(); case ServiceType.Rest_Xml: return new RestXmlExceptionUnmarshaller(); + case ServiceType.Cbor: + return new CborExceptionUnmarshaller(); default: throw new Exception("No structure unmarshaller for service type: " + this.Configuration.ServiceModel.Type); } diff --git a/generator/ServiceClientGeneratorLib/GeneratorEnumerations.cs b/generator/ServiceClientGeneratorLib/GeneratorEnumerations.cs index c4893f0ccf9c..4f49f69e46e2 100644 --- a/generator/ServiceClientGeneratorLib/GeneratorEnumerations.cs +++ b/generator/ServiceClientGeneratorLib/GeneratorEnumerations.cs @@ -9,7 +9,7 @@ namespace ServiceClientGenerator /// /// The set of data protocols used by AWS services /// - public enum ServiceType { Json, Query, Rest_Xml, Rest_Json }; + public enum ServiceType { Json, Query, Rest_Xml, Rest_Json, Cbor }; /// /// Where the properties of the request should be placed diff --git a/generator/ServiceClientGeneratorLib/Generators/Marshallers/CborExceptionUnmarshaller.cs b/generator/ServiceClientGeneratorLib/Generators/Marshallers/CborExceptionUnmarshaller.cs new file mode 100644 index 000000000000..93613d565db6 --- /dev/null +++ b/generator/ServiceClientGeneratorLib/Generators/Marshallers/CborExceptionUnmarshaller.cs @@ -0,0 +1,251 @@ +// ------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version: 17.0.0.0 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +// ------------------------------------------------------------------------------ +namespace ServiceClientGenerator.Generators.Marshallers +{ + using System.Linq; + using System.Text; + using System.Collections.Generic; + using System; + + /// + /// Class to produce the template output + /// + + #line 1 "C:\repos\aws-sdk-net-v4\generator\ServiceClientGeneratorLib\Generators\Marshallers\CborExceptionUnmarshaller.tt" + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.TextTemplating", "17.0.0.0")] + public partial class CborExceptionUnmarshaller : BaseResponseUnmarshaller + { +#line hidden + /// + /// Create the template output + /// + public override string TransformText() + { + + #line 6 "C:\repos\aws-sdk-net-v4\generator\ServiceClientGeneratorLib\Generators\Marshallers\CborExceptionUnmarshaller.tt" + + AddLicenseHeader(); + + AddCommonUsingStatements(); + + + #line default + #line hidden + this.Write("using Amazon.Util;\r\nusing System.Formats.Cbor;\r\nusing Amazon.Extensions.CborProto" + + "col.Internal.Transform;\r\n#pragma warning disable CS0612,CS0618\r\nnamespace "); + + #line 15 "C:\repos\aws-sdk-net-v4\generator\ServiceClientGeneratorLib\Generators\Marshallers\CborExceptionUnmarshaller.tt" + this.Write(this.ToStringHelper.ToStringWithCulture(this.Config.Namespace)); + + #line default + #line hidden + this.Write(".Model.Internal.MarshallTransformations\r\n{\r\n /// \r\n /// Response U" + + "nmarshaller for "); + + #line 18 "C:\repos\aws-sdk-net-v4\generator\ServiceClientGeneratorLib\Generators\Marshallers\CborExceptionUnmarshaller.tt" + this.Write(this.ToStringHelper.ToStringWithCulture(this.UnmarshallerBaseName)); + + #line default + #line hidden + this.Write(" Object\r\n /// \r\n public class "); + + #line 20 "C:\repos\aws-sdk-net-v4\generator\ServiceClientGeneratorLib\Generators\Marshallers\CborExceptionUnmarshaller.tt" + this.Write(this.ToStringHelper.ToStringWithCulture(this.UnmarshallerBaseName)); + + #line default + #line hidden + this.Write("Unmarshaller : ICborErrorResponseUnmarshaller<"); + + #line 20 "C:\repos\aws-sdk-net-v4\generator\ServiceClientGeneratorLib\Generators\Marshallers\CborExceptionUnmarshaller.tt" + this.Write(this.ToStringHelper.ToStringWithCulture(this.UnmarshallerBaseName)); + + #line default + #line hidden + this.Write(", CborUnmarshallerContext>\r\n {\r\n /// \r\n /// Unmarshalle" + + "r the response from the service to the response class.\r\n /// " + + "\r\n /// \r\n /// \r\n " + + " public "); + + #line 27 "C:\repos\aws-sdk-net-v4\generator\ServiceClientGeneratorLib\Generators\Marshallers\CborExceptionUnmarshaller.tt" + this.Write(this.ToStringHelper.ToStringWithCulture(this.UnmarshallerBaseName)); + + #line default + #line hidden + this.Write(@" Unmarshall(CborUnmarshallerContext context) + { + return this.Unmarshall(context, new Amazon.Runtime.Internal.ErrorResponse()); + } + + /// + /// Unmarshaller the response from the service to the response class. + /// + /// + /// + /// + public "); + + #line 38 "C:\repos\aws-sdk-net-v4\generator\ServiceClientGeneratorLib\Generators\Marshallers\CborExceptionUnmarshaller.tt" + this.Write(this.ToStringHelper.ToStringWithCulture(this.UnmarshallerBaseName)); + + #line default + #line hidden + this.Write(" Unmarshall(CborUnmarshallerContext context, Amazon.Runtime.Internal.ErrorRespons" + + "e errorResponse)\r\n {\r\n"); + + #line 40 "C:\repos\aws-sdk-net-v4\generator\ServiceClientGeneratorLib\Generators\Marshallers\CborExceptionUnmarshaller.tt" + + if (this.Config.ServiceModel.IsAwsQueryCompatible) + { + GenerateAWSQueryCompatibleBlock(); + + + #line default + #line hidden + this.Write(" "); + + #line 45 "C:\repos\aws-sdk-net-v4\generator\ServiceClientGeneratorLib\Generators\Marshallers\CborExceptionUnmarshaller.tt" + this.Write(this.ToStringHelper.ToStringWithCulture(this.UnmarshallerBaseName)); + + #line default + #line hidden + this.Write(" unmarshalledObject = new "); + + #line 45 "C:\repos\aws-sdk-net-v4\generator\ServiceClientGeneratorLib\Generators\Marshallers\CborExceptionUnmarshaller.tt" + this.Write(this.ToStringHelper.ToStringWithCulture(this.UnmarshallerBaseName)); + + #line default + #line hidden + this.Write("(errorResponse.Message, errorResponse.InnerException,\r\n errorType," + + " errorCode, errorResponse.RequestId, errorResponse.StatusCode);\r\n"); + + #line 47 "C:\repos\aws-sdk-net-v4\generator\ServiceClientGeneratorLib\Generators\Marshallers\CborExceptionUnmarshaller.tt" + + } + else + { + + + #line default + #line hidden + this.Write(" "); + + #line 52 "C:\repos\aws-sdk-net-v4\generator\ServiceClientGeneratorLib\Generators\Marshallers\CborExceptionUnmarshaller.tt" + this.Write(this.ToStringHelper.ToStringWithCulture(this.UnmarshallerBaseName)); + + #line default + #line hidden + this.Write(" unmarshalledObject = new "); + + #line 52 "C:\repos\aws-sdk-net-v4\generator\ServiceClientGeneratorLib\Generators\Marshallers\CborExceptionUnmarshaller.tt" + this.Write(this.ToStringHelper.ToStringWithCulture(this.UnmarshallerBaseName)); + + #line default + #line hidden + this.Write("(errorResponse.Message, errorResponse.InnerException,\r\n errorRespo" + + "nse.Type, errorResponse.Code, errorResponse.RequestId, errorResponse.StatusCode)" + + ";\r\n"); + + #line 54 "C:\repos\aws-sdk-net-v4\generator\ServiceClientGeneratorLib\Generators\Marshallers\CborExceptionUnmarshaller.tt" + + } + + + #line default + #line hidden + this.Write(" var reader = context.Reader;\r\n context.AddPathSegment(\""); + + #line 58 "C:\repos\aws-sdk-net-v4\generator\ServiceClientGeneratorLib\Generators\Marshallers\CborExceptionUnmarshaller.tt" + this.Write(this.ToStringHelper.ToStringWithCulture(this.UnmarshallerBaseName)); + + #line default + #line hidden + this.Write("\");\r\n reader.ReadStartMap();\r\n while (reader.PeekState() !=" + + " CborReaderState.EndMap)\r\n {\r\n string propertyName = r" + + "eader.ReadTextString();\r\n switch (propertyName)\r\n " + + "{\r\n"); + + #line 65 "C:\repos\aws-sdk-net-v4\generator\ServiceClientGeneratorLib\Generators\Marshallers\CborExceptionUnmarshaller.tt" + + if(this.Structure != null) + { + foreach (var member in this.Structure.Members) + { + + + #line default + #line hidden + this.Write(" case \""); + + #line 71 "C:\repos\aws-sdk-net-v4\generator\ServiceClientGeneratorLib\Generators\Marshallers\CborExceptionUnmarshaller.tt" + this.Write(this.ToStringHelper.ToStringWithCulture(member.MarshallName)); + + #line default + #line hidden + this.Write("\":\r\n {\r\n context.AddPathSegment" + + "(\""); + + #line 73 "C:\repos\aws-sdk-net-v4\generator\ServiceClientGeneratorLib\Generators\Marshallers\CborExceptionUnmarshaller.tt" + this.Write(this.ToStringHelper.ToStringWithCulture(member.PropertyName)); + + #line default + #line hidden + this.Write("\");\r\n var unmarshaller = "); + + #line 74 "C:\repos\aws-sdk-net-v4\generator\ServiceClientGeneratorLib\Generators\Marshallers\CborExceptionUnmarshaller.tt" + this.Write(this.ToStringHelper.ToStringWithCulture(member.DetermineTypeUnmarshallerInstantiate())); + + #line default + #line hidden + this.Write(";\r\n unmarshalledObject."); + + #line 75 "C:\repos\aws-sdk-net-v4\generator\ServiceClientGeneratorLib\Generators\Marshallers\CborExceptionUnmarshaller.tt" + this.Write(this.ToStringHelper.ToStringWithCulture(member.PropertyName)); + + #line default + #line hidden + this.Write(" = unmarshaller.Unmarshall(context);\r\n context.PopPath" + + "Segment();\r\n break;\r\n }\r\n"); + + #line 79 "C:\repos\aws-sdk-net-v4\generator\ServiceClientGeneratorLib\Generators\Marshallers\CborExceptionUnmarshaller.tt" + + } + } + + + #line default + #line hidden + this.Write(@" default: + reader.SkipValue(); + break; + } + } + reader.ReadEndMap(); + context.PopPathSegment(); + + return unmarshalledObject; + } + +"); + + #line 94 "C:\repos\aws-sdk-net-v4\generator\ServiceClientGeneratorLib\Generators\Marshallers\CborExceptionUnmarshaller.tt" + + this.AddStructureSingletonMethod(); + + + #line default + #line hidden + this.Write(" }\r\n}"); + return this.GenerationEnvironment.ToString(); + } + } + + #line default + #line hidden +} diff --git a/generator/ServiceClientGeneratorLib/Generators/Marshallers/CborExceptionUnmarshaller.tt b/generator/ServiceClientGeneratorLib/Generators/Marshallers/CborExceptionUnmarshaller.tt new file mode 100644 index 000000000000..c6167b04a301 --- /dev/null +++ b/generator/ServiceClientGeneratorLib/Generators/Marshallers/CborExceptionUnmarshaller.tt @@ -0,0 +1,98 @@ +<#@ template language="C#" inherits="BaseResponseUnmarshaller"#> +<#@ assembly name="System.Core" #> +<#@ import namespace="System.Linq" #> +<#@ import namespace="System.Text" #> +<#@ import namespace="System.Collections.Generic" #> +<# + AddLicenseHeader(); + + AddCommonUsingStatements(); +#> +using Amazon.Util; +using System.Formats.Cbor; +using Amazon.Extensions.CborProtocol.Internal.Transform; +#pragma warning disable CS0612,CS0618 +namespace <#=this.Config.Namespace #>.Model.Internal.MarshallTransformations +{ + /// + /// Response Unmarshaller for <#=this.UnmarshallerBaseName #> Object + /// + public class <#=this.UnmarshallerBaseName #>Unmarshaller : ICborErrorResponseUnmarshaller<<#=this.UnmarshallerBaseName #>, CborUnmarshallerContext> + { + /// + /// Unmarshaller the response from the service to the response class. + /// + /// + /// + public <#=this.UnmarshallerBaseName #> Unmarshall(CborUnmarshallerContext context) + { + return this.Unmarshall(context, new Amazon.Runtime.Internal.ErrorResponse()); + } + + /// + /// Unmarshaller the response from the service to the response class. + /// + /// + /// + /// + public <#=this.UnmarshallerBaseName #> Unmarshall(CborUnmarshallerContext context, Amazon.Runtime.Internal.ErrorResponse errorResponse) + { +<# + if (this.Config.ServiceModel.IsAwsQueryCompatible) + { + GenerateAWSQueryCompatibleBlock(); +#> + <#=this.UnmarshallerBaseName #> unmarshalledObject = new <#=this.UnmarshallerBaseName #>(errorResponse.Message, errorResponse.InnerException, + errorType, errorCode, errorResponse.RequestId, errorResponse.StatusCode); +<# + } + else + { +#> + <#=this.UnmarshallerBaseName #> unmarshalledObject = new <#=this.UnmarshallerBaseName #>(errorResponse.Message, errorResponse.InnerException, + errorResponse.Type, errorResponse.Code, errorResponse.RequestId, errorResponse.StatusCode); +<# + } +#> + var reader = context.Reader; + context.AddPathSegment("<#=this.UnmarshallerBaseName#>"); + reader.ReadStartMap(); + while (reader.PeekState() != CborReaderState.EndMap) + { + string propertyName = reader.ReadTextString(); + switch (propertyName) + { +<# + if(this.Structure != null) + { + foreach (var member in this.Structure.Members) + { +#> + case "<#=member.MarshallName#>": + { + context.AddPathSegment("<#=member.PropertyName#>"); + var unmarshaller = <#= member.DetermineTypeUnmarshallerInstantiate() #>; + unmarshalledObject.<#=member.PropertyName#> = unmarshaller.Unmarshall(context); + context.PopPathSegment(); + break; + } +<# + } + } +#> + default: + reader.SkipValue(); + break; + } + } + reader.ReadEndMap(); + context.PopPathSegment(); + + return unmarshalledObject; + } + +<# + this.AddStructureSingletonMethod(); +#> + } +} \ No newline at end of file diff --git a/generator/ServiceClientGeneratorLib/Generators/Marshallers/CborRequestMarshaller.cs b/generator/ServiceClientGeneratorLib/Generators/Marshallers/CborRequestMarshaller.cs new file mode 100644 index 000000000000..b3ef06623fab --- /dev/null +++ b/generator/ServiceClientGeneratorLib/Generators/Marshallers/CborRequestMarshaller.cs @@ -0,0 +1,357 @@ +// ------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version: 17.0.0.0 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +// ------------------------------------------------------------------------------ +namespace ServiceClientGenerator.Generators.Marshallers +{ + using System.Linq; + using System.Text; + using System.Collections.Generic; + using System; + + /// + /// Class to produce the template output + /// + + #line 1 "C:\repos\aws-sdk-net-v4\generator\ServiceClientGeneratorLib\Generators\Marshallers\CborRequestMarshaller.tt" + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.TextTemplating", "17.0.0.0")] + public partial class CborRequestMarshaller : CborStructureMarshaller + { +#line hidden + /// + /// Create the template output + /// + public override string TransformText() + { + + #line 6 "C:\repos\aws-sdk-net-v4\generator\ServiceClientGeneratorLib\Generators\Marshallers\CborRequestMarshaller.tt" + + AddLicenseHeader(); + + AddCommonUsingStatements(); + + + #line default + #line hidden + this.Write("using Amazon.Extensions.CborProtocol;\r\nusing Amazon.Extensions.CborProtocol.Inter" + + "nal;\r\nusing Amazon.Extensions.CborProtocol.Internal.Transform;\r\n\r\n#pragma warnin" + + "g disable CS0612,CS0618\r\nnamespace "); + + #line 16 "C:\repos\aws-sdk-net-v4\generator\ServiceClientGeneratorLib\Generators\Marshallers\CborRequestMarshaller.tt" + this.Write(this.ToStringHelper.ToStringWithCulture(this.Config.Namespace)); + + #line default + #line hidden + this.Write(".Model.Internal.MarshallTransformations\r\n{\r\n\t/// \r\n\t/// "); + + #line 19 "C:\repos\aws-sdk-net-v4\generator\ServiceClientGeneratorLib\Generators\Marshallers\CborRequestMarshaller.tt" + this.Write(this.ToStringHelper.ToStringWithCulture(this.Operation.Name)); + + #line default + #line hidden + this.Write(" Request Marshaller\r\n\t/// \r\n\tpublic class "); + + #line 21 "C:\repos\aws-sdk-net-v4\generator\ServiceClientGeneratorLib\Generators\Marshallers\CborRequestMarshaller.tt" + this.Write(this.ToStringHelper.ToStringWithCulture(this.Operation.Name)); + + #line default + #line hidden + this.Write("RequestMarshaller : IMarshaller\r\n public partial class "); - #line 19 "C:\codebase\v4\aws-sdk-net-v4\generator\ServiceClientGeneratorLib\Generators\Marshallers\EventStreamPublisherMarshaller.tt" + #line 28 "C:\repos\aws-sdk-net-v4\generator\ServiceClientGeneratorLib\Generators\Marshallers\EventStreamPublisherMarshaller.tt" this.Write(this.ToStringHelper.ToStringWithCulture(this.Structure.Name)); #line default #line hidden - this.Write("PublisherMarshaller : EventStreamPublisher\r\n {\r\n Func< Task