本文是实现常用编码的实例,编码规则不讲解。

本文涉及编码种类

  • base58
  • base64
  • protobuf
  • json
  • xml

base58

源于bitcoin里面的设计,为了支付方便,相比Base64,Base58不使用数字"0",字母大写"O",字母大写"I",和字母小写"l",以及"+“和”/"符号。

base64 版本1

base64有很多不同的实现方法,在阅读ceph的代码时,看到的是我觉得最精简,代码风格完美的代码,因此本文引用来自ceph源码里面的编码。

#include <linux/errno.h>

/*
* base64 encode/decode.
*/

const char *pem_key = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";

static int encode_bits(int c)
{
return pem_key[c];
}

static int decode_bits(char c)
{
if (c >= 'A' && c <= 'Z')
return c - 'A';
if (c >= 'a' && c <= 'z')
return c - 'a' + 26;
if (c >= '0' && c <= '9')
return c - '0' + 52;
if (c == '+')
return 62;
if (c == '/')
return 63;
if (c == '=')
return 0; /* just non-negative, please */
return -EINVAL;
}

/*
函数功能:实现base64编码
*/
int ceph_armor(char *dst, const char *src, const char *end)
{
int olen = 0;
int line = 0;

while (src < end) {
unsigned char a, b, c;

a = *src++;
*dst++ = encode_bits(a >> 2);
if (src < end) {
b = *src++;
*dst++ = encode_bits(((a & 3) << 4) | (b >> 4));
if (src < end) {
c = *src++;
*dst++ = encode_bits(((b & 15) << 2) | (c >> 6));
*dst++ = encode_bits(c & 63);
} else {
*dst++ = encode_bits((b & 15) << 2);
*dst++ = '=';
}
} else {
*dst++ = encode_bits(((a & 3) << 4));
*dst++ = '=';
*dst++ = '=';
}
olen += 4;
line += 4;
if (line == 64) {
line = 0;
*(dst++) = '\n';
olen++;
}
}
return olen;
}

/*
函数功能:实现base64解码
*/
int ceph_unarmor(char *dst, const char *src, const char *end)
{
int olen = 0;

while (src < end) {
int a, b, c, d;

if (src < end && src[0] == '\n')
src++;
if (src + 4 > end)
return -EINVAL;
a = decode_bits(src[0]);
b = decode_bits(src[1]);
c = decode_bits(src[2]);
d = decode_bits(src[3]);
if (a < 0 || b < 0 || c < 0 || d < 0)
return -EINVAL;

*dst++ = (a << 2) | (b >> 4);
if (src[2] == '=')
return olen + 1;
*dst++ = ((b & 15) << 4) | (c >> 2);
if (src[3] == '=')
return olen + 2;
*dst++ = ((c & 3) << 6) | d;
olen += 3;
src += 4;
}
return olen;
}

base64 版本2

使用了openssl库来实现,封装了openssl的操作,形成一个壳子api

#include <openssl/bio.h>
#include <openssl/evp.h>
#include <openssl/buffer.h>

#include <string.h>


int encode_base64(const char * in, int in_len, char * out, int out_len)
{
BIO * bmem, *b64;
BUF_MEM * bptr;

b64 = BIO_new(BIO_f_base64());
bmem = BIO_new(BIO_s_mem());
b64 = BIO_push(b64, bmem);
BIO_write(b64, in, in_len);

if (BIO_flush(b64) < 0) {
return - 1;
}

BIO_get_mem_ptr(b64, &bptr);

int len = BIO_pending(bmem);

if (out_len <= len) {
return - 1;
}

memcpy(out, bptr->data, len);
out[len - 1] = '\0';

BIO_free_all(b64);

return 0;
}


int decode_base64(const char * in, int in_len, char * out, int out_len)
{
BIO * b64, *bmem;
int ret;
char in_eol[in_len + 2];

memcpy(in_eol, in, in_len);
in_eol[in_len] = '\n';
in_eol[in_len + 1] = '\0';

b64 = BIO_new(BIO_f_base64());
bmem = BIO_new_mem_buf((unsigned char *) in_eol, in_len + 1);
bmem = BIO_push(b64, bmem);

ret = BIO_read(bmem, out, out_len);

BIO_free_all(bmem);

return ret;
}

结语:
由于c++工程师的缘故,我们写任何代码总喜欢流出来各种接口,参数可以是string 、vector、char *、list等等,一个简单的功能硬是衍生出来N个重载函数,也许这样写代码执行效率高一点,别人调用起来好用一些,搞得代码很臃肿。我在写本文时突然意识到,代码要精简,模在应用层也要有很好的分层,能牺牲效率,换来代码的精简是值得的。

因此我建议,c++开发人员在写基础函数时,只写一个或最多两个类型的参数接口就行了。

作为应用开发人员,而不是boost、stl库 或公司基础库开发人员,就是要勇于牺牲部分代码执行效率,换来代码的可维护性,这样你的代码才能长大,而且是健康的长大。

未完待续