< Summary

Information
Class: IceRpc.Slice.ServiceAddressSliceDecoderExtensions
Assembly: IceRpc.Slice
File(s): /home/runner/work/icerpc-csharp/icerpc-csharp/src/IceRpc.Slice/ServiceAddressSliceDecoderExtensions.cs
Tag: 275_13775359185
Line coverage
76%
Covered lines: 140
Uncovered lines: 42
Coverable lines: 182
Total lines: 302
Line coverage: 76.9%
Branch coverage
77%
Covered branches: 48
Total branches: 62
Branch coverage: 77.4%
Method coverage
100%
Covered methods: 5
Total methods: 5
Method coverage: 100%

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
DecodeServiceAddress(...)83.33%6.2682.35%
DecodeNullableServiceAddress(...)75%4.84462.5%
DecodeServerAddress(...)75%35.032879.22%
DecodeServiceAddressCore(...)77.77%21.851877.19%
DecodeTcpServerAddressBody(...)83.33%7.02669.56%

File(s)

/home/runner/work/icerpc-csharp/icerpc-csharp/src/IceRpc.Slice/ServiceAddressSliceDecoderExtensions.cs

#LineLine coverage
 1// Copyright (c) ZeroC, Inc.
 2
 3using IceRpc.Internal;
 4using IceRpc.Slice.Internal;
 5using System.Buffers;
 6using System.Collections.Immutable;
 7using System.Diagnostics;
 8using System.Globalization;
 9using System.Runtime.CompilerServices;
 10using ZeroC.Slice;
 11
 12namespace IceRpc.Slice;
 13
 14/// <summary>Provides extension methods for <see cref="SliceDecoder" /> to decode service addresses.</summary>
 15public static class ServiceAddressSliceDecoderExtensions
 16{
 17    /// <summary>Decodes a service address.</summary>
 18    /// <param name="decoder">The Slice decoder.</param>
 19    /// <returns>The decoded service address.</returns>
 20    public static ServiceAddress DecodeServiceAddress(this ref SliceDecoder decoder)
 2721    {
 2722        if (decoder.Encoding == SliceEncoding.Slice1)
 423        {
 424            return decoder.DecodeNullableServiceAddress() ??
 425                throw new InvalidDataException("Decoded null for a non-nullable service address.");
 26        }
 27        else
 2328        {
 2329            string serviceAddressString = decoder.DecodeString();
 30            try
 2331            {
 2332                if (serviceAddressString.StartsWith('/'))
 333                {
 34                    // relative service address
 335                    return new ServiceAddress { Path = serviceAddressString };
 36                }
 37                else
 2038                {
 2039                    return new ServiceAddress(new Uri(serviceAddressString, UriKind.Absolute));
 40                }
 41            }
 042            catch (Exception exception)
 043            {
 044                throw new InvalidDataException("Received an invalid service address.", exception);
 45            }
 46        }
 2747    }
 48
 49    /// <summary>Decodes a nullable service address (Slice1 only).</summary>
 50    /// <param name="decoder">The Slice decoder.</param>
 51    /// <returns>The decoded service address, or <see langword="null" />.</returns>
 52    public static ServiceAddress? DecodeNullableServiceAddress(this ref SliceDecoder decoder)
 4653    {
 4654        if (decoder.Encoding != SliceEncoding.Slice1)
 055        {
 056            throw new InvalidOperationException(
 057                $"Decoding a nullable service address with {decoder.Encoding} requires a bit sequence.");
 58        }
 59
 4660        string path = new Identity(ref decoder).ToPath();
 4661        return path != "/" ? decoder.DecodeServiceAddressCore(path) : null;
 4662    }
 63
 64    /// <summary>Decodes a server address (Slice1 only).</summary>
 65    /// <param name="decoder">The Slice decoder.</param>
 66    /// <param name="protocol">The protocol of this server address.</param>
 67    /// <returns>The server address decoded by this decoder.</returns>
 68    private static ServerAddress DecodeServerAddress(this ref SliceDecoder decoder, Protocol protocol)
 3769    {
 3770        Debug.Assert(decoder.Encoding == SliceEncoding.Slice1);
 71
 72        // The Slice1 ice server addresses are transport-specific, with a transport-specific encoding.
 73
 3774        ServerAddress? serverAddress = null;
 3775        var transportCode = (TransportCode)decoder.DecodeInt16();
 76
 3777        int size = decoder.DecodeInt32();
 3778        if (size < 6)
 079        {
 080            throw new InvalidDataException($"The Slice1 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.DecodeUInt8();
 3787        byte encodingMinor = decoder.DecodeUInt8();
 88
 3789        if (decoder.Remaining < size)
 090        {
 091            throw new InvalidDataException($"The Slice1 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)
 1999            {
 19100                switch (transportCode)
 101                {
 102                    case TransportCode.Tcp:
 13103                        serverAddress = decoder.DecodeTcpServerAddressBody(
 13104                            ServiceAddressSliceEncoderExtensions.TcpName);
 13105                        break;
 106
 107                    case TransportCode.Ssl:
 3108                        serverAddress = decoder.DecodeTcpServerAddressBody(
 3109                            ServiceAddressSliceEncoderExtensions.SslName);
 3110                        break;
 111
 112                    case TransportCode.Uri:
 1113                        serverAddress = new ServerAddress(new Uri(decoder.DecodeString()));
 1114                        if (serverAddress.Value.Protocol != protocol)
 0115                        {
 0116                            throw new InvalidDataException(
 0117                                $"Expected {protocol} server address but received '{serverAddress.Value}'.");
 118                        }
 1119                        break;
 120
 121                    default:
 122                        // Create a server address for transport opaque
 2123                        ImmutableDictionary<string, string>.Builder builder =
 2124                            ImmutableDictionary.CreateBuilder<string, string>();
 125
 2126                        if (encodingMinor == 0)
 1127                        {
 1128                            builder.Add("e", "1.0");
 1129                        }
 130                        // else no e
 131
 2132                        builder.Add("t", ((short)transportCode).ToString(CultureInfo.InvariantCulture));
 2133                        {
 2134                            using IMemoryOwner<byte> memoryOwner = MemoryPool<byte>.Shared.Rent(size);
 2135                            Span<byte> span = memoryOwner.Memory.Span[0..size];
 2136                            decoder.CopyTo(span);
 2137                            string value = Convert.ToBase64String(span);
 2138                            builder.Add("v", value);
 2139                            decoder.IncreaseCollectionAllocation(value.Length * Unsafe.SizeOf<char>());
 2140                        }
 141
 2142                        serverAddress = new ServerAddress(
 2143                            Protocol.Ice,
 2144                            host: "opaque", // not a real host obviously
 2145                            port: Protocol.Ice.DefaultPort,
 2146                            transport: ServiceAddressSliceEncoderExtensions.OpaqueName,
 2147                            builder.ToImmutable());
 2148                        break;
 149                }
 19150            }
 18151            else if (transportCode == TransportCode.Uri)
 18152            {
 153                // The server addresses of Slice1 encoded icerpc proxies only use TransportCode.Uri.
 18154                serverAddress = new ServerAddress(new Uri(decoder.DecodeString()));
 18155                if (serverAddress.Value.Protocol != protocol)
 0156                {
 0157                    throw new InvalidDataException(
 0158                        $"Expected {protocol} server address but received '{serverAddress.Value}'.");
 159                }
 18160            }
 161
 37162            if (serverAddress is not null)
 37163            {
 164                // Make sure we read the full encapsulation.
 37165                if (decoder.Consumed != oldPos + size)
 0166                {
 0167                    throw new InvalidDataException(
 0168                        $"There are {oldPos + size - decoder.Consumed} bytes left in server address encapsulation.");
 169                }
 37170            }
 37171        }
 172
 37173        if (serverAddress is null)
 0174        {
 0175            throw new InvalidDataException(
 0176                $"Cannot decode server address for protocol '{protocol}' and transport '{transportCode.ToString().ToLowe
 177        }
 178
 37179        return serverAddress.Value;
 37180    }
 181
 182    /// <summary>Decodes a service address encoded with Slice1.</summary>
 183    /// <param name="decoder">The Slice decoder.</param>
 184    /// <param name="path">The decoded path.</param>
 185    /// <returns>The decoded service address.</returns>
 186    private static ServiceAddress DecodeServiceAddressCore(this ref SliceDecoder decoder, string path)
 37187    {
 188        // With Slice1, a service address is encoded as a kind of discriminated union with:
 189        // - Identity
 190        // - If Identity is not the null identity:
 191        //     - the fragment, invocation mode, secure, protocol major and minor, and the encoding major and minor
 192        //     - a sequence of server addresses (can be empty)
 193        //     - an adapter ID string present only when the sequence of server addresses is empty
 194
 37195        string fragment = decoder.DecodeFragment();
 37196        _ = decoder.DecodeInvocationMode();
 37197        _ = decoder.DecodeBool();
 37198        byte protocolMajor = decoder.DecodeUInt8();
 37199        byte protocolMinor = decoder.DecodeUInt8();
 37200        decoder.Skip(2); // skip encoding major and minor
 201
 37202        if (protocolMajor == 0)
 0203        {
 0204            throw new InvalidDataException("Received service address with protocol set to 0.");
 205        }
 37206        if (protocolMinor != 0)
 0207        {
 0208            throw new InvalidDataException(
 0209                $"Received service address with invalid protocolMinor value: {protocolMinor}.");
 210        }
 211
 37212        int count = decoder.DecodeSize();
 213
 37214        ServerAddress? serverAddress = null;
 37215        IEnumerable<ServerAddress> altServerAddresses = ImmutableList<ServerAddress>.Empty;
 37216        var protocol = Protocol.FromByteValue(protocolMajor);
 37217        ImmutableDictionary<string, string> serviceAddressParams = ImmutableDictionary<string, string>.Empty;
 218
 37219        if (count == 0)
 2220        {
 2221            if (decoder.DecodeString() is string adapterId && adapterId.Length > 0)
 1222            {
 1223                serviceAddressParams = serviceAddressParams.Add("adapter-id", adapterId);
 1224            }
 2225        }
 226        else
 35227        {
 35228            serverAddress = decoder.DecodeServerAddress(protocol);
 35229            if (count >= 2)
 2230            {
 231                // A Slice1 encoded server address consumes at least 8 bytes (2 bytes for the server address type and 6
 232                // bytes for the encapsulation header). SizeOf ServerAddress is large but less than 8 * 8.
 2233                decoder.IncreaseCollectionAllocation(count * Unsafe.SizeOf<ServerAddress>());
 234
 2235                var serverAddressArray = new ServerAddress[count - 1];
 8236                for (int i = 0; i < count - 1; ++i)
 2237                {
 2238                    serverAddressArray[i] = decoder.DecodeServerAddress(protocol);
 2239                }
 2240                altServerAddresses = serverAddressArray;
 2241            }
 35242        }
 243
 244        try
 37245        {
 37246            if (!protocol.HasFragment && fragment.Length > 0)
 0247            {
 0248                throw new InvalidDataException($"Unexpected fragment in {protocol} service address.");
 249            }
 250
 37251            return new ServiceAddress(
 37252                protocol,
 37253                path,
 37254                serverAddress,
 37255                altServerAddresses.ToImmutableList(),
 37256                serviceAddressParams,
 37257                fragment);
 258        }
 0259        catch (InvalidDataException)
 0260        {
 0261            throw;
 262        }
 0263        catch (Exception exception)
 0264        {
 0265            throw new InvalidDataException("Received invalid service address.", exception);
 266        }
 37267    }
 268
 269    /// <summary>Decodes the body of a tcp or ssl server address encoded using Slice1.</summary>
 270    private static ServerAddress DecodeTcpServerAddressBody(this ref SliceDecoder decoder, string transport)
 16271    {
 16272        Debug.Assert(decoder.Encoding == SliceEncoding.Slice1);
 273
 16274        var body = new TcpServerAddressBody(ref decoder);
 275
 16276        if (Uri.CheckHostName(body.Host) == UriHostNameType.Unknown)
 0277        {
 0278            throw new InvalidDataException($"Received service address with invalid host '{body.Host}'.");
 279        }
 280
 16281        ImmutableDictionary<string, string> parameters = ImmutableDictionary<string, string>.Empty;
 16282        if (body.Timeout != ServiceAddressSliceEncoderExtensions.DefaultTcpTimeout)
 4283        {
 4284            parameters = parameters.Add("t", body.Timeout.ToString(CultureInfo.InvariantCulture));
 4285        }
 16286        if (body.Compress)
 1287        {
 1288            parameters = parameters.Add("z", "");
 1289        }
 290
 291        try
 16292        {
 16293            return new ServerAddress(Protocol.Ice, body.Host, checked((ushort)body.Port), transport, parameters);
 294        }
 0295        catch (OverflowException exception)
 0296        {
 0297            throw new InvalidDataException(
 0298                "Cannot decode a server address with a port number larger than 65,535.",
 0299                exception);
 300        }
 16301    }
 302}