1 // ==++== 2 // 3 // Copyright (c) Microsoft Corporation. All rights reserved. 4 // 5 // ==--== 6 // Implementation of CallContext ... currently leverages off 7 // the LocalDataStore facility. 8 namespace System.Runtime.Remoting.Messaging{ 9 10 using System.Threading; 11 using System.Runtime.Remoting; 12 using System.Security.Principal; 13 using System.Collections; 14 using System.Runtime.Serialization; 15 using System.Security.Permissions; 16 // This class exposes the API for the users of call context. All methods 17 // in CallContext are static and operate upon the call context in the Thread. 18 // NOTE: CallContext is a specialized form of something that behaves like 19 // TLS for method calls. However, since the call objects may get serialized 20 // and deserialized along the path, it is tough to guarantee identity 21 // preservation. 22 // The LogicalCallContext class has all the actual functionality. We have 23 // to use this scheme because Remoting message sinks etc do need to have 24 // the distinction between the call context on the physical thread and 25 // the call context that the remoting message actually carries. In most cases 26 // they will operate on the message's call context and hence the latter 27 // exposes the same set of methods as instance methods. 28 29 // Only statics does not need to marked with the serializable attribute 30 [System.Security.SecurityCritical] // auto-generated_required 31 [Serializable] 32 [System.Runtime.InteropServices.ComVisible(true)] 33 public sealed class CallContext 34 { CallContext()35 private CallContext() 36 { 37 } 38 39 #if MONO SetCurrentCallContext(LogicalCallContext ctx)40 internal static object SetCurrentCallContext (LogicalCallContext ctx) 41 { 42 return null; 43 } 44 #endif 45 46 // Sets the given logical call context object on the thread. 47 // Returns the previous one. SetLogicalCallContext( LogicalCallContext callCtx)48 internal static LogicalCallContext SetLogicalCallContext( 49 LogicalCallContext callCtx) 50 { 51 ExecutionContext ec = Thread.CurrentThread.GetMutableExecutionContext(); 52 LogicalCallContext prev = ec.LogicalCallContext; 53 ec.LogicalCallContext = callCtx; 54 return prev; 55 } 56 57 /*========================================================================= 58 ** Frees a named data slot. 59 =========================================================================*/ 60 [System.Security.SecurityCritical] // auto-generated FreeNamedDataSlot(String name)61 public static void FreeNamedDataSlot(String name) 62 { 63 ExecutionContext ec = Thread.CurrentThread.GetMutableExecutionContext(); 64 ec.LogicalCallContext.FreeNamedDataSlot(name); 65 ec.IllogicalCallContext.FreeNamedDataSlot(name); 66 } 67 68 /*========================================================================= 69 ** Get data on the logical call context 70 =========================================================================*/ 71 [System.Security.SecurityCritical] // auto-generated LogicalGetData(String name)72 public static Object LogicalGetData(String name) 73 { 74 return Thread.CurrentThread.GetExecutionContextReader().LogicalCallContext.GetData(name); 75 } 76 77 /*========================================================================= 78 ** Get data on the illogical call context 79 =========================================================================*/ IllogicalGetData(String name)80 private static Object IllogicalGetData(String name) 81 { 82 return Thread.CurrentThread.GetExecutionContextReader().IllogicalCallContext.GetData(name); 83 } 84 85 internal static IPrincipal Principal 86 { 87 [System.Security.SecurityCritical] // auto-generated 88 get 89 { 90 return Thread.CurrentThread.GetExecutionContextReader().LogicalCallContext.Principal; 91 } 92 93 [System.Security.SecurityCritical] // auto-generated 94 set 95 { 96 Thread.CurrentThread. 97 GetMutableExecutionContext().LogicalCallContext.Principal = value; 98 } 99 } 100 101 public static Object HostContext 102 { 103 [System.Security.SecurityCritical] // auto-generated 104 get 105 { 106 ExecutionContext.Reader ec = Thread.CurrentThread.GetExecutionContextReader(); 107 108 Object hC = ec.IllogicalCallContext.HostContext; 109 if (hC == null) 110 hC = ec.LogicalCallContext.HostContext; 111 return hC; 112 } 113 [System.Security.SecurityCritical] // auto-generated_required 114 set 115 { 116 ExecutionContext ec = Thread.CurrentThread.GetMutableExecutionContext(); 117 if (value is ILogicalThreadAffinative) 118 { 119 ec.IllogicalCallContext.HostContext = null; 120 ec.LogicalCallContext.HostContext = value; 121 } 122 else 123 { 124 ec.IllogicalCallContext.HostContext = value; 125 ec.LogicalCallContext.HostContext = null; 126 } 127 } 128 } 129 130 // < 131 132 133 [System.Security.SecurityCritical] // auto-generated GetData(String name)134 public static Object GetData(String name) 135 { 136 Object o = LogicalGetData(name); 137 if (o == null) 138 { 139 return IllogicalGetData(name); 140 } 141 else 142 { 143 return o; 144 } 145 } 146 147 [System.Security.SecurityCritical] // auto-generated SetData(String name, Object data)148 public static void SetData(String name, Object data) 149 { 150 if (data is ILogicalThreadAffinative) 151 { 152 LogicalSetData(name, data); 153 } 154 else 155 { 156 ExecutionContext ec = Thread.CurrentThread.GetMutableExecutionContext(); 157 ec.LogicalCallContext.FreeNamedDataSlot(name); 158 ec.IllogicalCallContext.SetData(name, data); 159 } 160 } 161 [System.Security.SecurityCritical] // auto-generated LogicalSetData(String name, Object data)162 public static void LogicalSetData(String name, Object data) 163 { 164 ExecutionContext ec = Thread.CurrentThread.GetMutableExecutionContext(); 165 ec.IllogicalCallContext.FreeNamedDataSlot(name); 166 ec.LogicalCallContext.SetData(name, data); 167 } 168 169 170 [System.Security.SecurityCritical] // auto-generated GetHeaders()171 public static Header[] GetHeaders() 172 { 173 // Header is mutable, so we need to get these from a mutable ExecutionContext 174 LogicalCallContext lcc = Thread.CurrentThread.GetMutableExecutionContext().LogicalCallContext; 175 return lcc.InternalGetHeaders(); 176 } // GetHeaders 177 178 [System.Security.SecurityCritical] // auto-generated SetHeaders(Header[] headers)179 public static void SetHeaders(Header[] headers) 180 { 181 LogicalCallContext lcc = Thread.CurrentThread.GetMutableExecutionContext().LogicalCallContext; 182 lcc.InternalSetHeaders(headers); 183 } // SetHeaders 184 185 } // class CallContext 186 187 [System.Runtime.InteropServices.ComVisible(true)] 188 public interface ILogicalThreadAffinative 189 { 190 } 191 192 internal class IllogicalCallContext 193 { 194 private Hashtable m_Datastore; 195 private Object m_HostContext; 196 197 internal struct Reader 198 { 199 IllogicalCallContext m_ctx; 200 ReaderSystem.Runtime.Remoting.Messaging.IllogicalCallContext.Reader201 public Reader(IllogicalCallContext ctx) { m_ctx = ctx; } 202 203 public bool IsNull { get { return m_ctx == null; } } 204 205 [System.Security.SecurityCritical] GetDataSystem.Runtime.Remoting.Messaging.IllogicalCallContext.Reader206 public Object GetData(String name) { return IsNull ? null : m_ctx.GetData(name); } 207 208 public Object HostContext { get { return IsNull ? null : m_ctx.HostContext; } } 209 } 210 211 private Hashtable Datastore 212 { 213 get 214 { 215 if (null == m_Datastore) 216 { 217 // The local store has not yet been created for this thread. 218 m_Datastore = new Hashtable(); 219 } 220 return m_Datastore; 221 } 222 } 223 224 internal Object HostContext 225 { 226 get 227 { 228 return m_HostContext; 229 } 230 set 231 { 232 m_HostContext = value; 233 } 234 } 235 236 internal bool HasUserData 237 { 238 get { return ((m_Datastore != null) && (m_Datastore.Count > 0));} 239 } 240 241 /*========================================================================= 242 ** Frees a named data slot. 243 =========================================================================*/ FreeNamedDataSlot(String name)244 public void FreeNamedDataSlot(String name) 245 { 246 Datastore.Remove(name); 247 } 248 GetData(String name)249 public Object GetData(String name) 250 { 251 return Datastore[name]; 252 } 253 SetData(String name, Object data)254 public void SetData(String name, Object data) 255 { 256 Datastore[name] = data; 257 } 258 CreateCopy()259 public IllogicalCallContext CreateCopy() 260 { 261 IllogicalCallContext ilcc = new IllogicalCallContext(); 262 ilcc.HostContext = this.HostContext; 263 if (HasUserData) 264 { 265 IDictionaryEnumerator de = this.m_Datastore.GetEnumerator(); 266 267 while (de.MoveNext()) 268 { 269 ilcc.Datastore[(String)de.Key] = de.Value; 270 } 271 } 272 return ilcc; 273 } 274 } 275 276 // This class handles the actual call context functionality. It leverages on the 277 // implementation of local data store ... except that the local store manager is 278 // not static. That is to say, allocating a slot in one call context has no effect 279 // on another call contexts. Different call contexts are entirely unrelated. 280 281 [System.Security.SecurityCritical] // auto-generated_required 282 [Serializable] 283 [System.Runtime.InteropServices.ComVisible(true)] 284 public sealed class LogicalCallContext : ISerializable, ICloneable 285 { 286 // Private static data 287 private static Type s_callContextType = typeof(LogicalCallContext); 288 private const string s_CorrelationMgrSlotName = "System.Diagnostics.Trace.CorrelationManagerSlot"; 289 290 /*========================================================================= 291 ** Data accessed from managed code that needs to be defined in 292 ** LogicalCallContextObject to maintain alignment between the two classes. 293 ** DON'T CHANGE THESE UNLESS YOU MODIFY LogicalContextObject in vm\object.h 294 =========================================================================*/ 295 296 // Private member data 297 private Hashtable m_Datastore; 298 private CallContextRemotingData m_RemotingData = null; 299 private CallContextSecurityData m_SecurityData = null; 300 private Object m_HostContext = null; 301 private bool m_IsCorrelationMgr = false; 302 303 // _sendHeaders is for Headers that should be sent out on the next call. 304 // _recvHeaders are for Headers that came from a response. 305 private Header[] _sendHeaders = null; 306 private Header[] _recvHeaders = null; 307 308 LogicalCallContext()309 internal LogicalCallContext() 310 { 311 } 312 313 internal struct Reader 314 { 315 LogicalCallContext m_ctx; 316 ReaderSystem.Runtime.Remoting.Messaging.LogicalCallContext.Reader317 public Reader(LogicalCallContext ctx) { m_ctx = ctx; } 318 319 public bool IsNull { get { return m_ctx == null; } } 320 public bool HasInfo { get { return IsNull ? false : m_ctx.HasInfo; } } 321 CloneSystem.Runtime.Remoting.Messaging.LogicalCallContext.Reader322 public LogicalCallContext Clone() { return (LogicalCallContext)m_ctx.Clone(); } 323 324 public IPrincipal Principal { get { return IsNull ? null : m_ctx.Principal; } } 325 326 [System.Security.SecurityCritical] GetDataSystem.Runtime.Remoting.Messaging.LogicalCallContext.Reader327 public Object GetData(String name) { return IsNull ? null : m_ctx.GetData(name); } 328 329 public Object HostContext { get { return IsNull ? null : m_ctx.HostContext; } } 330 } 331 332 [System.Security.SecurityCritical] // auto-generated LogicalCallContext(SerializationInfo info, StreamingContext context)333 internal LogicalCallContext(SerializationInfo info, StreamingContext context) 334 { 335 SerializationInfoEnumerator e = info.GetEnumerator(); 336 while (e.MoveNext()) 337 { 338 if (e.Name.Equals("__RemotingData")) 339 { 340 m_RemotingData = (CallContextRemotingData) e.Value; 341 } 342 else if (e.Name.Equals("__SecurityData")) 343 { 344 if (context.State == StreamingContextStates.CrossAppDomain) 345 { 346 m_SecurityData = (CallContextSecurityData) e.Value; 347 } 348 else 349 { 350 BCLDebug.Assert(false, "Security data should only be serialized in cross appdomain case."); 351 } 352 } 353 else if (e.Name.Equals("__HostContext")) 354 { 355 m_HostContext = e.Value; 356 } 357 else if (e.Name.Equals("__CorrelationMgrSlotPresent")) 358 { 359 m_IsCorrelationMgr = (bool)e.Value; 360 } 361 else 362 { 363 Datastore[e.Name] = e.Value; 364 } 365 366 } 367 } 368 369 [System.Security.SecurityCritical] // auto-generated_required GetObjectData(SerializationInfo info, StreamingContext context)370 public void GetObjectData(SerializationInfo info, StreamingContext context) 371 { 372 if (info == null) 373 throw new ArgumentNullException("info"); 374 info.SetType(s_callContextType); 375 if (m_RemotingData != null) 376 { 377 info.AddValue("__RemotingData", m_RemotingData); 378 } 379 if (m_SecurityData != null) 380 { 381 if (context.State == StreamingContextStates.CrossAppDomain) 382 { 383 info.AddValue("__SecurityData", m_SecurityData); 384 } 385 } 386 if (m_HostContext != null) 387 { 388 info.AddValue("__HostContext", m_HostContext); 389 } 390 if (m_IsCorrelationMgr) 391 { 392 info.AddValue("__CorrelationMgrSlotPresent", m_IsCorrelationMgr); 393 } 394 if (HasUserData) 395 { 396 IDictionaryEnumerator de = m_Datastore.GetEnumerator(); 397 398 while (de.MoveNext()) 399 { 400 info.AddValue((String)de.Key, de.Value); 401 } 402 } 403 404 } 405 406 407 // ICloneable::Clone 408 // Used to create a deep copy of the call context when an async 409 // call starts. 410 411 // < 412 413 414 415 [System.Security.SecuritySafeCritical] // overrides public transparent member Clone()416 public Object Clone() 417 { 418 LogicalCallContext lc = new LogicalCallContext(); 419 if (m_RemotingData != null) 420 lc.m_RemotingData = (CallContextRemotingData)m_RemotingData.Clone(); 421 if (m_SecurityData != null) 422 lc.m_SecurityData = (CallContextSecurityData)m_SecurityData.Clone(); 423 if (m_HostContext != null) 424 lc.m_HostContext = m_HostContext; 425 lc.m_IsCorrelationMgr = m_IsCorrelationMgr; 426 if (HasUserData) 427 { 428 IDictionaryEnumerator de = m_Datastore.GetEnumerator(); 429 430 if (!m_IsCorrelationMgr) 431 { 432 while (de.MoveNext()) 433 { 434 lc.Datastore[(String)de.Key] = de.Value; 435 } 436 } 437 else 438 { 439 while (de.MoveNext()) 440 { 441 String key = (String)de.Key; 442 443 // Deep clone "System.Diagnostics.Trace.CorrelationManagerSlot" 444 if (key.Equals(s_CorrelationMgrSlotName)) 445 { 446 lc.Datastore[key] = ((ICloneable)de.Value).Clone(); 447 } 448 else 449 lc.Datastore[key] = de.Value; 450 } 451 } 452 } 453 return lc; 454 } 455 456 // Used to do a (limited) merge the call context from a returning async call 457 [System.Security.SecurityCritical] // auto-generated Merge(LogicalCallContext lc)458 internal void Merge(LogicalCallContext lc) 459 { 460 // we ignore the RemotingData & SecurityData 461 // and only merge the user sections of the two call contexts 462 // the idea being that if the original call had any 463 // identity/remoting callID that should remain unchanged 464 465 // If we have a non-null callContext and it is not the same 466 // as the one on the current thread (can happen in x-context async) 467 // and there is any userData in the callContext, do the merge 468 if ((lc != null) && (this != lc) && lc.HasUserData) 469 { 470 IDictionaryEnumerator de = lc.Datastore.GetEnumerator(); 471 472 while (de.MoveNext()) 473 { 474 Datastore[(String)de.Key] = de.Value; 475 } 476 } 477 } 478 479 public bool HasInfo 480 { 481 [System.Security.SecurityCritical] // auto-generated 482 get 483 { 484 bool fInfo = false; 485 486 // Set the flag to true if there is either remoting data, or 487 // security data or user data 488 if( 489 (m_RemotingData != null && m_RemotingData.HasInfo) || 490 (m_SecurityData != null && m_SecurityData.HasInfo) || 491 (m_HostContext != null) || 492 HasUserData 493 ) 494 { 495 fInfo = true; 496 } 497 498 return fInfo; 499 } 500 } 501 502 private bool HasUserData 503 { 504 get { return ((m_Datastore != null) && (m_Datastore.Count > 0));} 505 } 506 507 internal CallContextRemotingData RemotingData 508 { 509 get 510 { 511 if (m_RemotingData == null) 512 m_RemotingData = new CallContextRemotingData(); 513 514 return m_RemotingData; 515 } 516 } 517 518 internal CallContextSecurityData SecurityData 519 { 520 get 521 { 522 if (m_SecurityData == null) 523 m_SecurityData = new CallContextSecurityData(); 524 525 return m_SecurityData; 526 } 527 } 528 529 internal Object HostContext 530 { 531 get 532 { 533 return m_HostContext; 534 } 535 set 536 { 537 m_HostContext = value; 538 } 539 } 540 541 private Hashtable Datastore 542 { 543 get 544 { 545 if (null == m_Datastore) 546 { 547 // The local store has not yet been created for this thread. 548 m_Datastore = new Hashtable(); 549 } 550 return m_Datastore; 551 } 552 } 553 554 // This is used for quick access to the current principal when going 555 // between appdomains. 556 internal IPrincipal Principal 557 { 558 get 559 { 560 // This MUST not fault in the security data object if it doesn't exist. 561 if (m_SecurityData != null) 562 return m_SecurityData.Principal; 563 564 return null; 565 } // get 566 567 [System.Security.SecurityCritical] // auto-generated 568 set 569 { 570 SecurityData.Principal = value; 571 } // set 572 } // Principal 573 574 /*========================================================================= 575 ** Frees a named data slot. 576 =========================================================================*/ 577 [System.Security.SecurityCritical] // auto-generated FreeNamedDataSlot(String name)578 public void FreeNamedDataSlot(String name) 579 { 580 Datastore.Remove(name); 581 } 582 583 [System.Security.SecurityCritical] // auto-generated GetData(String name)584 public Object GetData(String name) 585 { 586 return Datastore[name]; 587 } 588 589 [System.Security.SecurityCritical] // auto-generated SetData(String name, Object data)590 public void SetData(String name, Object data) 591 { 592 Datastore[name] = data; 593 if (name.Equals(s_CorrelationMgrSlotName)) 594 m_IsCorrelationMgr = true; 595 } 596 InternalGetOutgoingHeaders()597 private Header[] InternalGetOutgoingHeaders() 598 { 599 Header[] outgoingHeaders = _sendHeaders; 600 _sendHeaders = null; 601 602 // A new remote call is being made, so we null out the 603 // current received headers so these can't be confused 604 // with a response from the next call. 605 _recvHeaders = null; 606 607 return outgoingHeaders; 608 } // InternalGetOutgoingHeaders 609 610 InternalSetHeaders(Header[] headers)611 internal void InternalSetHeaders(Header[] headers) 612 { 613 _sendHeaders = headers; 614 _recvHeaders = null; 615 } // InternalSetHeaders 616 617 InternalGetHeaders()618 internal Header[] InternalGetHeaders() 619 { 620 // If _sendHeaders is currently set, we always want to return them. 621 if (_sendHeaders != null) 622 return _sendHeaders; 623 624 // Either _recvHeaders is non-null and those are the ones we want to 625 // return, or there are no currently set headers, so we'll return 626 // null. 627 return _recvHeaders; 628 } // InternalGetHeaders 629 630 // Nulls out the principal if its not serializable. 631 // Since principals do flow for x-appdomain cases 632 // we need to handle this behaviour both during invoke 633 // and response 634 [System.Security.SecurityCritical] // auto-generated RemovePrincipalIfNotSerializable()635 internal IPrincipal RemovePrincipalIfNotSerializable() 636 { 637 IPrincipal currentPrincipal = this.Principal; 638 // If the principal is not serializable, we need to 639 // null it out. 640 if (currentPrincipal != null) 641 { 642 if (!currentPrincipal.GetType().IsSerializable) 643 this.Principal = null; 644 } 645 return currentPrincipal; 646 } 647 648 // Takes outgoing headers and inserts them 649 [System.Security.SecurityCritical] // auto-generated PropagateOutgoingHeadersToMessage(IMessage msg)650 internal void PropagateOutgoingHeadersToMessage(IMessage msg) 651 { 652 Header[] headers = InternalGetOutgoingHeaders(); 653 654 if (headers != null) 655 { 656 BCLDebug.Assert(msg != null, "Why is the message null?"); 657 658 IDictionary properties = msg.Properties; 659 BCLDebug.Assert(properties != null, "Why are the properties null?"); 660 661 foreach (Header header in headers) 662 { 663 // add header to the message dictionary 664 if (header != null) 665 { 666 // The header key is composed from its name and namespace. 667 668 String name = GetPropertyKeyForHeader(header); 669 670 properties[name] = header; 671 } 672 } 673 } 674 } // PropagateOutgoingHeadersToMessage 675 676 // Retrieve key to use for header. GetPropertyKeyForHeader(Header header)677 internal static String GetPropertyKeyForHeader(Header header) 678 { 679 if (header == null) 680 return null; 681 682 if (header.HeaderNamespace != null) 683 return header.Name + ", " + header.HeaderNamespace; 684 else 685 return header.Name; 686 } // GetPropertyKeyForHeader 687 688 // Take headers out of message and stores them in call context 689 [System.Security.SecurityCritical] // auto-generated PropagateIncomingHeadersToCallContext(IMessage msg)690 internal void PropagateIncomingHeadersToCallContext(IMessage msg) 691 { 692 BCLDebug.Assert(msg != null, "Why is the message null?"); 693 694 // If it's an internal message, we can quickly tell if there are any 695 // headers. 696 IInternalMessage iim = msg as IInternalMessage; 697 if (iim != null) 698 { 699 if (!iim.HasProperties()) 700 { 701 // If there are no properties just return immediately. 702 return; 703 } 704 } 705 706 IDictionary properties = msg.Properties; 707 BCLDebug.Assert(properties != null, "Why are the properties null?"); 708 709 IDictionaryEnumerator e = (IDictionaryEnumerator) properties.GetEnumerator(); 710 711 // cycle through the properties to get a count of the headers 712 int count = 0; 713 while (e.MoveNext()) 714 { 715 String key = (String)e.Key; 716 if (!key.StartsWith("__", StringComparison.Ordinal)) 717 { 718 // We don't want to have to check for special values, so we 719 // blanketly state that header names can't start with 720 // double underscore. 721 if (e.Value is Header) 722 count++; 723 } 724 } 725 726 // If there are headers, create array and set it to the received header property 727 Header[] headers = null; 728 if (count > 0) 729 { 730 headers = new Header[count]; 731 732 count = 0; 733 e.Reset(); 734 while (e.MoveNext()) 735 { 736 String key = (String)e.Key; 737 if (!key.StartsWith("__", StringComparison.Ordinal)) 738 { 739 Header header = e.Value as Header; 740 if (header != null) 741 headers[count++] = header; 742 } 743 } 744 } 745 746 _recvHeaders = headers; 747 _sendHeaders = null; 748 } // PropagateIncomingHeadersToCallContext 749 } // class LogicalCallContext 750 751 752 753 [Serializable] 754 internal class CallContextSecurityData : ICloneable 755 { 756 // This is used for the special getter/setter for security related 757 // info in the callContext. 758 IPrincipal _principal; 759 // < 760 internal IPrincipal Principal 761 { 762 get {return _principal;} 763 set {_principal = value;} 764 } 765 766 // Checks if there is any useful data to be serialized 767 internal bool HasInfo 768 { 769 get 770 { 771 return (null != _principal); 772 } 773 774 } 775 Clone()776 public Object Clone() 777 { 778 CallContextSecurityData sd = new CallContextSecurityData(); 779 sd._principal = _principal; 780 return sd; 781 } 782 783 } 784 785 [Serializable] 786 internal class CallContextRemotingData : ICloneable 787 { 788 // This is used for the special getter/setter for remoting related 789 // info in the callContext. 790 String _logicalCallID; 791 792 internal String LogicalCallID 793 { 794 get {return _logicalCallID;} 795 set {_logicalCallID = value;} 796 } 797 798 // Checks if there is any useful data to be serialized 799 internal bool HasInfo 800 { 801 get 802 { 803 // Keep this updated if we add more stuff to remotingData! 804 return (_logicalCallID!=null); 805 } 806 } 807 Clone()808 public Object Clone() 809 { 810 CallContextRemotingData rd = new CallContextRemotingData(); 811 rd.LogicalCallID = LogicalCallID; 812 return rd; 813 } 814 } 815 } 816