Lev Walkin | da997b1 | 2017-08-04 01:38:41 -0700 | [diff] [blame] | 1 | #include <stdio.h> |
| 2 | #include <string.h> |
| 3 | #include <stdlib.h> |
| 4 | #include <sys/types.h> |
| 5 | #include <sys/stat.h> |
| 6 | #include <limits.h> |
| 7 | #include <assert.h> |
| 8 | #include <errno.h> |
| 9 | |
| 10 | #include "asn1p_integer.h" |
| 11 | |
| 12 | #define ASN_INTEGER_MAX \ |
| 13 | (~((asn1c_integer_t)0) \ |
| 14 | & ~((asn1c_integer_t)1 << (8 * sizeof(asn1c_integer_t) - 1))) |
| 15 | #define ASN_INTEGER_MIN (-(ASN_INTEGER_MAX)-1) |
| 16 | |
| 17 | /* |
| 18 | * Parse the number in the given string until the given *end position, |
| 19 | * returning the position after the last parsed character back using the |
| 20 | * same (*end) pointer. |
| 21 | * WARNING: This behavior is different from the standard strtol/strtoimax(3). |
| 22 | */ |
| 23 | enum strtox_result_e { |
| 24 | STRTOX_ERROR_RANGE = -3, /* Input outside of supported numeric range */ |
| 25 | STRTOX_ERROR_INVAL = -2, /* Invalid data encountered (e.g., "+-") */ |
| 26 | STRTOX_EXPECT_MORE = -1, /* More data expected (e.g. "+") */ |
| 27 | STRTOX_OK = 0, /* Conversion succeded, number ends at (*end) */ |
| 28 | STRTOX_EXTRA_DATA = |
| 29 | 1 /* Conversion succeded, but the string has extra stuff */ |
| 30 | }; |
| 31 | |
| 32 | static enum strtox_result_e |
| 33 | strtoaint_lim(const char *str, const char **end, asn1c_integer_t *intp) { |
| 34 | const asn1c_integer_t upper_boundary = ASN_INTEGER_MAX / 10; |
| 35 | int last_digit_max = ASN_INTEGER_MAX % 10; |
| 36 | int sign = 1; |
| 37 | asn1c_integer_t value; |
| 38 | |
| 39 | if(str >= *end) return STRTOX_ERROR_INVAL; |
| 40 | |
| 41 | switch(*str) { |
| 42 | case '-': |
| 43 | last_digit_max++; |
| 44 | sign = -1; |
| 45 | /* FALL THROUGH */ |
| 46 | case '+': |
| 47 | str++; |
| 48 | if(str >= *end) { |
| 49 | *end = str; |
| 50 | return STRTOX_EXPECT_MORE; |
| 51 | } |
| 52 | } |
| 53 | |
| 54 | for(value = 0; str < (*end); str++) { |
| 55 | switch(*str) { |
| 56 | case 0x30: case 0x31: case 0x32: case 0x33: case 0x34: |
| 57 | case 0x35: case 0x36: case 0x37: case 0x38: case 0x39: { |
| 58 | int d = *str - '0'; |
| 59 | if(value < upper_boundary) { |
| 60 | value = value * 10 + d; |
| 61 | } else if(value == upper_boundary) { |
| 62 | if(d <= last_digit_max) { |
| 63 | if(sign > 0) { |
| 64 | value = value * 10 + d; |
| 65 | } else { |
| 66 | sign = 1; |
| 67 | value = -value * 10 - d; |
| 68 | } |
| 69 | } else { |
| 70 | *end = str; |
| 71 | return STRTOX_ERROR_RANGE; |
| 72 | } |
| 73 | } else { |
| 74 | *end = str; |
| 75 | return STRTOX_ERROR_RANGE; |
| 76 | } |
| 77 | } |
| 78 | continue; |
| 79 | default: |
| 80 | *end = str; |
| 81 | *intp = sign * value; |
| 82 | return STRTOX_EXTRA_DATA; |
| 83 | } |
| 84 | } |
| 85 | |
| 86 | *end = str; |
| 87 | *intp = sign * value; |
| 88 | return STRTOX_OK; |
| 89 | } |
| 90 | |
| 91 | int |
| 92 | asn1p_atoi(const char *ptr, asn1c_integer_t *value) { |
| 93 | const char *end = ptr + strlen(ptr); |
| 94 | if(strtoaint_lim(ptr, &end, value) == STRTOX_OK) { |
| 95 | return 0; |
| 96 | } else { |
| 97 | return -1; |
| 98 | } |
| 99 | } |
| 100 | |
| 101 | const char *asn1p_itoa(asn1c_integer_t v) { |
| 102 | static char static_buf[128]; |
| 103 | int ret = asn1p_itoa_s(static_buf, sizeof(static_buf), v); |
| 104 | if(ret > 0) { |
Lev Walkin | 7268f33 | 2017-08-05 18:20:52 -0700 | [diff] [blame] | 105 | assert((size_t)ret < sizeof(static_buf)); |
Lev Walkin | da997b1 | 2017-08-04 01:38:41 -0700 | [diff] [blame] | 106 | return static_buf; |
| 107 | } else { |
| 108 | return NULL; |
| 109 | } |
| 110 | } |
| 111 | |
| 112 | int asn1p_itoa_s(char *buf, size_t size, asn1c_integer_t v) { |
| 113 | char tmp_buf[128]; |
| 114 | |
| 115 | if(v >= LONG_MIN && v < LONG_MAX) { |
| 116 | int ret = snprintf(buf, size, "%ld", (long)v); |
Lev Walkin | 2888f6c | 2017-08-05 23:49:03 -0700 | [diff] [blame] | 117 | if(ret < 0 || (size_t)ret >= size) { |
Lev Walkin | da997b1 | 2017-08-04 01:38:41 -0700 | [diff] [blame] | 118 | return -1; |
| 119 | } |
| 120 | return ret; |
| 121 | } |
| 122 | |
| 123 | int sign = 0; |
| 124 | if(v < 0) { |
| 125 | if(v == ASN_INTEGER_MIN) { |
| 126 | switch(sizeof(v)) { |
| 127 | case 16: |
| 128 | if(size < 41) |
| 129 | return -1; |
| 130 | memcpy(buf, "-170141183460469231731687303715884105729", 41); |
| 131 | return 41; |
| 132 | case 8: |
| 133 | if(size < 21) |
| 134 | return -1; |
| 135 | memcpy(buf, "-9223372036854775809", 21); |
| 136 | return 21; |
| 137 | default: |
| 138 | assert(!"unreachable"); |
| 139 | } |
| 140 | } |
| 141 | |
| 142 | sign = -1; |
| 143 | v = -v; /* Ditch the sign */ |
| 144 | } |
| 145 | |
| 146 | assert(v > 1000000000L); |
| 147 | char restbuf[10] = "000000000\0"; |
| 148 | const char *rest = asn1p_itoa((long)(v % 1000000000L)); |
| 149 | size_t restlen = strlen(rest); |
| 150 | assert(restlen <= 9); |
| 151 | memcpy(restbuf + (9 - restlen), rest, restlen); |
| 152 | rest = restbuf; |
| 153 | |
| 154 | const char *head = asn1p_itoa(v / 1000000000L); |
| 155 | assert(head); |
| 156 | int ret = snprintf(tmp_buf, sizeof(tmp_buf), "%s%s%s", sign ? "-" : "", |
| 157 | head, rest); |
Lev Walkin | 2888f6c | 2017-08-05 23:49:03 -0700 | [diff] [blame] | 158 | if(ret < 0 || (size_t)ret >= size) { |
| 159 | assert(ret > 0 && (size_t)ret < sizeof(tmp_buf)); |
Lev Walkin | da997b1 | 2017-08-04 01:38:41 -0700 | [diff] [blame] | 160 | return -1; |
Lev Walkin | 2888f6c | 2017-08-05 23:49:03 -0700 | [diff] [blame] | 161 | } |
Lev Walkin | da997b1 | 2017-08-04 01:38:41 -0700 | [diff] [blame] | 162 | memcpy(buf, tmp_buf, ret); |
| 163 | buf[ret] = '\0'; |
| 164 | return ret; |
| 165 | } |
| 166 | |