Пример #1
0
        /// <summary>
        /// 拦截服务请求
        /// </summary>
        public override async Task <TResponse> UnaryServerHandler <TRequest, TResponse>(TRequest request, ServerCallContext context, UnaryServerMethod <TRequest, TResponse> continuation)
        {
            Endpoint endpoint       = OwinContextReader.Current.GetEndpoint();
            bool     needAuthorize  = AspNetSetting.Authorized;
            bool     allowAnonymous = endpoint.Metadata.GetMetadata <AllowAnonymousAttribute>() != null;

            if (needAuthorize && !allowAnonymous)
            {
                string         headerKey   = SessionKey.PublicKey.ToLower();
                Metadata.Entry headerEntry = context.RequestHeaders.Get(headerKey);
                string         publicKey   = headerEntry?.Value;
                if (string.IsNullOrWhiteSpace(publicKey))
                {
                    string message = "身份认证消息头不存在,请检查程序!";
                    NoPermissionException innerException = new NoPermissionException(message);
                    Status status = new Status(StatusCode.Unauthenticated, message, innerException);
                    throw new RpcException(status);
                }

                LoginInfo loginInfo = CacheMediator.Get <LoginInfo>(publicKey);
                if (loginInfo == null)
                {
                    string message = "身份过期,请重新登录!";
                    NoPermissionException innerException = new NoPermissionException(message);
                    Status status = new Status(StatusCode.Unauthenticated, message, innerException);
                    throw new RpcException(status);
                }

                //通过后,重新设置缓存过期时间
                CacheMediator.Set(publicKey, loginInfo, DateTime.Now.AddMinutes(GlobalSetting.AuthenticationTimeout));
            }

            return(await continuation.Invoke(request, context));
        }
Пример #2
0
        public void KeysAreNormalized_UppercaseKey()
        {
            var uppercaseKey = "ABC";
            var entry        = new Metadata.Entry(uppercaseKey, "XYZ");

            Assert.AreEqual("abc", entry.Key);
        }
Пример #3
0
        public static Metadata ProcessHeaders(Metadata headers)
        {
            if (headers == null)
            {
                throw new ArgumentNullException(nameof(headers));
            }

            Metadata.Entry trackingEntry = headers.FirstOrDefault(x => string.CompareOrdinal(x.Key, s_TrackingContextKeyName) == 0);

            // Retrieve the tracking context from the message header, if it exists.
            if (trackingEntry != null)
            {
                // If an tracking context exists in the message header, always use it to replace the ambient context.
                TrackingContext tc = TrackingContext.DeSerialize(trackingEntry.ValueBytes);
                tc.SetAsCurrent();
            }
            else
            {
                // If no tracking context exists then create one.
                TrackingContext.NewCurrentIfEmpty();

                Debug.Assert(TrackingContext.Current != null);

                // Copy the tracking context to the message header.
                byte[] byteArray = TrackingContext.Serialize(TrackingContext.Current);
                headers.Add(s_TrackingContextKeyName, byteArray);
            }

            return(headers);
        }
Пример #4
0
        // call immediately after create.
        public async Task __ConnectAndSubscribeAsync(TReceiver receiver, CancellationToken cancellationToken)
        {
            var syncContext     = SynchronizationContext.Current; // capture SynchronizationContext.
            var callResult      = callInvoker.AsyncDuplexStreamingCall <byte[], byte[]>(DuplexStreamingAsyncMethod, host, option);
            var streamingResult = new DuplexStreamingResult <byte[], byte[]>(
                callResult,
                new MarshallingClientStreamWriter <byte[]>(callResult.RequestStream, serializerOptions),
                new MarshallingAsyncStreamReader <byte[]>(callResult.ResponseStream, serializerOptions),
                serializerOptions
                );

            this.connection = streamingResult;
            this.receiver   = receiver;

            // Establish StreamingHub connection between the client and the server.
            Metadata.Entry messageVersion = default;
            try
            {
                // The client can read the response headers before any StreamingHub's message.
                // MagicOnion.Server v4.0.x or before doesn't send any response headers. The client is incompatible with that versions.
                // NOTE: Grpc.Net:
                //           If the channel can not be connected, ResponseHeadersAsync will throw an exception.
                //       C-core:
                //           If the channel can not be connected, ResponseHeadersAsync will **return** an empty metadata.
                var headers = await streamingResult.ResponseHeadersAsync.ConfigureAwait(false);

                messageVersion = headers.FirstOrDefault(x => x.Key == StreamingHubVersionHeaderKey);

                cancellationToken.ThrowIfCancellationRequested();

                // Check message version of StreamingHub.
                if (messageVersion != null && messageVersion.Value != StreamingHubVersionHeaderValue)
                {
                    throw new RpcException(new Status(StatusCode.Internal, $"The message version of StreamingHub mismatch between the client and the server. (ServerVersion={messageVersion?.Value}; Expected={StreamingHubVersionHeaderValue})"));
                }
            }
            catch (RpcException e)
            {
                throw new RpcException(e.Status, $"Failed to connect to StreamingHub '{DuplexStreamingAsyncMethod.ServiceName}'. ({e.Status})");
            }

            var firstMoveNextTask = connection.RawStreamingCall.ResponseStream.MoveNext(CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, cts.Token).Token);

            if (firstMoveNextTask.IsFaulted || messageVersion == null)
            {
                // NOTE: Grpc.Net:
                //           If an error is returned from `StreamingHub.Connect` method on a server-side,
                //           ResponseStream.MoveNext synchronously returns a task that is `IsFaulted = true`.
                //           `ConnectAsync` method should throw an exception here immediately.
                //       C-core:
                //           `firstMoveNextTask` is incomplete task (`IsFaulted = false`) whether ResponseHeadersAsync is failed or not.
                //           If the channel is disconnected or the server returns an error (StatusCode != OK), awaiting the Task will throw an exception.
                await firstMoveNextTask.ConfigureAwait(false);

                // NOTE: C-core: If the execution reaches here, Connect method returns without any error (StatusCode = OK). but MessageVersion isn't provided from the server.
                throw new RpcException(new Status(StatusCode.Internal, $"The message version of StreamingHub is not provided from the server."));
            }

            this.subscription = StartSubscribe(syncContext, firstMoveNextTask);
        }
Пример #5
0
        private ClientInterceptorContext <TRequest, TResponse> SetTraceIdHeader <TRequest, TResponse>(ClientInterceptorContext <TRequest, TResponse> context)
            where TRequest : class
            where TResponse : class
        {
            var header = context.Options.Headers?.Where(p => p.Key == Consts.TraceId).FirstOrDefault();

            if (header == null)
            {
                var serverHeader = ServerCallContextAccessor.Current?.RequestHeaders.Where(p => p.Key == Consts.TraceId).FirstOrDefault();
                var traceId      = serverHeader == null?Guid.NewGuid().ToString() : serverHeader.Value;

                header = new Metadata.Entry(Consts.TraceId, traceId);
                if (context.Options.Headers == null)
                {
                    var meta = new Metadata();
                    meta.Add(header);
                    var callOptions = context.Options.WithHeaders(meta);
                    context = new ClientInterceptorContext <TRequest, TResponse>(context.Method, context.Host, callOptions);
                }
                else
                {
                    context.Options.Headers.Add(header);
                }
            }
            return(context);
        }
        public static bool Validate(ServerCallContext context)
        {
            Metadata.Entry metadataEntry = context.RequestHeaders.FirstOrDefault(m =>
                                                                                 String.Equals(m.Key, ActiveDirectoryCredentialsFactory.AuthorizationHeader, StringComparison.Ordinal));

            if (metadataEntry.Equals(default(Metadata.Entry)) || metadataEntry.Value == null)
            {
                return(false);
            }

            string authorizationHeaderValue = Encoding.UTF8.GetString(Convert.FromBase64String(metadataEntry.Value));

            if (String.IsNullOrWhiteSpace(authorizationHeaderValue))
            {
                return(false);
            }

            var splitedString = authorizationHeaderValue.Split(':');

            if (splitedString.Length < 3)
            {
                return(false);
            }

            string userName = splitedString[0];
            string password = splitedString[1];
            string domain   = splitedString[2];

            return(ValidateLdapCredentials(userName, password, domain));
        }
Пример #7
0
        public override Task <HelloResponse> Hello1(HelloRequest request, ServerCallContext context)
        {
            //check the accessToken
            Metadata.Entry accessToken = null;
            foreach (var entry in context.RequestHeaders)
            {
                if (entry.Key.Equals("accessToken", System.StringComparison.OrdinalIgnoreCase))
                {
                    accessToken = entry;
                    break;
                }
            }
            if (accessToken == null)
            {
                context.Status = new Status(StatusCode.Unauthenticated, "没有权限访问此接口,请检查是否有传递有效的访问令牌");
                return(Task.FromResult <HelloResponse>(null));
            }
            var tokenValue = new JwtBuilder()
                             .WithAlgorithm(new HMACSHA512Algorithm())
                             .WithSecret("*****@*****.**")
                             .MustVerifySignature()
                             .Decode(accessToken.Value);

            Console.WriteLine($"访问令牌:{tokenValue}");

            //say hello
            return(Task.FromResult(new HelloResponse {
                Message = $"hello1 {request.Name} from service1"
            }));
        }
Пример #8
0
        public void KeysAreNormalized_LowercaseKey()
        {
            var lowercaseKey = "abc";
            var entry        = new Metadata.Entry(lowercaseKey, "XYZ");

            // no allocation if key already lowercase
            Assert.AreSame(lowercaseKey, entry.Key);
        }
Пример #9
0
        public void Indexer_Set()
        {
            var metadata = CreateMetadata();
            var entry    = new Metadata.Entry("new-key", "new-value");

            metadata[1] = entry;
            Assert.AreEqual(entry, metadata[1]);
        }
Пример #10
0
        public void CopyTo()
        {
            var metadata = CreateMetadata();
            var array    = new Metadata.Entry[metadata.Count + 1];

            metadata.CopyTo(array, 1);
            Assert.AreEqual(default(Metadata.Entry), array[0]);
            Assert.AreEqual(metadata[0], array[1]);
        }
                static bool Match(Metadata.Entry currentEntry, string headerName, bool isBinary, ref Metadata.Entry?entry)
                {
                    if (!headerName.Equals(currentEntry.Key, StringComparison.OrdinalIgnoreCase) || currentEntry.IsBinary != isBinary)
                    {
                        return(false);
                    }

                    entry ??= currentEntry;
                    return(true);
                }
        private bool HasResourcePrefixHeader(CallSettings callSettings)
        {
            var expectedDatabaseName = DatabaseName.FromProjectInstanceDatabase(
                SpannerClientHelpers.ProjectId, SpannerClientHelpers.Instance,
                SpannerClientHelpers.Database);

            var metadata = new Metadata();
            callSettings.HeaderMutation?.Invoke(metadata);
            Metadata.Entry entry = Assert.Single(metadata, e => e.Key == SpannerClientImpl.ResourcePrefixHeader);
            return expectedDatabaseName.ToString() == entry.Value;
        }
Пример #13
0
        public override AsyncUnaryCall <TResponse> AsyncUnaryCall <TRequest, TResponse>(TRequest request,
                                                                                        ClientInterceptorContext <TRequest, TResponse> context,
                                                                                        AsyncUnaryCallContinuation <TRequest, TResponse> continuation)
        {
            var retryCount = 0;

            Metadata.Entry timeoutInMilliSecondsMetadataEntry = null;
            if (context.Options.Headers != null && context.Options.Headers.Any())
            {
                timeoutInMilliSecondsMetadataEntry = context.Options.Headers.FirstOrDefault(m =>
                                                                                            string.Equals(m.Key, GrpcConstants.TimeoutMetadataKey, StringComparison.Ordinal));
            }

            var timeoutSpan = timeoutInMilliSecondsMetadataEntry == null
                ? TimeSpan.FromMilliseconds(GrpcConstants.DefaultRequestTimeoutInMilliSeconds)
                : TimeSpan.FromMilliseconds(int.Parse(timeoutInMilliSecondsMetadataEntry.Value));

            async Task <TResponse> RetryCallback(Task <TResponse> responseTask)
            {
                var response = responseTask;

                // if no problem occured return
                if (!response.IsFaulted)
                {
                    return(response.Result);
                }

                // if a problem occured but reached the max retries
                if (retryCount == _retryCount)
                {
                    return(response.Result);
                }

                retryCount++;

                // try again
                var retryContext = BuildNewContext(context, timeoutSpan);
                var result       = continuation(request, retryContext).ResponseAsync.ContinueWith(RetryCallback).Unwrap();

                return(await result);
            }

            var newContext           = BuildNewContext(context, timeoutSpan);
            var responseContinuation = continuation(request, newContext);

            var responseAsync = responseContinuation.ResponseAsync.ContinueWith(RetryCallback).Unwrap();

            return(new AsyncUnaryCall <TResponse>(
                       responseAsync,
                       responseContinuation.ResponseHeadersAsync,
                       responseContinuation.GetStatus,
                       responseContinuation.GetTrailers,
                       responseContinuation.Dispose));
        }
Пример #14
0
        private DistributedTracingData getDistributedTracingData(ServerCallContext context)
        {
            Metadata.Entry         metadataEntry          = context.RequestHeaders.FirstOrDefault(m => String.Equals(m.Key.ToLower(), "elastic-apm-traceparent", StringComparison.Ordinal));
            DistributedTracingData distributedTracingData = null;

            if (metadataEntry != null && !metadataEntry.Equals(default(Metadata.Entry)) && metadataEntry.Value != null)
            {
                distributedTracingData = DistributedTracingData.TryDeserializeFromString(metadataEntry.Value);
            }
            return(distributedTracingData);
        }
        public void AsciiEntry()
        {
            var entry = new Metadata.Entry("ABC", "XYZ");
            Assert.IsFalse(entry.IsBinary);
            Assert.AreEqual("abc", entry.Key);  // key is in lowercase.
            Assert.AreEqual("XYZ", entry.Value);
            CollectionAssert.AreEqual(new[] { (byte)'X', (byte)'Y', (byte)'Z' }, entry.ValueBytes);

            Assert.Throws(typeof(ArgumentException), () => new Metadata.Entry("abc-bin", "xyz"));

            Assert.AreEqual("[Entry: key=abc, value=XYZ]", entry.ToString());
        }
Пример #16
0
        public void Entry_Immutable()
        {
            var origBytes = new byte[] { 1, 2, 3 };
            var bytes     = new byte[] { 1, 2, 3 };
            var entry     = new Metadata.Entry("ABC-BIN", bytes);

            bytes[0] = 255;  // changing the array passed to constructor should have any effect.
            CollectionAssert.AreEqual(origBytes, entry.ValueBytes);

            entry.ValueBytes[0] = 255;
            CollectionAssert.AreEqual(origBytes, entry.ValueBytes);
        }
Пример #17
0
        public static bool ContainsHeader(this Metadata metadata, Metadata.Entry entry)
        {
            for (var i = 0; i < metadata.Count; i++)
            {
                if (HeadersAreEqual(metadata[i], entry))
                {
                    return(true);
                }
            }

            return(false);
        }
Пример #18
0
        public override async Task <TResponse> UnaryServerHandler <TRequest, TResponse>(TRequest request,
                                                                                        ServerCallContext context, UnaryServerMethod <TRequest, TResponse> continuation)
        {
            var trace = context.RequestHeaders.FirstOrDefault(q => q.Key == Consts.TraceId);

            if (trace == null)
            {
                trace = new Metadata.Entry(Consts.TraceId, Guid.NewGuid().ToString());
                context.RequestHeaders.Add(trace);
            }
            var model = new MonitorModel
            {
                ClientIp    = context.Peer,
                RequestUrl  = context.Method,
                RequestData = request?.ToJson(),
                TraceId     = trace.Value
            };

            try
            {
                var result = await continuation(request, context);

                model.Status = "ok";

                model.ResponseData = MonitorManager.Instance.SaveResponseMethodEnable(context.Method) ? result?.ToJson() : Consts.NotResponseMsg;

                return(result);
            }
            catch (Exception ex)
            {
                if (ex is AggregateException aex)
                {
                    foreach (var e in aex.Flatten().InnerExceptions)
                    {
                        model.Exception += e?.ToString() + Environment.NewLine;
                    }
                }
                else
                {
                    model.Exception = ex?.ToString();
                }

                model.Status = "error";
                LoggerAccessor.Instance.LoggerError?.Invoke(new Exception(model.Exception));
                throw CommonError.BuildRpcException(ex);
            }
            finally
            {
                model.ResponseTime = DateTime.Now;
                LoggerAccessor.Instance.LoggerMonitor?.Invoke(model.ToJson());
            }
        }
        public void SetsHeaderOnPartitionQuery()
        {
            var invoker = new FakeCallInvoker();
            var client = new SpannerClientBuilder {
                CallInvoker = invoker
            }.Build();

            client.PartitionQuery(new PartitionQueryRequest {
                Session = SampleSessionName
            });
            Metadata.Entry entry = Assert.Single(invoker.Metadata, e => e.Key == ResourcePrefixHeader);
            Assert.Equal(SampleDatabaseName, entry.Value);
        }
Пример #20
0
        public void SetsHeaderOnGetDatabaseDdl()
        {
            var invoker = new FakeCallInvoker();
            var client = new DatabaseAdminClientBuilder {
                CallInvoker = invoker
            }.Build();

            client.GetDatabaseDdl(new GetDatabaseDdlRequest {
                Database = SampleDatabaseName
            });
            Metadata.Entry entry = Assert.Single(invoker.Metadata, e => e.Key == ResourcePrefixHeader);
            Assert.Equal(SampleDatabaseName, entry.Value);
        }
Пример #21
0
        public void SetsHeaderOnRestoreDatabase()
        {
            var invoker = new FakeCallInvoker();
            var client = new DatabaseAdminClientBuilder {
                CallInvoker = invoker
            }.Build();

            client.RestoreDatabase(new RestoreDatabaseRequest {
                Parent = SampleInstanceName
            });
            Metadata.Entry entry = Assert.Single(invoker.Metadata, e => e.Key == ResourcePrefixHeader);
            Assert.Equal(SampleInstanceName, entry.Value);
        }
Пример #22
0
        public void SetsHeaderOnListBackupOperations()
        {
            var invoker = new FakeCallInvoker();
            var client = new DatabaseAdminClientBuilder {
                CallInvoker = invoker
            }.Build();

            client.ListBackupOperations(new ListBackupOperationsRequest {
                Parent = SampleInstanceName
            }).AsRawResponses().First();
            Metadata.Entry entry = Assert.Single(invoker.Metadata, e => e.Key == ResourcePrefixHeader);
            Assert.Equal(SampleInstanceName, entry.Value);
        }
        public void SetsHeaderOnBatchCreateSessions()
        {
            var invoker = new FakeCallInvoker();
            var client = new SpannerClientBuilder {
                CallInvoker = invoker
            }.Build();

            client.BatchCreateSessions(new BatchCreateSessionsRequest {
                Database = SampleDatabaseName
            });
            Metadata.Entry entry = Assert.Single(invoker.Metadata, e => e.Key == ResourcePrefixHeader);
            Assert.Equal(SampleDatabaseName, entry.Value);
        }
Пример #24
0
        public void AsciiEntry()
        {
            var entry = new Metadata.Entry("ABC", "XYZ");

            Assert.IsFalse(entry.IsBinary);
            Assert.AreEqual("abc", entry.Key);  // key is in lowercase.
            Assert.AreEqual("XYZ", entry.Value);
            CollectionAssert.AreEqual(new[] { (byte)'X', (byte)'Y', (byte)'Z' }, entry.ValueBytes);

            Assert.Throws(typeof(ArgumentException), () => new Metadata.Entry("abc-bin", "xyz"));

            Assert.AreEqual("[Entry: key=abc, value=XYZ]", entry.ToString());
        }
        public void SetsHeaderOnGetInstanceConfig()
        {
            var invoker = new FakeCallInvoker();
            var client = new InstanceAdminClientBuilder {
                CallInvoker = invoker
            }.Build();

            client.GetInstanceConfig(new GetInstanceConfigRequest {
                Name = SampleInstanceConfigName
            });
            Metadata.Entry entry = Assert.Single(invoker.Metadata, e => e.Key == ResourcePrefixHeader);
            Assert.Equal(SampleProjectName, entry.Value);
        }
        public void BinaryEntry()
        {
            var bytes = new byte[] { 1, 2, 3 };
            var entry = new Metadata.Entry("ABC-BIN", bytes);
            Assert.IsTrue(entry.IsBinary);
            Assert.AreEqual("abc-bin", entry.Key);  // key is in lowercase.
            Assert.Throws(typeof(InvalidOperationException), () => { var v = entry.Value; });
            CollectionAssert.AreEqual(bytes, entry.ValueBytes);

            Assert.Throws(typeof(ArgumentException), () => new Metadata.Entry("abc", bytes));

            Assert.AreEqual("[Entry: key=abc-bin, valueBytes=System.Byte[]]", entry.ToString());
        }
Пример #27
0
        public void BinaryEntry()
        {
            var bytes = new byte[] { 1, 2, 3 };
            var entry = new Metadata.Entry("ABC-BIN", bytes);

            Assert.IsTrue(entry.IsBinary);
            Assert.AreEqual("abc-bin", entry.Key);  // key is in lowercase.
            Assert.Throws(typeof(InvalidOperationException), () => { var v = entry.Value; });
            CollectionAssert.AreEqual(bytes, entry.ValueBytes);

            Assert.Throws(typeof(ArgumentException), () => new Metadata.Entry("abc", bytes));

            Assert.AreEqual("[Entry: key=abc-bin, valueBytes=System.Byte[]]", entry.ToString());
        }
Пример #28
0
        /// <summary>
        /// Gets blockID from list of metadata
        /// </summary>
        /// <param name="metaData">List of metadata</param>
        /// <returns>BlockID</returns>
        public static Guid GetBlockID(List <Metadata.Entry> metaData)
        {
            Guid id = new Guid();

            try
            {
                Metadata.Entry blckId = metaData.Find(m => { return(m.Key == "blockid"); });
                id = Guid.Parse(blckId.Value);
                return(id);
            }
            catch
            {
                return(id);
            }
        }
Пример #29
0
        /// <summary>
        /// Gets block size from list of metadata
        /// </summary>
        /// <param name="metaData">List of metadata</param>
        /// <returns>Size of block</returns>
        public static int GetBlockSize(List <Metadata.Entry> metaData)
        {
            int blockSize = 0;

            try
            {
                Metadata.Entry size = metaData.Find(m => { return(m.Key == "blocksize"); });
                blockSize = Convert.ToInt32(size.Value);
                return(blockSize);
            }
            catch
            {
                return(blockSize);
            }
        }
Пример #30
0
        public void FreezeMakesReadOnly()
        {
            var entry    = new Metadata.Entry("new-key", "new-value");
            var metadata = CreateMetadata().Freeze();

            Assert.IsTrue(metadata.IsReadOnly);
            Assert.Throws <InvalidOperationException>(() => metadata.Insert(0, entry));
            Assert.Throws <InvalidOperationException>(() => metadata.RemoveAt(0));
            Assert.Throws <InvalidOperationException>(() => metadata[0] = entry);
            Assert.Throws <InvalidOperationException>(() => metadata.Add(entry));
            Assert.Throws <InvalidOperationException>(() => metadata.Add("new-key", "new-value"));
            Assert.Throws <InvalidOperationException>(() => metadata.Add("new-key-bin", new byte[] { 0xaa }));
            Assert.Throws <InvalidOperationException>(() => metadata.Clear());
            Assert.Throws <InvalidOperationException>(() => metadata.Remove(metadata[0]));
        }
Пример #31
0
        private static bool HeadersAreEqual(Metadata.Entry x, Metadata.Entry y)
        {
            if (!string.Equals(x.Key, y.Key, StringComparison.OrdinalIgnoreCase) ||
                x.IsBinary != y.IsBinary)
            {
                return(false);
            }

            if (x.IsBinary)
            {
                return(SequenceEqual(x.ValueBytes, y.ValueBytes));
            }

            return(string.Equals(x.Value, y.Value, StringComparison.Ordinal));
        }
Пример #32
0
        public void SetsHeaderOnUpdateBackup()
        {
            var invoker = new FakeCallInvoker();
            var client = new DatabaseAdminClientBuilder {
                CallInvoker = invoker
            }.Build();

            client.UpdateBackup(new UpdateBackupRequest
            {
                Backup = new Backup {
                    Name = SampleBackupName
                }
            });
            Metadata.Entry entry = Assert.Single(invoker.Metadata, e => e.Key == ResourcePrefixHeader);
            Assert.Equal(SampleInstanceName, entry.Value);
        }
        public void Entry_Immutable()
        {
            var origBytes = new byte[] { 1, 2, 3 };
            var bytes = new byte[] { 1, 2, 3 };
            var entry = new Metadata.Entry("ABC-BIN", bytes);
            bytes[0] = 255;  // changing the array passed to constructor should have any effect.
            CollectionAssert.AreEqual(origBytes, entry.ValueBytes);

            entry.ValueBytes[0] = 255;
            CollectionAssert.AreEqual(origBytes, entry.ValueBytes);
        }
        public void FreezeMakesReadOnly()
        {
            var entry = new Metadata.Entry("new-key", "new-value");
            var metadata = CreateMetadata().Freeze();

            Assert.IsTrue(metadata.IsReadOnly);
            Assert.Throws<InvalidOperationException>(() => metadata.Insert(0, entry));
            Assert.Throws<InvalidOperationException>(() => metadata.RemoveAt(0));
            Assert.Throws<InvalidOperationException>(() => metadata[0] = entry);
            Assert.Throws<InvalidOperationException>(() => metadata.Add(entry));
            Assert.Throws<InvalidOperationException>(() => metadata.Add("new-key", "new-value"));
            Assert.Throws<InvalidOperationException>(() => metadata.Add("new-key-bin", new byte[] { 0xaa }));
            Assert.Throws<InvalidOperationException>(() => metadata.Clear());
            Assert.Throws<InvalidOperationException>(() => metadata.Remove(metadata[0]));
        }
        public void CopyTo()
        {
            var metadata = CreateMetadata();
            var array = new Metadata.Entry[metadata.Count + 1];

            metadata.CopyTo(array, 1);
            Assert.AreEqual(default(Metadata.Entry), array[0]);
            Assert.AreEqual(metadata[0], array[1]);
        }
        public void Indexer_Set()
        {
            var metadata = CreateMetadata();
            var entry = new Metadata.Entry("new-key", "new-value");

            metadata[1] = entry;
            Assert.AreEqual(entry, metadata[1]);
        }