/** * Copyright 2007-2015, Kaazing Corporation. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.kaazing.k3po.pcap.converter.internal.author.script; import java.nio.charset.Charset; import java.util.StringTokenizer; import org.kaazing.k3po.pcap.converter.internal.author.emitter.Emitter; public class ByteArrayWriter { private static final int MAX_HEX_LINE_SIZE = 130; public static final int MAX_TEXT_LINE_SIZE = 130; private static final int ASSUME_TEXT_AFTER = 5; private static final Charset UTF8 = Charset.forName("UTF8"); public enum Type { WRITE, READ; @Override public String toString() { return name().toLowerCase(); } } private enum State { BINARY, CANDIDATE_TEXT, TEXT, END; } private State state; final String textStart, textEnd, binaryStart, binaryEnd; final int maxBinaryBytesPerLine; final int maxTextBytesPerLine; final Emitter emitter; int start; // position up to which bytes have been written int startCandidateText; // possible start of text bytes (CANDIDATE_TEXT state only) int position; // position of the byte which is being processed byte[] bytes; public ByteArrayWriter(Type type, Emitter emitter) { this(emitter, type + " \"", "\"", type + " [", "]"); } private ByteArrayWriter(Emitter emitter, String textStart, String textEnd, String hexStart, String hexEnd) { this.emitter = emitter; this.textStart = textStart; this.textEnd = textEnd; this.binaryStart = hexStart; this.binaryEnd = hexEnd; // each output binary byte (0xhh) takes 5 characters including a space after maxBinaryBytesPerLine = (MAX_HEX_LINE_SIZE - textStart.length() - textEnd.length() + 1) / 5; maxTextBytesPerLine = (MAX_TEXT_LINE_SIZE - textStart.length() - textEnd.length()); } public void write(byte[] bytes) { this.bytes = bytes; start = 0; state = State.BINARY; for (position=0; position < bytes.length; position++) { int codePoint = bytes[position] & 0xFF; if (shouldTreatAsText(codePoint)) { switch(state) { case BINARY: transitionBinaryToCandidateText(); break; case CANDIDATE_TEXT: if (position + 1 - startCandidateText == ASSUME_TEXT_AFTER) { transitionCandidateTextToText(); } break; default: break; } } else { switch(state) { case CANDIDATE_TEXT: transitionCandidateTextToBinary(); break; case TEXT: transitionTextToBinary(); break; default: break; } } } transitionToEnd(); } /** * Determine if the given data byte should be written out as text or binary (hexadecimal). * In order to ensure the generated Robot script can be read and edited in all environments we * only treat 7 bit characters (ascii) as text. This method returns true for all printable ascii * characters (digits, letters or punctuation), space, linefeed and newline. */ private boolean shouldTreatAsText(int codePoint) { return codePoint == '\r' || codePoint == '\n' || (codePoint >= 0x20 // space && codePoint <= 0x7E) // tilde, max printable ascii character ; } private void transitionBinaryToCandidateText() { startCandidateText = position; state = State.CANDIDATE_TEXT; } private void transitionCandidateTextToBinary() { state = State.BINARY; } private void transitionCandidateTextToText() { writeBinary(start, startCandidateText); start = startCandidateText; state = State.TEXT; } private void transitionTextToBinary() { writeText(start, position); start = position; state = State.BINARY; } private void transitionToEnd() { switch(state) { case BINARY: case CANDIDATE_TEXT: writeBinary(start, position); break; case TEXT: writeText(start, position); break; default: break; } state = State.END; } private void writeBinary(int fromInclusive, int toExclusive) { if (toExclusive == fromInclusive) { return; } int end = Math.min(toExclusive, fromInclusive + maxBinaryBytesPerLine); int outputLength = binaryStart.length() + binaryEnd.length() + 5 * (end - fromInclusive) - 1; StringBuilder sb = new StringBuilder(outputLength); sb.append(binaryStart); int i; for (i=fromInclusive; i < end; i++) { sb.append(String.format("0x%02X ", bytes[i])); } sb.replace(sb.length() - 1, sb.length(), binaryEnd); emitter.add(sb.toString()); emitter.add("\n"); if (end < toExclusive) { writeBinary(end, toExclusive); } } private void writeText(int fromInclusive, int toExclusive) { String text = new String(bytes, fromInclusive, toExclusive - fromInclusive, UTF8); text = text.replace("\r", "\\r"); text = text.replace("\n", "\\n\n"); StringTokenizer tokenizer = new StringTokenizer(text, "\n"); while (tokenizer.hasMoreTokens()) { writeTextLine(tokenizer.nextToken(), 0); } } private void writeTextLine(String line, int from) { int end = Math.min(line.length() - from, maxTextBytesPerLine) + from; emitter.add(textStart); emitter.add(line.substring(from, end)); emitter.add(textEnd); emitter.add("\n"); if (end < line.length()) { writeTextLine(line, end); } } }