1 /**
  2  *  <pre>Bubble Babble Binary Data Encoding
  3  *
  4  * Copyright 2013 Izzyway - <a href = "">http://izzyway.com/</a>
  5  *
  6  * This program is free software: you can redistribute it and/or modify
  7  * it under the terms of the GNU General Public License as published by
  8  * the Free Software Foundation, either version 3 of the License, or
  9  * any later version.
 10  *
 11  * This program is distributed in the hope that it will be useful,
 12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
 13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 14  * GNU General Public License for more details.
 15  *
 16  * You should have received a copy of the GNU General Public License
 17  * along with this program.  If not, see <a href = "http://www.gnu.org/licenses/">http://www.gnu.org/licenses/</a>.
 18  *
 19  *</pre>
 20  */
 21 /**
 22  * @constructor
 23  * @class Create a new BubbleBabble encoder/decoder class
 24  * @version 1.0
 25  */ 
 26 function BubbleBabble(){
 27 
 28     this.VOWELS = 'aeiouy';
 29     this.CONSONANTS = 'bcdfghklmnprstvzx';
 30 	
 31 	/**
 32 	 * Encode an input string to BubbleBabble string
 33 	 * @param {string} src Input string
 34 	 * @return {string} An encoded string
 35      * @example new BubbleBabble().encode('foo'); // Will return xinik-zorox
 36 	 */
 37     BubbleBabble.prototype.encode = function(src){
 38 		if (!src) throw 'Bad string';
 39 		var cursor = 0, checksum = 1, out = 'x', number;
 40         while (cursor < src.length){
 41 			number = cursor + 1 == src.length?1:2;	
 42 			var buffer = src.substring(cursor, cursor + number);			
 43 			cursor += number;
 44             var character1 = buffer.charCodeAt(0);			
 45             out += this.VOWELS[((((character1>>6)&3) + checksum)%6)];			
 46             out += this.CONSONANTS[((character1>>2)&15)];
 47 			out += this.VOWELS[(((character1 & 3) + Math.trunc(checksum / 6)) % 6)];
 48 			if (number == 1) break;
 49             var character2 = buffer.charCodeAt(1);
 50             out += this.CONSONANTS[((character2>>4)&15)];
 51             out += '-';
 52             out += this.CONSONANTS[(character2 & 15)];
 53             checksum = (checksum * 5 + character1 * 7 + character2) % 36;
 54         }
 55         if (number != 1){
 56             out += this.VOWELS[(checksum % 6)];
 57             out += this.CONSONANTS[16];
 58             out += this.VOWELS[Math.trunc(checksum / 6)];
 59         }
 60         out += 'x';
 61 		return out;
 62     }
 63 
 64     BubbleBabble.prototype.decode2WayByte = function(a1, a2, offset){
 65         if (a1 > 16) throw 'Bad string at offset ' + offset;
 66         if (a2 > 16) throw 'Bad string at offset ' +  (offset+2);
 67         return ((a1 << 4) | a2);
 68     }
 69 
 70     BubbleBabble.prototype.decode3WayByte = function(a1, a2, a3, offset, c){	
 71         var high2 = (a1 - (c%6) + 6) % 6;
 72         if (high2 >= 4) throw 'Bad string at offset ' + offset;
 73         if (a2 > 16) throw 'Bad string at offset ' + (offset+1);
 74         var low2 = (a3 - (Math.trunc(c/6)%6) + 6) % 6;				
 75         if (low2 >= 4) throw 'Bad string at offset ' + (offset+2);
 76         return (high2<<6 | a2<<2 | low2);
 77     }
 78 
 79     BubbleBabble.prototype.decodeTuple = function(src){
 80         var tuple = [];
 81         tuple.push(this.VOWELS.indexOf(src[0]));
 82         tuple.push(this.CONSONANTS.indexOf(src[1]));
 83         tuple.push(this.VOWELS.indexOf(src[2]));
 84         if (src.length > 3){
 85             tuple.push(this.CONSONANTS.indexOf(src[3]));
 86             tuple.push('-');
 87             tuple.push(this.CONSONANTS.indexOf(src[5]));
 88         }		
 89         return tuple;
 90     }
 91     /**
 92 	 * Transform an input string to decoded string or BubbleBabble string
 93 	 * @param {string} src Input string
 94 	 * @return {string} An decoded or encoded string
 95 	 * @example new BubbleBabble().transform('foo'); // Will return xinik-zorox
 96 	 * @example new BubbleBabble().transform('xinik-zorox'); // Will return foo
 97 	 */
 98     BubbleBabble.prototype.transform = function(src){
 99 		if (!src) throw 'Bad string';
100         if (this.isEncoded(src)) return this.decode(src);
101         else return this.encode(src);
102     }
103 	/**
104 	 * Decode an BubbleBabble string to string
105 	 * @param {string} src BubbleBabble string
106 	 * @return {string} A string
107      * @example new BubbleBabble().decode('xinik-zorox'); // Will return foo
108 	 * @throws {exception} when the BubbleBabble is not correctly encoded
109 	 */
110     BubbleBabble.prototype.decode = function(src){  
111 		if (!src) throw 'Bad string';
112         if (src[0] != 'x') throw 'Bad string at offset 0: must begin with a "x"';
113         var checksum = 1, index = 0, number, last = false, cursor = 1, out = '';
114         while (cursor < src.length){
115 			number = src.length - cursor > 5?6:4;
116 			var buffer = src.substring(cursor, cursor + number);
117 			cursor += number;
118             if (number == 4){
119                 if (buffer[3] != 'x') throw 'Bad string: must end with a "x"';
120                 last = true;
121                 number -= 1;
122             }else if (number != 6){
123                 throw 'Bad string: wrong length';
124             }            
125             var pos = index * 6;
126             var tuple = this.decodeTuple(buffer.substring(0, number));
127             if (last){
128                 if (tuple[1] == 16){
129                     if (tuple[0] != checksum % 6) throw 'Bad string at offset '+pos;
130                     if (tuple[2] != Math.trunc(checksum / 6)) throw 'Bad string at offset '+(pos+2);
131                 }else{
132                     out += String.fromCharCode(this.decode3WayByte(tuple[0], tuple[1], tuple[2], pos, checksum));
133                 }
134             }else{
135                 var b1 = this.decode3WayByte(tuple[0], tuple[1], tuple[2], pos, checksum);
136                 var b2 = this.decode2WayByte(tuple[3], tuple[5], pos);
137                 out += String.fromCharCode(b1) + String.fromCharCode(b2);                
138                 checksum = (checksum * 5 + b1 * 7 + b2) % 36;
139             }
140             index++;			
141         }
142 		return out;
143     }
144 	/**
145 	 * Return true if the input string is BubbleBabble encoded
146 	 * @param {string} src input string
147 	 * @return {booblean} true if the input is BubbleBabble encoded, false else
148      * @example new BubbleBabble().isEncoded('xinik-zorox'); // true	 
149 	 */
150     BubbleBabble.prototype.isEncoded = function(string){
151         if (string[0] != 'x' || string[string.length -1] != 'x') return false;
152         if (string.length != 5 && string.length%6 != 5) return false;
153         return /^([abcdefghijklmnopqrstuvwxyz]{5}?-)*[abcdefghijklmnopqrstuvwxyz]{5}?$/.test(string);
154     }
155 }