blob: 87288ac064ba193c0d7fb26dbef19875b3268cf2 [file] [log] [blame]
/**
* @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/opt.h"
#if LWIP_SNMP /* don't build if not configured for use in lwipopts.h */
#include "lwip/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 successfull, 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 = 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, upto 64k
* @return ERR_OK if successfull, 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 = 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 = 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 = 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 = 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 successfull, 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 = 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 = 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 = 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 successfull, 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 = 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 = 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 successfull, 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 = 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 = 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 = 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 = 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 successfull, 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 = 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 = 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;
}
}
#endif /* LWIP_SNMP */