Home | Info | Community | Development | myReactOS | Contact Us
ReactOS Development > Doxygenrpc_assoc.c
Go to the documentation of this file.
00001 /* 00002 * Associations 00003 * 00004 * Copyright 2007 Robert Shearman (for CodeWeavers) 00005 * 00006 * This library is free software; you can redistribute it and/or 00007 * modify it under the terms of the GNU Lesser General Public 00008 * License as published by the Free Software Foundation; either 00009 * version 2.1 of the License, or (at your option) any later version. 00010 * 00011 * This library is distributed in the hope that it will be useful, 00012 * but WITHOUT ANY WARRANTY; without even the implied warranty of 00013 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 00014 * Lesser General Public License for more details. 00015 * 00016 * You should have received a copy of the GNU Lesser General Public 00017 * License along with this library; if not, write to the Free Software 00018 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA 00019 * 00020 */ 00021 00022 #include <stdarg.h> 00023 #include <assert.h> 00024 00025 #include "rpc.h" 00026 #include "rpcndr.h" 00027 #include "winternl.h" 00028 00029 #include "wine/unicode.h" 00030 #include "wine/debug.h" 00031 00032 #include "rpc_binding.h" 00033 #include "rpc_assoc.h" 00034 #include "rpc_message.h" 00035 00036 WINE_DEFAULT_DEBUG_CHANNEL(rpc); 00037 00038 static CRITICAL_SECTION assoc_list_cs; 00039 static CRITICAL_SECTION_DEBUG assoc_list_cs_debug = 00040 { 00041 0, 0, &assoc_list_cs, 00042 { &assoc_list_cs_debug.ProcessLocksList, &assoc_list_cs_debug.ProcessLocksList }, 00043 0, 0, { (DWORD_PTR)(__FILE__ ": assoc_list_cs") } 00044 }; 00045 static CRITICAL_SECTION assoc_list_cs = { &assoc_list_cs_debug, -1, 0, 0, 0, 0 }; 00046 00047 static struct list client_assoc_list = LIST_INIT(client_assoc_list); 00048 static struct list server_assoc_list = LIST_INIT(server_assoc_list); 00049 00050 static LONG last_assoc_group_id; 00051 00052 typedef struct _RpcContextHandle 00053 { 00054 struct list entry; 00055 void *user_context; 00056 NDR_RUNDOWN rundown_routine; 00057 void *ctx_guard; 00058 UUID uuid; 00059 RTL_RWLOCK rw_lock; 00060 unsigned int refs; 00061 } RpcContextHandle; 00062 00063 static void RpcContextHandle_Destroy(RpcContextHandle *context_handle); 00064 00065 static RPC_STATUS RpcAssoc_Alloc(LPCSTR Protseq, LPCSTR NetworkAddr, 00066 LPCSTR Endpoint, LPCWSTR NetworkOptions, 00067 RpcAssoc **assoc_out) 00068 { 00069 RpcAssoc *assoc; 00070 assoc = HeapAlloc(GetProcessHeap(), 0, sizeof(*assoc)); 00071 if (!assoc) 00072 return RPC_S_OUT_OF_RESOURCES; 00073 assoc->refs = 1; 00074 list_init(&assoc->free_connection_pool); 00075 list_init(&assoc->context_handle_list); 00076 InitializeCriticalSection(&assoc->cs); 00077 assoc->Protseq = RPCRT4_strdupA(Protseq); 00078 assoc->NetworkAddr = RPCRT4_strdupA(NetworkAddr); 00079 assoc->Endpoint = RPCRT4_strdupA(Endpoint); 00080 assoc->NetworkOptions = NetworkOptions ? RPCRT4_strdupW(NetworkOptions) : NULL; 00081 assoc->assoc_group_id = 0; 00082 list_init(&assoc->entry); 00083 *assoc_out = assoc; 00084 return RPC_S_OK; 00085 } 00086 00087 static BOOL compare_networkoptions(LPCWSTR opts1, LPCWSTR opts2) 00088 { 00089 if ((opts1 == NULL) && (opts2 == NULL)) 00090 return TRUE; 00091 if ((opts1 == NULL) || (opts2 == NULL)) 00092 return FALSE; 00093 return !strcmpW(opts1, opts2); 00094 } 00095 00096 RPC_STATUS RPCRT4_GetAssociation(LPCSTR Protseq, LPCSTR NetworkAddr, 00097 LPCSTR Endpoint, LPCWSTR NetworkOptions, 00098 RpcAssoc **assoc_out) 00099 { 00100 RpcAssoc *assoc; 00101 RPC_STATUS status; 00102 00103 EnterCriticalSection(&assoc_list_cs); 00104 LIST_FOR_EACH_ENTRY(assoc, &client_assoc_list, RpcAssoc, entry) 00105 { 00106 if (!strcmp(Protseq, assoc->Protseq) && 00107 !strcmp(NetworkAddr, assoc->NetworkAddr) && 00108 !strcmp(Endpoint, assoc->Endpoint) && 00109 compare_networkoptions(NetworkOptions, assoc->NetworkOptions)) 00110 { 00111 assoc->refs++; 00112 *assoc_out = assoc; 00113 LeaveCriticalSection(&assoc_list_cs); 00114 TRACE("using existing assoc %p\n", assoc); 00115 return RPC_S_OK; 00116 } 00117 } 00118 00119 status = RpcAssoc_Alloc(Protseq, NetworkAddr, Endpoint, NetworkOptions, &assoc); 00120 if (status != RPC_S_OK) 00121 { 00122 LeaveCriticalSection(&assoc_list_cs); 00123 return status; 00124 } 00125 list_add_head(&client_assoc_list, &assoc->entry); 00126 *assoc_out = assoc; 00127 00128 LeaveCriticalSection(&assoc_list_cs); 00129 00130 TRACE("new assoc %p\n", assoc); 00131 00132 return RPC_S_OK; 00133 } 00134 00135 RPC_STATUS RpcServerAssoc_GetAssociation(LPCSTR Protseq, LPCSTR NetworkAddr, 00136 LPCSTR Endpoint, LPCWSTR NetworkOptions, 00137 ULONG assoc_gid, 00138 RpcAssoc **assoc_out) 00139 { 00140 RpcAssoc *assoc; 00141 RPC_STATUS status; 00142 00143 EnterCriticalSection(&assoc_list_cs); 00144 if (assoc_gid) 00145 { 00146 LIST_FOR_EACH_ENTRY(assoc, &server_assoc_list, RpcAssoc, entry) 00147 { 00148 /* FIXME: NetworkAddr shouldn't be NULL */ 00149 if (assoc->assoc_group_id == assoc_gid && 00150 !strcmp(Protseq, assoc->Protseq) && 00151 (!NetworkAddr || !assoc->NetworkAddr || !strcmp(NetworkAddr, assoc->NetworkAddr)) && 00152 !strcmp(Endpoint, assoc->Endpoint) && 00153 ((!assoc->NetworkOptions == !NetworkOptions) && 00154 (!NetworkOptions || !strcmpW(NetworkOptions, assoc->NetworkOptions)))) 00155 { 00156 assoc->refs++; 00157 *assoc_out = assoc; 00158 LeaveCriticalSection(&assoc_list_cs); 00159 TRACE("using existing assoc %p\n", assoc); 00160 return RPC_S_OK; 00161 } 00162 } 00163 *assoc_out = NULL; 00164 LeaveCriticalSection(&assoc_list_cs); 00165 return RPC_S_NO_CONTEXT_AVAILABLE; 00166 } 00167 00168 status = RpcAssoc_Alloc(Protseq, NetworkAddr, Endpoint, NetworkOptions, &assoc); 00169 if (status != RPC_S_OK) 00170 { 00171 LeaveCriticalSection(&assoc_list_cs); 00172 return status; 00173 } 00174 assoc->assoc_group_id = InterlockedIncrement(&last_assoc_group_id); 00175 list_add_head(&server_assoc_list, &assoc->entry); 00176 *assoc_out = assoc; 00177 00178 LeaveCriticalSection(&assoc_list_cs); 00179 00180 TRACE("new assoc %p\n", assoc); 00181 00182 return RPC_S_OK; 00183 } 00184 00185 ULONG RpcAssoc_Release(RpcAssoc *assoc) 00186 { 00187 ULONG refs; 00188 00189 EnterCriticalSection(&assoc_list_cs); 00190 refs = --assoc->refs; 00191 if (!refs) 00192 list_remove(&assoc->entry); 00193 LeaveCriticalSection(&assoc_list_cs); 00194 00195 if (!refs) 00196 { 00197 RpcConnection *Connection, *cursor2; 00198 RpcContextHandle *context_handle, *context_handle_cursor; 00199 00200 TRACE("destroying assoc %p\n", assoc); 00201 00202 LIST_FOR_EACH_ENTRY_SAFE(Connection, cursor2, &assoc->free_connection_pool, RpcConnection, conn_pool_entry) 00203 { 00204 list_remove(&Connection->conn_pool_entry); 00205 RPCRT4_DestroyConnection(Connection); 00206 } 00207 00208 LIST_FOR_EACH_ENTRY_SAFE(context_handle, context_handle_cursor, &assoc->context_handle_list, RpcContextHandle, entry) 00209 RpcContextHandle_Destroy(context_handle); 00210 00211 HeapFree(GetProcessHeap(), 0, assoc->NetworkOptions); 00212 HeapFree(GetProcessHeap(), 0, assoc->Endpoint); 00213 HeapFree(GetProcessHeap(), 0, assoc->NetworkAddr); 00214 HeapFree(GetProcessHeap(), 0, assoc->Protseq); 00215 00216 DeleteCriticalSection(&assoc->cs); 00217 00218 HeapFree(GetProcessHeap(), 0, assoc); 00219 } 00220 00221 return refs; 00222 } 00223 00224 #define ROUND_UP(value, alignment) (((value) + ((alignment) - 1)) & ~((alignment)-1)) 00225 00226 static RPC_STATUS RpcAssoc_BindConnection(const RpcAssoc *assoc, RpcConnection *conn, 00227 const RPC_SYNTAX_IDENTIFIER *InterfaceId, 00228 const RPC_SYNTAX_IDENTIFIER *TransferSyntax) 00229 { 00230 RpcPktHdr *hdr; 00231 RpcPktHdr *response_hdr; 00232 RPC_MESSAGE msg; 00233 RPC_STATUS status; 00234 unsigned char *auth_data = NULL; 00235 ULONG auth_length; 00236 00237 TRACE("sending bind request to server\n"); 00238 00239 hdr = RPCRT4_BuildBindHeader(NDR_LOCAL_DATA_REPRESENTATION, 00240 RPC_MAX_PACKET_SIZE, RPC_MAX_PACKET_SIZE, 00241 assoc->assoc_group_id, 00242 InterfaceId, TransferSyntax); 00243 00244 status = RPCRT4_Send(conn, hdr, NULL, 0); 00245 RPCRT4_FreeHeader(hdr); 00246 if (status != RPC_S_OK) 00247 return status; 00248 00249 status = RPCRT4_ReceiveWithAuth(conn, &response_hdr, &msg, &auth_data, &auth_length); 00250 if (status != RPC_S_OK) 00251 { 00252 ERR("receive failed with error %d\n", status); 00253 return status; 00254 } 00255 00256 switch (response_hdr->common.ptype) 00257 { 00258 case PKT_BIND_ACK: 00259 { 00260 RpcAddressString *server_address = msg.Buffer; 00261 if ((msg.BufferLength >= FIELD_OFFSET(RpcAddressString, string[0])) || 00262 (msg.BufferLength >= ROUND_UP(FIELD_OFFSET(RpcAddressString, string[server_address->length]), 4))) 00263 { 00264 unsigned short remaining = msg.BufferLength - 00265 ROUND_UP(FIELD_OFFSET(RpcAddressString, string[server_address->length]), 4); 00266 RpcResultList *results = (RpcResultList*)((ULONG_PTR)server_address + 00267 ROUND_UP(FIELD_OFFSET(RpcAddressString, string[server_address->length]), 4)); 00268 if ((results->num_results == 1) && 00269 (remaining >= FIELD_OFFSET(RpcResultList, results[results->num_results]))) 00270 { 00271 switch (results->results[0].result) 00272 { 00273 case RESULT_ACCEPT: 00274 /* respond to authorization request */ 00275 if (auth_length > sizeof(RpcAuthVerifier)) 00276 status = RPCRT4_ClientConnectionAuth(conn, 00277 auth_data + sizeof(RpcAuthVerifier), 00278 auth_length); 00279 if (status == RPC_S_OK) 00280 { 00281 conn->assoc_group_id = response_hdr->bind_ack.assoc_gid; 00282 conn->MaxTransmissionSize = response_hdr->bind_ack.max_tsize; 00283 conn->ActiveInterface = *InterfaceId; 00284 } 00285 break; 00286 case RESULT_PROVIDER_REJECTION: 00287 switch (results->results[0].reason) 00288 { 00289 case REASON_ABSTRACT_SYNTAX_NOT_SUPPORTED: 00290 ERR("syntax %s, %d.%d not supported\n", 00291 debugstr_guid(&InterfaceId->SyntaxGUID), 00292 InterfaceId->SyntaxVersion.MajorVersion, 00293 InterfaceId->SyntaxVersion.MinorVersion); 00294 status = RPC_S_UNKNOWN_IF; 00295 break; 00296 case REASON_TRANSFER_SYNTAXES_NOT_SUPPORTED: 00297 ERR("transfer syntax not supported\n"); 00298 status = RPC_S_SERVER_UNAVAILABLE; 00299 break; 00300 case REASON_NONE: 00301 default: 00302 status = RPC_S_CALL_FAILED_DNE; 00303 } 00304 break; 00305 case RESULT_USER_REJECTION: 00306 default: 00307 ERR("rejection result %d\n", results->results[0].result); 00308 status = RPC_S_CALL_FAILED_DNE; 00309 } 00310 } 00311 else 00312 { 00313 ERR("incorrect results size\n"); 00314 status = RPC_S_CALL_FAILED_DNE; 00315 } 00316 } 00317 else 00318 { 00319 ERR("bind ack packet too small (%d)\n", msg.BufferLength); 00320 status = RPC_S_PROTOCOL_ERROR; 00321 } 00322 break; 00323 } 00324 case PKT_BIND_NACK: 00325 switch (response_hdr->bind_nack.reject_reason) 00326 { 00327 case REJECT_LOCAL_LIMIT_EXCEEDED: 00328 case REJECT_TEMPORARY_CONGESTION: 00329 ERR("server too busy\n"); 00330 status = RPC_S_SERVER_TOO_BUSY; 00331 break; 00332 case REJECT_PROTOCOL_VERSION_NOT_SUPPORTED: 00333 ERR("protocol version not supported\n"); 00334 status = RPC_S_PROTOCOL_ERROR; 00335 break; 00336 case REJECT_UNKNOWN_AUTHN_SERVICE: 00337 ERR("unknown authentication service\n"); 00338 status = RPC_S_UNKNOWN_AUTHN_SERVICE; 00339 break; 00340 case REJECT_INVALID_CHECKSUM: 00341 ERR("invalid checksum\n"); 00342 status = ERROR_ACCESS_DENIED; 00343 break; 00344 default: 00345 ERR("rejected bind for reason %d\n", response_hdr->bind_nack.reject_reason); 00346 status = RPC_S_CALL_FAILED_DNE; 00347 } 00348 break; 00349 default: 00350 ERR("wrong packet type received %d\n", response_hdr->common.ptype); 00351 status = RPC_S_PROTOCOL_ERROR; 00352 break; 00353 } 00354 00355 I_RpcFree(msg.Buffer); 00356 RPCRT4_FreeHeader(response_hdr); 00357 HeapFree(GetProcessHeap(), 0, auth_data); 00358 return status; 00359 } 00360 00361 static RpcConnection *RpcAssoc_GetIdleConnection(RpcAssoc *assoc, 00362 const RPC_SYNTAX_IDENTIFIER *InterfaceId, 00363 const RPC_SYNTAX_IDENTIFIER *TransferSyntax, const RpcAuthInfo *AuthInfo, 00364 const RpcQualityOfService *QOS) 00365 { 00366 RpcConnection *Connection; 00367 EnterCriticalSection(&assoc->cs); 00368 /* try to find a compatible connection from the connection pool */ 00369 LIST_FOR_EACH_ENTRY(Connection, &assoc->free_connection_pool, RpcConnection, conn_pool_entry) 00370 { 00371 if (!memcmp(&Connection->ActiveInterface, InterfaceId, 00372 sizeof(RPC_SYNTAX_IDENTIFIER)) && 00373 RpcAuthInfo_IsEqual(Connection->AuthInfo, AuthInfo) && 00374 RpcQualityOfService_IsEqual(Connection->QOS, QOS)) 00375 { 00376 list_remove(&Connection->conn_pool_entry); 00377 LeaveCriticalSection(&assoc->cs); 00378 TRACE("got connection from pool %p\n", Connection); 00379 return Connection; 00380 } 00381 } 00382 00383 LeaveCriticalSection(&assoc->cs); 00384 return NULL; 00385 } 00386 00387 RPC_STATUS RpcAssoc_GetClientConnection(RpcAssoc *assoc, 00388 const RPC_SYNTAX_IDENTIFIER *InterfaceId, 00389 const RPC_SYNTAX_IDENTIFIER *TransferSyntax, RpcAuthInfo *AuthInfo, 00390 RpcQualityOfService *QOS, RpcConnection **Connection) 00391 { 00392 RpcConnection *NewConnection; 00393 RPC_STATUS status; 00394 00395 *Connection = RpcAssoc_GetIdleConnection(assoc, InterfaceId, TransferSyntax, AuthInfo, QOS); 00396 if (*Connection) 00397 return RPC_S_OK; 00398 00399 /* create a new connection */ 00400 status = RPCRT4_CreateConnection(&NewConnection, FALSE /* is this a server connection? */, 00401 assoc->Protseq, assoc->NetworkAddr, 00402 assoc->Endpoint, assoc->NetworkOptions, 00403 AuthInfo, QOS); 00404 if (status != RPC_S_OK) 00405 return status; 00406 00407 NewConnection->assoc = assoc; 00408 status = RPCRT4_OpenClientConnection(NewConnection); 00409 if (status != RPC_S_OK) 00410 { 00411 RPCRT4_DestroyConnection(NewConnection); 00412 return status; 00413 } 00414 00415 status = RpcAssoc_BindConnection(assoc, NewConnection, InterfaceId, TransferSyntax); 00416 if (status != RPC_S_OK) 00417 { 00418 RPCRT4_DestroyConnection(NewConnection); 00419 return status; 00420 } 00421 00422 *Connection = NewConnection; 00423 00424 return RPC_S_OK; 00425 } 00426 00427 void RpcAssoc_ReleaseIdleConnection(RpcAssoc *assoc, RpcConnection *Connection) 00428 { 00429 assert(!Connection->server); 00430 Connection->async_state = NULL; 00431 EnterCriticalSection(&assoc->cs); 00432 if (!assoc->assoc_group_id) assoc->assoc_group_id = Connection->assoc_group_id; 00433 list_add_head(&assoc->free_connection_pool, &Connection->conn_pool_entry); 00434 LeaveCriticalSection(&assoc->cs); 00435 } 00436 00437 RPC_STATUS RpcServerAssoc_AllocateContextHandle(RpcAssoc *assoc, void *CtxGuard, 00438 NDR_SCONTEXT *SContext) 00439 { 00440 RpcContextHandle *context_handle; 00441 00442 context_handle = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(*context_handle)); 00443 if (!context_handle) 00444 return ERROR_OUTOFMEMORY; 00445 00446 context_handle->ctx_guard = CtxGuard; 00447 RtlInitializeResource(&context_handle->rw_lock); 00448 context_handle->refs = 1; 00449 00450 /* lock here to mirror unmarshall, so we don't need to special-case the 00451 * freeing of a non-marshalled context handle */ 00452 RtlAcquireResourceExclusive(&context_handle->rw_lock, TRUE); 00453 00454 EnterCriticalSection(&assoc->cs); 00455 list_add_tail(&assoc->context_handle_list, &context_handle->entry); 00456 LeaveCriticalSection(&assoc->cs); 00457 00458 *SContext = (NDR_SCONTEXT)context_handle; 00459 return RPC_S_OK; 00460 } 00461 00462 BOOL RpcContextHandle_IsGuardCorrect(NDR_SCONTEXT SContext, void *CtxGuard) 00463 { 00464 RpcContextHandle *context_handle = (RpcContextHandle *)SContext; 00465 return context_handle->ctx_guard == CtxGuard; 00466 } 00467 00468 RPC_STATUS RpcServerAssoc_FindContextHandle(RpcAssoc *assoc, const UUID *uuid, 00469 void *CtxGuard, ULONG Flags, NDR_SCONTEXT *SContext) 00470 { 00471 RpcContextHandle *context_handle; 00472 00473 EnterCriticalSection(&assoc->cs); 00474 LIST_FOR_EACH_ENTRY(context_handle, &assoc->context_handle_list, RpcContextHandle, entry) 00475 { 00476 if (RpcContextHandle_IsGuardCorrect((NDR_SCONTEXT)context_handle, CtxGuard) && 00477 !memcmp(&context_handle->uuid, uuid, sizeof(*uuid))) 00478 { 00479 *SContext = (NDR_SCONTEXT)context_handle; 00480 if (context_handle->refs++) 00481 { 00482 LeaveCriticalSection(&assoc->cs); 00483 TRACE("found %p\n", context_handle); 00484 RtlAcquireResourceExclusive(&context_handle->rw_lock, TRUE); 00485 return RPC_S_OK; 00486 } 00487 } 00488 } 00489 LeaveCriticalSection(&assoc->cs); 00490 00491 ERR("no context handle found for uuid %s, guard %p\n", 00492 debugstr_guid(uuid), CtxGuard); 00493 return ERROR_INVALID_HANDLE; 00494 } 00495 00496 RPC_STATUS RpcServerAssoc_UpdateContextHandle(RpcAssoc *assoc, 00497 NDR_SCONTEXT SContext, 00498 void *CtxGuard, 00499 NDR_RUNDOWN rundown_routine) 00500 { 00501 RpcContextHandle *context_handle = (RpcContextHandle *)SContext; 00502 RPC_STATUS status; 00503 00504 if (!RpcContextHandle_IsGuardCorrect((NDR_SCONTEXT)context_handle, CtxGuard)) 00505 return ERROR_INVALID_HANDLE; 00506 00507 EnterCriticalSection(&assoc->cs); 00508 if (UuidIsNil(&context_handle->uuid, &status)) 00509 { 00510 /* add a ref for the data being valid */ 00511 context_handle->refs++; 00512 UuidCreate(&context_handle->uuid); 00513 context_handle->rundown_routine = rundown_routine; 00514 TRACE("allocated uuid %s for context handle %p\n", 00515 debugstr_guid(&context_handle->uuid), context_handle); 00516 } 00517 LeaveCriticalSection(&assoc->cs); 00518 00519 return RPC_S_OK; 00520 } 00521 00522 void RpcContextHandle_GetUuid(NDR_SCONTEXT SContext, UUID *uuid) 00523 { 00524 RpcContextHandle *context_handle = (RpcContextHandle *)SContext; 00525 *uuid = context_handle->uuid; 00526 } 00527 00528 static void RpcContextHandle_Destroy(RpcContextHandle *context_handle) 00529 { 00530 TRACE("freeing %p\n", context_handle); 00531 00532 if (context_handle->user_context && context_handle->rundown_routine) 00533 { 00534 TRACE("calling rundown routine %p with user context %p\n", 00535 context_handle->rundown_routine, context_handle->user_context); 00536 context_handle->rundown_routine(context_handle->user_context); 00537 } 00538 00539 RtlDeleteResource(&context_handle->rw_lock); 00540 00541 HeapFree(GetProcessHeap(), 0, context_handle); 00542 } 00543 00544 unsigned int RpcServerAssoc_ReleaseContextHandle(RpcAssoc *assoc, NDR_SCONTEXT SContext, BOOL release_lock) 00545 { 00546 RpcContextHandle *context_handle = (RpcContextHandle *)SContext; 00547 unsigned int refs; 00548 00549 if (release_lock) 00550 RtlReleaseResource(&context_handle->rw_lock); 00551 00552 EnterCriticalSection(&assoc->cs); 00553 refs = --context_handle->refs; 00554 if (!refs) 00555 list_remove(&context_handle->entry); 00556 LeaveCriticalSection(&assoc->cs); 00557 00558 if (!refs) 00559 RpcContextHandle_Destroy(context_handle); 00560 00561 return refs; 00562 } Generated on Fri May 25 2012 04:24:13 for ReactOS by
1.7.6.1
|