lwip/src/apps/snmp/asn1_dec.c
Dirk Ziegelmeier 5f642eb3e3 Completely decouple SNMP stack from lwIP core by using private memory pools;
Move SNMP stack to apps;
API breaking change: Users need to call snmp_init() now!
2015-11-12 21:21:14 +01:00

615 lines
17 KiB
C

/**
* @file
* Abstract Syntax Notation One (ISO 8824, 8825) decoding
*
* @todo not optimised (yet), favor correctness over speed, favor speed over size
*/
/*
* Copyright (c) 2006 Axon Digital Design B.V., The Netherlands.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modification,
* are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
* 3. The name of the author may not be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT
* SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
* OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
* IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
* OF SUCH DAMAGE.
*
* Author: Christiaan Simons <christiaan.simons@axon.tv>
*/
#include "lwip/apps/snmp_opts.h"
#if LWIP_SNMP /* don't build if not configured for use in lwipopts.h */
#include "lwip/apps/snmp_asn1.h"
/**
* Retrieves type field from incoming pbuf chain.
*
* @param p points to a pbuf holding an ASN1 coded type field
* @param ofs points to the offset within the pbuf chain of the ASN1 coded type field
* @param type return ASN1 type
* @return ERR_OK if successful, ERR_ARG if we can't (or won't) decode
*/
err_t
snmp_asn1_dec_type(struct pbuf *p, u16_t ofs, u8_t *type)
{
u16_t plen, base;
u8_t *msg_ptr;
plen = 0;
while (p != NULL) {
base = plen;
plen += p->len;
if (ofs < plen) {
msg_ptr = (u8_t*)p->payload;
msg_ptr += ofs - base;
*type = *msg_ptr;
return ERR_OK;
}
p = p->next;
}
/* p == NULL, ofs >= plen */
return ERR_ARG;
}
/**
* Decodes length field from incoming pbuf chain into host length.
*
* @param p points to a pbuf holding an ASN1 coded length
* @param ofs points to the offset within the pbuf chain of the ASN1 coded length
* @param octets_used returns number of octets used by the length code
* @param length return host order length, up to 64k
* @return ERR_OK if successful, ERR_ARG if we can't (or won't) decode
*/
err_t
snmp_asn1_dec_length(struct pbuf *p, u16_t ofs, u8_t *octets_used, u16_t *length)
{
u16_t plen, base;
u8_t *msg_ptr;
plen = 0;
while (p != NULL) {
base = plen;
plen += p->len;
if (ofs < plen) {
msg_ptr = (u8_t*)p->payload;
msg_ptr += ofs - base;
if (*msg_ptr < 0x80) {
/* primitive definite length format */
*octets_used = 1;
*length = *msg_ptr;
return ERR_OK;
} else if (*msg_ptr == 0x80) {
/* constructed indefinite length format, termination with two zero octets */
u8_t zeros;
u8_t i;
*length = 0;
zeros = 0;
while (zeros != 2) {
i = 2;
while (i > 0) {
i--;
(*length) += 1;
ofs += 1;
if (ofs >= plen) {
/* next octet in next pbuf */
p = p->next;
if (p == NULL) {
return ERR_ARG;
}
msg_ptr = (u8_t*)p->payload;
plen += p->len;
} else {
/* next octet in same pbuf */
msg_ptr++;
}
if (*msg_ptr == 0) {
zeros++;
if (zeros == 2) {
/* stop while (i > 0) */
i = 0;
}
} else {
zeros = 0;
}
}
}
*octets_used = 1;
return ERR_OK;
} else if (*msg_ptr == 0x81) {
/* constructed definite length format, one octet */
ofs += 1;
if (ofs >= plen) {
/* next octet in next pbuf */
p = p->next;
if (p == NULL) {
return ERR_ARG;
}
msg_ptr = (u8_t*)p->payload;
} else {
/* next octet in same pbuf */
msg_ptr++;
}
*length = *msg_ptr;
*octets_used = 2;
return ERR_OK;
} else if (*msg_ptr == 0x82) {
u8_t i;
/* constructed definite length format, two octets */
i = 2;
while (i > 0) {
i--;
ofs += 1;
if (ofs >= plen) {
/* next octet in next pbuf */
p = p->next;
if (p == NULL) {
return ERR_ARG;
}
msg_ptr = (u8_t*)p->payload;
plen += p->len;
} else {
/* next octet in same pbuf */
msg_ptr++;
}
if (i == 0) {
/* least significant length octet */
*length |= *msg_ptr;
} else {
/* most significant length octet */
*length = (*msg_ptr) << 8;
}
}
*octets_used = 3;
return ERR_OK;
} else {
/* constructed definite length format 3..127 octets, this is too big (>64k) */
/** @todo: do we need to accept inefficient codings with many leading zero's? */
*octets_used = 1 + ((*msg_ptr) & 0x7f);
return ERR_ARG;
}
}
p = p->next;
}
/* p == NULL, ofs >= plen */
return ERR_ARG;
}
/**
* Decodes positive integer (counter, gauge, timeticks) into u32_t.
*
* @param p points to a pbuf holding an ASN1 coded integer
* @param ofs points to the offset within the pbuf chain of the ASN1 coded integer
* @param len length of the coded integer field
* @param value return host order integer
* @return ERR_OK if successful, ERR_ARG if we can't (or won't) decode
*
* @note ASN coded integers are _always_ signed. E.g. +0xFFFF is coded
* as 0x00,0xFF,0xFF. Note the leading sign octet. A positive value
* of 0xFFFFFFFF is preceded with 0x00 and the length is 5 octets!!
*/
err_t
snmp_asn1_dec_u32t(struct pbuf *p, u16_t ofs, u16_t len, u32_t *value)
{
u16_t plen, base;
u8_t *msg_ptr;
plen = 0;
while (p != NULL) {
base = plen;
plen += p->len;
if (ofs < plen) {
msg_ptr = (u8_t*)p->payload;
msg_ptr += ofs - base;
if ((len > 0) && (len < 6)) {
/* start from zero */
*value = 0;
if (*msg_ptr & 0x80) {
/* negative, expecting zero sign bit! */
return ERR_ARG;
} else {
/* positive */
if ((len > 1) && (*msg_ptr == 0)) {
/* skip leading "sign byte" octet 0x00 */
len--;
ofs += 1;
if (ofs >= plen) {
/* next octet in next pbuf */
p = p->next;
if (p == NULL) {
return ERR_ARG;
}
msg_ptr = (u8_t*)p->payload;
plen += p->len;
} else {
/* next octet in same pbuf */
msg_ptr++;
}
}
}
/* OR octets with value */
while (len > 1) {
len--;
*value |= *msg_ptr;
*value <<= 8;
ofs += 1;
if (ofs >= plen) {
/* next octet in next pbuf */
p = p->next;
if (p == NULL) {
return ERR_ARG;
}
msg_ptr = (u8_t*)p->payload;
plen += p->len;
} else {
/* next octet in same pbuf */
msg_ptr++;
}
}
*value |= *msg_ptr;
return ERR_OK;
} else {
return ERR_ARG;
}
}
p = p->next;
}
/* p == NULL, ofs >= plen */
return ERR_ARG;
}
/**
* Decodes integer into s32_t.
*
* @param p points to a pbuf holding an ASN1 coded integer
* @param ofs points to the offset within the pbuf chain of the ASN1 coded integer
* @param len length of the coded integer field
* @param value return host order integer
* @return ERR_OK if successful, ERR_ARG if we can't (or won't) decode
*
* @note ASN coded integers are _always_ signed!
*/
err_t
snmp_asn1_dec_s32t(struct pbuf *p, u16_t ofs, u16_t len, s32_t *value)
{
u16_t plen, base;
u8_t *msg_ptr;
#if BYTE_ORDER == LITTLE_ENDIAN
u8_t *lsb_ptr = (u8_t*)value;
#endif
#if BYTE_ORDER == BIG_ENDIAN
u8_t *lsb_ptr = (u8_t*)value + sizeof(s32_t) - 1;
#endif
u8_t sign;
plen = 0;
while (p != NULL) {
base = plen;
plen += p->len;
if (ofs < plen) {
msg_ptr = (u8_t*)p->payload;
msg_ptr += ofs - base;
if ((len > 0) && (len < 5)) {
if (*msg_ptr & 0x80) {
/* negative, start from -1 */
*value = -1;
sign = 1;
} else {
/* positive, start from 0 */
*value = 0;
sign = 0;
}
/* OR/AND octets with value */
while (len > 1) {
len--;
if (sign) {
*lsb_ptr &= *msg_ptr;
*value <<= 8;
*lsb_ptr |= 255;
} else {
*lsb_ptr |= *msg_ptr;
*value <<= 8;
}
ofs += 1;
if (ofs >= plen) {
/* next octet in next pbuf */
p = p->next;
if (p == NULL) {
return ERR_ARG;
}
msg_ptr = (u8_t*)p->payload;
plen += p->len;
} else {
/* next octet in same pbuf */
msg_ptr++;
}
}
if (sign) {
*lsb_ptr &= *msg_ptr;
} else {
*lsb_ptr |= *msg_ptr;
}
return ERR_OK;
} else {
return ERR_ARG;
}
}
p = p->next;
}
/* p == NULL, ofs >= plen */
return ERR_ARG;
}
/**
* Decodes object identifier from incoming message into array of s32_t.
*
* @param p points to a pbuf holding an ASN1 coded object identifier
* @param ofs points to the offset within the pbuf chain of the ASN1 coded object identifier
* @param len length of the coded object identifier
* @param oid return object identifier struct
* @return ERR_OK if successful, ERR_ARG if we can't (or won't) decode
*/
err_t
snmp_asn1_dec_oid(struct pbuf *p, u16_t ofs, u16_t len, struct snmp_obj_id *oid)
{
u16_t plen, base;
u8_t *msg_ptr;
s32_t *oid_ptr;
plen = 0;
while (p != NULL) {
base = plen;
plen += p->len;
if (ofs < plen) {
msg_ptr = (u8_t*)p->payload;
msg_ptr += ofs - base;
oid->len = 0;
oid_ptr = &oid->id[0];
if (len > 0) {
/* first compressed octet */
if (*msg_ptr == 0x2B) {
/* (most) common case 1.3 (iso.org) */
*oid_ptr = 1;
oid_ptr++;
*oid_ptr = 3;
oid_ptr++;
} else if (*msg_ptr < 40) {
*oid_ptr = 0;
oid_ptr++;
*oid_ptr = *msg_ptr;
oid_ptr++;
} else if (*msg_ptr < 80) {
*oid_ptr = 1;
oid_ptr++;
*oid_ptr = (*msg_ptr) - 40;
oid_ptr++;
} else {
*oid_ptr = 2;
oid_ptr++;
*oid_ptr = (*msg_ptr) - 80;
oid_ptr++;
}
oid->len = 2;
} else {
/* accepting zero length identifiers e.g. for
getnext operation. uncommon but valid */
return ERR_OK;
}
len--;
if (len > 0) {
ofs += 1;
if (ofs >= plen) {
/* next octet in next pbuf */
p = p->next;
if (p == NULL) {
return ERR_ARG;
}
msg_ptr = (u8_t*)p->payload;
plen += p->len;
} else {
/* next octet in same pbuf */
msg_ptr++;
}
}
while ((len > 0) && (oid->len < LWIP_SNMP_OBJ_ID_LEN)) {
/* sub-identifier uses multiple octets */
if (*msg_ptr & 0x80) {
s32_t sub_id = 0;
while ((*msg_ptr & 0x80) && (len > 1)) {
len--;
sub_id = (sub_id << 7) + (*msg_ptr & ~0x80);
ofs += 1;
if (ofs >= plen) {
/* next octet in next pbuf */
p = p->next;
if (p == NULL) {
return ERR_ARG;
}
msg_ptr = (u8_t*)p->payload;
plen += p->len;
} else {
/* next octet in same pbuf */
msg_ptr++;
}
}
if (!(*msg_ptr & 0x80) && (len > 0)) {
/* last octet sub-identifier */
len--;
sub_id = (sub_id << 7) + *msg_ptr;
*oid_ptr = sub_id;
}
} else {
/* !(*msg_ptr & 0x80) sub-identifier uses single octet */
len--;
*oid_ptr = *msg_ptr;
}
if (len > 0) {
/* remaining oid bytes available ... */
ofs += 1;
if (ofs >= plen) {
/* next octet in next pbuf */
p = p->next;
if (p == NULL) {
return ERR_ARG;
}
msg_ptr = (u8_t*)p->payload;
plen += p->len;
} else {
/* next octet in same pbuf */
msg_ptr++;
}
}
oid_ptr++;
oid->len++;
}
if (len == 0) {
/* len == 0, end of oid */
return ERR_OK;
} else {
/* len > 0, oid->len == LWIP_SNMP_OBJ_ID_LEN or malformed encoding */
return ERR_ARG;
}
}
p = p->next;
}
/* p == NULL, ofs >= plen */
return ERR_ARG;
}
/**
* Decodes (copies) raw data (ip-addresses, octet strings, opaque encoding)
* from incoming message into array.
*
* @param p points to a pbuf holding an ASN1 coded raw data
* @param ofs points to the offset within the pbuf chain of the ASN1 coded raw data
* @param len length of the coded raw data (zero is valid, e.g. empty string!)
* @param raw_len length of the raw return value
* @param raw return raw bytes
* @return ERR_OK if successful, ERR_ARG if we can't (or won't) decode
*/
err_t
snmp_asn1_dec_raw(struct pbuf *p, u16_t ofs, u16_t len, u16_t raw_len, u8_t *raw)
{
u16_t plen, base;
u8_t *msg_ptr;
if (len > 0) {
plen = 0;
while (p != NULL) {
base = plen;
plen += p->len;
if (ofs < plen) {
msg_ptr = (u8_t*)p->payload;
msg_ptr += ofs - base;
if (raw_len >= len) {
while (len > 1) {
/* copy len - 1 octets */
len--;
*raw = *msg_ptr;
raw++;
ofs += 1;
if (ofs >= plen) {
/* next octet in next pbuf */
p = p->next;
if (p == NULL) {
return ERR_ARG;
}
msg_ptr = (u8_t*)p->payload;
plen += p->len;
} else {
/* next octet in same pbuf */
msg_ptr++;
}
}
/* copy last octet */
*raw = *msg_ptr;
return ERR_OK;
} else {
/* raw_len < len, not enough dst space */
return ERR_ARG;
}
}
p = p->next;
}
/* p == NULL, ofs >= plen */
return ERR_ARG;
} else {
/* len == 0, empty string */
return ERR_OK;
}
}
/**
* Decodes BITS pseudotype value from ASN.1 OctetString.
*
* @note Because BITS pseudo type is encoded as OCTET STRING, it cannot directly
* be encoded/decoded by the agent. Instead call this function as required from
* get/test/set methods.
*
* @param buf points to a buffer holding the ASN1 octet string
* @param buf_len length of octet string
* @param bit_value decoded Bit value with Bit0 == LSB
* @return ERR_OK if successful, ERR_ARG if bit value contains more than 32 bit
*/
err_t
snmp_asn1_dec_bits(const u8_t *buf, u32_t buf_len, u32_t *bit_value)
{
u8_t b;
u8_t bits_processed = 0;
*bit_value = 0;
while (buf_len > 0) {
/* any bit set in this byte? */
if (*buf != 0x00) {
if (bits_processed >= 32) {
/* accept more than 4 bytes, but only when no bits are set */
return ERR_ARG;
}
b = *buf;
do {
if (b & 0x80) {
*bit_value |= (1 << bits_processed);
}
bits_processed++;
b <<= 1;
}
while ((bits_processed % 8) != 0);
} else {
bits_processed += 8;
}
buf_len--;
buf++;
}
return ERR_OK;
}
#endif /* LWIP_SNMP */