bubble-babble.c 5.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192
  1. #include <stdio.h>
  2. #include <stdlib.h>
  3. #include <unistd.h>
  4. #include <string.h>
  5. #include <errno.h>
  6. #include <sys/types.h>
  7. // How long is a tuple like "...[exax-x]..."
  8. #define TUP_LEN 6
  9. // DEC_BUF_LEN must always be a multiple of TUP_LEN.
  10. //
  11. // Furthermore, until some way is found to deal with pipe buffering better,
  12. // this should be kept small. Modest data doesn't pose any issue, but decoding
  13. // isn't robust against getting hammered.
  14. //
  15. // The number 24 works okay for tests like this:
  16. //
  17. // $ time ( dd if=/dev/urandom bs=1M count=32 status=none | bb | buffer | bb -d > /dev/null )
  18. //
  19. // Without the 'buffer' command, even DEC_BUF_LEN = 6 is a problem.
  20. // Whereas, even with the 'buffer' command, 48 is too big.
  21. //
  22. #define DEC_BUF_LEN 24
  23. // This must always be a multiple of two.
  24. #define ENC_BUF_LEN 4096
  25. const char *V = "aeiouy";
  26. const char *C = "bcdfghklmnprstvzx";
  27. void encode() {
  28. unsigned char D[ENC_BUF_LEN] = { 0 };
  29. int navail = 0;
  30. int bytes_read = 0;
  31. unsigned int chksum = 1;
  32. unsigned int a, b, c, d, e, n;
  33. unsigned int byte[2];
  34. printf("x");
  35. while((bytes_read = read(0, D, ENC_BUF_LEN)) != 0) {
  36. int err = errno;
  37. if(bytes_read < 0) {
  38. fprintf(stderr, "Encoding error %d: %s\n", err, strerror(err));
  39. }
  40. navail = bytes_read; // need to save number available for after the loop exits
  41. for(n = 0; n < navail/2; ++n) {
  42. byte[0] = (unsigned int)D[n*2];
  43. byte[1] = (unsigned int)D[n*2+1];
  44. a = (((byte[0] >> 6) & 3) + chksum)%6;
  45. b = (byte[0] >> 2) & 15;
  46. c = ((byte[0] & 3) + chksum/6)%6;
  47. d = (byte[1] >> 4) & 15;
  48. e = byte[1] & 15;
  49. printf("%c%c%c%c-%c", V[a], C[b], V[c], C[d], C[e]);
  50. chksum = (chksum*5 + byte[0]*7 + byte[1]) % 36;
  51. }
  52. }
  53. int err = errno;
  54. if(bytes_read < 0) {
  55. fprintf(stderr, "Encoding error %d: %s\n", err, strerror(err));
  56. }
  57. if(navail%2) {
  58. byte[0] = (unsigned int)D[navail-1];
  59. a = (((byte[0] >> 6) & 3) + chksum) % 6;
  60. b = (byte[0] >> 2) & 15;
  61. c = ((byte[0] & 3) + chksum/6) % 6;
  62. } else {
  63. a = chksum % 6;
  64. b = 16;
  65. c = chksum/6;
  66. }
  67. printf("%c%c%c", V[a], C[b], V[c]);
  68. printf("x\n");
  69. }
  70. unsigned int decode_2way_byte(const int a1, const int a2) {
  71. if(a1 > 16 || a2 > 16) {
  72. fprintf(stderr, "Invalid input: 'x' detected mid-stream while decoding 2-way byte\n");
  73. exit(1);
  74. }
  75. return (a1 << 4) | a2;
  76. }
  77. unsigned int decode_3way_byte(const int a1, const int a2, const int a3, const int c) {
  78. if(a2 > 16) {
  79. fprintf(stderr, "Invalid input: 'x' detected mid-stream while decoding 3-way byte\n");
  80. exit(1);
  81. }
  82. unsigned int high2 = (a1 - (c%6) + 6) % 6;
  83. unsigned int mid4 = a2;
  84. unsigned int low2 = (a3 - (c/6)%6 + 6) % 6;
  85. if(high2 >= 4 || low2 >= 4) {
  86. fprintf(stderr, "Invalid input: corruption detected while decoding 3-way byte\n");
  87. exit(1);
  88. }
  89. return (high2 << 6) | (mid4 << 2) | low2;
  90. }
  91. // Based on https://github.com/eur0pa/bubblepy
  92. void decode() {
  93. int inv[256] = {0};
  94. int n = 0;
  95. for(n = 0; n < strnlen(V, 32); ++n) {
  96. inv[(int)V[n]] = n;
  97. }
  98. for(n = 0; n < strnlen(C, 32); ++n) {
  99. inv[(int)C[n]] = n;
  100. }
  101. unsigned char E[DEC_BUF_LEN] = {0};
  102. read(0, E, 1);
  103. if(E[0] != 'x') {
  104. fprintf(stderr, "Invalid input: first character was not 'x'\n");
  105. exit(1);
  106. }
  107. int navail = 0;
  108. int bytes_read = 0;
  109. unsigned int chksum = 1;
  110. int tup[TUP_LEN];
  111. unsigned int byte[2];
  112. while((bytes_read = read(0, E, DEC_BUF_LEN)) > 0) {
  113. int err = errno;
  114. if(bytes_read < 0) {
  115. fprintf(stderr, "Decoding error %d: %s\n", err, strerror(err));
  116. }
  117. navail = bytes_read;
  118. for(n = 0; n < navail/TUP_LEN; ++n) {
  119. tup[0] = inv[(int)E[n*TUP_LEN]];
  120. tup[1] = inv[(int)E[n*TUP_LEN+1]];
  121. tup[2] = inv[(int)E[n*TUP_LEN+2]];
  122. tup[3] = inv[(int)E[n*TUP_LEN+3]];
  123. if(E[n*TUP_LEN+4] != '-') {
  124. fprintf(stderr, "Invalid input: missing dash delimiter. (Got a '%c' with value %d instead.)\n", E[n*TUP_LEN]+4, E[n*TUP_LEN]+4);
  125. exit(1);
  126. }
  127. tup[5] = inv[(int)E[n*TUP_LEN+5]];
  128. byte[0] = decode_3way_byte(tup[0], tup[1], tup[2], chksum);
  129. byte[1] = decode_2way_byte(tup[3], tup[5]);
  130. printf("%c%c", byte[0], byte[1]);
  131. chksum = (chksum*5 + byte[0]*7 + byte[1]) % 36;
  132. }
  133. }
  134. int err = errno;
  135. if(bytes_read < 0) {
  136. fprintf(stderr, "Decoding error %d: %s\n", err, strerror(err));
  137. }
  138. int remaining = navail-n*TUP_LEN;
  139. if(E[n*TUP_LEN+remaining-1] == '\n') {
  140. --remaining;
  141. }
  142. if(E[n*TUP_LEN+remaining-1] == 'x') {
  143. --remaining;
  144. } else {
  145. unsigned char L = E[n*TUP_LEN+remaining-1];
  146. fprintf(stderr, "Invalid input: final character was '%c' with value '%d' instead of 'x'\n", L, L);
  147. exit(1);
  148. }
  149. tup[0] = inv[(int)E[n*TUP_LEN]];
  150. tup[1] = inv[(int)E[n*TUP_LEN+1]];
  151. tup[2] = inv[(int)E[n*TUP_LEN+2]];
  152. char last = '\0';
  153. if(tup[1] == 16) {
  154. if(tup[0] != chksum%6 || tup[2] != chksum/6) {
  155. fprintf(stderr, "Invalid input: corruption detected\n");
  156. exit(1);
  157. }
  158. } else {
  159. last = decode_3way_byte(tup[0], tup[1], tup[2], chksum);
  160. printf("%c", last);
  161. }
  162. // Add a newline at the end, but not if piping to other commands, and only if one isn't already there.
  163. if(last != '\n' && isatty(1)) {
  164. printf("\n");
  165. }
  166. }
  167. void usage(char *name) {
  168. printf("%s [-d]\n\nEncode data from stdin into 'bubble babble'.\n\n\t-d\tDecode instead of encode.\n", name);
  169. }
  170. int main(int argc, char **argv) {
  171. if(argc == 2) {
  172. if(strncmp(argv[1], "-d", 4) == 0) {
  173. decode();
  174. } else {
  175. usage(argv[0]);
  176. }
  177. } else {
  178. encode();
  179. }
  180. return 0;
  181. }