/* * include/haproxy/protobuf.h * This file contains functions and macros declarations for protocol buffers decoding. * * Copyright 2012 Willy Tarreau * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation, version 2.1 * exclusively. * * This library 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef _HAPROXY_PROTOBUF_H #define _HAPROXY_PROTOBUF_H #include #include #include #include #define PBUF_VARINT_DONT_STOP_BIT 7 #define PBUF_VARINT_DONT_STOP_BITMASK (1 << PBUF_VARINT_DONT_STOP_BIT) #define PBUF_VARINT_DATA_BITMASK ~PBUF_VARINT_DONT_STOP_BITMASK /* .skip and .smp_store prototypes. */ int protobuf_skip_varint(unsigned char **pos, size_t *len, size_t vlen); int protobuf_smp_store_varint(struct sample *smp, int type, unsigned char *pos, size_t len, size_t vlen); int protobuf_skip_64bit(unsigned char **pos, size_t *len, size_t vlen); int protobuf_smp_store_64bit(struct sample *smp, int type, unsigned char *pos, size_t len, size_t vlen); int protobuf_skip_vlen(unsigned char **pos, size_t *len, size_t vlen); int protobuf_smp_store_vlen(struct sample *smp, int type, unsigned char *pos, size_t len, size_t vlen); int protobuf_skip_32bit(unsigned char **pos, size_t *len, size_t vlen); int protobuf_smp_store_32bit(struct sample *smp, int type, unsigned char *pos, size_t len, size_t vlen); struct protobuf_parser_def protobuf_parser_defs [] = { [PBUF_TYPE_VARINT ] = { .skip = protobuf_skip_varint, .smp_store = protobuf_smp_store_varint, }, [PBUF_TYPE_64BIT ] = { .skip = protobuf_skip_64bit, .smp_store = protobuf_smp_store_64bit, }, [PBUF_TYPE_LENGTH_DELIMITED] = { .skip = protobuf_skip_vlen, .smp_store = protobuf_smp_store_vlen, }, [PBUF_TYPE_START_GROUP ] = { /* XXX Deprecated XXX */ }, [PBUF_TYPE_STOP_GROUP ] = { /* XXX Deprecated XXX */ }, [PBUF_TYPE_32BIT ] = { .skip = protobuf_skip_32bit, .smp_store = protobuf_smp_store_32bit, }, }; /* * Note that the field values with protocol buffers 32bit and 64bit fixed size as type * are sent in little-endian byte order to the network. */ /* Convert a little-endian ordered 32bit integer to the byte order of the host. */ static inline uint32_t pbuf_le32toh(uint32_t v) { uint8_t *p = (uint8_t *)&v; return (p[0] | (p[1] << 8) | (p[2] << 16) | (p[3] << 24)); } /* Convert a little-endian ordered 64bit integer to the byte order of the host. */ static inline uint64_t pbuf_le64toh(uint64_t v) { return (uint64_t)(pbuf_le32toh(v >> 32)) << 32 | pbuf_le32toh(v); } /* * Return a protobuf type enum from string if succeeded, -1 if not. */ int protobuf_type(const char *s) { /* varint types. */ if (strcmp(s, "int32") == 0) return PBUF_T_VARINT_INT32; else if (strcmp(s, "uint32") == 0) return PBUF_T_VARINT_UINT32; else if (strcmp(s, "sint32") == 0) return PBUF_T_VARINT_SINT32; else if (strcmp(s, "int64") == 0) return PBUF_T_VARINT_INT64; else if (strcmp(s, "uint64") == 0) return PBUF_T_VARINT_UINT64; else if (strcmp(s, "sint64") == 0) return PBUF_T_VARINT_SINT64; else if (strcmp(s, "bool") == 0) return PBUF_T_VARINT_BOOL; else if (strcmp(s, "enum") == 0) return PBUF_T_VARINT_ENUM; /* 32bit fixed size types. */ else if (strcmp(s, "fixed32") == 0) return PBUF_T_32BIT_FIXED32; else if (strcmp(s, "sfixed32") == 0) return PBUF_T_32BIT_SFIXED32; else if (strcmp(s, "float") == 0) return PBUF_T_32BIT_FLOAT; /* 64bit fixed size types. */ else if (strcmp(s, "fixed64") == 0) return PBUF_T_64BIT_FIXED64; else if (strcmp(s, "sfixed64") == 0) return PBUF_T_64BIT_SFIXED64; else if (strcmp(s, "double") == 0) return PBUF_T_64BIT_DOUBLE; else return -1; } /* * Decode a protocol buffers varint located in a buffer at address with * as length. The decoded value is stored at . * Returns 1 if succeeded, 0 if not. */ static inline int protobuf_varint(uint64_t *val, unsigned char *pos, size_t len) { unsigned int shift; *val = 0; shift = 0; while (len > 0) { int stop = !(*pos & PBUF_VARINT_DONT_STOP_BITMASK); *val |= ((uint64_t)(*pos & PBUF_VARINT_DATA_BITMASK)) << shift; ++pos; --len; if (stop) break; else if (!len) return 0; shift += 7; /* The maximum length in bytes of a 64-bit encoded value is 10. */ if (shift > 63) return 0; } return 1; } /* * Decode a protocol buffers varint located in a buffer at offset address with * as length address. Update and consequently. Decrease <*len> * by the number of decoded bytes. The decoded value is stored at . * Returns 1 if succeeded, 0 if not. */ static inline int protobuf_decode_varint(uint64_t *val, unsigned char **pos, size_t *len) { unsigned int shift; *val = 0; shift = 0; while (*len > 0) { int stop = !(**pos & PBUF_VARINT_DONT_STOP_BITMASK); *val |= ((uint64_t)**pos & PBUF_VARINT_DATA_BITMASK) << shift; ++*pos; --*len; if (stop) break; else if (!*len) return 0; shift += 7; /* The maximum length in bytes of a 64-bit encoded value is 10. */ if (shift > 63) return 0; } return 1; } /* * Skip a protocol buffer varint found at as position address with * as available length address. Update <*pos> to make it point to the next * available byte. Decrease <*len> by the number of skipped bytes. * Returns 1 if succeeded, 0 if not. */ int protobuf_skip_varint(unsigned char **pos, size_t *len, size_t vlen) { unsigned int shift; shift = 0; while (*len > 0) { int stop = !(**pos & PBUF_VARINT_DONT_STOP_BITMASK); ++*pos; --*len; if (stop) break; else if (!*len) return 0; shift += 7; /* The maximum length in bytes of a 64-bit encoded value is 10. */ if (shift > 63) return 0; } return 1; } /* * If succeeded, return the length of a prococol buffers varint found at as * position address, with as address of the available bytes at <*pos>. * Update <*pos> to make it point to the next available byte. Decrease <*len> * by the number of bytes used to encode this varint. * Return -1 if failed. */ static inline int protobuf_varint_getlen(unsigned char *pos, size_t len) { unsigned char *spos; unsigned int shift; shift = 0; spos = pos; while (len > 0) { int stop = !(*pos & PBUF_VARINT_DONT_STOP_BITMASK); ++pos; --len; if (stop) break; else if (!len) return -1; shift += 7; /* The maximum length in bytes of a 64-bit encoded value is 10. */ if (shift > 63) return -1; } return pos - spos; } /* * Store a varint field value in a sample from buffer * with available bytes after having decoded it if needed * depending on the expected protocol buffer type of the field. * Return 1 if succeeded, 0 if not. */ int protobuf_smp_store_varint(struct sample *smp, int type, unsigned char *pos, size_t len, size_t vlen) { switch (type) { case PBUF_T_BINARY: { int varint_len; varint_len = protobuf_varint_getlen(pos, len); if (varint_len == -1) return 0; smp->data.type = SMP_T_BIN; smp->data.u.str.area = (char *)pos; smp->data.u.str.data = varint_len; smp->flags = SMP_F_VOL_TEST; break; } case PBUF_T_VARINT_INT32 ... PBUF_T_VARINT_ENUM: { uint64_t varint; if (!protobuf_varint(&varint, pos, len)) return 0; smp->data.u.sint = varint; smp->data.type = SMP_T_SINT; break; } case PBUF_T_VARINT_SINT32 ... PBUF_T_VARINT_SINT64: { uint64_t varint; if (!protobuf_varint(&varint, pos, len)) return 0; /* zigzag decoding. */ smp->data.u.sint = (varint >> 1) ^ -(varint & 1); smp->data.type = SMP_T_SINT; break; } default: return 0; } return 1; } /* * Move forward <*pos> buffer by 8 bytes. Used to skip a 64bit field. */ int protobuf_skip_64bit(unsigned char **pos, size_t *len, size_t vlen) { if (*len < sizeof(uint64_t)) return 0; *pos += sizeof(uint64_t); *len -= sizeof(uint64_t); return 1; } /* * Store a fixed size 64bit field value in a sample from buffer * with available bytes after having decoded it depending on * the expected protocol buffer type of the field. * Return 1 if succeeded, 0 if not. */ int protobuf_smp_store_64bit(struct sample *smp, int type, unsigned char *pos, size_t len, size_t vlen) { if (len < sizeof(uint64_t)) return 0; switch (type) { case PBUF_T_BINARY: smp->data.type = SMP_T_BIN; smp->data.u.str.area = (char *)pos; smp->data.u.str.data = sizeof(uint64_t); smp->flags = SMP_F_VOL_TEST; break; case PBUF_T_64BIT_FIXED64: case PBUF_T_64BIT_SFIXED64: smp->data.type = SMP_T_SINT; smp->data.u.sint = pbuf_le64toh(read_u64(pos)); smp->flags = SMP_F_VOL_TEST; break; case PBUF_T_64BIT_DOUBLE: smp->data.type = SMP_T_SINT; smp->data.u.sint = pbuf_le64toh(read_dbl(pos)); smp->flags = SMP_F_VOL_TEST; break; default: return 0; } return 1; } /* * Move forward <*pos> buffer by bytes. Use to skip a length-delimited * field. */ int protobuf_skip_vlen(unsigned char **pos, size_t *len, size_t vlen) { if (*len < vlen) return 0; *pos += vlen; *len -= vlen; return 1; } /* * Store a -bytes length-delimited field value in a sample from * buffer with available bytes. * Return 1 if succeeded, 0 if not. */ int protobuf_smp_store_vlen(struct sample *smp, int type, unsigned char *pos, size_t len, size_t vlen) { if (len < vlen) return 0; if (type != PBUF_T_BINARY) return 0; smp->data.type = SMP_T_BIN; smp->data.u.str.area = (char *)pos; smp->data.u.str.data = vlen; smp->flags = SMP_F_VOL_TEST; return 1; } /* * Move forward <*pos> buffer by 4 bytes. Used to skip a 32bit field. */ int protobuf_skip_32bit(unsigned char **pos, size_t *len, size_t vlen) { if (*len < sizeof(uint32_t)) return 0; *pos += sizeof(uint32_t); *len -= sizeof(uint32_t); return 1; } /* * Store a fixed size 32bit field value in a sample from buffer * with available bytes after having decoded it depending on * the expected protocol buffer type of the field. * Return 1 if succeeded, 0 if not. */ int protobuf_smp_store_32bit(struct sample *smp, int type, unsigned char *pos, size_t len, size_t vlen) { if (len < sizeof(uint32_t)) return 0; switch (type) { case PBUF_T_BINARY: smp->data.type = SMP_T_BIN; smp->data.u.str.area = (char *)pos; smp->data.u.str.data = sizeof(uint32_t); smp->flags = SMP_F_VOL_TEST; break; case PBUF_T_32BIT_FIXED32: smp->data.type = SMP_T_SINT; smp->data.u.sint = pbuf_le32toh(read_u32(pos)); smp->flags = SMP_F_VOL_TEST; break; case PBUF_T_32BIT_SFIXED32: smp->data.type = SMP_T_SINT; smp->data.u.sint = (int32_t)pbuf_le32toh(read_u32(pos)); smp->flags = SMP_F_VOL_TEST; break; case PBUF_T_32BIT_FLOAT: smp->data.type = SMP_T_SINT; smp->data.u.sint = pbuf_le32toh(read_flt(pos)); smp->flags = SMP_F_VOL_TEST; break; default: return 0; } return 1; } /* * Lookup for a protocol buffers field whose parameters are provided by * first argument in the buffer with as address and as length address. * If found, store its value depending on the type of storage to use provided by * second argument and return 1, 0 if not. */ static inline int protobuf_field_lookup(const struct arg *arg_p, struct sample *smp, unsigned char **pos, size_t *len) { unsigned int *fid; size_t fid_sz; int type; uint64_t elen; int field; fid = arg_p[0].data.fid.ids; fid_sz = arg_p[0].data.fid.sz; type = arg_p[1].data.sint; /* Length of the length-delimited messages if any. */ elen = 0; field = 0; while (field < fid_sz) { int found; uint64_t key, sleft; struct protobuf_parser_def *pbuf_parser = NULL; unsigned int wire_type, field_number; if ((ssize_t)*len <= 0) return 0; /* Remaining bytes saving. */ sleft = *len; /* Key decoding */ if (!protobuf_decode_varint(&key, pos, len)) return 0; wire_type = key & 0x7; field_number = key >> 3; found = field_number == fid[field]; /* Skip the data if the current field does not match. */ switch (wire_type) { case PBUF_TYPE_VARINT: case PBUF_TYPE_32BIT: case PBUF_TYPE_64BIT: pbuf_parser = &protobuf_parser_defs[wire_type]; if (!found && !pbuf_parser->skip(pos, len, 0)) return 0; break; case PBUF_TYPE_LENGTH_DELIMITED: /* Decode the length of this length-delimited field. */ if (!protobuf_decode_varint(&elen, pos, len) || elen > *len) return 0; /* The size of the current field is computed from here to skip * the bytes used to encode the previous length.* */ sleft = *len; pbuf_parser = &protobuf_parser_defs[wire_type]; if (!found && !pbuf_parser->skip(pos, len, elen)) return 0; break; default: return 0; } /* Store the data if found. Note that is not NULL */ if (found && field == fid_sz - 1) return pbuf_parser->smp_store(smp, type, *pos, *len, elen); if ((ssize_t)(elen) > 0) elen -= sleft - *len; if (found) { field++; } else if ((ssize_t)elen <= 0) { field = 0; } } return 0; } #endif /* _HAPROXY_PROTOBUF_H */ /* * Local variables: * c-indent-level: 8 * c-basic-offset: 8 * End: */