|
1 // Copyright (c) 2005-2009 Nokia Corporation and/or its subsidiary(-ies). |
|
2 // All rights reserved. |
|
3 // This component and the accompanying materials are made available |
|
4 // under the terms of "Eclipse Public License v1.0" |
|
5 // which accompanies this distribution, and is available |
|
6 // at the URL "http://www.eclipse.org/legal/epl-v10.html". |
|
7 // |
|
8 // Initial Contributors: |
|
9 // Nokia Corporation - initial contribution. |
|
10 // |
|
11 // Contributors: |
|
12 // |
|
13 // Description: |
|
14 // This is part of an ECOM plug-in |
|
15 // |
|
16 // |
|
17 |
|
18 #include <implementationproxy.h> |
|
19 #include "ipcprfactory.h" // CIPNetworkProviderFactory |
|
20 #include "ipcprprovider.h" |
|
21 #include <es_sock.h> // KCommsNetworkLayerId |
|
22 #include <ss_glob.h> |
|
23 #include <shimcprfactory.h> |
|
24 #include <esockmessages.h> |
|
25 #include <commdbconnpref.h> // TConnPref |
|
26 #include <commsdattypesv1_1.h> // CommsDat |
|
27 #include <es_connpref.h> |
|
28 #include <in_sock.h> //KAfInet |
|
29 |
|
30 using namespace CommsDat; |
|
31 using namespace ESock; |
|
32 |
|
33 const TInt KIPConnectionProviderImplementationUid=0x102070EF; |
|
34 |
|
35 /** |
|
36 Data required for instantiating ECOM Plugin |
|
37 */ |
|
38 const TImplementationProxy ImplementationTable[] = |
|
39 { |
|
40 IMPLEMENTATION_PROXY_ENTRY(KIPConnectionProviderImplementationUid, CIPNetworkProviderFactory::NewL) |
|
41 }; |
|
42 |
|
43 /** |
|
44 ECOM Implementation Factory |
|
45 */ |
|
46 EXPORT_C const TImplementationProxy* ImplementationGroupProxy(TInt& aTableCount) |
|
47 { |
|
48 aTableCount = sizeof(ImplementationTable) / sizeof(TImplementationProxy); |
|
49 return ImplementationTable; |
|
50 } |
|
51 |
|
52 CIPNetworkProviderFactory* CIPNetworkProviderFactory::NewL(TAny* aParentContainer) |
|
53 { |
|
54 return new (ELeave) CIPNetworkProviderFactory(KIPConnectionProviderFactoryId, *(reinterpret_cast<CConnectionFactoryContainer*>(aParentContainer))); |
|
55 } |
|
56 |
|
57 CIPNetworkProviderFactory::CIPNetworkProviderFactory(TUint aFactoryId, CConnectionFactoryContainer& aParentContainer) |
|
58 : CConnectionProviderFactoryBase(aFactoryId,aParentContainer) |
|
59 { |
|
60 } |
|
61 |
|
62 CConnectionProviderBase* CIPNetworkProviderFactory::DoCreateProviderL() |
|
63 { |
|
64 return CIPNetworkConnectionProvider::NewL(*this); |
|
65 } |
|
66 |
|
67 MProviderSelector* CIPNetworkProviderFactory::DoSelectProvider( Meta::SMetaData& aPreferences, ISelectionNotify& aSelectionNotify, const RMessagePtr2* aMessage ) |
|
68 { |
|
69 //create self destructing object to select a provider |
|
70 CIPConnectionSelector* selector = new CIPConnectionSelector(aSelectionNotify,*this); |
|
71 TInt error; |
|
72 if (selector == 0) |
|
73 { |
|
74 error = KErrNoMemory; |
|
75 } |
|
76 else |
|
77 { |
|
78 error = selector->Select(aPreferences, aMessage); |
|
79 } |
|
80 |
|
81 if (error != KErrNone) |
|
82 { |
|
83 aSelectionNotify.SelectComplete(0, error); |
|
84 selector = NULL; //The selector will delete itself. |
|
85 } |
|
86 |
|
87 |
|
88 return selector; |
|
89 } |
|
90 |
|
91 MProviderSelector* CIPNetworkProviderFactory::DoSelectNextLayerProvider( Meta::SMetaData& aPreferences, ISelectionNotify& aSelectionNotify, const RMessagePtr2* /*aMessage*/ ) |
|
92 {//at the moment always uses the CConnectionProviderFactoryShim::SelectProviderL |
|
93 (void)aPreferences; |
|
94 (void)aSelectionNotify; |
|
95 return NULL; |
|
96 } |
|
97 |
|
98 void CIPNetworkProviderFactory::DoEnumerateConnectionsL(RPointerArray<TConnectionInfo>& aConnectionInfoPtrArray) |
|
99 { |
|
100 CConnectionFactoryContainer* connectionFactories = SockManGlobals::Get()->iConnectionFactories; |
|
101 ASSERT(connectionFactories); |
|
102 CConnectionProviderFactoryBase* factory = connectionFactories->FindFactory(KShimConnectionProviderFactoryId); |
|
103 ASSERT(factory); |
|
104 factory->EnumerateConnectionsL(aConnectionInfoPtrArray); |
|
105 } |
|
106 |
|
107 |
|
108 //CIPConnectionSelector-- |
|
109 TInt CIPConnectionSelector::Cancel() |
|
110 { |
|
111 return Cancel(KErrCancel,NULL); |
|
112 } |
|
113 |
|
114 TInt CIPConnectionSelector::Cancel(TInt aReason, const RMessage2* aMessage) |
|
115 { |
|
116 CActive::Cancel(); // There may be an outstanding selection request. |
|
117 |
|
118 //CIPConnectionSelector will be deleted from Detach(). |
|
119 //Detach will always be called as a result of Cancel() in the same call stack, |
|
120 //but only after all progress notifications have been passed up towards the |
|
121 //CConnection. |
|
122 TInt ret = KErrNotReady; |
|
123 if(iNextLayerSelector !=NULL) |
|
124 { |
|
125 ret = iNextLayerSelector->Cancel(aReason, aMessage); |
|
126 } |
|
127 |
|
128 iNotify.Detach(); //"PrevLayer"::Detach() will be called only once in the same call stack. |
|
129 return ret; |
|
130 } |
|
131 |
|
132 TInt CIPConnectionSelector::Select(Meta::SMetaData& aPreferences, const RMessagePtr2* aMessage) |
|
133 { |
|
134 __FLOG_OPEN(KIpcprTag, KIpcprSubTag); |
|
135 __FLOG_1(_L8("CIPConnectionSelector::Select() %08x"), this); |
|
136 |
|
137 STypeId tId = STypeId::CreateSTypeId(aPreferences.GetTypeId()); |
|
138 ASSERT(tId.iUid.iUid == KESockMessagesImplementationUid); |
|
139 ASSERT(tId.iType == EESockMessageConnStart); |
|
140 |
|
141 if (aMessage) |
|
142 iSelectMessage = *aMessage; // aMessage will be passed on to shim |
|
143 #ifdef SYMBIAN_NETWORKING_UMTSR5 |
|
144 //Here secure Id of application is stored and will be kept with |
|
145 if(!iSelectMessage.IsNull()) |
|
146 { |
|
147 iAppSecureId=iSelectMessage.SecureId(); |
|
148 } |
|
149 #endif // SYMBIAN_NETWORKING_UMTSR5 |
|
150 |
|
151 TRAPD(r, SelectL(aPreferences)); |
|
152 |
|
153 if (r!=KErrNone && iNextLayerSelector==NULL) |
|
154 { |
|
155 __FLOG_1(_L8("Error during selection of current - should detech now %08x"), this); |
|
156 Detach(); |
|
157 return r; |
|
158 } |
|
159 |
|
160 TRAP(r,SelectLinkLayerL()); |
|
161 if (r != KErrNone) |
|
162 { |
|
163 __FLOG_1(_L8("Error during select of link layer - detach should be called by the link layer %08x"), this); |
|
164 } |
|
165 |
|
166 return r; |
|
167 } |
|
168 |
|
169 void CIPConnectionSelector::SelectL(Meta::SMetaData& aPreferences) |
|
170 { |
|
171 ASSERT(iDbs==0); |
|
172 #ifdef SYMBIAN_NON_SEAMLESS_NETWORK_BEARER_MOBILITY |
|
173 iDbs = CMDBSession::NewL(KCDVersion1_2); |
|
174 #else |
|
175 iDbs = CMDBSession::NewL(KCDVersion1_1); |
|
176 #endif |
|
177 |
|
178 // Reveal hidden or private IAP records if a licensee has chosen to protect a record |
|
179 // using one of these flags - the API to do this is public so internal components |
|
180 // have to support the use of such records. |
|
181 iDbs->SetAttributeMask( ECDHidden | ECDPrivate ); |
|
182 |
|
183 ASSERT(iConnStart==0); |
|
184 iConnStart = CConnStart::NewL(); |
|
185 iConnStart->Copy(aPreferences); |
|
186 |
|
187 // Get "defaultSnap" and "promptForSnap" from CommsDat. |
|
188 CCDGlobalSettingsRecord* gs = LoadGlobalSettingsRecordLC(); |
|
189 TBool promptForSnap = gs->iPromptForSnap; |
|
190 iAPid = gs->iDefaultSnap; // Unless reassigned, iAPid becomes the default access point. |
|
191 CleanupStack::PopAndDestroy(gs); |
|
192 |
|
193 __FLOG_STMT(_LIT(K, "SelectL() Prompt%d Def%d")); |
|
194 __FLOG_2(K, promptForSnap, iAPid); |
|
195 |
|
196 if (iAPid != 0) |
|
197 // System is access point aware. |
|
198 { |
|
199 TConnStartType selectType(iConnStart->StartType()); |
|
200 TConnPref* selectPrefs = iConnStart->ConnPrefs(); |
|
201 |
|
202 if (selectType == EConnStartImplicit || |
|
203 selectPrefs == 0 || selectPrefs->ExtensionId() == TConnPref::EConnPrefUnknown) |
|
204 // Use default access point or dialogue if enabled. |
|
205 { |
|
206 __FLOG_STMT(_LIT(K, "SelectL() Default Type%d Prefs%d")); |
|
207 __FLOG_2(K, selectType, selectPrefs); |
|
208 |
|
209 // Use the default access point unless promptForSnap is ETrue in which case prompt |
|
210 // for the access point. |
|
211 if (promptForSnap) |
|
212 { |
|
213 User::LeaveIfError(iDlgServ.Connect()); |
|
214 iDlgServ.AccessPointConnection(iAPid,KAfInet,iStatus); |
|
215 SetActive(); |
|
216 return; // Don't do selection until RunL() gets the dialogue results. |
|
217 } |
|
218 |
|
219 CCDIAPPrioritySelectionPolicyRecord* policy = LoadPolicyRecordLC(iAPid); |
|
220 FillListL(*policy); |
|
221 CleanupStack::PopAndDestroy(policy); |
|
222 } |
|
223 else if (selectPrefs && selectPrefs->ExtensionId() == TConnPref::EConnPrefSnap) |
|
224 // Use access point id from preferences. |
|
225 { |
|
226 iAPid = static_cast<const TCommSnapPref*>(selectPrefs)->Snap(); |
|
227 |
|
228 __FLOG_STMT(_LIT(K, "SelectL() Type%d TConnPrefSnap AccessPoint%d ")); |
|
229 __FLOG_2(K, selectType, iAPid); |
|
230 |
|
231 CCDIAPPrioritySelectionPolicyRecord* policy = LoadPolicyRecordLC(iAPid); |
|
232 FillListL(*policy); |
|
233 CleanupStack::PopAndDestroy(policy); |
|
234 } |
|
235 } |
|
236 } |
|
237 |
|
238 void CIPConnectionSelector::FillListL(CCDIAPPrioritySelectionPolicyRecord& aPolicy) |
|
239 { |
|
240 __FLOG_0(_L("FillListL()")); |
|
241 |
|
242 // Make sure we have the TCommIdList. |
|
243 |
|
244 // Create the new Prefs on the heap so that they are always available |
|
245 // even in the asynchronous promptForSnap Active Object callback |
|
246 // The copy of the original Prefs are overwritten here |
|
247 // The original Prefs are deleted in esock. |
|
248 // The new Prefs are deleted on destruction of CIPConnectionSelector |
|
249 |
|
250 iConnStart->SetConnPrefs(NULL); |
|
251 iConnStart->SetConnPrefs(new (ELeave) TCommIdList); |
|
252 |
|
253 // Store Prefs for deletion on destruction of CIPConnectionSelector |
|
254 ASSERT(iPrefs==0); |
|
255 iPrefs = iConnStart->ConnPrefs(); |
|
256 |
|
257 TCommIdList& list = *static_cast<TCommIdList*>(iPrefs); |
|
258 |
|
259 CMDBRecordLink<CCDIAPRecord>* theIap = &aPolicy.iIap1; |
|
260 CMDBField<TUint32>* theCount = &aPolicy.iIapCount; |
|
261 TInt count = static_cast<TInt>(*theCount); |
|
262 if (count > CCDIAPPrioritySelectionPolicyRecord::EMaxNrOfIaps) |
|
263 { |
|
264 // The number of IAP's specified is more than allowed. Fix your table :-) |
|
265 ASSERT(EFalse); |
|
266 count = CCDIAPPrioritySelectionPolicyRecord::EMaxNrOfIaps; |
|
267 } |
|
268 for (TInt i = 0; i < count; i++, theIap++) |
|
269 { |
|
270 TInt theIapNumber = static_cast<TInt>(*theIap); |
|
271 ASSERT(theIapNumber>0); |
|
272 __FLOG_STMT(_LIT(K, "aList[%d].Append(%d)")); |
|
273 __FLOG_2(K, list.Count(), theIapNumber); |
|
274 list.Append(theIapNumber); |
|
275 } |
|
276 } |
|
277 |
|
278 void CIPConnectionSelector::SelectLinkLayerL() |
|
279 { |
|
280 CConnectionFactoryContainer* connectionFactories = SockManGlobals::Get()->iConnectionFactories; |
|
281 ASSERT(connectionFactories); |
|
282 CConnectionProviderFactoryBase* factory = connectionFactories->FindFactory(KShimConnectionProviderFactoryId); |
|
283 ASSERT(factory); |
|
284 ISelectionNotify selectNotify( this, TSelectionNotify<CIPConnectionSelector>::SelectComplete, |
|
285 TProgressNotify<CIPConnectionSelector>::ProgressNotification, |
|
286 TServiceChangeNotify<CIPConnectionSelector>::ServiceChangeNotification, |
|
287 TLayerUp<CIPConnectionSelector>::LayerUp, |
|
288 TSubConnectionEventTmpl<CIPConnectionSelector>::SubConnectionEvent, NULL); |
|
289 selectNotify.RegisterDetach(TDetachNotify<CIPConnectionSelector>::Detach); |
|
290 |
|
291 if (iNextLayerSelector!=NULL) |
|
292 iNextLayerSelector->Cancel(); |
|
293 |
|
294 // Select next (link) layer's provider. |
|
295 ASSERT(iNextLayerSelector==NULL); |
|
296 ASSERT(iConnStart!=NULL); |
|
297 |
|
298 iNextLayerSelector = factory->SelectProvider(*iConnStart, selectNotify, iSelectMessage.IsNull()? NULL : &iSelectMessage); |
|
299 |
|
300 if (iNextLayerSelector == NULL) |
|
301 { |
|
302 User::Leave(KErrGeneral); |
|
303 } |
|
304 |
|
305 } |
|
306 |
|
307 void CIPConnectionSelector::SelectComplete(CConnectionProviderBase* aConnProvider, TInt aError) |
|
308 { |
|
309 CIPNetworkConnectionProvider* connProvider = NULL; |
|
310 if (aError == KErrNone) |
|
311 { |
|
312 ASSERT(aConnProvider); |
|
313 XConnectionIPFactoryQuery query(aConnProvider); |
|
314 |
|
315 TRAP( aError, connProvider = static_cast<CIPNetworkConnectionProvider*>(iFactory.FindOrCreateProviderL(query))); |
|
316 if (aError == KErrNone && connProvider->NextLayer() == NULL) |
|
317 { |
|
318 |
|
319 #ifdef SYMBIAN_NETWORKING_UMTSR5 |
|
320 // This piece of code is added to keep the information about the application secure ID in the |
|
321 // IP Connection provider. So that when the information is required form the subconnection provider |
|
322 // we can do a fetch interface and get the App Secure ID to decide on to the Socket Blocking |
|
323 |
|
324 connProvider->SetAppSecurId(iAppSecureId.iId); |
|
325 |
|
326 |
|
327 #endif // SYMBIAN_NETWORKING_UMTSR5 |
|
328 |
|
329 // The factory returned a new instance - must set the lower layer. |
|
330 TRAP(aError,connProvider->JoinNextLayerL(aConnProvider)); |
|
331 } |
|
332 } |
|
333 iNotify.SelectComplete(connProvider, aError); |
|
334 } |
|
335 |
|
336 void CIPConnectionSelector::ProgressNotification(TInt aStage, TInt aError) |
|
337 { |
|
338 //The original ISelectionNotifier (iNotify) might be interested in the |
|
339 //progress, but we aren't. |
|
340 iNotify.ProgressNotification(aStage, aError); |
|
341 } |
|
342 |
|
343 void CIPConnectionSelector::LayerUp(TInt aError) |
|
344 { |
|
345 iNotify.LayerUp(aError); |
|
346 } |
|
347 |
|
348 void CIPConnectionSelector::SubConnectionEvent(CSubConnectionProviderBase* aSubConnNextLayerProvider, const TSubConnectionEvent& aSubConnectionEvent) |
|
349 { |
|
350 iNotify.SubConnectionEvent(aSubConnNextLayerProvider, aSubConnectionEvent); |
|
351 } |
|
352 |
|
353 void CIPConnectionSelector::ServiceChangeNotification(TUint32 aId, const TDesC& aType) |
|
354 { |
|
355 //The original ISelectionNotifier (iNotify) might be interested in the |
|
356 //notification, but we aren't. |
|
357 iNotify.ServiceChangeNotification(aId, aType); |
|
358 } |
|
359 |
|
360 void CIPConnectionSelector::Detach() |
|
361 { |
|
362 iNextLayerSelector = NULL; |
|
363 //Ensure the asynch destructor is ready to use. |
|
364 //If its not, then we have probably been already deleted which should never happen. |
|
365 //Detach is the only place we should be deleted from. |
|
366 ASSERT(!iAsyncDestructor.IsActive()); |
|
367 __FLOG_1(_L8("CIPConnectionSelector %08x::Detach()"), this); |
|
368 iAsyncDestructor.Call(); |
|
369 } |
|
370 |
|
371 CIPConnectionSelector::CIPConnectionSelector(ISelectionNotify& aNotify, CIPNetworkProviderFactory& aFactory) |
|
372 : CActive(CActive::EPriorityUserInput), |
|
373 iNotify(aNotify), |
|
374 iFactory(aFactory), |
|
375 iAsyncDestructor(CActive::EPriorityLow) |
|
376 { |
|
377 __FLOG_1(_L8("CIPConnectionSelector %08x::CIPConnectionSelector()"), this); |
|
378 CActiveScheduler::Add(this); |
|
379 iAsyncDestructor.Set(TCallBack(CIPConnectionSelector::DestroyMyself, this)); |
|
380 |
|
381 } |
|
382 |
|
383 TInt CIPConnectionSelector::DestroyMyself(TAny* aSelf) |
|
384 { |
|
385 delete static_cast<CIPConnectionSelector*>(aSelf); |
|
386 return KErrNone; |
|
387 } |
|
388 |
|
389 CIPConnectionSelector::~CIPConnectionSelector() |
|
390 { |
|
391 __FLOG_CLOSE; |
|
392 CActive::Cancel(); // There may be an outstanding selection request. |
|
393 |
|
394 // This destructor is private and is meant to be called asynchronously via Detach() or Cancel() only. |
|
395 // If is was called from anywhere else, the iNextLayerSelector would not be deleted! |
|
396 // Please note that deleting iNextLayerSelector here needs revision on the link layer selectors, |
|
397 // and specifically of the shim selector which - in such case - must not call Detach from its |
|
398 // synchronous destructor! |
|
399 ASSERT(iNextLayerSelector==NULL); // If still a valid pointer - probably not called via Detach() or Cancel(). |
|
400 |
|
401 delete iDbs; |
|
402 |
|
403 // Tidy up iConnStart and related objects |
|
404 delete iPrefs; |
|
405 delete iConnStart; |
|
406 |
|
407 iDlgServ.Close(); |
|
408 |
|
409 // Notify detach. |
|
410 iNotify.Detach(); |
|
411 } |
|
412 |
|
413 void CIPConnectionSelector::RunL() |
|
414 // The dialogue has been presented. |
|
415 // Normally completes with KErrNone or KErrCancel |
|
416 // Could, however, complete with another system error e.g. KErrOutOfMemory |
|
417 { |
|
418 __FLOG_STMT(_LIT(K, "RunL() Err%d Snap%d")); |
|
419 __FLOG_2(K, iStatus.Int(), iAPid); |
|
420 |
|
421 User::LeaveIfError(iStatus.Int()); |
|
422 ASSERT(iAPid); //Should not be 0 now. |
|
423 CCDIAPPrioritySelectionPolicyRecord* policy = LoadPolicyRecordLC(iAPid); |
|
424 FillListL(*policy); |
|
425 CleanupStack::PopAndDestroy(policy); |
|
426 SelectLinkLayerL(); |
|
427 } |
|
428 |
|
429 TInt CIPConnectionSelector::RunError(TInt aError) |
|
430 // Either the dialogue, the FillListL() or the SelectLinkLayerL() failed. |
|
431 // In each case the selection request is completed with the apropriate result code. |
|
432 { |
|
433 iNotify.SelectComplete(0, aError); |
|
434 |
|
435 //If we have failed before the call to iNextLayerSelector->Select() or it wasn't successful |
|
436 //we need to initiate the detach sequence by calling Detach(). |
|
437 if (iNextLayerSelector==NULL) |
|
438 { |
|
439 Detach(); //It will result in self deletion. |
|
440 } |
|
441 return KErrNone; |
|
442 } |
|
443 |
|
444 void CIPConnectionSelector::DoCancel() |
|
445 { |
|
446 iDlgServ.CancelAccessPointConnection(); |
|
447 } |
|
448 |
|
449 CCDGlobalSettingsRecord* CIPConnectionSelector::LoadGlobalSettingsRecordLC() |
|
450 { |
|
451 CCDGlobalSettingsRecord* gs = static_cast<CCDGlobalSettingsRecord*>(CCDConnectionPrefsRecord::RecordFactoryL(KCDTIdGlobalSettingsRecord)); |
|
452 CleanupStack::PushL(gs); |
|
453 gs->SetRecordId(1); |
|
454 gs->LoadL(*iDbs); |
|
455 ASSERT(gs->iDefaultSnap.TypeId() == KCDTIdDefaultSnap); // Panics if built against incorrect CommsDat. |
|
456 return gs; |
|
457 } |
|
458 |
|
459 CCDIAPPrioritySelectionPolicyRecord* CIPConnectionSelector::LoadPolicyRecordLC(TInt aAccessPoint) |
|
460 { |
|
461 // Get access point from CommsDat. |
|
462 CCDAccessPointRecord* apRecord = static_cast<CCDAccessPointRecord*> |
|
463 (CCDConnectionPrefsRecord::RecordFactoryL(KCDTIdAccessPointRecord)); |
|
464 CleanupStack::PushL(apRecord); |
|
465 apRecord->SetRecordId(aAccessPoint); |
|
466 apRecord->LoadL(*iDbs); |
|
467 TUint32 policyNumber = apRecord->iSelectionPolicy; |
|
468 CleanupStack::PopAndDestroy(apRecord); |
|
469 |
|
470 ASSERT((policyNumber & KCDMaskShowRecordType) == KCDTIdIapPrioritySelectionPolicyRecord); |
|
471 |
|
472 CCDIAPPrioritySelectionPolicyRecord* policy = static_cast<CCDIAPPrioritySelectionPolicyRecord*> |
|
473 (CCDConnectionPrefsRecord::RecordFactoryL(KCDTIdIapPrioritySelectionPolicyRecord)); |
|
474 CleanupStack::PushL(policy); |
|
475 policy->SetElementId(policyNumber); |
|
476 policy->LoadL(*iDbs); |
|
477 return policy; |
|
478 } |
|
479 |
|
480 MCommsFactoryQuery::TMatchResult XConnectionIPFactoryQuery::Match( TFactoryObjectInfo& aProviderInfo ) |
|
481 { |
|
482 CConnectionProviderBase* prov = static_cast<CConnectionProviderBase*>(aProviderInfo.iInfo.iFactoryObject); |
|
483 //if the next layer is the same as the one returned by the shim selection we have a match |
|
484 return prov->NextLayer() == iConnectionProviderBase ? EMatch : EContinue; |
|
485 } |
|
486 |