text: /**
   * @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>
   */
   /**
   * Support and FAQ: visit <a href="http://www.atmel.com/design-support/">Atmel Support</a>
   */
  
  #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 */
  