Imported Upstream version 1.18.0
[platform/upstream/grpc.git] / src / php / tests / unit_tests / ChannelTest.php
1 <?php
2 /*
3  *
4  * Copyright 2018 gRPC authors.
5  *
6  * Licensed under the Apache License, Version 2.0 (the "License");
7  * you may not use this file except in compliance with the License.
8  * You may obtain a copy of the License at
9  *
10  *     http://www.apache.org/licenses/LICENSE-2.0
11  *
12  * Unless required by applicable law or agreed to in writing, software
13  * distributed under the License is distributed on an "AS IS" BASIS,
14  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15  * See the License for the specific language governing permissions and
16  * limitations under the License.
17  *
18  */
19
20 class ChannelTest extends PHPUnit_Framework_TestCase
21 {
22     public function setUp()
23     {
24     }
25
26     public function tearDown()
27     {
28         if (!empty($this->channel)) {
29             $this->channel->close();
30         }
31     }
32
33     public function testInsecureCredentials()
34     {
35         $this->channel = new Grpc\Channel('localhost:50000',
36             ['credentials' => Grpc\ChannelCredentials::createInsecure()]);
37         $this->assertSame('Grpc\Channel', get_class($this->channel));
38     }
39
40     public function testGetConnectivityState()
41     {
42         $this->channel = new Grpc\Channel('localhost:50001',
43              ['credentials' => Grpc\ChannelCredentials::createInsecure()]);
44         $state = $this->channel->getConnectivityState();
45         $this->assertEquals(0, $state);
46     }
47
48     public function testGetConnectivityStateWithInt()
49     {
50         $this->channel = new Grpc\Channel('localhost:50002',
51              ['credentials' => Grpc\ChannelCredentials::createInsecure()]);
52         $state = $this->channel->getConnectivityState(123);
53         $this->assertEquals(0, $state);
54     }
55
56     public function testGetConnectivityStateWithString()
57     {
58         $this->channel = new Grpc\Channel('localhost:50003',
59              ['credentials' => Grpc\ChannelCredentials::createInsecure()]);
60         $state = $this->channel->getConnectivityState('hello');
61         $this->assertEquals(0, $state);
62     }
63
64     public function testGetConnectivityStateWithBool()
65     {
66         $this->channel = new Grpc\Channel('localhost:50004',
67              ['credentials' => Grpc\ChannelCredentials::createInsecure()]);
68         $state = $this->channel->getConnectivityState(true);
69         $this->assertEquals(0, $state);
70     }
71
72     public function testGetTarget()
73     {
74         $this->channel = new Grpc\Channel('localhost:50005',
75              ['credentials' => Grpc\ChannelCredentials::createInsecure()]);
76         $target = $this->channel->getTarget();
77         $this->assertTrue(is_string($target));
78     }
79
80     public function testWatchConnectivityState()
81     {
82         $this->channel = new Grpc\Channel('localhost:50006',
83              ['credentials' => Grpc\ChannelCredentials::createInsecure()]);
84         $now = Grpc\Timeval::now();
85         $deadline = $now->add(new Grpc\Timeval(100*1000));  // 100ms
86         // we act as if 'CONNECTING'(=1) was the last state
87         // we saw, so the default state of 'IDLE' should be delivered instantly
88         $state = $this->channel->watchConnectivityState(1, $deadline);
89         $this->assertTrue($state);
90         unset($now);
91         unset($deadline);
92     }
93
94     public function testClose()
95     {
96         $this->channel = new Grpc\Channel('localhost:50007',
97              ['credentials' => Grpc\ChannelCredentials::createInsecure()]);
98         $this->assertNotNull($this->channel);
99         $this->channel->close();
100     }
101
102     /**
103      * @expectedException InvalidArgumentException
104      */
105     public function testInvalidConstructorWithNull()
106     {
107         $this->channel = new Grpc\Channel();
108         $this->assertNull($this->channel);
109     }
110
111     /**
112      * @expectedException InvalidArgumentException
113      */
114     public function testInvalidConstructorWith()
115     {
116         $this->channel = new Grpc\Channel('localhost:50008', 'invalid');
117         $this->assertNull($this->channel);
118     }
119
120     /**
121      * @expectedException InvalidArgumentException
122      */
123     public function testInvalidCredentials()
124     {
125         $this->channel = new Grpc\Channel('localhost:50009',
126             ['credentials' => new Grpc\Timeval(100)]);
127     }
128
129     /**
130      * @expectedException InvalidArgumentException
131      */
132     public function testInvalidOptionsArray()
133     {
134         $this->channel = new Grpc\Channel('localhost:50010',
135             ['abc' => []]);
136     }
137
138     /**
139      * @expectedException InvalidArgumentException
140      */
141     public function testInvalidGetConnectivityStateWithArray()
142     {
143         $this->channel = new Grpc\Channel('localhost:50011',
144             ['credentials' => Grpc\ChannelCredentials::createInsecure()]);
145         $this->channel->getConnectivityState([]);
146     }
147
148     /**
149      * @expectedException InvalidArgumentException
150      */
151     public function testInvalidWatchConnectivityState()
152     {
153         $this->channel = new Grpc\Channel('localhost:50012',
154             ['credentials' => Grpc\ChannelCredentials::createInsecure()]);
155         $this->channel->watchConnectivityState([]);
156     }
157
158     /**
159      * @expectedException InvalidArgumentException
160      */
161     public function testInvalidWatchConnectivityState2()
162     {
163         $this->channel = new Grpc\Channel('localhost:50013',
164             ['credentials' => Grpc\ChannelCredentials::createInsecure()]);
165         $this->channel->watchConnectivityState(1, 'hi');
166     }
167
168
169     public function assertConnecting($state) {
170       $this->assertTrue($state == GRPC\CHANNEL_CONNECTING ||
171                         $state == GRPC\CHANNEL_TRANSIENT_FAILURE);
172     }
173
174     public function waitUntilNotIdle($channel) {
175         for ($i = 0; $i < 10; $i++) {
176             $now = Grpc\Timeval::now();
177             $deadline = $now->add(new Grpc\Timeval(1000));
178             if ($channel->watchConnectivityState(GRPC\CHANNEL_IDLE,
179                                                  $deadline)) {
180                 return true;
181             }
182         }
183         $this->assertTrue(false);
184     }
185
186     public function testPersistentChannelSameHost()
187     {
188         $this->channel1 = new Grpc\Channel('localhost:50014', [
189             "grpc_target_persist_bound" => 3,
190         ]);
191         // the underlying grpc channel is the same by default
192         // when connecting to the same host
193         $this->channel2 = new Grpc\Channel('localhost:50014', []);
194
195         // both channels should be IDLE
196         $state = $this->channel1->getConnectivityState();
197         $this->assertEquals(GRPC\CHANNEL_IDLE, $state);
198         $state = $this->channel2->getConnectivityState();
199         $this->assertEquals(GRPC\CHANNEL_IDLE, $state);
200
201         // try to connect on channel1
202         $state = $this->channel1->getConnectivityState(true);
203         $this->waitUntilNotIdle($this->channel1);
204
205         // both channels should now be in the CONNECTING state
206         $state = $this->channel1->getConnectivityState();
207         $this->assertConnecting($state);
208         $state = $this->channel2->getConnectivityState();
209         $this->assertConnecting($state);
210
211         $this->channel1->close();
212         $this->channel2->close();
213     }
214
215     public function testPersistentChannelDifferentHost()
216     {
217         // two different underlying channels because different hostname
218         $this->channel1 = new Grpc\Channel('localhost:50015', [
219             "grpc_target_persist_bound" => 3,
220         ]);
221         $this->channel2 = new Grpc\Channel('localhost:50016', []);
222
223         // both channels should be IDLE
224         $state = $this->channel1->getConnectivityState();
225         $this->assertEquals(GRPC\CHANNEL_IDLE, $state);
226         $state = $this->channel2->getConnectivityState();
227         $this->assertEquals(GRPC\CHANNEL_IDLE, $state);
228
229         // try to connect on channel1
230         $state = $this->channel1->getConnectivityState(true);
231         $this->waitUntilNotIdle($this->channel1);
232
233         // channel1 should now be in the CONNECTING state
234         $state = $this->channel1->getConnectivityState();
235         $this->assertConnecting($state);
236         // channel2 should still be in the IDLE state
237         $state = $this->channel2->getConnectivityState();
238         $this->assertEquals(GRPC\CHANNEL_IDLE, $state);
239
240         $this->channel1->close();
241         $this->channel2->close();
242     }
243
244     public function testPersistentChannelSameArgs()
245     {
246         $this->channel1 = new Grpc\Channel('localhost:50017', [
247           "grpc_target_persist_bound" => 3,
248           "abc" => "def",
249           ]);
250         $this->channel2 = new Grpc\Channel('localhost:50017', ["abc" => "def"]);
251
252         // try to connect on channel1
253         $state = $this->channel1->getConnectivityState(true);
254         $this->waitUntilNotIdle($this->channel1);
255
256         $state = $this->channel1->getConnectivityState();
257         $this->assertConnecting($state);
258         $state = $this->channel2->getConnectivityState();
259         $this->assertConnecting($state);
260
261         $this->channel1->close();
262         $this->channel2->close();
263     }
264
265     public function testPersistentChannelDifferentArgs()
266     {
267         $this->channel1 = new Grpc\Channel('localhost:50018', [
268             "grpc_target_persist_bound" => 3,
269           ]);
270         $this->channel2 = new Grpc\Channel('localhost:50018', ["abc" => "def"]);
271
272         // try to connect on channel1
273         $state = $this->channel1->getConnectivityState(true);
274         $this->waitUntilNotIdle($this->channel1);
275
276         $state = $this->channel1->getConnectivityState();
277         $this->assertConnecting($state);
278         $state = $this->channel2->getConnectivityState();
279         $this->assertEquals(GRPC\CHANNEL_IDLE, $state);
280
281         $this->channel1->close();
282         $this->channel2->close();
283     }
284
285     public function testPersistentChannelSameChannelCredentials()
286     {
287         $creds1 = Grpc\ChannelCredentials::createSsl();
288         $creds2 = Grpc\ChannelCredentials::createSsl();
289
290         $this->channel1 = new Grpc\Channel('localhost:50019',
291                                            ["credentials" => $creds1,
292                                              "grpc_target_persist_bound" => 3,
293                                              ]);
294         $this->channel2 = new Grpc\Channel('localhost:50019',
295                                            ["credentials" => $creds2]);
296
297         // try to connect on channel1
298         $state = $this->channel1->getConnectivityState(true);
299         $this->waitUntilNotIdle($this->channel1);
300
301         $state = $this->channel1->getConnectivityState();
302         $this->assertConnecting($state);
303         $state = $this->channel2->getConnectivityState();
304         $this->assertConnecting($state);
305
306         $this->channel1->close();
307         $this->channel2->close();
308     }
309
310     public function testPersistentChannelDifferentChannelCredentials()
311     {
312         $creds1 = Grpc\ChannelCredentials::createSsl();
313         $creds2 = Grpc\ChannelCredentials::createSsl(
314             file_get_contents(dirname(__FILE__).'/../data/ca.pem'));
315
316         $this->channel1 = new Grpc\Channel('localhost:50020',
317                                            ["credentials" => $creds1,
318                                              "grpc_target_persist_bound" => 3,
319                                              ]);
320         $this->channel2 = new Grpc\Channel('localhost:50020',
321                                            ["credentials" => $creds2]);
322
323         // try to connect on channel1
324         $state = $this->channel1->getConnectivityState(true);
325         $this->waitUntilNotIdle($this->channel1);
326
327         $state = $this->channel1->getConnectivityState();
328         $this->assertConnecting($state);
329         $state = $this->channel2->getConnectivityState();
330         $this->assertEquals(GRPC\CHANNEL_IDLE, $state);
331
332         $this->channel1->close();
333         $this->channel2->close();
334     }
335
336     public function testPersistentChannelSameChannelCredentialsRootCerts()
337     {
338         $creds1 = Grpc\ChannelCredentials::createSsl(
339             file_get_contents(dirname(__FILE__).'/../data/ca.pem'));
340         $creds2 = Grpc\ChannelCredentials::createSsl(
341             file_get_contents(dirname(__FILE__).'/../data/ca.pem'));
342
343         $this->channel1 = new Grpc\Channel('localhost:50021',
344                                            ["credentials" => $creds1,
345                                              "grpc_target_persist_bound" => 3,
346                                              ]);
347         $this->channel2 = new Grpc\Channel('localhost:50021',
348                                            ["credentials" => $creds2]);
349
350         // try to connect on channel1
351         $state = $this->channel1->getConnectivityState(true);
352         $this->waitUntilNotIdle($this->channel1);
353
354         $state = $this->channel1->getConnectivityState();
355         $this->assertConnecting($state);
356         $state = $this->channel2->getConnectivityState();
357         $this->assertConnecting($state);
358
359         $this->channel1->close();
360         $this->channel2->close();
361     }
362
363     public function testPersistentChannelDifferentSecureChannelCredentials()
364     {
365         $creds1 = Grpc\ChannelCredentials::createSsl();
366         $creds2 = Grpc\ChannelCredentials::createInsecure();
367
368         $this->channel1 = new Grpc\Channel('localhost:50022',
369                                            ["credentials" => $creds1,
370                                              "grpc_target_persist_bound" => 3,
371                                              ]);
372         $this->channel2 = new Grpc\Channel('localhost:50022',
373                                            ["credentials" => $creds2]);
374
375         // try to connect on channel1
376         $state = $this->channel1->getConnectivityState(true);
377         $this->waitUntilNotIdle($this->channel1);
378
379         $state = $this->channel1->getConnectivityState();
380         $this->assertConnecting($state);
381         $state = $this->channel2->getConnectivityState();
382         $this->assertEquals(GRPC\CHANNEL_IDLE, $state);
383
384         $this->channel1->close();
385         $this->channel2->close();
386     }
387
388     public function testPersistentChannelSharedChannelClose1()
389     {
390         // same underlying channel
391         $this->channel1 = new Grpc\Channel('localhost:50123', [
392             "grpc_target_persist_bound" => 3,
393         ]);
394         $this->channel2 = new Grpc\Channel('localhost:50123', []);
395
396         // close channel1
397         $this->channel1->close();
398
399         // channel2 can still be use. We need to exclude the possible that
400         // in testPersistentChannelSharedChannelClose2, the exception is thrown
401         // by channel1.
402         $state = $this->channel2->getConnectivityState();
403         $this->assertEquals(GRPC\CHANNEL_IDLE, $state);
404     }
405
406     /**
407      * @expectedException RuntimeException
408      */
409     public function testPersistentChannelSharedChannelClose2()
410     {
411         // same underlying channel
412         $this->channel1 = new Grpc\Channel('localhost:50223', [
413             "grpc_target_persist_bound" => 3,
414         ]);
415         $this->channel2 = new Grpc\Channel('localhost:50223', []);
416
417         // close channel1
418         $this->channel1->close();
419
420         // channel2 can still be use
421         $state = $this->channel2->getConnectivityState();
422         $this->assertEquals(GRPC\CHANNEL_IDLE, $state);
423
424         // channel 1 is closed
425         $state = $this->channel1->getConnectivityState();
426     }
427
428     public function testPersistentChannelCreateAfterClose()
429     {
430         $this->channel1 = new Grpc\Channel('localhost:50024', [
431             "grpc_target_persist_bound" => 3,
432         ]);
433
434         $this->channel1->close();
435
436         $this->channel2 = new Grpc\Channel('localhost:50024', []);
437         $state = $this->channel2->getConnectivityState();
438         $this->assertEquals(GRPC\CHANNEL_IDLE, $state);
439
440         $this->channel2->close();
441     }
442
443     public function testPersistentChannelSharedMoreThanTwo()
444     {
445         $this->channel1 = new Grpc\Channel('localhost:50025', [
446             "grpc_target_persist_bound" => 3,
447         ]);
448         $this->channel2 = new Grpc\Channel('localhost:50025', []);
449         $this->channel3 = new Grpc\Channel('localhost:50025', []);
450
451         // try to connect on channel1
452         $state = $this->channel1->getConnectivityState(true);
453         $this->waitUntilNotIdle($this->channel1);
454
455         // all 3 channels should be in CONNECTING state
456         $state = $this->channel1->getConnectivityState();
457         $this->assertConnecting($state);
458         $state = $this->channel2->getConnectivityState();
459         $this->assertConnecting($state);
460         $state = $this->channel3->getConnectivityState();
461         $this->assertConnecting($state);
462
463         $this->channel1->close();
464     }
465
466     public function callbackFunc($context)
467     {
468         return [];
469     }
470
471     public function callbackFunc2($context)
472     {
473         return ["k1" => "v1"];
474     }
475
476     public function testPersistentChannelWithCallCredentials()
477     {
478         $creds = Grpc\ChannelCredentials::createSsl();
479         $callCreds = Grpc\CallCredentials::createFromPlugin(
480             [$this, 'callbackFunc']);
481         $credsWithCallCreds = Grpc\ChannelCredentials::createComposite(
482             $creds, $callCreds);
483
484         // If a ChannelCredentials object is composed with a
485         // CallCredentials object, the underlying grpc channel will
486         // always be created new and NOT persisted.
487         $this->channel1 = new Grpc\Channel('localhost:50026',
488                                            ["credentials" =>
489                                             $credsWithCallCreds,
490                                             "grpc_target_persist_bound" => 3,
491                                             ]);
492         $this->channel2 = new Grpc\Channel('localhost:50026',
493                                            ["credentials" =>
494                                             $credsWithCallCreds]);
495
496         // try to connect on channel1
497         $state = $this->channel1->getConnectivityState(true);
498         $this->waitUntilNotIdle($this->channel1);
499
500         $state = $this->channel1->getConnectivityState();
501         $this->assertConnecting($state);
502         $state = $this->channel2->getConnectivityState();
503         $this->assertEquals(GRPC\CHANNEL_IDLE, $state);
504
505         $this->channel1->close();
506         $this->channel2->close();
507     }
508
509     public function testPersistentChannelWithDifferentCallCredentials()
510     {
511         $callCreds1 = Grpc\CallCredentials::createFromPlugin(
512             [$this, 'callbackFunc']);
513         $callCreds2 = Grpc\CallCredentials::createFromPlugin(
514             [$this, 'callbackFunc2']);
515
516         $creds1 = Grpc\ChannelCredentials::createSsl();
517         $creds2 = Grpc\ChannelCredentials::createComposite(
518             $creds1, $callCreds1);
519         $creds3 = Grpc\ChannelCredentials::createComposite(
520             $creds1, $callCreds2);
521
522         // Similar to the test above, anytime a ChannelCredentials
523         // object is composed with a CallCredentials object, the
524         // underlying grpc channel will always be separate and not
525         // persisted
526         $this->channel1 = new Grpc\Channel('localhost:50027',
527                                            ["credentials" => $creds1,
528                                             "grpc_target_persist_bound" => 3,
529                                             ]);
530         $this->channel2 = new Grpc\Channel('localhost:50027',
531                                            ["credentials" => $creds2]);
532         $this->channel3 = new Grpc\Channel('localhost:50027',
533                                            ["credentials" => $creds3]);
534
535         // try to connect on channel1
536         $state = $this->channel1->getConnectivityState(true);
537         $this->waitUntilNotIdle($this->channel1);
538
539         $state = $this->channel1->getConnectivityState();
540         $this->assertConnecting($state);
541         $state = $this->channel2->getConnectivityState();
542         $this->assertEquals(GRPC\CHANNEL_IDLE, $state);
543         $state = $this->channel3->getConnectivityState();
544         $this->assertEquals(GRPC\CHANNEL_IDLE, $state);
545
546         $this->channel1->close();
547         $this->channel2->close();
548         $this->channel3->close();
549     }
550
551     public function testPersistentChannelForceNew()
552     {
553         $this->channel1 = new Grpc\Channel('localhost:50028', [
554             "grpc_target_persist_bound" => 2,
555         ]);
556         // even though all the channel params are the same, channel2
557         // has a new and different underlying channel
558         $this->channel2 = new Grpc\Channel('localhost:50028',
559                                            ["force_new" => true]);
560
561         // try to connect on channel1
562         $state = $this->channel1->getConnectivityState(true);
563         $this->waitUntilNotIdle($this->channel1);
564
565         $state = $this->channel1->getConnectivityState();
566         $this->assertConnecting($state);
567         $state = $this->channel2->getConnectivityState();
568         $this->assertEquals(GRPC\CHANNEL_IDLE, $state);
569
570         $this->channel1->close();
571         $this->channel2->close();
572     }
573
574     public function testPersistentChannelForceNewOldChannelIdle1()
575     {
576
577         $this->channel1 = new Grpc\Channel('localhost:50029', [
578             "grpc_target_persist_bound" => 2,
579         ]);
580         $this->channel2 = new Grpc\Channel('localhost:50029',
581                                            ["force_new" => true]);
582         // channel3 shares with channel1
583         $this->channel3 = new Grpc\Channel('localhost:50029', []);
584
585         // try to connect on channel2
586         $state = $this->channel2->getConnectivityState(true);
587         $this->waitUntilNotIdle($this->channel2);
588         $state = $this->channel1->getConnectivityState();
589         $this->assertEquals(GRPC\CHANNEL_IDLE, $state);
590         $state = $this->channel2->getConnectivityState();
591         $this->assertConnecting($state);
592         $state = $this->channel3->getConnectivityState();
593         $this->assertEquals(GRPC\CHANNEL_IDLE, $state);
594
595         $this->channel1->close();
596         $this->channel2->close();
597     }
598
599     public function testPersistentChannelForceNewOldChannelIdle2()
600     {
601
602         $this->channel1 = new Grpc\Channel('localhost:50032', [
603             "grpc_target_persist_bound" => 2,
604         ]);
605         $this->channel2 = new Grpc\Channel('localhost:50032', []);
606
607         // try to connect on channel2
608         $state = $this->channel1->getConnectivityState(true);
609         $this->waitUntilNotIdle($this->channel2);
610         $state = $this->channel1->getConnectivityState();
611         $this->assertConnecting($state);
612         $state = $this->channel2->getConnectivityState();
613         $this->assertConnecting($state);
614
615         $this->channel1->close();
616         $this->channel2->close();
617     }
618
619     public function testPersistentChannelForceNewOldChannelClose1()
620     {
621
622         $this->channel1 = new Grpc\Channel('localhost:50130', [
623             "grpc_target_persist_bound" => 2,
624         ]);
625         $this->channel2 = new Grpc\Channel('localhost:50130',
626                                            ["force_new" => true]);
627         // channel3 shares with channel1
628         $this->channel3 = new Grpc\Channel('localhost:50130', []);
629
630         $this->channel1->close();
631
632         $state = $this->channel2->getConnectivityState();
633         $this->assertEquals(GRPC\CHANNEL_IDLE, $state);
634
635         // channel3 is still usable. We need to exclude the possibility that in
636         // testPersistentChannelForceNewOldChannelClose2, the exception is thrown
637         // by channel1 and channel2.
638         $state = $this->channel3->getConnectivityState();
639         $this->assertEquals(GRPC\CHANNEL_IDLE, $state);
640     }
641
642     /**
643      * @expectedException RuntimeException
644      */
645     public function testPersistentChannelForceNewOldChannelClose2()
646     {
647
648         $this->channel1 = new Grpc\Channel('localhost:50230', [
649             "grpc_target_persist_bound" => 2,
650         ]);
651         $this->channel2 = new Grpc\Channel('localhost:50230',
652           ["force_new" => true]);
653         // channel3 shares with channel1
654         $this->channel3 = new Grpc\Channel('localhost:50230', []);
655
656         $this->channel1->close();
657
658         $state = $this->channel2->getConnectivityState();
659         $this->assertEquals(GRPC\CHANNEL_IDLE, $state);
660
661         // channel3 is still usable
662         $state = $this->channel3->getConnectivityState();
663         $this->assertEquals(GRPC\CHANNEL_IDLE, $state);
664
665         // channel 1 is closed
666         $this->channel1->getConnectivityState();
667     }
668
669     public function testPersistentChannelForceNewNewChannelClose()
670     {
671
672         $this->channel1 = new Grpc\Channel('localhost:50031', [
673             "grpc_target_persist_bound" => 2,
674         ]);
675         $this->channel2 = new Grpc\Channel('localhost:50031',
676                                            ["force_new" => true]);
677         $this->channel3 = new Grpc\Channel('localhost:50031', []);
678
679         $this->channel2->close();
680
681         $state = $this->channel1->getConnectivityState();
682         $this->assertEquals(GRPC\CHANNEL_IDLE, $state);
683
684         // can still connect on channel1
685         $state = $this->channel1->getConnectivityState(true);
686         $this->waitUntilNotIdle($this->channel1);
687
688         $state = $this->channel1->getConnectivityState();
689         $this->assertConnecting($state);
690
691         $this->channel1->close();
692     }
693 }