Leaked source code of windows server 2003
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

675 lines
21 KiB

  1. /********************************************************************/
  2. /** Microsoft LAN Manager **/
  3. /** Copyright(c) Microsoft Corp., 1990-1993 **/
  4. /********************************************************************/
  5. /* :ts=4 */
  6. //** TLCOMMON.C - Common transport layer code.
  7. //
  8. // This file contains the code for routines that are common to
  9. // both TCP and UDP.
  10. //
  11. #include "precomp.h"
  12. #include "tlcommon.h"
  13. #include "tcpipbuf.h"
  14. extern TCPXSUM_ROUTINE tcpxsum_routine;
  15. //extern uint tcpxsum(uint Seed, void *Ptr, uint Length);
  16. extern IPInfo LocalNetInfo;
  17. //* TcpipCopyBufferToNdisBuffer
  18. //
  19. // This routine copies data described by the source buffer to the NDIS_BUFFER
  20. // chain described by DestinationNdisBuffer. On NT, this really translates
  21. // directly to TdiCopyBufferToMdl since an NDIS_BUFFER is an MDL.
  22. //
  23. // Input:
  24. //
  25. // SourceBuffer - pointer to the source buffer
  26. //
  27. // SourceOffset - Number of bytes to skip in the source data.
  28. //
  29. // SourceBytesToCopy - number of bytes to copy from the source buffer
  30. //
  31. // DestinationNdisBuffer - Pointer to a chain of NDIS_BUFFERs describing the
  32. // destination buffers.
  33. //
  34. // DestinationOffset - Number of bytes to skip in the destination data.
  35. //
  36. // BytesCopied - Pointer to a longword where the actual number of bytes
  37. // transferred will be returned.
  38. #if MILLEN
  39. NTSTATUS
  40. TcpipCopyBufferToNdisBuffer (
  41. IN PVOID SourceBuffer,
  42. IN ULONG SourceOffset,
  43. IN ULONG SourceBytesToCopy,
  44. IN PNDIS_BUFFER DestinationNdisBuffer,
  45. IN ULONG DestinationOffset,
  46. IN PULONG BytesCopied
  47. )
  48. {
  49. PUCHAR Dest, Src;
  50. ULONG DestBytesLeft, BytesSkipped=0;
  51. *BytesCopied = 0;
  52. if (SourceBytesToCopy == 0) {
  53. return STATUS_SUCCESS;
  54. }
  55. //
  56. // Skip Destination bytes.
  57. //
  58. Dest = NdisBufferVirtualAddress(DestinationNdisBuffer);
  59. DestBytesLeft = NdisBufferLength(DestinationNdisBuffer);
  60. while (BytesSkipped < DestinationOffset) {
  61. if (DestBytesLeft > (DestinationOffset - BytesSkipped)) {
  62. DestBytesLeft -= (DestinationOffset - BytesSkipped);
  63. Dest += (DestinationOffset - BytesSkipped);
  64. BytesSkipped = DestinationOffset;
  65. break;
  66. } else if (DestBytesLeft == (DestinationOffset - BytesSkipped)) {
  67. DestinationNdisBuffer = DestinationNdisBuffer->Next;
  68. if (DestinationNdisBuffer == NULL) {
  69. return STATUS_BUFFER_OVERFLOW; // no bytes copied.
  70. }
  71. BytesSkipped = DestinationOffset;
  72. Dest = NdisBufferVirtualAddress(DestinationNdisBuffer);
  73. DestBytesLeft = NdisBufferLength(DestinationNdisBuffer);
  74. break;
  75. } else {
  76. BytesSkipped += DestBytesLeft;
  77. DestinationNdisBuffer = DestinationNdisBuffer->Next;
  78. if (DestinationNdisBuffer == NULL) {
  79. return STATUS_BUFFER_OVERFLOW; // no bytes copied.
  80. }
  81. Dest = NdisBufferVirtualAddress(DestinationNdisBuffer);
  82. DestBytesLeft = NdisBufferLength(DestinationNdisBuffer);
  83. }
  84. }
  85. //
  86. // Skip source bytes.
  87. //
  88. Src = (PUCHAR)SourceBuffer + SourceOffset;
  89. //
  90. // Copy source data into the destination buffer until it's full or
  91. // we run out of data, whichever comes first.
  92. //
  93. while ((SourceBytesToCopy != 0) && (DestinationNdisBuffer != NULL)) {
  94. if (DestBytesLeft == 0) {
  95. DestinationNdisBuffer = DestinationNdisBuffer->Next;
  96. if (DestinationNdisBuffer == NULL) {
  97. return STATUS_BUFFER_OVERFLOW;
  98. }
  99. Dest = NdisBufferVirtualAddress(DestinationNdisBuffer);
  100. DestBytesLeft = NdisBufferLength(DestinationNdisBuffer);
  101. continue; // skip 0-length MDL's.
  102. }
  103. if (DestBytesLeft >= SourceBytesToCopy) {
  104. RtlCopyBytes (Dest, Src, SourceBytesToCopy);
  105. *BytesCopied += SourceBytesToCopy;
  106. return STATUS_SUCCESS;
  107. } else {
  108. RtlCopyBytes (Dest, Src, DestBytesLeft);
  109. *BytesCopied += DestBytesLeft;
  110. SourceBytesToCopy -= DestBytesLeft;
  111. Src += DestBytesLeft;
  112. DestBytesLeft = 0;
  113. }
  114. }
  115. return SourceBytesToCopy == 0 ? STATUS_SUCCESS : STATUS_BUFFER_OVERFLOW;
  116. }
  117. #endif // MILLEN
  118. //* PrefetchRcvbuf in to L1 cache
  119. // Called when received segment checksum is already computed by
  120. // the hardware
  121. //
  122. // Input: IPRcvBuf - Buffer chain indicated by IP
  123. // returns: None
  124. //
  125. //
  126. #if !MILLEN
  127. __inline
  128. void
  129. PrefetchRcvBuf(IPRcvBuf *BufChain)
  130. {
  131. while (BufChain) {
  132. RtlPrefetchMemoryNonTemporal(BufChain->ipr_buffer,BufChain->ipr_size);
  133. BufChain = BufChain->ipr_next;
  134. }
  135. }
  136. #endif // !MILLEN
  137. //* XsumSendChain - Checksum a chain of NDIS send buffers.
  138. //
  139. // Called to xsum a chain of NDIS send buffers. We're given the
  140. // pseudo-header xsum to start with, and we call xsum on each
  141. // buffer. We assume that this is a send chain, and that the
  142. // first buffer of the chain has room for an IP header that we
  143. // need to skip.
  144. //
  145. // Input: PHXsum - Pseudo-header xsum.
  146. // BufChain - Pointer to NDIS_BUFFER chain.
  147. //
  148. // Returns: The computed xsum.
  149. //
  150. ushort
  151. XsumSendChain(uint PHXsum, PNDIS_BUFFER BufChain)
  152. {
  153. uint HeaderSize;
  154. uint OldLength;
  155. uint SwapCount;
  156. uchar *Ptr;
  157. HeaderSize = LocalNetInfo.ipi_hsize;
  158. OldLength = 0;
  159. SwapCount = 0;
  160. //
  161. // ***** The following line of code can be removed if the pseudo
  162. // checksum never has any bits sets in the upper word.
  163. //
  164. PHXsum = (((PHXsum << 16) | (PHXsum >> 16)) + PHXsum) >> 16;
  165. do {
  166. //
  167. // If the length of the last buffer was odd, then swap the checksum.
  168. //
  169. if ((OldLength & 1) != 0) {
  170. PHXsum = ((PHXsum & 0xff) << 8) | (PHXsum >> 8);
  171. SwapCount ^= 1;
  172. }
  173. #if MILLEN
  174. //
  175. // Some TDI Clients on Windows ME have been known to end a buffer
  176. // chain with a 0 length buffer. Just continue to the next buffer.
  177. //
  178. if (NdisBufferLength(BufChain))
  179. #endif // MILLEN
  180. {
  181. Ptr = (uchar *) TcpipBufferVirtualAddress(BufChain, NormalPagePriority);
  182. if (Ptr == NULL) {
  183. // Return zero checksum. All should recover.
  184. return (0);
  185. }
  186. Ptr = Ptr + HeaderSize;
  187. //PHXsum = tcpxsum(PHXsum, Ptr, NdisBufferLength(BufChain));
  188. PHXsum = tcpxsum_routine(PHXsum, Ptr, NdisBufferLength(BufChain));
  189. HeaderSize = 0;
  190. OldLength = NdisBufferLength(BufChain);
  191. }
  192. BufChain = NDIS_BUFFER_LINKAGE(BufChain);
  193. } while (BufChain != NULL);
  194. //
  195. // If an odd number of swaps were done, then swap the xsum again.
  196. //
  197. // N.B. At this point the checksum is only a word.
  198. //
  199. if (SwapCount != 0) {
  200. PHXsum = ((PHXsum & 0xff) << 8) | (PHXsum >> 8);
  201. }
  202. return (ushort) PHXsum;
  203. }
  204. //* CopyRcvToNdis - Copy from an IPRcvBuf chain to an NDIS buffer chain.
  205. //
  206. // This is the function we use to copy from a chain of IP receive buffers
  207. // to a chain of NDIS buffers. The caller specifies the source and destination,
  208. // a maximum size to copy, and an offset into the first buffer to start
  209. // copying from. We copy as much as possible up to the size, and return
  210. // the size copied.
  211. //
  212. // Input: RcvBuf - Pointer to receive buffer chain.
  213. // DestBuf - Pointer to NDIS buffer chain.
  214. // Size - Size in bytes to copy.
  215. // RcvOffset - Offset into first buffer to copy from.
  216. // DestOffset - Offset into dest buffer to start copying at.
  217. //
  218. // Returns: Bytes copied.
  219. //
  220. uint
  221. CopyRcvToNdis(IPRcvBuf * RcvBuf, PNDIS_BUFFER DestBuf, uint Size,
  222. uint RcvOffset, uint DestOffset)
  223. {
  224. uint TotalBytesCopied = 0; // Bytes we've copied so far.
  225. uint BytesCopied = 0; // Bytes copied out of each buffer.
  226. uint DestSize, RcvSize; // Size left in current destination and
  227. // recv. buffers, respectively.
  228. uint BytesToCopy; // How many bytes to copy this time.
  229. NTSTATUS Status;
  230. PNDIS_BUFFER pTempBuf;
  231. ASSERT(RcvBuf != NULL);
  232. ASSERT(RcvOffset <= RcvBuf->ipr_size);
  233. // The destination buffer can be NULL - this is valid, if odd.
  234. if (DestBuf != NULL) {
  235. RcvSize = RcvBuf->ipr_size - RcvOffset;
  236. //
  237. // Need to calculate length of full MDL chain. TdiCopyBufferToMdl
  238. // will do the right thing with multiple MDLs.
  239. //
  240. pTempBuf = DestBuf;
  241. DestSize = 0;
  242. do {
  243. DestSize += NdisBufferLength(pTempBuf);
  244. pTempBuf = NDIS_BUFFER_LINKAGE(pTempBuf);
  245. }
  246. while (pTempBuf);
  247. if (Size < DestSize) {
  248. DestSize = Size;
  249. }
  250. do {
  251. // Compute the amount to copy, and then copy from the
  252. // appropriate offsets.
  253. BytesToCopy = MIN(DestSize, RcvSize);
  254. Status = TcpipCopyBufferToNdisBuffer(RcvBuf->ipr_buffer, RcvOffset,
  255. BytesToCopy, DestBuf, DestOffset, (PULONG)&BytesCopied);
  256. if (!NT_SUCCESS(Status)) {
  257. break;
  258. }
  259. ASSERT(BytesCopied == BytesToCopy);
  260. TotalBytesCopied += BytesCopied;
  261. DestSize -= BytesCopied;
  262. DestOffset += BytesCopied;
  263. RcvSize -= BytesToCopy;
  264. if (!RcvSize) {
  265. // Exhausted this buffer.
  266. RcvBuf = RcvBuf->ipr_next;
  267. // If we have another one, use it.
  268. if (RcvBuf != NULL) {
  269. RcvOffset = 0;
  270. RcvSize = RcvBuf->ipr_size;
  271. } else {
  272. break;
  273. }
  274. } else { // Buffer not exhausted, update offset.
  275. RcvOffset += BytesToCopy;
  276. }
  277. } while (DestSize);
  278. }
  279. return TotalBytesCopied;
  280. }
  281. uint
  282. CopyRcvToMdl(IPRcvBuf * RcvBuf, PMDL DestBuf, uint Size,
  283. uint RcvOffset, uint DestOffset)
  284. {
  285. uint TotalBytesCopied = 0; // Bytes we've copied so far.
  286. uint BytesCopied = 0; // Bytes copied out of each buffer.
  287. uint DestSize, RcvSize; // Size left in current destination and
  288. // recv. buffers, respectively.
  289. uint BytesToCopy; // How many bytes to copy this time.
  290. NTSTATUS Status;
  291. PMDL pTempBuf;
  292. ASSERT(RcvBuf != NULL);
  293. ASSERT(RcvOffset <= RcvBuf->ipr_size);
  294. // The destination buffer can be NULL - this is valid, if odd.
  295. if (DestBuf != NULL) {
  296. RcvSize = RcvBuf->ipr_size - RcvOffset;
  297. //
  298. // Need to calculate length of full MDL chain. TdiCopyBufferToMdl
  299. // will do the right thing with multiple MDLs.
  300. //
  301. pTempBuf = DestBuf;
  302. DestSize = 0;
  303. do {
  304. DestSize += MmGetMdlByteCount(pTempBuf);
  305. pTempBuf = pTempBuf->Next;
  306. }
  307. while (pTempBuf);
  308. if (Size < DestSize) {
  309. DestSize = Size;
  310. }
  311. do {
  312. // Compute the amount to copy, and then copy from the
  313. // appropriate offsets.
  314. BytesToCopy = MIN(DestSize, RcvSize);
  315. Status = TdiCopyBufferToMdl(RcvBuf->ipr_buffer, RcvOffset,
  316. BytesToCopy, DestBuf, DestOffset, (PULONG)&BytesCopied);
  317. if (!NT_SUCCESS(Status)) {
  318. break;
  319. }
  320. ASSERT(BytesCopied == BytesToCopy);
  321. TotalBytesCopied += BytesCopied;
  322. DestSize -= BytesCopied;
  323. DestOffset += BytesCopied;
  324. RcvSize -= BytesToCopy;
  325. if (!RcvSize) {
  326. // Exhausted this buffer.
  327. RcvBuf = RcvBuf->ipr_next;
  328. // If we have another one, use it.
  329. if (RcvBuf != NULL) {
  330. RcvOffset = 0;
  331. RcvSize = RcvBuf->ipr_size;
  332. } else {
  333. break;
  334. }
  335. } else { // Buffer not exhausted, update offset.
  336. RcvOffset += BytesToCopy;
  337. }
  338. } while (DestSize);
  339. }
  340. return TotalBytesCopied;
  341. }
  342. //* CopyRcvToBuffer - Copy from an IPRcvBuf chain to a flat buffer.
  343. //
  344. // Called during receive processing to copy from an IPRcvBuffer chain to a
  345. // flag buffer. We skip Offset bytes in the src chain, and then
  346. // copy Size bytes.
  347. //
  348. // Input: DestBuf - Pointer to destination buffer.
  349. // SrcRB - Pointer to SrcRB chain.
  350. // Size - Size in bytes to copy.
  351. // SrcOffset - Offset in SrcRB to start copying from.
  352. //
  353. // Returns: Nothing.
  354. //
  355. void
  356. CopyRcvToBuffer(uchar * DestBuf, IPRcvBuf * SrcRB, uint Size, uint SrcOffset)
  357. {
  358. #if DBG
  359. IPRcvBuf *TempRB;
  360. uint TempSize;
  361. #endif
  362. ASSERT(DestBuf != NULL);
  363. ASSERT(SrcRB != NULL);
  364. // In debug versions check to make sure we're copying a reasonable size
  365. // and from a reasonable offset.
  366. #if DBG
  367. TempRB = SrcRB;
  368. TempSize = 0;
  369. while (TempRB != NULL) {
  370. TempSize += TempRB->ipr_size;
  371. TempRB = TempRB->ipr_next;
  372. }
  373. ASSERT(SrcOffset < TempSize);
  374. ASSERT((SrcOffset + Size) <= TempSize);
  375. #endif
  376. // First, skip Offset bytes.
  377. while (SrcOffset >= SrcRB->ipr_size) {
  378. SrcOffset -= SrcRB->ipr_size;
  379. SrcRB = SrcRB->ipr_next;
  380. }
  381. while (Size != 0) {
  382. uint BytesToCopy, SrcSize;
  383. ASSERT(SrcRB != NULL);
  384. SrcSize = SrcRB->ipr_size - SrcOffset;
  385. BytesToCopy = MIN(Size, SrcSize);
  386. RtlCopyMemory(DestBuf, SrcRB->ipr_buffer + SrcOffset, BytesToCopy);
  387. if (BytesToCopy == SrcSize) {
  388. // Copied everything from this buffer.
  389. SrcRB = SrcRB->ipr_next;
  390. SrcOffset = 0;
  391. }
  392. DestBuf += BytesToCopy;
  393. Size -= BytesToCopy;
  394. }
  395. }
  396. //* CopyFlatToNdis - Copy a flat buffer to an NDIS_BUFFER chain.
  397. //
  398. // A utility function to copy a flat buffer to an NDIS buffer chain. We
  399. // assume that the NDIS_BUFFER chain is big enough to hold the copy amount;
  400. // in a debug build we'll debugcheck if this isn't true. We return a pointer
  401. // to the buffer where we stopped copying, and an offset into that buffer.
  402. // This is useful for copying in pieces into the chain.
  403. //
  404. // Input: DestBuf - Destination NDIS_BUFFER chain.
  405. // SrcBuf - Src flat buffer.
  406. // Size - Size in bytes to copy.
  407. // StartOffset - Pointer to start of offset into first buffer in
  408. // chain. Filled in on return with the offset to
  409. // copy into next.
  410. // BytesCopied - Pointer to a variable into which to store the
  411. // number of bytes copied by this operation
  412. //
  413. // Returns: Pointer to next buffer in chain to copy into.
  414. //
  415. PNDIS_BUFFER
  416. CopyFlatToNdis(PNDIS_BUFFER DestBuf, uchar * SrcBuf, uint Size,
  417. uint * StartOffset, uint * BytesCopied)
  418. {
  419. NTSTATUS Status = 0;
  420. *BytesCopied = 0;
  421. Status = TcpipCopyBufferToNdisBuffer(SrcBuf, 0, Size, DestBuf, *StartOffset,
  422. (PULONG)BytesCopied);
  423. *StartOffset += *BytesCopied;
  424. //
  425. // Always return the first buffer, since the TdiCopy function handles
  426. // finding the appropriate buffer based on offset.
  427. //
  428. return (DestBuf);
  429. }
  430. PMDL
  431. CopyFlatToMdl(PMDL DestBuf, uchar *SrcBuf, uint Size,
  432. uint *StartOffset, uint *BytesCopied
  433. )
  434. {
  435. NTSTATUS Status = 0;
  436. *BytesCopied = 0;
  437. Status = TdiCopyBufferToMdl(
  438. SrcBuf,
  439. 0,
  440. Size,
  441. DestBuf,
  442. *StartOffset,
  443. (PULONG)BytesCopied);
  444. *StartOffset += *BytesCopied;
  445. return (DestBuf);
  446. }
  447. //* BuildTAAddress - Builds a TA Address.
  448. //
  449. // Called to fill in the fields of a TA Address.
  450. //
  451. // Input: TAAddr - Buffer to be filled in as TA Address structure.
  452. // Addr - IP Address to fill in.
  453. // Port - Port to be filled in.
  454. //
  455. // Returns: Pointer to the byte after the end of the current TA Address.
  456. //
  457. FORCEINLINE
  458. PVOID
  459. BuildTAAddress(PTA_ADDRESS TAAddr, IPAddr Addr, ushort Port)
  460. {
  461. TAAddr->AddressType = TDI_ADDRESS_TYPE_IP;
  462. TAAddr->AddressLength = sizeof(TDI_ADDRESS_IP);
  463. ((PTDI_ADDRESS_IP) TAAddr->Address)->sin_port = Port;
  464. ((PTDI_ADDRESS_IP) TAAddr->Address)->in_addr = Addr;
  465. memset(((PTDI_ADDRESS_IP) TAAddr->Address)->sin_zero, 0,
  466. sizeof(((PTDI_ADDRESS_IP) TAAddr->Address)->sin_zero));
  467. return ((PUCHAR)TAAddr + FIELD_OFFSET(TA_ADDRESS, Address) +
  468. TAAddr->AddressLength);
  469. }
  470. //* BuildTDIAddress - Build a TDI address structure.
  471. //
  472. // Called when we need to build a TDI address structure. We fill in
  473. // the specifed buffer with the correct information in the correct
  474. // format.
  475. //
  476. // Input: Buffer - Buffer to be filled in as TDI address structure.
  477. // Addr - IP Address to fill in.
  478. // Port - Port to be filled in.
  479. //
  480. // Returns: Pointer to the byte after the end of the first TA Address.
  481. //
  482. PVOID
  483. BuildTDIAddress(uchar * Buffer, IPAddr Addr, ushort Port)
  484. {
  485. PTRANSPORT_ADDRESS XportAddr;
  486. XportAddr = (PTRANSPORT_ADDRESS) Buffer;
  487. XportAddr->TAAddressCount = 1;
  488. return BuildTAAddress(XportAddr->Address, Addr, Port);
  489. }
  490. //* AppendTDIAddress - Appends a TA Address to a TDI address structure.
  491. //
  492. // Called to add another TA Address to a TDI Address strcutre.
  493. //
  494. // Input: Buffer - Buffer pointing to a TDI address structure.
  495. // Addr - IP Address to fill in.
  496. // Port - Port to be filled in.
  497. //
  498. // Returns: Pointer to the byte after the end of the last TA Address.
  499. //
  500. PVOID
  501. AppendTDIAddress(uchar * Buffer, uchar * NextAddress, IPAddr Addr, ushort Port)
  502. {
  503. PTRANSPORT_ADDRESS XportAddr;
  504. XportAddr = (PTRANSPORT_ADDRESS) Buffer;
  505. XportAddr->TAAddressCount++;
  506. return BuildTAAddress((PTA_ADDRESS)NextAddress, Addr, Port);
  507. }
  508. //* UpdateConnInfo - Update a connection information structure.
  509. //
  510. // Called when we need to update a connection information structure. We
  511. // copy any options, and create a transport address. If any buffer is
  512. // too small we return an error.
  513. //
  514. // Input: ConnInfo - Pointer to TDI_CONNECTION_INFORMATION struc
  515. // to be filled in.
  516. // OptInfo - Pointer to IP options information.
  517. // SrcAddress - Source IP address.
  518. // SrcPort - Source port.
  519. //
  520. // Returns: TDI_SUCCESS if it worked, TDI_BUFFER_OVERFLOW for an error.
  521. //
  522. TDI_STATUS
  523. UpdateConnInfo(PTDI_CONNECTION_INFORMATION ConnInfo, IPOptInfo * OptInfo,
  524. IPAddr SrcAddress, ushort SrcPort)
  525. {
  526. TDI_STATUS Status = TDI_SUCCESS; // Default status to return.
  527. uint AddrLength, OptLength;
  528. if (ConnInfo != NULL) {
  529. ConnInfo->UserDataLength = 0; // No user data.
  530. // Fill in the options. If the provided buffer is too small,
  531. // we'll truncate the options and return an error. Otherwise
  532. // we'll copy the whole IP option buffer.
  533. if (ConnInfo->OptionsLength) {
  534. if (ConnInfo->OptionsLength < OptInfo->ioi_optlength) {
  535. Status = TDI_BUFFER_OVERFLOW;
  536. OptLength = ConnInfo->OptionsLength;
  537. } else
  538. OptLength = OptInfo->ioi_optlength;
  539. RtlCopyMemory(ConnInfo->Options, OptInfo->ioi_options, OptLength);
  540. ConnInfo->OptionsLength = OptLength;
  541. }
  542. // Options are copied. Build a TRANSPORT_ADDRESS structure in
  543. // the buffer.
  544. AddrLength = ConnInfo->RemoteAddressLength;
  545. if (AddrLength) {
  546. // Make sure we have at least enough to fill in the count and type.
  547. if (AddrLength >= TCP_TA_SIZE) {
  548. // The address fits. Fill it in.
  549. ConnInfo->RemoteAddressLength = TCP_TA_SIZE;
  550. BuildTDIAddress(ConnInfo->RemoteAddress, SrcAddress, SrcPort);
  551. } else {
  552. ConnInfo->RemoteAddressLength = 0;
  553. Status = TDI_INVALID_PARAMETER;
  554. }
  555. }
  556. }
  557. return Status;
  558. }