/* * Copyright 2017 ZhangJiupeng * * 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 cc.agentx.wrapper; import cc.agentx.util.KeyHelper; import java.io.ByteArrayOutputStream; import java.util.ArrayList; import java.util.Arrays; import java.util.List; public class FrameWrapper extends Wrapper { private ByteArrayOutputStream wrapBuffer; private ByteArrayOutputStream unwrapBuffer; private List<byte[]> unwrapFrames; private int frameLength; private int reservedHeaderLength; private Wrapper frameHandler; /* * notice that the fixed-frame-length does not require a accurate * fixed data. It will work in a pure variable frame mode if this * parameter is set to a large number. */ public FrameWrapper(int fixedFrameLength) { if (fixedFrameLength < 4) { throw new RuntimeException("bad fixed-frame length < 4"); } this.wrapBuffer = new ByteArrayOutputStream(); this.unwrapBuffer = new ByteArrayOutputStream(); this.unwrapFrames = new ArrayList<>(); this.frameLength = fixedFrameLength - 1; if (fixedFrameLength < 0xFF) reservedHeaderLength = 1; else if (fixedFrameLength < 0xFFFF - 2) reservedHeaderLength = 2; else if (fixedFrameLength < 0xFFFFFF - 3) reservedHeaderLength = 3; else reservedHeaderLength = 4; } /** * frameHandler will be invoked before the frame re-concat into * data stream, see unwrap(). */ public FrameWrapper(int fixedFrameLength, Wrapper frameHandler) { this(fixedFrameLength); this.frameHandler = frameHandler; } /* * data will be wrapped into several frames, but marked as a whole * chunk (we call it a data-package) */ @Override public byte[] wrap(final byte[] bytes) { byte[] payload; if (frameHandler == null) { payload = bytes; } else { payload = frameHandler.wrap(bytes); } int i, nof = (payload.length / frameLength) + (payload.length % frameLength == 0 ? 0 : 1); for (i = 0; i < nof - 1; i++) { wrapBuffer.write(1); wrapBuffer.write(payload, i * frameLength, frameLength); } wrapBuffer.write(0); wrapBuffer.write(KeyHelper.getBytes(reservedHeaderLength, payload.length - i * frameLength), 0, reservedHeaderLength); wrapBuffer.write(payload, i * frameLength, payload.length - i * frameLength); byte[] wrappedBytes = wrapBuffer.toByteArray(); wrapBuffer.reset(); return wrappedBytes; } /* * notice that the subscribe below was outdated * * data will not completely return if the received data is incomplete, * it will be held into the unwrap-buffer until the rest part are received; * if the incoming data are concatenated, it will return a data-package a time. * if no complete data (includes unwrap-buffer) is received, return null. * * notice that the unwrap methods affects each other because of their * usages of the same unwrap-buffer, please use one of these methods * from beginning to end. */ @Override public byte[] unwrap(final byte[] bytes) { byte[] ret; if (frameHandler == null) { ret = unwrapAll(bytes); } else { ByteArrayOutputStream stream = new ByteArrayOutputStream(); unwrapFrames(bytes).forEach(e -> unwrapFrames.add(frameHandler.unwrap(e))); unwrapFrames.forEach(e -> stream.write(e, 0, e.length)); unwrapFrames.clear(); ret = stream.toByteArray(); } return ret.length > 0 ? ret : null; /* unwrapFrames(bytes).forEach(e -> unwrapFrames.add(e)); if (unwrapFrames.size() > 0) { return unwrapFrames.remove(0); } else { return null; } */ } /* * return all complete data-packages in a byte array, incomplete data will be sent * into buffer, two or more concatenated packs will be merged into one. * * notice that the unwrap methods affects each other because of their * usages of the same unwrap-buffer, please use one of these methods * from beginning to end. */ public byte[] unwrapAll(byte[] bytes) { byte[] data = new byte[unwrapBuffer.size() + bytes.length]; System.arraycopy(unwrapBuffer.toByteArray(), 0, data, 0, unwrapBuffer.size()); System.arraycopy(bytes, 0, data, unwrapBuffer.size(), bytes.length); unwrapBuffer.reset(); ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); int pos = 0, limit = pos + 1 + frameLength, size = data.length; while (pos < size) { byte[] buffer; switch (data[pos]) { case 0: limit = Math.min(limit + 1 + frameLength + 1, size); buffer = Arrays.copyOfRange(data, pos, limit); if (buffer.length < 1 + reservedHeaderLength) { unwrapBuffer.write(buffer, 0, buffer.length); // frame-len was truncated return outputStream.toByteArray(); } int endFrameLength = KeyHelper.toBigEndianInteger(Arrays.copyOfRange(buffer, 1, 1 + reservedHeaderLength)); if (buffer.length < 1 + reservedHeaderLength + endFrameLength) { unwrapBuffer.write(buffer, 0, buffer.length); // data was truncated return outputStream.toByteArray(); } else { outputStream.write(buffer, 1 + reservedHeaderLength, endFrameLength); pos += 1 + reservedHeaderLength + endFrameLength; limit = pos + 1 + frameLength; } break; case 1: buffer = Arrays.copyOfRange(data, pos, limit); outputStream.write(buffer, 1, buffer.length - 1); pos += 1 + frameLength; limit = Math.min(limit + 1 + frameLength, data.length); break; default: throw new RuntimeException("unknown delimiter " + data[pos]); } } return outputStream.toByteArray(); } /* * if the incoming data are concatenated, it will return all complete * data-package a time. * * notice that the unwrap methods affects each other because of their * usages of the same unwrap-buffer, please use one of these methods * from beginning to end. */ public List<byte[]> unwrapFrames(byte[] bytes) { List<byte[]> ret = new ArrayList<>(); byte[] data = new byte[unwrapBuffer.size() + bytes.length]; System.arraycopy(unwrapBuffer.toByteArray(), 0, data, 0, unwrapBuffer.size()); System.arraycopy(bytes, 0, data, unwrapBuffer.size(), bytes.length); unwrapBuffer.reset(); ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); int pos = 0, limit = pos + 1 + frameLength, size = data.length; while (pos < size) { byte[] buffer; switch (data[pos]) { case 0: limit = Math.min(limit + 1 + frameLength + 1, size); buffer = Arrays.copyOfRange(data, pos, limit); if (buffer.length < 1 + reservedHeaderLength) { unwrapBuffer.write(buffer, 0, buffer.length); // frame-len was truncated return ret; } int endFrameLength = KeyHelper.toBigEndianInteger(Arrays.copyOfRange(buffer, 1, 1 + reservedHeaderLength)); if (buffer.length < 1 + reservedHeaderLength + endFrameLength) { unwrapBuffer.write(buffer, 0, buffer.length); // data was truncated return ret; } else { outputStream.write(buffer, 1 + reservedHeaderLength, endFrameLength); ret.add(outputStream.toByteArray()); outputStream.reset(); pos += 1 + reservedHeaderLength + endFrameLength; limit = pos + 1 + frameLength; } break; case 1: buffer = Arrays.copyOfRange(data, pos, limit); outputStream.write(buffer, 1, buffer.length - 1); pos += 1 + frameLength; limit = Math.min(limit + 1 + frameLength, data.length); break; default: throw new RuntimeException("unknown delimiter " + data[pos]); } } return ret; } }