< Summary - CoreWCF Coverage — PR #1766

Information
Class: CoreWCF.Dispatcher.DispatchOperationRuntime
Assembly: CoreWCF.Primitives
File(s): /home/runner/work/CoreWCF/CoreWCF/src/CoreWCF.Primitives/src/CoreWCF/Dispatcher/DispatchOperationRuntime.cs
Line coverage
71%
Covered lines: 148
Uncovered lines: 60
Coverable lines: 208
Total lines: 585
Line coverage: 71.1%
Branch coverage
63%
Covered branches: 82
Total branches: 130
Branch coverage: 63%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Cyclomatic complexity NPath complexity Sequence coverage
.ctor(...)63.63%222288.23%
DeserializeInputs(...)41.66%121259.09%
InitializeCallContext(...)100%22100%
InitializeCallContextCore(...)33.33%6653.84%
UninitializeCallContext(...)100%22100%
UninitializeCallContextCore(...)100%2270%
InspectInputs(...)50%2266.66%
InspectInputsCore(...)0%220%
InspectOutputs(...)50%2266.66%
InspectOutputsCore(...)0%220%
InvokeAsync()100%6695.23%
>c__DisplayClass71_0/<<InvokeAsync()100%11100%
SetClaimsPrincipalToOperationContext(...)70%101083.33%
SerializeOutputs(...)50%323242.3%
ValidateInstanceType(...)50%2240%
ValidateMustUnderstand(...)75%4466.66%
ValidateAuthorizedClaims(...)75%161688.88%
IsClaimFound(...)100%66100%

File(s)

/home/runner/work/CoreWCF/CoreWCF/src/CoreWCF.Primitives/src/CoreWCF/Dispatcher/DispatchOperationRuntime.cs

#LineLine coverage
 1// Licensed to the .NET Foundation under one or more agreements.
 2// The .NET Foundation licenses this file to you under the MIT license.
 3
 4using System;
 5using System.Collections.Concurrent;
 6using System.Collections.Generic;
 7using System.Collections.ObjectModel;
 8using System.Globalization;
 9using System.Reflection;
 10using System.Runtime.ExceptionServices;
 11using System.Security.Claims;
 12using System.Threading.Tasks;
 13using CoreWCF.Channels;
 14using CoreWCF.IdentityModel.Policy;
 15using CoreWCF.Runtime;
 16using CoreWCF.Security;
 17using Microsoft.AspNetCore.Authorization;
 18using Claim = CoreWCF.IdentityModel.Claims.Claim;
 19
 20namespace CoreWCF.Dispatcher
 21{
 22    internal class DispatchOperationRuntime
 23    {
 24        private readonly bool _isSessionOpenNotificationEnabled;
 25
 26        //readonly bool transactionAutoComplete;
 27        //readonly bool transactionRequired;
 28        private readonly bool _deserializeRequest;
 29
 30        //readonly bool isInsideTransactedReceiveScope;
 31
 367432        internal DispatchOperationRuntime(DispatchOperation operation, ImmutableDispatchRuntime parent)
 33        {
 367434            if (operation == null)
 35            {
 036                throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull(nameof(operation));
 37            }
 38
 367439            if (operation.Invoker == null)
 40            {
 041                throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new InvalidOperationException(SR.RuntimeRequir
 42            }
 43
 367444            DisposeParameters = ((operation.AutoDisposeParameters) && (!operation.HasNoDisposableParameters));
 367445            Parent = parent ?? throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull(nameof(parent));
 367446            CallContextInitializers = EmptyArray<ICallContextInitializer>.ToArray(operation.CallContextInitializers);
 367447            ParameterInspectors = EmptyArray<IParameterInspector>.ToArray(operation.ParameterInspectors);
 367448            FaultFormatter = operation.FaultFormatter;
 367449            Impersonation = operation.Impersonation;
 367450            AuthorizeClaims = operation.AuthorizeClaims;
 367451            AuthorizationPolicy = operation.AuthorizationPolicy;
 367452            _deserializeRequest = operation.DeserializeRequest;
 367453            SerializeReply = operation.SerializeReply;
 367454            Formatter = operation.Formatter;
 367455            Invoker = operation.Invoker;
 367456            IsTerminating = operation.IsTerminating;
 367457            _isSessionOpenNotificationEnabled = operation.IsSessionOpenNotificationEnabled;
 367458            Action = operation.Action;
 367459            Name = operation.Name;
 367460            ReleaseInstanceAfterCall = operation.ReleaseInstanceAfterCall;
 367461            ReleaseInstanceBeforeCall = operation.ReleaseInstanceBeforeCall;
 367462            ReplyAction = operation.ReplyAction;
 367463            IsOneWay = operation.IsOneWay;
 367464            ReceiveContextAcknowledgementMode = operation.ReceiveContextAcknowledgementMode;
 65
 367466            if (Formatter == null && (_deserializeRequest || SerializeReply))
 67            {
 068                throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new InvalidOperationException(SR.Format(SR.Dis
 69            }
 70
 367471            if ((operation.Parent.InstanceProvider == null) && (operation.Parent.Type != null))
 72            {
 96673                if (Invoker is SyncMethodInvoker sync)
 74                {
 375                    ValidateInstanceType(operation.Parent.Type, sync.Method);
 76                }
 77
 78                //AsyncMethodInvoker async = this.invoker as AsyncMethodInvoker;
 79                //if (async != null)
 80                //{
 81                //    this.ValidateInstanceType(operation.Parent.Type, async.BeginMethod);
 82                //    this.ValidateInstanceType(operation.Parent.Type, async.EndMethod);
 83                //}
 84
 96685                if (Invoker is TaskMethodInvoker task)
 86                {
 087                    ValidateInstanceType(operation.Parent.Type, task.TaskMethod);
 88                }
 89            }
 367490        }
 91
 292        internal string Action { get; }
 93
 507094        internal ICallContextInitializer[] CallContextInitializers { get; }
 95
 253896        internal bool DisposeParameters { get; }
 97
 98        internal bool HasDefaultUnhandledActionInvoker
 99        {
 0100            get { return (Invoker is DispatchRuntime.UnhandledActionInvoker); }
 101        }
 102
 681103        internal bool SerializeReply { get; }
 104
 103105        internal IDispatchFaultFormatter FaultFormatter { get; }
 106
 6763107        internal IDispatchMessageFormatter Formatter { get; }
 108
 4972109        internal ImpersonationOption Impersonation { get; }
 110
 2524111        internal ConcurrentDictionary<string, List<Claim>> AuthorizeClaims { get; }
 112
 55113        internal Lazy<AuthorizationPolicy> AuthorizationPolicy { get; }
 114
 6935115        internal IOperationInvoker Invoker { get; }
 116
 15065117        internal bool IsOneWay { get; }
 118
 2549119        internal bool IsTerminating { get; }
 120
 0121        internal string Name { get; }
 122
 4879123        internal IParameterInspector[] ParameterInspectors { get; }
 124
 17050125        internal ImmutableDispatchRuntime Parent { get; }
 126
 0127        internal ReceiveContextAcknowledgementMode ReceiveContextAcknowledgementMode { get; }
 128
 2536129        internal bool ReleaseInstanceAfterCall { get; }
 130
 2536131        internal bool ReleaseInstanceBeforeCall { get; }
 132
 10133        internal string ReplyAction { get; }
 134
 135        //internal bool TransactionAutoComplete
 136        //{
 137        //    get { return this.transactionAutoComplete; }
 138        //}
 139
 140        //internal bool TransactionRequired
 141        //{
 142        //    get { return this.transactionRequired; }
 143        //}
 144
 145        //internal bool IsInsideTransactedReceiveScope
 146        //{
 147        //    get { return this.isInsideTransactedReceiveScope; }
 148        //}
 149
 150        private void DeserializeInputs(MessageRpc rpc)
 151        {
 152            //bool success = false;
 153            try
 154            {
 155                try
 156                {
 2505157                    rpc.InputParameters = Invoker.AllocateInputs();
 2505158                }
 0159                catch (Exception e)
 160                {
 0161                    if (Fx.IsFatal(e))
 162                    {
 0163                        throw;
 164                    }
 0165                    if (ErrorBehavior.ShouldRethrowExceptionAsIs(e))
 166                    {
 0167                        throw;
 168                    }
 0169                    throw DiagnosticUtility.ExceptionUtility.ThrowHelperCallback(e);
 170                }
 171                try
 172                {
 173                    // If the field is true, then this operation is to be invoked at the time the service
 174                    // channel is opened. The incoming message is created at ChannelHandler level with no
 175                    // content, so we don't need to deserialize the message.
 2505176                    if (!_isSessionOpenNotificationEnabled)
 177                    {
 2504178                        if (_deserializeRequest)
 179                        {
 180                            //if (TD.DispatchFormatterDeserializeRequestStartIsEnabled())
 181                            //{
 182                            //    TD.DispatchFormatterDeserializeRequestStart(rpc.EventTraceActivity);
 183                            //}
 184
 2481185                            Formatter.DeserializeRequest(rpc.Request, rpc.InputParameters);
 186
 187                            //if (TD.DispatchFormatterDeserializeRequestStopIsEnabled())
 188                            //{
 189                            //    TD.DispatchFormatterDeserializeRequestStop(rpc.EventTraceActivity);
 190                            //}
 191                        }
 192                        else
 193                        {
 23194                            rpc.InputParameters[0] = rpc.Request;
 195                        }
 196                    }
 197
 198                    //success = true;
 2504199                }
 1200                catch (Exception e)
 201                {
 1202                    if (Fx.IsFatal(e))
 203                    {
 0204                        throw;
 205                    }
 1206                    if (ErrorBehavior.ShouldRethrowExceptionAsIs(e))
 207                    {
 1208                        throw;
 209                    }
 0210                    throw DiagnosticUtility.ExceptionUtility.ThrowHelperCallback(e);
 211                }
 212            }
 213            finally
 214            {
 2505215                rpc.DidDeserializeRequestBody = (rpc.Request.State != MessageState.Created);
 216
 217                //if (!success && MessageLogger.LoggingEnabled)
 218                //{
 219                //    MessageLogger.LogMessage(rpc.Request, MessageLoggingSource.Malformed);
 220                //}
 2505221            }
 2504222        }
 223
 224        private void InitializeCallContext(MessageRpc rpc)
 225        {
 2505226            if (CallContextInitializers.Length > 0)
 227            {
 12228                InitializeCallContextCore(rpc);
 229            }
 2505230        }
 231
 232        private void InitializeCallContextCore(MessageRpc rpc)
 233        {
 12234            IClientChannel channel = rpc.Channel.Proxy as IClientChannel;
 12235            int offset = Parent.CallContextCorrelationOffset;
 236
 237            try
 238            {
 48239                for (int i = 0; i < rpc.Operation.CallContextInitializers.Length; i++)
 240                {
 12241                    ICallContextInitializer initializer = CallContextInitializers[i];
 12242                    rpc.Correlation[offset + i] = initializer.BeforeInvoke(rpc.InstanceContext, channel, rpc.Request);
 243                }
 12244            }
 0245            catch (Exception e)
 246            {
 0247                if (Fx.IsFatal(e))
 248                {
 0249                    throw;
 250                }
 0251                if (ErrorBehavior.ShouldRethrowExceptionAsIs(e))
 252                {
 0253                    throw;
 254                }
 0255                throw DiagnosticUtility.ExceptionUtility.ThrowHelperCallback(e);
 256            }
 12257        }
 258
 259        private void UninitializeCallContext(MessageRpc rpc)
 260        {
 2505261            if (CallContextInitializers.Length > 0)
 262            {
 12263                UninitializeCallContextCore(rpc);
 264            }
 2505265        }
 266
 267        private void UninitializeCallContextCore(MessageRpc rpc)
 268        {
 12269            IClientChannel channel = rpc.Channel.Proxy as IClientChannel;
 12270            int offset = Parent.CallContextCorrelationOffset;
 271
 272            try
 273            {
 48274                for (int i = CallContextInitializers.Length - 1; i >= 0; i--)
 275                {
 12276                    ICallContextInitializer initializer = CallContextInitializers[i];
 12277                    initializer.AfterInvoke(rpc.Correlation[offset + i]);
 278                }
 12279            }
 0280            catch (Exception e)
 281            {
 282                // thread-local storage may be corrupt
 0283                DiagnosticUtility.FailFast(string.Format(CultureInfo.InvariantCulture, "ICallContextInitializer.BeforeIn
 0284            }
 12285        }
 286
 287        private void InspectInputs(MessageRpc rpc)
 288        {
 2504289            if (ParameterInspectors.Length > 0)
 290            {
 0291                InspectInputsCore(rpc);
 292            }
 2504293        }
 294
 295        private void InspectInputsCore(MessageRpc rpc)
 296        {
 0297            int offset = Parent.ParameterInspectorCorrelationOffset;
 298
 0299            for (int i = 0; i < ParameterInspectors.Length; i++)
 300            {
 0301                IParameterInspector inspector = ParameterInspectors[i];
 0302                rpc.Correlation[offset + i] = inspector.BeforeCall(Name, rpc.InputParameters);
 303                //if (TD.ParameterInspectorBeforeCallInvokedIsEnabled())
 304                //{
 305                //    TD.ParameterInspectorBeforeCallInvoked(rpc.EventTraceActivity, this.ParameterInspectors[i].GetType
 306                //}
 307            }
 0308        }
 309
 310        private void InspectOutputs(MessageRpc rpc)
 311        {
 2375312            if (ParameterInspectors.Length > 0)
 313            {
 0314                InspectOutputsCore(rpc);
 315            }
 2375316        }
 317
 318        private void InspectOutputsCore(MessageRpc rpc)
 319        {
 0320            int offset = Parent.ParameterInspectorCorrelationOffset;
 321
 0322            for (int i = ParameterInspectors.Length - 1; i >= 0; i--)
 323            {
 0324                IParameterInspector inspector = ParameterInspectors[i];
 0325                inspector.AfterCall(Name, rpc.OutputParameters, rpc.ReturnParameter, rpc.Correlation[offset + i]);
 326                //if (TD.ParameterInspectorAfterCallInvokedIsEnabled())
 327                //{
 328                //    TD.ParameterInspectorAfterCallInvoked(rpc.EventTraceActivity, this.ParameterInspectors[i].GetType(
 329                //}
 330            }
 0331        }
 332
 333        internal async Task<MessageRpc> InvokeAsync(MessageRpc rpc)
 334        {
 2505335            if (rpc.Error == null)
 336            {
 337                try
 338                {
 2505339                    InitializeCallContext(rpc);
 2505340                    object target = rpc.Instance;
 2505341                    DeserializeInputs(rpc);
 2504342                    InspectInputs(rpc);
 2504343                    ValidateMustUnderstand(rpc);
 2504344                    ValidateAuthorizedClaims(rpc);
 345
 2498346                    if (Parent.RequireClaimsPrincipalOnOperationContext)
 347                    {
 25348                        SetClaimsPrincipalToOperationContext(rpc);
 349                    }
 350
 2498351                    if (Parent.SecurityImpersonation != null)
 352                    {
 2486353                        await Parent.SecurityImpersonation.RunImpersonated(rpc, async () =>
 2486354                        {
 2486355                            (rpc.ReturnParameter, rpc.OutputParameters) = await Invoker.InvokeAsync(target, rpc.InputPar
 4849356                        });
 357                    }
 358                    else
 359                    {
 12360                        (rpc.ReturnParameter, rpc.OutputParameters) = await Invoker.InvokeAsync(target, rpc.InputParamet
 361                    }
 362
 2375363                    InspectOutputs(rpc);
 2375364                    SerializeOutputs(rpc);
 2375365                }
 366#pragma warning disable CA1031 // Do not catch general exception types
 367                catch (Exception ex)
 368                {
 130369                    ExceptionDispatchInfo.Capture(ex).Throw();
 0370                } // Make sure user Exception filters are not impersonated.
 371#pragma warning restore CA1031 // Do not catch general exception types
 372                finally
 373                {
 2505374                    UninitializeCallContext(rpc);
 375                }
 376            }
 377
 2375378            return rpc;
 2375379        }
 380
 381        internal void SetClaimsPrincipalToOperationContext(MessageRpc rpc)
 382        {
 80383            ServiceSecurityContext securityContext = rpc.SecurityContext;
 80384            if (!rpc.HasSecurityContext)
 385            {
 80386                SecurityMessageProperty securityContextProperty = rpc.Request.Properties.Security;
 80387                if (securityContextProperty != null)
 388                {
 80389                    securityContext = securityContextProperty.ServiceSecurityContext;
 390                }
 391            }
 392
 80393            if (securityContext != null)
 394            {
 395                object principal;
 80396                if (securityContext.AuthorizationContext.Properties.TryGetValue(IdentityModel.Tokens.AuthorizationPolicy
 397                {
 80398                    ClaimsPrincipal claimsPrincipal = principal as ClaimsPrincipal;
 80399                    if (claimsPrincipal != null)
 400                    {
 401                        //
 402                        // Always set ClaimsPrincipal to OperationContext.Current if identityModel pipeline is used.
 403                        //
 80404                        OperationContext.Current.ClaimsPrincipal = claimsPrincipal;
 405                    }
 406                    else
 407                    {
 0408                        throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new InvalidOperationException(SR.NoPri
 409                    }
 410                }
 411            }
 0412        }
 413
 414        private void SerializeOutputs(MessageRpc rpc)
 415        {
 2375416            if (!IsOneWay && Parent.EnableFaults)
 417            {
 418                Message reply;
 618419                if (SerializeReply)
 420                {
 421                    try
 422                    {
 423                        //if (TD.DispatchFormatterSerializeReplyStartIsEnabled())
 424                        //{
 425                        //    TD.DispatchFormatterSerializeReplyStart(rpc.EventTraceActivity);
 426                        //}
 427
 608428                        reply = Formatter.SerializeReply(rpc.RequestVersion, rpc.OutputParameters, rpc.ReturnParameter);
 429
 430                        //if (TD.DispatchFormatterSerializeReplyStopIsEnabled())
 431                        //{
 432                        //    TD.DispatchFormatterSerializeReplyStop(rpc.EventTraceActivity);
 433                        //}
 608434                    }
 0435                    catch (Exception e)
 436                    {
 0437                        if (Fx.IsFatal(e))
 438                        {
 0439                            throw;
 440                        }
 0441                        if (ErrorBehavior.ShouldRethrowExceptionAsIs(e))
 442                        {
 0443                            throw;
 444                        }
 0445                        throw DiagnosticUtility.ExceptionUtility.ThrowHelperCallback(e);
 446                    }
 447
 608448                    if (reply == null)
 449                    {
 0450                        string message = SR.Format(SR.SFxNullReplyFromFormatter2, Formatter.GetType().ToString(), (Name 
 0451                        ErrorBehavior.ThrowAndCatch(new InvalidOperationException(message));
 452                    }
 453                }
 454                else
 455                {
 10456                    if ((rpc.ReturnParameter == null) && (rpc.OperationContext.RequestContext != null))
 457                    {
 0458                        string message = SR.Format(SR.SFxDispatchRuntimeMessageCannotBeNull, Name);
 0459                        ErrorBehavior.ThrowAndCatch(new InvalidOperationException(message));
 460                    }
 461
 10462                    reply = (Message)rpc.ReturnParameter;
 463
 10464                    if ((reply != null) && (!ProxyOperationRuntime.IsValidAction(reply, ReplyAction)))
 465                    {
 0466                        throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new InvalidOperationException(SR.Forma
 467                    }
 468                }
 469
 470                //if (DiagnosticUtility.ShouldUseActivity && rpc.Activity != null && reply != null)
 471                //{
 472                //    TraceUtility.SetActivity(reply, rpc.Activity);
 473                //    if (TraceUtility.ShouldPropagateActivity)
 474                //    {
 475                //        TraceUtility.AddActivityHeader(reply);
 476                //    }
 477                //}
 478                //else if (TraceUtility.ShouldPropagateActivity && reply != null && rpc.ResponseActivityId != Guid.Empty
 479                //{
 480                //    ActivityIdHeader header = new ActivityIdHeader(rpc.ResponseActivityId);
 481                //    header.AddTo(reply);
 482                //}
 483
 484                //rely on the property set during the message receive to correlate the trace
 485                //if (TraceUtility.MessageFlowTracingOnly)
 486                //{
 487                //    //Guard against MEX scenarios where the message is closed by now
 488                //    if (null != rpc.OperationContext.IncomingMessage && MessageState.Closed != rpc.OperationContext.In
 489                //    {
 490                //        FxTrace.Trace.SetAndTraceTransfer(TraceUtility.GetReceivedActivityId(rpc.OperationContext), tr
 491                //    }
 492                //    else
 493                //    {
 494                //        if (rpc.ResponseActivityId != Guid.Empty)
 495                //        {
 496                //            FxTrace.Trace.SetAndTraceTransfer(rpc.ResponseActivityId, true);
 497                //        }
 498                //    }
 499                //}
 500
 501                // Add the ImpersonateOnSerializingReplyMessageProperty on the reply message iff
 502                // a. reply message is not null.
 503                // b. Impersonation is enabled on serializing Reply
 504
 618505                if (reply != null && Parent.IsImpersonationEnabledOnSerializingReply)
 506                {
 0507                    bool shouldImpersonate = Parent.SecurityImpersonation != null && Parent.SecurityImpersonation.IsImpe
 0508                    if (shouldImpersonate)
 509                    {
 0510                        reply.Properties.Add(ImpersonateOnSerializingReplyMessageProperty.Name, new ImpersonateOnSeriali
 0511                        reply = new ImpersonatingMessage(reply);
 512                    }
 513                }
 514
 515                //if (MessageLogger.LoggingEnabled && null != reply)
 516                //{
 517                //    MessageLogger.LogMessage(ref reply, MessageLoggingSource.ServiceLevelSendReply | MessageLoggingSou
 518                //}
 618519                rpc.Reply = reply;
 520            }
 2375521        }
 522
 523        private void ValidateInstanceType(Type type, MethodInfo method)
 524        {
 3525            if (!method.DeclaringType.IsAssignableFrom(type))
 526            {
 0527                string message = SR.Format(SR.SFxMethodNotSupportedByType2, type.FullName,
 0528                                              method.DeclaringType.FullName);
 529
 0530                throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new InvalidOperationException(message));
 531            }
 3532        }
 533
 534        private void ValidateMustUnderstand(MessageRpc rpc)
 535        {
 2504536            if (Parent.ValidateMustUnderstand)
 537            {
 2504538                rpc.NotUnderstoodHeaders = rpc.Request.Headers.GetHeadersNotUnderstood();
 2504539                if (rpc.NotUnderstoodHeaders != null)
 540                {
 0541                    throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(
 0542                        new MustUnderstandSoapException(rpc.NotUnderstoodHeaders, rpc.Request.Version.Envelope));
 543                }
 544            }
 2504545        }
 546
 547        private void ValidateAuthorizedClaims(MessageRpc rpc)
 548        {
 2504549            if (AuthorizeClaims.IsEmpty || AuthorizeClaims.Keys.Count == 0)
 550            {
 2494551                return;
 552            }
 553
 10554            if (rpc.OperationContext?.ServiceSecurityContext?.AuthorizationContext == null)
 555            {
 0556                throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(AuthorizationBehavior.CreateAccessDeniedFaultE
 557            }
 558
 36559            foreach (var eachAuthClaim in AuthorizeClaims)
 560            {
 11561                List<Claim> allClaims = eachAuthClaim.Value;
 11562                if(!IsClaimFound(rpc.OperationContext?.ServiceSecurityContext.AuthorizationContext.ClaimSets, allClaims)
 563                {
 6564                    throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(AuthorizationBehavior.CreateAccessDeniedFa
 565                }
 566            }
 4567        }
 568
 569        private bool IsClaimFound(ReadOnlyCollection<IdentityModel.Claims.ClaimSet> claimSets, List<Claim> anyClaims)
 570        {
 43571            foreach (var claim in anyClaims)
 572            {
 47573                foreach (var claimSet in claimSets)
 574                {
 13575                    if (claimSet.ContainsClaim(claim))
 576                    {
 5577                        return true;
 578                    }
 579                }
 580            }
 581
 6582            return false;
 5583        }
 584    }
 585}