]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
basic: util - add base64mem() function similar to hexmem() 566/head
authorTom Gundersen <teg@jklm.no>
Fri, 10 Jul 2015 12:38:19 +0000 (14:38 +0200)
committerTom Gundersen <teg@jklm.no>
Sun, 12 Jul 2015 17:24:14 +0000 (19:24 +0200)
This implements RFC4648 for a slightly more compact representation of
binary data compared to hex (6 bits per character rather than 4).

src/basic/util.c
src/basic/util.h
src/test/test-util.c

index 61dd6c4227301eb2d65ca9a71b16f1a747701f01..bc917ae5741ec1dd8cacf5ca665e4f3afce9c23a 100644 (file)
@@ -954,6 +954,180 @@ int unhexmem(const char *p, size_t l, void **mem, size_t *len) {
         return 0;
 }
 
+/* https://tools.ietf.org/html/rfc4648#section-4 */
+char base64char(int x) {
+        static const char table[64] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
+                                      "abcdefghijklmnopqrstuvwxyz"
+                                      "0123456789+/";
+        return table[x & 63];
+}
+
+int unbase64char(char c) {
+        unsigned offset;
+
+        if (c >= 'A' && c <= 'Z')
+                return c - 'A';
+
+        offset = 'Z' - 'A' + 1;
+
+        if (c >= 'a' && c <= 'z')
+                return c - 'a' + offset;
+
+        offset += 'z' - 'a' + 1;
+
+        if (c >= '0' && c <= '9')
+                return c - '0' + offset;
+
+        offset += '9' - '0' + 1;
+
+        if (c == '+')
+                return offset;
+
+        offset ++;
+
+        if (c == '/')
+                return offset;
+
+        return -EINVAL;
+}
+
+char *base64mem(const void *p, size_t l) {
+        char *r, *z;
+        const uint8_t *x;
+
+        /* three input bytes makes four output bytes, padding is added so we must round up */
+        z = r = malloc(4 * (l + 2) / 3 + 1);
+        if (!r)
+                return NULL;
+
+        for (x = p; x < (const uint8_t*) p + (l / 3) * 3; x += 3) {
+                /* x[0] == XXXXXXXX; x[1] == YYYYYYYY; x[2] == ZZZZZZZZ */
+                *(z++) = base64char(x[0] >> 2);                    /* 00XXXXXX */
+                *(z++) = base64char((x[0] & 3) << 4 | x[1] >> 4);  /* 00XXYYYY */
+                *(z++) = base64char((x[1] & 15) << 2 | x[2] >> 6); /* 00YYYYZZ */
+                *(z++) = base64char(x[2] & 63);                    /* 00ZZZZZZ */
+        }
+
+        switch (l % 3) {
+        case 2:
+                *(z++) = base64char(x[0] >> 2);                   /* 00XXXXXX */
+                *(z++) = base64char((x[0] & 3) << 4 | x[1] >> 4); /* 00XXYYYY */
+                *(z++) = base64char((x[1] & 15) << 2);            /* 00YYYY00 */
+                *(z++) = '=';
+
+                break;
+        case 1:
+                *(z++) = base64char(x[0] >> 2);        /* 00XXXXXX */
+                *(z++) = base64char((x[0] & 3) << 4);  /* 00XX0000 */
+                *(z++) = '=';
+                *(z++) = '=';
+
+                break;
+        }
+
+        *z = 0;
+        return r;
+}
+
+int unbase64mem(const char *p, size_t l, void **mem, size_t *_len) {
+        _cleanup_free_ uint8_t *t = NULL;
+        int a, b, c, d;
+        uint8_t *r, *z;
+        const char *x;
+        size_t len;
+
+        assert(p);
+
+        /* padding ensures any base63 input has input divisible by 4 */
+        if (l % 4 != 0)
+                return -EINVAL;
+
+        /* strip the padding */
+        if (l > 0 && p[l - 1] == '=')
+                l --;
+        if (l > 0 && p[l - 1] == '=')
+                l --;
+
+        /* a group of four input bytes needs three output bytes, in case of
+           padding we need to add two or three extra bytes */
+        len = (l / 4) * 3 + (l % 4 ? (l % 4) - 1 : 0);
+
+        z = r = malloc(len + 1);
+        if (!r)
+                return -ENOMEM;
+
+        for (x = p; x < p + (l / 4) * 4; x += 4) {
+                /* a == 00XXXXXX; b == 00YYYYYY; c == 00ZZZZZZ; d == 00WWWWWW */
+                a = unbase64char(x[0]);
+                if (a < 0)
+                        return -EINVAL;
+
+                b = unbase64char(x[1]);
+                if (b < 0)
+                        return -EINVAL;
+
+                c = unbase64char(x[2]);
+                if (c < 0)
+                        return -EINVAL;
+
+                d = unbase64char(x[3]);
+                if (d < 0)
+                        return -EINVAL;
+
+                *(z++) = (uint8_t) a << 2 | (uint8_t) b >> 4; /* XXXXXXYY */
+                *(z++) = (uint8_t) b << 4 | (uint8_t) c >> 2; /* YYYYZZZZ */
+                *(z++) = (uint8_t) c << 6 | (uint8_t) d;      /* ZZWWWWWW */
+        }
+
+        switch (l % 4) {
+        case 3:
+                a = unbase64char(x[0]);
+                if (a < 0)
+                        return -EINVAL;
+
+                b = unbase64char(x[1]);
+                if (b < 0)
+                        return -EINVAL;
+
+                c = unbase64char(x[2]);
+                if (c < 0)
+                        return -EINVAL;
+
+                /* c == 00ZZZZ00 */
+                if (c & 3)
+                        return -EINVAL;
+
+                *(z++) = (uint8_t) a << 2 | (uint8_t) b >> 4; /* XXXXXXYY */
+                *(z++) = (uint8_t) b << 4 | (uint8_t) c >> 2; /* YYYYZZZZ */
+
+                break;
+        case 2:
+                a = unbase64char(x[0]);
+                if (a < 0)
+                        return -EINVAL;
+
+                b = unbase64char(x[1]);
+                if (b < 0)
+                        return -EINVAL;
+
+                /* b == 00YY0000 */
+                if (b & 15)
+                        return -EINVAL;
+
+                *(z++) = (uint8_t) a << 2 | (uint8_t) (b >> 4); /* XXXXXXYY */
+
+                break;
+        }
+
+        *z = 0;
+
+        *mem = r;
+        r = NULL;
+        *_len = len;
+
+        return 0;
+}
+
 char octchar(int x) {
         return '0' + (x & 7);
 }
index ea77907f7f3a09a3afeefe33a9ada86fbdf35e18..dae43006e4dbdd056c21b8cffee4713c42917951 100644 (file)
@@ -240,6 +240,8 @@ char octchar(int x) _const_;
 int unoctchar(char c) _const_;
 char decchar(int x) _const_;
 int undecchar(char c) _const_;
+char base64char(int x) _const_;
+int unbase64char(char c) _const_;
 
 char *cescape(const char *s);
 size_t cescape_char(char c, char *buf);
@@ -616,6 +618,9 @@ static inline void *mempset(void *s, int c, size_t n) {
 char *hexmem(const void *p, size_t l);
 int unhexmem(const char *p, size_t l, void **mem, size_t *len);
 
+char *base64mem(const void *p, size_t l);
+int unbase64mem(const char *p, size_t l, void **mem, size_t *len);
+
 char *strextend(char **x, ...) _sentinel_;
 char *strrep(const char *s, unsigned n);
 
index f7949ebaac2341729df8e46ee2a49dc0ecd1e628..72fbc345c276a115b29859187fd314caf53ebe8c 100644 (file)
@@ -390,6 +390,24 @@ static void test_unhexchar(void) {
         assert_se(unhexchar('0') == 0x0);
 }
 
+static void test_base64char(void) {
+        assert_se(base64char(0) == 'A');
+        assert_se(base64char(26) == 'a');
+        assert_se(base64char(63) == '/');
+}
+
+static void test_unbase64char(void) {
+        assert_se(unbase64char('A') == 0);
+        assert_se(unbase64char('Z') == 25);
+        assert_se(unbase64char('a') == 26);
+        assert_se(unbase64char('z') == 51);
+        assert_se(unbase64char('0') == 52);
+        assert_se(unbase64char('9') == 61);
+        assert_se(unbase64char('+') == 62);
+        assert_se(unbase64char('/') == 63);
+        assert_se(unbase64char('=') == -EINVAL);
+}
+
 static void test_octchar(void) {
         assert_se(octchar(00) == '0');
         assert_se(octchar(07) == '7');
@@ -434,6 +452,84 @@ static void test_unhexmem(void) {
         assert_se(memcmp(hex, hex2, strlen(hex) - 1) == 0);
 }
 
+/* https://tools.ietf.org/html/rfc4648#section-10 */
+static void test_base64mem(void) {
+        char *b64;
+
+        b64 = base64mem("", strlen(""));
+        assert_se(b64);
+        assert_se(streq(b64, ""));
+        free(b64);
+
+        b64 = base64mem("f", strlen("f"));
+        assert_se(b64);
+        assert_se(streq(b64, "Zg=="));
+        free(b64);
+
+        b64 = base64mem("fo", strlen("fo"));
+        assert_se(b64);
+        assert_se(streq(b64, "Zm8="));
+        free(b64);
+
+        b64 = base64mem("foo", strlen("foo"));
+        assert_se(b64);
+        assert_se(streq(b64, "Zm9v"));
+        free(b64);
+
+        b64 = base64mem("foob", strlen("foob"));
+        assert_se(b64);
+        assert_se(streq(b64, "Zm9vYg=="));
+        free(b64);
+
+        b64 = base64mem("fooba", strlen("fooba"));
+        assert_se(b64);
+        assert_se(streq(b64, "Zm9vYmE="));
+        free(b64);
+
+        b64 = base64mem("foobar", strlen("foobar"));
+        assert_se(b64);
+        assert_se(streq(b64, "Zm9vYmFy"));
+        free(b64);
+}
+
+static void test_unbase64mem(void) {
+        void *mem;
+        size_t len;
+
+        assert_se(unbase64mem("", strlen(""), &mem, &len) == 0);
+        assert_se(streq(strndupa(mem, len), ""));
+        free(mem);
+
+        assert_se(unbase64mem("Zg==", strlen("Zg=="), &mem, &len) == 0);
+        assert_se(streq(strndupa(mem, len), "f"));
+        free(mem);
+
+        assert_se(unbase64mem("Zm8=", strlen("Zm8="), &mem, &len) == 0);
+        assert_se(streq(strndupa(mem, len), "fo"));
+        free(mem);
+
+        assert_se(unbase64mem("Zm9v", strlen("Zm9v"), &mem, &len) == 0);
+        assert_se(streq(strndupa(mem, len), "foo"));
+        free(mem);
+
+        assert_se(unbase64mem("Zm9vYg==", strlen("Zm9vYg=="), &mem, &len) == 0);
+        assert_se(streq(strndupa(mem, len), "foob"));
+        free(mem);
+
+        assert_se(unbase64mem("Zm9vYmE=", strlen("Zm9vYmE="), &mem, &len) == 0);
+        assert_se(streq(strndupa(mem, len), "fooba"));
+        free(mem);
+
+        assert_se(unbase64mem("Zm9vYmFy", strlen("Zm9vYmFy"), &mem, &len) == 0);
+        assert_se(streq(strndupa(mem, len), "foobar"));
+        free(mem);
+
+        assert_se(unbase64mem("A", strlen("A"), &mem, &len) == -EINVAL);
+        assert_se(unbase64mem("A====", strlen("A===="), &mem, &len) == -EINVAL);
+        assert_se(unbase64mem("AAB==", strlen("AAB=="), &mem, &len) == -EINVAL);
+        assert_se(unbase64mem("AAAB=", strlen("AAAB="), &mem, &len) == -EINVAL);
+}
+
 static void test_cescape(void) {
         _cleanup_free_ char *escaped;
 
@@ -1828,11 +1924,15 @@ int main(int argc, char *argv[]) {
         test_in_charset();
         test_hexchar();
         test_unhexchar();
+        test_base64char();
+        test_unbase64char();
         test_octchar();
         test_unoctchar();
         test_decchar();
         test_undecchar();
         test_unhexmem();
+        test_base64mem();
+        test_unbase64mem();
         test_cescape();
         test_cunescape();
         test_foreach_word();