1 | /** @file
|
---|
2 | The implementation of a dispatch routine for processing TCP requests.
|
---|
3 |
|
---|
4 | Copyright (c) 2009 - 2014, Intel Corporation. All rights reserved.<BR>
|
---|
5 |
|
---|
6 | This program and the accompanying materials
|
---|
7 | are licensed and made available under the terms and conditions of the BSD License
|
---|
8 | which accompanies this distribution. The full text of the license may be found at
|
---|
9 | http://opensource.org/licenses/bsd-license.php.
|
---|
10 |
|
---|
11 | THE PROGRAM IS DISTRIBUTED UNDER THE BSD LICENSE ON AN "AS IS" BASIS,
|
---|
12 | WITHOUT WARRANTIES OR REPRESENTATIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED.
|
---|
13 |
|
---|
14 | **/
|
---|
15 |
|
---|
16 | #include "TcpMain.h"
|
---|
17 |
|
---|
18 | /**
|
---|
19 | Add or remove a route entry in the IP route table associated with this TCP instance.
|
---|
20 |
|
---|
21 | @param[in] Tcb Pointer to the TCP_CB of this TCP instance.
|
---|
22 | @param[in] RouteInfo Pointer to the route information to be processed.
|
---|
23 |
|
---|
24 | @retval EFI_SUCCESS The operation completed successfully.
|
---|
25 | @retval EFI_NOT_STARTED The driver instance has not been started.
|
---|
26 | @retval EFI_NO_MAPPING When using the default address, configuration(DHCP,
|
---|
27 | BOOTP, RARP, etc.) is not finished yet.
|
---|
28 | @retval EFI_OUT_OF_RESOURCES Could not add the entry to the routing table.
|
---|
29 | @retval EFI_NOT_FOUND This route is not in the routing table
|
---|
30 | (when RouteInfo->DeleteRoute is TRUE).
|
---|
31 | @retval EFI_ACCESS_DENIED The route is already defined in the routing table
|
---|
32 | (when RouteInfo->DeleteRoute is FALSE).
|
---|
33 | **/
|
---|
34 | EFI_STATUS
|
---|
35 | Tcp4Route (
|
---|
36 | IN TCP_CB *Tcb,
|
---|
37 | IN TCP4_ROUTE_INFO *RouteInfo
|
---|
38 | )
|
---|
39 | {
|
---|
40 | IP_IO_IP_PROTOCOL Ip;
|
---|
41 |
|
---|
42 | Ip = Tcb->IpInfo->Ip;
|
---|
43 |
|
---|
44 | ASSERT (Ip.Ip4!= NULL);
|
---|
45 |
|
---|
46 | return Ip.Ip4->Routes (
|
---|
47 | Ip.Ip4,
|
---|
48 | RouteInfo->DeleteRoute,
|
---|
49 | RouteInfo->SubnetAddress,
|
---|
50 | RouteInfo->SubnetMask,
|
---|
51 | RouteInfo->GatewayAddress
|
---|
52 | );
|
---|
53 |
|
---|
54 | }
|
---|
55 |
|
---|
56 | /**
|
---|
57 | Get the operational settings of this TCPv4 instance.
|
---|
58 |
|
---|
59 | @param[in] Tcb Pointer to the TCP_CB of this TCP instance.
|
---|
60 | @param[in, out] Mode Pointer to the buffer to store the operational
|
---|
61 | settings.
|
---|
62 |
|
---|
63 | @retval EFI_SUCCESS The mode data was read.
|
---|
64 | @retval EFI_NOT_STARTED No configuration data is available because this
|
---|
65 | instance hasn't been started.
|
---|
66 |
|
---|
67 | **/
|
---|
68 | EFI_STATUS
|
---|
69 | Tcp4GetMode (
|
---|
70 | IN TCP_CB *Tcb,
|
---|
71 | IN OUT TCP4_MODE_DATA *Mode
|
---|
72 | )
|
---|
73 | {
|
---|
74 | SOCKET *Sock;
|
---|
75 | EFI_TCP4_CONFIG_DATA *ConfigData;
|
---|
76 | EFI_TCP4_ACCESS_POINT *AccessPoint;
|
---|
77 | EFI_TCP4_OPTION *Option;
|
---|
78 | EFI_IP4_PROTOCOL *Ip;
|
---|
79 |
|
---|
80 | Sock = Tcb->Sk;
|
---|
81 |
|
---|
82 | if (!SOCK_IS_CONFIGURED (Sock) && (Mode->Tcp4ConfigData != NULL)) {
|
---|
83 | return EFI_NOT_STARTED;
|
---|
84 | }
|
---|
85 |
|
---|
86 | if (Mode->Tcp4State != NULL) {
|
---|
87 | *(Mode->Tcp4State) = (EFI_TCP4_CONNECTION_STATE) Tcb->State;
|
---|
88 | }
|
---|
89 |
|
---|
90 | if (Mode->Tcp4ConfigData != NULL) {
|
---|
91 |
|
---|
92 | ConfigData = Mode->Tcp4ConfigData;
|
---|
93 | AccessPoint = &(ConfigData->AccessPoint);
|
---|
94 | Option = ConfigData->ControlOption;
|
---|
95 |
|
---|
96 | ConfigData->TypeOfService = Tcb->Tos;
|
---|
97 | ConfigData->TimeToLive = Tcb->Ttl;
|
---|
98 |
|
---|
99 | AccessPoint->UseDefaultAddress = Tcb->UseDefaultAddr;
|
---|
100 |
|
---|
101 | CopyMem (&AccessPoint->StationAddress, &Tcb->LocalEnd.Ip, sizeof (EFI_IPv4_ADDRESS));
|
---|
102 |
|
---|
103 | AccessPoint->SubnetMask = Tcb->SubnetMask;
|
---|
104 | AccessPoint->StationPort = NTOHS (Tcb->LocalEnd.Port);
|
---|
105 |
|
---|
106 | CopyMem (&AccessPoint->RemoteAddress, &Tcb->RemoteEnd.Ip, sizeof (EFI_IPv4_ADDRESS));
|
---|
107 |
|
---|
108 | AccessPoint->RemotePort = NTOHS (Tcb->RemoteEnd.Port);
|
---|
109 | AccessPoint->ActiveFlag = (BOOLEAN) (Tcb->State != TCP_LISTEN);
|
---|
110 |
|
---|
111 | if (Option != NULL) {
|
---|
112 | Option->ReceiveBufferSize = GET_RCV_BUFFSIZE (Tcb->Sk);
|
---|
113 | Option->SendBufferSize = GET_SND_BUFFSIZE (Tcb->Sk);
|
---|
114 | Option->MaxSynBackLog = GET_BACKLOG (Tcb->Sk);
|
---|
115 |
|
---|
116 | Option->ConnectionTimeout = Tcb->ConnectTimeout / TCP_TICK_HZ;
|
---|
117 | Option->DataRetries = Tcb->MaxRexmit;
|
---|
118 | Option->FinTimeout = Tcb->FinWait2Timeout / TCP_TICK_HZ;
|
---|
119 | Option->TimeWaitTimeout = Tcb->TimeWaitTimeout / TCP_TICK_HZ;
|
---|
120 | Option->KeepAliveProbes = Tcb->MaxKeepAlive;
|
---|
121 | Option->KeepAliveTime = Tcb->KeepAliveIdle / TCP_TICK_HZ;
|
---|
122 | Option->KeepAliveInterval = Tcb->KeepAlivePeriod / TCP_TICK_HZ;
|
---|
123 |
|
---|
124 | Option->EnableNagle = (BOOLEAN) (!TCP_FLG_ON (Tcb->CtrlFlag, TCP_CTRL_NO_NAGLE));
|
---|
125 | Option->EnableTimeStamp = (BOOLEAN) (!TCP_FLG_ON (Tcb->CtrlFlag, TCP_CTRL_NO_TS));
|
---|
126 | Option->EnableWindowScaling = (BOOLEAN) (!TCP_FLG_ON (Tcb->CtrlFlag, TCP_CTRL_NO_WS));
|
---|
127 |
|
---|
128 | Option->EnableSelectiveAck = FALSE;
|
---|
129 | Option->EnablePathMtuDiscovery = FALSE;
|
---|
130 | }
|
---|
131 | }
|
---|
132 |
|
---|
133 | Ip = Tcb->IpInfo->Ip.Ip4;
|
---|
134 | ASSERT (Ip != NULL);
|
---|
135 |
|
---|
136 | return Ip->GetModeData (Ip, Mode->Ip4ModeData, Mode->MnpConfigData, Mode->SnpModeData);
|
---|
137 | }
|
---|
138 |
|
---|
139 | /**
|
---|
140 | Get the operational settings of this TCPv6 instance.
|
---|
141 |
|
---|
142 | @param[in] Tcb Pointer to the TCP_CB of this TCP instance.
|
---|
143 | @param[in, out] Mode Pointer to the buffer to store the operational
|
---|
144 | settings.
|
---|
145 |
|
---|
146 | @retval EFI_SUCCESS The mode data was read.
|
---|
147 | @retval EFI_NOT_STARTED No configuration data is available because this
|
---|
148 | instance hasn't been started.
|
---|
149 |
|
---|
150 | **/
|
---|
151 | EFI_STATUS
|
---|
152 | Tcp6GetMode (
|
---|
153 | IN TCP_CB *Tcb,
|
---|
154 | IN OUT TCP6_MODE_DATA *Mode
|
---|
155 | )
|
---|
156 | {
|
---|
157 | SOCKET *Sock;
|
---|
158 | EFI_TCP6_CONFIG_DATA *ConfigData;
|
---|
159 | EFI_TCP6_ACCESS_POINT *AccessPoint;
|
---|
160 | EFI_TCP6_OPTION *Option;
|
---|
161 | EFI_IP6_PROTOCOL *Ip;
|
---|
162 |
|
---|
163 | Sock = Tcb->Sk;
|
---|
164 |
|
---|
165 | if (!SOCK_IS_CONFIGURED (Sock) && (Mode->Tcp6ConfigData != NULL)) {
|
---|
166 | return EFI_NOT_STARTED;
|
---|
167 | }
|
---|
168 |
|
---|
169 | if (Mode->Tcp6State != NULL) {
|
---|
170 | *(Mode->Tcp6State) = (EFI_TCP6_CONNECTION_STATE) (Tcb->State);
|
---|
171 | }
|
---|
172 |
|
---|
173 | if (Mode->Tcp6ConfigData != NULL) {
|
---|
174 |
|
---|
175 | ConfigData = Mode->Tcp6ConfigData;
|
---|
176 | AccessPoint = &(ConfigData->AccessPoint);
|
---|
177 | Option = ConfigData->ControlOption;
|
---|
178 |
|
---|
179 | ConfigData->TrafficClass = Tcb->Tos;
|
---|
180 | ConfigData->HopLimit = Tcb->Ttl;
|
---|
181 |
|
---|
182 | AccessPoint->StationPort = NTOHS (Tcb->LocalEnd.Port);
|
---|
183 | AccessPoint->RemotePort = NTOHS (Tcb->RemoteEnd.Port);
|
---|
184 | AccessPoint->ActiveFlag = (BOOLEAN) (Tcb->State != TCP_LISTEN);
|
---|
185 |
|
---|
186 | IP6_COPY_ADDRESS (&AccessPoint->StationAddress, &Tcb->LocalEnd.Ip);
|
---|
187 | IP6_COPY_ADDRESS (&AccessPoint->RemoteAddress, &Tcb->RemoteEnd.Ip);
|
---|
188 |
|
---|
189 | if (Option != NULL) {
|
---|
190 | Option->ReceiveBufferSize = GET_RCV_BUFFSIZE (Tcb->Sk);
|
---|
191 | Option->SendBufferSize = GET_SND_BUFFSIZE (Tcb->Sk);
|
---|
192 | Option->MaxSynBackLog = GET_BACKLOG (Tcb->Sk);
|
---|
193 |
|
---|
194 | Option->ConnectionTimeout = Tcb->ConnectTimeout / TCP_TICK_HZ;
|
---|
195 | Option->DataRetries = Tcb->MaxRexmit;
|
---|
196 | Option->FinTimeout = Tcb->FinWait2Timeout / TCP_TICK_HZ;
|
---|
197 | Option->TimeWaitTimeout = Tcb->TimeWaitTimeout / TCP_TICK_HZ;
|
---|
198 | Option->KeepAliveProbes = Tcb->MaxKeepAlive;
|
---|
199 | Option->KeepAliveTime = Tcb->KeepAliveIdle / TCP_TICK_HZ;
|
---|
200 | Option->KeepAliveInterval = Tcb->KeepAlivePeriod / TCP_TICK_HZ;
|
---|
201 |
|
---|
202 | Option->EnableNagle = (BOOLEAN) (!TCP_FLG_ON (Tcb->CtrlFlag, TCP_CTRL_NO_NAGLE));
|
---|
203 | Option->EnableTimeStamp = (BOOLEAN) (!TCP_FLG_ON (Tcb->CtrlFlag, TCP_CTRL_NO_TS));
|
---|
204 | Option->EnableWindowScaling = (BOOLEAN) (!TCP_FLG_ON (Tcb->CtrlFlag, TCP_CTRL_NO_WS));
|
---|
205 |
|
---|
206 | Option->EnableSelectiveAck = FALSE;
|
---|
207 | Option->EnablePathMtuDiscovery = FALSE;
|
---|
208 | }
|
---|
209 | }
|
---|
210 |
|
---|
211 | Ip = Tcb->IpInfo->Ip.Ip6;
|
---|
212 | ASSERT (Ip != NULL);
|
---|
213 |
|
---|
214 | return Ip->GetModeData (Ip, Mode->Ip6ModeData, Mode->MnpConfigData, Mode->SnpModeData);
|
---|
215 | }
|
---|
216 |
|
---|
217 | /**
|
---|
218 | If TcpAp->StationPort isn't zero, check whether the access point
|
---|
219 | is registered, else generate a random station port for this
|
---|
220 | access point.
|
---|
221 |
|
---|
222 | @param[in] TcpAp Pointer to the access point.
|
---|
223 | @param[in] IpVersion IP_VERSION_4 or IP_VERSION_6
|
---|
224 |
|
---|
225 | @retval EFI_SUCCESS The check passed or the port is assigned.
|
---|
226 | @retval EFI_INVALID_PARAMETER The non-zero station port is already used.
|
---|
227 | @retval EFI_OUT_OF_RESOURCES No port can be allocated.
|
---|
228 |
|
---|
229 | **/
|
---|
230 | EFI_STATUS
|
---|
231 | TcpBind (
|
---|
232 | IN TCP_ACCESS_POINT *TcpAp,
|
---|
233 | IN UINT8 IpVersion
|
---|
234 | )
|
---|
235 | {
|
---|
236 | BOOLEAN Cycle;
|
---|
237 | EFI_IP_ADDRESS Local;
|
---|
238 | UINT16 *Port;
|
---|
239 | UINT16 *RandomPort;
|
---|
240 |
|
---|
241 | if (IpVersion == IP_VERSION_4) {
|
---|
242 | CopyMem (&Local, &TcpAp->Tcp4Ap.StationAddress, sizeof (EFI_IPv4_ADDRESS));
|
---|
243 | Port = &TcpAp->Tcp4Ap.StationPort;
|
---|
244 | RandomPort = &mTcp4RandomPort;
|
---|
245 | } else {
|
---|
246 | IP6_COPY_ADDRESS (&Local, &TcpAp->Tcp6Ap.StationAddress);
|
---|
247 | Port = &TcpAp->Tcp6Ap.StationPort;
|
---|
248 | RandomPort = &mTcp6RandomPort;
|
---|
249 | }
|
---|
250 |
|
---|
251 | if (0 != *Port) {
|
---|
252 | //
|
---|
253 | // Check if a same endpoing is bound.
|
---|
254 | //
|
---|
255 | if (TcpFindTcbByPeer (&Local, *Port, IpVersion)) {
|
---|
256 |
|
---|
257 | return EFI_INVALID_PARAMETER;
|
---|
258 | }
|
---|
259 | } else {
|
---|
260 | //
|
---|
261 | // generate a random port
|
---|
262 | //
|
---|
263 | Cycle = FALSE;
|
---|
264 |
|
---|
265 | if (TCP_PORT_USER_RESERVED == *RandomPort) {
|
---|
266 | *RandomPort = TCP_PORT_KNOWN;
|
---|
267 | }
|
---|
268 |
|
---|
269 | (*RandomPort)++;
|
---|
270 |
|
---|
271 | while (TcpFindTcbByPeer (&Local, *RandomPort, IpVersion)) {
|
---|
272 | (*RandomPort)++;
|
---|
273 |
|
---|
274 | if (*RandomPort <= TCP_PORT_KNOWN) {
|
---|
275 | if (Cycle) {
|
---|
276 | DEBUG (
|
---|
277 | (EFI_D_ERROR,
|
---|
278 | "TcpBind: no port can be allocated for this pcb\n")
|
---|
279 | );
|
---|
280 | return EFI_OUT_OF_RESOURCES;
|
---|
281 | }
|
---|
282 |
|
---|
283 | *RandomPort = TCP_PORT_KNOWN + 1;
|
---|
284 |
|
---|
285 | Cycle = TRUE;
|
---|
286 | }
|
---|
287 | }
|
---|
288 |
|
---|
289 | *Port = *RandomPort;
|
---|
290 | }
|
---|
291 |
|
---|
292 | return EFI_SUCCESS;
|
---|
293 | }
|
---|
294 |
|
---|
295 | /**
|
---|
296 | Flush the Tcb add its associated protocols.
|
---|
297 |
|
---|
298 | @param[in, out] Tcb Pointer to the TCP_CB to be flushed.
|
---|
299 |
|
---|
300 | **/
|
---|
301 | VOID
|
---|
302 | TcpFlushPcb (
|
---|
303 | IN OUT TCP_CB *Tcb
|
---|
304 | )
|
---|
305 | {
|
---|
306 | SOCKET *Sock;
|
---|
307 |
|
---|
308 | IpIoConfigIp (Tcb->IpInfo, NULL);
|
---|
309 |
|
---|
310 | Sock = Tcb->Sk;
|
---|
311 |
|
---|
312 | if (SOCK_IS_CONFIGURED (Sock)) {
|
---|
313 | RemoveEntryList (&Tcb->List);
|
---|
314 |
|
---|
315 | if (Sock->DevicePath != NULL) {
|
---|
316 | //
|
---|
317 | // Uninstall the device path protocl.
|
---|
318 | //
|
---|
319 | gBS->UninstallProtocolInterface (
|
---|
320 | Sock->SockHandle,
|
---|
321 | &gEfiDevicePathProtocolGuid,
|
---|
322 | Sock->DevicePath
|
---|
323 | );
|
---|
324 |
|
---|
325 | FreePool (Sock->DevicePath);
|
---|
326 | Sock->DevicePath = NULL;
|
---|
327 | }
|
---|
328 | }
|
---|
329 |
|
---|
330 | NetbufFreeList (&Tcb->SndQue);
|
---|
331 | NetbufFreeList (&Tcb->RcvQue);
|
---|
332 | Tcb->State = TCP_CLOSED;
|
---|
333 | Tcb->RemoteIpZero = FALSE;
|
---|
334 | }
|
---|
335 |
|
---|
336 | /**
|
---|
337 | Attach a Pcb to the socket.
|
---|
338 |
|
---|
339 | @param[in] Sk Pointer to the socket of this TCP instance.
|
---|
340 |
|
---|
341 | @retval EFI_SUCCESS The operation completed successfully.
|
---|
342 | @retval EFI_OUT_OF_RESOURCES Failed due to resource limits.
|
---|
343 |
|
---|
344 | **/
|
---|
345 | EFI_STATUS
|
---|
346 | TcpAttachPcb (
|
---|
347 | IN SOCKET *Sk
|
---|
348 | )
|
---|
349 | {
|
---|
350 | TCP_CB *Tcb;
|
---|
351 | TCP_PROTO_DATA *ProtoData;
|
---|
352 | IP_IO *IpIo;
|
---|
353 | EFI_STATUS Status;
|
---|
354 | VOID *Ip;
|
---|
355 | EFI_GUID *IpProtocolGuid;
|
---|
356 |
|
---|
357 | if (Sk->IpVersion == IP_VERSION_4) {
|
---|
358 | IpProtocolGuid = &gEfiIp4ProtocolGuid;
|
---|
359 | } else {
|
---|
360 | IpProtocolGuid = &gEfiIp6ProtocolGuid;
|
---|
361 | }
|
---|
362 |
|
---|
363 | Tcb = AllocateZeroPool (sizeof (TCP_CB));
|
---|
364 |
|
---|
365 | if (Tcb == NULL) {
|
---|
366 |
|
---|
367 | DEBUG ((EFI_D_ERROR, "TcpConfigurePcb: failed to allocate a TCB\n"));
|
---|
368 |
|
---|
369 | return EFI_OUT_OF_RESOURCES;
|
---|
370 | }
|
---|
371 |
|
---|
372 | ProtoData = (TCP_PROTO_DATA *) Sk->ProtoReserved;
|
---|
373 | IpIo = ProtoData->TcpService->IpIo;
|
---|
374 |
|
---|
375 | //
|
---|
376 | // Create an IpInfo for this Tcb.
|
---|
377 | //
|
---|
378 | Tcb->IpInfo = IpIoAddIp (IpIo);
|
---|
379 | if (Tcb->IpInfo == NULL) {
|
---|
380 |
|
---|
381 | FreePool (Tcb);
|
---|
382 | return EFI_OUT_OF_RESOURCES;
|
---|
383 | }
|
---|
384 |
|
---|
385 | //
|
---|
386 | // Open the new created IP instance BY_CHILD.
|
---|
387 | //
|
---|
388 | Status = gBS->OpenProtocol (
|
---|
389 | Tcb->IpInfo->ChildHandle,
|
---|
390 | IpProtocolGuid,
|
---|
391 | &Ip,
|
---|
392 | IpIo->Image,
|
---|
393 | Sk->SockHandle,
|
---|
394 | EFI_OPEN_PROTOCOL_BY_CHILD_CONTROLLER
|
---|
395 | );
|
---|
396 | if (EFI_ERROR (Status)) {
|
---|
397 | IpIoRemoveIp (IpIo, Tcb->IpInfo);
|
---|
398 | return Status;
|
---|
399 | }
|
---|
400 |
|
---|
401 | InitializeListHead (&Tcb->List);
|
---|
402 | InitializeListHead (&Tcb->SndQue);
|
---|
403 | InitializeListHead (&Tcb->RcvQue);
|
---|
404 |
|
---|
405 | Tcb->State = TCP_CLOSED;
|
---|
406 | Tcb->Sk = Sk;
|
---|
407 | ProtoData->TcpPcb = Tcb;
|
---|
408 |
|
---|
409 | return EFI_SUCCESS;
|
---|
410 | }
|
---|
411 |
|
---|
412 | /**
|
---|
413 | Detach the Pcb of the socket.
|
---|
414 |
|
---|
415 | @param[in, out] Sk Pointer to the socket of this TCP instance.
|
---|
416 |
|
---|
417 | **/
|
---|
418 | VOID
|
---|
419 | TcpDetachPcb (
|
---|
420 | IN OUT SOCKET *Sk
|
---|
421 | )
|
---|
422 | {
|
---|
423 | TCP_PROTO_DATA *ProtoData;
|
---|
424 | TCP_CB *Tcb;
|
---|
425 | EFI_GUID *IpProtocolGuid;
|
---|
426 |
|
---|
427 | if (Sk->IpVersion == IP_VERSION_4) {
|
---|
428 | IpProtocolGuid = &gEfiIp4ProtocolGuid;
|
---|
429 | } else {
|
---|
430 | IpProtocolGuid = &gEfiIp6ProtocolGuid;
|
---|
431 | }
|
---|
432 |
|
---|
433 | ProtoData = (TCP_PROTO_DATA *) Sk->ProtoReserved;
|
---|
434 | Tcb = ProtoData->TcpPcb;
|
---|
435 |
|
---|
436 | ASSERT (Tcb != NULL);
|
---|
437 |
|
---|
438 | TcpFlushPcb (Tcb);
|
---|
439 |
|
---|
440 | //
|
---|
441 | // Close the IP protocol.
|
---|
442 | //
|
---|
443 | gBS->CloseProtocol (
|
---|
444 | Tcb->IpInfo->ChildHandle,
|
---|
445 | IpProtocolGuid,
|
---|
446 | ProtoData->TcpService->IpIo->Image,
|
---|
447 | Sk->SockHandle
|
---|
448 | );
|
---|
449 |
|
---|
450 | IpIoRemoveIp (ProtoData->TcpService->IpIo, Tcb->IpInfo);
|
---|
451 |
|
---|
452 | FreePool (Tcb);
|
---|
453 |
|
---|
454 | ProtoData->TcpPcb = NULL;
|
---|
455 | }
|
---|
456 |
|
---|
457 | /**
|
---|
458 | Configure the Pcb using CfgData.
|
---|
459 |
|
---|
460 | @param[in] Sk Pointer to the socket of this TCP instance.
|
---|
461 | @param[in] CfgData Pointer to the TCP configuration data.
|
---|
462 |
|
---|
463 | @retval EFI_SUCCESS The operation completed successfully.
|
---|
464 | @retval EFI_INVALID_PARAMETER A same access point has been configured in
|
---|
465 | another TCP instance.
|
---|
466 | @retval EFI_OUT_OF_RESOURCES Failed due to resource limits.
|
---|
467 |
|
---|
468 | **/
|
---|
469 | EFI_STATUS
|
---|
470 | TcpConfigurePcb (
|
---|
471 | IN SOCKET *Sk,
|
---|
472 | IN TCP_CONFIG_DATA *CfgData
|
---|
473 | )
|
---|
474 | {
|
---|
475 | IP_IO_IP_CONFIG_DATA IpCfgData;
|
---|
476 | EFI_STATUS Status;
|
---|
477 | EFI_TCP4_OPTION *Option;
|
---|
478 | TCP_PROTO_DATA *TcpProto;
|
---|
479 | TCP_CB *Tcb;
|
---|
480 | TCP_ACCESS_POINT *TcpAp;
|
---|
481 |
|
---|
482 | ASSERT ((CfgData != NULL) && (Sk != NULL) && (Sk->SockHandle != NULL));
|
---|
483 |
|
---|
484 | TcpProto = (TCP_PROTO_DATA *) Sk->ProtoReserved;
|
---|
485 | Tcb = TcpProto->TcpPcb;
|
---|
486 |
|
---|
487 | ASSERT (Tcb != NULL);
|
---|
488 |
|
---|
489 | if (Sk->IpVersion == IP_VERSION_4) {
|
---|
490 | //
|
---|
491 | // Add Ip for send pkt to the peer
|
---|
492 | //
|
---|
493 | CopyMem (&IpCfgData.Ip4CfgData, &mIp4IoDefaultIpConfigData, sizeof (EFI_IP4_CONFIG_DATA));
|
---|
494 | IpCfgData.Ip4CfgData.DefaultProtocol = EFI_IP_PROTO_TCP;
|
---|
495 | IpCfgData.Ip4CfgData.TypeOfService = CfgData->Tcp4CfgData.TypeOfService;
|
---|
496 | IpCfgData.Ip4CfgData.TimeToLive = CfgData->Tcp4CfgData.TimeToLive;
|
---|
497 | IpCfgData.Ip4CfgData.UseDefaultAddress = CfgData->Tcp4CfgData.AccessPoint.UseDefaultAddress;
|
---|
498 | IpCfgData.Ip4CfgData.SubnetMask = CfgData->Tcp4CfgData.AccessPoint.SubnetMask;
|
---|
499 | IpCfgData.Ip4CfgData.ReceiveTimeout = (UINT32) (-1);
|
---|
500 | CopyMem (
|
---|
501 | &IpCfgData.Ip4CfgData.StationAddress,
|
---|
502 | &CfgData->Tcp4CfgData.AccessPoint.StationAddress,
|
---|
503 | sizeof (EFI_IPv4_ADDRESS)
|
---|
504 | );
|
---|
505 |
|
---|
506 | } else {
|
---|
507 | ASSERT (Sk->IpVersion == IP_VERSION_6);
|
---|
508 |
|
---|
509 | CopyMem (&IpCfgData.Ip6CfgData, &mIp6IoDefaultIpConfigData, sizeof (EFI_IP6_CONFIG_DATA));
|
---|
510 | IpCfgData.Ip6CfgData.DefaultProtocol = EFI_IP_PROTO_TCP;
|
---|
511 | IpCfgData.Ip6CfgData.TrafficClass = CfgData->Tcp6CfgData.TrafficClass;
|
---|
512 | IpCfgData.Ip6CfgData.HopLimit = CfgData->Tcp6CfgData.HopLimit;
|
---|
513 | IpCfgData.Ip6CfgData.ReceiveTimeout = (UINT32) (-1);
|
---|
514 | IP6_COPY_ADDRESS (
|
---|
515 | &IpCfgData.Ip6CfgData.StationAddress,
|
---|
516 | &CfgData->Tcp6CfgData.AccessPoint.StationAddress
|
---|
517 | );
|
---|
518 | IP6_COPY_ADDRESS (
|
---|
519 | &IpCfgData.Ip6CfgData.DestinationAddress,
|
---|
520 | &CfgData->Tcp6CfgData.AccessPoint.RemoteAddress
|
---|
521 | );
|
---|
522 | }
|
---|
523 |
|
---|
524 | //
|
---|
525 | // Configure the IP instance this Tcb consumes.
|
---|
526 | //
|
---|
527 | Status = IpIoConfigIp (Tcb->IpInfo, &IpCfgData);
|
---|
528 | if (EFI_ERROR (Status)) {
|
---|
529 | goto OnExit;
|
---|
530 | }
|
---|
531 |
|
---|
532 | if (Sk->IpVersion == IP_VERSION_4) {
|
---|
533 | //
|
---|
534 | // Get the default address information if the instance is configured to use default address.
|
---|
535 | //
|
---|
536 | CfgData->Tcp4CfgData.AccessPoint.StationAddress = IpCfgData.Ip4CfgData.StationAddress;
|
---|
537 | CfgData->Tcp4CfgData.AccessPoint.SubnetMask = IpCfgData.Ip4CfgData.SubnetMask;
|
---|
538 |
|
---|
539 | TcpAp = (TCP_ACCESS_POINT *) &CfgData->Tcp4CfgData.AccessPoint;
|
---|
540 | } else {
|
---|
541 | IP6_COPY_ADDRESS (
|
---|
542 | &CfgData->Tcp6CfgData.AccessPoint.StationAddress,
|
---|
543 | &IpCfgData.Ip6CfgData.StationAddress
|
---|
544 | );
|
---|
545 |
|
---|
546 | TcpAp = (TCP_ACCESS_POINT *) &CfgData->Tcp6CfgData.AccessPoint;
|
---|
547 | }
|
---|
548 |
|
---|
549 | //
|
---|
550 | // check if we can bind this endpoint in CfgData
|
---|
551 | //
|
---|
552 | Status = TcpBind (TcpAp, Sk->IpVersion);
|
---|
553 |
|
---|
554 | if (EFI_ERROR (Status)) {
|
---|
555 | DEBUG (
|
---|
556 | (EFI_D_ERROR,
|
---|
557 | "TcpConfigurePcb: Bind endpoint failed with %r\n",
|
---|
558 | Status)
|
---|
559 | );
|
---|
560 |
|
---|
561 | goto OnExit;
|
---|
562 | }
|
---|
563 |
|
---|
564 | //
|
---|
565 | // Initalize the operating information in this Tcb
|
---|
566 | //
|
---|
567 | ASSERT (Tcb->State == TCP_CLOSED &&
|
---|
568 | IsListEmpty (&Tcb->SndQue) &&
|
---|
569 | IsListEmpty (&Tcb->RcvQue));
|
---|
570 |
|
---|
571 | TCP_SET_FLG (Tcb->CtrlFlag, TCP_CTRL_NO_KEEPALIVE);
|
---|
572 | Tcb->State = TCP_CLOSED;
|
---|
573 |
|
---|
574 | Tcb->SndMss = 536;
|
---|
575 | Tcb->RcvMss = TcpGetRcvMss (Sk);
|
---|
576 |
|
---|
577 | Tcb->SRtt = 0;
|
---|
578 | Tcb->Rto = 3 * TCP_TICK_HZ;
|
---|
579 |
|
---|
580 | Tcb->CWnd = Tcb->SndMss;
|
---|
581 | Tcb->Ssthresh = 0xffffffff;
|
---|
582 |
|
---|
583 | Tcb->CongestState = TCP_CONGEST_OPEN;
|
---|
584 |
|
---|
585 | Tcb->KeepAliveIdle = TCP_KEEPALIVE_IDLE_MIN;
|
---|
586 | Tcb->KeepAlivePeriod = TCP_KEEPALIVE_PERIOD;
|
---|
587 | Tcb->MaxKeepAlive = TCP_MAX_KEEPALIVE;
|
---|
588 | Tcb->MaxRexmit = TCP_MAX_LOSS;
|
---|
589 | Tcb->FinWait2Timeout = TCP_FIN_WAIT2_TIME;
|
---|
590 | Tcb->TimeWaitTimeout = TCP_TIME_WAIT_TIME;
|
---|
591 | Tcb->ConnectTimeout = TCP_CONNECT_TIME;
|
---|
592 |
|
---|
593 | if (Sk->IpVersion == IP_VERSION_4) {
|
---|
594 | //
|
---|
595 | // initialize Tcb in the light of CfgData
|
---|
596 | //
|
---|
597 | Tcb->Ttl = CfgData->Tcp4CfgData.TimeToLive;
|
---|
598 | Tcb->Tos = CfgData->Tcp4CfgData.TypeOfService;
|
---|
599 |
|
---|
600 | Tcb->UseDefaultAddr = CfgData->Tcp4CfgData.AccessPoint.UseDefaultAddress;
|
---|
601 |
|
---|
602 | CopyMem (&Tcb->LocalEnd.Ip, &CfgData->Tcp4CfgData.AccessPoint.StationAddress, sizeof (IP4_ADDR));
|
---|
603 | Tcb->LocalEnd.Port = HTONS (CfgData->Tcp4CfgData.AccessPoint.StationPort);
|
---|
604 | Tcb->SubnetMask = CfgData->Tcp4CfgData.AccessPoint.SubnetMask;
|
---|
605 |
|
---|
606 | CopyMem (&Tcb->RemoteEnd.Ip, &CfgData->Tcp4CfgData.AccessPoint.RemoteAddress, sizeof (IP4_ADDR));
|
---|
607 | Tcb->RemoteEnd.Port = HTONS (CfgData->Tcp4CfgData.AccessPoint.RemotePort);
|
---|
608 |
|
---|
609 | Option = CfgData->Tcp4CfgData.ControlOption;
|
---|
610 | } else {
|
---|
611 | Tcb->Ttl = CfgData->Tcp6CfgData.HopLimit;
|
---|
612 | Tcb->Tos = CfgData->Tcp6CfgData.TrafficClass;
|
---|
613 |
|
---|
614 | IP6_COPY_ADDRESS (&Tcb->LocalEnd.Ip, &CfgData->Tcp6CfgData.AccessPoint.StationAddress);
|
---|
615 | Tcb->LocalEnd.Port = HTONS (CfgData->Tcp6CfgData.AccessPoint.StationPort);
|
---|
616 |
|
---|
617 | IP6_COPY_ADDRESS (&Tcb->RemoteEnd.Ip, &CfgData->Tcp6CfgData.AccessPoint.RemoteAddress);
|
---|
618 | Tcb->RemoteEnd.Port = HTONS (CfgData->Tcp6CfgData.AccessPoint.RemotePort);
|
---|
619 |
|
---|
620 | //
|
---|
621 | // Type EFI_TCP4_OPTION and EFI_TCP6_OPTION are the same.
|
---|
622 | //
|
---|
623 | Option = (EFI_TCP4_OPTION *) CfgData->Tcp6CfgData.ControlOption;
|
---|
624 | }
|
---|
625 |
|
---|
626 | if (Option != NULL) {
|
---|
627 | SET_RCV_BUFFSIZE (
|
---|
628 | Sk,
|
---|
629 | (UINT32) (TCP_COMP_VAL (
|
---|
630 | TCP_RCV_BUF_SIZE_MIN,
|
---|
631 | TCP_RCV_BUF_SIZE,
|
---|
632 | TCP_RCV_BUF_SIZE,
|
---|
633 | Option->ReceiveBufferSize
|
---|
634 | )
|
---|
635 | )
|
---|
636 | );
|
---|
637 | SET_SND_BUFFSIZE (
|
---|
638 | Sk,
|
---|
639 | (UINT32) (TCP_COMP_VAL (
|
---|
640 | TCP_SND_BUF_SIZE_MIN,
|
---|
641 | TCP_SND_BUF_SIZE,
|
---|
642 | TCP_SND_BUF_SIZE,
|
---|
643 | Option->SendBufferSize
|
---|
644 | )
|
---|
645 | )
|
---|
646 | );
|
---|
647 |
|
---|
648 | SET_BACKLOG (
|
---|
649 | Sk,
|
---|
650 | (UINT32) (TCP_COMP_VAL (
|
---|
651 | TCP_BACKLOG_MIN,
|
---|
652 | TCP_BACKLOG,
|
---|
653 | TCP_BACKLOG,
|
---|
654 | Option->MaxSynBackLog
|
---|
655 | )
|
---|
656 | )
|
---|
657 | );
|
---|
658 |
|
---|
659 | Tcb->MaxRexmit = (UINT16) TCP_COMP_VAL (
|
---|
660 | TCP_MAX_LOSS_MIN,
|
---|
661 | TCP_MAX_LOSS,
|
---|
662 | TCP_MAX_LOSS,
|
---|
663 | Option->DataRetries
|
---|
664 | );
|
---|
665 | Tcb->FinWait2Timeout = TCP_COMP_VAL (
|
---|
666 | TCP_FIN_WAIT2_TIME,
|
---|
667 | TCP_FIN_WAIT2_TIME_MAX,
|
---|
668 | TCP_FIN_WAIT2_TIME,
|
---|
669 | (UINT32) (Option->FinTimeout * TCP_TICK_HZ)
|
---|
670 | );
|
---|
671 |
|
---|
672 | if (Option->TimeWaitTimeout != 0) {
|
---|
673 | Tcb->TimeWaitTimeout = TCP_COMP_VAL (
|
---|
674 | TCP_TIME_WAIT_TIME,
|
---|
675 | TCP_TIME_WAIT_TIME_MAX,
|
---|
676 | TCP_TIME_WAIT_TIME,
|
---|
677 | (UINT32) (Option->TimeWaitTimeout * TCP_TICK_HZ)
|
---|
678 | );
|
---|
679 | } else {
|
---|
680 | Tcb->TimeWaitTimeout = 0;
|
---|
681 | }
|
---|
682 |
|
---|
683 | if (Option->KeepAliveProbes != 0) {
|
---|
684 | TCP_CLEAR_FLG (Tcb->CtrlFlag, TCP_CTRL_NO_KEEPALIVE);
|
---|
685 |
|
---|
686 | Tcb->MaxKeepAlive = (UINT8) TCP_COMP_VAL (
|
---|
687 | TCP_MAX_KEEPALIVE_MIN,
|
---|
688 | TCP_MAX_KEEPALIVE,
|
---|
689 | TCP_MAX_KEEPALIVE,
|
---|
690 | Option->KeepAliveProbes
|
---|
691 | );
|
---|
692 | Tcb->KeepAliveIdle = TCP_COMP_VAL (
|
---|
693 | TCP_KEEPALIVE_IDLE_MIN,
|
---|
694 | TCP_KEEPALIVE_IDLE_MAX,
|
---|
695 | TCP_KEEPALIVE_IDLE_MIN,
|
---|
696 | (UINT32) (Option->KeepAliveTime * TCP_TICK_HZ)
|
---|
697 | );
|
---|
698 | Tcb->KeepAlivePeriod = TCP_COMP_VAL (
|
---|
699 | TCP_KEEPALIVE_PERIOD_MIN,
|
---|
700 | TCP_KEEPALIVE_PERIOD,
|
---|
701 | TCP_KEEPALIVE_PERIOD,
|
---|
702 | (UINT32) (Option->KeepAliveInterval * TCP_TICK_HZ)
|
---|
703 | );
|
---|
704 | }
|
---|
705 |
|
---|
706 | Tcb->ConnectTimeout = TCP_COMP_VAL (
|
---|
707 | TCP_CONNECT_TIME_MIN,
|
---|
708 | TCP_CONNECT_TIME,
|
---|
709 | TCP_CONNECT_TIME,
|
---|
710 | (UINT32) (Option->ConnectionTimeout * TCP_TICK_HZ)
|
---|
711 | );
|
---|
712 |
|
---|
713 | if (!Option->EnableNagle) {
|
---|
714 | TCP_SET_FLG (Tcb->CtrlFlag, TCP_CTRL_NO_NAGLE);
|
---|
715 | }
|
---|
716 |
|
---|
717 | if (!Option->EnableTimeStamp) {
|
---|
718 | TCP_SET_FLG (Tcb->CtrlFlag, TCP_CTRL_NO_TS);
|
---|
719 | }
|
---|
720 |
|
---|
721 | if (!Option->EnableWindowScaling) {
|
---|
722 | TCP_SET_FLG (Tcb->CtrlFlag, TCP_CTRL_NO_WS);
|
---|
723 | }
|
---|
724 | }
|
---|
725 |
|
---|
726 | //
|
---|
727 | // The socket is bound, the <SrcIp, SrcPort, DstIp, DstPort> is
|
---|
728 | // determined, construct the IP device path and install it.
|
---|
729 | //
|
---|
730 | Status = TcpInstallDevicePath (Sk);
|
---|
731 | if (EFI_ERROR (Status)) {
|
---|
732 | goto OnExit;
|
---|
733 | }
|
---|
734 |
|
---|
735 | //
|
---|
736 | // update state of Tcb and socket
|
---|
737 | //
|
---|
738 | if (((Sk->IpVersion == IP_VERSION_4) && !CfgData->Tcp4CfgData.AccessPoint.ActiveFlag) ||
|
---|
739 | ((Sk->IpVersion == IP_VERSION_6) && !CfgData->Tcp6CfgData.AccessPoint.ActiveFlag)
|
---|
740 | ) {
|
---|
741 |
|
---|
742 | TcpSetState (Tcb, TCP_LISTEN);
|
---|
743 | SockSetState (Sk, SO_LISTENING);
|
---|
744 |
|
---|
745 | Sk->ConfigureState = SO_CONFIGURED_PASSIVE;
|
---|
746 | } else {
|
---|
747 |
|
---|
748 | Sk->ConfigureState = SO_CONFIGURED_ACTIVE;
|
---|
749 | }
|
---|
750 |
|
---|
751 | if (Sk->IpVersion == IP_VERSION_6) {
|
---|
752 | Tcb->Tick = TCP6_REFRESH_NEIGHBOR_TICK;
|
---|
753 |
|
---|
754 | if (NetIp6IsUnspecifiedAddr (&Tcb->RemoteEnd.Ip.v6)) {
|
---|
755 | Tcb->RemoteIpZero = TRUE;
|
---|
756 | }
|
---|
757 | }
|
---|
758 |
|
---|
759 | TcpInsertTcb (Tcb);
|
---|
760 |
|
---|
761 | OnExit:
|
---|
762 |
|
---|
763 | return Status;
|
---|
764 | }
|
---|
765 |
|
---|
766 | /**
|
---|
767 | The procotol handler provided to the socket layer, which is used to
|
---|
768 | dispatch the socket level requests by calling the corresponding
|
---|
769 | TCP layer functions.
|
---|
770 |
|
---|
771 | @param[in] Sock Pointer to the socket of this TCP instance.
|
---|
772 | @param[in] Request The code of this operation request.
|
---|
773 | @param[in] Data Pointer to the operation specific data passed in
|
---|
774 | together with the operation request. This is an
|
---|
775 | optional parameter that may be NULL.
|
---|
776 |
|
---|
777 | @retval EFI_SUCCESS The socket request completed successfully.
|
---|
778 | @retval other The error status returned by the corresponding TCP
|
---|
779 | layer function.
|
---|
780 |
|
---|
781 | **/
|
---|
782 | EFI_STATUS
|
---|
783 | TcpDispatcher (
|
---|
784 | IN SOCKET *Sock,
|
---|
785 | IN UINT8 Request,
|
---|
786 | IN VOID *Data OPTIONAL
|
---|
787 | )
|
---|
788 | {
|
---|
789 | TCP_CB *Tcb;
|
---|
790 | TCP_PROTO_DATA *ProtoData;
|
---|
791 |
|
---|
792 | ProtoData = (TCP_PROTO_DATA *) Sock->ProtoReserved;
|
---|
793 | Tcb = ProtoData->TcpPcb;
|
---|
794 |
|
---|
795 | switch (Request) {
|
---|
796 | case SOCK_POLL:
|
---|
797 | if (Tcb->Sk->IpVersion == IP_VERSION_4) {
|
---|
798 | ProtoData->TcpService->IpIo->Ip.Ip4->Poll (ProtoData->TcpService->IpIo->Ip.Ip4);
|
---|
799 | } else {
|
---|
800 | ProtoData->TcpService->IpIo->Ip.Ip6->Poll (ProtoData->TcpService->IpIo->Ip.Ip6);
|
---|
801 | }
|
---|
802 |
|
---|
803 | break;
|
---|
804 |
|
---|
805 | case SOCK_CONSUMED:
|
---|
806 | //
|
---|
807 | // After user received data from socket buffer, socket will
|
---|
808 | // notify TCP using this message to give it a chance to send out
|
---|
809 | // window update information
|
---|
810 | //
|
---|
811 | ASSERT (Tcb != NULL);
|
---|
812 | TcpOnAppConsume (Tcb);
|
---|
813 | break;
|
---|
814 |
|
---|
815 | case SOCK_SND:
|
---|
816 |
|
---|
817 | ASSERT (Tcb != NULL);
|
---|
818 | TcpOnAppSend (Tcb);
|
---|
819 | break;
|
---|
820 |
|
---|
821 | case SOCK_CLOSE:
|
---|
822 |
|
---|
823 | TcpOnAppClose (Tcb);
|
---|
824 |
|
---|
825 | break;
|
---|
826 |
|
---|
827 | case SOCK_ABORT:
|
---|
828 |
|
---|
829 | TcpOnAppAbort (Tcb);
|
---|
830 |
|
---|
831 | break;
|
---|
832 |
|
---|
833 | case SOCK_SNDPUSH:
|
---|
834 | Tcb->SndPsh = TcpGetMaxSndNxt (Tcb) + GET_SND_DATASIZE (Tcb->Sk);
|
---|
835 | TCP_SET_FLG (Tcb->CtrlFlag, TCP_CTRL_SND_PSH);
|
---|
836 |
|
---|
837 | break;
|
---|
838 |
|
---|
839 | case SOCK_SNDURG:
|
---|
840 | Tcb->SndUp = TcpGetMaxSndNxt (Tcb) + GET_SND_DATASIZE (Tcb->Sk) - 1;
|
---|
841 | TCP_SET_FLG (Tcb->CtrlFlag, TCP_CTRL_SND_URG);
|
---|
842 |
|
---|
843 | break;
|
---|
844 |
|
---|
845 | case SOCK_CONNECT:
|
---|
846 |
|
---|
847 | TcpOnAppConnect (Tcb);
|
---|
848 |
|
---|
849 | break;
|
---|
850 |
|
---|
851 | case SOCK_ATTACH:
|
---|
852 |
|
---|
853 | return TcpAttachPcb (Sock);
|
---|
854 |
|
---|
855 | break;
|
---|
856 |
|
---|
857 | case SOCK_FLUSH:
|
---|
858 |
|
---|
859 | TcpFlushPcb (Tcb);
|
---|
860 |
|
---|
861 | break;
|
---|
862 |
|
---|
863 | case SOCK_DETACH:
|
---|
864 |
|
---|
865 | TcpDetachPcb (Sock);
|
---|
866 |
|
---|
867 | break;
|
---|
868 |
|
---|
869 | case SOCK_CONFIGURE:
|
---|
870 |
|
---|
871 | return TcpConfigurePcb (
|
---|
872 | Sock,
|
---|
873 | (TCP_CONFIG_DATA *) Data
|
---|
874 | );
|
---|
875 |
|
---|
876 | break;
|
---|
877 |
|
---|
878 | case SOCK_MODE:
|
---|
879 |
|
---|
880 | ASSERT ((Data != NULL) && (Tcb != NULL));
|
---|
881 |
|
---|
882 | if (Tcb->Sk->IpVersion == IP_VERSION_4) {
|
---|
883 |
|
---|
884 | return Tcp4GetMode (Tcb, (TCP4_MODE_DATA *) Data);
|
---|
885 | } else {
|
---|
886 |
|
---|
887 | return Tcp6GetMode (Tcb, (TCP6_MODE_DATA *) Data);
|
---|
888 | }
|
---|
889 |
|
---|
890 | break;
|
---|
891 |
|
---|
892 | case SOCK_ROUTE:
|
---|
893 |
|
---|
894 | ASSERT ((Data != NULL) && (Tcb != NULL) && (Tcb->Sk->IpVersion == IP_VERSION_4));
|
---|
895 |
|
---|
896 | return Tcp4Route (Tcb, (TCP4_ROUTE_INFO *) Data);
|
---|
897 |
|
---|
898 | default:
|
---|
899 |
|
---|
900 | return EFI_UNSUPPORTED;
|
---|
901 | }
|
---|
902 |
|
---|
903 | return EFI_SUCCESS;
|
---|
904 | }
|
---|