/*
* Copyright 2010 NCHOVY
*
* 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.krakenapps.pcap.util;
import java.io.UnsupportedEncodingException;
import java.nio.BufferUnderflowException;
import java.nio.InvalidMarkException;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
/**
* @author mindori
*/
public class ChainBuffer implements Buffer {
private List<byte[]> buffers;
/* start[0] = bufIndex of start point. baseOffset = offset of start point. */
private int baseIndex;
private int baseOffset;
private int markIndex = -1;
private int markOffset = -1;
private int bufIndex = 0;
private int bufOffset = 0;
public ChainBuffer() {
buffers = new ArrayList<byte[]>();
baseIndex = 0;
baseOffset = 0;
}
public ChainBuffer(byte[] b) {
this();
addLast(b);
}
/* copy constructor */
public ChainBuffer(Buffer other) {
buffers = new ArrayList<byte[]>();
buffers.addAll(other.getBuffers());
int[] metaData = other.getMetaData();
baseIndex = metaData[0];
baseOffset = metaData[1];
markIndex = metaData[2];
markOffset = metaData[3];
bufIndex = metaData[4];
bufOffset = metaData[5];
}
/* called by copy constructor */
@Override
public int[] getMetaData() {
return new int[] { baseIndex, baseOffset, markIndex, markOffset, bufIndex, bufOffset };
}
@Override
public List<byte[]> getBuffers() {
return buffers;
}
@Override
public int getCapacity() {
int capacity = 0;
for (byte[] b : buffers)
capacity += b.length;
return capacity;
}
@Override
public int getBaseIndex() {
return baseIndex;
}
@Override
public int getBaseOffset() {
return baseOffset;
}
@Override
public int getBufIndex() {
return bufIndex;
}
@Override
public int getOffset() {
return bufOffset;
}
@Override
public int position() {
int absPos = 0;
if (bufIndex > 0) {
int loopCount = 0;
for (byte[] buffer : buffers) {
absPos += buffer.length;
if (loopCount >= (bufIndex - 1))
break;
loopCount++;
}
}
absPos += bufOffset;
return absPos;
}
@Override
public Buffer position(int newPosition) {
int absPos = 0;
int i = 0;
if (newPosition < 0)
throw new IllegalArgumentException();
for (byte[] buffer : buffers) {
if (absPos + buffer.length >= newPosition) {
bufIndex = i;
bufOffset = newPosition - absPos;
return this;
}
absPos += buffer.length;
i++;
}
throw new IllegalArgumentException();
}
@Override
public void addFirst(byte[] buffer) {
if (buffer == null)
return;
buffers.add(0, buffer);
}
@Override
public void addLast(byte[] buffer) {
if (buffer == null)
return;
buffers.add(buffer);
}
@Override
public void addFirst(Buffer buffer) {
if (buffer == null)
return;
List<byte[]> newBufList = buffer.getBuffers();
buffers.addAll(0, newBufList);
}
@Override
public void addLast(Buffer buffer) {
if (buffer == null)
return;
/* copy to current offset ~ EOB */
List<byte[]> l = buffer.getBuffers();
int i = buffer.getBaseIndex();
int j = buffer.getBaseOffset();
if (i >= l.size())
return;
byte[] b = l.get(i);
if (j > 0) {
byte[] t = new byte[b.length - j];
for (int k = 0; k < t.length; k++) {
t[k] = b[k + j];
}
buffers.add(t);
if (i + 1 < l.size())
buffers.addAll(l.subList(i + 1, l.size()));
} else {
if (i < l.size())
buffers.addAll(l.subList(i, l.size()));
}
}
@Override
public void addLast(Buffer buffer, int length) {
if (buffer == null)
return;
List<byte[]> l = buffer.getBuffers();
int i = buffer.getBufIndex();
int j = buffer.getOffset();
if (i >= l.size() || length <= 0)
return;
/* calculate bufIndex */
buffer.mark();
if (buffer.skip(length) == null)
return;
int m = buffer.getBufIndex();
int n = buffer.getOffset();
buffer.reset();
byte[] b = l.get(i);
if (j > 0) {
if (m == i) {
byte[] t = new byte[n - j];
for (int k = 0; k < t.length; k++) {
t[k] = b[k + j];
}
buffers.add(t);
}
else {
if (m == i + 1) {
byte[] t = new byte[b.length - j];
for (int k = 0; k < t.length; k++) {
t[k] = b[k + j];
}
buffers.add(t);
if (n > 0) {
byte[] b1 = l.get(m);
byte[] t1 = new byte[n];
for (int k = 0; k < t1.length; k++) {
t1[k] = b1[k];
}
buffers.add(t1);
}
} else {
byte[] t = new byte[b.length - j];
for (int k = 0; k < t.length; k++) {
t[k] = b[k + j];
}
buffers.add(t);
if (i + 1 == m - 1)
buffers.add(l.get(i + 1));
else
buffers.addAll(l.subList(i + 1, m));
if (n > 0) {
byte[] b1 = l.get(m);
byte[] t1 = new byte[n];
for (int k = 0; k < t1.length; k++) {
t1[k] = b1[k];
}
buffers.add(t1);
}
}
}
} else {
/* j == 0 */
if (m == i) {
byte[] t1 = new byte[n];
for (int k = 0; k < t1.length; k++) {
t1[k] = b[k];
}
buffers.add(t1);
}
else {
if (i == m - 1) {
buffers.add(b);
if (n > 0) {
byte[] b1 = l.get(m);
byte[] t1 = new byte[n];
for (int k = 0; k < t1.length; k++) {
t1[k] = b1[k];
}
buffers.add(t1);
}
} else {
buffers.addAll(l.subList(i, m));
if (n > 0) {
byte[] b1 = l.get(m);
byte[] t1 = new byte[n];
for (int k = 0; k < t1.length; k++) {
t1[k] = b1[k];
}
buffers.add(t1);
}
}
}
}
}
@Override
public Buffer skip(int pos) {
/* move failed => don't moved. */
if (pos <= 0)
return null;
int skipped = 0;
int i = bufIndex;
byte[] buf = buffers.get(i);
int next = buf.length - bufOffset;
skipped += next;
if (skipped >= pos) {
bufOffset += pos;
if (bufOffset >= buf.length) {
bufIndex += 1;
bufOffset = 0;
}
// discardReadBytes();
return this;
}
i++;
while (i < buffers.size()) {
next = buffers.get(i).length;
skipped += next;
if (skipped >= pos) {
int remain = skipped - pos;
bufIndex = i;
bufOffset = (next - remain);
if (bufOffset >= next) {
bufIndex += 1;
bufOffset = 0;
}
// discardReadBytes();
return this;
}
i++;
}
return null;
}
@Override
public byte get() throws BufferUnderflowException {
/* fetch address: [bufIndex, offset] */
if (bufIndex >= buffers.size())
throw new BufferUnderflowException();
byte[] buf = buffers.get(bufIndex);
if (bufOffset >= buf.length) {
bufOffset = 0;
bufIndex += 1;
if (bufIndex >= buffers.size())
throw new BufferUnderflowException();
buf = buffers.get(bufIndex);
}
byte retVal = buf[bufOffset];
/* modify offset */
if ((bufOffset + 1) >= buf.length) {
bufIndex += 1;
bufOffset = 0;
} else
bufOffset += 1;
return retVal;
}
@Override
public short getShort() throws BufferUnderflowException {
try {
byte[] b = new byte[2];
gets(b, 0, 2);
short s = 0;
for (int i = 0; i < 2; i++) {
s <<= 8;
s ^= (long) b[i] & 0xFF;
}
return s;
} catch (BufferUnderflowException e) {
throw e;
}
}
@Override
public int getUnsignedShort() {
return getShort() & 0xFFFF;
}
@Override
public int getInt() throws BufferUnderflowException {
try {
byte[] b = new byte[4];
gets(b, 0, 4);
int s = 0;
for (int i = 0; i < 4; i++) {
s <<= 8;
s ^= (long) b[i] & 0xFF;
}
return s;
} catch (BufferUnderflowException e) {
throw e;
}
}
@Override
public long getUnsignedInt() {
return getInt() & 0xFFFFFFFFl;
}
@Override
public long getLong() throws BufferUnderflowException {
try {
byte[] b = new byte[8];
gets(b, 0, 8);
long s = 0;
for (int i = 0; i < 8; i++) {
s <<= 8;
s ^= (long) b[i] & 0xFFl;
}
return s;
} catch (BufferUnderflowException e) {
throw e;
}
}
@Override
public String getString(int length) throws BufferUnderflowException {
byte[] str = new byte[length];
try {
gets(str, 0, length);
return new String(str);
} catch (BufferUnderflowException e) {
throw e;
}
}
@Override
public String getString(int length, String charsetName) throws BufferUnderflowException {
byte[] str = new byte[length];
try {
gets(str, 0, length);
return new String(str, charsetName);
} catch (BufferUnderflowException e) {
throw e;
} catch (UnsupportedEncodingException e) {
return new String(str);
}
}
@Override
public String getString(int length, Charset charset) throws BufferUnderflowException {
byte[] str = new byte[length];
try {
gets(str, 0, length);
return new String(str, charset);
} catch (BufferUnderflowException e) {
throw e;
}
}
@Override
public void gets(byte[] buffer) {
gets(buffer, 0, buffer.length);
}
@Override
public void gets(byte[] buffer, int offset, int length) throws BufferUnderflowException {
if (length == 0)
return;
if (bufIndex >= buffers.size())
throw new BufferUnderflowException();
/* calculate length */
int remain = readableBytes();
if (remain < length)
throw new BufferUnderflowException();
int index = offset;
int i = 0;
int j = bufIndex;
int bufI = bufIndex;
int off = bufOffset;
int bufCount = 0;
int count = 0;
for (byte[] b : buffers) {
if (bufCount < j) {
/* skip buffer until count == bufIndex */
bufCount++;
continue;
}
for (i = off; i < b.length; i++) {
if (count == length) {
off = i;
break;
}
buffer[index] = b[i];
index++;
count++;
}
if (count == length) {
if (i == b.length) {
bufI++;
off = 0;
}
break;
} else {
/* go to next buffer */
bufI++;
off = 0;
}
}
bufIndex = bufI;
bufOffset = off;
}
@Override
public int bytesBefore(byte[] target) {
/* if, stateNum equals to target.length => pattern matching to target. */
mark();
int stateNum = 0;
int length = 0;
if (bufIndex >= buffers.size()) {
reset();
return 0;
}
/* retrieve first byte array */
byte[] bs = buffers.get(bufIndex);
for (int i = bufOffset; i < bs.length; i++) {
if (bs[i] == target[stateNum])
stateNum++;
else if (bs[i] == target[0]) {
stateNum = 1;
} else
stateNum = 0;
length++;
if (stateNum == target.length) {
/* return length */
reset();
return (length - target.length);
}
}
/* get sublist from bufList */
int index = bufIndex + 1;
List<byte[]> subList = buffers.subList(index, buffers.size());
for (byte[] bs2 : subList) {
for (byte b : bs2) {
if (b == target[stateNum])
stateNum++;
else if (b == target[0])
stateNum = 1;
else
stateNum = 0;
length++;
if (stateNum == target.length) {
/* return length */
reset();
return (length - target.length);
}
}
}
reset();
return 0;
}
@Override
public void mark() {
markIndex = bufIndex;
markOffset = bufOffset;
}
@Override
public void rewind() {
bufIndex = baseIndex;
bufOffset = baseOffset;
markIndex = -1;
markOffset = -1;
}
@Override
public Buffer reset() {
if (markIndex == -1 && markOffset == -1)
throw new InvalidMarkException();
if (bufIndex > markIndex) {
int rewindOffset = bufOffset;
for (int i = bufIndex - 1; i > markIndex; i--) {
byte[] buf = buffers.get(i);
rewindOffset += buf.length;
}
byte[] markBuf = buffers.get(markIndex);
rewindOffset += (markBuf.length - markOffset);
return reset(rewindOffset);
} else if (bufIndex < markIndex) {
int absPos = 0;
int i = bufIndex + 1;
while (i < markIndex) {
absPos += buffers.get(i).length;
i++;
}
absPos += buffers.get(bufIndex).length - bufOffset;
absPos += markOffset;
return skip(absPos);
} else {
/* markIndex = bufIndex */
if (bufOffset > markOffset) {
return reset(bufOffset - markOffset);
} else {
return skip(markOffset - bufOffset);
}
}
}
public Buffer reset(int rewindOffset) {
if (bufIndex > baseIndex) {
if (bufOffset >= rewindOffset) {
bufOffset -= rewindOffset;
} else {
int i = bufIndex;
int sumOffset = bufOffset;
byte[] b;
do {
i--;
b = buffers.get(i);
sumOffset += b.length;
} while (sumOffset < rewindOffset && i > 0);
if (sumOffset < rewindOffset) {
/* retrieve failed: bufList[bufIndex..0] */
return null;
} else {
bufIndex = i;
bufOffset = sumOffset - rewindOffset;
}
}
} else {
/* bufIndex = baseIndex */
if (bufOffset >= rewindOffset)
bufOffset -= rewindOffset;
else
/* Invalid rewindOffset */
return null;
}
return this;
}
public void discardReadBytes() {
/* Truncated start ~ current */
if (bufIndex >= buffers.size())
return;
byte[] buf = buffers.get(bufIndex);
if (bufOffset >= buf.length) {
bufIndex += 1;
bufOffset = 0;
}
baseIndex = bufIndex;
baseOffset = bufOffset;
}
@Override
public int readableBytes() {
if (buffers.size() <= 0 || isEOB())
return 0;
byte[] buf = buffers.get(bufIndex);
int remain = buf.length - bufOffset;
for (int i = bufIndex + 1; i < buffers.size(); i++) {
buf = buffers.get(i);
remain += buf.length;
}
return remain;
}
@Override
public Buffer clear() {
baseIndex = 0;
baseOffset = 0;
bufIndex = 0;
bufOffset = 0;
markIndex = -1;
markOffset = -1;
return this;
}
@Override
public boolean isEOB() {
if (bufIndex >= buffers.size()) {
return true;
}
return false;
}
@Override
public Buffer duplicate() {
// TODO
return null;
}
@Override
public Buffer flip() {
int i = bufIndex + 1;
while(i < buffers.size()) {
buffers.remove(i);
}
if(bufIndex >= buffers.size()) {
bufIndex = baseIndex;
bufOffset = baseOffset;
markIndex = -1;
markOffset = -1;
return this;
}
byte[] b = buffers.get(bufIndex);
byte[] newb = Arrays.copyOf(b, bufOffset);
buffers.remove(bufIndex);
buffers.add(bufIndex, newb);
bufIndex = baseIndex;
bufOffset = baseOffset;
markIndex = -1;
markOffset = -1;
return this;
}
}