blob: 0d43c430153a52c60b83be906934240ee0425ac7 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements. See the NOTICE
* file distributed with this work for additional information regarding copyright ownership. The ASF licenses this file
* to You 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.apache.tuweni.rlp;
import org.apache.tuweni.bytes.Bytes;
import java.util.function.Function;
final class BytesRLPReader implements RLPReader {
private final Bytes content;
private boolean lenient;
private int index = 0;
BytesRLPReader(Bytes content, boolean lenient) {
this.content = content;
this.lenient = lenient;
}
@Override
public boolean isLenient() {
return lenient;
}
@Override
public Bytes readValue(boolean lenient) {
int remaining = content.size() - index;
if (remaining == 0) {
throw new EndOfRLPException();
}
int prefix = (((int) content.get(index)) & 0xFF);
if (prefix <= 0x7f) {
return content.slice(index++, 1);
}
remaining--;
if (prefix <= 0xb7) {
int length = prefix - 0x80;
if (remaining < length) {
throw new InvalidRLPEncodingException(
"Insufficient bytes in RLP encoding: expected " + length + " but have only " + remaining);
}
Bytes bytes = content.slice(index + 1, length);
if (!lenient && length == 1 && (bytes.get(0) & 0xFF) <= 0x7f) {
throw new InvalidRLPEncodingException("Value should have been encoded as a single byte " + bytes.toHexString());
}
index += 1 + length;
return bytes;
}
if (prefix <= 0xbf) {
int lengthOfLength = prefix - 0xb7;
if (remaining < lengthOfLength) {
throw new InvalidRLPEncodingException(
"Insufficient bytes in RLP encoding: expected " + lengthOfLength + " but have only " + remaining);
}
remaining -= lengthOfLength;
int length = getLength(lengthOfLength, lenient, "value");
if (remaining < length) {
throw new InvalidRLPEncodingException(
"Insufficient bytes in RLP encoding: expected " + length + " but have only " + remaining);
}
index += 1 + lengthOfLength;
Bytes bytes = content.slice(index, length);
index += length;
return bytes;
}
throw new InvalidRLPTypeException("Attempted to read a value but next item is a list");
}
@Override
public boolean nextIsList() {
int remaining = content.size() - index;
if (remaining == 0) {
throw new EndOfRLPException();
}
int prefix = (((int) content.get(index)) & 0xFF);
return prefix > 0xbf;
}
@Override
public boolean nextIsEmpty() {
int remaining = content.size() - index;
if (remaining == 0) {
throw new EndOfRLPException();
}
int prefix = (((int) content.get(index)) & 0xFF);
return prefix == 0x80;
}
@Override
public <T> T readList(boolean lenient, Function<RLPReader, T> fn) {
return fn.apply(new BytesRLPReader(readList(lenient), lenient));
}
@Override
public void skipNext(boolean lenient) {
int remaining = content.size() - index;
if (remaining == 0) {
throw new EndOfRLPException();
}
int prefix = (((int) content.get(index)) & 0xFF);
if (prefix <= 0x7f) {
index++;
return;
}
remaining--;
if (prefix <= 0xb7) {
int length = prefix - 0x80;
if (remaining < length) {
throw new InvalidRLPEncodingException(
"Insufficient bytes in RLP encoding: expected " + length + " but have only " + remaining);
}
if (!lenient && length == 1 && (content.get(index + 1) & 0xFF) <= 0x7f) {
throw new InvalidRLPEncodingException(
"Value should have been encoded as a single byte " + content.slice(index + 1, 1).toHexString());
}
index += 1 + length;
return;
}
if (prefix <= 0xbf) {
int lengthOfLength = prefix - 0xb7;
if (remaining < lengthOfLength) {
throw new InvalidRLPEncodingException(
"Insufficient bytes in RLP encoding: expected " + lengthOfLength + " but have only " + remaining);
}
remaining -= lengthOfLength;
int length = getLength(lengthOfLength, lenient, "value");
if (remaining < length) {
throw new InvalidRLPEncodingException(
"Insufficient bytes in RLP encoding: expected " + length + " but have only " + remaining);
}
index += 1 + lengthOfLength + length;
return;
}
if (prefix <= 0xf7) {
int length = prefix - 0xc0;
if (remaining < length) {
throw new InvalidRLPEncodingException(
"Insufficient bytes in RLP encoding: expected " + length + " but have only " + remaining);
}
index += 1 + length;
return;
}
int lengthOfLength = prefix - 0xf7;
if (remaining < lengthOfLength) {
throw new InvalidRLPEncodingException(
"Insufficient bytes in RLP encoding: expected " + lengthOfLength + " but have only " + remaining);
}
remaining -= lengthOfLength;
int length = getLength(lengthOfLength, lenient, "list");
if (remaining < length) {
throw new InvalidRLPEncodingException(
"Insufficient bytes in RLP encoding: expected " + lengthOfLength + " but have only " + remaining);
}
index += 1 + lengthOfLength + length;
}
@Override
public int remaining() {
int oldIndex = index;
try {
int count = 0;
while (!isComplete()) {
count++;
skipNext();
}
return count;
} finally {
index = oldIndex;
}
}
@Override
public boolean isComplete() {
return (content.size() - index) == 0;
}
@Override
public int position() {
return index;
}
private Bytes readList(boolean lenient) {
int remaining = content.size() - index;
if (remaining == 0) {
throw new EndOfRLPException();
}
int prefix = (((int) content.get(index)) & 0xFF);
if (prefix <= 0xbf) {
throw new InvalidRLPTypeException("Attempted to read a list but next item is a value");
}
remaining--;
if (prefix <= 0xf7) {
int length = prefix - 0xc0;
if (remaining < length) {
throw new InvalidRLPEncodingException(
"Insufficient bytes in RLP encoding: expected " + length + " but have only " + remaining);
}
index++;
Bytes bytes = content.slice(index, length);
index += length;
return bytes;
}
int lengthOfLength = prefix - 0xf7;
if (remaining < lengthOfLength) {
throw new InvalidRLPEncodingException(
"Insufficient bytes in RLP encoding: expected " + lengthOfLength + " but have only " + remaining);
}
remaining -= lengthOfLength;
int length = getLength(lengthOfLength, lenient, "list");
if (remaining < length) {
throw new InvalidRLPEncodingException(
"Insufficient bytes in RLP encoding: expected " + length + " but have only " + remaining);
}
index += 1 + lengthOfLength;
Bytes bytes = content.slice(index, length);
index += length;
return bytes;
}
private int getLength(int lengthOfLength, boolean lenient, String type) {
Bytes lengthBytes = content.slice(index + 1, lengthOfLength);
if (!lenient) {
if (lengthBytes.hasLeadingZeroByte()) {
throw new InvalidRLPEncodingException("RLP " + type + " length contains leading zero bytes");
}
} else {
lengthBytes = lengthBytes.trimLeadingZeros();
}
if (lengthBytes.size() == 0) {
throw new InvalidRLPEncodingException("RLP " + type + " length is zero");
}
// Check if the length is greater than a 4 byte integer
if (lengthBytes.size() > 4) {
throw new InvalidRLPEncodingException("RLP " + type + " length is oversized");
}
int length = lengthBytes.toInt();
if (length < 0) {
// Java ints are two's compliment, so this was oversized
throw new InvalidRLPEncodingException("RLP " + type + " length is oversized");
}
assert length > 0;
if (!lenient && length <= 55) {
throw new InvalidRLPEncodingException("RLP " + type + " length of " + length + " was not minimally encoded");
}
return length;
}
}