|
1 /* This Source Code Form is subject to the terms of the Mozilla Public |
|
2 * License, v. 2.0. If a copy of the MPL was not distributed with this |
|
3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
|
4 |
|
5 #include "nsPKCS11Slot.h" |
|
6 #include "nsPK11TokenDB.h" |
|
7 |
|
8 #include "nsCOMPtr.h" |
|
9 #include "nsISupportsArray.h" |
|
10 #include "nsString.h" |
|
11 #include "nsReadableUtils.h" |
|
12 #include "nsCRT.h" |
|
13 |
|
14 #include "secmod.h" |
|
15 |
|
16 #ifdef PR_LOGGING |
|
17 extern PRLogModuleInfo* gPIPNSSLog; |
|
18 #endif |
|
19 |
|
20 NS_IMPL_ISUPPORTS(nsPKCS11Slot, nsIPKCS11Slot) |
|
21 |
|
22 nsPKCS11Slot::nsPKCS11Slot(PK11SlotInfo *slot) |
|
23 { |
|
24 nsNSSShutDownPreventionLock locker; |
|
25 if (isAlreadyShutDown()) |
|
26 return; |
|
27 |
|
28 PK11_ReferenceSlot(slot); |
|
29 mSlot = slot; |
|
30 mSeries = PK11_GetSlotSeries(slot); |
|
31 refreshSlotInfo(); |
|
32 } |
|
33 |
|
34 void |
|
35 nsPKCS11Slot::refreshSlotInfo() |
|
36 { |
|
37 CK_SLOT_INFO slot_info; |
|
38 if (PK11_GetSlotInfo(mSlot, &slot_info) == SECSuccess) { |
|
39 // Set the Description field |
|
40 const char *ccDesc = (const char*)slot_info.slotDescription; |
|
41 const nsACString &cDesc = Substring( |
|
42 ccDesc, |
|
43 ccDesc+PL_strnlen(ccDesc, sizeof(slot_info.slotDescription))); |
|
44 mSlotDesc = NS_ConvertUTF8toUTF16(cDesc); |
|
45 mSlotDesc.Trim(" ", false, true); |
|
46 // Set the Manufacturer field |
|
47 const char *ccManID = (const char*)slot_info.manufacturerID; |
|
48 const nsACString &cManID = Substring( |
|
49 ccManID, |
|
50 ccManID+PL_strnlen(ccManID, sizeof(slot_info.manufacturerID))); |
|
51 mSlotManID = NS_ConvertUTF8toUTF16(cManID); |
|
52 mSlotManID.Trim(" ", false, true); |
|
53 // Set the Hardware Version field |
|
54 mSlotHWVersion = EmptyString(); |
|
55 mSlotHWVersion.AppendInt(slot_info.hardwareVersion.major); |
|
56 mSlotHWVersion.AppendLiteral("."); |
|
57 mSlotHWVersion.AppendInt(slot_info.hardwareVersion.minor); |
|
58 // Set the Firmware Version field |
|
59 mSlotFWVersion = EmptyString(); |
|
60 mSlotFWVersion.AppendInt(slot_info.firmwareVersion.major); |
|
61 mSlotFWVersion.AppendLiteral("."); |
|
62 mSlotFWVersion.AppendInt(slot_info.firmwareVersion.minor); |
|
63 } |
|
64 |
|
65 } |
|
66 |
|
67 nsPKCS11Slot::~nsPKCS11Slot() |
|
68 { |
|
69 nsNSSShutDownPreventionLock locker; |
|
70 if (isAlreadyShutDown()) { |
|
71 return; |
|
72 } |
|
73 destructorSafeDestroyNSSReference(); |
|
74 shutdown(calledFromObject); |
|
75 } |
|
76 |
|
77 void nsPKCS11Slot::virtualDestroyNSSReference() |
|
78 { |
|
79 destructorSafeDestroyNSSReference(); |
|
80 } |
|
81 |
|
82 void nsPKCS11Slot::destructorSafeDestroyNSSReference() |
|
83 { |
|
84 if (mSlot) { |
|
85 PK11_FreeSlot(mSlot); |
|
86 mSlot = nullptr; |
|
87 } |
|
88 } |
|
89 |
|
90 /* readonly attribute wstring name; */ |
|
91 NS_IMETHODIMP |
|
92 nsPKCS11Slot::GetName(char16_t **aName) |
|
93 { |
|
94 nsNSSShutDownPreventionLock locker; |
|
95 if (isAlreadyShutDown()) |
|
96 return NS_ERROR_NOT_AVAILABLE; |
|
97 |
|
98 char *csn = PK11_GetSlotName(mSlot); |
|
99 if (*csn) { |
|
100 *aName = ToNewUnicode(NS_ConvertUTF8toUTF16(csn)); |
|
101 } else if (PK11_HasRootCerts(mSlot)) { |
|
102 // This is a workaround to an Root Module bug - the root certs module has |
|
103 // no slot name. Not bothering to localize, because this is a workaround |
|
104 // and for now all the slot names returned by NSS are char * anyway. |
|
105 *aName = ToNewUnicode(NS_LITERAL_STRING("Root Certificates")); |
|
106 } else { |
|
107 // same as above, this is a catch-all |
|
108 *aName = ToNewUnicode(NS_LITERAL_STRING("Unnamed Slot")); |
|
109 } |
|
110 if (!*aName) return NS_ERROR_OUT_OF_MEMORY; |
|
111 return NS_OK; |
|
112 } |
|
113 |
|
114 /* readonly attribute wstring desc; */ |
|
115 NS_IMETHODIMP |
|
116 nsPKCS11Slot::GetDesc(char16_t **aDesc) |
|
117 { |
|
118 nsNSSShutDownPreventionLock locker; |
|
119 if (isAlreadyShutDown()) |
|
120 return NS_ERROR_NOT_AVAILABLE; |
|
121 |
|
122 if (mSeries != PK11_GetSlotSeries(mSlot)) { |
|
123 refreshSlotInfo(); |
|
124 } |
|
125 |
|
126 *aDesc = ToNewUnicode(mSlotDesc); |
|
127 if (!*aDesc) return NS_ERROR_OUT_OF_MEMORY; |
|
128 return NS_OK; |
|
129 } |
|
130 |
|
131 /* readonly attribute wstring manID; */ |
|
132 NS_IMETHODIMP |
|
133 nsPKCS11Slot::GetManID(char16_t **aManID) |
|
134 { |
|
135 if (mSeries != PK11_GetSlotSeries(mSlot)) { |
|
136 refreshSlotInfo(); |
|
137 } |
|
138 *aManID = ToNewUnicode(mSlotManID); |
|
139 if (!*aManID) return NS_ERROR_OUT_OF_MEMORY; |
|
140 return NS_OK; |
|
141 } |
|
142 |
|
143 /* readonly attribute wstring HWVersion; */ |
|
144 NS_IMETHODIMP |
|
145 nsPKCS11Slot::GetHWVersion(char16_t **aHWVersion) |
|
146 { |
|
147 if (mSeries != PK11_GetSlotSeries(mSlot)) { |
|
148 refreshSlotInfo(); |
|
149 } |
|
150 *aHWVersion = ToNewUnicode(mSlotHWVersion); |
|
151 if (!*aHWVersion) return NS_ERROR_OUT_OF_MEMORY; |
|
152 return NS_OK; |
|
153 } |
|
154 |
|
155 /* readonly attribute wstring FWVersion; */ |
|
156 NS_IMETHODIMP |
|
157 nsPKCS11Slot::GetFWVersion(char16_t **aFWVersion) |
|
158 { |
|
159 if (mSeries != PK11_GetSlotSeries(mSlot)) { |
|
160 refreshSlotInfo(); |
|
161 } |
|
162 *aFWVersion = ToNewUnicode(mSlotFWVersion); |
|
163 if (!*aFWVersion) return NS_ERROR_OUT_OF_MEMORY; |
|
164 return NS_OK; |
|
165 } |
|
166 |
|
167 /* nsIPK11Token getToken (); */ |
|
168 NS_IMETHODIMP |
|
169 nsPKCS11Slot::GetToken(nsIPK11Token **_retval) |
|
170 { |
|
171 nsNSSShutDownPreventionLock locker; |
|
172 if (isAlreadyShutDown()) |
|
173 return NS_ERROR_NOT_AVAILABLE; |
|
174 |
|
175 nsCOMPtr<nsIPK11Token> token = new nsPK11Token(mSlot); |
|
176 *_retval = token; |
|
177 NS_ADDREF(*_retval); |
|
178 return NS_OK; |
|
179 } |
|
180 |
|
181 /* readonly attribute wstring tokenName; */ |
|
182 NS_IMETHODIMP |
|
183 nsPKCS11Slot::GetTokenName(char16_t **aName) |
|
184 { |
|
185 nsNSSShutDownPreventionLock locker; |
|
186 if (isAlreadyShutDown()) |
|
187 return NS_ERROR_NOT_AVAILABLE; |
|
188 |
|
189 if (!PK11_IsPresent(mSlot)) { |
|
190 *aName = nullptr; |
|
191 return NS_OK; |
|
192 } |
|
193 |
|
194 if (mSeries != PK11_GetSlotSeries(mSlot)) { |
|
195 refreshSlotInfo(); |
|
196 } |
|
197 |
|
198 |
|
199 *aName = ToNewUnicode(NS_ConvertUTF8toUTF16(PK11_GetTokenName(mSlot))); |
|
200 if (!*aName) return NS_ERROR_OUT_OF_MEMORY; |
|
201 return NS_OK; |
|
202 } |
|
203 |
|
204 NS_IMETHODIMP |
|
205 nsPKCS11Slot::GetStatus(uint32_t *_retval) |
|
206 { |
|
207 nsNSSShutDownPreventionLock locker; |
|
208 if (isAlreadyShutDown()) |
|
209 return NS_ERROR_NOT_AVAILABLE; |
|
210 |
|
211 if (PK11_IsDisabled(mSlot)) |
|
212 *_retval = SLOT_DISABLED; |
|
213 else if (!PK11_IsPresent(mSlot)) |
|
214 *_retval = SLOT_NOT_PRESENT; |
|
215 else if (PK11_NeedLogin(mSlot) && PK11_NeedUserInit(mSlot)) |
|
216 *_retval = SLOT_UNINITIALIZED; |
|
217 else if (PK11_NeedLogin(mSlot) && !PK11_IsLoggedIn(mSlot, nullptr)) |
|
218 *_retval = SLOT_NOT_LOGGED_IN; |
|
219 else if (PK11_NeedLogin(mSlot)) |
|
220 *_retval = SLOT_LOGGED_IN; |
|
221 else |
|
222 *_retval = SLOT_READY; |
|
223 return NS_OK; |
|
224 } |
|
225 |
|
226 NS_IMPL_ISUPPORTS(nsPKCS11Module, nsIPKCS11Module) |
|
227 |
|
228 nsPKCS11Module::nsPKCS11Module(SECMODModule *module) |
|
229 { |
|
230 nsNSSShutDownPreventionLock locker; |
|
231 if (isAlreadyShutDown()) |
|
232 return; |
|
233 |
|
234 SECMOD_ReferenceModule(module); |
|
235 mModule = module; |
|
236 } |
|
237 |
|
238 nsPKCS11Module::~nsPKCS11Module() |
|
239 { |
|
240 nsNSSShutDownPreventionLock locker; |
|
241 if (isAlreadyShutDown()) { |
|
242 return; |
|
243 } |
|
244 destructorSafeDestroyNSSReference(); |
|
245 shutdown(calledFromObject); |
|
246 } |
|
247 |
|
248 void nsPKCS11Module::virtualDestroyNSSReference() |
|
249 { |
|
250 destructorSafeDestroyNSSReference(); |
|
251 } |
|
252 |
|
253 void nsPKCS11Module::destructorSafeDestroyNSSReference() |
|
254 { |
|
255 if (mModule) { |
|
256 SECMOD_DestroyModule(mModule); |
|
257 mModule = nullptr; |
|
258 } |
|
259 } |
|
260 |
|
261 /* readonly attribute wstring name; */ |
|
262 NS_IMETHODIMP |
|
263 nsPKCS11Module::GetName(char16_t **aName) |
|
264 { |
|
265 nsNSSShutDownPreventionLock locker; |
|
266 if (isAlreadyShutDown()) |
|
267 return NS_ERROR_NOT_AVAILABLE; |
|
268 |
|
269 *aName = ToNewUnicode(NS_ConvertUTF8toUTF16(mModule->commonName)); |
|
270 return NS_OK; |
|
271 } |
|
272 |
|
273 /* readonly attribute wstring libName; */ |
|
274 NS_IMETHODIMP |
|
275 nsPKCS11Module::GetLibName(char16_t **aName) |
|
276 { |
|
277 nsNSSShutDownPreventionLock locker; |
|
278 if (isAlreadyShutDown()) |
|
279 return NS_ERROR_NOT_AVAILABLE; |
|
280 |
|
281 if ( mModule->dllName ) { |
|
282 *aName = ToNewUnicode(NS_ConvertUTF8toUTF16(mModule->dllName)); |
|
283 } else { |
|
284 *aName = nullptr; |
|
285 } |
|
286 return NS_OK; |
|
287 } |
|
288 |
|
289 /* nsIPKCS11Slot findSlotByName(in wstring name); */ |
|
290 NS_IMETHODIMP |
|
291 nsPKCS11Module::FindSlotByName(const char16_t *aName, |
|
292 nsIPKCS11Slot **_retval) |
|
293 { |
|
294 nsNSSShutDownPreventionLock locker; |
|
295 if (isAlreadyShutDown()) |
|
296 return NS_ERROR_NOT_AVAILABLE; |
|
297 |
|
298 char *asciiname = ToNewUTF8String(nsDependentString(aName)); |
|
299 PR_LOG(gPIPNSSLog, PR_LOG_DEBUG, ("Getting \"%s\"\n", asciiname)); |
|
300 PK11SlotInfo *slotinfo = nullptr; |
|
301 PK11SlotList *slotList = PK11_FindSlotsByNames(mModule->dllName, |
|
302 asciiname /* slotName */, nullptr /* token Name */, false); |
|
303 if (!slotList) { |
|
304 /* name must be the token name */ |
|
305 slotList = PK11_FindSlotsByNames(mModule->dllName, |
|
306 nullptr /*slot Name */, asciiname /* token Name */, false); |
|
307 } |
|
308 if (slotList) { |
|
309 /* should only be one */ |
|
310 if (slotList->head && slotList->head->slot) { |
|
311 slotinfo = PK11_ReferenceSlot(slotList->head->slot); |
|
312 } |
|
313 PK11_FreeSlotList(slotList); |
|
314 } |
|
315 if (!slotinfo) { |
|
316 // workaround - the builtin module has no name |
|
317 if (!asciiname) { |
|
318 return NS_ERROR_FAILURE; |
|
319 } else if (nsCRT::strcmp(asciiname, "Root Certificates") == 0) { |
|
320 slotinfo = PK11_ReferenceSlot(mModule->slots[0]); |
|
321 } else { |
|
322 // give up |
|
323 nsMemory::Free(asciiname); |
|
324 return NS_ERROR_FAILURE; |
|
325 } |
|
326 } |
|
327 nsMemory::Free(asciiname); |
|
328 nsCOMPtr<nsIPKCS11Slot> slot = new nsPKCS11Slot(slotinfo); |
|
329 PK11_FreeSlot(slotinfo); |
|
330 *_retval = slot; |
|
331 NS_ADDREF(*_retval); |
|
332 return NS_OK; |
|
333 } |
|
334 |
|
335 /* nsIEnumerator listSlots (); */ |
|
336 NS_IMETHODIMP |
|
337 nsPKCS11Module::ListSlots(nsIEnumerator **_retval) |
|
338 { |
|
339 nsNSSShutDownPreventionLock locker; |
|
340 if (isAlreadyShutDown()) |
|
341 return NS_ERROR_NOT_AVAILABLE; |
|
342 |
|
343 nsresult rv = NS_OK; |
|
344 int i; |
|
345 /* get isupports array */ |
|
346 nsCOMPtr<nsISupportsArray> array; |
|
347 rv = NS_NewISupportsArray(getter_AddRefs(array)); |
|
348 if (NS_FAILED(rv)) return rv; |
|
349 /* applications which allow new slot creation (which Firefox now does |
|
350 * since it uses the WaitForSlotEvent call) need to hold the |
|
351 * ModuleList Read lock to prevent the slot array from changing out |
|
352 * from under it. */ |
|
353 SECMODListLock *lock = SECMOD_GetDefaultModuleListLock(); |
|
354 SECMOD_GetReadLock(lock); |
|
355 for (i=0; i<mModule->slotCount; i++) { |
|
356 if (mModule->slots[i]) { |
|
357 nsCOMPtr<nsIPKCS11Slot> slot = new nsPKCS11Slot(mModule->slots[i]); |
|
358 array->AppendElement(slot); |
|
359 } |
|
360 } |
|
361 SECMOD_ReleaseReadLock(lock); |
|
362 rv = array->Enumerate(_retval); |
|
363 return rv; |
|
364 } |
|
365 |
|
366 NS_IMPL_ISUPPORTS(nsPKCS11ModuleDB, nsIPKCS11ModuleDB, nsICryptoFIPSInfo) |
|
367 |
|
368 nsPKCS11ModuleDB::nsPKCS11ModuleDB() |
|
369 { |
|
370 } |
|
371 |
|
372 nsPKCS11ModuleDB::~nsPKCS11ModuleDB() |
|
373 { |
|
374 } |
|
375 |
|
376 /* nsIPKCS11Module getInternal (); */ |
|
377 NS_IMETHODIMP |
|
378 nsPKCS11ModuleDB::GetInternal(nsIPKCS11Module **_retval) |
|
379 { |
|
380 nsNSSShutDownPreventionLock locker; |
|
381 SECMODModule *nssMod = |
|
382 SECMOD_CreateModule(nullptr, SECMOD_INT_NAME, nullptr, SECMOD_INT_FLAGS); |
|
383 nsCOMPtr<nsIPKCS11Module> module = new nsPKCS11Module(nssMod); |
|
384 SECMOD_DestroyModule(nssMod); |
|
385 *_retval = module; |
|
386 NS_ADDREF(*_retval); |
|
387 return NS_OK; |
|
388 } |
|
389 |
|
390 /* nsIPKCS11Module getInternalFIPS (); */ |
|
391 NS_IMETHODIMP |
|
392 nsPKCS11ModuleDB::GetInternalFIPS(nsIPKCS11Module **_retval) |
|
393 { |
|
394 nsNSSShutDownPreventionLock locker; |
|
395 SECMODModule *nssMod = |
|
396 SECMOD_CreateModule(nullptr, SECMOD_FIPS_NAME, nullptr, SECMOD_FIPS_FLAGS); |
|
397 nsCOMPtr<nsIPKCS11Module> module = new nsPKCS11Module(nssMod); |
|
398 SECMOD_DestroyModule(nssMod); |
|
399 *_retval = module; |
|
400 NS_ADDREF(*_retval); |
|
401 return NS_OK; |
|
402 } |
|
403 |
|
404 /* nsIPKCS11Module findModuleByName(in wstring name); */ |
|
405 NS_IMETHODIMP |
|
406 nsPKCS11ModuleDB::FindModuleByName(const char16_t *aName, |
|
407 nsIPKCS11Module **_retval) |
|
408 { |
|
409 nsNSSShutDownPreventionLock locker; |
|
410 NS_ConvertUTF16toUTF8 aUtf8Name(aName); |
|
411 SECMODModule *mod = |
|
412 SECMOD_FindModule(const_cast<char *>(aUtf8Name.get())); |
|
413 if (!mod) |
|
414 return NS_ERROR_FAILURE; |
|
415 nsCOMPtr<nsIPKCS11Module> module = new nsPKCS11Module(mod); |
|
416 SECMOD_DestroyModule(mod); |
|
417 *_retval = module; |
|
418 NS_ADDREF(*_retval); |
|
419 return NS_OK; |
|
420 } |
|
421 |
|
422 /* This is essentially the same as nsIPK11Token::findTokenByName, except |
|
423 * that it returns an nsIPKCS11Slot, which may be desired. |
|
424 */ |
|
425 /* nsIPKCS11Module findSlotByName(in wstring name); */ |
|
426 NS_IMETHODIMP |
|
427 nsPKCS11ModuleDB::FindSlotByName(const char16_t *aName, |
|
428 nsIPKCS11Slot **_retval) |
|
429 { |
|
430 nsNSSShutDownPreventionLock locker; |
|
431 NS_ConvertUTF16toUTF8 aUtf8Name(aName); |
|
432 PK11SlotInfo *slotinfo = |
|
433 PK11_FindSlotByName(const_cast<char*>(aUtf8Name.get())); |
|
434 if (!slotinfo) |
|
435 return NS_ERROR_FAILURE; |
|
436 nsCOMPtr<nsIPKCS11Slot> slot = new nsPKCS11Slot(slotinfo); |
|
437 PK11_FreeSlot(slotinfo); |
|
438 *_retval = slot; |
|
439 NS_ADDREF(*_retval); |
|
440 return NS_OK; |
|
441 } |
|
442 |
|
443 /* nsIEnumerator listModules (); */ |
|
444 NS_IMETHODIMP |
|
445 nsPKCS11ModuleDB::ListModules(nsIEnumerator **_retval) |
|
446 { |
|
447 nsNSSShutDownPreventionLock locker; |
|
448 nsresult rv = NS_OK; |
|
449 /* get isupports array */ |
|
450 nsCOMPtr<nsISupportsArray> array; |
|
451 rv = NS_NewISupportsArray(getter_AddRefs(array)); |
|
452 if (NS_FAILED(rv)) return rv; |
|
453 /* get the default list of modules */ |
|
454 SECMODModuleList *list = SECMOD_GetDefaultModuleList(); |
|
455 /* lock down the list for reading */ |
|
456 SECMODListLock *lock = SECMOD_GetDefaultModuleListLock(); |
|
457 SECMOD_GetReadLock(lock); |
|
458 while (list) { |
|
459 nsCOMPtr<nsIPKCS11Module> module = new nsPKCS11Module(list->module); |
|
460 array->AppendElement(module); |
|
461 list = list->next; |
|
462 } |
|
463 /* Get the modules in the database that didn't load */ |
|
464 list = SECMOD_GetDeadModuleList(); |
|
465 while (list) { |
|
466 nsCOMPtr<nsIPKCS11Module> module = new nsPKCS11Module(list->module); |
|
467 array->AppendElement(module); |
|
468 list = list->next; |
|
469 } |
|
470 SECMOD_ReleaseReadLock(lock); |
|
471 rv = array->Enumerate(_retval); |
|
472 return rv; |
|
473 } |
|
474 |
|
475 NS_IMETHODIMP nsPKCS11ModuleDB::GetCanToggleFIPS(bool *aCanToggleFIPS) |
|
476 { |
|
477 nsNSSShutDownPreventionLock locker; |
|
478 *aCanToggleFIPS = SECMOD_CanDeleteInternalModule(); |
|
479 return NS_OK; |
|
480 } |
|
481 |
|
482 |
|
483 /* void toggleFIPSMode (); */ |
|
484 NS_IMETHODIMP nsPKCS11ModuleDB::ToggleFIPSMode() |
|
485 { |
|
486 nsNSSShutDownPreventionLock locker; |
|
487 // The way to toggle FIPS mode in NSS is extremely obscure. |
|
488 // Basically, we delete the internal module, and voila it |
|
489 // gets replaced with the opposite module, ie if it was |
|
490 // FIPS before, then it becomes non-FIPS next. |
|
491 SECMODModule *internal; |
|
492 |
|
493 // This function returns us a pointer to a local copy of |
|
494 // the internal module stashed in NSS. We don't want to |
|
495 // delete it since it will cause much pain in NSS. |
|
496 internal = SECMOD_GetInternalModule(); |
|
497 if (!internal) |
|
498 return NS_ERROR_FAILURE; |
|
499 |
|
500 SECStatus srv = SECMOD_DeleteInternalModule(internal->commonName); |
|
501 if (srv != SECSuccess) |
|
502 return NS_ERROR_FAILURE; |
|
503 |
|
504 return NS_OK; |
|
505 } |
|
506 |
|
507 /* readonly attribute boolean isFIPSEnabled; */ |
|
508 NS_IMETHODIMP nsPKCS11ModuleDB::GetIsFIPSEnabled(bool *aIsFIPSEnabled) |
|
509 { |
|
510 nsNSSShutDownPreventionLock locker; |
|
511 *aIsFIPSEnabled = PK11_IsFIPS(); |
|
512 return NS_OK; |
|
513 } |
|
514 |
|
515 NS_IMETHODIMP nsPKCS11ModuleDB::GetIsFIPSModeActive(bool *aIsFIPSModeActive) |
|
516 { |
|
517 return GetIsFIPSEnabled(aIsFIPSModeActive); |
|
518 } |