aboutsummaryrefslogtreecommitdiffstats
path: root/pb_encode.c
blob: 2e740347f0b155556fc1fe25b24e698081f071c9 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
/* pb_encode.c -- encode a protobuf using minimal resources
 *
 * 2011 Petteri Aimonen <jpa@kapsi.fi>
 */

#include "pb.h"
#include "pb_encode.h"
#include <string.h>

typedef bool (*pb_encoder_t)(pb_ostream_t *stream, const pb_field_t *field, const void *src);

/* --- Function pointers to field encoders ---
 * Order in the array must match pb_action_t LTYPE numbering.
 */
static const pb_encoder_t PB_ENCODERS[PB_LTYPES_COUNT] = {
    &pb_enc_varint,
    &pb_enc_svarint,
    &pb_enc_fixed,
    
    &pb_enc_bytes,
    &pb_enc_string,
    &pb_enc_submessage
};

/* pb_ostream_t implementation */

static bool buf_write(pb_ostream_t *stream, const uint8_t *buf, size_t count)
{
    uint8_t *dest = (uint8_t*)stream->state;
    memcpy(dest, buf, count);
    stream->state = dest + count;
    return true;
}

pb_ostream_t pb_ostream_from_buffer(uint8_t *buf, size_t bufsize)
{
    pb_ostream_t stream;
    stream.callback = &buf_write;
    stream.state = buf;
    stream.max_size = bufsize;
    stream.bytes_written = 0;
    return stream;
}

bool pb_write(pb_ostream_t *stream, const uint8_t *buf, size_t count)
{
    if (stream->callback != NULL)
    {
        if (stream->bytes_written + count > stream->max_size)
            return false;
        
        if (!stream->callback(stream, buf, count))
            return false;
    }
    
    stream->bytes_written += count;
    return true;
}

/* Main encoding stuff */

static bool encode_array(pb_ostream_t *stream, const pb_field_t *field,
                         const void *pData, size_t count, pb_encoder_t func)
{
    int i;
    const void *p;
    size_t size;
    
    if (PB_LTYPE(field->type) < PB_LTYPE_LAST_PACKABLE)
    {
        if (!pb_encode_tag(stream, PB_WT_STRING, field->tag))
            return false;
        
        /* Determine the total size of packed array. */
        if (PB_LTYPE(field->type) == PB_LTYPE_FIXED)
        {
            size = field->data_size * count;
        }
        else
        {
            pb_ostream_t sizestream = {0};
            p = pData;
            for (i = 0; i < count; i++)
            {
                if (!func(&sizestream, field, p))
                    return false;
                p = (const char*)p + field->data_size;
            }
            size = sizestream.bytes_written;
        }
        
        pb_encode_varint(stream, size);
        
        if (stream->callback == NULL)
            return pb_write(stream, NULL, size); /* Just sizing.. */
        
        /* Write the data */
        p = pData;
        for (i = 0; i < count; i++)
        {
            if (!func(stream, field, p))
                return false;
            p = (const char*)p + field->data_size;
        }
    }
    else
    {
        p = pData;
        for (i = 0; i < count; i++)
        {
            if (!pb_encode_tag_for_field(stream, field))
                return false;
            if (!func(stream, field, p))
                return false;
            p = (const char*)p + field->data_size;
        }
    }
    
    return true;
}

bool pb_encode(pb_ostream_t *stream, const pb_field_t fields[], const void *src_struct)
{
    const pb_field_t *field = fields;
    const void *pData = src_struct;
    const void *pSize;
    size_t prev_size = 0;
    
    while (field->tag != 0)
    {
        pData = (const char*)pData + prev_size + field->data_offset;
        pSize = (const char*)pData + field->size_offset;
        
        prev_size = field->data_size;
        if (PB_HTYPE(field->type) == PB_HTYPE_ARRAY)
            prev_size *= field->array_size;
        
        pb_encoder_t func = PB_ENCODERS[PB_LTYPE(field->type)];
        
        switch (PB_HTYPE(field->type))
        {
            case PB_HTYPE_REQUIRED:
                if (!pb_encode_tag_for_field(stream, field))
                    return false;
                if (!func(stream, field, pData))
                    return false;
                break;
            
            case PB_HTYPE_OPTIONAL:
                if (*(bool*)pSize)
                {
                    if (!pb_encode_tag_for_field(stream, field))
                        return false;
                
                    if (!func(stream, field, pData))
                        return false;
                }
                break;
            
            case PB_HTYPE_ARRAY:
                if (!encode_array(stream, field, pData, *(size_t*)pSize, func))
                    return false;
                break;
            
            case PB_HTYPE_CALLBACK:
            {
                pb_callback_t *callback = (pb_callback_t*)pData;
                if (callback->funcs.encode != NULL)
                {
                    if (!callback->funcs.encode(stream, field, callback->arg))
                        return false;
                }
                break;
            }
        }
    
        field++;
    }
    
    return true;
}

/* Helper functions */
bool pb_encode_varint(pb_ostream_t *stream, uint64_t value)
{
    uint8_t buffer[10];
    int i = 0;
    
    if (value == 0)
        return pb_write(stream, (uint8_t*)&value, 1);
    
    while (value)
    {
        buffer[i] = (value & 0x7F) | 0x80;
        value >>= 7;
        i++;
    }
    buffer[i-1] &= 0x7F; /* Unset top bit on last byte */
    
    return pb_write(stream, buffer, i);
}

bool pb_encode_tag(pb_ostream_t *stream, pb_wire_type_t wiretype, int field_number)
{
    int tag = wiretype | (field_number << 3);
    return pb_encode_varint(stream, tag);
}

bool pb_encode_tag_for_field(pb_ostream_t *stream, const pb_field_t *field)
{
    pb_wire_type_t wiretype;
    switch (PB_LTYPE(field->type))
    {
        case PB_LTYPE_VARINT:
        case PB_LTYPE_SVARINT:
            wiretype = PB_WT_VARINT;
            break;
        
        case PB_LTYPE_FIXED:
            if (field->data_size == 4)
                wiretype = PB_WT_32BIT;
            else if (field->data_size == 8)
                wiretype = PB_WT_64BIT;
            else
                return false;
            break;
        
        case PB_LTYPE_BYTES:
        case PB_LTYPE_STRING:
        case PB_LTYPE_SUBMESSAGE:
            wiretype = PB_WT_STRING;
            break;
        
        default:
            return false;
    }
    
    return pb_encode_tag(stream, wiretype, field->tag);
}

bool pb_encode_string(pb_ostream_t *stream, const uint8_t *buffer, size_t size)
{
    if (!pb_encode_varint(stream, size))
        return false;
    
    return pb_write(stream, buffer, size);
}

/* Field encoders */

/* Copy srcsize bytes from src so that values are casted properly.
 * On little endian machine, copy to start of dest
 * On big endian machine, copy to end of dest
 * destsize must always be larger than srcsize
 * 
 * Note: This is the reverse of the endian_copy in pb_decode.c.
 */
static void endian_copy(void *dest, const void *src, size_t destsize, size_t srcsize)
{
#ifdef __BIG_ENDIAN__
    memcpy((char*)dest + (destsize - srcsize), src, srcsize);
#else
    memcpy(dest, src, srcsize);
#endif
}

bool pb_enc_varint(pb_ostream_t *stream, const pb_field_t *field, const void *src)
{
    uint64_t value = 0;
    endian_copy(&value, src, sizeof(value), field->data_size);
    return pb_encode_varint(stream, value);
}

bool pb_enc_svarint(pb_ostream_t *stream, const pb_field_t *field, const void *src)
{
    uint64_t value = 0;
    uint64_t zigzagged;
    uint64_t signbitmask, xormask;
    endian_copy(&value, src, sizeof(value), field->data_size);
    
    signbitmask = (uint64_t)0x80 << (field->data_size * 8 - 8);
    xormask = ((uint64_t)-1) >> (64 - field->data_size * 8);
    if (value & signbitmask)
        zigzagged = ((value ^ xormask) << 1) | 1;
    else
        zigzagged = value << 1;
    
    return pb_encode_varint(stream, zigzagged);
}

bool pb_enc_fixed(pb_ostream_t *stream, const pb_field_t *field, const void *src)
{
    #ifdef __BIG_ENDIAN__
    uint8_t bytes[8] = {0};
    endian_copy(bytes, src, sizeof(bytes), field->data_size);
    uint8_t lebytes[8] = {bytes[7], bytes[6], bytes[5], bytes[4], 
                          bytes[3], bytes[2], bytes[1], bytes[0]};
    return pb_write(stream, lebytes, field->data_size);
    #else
    return pb_write(stream, (uint8_t*)src, field->data_size);
    #endif
}

bool pb_enc_bytes(pb_ostream_t *stream, const pb_field_t *field, const void *src)
{
    pb_bytes_array_t *bytes = (pb_bytes_array_t*)src;
    return pb_encode_string(stream, bytes->bytes, bytes->size);
}

bool pb_enc_string(pb_ostream_t *stream, const pb_field_t *field, const void *src)
{
    return pb_encode_string(stream, (uint8_t*)src, strlen((char*)src));
}

bool pb_enc_submessage(pb_ostream_t *stream, const pb_field_t *field, const void *src)
{
    pb_ostream_t substream = {0};
    size_t size;
    bool status;
    
    if (field->ptr == NULL)
        return false;
    
    if (!pb_encode(&substream, (pb_field_t*)field->ptr, src))
        return false;
    
    size = substream.bytes_written;
    
    if (!pb_encode_varint(stream, size))
        return false;
    
    if (stream->callback == NULL)
        return pb_write(stream, NULL, size); /* Just sizing */
    
    if (stream->bytes_written + size > stream->max_size)
        return false;
        
    /* Use a substream to verify that a callback doesn't write more than
     * what it did the first time. */
    substream.callback = stream->callback;
    substream.state = stream->state;
    substream.max_size = size;
    substream.bytes_written = 0;
    
    status = pb_encode(&substream, (pb_field_t*)field->ptr, src);
    
    stream->bytes_written += substream.bytes_written;
    
    if (substream.bytes_written != size)
        return false;
    
    return status;
}