< Summary

Information
Class: IceRpc.Ice.Operations.IceProxyIceDecoderExtensions
Assembly: IceRpc.Ice
File(s): /home/runner/work/icerpc-csharp/icerpc-csharp/src/IceRpc.Ice/Operations/IceProxyIceDecoderExtensions.cs
Tag: 1856_27024993493
Line coverage
82%
Covered lines: 168
Uncovered lines: 36
Coverable lines: 204
Total lines: 353
Line coverage: 82.3%
Branch coverage
78%
Covered branches: 55
Total branches: 70
Branch coverage: 78.5%
Method coverage
100%
Covered methods: 9
Fully covered methods: 6
Total methods: 9
Method coverage: 100%
Full method coverage: 66.6%

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.cctor()100%11100%
DecodeProxy(...)100%22100%
CreateProxy(...)100%22100%
DecodeServiceAddress(...)100%22100%
DecodeServerAddress(...)64.28%362878.37%
DecodeUriServerAddress()100%11100%
DecodeServiceAddressCore(...)72.22%221877.58%
DecodeTcpServerAddressBody(...)83.33%7668.18%
EscapeAdapterId(...)100%1212100%

File(s)

/home/runner/work/icerpc-csharp/icerpc-csharp/src/IceRpc.Ice/Operations/IceProxyIceDecoderExtensions.cs

#LineLine coverage
 1// Copyright (c) ZeroC, Inc.
 2
 3using IceRpc.Ice.Codec;
 4using IceRpc.Ice.Internal;
 5using IceRpc.Ice.Operations.Internal;
 6using IceRpc.Internal;
 7using System.Buffers;
 8using System.Collections.Immutable;
 9using System.Diagnostics;
 10using System.Globalization;
 11using System.Runtime.CompilerServices;
 12using System.Text;
 13
 14namespace IceRpc.Ice.Operations;
 15
 16/// <summary>Provides extension methods for <see cref="IceDecoder" /> to decode proxies.</summary>
 17public static class IceProxyIceDecoderExtensions
 18{
 19    // The printable ASCII range. Characters outside this range must be percent-escaped in a service address parameter
 20    // value.
 21    private const char FirstValidChar = '\x21';
 22    private const char LastValidChar = '\x7E';
 23
 24    // Same as ServiceAddress's _notValidInParamValue, plus '%' which must be escaped to make the percent-escaped
 25    // value unambiguously decodable.
 226    private static readonly SearchValues<char> _mustEscapeInAdapterId = SearchValues.Create("\"<>#%&\\^`{|}");
 27
 28    /// <summary>Decodes a proxy struct.</summary>
 29    /// <typeparam name="TProxy">The type of the proxy struct to decode.</typeparam>
 30    /// <param name="decoder">The Ice decoder.</param>
 31    /// <returns>The decoded proxy, or <see langword="null" />.</returns>
 32    public static TProxy? DecodeProxy<TProxy>(this ref IceDecoder decoder) where TProxy : struct, IIceProxy =>
 6133        decoder.DecodeServiceAddress() is ServiceAddress serviceAddress ?
 6134            CreateProxy<TProxy>(serviceAddress, decoder.DecodingContext) : null;
 35
 36    private static TProxy CreateProxy<TProxy>(ServiceAddress serviceAddress, object? decodingContext)
 37        where TProxy : struct, IIceProxy
 4938    {
 4939        Debug.Assert(serviceAddress.Protocol is not null, "The Ice encoding does not support relative proxies.");
 40
 4941        if (decodingContext is null)
 3842        {
 3843            return new TProxy { Invoker = InvalidInvoker.Instance, ServiceAddress = serviceAddress };
 44        }
 45        else
 1146        {
 1147            var baseProxy = (IIceProxy)decodingContext;
 1148            return new TProxy
 1149            {
 1150                EncodeOptions = baseProxy.EncodeOptions,
 1151                Invoker = baseProxy.Invoker,
 1152                ServiceAddress = serviceAddress
 1153            };
 54        }
 4955    }
 56
 57    /// <summary>Decodes a service address.</summary>
 58    /// <param name="decoder">The Ice decoder.</param>
 59    /// <returns>The decoded service address, or <see langword="null" />.</returns>
 60    private static ServiceAddress? DecodeServiceAddress(this ref IceDecoder decoder)
 6161    {
 6162        string path = new Identity(ref decoder).ToPath();
 6163        return path != "/" ? decoder.DecodeServiceAddressCore(path) : null;
 5764    }
 65
 66    /// <summary>Decodes a server address.</summary>
 67    /// <param name="decoder">The Ice decoder.</param>
 68    /// <param name="protocol">The protocol of this server address.</param>
 69    /// <returns>The server address decoded by this decoder.</returns>
 70    private static ServerAddress DecodeServerAddress(this ref IceDecoder decoder, Protocol protocol)
 3771    {
 72        // With the Ice encoding, the ice server addresses are transport-specific, with a transport-specific encoding.
 73
 3774        ServerAddress? serverAddress = null;
 3775        var transportCode = (TransportCode)decoder.DecodeShort();
 76
 3777        int size = decoder.DecodeInt();
 3778        if (size < 6)
 079        {
 080            throw new InvalidDataException($"The Ice encapsulation's size ({size}) is too small.");
 81        }
 82
 83        // Remove 6 bytes from the encapsulation size (4 for encapsulation size, 2 for encoding).
 3784        size -= 6;
 85
 3786        byte encodingMajor = decoder.DecodeByte();
 3787        byte encodingMinor = decoder.DecodeByte();
 88
 3789        if (decoder.Remaining < size)
 090        {
 091            throw new InvalidDataException($"The Ice encapsulation's size ({size}) is too big.");
 92        }
 93
 3794        if (encodingMajor == 1 && encodingMinor <= 1)
 3795        {
 3796            long oldPos = decoder.Consumed;
 97
 3798            if (protocol == Protocol.Ice)
 1899            {
 18100                switch (transportCode)
 101                {
 102                    case TransportCode.Tcp:
 11103                        serverAddress = decoder.DecodeTcpServerAddressBody(IceProxyIceEncoderExtensions.TcpName);
 11104                        break;
 105
 106                    case TransportCode.Ssl:
 2107                        serverAddress = decoder.DecodeTcpServerAddressBody(IceProxyIceEncoderExtensions.SslName);
 2108                        break;
 109
 110                    case TransportCode.Uri:
 3111                        serverAddress = DecodeUriServerAddress(decoder.DecodeString());
 1112                        if (serverAddress.Value.Protocol != protocol)
 0113                        {
 0114                            throw new InvalidDataException(
 0115                                $"Expected {protocol} server address but received '{serverAddress.Value}'.");
 116                        }
 1117                        break;
 118
 119                    default:
 120                        // Create a server address for transport opaque
 2121                        ImmutableDictionary<string, string>.Builder builder =
 2122                            ImmutableDictionary.CreateBuilder<string, string>();
 123
 2124                        if (encodingMinor == 0)
 1125                        {
 1126                            builder.Add("e", "1.0");
 1127                        }
 128                        // else no e
 129
 2130                        builder.Add("t", ((short)transportCode).ToString(CultureInfo.InvariantCulture));
 2131                        {
 2132                            using IMemoryOwner<byte> memoryOwner = MemoryPool<byte>.Shared.Rent(size);
 2133                            Span<byte> span = memoryOwner.Memory.Span[0..size];
 2134                            decoder.CopyTo(span);
 2135                            string value = Convert.ToBase64String(span);
 2136                            builder.Add("v", value);
 2137                            decoder.IncreaseCollectionAllocation(value.Length, Unsafe.SizeOf<char>());
 2138                        }
 139
 2140                        serverAddress = new ServerAddress(
 2141                            Protocol.Ice,
 2142                            host: "opaque", // not a real host obviously
 2143                            port: Protocol.Ice.DefaultPort,
 2144                            transport: IceProxyIceEncoderExtensions.OpaqueName,
 2145                            builder.ToImmutable());
 2146                        break;
 147                }
 16148            }
 19149            else if (transportCode == TransportCode.Uri)
 19150            {
 151                // The server addresses of an Ice-encoded icerpc proxies only use TransportCode.Uri.
 19152                serverAddress = DecodeUriServerAddress(decoder.DecodeString());
 17153                if (serverAddress.Value.Protocol != protocol)
 0154                {
 0155                    throw new InvalidDataException(
 0156                        $"Expected {protocol} server address but received '{serverAddress.Value}'.");
 157                }
 17158            }
 159
 33160            if (serverAddress is not null)
 33161            {
 162                // Make sure we read the full encapsulation.
 33163                if (decoder.Consumed != oldPos + size)
 0164                {
 0165                    throw new InvalidDataException(
 0166                        $"There are {oldPos + size - decoder.Consumed} bytes left in server address encapsulation.");
 167                }
 33168            }
 33169        }
 170
 33171        if (serverAddress is null)
 0172        {
 0173            throw new InvalidDataException(
 0174                $"Cannot decode server address for protocol '{protocol}' and transport '{transportCode.ToString().ToLowe
 175        }
 176
 33177        return serverAddress.Value;
 178
 179        static ServerAddress DecodeUriServerAddress(string uriString)
 22180        {
 181            try
 22182            {
 22183                return new ServerAddress(new Uri(uriString));
 184            }
 4185            catch (Exception exception) when (exception is UriFormatException or ArgumentException)
 4186            {
 4187                throw new InvalidDataException(
 4188                    $"Received invalid server address URI '{uriString}'.",
 4189                    exception);
 190            }
 18191        }
 33192    }
 193
 194    /// <summary>Decodes a service address encoded with the Ice encoding.</summary>
 195    /// <param name="decoder">The Ice decoder.</param>
 196    /// <param name="path">The decoded path.</param>
 197    /// <returns>The decoded service address.</returns>
 198    private static ServiceAddress DecodeServiceAddressCore(this ref IceDecoder decoder, string path)
 53199    {
 200        // With the Ice encoding, a service address is encoded as a kind of discriminated union with:
 201        // - Identity
 202        // - If Identity is not the null identity:
 203        //     - the fragment, invocation mode, secure, protocol major and minor, and the encoding major and minor
 204        //     - a sequence of server addresses (can be empty)
 205        //     - an adapter ID string present only when the sequence of server addresses is empty
 206
 53207        string fragment = decoder.DecodeFacet().ToFragment();
 53208        _ = decoder.DecodeInvocationMode();
 53209        _ = decoder.DecodeBool();
 53210        byte protocolMajor = decoder.DecodeByte();
 53211        byte protocolMinor = decoder.DecodeByte();
 53212        decoder.Skip(2); // skip encoding major and minor
 213
 53214        if (protocolMajor == 0)
 0215        {
 0216            throw new InvalidDataException("Received service address with protocol set to 0.");
 217        }
 53218        if (protocolMinor != 0)
 0219        {
 0220            throw new InvalidDataException(
 0221                $"Received service address with invalid protocolMinor value: {protocolMinor}.");
 222        }
 223
 53224        int count = decoder.DecodeSize();
 225
 53226        ServerAddress? serverAddress = null;
 53227        IEnumerable<ServerAddress> altServerAddresses = ImmutableList<ServerAddress>.Empty;
 53228        var protocol = Protocol.FromByteValue(protocolMajor);
 53229        ImmutableDictionary<string, string> serviceAddressParams = ImmutableDictionary<string, string>.Empty;
 230
 53231        if (count == 0)
 17232        {
 17233            if (decoder.DecodeString() is string adapterId && adapterId.Length > 0)
 10234            {
 10235                serviceAddressParams =
 10236                    serviceAddressParams.Add("adapter-id", EscapeAdapterId(adapterId));
 10237            }
 17238        }
 239        else
 36240        {
 36241            serverAddress = decoder.DecodeServerAddress(protocol);
 32242            if (count >= 2)
 1243            {
 244                // An Ice-encoded server address consumes at least 8 bytes (2 bytes for the server address type and 6
 245                // bytes for the encapsulation header). SizeOf ServerAddress is large but less than 8 * 8.
 1246                decoder.IncreaseCollectionAllocation(count, Unsafe.SizeOf<ServerAddress>());
 247
 1248                var serverAddressArray = new ServerAddress[count - 1];
 4249                for (int i = 0; i < count - 1; ++i)
 1250                {
 1251                    serverAddressArray[i] = decoder.DecodeServerAddress(protocol);
 1252                }
 1253                altServerAddresses = serverAddressArray;
 1254            }
 32255        }
 256
 257        try
 49258        {
 49259            if (!protocol.HasFragment && fragment.Length > 0)
 0260            {
 0261                throw new InvalidDataException($"Unexpected fragment in {protocol} service address.");
 262            }
 263
 49264            return new ServiceAddress(
 49265                protocol,
 49266                path,
 49267                serverAddress,
 49268                altServerAddresses.ToImmutableList(),
 49269                serviceAddressParams,
 49270                fragment);
 271        }
 0272        catch (InvalidDataException)
 0273        {
 0274            throw;
 275        }
 0276        catch (Exception exception)
 0277        {
 0278            throw new InvalidDataException("Received invalid service address.", exception);
 279        }
 49280    }
 281
 282    /// <summary>Decodes the body of a tcp or ssl server address.</summary>
 283    private static ServerAddress DecodeTcpServerAddressBody(this ref IceDecoder decoder, string transport)
 13284    {
 13285        var body = new TcpServerAddressBody(ref decoder);
 286
 13287        if (Uri.CheckHostName(body.Host) == UriHostNameType.Unknown)
 0288        {
 0289            throw new InvalidDataException($"Received service address with invalid host '{body.Host}'.");
 290        }
 291
 13292        ImmutableDictionary<string, string> parameters = ImmutableDictionary<string, string>.Empty;
 13293        if (body.Timeout != IceProxyIceEncoderExtensions.DefaultTcpTimeout)
 4294        {
 4295            parameters = parameters.Add("t", body.Timeout.ToString(CultureInfo.InvariantCulture));
 4296        }
 13297        if (body.Compress)
 1298        {
 1299            parameters = parameters.Add("z", "");
 1300        }
 301
 302        try
 13303        {
 13304            return new ServerAddress(Protocol.Ice, body.Host, checked((ushort)body.Port), transport, parameters);
 305        }
 0306        catch (OverflowException exception)
 0307        {
 0308            throw new InvalidDataException(
 0309                "Cannot decode a server address with a port number larger than 65,535.",
 0310                exception);
 311        }
 13312    }
 313
 314    /// <summary>Percent-encodes only the characters that are invalid in a service address parameter value: characters
 315    /// outside the printable ASCII range <c>\x21..\x7E</c>, characters in
 316    /// <see cref="_mustEscapeInAdapterId" />, and the <c>%</c> character itself (which must be escaped to make the
 317    /// result unambiguously decodable).</summary>
 318    /// <param name="value">The raw adapter ID, as decoded from the wire.</param>
 319    /// <returns>An escaped string suitable for use as the value of the <c>adapter-id</c> service address parameter.
 320    /// </returns>
 321    /// <remarks>This is intentionally narrower than <see cref="Uri.EscapeDataString(string)" />, which over-escapes
 322    /// characters that are valid in service address parameter values such as <c>/</c>, <c>:</c>, and <c>@</c>.
 323    /// </remarks>
 324    private static string EscapeAdapterId(string value)
 10325    {
 10326        ReadOnlySpan<char> span = value.AsSpan();
 327
 328        // Fast path: nothing to escape. Adapter IDs are usually pure ASCII so we almost always take this path.
 10329        if (span.IndexOfAnyExceptInRange(FirstValidChar, LastValidChar) == -1 &&
 10330            span.IndexOfAny(_mustEscapeInAdapterId) == -1)
 5331        {
 5332            return value;
 333        }
 334
 335        // Slow path. Encode the whole string to UTF-8 bytes, then percent-escape every byte that is not a valid
 336        // unescaped char. UTF-8 continuation bytes (>= 0x80) naturally fall in the escape branch, so multi-byte
 337        // code points are handled without any surrogate-pair logic here.
 5338        byte[] utf8 = Encoding.UTF8.GetBytes(value);
 5339        var sb = new StringBuilder(utf8.Length + 8);
 111340        foreach (byte b in utf8)
 48341        {
 48342            if (b >= FirstValidChar && b <= LastValidChar && !_mustEscapeInAdapterId.Contains((char)b))
 40343            {
 40344                sb.Append((char)b);
 40345            }
 346            else
 8347            {
 8348                sb.Append('%').Append(b.ToString("X2", CultureInfo.InvariantCulture));
 8349            }
 48350        }
 5351        return sb.ToString();
 10352    }
 353}