001/**
002 *
003 * Copyright the original author or authors
004 *
005 * Licensed under the Apache License, Version 2.0 (the "License");
006 * you may not use this file except in compliance with the License.
007 * You may obtain a copy of the License at
008 *
009 *     http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017package org.jivesoftware.smack.util;
018
019
020import java.io.ByteArrayOutputStream;
021import java.io.DataOutputStream;
022import java.io.IOException;
023
024/**
025 * Base32 string encoding is useful for when filenames case-insensitive filesystems are encoded.
026 * Base32 representation takes roughly 20% more space then Base64.
027 * 
028 * @author Florian Schmaus
029 * Based on code by Brian Wellington (bwelling@xbill.org)
030 * @see <a href="http://en.wikipedia.org/wiki/Base32">Base32 Wikipedia entry</a>
031 *
032 */
033public class Base32Encoder implements StringEncoder {
034
035    private static Base32Encoder instance = new Base32Encoder();
036    private static final String ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZ2345678";
037
038    private Base32Encoder() {
039        // Use getInstance()
040    }
041
042    public static Base32Encoder getInstance() {
043        return instance;
044    }
045
046    @Override
047    public String decode(String str) {
048        ByteArrayOutputStream bs = new ByteArrayOutputStream();
049        byte[] raw = str.getBytes();
050        for (int i = 0; i < raw.length; i++) {
051            char c = (char) raw[i];
052            if (!Character.isWhitespace(c)) {
053                c = Character.toUpperCase(c);
054                bs.write((byte) c);
055            }
056        }
057
058        while (bs.size() % 8 != 0)
059            bs.write('8');
060
061        byte[] in = bs.toByteArray();
062
063        bs.reset();
064        DataOutputStream ds = new DataOutputStream(bs);
065
066        for (int i = 0; i < in.length / 8; i++) {
067            short[] s = new short[8];
068            int[] t = new int[5];
069
070            int padlen = 8;
071            for (int j = 0; j < 8; j++) {
072                char c = (char) in[i * 8 + j];
073                if (c == '8')
074                    break;
075                s[j] = (short) ALPHABET.indexOf(in[i * 8 + j]);
076                if (s[j] < 0)
077                    return null;
078                padlen--;
079            }
080            int blocklen = paddingToLen(padlen);
081            if (blocklen < 0)
082                return null;
083
084            // all 5 bits of 1st, high 3 (of 5) of 2nd
085            t[0] = (s[0] << 3) | s[1] >> 2;
086            // lower 2 of 2nd, all 5 of 3rd, high 1 of 4th
087            t[1] = ((s[1] & 0x03) << 6) | (s[2] << 1) | (s[3] >> 4);
088            // lower 4 of 4th, high 4 of 5th
089            t[2] = ((s[3] & 0x0F) << 4) | ((s[4] >> 1) & 0x0F);
090            // lower 1 of 5th, all 5 of 6th, high 2 of 7th
091            t[3] = (s[4] << 7) | (s[5] << 2) | (s[6] >> 3);
092            // lower 3 of 7th, all of 8th
093            t[4] = ((s[6] & 0x07) << 5) | s[7];
094
095            try {
096                for (int j = 0; j < blocklen; j++)
097                    ds.writeByte((byte) (t[j] & 0xFF));
098            } catch (IOException e) {
099            }
100        }
101
102        return new String(bs.toByteArray());
103    }
104
105    @Override
106    public String encode(String str) {
107        byte[] b = str.getBytes();
108        ByteArrayOutputStream os = new ByteArrayOutputStream();
109
110        for (int i = 0; i < (b.length + 4) / 5; i++) {
111            short s[] = new short[5];
112            int t[] = new int[8];
113
114            int blocklen = 5;
115            for (int j = 0; j < 5; j++) {
116                if ((i * 5 + j) < b.length)
117                    s[j] = (short) (b[i * 5 + j] & 0xFF);
118                else {
119                    s[j] = 0;
120                    blocklen--;
121                }
122            }
123            int padlen = lenToPadding(blocklen);
124
125            // convert the 5 byte block into 8 characters (values 0-31).
126
127            // upper 5 bits from first byte
128            t[0] = (byte) ((s[0] >> 3) & 0x1F);
129            // lower 3 bits from 1st byte, upper 2 bits from 2nd.
130            t[1] = (byte) (((s[0] & 0x07) << 2) | ((s[1] >> 6) & 0x03));
131            // bits 5-1 from 2nd.
132            t[2] = (byte) ((s[1] >> 1) & 0x1F);
133            // lower 1 bit from 2nd, upper 4 from 3rd
134            t[3] = (byte) (((s[1] & 0x01) << 4) | ((s[2] >> 4) & 0x0F));
135            // lower 4 from 3rd, upper 1 from 4th.
136            t[4] = (byte) (((s[2] & 0x0F) << 1) | ((s[3] >> 7) & 0x01));
137            // bits 6-2 from 4th
138            t[5] = (byte) ((s[3] >> 2) & 0x1F);
139            // lower 2 from 4th, upper 3 from 5th;
140            t[6] = (byte) (((s[3] & 0x03) << 3) | ((s[4] >> 5) & 0x07));
141            // lower 5 from 5th;
142            t[7] = (byte) (s[4] & 0x1F);
143
144            // write out the actual characters.
145            for (int j = 0; j < t.length - padlen; j++) {
146                char c = ALPHABET.charAt(t[j]);
147                os.write(c);
148            }
149        }
150        return new String(os.toByteArray());
151    }
152
153    private static int lenToPadding(int blocklen) {
154        switch (blocklen) {
155        case 1:
156            return 6;
157        case 2:
158            return 4;
159        case 3:
160            return 3;
161        case 4:
162            return 1;
163        case 5:
164            return 0;
165        default:
166            return -1;
167        }
168    }
169
170    private static int paddingToLen(int padlen) {
171        switch (padlen) {
172        case 6:
173            return 1;
174        case 4:
175            return 2;
176        case 3:
177            return 3;
178        case 1:
179            return 4;
180        case 0:
181            return 5;
182        default:
183            return -1;
184        }
185    }
186
187}