text: /*
   * Copyright (c) 2011-2013 Apple Inc. All rights reserved.
   *
   * @APPLE_APACHE_LICENSE_HEADER_START@
   *
   * Licensed under the Apache License, Version 2.0 (the "License");
   * you may not use this file except in compliance with the License.
   * You may obtain a copy of the License at
   *
   *     http://www.apache.org/licenses/LICENSE-2.0
   *
   * Unless required by applicable law or agreed to in writing, software
   * distributed under the License is distributed on an "AS IS" BASIS,
   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   * See the License for the specific language governing permissions and
   * limitations under the License.
   *
   * @APPLE_APACHE_LICENSE_HEADER_END@
   */
  
  #include "internal.h"
  
  #ifdef __APPLE__
  #include <libkern/OSByteOrder.h>
  #elif __linux__
  #include <endian.h>
  #define OSLittleEndian __LITTLE_ENDIAN
  #define OSBigEndian __BIG_ENDIAN
  #elif defined(__FreeBSD__)
  #include <sys/endian.h>
  #define OSLittleEndian _LITTLE_ENDIAN
  #define OSBigEndian _BIG_ENDIAN
  #endif
  
  #if defined(__linux__) || defined(__FreeBSD__)
  #define OSSwapLittleToHostInt16 le16toh
  #define OSSwapBigToHostInt16 be16toh
  #define OSSwapHostToLittleInt16 htole16
  #define OSSwapHostToBigInt16 htobe16
  #endif
  
  #if defined(__LITTLE_ENDIAN__)
  #define DISPATCH_DATA_FORMAT_TYPE_UTF16_HOST DISPATCH_DATA_FORMAT_TYPE_UTF16LE
  #define DISPATCH_DATA_FORMAT_TYPE_UTF16_REV DISPATCH_DATA_FORMAT_TYPE_UTF16BE
  #elif defined(__BIG_ENDIAN__)
  #define DISPATCH_DATA_FORMAT_TYPE_UTF16_HOST DISPATCH_DATA_FORMAT_TYPE_UTF16BE
  #define DISPATCH_DATA_FORMAT_TYPE_UTF16_REV DISPATCH_DATA_FORMAT_TYPE_UTF16LE
  #else
  #error Unsupported Endianness
  #endif
  
  enum {
  	_DISPATCH_DATA_FORMAT_NONE = 0x1,
  	_DISPATCH_DATA_FORMAT_UTF8 = 0x2,
  	_DISPATCH_DATA_FORMAT_UTF16LE = 0x4,
  	_DISPATCH_DATA_FORMAT_UTF16BE = 0x8,
  	_DISPATCH_DATA_FORMAT_UTF_ANY = 0x10,
  	_DISPATCH_DATA_FORMAT_BASE32 = 0x20,
  	_DISPATCH_DATA_FORMAT_BASE32HEX = 0x40,
  	_DISPATCH_DATA_FORMAT_BASE64 = 0x80,
  };
  
  #pragma mark -
  #pragma mark baseXX tables
  
  static const unsigned char base32_encode_table[] =
  		"ABCDEFGHIJKLMNOPQRSTUVWXYZ234567";
  
  static const signed char base32_decode_table[] = {
  	-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
  	-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
  	-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 26,
  	27, 28, 29, 30, 31, -1, -1, -1, -1, -1, -2, -1, -1, -1,  0,  1,  2,
  	 3,  4,  5,  6,  7,  8,  9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19,
  	20, 21, 22, 23, 24, 25
  };
  static const ssize_t base32_decode_table_size =
  		sizeof(base32_decode_table) / sizeof(*base32_decode_table);
  
  static const unsigned char base32hex_encode_table[] =
  		"0123456789ABCDEFGHIJKLMNOPQRSTUV";
  
  static const signed char base32hex_decode_table[] = {
  	-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
  	-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
  	-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,  0,  1,  2,
  	 3,  4,  5,  6,  7,  8,  9, -1, -1, -1, -2, -1, -1, -1, 10, 11, 12,
  	13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29,
  	30, 31
  };
  static const ssize_t base32hex_decode_table_size =
  		sizeof(base32hex_encode_table) / sizeof(*base32hex_encode_table);
  
  static const unsigned char base64_encode_table[] =
  		"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
  
  static const signed char base64_decode_table[] = {
  	-1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,
  	-1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,
  	-1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,
  	-1,  62,  -1,  -1,  -1,  63,  52,  53,  54,  55,  56,  57,  58,  59,
  	60,  61,  -1,  -1,  -1,  -2,  -1,  -1,  -1,   0,   1,   2,   3,   4,
  	 5,   6,   7,   8,   9,  10,  11,  12,  13,  14,  15,  16,  17,  18,
  	19,  20,  21,  22,  23,  24,  25,  -1,  -1,  -1,  -1,  -1,  -1,  26,
  	27,  28,  29,  30,  31,  32,  33,  34,  35,  36,  37,  38,  39,  40,
  	41,  42,  43,  44,  45,  46,  47,  48,  49,  50,  51
  };
  
  static const ssize_t base64_decode_table_size =
  		sizeof(base64_decode_table) / sizeof(*base64_decode_table);
  
  #pragma mark -
  #pragma mark dispatch_transform_buffer
  
  typedef struct dispatch_transform_buffer_s {
  	dispatch_data_t data;
  	uint8_t *start;
  	union {
  		uint8_t *u8;
  		uint16_t *u16;
  	} ptr;
  	size_t size;
  } dispatch_transform_buffer_s;
  
  #define BUFFER_MALLOC_MAX (100*1024*1024)
  
  static bool
  _dispatch_transform_buffer_new(dispatch_transform_buffer_s *buffer,
  		size_t required, size_t size)
  {
  	size_t remaining = buffer->size - (size_t)(buffer->ptr.u8 - buffer->start);
  	if (required == 0 || remaining < required) {
  		if (buffer->start) {
  			if (buffer->ptr.u8 > buffer->start) {
  				dispatch_data_t _new = dispatch_data_create(buffer->start,
  						(size_t)(buffer->ptr.u8 - buffer->start), NULL,
  						DISPATCH_DATA_DESTRUCTOR_FREE);
  				dispatch_data_t _concat = dispatch_data_create_concat(
  						buffer->data, _new);
  				dispatch_release(_new);
  				dispatch_release(buffer->data);
  				buffer->data = _concat;
  			} else {
  				free(buffer->start);
  			}
  		}
  		buffer->size = required + size;
  		buffer->start = NULL;
  		if (buffer->size > 0) {
  			if (buffer->size > BUFFER_MALLOC_MAX) {
  				return false;
  			}
  			buffer->start = (uint8_t*)malloc(buffer->size);
  			if (buffer->start == NULL) {
  				return false;
  			}
  		}
  		buffer->ptr.u8 = buffer->start;
  	}
  	return true;
  }
  
  #pragma mark -
  #pragma mark dispatch_transform_helpers
  
  static dispatch_data_t
  _dispatch_data_subrange_map(dispatch_data_t data, const void **ptr,
  		size_t offset, size_t size)
  {
  	dispatch_data_t subrange, map = NULL;
  
  	subrange = dispatch_data_create_subrange(data, offset, size);
  	if (dispatch_data_get_size(subrange) == size) {
  		map = dispatch_data_create_map(subrange, ptr, NULL);
  	}
  	dispatch_release(subrange);
  	return map;
  }
  
  static dispatch_data_format_type_t
  _dispatch_transform_detect_utf(dispatch_data_t data)
  {
  	const void *p;
  	dispatch_data_t subrange = _dispatch_data_subrange_map(data, &p, 0, 2);
  
  	if (subrange == NULL) {
  		return NULL;
  	}
  
  	const uint16_t ch = *(const uint16_t *)p;
  	dispatch_data_format_type_t type = DISPATCH_DATA_FORMAT_TYPE_UTF8;
  
  	if (ch == 0xfeff) {
  		type = DISPATCH_DATA_FORMAT_TYPE_UTF16_HOST;
  	} else if (ch == 0xfffe) {
  		type = DISPATCH_DATA_FORMAT_TYPE_UTF16_REV;
  	}
  
  	dispatch_release(subrange);
  
  	return type;
  }
  
  static uint16_t
  _dispatch_transform_swap_to_host(uint16_t x, int32_t byteOrder)
  {
  	if (byteOrder == OSLittleEndian) {
  		return OSSwapLittleToHostInt16(x);
  	}
  	return OSSwapBigToHostInt16(x);
  }
  
  static uint16_t
  _dispatch_transform_swap_from_host(uint16_t x, int32_t byteOrder)
  {
  	if (byteOrder == OSLittleEndian) {
  		return OSSwapHostToLittleInt16(x);
  	}
  	return OSSwapHostToBigInt16(x);
  }
  
  #pragma mark -
  #pragma mark UTF-8
  
  static uint8_t
  _dispatch_transform_utf8_length(uint8_t byte)
  {
  	if ((byte & 0x80) == 0) {
  		return 1;
  	} else if ((byte & 0xe0) == 0xc0) {
  		return 2;
  	} else if ((byte & 0xf0) == 0xe0) {
  		return 3;
  	} else if ((byte & 0xf8) == 0xf0) {
  		return 4;
  	}
  	return 0;
  }
  
  static uint32_t
  _dispatch_transform_read_utf8_sequence(const uint8_t *bytes)
  {
  	uint32_t wch = 0;
  	uint8_t seq_length = _dispatch_transform_utf8_length(*bytes);
  
  	switch (seq_length) {
  	case 4:
  		wch |= (*bytes & 0x7);
  		wch <<= 6;
  		break;
  	case 3:
  		wch |= (*bytes & 0xf);
  		wch <<= 6;
  		break;
  	case 2:
  		wch |= (*bytes & 0x1f);
  		wch <<= 6;
  		break;
  	case 1:
  		wch = (*bytes & 0x7f);
  		break;
  	default:
  		// Not a utf-8 sequence
  		break;
  	}
  
  	bytes++;
  	seq_length--;
  
  	while (seq_length > 0) {
  		wch |= (*bytes & 0x3f);
  		bytes++;
  		seq_length--;
  
  		if (seq_length > 0) {
  			wch <<= 6;
  		}
  	}
  	return wch;
  }
  
  #pragma mark -
  #pragma mark UTF-16
  
  static dispatch_data_t
  _dispatch_transform_to_utf16(dispatch_data_t data, int32_t byteOrder)
  {
  	__block size_t skip = 0;
  
  	__block dispatch_transform_buffer_s buffer = {
  		.data = dispatch_data_empty,
  	};
  
  	bool success = dispatch_data_apply(data, ^(
  			DISPATCH_UNUSED dispatch_data_t region,
  			size_t offset, const void *_buffer, size_t size) {
  		const uint8_t *src = _buffer;
  		size_t i, dest_size;
  
  		if (offset == 0) {
  			if (os_mul_and_add_overflow(size, sizeof(uint16_t),
  					sizeof(uint16_t), &dest_size)) {
  				return (bool)false;
  			}
  			if (!_dispatch_transform_buffer_new(&buffer, dest_size, 0)) {
  				return (bool)false;
  			}
  			// Insert BOM
  			*(buffer.ptr.u16)++ = _dispatch_transform_swap_from_host(0xfeff,
  					byteOrder);
  		}
  
  		// Skip is incremented if the previous block read-ahead into our block
  		if (skip >= size) {
  			skip -= size;
  			return (bool)true;
  		} else if (skip > 0) {
  			src += skip;
  			size -= skip;
  			skip = 0;
  		}
  
  		for (i = 0; i < size;) {
  			uint32_t wch = 0;
  			uint8_t byte_size = _dispatch_transform_utf8_length(*src);
  			size_t next;
  
  			if (byte_size == 0) {
  				return (bool)false;
  			} else if (byte_size + i > size) {
  				// UTF-8 byte sequence spans over into the next block(s)
  				const void *p;
  				dispatch_data_t subrange = _dispatch_data_subrange_map(data, &p,
  						offset + i, byte_size);
  				if (subrange == NULL) {
  					return (bool)false;
  				}
  
  				wch = _dispatch_transform_read_utf8_sequence(p);
  				skip += byte_size - (size - i);
  				src += byte_size;
  				i = size;
  
  				dispatch_release(subrange);
  			} else {
  				wch = _dispatch_transform_read_utf8_sequence(src);
  				src += byte_size;
  				i += byte_size;
  			}
  
  			if (os_mul_overflow(size - i, sizeof(uint16_t), &next)) {
  				return (bool)false;
  			}
  			if (wch >= 0xd800 && wch < 0xdfff) {
  				// Illegal range (surrogate pair)
  				return (bool)false;
  			} else if (wch >= 0x10000) {
  				// Surrogate pair
  				if (!_dispatch_transform_buffer_new(&buffer, 2 *
  						sizeof(uint16_t), next)) {
  					return (bool)false;
  				}
  				wch -= 0x10000;
  				*(buffer.ptr.u16)++ = _dispatch_transform_swap_from_host(
  						((wch >> 10) & 0x3ff) + 0xd800, byteOrder);
  				*(buffer.ptr.u16)++ = _dispatch_transform_swap_from_host(
  						(wch & 0x3ff) + 0xdc00, byteOrder);
  			} else {
  				if (!_dispatch_transform_buffer_new(&buffer, 1 *
  						sizeof(uint16_t), next)) {
  					return (bool)false;
  				}
  				*(buffer.ptr.u16)++ = _dispatch_transform_swap_from_host(
  						(wch & 0xffff), byteOrder);
  			}
  		}
  
  		(void)_dispatch_transform_buffer_new(&buffer, 0, 0);
  
  		return (bool)true;
  	});
  
  	if (!success) {
  		(void)_dispatch_transform_buffer_new(&buffer, 0, 0);
  		dispatch_release(buffer.data);
  		return NULL;
  	}
  
  	return buffer.data;
  }
  
  static dispatch_data_t
  _dispatch_transform_from_utf16(dispatch_data_t data, int32_t byteOrder)
  {
  	__block size_t skip = 0;
  
  	__block dispatch_transform_buffer_s buffer = {
  		.data = dispatch_data_empty,
  	};
  
  	bool success = dispatch_data_apply(data, ^(
  			DISPATCH_UNUSED dispatch_data_t region, size_t offset,
  			const void *_buffer, size_t size) {
  		const uint16_t *src = _buffer;
  
  		if (offset == 0) {
  			size_t dest_size = howmany(size, 3) * 2;
  			// Assume first buffer will be mostly single-byte UTF-8 sequences
  			if (!_dispatch_transform_buffer_new(&buffer, dest_size, 0)) {
  				return (bool)false;
  			}
  		}
  
  		size_t i = 0, max = size / 2;
  
  		// Skip is incremented if the previous block read-ahead into our block
  		if (skip >= size) {
  			skip -= size;
  			return (bool)true;
  		} else if (skip > 0) {
  			src = (uint16_t *)(((uint8_t *)src) + skip);
  			size -= skip;
  			max = (size / 2);
  			skip = 0;
  		}
  
  		// If the buffer is an odd size, allow read ahead into the next region
  		if ((size % 2) != 0) {
  			max += 1;
  		}
  
  		for (i = 0; i < max; i++) {
  			uint32_t wch = 0;
  			uint16_t ch;
  			size_t next;
  
  			if ((i == (max - 1)) && (max > (size / 2))) {
  				// Last byte of an odd sized range
  				const void *p;
  				dispatch_data_t range = _dispatch_data_subrange_map(data, &p,
  						offset + (i * 2), 2);
  				if (range == NULL) {
  					return (bool)false;
  				}
  				ch = _dispatch_transform_swap_to_host((uint16_t)*(uint64_t*)p,
  						byteOrder);
  				dispatch_release(range);
  				skip += 1;
  			} else {
  				ch =  _dispatch_transform_swap_to_host(src[i], byteOrder);
  			}
  
  			if (ch == 0xfffe && offset == 0 && i == 0) {
  				// Wrong-endian BOM at beginning of data
  				return (bool)false;
  			} else if (ch == 0xfeff && offset == 0 && i == 0) {
  				// Correct-endian BOM, skip it
  				continue;
  			}
  
  			if ((ch >= 0xd800) && (ch <= 0xdbff)) {
  				// Surrogate pair
  				wch = ((ch - 0xd800u) << 10);
  				if (++i >= max) {
  					// Surrogate byte isn't in this block
  					const void *p;
  					dispatch_data_t range = _dispatch_data_subrange_map(data,
  							&p, offset + (i * 2), 2);
  					if (range == NULL) {
  						return (bool)false;
  					}
  					ch = _dispatch_transform_swap_to_host(*(uint16_t *)p,
  							byteOrder);
  					dispatch_release(range);
  					skip += 2;
  				} else {
  					ch = _dispatch_transform_swap_to_host(src[i], byteOrder);
  				}
  				if (!((ch >= 0xdc00) && (ch <= 0xdfff))) {
  					return (bool)false;
  				}
  				wch = (wch | (ch & 0x3ff));
  				wch += 0x10000;
  			} else if ((ch >= 0xdc00) && (ch <= 0xdfff)) {
  				return (bool)false;
  			} else {
  				wch = ch;
  			}
  
  			if (os_mul_overflow(max - i, 2, &next)) {
  				return (bool)false;
  			}
  			if (wch < 0x80) {
  				if (!_dispatch_transform_buffer_new(&buffer, 1, next)) {
  					return (bool)false;
  				}
  				*(buffer.ptr.u8)++ = (uint8_t)(wch & 0xff);
  			} else if (wch < 0x800) {
  				if (!_dispatch_transform_buffer_new(&buffer, 2, next)) {
  					return (bool)false;
  				}
  				*(buffer.ptr.u8)++ = (uint8_t)(0xc0 | (wch >> 6));
  				*(buffer.ptr.u8)++ = (uint8_t)(0x80 | (wch & 0x3f));
  			} else if (wch < 0x10000) {
  				if (!_dispatch_transform_buffer_new(&buffer, 3, next)) {
  					return (bool)false;
  				}
  				*(buffer.ptr.u8)++ = (uint8_t)(0xe0 | (wch >> 12));
  				*(buffer.ptr.u8)++ = (uint8_t)(0x80 | ((wch >> 6) & 0x3f));
  				*(buffer.ptr.u8)++ = (uint8_t)(0x80 | (wch & 0x3f));
  			} else if (wch < 0x200000) {
  				if (!_dispatch_transform_buffer_new(&buffer, 4, next)) {
  					return (bool)false;
  				}
  				*(buffer.ptr.u8)++ = (uint8_t)(0xf0 | (wch >> 18));
  				*(buffer.ptr.u8)++ = (uint8_t)(0x80 | ((wch >> 12) & 0x3f));
  				*(buffer.ptr.u8)++ = (uint8_t)(0x80 | ((wch >> 6) & 0x3f));
  				*(buffer.ptr.u8)++ = (uint8_t)(0x80 | (wch & 0x3f));
  			}
  		}
  
  		(void)_dispatch_transform_buffer_new(&buffer, 0, 0);
  
  		return (bool)true;
  	});
  
  	if (!success) {
  		(void)_dispatch_transform_buffer_new(&buffer, 0, 0);
  		dispatch_release(buffer.data);
  		return NULL;
  	}
  
  	return buffer.data;
  }
  
  static dispatch_data_t
  _dispatch_transform_from_utf16le(dispatch_data_t data)
  {
  	return _dispatch_transform_from_utf16(data, OSLittleEndian);
  }
  
  static dispatch_data_t
  _dispatch_transform_from_utf16be(dispatch_data_t data)
  {
  	return _dispatch_transform_from_utf16(data, OSBigEndian);
  }
  
  static dispatch_data_t
  _dispatch_transform_to_utf16le(dispatch_data_t data)
  {
  	return _dispatch_transform_to_utf16(data, OSLittleEndian);
  }
  
  static dispatch_data_t
  _dispatch_transform_to_utf16be(dispatch_data_t data)
  {
  	return _dispatch_transform_to_utf16(data, OSBigEndian);
  }
  
  #pragma mark -
  #pragma mark base32
  
  static dispatch_data_t
  _dispatch_transform_from_base32_with_table(dispatch_data_t data,
  		const signed char* table, ssize_t table_size)
  {
  	__block uint64_t x = 0, count = 0, pad = 0;
  
  	__block dispatch_data_t rv = dispatch_data_empty;
  
  	bool success = dispatch_data_apply(data, ^(
  			DISPATCH_UNUSED dispatch_data_t region,
  			DISPATCH_UNUSED size_t offset, const void *buffer, size_t size) {
  		size_t i, dest_size = howmany(size, 8) * 5;
  		uint8_t *dest = (uint8_t*)malloc(dest_size * sizeof(uint8_t));
  		uint8_t *ptr = dest;
  		if (dest == NULL) {
  			return (bool)false;
  		}
  
  		const uint8_t *bytes = buffer;
  
  		for (i = 0; i < size; i++) {
  			if (bytes[i] == '\n' || bytes[i] == '\t' || bytes[i] == ' ') {
  				continue;
  			}
  
  			ssize_t index = bytes[i];
  			if (index >= table_size || table[index] == -1) {
  				free(dest);
  				return (bool)false;
  			}
  			count++;
  
  			signed char value = table[index];
  			if (value == -2) {
  				value = 0;
  				pad++;
  			}
  
  			x <<= 5;
  			x += (uint64_t)value;
  
  			if ((count & 0x7) == 0) {
  				*ptr++ = (x >> 32) & 0xff;
  				*ptr++ = (x >> 24) & 0xff;
  				*ptr++ = (x >> 16) & 0xff;
  				*ptr++ = (x >> 8) & 0xff;
  				*ptr++ = x & 0xff;
  			}
  		}
  
  		size_t final = (size_t)(ptr - dest);
  		switch (pad) {
  		case 1:
  			final -= 1;
  			break;
  		case 3:
  			final -= 2;
  			break;
  		case 4:
  			final -= 3;
  			break;
  		case 6:
  			final -= 4;
  			break;
  		}
  
  		dispatch_data_t val = dispatch_data_create(dest, final, NULL,
  				DISPATCH_DATA_DESTRUCTOR_FREE);
  		dispatch_data_t concat = dispatch_data_create_concat(rv, val);
  
  		dispatch_release(val);
  		dispatch_release(rv);
  		rv = concat;
  
  		return (bool)true;
  	});
  
  	if (!success) {
  		dispatch_release(rv);
  		return NULL;
  	}
  
  	return rv;
  }
  
  static dispatch_data_t
  _dispatch_transform_to_base32_with_table(dispatch_data_t data, const unsigned char* table)
  {
  	size_t total = dispatch_data_get_size(data), dest_size;
  	__block size_t count = 0;
  
  	dest_size = howmany(total, 5);
  	// <rdar://problem/25676583>
  	// os_mul_overflow(dest_size, 8, &dest_size)
  	if (dest_size > SIZE_T_MAX / 8) {
  		return NULL;
  	}
  	dest_size *= 8;
  
  	uint8_t *dest = (uint8_t*)malloc(dest_size);
  	if (dest == NULL) {
  		return NULL;
  	}
  
  	__block uint8_t *ptr = dest;
  
  	/*
  					    0        1        2        3        4
  	 8-bit bytes:   xxxxxxxx yyyyyyyy zzzzzzzz xxxxxxxx yyyyyyyy
  	 5-bit chunks:  aaaaabbb bbcccccd ddddeeee efffffgg ggghhhhh
  	 */
  
  	bool success = dispatch_data_apply(data, ^(
  			DISPATCH_UNUSED dispatch_data_t region,
  			size_t offset, const void *buffer, size_t size) {
  		const uint8_t *bytes = buffer;
  		size_t i;
  
  		for (i = 0; i < size; i++, count++) {
  			uint8_t curr = bytes[i], last = 0;
  
  			if ((count % 5) != 0) {
  				if (i == 0) {
  					const void *p;
  					dispatch_data_t subrange = _dispatch_data_subrange_map(data,
  							&p, offset - 1, 1);
  					if (subrange == NULL) {
  						return (bool)false;
  					}
  					last = *(uint8_t*)p;
  					dispatch_release(subrange);
  				} else {
  					last = bytes[i - 1];
  				}
  			}
  
  			switch (count % 5) {
  			case 0:
  				// a
  				*ptr++ = table[(curr >> 3) & 0x1fu];
  				break;
  			case 1:
  				// b + c
  				*ptr++ = table[((last << 2)|(curr >> 6)) & 0x1f];
  				*ptr++ = table[(curr >> 1) & 0x1f];
  				break;
  			case 2:
  				// d
  				*ptr++ = table[((last << 4)|(curr >> 4)) & 0x1f];
  				break;
  			case 3:
  				// e + f
  				*ptr++ = table[((last << 1)|(curr >> 7)) & 0x1f];
  				*ptr++ = table[(curr >> 2) & 0x1f];
  				break;
  			case 4:
  				// g + h
  				*ptr++ = table[((last << 3)|(curr >> 5)) & 0x1f];
  				*ptr++ = table[curr & 0x1f];
  				break;
  			}
  		}
  
  		// Last region, insert padding bytes, if needed
  		if (offset + size == total) {
  			switch (count % 5) {
  			case 0:
  				break;
  			case 1:
  				// b[4:2]
  				*ptr++ = table[(bytes[size-1] << 2) & 0x1c];
  				break;
  			case 2:
  				// d[4]
  				*ptr++ = table[(bytes[size-1] << 4) & 0x10];
  				break;
  			case 3:
  				// e[4:1]
  				*ptr++ = table[(bytes[size-1] << 1) & 0x1e];
  				break;
  			case 4:
  				// g[2:3]
  				*ptr++ = table[(bytes[size-1] << 3) & 0x18];
  				break;
  			}
  			switch (count % 5) {
  			case 0:
  				break;
  			case 1:
  				*ptr++ = '='; // c
  				*ptr++ = '='; // d
  			case 2:
  				*ptr++ = '='; // e
  			case 3:
  				*ptr++ = '='; // f
  				*ptr++ = '='; // g
  			case 4:
  				*ptr++ = '='; // h
  				break;
  			}
  		}
  
  		return (bool)true;
  	});
  
  	if (!success) {
  		free(dest);
  		return NULL;
  	}
  	return dispatch_data_create(dest, dest_size, NULL,
  			DISPATCH_DATA_DESTRUCTOR_FREE);
  }
  
  static dispatch_data_t
  _dispatch_transform_from_base32(dispatch_data_t data)
  {
  	return _dispatch_transform_from_base32_with_table(data, base32_decode_table,
  			base32_decode_table_size);
  }
  
  static dispatch_data_t
  _dispatch_transform_to_base32(dispatch_data_t data)
  {
  	return _dispatch_transform_to_base32_with_table(data, base32_encode_table);
  }
  
  static dispatch_data_t
  _dispatch_transform_from_base32hex(dispatch_data_t data)
  {
  	return _dispatch_transform_from_base32_with_table(data,
  			base32hex_decode_table, base32hex_decode_table_size);
  }
  
  static dispatch_data_t
  _dispatch_transform_to_base32hex(dispatch_data_t data)
  {
  	return _dispatch_transform_to_base32_with_table(data,
  			base32hex_encode_table);
  }
  
  #pragma mark -
  #pragma mark base64
  
  static dispatch_data_t
  _dispatch_transform_from_base64(dispatch_data_t data)
  {
  	__block uint64_t x = 0, count = 0;
  	__block size_t pad = 0;
  
  	__block dispatch_data_t rv = dispatch_data_empty;
  
  	bool success = dispatch_data_apply(data, ^(
  			DISPATCH_UNUSED dispatch_data_t region,
  			DISPATCH_UNUSED size_t offset, const void *buffer, size_t size) {
  		size_t i, dest_size = howmany(size, 4) * 3;
  
  		uint8_t *dest = (uint8_t*)malloc(dest_size * sizeof(uint8_t));
  		uint8_t *ptr = dest;
  		if (dest == NULL) {
  			return (bool)false;
  		}
  
  		const uint8_t *bytes = buffer;
  
  		for (i = 0; i < size; i++) {
  			if (bytes[i] == '\n' || bytes[i] == '\t' || bytes[i] == ' ') {
  				continue;
  			}
  
  			ssize_t index = bytes[i];
  			if (index >= base64_decode_table_size ||
  					base64_decode_table[index] == -1) {
  				free(dest);
  				return (bool)false;
  			}
  			count++;
  
  			signed char value = base64_decode_table[index];
  			if (value == -2) {
  				value = 0;
  				pad++;
  			}
  
  			x <<= 6;
  			x += (uint64_t)value;
  
  			if ((count & 0x3) == 0) {
  				*ptr++ = (x >> 16) & 0xff;
  				*ptr++ = (x >> 8) & 0xff;
  				*ptr++ = x & 0xff;
  			}
  		}
  
  		size_t final = (size_t)(ptr - dest);
  		if (pad > 0) {
  			// 2 bytes of pad means only had one char in final group
  			final -= pad;
  		}
  
  		dispatch_data_t val = dispatch_data_create(dest, final, NULL,
  				DISPATCH_DATA_DESTRUCTOR_FREE);
  		dispatch_data_t concat = dispatch_data_create_concat(rv, val);
  
  		dispatch_release(val);
  		dispatch_release(rv);
  		rv = concat;
  
  		return (bool)true;
  	});
  
  	if (!success) {
  		dispatch_release(rv);
  		return NULL;
  	}
  
  	return rv;
  }
  
  static dispatch_data_t
  _dispatch_transform_to_base64(dispatch_data_t data)
  {
  	// RFC 4648 states that we should not linebreak
  	// http://tools.ietf.org/html/rfc4648
  	size_t total = dispatch_data_get_size(data), dest_size;
  	__block size_t count = 0;
  
  	dest_size = howmany(total, 3);
  	// <rdar://problem/25676583>
  	// os_mul_overflow(dest_size, 4, &dest_size)
  	if (dest_size > SIZE_T_MAX / 4) {
  		return NULL;
  	}
  	dest_size *= 4;
  
  	uint8_t *dest = (uint8_t*)malloc(dest_size);
  	if (dest == NULL) {
  		return NULL;
  	}
  
  	__block uint8_t *ptr = dest;
  
  	/*
  	 * 3 8-bit bytes:	xxxxxxxx yyyyyyyy zzzzzzzz
  	 * 4 6-bit chunks:	aaaaaabb bbbbcccc ccdddddd
  	 */
  
  	bool success = dispatch_data_apply(data, ^(
  			DISPATCH_UNUSED dispatch_data_t region,
  			size_t offset, const void *buffer, size_t size) {
  		const uint8_t *bytes = buffer;
  		size_t i;
  
  		for (i = 0; i < size; i++, count++) {
  			uint8_t curr = bytes[i], last = 0;
  
  			if ((count % 3) != 0) {
  				if (i == 0) {
  					const void *p;
  					dispatch_data_t subrange = _dispatch_data_subrange_map(data,
  						&p, offset - 1, 1);
  					if (subrange == NULL) {
  						return (bool)false;
  					}
  					last = *(uint8_t*)p;
  					dispatch_release(subrange);
  				} else {
  					last = bytes[i - 1];
  				}
  			}
  
  			switch (count % 3) {
  			case 0:
  				*ptr++ = base64_encode_table[(curr >> 2) & 0x3f];
  				break;
  			case 1:
  				*ptr++ = base64_encode_table[((last << 4)|(curr >> 4)) & 0x3f];
  				break;
  			case 2:
  				*ptr++ = base64_encode_table[((last << 2)|(curr >> 6)) & 0x3f];
  				*ptr++ = base64_encode_table[(curr & 0x3f)];
  				break;
  			}
  		}
  
  		// Last region, insert padding bytes, if needed
  		if (offset + size == total) {
  			switch (count % 3) {
  			case 0:
  				break;
  			case 1:
  				*ptr++ = base64_encode_table[(bytes[size-1] << 4) & 0x30];
  				*ptr++ = '=';
  				*ptr++ = '=';
  				break;
  			case 2:
  				*ptr++ = base64_encode_table[(bytes[size-1] << 2) & 0x3c];
  				*ptr++ = '=';
  				break;
  			}
  		}
  
  		return (bool)true;
  	});
  
  	if (!success) {
  		free(dest);
  		return NULL;
  	}
  	return dispatch_data_create(dest, dest_size, NULL,
  			DISPATCH_DATA_DESTRUCTOR_FREE);
  }
  
  #pragma mark -
  #pragma mark dispatch_data_transform
  
  dispatch_data_t
  dispatch_data_create_with_transform(dispatch_data_t data,
  		dispatch_data_format_type_t input, dispatch_data_format_type_t output)
  {
  	if (input->type == _DISPATCH_DATA_FORMAT_UTF_ANY) {
  		input = _dispatch_transform_detect_utf(data);
  		if (input == NULL) {
  			return DISPATCH_BAD_INPUT;
  		}
  	}
  
  	if ((input->type & ~output->input_mask) != 0) {
  		return DISPATCH_BAD_INPUT;
  	}
  
  	if ((output->type & ~input->output_mask) != 0) {
  		return DISPATCH_BAD_INPUT;
  	}
  
  	if (dispatch_data_get_size(data) == 0) {
  		return data;
  	}
  
  	dispatch_data_t temp1;
  	if (input->decode) {
  		temp1 = input->decode(data);
  	} else {
  		dispatch_retain(data);
  		temp1 = data;
  	}
  
  	if (!temp1) {
  		return DISPATCH_BAD_INPUT;
  	}
  
  	dispatch_data_t temp2;
  	if (output->encode) {
  		temp2 = output->encode(temp1);
  	} else {
  		dispatch_retain(temp1);
  		temp2 = temp1;
  	}
  
  	dispatch_release(temp1);
  	return temp2;
  }
  
  const struct dispatch_data_format_type_s _dispatch_data_format_type_none = {
  	.type = _DISPATCH_DATA_FORMAT_NONE,
  	.input_mask = ~0u,
  	.output_mask = ~0u,
  	.decode = NULL,
  	.encode = NULL,
  };
  
  const struct dispatch_data_format_type_s _dispatch_data_format_type_base32 = {
  	.type = _DISPATCH_DATA_FORMAT_BASE32,
  	.input_mask = (_DISPATCH_DATA_FORMAT_NONE | _DISPATCH_DATA_FORMAT_BASE32 |
  			_DISPATCH_DATA_FORMAT_BASE32HEX | _DISPATCH_DATA_FORMAT_BASE64),
  	.output_mask = (_DISPATCH_DATA_FORMAT_NONE | _DISPATCH_DATA_FORMAT_BASE32 |
  			_DISPATCH_DATA_FORMAT_BASE32HEX | _DISPATCH_DATA_FORMAT_BASE64),
  	.decode = _dispatch_transform_from_base32,
  	.encode = _dispatch_transform_to_base32,
  };
  
  const struct dispatch_data_format_type_s _dispatch_data_format_type_base32hex =
  {
  	.type = _DISPATCH_DATA_FORMAT_BASE32HEX,
  	.input_mask = (_DISPATCH_DATA_FORMAT_NONE | _DISPATCH_DATA_FORMAT_BASE32 |
  			_DISPATCH_DATA_FORMAT_BASE32HEX | _DISPATCH_DATA_FORMAT_BASE64),
  	.output_mask = (_DISPATCH_DATA_FORMAT_NONE | _DISPATCH_DATA_FORMAT_BASE32 |
  			_DISPATCH_DATA_FORMAT_BASE32HEX | _DISPATCH_DATA_FORMAT_BASE64),
  	.decode = _dispatch_transform_from_base32hex,
  	.encode = _dispatch_transform_to_base32hex,
  };
  
  const struct dispatch_data_format_type_s _dispatch_data_format_type_base64 = {
  	.type = _DISPATCH_DATA_FORMAT_BASE64,
  	.input_mask = (_DISPATCH_DATA_FORMAT_NONE | _DISPATCH_DATA_FORMAT_BASE32 |
  			_DISPATCH_DATA_FORMAT_BASE32HEX | _DISPATCH_DATA_FORMAT_BASE64),
  	.output_mask = (_DISPATCH_DATA_FORMAT_NONE | _DISPATCH_DATA_FORMAT_BASE32 |
  			_DISPATCH_DATA_FORMAT_BASE32HEX | _DISPATCH_DATA_FORMAT_BASE64),
  	.decode = _dispatch_transform_from_base64,
  	.encode = _dispatch_transform_to_base64,
  };
  
  const struct dispatch_data_format_type_s _dispatch_data_format_type_utf16le = {
  	.type = _DISPATCH_DATA_FORMAT_UTF16LE,
  	.input_mask = (_DISPATCH_DATA_FORMAT_UTF8 | _DISPATCH_DATA_FORMAT_UTF16BE |
  			_DISPATCH_DATA_FORMAT_UTF16LE),
  	.output_mask = (_DISPATCH_DATA_FORMAT_UTF8 | _DISPATCH_DATA_FORMAT_UTF16BE |
  			_DISPATCH_DATA_FORMAT_UTF16LE),
  	.decode = _dispatch_transform_from_utf16le,
  	.encode = _dispatch_transform_to_utf16le,
  };
  
  const struct dispatch_data_format_type_s _dispatch_data_format_type_utf16be = {
  	.type = _DISPATCH_DATA_FORMAT_UTF16BE,
  	.input_mask = (_DISPATCH_DATA_FORMAT_UTF8 | _DISPATCH_DATA_FORMAT_UTF16BE |
  			_DISPATCH_DATA_FORMAT_UTF16LE),
  	.output_mask = (_DISPATCH_DATA_FORMAT_UTF8 | _DISPATCH_DATA_FORMAT_UTF16BE |
  			_DISPATCH_DATA_FORMAT_UTF16LE),
  	.decode = _dispatch_transform_from_utf16be,
  	.encode = _dispatch_transform_to_utf16be,
  };
  
  const struct dispatch_data_format_type_s _dispatch_data_format_type_utf8 = {
  	.type = _DISPATCH_DATA_FORMAT_UTF8,
  	.input_mask = (_DISPATCH_DATA_FORMAT_UTF8 | _DISPATCH_DATA_FORMAT_UTF16BE |
  			_DISPATCH_DATA_FORMAT_UTF16LE),
  	.output_mask = (_DISPATCH_DATA_FORMAT_UTF8 | _DISPATCH_DATA_FORMAT_UTF16BE |
  			_DISPATCH_DATA_FORMAT_UTF16LE),
  	.decode = NULL,
  	.encode = NULL,
  };
  
  const struct dispatch_data_format_type_s _dispatch_data_format_type_utf_any = {
  	.type = _DISPATCH_DATA_FORMAT_UTF_ANY,
  	.input_mask = 0,
  	.output_mask = 0,
  	.decode = NULL,
  	.encode = NULL,
  };
  