Added SILC Thread Queue API
[silc.git] / lib / silcasn1 / silcber.c
1 /*
2
3   silcber.c
4
5   Author: Pekka Riikonen <priikone@silcnet.org>
6
7   Copyright (C) 2003 - 2005 Pekka Riikonen
8
9   This program is free software; you can redistribute it and/or modify
10   it under the terms of the GNU General Public License as published by
11   the Free Software Foundation; version 2 of the License.
12
13   This program is distributed in the hope that it will be useful,
14   but WITHOUT ANY WARRANTY; without even the implied warranty of
15   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16   GNU General Public License for more details.
17
18 */
19 /* Basic Encoding Rules (BER) encoder and decoder. */
20
21 #include "silc.h"
22 #include "silcber.h"
23
24 /* Encodes a BER data block into the `ber', which must already have
25    sufficient space allocated.  Caller can use silc_ber_encoded_len
26    function to determine how much to allocate space before calling this
27    function.  If the `indefinite' is TRUE then the BER block will not
28    include the length of the data in the BER block. */
29
30 SilcBool silc_ber_encode(SilcBuffer ber, SilcBerClass ber_class,
31                          SilcBerEncoding encoding, SilcUInt32 tag,
32                          const unsigned char *data, SilcUInt32 data_len,
33                          SilcBool indefinite)
34 {
35   int i = 0, c;
36   SilcUInt32 tmp;
37
38   if (!ber)
39     return FALSE;
40
41   /* Check that buffer is of correct size */
42   if (silc_buffer_len(ber) < silc_ber_encoded_len(tag, data_len, indefinite))
43     return FALSE;
44
45   /* Put the class and encoding */
46   ber->data[i] = (ber_class << 6) | (encoding << 5);
47
48   /* Put the tag */
49   if (tag < 0x1f) {
50     /* Short tag */
51     ber->data[i++] |= tag;
52   } else {
53     /* Long tag */
54     ber->data[i++] |= 0x1f;
55
56     /* Save the tag in multiple octets where 7-bits in the octet is the tag
57        value and bit 8 is set, except for the last octet. */
58     tmp = tag;
59     c = 0;
60     while (tmp) {
61       c++;
62       tmp >>= 7;
63     }
64     while (c > 1)
65       ber->data[i++] = 0x80 | ((tag >> (--c * 7)) & 0x7f);
66     ber->data[i++] = tag & 0x7f;
67   }
68
69   /* Put the length of data */
70   if (!indefinite) {
71     if (data_len < 0x80) {
72       /* Use short form for less than 128 bytes */
73       ber->data[i++] = data_len;
74     } else {
75       /* Long form */
76
77       /* Calculate the number of octets for the length field */
78       tmp = data_len;
79       c = 0;
80       while (tmp) {
81         c++;
82         tmp >>= 8;
83       }
84       ber->data[i++] = 0x80 | c;
85
86       /* Put the actual length field octets, 8-bits per octet. */
87       while (c > 1)
88         ber->data[i++] = (data_len >> (--c * 8)) & 0xff;
89       ber->data[i++] = data_len & 0xff;
90     }
91   } else {
92     /* In indefinite form the length of data is not present in the BER */
93     ber->data[i++] = 0x80;
94   }
95
96   /* Put the data */
97   if (data)
98     memcpy(&ber->data[i], data, data_len);
99
100   /* Put end-of-content octets if length is indefinite */
101   if (indefinite)
102     ber->data[i + data_len] = ber->data[i + data_len + 1] = 0x00;
103
104   return TRUE;
105 }
106
107 /* Decodesa a BER data from the buffer `ber'.  Returns the class,
108    encoding and the tag number for the BER data into `ber_class',
109    `encoding' and `tag'.  A pointer to the start of the data area is
110    returned into `data'.  If the length of the data is available from
111    the BER data the length is returned into `data_len'.  If the
112    `indefinite' is TRUE then the length found in `data_len' was found
113    by finding end-of-contents octets from the data.  The
114    `identifier_len' is the length of the BER header, and the length
115    of the entire BER object is `identifier_len' + `data_len'. */
116
117 SilcBool silc_ber_decode(SilcBuffer ber, SilcBerClass *ber_class,
118                          SilcBerEncoding *encoding, SilcUInt32 *tag,
119                          const unsigned char **data, SilcUInt32 *data_len,
120                          SilcBool *indefinite, SilcUInt32 *identifier_len)
121 {
122   int i = 0, c;
123   SilcUInt32 t;
124
125   if (!ber || silc_buffer_len(ber) == 0) {
126     SILC_LOG_DEBUG(("Invalid data buffer"));
127     return FALSE;
128   }
129
130   /* Get class */
131   if (ber_class)
132     *ber_class = (ber->data[0] >> 6) & 0x03;
133
134   /* Get encoding */
135   if (encoding)
136     *encoding = (ber->data[0] >> 5) & 0x01;
137
138   /* Get the tag.  Assume short tag, the most common case */
139   t = ber->data[i++] & 0x1f;
140
141   /* If the tag is over 31 then take it from next octets */
142   if (t >= 0x1f) {
143     if (i >= silc_buffer_len(ber)) {
144       SILC_LOG_DEBUG(("Malformed BER: Not enough bytes"));
145       return FALSE;
146     }
147
148     /* The tag is in next octets in 7-bits parts, parse them out.  All
149        octets except the last one has bit 8 set. */
150     t = 0;
151     while (ber->data[i] & 0x80) {
152       t <<= 7;
153       t |= ber->data[i++] & 0x7f;
154
155       if (i >= silc_buffer_len(ber)) {
156         SILC_LOG_DEBUG(("Malformed BER: Not enough bytes"));
157         return FALSE;
158       }
159     }
160
161     /* Last 7-bits part */
162     t <<= 7;
163     t |= ber->data[i++] & 0x7f;
164   }
165   if (tag)
166     *tag = t;
167
168   if (i >= silc_buffer_len(ber)) {
169     SILC_LOG_DEBUG(("Malformed BER: Not enough bytes"));
170     return FALSE;
171   }
172
173   /* Get the data length and the actual data */
174   if (data && data_len) {
175     /* Assume short format for length */
176     *data_len = ber->data[i++];
177     if (indefinite)
178       *indefinite = FALSE;
179
180     /* The bit 8 is set if the length is in long format */
181     if (*data_len & 0x80) {
182       /* If the format is definite then this octet includes the number
183          of length octets.  If indefinite it is zero and data is ended
184          with end-of-contents octets (two zero bytes). */
185       c = *data_len & 0x7f;
186       if (c) {
187         if (i >= silc_buffer_len(ber)) {
188           SILC_LOG_DEBUG(("Malformed BER: Not enough bytes"));
189           return FALSE;
190         }
191
192         /* Get the length from c many octects (8-bits per octet) */
193         *data_len = 0;
194         while (c > 0) {
195           *data_len <<= 8;
196           *data_len |= ber->data[i++] & 0xff;
197
198           if (i >= silc_buffer_len(ber)) {
199             SILC_LOG_DEBUG(("Malformed BER: Length is too long"));
200             return FALSE;
201           }
202           c--;
203         }
204       } else {
205         /* It is indefinite and we attempt to find out the length by
206            finding the end-of-contents octets. */
207         if (indefinite)
208           *indefinite = TRUE;
209         c = i;
210         while (c + 1 < silc_buffer_len(ber)) {
211           if (ber->data[c] == 0x00 && ber->data[c + 1] == 0x00)
212             break;
213           c += 2;
214         }
215         if (c >= silc_buffer_len(ber)) {
216           SILC_LOG_DEBUG(("Malformed BER: could not find end-of-content"));
217           return FALSE;
218         }
219         *data_len = c - i;
220       }
221     }
222
223     if (*data_len > silc_buffer_len(ber) - i) {
224       SILC_LOG_DEBUG(("Malformed BER: Length is too long"));
225       return FALSE;
226     }
227
228     /* Pointer to data area */
229     *data = (const unsigned char *)ber->data + i;
230   }
231
232   if (identifier_len)
233     *identifier_len = i;
234
235   return TRUE;
236 }
237
238 /* Calculates the length of the encoded BER data object.  This utility
239    function can be used to calculate how much to allocate space before
240    encoding with silc_ber_encode. */
241
242 SilcUInt32 silc_ber_encoded_len(SilcUInt32 tag, SilcUInt32 data_len,
243                                 SilcBool indefinite)
244 {
245   SilcUInt32 len, tmp;
246
247   len = 1;
248   if (tag >= 0x1f) {
249     while (tag) {
250       len++;
251       tag >>= 7;
252     }
253   }
254
255   len++;
256   if (!indefinite) {
257     if (data_len >= 0x80) {
258       tmp = data_len;
259       while (tmp) {
260         len++;
261         tmp >>= 8;
262       }
263     }
264   } else {
265     len += 2;
266   }
267
268   return len + data_len;
269 }