Blob Blame History Raw
/*
 * libopenraw - ifdfile.cpp
 *
 * Copyright (C) 2006-2017 Hubert Figuière
 * Copyright (C) 2008 Novell, Inc.
 *
 * This library is free software: you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public License
 * as published by the Free Software Foundation, either version 3 of
 * the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library.  If not, see
 * <http://www.gnu.org/licenses/>.
 */

#include <stddef.h>

#include <algorithm>
#include <cstdint>
#include <exception>
#include <memory>
#include <numeric>
#include <string>

#include <libopenraw/consts.h>
#include <libopenraw/debug.h>
#include <libopenraw/metadata.h>

#include "bitmapdata.hpp"
#include "rawfile.hpp"
#include "rawdata.hpp"
#include "trace.hpp"
#include "io/stream.hpp"
#include "io/streamclone.hpp"
#include "ifd.hpp"
#include "ifdentry.hpp"
#include "ifdfile.hpp"
#include "ifdfilecontainer.hpp"
#include "jfifcontainer.hpp"
#include "rawfile_private.hpp"
#include "neffile.hpp" // I wonder if this is smart as it break the abstraction.
#include "unpack.hpp"

namespace OpenRaw {

class MetaValue;

namespace Internals {


IfdFile::IfdFile(const IO::Stream::Ptr &s, Type _type,
                 bool instantiateContainer)
  : RawFile(_type),
    m_io(s),
    m_container(nullptr)
{
  if(instantiateContainer) {
    m_container = new IfdFileContainer(m_io, 0);
  }
}

IfdFile::~IfdFile()
{
  delete m_container;
}

// this one seems to be pretty much the same for all the
// IFD based raw files
IfdDir::Ref  IfdFile::_locateExifIfd()
{
	const IfdDir::Ref & _mainIfd = mainIfd();
  if (!_mainIfd) {
    LOGERR("IfdFile::_locateExifIfd() main IFD not found\n");
    return IfdDir::Ref();
  }
  return _mainIfd->getExifIFD();
}

MakerNoteDir::Ref  IfdFile::_locateMakerNoteIfd()
{
	const IfdDir::Ref & _exifIfd = exifIfd();
	if(_exifIfd) {
		// to not have a recursive declaration, getMakerNoteIfd() return an IfdDir.
		return std::dynamic_pointer_cast<MakerNoteDir>(_exifIfd->getMakerNoteIfd());
	}
	return MakerNoteDir::Ref();
}

void IfdFile::_identifyId()
{
	const IfdDir::Ref & _mainIfd = mainIfd();
  if (!_mainIfd) {
    LOGERR("Main IFD not found to identify the file.\n");
    return;
  }

  auto make = _mainIfd->getValue<std::string>(IFD::EXIF_TAG_MAKE);
  auto model = _mainIfd->getValue<std::string>(IFD::EXIF_TAG_MODEL);
  if (make.ok() && model.ok()) {
    _setTypeId(_typeIdFromModel(make.unwrap(), model.unwrap()));
  }
}



::or_error
IfdFile::_enumThumbnailSizes(std::vector<uint32_t> &list)
{
  ::or_error err = OR_ERROR_NONE;

  LOGDBG1("_enumThumbnailSizes()\n");
  std::vector<IfdDir::Ref> & dirs = m_container->directories();

  LOGDBG1("num of dirs %lu\n", dirs.size());
  for(auto dir : dirs)
  {
    dir->load();
    or_error ret = _locateThumbnail(dir, list);
    if (ret == OR_ERROR_NONE) {
      LOGDBG1("Found %u pixels\n", list.back());
    }
    auto result = dir->getSubIFDs();
    if (result.ok()) {
      std::vector<IfdDir::Ref> subdirs = result.unwrap();
      LOGDBG1("Iterating subdirs\n");
      for(auto dir2 : subdirs)
      {
        dir2->load();
        ret = _locateThumbnail(dir2, list);
        if (ret == OR_ERROR_NONE) {
          LOGDBG1("Found %u pixels\n", list.back());
        }
      }
    }
  }
  if (list.size() <= 0) {
    err = OR_ERROR_NOT_FOUND;
  }
  return err;
}


::or_error IfdFile::_locateThumbnail(const IfdDir::Ref & dir,
                                     std::vector<uint32_t> &list)
{
  ::or_error ret = OR_ERROR_NOT_FOUND;
  ::or_data_type _type = OR_DATA_TYPE_NONE;
  uint32_t subtype = 0;

  LOGDBG1("_locateThumbnail\n");

  auto result = dir->getValue<uint32_t>(IFD::EXIF_TAG_NEW_SUBFILE_TYPE);
  if (result.empty()) {
    if(!m_cfaIfd) {
      m_cfaIfd = _locateCfaIfd();
    }
    if(m_cfaIfd == dir) {
      return OR_ERROR_NOT_FOUND;
    }
    else {
      subtype = 1;
    }
  } else {
    subtype = result.unwrap();
  }
  LOGDBG1("subtype %u\n", subtype);
  if (subtype == 1) {

    uint16_t photom_int =
      dir->getValue<uint16_t>(IFD::EXIF_TAG_PHOTOMETRIC_INTERPRETATION).unwrap_or(IFD::EV_PI_RGB);

    uint32_t x = dir->getIntegerValue(IFD::EXIF_TAG_IMAGE_WIDTH).unwrap_or(0);
    uint32_t y = dir->getIntegerValue(IFD::EXIF_TAG_IMAGE_LENGTH).unwrap_or(0);

    uint16_t compression = dir->getValue<uint16_t>(IFD::EXIF_TAG_COMPRESSION).unwrap_or(0);

    uint32_t offset = 0;
    uint32_t byte_count = dir->getValue<uint32_t>(IFD::EXIF_TAG_STRIP_BYTE_COUNTS).unwrap_or(0);

    result = dir->getValue<uint32_t>(IFD::EXIF_TAG_STRIP_OFFSETS);
    bool got_it = result.ok();
    if (result.ok()) {
      offset = result.unwrap();
    }
    if (!got_it || (compression == 6) || (compression == 7)) {
      if (!got_it) {
        byte_count =
          dir->getValue<uint32_t>(IFD::EXIF_TAG_JPEG_INTERCHANGE_FORMAT_LENGTH).unwrap_or(0);
        result = dir->getValue<uint32_t>(IFD::EXIF_TAG_JPEG_INTERCHANGE_FORMAT);
        got_it = result.ok();
        if (got_it) {
          offset = result.unwrap();
        }
      }
      if (got_it) {
        // workaround for CR2 files where 8RGB data is marked
        // as JPEG. Check the real data size.
        if(x && y) {
          if(byte_count >= (x * y * 3)) {
            //_type = OR_DATA_TYPE_PIXMAP_8RGB;
            _type = OR_DATA_TYPE_NONE;
            // See bug 72270
            LOGDBG1("8RGB as JPEG. Will ignore.\n");
            ret = OR_ERROR_INVALID_FORMAT;
          }
          else {
            _type = OR_DATA_TYPE_JPEG;
          }
        }
        else {
          _type = OR_DATA_TYPE_JPEG;
          LOGDBG1("looking for JPEG at %u\n", offset);
          if (x == 0 || y == 0) {
            IO::Stream::Ptr s(std::make_shared<IO::StreamClone>(
                                m_io, offset));
            std::unique_ptr<JfifContainer> jfif(new JfifContainer(s, 0));
            if (jfif->getDimensions(x,y)) {
              LOGDBG1("JPEG dimensions x=%u y=%u\n", x, y);
            }
            else {
              _type = OR_DATA_TYPE_NONE;
              LOGWARN("Couldn't get JPEG dimensions.\n");
            }
          }
          else {
            LOGDBG1("JPEG (supposed) dimensions x=%u y=%u\n", x, y);
          }
        }

      }
    }
    else if (photom_int == IFD::EV_PI_YCBCR) {
      LOGWARN("Unsupported YCbCr photometric interpretation in non JPEG.\n");
      ret = OR_ERROR_INVALID_FORMAT;
    }
    else {
      LOGDBG1("found strip offsets\n");
      if (x != 0 && y != 0) {
        // See bug 72270 - some CR2 have 16 bpc RGB thumbnails.
        // by default it is RGB8. Unless stated otherwise.
        bool isRGB8 = true;
        IfdEntry::Ref entry = dir->getEntry(IFD::EXIF_TAG_BITS_PER_SAMPLE);
        auto result2 = entry->getArray<uint16_t>();
        if (result2.ok()) {
          std::vector<uint16_t> arr = result2.unwrap();
          for(auto bpc : arr) {
            isRGB8 = (bpc == 8);
            if (!isRGB8) {
              LOGDBG1("bpc != 8, not RGB8 %u\n", bpc);
              break;
            }
          }
        } else {
          LOGDBG1("Error getting BPS\n");
        }
        if (isRGB8) {
          _type = OR_DATA_TYPE_PIXMAP_8RGB;
        }
      }
    }
    if(_type != OR_DATA_TYPE_NONE) {
      uint32_t dim = std::max(x, y);
      offset += dir->container().offset();
      _addThumbnail(dim, ThumbDesc(x, y, _type,
                                   offset, byte_count));
      list.push_back(dim);
      ret = OR_ERROR_NONE;
    }
  }

  return ret;
}

RawContainer* IfdFile::getContainer() const
{
  return m_container;
}

uint32_t IfdFile::_getJpegThumbnailOffset(const IfdDir::Ref & dir, uint32_t & byte_length)
{
  auto result = dir->getValue<uint32_t>(IFD::EXIF_TAG_JPEG_INTERCHANGE_FORMAT_LENGTH);
  if (result.ok()) {
    byte_length = result.unwrap();
    return dir->getValue<uint32_t>(IFD::EXIF_TAG_JPEG_INTERCHANGE_FORMAT).unwrap_or(0);
  }

  // some case it is STRIP_OFFSETS for JPEG
  byte_length = dir->getValue<uint32_t>(IFD::EXIF_TAG_STRIP_BYTE_COUNTS).unwrap_or(0);
  return dir->getValue<uint32_t>(IFD::EXIF_TAG_STRIP_OFFSETS).unwrap_or(0);
}



MetaValue *IfdFile::_getMetaValue(int32_t meta_index)
{
  MetaValue * val = nullptr;
  IfdDir::Ref ifd;
  if(META_INDEX_MASKOUT(meta_index) == META_NS_TIFF) {
    ifd = mainIfd();
  }
  else if(META_INDEX_MASKOUT(meta_index) == META_NS_EXIF) {
    ifd = exifIfd();
  }
  else {
    LOGERR("Unknown Meta Namespace\n");
  }
  if(ifd) {
    LOGDBG1("Meta value for %u\n", META_NS_MASKOUT(meta_index));

    IfdEntry::Ref e = ifd->getEntry(META_NS_MASKOUT(meta_index));
    if(e) {
      val = e->make_meta_value();
    }
  }
  return val;
}

/** by default we don't translate the compression
 */
uint32_t IfdFile::_translateCompressionType(IFD::TiffCompress tiff_compression)
{
	return (uint32_t)tiff_compression;
}



const IfdDir::Ref & IfdFile::cfaIfd()
{
	if(!m_cfaIfd) {
		m_cfaIfd = _locateCfaIfd();
	}
	return m_cfaIfd;
}


const IfdDir::Ref & IfdFile::mainIfd()
{
	if(!m_mainIfd) {
		m_mainIfd = _locateMainIfd();
	}
	return m_mainIfd;
}


const IfdDir::Ref & IfdFile::exifIfd()
{
	if(!m_exifIfd) {
		m_exifIfd = _locateExifIfd();
	}
	return m_exifIfd;
}


const MakerNoteDir::Ref & IfdFile::makerNoteIfd()
{
	if(!m_makerNoteIfd) {
		m_makerNoteIfd = _locateMakerNoteIfd();
	}
	return m_makerNoteIfd;
}


namespace {

::or_cfa_pattern
_convertArrayToCfaPattern(const std::vector<uint8_t> &cfaPattern)
{
  ::or_cfa_pattern cfa_pattern = OR_CFA_PATTERN_NON_RGB22;
  if(cfaPattern.size() != 4) {
    LOGWARN("Unsupported bayer pattern\n");
  }
  else {
    LOGDBG2("pattern is = %d, %d, %d, %d\n", cfaPattern[0],
            cfaPattern[1], cfaPattern[2], cfaPattern[3]);
    switch(cfaPattern[0]) {
    case IFD::CFA_RED:
      if ((cfaPattern[1] == IFD::CFA_GREEN)
          && (cfaPattern[2] == IFD::CFA_GREEN)
          && (cfaPattern[3] == IFD::CFA_BLUE)) {
        cfa_pattern = OR_CFA_PATTERN_RGGB;
      }
      break;
    case IFD::CFA_GREEN:
      switch(cfaPattern[1]) {
      case IFD::CFA_RED:
        if ((cfaPattern[2] == IFD::CFA_BLUE)
            && (cfaPattern[3] == IFD::CFA_GREEN)) {
          cfa_pattern = OR_CFA_PATTERN_GRBG;
        }
        break;
      case 2:
        if ((cfaPattern[2] == IFD::CFA_RED)
            && (cfaPattern[3] == IFD::CFA_GREEN)) {
          cfa_pattern = OR_CFA_PATTERN_GBRG;
        }
        break;
      }
      break;
    case IFD::CFA_BLUE:
      if ((cfaPattern[1] ==IFD::CFA_GREEN)
          && (cfaPattern[2] == IFD::CFA_GREEN)
          && (cfaPattern[3] == IFD::CFA_RED)) {
        cfa_pattern = OR_CFA_PATTERN_BGGR;
      }
      break;
    }
    //
  }
  return cfa_pattern;
}

::or_cfa_pattern _convertNewCfaPattern(const IfdEntry::Ref & e)
{
  ::or_cfa_pattern cfa_pattern = OR_CFA_PATTERN_NONE;
  if(!e || (e->count() < 4)) {
    return cfa_pattern;
  }

  uint16_t hdim = IfdTypeTrait<uint16_t>::get(*e, 0, true);
  uint16_t vdim = IfdTypeTrait<uint16_t>::get(*e, 1, true);
  if(hdim != 2 && vdim != 2) {
    cfa_pattern = OR_CFA_PATTERN_NON_RGB22;
  }
  else {
    std::vector<uint8_t> cfaPattern;
    cfaPattern.push_back(IfdTypeTrait<uint8_t>::get(*e, 4, true));
    cfaPattern.push_back(IfdTypeTrait<uint8_t>::get(*e, 5, true));
    cfaPattern.push_back(IfdTypeTrait<uint8_t>::get(*e, 6, true));
    cfaPattern.push_back(IfdTypeTrait<uint8_t>::get(*e, 7, true));
    cfa_pattern = _convertArrayToCfaPattern(cfaPattern);
  }
  return cfa_pattern;
}


/** convert the CFA Pattern as stored in the entry */
::or_cfa_pattern _convertCfaPattern(const IfdEntry::Ref & e)
{
  ::or_cfa_pattern cfa_pattern = OR_CFA_PATTERN_NONE;

  auto result = e->getArray<uint8_t>();
  if (result.ok()) {
    cfa_pattern = _convertArrayToCfaPattern(result.unwrap());
  }

  return cfa_pattern;
}

/** get the CFA Pattern out of the directory
 * @param dir the directory
 * @return the cfa_pattern value. %OR_CFA_PATTERN_NONE mean that
 * nothing has been found.
 */
static ::or_cfa_pattern _getCfaPattern(const IfdDir::Ref & dir)
{
  LOGDBG1("%s\n", __FUNCTION__);
  ::or_cfa_pattern cfa_pattern = OR_CFA_PATTERN_NONE;
  try {
    IfdEntry::Ref e = dir->getEntry(IFD::EXIF_TAG_CFA_PATTERN);
    if(e) {
      cfa_pattern = _convertCfaPattern(e);
    }
    else {
      e = dir->getEntry(IFD::EXIF_TAG_NEW_CFA_PATTERN);
      if(e)  {
        cfa_pattern = _convertNewCfaPattern(e);
      }
    }
  }
  catch(...)
  {
    LOGERR("Exception in _getCfaPattern().\n");
  }
  return cfa_pattern;
}

} // end anon namespace


::or_error IfdFile::_getRawData(RawData & data, uint32_t options)
{
  ::or_error ret = OR_ERROR_NONE;
  const IfdDir::Ref & _cfaIfd = cfaIfd();
  LOGDBG1("_getRawData()\n");

  if(_cfaIfd) {
    ret = _getRawDataFromDir(data, _cfaIfd);
    if (ret != OR_ERROR_NONE) {
      return ret;
    }
    ret = _decompressIfNeeded(data, options);
  }
  else {
    ret = OR_ERROR_NOT_FOUND;
  }
  return ret;
}

::or_error IfdFile::_decompressIfNeeded(RawData&, uint32_t)
{
  return OR_ERROR_NONE;
}


::or_error IfdFile::_getRawDataFromDir(RawData & data, const IfdDir::Ref & dir)
{
  ::or_error ret = OR_ERROR_NONE;

  uint32_t offset = 0;
  uint32_t byte_length = 0;

  if(!dir) {
    LOGERR("dir is NULL\n");
    return OR_ERROR_NOT_FOUND;
  }
  auto result = dir->getValue<uint16_t>(IFD::EXIF_TAG_BITS_PER_SAMPLE);
  if(result.empty()) {
    LOGERR("unable to guess Bits per sample\n");
  }
  uint16_t bpc = result.unwrap_or(0);

  auto result2 = dir->getValue<uint32_t>(IFD::EXIF_TAG_STRIP_OFFSETS);
  if(result2.ok()) {
    offset = result2.unwrap();
    IfdEntry::Ref e = dir->getEntry(IFD::EXIF_TAG_STRIP_BYTE_COUNTS);
    if(!e) {
      LOGDBG1("byte len not found\n");
      return OR_ERROR_NOT_FOUND;
    }
    auto result3 = e->getArray<uint32_t>();
    if (result3.ok()) {
      std::vector<uint32_t> counts = result3.unwrap();
      LOGDBG1("counting tiles\n");
      byte_length = std::accumulate(counts.cbegin(), counts.cend(), 0);
    }
  }
  else {
    // the tile are individual JPEGS....
    // TODO extract all of them.
    IfdEntry::Ref e = dir->getEntry(IFD::TIFF_TAG_TILE_OFFSETS);
    if(!e) {
      LOGDBG1("tile offsets empty\n");
      return OR_ERROR_NOT_FOUND;
    }
    auto result3 = e->getArray<uint32_t>();
    if (result3.empty()) {
      LOGDBG1("tile offsets not found\n");
      return OR_ERROR_NOT_FOUND;
    }
    std::vector<uint32_t> offsets = result3.unwrap();
    offset = offsets[0];
    e = dir->getEntry(IFD::TIFF_TAG_TILE_BYTECOUNTS);
    if(!e) {
      LOGDBG1("tile byte counts not found\n");
      return OR_ERROR_NOT_FOUND;
    }
    result3 = e->getArray<uint32_t>();
    if (result3.ok()) {
      std::vector<uint32_t> counts = result3.unwrap();
      LOGDBG1("counting tiles\n");
      byte_length = std::accumulate(counts.cbegin(), counts.cend(), 0);
    }
  }

  result2 = dir->getIntegerValue(IFD::EXIF_TAG_IMAGE_WIDTH);
  if(result2.empty()) {
    LOGDBG1("X not found\n");
    return OR_ERROR_NOT_FOUND;
  }
  uint32_t x = result2.unwrap();

  result2 = dir->getIntegerValue(IFD::EXIF_TAG_IMAGE_LENGTH);
  if(result2.empty()) {
    LOGDBG1("Y not found\n");
    return OR_ERROR_NOT_FOUND;
  }
  uint32_t y = result2.unwrap();

  uint32_t photo_int
    = dir->getIntegerValue(IFD::EXIF_TAG_PHOTOMETRIC_INTERPRETATION)
    .unwrap_or(IFD::EV_PI_CFA);

  BitmapData::DataType data_type = OR_DATA_TYPE_NONE;

  result = dir->getValue<uint16_t>(IFD::EXIF_TAG_COMPRESSION);
  if(result.empty()) {
    LOGDBG1("Compression type not found\n");
  }
	uint32_t compression = _translateCompressionType(
    static_cast<IFD::TiffCompress>(result.unwrap_or(0)));

  switch(compression)
  {
  case IFD::COMPRESS_NONE:
    data_type = OR_DATA_TYPE_RAW;
    break;
  case IFD::COMPRESS_NIKON_PACK:
    data_type = OR_DATA_TYPE_RAW;
    break;
  case IFD::COMPRESS_NIKON_QUANTIZED:
    // must check whether it is really compressed
    // only for D100
    if (!NefFile::isCompressed(*m_container, offset)) {
      compression = IFD::COMPRESS_NIKON_PACK;
      data_type = OR_DATA_TYPE_RAW;
      // this is a hack. we should check if
      // we have a D100 instead, but that case is already
      // a D100 corner case. WILL BREAK on compressed files.
      // according to dcraw we must increase the size by 6.
      x += 6;
      break;
    }
  default:
    data_type = OR_DATA_TYPE_COMPRESSED_RAW;
    break;
  }

  LOGDBG1("RAW Compression is %u\n", compression);
  LOGDBG1("bpc is %u\n", bpc);

  ::or_cfa_pattern cfa_pattern = _getCfaPattern(dir);
  if(cfa_pattern == OR_CFA_PATTERN_NONE) {
    // some file have it in the exif IFD instead.
    if(!m_exifIfd) {
      m_exifIfd = _locateExifIfd();
    }
    cfa_pattern = _getCfaPattern(m_exifIfd);
  }


  if((bpc == 12 || bpc == 14) && (compression == IFD::COMPRESS_NONE)
     && (byte_length == (x * y * 2))) {
    // We turn this to a 16-bits per sample. MSB are 0
    LOGDBG1("setting bpc from %u to 16\n", bpc);
    bpc = 16;
  }
  if((bpc == 16) || (data_type == OR_DATA_TYPE_COMPRESSED_RAW)) {
    void *p = data.allocData(byte_length);
    size_t real_size = m_container->fetchData(p, offset,
                                              byte_length);
    if (real_size < byte_length) {
      LOGWARN("Size mismatch for data: ignoring.\n");
    }
  }
  else if((bpc == 12) || (bpc == 8)) {
    ret = _unpackData(bpc, compression, data, x, y, offset, byte_length);
    LOGDBG1("unpack result %d\n", ret);
  }
  else {
    LOGERR("Unsupported bpc %u\n", bpc);
    return OR_ERROR_INVALID_FORMAT;
  }
  data.setCfaPatternType(cfa_pattern);
  data.setDataType(data_type);
  data.setBpc(bpc);
  data.setCompression(data_type == OR_DATA_TYPE_COMPRESSED_RAW
                      ? compression : 1);
  data.setPhotometricInterpretation((ExifPhotometricInterpretation)photo_int);
  if((data_type == OR_DATA_TYPE_RAW) && (data.whiteLevel() == 0)) {
    data.setWhiteLevel((1 << bpc) - 1);
  }
  data.setDimensions(x, y);

  return ret;
}


::or_error
IfdFile::_unpackData(uint16_t bpc, uint32_t compression, RawData & data,
                     uint32_t x, uint32_t y, uint32_t offset, uint32_t byte_length)
{
  ::or_error ret = OR_ERROR_NONE;
  size_t fetched = 0;
  uint32_t current_offset = offset;
  Unpack unpack(x, compression);
  const size_t blocksize = (bpc == 8 ? x : unpack.block_size());
  LOGDBG1("Block size = %lu\n", blocksize);
  LOGDBG1("dimensions (x, y) %u, %u\n", x, y);
  std::unique_ptr<uint8_t[]> block(new uint8_t[blocksize]);
  size_t outsize = x * y * 2;
  uint8_t * outdata = (uint8_t*)data.allocData(outsize);
  size_t got;
  LOGDBG1("offset of RAW data = %u\n", current_offset);
  do {
    got = m_container->fetchData (block.get(),
                                  current_offset, blocksize);
    fetched += got;
    offset += got;
    current_offset += got;
    if(got) {
      if(bpc == 12) {
        size_t out;
        ret = unpack.unpack_be12to16(outdata, outsize,
                                     block.get(),
                                     got, out);
        outdata += out;
        outsize -= out;
        if(ret != OR_ERROR_NONE) {
          break;
        }
      }
      else {
        // outdata point to uint16_t
        std::copy(block.get(), block.get()+got,
                  (uint16_t*)outdata);
        outdata += (got << 1);
      }
    }
  } while((got != 0) && (fetched < byte_length));

  return ret;
}

}
}

/*
  Local Variables:
  mode:c++
  c-file-style:"stroustrup"
  c-file-offsets:((innamespace . 0))
  tab-width:2
  c-basic-offset:2
  indent-tabs-mode:nil
  fill-column:80
  End:
*/