package org.cablelabs.safi.tools; /** * <BeginCopyright> * Notice * <p> * This document is the result of a cooperative effort undertaken at the direction of Cable * Television Laboratories, Inc. for the benefit of the cable industry and its customers. This * document may contain references to other documents not owned or controlled by CableLabs. Use * and understanding of this document may require access to such other documents. Designing, * manufacturing, distributing, using, selling, or servicing products, or providing services, based * on this document may require intellectual property licenses from third parties for technology * referenced in this document. Neither CableLabs nor any member company is responsible to any party * for any liability of any nature whatsoever resulting from or arising out of use or reliance upon * this document, or any document referenced herein. This document is furnished on an "AS IS" basis * and neither CableLabs nor its members provides any representation or warranty, express or * implied, regarding the accuracy, completeness, non-infringement, or fitness for a particular * purpose of this document, or any document referenced herein. Distribution of this document is * restricted pursuant to the terms of separate access agreements negotiated with each of the * parties to whom this document has been furnished. * <p> * Copyright 2009 Cable Television Laboratories, Inc. All rights reserved. * <EndCopyright> */ import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.io.StringBufferInputStream; import org.w3c.tools.codec.Base64UrlDecoder; import org.w3c.tools.codec.Base64UrlEncoder; import org.w3c.tools.codec.Base64FormatException; /** * This is a class that holds a PEID (per RFC 4122) and can accept and return in a variety of forms. * They are 1) native, an array of 16 binary bytes, 2) canonical form, (32 characters * consisting of 32 hex digits with dashes at specific places per RFC 4122), 3) the normal 24 byte * Base64 version, and 4) Cablelabs(R) SaFI PEID form - 22 characters of Base64 form per RFC 3548 * with padding suppressed. */ public class Peid { private static final int PEID_LENGTH = 16; private final static String kHexChars = "0123456789abcdefABCDEF"; private byte[] mPeid; boolean loaded = false; /** * Constructor, empty requiring one of the set calls */ public Peid() { mPeid = new byte[PEID_LENGTH]; for (int i = 0; i < PEID_LENGTH; i++) { mPeid[i] = 0; } loaded = false; } /* Routine for loading a PEID from the canonical string * representation. * * Note that implementation is optimized for speed, not necessarily * code clarity * * @param id String that contains the canonical representation of * the PEID to build; 36-char string (see UUID specs for details). * Hex-chars may be in upper-case too; UUID class will always output * them in lowercase. */ public byte[] setPeidCanonical(String canonicalId) throws NumberFormatException { if (canonicalId == null) { throw new NullPointerException(); } if (canonicalId.length() != 36) { throw new NumberFormatException("UUID has to be represented by the standard 36-char representation"); } for (int i = 0, j = 0; i < 36; ++j) { // Need to bypass hyphens: switch (i) { case 8: case 13: case 18: case 23: if (canonicalId.charAt(i) != '-') { throw new NumberFormatException("UUID has to be represented by the standard 36-char canonical representation"); } ++i; } int index; char c = canonicalId.charAt(i); if (c >= '0' && c <= '9') { mPeid[j] = (byte) ((c - '0') << 4); } else if (c >= 'a' && c <= 'f') { mPeid[j] = (byte) ((c - 'a' + 10) << 4); } else if (c >= 'A' && c <= 'F') { mPeid[j] = (byte) ((c - 'A' + 10) << 4); } else { throw new NumberFormatException("Non-hex character '"+c+"'"); } c = canonicalId.charAt(++i); if (c >= '0' && c <= '9') { mPeid[j] |= (byte) (c - '0'); } else if (c >= 'a' && c <= 'f') { mPeid[j] |= (byte) (c - 'a' + 10); } else if (c >= 'A' && c <= 'F') { mPeid[j] |= (byte) (c - 'A' + 10); } else { throw new NumberFormatException("Non-hex character '"+c+"'"); } ++i; } loaded = true; return mPeid; } /** * Routine for loading the UUID from base 64 representation. All Base64 encodings of a PEID end * in two '=' signs. */ public byte[] setPeidBase64(String base64Str) throws java.io.IOException, org.w3c.tools.codec.Base64FormatException { if (base64Str == null) { throw new NullPointerException(); } if (base64Str.length() != 24) { throw new NumberFormatException("UUID has to be represented by the standard 24-char base64 representation"); } StringBufferInputStream sbrdr = new java.io.StringBufferInputStream(base64Str); ByteArrayOutputStream wtr = new ByteArrayOutputStream(); Base64UrlDecoder b = new Base64UrlDecoder (sbrdr,wtr); b.setEncodeUrl(false); b.setDropPadding(false); b.process(); byte[] tmpPeid = wtr.toByteArray(); if (tmpPeid.length != PEID_LENGTH) { throw new NumberFormatException("UUID conversion error, resulting length is "+tmpPeid.length+", must be "+PEID_LENGTH); } loaded = true; mPeid = tmpPeid; return mPeid; } /** * Routine for loading a PEID from the modified base 64 URL representation. This encoding * uses the normal Base64 char substituions, but has the two trailing '==' removed, as in * Base64URL */ public byte[] setPeidBase64Unpadded(String base64Str) throws java.io.IOException, org.w3c.tools.codec.Base64FormatException { if (base64Str == null) { throw new NullPointerException(); } if (base64Str.length() != 22) { throw new NumberFormatException("PEID has to be represented by the modified 22-char base64Url representation"); } StringBufferInputStream sbrdr = new java.io.StringBufferInputStream(base64Str); ByteArrayOutputStream wtr = new ByteArrayOutputStream(); Base64UrlDecoder b = new Base64UrlDecoder (sbrdr,wtr); b.setEncodeUrl(false); b.setDropPadding(true); b.process(); byte[] tmpPeid = wtr.toByteArray(); //Note we get extra byte from partial if (tmpPeid.length != PEID_LENGTH+1) { throw new NumberFormatException("UUID conversion error, resulting length is "+tmpPeid.length+", must be "+PEID_LENGTH); } loaded = true; mPeid = new byte[PEID_LENGTH]; System.arraycopy(tmpPeid, 0, mPeid, 0, PEID_LENGTH); return mPeid; } /** * Routine for loading the PEID from an existing byte array */ public byte[] setPeid(byte[] peid) { if (peid.length != PEID_LENGTH) { throw new NumberFormatException("Peid byte array length is "+peid.length+", must be "+PEID_LENGTH); } mPeid = peid; loaded = true; return mPeid; } /** * Return the current peid. All 0 if never loaded, else last value set from * a canonical or base64 representation * * @return byte[] */ public byte[] getPeid() { if (!loaded) { throw new NumberFormatException("No PEID has been loaded"); } return mPeid; } /** * Return the current peid value as a 22 char unpadded Base64 string * * @return String */ public String getPeidBase64Unpadded() { if (!loaded) { throw new NumberFormatException("No PEID has been loaded"); } Base64UrlEncoder b = new Base64UrlEncoder(mPeid); b.setEncodeUrl(false); b.setDropPadding(true); return b.processString(); } /** * Return the current peid value as a 24 char Base64 string * * @return String */ public String getPeidBase64() { if (!loaded) { throw new NumberFormatException("No PEID has been loaded"); } Base64UrlEncoder b = new Base64UrlEncoder(mPeid); b.setEncodeUrl(false); b.setDropPadding(false); return b.processString(); } /** * Return the current peid value as a canonical string * * @return String */ public String getPeidCanonical() { /* Could be synchronized, but there isn't much harm in just taking * our chances (ie. in the worst case we'll form the string more * than once... but result is the same) */ if (!loaded) { throw new NumberFormatException("No PEID has been loaded"); } StringBuffer b = new StringBuffer(36); for (int i = 0; i < PEID_LENGTH; ++i) { // Need to bypass hyphens: switch (i) { case 4: case 6: case 8: case 10: b.append('-'); } int toHex = mPeid[i] & 0xFF; b.append(kHexChars.charAt(toHex >> 4)); b.append(kHexChars.charAt(toHex & 0x0f)); } return b.toString(); } /** * Driver for conversions * Command line args: output-format input * where output-format is one of C (canonical) or B (base-64) * and input is either a 24 byte base64 formated PEID or a 36 byte canonical PEID */ public static void main(String[] args) { String usage = "Command line args: output-format input\r\n" + " where output-format is one of C (canonical), B (base-64, 24\r\n" + " characters), or U (base64 unpadded, 22 characters)\r\n" + "and input is a single or comma-delimited (no space) list of\r\n" + " a 24 byte base64 PEID, a 22 byte base64 unpadded PEID\r\n" + " a base64Unpadded formated PEID or a 36 byte canonical PEID"; //Check args if (args.length != 2) { System.out.println("Wrong number of args"); System.out.println(usage); System.exit(0); } //Try this //First, load peid with input value Peid peid = new Peid(); String[] inputs = args[1].split(","); if (inputs.length == 0) { System.out.println("Found no elements in (comma-delimited) input"); System.out.println(usage); System.exit(0); } //Process each input for (String input : inputs) { if (input.length() == 24) { try { peid.setPeidBase64(input); } catch (java.io.IOException ioe) { System.out.println("Conversion error, probably invalid base 64 string: "+ioe); System.exit(1); } catch (org.w3c.tools.codec.Base64FormatException bfe) { System.out.println("Conversion error, probably invalid base 64 string: "+bfe); System.exit(1); } } else if (input.length() == 22) { try { peid.setPeidBase64Unpadded(input); } catch (java.io.IOException ioe) { System.out.println("Conversion error, probably invalid base 64 string: "+ioe); System.exit(1); } catch (org.w3c.tools.codec.Base64FormatException bfe) { System.out.println("Conversion error, probably invalid base 64 string: "+bfe); System.exit(1); } } else if (input.length() == 36) { try { peid.setPeidCanonical(input); } catch (NumberFormatException nfe) { System.out.println("Conversion error, probably invalid canonical formatted string: "+nfe); System.exit(1); } } else { System.out.println("Invalid input format"); System.out.println(usage); System.exit(0); } //Now display in requested output form if (args[0].equalsIgnoreCase("c")) { System.out.println(input+"="+peid.getPeidCanonical()); } else if (args[0].equalsIgnoreCase("b")) { System.out.println(input+"="+peid.getPeidBase64()); } else if (args[0].equalsIgnoreCase("u")) { System.out.println(input+"="+peid.getPeidBase64Unpadded()); } else { System.out.println("Invalid output-format"); System.out.println(usage); System.exit(0); } } //end for each input } }