PageRenderTime 46ms CodeModel.GetById 21ms RepoModel.GetById 0ms app.codeStats 0ms

/base/Kernel/Singularity/Channels/EndpointCore.cs

#
C# | 677 lines | 418 code | 82 blank | 177 comment | 68 complexity | c164c683ec7e7212e5229f8ca60c65e7 MD5 | raw file
  1. ////////////////////////////////////////////////////////////////////////////////
  2. //
  3. // Microsoft Research Singularity
  4. //
  5. // Copyright (c) Microsoft Corporation. All rights reserved.
  6. //
  7. // File: EndpointCore.cs
  8. //
  9. // HACK: Because we currently compile this file as part of the Kernel with C#, we can't
  10. // make EndpointCore a rep struct, which is necessary for inheriting from it.
  11. // The compiler and Bartok recognize EndpointCore as special and treat it as
  12. // unsealed.
  13. // This hack can be removed, once we compile it with Sing#.
  14. // DON'T change the name until then!
  15. using System;
  16. using System.Runtime.CompilerServices;
  17. using System.Runtime.InteropServices;
  18. using System.Diagnostics;
  19. using Microsoft.Singularity.Channels;
  20. using Microsoft.Singularity.Security;
  21. using Microsoft.Singularity.Memory;
  22. namespace Microsoft.Singularity.Channels
  23. {
  24. using Microsoft.Singularity.V1.Threads;
  25. using Microsoft.Singularity.V1.Security;
  26. using Microsoft.Singularity.V1.Services;
  27. using Microsoft.Singularity.V1.Types;
  28. using Allocation = Microsoft.Singularity.Memory.SharedHeap.Allocation;
  29. using System.Threading;
  30. using SharedHeap = Microsoft.Singularity.Memory.SharedHeap;
  31. [CLSCompliant(false)]
  32. public enum EndpointCoreEvent : ushort
  33. {
  34. Connect = 4,
  35. TransferToProcess = 5,
  36. }
  37. [CLSCompliant(false)]
  38. [CCtorIsRunDuringStartup]
  39. unsafe public struct EndpointCore
  40. {
  41. ////////////////////////////////////////////////////////////////////
  42. // Fields
  43. ////////////////////////////////////////////////////////////////////
  44. //
  45. // NOTE: The fields specified here must match those in:
  46. // Kernel/Singularity/Channels/EndpointCore.cs
  47. // Kernel/Singularity/V1/Services/ChannelServices.cs
  48. // Libraries/Singuarity.V1/Services/ChannelServices.cs
  49. /// <summary>
  50. /// Handle to the actual message delivery mechanism
  51. /// </summary>
  52. private DeliveryHandle deliveryHandle;
  53. /// <summary>
  54. /// Event handle in case this endpoint is part of a collection
  55. /// </summary>
  56. private AutoResetEventHandle collectionEvent;
  57. //
  58. // These "cached" fields are directly accessable by user programs,
  59. // but are not trusted by the kernel (as they could be modified by untrusted
  60. // code). The kernel relies on the trusted shadow copies held in the
  61. // deliveryImpl object, but updates these fields to reflect any changes to user
  62. // apps.
  63. //
  64. /// <summary>
  65. /// Event on which sends are signaled to this endpoint.
  66. /// The handle is owned by the kernel, since the endpoint can move.
  67. /// The kernel deallocates the handle when the channel is deallocated.
  68. /// NOTE: stays valid until the entire channel gets collected.
  69. /// </summary>
  70. private AutoResetEventHandle cachedMessageEvent;
  71. /// <summary>
  72. /// Closed flag
  73. /// </summary>
  74. private bool cachedClosed;
  75. /// <summary>
  76. /// Contains the process id of the process currently owning this end of the
  77. /// channel.
  78. /// </summary>
  79. private int cachedOwnerProcessId;
  80. /// <summary>
  81. /// Contains the channelId (positive on the EXP endpoint, negative on the imp endpoint)
  82. /// </summary>
  83. private int cachedChannelId;
  84. /// <summary>
  85. /// Whether to marshall or not
  86. /// </summary>
  87. private bool cachedMarshall;
  88. /// <summary>
  89. /// Points to the peer endpoint
  90. /// </summary>
  91. private Allocation* /*EndpointCore* opt(ExHeap)*/ cachedPeer;
  92. /// <summary>
  93. /// If true then the peer state can be queried directly from cachedPeer
  94. /// </summary>
  95. private bool peerStateValid;
  96. ////////////////////////////////////////////////////////////////////
  97. // Static Fields
  98. ////////////////////////////////////////////////////////////////////
  99. /// <summary>
  100. /// Number of open channels (using any delivery mechanism)
  101. /// </summary>
  102. private static int openChannelCount; // TODO open channel count!!!
  103. /// <summary>
  104. /// Channel id generator used to create unique channel id's accross delivery
  105. /// mechanisms.
  106. /// </summary>
  107. private static int channelIdGenerator;
  108. ////////////////////////////////////////////////////////////////////
  109. // Types
  110. ////////////////////////////////////////////////////////////////////
  111. public enum DelegationState {None, ByCapability, ByMediation, Mediated};
  112. ////////////////////////////////////////////////////////////////////
  113. // Methods
  114. ////////////////////////////////////////////////////////////////////
  115. /// <summary>
  116. /// Retrieve underlying delivery mechanism for a given endpoint allocation pointer
  117. /// </summary>
  118. [NoHeapAllocation]
  119. internal static DeliveryImpl AllocationEndpointDeliveryImpl(
  120. Allocation* /*EndpointCore* opt(ExHeap)!*/ endpoint)
  121. {
  122. EndpointCore * ep = (EndpointCore *) Allocation.GetData(endpoint);
  123. if (ep == null) {
  124. return null;
  125. } else {
  126. return ep->EndpointDeliveryImpl;
  127. }
  128. }
  129. /// <summary>
  130. /// Retrieve underlying delivery mechanism for a given endpoint allocation pointer
  131. /// using GetDataUnchecked.
  132. /// </summary>
  133. [NoHeapAllocation]
  134. internal static DeliveryImpl AllocationEndpointDeliveryImplUnchecked(
  135. Allocation* /*EndpointCore* opt(ExHeap)!*/ endpoint)
  136. {
  137. EndpointCore * ep = (EndpointCore *) Allocation.GetDataUnchecked(endpoint);
  138. if (ep == null) {
  139. return null;
  140. } else {
  141. return ep->EndpointDeliveryImpl;
  142. }
  143. }
  144. /// <summary>
  145. /// Retrieve underlying delivery mechanism for this endpoint
  146. /// </summary>
  147. internal DeliveryImpl EndpointDeliveryImpl {
  148. [NoHeapAllocation]
  149. get {
  150. if (deliveryHandle != DeliveryHandle.Zero) {
  151. return DeliveryHandle.GetImpl(deliveryHandle);
  152. } else {
  153. unsafe {
  154. DebugStub.WriteLine("deliveryHandle value is {0,8:x}\n", __arglist((uint) deliveryHandle.id));
  155. }
  156. DebugStub.Break();
  157. return null;
  158. }
  159. }
  160. }
  161. /// <summary>
  162. /// Performs the initialization of the core part of each endpoint and cross links
  163. /// them to form a channel. Uses the standard shared address space delivery
  164. /// mechanism.
  165. /// </summary>
  166. public static void Connect(
  167. Allocation* /*EndpointCore* opt(ExHeap)!*/ imp,
  168. Allocation* /*EndpointCore* opt(ExHeap)!*/ exp,
  169. Allocation* /*EndpointCore* opt(ExHeap)!*/ securityEp)
  170. {
  171. Connect(imp, exp, securityEp, SingleAddrSpaceDelivery.ImplName);
  172. }
  173. /// <summary>
  174. /// Performs the initialization of the core part of each endpoint and cross links
  175. /// them to form a channel. Uses the given delivery mechanism.
  176. /// </summary>
  177. public static void Connect(
  178. Allocation* /*EndpointCore* opt(ExHeap)!*/ imp,
  179. Allocation* /*EndpointCore* opt(ExHeap)!*/ exp,
  180. Allocation* /*EndpointCore* opt(ExHeap)!*/ securityEp,
  181. string deliveryImplType)
  182. {
  183. if (imp == null || exp == null) {
  184. throw new ApplicationException("Connect called with null endpoints");
  185. }
  186. EndpointCore* impData = (EndpointCore*)Allocation.GetData(imp);
  187. EndpointCore* expData = (EndpointCore*)Allocation.GetData(exp);
  188. if (impData == null || expData == null) {
  189. throw new ApplicationException("SharedHeap.GetData return null");
  190. }
  191. Tracing.Log(Tracing.Debug, "connect {0:x8} and {1:x8}",
  192. (UIntPtr)imp, (UIntPtr)exp);
  193. if (!(DeliveryHandle.Create(deliveryImplType, imp, out impData->deliveryHandle) &&
  194. DeliveryHandle.Create(deliveryImplType, exp, out expData->deliveryHandle))) {
  195. throw new EndpointCoreException(
  196. "Error trying to create EndpointCore using \"" +
  197. deliveryImplType +
  198. "\" delivery implementation");
  199. }
  200. #if false
  201. DebugStub.Print("imp handle {0,8:x} exp handle {1,8:x}\n",
  202. __arglist((uint)impData->deliveryHandle.id, (uint)expData->deliveryHandle.id));
  203. #endif
  204. DeliveryImpl impDi = impData->EndpointDeliveryImpl;
  205. DeliveryImpl expDi = expData->EndpointDeliveryImpl;
  206. VTable.Assert(impDi != null && expDi != null);
  207. impDi.Connect(expDi, securityEp);
  208. // keep track of how many channels are open
  209. Interlocked.Increment(ref openChannelCount);
  210. #if CHANNEL_COUNT
  211. PerfCounters.IncrementChannelsCreated();
  212. #endif
  213. Monitoring.Log(Monitoring.Provider.EndpointCore,
  214. (ushort)EndpointCoreEvent.Connect, 0,
  215. (uint)expData->ChannelId, 0, 0, 0, 0);
  216. }
  217. /// <summary>
  218. /// Set this end to closed
  219. /// </summary>
  220. public void Close()
  221. {
  222. DeliveryImpl di = EndpointDeliveryImpl;
  223. if (di != null) {
  224. di.Close();
  225. }
  226. }
  227. [NoHeapAllocation]
  228. public bool Closed()
  229. {
  230. DeliveryImpl di = EndpointDeliveryImpl;
  231. if (di != null) {
  232. return di.Closed;
  233. } else {
  234. // endpoint has not yet been connected
  235. return true;
  236. }
  237. }
  238. /// <summary>
  239. /// Closes this end of the channel and frees associated resources, EXCEPT the block
  240. /// of memory for this endpoint. It must be released by the caller. Sing# does this
  241. /// for the programmer.
  242. ///
  243. /// This runs in the kernel to avoid a race condition with Process.Stop.
  244. /// </summary>
  245. public bool Dispose()
  246. {
  247. DeliveryImpl di = EndpointDeliveryImpl;
  248. if (di != null) {
  249. return EndpointDeliveryImpl.Dispose();
  250. } else {
  251. return true; // endpoint was not yet connected
  252. }
  253. }
  254. /// <summary>
  255. /// Explicitly frees this end of the channel.
  256. ///
  257. /// Since both threads on the channel could try to do this simultaneously,
  258. /// we use the ref counting on the underlying endpoints to let the last
  259. /// free operation (the one pulling the ref count to 0) to free the associated
  260. /// event.
  261. /// </summary>
  262. public unsafe static void Free(Allocation* /*EndpointCore* opt(ExHeap)!*/ endpoint)
  263. {
  264. // Use unchecked GetData here, since this may be called from the
  265. // cleanup threading running in the kernel.
  266. EndpointCore * ep = ((EndpointCore*)Allocation.GetDataUnchecked(endpoint));
  267. if (ep->deliveryHandle != DeliveryHandle.Zero) {
  268. if (DeliveryHandle.Free(ep->deliveryHandle)) {
  269. Interlocked.Decrement(ref openChannelCount);
  270. }
  271. } else {
  272. // was never connected, just free this endpoint
  273. AutoResetEventHandle areHandle = ep->cachedMessageEvent;
  274. SharedHeap.KernelSharedHeap.Free(endpoint,
  275. SharedHeap.CurrentProcessSharedHeap.EndpointPeerOwnerId);
  276. if (areHandle.id != UIntPtr.Zero) {
  277. Process.kernelProcess.ReleaseHandle(areHandle.id);
  278. }
  279. }
  280. }
  281. /// <summary>
  282. /// The event to wait for messages on this endpoint. Used by Select.
  283. /// </summary>
  284. public SyncHandle GetWaitHandle() {
  285. DeliveryImpl di = EndpointDeliveryImpl;
  286. VTable.Assert(di != null);
  287. return di.MessageEvent;
  288. }
  289. /// <summary>
  290. /// Get the AutoResetEventHandle
  291. /// </summary>
  292. public AutoResetEventHandle GetAreHandle() {
  293. DeliveryImpl di = EndpointDeliveryImpl;
  294. VTable.Assert(di != null);
  295. return di.AreHandle;
  296. }
  297. /// <summary>
  298. /// Notify the peer of this endpoint that a message is ready.
  299. /// Notifies the set owner if this endpoint is part of a set.
  300. /// </summary>
  301. public void NotifyPeer() {
  302. DeliveryImpl di = EndpointDeliveryImpl;
  303. VTable.Assert(di != null);
  304. di.NotifyPeer();
  305. }
  306. /// <summary>
  307. /// Used internally by the kernel to transfer an endpoint to a new owner
  308. ///
  309. /// Can be used to transfer ANY kind of shared heap data, not just endpoints.
  310. /// </summary>
  311. public static Allocation* MoveEndpoint(SharedHeap fromHeap,
  312. SharedHeap toHeap,
  313. Process newOwner,
  314. Allocation *ep)
  315. {
  316. return DeliveryImpl.MoveData(fromHeap, toHeap, newOwner, ep);
  317. }
  318. public static void AcceptDelegation(Allocation* /*EndpointCore* opt(ExHeap)!*/ imp,
  319. Allocation* /*EndpointCore* opt(ExHeap)!*/ exp,
  320. Allocation* /*EndpointCore* opt(ExHeap)!*/ ep)
  321. {
  322. DeliveryImpl impDi = AllocationEndpointDeliveryImpl(imp);
  323. DeliveryImpl expDi = AllocationEndpointDeliveryImpl(exp);
  324. DeliveryImpl epDi = AllocationEndpointDeliveryImpl(ep);
  325. VTable.Assert((impDi != null && expDi != null && epDi != null));
  326. VTable.Assert((impDi.GetType() == expDi.GetType()) &&
  327. (impDi.GetType() == epDi.GetType()));
  328. impDi.AcceptDelegation(expDi, epDi);
  329. }
  330. public void EnableDelegation(bool allowMediation)
  331. {
  332. EndpointDeliveryImpl.EnableDelegation(allowMediation);
  333. }
  334. public DelegationState OwnerDelegationState
  335. {
  336. [NoHeapAllocation]
  337. get { return EndpointDeliveryImpl.OwnerDelegationState; }
  338. }
  339. public PrincipalHandle OwnerPrincipalHandle
  340. {
  341. [NoHeapAllocation]
  342. get { return EndpointDeliveryImpl.OwnerPrincipalHandle; }
  343. }
  344. public PrincipalHandle PeerPrincipalHandle
  345. {
  346. [NoHeapAllocation]
  347. get { return EndpointDeliveryImpl.PeerPrincipalHandle; }
  348. }
  349. public int PeerReceiveCount
  350. {
  351. [NoHeapAllocation]
  352. get {
  353. DeliveryImpl di = EndpointDeliveryImpl;
  354. if (di != null) {
  355. return di.PeerReceiveCount;
  356. } else {
  357. // not yet connected, therefore no peer yet
  358. return 0;
  359. }
  360. }
  361. }
  362. //versions of begin update and marshallpointer that handle objects with pointers
  363. //greater than one level deep
  364. internal void BeginUpdate(byte* basep, byte* source, int* tagAddress, int msgSize)
  365. {
  366. DeliveryImpl di = EndpointDeliveryImpl;
  367. VTable.Assert(di != null);
  368. // DebugStub.Print("BeginUpdate delivery handle {0,8:x}\n", __arglist((uint)deliveryHandle.id));
  369. di.BeginUpdate(basep, source, tagAddress, msgSize);
  370. }
  371. public void MarshallPointer(byte* basep, byte** target, SystemType type, byte* parent, int offset)
  372. {
  373. if (target == null) return;
  374. DeliveryImpl di = EndpointDeliveryImpl;
  375. // DebugStub.Print("Marshall Pointer delivery handle {0,8:x}\n", __arglist((uint)deliveryHandle.id));
  376. VTable.Assert(di != null);
  377. di.MarshallPointer(basep, target, type, parent, offset);
  378. }
  379. public static PrincipalHandle TransferPrincipal(PrincipalHandle oldH,
  380. int processId,
  381. ref DelegationState delegationState)
  382. {
  383. Process p = Process.GetProcessByID(processId);
  384. if (p == null)
  385. throw new ApplicationException("Delegate endpoint process is null");
  386. DelegationState newstate;
  387. Principal pr;
  388. switch (delegationState) {
  389. case DelegationState.ByMediation:
  390. pr = PrincipalImpl.NewDelegation(
  391. PrincipalImpl.MakePrincipal(oldH.val), p.Principal);
  392. newstate = DelegationState.Mediated;
  393. break;
  394. case DelegationState.ByCapability:
  395. pr = PrincipalImpl.NewDelegation(
  396. PrincipalImpl.MakePrincipal(oldH.val), p.Principal);
  397. newstate = DelegationState.None;
  398. break;
  399. case DelegationState.Mediated:
  400. pr = p.Principal;
  401. newstate = DelegationState.None;
  402. break;
  403. case DelegationState.None:
  404. default:
  405. pr = p.Principal;
  406. newstate = DelegationState.None;
  407. break;
  408. }
  409. delegationState = newstate;
  410. return new PrincipalHandle(pr.Val);
  411. }
  412. #if SINGULARITY_KERNEL && CHANNEL_COUNT
  413. internal static void IncreaseBytesSentCount(long bytes)
  414. {
  415. PerfCounters.AddBytesSent(bytes);
  416. }
  417. #endif // SINGULARITY_KERNEL
  418. /// <summary>
  419. /// Transfer the given Allocation block to the target endpoint
  420. /// </summary>
  421. public static void TransferBlockOwnership(Allocation* ptr, ref EndpointCore target)
  422. {
  423. Allocation.SetOwnerProcessId(ptr, target.cachedOwnerProcessId);
  424. // TODO MAKE THIS APROPRIATE TO BOTH SINGLE AND PAGED IMPLS
  425. DeliveryImpl di = target.EndpointDeliveryImpl;
  426. VTable.Assert(di != null);
  427. //Monitoring.Log(Monitoring.Provider.ChannelService,
  428. // (ushort)ChannelServiceEvent.TransferBlockOwnership, 0,
  429. // (uint)di.ChannelId,
  430. // (uint)di.ProcessId,
  431. // 0, 0, 0);
  432. #if CHANNEL_COUNT
  433. IncreaseBytesSentCount((long) Allocation.GetSize(ptr));
  434. #endif
  435. Allocation.SetOwnerProcessId(ptr, di.ProcessId);
  436. }
  437. /// <summary>
  438. /// Transfer any contents that needs to be adjusted from the transferee to the target
  439. /// endpoint.
  440. /// </summary>
  441. // TODO: change "ref EndpointCore" to "EndpointCore"
  442. public static void TransferContentOwnership(
  443. ref EndpointCore transferee,
  444. ref EndpointCore target)
  445. {
  446. // TODO MAKE THIS APROPRIATE TO BOTH SINGLE AND PAGED IMPLS
  447. DeliveryImpl transfereeDi = transferee.EndpointDeliveryImpl;
  448. // XXX BUG? BUG? BUG?
  449. // targetDi = transferee.EndpointDeliveryImpl
  450. // should be:
  451. // targetDi = target.EndpointDeliveryImpl
  452. DeliveryImpl targetDi = transferee.EndpointDeliveryImpl;
  453. VTable.Assert((transfereeDi != null) && (targetDi != null));
  454. //Monitoring.Log(Monitoring.Provider.ChannelService,
  455. // (ushort)ChannelServiceEvent.TransferContentOwnership, 0,
  456. // (uint)transfereeDi.ProcessId,
  457. // (uint)targetDi.ProcessId,
  458. // (uint)transfereeDi.ChannelId,
  459. // (uint)targetDi.ChannelId,
  460. // (uint)targetDi.Peer.ChannelId);
  461. int toProcessId = targetDi.ProcessId;
  462. transfereeDi.ProcessId = toProcessId;
  463. DelegationState newstate = transfereeDi.OwnerDelegationState;
  464. transfereeDi.OwnerPrincipalHandle =
  465. TransferPrincipal(transfereeDi.OwnerPrincipalHandle, toProcessId, ref newstate);
  466. transfereeDi.OwnerDelegationState = newstate;
  467. Allocation* transfereePeerAllocation = transfereeDi.Peer();
  468. // also transfer the peer allocation
  469. Allocation.SetOwnerProcessId(transfereePeerAllocation, toProcessId);
  470. }
  471. public static int OpenChannelCount {
  472. [NoHeapAllocation]
  473. get { return openChannelCount; }
  474. }
  475. public static void IncOpenChannelCount () {
  476. Interlocked.Increment(ref openChannelCount);
  477. }
  478. public static void DecOpenChannelCount () {
  479. Interlocked.Decrement(ref openChannelCount);
  480. }
  481. //
  482. // These getter defer to the delivery implementation, as the kernel only
  483. // trusts the shadow versions held in Kernel memory space.
  484. //
  485. public bool PeerClosed()
  486. {
  487. DeliveryImpl di = EndpointDeliveryImpl;
  488. if (di != null) {
  489. return di.PeerClosed();
  490. } else {
  491. // endpoint has not yet been connected
  492. return true;
  493. }
  494. }
  495. /// <summary>
  496. /// Return a handle for the peer
  497. /// </summary>
  498. public Allocation* Peer(out bool marshall)
  499. {
  500. DeliveryImpl di = EndpointDeliveryImpl;
  501. VTable.Assert(di != null);
  502. return di.Peer(out marshall);
  503. }
  504. public int ChannelId
  505. {
  506. [NoHeapAllocation]
  507. get {
  508. DeliveryImpl di = EndpointDeliveryImpl;
  509. VTable.Assert(di != null);
  510. return di.ChannelId;
  511. }
  512. }
  513. public int ProcessId
  514. {
  515. [NoHeapAllocation]
  516. get {
  517. DeliveryImpl di = EndpointDeliveryImpl;
  518. VTable.Assert(di != null);
  519. return di.ProcessId;
  520. }
  521. }
  522. public int ReceiveCount
  523. {
  524. get {
  525. DeliveryImpl di = EndpointDeliveryImpl;
  526. VTable.Assert(di != null);
  527. return di.ReceiveCount;
  528. }
  529. }
  530. public int PeerProcessId
  531. {
  532. [NoHeapAllocation]
  533. get {
  534. DeliveryImpl di = EndpointDeliveryImpl;
  535. VTable.Assert(di != null);
  536. return di.PeerProcessId;
  537. }
  538. }
  539. public DeliveryHandle dImpHandle
  540. {
  541. get {
  542. return deliveryHandle;
  543. }
  544. set {
  545. deliveryHandle = value;
  546. }
  547. }
  548. public AutoResetEventHandle CollectionEvent
  549. {
  550. get {
  551. return collectionEvent;
  552. }
  553. set {
  554. collectionEvent = value;
  555. }
  556. }
  557. internal static int GetNextChannelId()
  558. {
  559. return Interlocked.Increment(ref channelIdGenerator);
  560. }
  561. //
  562. // These methods only set the user accessable cached copies, real changes should
  563. // be made to the delivery implementation copies.
  564. //
  565. [NoHeapAllocation]
  566. public void SetCachedClose(bool closed) { cachedClosed = closed; }
  567. [NoHeapAllocation]
  568. public void SetCachedProcessId(int pid) { cachedOwnerProcessId = pid; }
  569. [NoHeapAllocation]
  570. public void SetCachedChannelId(int cid) { cachedChannelId = cid; }
  571. [NoHeapAllocation]
  572. public void SetCachedMarshall(bool marshall) { cachedMarshall = marshall; }
  573. [NoHeapAllocation]
  574. public void SetCachedMessageEvent(AutoResetEventHandle mh)
  575. {
  576. cachedMessageEvent = mh;
  577. }
  578. [NoHeapAllocation]
  579. public void SetCachedPeer(Allocation * /* EndpointCore */ peer)
  580. {
  581. cachedPeer = peer;
  582. }
  583. [NoHeapAllocation]
  584. public void SetPeerStateValid(bool valid)
  585. {
  586. peerStateValid = valid;
  587. }
  588. }
  589. public class EndpointCoreException: Exception
  590. {
  591. public EndpointCoreException (string message) : base(message)
  592. {
  593. }
  594. }
  595. }