• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2010 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package android.util.cts;
18 
19 import static org.junit.Assert.assertArrayEquals;
20 import static org.junit.Assert.assertEquals;
21 import static org.junit.Assert.fail;
22 
23 import android.util.Base64;
24 import android.util.Base64InputStream;
25 import android.util.Base64OutputStream;
26 
27 import androidx.test.filters.LargeTest;
28 import androidx.test.runner.AndroidJUnit4;
29 
30 import org.junit.Test;
31 import org.junit.runner.RunWith;
32 
33 import java.io.ByteArrayInputStream;
34 import java.io.ByteArrayOutputStream;
35 import java.io.IOException;
36 import java.io.InputStream;
37 import java.io.OutputStream;
38 import java.util.Arrays;
39 import java.util.Collections;
40 import java.util.List;
41 import java.util.Random;
42 import java.util.stream.Collectors;
43 
44 @LargeTest
45 @RunWith(AndroidJUnit4.class)
46 public class Base64Test {
47     private static final String TAG = "Base64Test";
48 
49     /** Decodes a string, returning a string. */
decodeString(String in)50     private String decodeString(String in) throws Exception {
51         byte[] out = Base64.decode(in, 0);
52         return new String(out);
53     }
54 
55     /**
56      * Encodes the string 'in' using 'flags'.  Asserts that decoding
57      * gives the same string.  Returns the encoded string.
58      */
encodeToString(String in, int flags)59     private String encodeToString(String in, int flags) throws Exception {
60         String b64 = Base64.encodeToString(in.getBytes(), flags);
61         String dec = decodeString(b64);
62         assertEquals(in, dec);
63         return b64;
64     }
65 
66     /** Assert that decoding 'in' throws IllegalArgumentException. */
assertBad(String in)67     private void assertBad(String in) throws Exception {
68         try {
69             byte[] out = Base64.decode(in, 0);
70             fail("should have failed to decode");
71         } catch (IllegalArgumentException e) {
72         }
73     }
74 
75     /** Assert that actual equals the first len bytes of expected. */
assertPartialEquals(byte[] expected, int len, byte[] actual)76     private void assertPartialEquals(byte[] expected, int len, byte[] actual) {
77         assertEquals(len, actual.length);
78         for (int i = 0; i < len; ++i) {
79             assertEquals(expected[i], actual[i]);
80         }
81     }
82 
83     /** Assert that actual equals the first len bytes of expected. */
assertPartialEquals(byte[] expected, int len, byte[] actual, int alen)84     private void assertPartialEquals(byte[] expected, int len, byte[] actual, int alen) {
85         assertEquals(len, alen);
86         for (int i = 0; i < len; ++i) {
87             assertEquals(expected[i], actual[i]);
88         }
89     }
90 
91     @Test
testDecodeExtraChars()92     public void testDecodeExtraChars() throws Exception {
93         // padding 0
94         assertEquals("hello, world", decodeString("aGVsbG8sIHdvcmxk"));
95         assertBad("aGVsbG8sIHdvcmxk=");
96         assertBad("aGVsbG8sIHdvcmxk==");
97         assertBad("aGVsbG8sIHdvcmxk =");
98         assertBad("aGVsbG8sIHdvcmxk = = ");
99         assertEquals("hello, world", decodeString(" aGVs bG8s IHdv cmxk  "));
100         assertEquals("hello, world", decodeString(" aGV sbG8 sIHd vcmx k "));
101         assertEquals("hello, world", decodeString(" aG VsbG 8sIH dvcm xk "));
102         assertEquals("hello, world", decodeString(" a GVsb G8sI Hdvc mxk "));
103         assertEquals("hello, world", decodeString(" a G V s b G 8 s I H d v c m x k "));
104         assertEquals("hello, world", decodeString("_a*G_V*s_b*G_8*s_I*H_d*v_c*m_x*k_"));
105         assertEquals("hello, world", decodeString("aGVsbG8sIHdvcmxk"));
106 
107         // padding 1
108         assertEquals("hello, world?!", decodeString("aGVsbG8sIHdvcmxkPyE="));
109         assertEquals("hello, world?!", decodeString("aGVsbG8sIHdvcmxkPyE"));
110         assertBad("aGVsbG8sIHdvcmxkPyE==");
111         assertBad("aGVsbG8sIHdvcmxkPyE ==");
112         assertBad("aGVsbG8sIHdvcmxkPyE = = ");
113         assertEquals("hello, world?!", decodeString("aGVsbG8sIHdvcmxkPy E="));
114         assertEquals("hello, world?!", decodeString("aGVsbG8sIHdvcmxkPy E"));
115         assertEquals("hello, world?!", decodeString("aGVsbG8sIHdvcmxkPy E ="));
116         assertEquals("hello, world?!", decodeString("aGVsbG8sIHdvcmxkPy E "));
117         assertEquals("hello, world?!", decodeString("aGVsbG8sIHdvcmxkPy E = "));
118         assertEquals("hello, world?!", decodeString("aGVsbG8sIHdvcmxkPy E   "));
119 
120         // padding 2
121         assertEquals("hello, world.", decodeString("aGVsbG8sIHdvcmxkLg=="));
122         assertEquals("hello, world.", decodeString("aGVsbG8sIHdvcmxkLg"));
123         assertBad("aGVsbG8sIHdvcmxkLg=");
124         assertBad("aGVsbG8sIHdvcmxkLg =");
125         assertBad("aGVsbG8sIHdvcmxkLg = ");
126         assertEquals("hello, world.", decodeString("aGVsbG8sIHdvcmxkL g=="));
127         assertEquals("hello, world.", decodeString("aGVsbG8sIHdvcmxkL g"));
128         assertEquals("hello, world.", decodeString("aGVsbG8sIHdvcmxkL g =="));
129         assertEquals("hello, world.", decodeString("aGVsbG8sIHdvcmxkL g "));
130         assertEquals("hello, world.", decodeString("aGVsbG8sIHdvcmxkL g = = "));
131         assertEquals("hello, world.", decodeString("aGVsbG8sIHdvcmxkL g   "));
132     }
133 
134     private static final byte[] BYTES = { (byte) 0xff, (byte) 0xee, (byte) 0xdd,
135                                           (byte) 0xcc, (byte) 0xbb, (byte) 0xaa,
136                                           (byte) 0x99, (byte) 0x88, (byte) 0x77 };
137 
138     @Test
testBinaryDecode()139     public void testBinaryDecode() throws Exception {
140         assertPartialEquals(BYTES, 0, Base64.decode("", 0));
141         assertPartialEquals(BYTES, 1, Base64.decode("/w==", 0));
142         assertPartialEquals(BYTES, 2, Base64.decode("/+4=", 0));
143         assertPartialEquals(BYTES, 3, Base64.decode("/+7d", 0));
144         assertPartialEquals(BYTES, 4, Base64.decode("/+7dzA==", 0));
145         assertPartialEquals(BYTES, 5, Base64.decode("/+7dzLs=", 0));
146         assertPartialEquals(BYTES, 6, Base64.decode("/+7dzLuq", 0));
147         assertPartialEquals(BYTES, 7, Base64.decode("/+7dzLuqmQ==", 0));
148         assertPartialEquals(BYTES, 8, Base64.decode("/+7dzLuqmYg=", 0));
149     }
150 
151     @Test
testWebSafe()152     public void testWebSafe() throws Exception {
153         assertPartialEquals(BYTES, 0, Base64.decode("", Base64.URL_SAFE));
154         assertPartialEquals(BYTES, 1, Base64.decode("_w==", Base64.URL_SAFE));
155         assertPartialEquals(BYTES, 2, Base64.decode("_-4=", Base64.URL_SAFE));
156         assertPartialEquals(BYTES, 3, Base64.decode("_-7d", Base64.URL_SAFE));
157         assertPartialEquals(BYTES, 4, Base64.decode("_-7dzA==", Base64.URL_SAFE));
158         assertPartialEquals(BYTES, 5, Base64.decode("_-7dzLs=", Base64.URL_SAFE));
159         assertPartialEquals(BYTES, 6, Base64.decode("_-7dzLuq", Base64.URL_SAFE));
160         assertPartialEquals(BYTES, 7, Base64.decode("_-7dzLuqmQ==", Base64.URL_SAFE));
161         assertPartialEquals(BYTES, 8, Base64.decode("_-7dzLuqmYg=", Base64.URL_SAFE));
162 
163         assertEquals("", Base64.encodeToString(BYTES, 0, 0, Base64.URL_SAFE));
164         assertEquals("_w==\n", Base64.encodeToString(BYTES, 0, 1, Base64.URL_SAFE));
165         assertEquals("_-4=\n", Base64.encodeToString(BYTES, 0, 2, Base64.URL_SAFE));
166         assertEquals("_-7d\n", Base64.encodeToString(BYTES, 0, 3, Base64.URL_SAFE));
167         assertEquals("_-7dzA==\n", Base64.encodeToString(BYTES, 0, 4, Base64.URL_SAFE));
168         assertEquals("_-7dzLs=\n", Base64.encodeToString(BYTES, 0, 5, Base64.URL_SAFE));
169         assertEquals("_-7dzLuq\n", Base64.encodeToString(BYTES, 0, 6, Base64.URL_SAFE));
170         assertEquals("_-7dzLuqmQ==\n", Base64.encodeToString(BYTES, 0, 7, Base64.URL_SAFE));
171         assertEquals("_-7dzLuqmYg=\n", Base64.encodeToString(BYTES, 0, 8, Base64.URL_SAFE));
172     }
173 
174     @Test
testFlags()175     public void testFlags() throws Exception {
176         assertEquals("YQ==\n",       encodeToString("a", 0));
177         assertEquals("YQ==",         encodeToString("a", Base64.NO_WRAP));
178         assertEquals("YQ\n",         encodeToString("a", Base64.NO_PADDING));
179         assertEquals("YQ",           encodeToString("a", Base64.NO_PADDING | Base64.NO_WRAP));
180         assertEquals("YQ==\r\n",     encodeToString("a", Base64.CRLF));
181         assertEquals("YQ\r\n",       encodeToString("a", Base64.CRLF | Base64.NO_PADDING));
182 
183         assertEquals("YWI=\n",       encodeToString("ab", 0));
184         assertEquals("YWI=",         encodeToString("ab", Base64.NO_WRAP));
185         assertEquals("YWI\n",        encodeToString("ab", Base64.NO_PADDING));
186         assertEquals("YWI",          encodeToString("ab", Base64.NO_PADDING | Base64.NO_WRAP));
187         assertEquals("YWI=\r\n",     encodeToString("ab", Base64.CRLF));
188         assertEquals("YWI\r\n",      encodeToString("ab", Base64.CRLF | Base64.NO_PADDING));
189 
190         assertEquals("YWJj\n",       encodeToString("abc", 0));
191         assertEquals("YWJj",         encodeToString("abc", Base64.NO_WRAP));
192         assertEquals("YWJj\n",       encodeToString("abc", Base64.NO_PADDING));
193         assertEquals("YWJj",         encodeToString("abc", Base64.NO_PADDING | Base64.NO_WRAP));
194         assertEquals("YWJj\r\n",     encodeToString("abc", Base64.CRLF));
195         assertEquals("YWJj\r\n",     encodeToString("abc", Base64.CRLF | Base64.NO_PADDING));
196 
197         assertEquals("YWJjZA==\n",   encodeToString("abcd", 0));
198         assertEquals("YWJjZA==",     encodeToString("abcd", Base64.NO_WRAP));
199         assertEquals("YWJjZA\n",     encodeToString("abcd", Base64.NO_PADDING));
200         assertEquals("YWJjZA",       encodeToString("abcd", Base64.NO_PADDING | Base64.NO_WRAP));
201         assertEquals("YWJjZA==\r\n", encodeToString("abcd", Base64.CRLF));
202         assertEquals("YWJjZA\r\n",   encodeToString("abcd", Base64.CRLF | Base64.NO_PADDING));
203     }
204 
205     @Test
testLineLength()206     public void testLineLength() throws Exception {
207         String in_56 = "abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcd";
208         String in_57 = in_56 + "e";
209         String in_58 = in_56 + "ef";
210         String in_59 = in_56 + "efg";
211         String in_60 = in_56 + "efgh";
212         String in_61 = in_56 + "efghi";
213 
214         String prefix = "YWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXphYmNkZWZnaGlqa2xtbm9wcXJzdHV2d3h5emFi";
215         String out_56 = prefix + "Y2Q=\n";
216         String out_57 = prefix + "Y2Rl\n";
217         String out_58 = prefix + "Y2Rl\nZg==\n";
218         String out_59 = prefix + "Y2Rl\nZmc=\n";
219         String out_60 = prefix + "Y2Rl\nZmdo\n";
220         String out_61 = prefix + "Y2Rl\nZmdoaQ==\n";
221 
222         // no newline for an empty input array.
223         assertEquals("", encodeToString("", 0));
224 
225         assertEquals(out_56, encodeToString(in_56, 0));
226         assertEquals(out_57, encodeToString(in_57, 0));
227         assertEquals(out_58, encodeToString(in_58, 0));
228         assertEquals(out_59, encodeToString(in_59, 0));
229         assertEquals(out_60, encodeToString(in_60, 0));
230         assertEquals(out_61, encodeToString(in_61, 0));
231 
232         assertEquals(out_56.replaceAll("=", ""), encodeToString(in_56, Base64.NO_PADDING));
233         assertEquals(out_57.replaceAll("=", ""), encodeToString(in_57, Base64.NO_PADDING));
234         assertEquals(out_58.replaceAll("=", ""), encodeToString(in_58, Base64.NO_PADDING));
235         assertEquals(out_59.replaceAll("=", ""), encodeToString(in_59, Base64.NO_PADDING));
236         assertEquals(out_60.replaceAll("=", ""), encodeToString(in_60, Base64.NO_PADDING));
237         assertEquals(out_61.replaceAll("=", ""), encodeToString(in_61, Base64.NO_PADDING));
238 
239         assertEquals(out_56.replaceAll("\n", ""), encodeToString(in_56, Base64.NO_WRAP));
240         assertEquals(out_57.replaceAll("\n", ""), encodeToString(in_57, Base64.NO_WRAP));
241         assertEquals(out_58.replaceAll("\n", ""), encodeToString(in_58, Base64.NO_WRAP));
242         assertEquals(out_59.replaceAll("\n", ""), encodeToString(in_59, Base64.NO_WRAP));
243         assertEquals(out_60.replaceAll("\n", ""), encodeToString(in_60, Base64.NO_WRAP));
244         assertEquals(out_61.replaceAll("\n", ""), encodeToString(in_61, Base64.NO_WRAP));
245     }
246 
247     private static final String lipsum =
248             "Lorem ipsum dolor sit amet, consectetur adipiscing elit. " +
249             "Quisque congue eleifend odio, eu ornare nulla facilisis eget. " +
250             "Integer eget elit diam, sit amet laoreet nibh. Quisque enim " +
251             "urna, pharetra vitae consequat eget, adipiscing eu ante. " +
252             "Aliquam venenatis arcu nec nibh imperdiet tempor. In id dui " +
253             "eget lorem aliquam rutrum vel vitae eros. In placerat ornare " +
254             "pretium. Curabitur non fringilla mi. Fusce ultricies, turpis " +
255             "eu ultrices suscipit, ligula nisi consectetur eros, dapibus " +
256             "aliquet dui sapien a turpis. Donec ultricies varius ligula, " +
257             "ut hendrerit arcu malesuada at. Praesent sed elit pretium " +
258             "eros luctus gravida. In ac dolor lorem. Cras condimentum " +
259             "convallis elementum. Phasellus vel felis in nulla ultrices " +
260             "venenatis. Nam non tortor non orci convallis convallis. " +
261             "Nam tristique lacinia hendrerit. Pellentesque habitant morbi " +
262             "tristique senectus et netus et malesuada fames ac turpis " +
263             "egestas. Vivamus cursus, nibh eu imperdiet porta, magna " +
264             "ipsum mollis mauris, sit amet fringilla mi nisl eu mi. " +
265             "Phasellus posuere, leo at ultricies vehicula, massa risus " +
266             "volutpat sapien, eu tincidunt diam ipsum eget nulla. Cras " +
267             "molestie dapibus commodo. Ut vel tellus at massa gravida " +
268             "semper non sed orci.";
269 
270     @Test
testInputStream()271     public void testInputStream() throws Exception {
272         int[] flagses = { Base64.DEFAULT,
273                           Base64.NO_PADDING,
274                           Base64.NO_WRAP,
275                           Base64.NO_PADDING | Base64.NO_WRAP,
276                           Base64.CRLF,
277                           Base64.URL_SAFE };
278         int[] writeLengths = { -10, -5, -1, 0, 1, 1, 2, 2, 3, 10, 100 };
279         Random rng = new Random(32176L);
280 
281         // Test input needs to be at least 2048 bytes to fill up the
282         // read buffer of Base64InputStream.
283         byte[] plain = (lipsum + lipsum + lipsum + lipsum + lipsum).getBytes();
284 
285         for (int flags: flagses) {
286             byte[] encoded = Base64.encode(plain, flags);
287 
288             ByteArrayInputStream bais;
289             Base64InputStream b64is;
290             byte[] actual = new byte[plain.length * 2];
291             int ap;
292             int b;
293 
294             // ----- test decoding ("encoded" -> "plain") -----
295 
296             // read as much as it will give us in one chunk
297             bais = new ByteArrayInputStream(encoded);
298             b64is = new Base64InputStream(bais, flags);
299             ap = 0;
300             while ((b = b64is.read(actual, ap, actual.length-ap)) != -1) {
301                 ap += b;
302             }
303             assertPartialEquals(actual, ap, plain);
304 
305             // read individual bytes
306             bais = new ByteArrayInputStream(encoded);
307             b64is = new Base64InputStream(bais, flags);
308             ap = 0;
309             while ((b = b64is.read()) != -1) {
310                 actual[ap++] = (byte) b;
311             }
312             assertPartialEquals(actual, ap, plain);
313 
314             // mix reads of variously-sized arrays with one-byte reads
315             bais = new ByteArrayInputStream(encoded);
316             b64is = new Base64InputStream(bais, flags);
317             ap = 0;
318             readloop: while (true) {
319                 int l = writeLengths[rng.nextInt(writeLengths.length)];
320                 if (l >= 0) {
321                     b = b64is.read(actual, ap, l);
322                     if (b == -1) break readloop;
323                     ap += b;
324                 } else {
325                     for (int i = 0; i < -l; ++i) {
326                         if ((b = b64is.read()) == -1) break readloop;
327                         actual[ap++] = (byte) b;
328                     }
329                 }
330             }
331             assertPartialEquals(actual, ap, plain);
332         }
333     }
334 
335     /** http://b/3026478 */
336     @Test
testSingleByteReads()337     public void testSingleByteReads() throws IOException {
338         InputStream in = new Base64InputStream(
339                 new ByteArrayInputStream("/v8=".getBytes()), Base64.DEFAULT);
340         assertEquals(254, in.read());
341         assertEquals(255, in.read());
342     }
343 
344     /**
345      * Tests that Base64OutputStream produces exactly the same results
346      * as calling Base64.encode/.decode on an in-memory array.
347      */
348     @Test
testOutputStream()349     public void testOutputStream() throws Exception {
350         int[] flagses = { Base64.DEFAULT,
351                           Base64.NO_PADDING,
352                           Base64.NO_WRAP,
353                           Base64.NO_PADDING | Base64.NO_WRAP,
354                           Base64.CRLF,
355                           Base64.URL_SAFE };
356         int[] writeLengths = { -10, -5, -1, 0, 1, 1, 2, 2, 3, 10, 100 };
357         Random rng = new Random(32176L);
358 
359         // Test input needs to be at least 1024 bytes to test filling
360         // up the write(int) buffer of Base64OutputStream.
361         byte[] plain = (lipsum + lipsum).getBytes();
362 
363         for (int flags: flagses) {
364             byte[] encoded = Base64.encode(plain, flags);
365 
366             ByteArrayOutputStream baos;
367             Base64OutputStream b64os;
368             byte[] actual;
369             int p;
370 
371             // ----- test encoding ("plain" -> "encoded") -----
372 
373             // one large write(byte[]) of the whole input
374             baos = new ByteArrayOutputStream();
375             b64os = new Base64OutputStream(baos, flags);
376             b64os.write(plain);
377             b64os.close();
378             actual = baos.toByteArray();
379             assertArrayEquals(encoded, actual);
380 
381             // many calls to write(int)
382             baos = new ByteArrayOutputStream();
383             b64os = new Base64OutputStream(baos, flags);
384             for (int i = 0; i < plain.length; ++i) {
385                 b64os.write(plain[i]);
386             }
387             b64os.close();
388             actual = baos.toByteArray();
389             assertArrayEquals(encoded, actual);
390 
391             // intermixed sequences of write(int) with
392             // write(byte[],int,int) of various lengths.
393             baos = new ByteArrayOutputStream();
394             b64os = new Base64OutputStream(baos, flags);
395             p = 0;
396             while (p < plain.length) {
397                 int l = writeLengths[rng.nextInt(writeLengths.length)];
398                 l = Math.min(l, plain.length-p);
399                 if (l >= 0) {
400                     b64os.write(plain, p, l);
401                     p += l;
402                 } else {
403                     l = Math.min(-l, plain.length-p);
404                     for (int i = 0; i < l; ++i) {
405                         b64os.write(plain[p+i]);
406                     }
407                     p += l;
408                 }
409             }
410             b64os.close();
411             actual = baos.toByteArray();
412             assertArrayEquals(encoded, actual);
413         }
414     }
415 
416     @Test
testOutputStream_ioExceptionDuringClose()417     public void testOutputStream_ioExceptionDuringClose() {
418         OutputStream out = new OutputStream() {
419             @Override public void write(int b) throws IOException { }
420             @Override public void close() throws IOException {
421                 throw new IOException("close()");
422             }
423         };
424         OutputStream out2 = new Base64OutputStream(out, Base64.DEFAULT);
425         try {
426             out2.close();
427             fail();
428         } catch (IOException expected) {
429         }
430     }
431 
432     @Test
testOutputStream_ioExceptionDuringCloseAndWrite()433     public void testOutputStream_ioExceptionDuringCloseAndWrite() {
434         OutputStream out = new OutputStream() {
435             @Override public void write(int b) throws IOException {
436                 throw new IOException("write()");
437             }
438             @Override public void write(byte[] b) throws IOException {
439                 throw new IOException("write()");
440             }
441             @Override public void write(byte[] b, int off, int len) throws IOException {
442                 throw new IOException("write()");
443             }
444             @Override public void close() throws IOException {
445                 throw new IOException("close()");
446             }
447         };
448         OutputStream out2 = new Base64OutputStream(out, Base64.DEFAULT);
449         try {
450             out2.close();
451             fail();
452         } catch (IOException expected) {
453             // Base64OutputStream write()s pending (possibly empty) data
454             // before close(), so the IOE from write() should be thrown and
455             // any later exception suppressed.
456             assertEquals("write()", expected.getMessage());
457             Throwable[] suppressed = expected.getSuppressed();
458             List<String> suppressedMessages = Arrays.asList(suppressed).stream()
459                     .map((e) -> e.getMessage())
460                     .collect(Collectors.toList());
461             assertEquals(Collections.singletonList("close()"), suppressedMessages);
462         }
463     }
464 
465     @Test
testOutputStream_ioExceptionDuringWrite()466     public void testOutputStream_ioExceptionDuringWrite() {
467         OutputStream out = new OutputStream() {
468             @Override public void write(int b) throws IOException {
469                 throw new IOException("write()");
470             }
471             @Override public void write(byte[] b) throws IOException {
472                 throw new IOException("write()");
473             }
474             @Override public void write(byte[] b, int off, int len) throws IOException {
475                 throw new IOException("write()");
476             }
477         };
478         // Base64OutputStream write()s pending (possibly empty) data
479         // before close(), so the IOE from write() should be thrown.
480         OutputStream out2 = new Base64OutputStream(out, Base64.DEFAULT);
481         try {
482             out2.close();
483             fail();
484         } catch (IOException expected) {
485         }
486     }
487 
488 }
489