The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
/* Copyright (C) 2010-2016 MET Norway */
/* This module is free software; you can redistribute it and/or */
/* modify it under the same terms as Perl itself. */

#include "EXTERN.h"
#include "perl.h"
#include "XSUB.h"

static unsigned char SetFirstBits[] = {
  0,
  0x80, 0xc0, 0xe0, 0xf0,
  0xf8, 0xfc, 0xfe, 0xff
};
static unsigned char SetLastBits[] = {
  0,
  0x01, 0x03, 0x07, 0x0f,
  0x1f, 0x3f, 0x7f, 0xff
};

MODULE = Geo::BUFR              PACKAGE = Geo::BUFR

double
bitstream2dec(unsigned char *bitstream,   \
              int bitpos, int wordlength)

    PROTOTYPE: $$$
    CODE:
        /* Extract wordlength bits from bitstream, starting at bitpos. */
        /* The extracted bits is interpreted as a non negative integer. */
        /* Returns undef if all bits extracted are 1 bits. */

        static unsigned int bitmask[] = {
            0,
            0x00000001, 0x00000003, 0x00000007, 0x0000000f,
            0x0000001f, 0x0000003f, 0x0000007f, 0x000000ff,
            0x000001ff, 0x000003ff, 0x000007ff, 0x00000fff,
            0x00001fff, 0x00003fff, 0x00007fff, 0x0000ffff,
            0x0001ffff, 0x0003ffff, 0x0007ffff, 0x000fffff,
            0x001fffff, 0x003fffff, 0x007fffff, 0x00ffffff,
            0x01ffffff, 0x03ffffff, 0x07ffffff, 0x0fffffff,
            0x1fffffff, 0x3fffffff, 0x7fffffff, 0xffffffff
        };
        int octet = bitpos/8;    /* Which octet the word starts in              */
        int startbit = bitpos & 0x07; /* Offset from start of octet to start of word */
        int bits, lastbits;
        unsigned long word;

        if (wordlength == 0) {
            word = 0;
        } else if (wordlength > 32) {
            /* For now, we restrict ourselves to 32-bit words */
            XSRETURN_UNDEF;
        } else {
            if (wordlength+startbit <= 8) {
                /* Word to be extracted is within a single octet */
                word = bitstream[octet] >> (8-wordlength-startbit);
                word &= bitmask[wordlength];
            } else {
                /* Extract bits in first octet */
                bits = 8-startbit;
                word = bitstream[octet++] & bitmask[bits];
                /* Extract complete octets */
                while (wordlength-bits >= 8) {
                    word = (word << 8) | bitstream[octet++];
                    bits += 8;
                }
                /* Extract remaining bits */
                lastbits = wordlength-bits;
                if (lastbits > 0) {
                    word <<= lastbits;
                    word |= (bitstream[octet] >> (8-lastbits)) & bitmask[lastbits];
                }
            }
            /* If word contains all ones, it is undefined */
            if (word == bitmask[wordlength]) {
                XSRETURN_UNDEF;
            }
        }

        RETVAL = word;

    OUTPUT:
        RETVAL


SV *
bitstream2ascii(unsigned char *bitstream, int bitpos, int len)

    PROTOTYPE: $$$
    CODE:
        /* Extract len bytes from bitstream, starting at bitpos, and */
        /* interpret the extracted bytes as an ascii string. Return */
        /* undef if the extracted bytes are all 1 bits */

        int octet = bitpos/8;
        int lshift = bitpos & 0x07;
        unsigned char str[len+1];
        int rshift, missing, i;
        SV *ascii;

        if (lshift == 0) {
            for (i = 0; i < len; i++)
                str[i] = bitstream[octet+i];
        } else {
            rshift = 8-lshift;
            for (i = 0; i < len; i++) {
                str[i] = (bitstream[octet+i  ] << lshift) |
                         (bitstream[octet+i+1] >> rshift);
            }
        }
        str[len] = '\0';

        /* Check for missing value, i.e, all bits are ones */
        missing = 1;
        for (i = 0; i < len; i++) {
            if (str[i] != 0xff) {
                missing = 0;
            }
        }
        if (missing == 1) {
            XSRETURN_UNDEF;
        }

        ascii = newSVpv((char*)str, len);
        RETVAL = ascii;

    OUTPUT:
        RETVAL


void
dec2bitstream(unsigned long word, \
              unsigned char *bitstream, \
              int bitpos, int wordlength)

    PROTOTYPE: $$$$
    CODE:
        /* Encode non negative integer value word in wordlength bits in bitstream, */
        /* starting at bit bitpos. Last byte will be padded with 1 bits */

  int octet = bitpos/8;    /* Which octet the word should start in */
  int startbit = bitpos & 0x07; /* Offset from start of octet to start of word */
  int num_encodedbits, num_onebits, num_lastbits, i;
  unsigned char lastbyte;

  if (wordlength > 32) {
    /* Data width in table B for numerical data will hopefully never
       exceed 32. Since 'long' in C is assured to be 4 bytes, we are
       not able to encode that big values with present method. */
    exit(1);
  }
  if (wordlength > 0) {
    /* First set the bits after startbit to 0 in first byte of bitstream */
    bitstream[octet] &= SetFirstBits[startbit];
    if (wordlength+startbit <= 32) {
    /* Shift the part of word we want to encode (the last wordlength bits)
       so that it starts at startbit in first byte (will be preceded by 0 bits) */
      word <<= (32-wordlength-startbit);
      /* Then extract first byte, which must be shifted to last byte
         before being assigned to an unsigned char */
      bitstream[octet] |= word >> 24;
      /* Then encode remaining bytes in word, if any */
      num_encodedbits = 8-startbit;
      while (num_encodedbits < wordlength) {
        word <<= 8;
        bitstream[++octet] = word >> 24;
        num_encodedbits += 8;
      }
      /* Finally pad last encoded byte in bitstream with one bits */
      num_onebits = (8 - (startbit + wordlength)) & 0x07;
      bitstream[octet] |= SetLastBits[num_onebits];
    } else {
      /* When aligning word with bitstream[octet], we will in this
         case lose some of the rightmost bits, which we therefore need
         to save first */
      num_lastbits = startbit+wordlength-32;
      lastbyte = word << (8-num_lastbits);
      /* Align word with bitstream[octet] */
      word >>= num_lastbits;
      /* Then extract and encode the bytes in word, which must be
         shifted to last byte before being assigned to an unsigned
         char */
      bitstream[octet++] |= word >> 24;
      word <<= 8;
      for (i=0; i<3; i++) {
        bitstream[octet++] = word >> 24;
        word <<= 8;
      }
      /* Finally encode last bits (which we shifted off from word above),
         padded with one bits */
      bitstream[octet] = lastbyte | SetLastBits[8-num_lastbits];
    }
  }


void
ascii2bitstream(unsigned char *ascii, \
              unsigned char *bitstream, \
              int bitpos, int width)

    PROTOTYPE: $$$$
    CODE:
        /* Encode ASCII string ascii in width bytes in bitstream, starting at */
        /* bit bitpos. Last byte will be padded with 1 bits */

        int octet = bitpos/8;    /* Which octet the word should start in */
        int startbit = bitpos & 0x07; /* Offset from start of octet to start of word */
        int lshift, i;

        if (width > 0) {
          if (startbit == 0) {
            /* The easy case: just copy byte for byte */
            for (i = 0; i < width; i++)
              bitstream[octet+i] = ascii[i];
          } else {
            lshift = 8-startbit;
            /* First byte should be first startbit bits of first bitstream byte,
               then first 8-startbit bits of first byte of ascii byte */
            bitstream[octet] = (bitstream[octet] & SetFirstBits[startbit]) |
              (ascii[0] >> startbit);
            /* Next bytes should be last startbit bits of previous ascii byte,
               then first 8-startbit bits of next ascii byte */
            for (i = 1; i < width; i++)
                bitstream[octet+i] = (ascii[i-1] << lshift) | (ascii[i] >> startbit);
            /* Last byte should be remaining startbit bits of last ascii byte,
               padded with 8-startbit one bits */
            bitstream[octet+width] = (ascii[width-1] << lshift) | SetLastBits[8-startbit];
          }
        }


void
null2bitstream(unsigned char *bitstream, \
              int bitpos, int wordlength)

    PROTOTYPE: $$$$
    CODE:
        /* Set wordlength bits in bitstream starting at bit bitpos to 0 */
        /* bits. Last byte will be padded with 1 bits */

        int octet = bitpos/8;    /* Which octet the word should start in */
        int startbit = bitpos & 0x07; /* Offset from start of octet to start of word */
        int bits, num_onebits;

        if (wordlength > 0) {
          /* First set the bits after startbit to 0 in first byte of bitstream */
          bitstream[octet] &= SetFirstBits[startbit];
          bits = 8 - startbit;
          while (wordlength-bits > 0) {
            bitstream[++octet] = 0x00;
            bits += 8;
          }
          /* Finally pad last encoded byte in bitstream with one bits */
          num_onebits = (8 - (startbit + wordlength)) & 0x07;
          bitstream[octet] |= SetLastBits[num_onebits];
        }