658 lines
16 KiB
C
658 lines
16 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/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 = (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, 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 = (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 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 = (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 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 = (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 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 = (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 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 = (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;
|
|
}
|
|
}
|
|
|
|
#endif /* LWIP_SNMP */
|