diff --git a/ssz/src/main/java/net/consensys/cava/ssz/BytesSSZReaderProxy.java b/ssz/src/main/java/net/consensys/cava/ssz/BytesSSZReaderProxy.java deleted file mode 100644 index e6ac28b4a..000000000 --- a/ssz/src/main/java/net/consensys/cava/ssz/BytesSSZReaderProxy.java +++ /dev/null @@ -1,227 +0,0 @@ -/* - * Copyright 2018 ConsenSys AG. - * - * 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 net.consensys.cava.ssz; - -import net.consensys.cava.bytes.Bytes; -import net.consensys.cava.units.bigints.UInt256; -import java.math.BigInteger; -import java.util.List; - -public class BytesSSZReaderProxy { - private BytesSSZReader reader; - - public BytesSSZReaderProxy(Bytes bytes) { - this(new BytesSSZReader(bytes)); - } - - private BytesSSZReaderProxy(BytesSSZReader reader) { - this.reader = reader; - } - - public Bytes readBytes() { - return reader.readBytes(); - } - - public byte[] readByteArray() { - return reader.readByteArray(); - } - - public byte[] readByteArray(int limit) { - return reader.readByteArray(limit); - } - - public String readString() { - return reader.readString(); - } - - public String readString(int limit) { - return reader.readString(limit); - } - - public int readInt8() { - return reader.readInt8(); - } - - public int readInt16() { - return reader.readInt16(); - } - - public int readInt32() { - return reader.readInt32(); - } - - public long readInt64() { - return reader.readInt64(); - } - - public int readUInt(int bitLength) { - return reader.readUInt(bitLength); - } - - public long readULong(int bitLength) { - return reader.readULong(bitLength); - } - - public int readUInt8() { - return reader.readUInt8(); - } - - public int readUInt16() { - return reader.readUInt16(); - } - - public long readUInt32() { - return reader.readUInt32(); - } - - public long readUInt64() { - return reader.readUInt64(); - } - - public boolean readBoolean() { - return reader.readBoolean(); - } - - public List readBytesList() { - return reader.readBytesList(); - } - - public List readByteArrayList() { - return reader.readByteArrayList(); - } - - public List readByteArrayList(int limit) { - return reader.readByteArrayList(limit); - } - - public List readStringList() { - return reader.readStringList(); - } - - public List readInt8List() { - return reader.readInt8List(); - } - - public List readInt16List() { - return reader.readInt16List(); - } - - public List readInt32List() { - return reader.readInt32List(); - } - - public List readInt64List() { - return reader.readInt64List(); - } - - public List readUIntList(int bitLength) { - return reader.readUIntList(bitLength); - } - - public List readULongIntList(int bitLength) { - return reader.readULongIntList(bitLength); - } - - public List readUInt8List() { - return reader.readUInt8List(); - } - - public List readUInt16List() { - return reader.readUInt16List(); - } - - public List readUInt32List() { - return reader.readUInt32List(); - } - - public List readUInt64List() { - return reader.readUInt64List(); - } - - public Bytes readBytes(int limit) { - return reader.readBytes(limit); - } - - public int readInt(int bitLength) { - return reader.readInt(bitLength); - } - - public long readLong(int bitLength) { - return reader.readLong(bitLength); - } - - public BigInteger readBigInteger(int bitLength) { - return reader.readBigInteger(bitLength); - } - - public BigInteger readUnsignedBigInteger(int bitLength) { - return reader.readUnsignedBigInteger(bitLength); - } - - public UInt256 readUInt256() { - return reader.readUInt256(); - } - - public Bytes readAddress() { - return reader.readAddress(); - } - - public Bytes readHash(int hashLength) { - return reader.readHash(hashLength); - } - - public List readBytesList(int limit) { - return reader.readBytesList(limit); - } - - public List readStringList(int limit) { - return reader.readStringList(limit); - } - - public List readIntList(int bitLength) { - return reader.readIntList(bitLength); - } - - public List readLongIntList(int bitLength) { - return reader.readLongIntList(bitLength); - } - - public List readBigIntegerList(int bitLength) { - return reader.readBigIntegerList(bitLength); - } - - public List readUnsignedBigIntegerList(int bitLength) { - return reader.readUnsignedBigIntegerList(bitLength); - } - - public List readUInt256List() { - return reader.readUInt256List(); - } - - public List readAddressList() { - return reader.readAddressList(); - } - - public List readHashList(int hashLength) { - return reader.readHashList(hashLength); - } - - public List readBooleanList() { - return reader.readBooleanList(); - } - - public boolean isComplete() { - return reader.isComplete(); - } -} diff --git a/ssz/src/main/java/org/ethereum/beacon/ssz/SSZSerializer.java b/ssz/src/main/java/org/ethereum/beacon/ssz/SSZSerializer.java index f612c12d1..3549ec8e5 100644 --- a/ssz/src/main/java/org/ethereum/beacon/ssz/SSZSerializer.java +++ b/ssz/src/main/java/org/ethereum/beacon/ssz/SSZSerializer.java @@ -7,15 +7,16 @@ import org.ethereum.beacon.ssz.type.SSZListType; import org.ethereum.beacon.ssz.type.SSZType; import org.ethereum.beacon.ssz.type.TypeResolver; -import org.ethereum.beacon.ssz.visitor.SSZSimpleDeserializer; -import org.ethereum.beacon.ssz.visitor.SSZSimpleDeserializer.DecodeResult; -import org.ethereum.beacon.ssz.visitor.SSZSimpleSerializer; -import org.ethereum.beacon.ssz.visitor.SSZSimpleSerializer.SSZSerializerResult; +import org.ethereum.beacon.ssz.visitor.SosDeserializer; +import org.ethereum.beacon.ssz.visitor.SosDeserializer.DecodeResult; +import org.ethereum.beacon.ssz.visitor.SosSerializer; +import org.ethereum.beacon.ssz.visitor.SosSerializer.SerializerResult; import org.ethereum.beacon.ssz.visitor.SSZVisitorHandler; import org.ethereum.beacon.ssz.visitor.SSZVisitorHost; +import org.javatuples.Pair; /** SSZ serializer/deserializer */ -public class SSZSerializer implements BytesSerializer, SSZVisitorHandler { +public class SSZSerializer implements BytesSerializer, SSZVisitorHandler { private final SSZVisitorHost sszVisitorHost; private final TypeResolver typeResolver; @@ -35,21 +36,21 @@ public SSZSerializer(SSZVisitorHost sszVisitorHost, */ @Override public byte[] encode(@Nullable C inputObject, Class inputClazz) { - return visit(inputObject, inputClazz).getSerialized().getArrayUnsafe(); + return visit(inputObject, inputClazz).getSerializedBody().getArrayUnsafe(); } - private SSZSerializerResult visit(C input, Class clazz) { + private SerializerResult visit(C input, Class clazz) { return visitAny(typeResolver.resolveSSZType(new SSZField(clazz)), input); } @Override - public SSZSerializerResult visitAny(SSZType sszType, Object value) { - return sszVisitorHost.handleAny(sszType, value, new SSZSimpleSerializer()); + public SerializerResult visitAny(SSZType sszType, Object value) { + return sszVisitorHost.handleAny(sszType, value, new SosSerializer()); } @Override - public SSZSerializerResult visitList(SSZListType descriptor, Object listValue, int startIdx, int len) { - return sszVisitorHost.handleSubList(descriptor, listValue, startIdx, len, new SSZSimpleSerializer()); + public SerializerResult visitList(SSZListType descriptor, Object listValue, int startIdx, int len) { + return sszVisitorHost.handleSubList(descriptor, listValue, startIdx, len, new SosSerializer()); } /** @@ -62,11 +63,10 @@ public SSZSerializerResult visitList(SSZListType descriptor, Object listValue, i public C decode(byte[] data, Class clazz) { DecodeResult decodeResult = sszVisitorHost.handleAny( typeResolver.resolveSSZType(new SSZField(clazz)), - Bytes.wrap(data), - new SSZSimpleDeserializer()); - if (data.length > decodeResult.readBytes) { - throw new SSZSerializeException("Invalid SSZ data length (data is bigger than required): " - + data.length + " > " + decodeResult.readBytes); + Pair.with(Bytes.wrap(data), null), + new SosDeserializer()); + if (data.length != decodeResult.readBytes) { + throw new SSZSerializeException(String.format("Invalid SSZ encoding, calculated data size %s bytes, while provided %s bytes", decodeResult.readBytes, data.length)); } return (C) decodeResult.decodedInstance; } diff --git a/ssz/src/main/java/org/ethereum/beacon/ssz/access/SSZBasicAccessor.java b/ssz/src/main/java/org/ethereum/beacon/ssz/access/SSZBasicAccessor.java index 6d54423da..57b9ce253 100644 --- a/ssz/src/main/java/org/ethereum/beacon/ssz/access/SSZBasicAccessor.java +++ b/ssz/src/main/java/org/ethereum/beacon/ssz/access/SSZBasicAccessor.java @@ -1,10 +1,9 @@ package org.ethereum.beacon.ssz.access; -import net.consensys.cava.ssz.BytesSSZReaderProxy; +import org.ethereum.beacon.ssz.visitor.SSZReader; import org.ethereum.beacon.ssz.SSZSchemeException; import org.ethereum.beacon.ssz.SSZSerializer; import java.io.OutputStream; -import java.util.Arrays; import java.util.List; import java.util.Set; @@ -44,16 +43,6 @@ public interface SSZBasicAccessor { */ void encode(Object value, SSZField field, OutputStream result); - /** - * Encodes list field as SSZ type and writes it to output stream - * - * @param value Field value - * @param field Field type - * @param result Output stream - */ - void encodeList( - List value, SSZField field, OutputStream result); - /** * Decodes SSZ encoded data and returns result * @@ -62,17 +51,7 @@ void encodeList( * moved to the end of this field/beginning of next one after reading is performed. * @return field value */ - Object decode(SSZField field, BytesSSZReaderProxy reader); - - /** - * Decodes SSZ encoded data and returns result - * - * @param field Type of field to read at this point, list - * @param reader Reader which holds SSZ encoded data at the appropriate point. Pointer will be - * moved to the end of this field/beginning of next one after reading is performed. - * @return field list value - */ - List decodeList(SSZField field, BytesSSZReaderProxy reader); + Object decode(SSZField field, SSZReader reader); /** * Helper designed to throw usual error diff --git a/ssz/src/main/java/org/ethereum/beacon/ssz/access/basic/BooleanPrimitive.java b/ssz/src/main/java/org/ethereum/beacon/ssz/access/basic/BooleanPrimitive.java index a4970f498..b0955ad3d 100644 --- a/ssz/src/main/java/org/ethereum/beacon/ssz/access/basic/BooleanPrimitive.java +++ b/ssz/src/main/java/org/ethereum/beacon/ssz/access/basic/BooleanPrimitive.java @@ -1,13 +1,12 @@ package org.ethereum.beacon.ssz.access.basic; import net.consensys.cava.bytes.Bytes; -import net.consensys.cava.ssz.BytesSSZReaderProxy; -import net.consensys.cava.ssz.SSZ; +import org.ethereum.beacon.ssz.visitor.SSZReader; +import org.ethereum.beacon.ssz.visitor.SSZWriter; import net.consensys.cava.ssz.SSZException; import java.io.IOException; import java.io.OutputStream; import java.util.HashSet; -import java.util.List; import java.util.Set; import org.ethereum.beacon.ssz.access.SSZField; import org.ethereum.beacon.ssz.access.SSZBasicAccessor; @@ -47,7 +46,7 @@ public int getSize(SSZField field) { @Override public void encode(Object value, SSZField field, OutputStream result) { boolean boolValue = (boolean) value; - Bytes res = SSZ.encodeBoolean(boolValue); + Bytes res = SSZWriter.encodeBoolean(boolValue); try { result.write(res.toArrayUnsafe()); } catch (IOException e) { @@ -57,29 +56,7 @@ public void encode(Object value, SSZField field, OutputStream result) { } @Override - public void encodeList( - List value, SSZField field, OutputStream result) { - try { - boolean[] data = new boolean[value.size()]; - for (int i = 0; i < value.size(); ++i) { - data[i] = (boolean) value.get(i); - } - result.write(SSZ.encodeBooleanList(data).toArrayUnsafe()); - } catch (IOException ex) { - String error = String.format("Failed to write data from field \"%s\" to stream", - field.getName()); - throw new SSZException(error, ex); - } - } - - @Override - public Object decode(SSZField field, BytesSSZReaderProxy reader) { + public Object decode(SSZField field, SSZReader reader) { return reader.readBoolean(); } - - @Override - public List decodeList( - SSZField field, BytesSSZReaderProxy reader) { - return (List) (List) reader.readBooleanList(); - } } diff --git a/ssz/src/main/java/org/ethereum/beacon/ssz/access/basic/BytesCodec.java b/ssz/src/main/java/org/ethereum/beacon/ssz/access/basic/BytesCodec.java index 4c4115efb..9ad889688 100644 --- a/ssz/src/main/java/org/ethereum/beacon/ssz/access/basic/BytesCodec.java +++ b/ssz/src/main/java/org/ethereum/beacon/ssz/access/basic/BytesCodec.java @@ -1,8 +1,8 @@ package org.ethereum.beacon.ssz.access.basic; import net.consensys.cava.bytes.Bytes; -import net.consensys.cava.ssz.BytesSSZReaderProxy; -import net.consensys.cava.ssz.SSZ; +import org.ethereum.beacon.ssz.visitor.SSZReader; +import org.ethereum.beacon.ssz.visitor.SSZWriter; import net.consensys.cava.ssz.SSZException; import org.ethereum.beacon.ssz.access.SSZField; import org.ethereum.beacon.ssz.SSZSchemeException; @@ -21,7 +21,6 @@ import java.util.List; import java.util.Map; import java.util.Set; -import java.util.stream.Collectors; /** * SSZ Codec designed to work with fixed size bytes data classes, check list in {@link @@ -84,9 +83,9 @@ public void encode(Object value, SSZField field, OutputStream result) { BytesValue data = (BytesValue) value; BytesType bytesType = parseFieldType(field); if (bytesType.size == null) { - res = SSZ.encodeBytes(Bytes.of(data.getArrayUnsafe())); + res = SSZWriter.encodeBytes(Bytes.of(data.getArrayUnsafe())); } else { - res = SSZ.encodeHash(Bytes.of(data.getArrayUnsafe())); + res = SSZWriter.encodeBytes(Bytes.of(data.getArrayUnsafe()), bytesType.size); } try { @@ -99,28 +98,7 @@ public void encode(Object value, SSZField field, OutputStream result) { } @Override - public void encodeList( - List value, SSZField field, OutputStream result) { - Bytes[] data = repackBytesList((List) (List) value); - - try { - Bytes res; - BytesType bytesType = parseFieldType(field); - if (bytesType.size == null) { - res = SSZ.encodeBytesList(data); - } else { - res = SSZ.encodeHashList(data); - } - result.write(res.toArrayUnsafe()); - } catch (IOException ex) { - String error = String.format("Failed to write data from field \"%s\" to stream", - field.getName()); - throw new SSZException(error, ex); - } - } - - @Override - public Object decode(SSZField field, BytesSSZReaderProxy reader) { + public Object decode(SSZField field, SSZReader reader) { BytesType bytesType = parseFieldType(field); if (bytesType.size == null) { @@ -162,86 +140,6 @@ public Object decode(SSZField field, BytesSSZReaderProxy reader) { return throwUnsupportedType(field); } - @Override - public List decodeList( - SSZField field, BytesSSZReaderProxy reader) { - BytesType bytesType = parseFieldType(field); - - if (bytesType.size == null) { - return reader.readBytesList().stream() - .map(Bytes::toArrayUnsafe) - .map(BytesValue::wrap) - .collect(Collectors.toList()); - } - List res = null; - try { - List bytesList = reader.readHashList(bytesType.size); - switch (bytesType.size) { - case 1: - { - res = - bytesList.stream() - .map(Bytes::toArrayUnsafe) - .map(Bytes1::wrap) - .collect(Collectors.toList()); - break; - } - case 4: - { - res = - bytesList.stream() - .map(Bytes::toArrayUnsafe) - .map(Bytes4::wrap) - .collect(Collectors.toList()); - break; - } - case 20: - { - res = - bytesList.stream() - .map(Bytes::toArrayUnsafe) - .map(BytesValue::wrap) - .map(Address::wrap) - .collect(Collectors.toList()); - break; - } - case 32: - { - res = - bytesList.stream() - .map(Bytes::toArrayUnsafe) - .map(Bytes32::wrap) - .collect(Collectors.toList()); - break; - } - case 48: - { - res = - bytesList.stream() - .map(Bytes::toArrayUnsafe) - .map(Bytes48::wrap) - .collect(Collectors.toList()); - break; - } - case 96: - { - res = - bytesList.stream() - .map(Bytes::toArrayUnsafe) - .map(Bytes96::wrap) - .collect(Collectors.toList()); - break; - } - } - } catch (Exception ex) { - String error = - String.format("Failed to read list data from stream to field \"%s\"", field.getName()); - throw new SSZException(error, ex); - } - - return (List) (List) res; - } - private BytesType parseFieldType(SSZField field) { if (classToByteType.containsKey(field.getRawClass())) { return classToByteType.get(field.getRawClass()); diff --git a/ssz/src/main/java/org/ethereum/beacon/ssz/access/basic/BytesPrimitive.java b/ssz/src/main/java/org/ethereum/beacon/ssz/access/basic/BytesPrimitive.java index 3376bc45d..f002236a4 100644 --- a/ssz/src/main/java/org/ethereum/beacon/ssz/access/basic/BytesPrimitive.java +++ b/ssz/src/main/java/org/ethereum/beacon/ssz/access/basic/BytesPrimitive.java @@ -1,17 +1,15 @@ package org.ethereum.beacon.ssz.access.basic; import net.consensys.cava.bytes.Bytes; -import net.consensys.cava.ssz.BytesSSZReaderProxy; -import net.consensys.cava.ssz.SSZ; +import org.ethereum.beacon.ssz.visitor.SSZReader; +import org.ethereum.beacon.ssz.visitor.SSZWriter; import net.consensys.cava.ssz.SSZException; import org.ethereum.beacon.ssz.access.SSZBasicAccessor; import org.ethereum.beacon.ssz.access.SSZField; -import org.ethereum.beacon.ssz.SSZSchemeException; import java.io.IOException; import java.io.OutputStream; import java.util.HashSet; -import java.util.List; import java.util.Map; import java.util.Set; import java.util.stream.Collectors; @@ -31,7 +29,7 @@ * * * Type could be clarified by {@link - * SSZField#extraType} + * SSZField#getExtraType()} */ public class BytesPrimitive implements SSZBasicAccessor { @@ -40,24 +38,12 @@ public class BytesPrimitive implements SSZBasicAccessor { static { supportedTypes.add("bytes"); - supportedTypes.add("hash"); - supportedTypes.add("address"); } static { supportedClassTypes.add(byte[].class); } - private static Bytes[] repackBytesList(List list) { - Bytes[] data = new Bytes[list.size()]; - for (int i = 0; i < list.size(); i++) { - byte[] el = list.get(i); - data[i] = Bytes.of(el); - } - - return data; - } - @Override public Set getSupportedSSZTypes() { return supportedTypes; @@ -79,31 +65,10 @@ public void encode(Object value, SSZField field, OutputStream result) { BytesType byteType = parseFieldType(field); Bytes res = null; byte[] data = (byte[]) value; - - switch (byteType.type) { - case HASH: - { - res = SSZ.encodeHash(Bytes.of(data)); - break; - } - case ADDRESS: - { - res = SSZ.encodeAddress(Bytes.of(data)); - break; - } - case BYTES: - { - if (byteType.size == null) { - res = SSZ.encodeByteArray(data); - } else { - res = SSZ.encodeHash(Bytes.wrap(data)); // w/o length prefix - } - break; - } - default: - { - throwUnsupportedType(field); - } + if (byteType.size == null) { + res = SSZWriter.encodeByteArray(data); + } else { + res = SSZWriter.encodeBytes(Bytes.wrap(data), byteType.size); } try { @@ -116,123 +81,19 @@ public void encode(Object value, SSZField field, OutputStream result) { } @Override - public void encodeList( - List value, SSZField field, OutputStream result) { + public Object decode(SSZField field, SSZReader reader) { BytesType bytesType = parseFieldType(field); - Bytes[] data = repackBytesList((List) (List) value); - - try { - switch (bytesType.type) { - case HASH: - { - result.write(SSZ.encodeHashList(data).toArrayUnsafe()); - break; - } - case BYTES: - { - result.write(SSZ.encodeBytesList(data).toArrayUnsafe()); - break; - } - case ADDRESS: - { - if (bytesType.size == null) { - result.write(SSZ.encodeAddressList(data).toArrayUnsafe()); - } else { - result.write(SSZ.encodeHashList(data).toArrayUnsafe()); - } - break; - } - default: - { - throwUnsupportedType(field); - } - } - } catch (IOException ex) { - String error = String.format("Failed to write data from field \"%s\" to stream", - field.getName()); - throw new SSZException(error, ex); - } - } - - @Override - public Object decode(SSZField field, BytesSSZReaderProxy reader) { - BytesType bytesType = parseFieldType(field); - switch (bytesType.type) { - case BYTES: - { - return (bytesType.size == null) - ? reader.readBytes().toArrayUnsafe() - : reader.readHash(bytesType.size).toArrayUnsafe(); - } - case HASH: - { - return reader.readHash(bytesType.size).toArrayUnsafe(); - } - case ADDRESS: - { - return reader.readAddress().toArrayUnsafe(); - } - } - - return throwUnsupportedType(field); - } - - @Override - public List decodeList( - SSZField field, BytesSSZReaderProxy reader) { - BytesType bytesType = parseFieldType(field); - - switch (bytesType.type) { - case BYTES: - { - if (bytesType.size == null) { - return (List) (List) reader.readByteArrayList(); - } else { - return (List) (List) reader.readHashList(bytesType.size); - } - } - case HASH: - { - return (List) (List) reader.readHashList(bytesType.size); - } - case ADDRESS: - { - return (List) (List) reader.readAddressList(); - } - - default: - { - return throwUnsupportedListType(field); - } - } + return (bytesType.size == null) + ? reader.readBytes().toArrayUnsafe() + : reader.readHash(bytesType.size).toArrayUnsafe(); } private BytesType parseFieldType(SSZField field) { - Type type = Type.fromValue(field.getExtraType()); - - if (type == null || type.equals(Type.BYTES)) { - return BytesType.of(Type.BYTES, field.getExtraSize()); - } - - if (type.equals(Type.ADDRESS)) { - if (field.getExtraSize() != null) { - throw new SSZSchemeException("Address is fixed 20 bytes type"); - } else { - return BytesType.of(Type.ADDRESS, 20); - } - } else { - if (field.getExtraSize() == null) { - throw new SSZSchemeException("Hash size is required!"); - } else { - return BytesType.of(Type.HASH, field.getExtraSize()); - } - } + return BytesType.of(Type.BYTES, field.getExtraSize()); } enum Type { - BYTES("bytes"), - HASH("hash"), - ADDRESS("address"); + BYTES("bytes"); private static final Map ENUM_MAP; diff --git a/ssz/src/main/java/org/ethereum/beacon/ssz/access/basic/HashCodec.java b/ssz/src/main/java/org/ethereum/beacon/ssz/access/basic/HashCodec.java index 9a84abe57..bba626af9 100644 --- a/ssz/src/main/java/org/ethereum/beacon/ssz/access/basic/HashCodec.java +++ b/ssz/src/main/java/org/ethereum/beacon/ssz/access/basic/HashCodec.java @@ -5,17 +5,15 @@ import java.util.HashSet; import java.util.List; import java.util.Set; -import java.util.stream.Collectors; import net.consensys.cava.bytes.Bytes; -import net.consensys.cava.ssz.BytesSSZReaderProxy; -import net.consensys.cava.ssz.SSZ; +import org.ethereum.beacon.ssz.visitor.SSZReader; +import org.ethereum.beacon.ssz.visitor.SSZWriter; import net.consensys.cava.ssz.SSZException; import org.ethereum.beacon.ssz.access.SSZField; import org.ethereum.beacon.ssz.SSZSchemeException; import org.ethereum.beacon.ssz.access.SSZBasicAccessor; import tech.pegasys.artemis.ethereum.core.Hash32; import tech.pegasys.artemis.util.bytes.Bytes32; -import tech.pegasys.artemis.util.bytes.Bytes48; import tech.pegasys.artemis.util.bytes.BytesValue; /** @@ -59,10 +57,9 @@ public int getSize(SSZField field) { @Override public void encode(Object value, SSZField field, OutputStream result) { - HashType hashType = parseFieldType(field); Bytes res = null; BytesValue data = (BytesValue) value; - res = SSZ.encodeHash(Bytes.of(data.getArrayUnsafe())); + res = SSZWriter.encodeBytes(Bytes.of(data.getArrayUnsafe()), getSize(field)); try { result.write(res.toArrayUnsafe()); @@ -74,76 +71,16 @@ public void encode(Object value, SSZField field, OutputStream result) { } @Override - public void encodeList( - List value, SSZField field, OutputStream result) { + public Object decode(SSZField field, SSZReader reader) { HashType hashType = parseFieldType(field); - Bytes[] data = repackBytesList((List) (List) value); try { - result.write(SSZ.encodeHashList(data).toArrayUnsafe()); - } catch (IOException ex) { - String error = String.format("Failed to write data from field \"%s\" to stream", - field.getName()); - throw new SSZException(error, ex); - } - } - - @Override - public Object decode(SSZField field, BytesSSZReaderProxy reader) { - HashType hashType = parseFieldType(field); - - try { - switch (hashType.size) { - case 32: - { - return Hash32.wrap(Bytes32.wrap(reader.readHash(hashType.size).toArrayUnsafe())); - } - } + return Hash32.wrap(Bytes32.wrap(reader.readHash(hashType.size).toArrayUnsafe())); } catch (Exception ex) { String error = String.format("Failed to read data from stream to field \"%s\"", field.getName()); throw new SSZException(error, ex); } - - return throwUnsupportedType(field); - } - - @Override - public List decodeList( - SSZField field, BytesSSZReaderProxy reader) { - HashType hashType = parseFieldType(field); - - List bytesList = reader.readHashList(hashType.size); - List res = null; - try { - switch (hashType.size) { - case 32: - { - res = - bytesList.stream() - .map(Bytes::toArrayUnsafe) - .map(Bytes32::wrap) - .map(Hash32::wrap) - .collect(Collectors.toList()); - break; - } - case 48: - { - res = - bytesList.stream() - .map(Bytes::toArrayUnsafe) - .map(Bytes48::wrap) - .collect(Collectors.toList()); - break; - } - } - } catch (Exception ex) { - String error = - String.format("Failed to read list data from stream to field \"%s\"", field.getName()); - throw new SSZException(error, ex); - } - - return (List) (List) res; } private HashType parseFieldType(SSZField field) { diff --git a/ssz/src/main/java/org/ethereum/beacon/ssz/access/basic/StringPrimitive.java b/ssz/src/main/java/org/ethereum/beacon/ssz/access/basic/StringPrimitive.java index 4ddf1ef2a..4d92dbc1b 100644 --- a/ssz/src/main/java/org/ethereum/beacon/ssz/access/basic/StringPrimitive.java +++ b/ssz/src/main/java/org/ethereum/beacon/ssz/access/basic/StringPrimitive.java @@ -1,13 +1,12 @@ package org.ethereum.beacon.ssz.access.basic; import net.consensys.cava.bytes.Bytes; -import net.consensys.cava.ssz.BytesSSZReaderProxy; -import net.consensys.cava.ssz.SSZ; +import org.ethereum.beacon.ssz.visitor.SSZReader; +import org.ethereum.beacon.ssz.visitor.SSZWriter; import net.consensys.cava.ssz.SSZException; import java.io.IOException; import java.io.OutputStream; import java.util.HashSet; -import java.util.List; import java.util.Set; import org.ethereum.beacon.ssz.access.SSZBasicAccessor; @@ -45,7 +44,7 @@ public Set getSupportedClasses() { @Override public void encode(Object value, SSZField field, OutputStream result) { String sValue = (String) value; - Bytes res = SSZ.encodeString(sValue); + Bytes res = SSZWriter.encodeString(sValue); try { result.write(res.toArrayUnsafe()); } catch (IOException e) { @@ -55,26 +54,7 @@ public void encode(Object value, SSZField field, OutputStream result) { } @Override - public void encodeList( - List value, SSZField field, OutputStream result) { - try { - String[] data = value.toArray(new String[0]); - result.write(SSZ.encodeStringList(data).toArrayUnsafe()); - } catch (IOException ex) { - String error = String.format("Failed to write data from field \"%s\" to stream", - field.getName()); - throw new SSZException(error, ex); - } - } - - @Override - public Object decode(SSZField field, BytesSSZReaderProxy reader) { + public Object decode(SSZField field, SSZReader reader) { return reader.readString(); } - - @Override - public List decodeList( - SSZField field, BytesSSZReaderProxy reader) { - return (List) (List) reader.readStringList(); - } } diff --git a/ssz/src/main/java/org/ethereum/beacon/ssz/access/basic/SubclassCodec.java b/ssz/src/main/java/org/ethereum/beacon/ssz/access/basic/SubclassCodec.java index 2ed3296e6..bfb425d17 100644 --- a/ssz/src/main/java/org/ethereum/beacon/ssz/access/basic/SubclassCodec.java +++ b/ssz/src/main/java/org/ethereum/beacon/ssz/access/basic/SubclassCodec.java @@ -1,10 +1,9 @@ package org.ethereum.beacon.ssz.access.basic; import java.io.OutputStream; -import java.util.List; import java.util.Set; -import java.util.stream.Collectors; -import net.consensys.cava.ssz.BytesSSZReaderProxy; + +import org.ethereum.beacon.ssz.visitor.SSZReader; import org.ethereum.beacon.ssz.access.SSZBasicAccessor; import org.ethereum.beacon.ssz.creator.ConstructorObjCreator; import org.ethereum.beacon.ssz.access.SSZField; @@ -46,35 +45,13 @@ public void encode(Object value, SSZField field, } @Override - public void encodeList(List value, - SSZField field, OutputStream result) { - superclassCodec.encodeList(value, getSerializableField(field), result); - } - - @Override - public Object decode(SSZField field, - BytesSSZReaderProxy reader) { + public Object decode(SSZField field, SSZReader reader) { SSZField serializableField = getSerializableField(field); Object serializableTypeObject = superclassCodec.decode(serializableField, reader); return ConstructorObjCreator.createInstanceWithConstructor( field.getRawClass(), new Class[] {serializableField.getRawClass()}, new Object[] {serializableTypeObject}); } - @Override - public List decodeList(SSZField field, - BytesSSZReaderProxy reader) { - SSZField serializableField = getSerializableField(field); - List serializableTypeList = superclassCodec.decodeList(serializableField, reader); - return serializableTypeList.stream() - .map( - serializableTypeObject -> - ConstructorObjCreator.createInstanceWithConstructor( - field.getRawClass(), - new Class[] {serializableField.getRawClass()}, - new Object[] {serializableTypeObject})) - .collect(Collectors.toList()); - } - private static SSZField getSerializableField(SSZField field) { return new SSZField(getSerializableClass(field.getRawClass()), field.getFieldAnnotation(), diff --git a/ssz/src/main/java/org/ethereum/beacon/ssz/access/basic/UIntCodec.java b/ssz/src/main/java/org/ethereum/beacon/ssz/access/basic/UIntCodec.java index 983073350..8f600e062 100644 --- a/ssz/src/main/java/org/ethereum/beacon/ssz/access/basic/UIntCodec.java +++ b/ssz/src/main/java/org/ethereum/beacon/ssz/access/basic/UIntCodec.java @@ -1,8 +1,8 @@ package org.ethereum.beacon.ssz.access.basic; import net.consensys.cava.bytes.Bytes; -import net.consensys.cava.ssz.BytesSSZReaderProxy; -import net.consensys.cava.ssz.SSZ; +import org.ethereum.beacon.ssz.visitor.SSZReader; +import org.ethereum.beacon.ssz.visitor.SSZWriter; import net.consensys.cava.ssz.SSZException; import org.ethereum.beacon.ssz.access.SSZBasicAccessor; import org.ethereum.beacon.ssz.access.SSZField; @@ -13,10 +13,8 @@ import tech.pegasys.artemis.util.uint.UInt64; import java.io.IOException; import java.io.OutputStream; -import java.math.BigInteger; import java.util.HashMap; import java.util.HashSet; -import java.util.List; import java.util.Map; import java.util.Set; import java.util.stream.Collectors; @@ -87,14 +85,14 @@ public void encode(Object value, SSZField field, OutputStream result) { case 24: { UInt24 uValue = (UInt24) value; - Bytes bytes = SSZ.encodeULong(uValue.getValue(), numericType.size); + Bytes bytes = SSZWriter.encodeULong(uValue.getValue(), numericType.size); writeBytes(bytes, result); break; } case 64: { UInt64 uValue = (UInt64) value; - Bytes bytes = SSZ.encodeULong(uValue.getValue(), numericType.size); + Bytes bytes = SSZWriter.encodeULong(uValue.getValue(), numericType.size); writeBytes(bytes, result); break; } @@ -112,76 +110,7 @@ public void encode(Object value, SSZField field, OutputStream result) { } @Override - public void encodeList( - List value, SSZField field, OutputStream result) { - NumericType numericType = parseFieldType(field); - - try { - switch (numericType.type) { - case BIGINT: - { - encodeBigIntList(value, numericType, result); - break; - } - case LONG: - { - encodeLongList(value, numericType, result); - break; - } - default: - { - throwUnsupportedType(field); - } - } - } catch (IOException ex) { - String error = String.format("Failed to write data from field \"%s\" to stream", - field.getName()); - throw new SSZException(error, ex); - } - } - - private void encodeLongList(List value, NumericType type, OutputStream result) - throws IOException { - long[] data = new long[value.size()]; - for (int i = 0; i < value.size(); ++i) { - switch (type.size) { - case 24: - { - data[i] = ((UInt24) value.get(i)).getValue(); - break; - } - case 64: - { - data[i] = ((UInt64) value.get(i)).getValue(); - break; - } - } - } - result.write(SSZ.encodeULongIntList(type.size, data).toArrayUnsafe()); - } - - private void encodeBigIntList(List value, NumericType type, OutputStream result) - throws IOException { - Bytes[] data = new Bytes[value.size()]; - for (int i = 0; i < value.size(); ++i) { - switch (type.size) { - case 256: - { - data[i] = Bytes.of(((UInt256) value.get(i)).bytes().getArrayUnsafe()); - break; - } - default: - { - String error = String.format("Unsupported type \"%s\"", type); - throw new SSZSchemeException(error); - } - } - } - result.write(SSZ.encodeBytesList(data).toArrayUnsafe()); - } - - @Override - public Object decode(SSZField field, BytesSSZReaderProxy reader) { + public Object decode(SSZField field, SSZReader reader) { NumericType numericType = parseFieldType(field); switch (numericType.type) { case LONG: @@ -197,7 +126,7 @@ public Object decode(SSZField field, BytesSSZReaderProxy reader) { return throwUnsupportedType(field); } - private Object decodeLong(NumericType type, BytesSSZReaderProxy reader) { + private Object decodeLong(NumericType type, SSZReader reader) { // XXX: reader.readULong is buggy switch (type.size) { case 24: @@ -213,7 +142,7 @@ private Object decodeLong(NumericType type, BytesSSZReaderProxy reader) { throw new SSZSchemeException(error); } - private Object decodeBigInt(NumericType type, BytesSSZReaderProxy reader) { + private Object decodeBigInt(NumericType type, SSZReader reader) { switch (type.size) { case 256: { @@ -228,67 +157,6 @@ private Object decodeBigInt(NumericType type, BytesSSZReaderProxy reader) { } } - @Override - public List decodeList( - SSZField field, BytesSSZReaderProxy reader) { - NumericType numericType = parseFieldType(field); - - switch (numericType.type) { - case LONG: - { - switch (numericType.size) { - case 24: - { - return readUInt24List(numericType, reader); - } - case 64: - { - return readUInt64List(numericType, reader); - } - } - } - case BIGINT: - { - if (numericType.size == 256) { - return readUInt256List(numericType, reader); - } - } - default: - { - return throwUnsupportedListType(field); - } - } - } - - private List readUInt24List(NumericType numericType, BytesSSZReaderProxy reader) { - List longList = reader.readUnsignedBigIntegerList(numericType.size); - List res = - longList.stream() - .map(BigInteger::intValue) - .map(UInt24::valueOf) - .collect(Collectors.toList()); - - return res; - } - - private List readUInt64List(NumericType numericType, BytesSSZReaderProxy reader) { - List longList = reader.readUnsignedBigIntegerList(numericType.size); - List res = - longList.stream() - .map(BigInteger::longValue) - .map(UInt64::valueOf) - .collect(Collectors.toList()); - - return res; - } - - private List readUInt256List(NumericType numericType, BytesSSZReaderProxy reader) { - List bigIntList = reader.readBigIntegerList(256); - List res = bigIntList.stream().map(UInt256::of).collect(Collectors.toList()); - - return res; - } - private NumericType parseFieldType(SSZField field) { return classToNumericType.get(field.getRawClass()); } diff --git a/ssz/src/main/java/org/ethereum/beacon/ssz/access/basic/UIntPrimitive.java b/ssz/src/main/java/org/ethereum/beacon/ssz/access/basic/UIntPrimitive.java index b1ce44e9e..117fb5e24 100644 --- a/ssz/src/main/java/org/ethereum/beacon/ssz/access/basic/UIntPrimitive.java +++ b/ssz/src/main/java/org/ethereum/beacon/ssz/access/basic/UIntPrimitive.java @@ -1,8 +1,8 @@ package org.ethereum.beacon.ssz.access.basic; import net.consensys.cava.bytes.Bytes; -import net.consensys.cava.ssz.BytesSSZReaderProxy; -import net.consensys.cava.ssz.SSZ; +import org.ethereum.beacon.ssz.visitor.SSZReader; +import org.ethereum.beacon.ssz.visitor.SSZWriter; import net.consensys.cava.ssz.SSZException; import org.ethereum.beacon.ssz.access.SSZBasicAccessor; import org.ethereum.beacon.ssz.access.SSZField; @@ -12,7 +12,6 @@ import java.math.BigInteger; import java.util.HashMap; import java.util.HashSet; -import java.util.List; import java.util.Map; import java.util.Set; import java.util.stream.Collectors; @@ -74,7 +73,7 @@ private static void encodeLong(Object value, NumericType type, OutputStream resu } private static void encodeLong(long value, int bitLength, OutputStream result) { - Bytes res = SSZ.encodeULong(value, bitLength); + Bytes res = SSZWriter.encodeULong(value, bitLength); try { result.write(res.toArrayUnsafe()); } catch (IOException e) { @@ -84,7 +83,7 @@ private static void encodeLong(long value, int bitLength, OutputStream result) { private static void encodeBigInt(Object value, NumericType type, OutputStream result) { BigInteger valueBI = (BigInteger) value; - Bytes res = SSZ.encodeBigInteger(valueBI, type.size); + Bytes res = SSZWriter.encodeBigInteger(valueBI, type.size); try { result.write(res.toArrayUnsafe()); @@ -136,65 +135,7 @@ public void encode(Object value, SSZField field, OutputStream result) { } @Override - public void encodeList( - List value, SSZField field, OutputStream result) { - NumericType numericType = parseFieldType(field); - - try { - switch (numericType.type) { - case BIGINT: - { - encodeBigIntList(value, numericType, result); - break; - } - case INT: - { - encodeIntList(value, numericType, result); - break; - } - case LONG: - { - encodeLongList(value, numericType, result); - break; - } - default: - { - throwUnsupportedType(field); - } - } - } catch (IOException ex) { - String error = String.format("Failed to write data from field \"%s\" to stream", - field.getName()); - throw new SSZException(error, ex); - } - } - - private void encodeIntList(List value, NumericType type, OutputStream result) - throws IOException { - int[] data = new int[value.size()]; - for (int i = 0; i < value.size(); ++i) { - data[i] = (int) value.get(i); - } - result.write(SSZ.encodeUIntList(type.size, data).toArrayUnsafe()); - } - - private void encodeLongList(List value, NumericType type, OutputStream result) - throws IOException { - long[] data = new long[value.size()]; - for (int i = 0; i < value.size(); ++i) { - data[i] = (long) value.get(i); - } - result.write(SSZ.encodeULongIntList(type.size, data).toArrayUnsafe()); - } - - private void encodeBigIntList(List value, NumericType type, OutputStream result) - throws IOException { - BigInteger[] data = value.toArray(new BigInteger[0]); - result.write(SSZ.encodeBigIntegerList(type.size, data).toArrayUnsafe()); - } - - @Override - public Object decode(SSZField field, BytesSSZReaderProxy reader) { + public Object decode(SSZField field, SSZReader reader) { NumericType numericType = parseFieldType(field); switch (numericType.type) { case INT: @@ -214,43 +155,18 @@ public Object decode(SSZField field, BytesSSZReaderProxy reader) { return throwUnsupportedType(field); } - private Object decodeInt(NumericType type, BytesSSZReaderProxy reader) { + private Object decodeInt(NumericType type, SSZReader reader) { return reader.readUInt(type.size); } - private Object decodeLong(NumericType type, BytesSSZReaderProxy reader) { + private Object decodeLong(NumericType type, SSZReader reader) { return reader.readULong(type.size); } - private Object decodeBigInt(NumericType type, BytesSSZReaderProxy reader) { + private Object decodeBigInt(NumericType type, SSZReader reader) { return reader.readUnsignedBigInteger(type.size); } - @Override - public List decodeList( - SSZField field, BytesSSZReaderProxy reader) { - NumericType numericType = parseFieldType(field); - - switch (numericType.type) { - case INT: - { - return reader.readUIntList(numericType.size); - } - case LONG: - { - return reader.readULongIntList(numericType.size); - } - case BIGINT: - { - return reader.readUnsignedBigIntegerList(numericType.size); - } - default: - { - return throwUnsupportedListType(field); - } - } - } - private NumericType parseFieldType(SSZField field) { if (field.getExtraSize() != null && field.getExtraSize() % Byte.SIZE != 0) { String error = diff --git a/ssz/src/main/java/org/ethereum/beacon/ssz/annotation/SSZ.java b/ssz/src/main/java/org/ethereum/beacon/ssz/annotation/SSZ.java index e87f6a237..0bfd30617 100644 --- a/ssz/src/main/java/org/ethereum/beacon/ssz/annotation/SSZ.java +++ b/ssz/src/main/java/org/ethereum/beacon/ssz/annotation/SSZ.java @@ -31,9 +31,7 @@ String UInt384 = "uint384"; String UInt512 = "uint512"; String Bytes = "bytes"; - String Hash = "hash"; - String Hash32 = "hash32"; - String Hash48 = "hash48"; + String Hash32 = "bytes32"; String Bool = "bool"; String Address = "address"; String String = "string"; diff --git a/ssz/src/main/java/org/ethereum/beacon/ssz/visitor/SSZIncrementalHasher.java b/ssz/src/main/java/org/ethereum/beacon/ssz/visitor/SSZIncrementalHasher.java index 4dea95c89..a4469f045 100644 --- a/ssz/src/main/java/org/ethereum/beacon/ssz/visitor/SSZIncrementalHasher.java +++ b/ssz/src/main/java/org/ethereum/beacon/ssz/visitor/SSZIncrementalHasher.java @@ -11,14 +11,12 @@ import java.util.TreeSet; import java.util.function.BiFunction; import java.util.function.Function; -import java.util.stream.Collectors; import org.ethereum.beacon.ssz.incremental.ObservableComposite; import org.ethereum.beacon.ssz.incremental.UpdateListener; import org.ethereum.beacon.ssz.type.SSZCompositeType; import org.ethereum.beacon.ssz.type.SSZListType; -import org.ethereum.beacon.ssz.visitor.SSZSimpleSerializer.SSZSerializerResult; +import org.ethereum.beacon.ssz.visitor.SosSerializer.SerializerResult; import tech.pegasys.artemis.ethereum.core.Hash32; -import tech.pegasys.artemis.util.bytes.Bytes32; import tech.pegasys.artemis.util.bytes.BytesValue; public class SSZIncrementalHasher extends SSZSimpleHasher { @@ -51,7 +49,7 @@ public UpdateListener fork() { } public SSZIncrementalHasher( - SSZVisitorHandler serializer, + SSZVisitorHandler serializer, Function hashFunction, int bytesPerChunk) { super(serializer, hashFunction, bytesPerChunk); } diff --git a/ssz/src/main/java/org/ethereum/beacon/ssz/visitor/SSZReader.java b/ssz/src/main/java/org/ethereum/beacon/ssz/visitor/SSZReader.java new file mode 100644 index 000000000..05973298d --- /dev/null +++ b/ssz/src/main/java/org/ethereum/beacon/ssz/visitor/SSZReader.java @@ -0,0 +1,122 @@ +/* + * Copyright 2018 ConsenSys AG. + * + * 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.ethereum.beacon.ssz.visitor; + +import net.consensys.cava.bytes.Bytes; +import net.consensys.cava.ssz.EndOfSSZException; +import net.consensys.cava.ssz.InvalidSSZTypeException; +import org.javatuples.Pair; + +import java.math.BigInteger; +import java.util.function.Supplier; + +import static com.google.common.base.Preconditions.checkArgument; +import static java.nio.ByteOrder.LITTLE_ENDIAN; +import static java.nio.charset.StandardCharsets.UTF_8; + +/** Reader with some adoption of {@link net.consensys.cava.ssz.BytesSSZReader} code */ +public class SSZReader { + + private final Bytes content; + private int index = 0; + + public SSZReader(Bytes bytes) { + this.content = bytes; + } + + public Bytes readBytes() { + return readHash(content.size() - index); + } + + public String readString() { + return new String(readBytes().toArray(), UTF_8); + } + + public int readUInt(int bitLength) { + Pair params = + checkAndConsumeNumber(bitLength, Integer.BYTES, "decoded integer is too large for an int"); + return params + .getValue0() + .slice(0, params.getValue0().size() - params.getValue1()) + .toInt(LITTLE_ENDIAN); + } + + private Pair checkAndConsumeNumber( + int bitLength, int maxByteSize, String errorMessage) { + checkArgument( + bitLength % Byte.SIZE == 0, String.format("bitLength must be a multiple of %s", Byte.SIZE)); + int byteLength = bitLength / Byte.SIZE; + ensureBytes( + byteLength, + () -> "SSZ encoded data has insufficient length to read a " + bitLength + "-bit numeric"); + Bytes bytes = consumeBytes(byteLength); + int zeroBytes = bytes.numberOfTrailingZeroBytes(); + if ((byteLength - zeroBytes) > maxByteSize) { + throw new InvalidSSZTypeException(errorMessage); + } + + return Pair.with(bytes, zeroBytes); + } + + private Bytes consumeBytes(int size) { + Bytes bytes = content.slice(index, size); + index += size; + return bytes; + } + + public long readULong(int bitLength) { + Pair params = + checkAndConsumeNumber(bitLength, Long.BYTES, "decoded number is too large for an long"); + return params + .getValue0() + .slice(0, params.getValue0().size() - params.getValue1()) + .toLong(LITTLE_ENDIAN); + } + + public boolean readBoolean() { + int value = readUInt(Byte.SIZE); + if (value == 0) { + return false; + } else if (value == 1) { + return true; + } else { + throw new InvalidSSZTypeException("decoded value is not a boolean"); + } + } + + public BigInteger readUnsignedBigInteger(int bitLength) { + checkArgument(bitLength % 8 == 0, "bitLength must be a multiple of 8"); + int byteLength = bitLength / 8; + ensureBytes( + byteLength, + () -> "SSZ encoded data has insufficient length to read a " + bitLength + "-bit integer"); + return consumeBytes(byteLength).toUnsignedBigInteger(LITTLE_ENDIAN); + } + + public Bytes readHash(int hashLength) { + ensureBytes( + hashLength, + () -> "SSZ encoded data has insufficient length to read a " + hashLength + "-byte hash"); + return consumeBytes(hashLength); + } + + private void ensureBytes(int byteLength, Supplier message) { + if (index == content.size() && byteLength != 0) { + throw new EndOfSSZException(); + } + if (content.size() - index - byteLength < 0) { + throw new InvalidSSZTypeException(message.get()); + } + } +} diff --git a/ssz/src/main/java/org/ethereum/beacon/ssz/visitor/SSZSimpleDeserializer.java b/ssz/src/main/java/org/ethereum/beacon/ssz/visitor/SSZSimpleDeserializer.java deleted file mode 100644 index 9a1231c76..000000000 --- a/ssz/src/main/java/org/ethereum/beacon/ssz/visitor/SSZSimpleDeserializer.java +++ /dev/null @@ -1,65 +0,0 @@ -package org.ethereum.beacon.ssz.visitor; - -import java.nio.ByteOrder; -import java.util.function.BiFunction; -import net.consensys.cava.bytes.Bytes; -import net.consensys.cava.ssz.BytesSSZReaderProxy; -import org.ethereum.beacon.ssz.SSZSerializeException; -import org.ethereum.beacon.ssz.access.SSZCompositeAccessor.CompositeInstanceBuilder; -import org.ethereum.beacon.ssz.type.SSZBasicType; -import org.ethereum.beacon.ssz.type.SSZCompositeType; -import org.ethereum.beacon.ssz.visitor.SSZSimpleDeserializer.DecodeResult; - -public class SSZSimpleDeserializer implements SSZVisitor { - static final int BYTES_PER_LENGTH_PREFIX = 4; - - public static class DecodeResult { - public final Object decodedInstance; - public final int readBytes; - - public DecodeResult(Object decodedInstance, int readBytes) { - this.decodedInstance = decodedInstance; - this.readBytes = readBytes; - } - } - - @Override - public DecodeResult visitBasicValue(SSZBasicType sszType, Bytes param) { - BytesSSZReaderProxy reader = new BytesSSZReaderProxy(param); - // TODO support basic codecs with variable size -// int readBytes = sszType.isFixedSize() ? sszType.getSize() : reader. - return new DecodeResult(sszType.getAccessor().decode(sszType.getTypeDescriptor(), - reader), sszType.getSize()); - } - - @Override - public DecodeResult visitComposite(SSZCompositeType type, Bytes param, - ChildVisitor childVisitor) { - int pos = 0; - int size; - if (type.isVariableSize()) { - size = deserializeLength(param.slice(0, 4)) + BYTES_PER_LENGTH_PREFIX; - pos += BYTES_PER_LENGTH_PREFIX; - } else { - size = type.getSize(); - } - int idx = 0; - CompositeInstanceBuilder instanceBuilder = type.getAccessor() - .createInstanceBuilder(type.getTypeDescriptor()); - while (pos < size) { - DecodeResult childRes = childVisitor.apply(idx, param.slice(pos)); - instanceBuilder.setChild(idx, childRes.decodedInstance); - pos += childRes.readBytes; - idx++; - } - if (pos != size) { - throw new SSZSerializeException("Error reading serialized composite, expected to read " + size - + " bytes but read " + pos + " bytes"); - } - return new DecodeResult(instanceBuilder.build(), pos); - } - - private int deserializeLength(Bytes lenBytes) { - return lenBytes.toInt(ByteOrder.LITTLE_ENDIAN); - } -} diff --git a/ssz/src/main/java/org/ethereum/beacon/ssz/visitor/SSZSimpleHasher.java b/ssz/src/main/java/org/ethereum/beacon/ssz/visitor/SSZSimpleHasher.java index 3ba059f7d..43c27bb8a 100644 --- a/ssz/src/main/java/org/ethereum/beacon/ssz/visitor/SSZSimpleHasher.java +++ b/ssz/src/main/java/org/ethereum/beacon/ssz/visitor/SSZSimpleHasher.java @@ -3,14 +3,12 @@ import static tech.pegasys.artemis.util.bytes.BytesValue.concat; import java.util.ArrayList; -import java.util.Arrays; import java.util.List; -import java.util.function.BiFunction; import java.util.function.Function; import org.ethereum.beacon.ssz.type.SSZBasicType; import org.ethereum.beacon.ssz.type.SSZCompositeType; import org.ethereum.beacon.ssz.type.SSZListType; -import org.ethereum.beacon.ssz.visitor.SSZSimpleSerializer.SSZSerializerResult; +import org.ethereum.beacon.ssz.visitor.SosSerializer.SerializerResult; import tech.pegasys.artemis.ethereum.core.Hash32; import tech.pegasys.artemis.util.bytes.Bytes32; import tech.pegasys.artemis.util.bytes.BytesValue; @@ -19,12 +17,12 @@ public class SSZSimpleHasher implements SSZVisitor { private final Hash32[] zeroHashes = new Hash32[32]; - final SSZVisitorHandler serializer; + final SSZVisitorHandler serializer; final Function hashFunction; final int bytesPerChunk; public SSZSimpleHasher( - SSZVisitorHandler serializer, + SSZVisitorHandler serializer, Function hashFunction, int bytesPerChunk) { this.serializer = serializer; this.hashFunction = hashFunction; @@ -33,7 +31,7 @@ public SSZSimpleHasher( @Override public MerkleTrie visitBasicValue(SSZBasicType descriptor, Object value) { - SSZSerializerResult sszSerializerResult = serializer.visitAny(descriptor, value); + SerializerResult sszSerializerResult = serializer.visitAny(descriptor, value); return merkleize(pack(sszSerializerResult.serializedBody)); } @@ -45,7 +43,7 @@ public MerkleTrie visitComposite(SSZCompositeType type, Object rawValue, if (type.getChildrenCount(rawValue) == 0) { // empty chunk list } else if (type.isList() && ((SSZListType) type).getElementType().isBasicType()) { - SSZSerializerResult sszSerializerResult = serializer.visitAny(type, rawValue); + SerializerResult sszSerializerResult = serializer.visitAny(type, rawValue); chunks = pack(sszSerializerResult.serializedBody); } else { @@ -128,7 +126,7 @@ public Hash32 getZeroHash(int distanceFromBottom) { return zeroHashes[distanceFromBottom]; } - BytesValue serializeLength(long len) { - return concat(BytesValues.ofUnsignedIntLittleEndian(len), BytesValue.wrap(new byte[32 - 4])); + static BytesValue serializeLength(long len) { + return concat(BytesValues.ofUnsignedIntLittleEndian(len), BytesValue.wrap(new byte[Hash32.SIZE - Integer.BYTES])); } } diff --git a/ssz/src/main/java/org/ethereum/beacon/ssz/visitor/SSZSimpleSerializer.java b/ssz/src/main/java/org/ethereum/beacon/ssz/visitor/SSZSimpleSerializer.java deleted file mode 100644 index 42f8929e0..000000000 --- a/ssz/src/main/java/org/ethereum/beacon/ssz/visitor/SSZSimpleSerializer.java +++ /dev/null @@ -1,100 +0,0 @@ -package org.ethereum.beacon.ssz.visitor; - -import java.io.ByteArrayOutputStream; -import java.util.ArrayList; -import java.util.List; -import java.util.function.BiFunction; -import org.ethereum.beacon.ssz.SSZSerializeException; -import org.ethereum.beacon.ssz.type.SSZBasicType; -import org.ethereum.beacon.ssz.type.SSZCompositeType; -import org.ethereum.beacon.ssz.type.SSZListType; -import tech.pegasys.artemis.util.bytes.BytesValue; -import tech.pegasys.artemis.util.bytes.BytesValues; - -public class SSZSimpleSerializer implements SSZVisitor { - - public static class SSZSerializerResult { - BytesValue serializedBody; - BytesValue serializedLength; - - public SSZSerializerResult(BytesValue serializedBody) { - this.serializedBody = serializedBody; - this.serializedLength = BytesValue.EMPTY; - } - - public SSZSerializerResult(BytesValue serializedBody, - BytesValue serializedLength) { - this.serializedBody = serializedBody; - this.serializedLength = serializedLength; - } - - public BytesValue getSerializedBody() { - return serializedBody; - } - - public BytesValue getSerializedLength() { - return serializedLength; - } - - public BytesValue getSerialized() { - return BytesValue.concat(getSerializedLength(), getSerializedBody()); - } - - public boolean isFixedSize() { - return serializedLength.isEmpty(); - } - } - - @Override - public SSZSerializerResult visitBasicValue(SSZBasicType type, Object value) { - ByteArrayOutputStream baos = new ByteArrayOutputStream(); - type.getAccessor().encode(value, type.getTypeDescriptor(), baos); - return new SSZSerializerResult(BytesValue.wrap(baos.toByteArray())); - } - - @Override - public SSZSerializerResult visitList(SSZListType type, Object param, - ChildVisitor childVisitor) { - - if (type.isVector()) { - if (type.getChildrenCount(param) != type.getVectorLength()) { - throw new SSZSerializeException("Vector type length doesn't match actual list length: " - + type.getVectorLength() + " != " + type.getChildrenCount(param) + " for " + type.toStringHelper()); - } - } - return visitComposite(type, param, childVisitor); - } - - @Override - public SSZSerializerResult visitSubList(SSZListType type, Object param, - int startIdx, int len, ChildVisitor childVisitor) { - return visitComposite(type, param, childVisitor, startIdx, len); - } - - @Override - public SSZSerializerResult visitComposite(SSZCompositeType type, Object rawValue, - ChildVisitor childVisitor) { - return visitComposite(type, rawValue, childVisitor, 0, type.getChildrenCount(rawValue)); - } - - private SSZSerializerResult visitComposite(SSZCompositeType type, Object rawValue, - ChildVisitor childVisitor, int startIdx, int len) { - List childSerializations = new ArrayList<>(); - boolean fixedSize = type.isFixedSize(); - int length = 0; - for (int i = startIdx; i < startIdx + len; i++) { - SSZSerializerResult res = childVisitor.apply(i, type.getChild(rawValue, i)); - childSerializations.add(res.serializedLength); - childSerializations.add(res.serializedBody); - fixedSize &= res.isFixedSize(); - length += res.serializedBody.size() + res.serializedLength.size(); - } - - return new SSZSerializerResult(BytesValue.concat(childSerializations), - fixedSize ? BytesValue.EMPTY : serializeLength(length)); - } - - private BytesValue serializeLength(int len) { - return BytesValues.ofUnsignedIntLittleEndian(len); - } -} diff --git a/ssz/src/main/java/org/ethereum/beacon/ssz/visitor/SSZWriter.java b/ssz/src/main/java/org/ethereum/beacon/ssz/visitor/SSZWriter.java new file mode 100644 index 000000000..133b9447a --- /dev/null +++ b/ssz/src/main/java/org/ethereum/beacon/ssz/visitor/SSZWriter.java @@ -0,0 +1,171 @@ +/* + * Copyright 2018 ConsenSys AG. + * + * 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.ethereum.beacon.ssz.visitor; + +import net.consensys.cava.bytes.Bytes; +import net.consensys.cava.ssz.InvalidSSZTypeException; + +import java.math.BigInteger; + +import static com.google.common.base.Preconditions.checkArgument; +import static java.nio.charset.StandardCharsets.UTF_8; + +/** Writer with some adoption of {@link net.consensys.cava.ssz.SSZ} code */ +public class SSZWriter { + + private static final Bytes TRUE = Bytes.of((byte) 1); + private static final Bytes FALSE = Bytes.of((byte) 0); + + private SSZWriter() {} + + /** + * Encode {@link Bytes}. + * + * @param value The value to encode. + * @return The SSZ encoding in a {@link Bytes} value. + */ + public static Bytes encodeBytes(Bytes value) { + return Bytes.wrap(value); + } + + /** + * Encode fixed-size bytes value as ssz element + * + * @param bytes Bytes data + * @param byteLength Byte length of ssz element + * @return the SSZ encoding in a {@link Bytes} value + */ + public static Bytes encodeBytes(Bytes bytes, int byteLength) { + if (bytes.size() != byteLength) { + throw new InvalidSSZTypeException( + String.format( + "Error attempt to encode bytes data with length of %s to ssz element with fixed length %s. Lengths must match.", + bytes.size(), byteLength)); + } + return bytes; + } + + /** + * Encode a value to a {@link Bytes} value. + * + * @param value The value to encode. + * @return The SSZ encoding in a {@link Bytes} value. + */ + public static Bytes encodeByteArray(byte[] value) { + return encodeBytes(Bytes.wrap(value)); + } + + /** + * Encode a string to a {@link Bytes} value. + * + * @param str The string to encode. + * @return The SSZ encoding in a {@link Bytes} value. + */ + public static Bytes encodeString(String str) { + return encodeByteArray(str.getBytes(UTF_8)); + } + + /** + * Encode a big integer to a {@link Bytes} value. + * + * @param value the big integer to encode + * @param bitLength the bit length of the integer value (must be a multiple of 8) + * @return the SSZ encoding in a {@link Bytes} value + * @throws IllegalArgumentException if the value is too large for the specified {@code bitLength} + */ + public static Bytes encodeBigInteger(BigInteger value, int bitLength) { + return Bytes.wrap(encodeBigIntegerToByteArray(value, bitLength)); + } + + private static byte[] encodeBigIntegerToByteArray(BigInteger value, int bitLength) { + checkArgument(bitLength % 8 == 0, "bitLength must be a multiple of 8"); + + byte[] bytes = value.toByteArray(); + int valueBytes = bytes.length; + int offset = 0; + if (value.signum() >= 0 && bytes[0] == 0) { + valueBytes = bytes.length - 1; + offset = 1; + } + + int byteLength = bitLength / 8; + checkArgument(valueBytes <= byteLength, "value is too large for the desired bitLength"); + + byte[] encoded; + if (valueBytes == byteLength && offset == 0) { + encoded = bytes; + } else { + encoded = new byte[byteLength]; + int padLength = byteLength - valueBytes; + System.arraycopy(bytes, offset, encoded, padLength, valueBytes); + if (value.signum() < 0) { + // Extend the two's-compliment integer by setting all leading bits to 1. + for (int i = 0; i < padLength; i++) { + encoded[i] = (byte) 0xFF; + } + } + } + // reverse the array to make it little endian + for (int i = 0; i < (encoded.length / 2); i++) { + byte swapped = encoded[i]; + encoded[i] = encoded[encoded.length - i - 1]; + encoded[encoded.length - i - 1] = swapped; + } + return encoded; + } + + /** + * Encode an unsigned long integer to a {@link Bytes} value. + * + *

Note that {@code value} is a native signed long, but will be interpreted as an unsigned + * value. + * + * @param value the long to encode + * @param bitLength the bit length of the integer value (must be a multiple of 8) + * @return the SSZ encoding in a {@link Bytes} value + * @throws IllegalArgumentException if the value is too large for the specified {@code bitLength} + */ + public static Bytes encodeULong(long value, int bitLength) { + return Bytes.wrap(encodeULongToByteArray(value, bitLength)); + } + + private static byte[] encodeULongToByteArray(long value, int bitLength) { + checkArgument(bitLength % 8 == 0, "bitLength must be a multiple of 8"); + + int zeros = Long.numberOfLeadingZeros(value); + int valueBytes = 8 - (zeros / 8); + checkArgument(zeros == 0 || value >= 0, "Value must be positive or zero"); + + int byteLength = bitLength / 8; + checkArgument(valueBytes <= byteLength, "value is too large for the desired bitLength"); + + byte[] encoded = new byte[byteLength]; + + int shift = 0; + for (int i = 0; i < valueBytes; i++) { + encoded[i] = (byte) ((value >> shift) & 0xFF); + shift += 8; + } + return encoded; + } + + /** + * Encode a boolean to a {@link Bytes} value. + * + * @param value the boolean to encode + * @return the SSZ encoding in a {@link Bytes} value + */ + public static Bytes encodeBoolean(boolean value) { + return value ? TRUE : FALSE; + } +} diff --git a/ssz/src/main/java/org/ethereum/beacon/ssz/visitor/SosDeserializer.java b/ssz/src/main/java/org/ethereum/beacon/ssz/visitor/SosDeserializer.java new file mode 100644 index 000000000..269e238bf --- /dev/null +++ b/ssz/src/main/java/org/ethereum/beacon/ssz/visitor/SosDeserializer.java @@ -0,0 +1,180 @@ +package org.ethereum.beacon.ssz.visitor; + +import net.consensys.cava.bytes.Bytes; +import org.ethereum.beacon.ssz.access.SSZCompositeAccessor.CompositeInstanceBuilder; +import org.ethereum.beacon.ssz.type.SSZBasicType; +import org.ethereum.beacon.ssz.type.SSZCompositeType; +import org.ethereum.beacon.ssz.type.SSZContainerType; +import org.ethereum.beacon.ssz.type.SSZListType; +import org.ethereum.beacon.ssz.type.SSZType; +import org.ethereum.beacon.ssz.visitor.SosDeserializer.DecodeResult; +import org.javatuples.Pair; + +import java.nio.ByteOrder; +import java.util.Optional; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.function.Consumer; + +/** + * SSZ deserializer with offset-based decoding of variable sized elements + */ +public class SosDeserializer implements SSZVisitor> { + static final int BYTES_PER_LENGTH_OFFSET = 4; + + /** + * Decodes basic value + * + * @param sszType Type of field, should be some basic type + * @param param Bytes data / Boolean flag whether to extract body or offset for variable size types. + * If type is with variable size and this flag is set to TRUE, offset is extracted. Otherwise body. + * @return DecodeResult + */ + @Override + public DecodeResult visitBasicValue(SSZBasicType sszType, Pair param) { + Optional offsetLength = extractVariableTypeOffset(sszType, param); + if (offsetLength.isPresent()) { + return offsetLength.get(); + } + + SSZReader reader = new SSZReader(param.getValue0()); + if (sszType.isFixedSize()) { + int readBytes = sszType.getSize(); + return new DecodeResult( + sszType.getAccessor().decode(sszType.getTypeDescriptor(), reader), readBytes, true); + } else { + return new DecodeResult( + sszType.getAccessor().decode(sszType.getTypeDescriptor(), reader), + param.getValue0().size(), + false); + } + } + + private Optional extractVariableTypeOffset(SSZType sszType, Pair param) { + if (Boolean.TRUE.equals(param.getValue1()) && !sszType.isFixedSize()) { + return Optional.of(new DecodeResult( + deserializeLength(param.getValue0().slice(0, BYTES_PER_LENGTH_OFFSET)), + BYTES_PER_LENGTH_OFFSET, + false)); + } + + return Optional.empty(); + } + + /** + * Decodes composite value + * + * @param type Type of field, should be some composite type + * @param param Bytes data / Boolean flag whether to extract body or offset for variable size types. + * If type is with variable size and this flag is set to TRUE, offset is extracted. Otherwise body. + * @param childVisitor Visitor which will be used for children + * @return DecodeResult + */ + @Override + public DecodeResult visitComposite( + SSZCompositeType type, + Pair param, + ChildVisitor, DecodeResult> childVisitor) { + Optional offsetLength = extractVariableTypeOffset(type, param); + if (offsetLength.isPresent()) { + return offsetLength.get(); + } + + int fixedPos = 0; + AtomicInteger variablePartConsumed = new AtomicInteger(0); + int idx = 0; + CompositeInstanceBuilder instanceBuilder = + type.getAccessor().createInstanceBuilder(type.getTypeDescriptor()); + boolean isFixedSize = true; + VisitLater visitLater = null; + int maxIndex = calcTypeMaxIndex(type); + int firstOffset = Integer.MAX_VALUE; + while (fixedPos < param.getValue0().size() && idx < maxIndex && fixedPos < firstOffset) { + DecodeResult childRes = + childVisitor.apply(idx, Pair.with(param.getValue0().slice(fixedPos), true)); + if (childRes.isFixedSize) { + instanceBuilder.setChild(idx, childRes.decodedInstance); + } else { + isFixedSize = false; + int offset = (Integer) childRes.decodedInstance; + + // First time we found some item with variable size + if (firstOffset == Integer.MAX_VALUE) { + firstOffset = offset; + } + + final int idxBackup = idx; + if (visitLater != null) { + visitLater.run(offset); + } + visitLater = + new VisitLater( + param.getValue0().slice(offset), + offset, + objects -> { + DecodeResult res = + childVisitor.apply( + idxBackup, + Pair.with(objects.getValue0().slice(0, objects.getValue1()), false)); + variablePartConsumed.addAndGet(res.readBytes); + instanceBuilder.setChild(idxBackup, res.decodedInstance); + }); + } + fixedPos += childRes.readBytes; + idx++; + } + + if (visitLater != null) { + visitLater.run(); + } + fixedPos += variablePartConsumed.get(); + + return new DecodeResult(instanceBuilder.build(), fixedPos, isFixedSize); + } + + private int calcTypeMaxIndex(SSZType type) { + int maxIndex = Integer.MAX_VALUE; + if (type instanceof SSZContainerType) { + maxIndex = ((SSZContainerType) type).getChildTypes().size(); + } else if (type instanceof SSZListType && ((SSZListType) type).isVector()) { + maxIndex = ((SSZListType) type).getVectorLength(); + } + + return maxIndex; + } + + private int deserializeLength(Bytes lenBytes) { + return lenBytes.toInt(ByteOrder.LITTLE_ENDIAN); + } + + public static class DecodeResult { + public final Object decodedInstance; + public final int readBytes; + public final boolean isFixedSize; + + public DecodeResult(Object decodedInstance, int readBytes, boolean isFixedSize) { + this.decodedInstance = decodedInstance; + this.readBytes = readBytes; + this.isFixedSize = isFixedSize; + } + } + + class VisitLater { + private final Bytes input; + private final int inputOffset; + private final Consumer> runLater; + + public VisitLater(Bytes input, int inputOffset, Consumer> runLater) { + this.input = input; + this.inputOffset = inputOffset; + this.runLater = runLater; + } + + public void run(int end) { + runLater.accept(Pair.with(input, end - inputOffset)); + } + + public void run() { + runLater.accept(Pair.with(input, input.size())); + } + } +} diff --git a/ssz/src/main/java/org/ethereum/beacon/ssz/visitor/SosSerializer.java b/ssz/src/main/java/org/ethereum/beacon/ssz/visitor/SosSerializer.java new file mode 100644 index 000000000..dfe33992d --- /dev/null +++ b/ssz/src/main/java/org/ethereum/beacon/ssz/visitor/SosSerializer.java @@ -0,0 +1,132 @@ +package org.ethereum.beacon.ssz.visitor; + +import org.ethereum.beacon.ssz.SSZSerializeException; +import org.ethereum.beacon.ssz.type.SSZBasicType; +import org.ethereum.beacon.ssz.type.SSZCompositeType; +import org.ethereum.beacon.ssz.type.SSZListType; +import tech.pegasys.artemis.util.bytes.BytesValue; +import tech.pegasys.artemis.util.bytes.BytesValues; + +import java.io.ByteArrayOutputStream; +import java.util.ArrayList; +import java.util.List; + +import static org.ethereum.beacon.ssz.visitor.SosDeserializer.BYTES_PER_LENGTH_OFFSET; + +/** + * SSZ serializer with offset-based encoding of variable sized elements + */ +public class SosSerializer + implements SSZVisitor { + private static BytesValue serializeLength(long len) { + return BytesValues.ofUnsignedIntLittleEndian(len); + } + + @Override + public SerializerResult visitBasicValue(SSZBasicType type, Object value) { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + type.getAccessor().encode(value, type.getTypeDescriptor(), baos); + return new SerializerResult( + BytesValue.wrap(baos.toByteArray()), baos.size(), type.isFixedSize()); + } + + @Override + public SerializerResult visitList( + SSZListType type, Object param, ChildVisitor childVisitor) { + + if (type.isVector()) { + if (type.getChildrenCount(param) != type.getVectorLength()) { + throw new SSZSerializeException( + "Vector type length doesn't match actual list length: " + + type.getVectorLength() + + " != " + + type.getChildrenCount(param) + + " for " + + type.toStringHelper()); + } + } + return visitComposite(type, param, childVisitor); + } + + @Override + public SerializerResult visitSubList( + SSZListType type, + Object param, + int startIdx, + int len, + ChildVisitor childVisitor) { + return visitComposite(type, param, childVisitor, startIdx, len); + } + + @Override + public SerializerResult visitComposite( + SSZCompositeType type, + Object rawValue, + ChildVisitor childVisitor) { + return visitComposite(type, rawValue, childVisitor, 0, type.getChildrenCount(rawValue)); + } + + private SerializerResult visitComposite( + SSZCompositeType type, + Object rawValue, + ChildVisitor childVisitor, + int startIdx, + int len) { + + List childSerializations = new ArrayList<>(); + boolean fixedSize = type.isFixedSize(); + for (int i = startIdx; i < startIdx + len; i++) { + SerializerResult res = childVisitor.apply(i, type.getChild(rawValue, i)); + childSerializations.add(res); + } + + int currentOffset = + childSerializations.stream() + .mapToInt(r -> r.isFixedSize() ? r.serializedBody.size() : BYTES_PER_LENGTH_OFFSET) + .sum(); + BytesValue composite = BytesValue.EMPTY; + + // Fixed part + for (SerializerResult s : childSerializations) { + composite = + composite.concat(s.isFixedSize() ? s.serializedBody : serializeLength(currentOffset)); + if (!s.isFixedSize()) { + currentOffset = currentOffset + s.serializedLength; + } + } + + // Variable part + for (SerializerResult s : childSerializations) { + if (s.isFixedSize()) { + continue; + } + composite = composite.concat(s.serializedBody); + } + + return new SerializerResult(composite, currentOffset, fixedSize); + } + + public static class SerializerResult { + final BytesValue serializedBody; + final int serializedLength; + final boolean fixedSize; + + public SerializerResult(BytesValue serializedBody, int serializedLength, boolean fixedSize) { + this.serializedBody = serializedBody; + this.serializedLength = serializedLength; + this.fixedSize = fixedSize; + } + + public BytesValue getSerializedBody() { + return serializedBody; + } + + public int getSerializedLength() { + return serializedLength; + } + + public boolean isFixedSize() { + return fixedSize; + } + } +} diff --git a/ssz/src/test/java/org/ethereum/beacon/ssz/SSZSerializerTest.java b/ssz/src/test/java/org/ethereum/beacon/ssz/SSZSerializerTest.java index bc6cde12b..0569bc459 100644 --- a/ssz/src/test/java/org/ethereum/beacon/ssz/SSZSerializerTest.java +++ b/ssz/src/test/java/org/ethereum/beacon/ssz/SSZSerializerTest.java @@ -1,6 +1,8 @@ package org.ethereum.beacon.ssz; import java.util.Arrays; + +import com.google.common.base.Objects; import org.ethereum.beacon.crypto.Hashes; import org.ethereum.beacon.ssz.access.container.SSZAnnotationSchemeBuilder; import org.ethereum.beacon.ssz.annotation.SSZSerializable; @@ -75,10 +77,13 @@ public void SignatureTest() { @Test public void simpleTest() { + List obliqueParentHashes = new ArrayList<>(); + obliqueParentHashes.add(new byte[] {0x12, 0x34, 0x56}); + obliqueParentHashes.add(new byte[] {0x55, 0x66, 0x77}); AttestationRecord expected = new AttestationRecord( 123, - Collections.emptyList(), + obliqueParentHashes, DEFAULT_HASH, new Bitfield(BytesValue.fromHexString("abcdef45").getArrayUnsafe()), DEFAULT_HASH, @@ -156,8 +161,49 @@ public void nullListTest() { sszSerializer.encode(expected4); } + @SSZSerializable + public static class ListsObject { + private final List list1; + private final List list2; + + public ListsObject(List list1, List list2) { + this.list1 = list1; + this.list2 = list2; + } + + public List getList1() { + return list1; + } + + public List getList2() { + return list2; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + ListsObject that = (ListsObject) o; + return Objects.equal(list1, that.list1) && + Objects.equal(list2, that.list2); + } + } + + @Test + public void shouldHandleLists() { + List list1 = new ArrayList<>(); + list1.add(1); + list1.add(2); + List list2 = new ArrayList<>(); + list2.add("aa"); + ListsObject expected = new ListsObject(list1, list2); + byte[] encoded = sszSerializer.encode(expected); + ListsObject actual = sszSerializer.decode(encoded, ListsObject.class); + + assertEquals(expected, actual); + } + /** Checks that we could handle list placed inside another list */ - @Ignore("Implement me!") @Test public void shouldHandleListList() { List list1 = new ArrayList<>(); diff --git a/ssz/src/test/java/org/ethereum/beacon/ssz/fixtures/AttestationRecord.java b/ssz/src/test/java/org/ethereum/beacon/ssz/fixtures/AttestationRecord.java index 5202727f6..781f9356d 100644 --- a/ssz/src/test/java/org/ethereum/beacon/ssz/fixtures/AttestationRecord.java +++ b/ssz/src/test/java/org/ethereum/beacon/ssz/fixtures/AttestationRecord.java @@ -105,7 +105,7 @@ public boolean equals(Object o) { return slot == that.slot && shardId == that.shardId && justifiedSlot == that.justifiedSlot - && Objects.equals(obliqueParentHashes, that.obliqueParentHashes) + && Arrays.deepEquals(obliqueParentHashes.toArray(), that.obliqueParentHashes.toArray()) && Arrays.equals(shardBlockHash, that.shardBlockHash) && Objects.equals(attesterBitfield, that.attesterBitfield) && Arrays.equals(justifiedBlockHash, that.justifiedBlockHash) diff --git a/types/src/main/java/tech/pegasys/artemis/util/bytes/BytesValue.java b/types/src/main/java/tech/pegasys/artemis/util/bytes/BytesValue.java index 74b2d10da..b9a774322 100644 --- a/types/src/main/java/tech/pegasys/artemis/util/bytes/BytesValue.java +++ b/types/src/main/java/tech/pegasys/artemis/util/bytes/BytesValue.java @@ -13,16 +13,16 @@ package tech.pegasys.artemis.util.bytes; -import static com.google.common.base.Preconditions.checkArgument; -import static com.google.common.base.Preconditions.checkElementIndex; - -import java.security.MessageDigest; - import com.google.common.annotations.VisibleForTesting; import io.netty.buffer.ByteBuf; import io.vertx.core.buffer.Buffer; + +import java.security.MessageDigest; import java.util.List; +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkElementIndex; + /** * A value made of bytes. * @@ -80,82 +80,28 @@ static BytesValue wrap(byte[] value, int offset, int length) { } /** - * Wraps two other value into a concatenated view. - * - *

- * Note that the values are not copied, only wrapped, and thus any future update to {@code v1} or - * {@code v2} will be reflected in the returned value. If copying the inputs is desired instead, - * please use {@link BytesValues#concatenate(BytesValue...)}. + * Wraps two other value into the new concatenated value. * * @param v1 The first value to wrap. * @param v2 The second value to wrap. - * @return A value representing a view over the concatenation of {@code v1} and {@code v2}. + * @return v1 concatenated with v2. */ static BytesValue wrap(BytesValue v1, BytesValue v2) { - return new AbstractBytesValue() { - @Override - public int size() { - return v1.size() + v2.size(); - } - - @Override - public byte get(int i) { - checkElementIndex(i, size()); - return i < v1.size() ? v1.get(i) : v2.get(i - v1.size()); - } - - @Override - public BytesValue slice(int i, int length) { - if (i == 0 && length == size()) - return this; - if (length == 0) - return BytesValue.EMPTY; - - checkElementIndex(i, size()); - checkArgument(i + length <= size(), - "Provided length %s is too big: the value has size %s and has only %s bytes from %s", - length, size(), size() - i, i); - - if (i + length < v1.size()) - return v1.slice(i, length); - - if (i >= v1.size()) - return v2.slice(i - v1.size(), length); - - MutableBytesValue res = MutableBytesValue.create(length); - int lengthInV1 = v1.size() - i; - v1.slice(i, lengthInV1).copyTo(res, 0); - v2.slice(0, length - lengthInV1).copyTo(res, lengthInV1); - return res; - } - - @Override - public void update(MessageDigest digest) { - v1.update(digest); - v2.update(digest); - } - }; + return v1.concat(v2); } /** * Concatenates two values * - * The resulting value would be either backed by supplied values with - * {@link #wrap(BytesValue, BytesValue)} or create a copy of backing bytes - * depending on the target value size + * The resulting value would be a copy of backing bytes */ static BytesValue concat(BytesValue v1, BytesValue v2) { - if (v1.size() + v2.size() < 512) { - // it should be generally cheaper for further usage - byte[] bb = new byte[v1.size() + v2.size()]; - byte[] arr1 = v1.getArrayUnsafe(); - byte[] arr2 = v2.getArrayUnsafe(); - System.arraycopy(arr1, 0, bb, 0, arr1.length); - System.arraycopy(arr2, 0, bb, arr1.length, arr2.length); - return wrap(bb); - } else { - return wrap(v1, v2); - } + byte[] bb = new byte[v1.size() + v2.size()]; + byte[] arr1 = v1.getArrayUnsafe(); + byte[] arr2 = v2.getArrayUnsafe(); + System.arraycopy(arr1, 0, bb, 0, arr1.length); + System.arraycopy(arr2, 0, bb, arr1.length, arr2.length); + return wrap(bb); } static BytesValue concat(List vals) { diff --git a/types/src/test/java/tech/pegasys/artemis/util/bytes/BytesValueTest.java b/types/src/test/java/tech/pegasys/artemis/util/bytes/BytesValueTest.java index 4f5e4aa68..762682236 100644 --- a/types/src/test/java/tech/pegasys/artemis/util/bytes/BytesValueTest.java +++ b/types/src/test/java/tech/pegasys/artemis/util/bytes/BytesValueTest.java @@ -173,7 +173,7 @@ private static void assertConcatenatedWrap(byte[] first, byte[] second) { } @Test - public void concatenatedWrapReflectsUpdates() { + public void concatenatedWrapDoesntReflectsUpdates() { byte[] first = new byte[] {1, 2, 3}; byte[] second = new byte[] {4, 5}; byte[] expected1 = new byte[] {1, 2, 3, 4, 5}; @@ -182,8 +182,7 @@ public void concatenatedWrapReflectsUpdates() { first[1] = 42; second[0] = 42; - byte[] expected2 = new byte[] {1, 42, 3, 42, 5}; - assertArrayEquals(expected2, res.extractArray()); + assertArrayEquals(expected1, res.extractArray()); } @Test