/** | |
* @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 | |
#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; | |
u8_t *lsb_ptr = (u8_t*)value; | |
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 */ | |