+tags
+*~
*.gcda
*.gcno
*.gcov
tests/Makefile.in
tests/Test_encoding
tests/Test_encoding.o
+tests/Test_strings.o
+tests/Test_strings
tests/Test_errors
tests/Test_errors.o
tests/Test_indefinite
PrintableString, UniversalString, BMPString, UTF8String. When re-defined
a warning is being print instead of failing.
- Parser outputs more detailed syntax error message.
+- Added asn1_decode_string_der() and asn1_encode_string_der().
* Noteworthy changes in release 3.0 (2012-10-28) [stable]
- Added tool in tests/ to benchmark X.509 structure decoding.
nw="$nw -Wundef" #
nw="$nw -Wunreachable-code" # Too many false positives
nw="$nw -Wunused-macros" # Breaks on bison generated ASN1.c
+ nw="$nw -Wswitch-default" # Breaks on bison generated ASN1.c
nw="$nw -Wunsafe-loop-optimizations"
nw="$nw -Wstrict-overflow"
nw="$nw -Wsuggest-attribute=pure" # Is it worth using pure attributes?
snprintf (last_error_token, ASN1_MAX_ERROR_DESCRIPTION_SIZE,
"%s", last_token);
fprintf(stderr,
- "%s:%ld: Warning: %s is a built-in ASN.1 type.\n",
+ "%s:%u: Warning: %s is a built-in ASN.1 type.\n",
file_name, line_number, last_token);
return;
}
if (result_parse != ASN1_NAME_TOO_LONG)
{
snprintf (last_error, ASN1_MAX_ERROR_DESCRIPTION_SIZE,
- "%s:%ld: Error: %s near '%s'", file_name,
+ "%s:%u: Error: %s near '%s'", file_name,
line_number, s, last_token);
result_parse = ASN1_SYNTAX_ERROR;
}
snprintf (last_error_token, ASN1_MAX_ERROR_DESCRIPTION_SIZE,
"%s", last_token);
fprintf(stderr,
- "%s:%ld: Warning: %s is a built-in ASN.1 type.\n",
+ "%s:%u: Warning: %s is a built-in ASN.1 type.\n",
file_name, line_number, last_token);
return;
}
if (result_parse != ASN1_NAME_TOO_LONG)
{
snprintf (last_error, ASN1_MAX_ERROR_DESCRIPTION_SIZE,
- "%s:%ld: Error: %s near '%s'", file_name,
+ "%s:%u: Error: %s near '%s'", file_name,
line_number, s, last_token);
result_parse = ASN1_SYNTAX_ERROR;
}
/**
* asn1_length_der:
* @len: value to convert.
- * @ans: string returned.
- * @ans_len: number of meaningful bytes of ANS (ans[0]..ans[ans_len-1]).
+ * @der: the encoding (may be %NULL).
+ * @der_len: number of meaningful bytes of ANS (der[0]..der[der_len-1]).
*
- * Creates the DER coding for the LEN parameter (only the length).
- * The @ans buffer is pre-allocated and must have room for the output.
+ * Creates the DER encoding of the provided length value.
+ * The @der buffer must have enough room for the output. The maximum
+ * length this function will encode is %ASN1_MAX_LENGTH_SIZE.
+ *
+ * To know the size of the DER encoding use a %NULL value for @der.
**/
void
-asn1_length_der (unsigned long int len, unsigned char *ans, int *ans_len)
+asn1_length_der (unsigned long int len, unsigned char *der, int *der_len)
{
int k;
- unsigned char temp[SIZEOF_UNSIGNED_LONG_INT];
+ unsigned char temp[ASN1_MAX_LENGTH_SIZE];
+#if SIZEOF_UNSIGNED_LONG_INT > 8
+ len &= 0xFFFFFFFFFFFFFFFF;
+#endif
if (len < 128)
{
/* short form */
- if (ans != NULL)
- ans[0] = (unsigned char) len;
- *ans_len = 1;
+ if (der != NULL)
+ der[0] = (unsigned char) len;
+ *der_len = 1;
}
else
{
temp[k++] = len & 0xFF;
len = len >> 8;
}
- *ans_len = k + 1;
- if (ans != NULL)
+ *der_len = k + 1;
+ if (der != NULL)
{
- ans[0] = ((unsigned char) k & 0x7F) + 128;
+ der[0] = ((unsigned char) k & 0x7F) + 128;
while (k--)
- ans[*ans_len - 1 - k] = temp[k];
+ der[*der_len - 1 - k] = temp[k];
}
}
}
/* Function : _asn1_tag_der */
/* Description: creates the DER coding for the CLASS */
/* and TAG parameters. */
+/* It is limited by the ASN1_MAX_TAG_SIZE variable */
/* Parameters: */
/* class: value to convert. */
/* tag_value: value to convert. */
unsigned char *ans, int *ans_len)
{
int k;
- unsigned char temp[SIZEOF_UNSIGNED_INT];
+ unsigned char temp[ASN1_MAX_TAG_SIZE];
if (tag_value < 31)
{
/* Long form */
ans[0] = (class & 0xE0) + 31;
k = 0;
- while (tag_value)
+ while (tag_value != 0)
{
temp[k++] = tag_value & 0x7F;
- tag_value = tag_value >> 7;
+ tag_value >>= 7;
+
+ if (k > ASN1_MAX_TAG_SIZE-1)
+ break; /* will not encode larger tags */
}
*ans_len = k + 1;
while (k--)
/**
* asn1_octet_der:
- * @str: OCTET string.
- * @str_len: STR length (str[0]..str[str_len-1]).
- * @der: string returned.
- * @der_len: number of meaningful bytes of DER (der[0]..der[ans_len-1]).
+ * @str: the input data.
+ * @str_len: STR length (str[0]..str[*str_len-1]).
+ * @der: encoded string returned.
+ * @der_len: number of meaningful bytes of DER (der[0]..der[der_len-1]).
*
- * Creates the DER coding for an OCTET type (length included).
+ * Creates a length-value DER encoding for the input data.
+ * The DER encoding of the input data will be placed in the @der variable.
+ *
+ * Note that the OCTET STRING tag is not included in the output.
+ *
+ * This function does not return any value because it is expected
+ * that @der_len will contain enough bytes to store the string
+ * plus the DER encoding. The DER encoding size can be obtained using
+ * asn1_length_der().
**/
void
asn1_octet_der (const unsigned char *str, int str_len,
if (der == NULL || str_len < 0)
return;
+
asn1_length_der (str_len, der, &len_len);
memcpy (der + len_len, str, str_len);
*der_len = str_len + len_len;
}
+
+/**
+ * asn1_encode_string_der:
+ * @etype: The type of the string to be encoded (ASN1_ETYPE_)
+ * @str: the string data.
+ * @str_len: the string length
+ * @der: the encoded string
+ * @der_len: the bytes of the encoded string
+ *
+ * Creates the DER encoding for the various ASN.1 STRING types.
+ * The DER encoding of the input data will be placed in the @der variable.
+ *
+ * Returns: %ASN1_SUCCESS if successful or an error value.
+ **/
+int
+asn1_encode_string_der (unsigned int etype, const unsigned char *str, unsigned int str_len,
+ unsigned char **der, unsigned int *der_len)
+{
+ int tag_len, len_len, tlen;
+ unsigned char der_tag[ASN1_MAX_TAG_SIZE];
+ unsigned char der_length[ASN1_MAX_LENGTH_SIZE];
+ unsigned char* p;
+
+ if (der == NULL)
+ return ASN1_VALUE_NOT_VALID;
+
+ if (ETYPE_OK(etype) == 0)
+ return ASN1_VALUE_NOT_VALID;
+
+ _asn1_tag_der (ETYPE_CLASS(etype), ETYPE_TAG(etype),
+ der_tag, &tag_len);
+
+ asn1_length_der(str_len, der_length, &len_len);
+
+ if (tag_len <= 0 || len_len <= 0)
+ return ASN1_VALUE_NOT_VALID;
+
+ tlen = tag_len + len_len + str_len;
+
+ p = malloc(tlen);
+ if (p == NULL)
+ return ASN1_MEM_ALLOC_ERROR;
+
+ *der = p;
+ *der_len = tag_len + len_len + str_len;
+
+ memcpy(p, der_tag, tag_len);
+ p+=tag_len;
+ memcpy(p, der_length, len_len);
+ p+=len_len;
+ memcpy(p, str, str_len);
+
+ return ASN1_SUCCESS;
+}
+
/******************************************************/
/* Function : _asn1_time_der */
/* Description: creates the DER coding for a TIME */
* @der_len: number of meaningful bytes of DER
* (der[0]..der[ans_len-1]).
*
- * Creates the DER coding for a BIT STRING type (length and pad
- * included).
+ * Creates a length-value DER encoding for the input data
+ * as it would have been for a BIT STRING.
+ * The DER encoded data will be copied in @der.
+ *
+ * Note that the BIT STRING tag is not included in the output.
+ *
+ * This function does not return any value because it is expected
+ * that @der_len will contain enough bytes to store the string
+ * plus the DER encoding. The DER encoding size can be obtained using
+ * asn1_length_der().
**/
void
asn1_bit_der (const unsigned char *str, int bit_len,
if (der == NULL)
return;
+
len_byte = bit_len >> 3;
len_pad = 8 - (bit_len & 7);
if (len_pad == 8)
[ASN1_ETYPE_TELETEXSTRING] = {ASN1_TAG_TELETEXSTRING, ASN1_CLASS_UNIVERSAL, "type:TELETEX_STR"},
[ASN1_ETYPE_PRINTABLESTRING] = {ASN1_TAG_PRINTABLESTRING, ASN1_CLASS_UNIVERSAL, "type:PRINTABLE_STR"},
[ASN1_ETYPE_UNIVERSALSTRING] = {ASN1_TAG_UNIVERSALSTRING, ASN1_CLASS_UNIVERSAL, "type:UNIVERSAL_STR"},
- [ASN1_ETYPE_BMPSTRING] = {ASN1_TAG_BMPSTRING, ASN1_CLASS_UNIVERSAL, "type:BMP_STR"},
- [ASN1_ETYPE_UTF8STRING] = {ASN1_TAG_UTF8STRING, ASN1_CLASS_UNIVERSAL, "type:UTF8_STR"},
- [ASN1_ETYPE_VISIBLESTRING] = {ASN1_TAG_VISIBLESTRING, ASN1_CLASS_UNIVERSAL, "type:VISIBLE_STR"},
- [ASN1_ETYPE_OCTET_STRING] = {ASN1_TAG_OCTET_STRING, ASN1_CLASS_UNIVERSAL, "type:OCT_STR"},
+ [ASN1_ETYPE_BMPSTRING] = {ASN1_TAG_BMPSTRING, ASN1_CLASS_UNIVERSAL, "type:BMP_STR"},
+ [ASN1_ETYPE_UTF8STRING] = {ASN1_TAG_UTF8STRING, ASN1_CLASS_UNIVERSAL, "type:UTF8_STR"},
+ [ASN1_ETYPE_VISIBLESTRING] = {ASN1_TAG_VISIBLESTRING, ASN1_CLASS_UNIVERSAL, "type:VISIBLE_STR"},
+ [ASN1_ETYPE_OCTET_STRING] = {ASN1_TAG_OCTET_STRING, ASN1_CLASS_UNIVERSAL, "type:OCT_STR"},
[ASN1_ETYPE_BIT_STRING] = {ASN1_TAG_BIT_STRING, ASN1_CLASS_UNIVERSAL, "type:BIT_STR"},
[ASN1_ETYPE_OBJECT_ID] = {ASN1_TAG_OBJECT_ID, ASN1_CLASS_UNIVERSAL, "type:OBJ_STR"},
[ASN1_ETYPE_NULL] = {ASN1_TAG_NULL, ASN1_CLASS_UNIVERSAL, "type:NULL"},
[ASN1_ETYPE_SET] = {ASN1_TAG_SET, ASN1_CLASS_UNIVERSAL | ASN1_CLASS_STRUCTURED, "type:SET"},
[ASN1_ETYPE_SET_OF] = {ASN1_TAG_SET, ASN1_CLASS_UNIVERSAL | ASN1_CLASS_STRUCTURED, "type:SET_OF"},
};
+unsigned int _asn1_tags_size = sizeof(_asn1_tags)/sizeof(_asn1_tags[0]);
/******************************************************/
/* Function : _asn1_insert_tag_der */
if (len4 != -1)
{
len2 += len4;
- _asn1_set_value_octet (p, der + counter, len2 + len3);
+ _asn1_set_value_lv (p, der + counter, len2 + len3);
counter += len2 + len3;
}
else
if (result != ASN1_SUCCESS)
goto cleanup;
- _asn1_set_value_octet (p, der + counter, len2);
+ _asn1_set_value_lv (p, der + counter, len2);
counter += len2;
/* Check if a couple of 0x00 are present due to an EXPLICIT TAG with
len2 += len4;
if (state == FOUND)
{
- _asn1_set_value_octet (p, der + counter, len2 + len3);
+ _asn1_set_value_lv (p, der + counter, len2 + len3);
if (p == nodeFound)
state = EXIT;
if (state == FOUND)
{
- _asn1_set_value_octet (p, der + counter, len2);
+ _asn1_set_value_lv (p, der + counter, len2);
if (p == nodeFound)
state = EXIT;
return retCode;
}
+
+/**
+ * asn1_decode_string_der:
+ * @etype: The type of the string to be encoded (ASN1_ETYPE_)
+ * @der: the encoded string
+ * @der_len: the bytes of the encoded string
+ * @str: the decoded string data.
+ * @str_len: the string length
+ *
+ * Creates the DER encoding for the various ASN.1 STRING types.
+ * The DER encoding of the input data will be placed in the @der variable.
+ *
+ * Returns: %ASN1_SUCCESS if successful or an error value.
+ **/
+int
+asn1_decode_string_der (unsigned int etype, const unsigned char *der, unsigned int der_len,
+ unsigned char **str, unsigned int *str_len)
+{
+ int tag_len, len_len, tlen;
+ const unsigned char* p;
+ unsigned char class;
+ unsigned long tag;
+ long ret;
+
+ if (der == NULL || der_len == 0)
+ return ASN1_VALUE_NOT_VALID;
+
+ if (ETYPE_OK(etype) == 0)
+ return ASN1_VALUE_NOT_VALID;
+
+ p = der;
+ ret = asn1_get_tag_der (p, der_len, &class, &tag_len, &tag);
+ if (ret != ASN1_SUCCESS)
+ return ret;
+
+ p += tag_len;
+ der_len -= tag_len;
+
+ ret = asn1_get_length_der (p, der_len, &len_len);
+ if (ret < 0)
+ return ASN1_DER_ERROR;
+
+ p += len_len;
+ der_len -= len_len;
+ tlen = ret;
+
+ *str = malloc(tlen);
+ if (*str == NULL)
+ return ASN1_MEM_ALLOC_ERROR;
+
+ memcpy(*str, p, tlen);
+ *str_len = tlen;
+
+ return ASN1_SUCCESS;
+}
(!negative && (value_temp[k] & 0x80)))
k--;
- _asn1_set_value_octet (node, value_temp + k, len - k);
+ _asn1_set_value_lv (node, value_temp + k, len - k);
if (node->type & CONST_DEFAULT)
{
case ASN1_ETYPE_VISIBLESTRING:
if (len == 0)
len = _asn1_strlen (value);
- _asn1_set_value_octet (node, value, len);
+ _asn1_set_value_lv (node, value, len);
break;
case ASN1_ETYPE_BIT_STRING:
if (len == 0)
return ASN1_ELEMENT_NOT_FOUND;
break;
case ASN1_ETYPE_ANY:
- _asn1_set_value_octet (node, value, len);
+ _asn1_set_value_lv (node, value, len);
break;
case ASN1_ETYPE_SEQUENCE_OF:
case ASN1_ETYPE_SET_OF:
#include <libtasn1.h>
#define ASN1_SMALL_VALUE_SIZE 16
+#define ASN1_MAX_TAG_SIZE 4
/* This structure is also in libtasn1.h, but then contains less
fields. You cannot make any modifications to these first fields
case ASN1_ETYPE_SET: \
case ASN1_ETYPE_SET_OF
+#define ETYPE_TAG(etype) (_asn1_tags[etype].tag)
+#define ETYPE_CLASS(etype) (_asn1_tags[etype].class)
+#define ETYPE_OK(etype) ((etype <= _asn1_tags_size)?1:0)
+
+extern unsigned int _asn1_tags_size;
extern const tag_and_class_st _asn1_tags[];
#define _asn1_strlen(s) strlen((const char *) s)
extern ASN1_API void asn1_perror (int error);
- /* DER utility functions. */
+#define ASN1_MAX_LENGTH_SIZE 9
+ extern ASN1_API long
+ asn1_get_length_der (const unsigned char *der, int der_len, int *len);
+
+ extern ASN1_API long
+ asn1_get_length_ber (const unsigned char *ber, int ber_len, int *len);
+
+ extern ASN1_API void
+ asn1_length_der (unsigned long int len, unsigned char *ans, int *ans_len);
+
+ /* Other utility functions. */
+
+ extern ASN1_API
+ int asn1_decode_string_der (unsigned int etype, const unsigned char *der, unsigned int der_len,
+ unsigned char **str, unsigned int *str_len);
+
+ extern ASN1_API
+ int asn1_encode_string_der (unsigned int etype, const unsigned char *str, unsigned int str_len,
+ unsigned char **der, unsigned int *der_len);
+
+ extern ASN1_API asn1_node
+ asn1_find_node (asn1_node pointer, const char *name);
+
+ extern ASN1_API int
+ asn1_copy_node (asn1_node dst, const char *dst_name,
+ asn1_node src, const char *src_name);
+
+ /* Internal and low-level DER utility functions. */
extern ASN1_API int
asn1_get_tag_der (const unsigned char *der, int der_len,
int *ret_len, unsigned char *str,
int str_size, int *bit_len);
- extern ASN1_API long
- asn1_get_length_der (const unsigned char *der, int der_len, int *len);
-
- extern ASN1_API long
- asn1_get_length_ber (const unsigned char *ber, int ber_len, int *len);
-
- extern ASN1_API void
- asn1_length_der (unsigned long int len, unsigned char *ans, int *ans_len);
-
- /* Other utility functions. */
-
- extern ASN1_API asn1_node
- asn1_find_node (asn1_node pointer, const char *name);
-
- extern ASN1_API int
- asn1_copy_node (asn1_node dst, const char *dst_name,
- asn1_node src, const char *src_name);
-
/* Compatibility types */
typedef int asn1_retCode; /* type returned by libtasn1 functions */
asn1_strerror;
asn1_write_value;
asn1_read_node_value;
+ asn1_encode_string_der;
+ asn1_decode_string_der;
# Old symbols
libtasn1_strerror;
}
/******************************************************************/
-/* Function : _asn1_set_value_octet */
+/* Function : _asn1_set_value_lv */
/* Description: sets the field VALUE in a NODE_ASN element. The */
/* previous value (if exist) will be lost. The value */
-/* given is stored as an octet string. */
+/* given is stored as an length-value format (LV */
/* Parameters: */
/* node: element pointer. */
/* value: pointer to the value that you want to set. */
/* Return: pointer to the NODE_ASN element. */
/******************************************************************/
asn1_node
-_asn1_set_value_octet (asn1_node node, const void *value, unsigned int len)
+_asn1_set_value_lv (asn1_node node, const void *value, unsigned int len)
{
int len2;
void *temp;
asn1_node _asn1_set_value_m (asn1_node node, void *value, unsigned int len);
asn1_node
-_asn1_set_value_octet (asn1_node node, const void *value, unsigned int len);
+_asn1_set_value_lv (asn1_node node, const void *value, unsigned int len);
asn1_node
_asn1_append_value (asn1_node node, const void *value, unsigned int len);
MOSTLYCLEANFILES = Test_parser_ERROR.asn
-check_PROGRAMS = Test_parser Test_tree Test_encoding Test_indefinite \
- Test_errors Test_simple Test_overflow
+check_PROGRAMS = Test_parser Test_tree Test_encoding Test_indefinite \
+ Test_errors Test_simple Test_overflow Test_strings
TESTS = Test_parser Test_tree Test_encoding Test_indefinite \
- Test_errors Test_simple Test_overflow crlf threadsafety
+ Test_errors Test_simple Test_overflow crlf threadsafety \
+ Test_strings
TESTS_ENVIRONMENT = \
ASN1PARSER=$(srcdir)/Test_parser.asn \
--- /dev/null
+/*
+ * Copyright (C) 2012 Free Software Foundation, Inc.
+ *
+ * This file is part of LIBTASN1.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 <http://www.gnu.org/licenses/>.
+ *
+ * Written by Simon Josefsson
+ *
+ */
+
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+
+#include "libtasn1.h"
+
+struct tv
+{
+ unsigned int etype;
+ unsigned int str_len;
+ const void *str;
+ unsigned int der_len;
+ const void *der;
+};
+
+static const struct tv tv[] = {
+ {ASN1_ETYPE_IA5STRING, 20, "\x63\x73\x63\x61\x40\x70\x61\x73\x73\x70\x6f\x72\x74\x2e\x67\x6f\x76\x2e\x67\x72",
+ 22, "\x16\x14\x63\x73\x63\x61\x40\x70\x61\x73\x73\x70\x6f\x72\x74\x2e\x67\x6f\x76\x2e\x67\x72"},
+ {ASN1_ETYPE_PRINTABLESTRING, 5, "\x4e\x69\x6b\x6f\x73",
+ 7, "\x13\x05\x4e\x69\x6b\x6f\x73"},
+ {ASN1_ETYPE_UTF8STRING, 12, "Αττική",
+ 14, "\x0c\x0c\xce\x91\xcf\x84\xcf\x84\xce\xb9\xce\xba\xce\xae"},
+ {ASN1_ETYPE_TELETEXSTRING, 15, "\x53\x69\x6d\x6f\x6e\x20\x4a\x6f\x73\x65\x66\x73\x73\x6f\x6e",
+ 17, "\x14\x0f\x53\x69\x6d\x6f\x6e\x20\x4a\x6f\x73\x65\x66\x73\x73\x6f\x6e"},
+ {ASN1_ETYPE_OCTET_STRING, 36, "\x30\x22\x80\x0F\x32\x30\x31\x31\x30\x38\x32\x31\x30\x38\x30\x30\x30\x36\x5A\x81\x0F\x32\x30\x31\x31\x30\x38\x32\x33\x32\x30\x35\x39\x35\x39\x5A",
+ 38, "\x04\x24\x30\x22\x80\x0F\x32\x30\x31\x31\x30\x38\x32\x31\x30\x38\x30\x30\x30\x36\x5A\x81\x0F\x32\x30\x31\x31\x30\x38\x32\x33\x32\x30\x35\x39\x35\x39\x5A"}
+};
+
+int
+main (int argc, char *argv[])
+{
+ int ret;
+ unsigned char* der;
+ unsigned int der_len;
+ unsigned int i;
+
+ /* Dummy test */
+
+ for (i = 0; i < sizeof (tv) / sizeof (tv[0]); i++)
+ {
+ /* Encode */
+ ret = asn1_encode_string_der(tv[i].etype, tv[i].str, tv[i].str_len,
+ &der, &der_len);
+ if (ret != ASN1_SUCCESS)
+ {
+ fprintf(stderr, "Encoding error in %u: %s\n", i, asn1_strerror(ret));
+ return 1;
+ }
+
+ if (der_len != tv[i].der_len || memcmp(der, tv[i].der, der_len) != 0)
+ {
+ fprintf(stderr, "DER encoding differs in %u! (size: %u, expected: %u)\n", i, der_len, tv[i].der_len);
+ return 1;
+ }
+ free(der);
+
+ /* decoding */
+ ret = asn1_decode_string_der(tv[i].etype, tv[i].der, tv[i].der_len, &der, &der_len);
+ if (ret != ASN1_SUCCESS)
+ {
+ fprintf(stderr, "Decoding error in %u: %s\n", i, asn1_strerror(ret));
+ return 1;
+ }
+
+ if (der_len != tv[i].str_len || memcmp(der, tv[i].str, der_len) != 0)
+ {
+ fprintf(stderr, "DER decoded data differ in %u! (size: %u, expected: %u)\n", i, der_len, tv[i].str_len);
+ return 1;
+ }
+ free(der);
+ }
+
+
+ return 0;
+}