]> git.saurik.com Git - apple/libdispatch.git/blobdiff - src/transform.c
libdispatch-228.18.tar.gz
[apple/libdispatch.git] / src / transform.c
diff --git a/src/transform.c b/src/transform.c
new file mode 100644 (file)
index 0000000..775ce41
--- /dev/null
@@ -0,0 +1,1015 @@
+/*
+ * Copyright (c) 2011 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"
+
+#include <libkern/OSByteOrder.h>
+
+#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
+#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_BASE64 = 0x40,
+};
+
+#pragma mark -
+#pragma mark baseXX tables
+
+static const char base32_encode_table[] =
+               "ABCDEFGHIJKLMNOPQRSTUVWXYZ23456789";
+
+static const 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 char base64_encode_table[] =
+               "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
+
+static const 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;
+
+static size_t
+_dispatch_transform_sizet_mul(size_t a, size_t b)
+{
+       size_t rv = SIZE_MAX;
+       if (a == 0 || rv/a >= b) {
+               rv = a * b;
+       }
+       return rv;
+}
+
+#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 - (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,
+                                               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;
+
+               if (offset == 0) {
+                       size_t dest_size = 2 + _dispatch_transform_sizet_mul(size,
+                                       sizeof(uint16_t));
+                       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);
+
+                       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;
+                       }
+
+                       size_t next = _dispatch_transform_sizet_mul(size - i, sizeof(uint16_t));
+                       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) {
+               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) {
+                       // Assume first buffer will be mostly single-byte UTF-8 sequences
+                       size_t dest_size = _dispatch_transform_sizet_mul(size, 2) / 3;
+                       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;
+
+                       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(*(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 - 0xd800) << 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;
+                       }
+
+                       size_t next = _dispatch_transform_sizet_mul(max - i, 2);
+                       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) {
+               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(dispatch_data_t data)
+{
+       __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 = (size * 5) / 8;
+
+               uint8_t *dest = (uint8_t*)malloc(dest_size * sizeof(char));
+               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 >= base32_decode_table_size ||
+                                       base32_decode_table[index] == -1) {
+                               free(dest);
+                               return (bool)false;
+                       }
+                       count++;
+
+                       char value = base32_decode_table[index];
+                       if (value == -2) {
+                               value = 0;
+                               pad++;
+                       }
+
+                       x <<= 5;
+                       x += 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 = (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(dispatch_data_t data)
+{
+       size_t total = dispatch_data_get_size(data);
+       __block size_t count = 0;
+
+       size_t dest_size = ((total + 4) * 8) / 5;
+       dest_size -= dest_size % 8;
+
+       uint8_t *dest = (uint8_t*)malloc(dest_size * sizeof(uint8_t));
+       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++ = base32_encode_table[(curr >> 3) & 0x1f];
+                               break;
+                       case 1:
+                               // b + c
+                               *ptr++ = base32_encode_table[((last << 2)|(curr >> 6)) & 0x1f];
+                               *ptr++ = base32_encode_table[(curr >> 1) & 0x1f];
+                               break;
+                       case 2:
+                               // d
+                               *ptr++ = base32_encode_table[((last << 4)|(curr >> 4)) & 0x1f];
+                               break;
+                       case 3:
+                               // e + f
+                               *ptr++ = base32_encode_table[((last << 1)|(curr >> 7)) & 0x1f];
+                               *ptr++ = base32_encode_table[(curr >> 2) & 0x1f];
+                               break;
+                       case 4:
+                               // g + h
+                               *ptr++ = base32_encode_table[((last << 3)|(curr >> 5)) & 0x1f];
+                               *ptr++ = base32_encode_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++ = base32_encode_table[(bytes[size-1] << 2) & 0x1c];
+                               break;
+                       case 2:
+                               // d[4]
+                               *ptr++ = base32_encode_table[(bytes[size-1] << 4) & 0x10];
+                               break;
+                       case 3:
+                               // e[4:1]
+                               *ptr++ = base32_encode_table[(bytes[size-1] << 1) & 0x1e];
+                               break;
+                       case 4:
+                               // g[4:3]
+                               *ptr++ = base32_encode_table[bytes[size-1] & 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);
+}
+
+#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 = (size * 3) / 4;
+
+               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++;
+
+                       char value = base64_decode_table[index];
+                       if (value == -2) {
+                               value = 0;
+                               pad++;
+                       }
+
+                       x <<= 6;
+                       x += value;
+
+                       if ((count & 0x3) == 0) {
+                               *ptr++ = (x >> 16) & 0xff;
+                               *ptr++ = (x >> 8) & 0xff;
+                               *ptr++ = x & 0xff;
+                       }
+               }
+
+               size_t final = (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);
+       __block size_t count = 0;
+
+       size_t dest_size = ((total + 2) * 4) / 3;
+       dest_size -= dest_size % 4;
+
+       uint8_t *dest = (uint8_t*)malloc(dest_size * sizeof(uint8_t));
+       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->type & ~output->input_mask) != 0) {
+               return NULL;
+       }
+
+       if ((output->type & ~input->output_mask) != 0) {
+               return NULL;
+       }
+
+       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 NULL;
+       }
+
+       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 = ~0,
+       .output_mask = ~0,
+       .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_BASE64),
+       .output_mask = (_DISPATCH_DATA_FORMAT_NONE | _DISPATCH_DATA_FORMAT_BASE32 |
+                       _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_base64 = {
+       .type = _DISPATCH_DATA_FORMAT_BASE64,
+       .input_mask = (_DISPATCH_DATA_FORMAT_NONE | _DISPATCH_DATA_FORMAT_BASE32 |
+                       _DISPATCH_DATA_FORMAT_BASE64),
+       .output_mask = (_DISPATCH_DATA_FORMAT_NONE | _DISPATCH_DATA_FORMAT_BASE32 |
+                       _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,
+};