2023-05-11 15:50:40 +02:00
|
|
|
/* t-iobuf.c - Simple module test for iobuf.c
|
|
|
|
* Copyright (C) 2015 g10 Code GmbH
|
|
|
|
*
|
|
|
|
* This file is part of GnuPG.
|
|
|
|
*
|
|
|
|
* This file is free software; you can redistribute it and/or modify
|
|
|
|
* it under the terms of either
|
|
|
|
*
|
|
|
|
* - the GNU Lesser General Public License as published by the Free
|
|
|
|
* Software Foundation; either version 3 of the License, or (at
|
|
|
|
* your option) any later version.
|
|
|
|
*
|
|
|
|
* or
|
|
|
|
*
|
|
|
|
* - the GNU General Public License as published by the Free
|
|
|
|
* Software Foundation; either version 2 of the License, or (at
|
|
|
|
* your option) any later version.
|
|
|
|
*
|
|
|
|
* or both in parallel, as here.
|
|
|
|
*
|
|
|
|
* This file is distributed in the hope that it will be useful,
|
|
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
* GNU General Public License for more details.
|
|
|
|
*
|
|
|
|
* You should have received a copy of the GNU General Public License
|
|
|
|
* along with this program; if not, see <https://www.gnu.org/licenses/>.
|
|
|
|
* SPDX-License-Identifier: (LGPL-3.0-or-later OR GPL-2.0-or-later)
|
|
|
|
*/
|
|
|
|
|
|
|
|
/* The whole code here does not very fill into our general test frame
|
2024-05-13 00:09:23 +02:00
|
|
|
* work pattern. But let's keep it as it is. */
|
2023-05-11 15:50:40 +02:00
|
|
|
|
2015-08-12 02:19:05 +02:00
|
|
|
#include <config.h>
|
|
|
|
#include <stdio.h>
|
|
|
|
#include <string.h>
|
|
|
|
#include <assert.h>
|
|
|
|
#include <stdlib.h>
|
|
|
|
|
|
|
|
#include "iobuf.h"
|
common: Fix iobuf API of filter function for alignment.
* common/iobuf.h (IOBUFCTRL_DESC): Change the call semantics.
* common/iobuf.c (iobuf_desc): Add the second argument DESC.
(print_chain, iobuf_close, do_open, iobuf_sockopen, iobuf_ioctl)
(iobuf_push_filter2, pop_filter, iobuf_write_temp): Change calls
of iobuf_desc.
(file_filter, file_es_filter, sock_filter, block_filter): Fill the
description.
* common/t-iobuf.c (every_other_filter, double_filter): Likewise.
* g10/armor.c, g10/cipher.c, g10/compress-bz2.c, g10/compress.c,
g10/decrypt-data.c, g10/encrypt.c, g10/mdfilter.c, g10/progress.c,
g10/textfilter.c: Likewise.
--
Newer GCC warns against possible alignment difference of pointers.
This change can silence those warnings.
Signed-off-by: NIIBE Yutaka <gniibe@fsij.org>
2016-01-12 02:32:20 +01:00
|
|
|
#include "stringhelp.h"
|
2015-08-12 02:19:05 +02:00
|
|
|
|
2023-05-11 15:50:40 +02:00
|
|
|
|
|
|
|
static void *
|
|
|
|
xmalloc (size_t n)
|
|
|
|
{
|
|
|
|
void *p = malloc (n);
|
|
|
|
if (!p)
|
|
|
|
{
|
|
|
|
fprintf (stderr, "t-iobuf: out of core\n");
|
|
|
|
abort ();
|
|
|
|
}
|
|
|
|
return p;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2015-08-17 11:56:42 +02:00
|
|
|
/* Return every other byte. In particular, reads two bytes, returns
|
|
|
|
the second one. */
|
2015-08-12 02:19:05 +02:00
|
|
|
static int
|
|
|
|
every_other_filter (void *opaque, int control,
|
|
|
|
iobuf_t chain, byte *buf, size_t *len)
|
|
|
|
{
|
|
|
|
(void) opaque;
|
|
|
|
|
|
|
|
if (control == IOBUFCTRL_DESC)
|
|
|
|
{
|
common: Fix iobuf API of filter function for alignment.
* common/iobuf.h (IOBUFCTRL_DESC): Change the call semantics.
* common/iobuf.c (iobuf_desc): Add the second argument DESC.
(print_chain, iobuf_close, do_open, iobuf_sockopen, iobuf_ioctl)
(iobuf_push_filter2, pop_filter, iobuf_write_temp): Change calls
of iobuf_desc.
(file_filter, file_es_filter, sock_filter, block_filter): Fill the
description.
* common/t-iobuf.c (every_other_filter, double_filter): Likewise.
* g10/armor.c, g10/cipher.c, g10/compress-bz2.c, g10/compress.c,
g10/decrypt-data.c, g10/encrypt.c, g10/mdfilter.c, g10/progress.c,
g10/textfilter.c: Likewise.
--
Newer GCC warns against possible alignment difference of pointers.
This change can silence those warnings.
Signed-off-by: NIIBE Yutaka <gniibe@fsij.org>
2016-01-12 02:32:20 +01:00
|
|
|
mem2str (buf, "every_other_filter", *len);
|
2015-08-12 02:19:05 +02:00
|
|
|
}
|
|
|
|
if (control == IOBUFCTRL_UNDERFLOW)
|
|
|
|
{
|
|
|
|
int c = iobuf_readbyte (chain);
|
|
|
|
int c2;
|
|
|
|
if (c == -1)
|
|
|
|
c2 = -1;
|
|
|
|
else
|
|
|
|
c2 = iobuf_readbyte (chain);
|
|
|
|
|
2015-08-20 17:42:55 +02:00
|
|
|
/* printf ("Discarding %d (%c); return %d (%c)\n", c, c, c2, c2); */
|
2015-08-12 02:19:05 +02:00
|
|
|
|
|
|
|
if (c2 == -1)
|
|
|
|
{
|
|
|
|
*len = 0;
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
*buf = c2;
|
|
|
|
*len = 1;
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2015-08-17 11:56:42 +02:00
|
|
|
static int
|
|
|
|
double_filter (void *opaque, int control,
|
|
|
|
iobuf_t chain, byte *buf, size_t *len)
|
|
|
|
{
|
|
|
|
(void) opaque;
|
|
|
|
|
|
|
|
if (control == IOBUFCTRL_DESC)
|
|
|
|
{
|
common: Fix iobuf API of filter function for alignment.
* common/iobuf.h (IOBUFCTRL_DESC): Change the call semantics.
* common/iobuf.c (iobuf_desc): Add the second argument DESC.
(print_chain, iobuf_close, do_open, iobuf_sockopen, iobuf_ioctl)
(iobuf_push_filter2, pop_filter, iobuf_write_temp): Change calls
of iobuf_desc.
(file_filter, file_es_filter, sock_filter, block_filter): Fill the
description.
* common/t-iobuf.c (every_other_filter, double_filter): Likewise.
* g10/armor.c, g10/cipher.c, g10/compress-bz2.c, g10/compress.c,
g10/decrypt-data.c, g10/encrypt.c, g10/mdfilter.c, g10/progress.c,
g10/textfilter.c: Likewise.
--
Newer GCC warns against possible alignment difference of pointers.
This change can silence those warnings.
Signed-off-by: NIIBE Yutaka <gniibe@fsij.org>
2016-01-12 02:32:20 +01:00
|
|
|
mem2str (buf, "double_filter", *len);
|
2015-08-17 11:56:42 +02:00
|
|
|
}
|
|
|
|
if (control == IOBUFCTRL_FLUSH)
|
|
|
|
{
|
|
|
|
int i;
|
|
|
|
|
|
|
|
for (i = 0; i < *len; i ++)
|
|
|
|
{
|
|
|
|
int rc;
|
|
|
|
|
|
|
|
rc = iobuf_writebyte (chain, buf[i]);
|
|
|
|
if (rc)
|
|
|
|
return rc;
|
|
|
|
rc = iobuf_writebyte (chain, buf[i]);
|
|
|
|
if (rc)
|
|
|
|
return rc;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2015-08-12 11:44:59 +02:00
|
|
|
struct content_filter_state
|
|
|
|
{
|
|
|
|
int pos;
|
|
|
|
int len;
|
|
|
|
const char *buffer;
|
|
|
|
};
|
|
|
|
|
|
|
|
static struct content_filter_state *
|
|
|
|
content_filter_new (const char *buffer)
|
|
|
|
{
|
|
|
|
struct content_filter_state *state
|
2023-05-11 15:50:40 +02:00
|
|
|
= xmalloc (sizeof (struct content_filter_state));
|
2015-08-12 11:44:59 +02:00
|
|
|
|
|
|
|
state->pos = 0;
|
|
|
|
state->len = strlen (buffer);
|
|
|
|
state->buffer = buffer;
|
|
|
|
|
|
|
|
return state;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int
|
|
|
|
content_filter (void *opaque, int control,
|
|
|
|
iobuf_t chain, byte *buf, size_t *len)
|
|
|
|
{
|
|
|
|
struct content_filter_state *state = opaque;
|
|
|
|
|
|
|
|
(void) chain;
|
|
|
|
|
|
|
|
if (control == IOBUFCTRL_UNDERFLOW)
|
|
|
|
{
|
|
|
|
int remaining = state->len - state->pos;
|
|
|
|
int toread = *len;
|
|
|
|
assert (toread > 0);
|
|
|
|
|
|
|
|
if (toread > remaining)
|
|
|
|
toread = remaining;
|
|
|
|
|
|
|
|
memcpy (buf, &state->buffer[state->pos], toread);
|
|
|
|
|
|
|
|
state->pos += toread;
|
|
|
|
|
|
|
|
*len = toread;
|
|
|
|
|
2015-09-01 22:13:45 +02:00
|
|
|
if (toread == 0)
|
|
|
|
return -1;
|
2015-08-12 11:44:59 +02:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2015-08-12 02:19:05 +02:00
|
|
|
int
|
|
|
|
main (int argc, char *argv[])
|
|
|
|
{
|
|
|
|
(void) argc;
|
|
|
|
(void) argv;
|
|
|
|
|
|
|
|
/* A simple test to make sure filters work. We use a static buffer
|
|
|
|
and then add a filter in front of it that returns every other
|
|
|
|
character. */
|
|
|
|
{
|
|
|
|
char *content = "0123456789abcdefghijklm";
|
|
|
|
iobuf_t iobuf;
|
|
|
|
int c;
|
|
|
|
int n;
|
|
|
|
int rc;
|
|
|
|
|
|
|
|
iobuf = iobuf_temp_with_content (content, strlen (content));
|
|
|
|
rc = iobuf_push_filter (iobuf, every_other_filter, NULL);
|
|
|
|
assert (rc == 0);
|
|
|
|
|
|
|
|
n = 0;
|
|
|
|
while ((c = iobuf_readbyte (iobuf)) != -1)
|
|
|
|
{
|
2015-08-20 17:42:55 +02:00
|
|
|
/* printf ("%d: %c\n", n + 1, (char) c); */
|
2015-08-12 02:19:05 +02:00
|
|
|
assert (content[2 * n + 1] == c);
|
|
|
|
n ++;
|
|
|
|
}
|
2015-08-20 17:42:55 +02:00
|
|
|
/* printf ("Got EOF after reading %d bytes (content: %d)\n", */
|
|
|
|
/* n, strlen (content)); */
|
2015-08-12 02:19:05 +02:00
|
|
|
assert (n == strlen (content) / 2);
|
|
|
|
|
|
|
|
iobuf_close (iobuf);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* A simple test to check buffering. Make sure that when we add a
|
|
|
|
filter to a pipeline, any buffered data gets processed by the */
|
|
|
|
{
|
|
|
|
char *content = "0123456789abcdefghijklm";
|
|
|
|
iobuf_t iobuf;
|
|
|
|
int c;
|
|
|
|
int n;
|
|
|
|
int rc;
|
|
|
|
int i;
|
|
|
|
|
|
|
|
iobuf = iobuf_temp_with_content (content, strlen (content));
|
|
|
|
|
|
|
|
n = 0;
|
|
|
|
for (i = 0; i < 10; i ++)
|
|
|
|
{
|
|
|
|
c = iobuf_readbyte (iobuf);
|
|
|
|
assert (content[i] == c);
|
|
|
|
n ++;
|
|
|
|
}
|
|
|
|
|
|
|
|
rc = iobuf_push_filter (iobuf, every_other_filter, NULL);
|
|
|
|
assert (rc == 0);
|
|
|
|
|
|
|
|
while ((c = iobuf_readbyte (iobuf)) != -1)
|
|
|
|
{
|
2015-08-20 17:42:55 +02:00
|
|
|
/* printf ("%d: %c\n", n + 1, (char) c); */
|
2015-08-12 02:19:05 +02:00
|
|
|
assert (content[2 * (n - 5) + 1] == c);
|
|
|
|
n ++;
|
|
|
|
}
|
|
|
|
assert (n == 10 + (strlen (content) - 10) / 2);
|
2016-06-28 17:59:17 +02:00
|
|
|
|
|
|
|
iobuf_close (iobuf);
|
2015-08-12 02:19:05 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* A simple test to check that iobuf_read_line works. */
|
|
|
|
{
|
|
|
|
/* - 3 characters plus new line
|
|
|
|
- 4 characters plus new line
|
|
|
|
- 5 characters plus new line
|
|
|
|
- 5 characters, no new line
|
|
|
|
*/
|
|
|
|
char *content = "abc\ndefg\nhijkl\nmnopq";
|
|
|
|
iobuf_t iobuf;
|
|
|
|
byte *buffer;
|
|
|
|
unsigned size;
|
|
|
|
unsigned max_len;
|
|
|
|
int n;
|
|
|
|
|
|
|
|
iobuf = iobuf_temp_with_content (content, strlen(content));
|
|
|
|
|
|
|
|
/* We read a line with 3 characters plus a newline. If we
|
|
|
|
allocate a buffer that is 5 bytes long, then no reallocation
|
|
|
|
should be required. */
|
|
|
|
size = 5;
|
2023-05-11 15:50:40 +02:00
|
|
|
buffer = xmalloc (size);
|
2015-08-12 02:19:05 +02:00
|
|
|
max_len = 100;
|
|
|
|
n = iobuf_read_line (iobuf, &buffer, &size, &max_len);
|
|
|
|
assert (n == 4);
|
|
|
|
assert (strcmp (buffer, "abc\n") == 0);
|
|
|
|
assert (size == 5);
|
|
|
|
assert (max_len == 100);
|
|
|
|
free (buffer);
|
|
|
|
|
|
|
|
/* We now read a line with 4 characters plus a newline. This
|
|
|
|
requires 6 bytes of storage. We pass a buffer that is 5 bytes
|
|
|
|
large and we allow the buffer to be grown. */
|
|
|
|
size = 5;
|
2023-05-11 15:50:40 +02:00
|
|
|
buffer = xmalloc (size);
|
2015-08-12 02:19:05 +02:00
|
|
|
max_len = 100;
|
|
|
|
n = iobuf_read_line (iobuf, &buffer, &size, &max_len);
|
|
|
|
assert (n == 5);
|
|
|
|
assert (strcmp (buffer, "defg\n") == 0);
|
|
|
|
assert (size >= 6);
|
|
|
|
/* The string shouldn't have been truncated (max_len == 0). */
|
|
|
|
assert (max_len == 100);
|
|
|
|
free (buffer);
|
|
|
|
|
|
|
|
/* We now read a line with 5 characters plus a newline. This
|
|
|
|
requires 7 bytes of storage. We pass a buffer that is 5 bytes
|
|
|
|
large and we don't allow the buffer to be grown. */
|
|
|
|
size = 5;
|
2023-05-11 15:50:40 +02:00
|
|
|
buffer = xmalloc (size);
|
2015-08-12 02:19:05 +02:00
|
|
|
max_len = 5;
|
|
|
|
n = iobuf_read_line (iobuf, &buffer, &size, &max_len);
|
|
|
|
assert (n == 4);
|
|
|
|
/* Note: the string should still have a trailing \n. */
|
|
|
|
assert (strcmp (buffer, "hij\n") == 0);
|
|
|
|
assert (size == 5);
|
|
|
|
/* The string should have been truncated (max_len == 0). */
|
|
|
|
assert (max_len == 0);
|
|
|
|
free (buffer);
|
|
|
|
|
|
|
|
/* We now read a line with 6 characters without a newline. This
|
|
|
|
requires 7 bytes of storage. We pass a NULL buffer and we
|
|
|
|
don't allow the buffer to be grown larger than 5 bytes. */
|
|
|
|
size = 5;
|
|
|
|
buffer = NULL;
|
|
|
|
max_len = 5;
|
|
|
|
n = iobuf_read_line (iobuf, &buffer, &size, &max_len);
|
|
|
|
assert (n == 4);
|
|
|
|
/* Note: the string should still have a trailing \n. */
|
|
|
|
assert (strcmp (buffer, "mno\n") == 0);
|
|
|
|
assert (size == 5);
|
|
|
|
/* The string should have been truncated (max_len == 0). */
|
|
|
|
assert (max_len == 0);
|
|
|
|
free (buffer);
|
2016-06-28 17:59:17 +02:00
|
|
|
|
|
|
|
iobuf_close (iobuf);
|
2015-08-12 02:19:05 +02:00
|
|
|
}
|
|
|
|
|
2015-08-12 11:44:59 +02:00
|
|
|
{
|
2015-09-01 22:13:45 +02:00
|
|
|
/* - 10 characters, EOF
|
|
|
|
- 17 characters, EOF
|
2015-08-12 11:44:59 +02:00
|
|
|
*/
|
|
|
|
char *content = "abcdefghijklmnopq";
|
|
|
|
char *content2 = "0123456789";
|
|
|
|
iobuf_t iobuf;
|
|
|
|
int rc;
|
|
|
|
int c;
|
|
|
|
int n;
|
|
|
|
int lastc = 0;
|
2016-06-28 17:59:17 +02:00
|
|
|
struct content_filter_state *state;
|
2015-08-12 11:44:59 +02:00
|
|
|
|
|
|
|
iobuf = iobuf_temp_with_content (content, strlen(content));
|
|
|
|
rc = iobuf_push_filter (iobuf,
|
2016-06-28 17:59:17 +02:00
|
|
|
content_filter,
|
|
|
|
state=content_filter_new (content2));
|
2015-08-12 11:44:59 +02:00
|
|
|
assert (rc == 0);
|
|
|
|
|
|
|
|
n = 0;
|
|
|
|
while (1)
|
|
|
|
{
|
|
|
|
c = iobuf_readbyte (iobuf);
|
|
|
|
if (c == -1 && lastc == -1)
|
|
|
|
{
|
2015-08-20 17:42:55 +02:00
|
|
|
/* printf("Two EOFs in a row. Done.\n"); */
|
2015-09-01 22:13:45 +02:00
|
|
|
assert (n == 27);
|
2015-08-12 11:44:59 +02:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
lastc = c;
|
|
|
|
|
|
|
|
if (c == -1)
|
2015-08-17 11:56:42 +02:00
|
|
|
{
|
2015-08-20 17:42:55 +02:00
|
|
|
/* printf("After %d bytes, got EOF.\n", n); */
|
2015-09-01 22:13:45 +02:00
|
|
|
assert (n == 10 || n == 27);
|
2015-08-17 11:56:42 +02:00
|
|
|
}
|
2015-08-12 11:44:59 +02:00
|
|
|
else
|
|
|
|
{
|
|
|
|
n ++;
|
2015-08-20 17:42:55 +02:00
|
|
|
/* printf ("%d: '%c' (%d)\n", n, c, c); */
|
2015-08-12 11:44:59 +02:00
|
|
|
}
|
|
|
|
}
|
2016-06-28 17:59:17 +02:00
|
|
|
|
|
|
|
iobuf_close (iobuf);
|
|
|
|
free (state);
|
2015-08-12 11:44:59 +02:00
|
|
|
}
|
|
|
|
|
2015-08-17 11:56:42 +02:00
|
|
|
/* Write some data to a temporary filter. Push a new filter. The
|
|
|
|
already written data should not be processed by the new
|
|
|
|
filter. */
|
|
|
|
{
|
|
|
|
iobuf_t iobuf;
|
|
|
|
int rc;
|
|
|
|
char *content = "0123456789";
|
|
|
|
char *content2 = "abc";
|
|
|
|
char buffer[4096];
|
|
|
|
int n;
|
|
|
|
|
|
|
|
iobuf = iobuf_temp ();
|
|
|
|
assert (iobuf);
|
|
|
|
|
|
|
|
rc = iobuf_write (iobuf, content, strlen (content));
|
|
|
|
assert (rc == 0);
|
|
|
|
|
|
|
|
rc = iobuf_push_filter (iobuf, double_filter, NULL);
|
|
|
|
assert (rc == 0);
|
|
|
|
|
|
|
|
/* Include a NUL. */
|
|
|
|
rc = iobuf_write (iobuf, content2, strlen (content2) + 1);
|
|
|
|
assert (rc == 0);
|
|
|
|
|
|
|
|
n = iobuf_temp_to_buffer (iobuf, buffer, sizeof (buffer));
|
2015-09-02 10:30:59 +02:00
|
|
|
#if 0
|
2015-08-17 11:56:42 +02:00
|
|
|
printf ("Got %d bytes\n", n);
|
|
|
|
printf ("buffer: `");
|
|
|
|
fwrite (buffer, n, 1, stdout);
|
|
|
|
fputc ('\'', stdout);
|
|
|
|
fputc ('\n', stdout);
|
2015-09-02 10:30:59 +02:00
|
|
|
#endif
|
2015-08-17 11:56:42 +02:00
|
|
|
|
|
|
|
assert (n == strlen (content) + 2 * (strlen (content2) + 1));
|
|
|
|
assert (strcmp (buffer, "0123456789aabbcc") == 0);
|
2016-06-28 17:59:17 +02:00
|
|
|
|
|
|
|
iobuf_close (iobuf);
|
2015-08-17 11:56:42 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
{
|
|
|
|
iobuf_t iobuf;
|
|
|
|
int rc;
|
2017-01-02 13:29:18 +01:00
|
|
|
char content[] = "0123456789";
|
2015-08-17 11:56:42 +02:00
|
|
|
int n;
|
|
|
|
int c;
|
2017-01-02 13:29:18 +01:00
|
|
|
char buffer[10];
|
|
|
|
|
|
|
|
assert (sizeof buffer == sizeof content - 1);
|
2015-08-17 11:56:42 +02:00
|
|
|
|
|
|
|
iobuf = iobuf_temp_with_content (content, strlen (content));
|
|
|
|
assert (iobuf);
|
|
|
|
|
|
|
|
rc = iobuf_push_filter (iobuf, every_other_filter, NULL);
|
|
|
|
assert (rc == 0);
|
|
|
|
rc = iobuf_push_filter (iobuf, every_other_filter, NULL);
|
|
|
|
assert (rc == 0);
|
|
|
|
|
|
|
|
for (n = 0; (c = iobuf_get (iobuf)) != -1; n ++)
|
|
|
|
{
|
2015-08-20 17:42:55 +02:00
|
|
|
/* printf ("%d: `%c'\n", n, c); */
|
2015-08-17 11:56:42 +02:00
|
|
|
buffer[n] = c;
|
|
|
|
}
|
|
|
|
|
|
|
|
assert (n == 2);
|
|
|
|
assert (buffer[0] == '3');
|
|
|
|
assert (buffer[1] == '7');
|
2016-06-28 17:59:17 +02:00
|
|
|
|
|
|
|
iobuf_close (iobuf);
|
2015-08-17 11:56:42 +02:00
|
|
|
}
|
|
|
|
|
2015-08-12 02:19:05 +02:00
|
|
|
return 0;
|
|
|
|
}
|