1/**
2 * All rights reserved. Licensed under the Apache License, Version 2.0 (the "License");
3 * you may not use this file except in compliance with the License.
4 * You may obtain a copy of the License at
5 *
6 *     http://www.apache.org/licenses/LICENSE-2.0
7 *
8 * Unless required by applicable law or agreed to in writing, software
9 * distributed under the License is distributed on an "AS IS" BASIS,
10 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11 * See the License for the specific language governing permissions and
12 * limitations under the License.
13 */
14package org.jivesoftware.smack.util;
15
16
17import java.io.ByteArrayOutputStream;
18import java.io.DataOutputStream;
19import java.io.IOException;
20
21/**
22 * Base32 string encoding is useful for when filenames case-insensitive filesystems are encoded.
23 * Base32 representation takes roughly 20% more space then Base64.
24 *
25 * @author Florian Schmaus
26 * Based on code by Brian Wellington (bwelling@xbill.org)
27 * @see <a href="http://en.wikipedia.org/wiki/Base32">Base32 Wikipedia entry<a>
28 *
29 */
30public class Base32Encoder implements StringEncoder {
31
32    private static Base32Encoder instance = new Base32Encoder();
33    private static final String ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZ2345678";
34
35    private Base32Encoder() {
36        // Use getInstance()
37    }
38
39    public static Base32Encoder getInstance() {
40        return instance;
41    }
42
43    @Override
44    public String decode(String str) {
45        ByteArrayOutputStream bs = new ByteArrayOutputStream();
46        byte[] raw = str.getBytes();
47        for (int i = 0; i < raw.length; i++) {
48            char c = (char) raw[i];
49            if (!Character.isWhitespace(c)) {
50                c = Character.toUpperCase(c);
51                bs.write((byte) c);
52            }
53        }
54
55        while (bs.size() % 8 != 0)
56            bs.write('8');
57
58        byte[] in = bs.toByteArray();
59
60        bs.reset();
61        DataOutputStream ds = new DataOutputStream(bs);
62
63        for (int i = 0; i < in.length / 8; i++) {
64            short[] s = new short[8];
65            int[] t = new int[5];
66
67            int padlen = 8;
68            for (int j = 0; j < 8; j++) {
69                char c = (char) in[i * 8 + j];
70                if (c == '8')
71                    break;
72                s[j] = (short) ALPHABET.indexOf(in[i * 8 + j]);
73                if (s[j] < 0)
74                    return null;
75                padlen--;
76            }
77            int blocklen = paddingToLen(padlen);
78            if (blocklen < 0)
79                return null;
80
81            // all 5 bits of 1st, high 3 (of 5) of 2nd
82            t[0] = (s[0] << 3) | s[1] >> 2;
83            // lower 2 of 2nd, all 5 of 3rd, high 1 of 4th
84            t[1] = ((s[1] & 0x03) << 6) | (s[2] << 1) | (s[3] >> 4);
85            // lower 4 of 4th, high 4 of 5th
86            t[2] = ((s[3] & 0x0F) << 4) | ((s[4] >> 1) & 0x0F);
87            // lower 1 of 5th, all 5 of 6th, high 2 of 7th
88            t[3] = (s[4] << 7) | (s[5] << 2) | (s[6] >> 3);
89            // lower 3 of 7th, all of 8th
90            t[4] = ((s[6] & 0x07) << 5) | s[7];
91
92            try {
93                for (int j = 0; j < blocklen; j++)
94                    ds.writeByte((byte) (t[j] & 0xFF));
95            } catch (IOException e) {
96            }
97        }
98
99        return new String(bs.toByteArray());
100    }
101
102    @Override
103    public String encode(String str) {
104        byte[] b = str.getBytes();
105        ByteArrayOutputStream os = new ByteArrayOutputStream();
106
107        for (int i = 0; i < (b.length + 4) / 5; i++) {
108            short s[] = new short[5];
109            int t[] = new int[8];
110
111            int blocklen = 5;
112            for (int j = 0; j < 5; j++) {
113                if ((i * 5 + j) < b.length)
114                    s[j] = (short) (b[i * 5 + j] & 0xFF);
115                else {
116                    s[j] = 0;
117                    blocklen--;
118                }
119            }
120            int padlen = lenToPadding(blocklen);
121
122            // convert the 5 byte block into 8 characters (values 0-31).
123
124            // upper 5 bits from first byte
125            t[0] = (byte) ((s[0] >> 3) & 0x1F);
126            // lower 3 bits from 1st byte, upper 2 bits from 2nd.
127            t[1] = (byte) (((s[0] & 0x07) << 2) | ((s[1] >> 6) & 0x03));
128            // bits 5-1 from 2nd.
129            t[2] = (byte) ((s[1] >> 1) & 0x1F);
130            // lower 1 bit from 2nd, upper 4 from 3rd
131            t[3] = (byte) (((s[1] & 0x01) << 4) | ((s[2] >> 4) & 0x0F));
132            // lower 4 from 3rd, upper 1 from 4th.
133            t[4] = (byte) (((s[2] & 0x0F) << 1) | ((s[3] >> 7) & 0x01));
134            // bits 6-2 from 4th
135            t[5] = (byte) ((s[3] >> 2) & 0x1F);
136            // lower 2 from 4th, upper 3 from 5th;
137            t[6] = (byte) (((s[3] & 0x03) << 3) | ((s[4] >> 5) & 0x07));
138            // lower 5 from 5th;
139            t[7] = (byte) (s[4] & 0x1F);
140
141            // write out the actual characters.
142            for (int j = 0; j < t.length - padlen; j++) {
143                char c = ALPHABET.charAt(t[j]);
144                os.write(c);
145            }
146        }
147        return new String(os.toByteArray());
148    }
149
150    private static int lenToPadding(int blocklen) {
151        switch (blocklen) {
152        case 1:
153            return 6;
154        case 2:
155            return 4;
156        case 3:
157            return 3;
158        case 4:
159            return 1;
160        case 5:
161            return 0;
162        default:
163            return -1;
164        }
165    }
166
167    private static int paddingToLen(int padlen) {
168        switch (padlen) {
169        case 6:
170            return 1;
171        case 4:
172            return 2;
173        case 3:
174            return 3;
175        case 1:
176            return 4;
177        case 0:
178            return 5;
179        default:
180            return -1;
181        }
182    }
183
184}
185