All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.esaulpaugh.headlong.rlp.RLPDecoder Maven / Gradle / Ivy

There is a newer version: 12.3.3
Show newest version
/*
   Copyright 2019 Evan Saulpaugh

   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 com.esaulpaugh.headlong.rlp;

import com.esaulpaugh.headlong.util.Integers;
import com.esaulpaugh.headlong.util.Strings;

import java.io.IOException;
import java.io.InputStream;
import java.util.Iterator;
import java.util.Spliterator;
import java.util.Spliterators;
import java.util.stream.Stream;
import java.util.stream.StreamSupport;

import static com.esaulpaugh.headlong.rlp.DataType.LIST_SHORT_OFFSET;
import static com.esaulpaugh.headlong.rlp.DataType.MIN_LONG_DATA_LEN;
import static com.esaulpaugh.headlong.rlp.DataType.ORDINAL_LIST_LONG;
import static com.esaulpaugh.headlong.rlp.DataType.ORDINAL_LIST_SHORT;
import static com.esaulpaugh.headlong.rlp.DataType.ORDINAL_SINGLE_BYTE;
import static com.esaulpaugh.headlong.rlp.DataType.ORDINAL_STRING_LONG;
import static com.esaulpaugh.headlong.rlp.DataType.ORDINAL_STRING_SHORT;
import static com.esaulpaugh.headlong.rlp.DataType.STRING_SHORT_OFFSET;

/** Decodes RLP-formatted data. */
public final class RLPDecoder {

    public static final RLPDecoder RLP_STRICT = new RLPDecoder(false);
    public static final RLPDecoder RLP_LENIENT = new RLPDecoder(true);

    public final boolean lenient;

    private RLPDecoder(boolean lenient) {
        this.lenient = lenient;
    }

    public Iterator sequenceIterator(byte[] buffer) {
        return sequenceIterator(buffer, 0);
    }

    /**
     * Returns an iterator over the sequence of RLP items starting at {@code index}.
     *
     * @param buffer the array containing the sequence
     * @param index  the index of the sequence
     * @return an iterator over the items in the sequence
     */
    public Iterator sequenceIterator(byte[] buffer, int index) {
        return new RLPSequenceIterator(RLPDecoder.this, buffer, index);
    }

    /**
     * Returns an iterator over the sequence of RLPItems in the given {@link InputStream}.
     *
     * @param is    the stream of RLP data
     * @return  an iterator over the items in the stream
     */
    public Iterator sequenceIterator(final InputStream is) {
        return new RLPSequenceIterator(RLPDecoder.this, Strings.EMPTY_BYTE_ARRAY, 0) { // make sure index == buffer.length
            @Override
            public boolean hasNext() {
                if (next != null) {
                    return true;
                }
                try {
                    final int available = is.available();
                    if (available > 0) {
                        int keptBytes = buffer.length - index;
                        byte[] newBuffer = new byte[keptBytes + available];
                        System.arraycopy(buffer, index, newBuffer, 0, keptBytes);
                        buffer = newBuffer;
                        index = 0;
                        int read = is.read(buffer, keptBytes, available);
                        if (read != available) {
                            throw new IOException("read failed: " + read + " != " + available);
                        }
                    } else if (index >= buffer.length) {
                        return false;
                    }
                    next = decoder.wrap(buffer, index);
                    return true;
                } catch (ShortInputException e) {
                    return false;
                } catch (IOException io) {
                    throw new RuntimeException(io);
                }
            }
        };
    }

    public static Stream stream(Iterator iter) {
        return StreamSupport.stream(Spliterators.spliteratorUnknownSize(iter, Spliterator.ORDERED), false);
    }

    public Iterator listIterator(byte[] buffer) {
        return listIterator(buffer, 0);
    }

    /**
     * Returns an iterator over the elements in the RLP list item at {@code index}.
     *
     * @param buffer    the array containing the list item
     * @param index the index of the RLP list item
     * @return the iterator over the elements in the list
     * @throws IllegalArgumentException  if the RLP list failed to decode
     */
    public Iterator listIterator(byte[] buffer, int index) {
        return wrapList(buffer, index).iterator(this);
    }

    public  T wrapBits(long bits) {
        return wrap(RLPEncoder.bitsToBytes(bits), 0);
    }

    public RLPString wrapString(byte[] buffer) {
        return wrapString(buffer, 0);
    }

    public RLPList wrapList(byte[] buffer) {
        return wrapList(buffer, 0);
    }

    public RLPItem wrapItem(byte[] buffer) {
        return wrapItem(buffer, 0);
    }

    public RLPString wrapString(byte[] buffer, int index) {
        return wrap(buffer, index);
    }

    public RLPList wrapList(byte[] buffer, int index) {
        return wrap(buffer, index);
    }

    public RLPItem wrapItem(byte[] buffer, int index) {
        return wrap(buffer, index);
    }

    public  T wrap(byte[] buffer) {
        return wrap(buffer, 0);
    }

    public  T wrap(byte[] buffer, int index) {
        return wrap(buffer, index, buffer.length);
    }

    @SuppressWarnings("unchecked")
     T wrap(byte[] buffer, int index, int containerEnd) {
        byte lead = buffer[index];
        DataType type = DataType.type(lead);
        switch (type.ordinal()) {
        case ORDINAL_SINGLE_BYTE: return (T) newSingleByte(buffer, index, containerEnd);
        case ORDINAL_STRING_SHORT: return (T) newStringShort(buffer, index, lead, containerEnd, lenient);
        case ORDINAL_LIST_SHORT: return (T) newListShort(buffer, index, lead, containerEnd);
        case ORDINAL_STRING_LONG:
        case ORDINAL_LIST_LONG: return newLongItem(lead, type.offset, type.isString, buffer, index, containerEnd, lenient);
        default: throw new AssertionError();
        }
    }

    private static RLPString newSingleByte(byte[] buffer, int index, int containerEnd) {
        return new RLPString(buffer, index, index, 1, requireInBounds(index + 1L, containerEnd, buffer, index));
    }

    private static RLPString newStringShort(byte[] buffer, int index, byte lead, int containerEnd, boolean lenient) {
        final int dataIndex = index + 1;
        final int dataLength = lead - STRING_SHORT_OFFSET;
        final int endIndex = requireInBounds((long) dataIndex + dataLength, containerEnd, buffer, index);
        if (!lenient && dataLength == 1 && DataType.isSingleByte(buffer[dataIndex])) {
            throw new IllegalArgumentException("invalid rlp for single byte @ " + index);
        }
        return new RLPString(buffer, index, dataIndex, dataLength, endIndex);
    }

    private static RLPList newListShort(byte[] buffer, int index, byte lead, int containerEnd) {
        final int dataIndex = index + 1;
        final int dataLength = lead - LIST_SHORT_OFFSET;
        return new RLPList(buffer, index, dataIndex, dataLength, requireInBounds((long) dataIndex + dataLength, containerEnd, buffer, index));
    }

    @SuppressWarnings("unchecked")
    private static  T newLongItem(byte lead, byte offset, boolean isString, byte[] buffer, int index, int containerEnd, boolean lenient) {
        final int diff = lead - offset;
        final int lengthIndex = index + 1;
        final int dataIndex = requireInBounds((long) lengthIndex + diff, containerEnd, buffer, index);
        final long dataLength = Integers.getLong(buffer, lengthIndex, diff, lenient);
        if (dataLength < MIN_LONG_DATA_LEN) {
            throw new IllegalArgumentException("long element data length must be " + MIN_LONG_DATA_LEN
                    + " or greater; found: " + dataLength + " for element @ " + index);
        }
        final int dataLen = requireInBounds(dataLength, containerEnd, buffer, index);
        final int endIndex = requireInBounds(dataIndex + dataLength, containerEnd, buffer, index);
        return (T) (isString
                ? new RLPString(buffer, index, dataIndex, dataLen, endIndex)
                : new RLPList(buffer, index, dataIndex, dataLen, endIndex)
        );
    }

    private static int requireInBounds(long val, int containerEnd, byte[] buffer, int index) {
        if (val > containerEnd) {
            String msg = "element @ index " + index + " exceeds its container: " + val + " > " + containerEnd;
            throw buffer.length == containerEnd ? new ShortInputException(msg) : new IllegalArgumentException(msg);
        }
        return (int) val;
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy