/* This file is part of VoltDB. * Copyright (C) 2008-2017 VoltDB Inc. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with VoltDB. If not, see <http://www.gnu.org/licenses/>. */ package org.voltdb.utils; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.util.zip.DeflaterOutputStream; import java.util.zip.InflaterInputStream; import org.voltdb.common.Constants; /** * Encode and decode strings and byte arrays to/from hexidecimal * string values. This was originally added so binary values could * be added to the VoltDB catalogs. * */ public class Encoder { private static final int caseDiff = ('a' - 'A'); /** * * @param data A binary array of bytes. * @return A hex-encoded string with double length. */ public static String hexEncode(byte[] data) { if (data == null) return null; StringBuilder sb = new StringBuilder(); for (byte b : data) { // hex encoding same way as java.net.URLEncoder. char ch = Character.forDigit((b >> 4) & 0xF, 16); // to uppercase if (Character.isLetter(ch)) { ch -= caseDiff; } sb.append(ch); ch = Character.forDigit(b & 0xF, 16); if (Character.isLetter(ch)) { ch -= caseDiff; } sb.append(ch); } return sb.toString(); } /** * * @param string A string to be hex encoded. * @return The double-length hex encoded string. */ public static String hexEncode(String string) { // this will need to be less "western" in the future return hexEncode(string.getBytes(Constants.UTF8ENCODING)); } /** * * @param hexString An (even-length) hexidecimal string to be decoded. * @return The binary byte array value for the string (half length). */ public static byte[] hexDecode(String hexString) { byte[] retval = null; final String errorText = "String is not properly hex-encoded."; if ((hexString.length() % 2) != 0) throw new RuntimeException(errorText); try { retval = new byte[hexString.length() / 2]; for (int i = 0; i < retval.length; i++) { int value = Integer.parseInt(hexString.substring(2 * i, 2 * i + 2), 16); retval[i] = (byte) value; } } catch (IllegalArgumentException exc) { // parseInt can throw a NumberFormatException, which is a subclass of // IllegalArgumentException, so both kinds of failure come here. throw new RuntimeException(errorText); } return retval; } /** * * @param hexString A string of hexidecimal chars of even length. * @return A string value de-hexed from the input. */ public static String hexDecodeToString(String hexString) { byte[] decodedValue = hexDecode(hexString); return new String(decodedValue, Constants.UTF8ENCODING); } public static boolean isHexEncodedString(String hexString) { if ((hexString.length() % 2) != 0) return false; try { for (int i = 0; i < hexString.length(); i++) { int value = Integer.parseInt(hexString.substring(i, i + 1), 16); if ((value < 0) || (value > 15)) return false; } } catch (NumberFormatException e) { return false; } return true; } public static String compressAndBase64Encode(String string) { try { byte[] inBytes = string.getBytes(Constants.UTF8ENCODING); ByteArrayOutputStream baos = new ByteArrayOutputStream((int)(string.length() * 0.7)); DeflaterOutputStream dos = new DeflaterOutputStream(baos); dos.write(inBytes); dos.close(); byte[] outBytes = baos.toByteArray(); return Base64.encodeToString(outBytes, false); } catch (Exception e) { throw new RuntimeException(e); } } public static String compressAndBase64Encode(byte[] bytes) { try { ByteArrayOutputStream baos = new ByteArrayOutputStream(bytes.length); DeflaterOutputStream dos = new DeflaterOutputStream(baos); dos.write(bytes); dos.close(); byte[] outBytes = baos.toByteArray(); return Base64.encodeToString(outBytes, false); } catch (Exception e) { throw new RuntimeException(e); } } public static String decodeBase64AndDecompress(String string) { if (string.length() == 0) { return ""; } byte bytes[] = decodeBase64AndDecompressToBytes(string); return new String(bytes, Constants.UTF8ENCODING); } public static byte[] decodeBase64AndDecompressToBytes(String string) { byte bytes[] = Base64.decodeFast(string); if (string.length() == 0) { return new byte[0]; } ByteArrayInputStream bais = new ByteArrayInputStream(bytes); InflaterInputStream dis = new InflaterInputStream(bais); byte buffer[] = new byte[1024 * 8]; int length = 0; ByteArrayOutputStream baos = new ByteArrayOutputStream(); try { while ( (length = dis.read( buffer )) >= 0) { baos.write(buffer, 0, length); } } catch (Exception e) { throw new RuntimeException(e); } return baos.toByteArray(); } public static String base64Encode(String string) { try { final byte[] inBytes = string.getBytes(Constants.UTF8ENCODING); return Base64.encodeToString(inBytes, false); } catch (Exception e) { throw new RuntimeException(e); } } public static String base64Encode(byte[] bytes) { return Base64.encodeToString(bytes, false); } public static byte[] base64Decode(String string) { return Base64.decodeFast(string); } public static String base64DecodeToString(String string) { byte[] decodedValue = base64Decode(string); return new String(decodedValue, Constants.UTF8ENCODING); } }